/* Copyright (C) 2002 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Inetutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Inetutils; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include #endif #ifdef KRB5 #include #include #include #include #include #include #include #include #include #ifdef HAVE_STRING_H # include #else # include #endif #include "auth.h" #include "misc.h" #ifndef KRB5_ENV_CCNAME # define KRB5_ENV_CCNAME "KRB5CCNAME" #endif #ifdef ENCRYPTION #include "encrypt.h" #endif #ifdef FORWARD /* FIXME: This is set directly from telnet/main.c */ int forward_flags = 0; extern int net; /*FIXME*/ void kerberos5_forward (); #endif /* FORWARD */ static unsigned char str_data[2048] = { IAC, SB, TELOPT_AUTHENTICATION, 0, AUTHTYPE_KERBEROS_V5, }; #define KRB_AUTH 0 /* Authentication data follows */ #define KRB_REJECT 1 /* Rejected (reason might follow) */ #define KRB_ACCEPT 2 /* Accepted */ #define KRB_RESPONSE 3 /* Response for mutual auth. */ #define KRB_FORWARD 4 /* Forwarded credentials follow */ #define KRB_FORWARD_ACCEPT 5 /* Forwarded credentials accepted */ #define KRB_FORWARD_REJECT 6 /* Forwarded credentials rejected */ krb5_auth_context auth_context = 0; krb5_context telnet_context = 0; static krb5_data auth; /* session key for telnet */ static krb5_ticket *ticket = NULL; /* telnet matches the AP_REQ and AP_REP with this */ krb5_keyblock *session_key = 0; char *telnet_srvtab = NULL; char *telnet_krb5_realm = NULL; #define DEBUG(c) if (auth_debug_mode) printf c static int Data (TN_Authenticator *ap, int type, krb5_pointer d, int c) { unsigned char *p = str_data + 4; unsigned char *cd = (unsigned char *) d; if (c == -1) c = strlen (cd); if (auth_debug_mode) { printf ("%s:%d: [%d] (%d)", str_data[3] == TELQUAL_IS ? ">>>IS" : ">>>REPLY", str_data[3], type, c); printd (d, c); printf ("\r\n"); } *p++ = ap->type; *p++ = ap->way; *p++ = type; while (c-- > 0) { if ((*p++ = *cd++) == IAC) *p++ = IAC; } *p++ = IAC; *p++ = SE; if (str_data[3] == TELQUAL_IS) printsub ('>', &str_data[2], p - &str_data[2]); return (net_write (str_data, p - str_data)); } /* FIXME: Reverse return code! */ int kerberos5_init (TN_Authenticator *ap, int server) { str_data[3] = server ? TELQUAL_REPLY : TELQUAL_IS; if (telnet_context == 0 && krb5_init_context(&telnet_context)) return 0; return 1; } void kerberos5_cleanup () { krb5_ccache ccache; char *ccname; if (telnet_context == 0) return; ccname = getenv (KRB5_ENV_CCNAME); if (ccname) { if (!krb5_cc_resolve (telnet_context, ccname, &ccache)) krb5_cc_destroy (telnet_context, ccache); } krb5_free_context (telnet_context); telnet_context = 0; } #ifdef ENCRYPTION void encryption_init (krb5_creds *creds) { krb5_keyblock *newkey = 0; krb5_auth_con_getlocalsubkey (telnet_context, auth_context, &newkey); if (session_key) { krb5_free_keyblock (telnet_context, session_key); session_key = 0; } if (newkey) { switch (newkey->enctype) { case ENCTYPE_DES_CBC_CRC: case ENCTYPE_DES_CBC_MD5: krb5_copy_keyblock (telnet_context, newkey, &session_key); break; default: switch (creds->keyblock.enctype) { case ENCTYPE_DES_CBC_CRC: case ENCTYPE_DES_CBC_MD5: krb5_copy_keyblock (telnet_context, &creds->keyblock, &session_key); break; default: DEBUG(("can't determine which keyblock to use")); /*FIXME: abort?*/ } } krb5_free_keyblock(telnet_context, newkey); } } #else # define encryption_init(c) #endif int kerberos5_send (TN_Authenticator *ap) { krb5_error_code r; krb5_ccache ccache; krb5_creds creds; krb5_creds *new_creds = 0; int ap_opts; char type_check[2]; krb5_data check_data; if (!UserNameRequested) { DEBUG(("telnet: Kerberos V5: no user name supplied\r\n")); return 0; } if ((r = krb5_cc_default (telnet_context, &ccache))) { DEBUG(("telnet: Kerberos V5: could not get default ccache\r\n")); return 0; } memset (&creds, 0, sizeof (creds)); if ((r = krb5_sname_to_principal (telnet_context, RemoteHostName, "host", KRB5_NT_SRV_HST, &creds.server))) { DEBUG(("telnet: Kerberos V5: error while constructing service name: %s\r\n", error_message(r))); return 0; } if (telnet_krb5_realm) { krb5_data rdata; rdata.length = strlen (telnet_krb5_realm); rdata.data = malloc (rdata.length + 1); assert (rdata.data); strcpy (rdata.data, telnet_krb5_realm); krb5_princ_set_realm (telnet_context, creds.server, &rdata); } if ((r = krb5_cc_get_principal (telnet_context, ccache, &creds.client))) { DEBUG(("telnet: Kerberos V5: failure on principal (%s)\r\n", error_message(r))); krb5_free_cred_contents (telnet_context, &creds); return 0; } creds.keyblock.enctype = ENCTYPE_DES_CBC_CRC; if ((r = krb5_get_credentials (telnet_context, 0, ccache, &creds, &new_creds))) { DEBUG(("telnet: Kerberos V5: failure on credentials(%s)\r\n", error_message(r))); krb5_free_cred_contents (telnet_context, &creds); return 0; } if ((ap->way & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL) ap_opts = AP_OPTS_MUTUAL_REQUIRED; else ap_opts = 0; #ifdef ENCRYPTION ap_opts |= AP_OPTS_USE_SUBKEY; #endif if (auth_context) { krb5_auth_con_free (telnet_context, auth_context); auth_context = 0; } if ((r = krb5_auth_con_init (telnet_context, &auth_context))) { DEBUG(("Kerberos V5: failed to init auth_context (%s)\r\n", error_message(r))); return 0; } krb5_auth_con_setflags (telnet_context, auth_context, KRB5_AUTH_CONTEXT_RET_TIME); type_check[0] = ap->type; type_check[1] = ap->way; check_data.magic = KV5M_DATA; check_data.length = 2; check_data.data = (char *) &type_check; r = krb5_mk_req_extended (telnet_context, &auth_context, ap_opts, &check_data, new_creds, &auth); encryption_init (new_creds); krb5_free_cred_contents (telnet_context, &creds); krb5_free_creds (telnet_context, new_creds); if (r) { DEBUG(("telnet: Kerberos V5: mk_req failed (%s)\r\n", error_message(r))); return 0; } if (!auth_sendname (UserNameRequested, strlen (UserNameRequested))) { DEBUG(("telnet: Not enough room for user name\r\n")); return 0; } if (!Data (ap, KRB_AUTH, auth.data, auth.length)) { DEBUG(("telnet: Not enough room for authentication data\r\n")); return 0; } DEBUG(("telnet: Sent Kerberos V5 credentials to server\r\n")); return 1; } #ifdef ENCRYPTION void telnet_encrypt_key (Session_Key *skey) { if (session_key) { skey->type = SK_DES; skey->length = 8; skey->data = session_key->contents; encrypt_session_key (skey, 0); } } #else # define telnet_encrypt_key(s) #endif void kerberos5_reply (TN_Authenticator *ap, unsigned char *data, int cnt) { #ifdef ENCRYPTION Session_Key skey; #endif static int mutual_complete = 0; if (cnt-- < 1) return; switch (*data++) { case KRB_REJECT: if (cnt > 0) printf ("[ Kerberos V5 refuses authentication because %.*s ]\r\n", cnt, data); else printf ("[ Kerberos V5 refuses authentication ]\r\n"); auth_send_retry(); return; case KRB_ACCEPT: if (!mutual_complete) { if ((ap->way & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL) { printf ("[ Kerberos V5 accepted you, but didn't provide mutual authentication! ]\r\n"); auth_send_retry (); break; } telnet_encrypt_key (&skey); } if (cnt) printf ("[ Kerberos V5 accepts you as ``%.*s''%s ]\r\n", cnt, data, mutual_complete ? " (server authenticated)" : " (server NOT authenticated)"); else printf ("[ Kerberos V5 accepts you ]\r\n"); auth_finished(ap, AUTH_USER); #ifdef FORWARD if (forward_flags & OPTS_FORWARD_CREDS) kerberos5_forward (ap); #endif break; case KRB_RESPONSE: if ((ap->way & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL) { krb5_ap_rep_enc_part *reply; krb5_data inbuf; krb5_error_code r; inbuf.length = cnt; inbuf.data = (char *)data; if ((r = krb5_rd_rep (telnet_context, auth_context, &inbuf, &reply))) { printf ("[ Mutual authentication failed: %s ]\r\n", error_message (r)); auth_send_retry (); break; } krb5_free_ap_rep_enc_part (telnet_context, reply); telnet_encrypt_key (&skey); mutual_complete = 1; } break; #ifdef FORWARD case KRB_FORWARD_ACCEPT: printf ("[ Kerberos V5 accepted forwarded credentials ]\r\n"); break; case KRB_FORWARD_REJECT: printf ("[ Kerberos V5 refuses forwarded credentials because %.*s ]\r\n", cnt, data); break; #endif /* FORWARD */ default: DEBUG(("Unknown Kerberos option %d\r\n", data[-1])); } } int kerberos5_status (TN_Authenticator *ap, char *name, int level) { if (level < AUTH_USER) return level; if (UserNameRequested && krb5_kuserok (telnet_context, ticket->enc_part2->client, UserNameRequested)) { /* FIXME: Check buffer length */ strcpy (name, UserNameRequested); return AUTH_VALID; } return AUTH_USER; } int kerberos5_is_auth (TN_Authenticator *ap, unsigned char *data, int cnt, char *errbuf, int errbuflen) { int r = 0; krb5_keytab keytabid = 0; krb5_authenticator *authenticator; char *name; krb5_data outbuf; krb5_keyblock *newkey = NULL; krb5_principal server; #ifdef ENCRYPTION Session_Key skey; #endif auth.data = (char *)data; auth.length = cnt; if (!r && !auth_context) r = krb5_auth_con_init (telnet_context, &auth_context); if (!r) { krb5_rcache rcache; r = krb5_auth_con_getrcache (telnet_context, auth_context, &rcache); if (!r && !rcache) { r = krb5_sname_to_principal(telnet_context, 0, 0, KRB5_NT_SRV_HST, &server); if (!r) { r = krb5_get_server_rcache(telnet_context, krb5_princ_component (telnet_context, server, 0), &rcache); krb5_free_principal (telnet_context, server); } } if (!r) r = krb5_auth_con_setrcache (telnet_context, auth_context, rcache); } if (!r && telnet_srvtab) r = krb5_kt_resolve (telnet_context, telnet_srvtab, &keytabid); if (!r) r = krb5_rd_req (telnet_context, &auth_context, &auth, NULL, keytabid, NULL, &ticket); if (r) { snprintf (errbuf, errbuflen, "krb5_rd_req failed: %s", error_message (r)); return r; } /* 256 bytes should be much larger than any reasonable first component of a service name especially since the default is of length 4. */ if (krb5_princ_component (telnet_context,ticket->server,0)->length < 256) { char princ[256]; strncpy (princ, krb5_princ_component (telnet_context, ticket->server,0)->data, krb5_princ_component (telnet_context, ticket->server,0)->length); princ[krb5_princ_component (telnet_context, ticket->server,0)->length] = '\0'; if (strcmp ("host", princ)) { snprintf(errbuf, errbuflen, "incorrect service name: \"%s\" != \"host\"", princ); return 1; } } else { strncpy (errbuf, "service name too long", errbuflen); return 1; } r = krb5_auth_con_getauthenticator (telnet_context, auth_context, &authenticator); if (r) { snprintf (errbuf, errbuflen, "krb5_auth_con_getauthenticator failed: %s", error_message (r)); return 1; } #ifdef AUTH_ENCRYPT_MASK if ((ap->way & AUTH_ENCRYPT_MASK) == AUTH_ENCRYPT_ON && !authenticator->checksum) { snprintf (errbuf, errbuflen, "authenticator is missing required checksum"); return 1; } #endif if (authenticator->checksum) { char type_check[2]; krb5_checksum *cksum = authenticator->checksum; krb5_keyblock *key; type_check[0] = ap->type; type_check[1] = ap->way; r = krb5_auth_con_getkey (telnet_context, auth_context, &key); if (r) { snprintf (errbuf, errbuflen, "krb5_auth_con_getkey failed: %s", error_message (r)); return 1; } r = krb5_verify_checksum (telnet_context, cksum->checksum_type, cksum, &type_check, 2, key->contents, key->length); if (r) { snprintf (errbuf, errbuflen, "checksum verification failed: %s", error_message (r)); return 1; } krb5_free_keyblock (telnet_context, key); } krb5_free_authenticator (telnet_context, authenticator); if ((ap->way & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL) { if ((r = krb5_mk_rep (telnet_context, auth_context, &outbuf))) { snprintf (errbuf, errbuflen, "Make reply failed: %s", error_message(r)); return 1; } Data (ap, KRB_RESPONSE, outbuf.data, outbuf.length); } if (krb5_unparse_name (telnet_context, ticket->enc_part2 ->client, &name)) name = 0; Data (ap, KRB_ACCEPT, name, name ? -1 : 0); DEBUG(("telnetd: Kerberos5 identifies him as ``%s''\r\n", name ? name : "")); auth_finished (ap, AUTH_USER); if (name) free (name); krb5_auth_con_getremotesubkey (telnet_context, auth_context, &newkey); if (session_key) { krb5_free_keyblock (telnet_context, session_key); session_key = 0; } if (newkey) { krb5_copy_keyblock (telnet_context, newkey, &session_key); krb5_free_keyblock (telnet_context, newkey); } else { krb5_copy_keyblock (telnet_context, ticket->enc_part2->session, &session_key); } telnet_encrypt_key (&skey); return 0; } #ifdef FORWARD int kerberos5_is_forward (TN_Authenticator *ap, unsigned char *data, int cnt, char *errbuf, int errbuflen) { int r = 0; krb5_data inbuf; inbuf.length = cnt; inbuf.data = (char *)data; if ((r = krb5_auth_con_genaddrs (telnet_context, auth_context, net, KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR)) || (r = rd_and_store_for_creds(telnet_context, auth_context, &inbuf, ticket))) { snprintf (errbuf, errbuflen, "Read forwarded creds failed: %s", error_message (r)); Data (ap, KRB_FORWARD_REJECT, errbuf, -1); DEBUG(("Could not read forwarded credentials\r\n")); } else { Data(ap, KRB_FORWARD_ACCEPT, 0, 0); DEBUG(("Forwarded credentials obtained\r\n")); } return r; } #else # define kerberos5_is_forward(ap,data,cnt,errbuf,errbuflen) 0 #endif void kerberos5_is (TN_Authenticator *ap, unsigned char *data, int cnt) { int r = 0; char errbuf[512]; if (cnt-- < 1) return; errbuf[0] = 0; switch (*data++) { case KRB_AUTH: r = kerberos5_is_auth (ap, data, cnt, errbuf, sizeof errbuf); break; case KRB_FORWARD: r = kerberos5_is_forward (ap, data, cnt, errbuf, sizeof errbuf); break; default: DEBUG(("Unknown Kerberos option %d\r\n", data[-1])); Data(ap, KRB_REJECT, 0, 0); break; } if (r) { if (!errbuf[0]) snprintf (errbuf, sizeof errbuf, "kerberos_is: %s", error_message(r)); Data (ap, KRB_REJECT, errbuf, -1); DEBUG(("%s\r\n", errbuf)); syslog (LOG_ERR, "%s", errbuf); if (auth_context) { krb5_auth_con_free (telnet_context, auth_context); auth_context = 0; } } } static char * req_type_str (int type) { switch (type) { case KRB_REJECT: return "REJECT"; case KRB_ACCEPT: return "ACCEPT"; case KRB_AUTH: return "AUTH"; case KRB_RESPONSE: return "RESPONSE"; case KRB_FORWARD: return "FORWARD"; case KRB_FORWARD_ACCEPT: return "FORWARD_ACCEPT"; case KRB_FORWARD_REJECT: return "FORWARD_REJECT"; } return NULL; } #define ADDC(p,l,c) if ((l) > 0) {*(p)++ = (c); --(l);} void kerberos5_printsub (unsigned char *data, int cnt, unsigned char *buf, int buflen) { char *p; int i; buf[buflen-1] = '\0'; /* make sure its NULL terminated */ buflen -= 1; p = req_type_str (data[3]); if (!p) { int l = snprintf (buf, buflen, " %d (unknown)", data[3]); buf += l; buflen -= l; } else { while (buflen > 0 && (*buf++ = *p++) != 0) buflen--; } switch (data[3]) { case KRB_REJECT: /* Rejected (reason might follow) */ case KRB_ACCEPT: /* Accepted (username might follow) */ if (cnt <= 4) break; ADDC(buf, buflen, '"'); for (i = 4; i < cnt; i++) ADDC(buf, buflen, data[i]); ADDC(buf, buflen, '"'); ADDC(buf, buflen, '\0'); break; case KRB_AUTH: case KRB_RESPONSE: case KRB_FORWARD: case KRB_FORWARD_ACCEPT: case KRB_FORWARD_REJECT: for (i = 4; buflen > 0 && i < cnt; i++) { int l = snprintf (buf, buflen, " %d", data[i]); buf += l; buflen -= l; } } } #ifdef FORWARD void kerberos5_forward (TN_Authenticator *ap) { krb5_error_code r; krb5_ccache ccache; krb5_principal client = 0; krb5_principal server = 0; krb5_data forw_creds; forw_creds.data = 0; if ((r = krb5_cc_default (telnet_context, &ccache))) { DEBUG(("Kerberos V5: could not get default ccache - %s\r\n", error_message(r))); return; } for (;;) /* Fake loop */ { if ((r = krb5_cc_get_principal (telnet_context, ccache, &client))) { DEBUG(("Kerberos V5: could not get default principal - %s\r\n", error_message(r))); break; } if ((r = krb5_sname_to_principal (telnet_context, RemoteHostName, "host", KRB5_NT_SRV_HST, &server))) { DEBUG(("Kerberos V5: could not make server principal - %s\r\n", error_message(r))); break; } if ((r = krb5_auth_con_genaddrs (telnet_context, auth_context, net, KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR))) { DEBUG(("Kerberos V5: could not gen local full address - %s\r\n", error_message(r))); break; } if ((r = krb5_fwd_tgt_creds (telnet_context, auth_context, 0, client, server, ccache, forward_flags & OPTS_FORWARDABLE_CREDS, &forw_creds))) { DEBUG(("Kerberos V5: error getting forwarded creds - %s\r\n", error_message(r))); break; } /* Send forwarded credentials */ if (!Data(ap, KRB_FORWARD, forw_creds.data, forw_creds.length)) { DEBUG(("Not enough room for authentication data\r\n")); } else { DEBUG(("Forwarded local Kerberos V5 credentials to server\r\n")); } break; } if (client) krb5_free_principal(telnet_context, client); if (server) krb5_free_principal(telnet_context, server); if (forw_creds.data) free(forw_creds.data); krb5_cc_close(telnet_context, ccache); } #endif #endif /* KRB5 */