/* Copyright (c) 1995-1998 by Cisco systems, Inc. Permission to use, copy, modify, and distribute this software for any purpose and without fee is hereby granted, provided that this copyright and permission notice appear on all copies of the software and supporting documentation, the name of Cisco Systems, Inc. not be used in advertising or publicity pertaining to distribution of the program without specific prior permission, and notice be given in supporting documentation that modification, copying and distribution is by permission of Cisco Systems, Inc. Cisco Systems, Inc. makes no representations about the suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include "tac_plus.h" #include #include #include #include "default_fn.h" #include "expire.h" #include "md5.h" #include "report.h" #include "utils.h" #include "cfgfile.h" #include "pwlib.h" #include "choose_authen.h" /* for "struct authen_data" */ #include "do_author.h" /* for "struct identity" */ #include "packet.h" #include "main.h" #ifdef MSCHAP #include "md4.h" #include "mschap.h" #ifdef MSCHAP_DES #include "arap_des.h" #endif /* MSCHAP_DES */ #endif /* MSCHAP */ #ifdef ARAP_DES #include "arap_des.h" #endif struct private_data; static void chap_verify TAC_ARGS((struct authen_data *data)); static void arap_verify TAC_ARGS((struct authen_data *data)); static void pap_verify TAC_ARGS((struct authen_data *data)); static void tac_login TAC_ARGS((struct authen_data *data, struct private_data *p)); #ifdef MSCHAP static void mschap_verify TAC_ARGS((struct authen_data *data)); #endif /* internal state variables */ #define STATE_AUTHEN_START 0 /* no requests issued */ #define STATE_AUTHEN_GETUSER 1 /* username has been requested */ #define STATE_AUTHEN_GETPASS 2 /* password has been requested */ struct private_data { char password[MAX_PASSWD_LEN + 1]; int state; }; /* * Default tacacs login authentication function. Wants a username * and a password, and tries to verify them. * * Choose_authen will ensure that we already have a username before this * gets called. * * We will query for a password and keep it in the method_data. * * Any strings returned via pointers in authen_data must come from the * heap. They will get freed by the caller. * * Return 0 if data->status is valid, otherwise 1 */ int default_fn TAC_ARGS((struct authen_data *data)); int default_fn(data) struct authen_data *data; { struct private_data *p; char *name = data->NAS_id->username; p = (struct private_data *) data->method_data; /* An abort has been received. Clean up and return */ if (data->flags & TAC_PLUS_CONTINUE_FLAG_ABORT) { if (data->method_data) free(data->method_data); data->method_data = NULL; return (1); } /* Initialise method_data if first time through */ if (!p) { p = (struct private_data *) tac_malloc(sizeof(struct private_data)); bzero(p, sizeof(struct private_data)); data->method_data = p; p->state = STATE_AUTHEN_START; } if (STREQ(name, DEFAULT_USERNAME)) { /* Never authenticate this user. It's for authorization only */ data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; if (debug) { report(LOG_DEBUG, "authentication query for '%s' %s from %s rejected", name && name[0] ? name : "unknown", session.port, session.peer); } return (0); } if (data->action != TAC_PLUS_AUTHEN_LOGIN) { data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; } else { switch (data->type) { case TAC_PLUS_AUTHEN_TYPE_CHAP: /* set status inside chap_verify */ chap_verify(data); if (debug) { report(LOG_DEBUG, "chap-login query for '%s' %s from %s %s", name && name[0] ? name : "unknown", session.port, session.peer, (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ? "accepted" : "rejected"); } break; #ifdef MSCHAP case TAC_PLUS_AUTHEN_TYPE_MSCHAP: /* set status inside mschap_verify */ mschap_verify(data); if (debug) { report(LOG_DEBUG, "mschap-login query for '%s' %s from %s %s", name && name[0] ? name : "unknown", session.port, session.peer, (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ? "accepted" : "rejected"); } break; #endif /* MSCHAP */ case TAC_PLUS_AUTHEN_TYPE_ARAP: /* set status inside arap_verify */ arap_verify(data); if (debug) { report(LOG_DEBUG, "arap query for '%s' %s from %s %s", name && name[0] ? name : "unknown", session.port, session.peer, (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ? "accepted" : "rejected"); } break; case TAC_PLUS_AUTHEN_TYPE_PAP: pap_verify(data); if (debug) { report(LOG_INFO, "pap-login query for '%s' %s from %s %s", name && name[0] ? name : "unknown", session.port, session.peer, (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ? "accepted" : "rejected"); } break; case TAC_PLUS_AUTHEN_TYPE_ASCII: tac_login(data, p); switch (data->status) { case TAC_PLUS_AUTHEN_STATUS_GETPASS: case TAC_PLUS_AUTHEN_STATUS_GETUSER: case TAC_PLUS_AUTHEN_STATUS_GETDATA: /* Authentication still in progress. More data required */ return (0); default: /* Authentication finished */ if (debug) report(LOG_INFO, "login query for '%s' %s from %s %s", name && name[0] ? name : "unknown", session.port, session.peer, (data->status == TAC_PLUS_AUTHEN_STATUS_PASS) ? "accepted" : "rejected"); } break; default: data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; break; } } if (data->method_data) free(data->method_data); data->method_data = NULL; switch (data->status) { case TAC_PLUS_AUTHEN_STATUS_ERROR: case TAC_PLUS_AUTHEN_STATUS_FAIL: case TAC_PLUS_AUTHEN_STATUS_PASS: return (0); default: report(LOG_ERR, "%s %s: default_fn set bogus status value %d", session.peer, session.port, data->status); data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return (0); } } /* Do a login requiring a username & password. We already know the * username. We may return GETPASS to get a password if we need it. * The password will be stored in the private data * */ static void tac_login TAC_ARGS((struct authen_data *data, struct private_data *p)); static void tac_login(data, p) struct authen_data *data; struct private_data *p; { char *name, *passwd; int pwlen; name = data->NAS_id->username; if (!name[0]) { /* something awful has happened. Give up and die */ report(LOG_ERR, "%s %s: no username for login", session.peer, session.port); data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } /* Do we have a password? */ passwd = p->password; if (!passwd[0]) { /* no password yet. Either we need to ask for one and expect to get * called again when it's supplied, or we already asked for one and * we should have a reply. */ switch (p->state) { case STATE_AUTHEN_GETPASS: /* We already asked for a password. This should be the reply */ if (data->client_msg) { pwlen = MIN((int) strlen(data->client_msg), MAX_PASSWD_LEN); } else { pwlen = 0; } strncpy(passwd, data->client_msg, pwlen); passwd[pwlen] = '\0'; break; case STATE_AUTHEN_START: /* if we're at the username stage, and the user has * nopasswd defined, then return a PASS */ if (cfg_get_user_nopasswd(name, TAC_PLUS_RECURSE)) { data->status = TAC_PLUS_AUTHEN_STATUS_PASS; return; } /* FALL-THRU */ default: data->flags = TAC_PLUS_AUTHEN_FLAG_NOECHO; data->server_msg = tac_strdup("Password: "); data->status = TAC_PLUS_AUTHEN_STATUS_GETPASS; p->state = STATE_AUTHEN_GETPASS; return; } } /* Now we have a username and password. Try validating */ /* Assume the worst */ data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; verify(name, passwd, data, TAC_PLUS_RECURSE); return; } /* * Process an inbound PAP login. The username & password should be in * the START packet. */ static void pap_verify TAC_ARGS((struct authen_data *data)); static void pap_verify(data) struct authen_data *data; { char *name, *passwd; name = data->NAS_id->username; if (!name[0]) { /* something awful has happened. Give up and die */ report(LOG_ERR, "%s %s: no username for inbound PAP login", session.peer, session.port); data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } /* get the password */ passwd = tac_malloc(data->client_dlen + 1); bcopy(data->client_data, passwd, data->client_dlen); passwd[data->client_dlen] = '\0'; /* Assume the worst */ data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; verify(name, passwd, data, TAC_PLUS_RECURSE); free(passwd); } static void chap_verify TAC_ARGS((struct authen_data *data)); /* Verify the challenge and id against the response by looking up the * chap secret in the config file. Set data->status appropriately. */ static void chap_verify(data) struct authen_data *data; { char *name, *chal, digest[MD5_LEN]; const char *secret; const char *exp_date, *p; u_char *mdp; char id; int chal_len, inlen; MD5_CTX mdcontext; if (!(char) data->NAS_id->username[0]) { report(LOG_ERR, "%s %s: no username for chap_verify", session.peer, session.port); data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } name = data->NAS_id->username; id = data->client_data[0]; chal_len = data->client_dlen - 1 - MD5_LEN; if (chal_len < 0) { data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } if (debug & DEBUG_AUTHEN_FLAG) { report(LOG_DEBUG, "%s %s: chap user=%s, id=%d chal_len=%d", session.peer, session.port, name, (int) id, chal_len); /* report_hex(LOG_DEBUG, (u_char *)data->client_data + 1, chal_len); */ } /* Assume failure */ data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; /* Get the secret */ secret = cfg_get_chap_secret(name, TAC_PLUS_RECURSE); /* If there is no chap password for this user, see if there is a global * password for her that we can use */ if (!secret) { secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE); } if (!secret) { /* No secret. Fail */ if (debug & DEBUG_AUTHEN_FLAG) { report(LOG_DEBUG, "%s %s: No chap or global secret for %s", session.peer, session.port, name); } data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; return; } p = tac_find_substring("cleartext ", secret); if (!p) { report(LOG_ERR, "%s %s: %s chap secret %s is not cleartext", session.peer, session.port, name, secret); data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } secret = p; /* We now have the secret, the id, and the challenge value. Put them all * together, and run them through the MD5 digest algorithm. */ inlen = sizeof(u_char) + strlen(secret) + chal_len; mdp = (u_char *) tac_malloc(inlen); mdp[0] = id; bcopy(secret, &mdp[1], strlen(secret)); chal = data->client_data + 1; bcopy(chal, mdp + strlen(secret) + 1, chal_len); MD5Init(&mdcontext); MD5Update(&mdcontext, mdp, inlen); MD5Final((u_char *) digest, &mdcontext); free(mdp); /* Now compare the received response value with the just calculated * digest value. If they are equal, it's a pass, otherwise it's a * failure */ if (bcmp(digest, data->client_data + 1 + chal_len, MD5_LEN)) { data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; } else { data->status = TAC_PLUS_AUTHEN_STATUS_PASS; } exp_date = cfg_get_expires(name, TAC_PLUS_RECURSE); set_expiration_status(exp_date, data); } static void pw_bitshift TAC_ARGS((char *pw)); /* * Force the "parity" bit to zero on a password before passing it to * des. This is not documented anywhere. (I believe forcing the parity * to zero reduces the integrity of the encrypted keys but this is * what Apple chose to do). */ static void pw_bitshift(pw) char *pw; { int i; unsigned char pws[8]; /* key is 0 padded */ for (i = 0; i < 8; i++) pws[i] = 0; /* parity bit is always zero (this seem bogus) */ for (i = 0; i < 8 && pw[i]; i++) pws[i] = pw[i] << 1; bcopy(pws, pw, 8); } static void arap_verify TAC_ARGS((struct authen_data *data)); static void arap_verify(data) struct authen_data *data; { char nas_chal[8], r_chal[8], r_resp[8], secret[8]; const char *name, *cfg_secret, *exp_date, *p; if (!(char) data->NAS_id->username[0]) { report(LOG_ERR, "%s %s: no username for arap_verify", session.peer, session.port); data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } name = data->NAS_id->username; bcopy(data->client_data, nas_chal, 8); bcopy(data->client_data + 8, r_chal, 8); bcopy(data->client_data + 8 + 8, r_resp, 8); /* Assume failure */ data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; /* Get the secret */ cfg_secret = cfg_get_arap_secret(name, TAC_PLUS_RECURSE); /* If there is no arap password for this user, see if there is a global * password for her that we can use */ if (!cfg_secret) { cfg_secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE); } if (!cfg_secret) { /* No secret. Fail */ if (debug & DEBUG_AUTHEN_FLAG) { report(LOG_DEBUG, "%s %s: No arap or global secret for %s", session.peer, session.port, name); } data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; return; } p = tac_find_substring("cleartext ", cfg_secret); if (!p) { report(LOG_ERR, "%s %s: %s arap secret %s is not cleartext", session.peer, session.port, name, cfg_secret); data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } /* need to allocate 8 bytes for secret, even if it's actually shorter */ bzero(secret, sizeof(secret)); strcpy(secret, p); pw_bitshift(secret); #ifdef ARAP_DES des_init(0); des_setkey(secret); des_endes(nas_chal); des_done(); #endif /* ARAP_DES */ /* Now compare the remote's response value with the just calculated one * value. If they are equal, it's a pass, otherwise it's a failure */ if (bcmp(nas_chal, r_resp, 8)) { data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; } else { data->status = TAC_PLUS_AUTHEN_STATUS_PASS; } #ifdef ARAP_DES /* Now calculate the response to the remote's challenge */ des_init(0); des_setkey(secret); des_endes(r_chal); des_done(); #endif /* ARAP_DES */ data->server_data = tac_malloc(8); data->server_dlen = 8; bcopy(r_chal, data->server_data, 8); exp_date = cfg_get_expires(name, TAC_PLUS_RECURSE); set_expiration_status(exp_date, data); } #ifdef MSCHAP /* Following code is added for ms-chap */ static void mschap_desencrypt TAC_ARGS((const char *clear, unsigned char *str, unsigned char *cypher)); static void mschap_desencrypt(clear, str, cypher) const char *clear; unsigned char *str; unsigned char *cypher; { unsigned char key[8]; /* des_state_type *des_state = NULL; */ memset(key, 0, 8); /* Copy the key inserting parity bits */ #ifdef old /* This method makes it obvious what we are doing */ #define getbit(bit,array) ((array[bit/8] & (1 << (7-(bit%8)))) !=0) #define setbit(bit,array) (array[bit/8] |= (1 << (7-(bit%8)))) { int i, j; j = 0; for (i = 0; i < 56; i++) { if (i && (i % 7 == 0)) { j++; } if (getbit(i, str)) setbit(j, key); j++; } } #else /* this is a little more cryptic, but faster basicly we are insering a * bit into the stream after every 7 bits */ key[0] = ((str[0] & 0xfe)); key[1] = ((str[0] & 0x01) << 7) | ((str[1] & 0x0fc) >> 1); key[2] = ((str[1] & 0x03) << 6) | ((str[2] & 0x0f8) >> 2); key[3] = ((str[2] & 0x07) << 5) | ((str[3] & 0x0f0) >> 3); key[4] = ((str[3] & 0x0f) << 4) | ((str[4] & 0x0e0) >> 4); key[5] = ((str[4] & 0x1f) << 3) | ((str[5] & 0x0c0) >> 5); key[6] = ((str[5] & 0x3f) << 2) | ((str[6] & 0x080) >> 6); key[7] = ((str[6] & 0x7f) << 1); #endif /* copy clear to cypher, cause our des encrypts in place */ memcpy(cypher, clear, 8); /* des_init(0,&des_state); des_setkey(des_state,key); des_endes(des_state,cypher); des_done(des_state); */ #ifdef MSCHAP_DES des_init(0); des_setkey(key); des_endes(cypher); des_done(); #endif /* MSCHAP_DES */ } static void mschap_deshash TAC_ARGS((unsigned char *clear, unsigned char *cypher)); static void mschap_deshash(clear, cypher) unsigned char *clear; unsigned char *cypher; { mschap_desencrypt(MSCHAP_KEY, clear, cypher); } static void mschap_lmpasswordhash TAC_ARGS((const char *password, unsigned char *passwordhash)); static void mschap_lmpasswordhash(password, passwordhash) const char *password; unsigned char *passwordhash; { unsigned char upassword[15]; int i = 0; memset(upassword, 0, 15); while (password[i]) { upassword[i] = toupper(password[i]); i++; }; mschap_deshash(&upassword[0], &passwordhash[0]); mschap_deshash(&upassword[7], &passwordhash[8]); } static void mschap_challengeresponse TAC_ARGS((const char *challenge, unsigned char *passwordhash, unsigned char *response)); static void mschap_challengeresponse(challenge, passwordhash, response) const char *challenge; unsigned char *passwordhash; unsigned char *response; { unsigned char zpasswordhash[21]; memset(zpasswordhash, 0, 21); memcpy(zpasswordhash, passwordhash, 16); mschap_desencrypt(challenge, &zpasswordhash[0], &response[0]); mschap_desencrypt(challenge, &zpasswordhash[7], &response[8]); mschap_desencrypt(challenge, &zpasswordhash[14], &response[16]); } void mschap_lmchallengeresponse TAC_ARGS((const char *challenge, const char *password, unsigned char *response)); void mschap_lmchallengeresponse(challenge, password, response) const char *challenge; const char *password; unsigned char *response; { unsigned char passwordhash[16]; mschap_lmpasswordhash(password, passwordhash); mschap_challengeresponse(challenge, passwordhash, response); } static int mschap_unicode_len TAC_ARGS((unsigned char *password)); static int mschap_unicode_len(password) unsigned char *password; { int i; i = 0; while ((password[i] || password[i + 1]) && (i < 512)) { i += 2; } return i; } static void mschap_ntpasswordhash TAC_ARGS((const char *password, unsigned char *passwordhash)); static void mschap_ntpasswordhash(password, passwordhash) const char *password; unsigned char *passwordhash; { MD4_CTX context; int i; const char *cp; unsigned char unicode_password[512]; memset(unicode_password, 0, 512); i = 0; memset(unicode_password, 0, 512); cp = password; while (*cp) { unicode_password[i++] = *cp++; unicode_password[i++] = '\0'; } MD4Init(&context); MD4Update(&context, unicode_password, mschap_unicode_len(unicode_password)); MD4Final(passwordhash, &context); } void mschap_ntchallengeresponse TAC_ARGS((const char *challenge, const char *password, unsigned char *response)); void mschap_ntchallengeresponse(challenge, password, response) const char *challenge; const char *password; unsigned char *response; { unsigned char passwordhash[16]; mschap_ntpasswordhash(password, passwordhash); mschap_challengeresponse(challenge, passwordhash, response); } /* Verify the challenge and id against the response by looking up the * ms-chap secret in the config file. Set data->status appropriately. */ static void mschap_verify TAC_ARGS((struct authen_data *data)); static void mschap_verify(data) struct authen_data *data; { const char *name, *secret, *chal, *resp; const char *exp_date, *p; char id; int chal_len; unsigned char lmresponse[24]; unsigned char ntresponse[24]; int bcmp_status; if (!(char) data->NAS_id->username[0]) { report(LOG_ERR, "%s %s: no username for mschap_verify", session.peer, session.port); data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } name = data->NAS_id->username; id = data->client_data[0]; chal_len = data->client_dlen - 1 - MSCHAP_DIGEST_LEN; if (data->client_dlen <= (MSCHAP_DIGEST_LEN + 2)) { /* Invalid packet or NULL challenge */ data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } if (debug & DEBUG_AUTHEN_FLAG) { report(LOG_DEBUG, "%s %s: ms-chap user=%s, id=%d chal_len=%d", session.peer, session.port, name, (int) id, chal_len); /* report_hex(LOG_DEBUG, (u_char *)data->client_data + 1, chal_len); */ } /* Assume failure */ data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; /* Get the secret */ secret = cfg_get_mschap_secret(name, TAC_PLUS_RECURSE); /* If there is no ms-chap password for this user, see if there is a * global password for her that we can use */ if (!secret) { secret = cfg_get_global_secret(name, TAC_PLUS_RECURSE); } if (!secret) { /* No secret. Fail */ if (debug & DEBUG_AUTHEN_FLAG) { report(LOG_DEBUG, "%s %s: No ms-chap or global secret for %s", session.peer, session.port, name); } data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; return; } p = tac_find_substring("cleartext ", secret); if (!p) { report(LOG_ERR, "%s %s: %s ms-chap secret %s is not cleartext", session.peer, session.port, name, secret); data->status = TAC_PLUS_AUTHEN_STATUS_ERROR; return; } secret = p; /* We now have the secret, the id, and the challenge value. Put them all * together, and run them through the MD4 digest algorithm. */ chal = data->client_data + 1; resp = data->client_data + 1 + chal_len; mschap_lmchallengeresponse(chal, secret, lmresponse); mschap_ntchallengeresponse(chal, secret, ntresponse); /* Now compare the received response value with the just calculated * digest value. If they are equal, it's a pass, otherwise it's a * failure */ if (resp[48]) bcmp_status = bcmp(ntresponse, &resp[24], 24); else bcmp_status = bcmp(lmresponse, &resp[0], 24); if (bcmp_status) { data->status = TAC_PLUS_AUTHEN_STATUS_FAIL; } else { data->status = TAC_PLUS_AUTHEN_STATUS_PASS; } exp_date = cfg_get_expires(name, TAC_PLUS_RECURSE); set_expiration_status(exp_date, data); } #endif /* MSCHAP */