[2535] in Kerberos-V5-bugs

home help back first fref pref prev next nref lref last post

krb5-appl/255: Changes for ftp/ftpd to support ticket forwarding & AFS

daemon@ATHENA.MIT.EDU (Ken Hornstein)
Tue Nov 26 18:48:18 1996

Resent-From: gnats@rt-11.MIT.EDU (GNATS Management)
Resent-To: krb5-unassigned@RT-11.MIT.EDU
Resent-Reply-To: krb5-bugs@MIT.EDU, kenh@cmf.nrl.navy.mil
Date: Tue, 26 Nov 1996 18:47:06 -0500
From: Ken Hornstein <kenh@cmf.nrl.navy.mil>
Reply-To: kenh@cmf.nrl.navy.mil
To: krb5-bugs@MIT.EDU


>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 <krb5.h>
+#include <com_err.h>
+#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}/<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:

home help back first fref pref prev next nref lref last post