gss.patch   [plain text]


diff -Naur tnftpd-20100324/configure tnftpd/configure
--- tnftpd-20100324/configure	2010-06-18 15:33:24.000000000 -0700
+++ tnftpd/configure	2010-06-18 15:47:13.000000000 -0700
@@ -881,6 +881,7 @@
 with_pam
 with_sia
 with_skey
+with_gssapi
 enable_dependency_tracking
 enable_shared
 enable_static
@@ -1552,6 +1553,8 @@
                           Architecture (SIA) authentication [default=auto]
   --with-skey             enable support for S/Key authentication (not
                           compatible with --with-pam) [default=no]
+  --with-gssapi           enable support for GSSAPI Kerberos authentication
+                          [default=no]
   --with-gnu-ld           assume the C compiler uses GNU ld [default=no]
   --with-pic              try to use only PIC/non-PIC objects [default=use
                           both]
@@ -3487,6 +3490,14 @@
 fi
 
 
+# Check whether --with-gssapi was given.
+if test "${with_gssapi+set}" = set; then :
+  withval=$with_gssapi;
+else
+  with_gssapi=no
+fi
+
+
 #
 # Autoheader templates symbols.
 #
@@ -22030,6 +22042,80 @@
 
 fi
 
+# Check for GSSAPI
+#
+if test "$with_gssapi" != no; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: --with-gssapi=$with_gssapi; checking for required GSSAPI features" >&5
+$as_echo "$as_me: --with-gssapi=$with_gssapi; checking for required GSSAPI features" >&6;}
+       { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gss_krb5_copy_ccache in -lgssapi_krb5" >&5
+$as_echo_n "checking for gss_krb5_copy_ccache in -lgssapi_krb5... " >&6; }
+if test "${ac_cv_lib_gssapi_krb5_gss_krb5_copy_ccache+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lgssapi_krb5  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char gss_krb5_copy_ccache ();
+int
+main ()
+{
+return gss_krb5_copy_ccache ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_gssapi_krb5_gss_krb5_copy_ccache=yes
+else
+  ac_cv_lib_gssapi_krb5_gss_krb5_copy_ccache=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gssapi_krb5_gss_krb5_copy_ccache" >&5
+$as_echo "$ac_cv_lib_gssapi_krb5_gss_krb5_copy_ccache" >&6; }
+if test "x$ac_cv_lib_gssapi_krb5_gss_krb5_copy_ccache" = x""yes; then :
+  LIBS="-lgssapi_krb5 $LIBS"
+                     $as_echo "#define USE_GSSAPI 1" >>confdefs.h
+
+                     for ac_header in gssapi.h
+do :
+  ac_fn_c_check_header_mongrel "$LINENO" "gssapi.h" "ac_cv_header_gssapi_h" "$ac_includes_default"
+if test "x$ac_cv_header_gssapi_h" = x""yes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_GSSAPI_H 1
+_ACEOF
+
+fi
+
+done
+
+                     { $as_echo "$as_me:${as_lineno-$LINENO}: enabling GSSAPI/Kerberos authentication" >&5
+$as_echo "$as_me: enabling GSSAPI/Kerberos authentication" >&6;}
+                     with_gssapi=yes
+else
+  if test "$with_gssapi" != auto; then :
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error "--with-gssapi was given, but gss_krb5_copy_ccache() wasn't found
+See \`config.log' for more details." "$LINENO" 5; }
+fi
+                     { $as_echo "$as_me:${as_lineno-$LINENO}: disabling --with-gssapi" >&5
+$as_echo "$as_me: disabling --with-gssapi" >&6;}
+                     with_gssapi=no
+fi
+
+fi
+
 # Tests for items required for inbuilt ls.
 #
 if test "$opt_builtinls" = yes; then :
@@ -23548,5 +23634,7 @@
 $as_echo "$as_me: SIA authentication:    $with_sia" >&6;}
 { $as_echo "$as_me:${as_lineno-$LINENO}: S/Key authentication:  $with_skey" >&5
 $as_echo "$as_me: S/Key authentication:  $with_skey" >&6;}
+{ $as_echo "$as_me:${as_lineno-$LINENO}: GSSAPI authentication: $with_gssapi" >&5
+$as_echo "$as_me: GSSAPI authentication: $with_gssapi" >&6;}
 { $as_echo "$as_me:${as_lineno-$LINENO}:  =============================" >&5
 $as_echo "$as_me:  =============================" >&6;}
diff -Naur tnftpd-20100324/configure.ac tnftpd/configure.ac
--- tnftpd-20100324/configure.ac	2010-06-18 15:33:24.000000000 -0700
+++ tnftpd/configure.ac	2010-06-18 15:47:06.000000000 -0700
@@ -57,6 +57,12 @@
                              [default=no]])],
             [],
             [with_skey=no])
+AC_ARG_WITH([gssapi],
+            [AS_HELP_STRING([--with-gssapi],
+                            [enable support for GSSAPI Kerberos authentication
+                             [default=no]])],
+            [],
+            [with_gssapi=no])
 
 #
 # Autoheader templates symbols.
@@ -81,6 +87,8 @@
             [Define if using SIA authentication.])
 AH_TEMPLATE([USE_SKEY],
             [Define if using S/Key authentication.])
+AH_TEMPLATE([USE_GSSAPI],
+            [Define if using GSSAPI authentication.])
 
 #
 # Checks for programs.
@@ -414,6 +433,23 @@
                      AC_MSG_NOTICE([disabling --with-skey])
                      with_skey=no])])
 
+# Check for GSSAPI
+#
+AS_IF([test "$with_gssapi" != no],
+      [AC_MSG_NOTICE([--with-gssapi=$with_gssapi; checking for required GSSAPI features])
+       AC_CHECK_LIB([gssapi_krb5],
+                    [gss_krb5_copy_ccache],
+                    [LIBS="-lgssapi_krb5 $LIBS"
+                     AC_DEFINE([USE_GSSAPI], [1])
+                     AC_CHECK_HEADERS([gssapi.h])
+                     AC_MSG_NOTICE([enabling GSSAPI/Kerberos authentication])
+                     with_gssapi=yes],
+                    [AS_IF([test "$with_gssapi" != auto],
+                           [AC_MSG_FAILURE(
+                [--with-gssapi was given, but gss_krb5_copy_ccache() wasn't found])])
+                     AC_MSG_NOTICE([disabling --with-gssapi])
+                     with_gssapi=no])])
+
 # Tests for items required for inbuilt ls.
 #
 AS_IF([test "$opt_builtinls" = yes],
@@ -451,4 +487,5 @@
 AC_MSG_NOTICE([PAM authentication:    $with_pam])
 AC_MSG_NOTICE([SIA authentication:    $with_sia])
 AC_MSG_NOTICE([S/Key authentication:  $with_skey])
+AC_MSG_NOTICE([GSSAPI authentication: $with_gssapi])
 AC_MSG_NOTICE([ =============================])
diff -Naur tnftpd-20100324/src/cmds.c tnftpd/src/cmds.c
--- tnftpd-20100324/src/cmds.c	2009-11-06 19:26:48.000000000 -0800
+++ tnftpd/src/cmds.c	2010-06-18 15:35:42.000000000 -0700
@@ -134,7 +134,6 @@
 } factelem;
 
 static void	ack(const char *);
-static void	base64_encode(const char *, size_t, char *, int);
 static void	fact_type(const char *, FILE *, factelem *);
 static void	fact_size(const char *, FILE *, factelem *);
 static void	fact_modify(const char *, FILE *, factelem *);
@@ -558,7 +557,7 @@
  * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
  * with `='.
  */
-static void
+void
 base64_encode(const char *clear, size_t len, char *encoded, int nulterm)
 {
 	static const char base64[] =
@@ -589,6 +588,60 @@
 	*e = '\0';
 }
 
+/*
+ * decode base64-encoded into clear, to a max of len clear bytes
+ * return (positive) length if decode okay,
+ * or negative code for use with base64_decode_error() below
+ */
+
+int
+base64_decode(const char *encoded, char *clear, size_t len)
+{
+	static const char base64[] =
+	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+	int i, j = 0;
+	int Q = 0;
+	char *p;
+	unsigned char c = 0;
+	len--;
+
+	for (i = 0; encoded[i] && encoded[i] != '='; i++) {
+		if ((p = strchr(base64, encoded[i])) == NULL) return(-1);
+		if (j == len) return(-7);
+		Q = p - base64;
+		switch (i & 3) {
+			case 0: c = Q<<2;				break;
+			case 1:	clear[j++] = c | Q>>4;	c = (Q&15)<<4;	break;
+			case 2:	clear[j++] = c | Q>>2;	c = (Q&3)<<6;	break;
+			case 3:	clear[j++] = c | Q;			break;
+		}
+	}
+	/* failure: bad padding, or decoded bits % 8 */
+	switch (i & 3) {
+		case 1: return(-3);
+		case 2: if (Q&15) return(-3);
+			if (strcmp((char *)&clear[i], "==")) return(-2);
+			break;
+		case 3: if (Q&3) return(-3);
+			if (strcmp((char *)&clear[i], "=")) return(-2);
+	}
+	clear[j] = '\0';
+	return(j);
+}
+
+char *
+base64_decode_error(int reason)
+{
+	switch(reason) {
+		case 0:  return("OK");
+		case -1:  return("Bad character in encoded message");
+		case -2:  return("Encoded message not properly padded");
+		case -3:  return("Encoded message has odd number of bits");
+		case -7:  return("Message too long for decode buffer");
+		default: return("Unknown error");
+	}
+}
+
 static void
 fact_modify(const char *fact, FILE *fd, factelem *fe)
 {
diff -Naur tnftpd-20100324/src/extern.h tnftpd/src/extern.h
--- tnftpd-20100324/src/extern.h	2009-11-06 19:26:48.000000000 -0800
+++ tnftpd/src/extern.h	2010-06-18 15:35:42.000000000 -0700
@@ -123,6 +123,9 @@
 #define FTP_BUFLEN	512
 
 void	abor(void);
+int	base64_decode(const char *, char *, size_t);
+char	*base64_decode_error(int);
+void	base64_encode(const char *, size_t, char *, int);
 void	closedataconn(FILE *);
 char   *conffilename(const char *);
 void	count_users(void);
@@ -347,6 +350,33 @@
 GLOBAL	const char     *version;
 GLOBAL	int		is_oob;
 
+#if (USE_GSSAPI && HAVE_GSSAPI_H)
+#include <gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+
+GLOBAL	gss_ctx_id_t	gcontext;
+GLOBAL	gss_buffer_desc	client_name;
+GLOBAL	krb5_context	kcontext;
+GLOBAL	krb5_ccache	kccache;
+#endif
+
+GLOBAL	char		*auth_type;
+GLOBAL	char		*auth_type_tmp;		/* post-AUTH && pre-ADAT */
+GLOBAL	int		auth_control_prot;	/* protection levels */
+GLOBAL	int		auth_data_prot;
+GLOBAL	int		authorized;		/* via supported AUTH */
+GLOBAL	int		creds_valid;
+GLOBAL	int		auth_want_creds;
+GLOBAL	int		auth_have_creds;
+
+/* these should be defined in <arpa/ftp.h> */
+#ifndef PROT_C
+#define PROT_C		1	/* clear */
+#define PROT_S		2	/* safe */
+#define PROT_P		3	/* private */
+#define PROT_E		4	/* confidential */
+#endif
+GLOBAL	char		*protnames[];
 						/* total file data bytes */
 GLOBAL	off_t		total_data_in,  total_data_out,  total_data;
 						/* total number of data files */
diff -Naur tnftpd-20100324/src/ftpcmd.y tnftpd/src/ftpcmd.y
--- tnftpd-20100324/src/ftpcmd.y	2009-11-06 19:26:48.000000000 -0800
+++ tnftpd/src/ftpcmd.y	2010-06-18 15:35:42.000000000 -0700
@@ -119,6 +119,15 @@
 char	*cmdp;
 char	*fromname;
 
+extern	int	auth_required;
+extern	int	auth_ccc_allowed;
+extern	int	auth_ccc_enabled;
+extern	int	auth_control_prot;
+unsigned int	auth_max_buffer;
+unsigned char	*auth_max_buffer_ptr;
+unsigned int	auth_max_buffer_tmp;
+static	int	prot_type;
+
 extern int	epsvall;
 struct tab	sitetab[];
 
@@ -748,33 +757,77 @@
 						/* RFC 2228 */
 	| AUTH SP mechanism_name CRLF
 		{
-			reply(502, "RFC 2228 authentication not implemented.");
+			auth((char *) $3);
 			free($3);
 		}
 
 	| ADAT SP base64data CRLF
 		{
-			reply(503,
-			    "Please set authentication state with AUTH.");
+			adat((unsigned char *) $3);
 			free($3);
 		}
 
 	| PROT SP prot_code CRLF
 		{
-			reply(503,
-			    "Please set protection buffer size with PBSZ.");
-			free($3);
+			if (auth_max_buffer)
+			{
+				switch (prot_type) {
+				case PROT_S:
+#ifdef DO_NOT_ENCRYPT
+				case PROT_P:
+#endif
+					if (auth_type)
+				case PROT_C:
+						reply(200, "Data channel protected at level: %s",
+							(auth_data_prot = prot_type) == PROT_S ?
+								"safe" : auth_data_prot == PROT_C ?
+								"clear" : "private");
+					else
+				default:
+					reply(536, "protection level %s not supported",
+						protnames[prot_type]);
+				}
+
+			} else
+				reply(503, "Must set PBSZ first.");
 		}
 
-	| PBSZ SP decimal_integer CRLF
+	| PBSZ SP STRING CRLF
 		{
-			reply(503,
-			    "Please set authentication state with AUTH.");
+			if (!auth_type)
+				reply(503, "Must perform authentication first");
+			else if (strlen($3) > 10 ||
+					(strlen($3) == 10 && strcmp($3,"4294967296") >= 0))
+				reply(501, "Bad value for PBSZ: %s", $3);
+			else {
+				if (auth_max_buffer_ptr) (void) free(auth_max_buffer_ptr);
+				auth_max_buffer_tmp = (unsigned int) atol($3);
+				/* try requested, then smaller buffers until too bad */
+				while ((auth_max_buffer_ptr = (unsigned char *)malloc(auth_max_buffer_tmp)) == NULL)
+					if (auth_max_buffer_tmp)
+						reply(-200, "Trying %u", auth_max_buffer_tmp >>= 2);
+					else {
+						perror_reply(421,
+							"Local resource failure: malloc");
+						dologout(1);
+					}
+				reply(200, "PBSZ=%u", auth_max_buffer = auth_max_buffer_tmp);
+                        }
+			free ($3);
 		}
 
 	| CCC CRLF
 		{
-			reply(533, "No protection enabled.");
+			if (!auth_ccc_allowed) {
+				reply(534, "CCC not supported");
+			} else {
+				if(auth_control_prot == PROT_C && !auth_ccc_enabled) {
+					reply(533, "CCC command must be integrity protected");
+				} else {
+					reply(200, "CCC command successful.");
+					auth_ccc_enabled = 1;
+				}
+			}
 		}
 
 	| MIC SP base64data CRLF
@@ -1017,6 +1070,25 @@
 		}
 	;
 
+prot_code
+	: C
+		{
+			prot_type = PROT_C;
+		}
+	| S
+		{
+			prot_type = PROT_S;
+		}
+	| P
+		{
+			prot_type = PROT_P;
+		}
+	| E
+		{
+			prot_type = PROT_E;
+		}
+	;
+
 type_code
 	: A
 		{
@@ -1264,7 +1336,7 @@
 	{ "AUTH", AUTH, STR1,	1,	"<sp> mechanism-name", 0, },
 	{ "ADAT", ADAT, STR1,	1,	"<sp> base-64-data", 0, },
 	{ "PROT", PROT, STR1,	1,	"<sp> prot-code", 0, },
-	{ "PBSZ", PBSZ, ARGS,	1,	"<sp> decimal-integer", 0, },
+	{ "PBSZ", PBSZ, STR1,	1,	"<sp> decimal-integer", 0, },
 	{ "CCC",  CCC,  NOARGS,	1,	"(Disable data protection)", 0, },
 	{ "MIC",  MIC,  STR1,	4,	"<sp> base64data", 0, },
 	{ "CONF", CONF, STR1,	4,	"<sp> base64data", 0, },
@@ -1361,6 +1433,16 @@
 	return (0);
 }
 
+void
+upper(register char *s)
+{
+	while (*s != '\0') {
+		if (islower((int)(*s)))
+			*s = toupper((int) *s);
+		s++;
+	}
+}
+
 #include <arpa/telnet.h>
 
 /*
@@ -1444,6 +1526,130 @@
 	if (c == EOF && cs == s)
 		return (-1);
 	*cs++ = '\0';
+
+	if (auth_type) {
+		unsigned char out[sizeof(cbuf)];
+		char *cp;
+		int len;
+		int mic;
+
+
+		/* do we have a protected command? */
+		if (!((mic = strncmp(s, "ENC", 3)) && strncmp(s, "MIC", 3)
+			&& strncmp(s, "AUTH", 4)
+#ifdef DO_CONFIDENTIAL
+			&& strncmp(s, "CONF", 4)
+#endif
+			) && (cs = strpbrk(s, " \r\n"))) {
+				*cs++ = '\0'; /* yes, split into s and cs. */
+		} else { /* no, check if that's okay */
+			if (auth_ccc_enabled) {
+				auth_control_prot = PROT_C;
+				upper(s);
+				return (0);
+			} else {
+				reply(533, "Commands must be protected.");
+				syslog(LOG_ERR, "Unprotected command received");
+				*s = '\0';
+				return (0);
+			}
+		}
+		upper(s);
+		if (ftpd_debug)
+			syslog(LOG_INFO, "command %s received (mic=%d)", s, mic);
+#ifndef DO_CONFIDENTIAL
+		if (!strcmp(s, "CONF")) {
+			reply(537, "CONF protected commands not supported.");
+			*s = '\0';
+			return(0);
+		}
+#endif
+#ifdef DO_PARANOID
+		/* truly paranoid sites will also require command encryption */
+		if (mic) {
+			reply(533, "All commands must be ENC protected.  Retry command under ENC.");
+			*s = '\0';
+			return(0);
+		}
+#endif /* DO_PARANOID */
+#ifdef DO_NOT_ENCRYPT
+		if (!mic) {
+			reply(533, "ENC protection not supported.  Retry command under MIC.");
+			*s = '\0';
+			return(0);
+		}
+#endif /* DO_NOT_ENCRYPT */
+		if ((cp = strpbrk(cs, " \r\n")))
+			*cp = '\0';
+		len = base64_decode((unsigned char)cs, out, strlen(cs));
+		if (len < 0) {
+			reply(501, "could not decode argument to %s command: %s",
+				mic ? "MIC" : "ENC", base64_decode_error(len));
+			*s = '\0';
+			return(0);
+		}
+		if (ftpd_debug) syslog(LOG_DEBUG, "getline got %lu from %s <%s>\n", 
+					  (unsigned long) len, cs, mic ? "MIC" : "ENC");
+		auth_control_prot = mic ? PROT_S : PROT_P;
+#ifdef USE_GSSAPI
+		/* we know this is a MIC or ENC already, and out/len already has the bits */
+		if (strcmp(auth_type, "GSSAPI") == 0) {
+			gss_buffer_desc transmit_buf, message_buf;
+			OM_uint32 major, minor;
+			int conf_state;
+
+			transmit_buf.value = out;
+			transmit_buf.length = len;
+			/* decrypt the message */
+			conf_state = !mic;
+			major = gss_unseal(&minor, gcontext, &transmit_buf,
+				  &message_buf, &conf_state, NULL);
+			if (major == GSS_S_CONTINUE_NEEDED) {
+				if (ftpd_debug) syslog(LOG_DEBUG, "%s-unseal continued", 
+						  mic ? "MIC" : "ENC");
+				reply(535, "%s-unseal continued, oops",
+					  mic ? "MIC" : "ENC");
+				*s = '\0';
+				return(0);
+			}
+			if (major != GSS_S_COMPLETE) {
+				reply_gss_error(535, major, minor, 
+					mic ? "failed unsealing MIC message" :
+						"failed unsealing ENC message");
+				*s = '\0';
+				return(0);
+			}
+
+			memcpy(s, message_buf.value, message_buf.length);
+			memcpy(s + message_buf.length - (s[message_buf.length-1] ? 0 : 1), "\r\n", 3);
+			gss_release_buffer(&minor, &message_buf);
+		}
+#endif /* USE_GSSAPI */
+		/* other auth types ... */
+
+		/* some antique clients misunderstood the spec */
+		if (auth_required && mic && !strncmp(s, "PASS", 4)) {
+			reply(-530, "Password refused: there is a problem with your ftp client.");
+			reply(530, "Enable encryption before logging in, or update your ftp program.");
+			*s = '\0';
+			return(0);
+		}
+
+	}
+#ifdef USE_GSSAPI	/* or other auth types */
+	else {	/* !auth_type */
+		if ( (!(strncmp(s, "ENC", 3))) || (!(strncmp(s, "MIC", 3)))
+#ifdef DO_CONFIDENTIAL
+			|| (!(strncmp(s, "CONF", 4)))
+#endif
+		) {
+			reply(503, "Must perform authentication before sending protected commands");
+			*s = '\0';
+			return(0);
+		}
+	}
+#endif /*  */
+
 	if (ftpd_debug) {
 		if ((curclass.type != CLASS_GUEST &&
 		    strncasecmp(s, "PASS ", 5) == 0) ||
diff -Naur tnftpd-20100324/src/ftpd.c tnftpd/src/ftpd.c
--- tnftpd-20100324/src/ftpd.c	2010-06-18 15:33:24.000000000 -0700
+++ tnftpd/src/ftpd.c	2010-06-18 15:35:42.000000000 -0700
@@ -176,6 +176,14 @@
 #include <security/pam_appl.h>
 #endif
 
+#ifdef USE_GSSAPI
+#include <com_err.h>
+#include <krb5/krb5.h>
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#include <gssapi/gssapi_krb5.h>
+#endif
+
 #endif /* !defined(HAVE_TNFTPD_H) */
 
 #define	GLOBAL
@@ -235,6 +243,41 @@
 int	login_krb5_forwardable_tgt = 0;
 #endif
 
+/*
+ * RFC2228 theoretically supports other auth types,
+ * so these are not necessarily GSSAPI-specific
+ */
+int	auth_required = 0;	/* needs cli flag */
+int	auth_want_creds = 0;	/* needs cli flag */
+int	auth_have_creds = 0;
+int	auth_ccc_allowed;	/* needs cli flag */
+int	auth_ccc_enabled;	/* CCC command */
+char	*auth_type;		/* AUTH command */
+char	*auth_type_tmp;		/* post-AUTH && pre-ADAT */
+
+int	auth_control_prot;
+int	auth_data_prot;
+int	auth_user_ok;
+
+				/* should be <arpa/ftp.h> */
+char	*protnames[] =
+		{"0", "Clear", "Safe", "Private", "Confidential" };
+
+#define	AUTH_NONE		0
+#define	AUTH_MUST_AUTHENTICATE	1
+#define	AUTH_MUST_AUTHORIZE	2
+
+#ifdef USE_GSSAPI
+gss_buffer_desc	auth_gss_client_name;
+krb5_context	kcontext;
+krb5_ccache	kccache;
+
+static	void ftpd_gss_convert_creds(char *name, gss_cred_id_t);
+static	int ftpd_gss_userok(gss_buffer_t, char *name);
+void reply_gss_error(int code, OM_uint32 major, OM_uint32 minor, char *s);
+
+#endif
+
 int epsvall = 0;
 
 /*
@@ -340,6 +383,10 @@
 	gidcount = 0;
 	is_oob = 0;
 	version = FTPD_VERSION;
+	auth_required = AUTH_NONE;
+	auth_ccc_allowed = 0;
+	auth_ccc_enabled = 0;
+	auth_want_creds = 0;
 
 	/*
 	 * LOG_NDELAY sets up the logging connection immediately,
@@ -348,7 +395,7 @@
 	openlog("ftpd", LOG_PID | LOG_NDELAY, FTPD_LOGTYPE);
 
 	while ((ch = getopt(argc, argv,
-	    "46a:c:C:Dde:h:HlL:nP:qQrst:T:uUvV:wWX")) != -1) {
+	    "46a:c:C:Dde:h:HkKlL:nP:qQrst:T:uUvV:wWXzZ")) != -1) {
 		switch (ch) {
 		case '4':
 			af = AF_INET;
@@ -417,6 +464,14 @@
 			hostname[sizeof(hostname) - 1] = '\0';
 			break;
 
+		case 'k':
+			auth_ccc_allowed = 1;
+			break;
+
+		case 'K':
+			auth_want_creds = 1;
+			break;
+
 		case 'l':
 			logging++;	/* > 1 == extra logging */
 			break;
@@ -493,6 +548,14 @@
 			doxferlog |= 1;
 			break;
 
+		case 'z':
+			auth_required = AUTH_MUST_AUTHORIZE;
+			break;
+
+		case 'Z':
+			auth_required = AUTH_MUST_AUTHENTICATE;
+			break;
+
 		default:
 			if (optopt == 'a' || optopt == 'C')
 				exit(1);
@@ -766,6 +829,9 @@
 	mode = MODE_S;
 	tmpline[0] = '\0';
 	hasyyerrored = 0;
+#ifdef USE_GSSAPI
+	auth_control_prot = auth_data_prot = PROT_C;
+#endif
 
 #ifdef KERBEROS5
 	kerror = krb5_init_context(&kcontext);
@@ -998,6 +1064,9 @@
 #if defined(KERBEROS5)
 	k5destroy();
 #endif
+#ifdef USE_GSSAPI /* or other auth */
+	auth_user_ok = 0;
+#endif
 
 	curclass.type = CLASS_REAL;
 	askpasswd = 0;
@@ -1024,6 +1093,9 @@
 			goto cleanup_user;
 		}
 		name = "ftp";
+	} else if (auth_required && !auth_type) {
+		reply(530, "must authenticate before USER");
+		goto cleanup_user;
 	} else {
 		pw = sgetpwnam(name);
 
@@ -1102,6 +1174,47 @@
 		goto cleanup_user;
 	}
 
+	if (auth_type) {
+		int result;
+		char buf[MAXPATHLEN];
+#ifdef USE_GSSAPI
+		if (auth_type && strcmp(auth_type, "GSSAPI") == 0) {
+			size_t len;
+
+			auth_user_ok = ftpd_gss_userok(&auth_gss_client_name, (char*)name) == 0;
+			len = sizeof("GSSAPI user  is not authorized as "
+					"; Password required.")
+					+ strlen(auth_gss_client_name.value)
+					+ strlen(name);
+			if (len >= sizeof(buf)) {
+				syslog(LOG_ERR, "user: username too long");
+				name = "[username too long]";
+			}
+			snprintf(buf, sizeof(buf),
+					"GSSAPI user %s is%s authorized as %s",
+					(char *) auth_gss_client_name.value,
+					auth_user_ok ? "" : " not",
+					name);
+		}
+#endif
+		/* "ok" should apply to other auth types as well */
+		if (!auth_user_ok && auth_required == AUTH_MUST_AUTHORIZE) {
+			strncat(buf, "; Access denied.",
+				sizeof(buf) - strlen(buf) - 1);
+			result = 530;
+			pw = NULL;
+		} else if (!auth_user_ok || (auth_want_creds && !auth_have_creds)) {
+			strncat(buf, "; Password required.",
+			sizeof(buf) - strlen(buf) - 1);
+			askpasswd = 1;
+			result = 331;
+		} /* else result = 232; */ /* login somehow? */
+		reply(result, "%s", buf);
+		syslog(auth_user_ok ? LOG_INFO : LOG_ERR, "%s", buf);
+
+		goto cleanup_user;
+	}
+
 			/* if haven't asked yet (i.e, not anon), ask now */
 	if (!askpasswd) {
 		askpasswd = 1;
@@ -1421,6 +1534,96 @@
 		pamh = NULL;
 	}
 #endif
+	if (auth_have_creds) {
+#ifdef USE_GSSAPI
+		krb5_cc_destroy(kcontext, kccache);
+#endif
+		auth_have_creds = 0;
+	}
+}
+
+void
+setdataprot(int new_prot)
+{
+	switch (new_prot) {
+		case PROT_S:
+#ifdef DO_NOT_ENCRYPT
+		case PROT_P:
+#endif
+			if (auth_type)
+		case PROT_C:
+				reply(200, "Data channel protected at level: %s",
+					(auth_data_prot = new_prot) == PROT_S ?
+						"safe" : auth_data_prot == PROT_C ?
+						"clear" : "private");
+			else
+		default:
+				reply(536, "protection level %s not supported",
+					protnames[new_prot]);
+	}
+}
+
+
+static int
+kpass(name, passwd)
+char *name, *passwd;
+{
+#ifdef USE_GSSAPI
+	krb5_principal server, me;
+	krb5_creds my_creds;
+	krb5_timestamp now;
+#endif /* USE_GSSAPI */
+	char ccname[MAXPATHLEN];
+
+#ifdef USE_GSSAPI
+	memset(&my_creds, 0, sizeof(my_creds));
+	if (krb5_parse_name(kcontext, name, &me))
+		return 0;
+	my_creds.client = me;
+
+	snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_ftpd%ld",
+			(long) getpid());
+	if (krb5_cc_resolve(kcontext, ccname, &kccache))
+		return(0);
+	if (krb5_cc_initialize(kcontext, kccache, 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 wipe_ccache;
+
+	my_creds.server = server;
+	if (krb5_timeofday(kcontext, &now))
+		goto wipe_ccache;
+	my_creds.times.starttime = 0; /* real time */
+	my_creds.times.endtime = now + 60 * 60 * 10;
+	my_creds.times.renew_till = 0;
+
+	if (krb5_get_init_creds_password(kcontext, &my_creds, me,
+			passwd, NULL, NULL, 0, NULL, NULL))
+		goto wipe_ccache;
+
+	if (krb5_cc_store_cred(kcontext, kccache, &my_creds))
+		goto wipe_ccache;
+
+	if (!auth_want_creds) {
+		krb5_cc_destroy(kcontext, kccache);
+		return(1);
+	}
+
+	auth_have_creds = 1;
+	return(1);
+
+#endif /* USE_GSSAPI */
+wipe_ccache:
+#ifdef USE_GSSAPI
+	krb5_cc_destroy(kcontext, kccache);
+#endif /* USE_GSSAPI */
+	return(0);
 }
 
 void
@@ -1510,6 +1713,12 @@
 			}
 		}
 #endif
+#ifdef USE_GSSAPI
+		/* creds desired and kpass fails */
+		rval = auth_want_creds && kpass(pw->pw_name, passwd);
+		goto skip;
+#endif
+
 		if (!sflag)
 			rval = checkpassword(pw, passwd);
 		else
@@ -1567,6 +1779,12 @@
 		goto bad;
 	}
 #endif /* HAVE_MEMBERSHIP_H */
+#ifdef USE_GSSAPI
+	if (auth_have_creds) {
+		char *ccname = krb5_cc_get_name(kcontext, kccache);
+		chown(ccname, pw->pw_uid, pw->pw_gid);
+	}
+#endif
 	login_attempts = 0;		/* this time successful */
 	if (setegid((gid_t)pw->pw_gid) < 0) {
 		reply(550, "Can't set gid.");
@@ -2727,6 +2945,7 @@
 	else
 		reply(0, "Connected to %s", remotehost);
 
+	if (auth_type) reply(0, "     Authentication type: %s", auth_type);
 	if (logged_in) {
 		if (curclass.type == CLASS_GUEST)
 			reply(0, "Logged in anonymously");
@@ -2735,8 +2954,11 @@
 			    curclass.type == CLASS_CHROOT ? " (chroot)" : "");
 	} else if (askpasswd)
 		reply(0, "Waiting for password");
+	else if (auth_type_tmp)
+		reply(0, "     Waiting for authentication data");
 	else
 		reply(0, "Waiting for user name");
+	reply(0, "     Protection level: %s", protnames[auth_data_prot]);
 	cprintf(stdout, "    TYPE: %s", typenames[type]);
 	if (type == TYPE_A || type == TYPE_E)
 		cprintf(stdout, ", FORM: %s", formnames[form]);
@@ -2990,12 +3212,14 @@
  *	n < -1		prefix the message with abs(n) + "-"	(initial line)
  *	n == 0		prefix the message with 4 spaces	(middle lines)
  *	n >  0		prefix the message with n + " "		(final line)
+ *	encrypted replies will use the continuation of the cleartext message
  */
 void
 reply(int n, const char *fmt, ...)
 {
 	char	msg[MAXPATHLEN * 2 + 100];
 	size_t	b;
+	int	secure = 0;
 	va_list	ap;
 
 	b = 0;
@@ -3008,7 +3232,52 @@
 	va_start(ap, fmt);
 	vsnprintf(msg + b, sizeof(msg) - b, fmt, ap);
 	va_end(ap);
-	cprintf(stdout, "%s\r\n", msg);
+
+	if (auth_type) {
+		/* base64 expansion, signature, slop */
+		char msg_secure[(MAXPATHLEN * 2 + 100) * 3 / 2];
+		size_t msg_length = 0;
+
+#ifdef USE_GSSAPI
+		/* reply (based on level) */
+		if (strcmp(auth_type, "GSSAPI") == 0) {
+			gss_buffer_desc input_buf, output_buf;
+			OM_uint32 major, minor;
+			int conf_state;
+		
+			input_buf.value = msg;
+			input_buf.length = strlen(msg);
+			major = gss_seal(&minor, gcontext,
+					    auth_control_prot == PROT_P, /* private */
+					    GSS_C_QOP_DEFAULT,
+					    &input_buf, &conf_state,
+					    &output_buf);
+			if (major != GSS_S_COMPLETE) {
+				/* gss_seal didn't complete */
+				/* xxx no good way to handle this without infinite loop */
+			} else if ((auth_control_prot == PROT_P) && !conf_state) {
+				/* message wasn't encrypted */
+				/* xxx no good way to handle this without infinite loop */
+			} else {
+				memcpy(msg_secure, output_buf.value, 
+				       msg_length = output_buf.length);
+				gss_release_buffer(&minor, &output_buf);
+			}
+		}
+#endif /* USE_GSSAPI */
+		/* other auth types ... */
+		if (msg_length >= sizeof(msg) / 4 * 3) {
+			syslog(LOG_ERR, "input to base64_encode too long");
+		} else {
+			/* rewrite the msg with encoded encrypted copy */
+			base64_encode(msg_secure, msg_length, msg, 0);
+			secure = 1;
+		}
+	}
+
+	if(secure) cprintf(stdout, "%s%c%s\r\n",
+		auth_control_prot == PROT_P ? "632" : "631", n > 0 ? ' ' : '-', msg);
+	else cprintf(stdout, "%s\r\n", msg);
 	(void)fflush(stdout);
 	if (ftpd_debug)
 		syslog(LOG_DEBUG, "<--- %s", msg);
@@ -3063,6 +3332,11 @@
 			unlink(krbtkfile_env);
 #endif
 	}
+#ifdef USE_GSSAPI
+	if (auth_have_creds)
+		krb5_cc_destroy(kcontext, kccache);
+	auth_ccc_enabled = 0;
+#endif
 	/* beware of flushing buffers after a SIGPIPE */
 	if (xferlogfd != -1)
 		close(xferlogfd);
@@ -4247,3 +4521,261 @@
 	return 0;
 }
 #endif /* USE_SIA */
+
+#ifdef USE_GSSAPI
+
+static void
+ftpd_gss_convert_creds(char *name, gss_cred_id_t creds)
+{
+	OM_uint32 major, minor;
+	krb5_principal me;
+	char ccname[MAXPATHLEN];
+
+	/* get ccache */
+	if (krb5_parse_name(kcontext, name, &me))
+		return;
+
+	snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_ftpd%ld",
+			(long) getpid());
+	if (krb5_cc_resolve(kcontext, ccname, &kccache))
+		return;
+	if (krb5_cc_initialize(kcontext, kccache, me))
+		return;
+
+	/* extract gss-wrapped krb creds into our cache */
+	major = gss_krb5_copy_ccache(&minor, creds, kccache);
+	if (major != GSS_S_COMPLETE)
+		goto cleanup;
+
+	auth_have_creds = 1;
+	return;
+
+	cleanup:
+	krb5_cc_destroy(kcontext, kccache);
+}
+
+/* verify gss-wrapped kerberos user */
+static int
+ftpd_gss_userok(gss_buffer_t gclient_name, char *name)
+{
+	int retval = -1;
+	krb5_principal p;
+
+	if (krb5_parse_name(kcontext, gclient_name->value, &p) != 0)
+		return -1;
+	if (krb5_kuserok(kcontext, p, name))
+		retval = 0;
+	else
+		retval = 1;
+	krb5_free_principal(kcontext, p);
+	return retval;
+}
+
+/* callback seems overkill, but easiest way to handle gssapi */
+static void
+reply_gss_error_text(void (*cb)(const char *, int, int),
+		OM_uint32 major, OM_uint32 minor, int misc)
+{
+	OM_uint32 gmajor, gminor;
+	gss_buffer_desc msg;
+	OM_uint32 msg_ctx;
+	msg_ctx = 0;
+	while (!msg_ctx) {
+		gmajor= gss_display_status(&gminor, major,
+				GSS_C_GSS_CODE,
+				GSS_C_NULL_OID,
+				&msg_ctx, &msg);
+		if ((gmajor == GSS_S_COMPLETE)||
+				(gmajor == GSS_S_CONTINUE_NEEDED)) {
+			(*cb)((char*)msg.value, misc, 1);
+			(void) gss_release_buffer(&gminor, &msg);
+		}
+		if (gmajor != GSS_S_CONTINUE_NEEDED)
+			break;
+	}
+	msg_ctx = 0;
+	while (!msg_ctx) {
+		gmajor = gss_display_status(&gminor, minor,
+				GSS_C_MECH_CODE,
+				GSS_C_NULL_OID,
+				&msg_ctx, &msg);
+		if ((gmajor == GSS_S_COMPLETE)||
+				(gmajor == GSS_S_CONTINUE_NEEDED)) {
+			(*cb)((char*)msg.value, misc, 0);
+			(void) gss_release_buffer(&gminor, &msg);
+		}
+		if (gmajor != GSS_S_CONTINUE_NEEDED)
+			break;
+	}
+}
+
+static void
+reply_gss_error_wrapper(const char *msg, int code, int major)
+{
+	if(code > 0) code *= -1;	/* all are continuation messages */
+	reply(code, "GSSAPI error %s: %s", major ? "major" : "minor", msg);
+}
+
+void
+reply_gss_error(int code, OM_uint32 major, OM_uint32 minor, char *s)
+{
+	reply_gss_error_text(reply_gss_error_wrapper, major, minor, code);
+	reply(code, "GSSAPI error: %s", s);
+}
+
+#endif
+
+/*
+ * AUTH command
+ */
+void
+auth(char *new_type)
+{
+	if (auth_type)
+		reply(534, "Authentication type is already %s", auth_type);
+	else
+#ifdef USE_GSSAPI
+	if (strcmp(new_type, "GSSAPI") == 0)
+		reply(334, "ADAT must follow for authentication type %s",
+			auth_type_tmp = new_type);
+#endif
+	reply(504, "Authentication via %s not supported", new_type);
+}
+
+/*
+ * ADAT command
+ *
+ */
+void
+adat(unsigned char *new_key)
+{
+	if (auth_type) {
+		reply(503, "Authentication has already been set");
+		return;
+	}
+	if (!auth_type_tmp) {
+		reply(503, "AUTH type required before ADAT");
+		return;
+	}
+
+#ifdef USE_GSSAPI
+	if (strcmp(auth_type_tmp, "GSSAPI") == 0) {
+		gss_cred_id_t deleg_creds;
+		gss_name_t client;
+		OM_uint32 ret_flags, major, minor;
+		gss_OID mech_type;
+		gss_buffer_desc g_token, g_out_token;
+		char buf[MAXPATHLEN];
+		unsigned char g_buf[MAXPATHLEN];
+		u_char g_out_buf[MAXPATHLEN];
+		int length, replied = 0;
+		size_t rad_len;
+		char localname[MAXHOSTNAMELEN];
+		struct hostent *hp;
+
+		length = base64_decode(new_key, g_out_buf, MAXPATHLEN);
+		if (length < 0) {
+			reply(501, "Couldn't decode ADAT (%s)",
+			      base64_decode_error(length));
+			return;
+		}
+		g_token.value = g_out_buf;
+		g_token.length = length;
+
+		if (gethostname(localname, MAXHOSTNAMELEN)) {
+			reply(501, "couldn't get local hostname (%d)\n", errno);
+			return;
+		}
+		if (!(hp = gethostbyname(localname))) {
+			reply(501, "couldn't canonicalize local hostname\n");
+			return;
+		}
+		strncpy(localname, hp->h_name, sizeof(localname) - 1);
+		localname[sizeof(localname) - 1] = '\0';
+
+		major = gss_accept_sec_context(&minor,
+					     &gcontext,
+					     GSS_C_NO_CREDENTIAL, /* no verifier */
+					     &g_token, /* input */
+					     GSS_C_NO_CHANNEL_BINDINGS,
+					     &client, /* source */
+					     &mech_type,
+					     &g_out_token,
+					     &ret_flags,
+					     NULL, 	/* ignore time_rec */
+					     &deleg_creds  /* forwarded credentials */
+					     );
+
+		if (major != GSS_S_COMPLETE && major != GSS_S_CONTINUE_NEEDED) {
+			reply_gss_error(535, major, minor, "accepting context");
+			if (ret_flags & GSS_C_DELEG_FLAG)
+				(void) gss_release_cred(&minor, &deleg_creds);
+			return;
+		}
+
+		if (g_out_token.length) {
+			if (g_out_token.length >= ((MAXPATHLEN - sizeof("ADAT="))
+					       / 4 * 3)) {
+				reply(535, "ADAT: reply too long");
+				/* syslog(LOG_ERR, "ADAT: reply too long");
+				if (ret_flags & GSS_C_DELEG_FLAG)
+					(void) gss_release_cred(&minor, &deleg_creds);
+				return;
+			}
+
+			base64_encode(g_out_token.value, g_out_token.length, g_buf, 1);
+
+			if (major == GSS_S_COMPLETE) {
+				/* 2xx finished */
+				reply(235, "ADAT=%s", g_buf);
+			} else {
+				/* 3xx require additional data */
+				reply(335, "ADAT=%s", g_buf);
+			}
+			replied = 1;
+			(void) gss_release_buffer(&minor, &g_out_token);
+		}
+		if (major == GSS_S_COMPLETE) {
+			/* authentication succeeded */
+			major = gss_display_name(&minor, client, &auth_gss_client_name,
+					       &mech_type);
+			if (major != GSS_S_COMPLETE) {
+				/* rejecting security data ... reply with 535 */
+				reply_gss_error(535, major, minor,
+						"extracting GSSAPI identity name");
+				if (ret_flags & GSS_C_DELEG_FLAG)
+					(void) gss_release_cred(&minor, &deleg_creds);
+				return;
+			}
+			auth_type = auth_type_tmp; /* auth complete */
+			auth_type_tmp = NULL;
+
+			if (ret_flags & GSS_C_DELEG_FLAG) {
+			  if (auth_want_creds)
+			    ftpd_gss_convert_creds(auth_gss_client_name.value, deleg_creds);
+			  (void) gss_release_cred(&minor, &deleg_creds);
+			}
+
+			/* more data not required, so must reply 2xx */
+			if (!replied)
+			  {
+			    /* this 235 not accurate but "correct" */
+			    if (ret_flags & GSS_C_DELEG_FLAG && !auth_have_creds)
+			      reply(235, "GSSAPI Authentication succeeded; could not accept forwarded credentials");
+			    else
+			      reply(235, "GSSAPI Authentication succeeded");
+			  }
+				
+			return;
+		} else {
+			if (!replied)
+			    reply(335, "more data needed");
+			if (ret_flags & GSS_C_DELEG_FLAG)
+			  (void) gss_release_cred(&minor, &deleg_creds);
+			return;
+		}
+	}
+#endif /* USE_GSSAPI */
+	return;
+}
+
diff -Naur tnftpd-20100324/src/tnftpd.manin tnftpd/src/tnftpd.manin
--- tnftpd-20100324/src/tnftpd.manin	2010-01-03 17:10:43.000000000 -0800
+++ tnftpd/src/tnftpd.manin	2010-06-18 15:35:42.000000000 -0700
@@ -66,7 +66,7 @@
 Internet File Transfer Protocol server
 .Sh SYNOPSIS
 .Nm
-.Op Fl 46DdHlnQqrsUuWwX
+.Op Fl 46DdHkKlnQqrsUuWwXzZ
 .Op Fl a Ar anondir
 .Op Fl C Ar user Ns Op @ Ns Ar host
 .Op Fl c Ar confdir
@@ -178,6 +178,10 @@
 Refer to
 .Xr inetd.conf 5
 for more information on starting services to listen on specific IP addresses.
+.It Fl k
+Allow use of the CCC command.
+.It Fl K
+Require authenticated credentials.
 .It Fl L Ar xferlogfile
 Log
 .Tn wu-ftpd
@@ -280,6 +284,10 @@
 file suitable for input into a third-party log analysis tool with a command
 similar to:
 .Dl "sed -ne 's/^.*xferlog: //p' /var/log/xferlog \*[Gt] wuxferlog"
+.It Fl z
+Require authorization.
+.It Fl Z
+Require authentication.
 .El
 .Pp
 The file
@@ -317,11 +325,15 @@
 .It Sy Request Ta Sy Description
 .It ABOR Ta "abort previous command"
 .It ACCT Ta "specify account (ignored)"
+.It ADAT Ta "supply authentication data"
 .It ALLO Ta "allocate storage (vacuously)"
 .It APPE Ta "append to a file"
+.It AUTH Ta "select authentication paradigm"
+.It CCC Ta "force cleartext commands"
 .It CDUP Ta "change to parent of current working directory"
 .It CWD Ta "change working directory"
 .It DELE Ta "delete a file"
+.It ENC Ta "send privacy protected command"
 .It EPSV Ta "prepare for server-to-server transfer"
 .It EPRT Ta "specify data connection port"
 .It FEAT Ta "list extra features that are not defined in" Cm "RFC 959"
@@ -329,6 +341,7 @@
 .It LIST Ta "give list files in a directory" Pq Dq Li "ls -lA"
 .It LPSV Ta "prepare for server-to-server transfer"
 .It LPRT Ta "specify data connection port"
+.It MIC Ta "send integrity protected command"
 .It MLSD Ta "list contents of directory in a machine-processable form"
 .It MLST Ta "show a pathname in a machine-processable form"
 .It MKD Ta "make a directory"
@@ -339,7 +352,9 @@
 .It OPTS Ta "define persistent options for a given command"
 .It PASS Ta "specify password"
 .It PASV Ta "prepare for server-to-server transfer"
+.It PBSZ Ta "set protection buffer size"
 .It PORT Ta "specify data connection port"
+.It PROT Ta "set data protection mode"
 .It PWD Ta "print the current working directory"
 .It QUIT Ta "terminate session"
 .It REST Ta "restart incomplete transfer"
diff -Naur tnftpd-20100324/tnftpd.h tnftpd/tnftpd.h
--- tnftpd-20100324/tnftpd.h	2010-06-18 15:33:24.000000000 -0700
+++ tnftpd/tnftpd.h	2010-06-21 18:17:41.000000000 -0700
@@ -1,9 +1,15 @@
 /* $NetBSD: tnftpd.h,v 1.33 2009/11/07 09:59:09 lukem Exp $	*/
 
-#define	FTPD_VERSION	PACKAGE_STRING
-
 #include "tnftpd_config.h"
 
+#if defined(USE_GSSAPI)
+# warning "GSSAPI enabled!"
+# define	FTPD_VERSION	PACKAGE_STRING "+GSSAPI"
+#else	/* !USE_GSSAPI */
+# warning "GSSAPI disabled."
+# define	FTPD_VERSION	PACKAGE_STRING
+#endif
+
 #include <stdio.h>
 #include <ctype.h>
 #include <errno.h>
diff -Naur tnftpd-20100324/tnftpd_config.h.in tnftpd/tnftpd_config.h.in
--- tnftpd-20100324/tnftpd_config.h.in	2010-06-18 15:33:24.000000000 -0700
+++ tnftpd/tnftpd_config.h.in	2010-06-21 18:12:40.000000000 -0700
@@ -148,6 +148,9 @@
 /* Define to 1 if you have the <grp.h> header file. */
 #undef HAVE_GRP_H
 
+/* Define to 1 if you have the <gssapi.h> header file. */
+#undef HAVE_GSSAPI_H
+
 /* Define to 1 if you have the `inet_net_pton' function. */
 #undef HAVE_INET_NET_PTON
 
@@ -488,6 +491,9 @@
 /* Define to 1 if your <sys/time.h> declares `struct tm'. */
 #undef TM_IN_SYS_TIME
 
+/* Define if using GSSAPI authentication. */
+#undef USE_GSSAPI
+
 /* Define if using IPv6 support. */
 #undef USE_INET6