Checked in changes from ISI
authorTheodore Tso <tytso@mit.edu>
Wed, 2 Jun 1993 13:31:03 +0000 (13:31 +0000)
committerTheodore Tso <tytso@mit.edu>
Wed, 2 Jun 1993 13:31:03 +0000 (13:31 +0000)
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@2534 dc483132-0cff-0310-8789-dd5450dbe970

src/appl/bsd/krlogind.c

index 9360b6feff483796afe15a0d8de2954d913e87a3..9fb3e0c4807d4d0b15114cdc6caaa128d1207384 100644 (file)
@@ -43,66 +43,80 @@ static char sccsid[] = "@(#)rlogind.c       5.17 (Berkeley) 8/31/88";
       *        data
       */
      
-     /*
-      * This is the rlogin daemon. The very basic protocol for checking 
-      * authentication and authorization is:
-      * 1) Check authentication.
-      * 2) Check authorization via the access-control files: 
-      *    ~/.k5login (using krb5_kuserok) and/or
-      *    ~/.rhosts  (using ruserok).
-      * 3) Prompt for password if any checks fail, or if so configured.
-      * Allow login if all goes well either by calling the accompanying login.krb
-      * or /bin/login, according to the definition of DO_NOT_USE_K_LOGIN.
-      * 
-      * The configuration is done either by command-line arguments passed by inetd, 
-      * or by the name of the daemon. If command-line arguments are present, they 
-      * take priority. The options are:
-      * -k and -K means check .k5login (using krb5_kuserok).
-      * -r and -R means check .rhosts  (using ruserok).
-      * -p and -P means prompt for password.
-      * The difference between upper and lower case is as follows:
-      *    If lower case -r or -k, then as long as one of krb5_kuserok or ruserok 
-      * passes, allow login without password. If the -p option is passed with -r 
-      * or -k, then if both checks fail, allow login but only after password 
-      * verification. 
-      *    If uppercase -R or -K, then those checks must be passed, regardless of
-      * other checks, else no login with or without password.
-      *    If the -P option is passed, then the password is verified in 
-      * addition to all other checks. If -p is not passed with -k or -r, and both
-      * checks fail, then login permission is denied.
-      * -x and -e means use encryption.
-      *     If no command-line arguments are present, then the presence of the 
-      * letters kKrRexpP in the program-name before "logind" determine the 
-      * behaviour of the program exactly as with the command-line arguments.
-      *
-      * If the ruserok check is to be used, then the client should connect from a 
-      * privileged port, else deny permission.
-      */ 
-     
-     /* DEFINES:
-      *   KERBEROS - Define this if application is to be kerberised.
-      *   CRYPT    - Define this if encryption is to be an option.
-      *   DO_NOT_USE_K_LOGIN - Define this if you want to use /bin/login instead 
-      *              of the accompanying login.krb. In that case, the remote user's
-      *              name must be present in the local .rhosts file, regardless of
-      *              any options specified.
-      *   LOG_ALL_LOGINS - Define this if you want to log all logins.
-      *   LOG_OTHER_USERS - Define this if you want to log all principals that do
-      *              not map onto the local user.
-      *   LOG_REMOTE_REALM - Define this if you want to log all principals from 
-      *              remote realms.
-      *       Note:  Root logins are always logged.
-      */
+/*
+ * This is the rlogin daemon. The very basic protocol for checking 
+ * authentication and authorization is:
+ * 1) Check authentication.
+ * 2) Check authorization via the access-control files: 
+ *    ~/.k5login (using krb5_kuserok) and/or
+ *    ~/.rhosts  (using ruserok).
+ * 3) Prompt for password if any checks fail, or if so configured.
+ * Allow login if all goes well either by calling the accompanying 
+ * login.krb5 or /bin/login, according to the definition of 
+ * DO_NOT_USE_K_LOGIN.
+ * 
+ * The configuration is done either by command-line arguments passed by 
+ * inetd, or by the name of the daemon. If command-line arguments are
+ * present, they  take priority. The options are:
+ * -k and -K means check .k5login (using krb5_kuserok).
+ * -r and -R means check .rhosts  (using ruserok).
+ * -p and -P means prompt for password.
+ * The difference between upper and lower case is as follows:
+ *    If lower case -r or -k, then as long as one of krb5_kuserok or 
+ * ruserok passes, allow login without password. If the -p option is
+ * passed with -r or -k, then if both checks fail, allow login but
+ * only after password verification. 
+ *    If uppercase -R or -K, then those checks must be passed,
+ * regardless of other checks, else no login with or without password.
+ *    If the -P option is passed, then the password is verified in 
+ * addition to all other checks. If -p is not passed with -k or -r,
+ * and both checks fail, then login permission is denied.
+ *    -x and -e means use encryption.
+ *
+ *    If no command-line arguments are present, then the presence of the 
+ * letters kKrRexpP in the program-name before "logind" determine the 
+ * behaviour of the program exactly as with the command-line arguments.
+ *
+ * If the ruserok check is to be used, then the client should connect
+ * from a privileged port, else deny permission.
+ */ 
      
+/* DEFINES:
+ *   KERBEROS - Define this if application is to be kerberised.
+ *   CRYPT    - Define this if encryption is to be an option.
+ *   DO_NOT_USE_K_LOGIN - Define this if you want to use /bin/login
+ *              instead  of the accompanying login.krb5. In that case,
+ *              the remote user's name must be present in the local
+ *              .rhosts file, regardless of any options specified.
+ *   SERVE_V4 - Define this if v4 rlogin clients are also to be served.
+ *   ALWAYS_V5_KUSEROK - Define this if you want .k5login to be
+ *              checked even for v4 clients (instead of .klogin).
+ *   SERVE_NON_KRB - Define this if non-kerberized rlogin clients are 
+ *              to be served. NOTE HOWEVER THAT THIS IS A SERIOUS
+ *              SECURITY FLAW!
+ *   LOG_ALL_LOGINS - Define this if you want to log all logins.
+ *   LOG_OTHER_USERS - Define this if you want to log all principals
+ *              that do not map onto the local user.
+ *   LOG_REMOTE_REALM - Define this if you want to log all principals from 
+ *              remote realms.
+ *       Note:  Root logins are always logged.
+ */
+
+/* This is usually done in site.def. If not, then uncomment this. 
+#define KERBEROS
+*/
 #define LOG_REMOTE_REALM
-#define KERBEROS 
-     
+#define CRYPT
+#define SERVE_V4
+
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <sys/file.h>
+#include <ctype.h>
+
 /* #include <sys/unistd.h>  ??? What system has a sys/unistd.h? */
      
 #include <netinet/in.h>
@@ -152,20 +166,36 @@ struct winsize {
 #ifdef KERBEROS
      
 #include <krb5/krb5.h>
+#include <krb5/osconf.h>
 #include <krb5/asn1.h>
 #include <krb5/crc-32.h>
 #include <krb5/mit-des.h>
 #include <krb5/los-proto.h>
 
-#include <com_err.h>
-     
 #ifdef BUFSIZ
 #undef BUFSIZ
 #endif
-#define BUFSIZ 4096
+
+int V4 = 0;             /* set when v4 client detected. */
+int non_privileged = 0; /* set when connection is seen to be from */
+                       /* a non-privileged port */
+#ifdef SERVE_V4
+#include <kerberosIV/krb.h>
+AUTH_DAT       *v4_kdata;
+KTEXT          v4_ticket;
+Key_schedule schedule;
+int v4_des_read(), v4_des_write();
+#endif
+
+#define BUFSIZ 5120
+
+int v5_des_read(), v5_des_write();
+
+#include <com_err.h>
      
 #define SECURE_MESSAGE  "This rlogin session is using DES encryption for all data transmissions.\r\n"
 
+int (*des_read)(), (*des_write)();
 char des_inbuf[2*BUFSIZ];         /* needs to be > largest read size */
 char des_outbuf[2*BUFSIZ];        /* needs to be > largest write size */
 krb5_data desinbuf,desoutbuf;
@@ -180,13 +210,14 @@ krb5_ticket     *ticket = 0;
 #endif
 #endif
 
-#define ARGSTR "rRkKeExXpP?"
+#define ARGSTR "rRkKeExXpPD:?"
 #else /* !KERBEROS */
-#define ARGSTR "rRpP?"
-#define des_read        read
-#define des_write       write
+#define ARGSTR "rRpPD:?"
+#define (*des_read)  read
+#define (*des_write) write
 #endif /* KERBEROS */
 
+#ifndef LOGIN_PROGRAM
 #ifdef DO_NOT_USE_K_LOGIN
 #ifdef sysvimp
 #define LOGIN_PROGRAM "/bin/remlogin"
@@ -194,20 +225,21 @@ krb5_ticket     *ticket = 0;
 #define LOGIN_PROGRAM "/bin/login"
 #endif
 #else /* DO_NOT_USE_K_LOGIN */
-#define LOGIN_PROGRAM "/krb5/etc/login.krb5"
-#endif
+#define LOGIN_PROGRAM KRB5_PATH_LOGIN
+#endif /* DO_NOT_USE_K_LOGIN */
+#endif /* LOGIN_PROGRAM */
 
 struct utmp    wtmp;
-#define NMAX    sizeof(wtmp.ut_name)
 #define MAXRETRIES 4
-#define        UT_HOSTSIZE     sizeof(((struct utmp *)0)->ut_host)
+#define        UT_NAMESIZE     sizeof(((struct utmp *)0)->ut_name)
 #define MAX_PROG_NAME 16
 
-char           lusername[NMAX+1];
-char           *rusername  = 0;
+char           lusername[UT_NAMESIZE+1];
+char           rusername[UT_NAMESIZE+1];
 char            *krusername = 0;
 char           term[64];
 char            rhost_name[128];
+krb5_principal  client;
 
 extern int errno;
 int    reapchild();
@@ -217,7 +249,7 @@ char        *malloc();
 #endif
 char   *progname;
 
-void   fatal(), fatalperror(), doit(), usage();
+void   fatal(), fatalperror(), doit(), usage(), do_krb_login();
 int    princ_maps_to_lname(), default_realm();
 
 int must_pass_rhosts = 0, must_pass_k5 = 0, must_pass_one = 0;
@@ -228,10 +260,12 @@ main(argc, argv)
      int argc;
      char **argv;
 {
-    extern int opterr, optind;
+    extern int opterr, optind, optarg;
     int on = 1, fromlen, ch, i;
     struct sockaddr_in from;
     char *options;
+    int debug_port = 0;
+    int fd;
     
     progname = *argv;
     
@@ -267,11 +301,20 @@ main(argc, argv)
        options[0] = '\0';
        for (i = 0; (progname[i] != '\0') && (i < MAX_PROG_NAME); i++)
          if (!strcmp(progname+i, "logind")) {
+             char **newargv;
+
+             newargv = (char **) malloc(sizeof(char *) 3);
+
              strcpy(options, "-");
              strncat(options, progname, i);
+
              argc = 2;
-             argv[1] = options;
-             argv[2] = NULL;
+             
+             newargv[0] = argv[0];
+             newargv[1] = options;
+             newargv[2] = NULL;
+
+             argv = newargv;
              break;
          }
        if (options[0] == '\0') {
@@ -316,6 +359,9 @@ main(argc, argv)
        case 'P':         /* passwd is a must */
          passwd_req = 1;
          break;
+       case 'D':
+         debug_port = atoi(optarg);
+         break;
        case '?':
        default:
          usage();
@@ -326,19 +372,54 @@ main(argc, argv)
     argv += optind;
     
     fromlen = sizeof (from);
-    if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) {
-       syslog(LOG_ERR,"Can't get peer name of remote host: %m");
+
+     if (debug_port) {
+        int s;
+        struct sockaddr_in sin;
+        
+        if ((s = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) < 0) {
+            fprintf(stderr, "Error in socket: %s\n", strerror(errno));
+            exit(2);
+        }
+        
+        bzero((char *) &sin,sizeof(sin));
+        sin.sin_family = AF_INET;
+        sin.sin_port = htons(debug_port);
+        sin.sin_addr.s_addr = INADDR_ANY;
+        
+        if ((bind(s, (struct sockaddr *) &sin, sizeof(sin))) < 0) {
+            fprintf(stderr, "Error in bind: %s\n", strerror(errno));
+            exit(2);
+        }
+        
+        if ((listen(s, 5)) < 0) {
+            fprintf(stderr, "Error in listen: %s\n", strerror(errno));
+            exit(2);
+        }
+        
+        if ((fd = accept(s, &from, &fromlen)) < 0) {
+            fprintf(stderr, "Error in accept: %s\n", strerror(errno));
+            exit(2);
+        }
+        
+        close(s);
+     } 
+     else {
+        if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) {
+            syslog(LOG_ERR,"Can't get peer name of remote host: %m");
 #ifdef STDERR_FILENO
-       fatal(STDERR_FILENO, "Can't get peer name of remote host", 1);
+            fatal(STDERR_FILENO, "Can't get peer name of remote host", 1);
 #else
-       fatal(3, "Can't get peer name of remote host", 1);
+            fatal(3, "Can't get peer name of remote host", 1);
 #endif
-       
-    }
-    if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0)
+        }
+        fd = 0;
+     }
+
+    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0)
       syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
     
-    doit(0, &from);
+    doit(fd, &from);
 }
 
 
@@ -350,7 +431,6 @@ main(argc, argv)
 int    child;
 int    cleanup();
 int    netf;
-krb5_principal client;
 char   line[MAXPATHLEN];
 extern char    *inet_ntoa();
 
@@ -406,24 +486,18 @@ void doit(f, fromp)
     if ((must_pass_rhosts || must_pass_one) 
        && (fromp->sin_port >= IPPORT_RESERVED ||
            fromp->sin_port < IPPORT_RESERVED/2))
+      non_privileged = 1;
 #else /* !KERBEROS */
-      if (fromp->sin_port >= IPPORT_RESERVED ||
-         fromp->sin_port < IPPORT_RESERVED/2)
+    if (fromp->sin_port >= IPPORT_RESERVED ||
+       fromp->sin_port < IPPORT_RESERVED/2)
+      fatal(f, "Permission denied - Connection from bad port");
 #endif /* KERBEROS */
-       fatal(f, "Permission denied - Connection from bad port");
     
     /* Set global netf to f now : we may need to drop everything
        in do_krb_login. */
     netf = f;
     
 #if defined(KERBEROS)
-    /*
-     * If encrypting, we need to respond here, since we have to send
-     * the mutual authentication stuff before the response
-     *
-     * Do_krb_login has been modified to handle rlogin type requests
-     * also....
-     */
     /* All validation, and authorization goes through do_krb_login() */
     do_krb_login(rhost_name);
     
@@ -434,10 +508,9 @@ void doit(f, fromp)
          fatal(netf, "Permission denied");
     }
 #else
-    rusername = malloc(sizeof (lusername) + 1);
-    getstr(rusername, sizeof(lusername), "remuser");
-    getstr(lusername, sizeof(lusername), "locuser");
-    getstr(term, sizeof(term), "Terminal type");
+    getstr(f, rusername, sizeof(rusername), "remuser");
+    getstr(f, lusername, sizeof(lusername), "locuser");
+    getstr(f, term, sizeof(term), "Terminal type");
 #endif
     
     write(f, "", 1);
@@ -458,17 +531,23 @@ void doit(f, fromp)
 #ifdef NOFCHMOD
     if (chmod(t,0))
 #else
-      if (fchmod(t, 0))
+    if (fchmod(t, 0))
 #endif
-       fatalperror(f, line);
+      fatalperror(f, line);
 #ifndef SYSV
-    signal(SIGHUP, SIG_IGN);
-    vhangup();
-    signal(SIGHUP, SIG_DFL);
+    if (f == 0) {  /* if operating standalone, do not reset tty!! */
+       signal(SIGHUP, SIG_IGN);
+       vhangup();
+       signal(SIGHUP, SIG_DFL);
+    }
 #ifdef ultrix   /* Someone needs to cleanup all this and have a consistant
                   way of associating controlling tty to a process. */
     setpgrp();
 #endif
+#if defined (sun) || defined (POSIX)
+    setsid();
+#endif
+
     t = open(line, O_RDWR);
     if (t < 0)
       fatalperror(f, line);
@@ -570,46 +649,48 @@ void doit(f, fromp)
        if (pwd && (pwd->pw_uid == 0)) {
            if (passwd_req)
              syslog(LOG_NOTICE, "ROOT login by %s (%s@%s) forcing password access",
-                    krusername, rusername, rhost_name);
+                    krusername ? krusername : "", rusername, rhost_name);
            else
              syslog(LOG_NOTICE, "ROOT login by %s (%s@%s) ", 
-                    krusername, rusername, rhost_name);
+                    krusername ? krusername : "", rusername, rhost_name);
        }
        
 #if defined(KERBEROS) && defined(LOG_REMOTE_REALM) && !defined(LOG_OTHER_USERS) && !defined(LOG_ALL_LOGINS)
        /* Log if principal is from a remote realm */
-else if (!default_realm(client))
+        else if (client && !default_realm(client))
 #endif
   
 #if defined(KERBEROS) && defined(LOG_OTHER_USERS) && !defined(LOG_ALL_LOGINS) 
-  /* Log if principal name does not map to local username */
-else if (!princ_maps_to_lname(client, lusername))
+       /* Log if principal name does not map to local username */
+        else if (client && !princ_maps_to_lname(client, lusername))
 #endif /* LOG_OTHER_USERS */
   
 #ifdef LOG_ALL_LOGINS /* Log everything */
-else 
+       else 
 #endif
   
 #if defined(LOG_REMOTE_REALM) || defined(LOG_OTHER_USERS) || defined(LOG_ALL_LOGINS)
-  {
-      if (passwd_req)
-       syslog(LOG_NOTICE,
-              "login by %s (%s@%s) as %s forcing password access\n",
-              krusername, rusername, rhost_name, lusername);
-      else 
-       syslog(LOG_NOTICE,
-              "login by %s (%s@%s) as %s\n",
-              krusername, rusername, rhost_name, lusername); 
-  }
+        {
+           if (passwd_req)
+             syslog(LOG_NOTICE,
+                    "login by %s (%s@%s) as %s forcing password access\n",
+                    krusername ? krusername : "", rusername,
+                    rhost_name, lusername);
+           else 
+             syslog(LOG_NOTICE,
+                    "login by %s (%s@%s) as %s\n",
+                    krusername ? krusername : "", rusername,
+                    rhost_name, lusername); 
+       }
 #endif
        
 #ifdef DO_NOT_USE_K_LOGIN
        execl(LOGIN_PROGRAM, "login", "-r", rhost_name, 0);
 #else
        if (passwd_req)
-         execl(LOGIN_PROGRAM, "login", rhost_name,0);
+         execl(LOGIN_PROGRAM, "login","-r", rhost_name, 0);
        else
-         execl(LOGIN_PROGRAM, "login", "-f", rhost_name, 0);
+         execl(LOGIN_PROGRAM, "login", "-fr", rhost_name, 0);
 #endif
        
        fatalperror(2, LOGIN_PROGRAM, errno);
@@ -630,21 +711,20 @@ else
     }
     
 #if defined(KERBEROS) 
-    if (do_encrypt)
-      if ((des_write(f, SECURE_MESSAGE, sizeof(SECURE_MESSAGE))) < 0){
-         sprintf(buferror, "Cannot encrypt-write network.");
-         fatal(p,buferror);
-      }
-      else 
-       /*
-        * if encrypting, don't turn on NBIO, else the read/write routines
-        * will fail to work properly
-        */
-#endif /* KERBEROS */
-       {
-           ioctl(f, FIONBIO, &on);
-           ioctl(p, FIONBIO, &on);
+    if (do_encrypt) {
+       if (((*des_write)(f, SECURE_MESSAGE, sizeof(SECURE_MESSAGE))) < 0){
+           sprintf(buferror, "Cannot encrypt-write network.");
+           fatal(p,buferror);
        }
+    }
+    else 
+      /*
+       * if encrypting, don't turn on NBIO, else the read/write routines
+       * will fail to work properly
+       */
+#endif /* KERBEROS */
+      ioctl(f, FIONBIO, &on);
+    ioctl(p, FIONBIO, &on);
 #ifdef hpux
     /******** FIONBIO doesn't currently work on ptys, should be O_NDELAY? **/
     /*** get flags and add O_NDELAY **/
@@ -659,11 +739,7 @@ else
     setpgrp(0, 0);
 #endif
     
-#if defined(KERBEROS) 
-    /* Pass down rusername and lusername which we have
-       obtained from ticket and authorized by PWC_ACCESS.
-       Note lusername's .rhost should have entry for rusername.
-       */
+    /* Pass down rusername and lusername to login. */
     (void) write(p, rusername, strlen(rusername) +1);
     (void) write(p, lusername, strlen(lusername) +1);
     /* stuff term info down to login */
@@ -674,7 +750,6 @@ else
        sprintf(buferror,"Cannot write slave pty %s ",line);
        fatalperror(f,buferror,errno);
     } 
-#endif /* KERBEROS */
     
     protocol(f, p);
     signal(SIGCHLD, SIG_IGN);
@@ -782,7 +857,7 @@ protocol(f, p)
            }
        }
        if (ibits & (1<<f)) {
-           fcc = des_read(f, fibuf, sizeof (fibuf));
+           fcc = (*des_read)(f, fibuf, sizeof (fibuf));
            if (fcc < 0 && errno == EWOULDBLOCK)
              fcc = 0;
            else {
@@ -838,7 +913,7 @@ protocol(f, p)
            }
        }
        if ((obits & (1<<f)) && pcc > 0) {
-           cc = des_write(f, pbp, pcc);
+           cc = (*des_write)(f, pbp, pcc);
            if (cc < 0 && errno == EWOULDBLOCK) {
                /* also shouldn't happen */
                sleep(5);
@@ -897,7 +972,7 @@ void fatal(f, msg)
     buf[0] = '\01';            /* error indicator */
     (void) sprintf(buf + 1, "%s: %s.\r\n",progname, msg);
     if ((f == netf) && (pid > 0))
-      (void) des_write(f, buf, strlen(buf));
+      (void) (*des_write)(f, buf, strlen(buf));
     else
       (void) write(f, buf, strlen(buf));
     syslog(LOG_ERR,"%s\n",msg);
@@ -933,152 +1008,84 @@ void fatalperror(f, msg)
 
 #ifdef KERBEROS
 
-
+void
 do_krb_login(host)
      char *host;
 {
-    int rc;
     krb5_error_code status;
-    struct sockaddr_in peersin;
-    krb5_address peeraddr;
     struct passwd *pwd;
-    krb5_principal server;
-    char srv_name[100];
-    char def_host[100];
-    krb5_data inbuf;
-    
+
     if (getuid()) {
        exit(1);
     }
-    
-    /* we want mutual authentication */
-#ifdef unicos61
-#define SIZEOF_INADDR  SIZEOF_in_addr
-#else
-#define SIZEOF_INADDR sizeof(struct in_addr)
-#endif
-    
-    rc = sizeof(peersin);
-    if (getpeername(netf, (struct sockaddr *)&peersin, &rc)) {
-       syslog(LOG_ERR, "get peer name failed %d", netf);
-       exit(1);
-    }
-    
-    peeraddr.addrtype = peersin.sin_family;
-    peeraddr.length = SIZEOF_INADDR;
-    peeraddr.contents = (krb5_octet *)&peersin.sin_addr;
-    
-    strcpy(srv_name, "host/");
-    gethostname(def_host, 100);
-    strcat(srv_name, def_host);
-    if (status = krb5_parse_name(srv_name, &server)) {
-       syslog(LOG_ERR, "parse server name %s: %s", "host",
-              error_message(status));
-       exit(1);
-    }
-    krb5_princ_type(server) = KRB5_NT_SRV_HST;
-    
-    if (status = krb5_recvauth(&netf, 
-                              "KCMDV0.1",
-                              server,     /* no match on server 
-                                             incase we have are
-                                             serving multiple realms*/
-                              &peeraddr,  /* We do want to match this
-                                             against caddrs in the
-                                             ticket. */
-                              0,               /* use srv5tab */
-                              0,               /* no keyproc */
-                              0,               /* no keyproc arg */
-                              0,               /* no rc_type */
-                              0,               /* no seq number */
-                              &client, /* return client */
-                              &ticket, /* return ticket */
-                              &kdata        /* return authenticator */
-                              )) {
-       syslog(LOG_ERR,
-              "Kerberos authentication failed from %s: %s\n",
-              host,error_message(status));
-       
-       /* Dont exit out for klogin, but
-          grab locuser, terminal, and remuser.
-          */
-       
-       /* These two reads will be used in the next release to obtain
-          a forwarded TGT and related info. */
-       if (status = krb5_read_message((krb5_pointer)&netf, &inbuf))
-         fatal(netf, "Error reading message");
-       if (inbuf.length)
-         fatal(netf, "Forwarding is not yet supported");
-       if (status = krb5_read_message((krb5_pointer)&netf, &inbuf))
-         fatal(netf, "Error reading message");
-       if (inbuf.length)
-         fatal(netf, "Forwarding is not yet supported");
-       
-       getstr(lusername, sizeof(lusername), "locuser");
-       getstr(term, sizeof(term), "Terminal type");
-       rusername = malloc(sizeof (lusername) + 1);
-       getstr(rusername, sizeof(lusername), "remuser");
-       
+
+    /* Check authentication. This can be either Kerberos V5, */
+    /* Kerberos V4, or host-based. */
+    if (status = recvauth()) {
        failed_auth = 1;
        if (ticket)
          krb5_free_ticket(ticket);
+       if (status != 255)
+         syslog(LOG_ERR,
+                "Authentication failed from %s: %s\n",
+                host,error_message(status));
        return;
     }
     
-    /* Setup up eblock if encrypted login session */
-    /* otherwise zero out session key */
-    if (do_encrypt) {
-       krb5_use_keytype(&eblock,
-                        ticket->enc_part2->session->keytype);
-       if (status = krb5_process_key(&eblock,
-                                     ticket->enc_part2->session))
-         fatal(netf, "Permission denied");
-    }
-    
-    /* These two reads will be used in the next release to obtain
-       a forwarded TGT and related info. */
-    if (status = krb5_read_message((krb5_pointer)&netf, &inbuf))
-      fatal(netf, "Error reading message");
-    if (inbuf.length)
-      fatal(netf, "Forwarding is not yet supported");
-    if (status = krb5_read_message((krb5_pointer)&netf, &inbuf))
-      fatal(netf, "Error reading message");
-    if (inbuf.length)
-      fatal(netf, "Forwarding is not yet supported");
-
-    getstr(lusername, sizeof(lusername), "locuser");
-    getstr(term, sizeof(term), "Terminal type");
-    rusername = malloc(sizeof (lusername) + 1);
-    getstr(rusername, sizeof(lusername), "remuser");
-    
     /* OK we have authenticated this user - now check authorization. */
-    /* We must do this here since we want the same functionality as */
-    /* the MIT version without having to provide the login.krb program.*/
-    
-    /* The Kerberos authenticated programs must use krb5_kuserok */
-    
-    krb5_unparse_name(kdata->client,&krusername);
+    /* The Kerberos authenticated programs must use krb5_kuserok or kuserok*/
     
     if (must_pass_k5 || must_pass_one) {
+#ifdef ALWAYS_V5_KUSEROK
        /* krb5_kuserok returns 1 if OK */
-       rc = !(krb5_kuserok(kdata->client,lusername));
-       
-       if (rc){
+       if (!client || !krb5_kuserok(client, lusername)){
+           if (ticket)
+             krb5_free_ticket(ticket);
            syslog(LOG_ERR,
                   "Principal %s (%s@%s) logging in as %s failed krb5_kuserok.\n",
-                  krusername, rusername, host, lusername);
+                  krusername ? krusername : "", rusername, host, lusername);
            if (must_pass_k5)
              fatal(netf, "Permission denied"); 
            failed_k5 = 1;
-           if (ticket)
-             krb5_free_ticket(ticket);
        }
+#else
+       if (V4) {
+           /* kuserok returns 0 if OK */
+           if (kuserok(v4_kdata, lusername)){
+               syslog(LOG_ERR,
+                      "Principal %s (%s@%s) logging in as %s failed kuserok.\n",
+                      krusername ? krusername : "", rusername, host,
+                      lusername);
+               if (must_pass_k5)
+                 fatal(netf, "Permission denied");
+               failed_k5 = 1;
+           }
+       }
+       else {
+           /* krb5_kuserok returns 1 if OK */
+           if (!client || !krb5_kuserok(client, lusername)){
+               if (ticket)
+                 krb5_free_ticket(ticket);
+               syslog(LOG_ERR,
+                      "Principal %s (%s@%s) logging in as %s failed krb5_kuserok.\n",
+                      krusername ? krusername : "", rusername, host,
+                      lusername);
+               if (must_pass_k5)
+                 fatal(netf, "Permission denied");
+               failed_k5 = 1;
+           }
+       }
+#endif
     }
     
     /*  The kerberos authenticated request must pass ruserok also
        if asked for. */
     
     if (must_pass_rhosts || (failed_k5 && must_pass_one)) {
+       /* Cannot check .rhosts unless connection from a privileged port. */
+       if (non_privileged) 
+         fatal(netf, "Permission denied - Connection from bad port");
+
        pwd = (struct passwd *) getpwnam(lusername);
        if ((pwd == (struct passwd *) 0) ||
            (ruserok(rhost_name, pwd->pw_uid == 0, rusername, lusername))) {
@@ -1089,11 +1096,11 @@ do_krb_login(host)
            if (pwd == (struct passwd *) 0) 
              syslog(LOG_ERR,
                     "Principal %s (%s@%s) logging in as %s has no account.\n",
-                    krusername, rusername, rhost_name, lusername);
+                    krusername ? krusername : "", rusername, rhost_name, lusername);
            else
              syslog(LOG_ERR,
                     "Principal %s (%s@%s) logging in as %s failed ruserok.\n",
-                    krusername, rusername, rhost_name, lusername);
+                    krusername ? krusername : "", rusername, rhost_name, lusername);
            
            if (must_pass_rhosts)
              fatal(netf, "Permission denied");
@@ -1105,7 +1112,8 @@ do_krb_login(host)
 
 
 
-getstr(buf, cnt, err)
+getstr(fd, buf, cnt, err)
+     int fd;
      char *buf;
      int cnt;
      char *err;
@@ -1114,7 +1122,7 @@ getstr(buf, cnt, err)
     char c;
     
     do {
-       if (read(0, &c, 1) != 1) {
+       if (read(fd, &c, 1) != 1) {
            exit(1);
        }
        if (--cnt < 0) {
@@ -1131,8 +1139,8 @@ char storage[2*BUFSIZ];                    /* storage for the decryption */
 int nstored = 0;
 char *store_ptr = storage;
 
-
-des_read(fd, buf, len)
+int
+v5_des_read(fd, buf, len)
      int fd;
      register char *buf;
      int len;
@@ -1222,8 +1230,8 @@ des_read(fd, buf, len)
 }
     
 
-
-des_write(fd, buf, len)
+int
+v5_des_write(fd, buf, len)
      int fd;
      char *buf;
      int len;
@@ -1260,7 +1268,6 @@ des_write(fd, buf, len)
     }
     else return(len);
 }
-
 #endif /* KERBEROS */
 
 
@@ -1319,9 +1326,10 @@ void usage()
 {
 #ifdef KERBEROS
     syslog(LOG_ERR, 
-          "usage: klogind [-rRkKxpP] or [r/R][k/K][x/e][p/P]logind");
+          "usage: klogind [-rRkKxpP] [-D port] or [r/R][k/K][x/e][p/P]logind");
 #else
-    syslog(LOG_ERR, "usage: rlogind [-rRpP] or [r/R][p/P]logind");
+    syslog(LOG_ERR, 
+          "usage: rlogind [-rRpP] [-D port] or [r/R][p/P]logind");
 #endif
 }
 
@@ -1362,4 +1370,739 @@ int default_realm(principal)
     free(def_realm);
     return 1;
 }
+
+
+#ifndef KRB_SENDAUTH_VLEN
+#define        KRB_SENDAUTH_VLEN 8         /* length for version strings */
 #endif
+
+#define        KRB_SENDAUTH_VERS       "AUTHV0.1" /* MUST be KRB_SENDAUTH_VLEN
+                                             chars */
+
+krb5_error_code
+recvauth()
+{
+    krb5_error_code status;
+    struct sockaddr_in peersin;
+    char hostname[100];
+    char krb_vers[KRB_SENDAUTH_VLEN + 1];
+    int len;
+
+    len = sizeof(peersin);
+    if (getpeername(netf, (struct sockaddr *)&peersin, &len)) {
+       syslog(LOG_ERR, "get peer name failed %d", netf);
+       exit(1);
+    }
+
+    len = sizeof(int);
+    if ((status = krb5_net_read(netf, krb_vers, len)) != len)
+      return((status < 0) ? errno : ECONNABORTED);
+
+    krb_vers[len] = '\0';
+
+    if (!strncmp(krb_vers, KRB_SENDAUTH_VERS, len)) {
+       /* Must be V4 rlogin client */
+#ifdef SERVE_V4
+       char version[9];
+       struct sockaddr_in faddr;
+       char instance[INST_SZ];
+       long authoptions;
+
+       V4 = 1;        /* Set flag for rest of code */
+
+       des_read  = v4_des_read;
+       des_write = v4_des_write;
+
+       if (do_encrypt)
+         authoptions = KOPT_DO_MUTUAL;
+       else
+         authoptions = 0L;
+
+       len = sizeof(faddr);
+       if (getsockname(netf, (struct sockaddr *)&faddr, &len)) {
+           exit(1);
+       }
+       
+       v4_kdata = (AUTH_DAT *)malloc( sizeof(AUTH_DAT) );
+       v4_ticket = (KTEXT) malloc(sizeof(KTEXT_ST));
+
+       strcpy(instance, "*");
+
+       if (status = v4_recvauth(krb_vers, authoptions, netf, v4_ticket, 
+                                "rcmd", instance, 
+                                &peersin, &faddr,
+                                v4_kdata, "", 
+                                schedule, version)) {
+           return(status);
+       }
+
+       getstr(netf, lusername, sizeof (lusername), "locuser");
+       getstr(netf, term, sizeof(term), "Terminal type");
+       /* We do not really know the remote user's login name.
+         * Assume it to be the same as the first component of the
+        * principal's name. 
+         */
+       strcpy(rusername, v4_kdata->pname);
+       krusername = (char *) malloc(strlen(v4_kdata->pname) + 1 +
+                                    strlen(v4_kdata->pinst) + 1 +
+                                    strlen(v4_kdata->prealm) + 1);
+       sprintf(krusername, "%s/%s@%s", v4_kdata->pname,
+               v4_kdata->pinst, v4_kdata->prealm);
+       
+       if (status = krb5_parse_name(krusername, &client))
+         return(status);
+#else
+       syslog(LOG_ERR, "Kerberos V4 authentication: rejected!");
+       fatal(netf, "Permission denied");
+#endif
+    }
+    else if (isprint(krb_vers[0])) { 
+       /* Un-kerberized rlogin client */
+#ifdef SERVE_NON_KRB
+       des_read  = read;
+       des_write = write;
+
+       strncpy(rusername, krb_vers, sizeof(int));
+       getstr(netf, rusername+4, sizeof(rusername)-sizeof(int), "remuser");
+       getstr(netf, lusername, sizeof(lusername), "locuser");
+       getstr(netf, term, sizeof(term), "Terminal type");      
+#else
+       syslog(LOG_ERR, "Un-kerberized client: authentication rejected!");
+       fatal(netf, "Permission denied");
+#endif
+    }
+    else {
+       /* Must be V5 rlogin client */
+       krb5_principal server;
+       krb5_address peeraddr;
+       krb5_data inbuf;
+
+       des_read  = v5_des_read;
+       des_write = v5_des_write;
+
+       /*
+        * First read the sendauth version string and check it.
+        */
+       inbuf.length = ntohl(*((int *) krb_vers));
+
+       if (inbuf.length < 0 || inbuf.length > 25)
+         return 255;
+       
+       if (!(inbuf.data = malloc(inbuf.length))) {
+           return(ENOMEM);
+       }
+       
+       if ((len = krb5_net_read(netf, inbuf.data, inbuf.length)) !=
+           inbuf.length) {
+           xfree(inbuf.data);
+           return((len < 0) ? errno : ECONNABORTED);
+       }
+
+       if (strcmp(inbuf.data, "KRB5_SENDAUTH_V1.0")) {
+           xfree(inbuf.data);
+           status = KRB5_SENDAUTH_BADAUTHVERS;
+           return status;
+       }
+       xfree(inbuf.data);
+
+#ifdef unicos61
+#define SIZEOF_INADDR  SIZEOF_in_addr
+#else
+#define SIZEOF_INADDR sizeof(struct in_addr)
+#endif
+
+       peeraddr.addrtype = peersin.sin_family;
+       peeraddr.length = SIZEOF_INADDR;
+       peeraddr.contents = (krb5_octet *)&peersin.sin_addr;
+       
+       gethostname(hostname, 100);
+       if (status = krb5_sname_to_principal(hostname,"host", KRB5_NT_SRV_HST,
+                                            &server)) {
+           syslog(LOG_ERR, "parse server name %s: %s", "host",
+                  error_message(status));
+           exit(1);
+       }
+       krb5_princ_type(server) = KRB5_NT_SRV_HST;
+
+       if (status = v5_recvauth(&netf, 
+                                "KCMDV0.1",
+                                server,     /* Specify daemon principal */
+                                &peeraddr,  /* We do want to match this
+                                               against caddrs in the
+                                               ticket. */
+                                0,             /* use v5srvtab */
+                                0,             /* no keyproc */
+                                0,             /* no keyproc arg */
+                                0,             /* no rc_type */
+                                0,             /* no seq number */
+                                &client,       /* return client */
+                                &ticket,       /* return ticket */
+                                &kdata      /* return authenticator */
+                                )) {
+           /* Dummy forwarding reads */
+           if (status = krb5_read_message((krb5_pointer)&netf, &inbuf))
+             fatal(netf, "Error reading message");
+           if (status = krb5_read_message((krb5_pointer)&netf, &inbuf))
+             fatal(netf, "Error reading message");
+           
+           /* Dont exit out for klogin, but
+              grab locuser, terminal, and remuser.
+              */
+           getstr(netf, lusername, sizeof(lusername), "locuser");
+           getstr(netf, term, sizeof(term), "Terminal type");
+           getstr(netf, rusername, sizeof(rusername), "remuser");
+           return status;
+       }
+       
+       if (status = krb5_unparse_name(client, &krusername))
+         return status;
+    
+       /* Setup up eblock if encrypted login session */
+       /* otherwise zero out session key */
+       if (do_encrypt) {
+           krb5_use_keytype(&eblock,
+                            ticket->enc_part2->session->keytype);
+           if (status = krb5_process_key(&eblock,
+                                         ticket->enc_part2->session))
+             fatal(netf, "Permission denied");
+       }      
+
+       getstr(netf, lusername, sizeof(lusername), "locuser");
+       getstr(netf, term, sizeof(term), "Terminal type");
+       getstr(netf, rusername, sizeof(rusername), "remuser");
+
+       if (status = krb5_read_message((krb5_pointer)&netf, &inbuf))
+         fatal(netf, "Error reading message");
+
+       if (inbuf.length) { /* Forwarding being done, read creds */
+           if (status = rd_and_store_for_creds(&inbuf, ticket, lusername))
+             fatal(netf, "Can't get forwarded credentials");
+       }
+    }
+    return 0;
+}
+
+
+#ifdef SERVE_V4
+
+#ifndef max
+#define        max(a,b) (((a) > (b)) ? (a) : (b))
+#endif /* max */
+
+krb5_error_code
+v4_recvauth(krb_vers, options, fd, ticket, service, instance, faddr,
+           laddr, kdata, filename, schedule, version)
+     char *krb_vers;
+     long options;                      /* bit-pattern of options */
+     int fd;                            /* file descr. to read from */
+     KTEXT ticket;                      /* storage for client's ticket */
+     char *service;                     /* service expected */
+     char *instance;                    /* inst expected (may be filled in) */
+     struct sockaddr_in *faddr;         /* address of foreign host on fd */
+     struct sockaddr_in *laddr;         /* local address */
+     AUTH_DAT *kdata;           /* kerberos data (returned) */
+     char *filename;                    /* name of file with service keys */
+     Key_schedule schedule;             /* key schedule (return) */
+     char *version;                     /* version string (filled in) */
+{
+    
+    int i, cc;
+    char *cp;
+    int rem;
+    long tkt_len, priv_len;
+    u_long cksum;
+    u_char tmp_buf[MAX_KTXT_LEN+max(KRB_SENDAUTH_VLEN+1,21)];
+    
+    /* read the protocol version number */
+    if (krb_net_read(fd, krb_vers+sizeof(int), 
+                    KRB_SENDAUTH_VLEN-sizeof(int)) !=
+       KRB_SENDAUTH_VLEN-sizeof(int))
+      return(errno);
+    krb_vers[KRB_SENDAUTH_VLEN] = '\0';
+    
+    /* check version string */
+    if (strcmp(krb_vers,KRB_SENDAUTH_VERS)) {
+       return(KFAILURE);
+    } else {
+       /* read the application version string */
+       if (krb_net_read(fd, version, KRB_SENDAUTH_VLEN) !=
+           KRB_SENDAUTH_VLEN)
+         return(errno);
+       version[KRB_SENDAUTH_VLEN] = '\0';
+       
+       /* get the length of the ticket */
+       if (krb_net_read(fd, (char *)&tkt_len, sizeof(tkt_len)) !=
+           sizeof(tkt_len))
+         return(errno);
+       
+       /* sanity check */
+       ticket->length = ntohl((unsigned long)tkt_len);
+       if ((ticket->length <= 0) || (ticket->length > MAX_KTXT_LEN)) {
+           if (options & KOPT_DO_MUTUAL) {
+               rem = KFAILURE;
+               goto mutual_fail;
+           } else
+             return(KFAILURE); /* XXX there may still be junk on the fd? */
+       }
+       
+       /* read the ticket */
+       if (krb_net_read(fd, (char *) ticket->dat, ticket->length)
+           != ticket->length)
+         return(errno);
+    }
+    /*
+     * now have the ticket.  decrypt it to get the authenticated
+     * data.
+     */
+    if (rem = krb_rd_req(ticket,service,instance,faddr->sin_addr.s_addr,
+                    kdata,filename))
+      return(KFAILURE);
+    
+    /* if we are doing mutual auth (used by erlogin), compose a response */
+    if (options & KOPT_DO_MUTUAL) {
+       if (rem != KSUCCESS)
+         /* the krb_rd_req failed */
+         goto mutual_fail;
+       
+       /* add one to the (formerly) sealed checksum, and re-seal it
+          for return to the client */
+       cksum = kdata->checksum + 1;
+       cksum = htonl(cksum);
+#ifdef CRYPT
+       key_sched(kdata->session,schedule);
+#endif
+       priv_len = krb_mk_priv((unsigned char *)&cksum,
+                              tmp_buf,
+                              (unsigned long) sizeof(cksum),
+                              schedule,
+                              kdata->session,
+                              laddr,
+                              faddr);
+       if (priv_len < 0) {
+           /* re-sealing failed; notify the client */
+           rem = KFAILURE;      /* XXX */
+         mutual_fail:
+           priv_len = -1;
+           tkt_len = htonl((unsigned long) priv_len);
+           /* a length of -1 is interpreted as an authentication
+              failure by the client */
+           if ((cc = krb_net_write(fd, (char *)&tkt_len, sizeof(tkt_len)))
+               != sizeof(tkt_len))
+             return(cc);
+           return(rem);
+       } else {
+           /* re-sealing succeeded, send the private message */
+           tkt_len = htonl((unsigned long)priv_len);
+           if ((cc = krb_net_write(fd, (char *)&tkt_len, sizeof(tkt_len)))
+               != sizeof(tkt_len))
+             return(cc);
+           if ((cc = krb_net_write(fd, (char *)tmp_buf, (int) priv_len))
+               != (int) priv_len)
+             return(cc);
+       }
+    }
+    return(0);
+}
+
+#endif /* SERVE_V4 */
+
+extern krb5_flags      krb5_kdc_default_options;
+
+krb5_error_code
+v5_recvauth(/* IN */
+           fd, appl_version, server, sender_addr, fetch_from,
+           keyproc, keyprocarg, rc_type, 
+           /* OUT */
+           seq_number, client, ticket, authent)
+     krb5_pointer      fd;
+     char      *appl_version;
+     krb5_principal    server;
+     krb5_address      *sender_addr;
+     krb5_pointer      fetch_from;
+     krb5_int32        *seq_number;
+     char              *rc_type;
+     krb5_rdreq_key_proc keyproc;
+     krb5_pointer keyprocarg;
+     krb5_principal    *client;
+     krb5_ticket       **ticket;
+     krb5_authenticator        **authent;
+{
+    krb5_error_code    retval, problem;
+    krb5_data  inbuf;
+    krb5_tkt_authent   *authdat;
+    krb5_data          outbuf;
+    krb5_rcache rcache;
+    krb5_octet         response;
+    krb5_data  *server_name;
+    char *cachename;
+    extern krb5_deltat krb5_clockskew;
+    static char                *rc_base = "rc_";
+    
+    /*
+     * Zero out problem variable.  If problem is set at the end of
+     * the intial version negotiation section, it means that we
+     * need to send an error code back to the client application
+     * and exit.
+     */
+    problem = 0;
+    
+    /*
+     * Read and check the application version string.
+     */
+    if (retval = krb5_read_message(fd, &inbuf))
+      return(retval);
+    if (strcmp(inbuf.data, appl_version)) {
+       xfree(inbuf.data);
+       if (!problem)
+         problem = KRB5_SENDAUTH_BADAPPLVERS;
+    }
+    xfree(inbuf.data);
+    /*
+     * OK, now check the problem variable.  If it's zero, we're
+     * fine and we can continue.  Otherwise, we have to signal an
+     * error to the client side and bail out.
+     */
+    switch (problem) {
+      case 0:
+       response = 0;
+       break;
+      case KRB5_SENDAUTH_BADAUTHVERS:
+       response = 1;
+       break;
+      case KRB5_SENDAUTH_BADAPPLVERS:
+       response = 2;
+       break;
+      default:
+       /*
+        * Should never happen!
+        */
+       response = 255;
+#ifdef SENDAUTH_DEBUG
+       fprintf(stderr, "Programming botch in recvauth!  problem = %d",
+               problem);
+       abort();
+#endif
+       break;
+    }
+
+    /*
+     * Now we actually write the response.  If the response is non-zero,
+     * exit with a return value of problem
+     */
+    if ((krb5_net_write(*((int *) fd), (char *)&response, 1)) < 0) {
+       return(problem); /* We'll return the top-level problem */
+    }
+    if (problem)
+      return(problem);
+    rcache = NULL;
+#ifdef WORKING_RCACHE
+    /*
+     * Setup the replay cache.
+     */
+    if (!(rcache = (krb5_rcache) malloc(sizeof(*rcache)))) 
+      problem = ENOMEM;
+    if (!problem) 
+      problem = krb5_rc_resolve_type(&rcache,
+                                    rc_type ? rc_type : "dfl");
+    cachename = NULL;
+    server_name = krb5_princ_component(server, 0);
+    if (!problem && !(cachename = malloc(server_name->length+1+strlen(rc_base))))
+      problem = ENOMEM;
+    if (!problem) {
+       strcpy(cachename, rc_base ? rc_base : "rc_");
+       strncat(cachename, server_name->data, server_name->length);
+       cachename[server_name->length+strlen(rc_base)] = '\0';
+       problem = krb5_rc_resolve(rcache, cachename);
+    }
+    if (!problem) {
+       if (krb5_rc_recover(rcache))
+         /*
+          * If the rc_recover didn't work, then try
+          * initializing the replay cache.
+          */
+         problem = krb5_rc_initialize(rcache, krb5_clockskew);
+       if (problem) {
+           krb5_rc_close(rcache);
+           rcache = NULL;
+       }
+    }
+#endif
+
+    /*
+     * Now, let's read the AP_REQ message and decode it
+     */
+    if (retval = krb5_read_message(fd, &inbuf)) {
+#ifdef WORKING_RCACHE          
+       (void) krb5_rc_close(rcache);
+       if (cachename)
+         free(cachename);
+#endif
+       return(retval);
+    }
+    authdat = 0;                       /* so we can tell if we need to
+                                          free it later... */
+    if (!problem)
+      problem = krb5_rd_req(&inbuf, server, sender_addr, fetch_from,
+                           keyproc, keyprocarg, rcache, &authdat);
+    xfree(inbuf.data);
+#ifdef WORKING_RCACHE
+    if (rcache)
+      retval = krb5_rc_close(rcache);
+#endif
+    if (!problem && retval)
+      problem = retval;
+#ifdef WORKING_RCACHE
+    if (cachename)
+      free(cachename);
+#endif
+    
+    /*
+     * If there was a problem, send back a krb5_error message,
+     * preceeded by the length of the krb5_error message.  If
+     * everything's ok, send back 0 for the length.
+     */
+    if (problem) {
+       krb5_error      error;
+       const   char *message;
+       
+       memset((char *)&error, 0, sizeof(error));
+       krb5_us_timeofday(&error.stime, &error.susec);
+       error.server = server;
+       error.error = problem - ERROR_TABLE_BASE_krb5;
+       if (error.error > 127)
+         error.error = KRB_ERR_GENERIC;
+       message = error_message(problem);
+       error.text.length  = strlen(message) + 1;
+       if (!(error.text.data = malloc(error.text.length)))
+         return(ENOMEM);
+       strcpy(error.text.data, message);
+       if (retval = krb5_mk_error(&error, &outbuf)) {
+           free(error.text.data);
+           return(retval);
+       }
+       free(error.text.data);
+    } else {
+       outbuf.length = 0;
+       outbuf.data = 0;
+    }
+    if (retval = krb5_write_message(fd, &outbuf)) {
+       if (outbuf.data)
+         xfree(outbuf.data);
+       if (!problem)
+         krb5_free_tkt_authent(authdat);
+       return(retval);
+    }
+    if (problem) {
+       /*
+        * We sent back an error, we need to return
+        */
+       if (authdat) krb5_free_tkt_authent(authdat);
+       return(problem);
+    }
+    /*
+     * Here lies the mutual authentication stuff...
+     *
+     * We're going to compose and send a AP_REP message.
+     */
+    if ((authdat->ap_options & AP_OPTS_MUTUAL_REQUIRED)) {
+       krb5_ap_rep_enc_part    repl;
+       
+       /*
+        * Generate a random sequence number
+        */
+       if (seq_number &&
+           (retval = krb5_generate_seq_number(authdat->ticket->enc_part2->session,
+                                              seq_number))) {
+           krb5_free_tkt_authent(authdat);
+           return(retval);
+       }
+       
+       repl.ctime = authdat->authenticator->ctime;
+       repl.cusec = authdat->authenticator->cusec;
+       repl.subkey = authdat->authenticator->subkey;
+       if (seq_number)
+         repl.seq_number = *seq_number;
+       else
+         repl.seq_number = 0;
+       
+       if (retval = krb5_mk_rep(&repl,
+                                authdat->ticket->enc_part2->session,
+                                &outbuf)) {
+           krb5_free_tkt_authent(authdat);
+           return(retval);
+       }
+       if (retval = krb5_write_message(fd, &outbuf)) {
+           xfree(outbuf.data);
+           krb5_free_tkt_authent(authdat);
+           return(retval);
+       }
+       xfree(outbuf.data);
+    }
+    /*
+     * At this point, we've won.  We just need to copy whatever
+     * parts of the authdat structure which the user wants, clean
+     * up, and exit.
+     */
+    if (client)
+      if (retval =
+         krb5_copy_principal(authdat->ticket->enc_part2->client,
+                             client))
+       return(retval);
+    /*
+     * The following efficiency hack assumes knowledge about the
+     * structure of krb5_tkt_authent.  If we later add additional
+     * allocated substructures to krb5_tkt_authent, they will have
+     * to be reflected here; otherwise, we will probably have a
+     * memory leak.
+     *
+     * If the user wants that part of the authdat structure,
+     * return it; otherwise free it.
+     */
+    if (ticket)
+      *ticket = authdat->ticket;
+    else
+      krb5_free_ticket(authdat->ticket);
+    if (authent)
+      *authent = authdat->authenticator;
+    else
+      krb5_free_authenticator(authdat->authenticator);
+    xfree(authdat);
+    return 0;
+}
+
+#ifdef SERVE_V4
+
+int
+v4_des_read(fd, buf, len)
+int fd;
+register char *buf;
+int len;
+{
+       int nreturned = 0;
+       long net_len, rd_len;
+       int cc;
+
+       if (!do_encrypt)
+               return(read(fd, buf, len));
+
+       if (nstored >= len) {
+               bcopy(store_ptr, buf, len);
+               store_ptr += len;
+               nstored -= len;
+               return(len);
+       } else if (nstored) {
+               bcopy(store_ptr, buf, nstored);
+               nreturned += nstored;
+               buf += nstored;
+               len -= nstored;
+               nstored = 0;
+       }
+       
+       if ((cc = krb_net_read(fd, &net_len, sizeof(net_len))) != sizeof(net_len)) {
+               /* XXX can't read enough, pipe
+                  must have closed */
+               return(0);
+       }
+       net_len = ntohl(net_len);
+       if (net_len < 0 || net_len > sizeof(des_inbuf)) {
+               /* XXX preposterous length, probably out of sync.
+                  act as if pipe closed */
+               return(0);
+       }
+       /* the writer tells us how much real data we are getting, but
+          we need to read the pad bytes (8-byte boundary) */
+       rd_len = roundup(net_len, 8);
+       if ((cc = krb_net_read(fd, des_inbuf, rd_len)) != rd_len) {
+               /* XXX can't read enough, pipe
+                  must have closed */
+               return(0);
+       }
+       (void) pcbc_encrypt(des_inbuf,
+                           storage,
+                           (net_len < 8) ? 8 : net_len,
+                           schedule,
+                           v4_kdata->session,
+                           DECRYPT);
+       /* 
+        * when the cleartext block is < 8 bytes, it is "right-justified"
+        * in the block, so we need to adjust the pointer to the data
+        */
+       if (net_len < 8)
+               store_ptr = storage + 8 - net_len;
+       else
+               store_ptr = storage;
+       nstored = net_len;
+       if (nstored > len) {
+               bcopy(store_ptr, buf, len);
+               nreturned += len;
+               store_ptr += len;
+               nstored -= len;
+       } else {
+               bcopy(store_ptr, buf, nstored);
+               nreturned += nstored;
+               nstored = 0;
+       }
+       
+       return(nreturned);
+}
+
+int
+v4_des_write(fd, buf, len)
+int fd;
+char *buf;
+int len;
+{
+       long net_len;
+       static int seeded = 0;
+       static char garbage_buf[8];
+       long garbage;
+
+       if (!do_encrypt)
+               return(write(fd, buf, len));
+
+       /* 
+        * pcbc_encrypt outputs in 8-byte (64 bit) increments
+        *
+        * it zero-fills the cleartext to 8-byte padding,
+        * so if we have cleartext of < 8 bytes, we want
+        * to insert random garbage before it so that the ciphertext
+        * differs for each transmission of the same cleartext.
+        * if len < 8 - sizeof(long), sizeof(long) bytes of random
+        * garbage should be sufficient; leave the rest as-is in the buffer.
+        * if len > 8 - sizeof(long), just garbage fill the rest.
+        */
+
+#ifdef min
+#undef min
+#endif
+#define min(a,b) ((a < b) ? a : b)
+
+       if (len < 8) {
+               if (!seeded) {
+                       seeded = 1;
+                       srandom((int) time((long *)0));
+               }
+               garbage = random();
+               /* insert random garbage */
+               (void) bcopy(&garbage, garbage_buf, min(sizeof(long),8));
+
+               /* this "right-justifies" the data in the buffer */
+               (void) bcopy(buf, garbage_buf + 8 - len, len);
+       }
+       (void) pcbc_encrypt((len < 8) ? garbage_buf : buf,
+                           des_outbuf,
+                           (len < 8) ? 8 : len,
+                           schedule,
+                           v4_kdata->session,
+                           ENCRYPT);
+
+       /* tell the other end the real amount, but send an 8-byte padded
+          packet */
+       net_len = htonl(len);
+       (void) write(fd, &net_len, sizeof(net_len));
+       (void) write(fd, des_outbuf, roundup(len,8));
+       return(len);
+}
+
+#endif /* SERVE_V4 */
+#endif /* KERBEROS */