Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: binary MIME-Version: 1.0 X-Mailer: MIME-tools 5.411 (Entity 5.404) X-RT-Original-Encoding: iso-8859-1 Content-Length: 19135 From kenh@cmf.nrl.navy.mil Tue Nov 26 18:47:13 1996 Received: from MIT.EDU (SOUTH-STATION-ANNEX.MIT.EDU [18.72.1.2]) by rt-11.MIT.EDU (8.7.5/8.7.3) with SMTP id SAA19278 for ; Tue, 26 Nov 1996 18:47:11 -0500 Received: from ginger.cmf.nrl.navy.mil by MIT.EDU with SMTP id AA18971; Tue, 26 Nov 96 18:47:10 EST Received: from elvis.cmf.nrl.navy.mil (kenh@elvis.cmf.nrl.navy.mil [134.207.10.38]) by ginger.cmf.nrl.navy.mil (8.7.5/8.7.3) with ESMTP id SAA22018 for ; Tue, 26 Nov 1996 18:47:11 -0500 (EST) Received: (kenh@localhost) by elvis.cmf.nrl.navy.mil (8.6.12/8.6.11) id SAA09013; Tue, 26 Nov 1996 18:47:06 -0500 Message-Id: <199611262347.SAA09013@elvis.cmf.nrl.navy.mil> Date: Tue, 26 Nov 1996 18:47:06 -0500 From: Ken Hornstein Reply-To: kenh@cmf.nrl.navy.mil To: krb5-bugs@MIT.EDU Subject: Changes for ftp/ftpd to support ticket forwarding & AFS X-Send-Pr-Version: 3.2 >Number: 255 >Category: krb5-appl >Synopsis: Changes for ftp/ftpd to support ticket forwarding & AFS >Confidential: no >Severity: non-critical >Priority: low >Responsible: krb5-unassigned >State: open >Class: change-request >Submitter-Id: unknown >Arrival-Date: Tue Nov 26 18:48:00 EST 1996 >Last-Modified: >Originator: Ken Hornstein >Organization: Naval Research Lab >Release: beta-7 >Environment: System: SunOS elvis 4.1.3_U1 13 sun4m Architecture: sun4 >Description: I've included my patches for doing ticket forwarding and cleartext passwords for ftp/ftpd. This also includes AFS support (ie - get a PAG and run aklog at the right time). Comments/suggestions are welcome. >How-To-Repeat: Try using the Kerberos 5 ftp/ftpd with AFS. >Fix: --- appl/gssftp/ftp/Makefile.in.orig Wed Nov 6 15:09:01 1996 +++ appl/gssftp/ftp/Makefile.in Wed Nov 6 15:09:47 1996 @@ -1,7 +1,7 @@ # # appl/gssftp/ftp/Makefile.in # -CFLAGS = -DGSSAPI -DFTP_BUFSIZ=10240 $(CCOPTS) $(DEFS) $(LOCALINCLUDE) +CFLAGS = -DGSSAPI -DKERBEROS5 -DFTP_BUFSIZ=10240 $(CCOPTS) $(DEFS) $(LOCALINCLUDE) COMERRLIB=$(BUILDTOP)/util/et/libcom_err.a --- appl/gssftp/ftp/main.c.orig Wed Nov 6 15:10:26 1996 +++ appl/gssftp/ftp/main.c Wed Nov 6 16:31:31 1996 @@ -73,6 +73,11 @@ extern char realm[]; #endif /* KERBEROS */ +#ifdef KERBEROS5 +#include +#include +#endif + main(argc, argv) char *argv[]; { @@ -80,6 +85,12 @@ int top; struct passwd *pw = NULL; char homedir[MAXPATHLEN]; +#ifdef KERBEROS5 + krb5_context context; + krb5_ccache ccache; + krb5_error_code code = 0; + krb5_principal princ; +#endif sp = getservbyname("ftp", "tcp"); if (sp == 0) { @@ -92,6 +103,33 @@ memcpy(&staticsp,sp,sizeof(struct servent)); sp = &staticsp; #endif /* KERBEROS */ + +#ifdef KERBEROS5 + krb5_init_context(&context); + krb5_init_ets(context); + + /* + * Forward credentials if we get a command-line flag or if we + * have the right stuff set in the profile, _AND_ if our TGT + * is forwardable + */ + + if ((code = krb5_cc_default(context, &ccache)) != 0) { + com_err(argv[0], code, "while reading credential cache"); + } + + if ((code == 0) && + (code = krb5_cc_get_principal(context, ccache, &princ)) != 0) { + com_err(argv[0], code, "while getting primary principal"); + } + + if (code == 0) { + krb5_appdefault_boolean(context, "ftp", + krb5_princ_realm(context, princ), + "forward", 0, &forward); + } +#endif /* KERBEROS5 */ + doglob = 1; interactive = 1; autologin = 1; @@ -137,6 +175,15 @@ case 'g': doglob = 0; break; +#ifdef KERBEROS5 + case 'f': + forward = 1; + break; + + case 'F': + forward = 0; + break; +#endif /* KERBEROS5 */ default: fprintf(stdout, @@ -146,6 +193,51 @@ nextopt: argc--, argv++; } + +#ifdef KERBEROS5 + + if (code != 0) + forward = 0; + + if (forward) { + krb5_creds creds, mcreds; + + creds.client = princ; + code = krb5_build_principal(context, &creds.server, + krb5_princ_realm(context, princ)->length, + krb5_princ_realm(context, princ)->data, + "krbtgt", + krb5_princ_realm(context, princ)->data, 0); + + if (code != 0) { + com_err(argv[0], code, "while building TGT principal"); + forward = 0; + } + + if (code == 0) + code = krb5_cc_retrieve_cred(context, ccache, 0, + &creds, &mcreds); + + if (code == 0) { + krb5_free_principal(context, creds.server); + + if ((mcreds.ticket_flags & TKT_FLG_FORWARDABLE) == 0) + forward = 0; + + krb5_free_creds(context, &mcreds); + } + + if (code != 0) + forward = 0; + } + + krb5_cc_close(context, ccache); + + krb5_free_principal(context, princ); + krb5_free_context(context); + +#endif KERBEROS5 + fromatty = isatty(fileno(stdin)); if (fromatty) verbose++; --- appl/gssftp/ftp/ftp_var.h.orig Wed Nov 6 15:10:38 1996 +++ appl/gssftp/ftp/ftp_var.h Wed Nov 6 15:11:27 1996 @@ -103,6 +103,10 @@ extern int options; /* used during socket creation */ +#ifdef KERBEROS5 +extern int forward; /* Should we forward credentials? */ +#endif + /* * Format of command table. */ --- appl/gssftp/ftp/ftp.c.orig Wed Nov 6 16:46:57 1996 +++ appl/gssftp/ftp/ftp.c Wed Nov 6 16:47:58 1996 @@ -1963,7 +1963,8 @@ &gcontext, target_name, GSS_C_NULL_OID, - GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, + GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | + (forward ? GSS_C_DELEG_FLAG : 0), 0, &chan, /* channel bindings */ token_ptr, --- appl/gssftp/ftpd/ftpd.c.orig Thu Nov 7 12:06:33 1996 +++ appl/gssftp/ftpd/ftpd.c Fri Nov 15 18:34:25 1996 @@ -225,6 +225,7 @@ { int addrlen, on = 1, tos, port = -1; char *cp; + char ccname[35]; debug = 0; #ifdef SETPROCTITLE @@ -375,6 +376,17 @@ #define LOG_DAEMON 0 #endif openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_DAEMON); + +#ifdef GSSAPI + /* + * Just in case we're using Kerberos, setup a private + * credential cache + */ + + sprintf(ccname, "FILE:/tmp/krb5cc_p%d", getpid()); + setenv("KRB5CCNAME", ccname, 0); +#endif + addrlen = sizeof (his_addr); if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) { syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); @@ -688,6 +700,9 @@ */ end_login() { +#ifdef GSSAPI + afs_logout(); +#endif (void) seteuid((uid_t)0); if (logged_in) @@ -783,6 +798,12 @@ (*pw->pw_passwd && strcmp(xpasswd, pw->pw_passwd) && !kpass(pw->pw_name, passwd)) || (!*pw->pw_passwd && !kpass(pw->pw_name, passwd))) { +#elif defined(GSSAPI) + /* null pw_passwd ok if Kerberos 5 password ok */ + if (pw == NULL || + (*pw->pw_passwd && strcmp(xpasswd, pw->pw_passwd) && + !k5pass(pw->pw_name, passwd)) || + (!*pw->pw_passwd && !k5pass(pw->pw_name, passwd))) { #else /* The strcmp does not catch null passwords! */ if (pw == NULL || *pw->pw_passwd == '\0' || @@ -818,18 +839,41 @@ reply(550, "Can't set guest privileges."); goto bad; } - } else if (chdir(pw->pw_dir) < 0) { - if (chdir("/") < 0) { - reply(530, "User %s: can't change directory to %s.", - pw->pw_name, pw->pw_dir); + if (seteuid((uid_t)pw->pw_uid) < 0) { + reply(550, "Can't set uid."); goto bad; - } else - lreply(230, "No directory! Logging in with home=/"); - } - if (seteuid((uid_t)pw->pw_uid) < 0) { - reply(550, "Can't set uid."); - goto bad; + } + } else { + /* + * Call afs_login to hide the extra magic we need to do + * + * Note that much of this stuff needs to happen as the user, + * so we're setting the effective uid now. + */ +#ifdef GSSAPI + save_credentials(); +#endif + + if (seteuid((uid_t)pw->pw_uid) < 0) { + reply(550, "Can't set uid."); + goto bad; + } + + +#ifdef GSSAPI + afs_login(pw->pw_uid); +#endif + + if (chdir(pw->pw_dir) < 0) { + if (chdir("/") < 0) { + reply(530, "User %s: can't change directory to %s.", + pw->pw_name, pw->pw_dir); + goto bad; + } else + lreply(230, "No directory! Logging in with home=/"); + } } + if (guest) { reply(230, "Guest login ok, access restrictions apply."); #ifdef SETPROCTITLE @@ -1669,6 +1713,9 @@ dologout(status) int status; { +#ifdef GSSAPI + afs_logout(); +#endif if (logged_in) { (void) seteuid((uid_t)0); logwtmp(ttyline, "", ""); @@ -1985,6 +2032,8 @@ ); if (accept_maj!=GSS_S_COMPLETE && accept_maj!=GSS_S_CONTINUE_NEEDED) continue; + else + break; } if (found) { @@ -2327,4 +2376,390 @@ krb5_free_context(kc); return retval; } + +/* + * Save our credentials and destroy a credential cache before we call setuid + */ + +static krb5_creds *saved_creds; + +save_credentials() +{ + krb5_context context; + krb5_ccache cc; + krb5_principal me, server; + krb5_creds mcred; + + krb5_init_context(&context); + + saved_creds = NULL; + + if (krb5_cc_default(context, &cc)) + goto leave; + + if (krb5_cc_get_principal(context, cc, &me)) { + krb5_cc_destroy(context, cc); + goto leave; + } + + if (krb5_build_principal_ext(context, &server, + krb5_princ_realm(context, me)->length, + krb5_princ_realm(context, me)->data, + KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, + krb5_princ_realm(context, me)->length, + krb5_princ_realm(context, me)->data, + 0)) { + krb5_cc_destroy(context, cc); + krb5_free_principal(context, me); + goto leave; + } + + saved_creds = (krb5_creds *) malloc(sizeof(krb5_creds)); + mcred.server = server; + mcred.client = me; + + if (krb5_cc_retrieve_cred(context, cc, KRB5_TC_MATCH_SRV_NAMEONLY, + &mcred, saved_creds)) { + free(saved_creds); + saved_creds = NULL; + krb5_cc_destroy(context, cc); + krb5_free_principal(context, me); + krb5_free_principal(context, server); + goto leave; + } + + krb5_cc_destroy(context, cc); + krb5_free_principal(context, me); + krb5_free_principal(context, server); + +leave: + krb5_free_context(context); + + return; +} + +/* + * Handle the magic we need for AFS. Get a PAG and try to run aklog. + * + * Most of this was taken from login. + */ + +#ifdef SETPAG + +typedef krb5_sigtype sigtype; + +#ifndef POSIX_SETJMP +#undef sigjmp_buf +#undef sigsetjmp +#undef siglongjmp +#define sigjmp_buf jmp_buf +#define sigsetjmp(j,s) setjmp(j) +#define siglongjmp longjmp +#endif + +#ifdef POSIX_SIGNALS +typedef struct sigaction handler; +#define handler_init(H,F) (sigemptyset(&(H).sa_mask), \ + (H).sa_flags=0, \ + (H).sa_handler=(F)) +#define handler_swap(S,NEW,OLD) sigaction(S, &NEW, &OLD) +#define handler_set(S,OLD) sigaction(S, &OLD, NULL) +#else +typedef sigtype (*handler)(); +#define handler_init(H,F) ((H) = (F)) +#define handler_swap(S,NEW,OLD) ((OLD) = signal ((S), (NEW))) +#define handler_set(S,OLD) (signal ((S), (OLD))) +#endif + +static sigjmp_buf setpag_buf; + +static sigtype sigsys () +{ + siglongjmp(setpag_buf, 1); +} +#endif + +static int try_setpag () +{ +#ifdef SETPAG + handler sa, osa; + volatile int retval = 0; + + (void) &retval; + handler_init(sa, sigsys); + handler_swap(SIGSYS, sa, osa); + if (sigsetjmp(setpag_buf, 1) == 0) { + setpag(); + retval = 1; + } + handler_set(SIGSYS, osa); + return retval; +#endif +} + +afs_login(uid) + int uid; +{ + krb5_context context; + krb5_ccache cc; + int try_aklog = 0; + char *aklog_path; + struct stat st; + + if (saved_creds == NULL) + return; + + krb5_init_context(&context); + + krb5_appdefault_boolean(context, "ftpd", + krb5_princ_realm(context, saved_creds->client), + "krb5_run_aklog", try_aklog, &try_aklog); + + if (try_aklog) { + + if (krb5_cc_default(context, &cc)) + goto leave; + + if (krb5_cc_initialize(context, cc, saved_creds->client)) + goto leave; + + if (krb5_cc_store_cred(context, cc, saved_creds)) + goto leave; + + krb5_appdefault_string(context, "ftpd", + krb5_princ_realm(context, saved_creds->client), + "krb5_aklog_path", KPROGDIR "/aklog", + &aklog_path); + + if (stat(aklog_path, &st) == 0) { + int pid, testpid; + + try_setpag(); + + /* + * Aklog _really_ wants to run as "real user", + * and in some cases, aklog breaks when using it + * with an NFS translator. Make sure that we run + * aklog as the real user. + */ + + if ((pid = fork()) == 0) { + seteuid(0); + setuid((uid_t) uid); + system(aklog_path); + exit(0); + } else { + while ((testpid = wait(NULL)) != pid && + testpid != -1); + } + } + + free(aklog_path); + } + +leave: + + if (saved_creds) + krb5_free_creds(context, saved_creds); + + krb5_free_context(context); + +} + +afs_logout() +{ + krb5_context context; + krb5_ccache cc; + + krb5_init_context(&context); + + krb5_cc_default(context, &cc); + + krb5_cc_destroy(context, cc); + + krb5_free_context(context); +} + +static char *k5services[] = { "ftp", "host", NULL }; + +#define KRB5_DEFAULT_LIFETIME "5h 0m 0s" + +/* + * Check our Kerberos 5 password + */ + +k5pass(name, passwd) + char *name, *passwd; +{ + krb5_context context; + krb5_principal me = NULL, server = NULL, ver_princ = NULL; + krb5_keyblock *kb = NULL; + krb5_creds my_creds; + krb5_ccache cc; + krb5_timestamp now; + krb5_data packet; + krb5_auth_context auth_context = NULL; + char *lifetimestring, **service; + krb5_deltat lifetime; + int valid = 0; + + /* + * Setup Kerberos for getting the initial TGT + */ + + krb5_init_context(&context); + + if (krb5_cc_default(context, &cc)) + goto leave; + + memset((char *) &my_creds, 0, sizeof(my_creds)); + + if (krb5_parse_name(context, name, &me)) { + krb5_cc_destroy(context, cc); + goto leave; + } + + my_creds.client = me; + + if (krb5_cc_initialize(context, cc, me)) { + krb5_cc_destroy(context, cc); + goto leave; + } + + if (krb5_build_principal_ext(context, &server, + krb5_princ_realm(context, me)->length, + krb5_princ_realm(context, me)->data, + KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, + krb5_princ_realm(context, me)->length, + krb5_princ_realm(context, me)->data, + 0)) { + krb5_cc_destroy(context, cc); + goto leave; + } + + my_creds.server = server; + + if (krb5_timeofday(context, &now)) { + krb5_cc_destroy(context, cc); + goto leave; + } + + my_creds.times.starttime = 0; + + krb5_appdefault_string(context, "ftpd", krb5_princ_realm(context, me), + "default_lifetime", KRB5_DEFAULT_LIFETIME, + &lifetimestring); + + if (krb5_string_to_deltat(lifetimestring, &lifetime)) { + krb5_cc_destroy(context, cc); + free(lifetimestring); + goto leave; + } + + free(lifetimestring); + + my_creds.times.endtime = now + lifetime; + my_creds.times.renew_till = 0; + + if (krb5_get_in_tkt_with_password(context, 0, 0, NULL, 0, + passwd, cc, &my_creds, 0)) { + krb5_cc_destroy(context, cc); + goto leave; + } + + /* + * Ok, if we've reached this part successfully, then that means that + * we've gotten a valid ticket from the KDC. Now we try to get a + * service ticket from the KDC using a known key. In this case, + * we try "ftp" and "host". If neither of these principals are in + * the keytab, then let this user in anyway. + */ + + for (service = k5services; *service; service++) { + if (krb5_sname_to_principal(context, NULL, *service, + KRB5_NT_SRV_HST, &ver_princ)) { + krb5_cc_destroy(context, cc); + goto leave; + } + + if (krb5_kt_read_service_key(context, NULL, ver_princ, 0, + ENCTYPE_DES_CBC_CRC, &kb) == 0) + break; + else { + krb5_free_principal(context, ver_princ); + ver_princ = NULL; + } + } + + + if (ver_princ == NULL) { + valid = 1; + goto leave; + } + + /* + * The reason we're doing all of this is probably not very + * obvious. Here's the theory: + * + * Just getting a valid ticket from the KDC for that user isn't + * 100% secure, because if someone else is faking replies from the + * KDC, then they could forge a reponse that appeared to be valid, + * but actually wasn't. So what we do here is use a service + * ticket for the principal {ftp|host}/} and generate an + * AP_REQ message (using krb5_mk_req()), and then use krb5_rd_req() + * to validate that message. Since the request generated by mk_req() + * was encrypted with the host or ftp principal's secret key, and + * that service ticket was generated by using the user's TGT, + * then if we can decode the mk_req() message, that means that + * the user's credentials are legitmate. + */ + + packet.data = NULL; + + if (krb5_mk_req(context, &auth_context, 0, *service, + krb5_princ_component(context, ver_princ, 1)->data, + NULL, cc, &packet)) { + krb5_cc_destroy(context, cc); + goto leave; + } + + if (auth_context) { + krb5_auth_con_free(context, auth_context); + auth_context = NULL; + } + + if (krb5_rd_req(context, &auth_context, &packet, ver_princ, NULL, + NULL, NULL)) { + krb5_cc_destroy(context, cc); + goto leave; + } + + /* + * _Whew_! Everything is cool! + */ + + valid = 1; + +leave: + + if (packet.data) + krb5_xfree(packet.data); + if (auth_context) + krb5_auth_con_free(context, auth_context); + if (kb) + krb5_free_keyblock(context, kb); + if (my_creds.keyblock.contents) + krb5_free_cred_contents(context, &my_creds); + if (me) + krb5_free_principal(context, me); + if (server) + krb5_free_principal(context, server); + if (ver_princ) + krb5_free_principal(context, ver_princ); + + krb5_free_context(context); + + return(valid); +} + #endif /* GSSAPI */ --- appl/gssftp/ftpd/configure.in.orig Thu Nov 7 16:06:23 1996 +++ appl/gssftp/ftpd/configure.in Wed Nov 13 00:25:54 1996 @@ -7,6 +7,8 @@ CHECK_UTMP CHECK_SIGPROCMASK CHECK_WAIT_TYPE +CHECK_SIGNALS +CHECK_SETJMP AC_CHECK_SIZEOF(short) AC_CHECK_SIZEOF(int) AC_CHECK_SIZEOF(long) @@ -17,6 +19,17 @@ AC_REPLACE_FUNCS(getdtablesize) AC_HAVE_FUNCS(getcwd getusershell seteuid setreuid setresuid) AC_CHECK_LIB(crypt,crypt) dnl +dnl copied (mostly) from appl/bsd/configure.in +AFSLIBS= +AC_ARG_WITH([afs], +[ --without-afs don't have afs libraries to build against (default) + --with-afs=AFSDIR use preinstalled AFS library tree], +,with_afs=no)dnl +if test $with_afs != no; then + AC_DEFINE(SETPAG) + AFSLIBS="$AFSLIBS -L$with_afs/lib -L$with_afs/lib/afs -lauth -lsys -lrx -llwp" +fi +AC_SUBST(AFSLIBS) dnl dnl copied from appl/bsd/configure.in AC_MSG_CHECKING([setenv]) --- appl/gssftp/ftpd/Makefile.in.orig Thu Nov 7 16:09:43 1996 +++ appl/gssftp/ftpd/Makefile.in Wed Nov 13 15:50:45 1996 @@ -1,12 +1,13 @@ # # appl/gssftp/ftpd/Makefile.in # -CFLAGS = -DGSSAPI -DFTP_BUFSIZ=10240 $(CCOPTS) $(DEFS) $(LOCALINCLUDE) +CFLAGS = -DGSSAPI -DFTP_BUFSIZ=10240 $(CCOPTS) $(DEFS) $(LOCALINCLUDE) -DKPROGDIR=\"$(CLIENT_BINDIR)\" SETENVSRC=@SETENVSRC@ SETENVOBJ=@SETENVOBJ@ LIBOBJS=@LIBOBJS@ COMERRLIB=$(BUILDTOP)/util/et/libcom_err.a +AFSLIBS=@AFSLIBS@ SRCS = ftpd.c ftpcmd.y logwtmp.c popen.c vers.c \ $(srcdir)../ftp/glob.c \ @@ -27,7 +28,7 @@ all:: ftpd ftpd: $(OBJS) $(DEPKLIB) - $(LD) $(LDFLAGS) $(LDARGS) -o $@ $(OBJS) $(KLIB) $(LIBS) + $(LD) $(LDFLAGS) $(LDARGS) -o $@ $(OBJS) $(KLIB) $(LIBS) $(AFSLIBS) clean:: $(RM) ftpd ftpcmd.c >Audit-Trail: >Unformatted: