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
#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
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;
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
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) {
break;
case 'a':
- authenticate = 1;
+ authlevel = AUTHLEVEL_AUTHORIZE;
+ break;
+
+ case 'A':
+ authlevel = AUTHLEVEL_AUTHENTICATE;
+ break;
+
+ case 'C':
+ want_creds = 1;
break;
case 'c':
{
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.");
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);
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";
}
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;
/*
(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;
}
return;
}
- if (!auth_ok() && !guest) {
+ if (!guest) {
/* "ftp" is only account allowed no password */
if (pw == NULL)
salt = "xx";
#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) {
syslog(LOG_NOTICE,
"repeated login failures from %s",
remotehost);
- exit(0);
+ dologout(0);
}
reply(530, "Login incorrect.");
return;
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);
(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);
}
&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
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.
*p++ = ' ';
}
#endif /* SETPROCTITLE */
+
#ifdef GSSAPI
reply_gss_error(code, maj_stat, min_stat, s)
int code;
}
-#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 */