Add -A (require authentication but not necessarily authorization) and
authorDan Winship <danw@mit.edu>
Mon, 26 Oct 1998 19:00:46 +0000 (19:00 +0000)
committerDan Winship <danw@mit.edu>
Mon, 26 Oct 1998 19:00:46 +0000 (19:00 +0000)
-C (user wants local credentials) flags to ftpd.

If -C is set, write out forwarded credentials to disk and use krb524 to
get krb4 tickets as well. If user doesn't forward credentials, require
a password even if auth was successful and use that to get credentials.

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@10993 dc483132-0cff-0310-8789-dd5450dbe970

src/appl/gssftp/configure.in
src/appl/gssftp/ftpd/ChangeLog
src/appl/gssftp/ftpd/Makefile.in
src/appl/gssftp/ftpd/ftpd.M
src/appl/gssftp/ftpd/ftpd.c

index 2ede0ba169d24527d762823d94409f22779ed685..112a29748eca343025c68bb5679e86ad5f501176 100644 (file)
@@ -52,6 +52,20 @@ AC_MSG_RESULT($krb5_cv_shadow_pwd)
 if test $krb5_cv_shadow_pwd = yes; then
 AC_DEFINE(HAVE_SHADOW)
 fi
+AC_ARG_WITH([krb4],
+[  --without-krb4          don't include Kerberos V4 backwards compatibility
+  --with-krb4             use V4 libraries included with V5 (default)
+  --with-krb4=KRB4DIR     use preinstalled V4 libraries],
+,
+withval=yes
+)dnl
+if test $withval = no; then
+       AC_MSG_RESULT(no krb4 support)
+else 
+       AC_MSG_RESULT(Adding in krb4 support)
+       FTPD_LIBS="../../../krb524/libkrb524.a"
+fi
+AC_SUBST(FTPD_LIBS)
 dnl
 dnl
 dnl
index 92e0e9b5a2d957beedb3994a995766f3ab8e1693..37681c479d7266bde96fc8d3fba69b47ac56fbbe 100644 (file)
@@ -1,3 +1,30 @@
+Mon Oct 26 13:46:47 1998  Dan Winship  <danw@mit.edu>
+
+       * ftpd.c (main): Add -A (require authentication, but not
+       necessarily authorization) and -C (user wants local credentials).
+
+       (user): Implement -A. Reorganize code a bit. If want_creds (-C) is
+       set, require a password even if authorization succeeds.
+
+       (kpass): Add krb5 ticket-getting code too. If want_creds is set,
+       don't destroy the tickets after verifying the Kerberos password.
+
+       (pass): Check krb password before local password, so we can
+       get credentials if we need them. Ignore local password if
+       want_creds is set. In case of "too many failed login attempts",
+       exit via dologout() instead of exit() so on-disk credentials are
+       destroyed.
+
+       (auth_data): If user forwards GSSAPI creds and want_creds is set,
+       write them out to a krb5 ccache. If doing krb4 compat, convert
+       them to krb4 tickets as well. (If want_creds is not set, smile and
+       nod at the user and then destroy the creds.)
+
+       (end_login): If the user has creds on disk, destroy them.
+       (dologout): If the user has creds on disk, destroy them.
+
+       * ftpd.M: Add -A and -C.
+       
 Fri Oct 23 18:18:52 1998  Theodore Y. Ts'o  <tytso@mit.edu>
 
        * ftpd.c (pass): Wait 5 seconds before returning "password
index 41e7333bceb7ba4f69bddc3a5ab4bc26c89503fd..0988bb95e6407b850710d7109783394ac846bc8c 100644 (file)
@@ -11,6 +11,7 @@ SETENVSRC=@SETENVSRC@
 SETENVOBJ=@SETENVOBJ@
 LIBOBJS=@LIBOBJS@
 COMERRLIB=$(BUILDTOP)/util/et/libcom_err.a
+FTPD_LIBS=@FTPD_LIBS@
 
 SRCS   = $(srcdir)/ftpd.c ftpcmd.c $(srcdir)/logwtmp.c $(srcdir)/popen.c \
          $(srcdir)/vers.c \
@@ -28,7 +29,7 @@ DEFINES = -DGSSAPI -DNOCONFIDENTIAL
 all::  ftpd
 
 ftpd:  $(OBJS) $(GSS_DEPLIBS) $(UTIL_DEPLIB) $(KRB4COMPAT_DEPLIBS)
-       $(CC_LINK) -o $@ $(OBJS) $(GSS_LIBS) $(UTIL_LIB) $(KRB4COMPAT_LIBS)
+       $(CC_LINK) -o $@ $(OBJS) $(FTPD_LIBS) $(GSS_LIBS) $(UTIL_LIB) $(KRB4COMPAT_LIBS)
 
 clean::
        $(RM) ftpd ftpcmd.c
index cdbb69b541402b0fa904833c5f41f76c8aa89fce..30d8c18b153e51206d2eb27b6f8c327f93eb336c 100644 (file)
@@ -39,7 +39,7 @@
 Internet File Transfer Protocol server
 .SH SYNOPSIS
 .B ftpd
-[\fB\-a\fP] [\fB\-c\fP] [\fB\-d\fP] [\fB\-l\fP] 
+[\fB\-a \fP|\fB -A\fP] [\fB\-c\fP] [\fB\-C\fP] [\fB\-d\fP] [\fB\-l\fP]
 [\fB\-t\fP \fItimeout\fP] [\fB\-T\fP \fImaxtimeout\fP]
 [\fB\-p\fP \fIport\fP] [\fB\-u\fP \fIumask\fP]
 [\fB\-r\fP \fIrealm-file\fP] [\fB\-s\fP \fIsrvtab\fP]
@@ -55,8 +55,22 @@ specification; see
 .PP
 Available options:
 .TP
+.B \-A
+Connections are only allowed for users who can authenticate via the
+ftp AUTH mechanism. (Anonymous ftp may also be allowed if it is 
+configured.) Ftpd will ask the user for a password if one is
+required.
+.TP
 .B \-a
-Only permit Kerberos-authenticated or anonymous logins.
+Connections are only allowed for users who can authenticate (via the
+ftp AUTH mechanism) and who are authorized to connect to the named 
+account without a password. (Anonymous ftp may also be allowed if it is 
+configured.)
+.TP
+.B \-C
+Non-anonymous users need local credentials (for example, to authenticate
+to remote fileservers), and so they should be prompted for a password
+unless they forwarded credentials as part of authentication.
 .TP
 .B \-c
 Allow the CCC (Clear Command Channel) command to be used. This allows
@@ -95,14 +109,14 @@ Sets the umask for the ftpd process.  The default value is normally 027.
 \fB\-r\fP \fIrealm-file\fP
 Sets the name of the
 .I krb.conf
-file to use.  The default value is normally
-.IR /usr/kerberos/lib/krb.conf .
+file to use.  The default value is normally set by
+.IR /etc/krb5.conf .
 .TP
 \fB\-s\fP \fIsrvtab\fP
 Sets the name of the
 .I srvtab
-file to use.  The default value is normally
-.IR /etc/krb5.keytab .
+file to use for Kerberos V4 authentication.  The default value is normally
+.IR /etc/srvtab .
 .PP
 The ftp server currently supports the following ftp requests; case is
 not distinguished.
index 5b861c6f14e3d1f1265f184c213bc07fbca98aa4..db424a597189c79c8ca4abef92fedb1dadb5966a 100644 (file)
@@ -129,8 +129,8 @@ AUTH_DAT kdata;
 KTEXT_ST ticket;
 MSG_DAT msg_data;
 Key_schedule schedule;
-int kerb_ok;   /* Kerberos authentication and authorization succeeded */
 char *keyfile;
+static char *krb4_services[] = { "ftp", "rcmd", NULL };
 #endif /* KRB5_KRB4_COMPAT */
 
 #ifdef GSSAPI
@@ -138,12 +138,17 @@ char *keyfile;
 #include <gssapi/gssapi_generic.h>
 gss_ctx_id_t gcontext;
 gss_buffer_desc client_name;
-int gss_ok;    /* GSSAPI authentication and userok authorization succeeded */
-char* gss_services[] = { "ftp", "host", 0 };
+static char *gss_services[] = { "ftp", "host", NULL };
+
+#include <krb5.h>
+krb5_context kcontext;
+krb5_ccache ccache;
 #endif /* GSSAPI */
 
 char *auth_type;       /* Authentication succeeded?  If so, what type? */
 static char *temp_auth_type;
+int authorized;                /* Auth succeeded and was accepted by krb4 or gssapi */
+int have_creds;                /* User has credentials on disk */
 
 /*
  * File containing login names
@@ -177,7 +182,8 @@ int ccc_ok = 0;       /* whether or not to accept cleartext commands */
 int    timeout = 900;    /* timeout after 15 minutes of inactivity */
 int    maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
 int    logging;
-int    authenticate;
+int    authlevel;
+int    want_creds;
 int    guest;
 int    restricted;
 int    type;
@@ -201,6 +207,11 @@ char    pathbuf[MAXPATHLEN + 1];
 char   hostname[MAXHOSTNAMELEN];
 char   remotehost[MAXHOSTNAMELEN];
 
+/* Defines for authlevel */
+#define AUTHLEVEL_NONE         0
+#define AUTHLEVEL_AUTHENTICATE 1
+#define AUTHLEVEL_AUTHORIZE    2
+
 /*
  * Timeout intervals for retrying connections
  * to hosts that don't accept PORT cmds.  This
@@ -266,6 +277,13 @@ main(argc, argv, envp)
        LastArgv = envp[-1] + strlen(envp[-1]);
 #endif /* SETPROCTITLE */
 
+#ifdef GSSAPI
+       krb5_init_context(&kcontext);
+#ifdef KRB5_KRB4_COMPAT
+       krb524_init_ets(kcontext);
+#endif
+#endif
+
        argc--, argv++;
        while (argc > 0 && *argv[0] == '-') {
                for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
@@ -283,7 +301,15 @@ main(argc, argv, envp)
                        break;
 
                case 'a':
-                       authenticate = 1;
+                       authlevel = AUTHLEVEL_AUTHORIZE;
+                       break;
+
+               case 'A':
+                       authlevel = AUTHLEVEL_AUTHENTICATE;
+                       break;
+
+               case 'C':
+                       want_creds = 1;
                        break;
 
                case 'c':
@@ -618,20 +644,11 @@ user(name)
 {
        register char *cp;
        char *shell;
+       char buf[FTP_BUFSIZ];
 #ifdef HAVE_GETUSERSHELL
        char *getusershell();
 #endif
 
-#ifdef PARANOID
-       /*
-        * Some paranoid sites may want the client to authenticate
-        * before accepting the USER command.
-        */
-       if (!auth_type) {
-               reply(530,
-                       "Must perform authentication before identifying USER.");
-               return;
-#endif
        if (logged_in) {
                if (guest) {
                        reply(530, "Can't change user from guest login.");
@@ -640,7 +657,7 @@ user(name)
                end_login();
        }
 
-       guest = 0;
+       authorized = guest = 0;
        if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
                if (disallowed_user("ftp") || disallowed_user("anonymous"))
                        reply(530, "User %s access denied.", name);
@@ -652,6 +669,17 @@ user(name)
                        reply(530, "User %s unknown.", name);
                return;
        }
+
+       /*
+        * If authentication is required, check that before anything
+        * else to avoid leaking information.
+        */
+       if (authlevel && !auth_type) {
+               reply(530,
+                     "Must perform authentication before identifying USER.");
+               return;
+       }
+
        if (pw = sgetpwnam(name)) {
                if ((shell = pw->pw_shell) == NULL || *shell == 0)
                        shell = "/bin/sh";
@@ -675,71 +703,50 @@ user(name)
                }
                restricted = restricted_user(name);
        }
+
+       if (auth_type) {
+               int result;
 #ifdef GSSAPI
-       if (auth_type && strcmp(auth_type, "GSSAPI") == 0) {
-               char buf[FTP_BUFSIZ];
-               gss_ok = ftpd_userok(&client_name, name) == 0;
-               if (! gss_ok && authenticate) {
-                       reply(530, "User %s access denied.", name);
-                       if (logging)
-                               syslog(LOG_NOTICE,
-                                      "FTP GSSAPI LOGIN REFUSED FROM %s, %s",
-                                      remotehost, name);
-                       pw = (struct passwd *) NULL;
-                       return;
+               if (auth_type && strcmp(auth_type, "GSSAPI") == 0) {
+                       authorized = ftpd_gss_userok(&client_name, name) == 0;
+                       sprintf(buf, "GSSAPI user %s is%s authorized as %s",
+                               client_name.value, authorized ? "" : " not",
+                               name);
                }
-               sprintf(buf, "GSSAPI user %s is%s authorized as %s%s",
-                       client_name.value,
-                       gss_ok ? "" : " not",
-                       name, gss_ok ? "" : "; Password required.");
-               /* 232 is per draft-8, but why 331 not 53z? */
-               reply(gss_ok ? 232 : 331, "%s", buf);
-               syslog(gss_ok ? LOG_INFO : LOG_ERR, "%s", buf);
-               if (gss_ok) {
-                       login((char *) NULL);
-                       return;
-               }
-       } else
+#ifdef KRB5_KRB4_COMPAT
+               else
+#endif /* KRB5_KRB4_COMPAT */
 #endif /* GSSAPI */
 #ifdef KRB5_KRB4_COMPAT
-       if (auth_type && strcmp(auth_type, "KERBEROS_V4") == 0) {
-               char buf[FTP_BUFSIZ];
-               kerb_ok = kuserok(&kdata,name) == 0;
-               if (! kerb_ok && authenticate) {
-                       reply(530, "User %s access denied.", name);
-                       if (logging)
-                               syslog(LOG_NOTICE,
-                                      "FTP KERBEROS LOGIN REFUSED FROM %s, %s",
-                                      remotehost, name);
-                       pw = (struct passwd *) NULL;
-                       return;
+               if (auth_type && strcmp(auth_type, "KERBEROS_V4") == 0) {
+                       authorized = kuserok(&kdata,name) == 0;
+                       sprintf(buf, "Kerberos user %s%s%s@%s is%s authorized as %s%s",
+                               kdata.pname, *kdata.pinst ? "." : "",
+                               kdata.pinst, kdata.prealm,
+                               authorized ? "" : " not", name);
                }
-               sprintf(buf, "Kerberos user %s%s%s@%s is%s authorized as %s%s",
-                       kdata.pname, *kdata.pinst ? "." : "",
-                       kdata.pinst, kdata.prealm,
-                       kerb_ok ? "" : " not",
-                       name, kerb_ok ? "" : "; Password required.");
-               reply(kerb_ok ? 232 : 331, "%s", buf);
-               syslog(kerb_ok ? LOG_INFO : LOG_ERR, "%s", buf);
-               if (kerb_ok) {
-                       login((char *) NULL);
-                       return;
-               }               
-       } else
 #endif /* KRB5_KRB4_COMPAT */
-       /* Other auth types go here ... */
-       if (authenticate) {
-               reply(530, "User %s access denied: authentication required.",
-                     name);
-               if (logging)
-                       syslog(LOG_NOTICE,
-                              "FTP LOGIN REFUSED FROM %s, %s",
-                              remotehost, name);
-               pw = (struct passwd *) NULL;
+
+               if (!authorized && authlevel == AUTHLEVEL_AUTHORIZE) {
+                       strcat(buf, "; Access denied.");
+                       result = 530;
+                       pw = NULL;
+               } else if (!authorized || (want_creds && !have_creds)) {
+                       strcat(buf, "; Password required.");
+                       askpasswd = 1;
+                       result = 331;
+               } else
+                       result = 232;
+               reply(result, "%s", buf);
+               syslog(authorized ? LOG_INFO : LOG_ERR, "%s", buf);
+
+               if (result == 232)
+                       login(NULL);
                return;
-       } else
-               reply(331, "Password required for %s.", name);
+       }
 
+       /* User didn't authenticate and authentication wasn't required. */
+       reply(331, "Password required for %s.", name);
        askpasswd = 1;
 
        /*
@@ -814,69 +821,148 @@ end_login()
        (void) krb5_seteuid((uid_t)0);
        if (logged_in)
                ftp_logwtmp(ttyline, "", "");
+       if (have_creds) {
+#ifdef GSSAPI
+               krb5_cc_destroy(kcontext, ccache);
+#endif
+#ifdef KRB5_KRB4_COMPAT
+               dest_tkt();
+#endif
+               have_creds = 0;
+       }
        pw = NULL;
        logged_in = 0;
        guest = 0;
 }
 
-#ifdef KRB5_KRB4_COMPAT
-static char *services[] = { "ftp", "rcmd", NULL };
-
 kpass(name, passwd)
 char *name, *passwd;
 {
-       char **service;
-       char instance[INST_SZ];
+#ifdef GSSAPI
+       krb5_error_code code;
+       krb5_principal server, me;
+       krb5_creds my_creds;
+       krb5_timestamp now;
+#endif /* GSSAPI */
+#ifdef KRB5_KRB4_COMPAT
        char realm[REALM_SZ];
-       char tkt_file[20];
+#ifndef GSSAPI
+       char **service;
        KTEXT_ST ticket;
        AUTH_DAT authdata;
        des_cblock key;
+       char instance[INST_SZ];
        unsigned long faddr;
        struct hostent *hp;
+#endif /* GSSAPI */
+#endif /* KRB5_KRB4_COMPAT */
+       char ccname[MAXPATHLEN];
 
-       if (krb_get_lrealm(realm, 1) != KSUCCESS)
+#ifdef GSSAPI
+       memset((char *)&my_creds, 0, sizeof(my_creds));
+       if (krb5_parse_name(kcontext, name, &me))
+               return 0;
+       my_creds.client = me;
+
+       sprintf(ccname, "FILE:/tmp/krb5cc_ftpd%d", getpid());
+       if (krb5_cc_resolve(kcontext, ccname, &ccache))
+               return(0);
+       if (krb5_cc_initialize(kcontext, ccache, me))
                return(0);
+       if (krb5_build_principal_ext(kcontext, &server,
+                                    krb5_princ_realm(kcontext, me)->length,
+                                    krb5_princ_realm(kcontext, me)->data,
+                                    KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
+                                    krb5_princ_realm(kcontext, me)->length,
+                                    krb5_princ_realm(kcontext, me)->data,
+                                    0))
+               goto nuke_ccache;
+
+       my_creds.server = server;
+       if (krb5_timeofday(kcontext, &now))
+               goto nuke_ccache;
+       my_creds.times.starttime = 0; /* start timer when 
+                                        request gets to KDC */
+       my_creds.times.endtime = now + 60 * 60 * 10;
+       my_creds.times.renew_till = 0;
+
+       if (krb5_get_in_tkt_with_password(kcontext, 0,
+                                         0, NULL, 0 /*preauth*/,
+                                         passwd,
+                                         ccache,
+                                         &my_creds, 0))
+               goto nuke_ccache;
+
+       if (!want_creds) {
+               krb5_cc_destroy(kcontext, ccache);
+               return(1);
+       }
+#endif /* GSSAPI */
+
+#ifdef KRB5_KRB4_COMPAT
+       if (krb_get_lrealm(realm, 1) != KSUCCESS)
+               goto nuke_ccache;
 
-       strcpy(tkt_file, TKT_ROOT);
-       strcat(tkt_file, "_ftpdXXXXXX");
-       krb_set_tkt_string(mktemp(tkt_file));
+       sprintf(ccname, "%s_ftpd%d", TKT_ROOT, getpid());
+       krb_set_tkt_string(ccname);
 
-       (void) strncpy(instance, krb_get_phost(hostname), sizeof(instance));
+       if (krb_get_pw_in_tkt(name, "", realm, "krbtgt", realm, 1, passwd))
+               goto nuke_ccache;
 
-       if ((hp = gethostbyname(instance)) == NULL)
-               return(0);
+#ifndef GSSAPI
+       /* Verify the ticket since we didn't verify the krb5 one. */
+       strncpy(instance, krb_get_phost(hostname), sizeof(instance));
 
+       if ((hp = gethostbyname(instance)) == NULL)
+               goto nuke_ccache;
        memcpy((char *) &faddr, (char *)hp->h_addr, sizeof(faddr));
 
-       if (krb_get_pw_in_tkt(name, "", realm, "krbtgt", realm, 1, passwd)) {
-         for (service = services; *service; service++)
-           if (!read_service_key(*service, instance, realm, 0, keyfile, key)) {
-             (void) memset(key, 0, sizeof(key));
-             if (krb_mk_req(&ticket, *service, instance, realm, 33) ||
-                 krb_rd_req(&ticket, *service, instance, faddr, &authdata,keyfile)||
-                 kuserok(&authdata, name)) {
+       for (service = krb4_services; *service; service++) {
+               if (!read_service_key(*service, instance,
+                                     realm, 0, keyfile, key)) {
+                       (void) memset(key, 0, sizeof(key));
+                       if (krb_mk_req(&ticket, *service,
+                                      instance, realm, 33) ||
+                           krb_rd_req(&ticket, *service, instance,
+                                      faddr, &authdata,keyfile) ||
+                           kuserok(&authdata, name)) {
+                               dest_tkt();
+                               goto nuke_ccache;
+                       } else
+                               break;
+               }
+       }
+
+       if (!*service) {
                dest_tkt();
-               return(0);
-             } else {
+               goto nuke_ccache;
+       }
+
+       if (!want_creds) {
                dest_tkt();
                return(1);
-             }
-           }
-         dest_tkt();
-         return(0);
        }
-       dest_tkt();
+#endif /* GSSAPI */
+#endif /* KRB5_KRB4_COMPAT */
+
+#if defined(GSSAPI) || defined(KRB5_KRB4_COMPAT)
+       have_creds = 1;
        return(1);
+#endif /* GSSAPI || KRB5_KRB4_COMPAT */
+
+nuke_ccache:
+#ifdef GSSAPI
+       krb5_cc_destroy(kcontext, ccache);
+#endif /* GSSAPI */
+       return(0);
 }
-#endif /* KRB5_KRB4_COMPAT */
 
 pass(passwd)
        char *passwd;
 {
        char *xpasswd, *salt;
 
-       if (auth_ok()) {
+       if (authorized && !want_creds) {
                reply(202, "PASS command superfluous.");
                return;
        }
@@ -886,7 +972,7 @@ pass(passwd)
                return;
        } 
 
-       if (!auth_ok() && !guest) {
+       if (!guest) {
                /* "ftp" is only account allowed no password */
                if (pw == NULL)
                        salt = "xx";
@@ -898,18 +984,15 @@ pass(passwd)
 #else
                xpasswd = crypt(passwd, salt);
 #endif
-#ifdef KRB5_KRB4_COMPAT
-               /* null pw_passwd ok if Kerberos password ok */
-               if (pw == NULL ||
-                   (*pw->pw_passwd && strcmp(xpasswd, pw->pw_passwd) &&
-                       !kpass(pw->pw_name, passwd)) ||
-                   (!*pw->pw_passwd && !kpass(pw->pw_name, passwd)))
-#else
-               /* The strcmp does not catch null passwords! */
-               if (pw == NULL || *pw->pw_passwd == '\0' ||
-                   strcmp(xpasswd, pw->pw_passwd))
-#endif /* KRB5_KRB4_COMPAT */
-                                                                     {
+               /* Fail if:
+                *   pw is NULL
+                *   kpass fails and we want_creds
+                *   kpass fails and the user has no local password
+                *   kpass fails and the provided password doesn't match pw
+                */
+               if (pw == NULL || (!kpass(pw->pw_name, passwd) &&
+                                  (want_creds || !*pw->pw_passwd ||
+                                   strcmp(xpasswd, pw->pw_passwd)))) {
                        pw = NULL;
                        sleep(5);
                        if (++login_attempts >= 3) {
@@ -918,7 +1001,7 @@ pass(passwd)
                                syslog(LOG_NOTICE,
                                    "repeated login failures from %s",
                                    remotehost);
-                               exit(0);
+                               dologout(0);
                        }
                        reply(530, "Login incorrect.");
                        return;
@@ -933,6 +1016,16 @@ pass(passwd)
 login(passwd)
        char *passwd;
 {
+       if (have_creds) {
+#ifdef GSSAPI
+               char *ccname = krb5_cc_get_name(kcontext, ccache);
+               chown(ccname, pw->pw_uid, pw->pw_gid);
+#endif
+#ifdef KRB5_KRB4_COMPAT
+               chown(tkt_string(), pw->pw_uid, pw->pw_gid);
+#endif
+       }
+
        (void) krb5_setegid((gid_t)pw->pw_gid);
        (void) initgroups(pw->pw_name, pw->pw_gid);
 
@@ -1834,6 +1927,14 @@ dologout(status)
                (void) krb5_seteuid((uid_t)0);
                ftp_logwtmp(ttyline, "", "");
        }
+       if (have_creds) {
+#ifdef GSSAPI
+               krb5_cc_destroy(kcontext, ccache);
+#endif
+#ifdef KRB5_KRB4_COMPAT
+               dest_tkt();
+#endif
+       }
        /* beware of flushing buffers after a SIGPIPE */
        _exit(status);
 }
@@ -2210,22 +2311,29 @@ char *data;
                                                                &deleg_creds);
                                return 0;
                        }
-                       /* If the server accepts the security data, but does
-                          not require any additional data (i.e., the security
-                          data exchange has completed successfully), it must
-                          respond with reply code 235. */
-                       if (!replied)
-                         reply(235, "GSSAPI Authentication succeeded");
-                               
                        auth_type = temp_auth_type;
                        temp_auth_type = NULL;
 
                        (void) gss_release_cred(&stat_min, &server_creds);
                        if (ret_flags & GSS_C_DELEG_FLAG) {
-                         /* This would be a good place to do something
-                            useful with the forwarded credentials... */
+                         if (want_creds)
+                           ftpd_gss_convert_creds(client_name.value,
+                                                  deleg_creds);
                          (void) gss_release_cred(&stat_min, &deleg_creds);
                        }
+
+                       /* If the server accepts the security data, but does
+                          not require any additional data (i.e., the security
+                          data exchange has completed successfully), it must
+                          respond with reply code 235. */
+                       if (!replied)
+                         {
+                           if (ret_flags & GSS_C_DELEG_FLAG && !have_creds)
+                             reply(235, "GSSAPI Authentication succeeded, but could not accept forwarded credentials");
+                           else
+                             reply(235, "GSSAPI Authentication succeeded");
+                         }
+                               
                        return(1);
                } else if (stat_maj == GSS_S_CONTINUE_NEEDED) {
                        /* If the server accepts the security data, and
@@ -2428,18 +2536,6 @@ data_err:
        pdata = -1;
 }
 
-int auth_ok(void)
-{
-       return(0
-#ifdef KRB5_KRB4_COMPAT
-              || kerb_ok
-#endif /* KRB5_KRB4_COMPAT */
-#ifdef GSSAPI
-              || gss_ok
-#endif /* GSSAPI */
-              );
-}
-
 #ifdef SETPROCTITLE
 /*
  * clobber argv so ps will show what we're doing.
@@ -2471,6 +2567,7 @@ char *buf;
                *p++ = ' ';
 }
 #endif /* SETPROCTITLE */
+
 #ifdef GSSAPI
 reply_gss_error(code, maj_stat, min_stat, s)
 int code;
@@ -2522,34 +2619,102 @@ char *s;
 }
 
 
-#include <krb5.h>
-/* ftpd_userok -- hide details of getting the name and verifying it */
+/* ftpd_gss_userok -- hide details of getting the name and verifying it */
 /* returns 0 for OK */
-ftpd_userok(client_name, name)
+ftpd_gss_userok(client_name, name)
        gss_buffer_t client_name;
        char *name;
 {
        int retval = -1;
-       krb5_boolean k5ret;
-       krb5_context kc;
        krb5_principal p;
-       krb5_error_code kerr;
        
-       kerr = krb5_init_context(&kc);
-       if (kerr)
+       if (krb5_parse_name(kcontext, client_name->value, &p) != 0)
                return -1;
-
-       kerr = krb5_parse_name(kc, client_name->value, &p);
-       if (kerr) { retval = -1; goto fail; }
-       k5ret = krb5_kuserok(kc, p, name);
-       if (k5ret == TRUE)
+       if (krb5_kuserok(kcontext, p, name))
                retval = 0;
        else 
                retval = 1;
-       krb5_free_principal(kc, p);
- fail:
-       krb5_free_context(kc);
+       krb5_free_principal(kcontext, p);
        return retval;
 }
+
+/* ftpd_gss_convert_creds -- write out forwarded creds */
+/* (code lifted from login.krb5) */
+ftpd_gss_convert_creds(name, creds)
+       char *name;
+       gss_cred_id_t creds;
+{
+       OM_uint32 major_status, minor_status;
+       krb5_principal me;
+       char ccname[MAXPATHLEN];
+#ifdef KRB5_KRB4_COMPAT
+       krb5_principal kpcserver;
+       krb5_error_code kpccode;
+       int kpcval;
+       krb5_creds increds, *v5creds;
+       CREDENTIALS v4creds;
+#endif
+
+       /* Set up ccache */
+       if (krb5_parse_name(kcontext, name, &me))
+               return;
+
+       sprintf(ccname, "FILE:/tmp/krb5cc_ftpd%d", getpid());
+       if (krb5_cc_resolve(kcontext, ccname, &ccache))
+               return;
+       if (krb5_cc_initialize(kcontext, ccache, me))
+               return;
+
+       /* Copy GSS creds into ccache */
+       major_status = gss_krb5_copy_ccache(&minor_status, creds, ccache);
+       if (major_status != GSS_S_COMPLETE)
+               goto cleanup;
+
+#ifdef KRB5_KRB4_COMPAT
+       /* Convert krb5 creds to krb4 */
+
+       if (krb5_build_principal_ext(kcontext, &kpcserver, 
+                                    krb5_princ_realm(kcontext, me)->length,
+                                    krb5_princ_realm(kcontext, me)->data,
+                                    6, "krbtgt",
+                                    krb5_princ_realm(kcontext, me)->length,
+                                    krb5_princ_realm(kcontext, me)->data,
+                                    NULL))
+               goto cleanup;
+
+       memset((char *) &increds, 0, sizeof(increds));
+       increds.client = me;
+       increds.server = kpcserver;
+       increds.times.endtime = 0;
+       increds.keyblock.enctype = ENCTYPE_DES_CBC_CRC;
+       if (krb5_get_credentials(kcontext, 0, ccache, &increds, &v5creds))
+               goto cleanup;
+       if (krb524_convert_creds_kdc(kcontext, v5creds, &v4creds))
+               goto cleanup;
+
+       sprintf(ccname, "%s_ftpd%d", TKT_ROOT, getpid());
+       krb_set_tkt_string(ccname);
+
+       if (in_tkt(v4creds.pname, v4creds.pinst) != KSUCCESS)
+               goto cleanup;
+
+       if (krb_save_credentials(v4creds.service, v4creds.instance,
+                                v4creds.realm, v4creds.session,
+                                v4creds.lifetime, v4creds.kvno,
+                                &(v4creds.ticket_st), v4creds.issue_date))
+               goto cleanup_v4;
+#endif /* KRB5_KRB4_COMPAT */
+       have_creds = 1;
+       return;
+
+#ifdef KRB5_KRB4_COMPAT
+cleanup_v4:
+       dest_tkt();
+#endif
+cleanup:
+       krb5_cc_destroy(kcontext, ccache);
+}
+
+
 #endif /* GSSAPI */