/* * eap.c - Extensible Authentication Protocol for PPP (RFC 2284) * * Copyright (c) 2001 by Sun Microsystems, Inc. * All rights reserved. * * Non-exclusive rights to redistribute, modify, translate, and use * this software in source and binary forms, in whole or in part, is * hereby granted, provided that the above copyright notice is * duplicated in any source form, and that neither the name of the * copyright holder nor the author is used to endorse or promote * products derived from this software. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * Original version by James Carlson * * This implementation of EAP supports MD5-Challenge and SRP-SHA1 * authentication styles. Note that support of MD5-Challenge is a * requirement of RFC 2284, and that it's essentially just a * reimplementation of regular RFC 1994 CHAP using EAP messages. * * As an authenticator ("server"), there are multiple phases for each * style. In the first phase of each style, the unauthenticated peer * name is queried using the EAP Identity request type. If the * "remotename" option is used, then this phase is skipped, because * the peer's name is presumed to be known. * * For MD5-Challenge, there are two phases, and the second phase * consists of sending the challenge itself and handling the * associated response. * * For SRP-SHA1, there are four phases. The second sends 's', 'N', * and 'g'. The reply contains 'A'. The third sends 'B', and the * reply contains 'M1'. The forth sends the 'M2' value. * * As an authenticatee ("client"), there's just a single phase -- * responding to the queries generated by the peer. EAP is an * authenticator-driven protocol. * * Based on draft-ietf-pppext-eap-srp-03.txt. */ /* * Modification by Beniamino Galvani, Mar 2005 * Implemented EAP-TLS authentication */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include "pppd-private.h" #include "options.h" #include "pathnames.h" #include "crypto.h" #include "crypto_ms.h" #include "eap.h" #ifdef PPP_WITH_PEAP #include "peap.h" #endif /* PPP_WITH_PEAP */ #ifdef PPP_WITH_SRP #ifdef HAVE_TIME_H #include #endif #include #include #include #endif /* PPP_WITH_SRP */ #ifdef PPP_WITH_EAPTLS #include "eap-tls.h" #endif /* PPP_WITH_EAPTLS */ #ifdef PPP_WITH_CHAPMS #include "chap.h" #include "chap_ms.h" extern int chapms_strip_domain; #endif /* PPP_WITH_CHAPMS */ eap_state eap_states[NUM_PPP]; /* EAP state; one for each unit */ #ifdef PPP_WITH_SRP static char *pn_secret = NULL; /* Pseudonym generating secret */ #endif /* * Command-line options. */ static struct option eap_option_list[] = { { "eap-restart", o_int, &eap_states[0].es_server.ea_timeout, "Set retransmit timeout for EAP Requests (server)" }, { "eap-max-sreq", o_int, &eap_states[0].es_server.ea_maxrequests, "Set max number of EAP Requests sent (server)" }, { "eap-timeout", o_int, &eap_states[0].es_client.ea_timeout, "Set time limit for peer EAP authentication" }, { "eap-max-rreq", o_int, &eap_states[0].es_client.ea_maxrequests, "Set max number of EAP Requests allows (client)" }, { "eap-interval", o_int, &eap_states[0].es_rechallenge, "Set interval for EAP rechallenge" }, #ifdef PPP_WITH_SRP { "srp-interval", o_int, &eap_states[0].es_lwrechallenge, "Set interval for SRP lightweight rechallenge" }, { "srp-pn-secret", o_string, &pn_secret, "Long term pseudonym generation secret" }, { "srp-use-pseudonym", o_bool, &eap_states[0].es_usepseudo, "Use pseudonym if offered one by server", 1 }, #endif { NULL } }; /* * Protocol entry points. */ static void eap_init (int unit); static void eap_input (int unit, u_char *inp, int inlen); static void eap_protrej (int unit); static void eap_lowerup (int unit); static void eap_lowerdown (int unit); static int eap_printpkt (u_char *inp, int inlen, void (*)(void *arg, char *fmt, ...), void *arg); struct protent eap_protent = { PPP_EAP, /* protocol number */ eap_init, /* initialization procedure */ eap_input, /* process a received packet */ eap_protrej, /* process a received protocol-reject */ eap_lowerup, /* lower layer has gone up */ eap_lowerdown, /* lower layer has gone down */ NULL, /* open the protocol */ NULL, /* close the protocol */ eap_printpkt, /* print a packet in readable form */ NULL, /* process a received data packet */ 1, /* protocol enabled */ "EAP", /* text name of protocol */ NULL, /* text name of corresponding data protocol */ eap_option_list, /* list of command-line options */ NULL, /* check requested options; assign defaults */ NULL, /* configure interface for demand-dial */ NULL /* say whether to bring up link for this pkt */ }; #ifdef PPP_WITH_SRP /* * A well-known 2048 bit modulus. */ static const u_char wkmodulus[] = { 0xAC, 0x6B, 0xDB, 0x41, 0x32, 0x4A, 0x9A, 0x9B, 0xF1, 0x66, 0xDE, 0x5E, 0x13, 0x89, 0x58, 0x2F, 0xAF, 0x72, 0xB6, 0x65, 0x19, 0x87, 0xEE, 0x07, 0xFC, 0x31, 0x92, 0x94, 0x3D, 0xB5, 0x60, 0x50, 0xA3, 0x73, 0x29, 0xCB, 0xB4, 0xA0, 0x99, 0xED, 0x81, 0x93, 0xE0, 0x75, 0x77, 0x67, 0xA1, 0x3D, 0xD5, 0x23, 0x12, 0xAB, 0x4B, 0x03, 0x31, 0x0D, 0xCD, 0x7F, 0x48, 0xA9, 0xDA, 0x04, 0xFD, 0x50, 0xE8, 0x08, 0x39, 0x69, 0xED, 0xB7, 0x67, 0xB0, 0xCF, 0x60, 0x95, 0x17, 0x9A, 0x16, 0x3A, 0xB3, 0x66, 0x1A, 0x05, 0xFB, 0xD5, 0xFA, 0xAA, 0xE8, 0x29, 0x18, 0xA9, 0x96, 0x2F, 0x0B, 0x93, 0xB8, 0x55, 0xF9, 0x79, 0x93, 0xEC, 0x97, 0x5E, 0xEA, 0xA8, 0x0D, 0x74, 0x0A, 0xDB, 0xF4, 0xFF, 0x74, 0x73, 0x59, 0xD0, 0x41, 0xD5, 0xC3, 0x3E, 0xA7, 0x1D, 0x28, 0x1E, 0x44, 0x6B, 0x14, 0x77, 0x3B, 0xCA, 0x97, 0xB4, 0x3A, 0x23, 0xFB, 0x80, 0x16, 0x76, 0xBD, 0x20, 0x7A, 0x43, 0x6C, 0x64, 0x81, 0xF1, 0xD2, 0xB9, 0x07, 0x87, 0x17, 0x46, 0x1A, 0x5B, 0x9D, 0x32, 0xE6, 0x88, 0xF8, 0x77, 0x48, 0x54, 0x45, 0x23, 0xB5, 0x24, 0xB0, 0xD5, 0x7D, 0x5E, 0xA7, 0x7A, 0x27, 0x75, 0xD2, 0xEC, 0xFA, 0x03, 0x2C, 0xFB, 0xDB, 0xF5, 0x2F, 0xB3, 0x78, 0x61, 0x60, 0x27, 0x90, 0x04, 0xE5, 0x7A, 0xE6, 0xAF, 0x87, 0x4E, 0x73, 0x03, 0xCE, 0x53, 0x29, 0x9C, 0xCC, 0x04, 0x1C, 0x7B, 0xC3, 0x08, 0xD8, 0x2A, 0x56, 0x98, 0xF3, 0xA8, 0xD0, 0xC3, 0x82, 0x71, 0xAE, 0x35, 0xF8, 0xE9, 0xDB, 0xFB, 0xB6, 0x94, 0xB5, 0xC8, 0x03, 0xD8, 0x9F, 0x7A, 0xE4, 0x35, 0xDE, 0x23, 0x6D, 0x52, 0x5F, 0x54, 0x75, 0x9B, 0x65, 0xE3, 0x72, 0xFC, 0xD6, 0x8E, 0xF2, 0x0F, 0xA7, 0x11, 0x1F, 0x9E, 0x4A, 0xFF, 0x73 }; #endif /* PPP_WITH_SRP */ /* Local forward declarations. */ static void eap_server_timeout (void *arg); /* * Convert EAP state code to printable string for debug. */ static const char * eap_state_name(enum eap_state_code esc) { static const char *state_names[] = { EAP_STATES }; return (state_names[(int)esc]); } /* * eap_init - Initialize state for an EAP user. This is currently * called once by main() during start-up. */ static void eap_init(int unit) { eap_state *esp = &eap_states[unit]; BZERO(esp, sizeof (*esp)); esp->es_unit = unit; esp->es_server.ea_timeout = EAP_DEFTIMEOUT; esp->es_server.ea_maxrequests = EAP_DEFTRANSMITS; esp->es_server.ea_id = (u_char)(drand48() * 0x100); esp->es_client.ea_timeout = EAP_DEFREQTIME; esp->es_client.ea_maxrequests = EAP_DEFALLOWREQ; #ifdef PPP_WITH_EAPTLS esp->es_client.ea_using_eaptls = 0; #endif /* PPP_WITH_EAPTLS */ #ifdef PPP_WITH_CHAPMS esp->es_client.digest = chap_find_digest(CHAP_MICROSOFT_V2); esp->es_server.digest = chap_find_digest(CHAP_MICROSOFT_V2); #endif } /* * eap_client_timeout - Give up waiting for the peer to send any * Request messages. */ static void eap_client_timeout(void *arg) { eap_state *esp = (eap_state *) arg; if (!eap_client_active(esp)) return; error("EAP: timeout waiting for Request from peer"); auth_withpeer_fail(esp->es_unit, PPP_EAP); esp->es_client.ea_state = eapBadAuth; } /* * eap_authwithpeer - Authenticate to our peer (behave as client). * * Start client state and wait for requests. This is called only * after eap_lowerup. */ void eap_authwithpeer(int unit, char *localname) { eap_state *esp = &eap_states[unit]; /* Save the peer name we're given */ esp->es_client.ea_name = localname; esp->es_client.ea_namelen = strlen(localname); esp->es_client.ea_state = eapListen; /* * Start a timer so that if the other end just goes * silent, we don't sit here waiting forever. */ if (esp->es_client.ea_timeout > 0) TIMEOUT(eap_client_timeout, (void *)esp, esp->es_client.ea_timeout); } /* * Format a standard EAP Failure message and send it to the peer. * (Server operation) */ static void eap_send_failure(eap_state *esp) { u_char *outp; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_FAILURE, outp); esp->es_server.ea_id++; PUTCHAR(esp->es_server.ea_id, outp); PUTSHORT(EAP_HEADERLEN, outp); output(esp->es_unit, outpacket_buf, EAP_HEADERLEN + PPP_HDRLEN); esp->es_server.ea_state = eapBadAuth; auth_peer_fail(esp->es_unit, PPP_EAP); } /* * Format a standard EAP Success message and send it to the peer. * (Server operation) */ static void eap_send_success(eap_state *esp) { u_char *outp; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_SUCCESS, outp); esp->es_server.ea_id++; PUTCHAR(esp->es_server.ea_id, outp); PUTSHORT(EAP_HEADERLEN, outp); output(esp->es_unit, outpacket_buf, PPP_HDRLEN + EAP_HEADERLEN); auth_peer_success(esp->es_unit, PPP_EAP, 0, esp->es_server.ea_peer, esp->es_server.ea_peerlen); } #ifdef PPP_WITH_SRP /* * Set DES key according to pseudonym-generating secret and current * date. */ static bool pncrypt_getkey(int timeoffs, unsigned char *key, int keylen) { struct tm *tp; char tbuf[9]; PPP_MD_CTX *ctxt; time_t reftime; if (pn_secret == NULL) return (0); reftime = time(NULL) + timeoffs; tp = localtime(&reftime); ctxt = PPP_MD_CTX_new(); if (ctxt) { strftime(tbuf, sizeof (tbuf), "%Y%m%d", tp); PPP_DigestInit(ctxt, PPP_sha1()); PPP_DigestUpdate(ctxt, pn_secret, strlen(pn_secret)); PPP_DigestUpdate(ctxt, tbuf, strlen(tbuf)); PPP_DigestFinal(ctxt, key, &keylen); PPP_MD_CTX_free(ctxt); return 1; } return (0); } static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; struct b64state { u_int32_t bs_bits; int bs_offs; }; static int b64enc(struct b64state *bs, u_char *inp, int inlen, u_char *outp) { int outlen = 0; while (inlen > 0) { bs->bs_bits = (bs->bs_bits << 8) | *inp++; inlen--; bs->bs_offs += 8; if (bs->bs_offs >= 24) { *outp++ = base64[(bs->bs_bits >> 18) & 0x3F]; *outp++ = base64[(bs->bs_bits >> 12) & 0x3F]; *outp++ = base64[(bs->bs_bits >> 6) & 0x3F]; *outp++ = base64[bs->bs_bits & 0x3F]; outlen += 4; bs->bs_offs = 0; bs->bs_bits = 0; } } return (outlen); } static int b64flush(struct b64state *bs, u_char *outp) { int outlen = 0; if (bs->bs_offs == 8) { *outp++ = base64[(bs->bs_bits >> 2) & 0x3F]; *outp++ = base64[(bs->bs_bits << 4) & 0x3F]; outlen = 2; } else if (bs->bs_offs == 16) { *outp++ = base64[(bs->bs_bits >> 10) & 0x3F]; *outp++ = base64[(bs->bs_bits >> 4) & 0x3F]; *outp++ = base64[(bs->bs_bits << 2) & 0x3F]; outlen = 3; } bs->bs_offs = 0; bs->bs_bits = 0; return (outlen); } static int b64dec(struct b64state *bs, u_char *inp, int inlen, u_char *outp) { int outlen = 0; char *cp; while (inlen > 0) { if ((cp = strchr(base64, *inp++)) == NULL) break; bs->bs_bits = (bs->bs_bits << 6) | (cp - base64); inlen--; bs->bs_offs += 6; if (bs->bs_offs >= 8) { *outp++ = bs->bs_bits >> (bs->bs_offs - 8); outlen++; bs->bs_offs -= 8; } } return (outlen); } #endif /* PPP_WITH_SRP */ /* * Assume that current waiting server state is complete and figure * next state to use based on available authentication data. 'status' * indicates if there was an error in handling the last query. It is * 0 for success and non-zero for failure. */ static void eap_figure_next_state(eap_state *esp, int status) { #ifdef PPP_WITH_SRP unsigned char secbuf[MAXWORDLEN], clear[8], *sp, *dp, key[SHA_DIGEST_LENGTH]; struct t_pw tpw; struct t_confent *tce, mytce; char *cp, *cp2; struct t_server *ts; int id, i, plen, clen, toffs, keylen; u_char vals[2]; struct b64state bs; #endif /* PPP_WITH_SRP */ #ifdef PPP_WITH_EAPTLS struct eaptls_session *ets; int secret_len; char secret[MAXWORDLEN]; #endif /* PPP_WITH_EAPTLS */ esp->es_server.ea_timeout = esp->es_savedtime; #ifdef PPP_WITH_EAPTLS esp->es_server.ea_prev_state = esp->es_server.ea_state; #endif /* PPP_WITH_EAPTLS */ switch (esp->es_server.ea_state) { case eapBadAuth: return; case eapIdentify: #ifdef PPP_WITH_SRP /* Discard any previous session. */ ts = (struct t_server *)esp->es_server.ea_session; if (ts != NULL) { t_serverclose(ts); esp->es_server.ea_session = NULL; esp->es_server.ea_skey = NULL; } #endif /* PPP_WITH_SRP */ if (status != 0) { esp->es_server.ea_state = eapBadAuth; break; } #ifdef PPP_WITH_SRP /* If we've got a pseudonym, try to decode to real name. */ if (esp->es_server.ea_peerlen > SRP_PSEUDO_LEN && strncmp(esp->es_server.ea_peer, SRP_PSEUDO_ID, SRP_PSEUDO_LEN) == 0 && (esp->es_server.ea_peerlen - SRP_PSEUDO_LEN) * 3 / 4 < sizeof (secbuf)) { BZERO(&bs, sizeof (bs)); plen = b64dec(&bs, esp->es_server.ea_peer + SRP_PSEUDO_LEN, esp->es_server.ea_peerlen - SRP_PSEUDO_LEN, secbuf); toffs = 0; for (i = 0; i < 5; i++) { pncrypt_getkey(toffs, key, keylen); toffs -= 86400; if (!DesDecrypt(secbuf, key, clear)) { dbglog("no DES here; cannot decode " "pseudonym"); return; } id = *(unsigned char *)clear; if (id + 1 <= plen && id + 9 > plen) break; } if (plen % 8 == 0 && i < 5) { /* * Note that this is always shorter than the * original stored string, so there's no need * to realloc. */ if ((i = plen = *(unsigned char *)clear) > 7) i = 7; esp->es_server.ea_peerlen = plen; dp = (unsigned char *)esp->es_server.ea_peer; BCOPY(clear + 1, dp, i); plen -= i; dp += i; sp = secbuf + 8; while (plen > 0) { DesDecrypt(sp, key, dp); sp += 8; dp += 8; plen -= 8; } esp->es_server.ea_peer[ esp->es_server.ea_peerlen] = '\0'; dbglog("decoded pseudonym to \"%.*q\"", esp->es_server.ea_peerlen, esp->es_server.ea_peer); } else { dbglog("failed to decode real name"); /* Stay in eapIdentfy state; requery */ break; } } /* Look up user in secrets database. */ if (get_srp_secret(esp->es_unit, esp->es_server.ea_peer, esp->es_server.ea_name, (char *)secbuf, 1) != 0) { /* Set up default in case SRP entry is bad */ esp->es_server.ea_state = eapMD5Chall; /* Get t_confent based on index in srp-secrets */ id = strtol((char *)secbuf, &cp, 10); if (*cp++ != ':' || id < 0) break; if (id == 0) { mytce.index = 0; mytce.modulus.data = (u_char *)wkmodulus; mytce.modulus.len = sizeof (wkmodulus); mytce.generator.data = (u_char *)"\002"; mytce.generator.len = 1; tce = &mytce; } else if ((tce = gettcid(id)) != NULL) { /* * Client will have to verify this modulus/ * generator combination, and that will take * a while. Lengthen the timeout here. */ if (esp->es_server.ea_timeout > 0 && esp->es_server.ea_timeout < 30) esp->es_server.ea_timeout = 30; } else { break; } if ((cp2 = strchr(cp, ':')) == NULL) break; *cp2++ = '\0'; tpw.pebuf.name = esp->es_server.ea_peer; tpw.pebuf.password.len = t_fromb64((char *)tpw.pwbuf, cp); tpw.pebuf.password.data = (char*) tpw.pwbuf; tpw.pebuf.salt.len = t_fromb64((char *)tpw.saltbuf, cp2); tpw.pebuf.salt.data = tpw.saltbuf; if ((ts = t_serveropenraw(&tpw.pebuf, tce)) == NULL) break; esp->es_server.ea_session = (void *)ts; esp->es_server.ea_state = eapSRP1; vals[0] = esp->es_server.ea_id + 1; vals[1] = EAPT_SRP; t_serveraddexdata(ts, vals, 2); /* Generate B; must call before t_servergetkey() */ t_servergenexp(ts); break; } #endif /* PPP_WITH_SRP */ #ifdef PPP_WITH_EAPTLS if (!get_secret(esp->es_unit, esp->es_server.ea_peer, esp->es_server.ea_name, secret, &secret_len, 1)) { esp->es_server.ea_state = eapTlsStart; break; } #endif /* PPP_WITH_EAPTLS */ esp->es_server.ea_state = eapMD5Chall; break; #ifdef PPP_WITH_EAPTLS case eapTlsStart: /* Initialize ssl session */ if(!eaptls_init_ssl_server(esp)) { esp->es_server.ea_state = eapBadAuth; break; } esp->es_server.ea_state = eapTlsRecv; break; case eapTlsRecv: ets = (struct eaptls_session *) esp->es_server.ea_session; if(ets->alert_sent) { esp->es_server.ea_state = eapTlsSendAlert; break; } if (status) { esp->es_server.ea_state = eapBadAuth; break; } ets = (struct eaptls_session *) esp->es_server.ea_session; if(ets->frag) esp->es_server.ea_state = eapTlsSendAck; else esp->es_server.ea_state = eapTlsSend; break; case eapTlsSend: ets = (struct eaptls_session *) esp->es_server.ea_session; if(ets->frag) esp->es_server.ea_state = eapTlsRecvAck; else if(SSL_is_init_finished(ets->ssl)) esp->es_server.ea_state = eapTlsRecvClient; else /* JJK Add "TLS empty record" message here ??? */ esp->es_server.ea_state = eapTlsRecv; break; case eapTlsSendAck: esp->es_server.ea_state = eapTlsRecv; break; case eapTlsRecvAck: if (status) { esp->es_server.ea_state = eapBadAuth; break; } esp->es_server.ea_state = eapTlsSend; break; case eapTlsSendAlert: esp->es_server.ea_state = eapTlsRecvAlertAck; break; #endif /* PPP_WITH_EAPTLS */ case eapSRP1: #ifdef PPP_WITH_SRP ts = (struct t_server *)esp->es_server.ea_session; if (ts != NULL && status != 0) { t_serverclose(ts); esp->es_server.ea_session = NULL; esp->es_server.ea_skey = NULL; } #endif /* PPP_WITH_SRP */ if (status == 1) { esp->es_server.ea_state = eapMD5Chall; } else if (status != 0 || esp->es_server.ea_session == NULL) { esp->es_server.ea_state = eapBadAuth; } else { esp->es_server.ea_state = eapSRP2; } break; case eapSRP2: #ifdef PPP_WITH_SRP ts = (struct t_server *)esp->es_server.ea_session; if (ts != NULL && status != 0) { t_serverclose(ts); esp->es_server.ea_session = NULL; esp->es_server.ea_skey = NULL; } #endif /* PPP_WITH_SRP */ if (status != 0 || esp->es_server.ea_session == NULL) { esp->es_server.ea_state = eapBadAuth; } else { esp->es_server.ea_state = eapSRP3; } break; case eapSRP3: case eapSRP4: #ifdef PPP_WITH_SRP ts = (struct t_server *)esp->es_server.ea_session; if (ts != NULL && status != 0) { t_serverclose(ts); esp->es_server.ea_session = NULL; esp->es_server.ea_skey = NULL; } #endif /* PPP_WITH_SRP */ if (status != 0 || esp->es_server.ea_session == NULL) { esp->es_server.ea_state = eapBadAuth; } else { esp->es_server.ea_state = eapOpen; } break; #ifdef PPP_WITH_CHAPMS case eapMSCHAPv2Chall: #endif case eapMD5Chall: if (status != 0) { esp->es_server.ea_state = eapBadAuth; } else { esp->es_server.ea_state = eapOpen; } break; default: esp->es_server.ea_state = eapBadAuth; break; } if (esp->es_server.ea_state == eapBadAuth) eap_send_failure(esp); #ifdef PPP_WITH_EAPTLS dbglog("EAP id=0x%2x '%s' -> '%s'", esp->es_server.ea_id, eap_state_name(esp->es_server.ea_prev_state), eap_state_name(esp->es_server.ea_state)); #endif /* PPP_WITH_EAPTLS */ } #if PPP_WITH_CHAPMS /* * eap_chap_verify_response - check whether the peer's response matches * what we think it should be. Returns 1 if it does (authentication * succeeded), or 0 if it doesn't. */ static int eap_chap_verify_response(char *name, char *ourname, int id, struct chap_digest_type *digest, unsigned char *challenge, unsigned char *response, char *message, int message_space) { int ok; unsigned char secret[MAXSECRETLEN]; int secret_len; /* Get the secret that the peer is supposed to know */ if (!get_secret(0, name, ourname, (char *)secret, &secret_len, 1)) { error("No CHAP secret found for authenticating %q", name); return 0; } ok = digest->verify_response(id, name, secret, secret_len, challenge, response, message, message_space); memset(secret, 0, sizeof(secret)); return ok; } /* * Format and send an CHAPV2-Success/Failure EAP Request message. */ static void eap_chapms2_send_request(eap_state *esp, u_char id, u_char opcode, u_char chapid, char *message, int message_len) { u_char *outp; int msglen; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); msglen = EAP_HEADERLEN + 5 * sizeof (u_char); msglen += message_len; PUTCHAR(EAP_REQUEST, outp); PUTCHAR(id, outp); PUTSHORT(msglen, outp); PUTCHAR(EAPT_MSCHAPV2, outp); PUTCHAR(opcode, outp); PUTCHAR(chapid, outp); /* MS len */ PUTSHORT(msglen - 5, outp); BCOPY(message, outp, message_len); output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); if (opcode == CHAP_SUCCESS) { auth_peer_success(esp->es_unit, PPP_EAP, 0, esp->es_server.ea_peer, esp->es_server.ea_peerlen); } else { esp->es_server.ea_state = eapBadAuth; auth_peer_fail(esp->es_unit, PPP_EAP); } } #endif /* PPP_WITH_CHAPMS */ /* * Format an EAP Request message and send it to the peer. Message * type depends on current state. (Server operation) */ static void eap_send_request(eap_state *esp) { u_char *outp; u_char *lenloc; u_char *ptr; int outlen; int challen; char *str; #ifdef PPP_WITH_SRP struct t_server *ts; u_char clear[8], cipher[8], dig[SHA_DIGEST_LENGTH], *optr, *cp, key[SHA_DIGEST_LENGTH]; int i, j, diglen, clen, keylen = sizeof(key); struct b64state b64; PPP_MD_CTX *ctxt; #endif /* PPP_WITH_SRP */ /* Handle both initial auth and restart */ if (esp->es_server.ea_state < eapIdentify && esp->es_server.ea_state != eapInitial) { esp->es_server.ea_state = eapIdentify; if (explicit_remote) { /* * If we already know the peer's * unauthenticated name, then there's no * reason to ask. Go to next state instead. */ esp->es_server.ea_peer = remote_name; esp->es_server.ea_peerlen = strlen(remote_name); eap_figure_next_state(esp, 0); } } if (esp->es_server.ea_maxrequests > 0 && esp->es_server.ea_requests >= esp->es_server.ea_maxrequests) { if (esp->es_server.ea_responses > 0) error("EAP: too many Requests sent"); else error("EAP: no response to Requests"); eap_send_failure(esp); return; } outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_REQUEST, outp); PUTCHAR(esp->es_server.ea_id, outp); lenloc = outp; INCPTR(2, outp); switch (esp->es_server.ea_state) { case eapIdentify: PUTCHAR(EAPT_IDENTITY, outp); str = "Name"; challen = strlen(str); BCOPY(str, outp, challen); INCPTR(challen, outp); break; case eapMD5Chall: PUTCHAR(EAPT_MD5CHAP, outp); /* * pick a random challenge length between * MIN_CHALLENGE_LENGTH and MAX_CHALLENGE_LENGTH */ challen = (drand48() * (MAX_CHALLENGE_LENGTH - MIN_CHALLENGE_LENGTH)) + MIN_CHALLENGE_LENGTH; PUTCHAR(challen, outp); esp->es_challen = challen; ptr = esp->es_challenge; while (--challen >= 0) *ptr++ = (u_char) (drand48() * 0x100); BCOPY(esp->es_challenge, outp, esp->es_challen); INCPTR(esp->es_challen, outp); BCOPY(esp->es_server.ea_name, outp, esp->es_server.ea_namelen); INCPTR(esp->es_server.ea_namelen, outp); break; #ifdef PPP_WITH_CHAPMS case eapMSCHAPv2Chall: esp->es_server.digest->generate_challenge(esp->es_challenge); challen = esp->es_challenge[0]; esp->es_challen = challen; PUTCHAR(EAPT_MSCHAPV2, outp); PUTCHAR(CHAP_CHALLENGE, outp); PUTCHAR(esp->es_server.ea_id, outp); /* MS len */ PUTSHORT(5 + challen + esp->es_server.ea_namelen, outp); /* challen + challenge */ BCOPY(esp->es_challenge, outp, challen+1); INCPTR(challen+1, outp); BCOPY(esp->es_server.ea_name, outp, esp->es_server.ea_namelen); INCPTR(esp->es_server.ea_namelen, outp); break; #endif /* PPP_WITH_CHAPMS */ #ifdef PPP_WITH_EAPTLS case eapTlsStart: PUTCHAR(EAPT_TLS, outp); PUTCHAR(EAP_TLS_FLAGS_START, outp); eap_figure_next_state(esp, 0); break; case eapTlsSend: eaptls_send(esp->es_server.ea_session, &outp); eap_figure_next_state(esp, 0); break; case eapTlsSendAck: PUTCHAR(EAPT_TLS, outp); PUTCHAR(0, outp); eap_figure_next_state(esp, 0); break; case eapTlsSendAlert: eaptls_send(esp->es_server.ea_session, &outp); eap_figure_next_state(esp, 0); break; #endif /* PPP_WITH_EAPTLS */ #ifdef PPP_WITH_SRP case eapSRP1: PUTCHAR(EAPT_SRP, outp); PUTCHAR(EAPSRP_CHALLENGE, outp); PUTCHAR(esp->es_server.ea_namelen, outp); BCOPY(esp->es_server.ea_name, outp, esp->es_server.ea_namelen); INCPTR(esp->es_server.ea_namelen, outp); ts = (struct t_server *)esp->es_server.ea_session; assert(ts != NULL); PUTCHAR(ts->s.len, outp); BCOPY(ts->s.data, outp, ts->s.len); INCPTR(ts->s.len, outp); if (ts->g.len == 1 && ts->g.data[0] == 2) { PUTCHAR(0, outp); } else { PUTCHAR(ts->g.len, outp); BCOPY(ts->g.data, outp, ts->g.len); INCPTR(ts->g.len, outp); } if (ts->n.len != sizeof (wkmodulus) || BCMP(ts->n.data, wkmodulus, sizeof (wkmodulus)) != 0) { BCOPY(ts->n.data, outp, ts->n.len); INCPTR(ts->n.len, outp); } break; case eapSRP2: PUTCHAR(EAPT_SRP, outp); PUTCHAR(EAPSRP_SKEY, outp); ts = (struct t_server *)esp->es_server.ea_session; assert(ts != NULL); BCOPY(ts->B.data, outp, ts->B.len); INCPTR(ts->B.len, outp); break; case eapSRP3: PUTCHAR(EAPT_SRP, outp); PUTCHAR(EAPSRP_SVALIDATOR, outp); PUTLONG(SRPVAL_EBIT, outp); ts = (struct t_server *)esp->es_server.ea_session; assert(ts != NULL); BCOPY(t_serverresponse(ts), outp, SHA_DIGEST_LENGTH); INCPTR(SHA_DIGEST_LENGTH, outp); if (pncrypt_getkey(0, key, keylen)) { /* Generate pseudonym */ optr = outp; cp = (unsigned char *)esp->es_server.ea_peer; if ((j = i = esp->es_server.ea_peerlen) > 7) j = 7; clear[0] = i; BCOPY(cp, clear + 1, j); i -= j; cp += j; if (!DesEncrypt(clear, key, cipher)) { dbglog("no DES here; not generating pseudonym"); break; } BZERO(&b64, sizeof (b64)); outp++; /* space for pseudonym length */ outp += b64enc(&b64, cipher, 8, outp); while (i >= 8) { DesEncrypt(cp, key, cipher); outp += b64enc(&b64, cipher, 8, outp); cp += 8; i -= 8; } if (i > 0) { BCOPY(cp, clear, i); cp += i; while (i < 8) { *cp++ = drand48() * 0x100; i++; } DesEncrypt(clear, key, cipher); outp += b64enc(&b64, cipher, 8, outp); } outp += b64flush(&b64, outp); /* Set length and pad out to next 20 octet boundary */ i = outp - optr - 1; *optr = i; i %= SHA_DIGEST_LENGTH; if (i != 0) { while (i < SHA_DIGEST_LENGTH) { *outp++ = drand48() * 0x100; i++; } } /* Obscure the pseudonym with SHA1 hash */ ctxt = PPP_MD_CTX_new(); if (ctxt) { PPP_DigestInit(ctxt, PPP_sha1()); PPP_DigestUpdate(ctxt, &esp->es_server.ea_id, 1); PPP_DigestUpdate(ctxt, &esp->es_server.ea_skey, SESSION_KEY_LEN); PPP_DigestUpdate(ctxt, esp->es_server.ea_peer, esp->es_server.ea_peerlen); while (optr < outp) { diglen = SHA_DIGEST_LENGTH; PPP_DigestFinal(ctxt, dig, &diglen); cp = dig; while (cp < dig + SHA_DIGEST_LENGTH) *optr++ ^= *cp++; PPP_DigestInit(ctxt, PPP_sha1()); PPP_DigestUpdate(ctxt, &esp->es_server.ea_id, 1); PPP_DigestUpdate(ctxt, esp->es_server.ea_skey, SESSION_KEY_LEN); PPP_DigestUpdate(ctxt, optr - SHA_DIGEST_LENGTH, SHA_DIGEST_LENGTH); } PPP_MD_CTX_free(ctxt); } } break; case eapSRP4: PUTCHAR(EAPT_SRP, outp); PUTCHAR(EAPSRP_LWRECHALLENGE, outp); challen = MIN_CHALLENGE_LENGTH + ((MAX_CHALLENGE_LENGTH - MIN_CHALLENGE_LENGTH) * drand48()); esp->es_challen = challen; ptr = esp->es_challenge; while (--challen >= 0) *ptr++ = drand48() * 0x100; BCOPY(esp->es_challenge, outp, esp->es_challen); INCPTR(esp->es_challen, outp); break; #endif /* PPP_WITH_SRP */ default: return; } outlen = (outp - outpacket_buf) - PPP_HDRLEN; PUTSHORT(outlen, lenloc); output(esp->es_unit, outpacket_buf, outlen + PPP_HDRLEN); esp->es_server.ea_requests++; if (esp->es_server.ea_timeout > 0) TIMEOUT(eap_server_timeout, esp, esp->es_server.ea_timeout); } /* * eap_authpeer - Authenticate our peer (behave as server). * * Start server state and send first request. This is called only * after eap_lowerup. */ void eap_authpeer(int unit, char *localname) { eap_state *esp = &eap_states[unit]; /* Save the name we're given. */ esp->es_server.ea_name = localname; esp->es_server.ea_namelen = strlen(localname); esp->es_savedtime = esp->es_server.ea_timeout; /* Lower layer up yet? */ if (esp->es_server.ea_state == eapInitial || esp->es_server.ea_state == eapPending) { esp->es_server.ea_state = eapPending; return; } esp->es_server.ea_state = eapPending; /* ID number not updated here intentionally; hashed into M1 */ eap_send_request(esp); } /* * eap_server_timeout - Retransmission timer for sending Requests * expired. */ static void eap_server_timeout(void *arg) { #ifdef PPP_WITH_EAPTLS u_char *outp; u_char *lenloc; int outlen; #endif /* PPP_WITH_EAPTLS */ eap_state *esp = (eap_state *) arg; if (!eap_server_active(esp)) return; #ifdef PPP_WITH_EAPTLS switch(esp->es_server.ea_prev_state) { /* * In eap-tls the state changes after a request, so we return to * previous state ... */ case(eapTlsStart): case(eapTlsSendAck): esp->es_server.ea_state = esp->es_server.ea_prev_state; break; /* * ... or resend the stored data */ case(eapTlsSend): case(eapTlsSendAlert): outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_REQUEST, outp); PUTCHAR(esp->es_server.ea_id, outp); lenloc = outp; INCPTR(2, outp); eaptls_retransmit(esp->es_server.ea_session, &outp); outlen = (outp - outpacket_buf) - PPP_HDRLEN; PUTSHORT(outlen, lenloc); output(esp->es_unit, outpacket_buf, outlen + PPP_HDRLEN); esp->es_server.ea_requests++; if (esp->es_server.ea_timeout > 0) TIMEOUT(eap_server_timeout, esp, esp->es_server.ea_timeout); return; default: break; } #endif /* PPP_WITH_EAPTLS */ /* EAP ID number must not change on timeout. */ eap_send_request(esp); } /* * When it's time to send rechallenge the peer, this timeout is * called. Once the rechallenge is successful, the response handler * will restart the timer. If it fails, then the link is dropped. */ static void eap_rechallenge(void *arg) { eap_state *esp = (eap_state *)arg; if (esp->es_server.ea_state != eapOpen && esp->es_server.ea_state != eapSRP4) return; esp->es_server.ea_requests = 0; esp->es_server.ea_state = eapIdentify; eap_figure_next_state(esp, 0); esp->es_server.ea_id++; eap_send_request(esp); } static void srp_lwrechallenge(void *arg) { eap_state *esp = (eap_state *)arg; if (esp->es_server.ea_state != eapOpen || esp->es_server.ea_type != EAPT_SRP) return; esp->es_server.ea_requests = 0; esp->es_server.ea_state = eapSRP4; esp->es_server.ea_id++; eap_send_request(esp); } /* * eap_lowerup - The lower layer is now up. * * This is called before either eap_authpeer or eap_authwithpeer. See * link_established() in auth.c. All that's necessary here is to * return to closed state so that those two routines will do the right * thing. */ static void eap_lowerup(int unit) { eap_state *esp = &eap_states[unit]; /* Discard any (possibly authenticated) peer name. */ if (esp->es_server.ea_peer != NULL && esp->es_server.ea_peer != remote_name) free(esp->es_server.ea_peer); esp->es_server.ea_peer = NULL; if (esp->es_client.ea_peer != NULL) free(esp->es_client.ea_peer); esp->es_client.ea_peer = NULL; esp->es_client.ea_state = eapClosed; esp->es_server.ea_state = eapClosed; } /* * eap_lowerdown - The lower layer is now down. * * Cancel all timeouts and return to initial state. */ static void eap_lowerdown(int unit) { eap_state *esp = &eap_states[unit]; if (eap_client_active(esp) && esp->es_client.ea_timeout > 0) { UNTIMEOUT(eap_client_timeout, (void *)esp); } if (eap_server_active(esp)) { if (esp->es_server.ea_timeout > 0) { UNTIMEOUT(eap_server_timeout, (void *)esp); } } else { if ((esp->es_server.ea_state == eapOpen || esp->es_server.ea_state == eapSRP4) && esp->es_rechallenge > 0) { UNTIMEOUT(eap_rechallenge, (void *)esp); } if (esp->es_server.ea_state == eapOpen && esp->es_lwrechallenge > 0) { UNTIMEOUT(srp_lwrechallenge, (void *)esp); } } esp->es_client.ea_state = esp->es_server.ea_state = eapInitial; esp->es_client.ea_requests = esp->es_server.ea_requests = 0; } /* * eap_protrej - Peer doesn't speak this protocol. * * This shouldn't happen. If it does, it represents authentication * failure. */ static void eap_protrej(int unit) { eap_state *esp = &eap_states[unit]; if (eap_client_active(esp)) { error("EAP authentication failed due to Protocol-Reject"); auth_withpeer_fail(unit, PPP_EAP); } if (eap_server_active(esp)) { error("EAP authentication of peer failed on Protocol-Reject"); auth_peer_fail(unit, PPP_EAP); } eap_lowerdown(unit); } /* * Format and send a regular EAP Response message. */ static void eap_send_response(eap_state *esp, u_char id, u_char typenum, u_char *str, int lenstr) { u_char *outp; int msglen; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); esp->es_client.ea_id = id; msglen = EAP_HEADERLEN + sizeof (u_char) + lenstr; PUTSHORT(msglen, outp); PUTCHAR(typenum, outp); if (lenstr > 0) { BCOPY(str, outp, lenstr); } output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); } /* * Format and send an MD5-Challenge EAP Response message. */ static void eap_chap_response(eap_state *esp, u_char id, u_char *hash, char *name, int namelen) { u_char *outp; int msglen; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); esp->es_client.ea_id = id; msglen = EAP_HEADERLEN + 2 * sizeof (u_char) + MD5_DIGEST_LENGTH + namelen; PUTSHORT(msglen, outp); PUTCHAR(EAPT_MD5CHAP, outp); PUTCHAR(MD5_DIGEST_LENGTH, outp); BCOPY(hash, outp, MD5_DIGEST_LENGTH); INCPTR(MD5_DIGEST_LENGTH, outp); if (namelen > 0) { BCOPY(name, outp, namelen); } output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); } #ifdef PPP_WITH_SRP /* * Format and send a SRP EAP Response message. */ static void eap_srp_response(eap_state *esp, u_char id, u_char subtypenum, u_char *str, int lenstr) { u_char *outp; int msglen; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); esp->es_client.ea_id = id; msglen = EAP_HEADERLEN + 2 * sizeof (u_char) + lenstr; PUTSHORT(msglen, outp); PUTCHAR(EAPT_SRP, outp); PUTCHAR(subtypenum, outp); if (lenstr > 0) { BCOPY(str, outp, lenstr); } output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); } /* * Format and send a SRP EAP Client Validator Response message. */ static void eap_srpval_response(eap_state *esp, u_char id, u_int32_t flags, u_char *str) { u_char *outp; int msglen; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); esp->es_client.ea_id = id; msglen = EAP_HEADERLEN + 2 * sizeof (u_char) + sizeof (u_int32_t) + SHA_DIGEST_LENGTH; PUTSHORT(msglen, outp); PUTCHAR(EAPT_SRP, outp); PUTCHAR(EAPSRP_CVALIDATOR, outp); PUTLONG(flags, outp); BCOPY(str, outp, SHA_DIGEST_LENGTH); output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); } #endif /* PPP_WITH_SRP */ #ifdef PPP_WITH_EAPTLS /* * Send an EAP-TLS response message with tls data */ static void eap_tls_response(eap_state *esp, u_char id) { u_char *outp; int outlen; u_char *lenloc; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); lenloc = outp; INCPTR(2, outp); /* If the id in the request is unchanged, we must retransmit the old data */ if(id == esp->es_client.ea_id) eaptls_retransmit(esp->es_client.ea_session, &outp); else eaptls_send(esp->es_client.ea_session, &outp); outlen = (outp - outpacket_buf) - PPP_HDRLEN; PUTSHORT(outlen, lenloc); output(esp->es_unit, outpacket_buf, PPP_HDRLEN + outlen); esp->es_client.ea_id = id; } /* * Send an EAP-TLS ack */ static void eap_tls_sendack(eap_state *esp, u_char id) { u_char *outp; int outlen; u_char *lenloc; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); esp->es_client.ea_id = id; lenloc = outp; INCPTR(2, outp); PUTCHAR(EAPT_TLS, outp); PUTCHAR(0, outp); outlen = (outp - outpacket_buf) - PPP_HDRLEN; PUTSHORT(outlen, lenloc); output(esp->es_unit, outpacket_buf, PPP_HDRLEN + outlen); } #endif /* PPP_WITH_EAPTLS */ static void eap_send_nak(eap_state *esp, u_char id, u_char type) { u_char *outp; int msglen; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); esp->es_client.ea_id = id; msglen = EAP_HEADERLEN + 2 * sizeof (u_char); PUTSHORT(msglen, outp); PUTCHAR(EAPT_NAK, outp); PUTCHAR(type, outp); output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); } #ifdef PPP_WITH_SRP static char * name_of_pn_file(void) { char *user, *path, *file; struct passwd *pw; size_t pl; static bool pnlogged = 0; pw = getpwuid(getuid()); if (pw == NULL || (user = pw->pw_dir) == NULL || user[0] == 0) { errno = EINVAL; return (NULL); } file = PPP_PATH_PSEUDONYM; pl = strlen(user) + strlen(file) + 2; path = malloc(pl); if (path == NULL) return (NULL); (void) slprintf(path, pl, "%s/%s", user, file); if (!pnlogged) { dbglog("pseudonym file: %s", path); pnlogged = 1; } return (path); } static int open_pn_file(mode_t modebits) { char *path; int fd, err; if ((path = name_of_pn_file()) == NULL) return (-1); fd = open(path, modebits, S_IRUSR | S_IWUSR); err = errno; free(path); errno = err; return (fd); } static void remove_pn_file(void) { char *path; if ((path = name_of_pn_file()) != NULL) { (void) unlink(path); (void) free(path); } } static void write_pseudonym(eap_state *esp, u_char *inp, int len, int id) { u_char val; u_char *datp, *digp; PPP_MD_CTX *ctxt; u_char dig[SHA_DIGEST_LENGTH]; int dsize, fd, olen = len, diglen = sizeof(dig); /* * Do the decoding by working backwards. This eliminates the need * to save the decoded output in a separate buffer. */ val = id; while (len > 0) { if ((dsize = len % SHA_DIGEST_LENGTH) == 0) dsize = SHA_DIGEST_LENGTH; len -= dsize; datp = inp + len; ctxt = PPP_MD_CTX_new(); if (ctxt) { PPP_DigestInit(ctxt, PPP_sha1()); PPP_DigestUpdate(ctxt, &val, 1); PPP_DigestUpdate(ctxt, esp->es_client.ea_skey, SESSION_KEY_LEN); if (len > 0) { PPP_DigestUpdate(ctxt, datp, SHA_DIGEST_LENGTH); } else { PPP_DigestUpdate(ctxt, esp->es_client.ea_name, esp->es_client.ea_namelen); } PPP_DigestFinal(ctxt, dig, &diglen); for (digp = dig; digp < dig + SHA_DIGEST_LENGTH; digp++) *datp++ ^= *digp; PPP_MD_CTX_free(ctxt); } } /* Now check that the result is sane */ if (olen <= 0 || *inp + 1 > olen) { dbglog("EAP: decoded pseudonym is unusable <%.*B>", olen, inp); return; } /* Save it away */ fd = open_pn_file(O_WRONLY | O_CREAT | O_TRUNC); if (fd < 0) { dbglog("EAP: error saving pseudonym: %m"); return; } len = write(fd, inp + 1, *inp); if (close(fd) != -1 && len == *inp) { dbglog("EAP: saved pseudonym"); esp->es_usedpseudo = 0; } else { dbglog("EAP: failed to save pseudonym"); remove_pn_file(); } } #endif /* PPP_WITH_SRP */ #if PPP_WITH_CHAPMS /* * Format and send an CHAPV2-Challenge EAP Response message. */ static void eap_chapv2_response(eap_state *esp, u_char id, u_char chapid, u_char *response, char *user, int user_len) { u_char *outp; int msglen; outp = outpacket_buf; MAKEHEADER(outp, PPP_EAP); PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); esp->es_client.ea_id = id; msglen = EAP_HEADERLEN + 6 * sizeof (u_char) + MS_CHAP2_RESPONSE_LEN + user_len; PUTSHORT(msglen, outp); PUTCHAR(EAPT_MSCHAPV2, outp); PUTCHAR(CHAP_RESPONSE, outp); PUTCHAR(chapid, outp); PUTCHAR(0, outp); /* len */ PUTCHAR(5 + user_len + MS_CHAP2_RESPONSE_LEN, outp); BCOPY(response, outp, MS_CHAP2_RESPONSE_LEN+1); // VLEN + VALUE INCPTR(MS_CHAP2_RESPONSE_LEN+1, outp); BCOPY(user, outp, user_len); output(esp->es_unit, outpacket_buf, PPP_HDRLEN + msglen); } #endif /* * eap_request - Receive EAP Request message (client mode). */ static void eap_request(eap_state *esp, u_char *inp, int id, int len) { u_char typenum; u_char vallen; int secret_len; char secret[MAXWORDLEN]; char rhostname[256]; PPP_MD_CTX *mdctx; u_char hash[MD5_DIGEST_LENGTH]; int hashlen = MD5_DIGEST_LENGTH; #ifdef PPP_WITH_EAPTLS u_char flags; struct eaptls_session *ets = esp->es_client.ea_session; #endif /* PPP_WITH_EAPTLS */ #ifdef PPP_WITH_SRP struct t_client *tc; struct t_num sval, gval, Nval, *Ap, Bval; u_char vals[2]; PPP_MD_CTX *ctxt; u_char dig[SHA_DIGEST_LENGTH]; int diglen = sizeof(dig); int fd; #endif /* PPP_WITH_SRP */ /* * Ignore requests if we're not open */ if (esp->es_client.ea_state <= eapClosed) return; /* * Note: we update es_client.ea_id *only if* a Response * message is being generated. Otherwise, we leave it the * same for duplicate detection purposes. */ esp->es_client.ea_requests++; if (esp->es_client.ea_maxrequests != 0 && esp->es_client.ea_requests > esp->es_client.ea_maxrequests) { info("EAP: received too many Request messages"); if (esp->es_client.ea_timeout > 0) { UNTIMEOUT(eap_client_timeout, (void *)esp); } auth_withpeer_fail(esp->es_unit, PPP_EAP); return; } if (len <= 0) { error("EAP: empty Request message discarded"); return; } GETCHAR(typenum, inp); len--; switch (typenum) { case EAPT_IDENTITY: if (len > 0) info("EAP: Identity prompt \"%.*q\"", len, inp); #ifdef PPP_WITH_SRP if (esp->es_usepseudo && (esp->es_usedpseudo == 0 || (esp->es_usedpseudo == 1 && id == esp->es_client.ea_id))) { esp->es_usedpseudo = 1; /* Try to get a pseudonym */ if ((fd = open_pn_file(O_RDONLY)) >= 0) { strcpy(rhostname, SRP_PSEUDO_ID); len = read(fd, rhostname + SRP_PSEUDO_LEN, sizeof (rhostname) - SRP_PSEUDO_LEN); /* XXX NAI unsupported */ if (len > 0) { eap_send_response(esp, id, typenum, rhostname, len + SRP_PSEUDO_LEN); } (void) close(fd); if (len > 0) break; } } /* Stop using pseudonym now. */ if (esp->es_usepseudo && esp->es_usedpseudo != 2) { remove_pn_file(); esp->es_usedpseudo = 2; } #endif /* PPP_WITH_SRP */ eap_send_response(esp, id, typenum, (u_char *)esp->es_client.ea_name, esp->es_client.ea_namelen); break; case EAPT_NOTIFICATION: if (len > 0) info("EAP: Notification \"%.*q\"", len, inp); eap_send_response(esp, id, typenum, NULL, 0); break; case EAPT_NAK: /* * Avoid the temptation to send Response Nak in reply * to Request Nak here. It can only lead to trouble. */ warn("EAP: unexpected Nak in Request; ignored"); /* Return because we're waiting for something real. */ return; case EAPT_MD5CHAP: if (len < 1) { error("EAP: received MD5-Challenge with no data"); /* Bogus request; wait for something real. */ return; } GETCHAR(vallen, inp); len--; if (vallen < 8 || vallen > len) { error("EAP: MD5-Challenge with bad length %d (8..%d)", vallen, len); /* Try something better. */ eap_send_nak(esp, id, EAPT_SRP); break; } /* Not so likely to happen. */ if (len - vallen >= sizeof (rhostname)) { dbglog("EAP: trimming really long peer name down"); BCOPY(inp + vallen, rhostname, sizeof (rhostname) - 1); rhostname[sizeof (rhostname) - 1] = '\0'; } else { BCOPY(inp + vallen, rhostname, len - vallen); rhostname[len - vallen] = '\0'; } /* In case the remote doesn't give us his name. */ if (explicit_remote || (remote_name[0] != '\0' && vallen == len)) strlcpy(rhostname, remote_name, sizeof (rhostname)); /* * Get the secret for authenticating ourselves with * the specified host. */ if (!get_secret(esp->es_unit, esp->es_client.ea_name, rhostname, secret, &secret_len, 0)) { dbglog("EAP: no MD5 secret for auth to %q", rhostname); eap_send_nak(esp, id, EAPT_SRP); break; } mdctx = PPP_MD_CTX_new(); if (mdctx != NULL) { if (PPP_DigestInit(mdctx, PPP_md5())) { typenum = id; if (PPP_DigestUpdate(mdctx, &typenum, 1)) { if (PPP_DigestUpdate(mdctx, secret, secret_len)) { BZERO(secret, sizeof(secret)); if (PPP_DigestUpdate(mdctx, inp, vallen)) { if (PPP_DigestFinal(mdctx, hash, &hashlen)) { eap_chap_response(esp, id, hash, esp->es_client.ea_name, esp->es_client.ea_namelen); PPP_MD_CTX_free(mdctx); break; } } } } } PPP_MD_CTX_free(mdctx); } dbglog("EAP: Invalid MD5 checksum"); eap_send_nak(esp, id, EAPT_SRP); break; #ifdef PPP_WITH_EAPTLS case EAPT_TLS: switch(esp->es_client.ea_state) { case eapListen: if (len < 1) { error("EAP: received EAP-TLS Listen packet with no data"); /* Bogus request; wait for something real. */ return; } GETCHAR(flags, inp); if(flags & EAP_TLS_FLAGS_START){ esp->es_client.ea_using_eaptls = 1; if (explicit_remote){ esp->es_client.ea_peer = strdup(remote_name); esp->es_client.ea_peerlen = strlen(remote_name); } else esp->es_client.ea_peer = NULL; /* Init ssl session */ if(!eaptls_init_ssl_client(esp)) { dbglog("cannot init ssl"); eap_send_nak(esp, id, EAPT_MSCHAPV2); esp->es_client.ea_using_eaptls = 0; break; } ets = esp->es_client.ea_session; eap_tls_response(esp, id); esp->es_client.ea_state = (ets->frag ? eapTlsRecvAck : eapTlsRecv); break; } /* The server has sent a bad start packet. */ eap_send_nak(esp, id, EAPT_MSCHAPV2); break; case eapTlsRecvAck: eap_tls_response(esp, id); esp->es_client.ea_state = (ets->frag ? eapTlsRecvAck : eapTlsRecv); break; case eapTlsRecv: if (len < 1) { error("EAP: discarding EAP-TLS Receive packet with no data"); /* Bogus request; wait for something real. */ return; } eaptls_receive(ets, inp, len); if(ets->frag) { eap_tls_sendack(esp, id); esp->es_client.ea_state = eapTlsRecv; break; } if(ets->alert_recv) { eap_tls_sendack(esp, id); esp->es_client.ea_state = eapTlsRecvFailure; break; } /* Check if TLS handshake is finished */ if(eaptls_is_init_finished(ets)) { #ifdef PPP_WITH_MPPE eaptls_gen_mppe_keys(ets, 1); #endif eaptls_free_session(ets); eap_tls_sendack(esp, id); esp->es_client.ea_state = eapTlsRecvSuccess; break; } eap_tls_response(esp,id); esp->es_client.ea_state = (ets->frag ? eapTlsRecvAck : eapTlsRecv); break; default: eap_send_nak(esp, id, EAPT_MSCHAPV2); esp->es_client.ea_using_eaptls = 0; break; } break; #endif /* PPP_WITH_EAPTLS */ #ifdef PPP_WITH_SRP case EAPT_SRP: if (len < 1) { error("EAP: received empty SRP Request"); /* Bogus request; wait for something real. */ return; } /* Get subtype */ GETCHAR(vallen, inp); len--; switch (vallen) { case EAPSRP_CHALLENGE: tc = NULL; if (esp->es_client.ea_session != NULL) { tc = (struct t_client *)esp->es_client. ea_session; /* * If this is a new challenge, then start * over with a new client session context. * Otherwise, just resend last response. */ if (id != esp->es_client.ea_id) { t_clientclose(tc); esp->es_client.ea_session = NULL; tc = NULL; } } /* No session key just yet */ esp->es_client.ea_skey = NULL; if (tc == NULL) { GETCHAR(vallen, inp); len--; if (vallen >= len) { error("EAP: badly-formed SRP Challenge" " (name)"); /* Ignore badly-formed messages */ return; } BCOPY(inp, rhostname, vallen); rhostname[vallen] = '\0'; INCPTR(vallen, inp); len -= vallen; /* * In case the remote doesn't give us his name, * use configured name. */ if (explicit_remote || (remote_name[0] != '\0' && vallen == 0)) { strlcpy(rhostname, remote_name, sizeof (rhostname)); } if (esp->es_client.ea_peer != NULL) free(esp->es_client.ea_peer); esp->es_client.ea_peer = strdup(rhostname); esp->es_client.ea_peerlen = strlen(rhostname); GETCHAR(vallen, inp); len--; if (vallen >= len) { error("EAP: badly-formed SRP Challenge" " (s)"); /* Ignore badly-formed messages */ return; } sval.data = inp; sval.len = vallen; INCPTR(vallen, inp); len -= vallen; GETCHAR(vallen, inp); len--; if (vallen > len) { error("EAP: badly-formed SRP Challenge" " (g)"); /* Ignore badly-formed messages */ return; } /* If no generator present, then use value 2 */ if (vallen == 0) { gval.data = (u_char *)"\002"; gval.len = 1; } else { gval.data = inp; gval.len = vallen; } INCPTR(vallen, inp); len -= vallen; /* * If no modulus present, then use well-known * value. */ if (len == 0) { Nval.data = (u_char *)wkmodulus; Nval.len = sizeof (wkmodulus); } else { Nval.data = inp; Nval.len = len; } tc = t_clientopen(esp->es_client.ea_name, &Nval, &gval, &sval); if (tc == NULL) { eap_send_nak(esp, id, EAPT_MD5CHAP); break; } esp->es_client.ea_session = (void *)tc; /* Add Challenge ID & type to verifier */ vals[0] = id; vals[1] = EAPT_SRP; t_clientaddexdata(tc, vals, 2); } Ap = t_clientgenexp(tc); eap_srp_response(esp, id, EAPSRP_CKEY, Ap->data, Ap->len); break; case EAPSRP_SKEY: tc = (struct t_client *)esp->es_client.ea_session; if (tc == NULL) { warn("EAP: peer sent Subtype 2 without 1"); eap_send_nak(esp, id, EAPT_MD5CHAP); break; } if (esp->es_client.ea_skey != NULL) { /* * ID number should not change here. Warn * if it does (but otherwise ignore). */ if (id != esp->es_client.ea_id) { warn("EAP: ID changed from %d to %d " "in SRP Subtype 2 rexmit", esp->es_client.ea_id, id); } } else { if (get_srp_secret(esp->es_unit, esp->es_client.ea_name, esp->es_client.ea_peer, secret, 0) == 0) { /* * Can't work with this peer because * the secret is missing. Just give * up. */ eap_send_nak(esp, id, EAPT_MD5CHAP); break; } Bval.data = inp; Bval.len = len; t_clientpasswd(tc, secret); BZERO(secret, sizeof (secret)); esp->es_client.ea_skey = t_clientgetkey(tc, &Bval); if (esp->es_client.ea_skey == NULL) { /* Server is rogue; stop now */ error("EAP: SRP server is rogue"); goto client_failure; } } eap_srpval_response(esp, id, SRPVAL_EBIT, t_clientresponse(tc)); break; case EAPSRP_SVALIDATOR: tc = (struct t_client *)esp->es_client.ea_session; if (tc == NULL || esp->es_client.ea_skey == NULL) { warn("EAP: peer sent Subtype 3 without 1/2"); eap_send_nak(esp, id, EAPT_MD5CHAP); break; } /* * If we're already open, then this ought to be a * duplicate. Otherwise, check that the server is * who we think it is. */ if (esp->es_client.ea_state == eapOpen) { if (id != esp->es_client.ea_id) { warn("EAP: ID changed from %d to %d " "in SRP Subtype 3 rexmit", esp->es_client.ea_id, id); } } else { len -= sizeof (u_int32_t) + SHA_DIGEST_LENGTH; if (len < 0 || t_clientverify(tc, inp + sizeof (u_int32_t)) != 0) { error("EAP: SRP server verification " "failed"); goto client_failure; } GETLONG(esp->es_client.ea_keyflags, inp); /* Save pseudonym if user wants it. */ if (len > 0 && esp->es_usepseudo) { INCPTR(SHA_DIGEST_LENGTH, inp); write_pseudonym(esp, inp, len, id); } } /* * We've verified our peer. We're now mostly done, * except for waiting on the regular EAP Success * message. */ eap_srp_response(esp, id, EAPSRP_ACK, NULL, 0); break; case EAPSRP_LWRECHALLENGE: if (len < 4) { warn("EAP: malformed Lightweight rechallenge"); return; } ctxt = PPP_MD_CTX_new(); if (ctxt) { vals[0] = id; PPP_DigestInit(ctxt, PPP_sha1()); PPP_DigestUpdate(ctxt, vals, 1); PPP_DigestUpdate(ctxt, esp->es_client.ea_skey, SESSION_KEY_LEN); PPP_DigestUpdate(ctxt, inp, len); PPP_DigestUpdate(ctxt, esp->es_client.ea_name, esp->es_client.ea_namelen); PPP_DigestFinal(ctxt, dig, &diglen); PPP_MD_CTX_free(ctxt); eap_srp_response(esp, id, EAPSRP_LWRECHALLENGE, dig, SHA_DIGEST_LENGTH); } break; default: error("EAP: unknown SRP Subtype %d", vallen); eap_send_nak(esp, id, EAPT_MD5CHAP); break; } break; #endif /* PPP_WITH_SRP */ #ifdef PPP_WITH_CHAPMS case EAPT_MSCHAPV2: if (len < 4) { error("EAP: received invalid MSCHAPv2 packet, too short"); return; } unsigned char opcode; GETCHAR(opcode, inp); unsigned char chapid; /* Chapv2-ID */ GETCHAR(chapid, inp); short mssize; GETSHORT(mssize, inp); /* Validate the mssize field */ if (len != mssize) { error("EAP: received invalid MSCHAPv2 packet, invalid length"); return; } len -= 4; /* If MSCHAPv2 digest was not found, NAK the packet */ if (!esp->es_client.digest) { error("EAP MSCHAPv2 not supported"); eap_send_nak(esp, id, EAPT_SRP); return; } switch (opcode) { case CHAP_CHALLENGE: { /* make_response() expects: VLEN + VALUE */ u_char *challenge = inp; unsigned char vsize; GETCHAR(vsize, inp); len -= 1; /* Validate the VALUE field */ if (vsize != MS_CHAP2_PEER_CHAL_LEN || len < MS_CHAP2_PEER_CHAL_LEN) { error("EAP: received invalid MSCHAPv2 packet, invalid value-length: %d", vsize); return; } /* Increment past the VALUE field */ INCPTR(MS_CHAP2_PEER_CHAL_LEN, inp); len -= MS_CHAP2_PEER_CHAL_LEN; /* Extract the hostname */ rhostname[0] = '\0'; if (len > 0) { if (len >= sizeof (rhostname)) { dbglog("EAP: trimming really long peer name down"); len = sizeof(rhostname) - 1; } BCOPY(inp, rhostname, len); rhostname[len] = '\0'; } /* In case the remote doesn't give us his name. */ if (explicit_remote || (remote_name[0] != '\0' && len == 0)) strlcpy(rhostname, remote_name, sizeof(rhostname)); /* Get the secret for authenticating ourselves with the specified host. */ if (!get_secret(esp->es_unit, esp->es_client.ea_name, rhostname, secret, &secret_len, 0)) { dbglog("EAP: no CHAP secret for auth to %q", rhostname); eap_send_nak(esp, id, EAPT_SRP); break; } esp->es_client.ea_namelen = strlen(esp->es_client.ea_name); /* Create the MSCHAPv2 response (and add to cache) */ unsigned char response[MS_CHAP2_RESPONSE_LEN+1]; // VLEN + VALUE esp->es_client.digest->make_response(response, chapid, esp->es_client.ea_name, challenge, secret, secret_len, NULL); eap_chapv2_response(esp, id, chapid, response, esp->es_client.ea_name, esp->es_client.ea_namelen); break; } case CHAP_SUCCESS: { /* Check response for mutual authentication */ u_char status = CHAP_FAILURE; if (esp->es_client.digest->check_success(chapid, inp, len) == 1) { info("Chap authentication succeeded! %.*v", len, inp); status = CHAP_SUCCESS; } eap_send_response(esp, id, EAPT_MSCHAPV2, &status, sizeof(status)); break; } case CHAP_FAILURE: { /* Process the failure string, and log appropriate information */ esp->es_client.digest->handle_failure(inp, len); u_char status = CHAP_FAILURE; eap_send_response(esp, id, EAPT_MSCHAPV2, &status, sizeof(status)); goto client_failure; /* force termination */ } default: error("EAP: received invalid MSCHAPv2 packet, invalid or unsupported opcode: %d", opcode); eap_send_nak(esp, id, EAPT_SRP); } break; #endif /* PPP_WITH_CHAPMS */ #ifdef PPP_WITH_PEAP case EAPT_PEAP: /* Initialize the PEAP context (if not already initialized) */ if (!esp->ea_peap) { rhostname[0] = '\0'; if (explicit_remote || (remote_name[0] != '\0')) { strlcpy(rhostname, remote_name, sizeof (rhostname)); } if (peap_init(&esp->ea_peap, rhostname)) { eap_send_nak(esp, id, EAPT_TLS); break; } } /* Process the PEAP packet */ if (peap_process(esp, id, inp, len)) { eap_send_nak(esp, id, EAPT_TLS); } break; #endif // PPP_WITH_PEAP default: info("EAP: unknown authentication type %d; Naking", typenum); eap_send_nak(esp, id, EAPT_SRP); break; } if (esp->es_client.ea_timeout > 0) { UNTIMEOUT(eap_client_timeout, (void *)esp); TIMEOUT(eap_client_timeout, (void *)esp, esp->es_client.ea_timeout); } return; client_failure: esp->es_client.ea_state = eapBadAuth; if (esp->es_client.ea_timeout > 0) { UNTIMEOUT(eap_client_timeout, (void *)esp); } esp->es_client.ea_session = NULL; #ifdef PPP_WITH_SRP t_clientclose(tc); auth_withpeer_fail(esp->es_unit, PPP_EAP); #endif /* PPP_WITH_SRP */ } /* * eap_response - Receive EAP Response message (server mode). */ static void eap_response(eap_state *esp, u_char *inp, int id, int len) { u_char typenum; u_char vallen; int secret_len; char secret[MAXSECRETLEN]; char rhostname[256]; PPP_MD_CTX *mdctx; u_char hash[MD5_DIGEST_LENGTH]; int hashlen = MD5_DIGEST_LENGTH; #ifdef PPP_WITH_SRP struct t_server *ts; struct t_num A; PPP_MD_CTX *ctxt; u_char dig[SHA_DIGEST_LENGTH]; int diglen = sizeof(dig); #endif /* PPP_WITH_SRP */ #ifdef PPP_WITH_EAPTLS struct eaptls_session *ets; u_char flags; #endif /* PPP_WITH_EAPTLS */ #ifdef PPP_WITH_CHAPMS u_char opcode; chap_verify_hook_fn *chap_verifier; char response_message[256]; #endif /* PPP_WITH_CHAPMS */ /* * Ignore responses if we're not open */ if (esp->es_server.ea_state <= eapClosed) return; if (esp->es_server.ea_id != id) { dbglog("EAP: discarding Response %d; expected ID %d", id, esp->es_server.ea_id); return; } esp->es_server.ea_responses++; if (len <= 0) { error("EAP: empty Response message discarded"); return; } GETCHAR(typenum, inp); len--; switch (typenum) { case EAPT_IDENTITY: if (esp->es_server.ea_state != eapIdentify) { dbglog("EAP discarding unwanted Identify \"%.q\"", len, inp); break; } info("EAP: unauthenticated peer name \"%.*q\"", len, inp); if (esp->es_server.ea_peer != NULL && esp->es_server.ea_peer != remote_name) free(esp->es_server.ea_peer); esp->es_server.ea_peer = malloc(len + 1); if (esp->es_server.ea_peer == NULL) { esp->es_server.ea_peerlen = 0; eap_figure_next_state(esp, 1); break; } BCOPY(inp, esp->es_server.ea_peer, len); esp->es_server.ea_peer[len] = '\0'; esp->es_server.ea_peerlen = len; eap_figure_next_state(esp, 0); break; #ifdef PPP_WITH_EAPTLS case EAPT_TLS: switch(esp->es_server.ea_state) { case eapTlsRecv: ets = (struct eaptls_session *) esp->es_server.ea_session; eap_figure_next_state(esp, eaptls_receive(esp->es_server.ea_session, inp, len)); if(ets->alert_recv) { eap_send_failure(esp); break; } break; case eapTlsRecvAck: if(len > 1) { dbglog("EAP-TLS ACK with extra data"); } eap_figure_next_state(esp, 0); break; case eapTlsRecvClient: /* Receive authentication response from client */ if (len > 0) { GETCHAR(flags, inp); if(len == 1 && !flags) { /* Ack = ok */ #ifdef PPP_WITH_MPPE eaptls_gen_mppe_keys( esp->es_server.ea_session, 0 ); #endif eap_send_success(esp); } else { /* failure */ warn("Server authentication failed"); eap_send_failure(esp); } } else warn("Bogus EAP-TLS packet received from client"); eaptls_free_session(esp->es_server.ea_session); break; case eapTlsRecvAlertAck: eap_send_failure(esp); break; default: eap_figure_next_state(esp, 1); break; } break; #endif /* PPP_WITH_EAPTLS */ case EAPT_NOTIFICATION: dbglog("EAP unexpected Notification; response discarded"); break; case EAPT_NAK: if (len < 1) { info("EAP: Nak Response with no suggested protocol"); eap_figure_next_state(esp, 1); break; } GETCHAR(vallen, inp); len--; if (!explicit_remote && esp->es_server.ea_state == eapIdentify){ /* Peer cannot Nak Identify Request */ eap_figure_next_state(esp, 1); break; } switch (vallen) { case EAPT_SRP: /* Run through SRP validator selection again. */ esp->es_server.ea_state = eapIdentify; eap_figure_next_state(esp, 0); break; case EAPT_MD5CHAP: esp->es_server.ea_state = eapMD5Chall; break; #ifdef PPP_WITH_EAPTLS /* Send EAP-TLS start packet */ case EAPT_TLS: esp->es_server.ea_state = eapTlsStart; break; #endif /* PPP_WITH_EAPTLS */ #ifdef PPP_WITH_CHAPMS case EAPT_MSCHAPV2: info("EAP: peer proposes MSCHAPv2"); /* If MSCHAPv2 digest was not found, NAK the packet */ if (!esp->es_server.digest) { error("EAP MSCHAPv2 not supported"); eap_send_nak(esp, id, EAPT_SRP); break; } esp->es_server.ea_state = eapMSCHAPv2Chall; break; #endif /* PPP_WITH_CHAPMS */ default: dbglog("EAP: peer requesting unknown Type %d", vallen); switch (esp->es_server.ea_state) { case eapSRP1: case eapSRP2: case eapSRP3: esp->es_server.ea_state = eapMD5Chall; break; case eapMD5Chall: case eapSRP4: esp->es_server.ea_state = eapIdentify; eap_figure_next_state(esp, 0); break; default: break; } break; } break; case EAPT_MD5CHAP: if (esp->es_server.ea_state != eapMD5Chall) { error("EAP: unexpected MD5-Response"); eap_figure_next_state(esp, 1); break; } if (len < 1) { error("EAP: received MD5-Response with no data"); eap_figure_next_state(esp, 1); break; } GETCHAR(vallen, inp); len--; if (vallen != 16 || vallen > len) { error("EAP: MD5-Response with bad length %d", vallen); eap_figure_next_state(esp, 1); break; } /* Not so likely to happen. */ if (len - vallen >= sizeof (rhostname)) { dbglog("EAP: trimming really long peer name down"); BCOPY(inp + vallen, rhostname, sizeof (rhostname) - 1); rhostname[sizeof (rhostname) - 1] = '\0'; } else { BCOPY(inp + vallen, rhostname, len - vallen); rhostname[len - vallen] = '\0'; } /* In case the remote doesn't give us his name. */ if (explicit_remote || (remote_name[0] != '\0' && vallen == len)) strlcpy(rhostname, remote_name, sizeof (rhostname)); /* * Get the secret for authenticating the specified * host. */ if (!get_secret(esp->es_unit, rhostname, esp->es_server.ea_name, secret, &secret_len, 1)) { dbglog("EAP: no MD5 secret for auth of %q", rhostname); eap_send_failure(esp); break; } mdctx = PPP_MD_CTX_new(); if (mdctx != NULL) { if (PPP_DigestInit(mdctx, PPP_md5())) { if (PPP_DigestUpdate(mdctx, &esp->es_server.ea_id, 1)) { if (PPP_DigestUpdate(mdctx, &secret, secret_len)) { BZERO(secret, sizeof(secret)); if (PPP_DigestUpdate(mdctx, esp->es_challenge, esp->es_challen)) { if (PPP_DigestFinal(mdctx, hash, &hashlen)) { if (BCMP(hash, inp, MD5_DIGEST_LENGTH) == 0) { esp->es_server.ea_type = EAPT_MD5CHAP; eap_send_success(esp); eap_figure_next_state(esp, 0); if (esp->es_rechallenge != 0) { TIMEOUT(eap_rechallenge, esp, esp->es_rechallenge); } PPP_MD_CTX_free(mdctx); break; } } } } } } PPP_MD_CTX_free(mdctx); } eap_send_failure(esp); break; #ifdef PPP_WITH_CHAPMS case EAPT_MSCHAPV2: if (len < 1) { error("EAP: received MSCHAPv2 with no data"); eap_figure_next_state(esp, 1); break; } GETCHAR(opcode, inp); len--; switch (opcode) { case CHAP_RESPONSE: if (esp->es_server.ea_state != eapMSCHAPv2Chall) { error("EAP: unexpected MSCHAPv2-Response"); eap_figure_next_state(esp, 1); break; } /* skip MS ID + len */ INCPTR(3, inp); GETCHAR(vallen, inp); len -= 4; if (vallen != MS_CHAP2_RESPONSE_LEN || vallen > len) { error("EAP: Invalid MSCHAPv2-Response " "length %d", vallen); eap_figure_next_state(esp, 1); break; } /* Not so likely to happen. */ if (len - vallen >= sizeof (rhostname)) { dbglog("EAP: trimming really long peer name down"); BCOPY(inp + vallen, rhostname, sizeof (rhostname) - 1); rhostname[sizeof (rhostname) - 1] = '\0'; } else { BCOPY(inp + vallen, rhostname, len - vallen); rhostname[len - vallen] = '\0'; } /* In case the remote doesn't give us his name. */ if (explicit_remote || (remote_name[0] != '\0' && vallen == len)) strlcpy(rhostname, remote_name, sizeof (rhostname)); /* strip the MS domain name */ if (chapms_strip_domain && strrchr(rhostname, '\\')) { char tmp[MAXNAMELEN+1]; strcpy(tmp, strrchr(rhostname, '\\') + 1); strlcpy(rhostname, tmp, sizeof(rhostname)); } if (chap_verify_hook) chap_verifier = chap_verify_hook; else chap_verifier = eap_chap_verify_response; esp->es_server.ea_id += 1; if ((*chap_verifier)(rhostname, esp->es_server.ea_name, id, esp->es_server.digest, esp->es_challenge, inp - 1, response_message, sizeof(response_message))) { info("EAP: MSCHAPv2 success for peer %q", rhostname); esp->es_server.ea_type = EAPT_MSCHAPV2; eap_chapms2_send_request(esp, esp->es_server.ea_id, CHAP_SUCCESS, esp->es_server.ea_id, response_message, strlen(response_message)); eap_figure_next_state(esp, 0); if (esp->es_rechallenge != 0) TIMEOUT(eap_rechallenge, esp, esp->es_rechallenge); } else { warn("EAP: MSCHAPv2 failure for peer %q", rhostname); eap_chapms2_send_request(esp, esp->es_server.ea_id, CHAP_FAILURE, esp->es_server.ea_id, response_message, strlen(response_message)); } break; case CHAP_SUCCESS: info("EAP: MSCHAPv2 success confirmed"); break; case CHAP_FAILURE: info("EAP: MSCHAPv2 failure confirmed"); break; default: error("EAP: Unhandled MSCHAPv2 opcode %d", opcode); eap_send_nak(esp, id, EAPT_SRP); } break; #endif /* PPP_WITH_CHAPMS */ #ifdef PPP_WITH_SRP case EAPT_SRP: if (len < 1) { error("EAP: empty SRP Response"); eap_figure_next_state(esp, 1); break; } GETCHAR(typenum, inp); len--; switch (typenum) { case EAPSRP_CKEY: if (esp->es_server.ea_state != eapSRP1) { error("EAP: unexpected SRP Subtype 1 Response"); eap_figure_next_state(esp, 1); break; } A.data = inp; A.len = len; ts = (struct t_server *)esp->es_server.ea_session; assert(ts != NULL); esp->es_server.ea_skey = t_servergetkey(ts, &A); if (esp->es_server.ea_skey == NULL) { /* Client's A value is bogus; terminate now */ error("EAP: bogus A value from client"); eap_send_failure(esp); } else { eap_figure_next_state(esp, 0); } break; case EAPSRP_CVALIDATOR: if (esp->es_server.ea_state != eapSRP2) { error("EAP: unexpected SRP Subtype 2 Response"); eap_figure_next_state(esp, 1); break; } if (len < sizeof (u_int32_t) + SHA_DIGEST_LENGTH) { error("EAP: M1 length %d < %d", len, sizeof (u_int32_t) + SHA_DIGEST_LENGTH); eap_figure_next_state(esp, 1); break; } GETLONG(esp->es_server.ea_keyflags, inp); ts = (struct t_server *)esp->es_server.ea_session; assert(ts != NULL); if (t_serververify(ts, inp)) { info("EAP: unable to validate client identity"); eap_send_failure(esp); break; } eap_figure_next_state(esp, 0); break; case EAPSRP_ACK: if (esp->es_server.ea_state != eapSRP3) { error("EAP: unexpected SRP Subtype 3 Response"); eap_send_failure(esp); break; } esp->es_server.ea_type = EAPT_SRP; eap_send_success(esp); eap_figure_next_state(esp, 0); if (esp->es_rechallenge != 0) TIMEOUT(eap_rechallenge, esp, esp->es_rechallenge); if (esp->es_lwrechallenge != 0) TIMEOUT(srp_lwrechallenge, esp, esp->es_lwrechallenge); break; case EAPSRP_LWRECHALLENGE: if (esp->es_server.ea_state != eapSRP4) { info("EAP: unexpected SRP Subtype 4 Response"); return; } if (len != SHA_DIGEST_LENGTH) { error("EAP: bad Lightweight rechallenge " "response"); return; } ctxt = PPP_MD_CTX_new(); if (ctxt) { vallen = id; PPP_DigestInit(ctxt, PPP_sha1()); PPP_DigestUpdate(ctxt, &vallen, 1); PPP_DigestUpdate(ctxt, esp->es_server.ea_skey, SESSION_KEY_LEN); PPP_DigestUpdate(ctxt, esp->es_challenge, esp->es_challen); PPP_DigestUpdate(ctxt, esp->es_server.ea_peer, esp->es_server.ea_peerlen); PPP_DigestFinal(ctxt, dig, &diglen); PPP_MD_CTX_free(ctxt); if (BCMP(dig, inp, SHA_DIGEST_LENGTH) != 0) { error("EAP: failed Lightweight rechallenge"); eap_send_failure(esp); break; } esp->es_server.ea_state = eapOpen; if (esp->es_lwrechallenge != 0) TIMEOUT(srp_lwrechallenge, esp, esp->es_lwrechallenge); } break; } break; #endif /* PPP_WITH_SRP */ default: /* This can't happen. */ error("EAP: unknown Response type %d; ignored", typenum); return; } if (esp->es_server.ea_timeout > 0) { UNTIMEOUT(eap_server_timeout, (void *)esp); } if (esp->es_server.ea_state != eapBadAuth && esp->es_server.ea_state != eapOpen) { esp->es_server.ea_id++; eap_send_request(esp); } } /* * eap_success - Receive EAP Success message (client mode). */ static void eap_success(eap_state *esp, u_char *inp, int id, int len) { if (esp->es_client.ea_state != eapOpen && !eap_client_active(esp) #ifdef PPP_WITH_EAPTLS && esp->es_client.ea_state != eapTlsRecvSuccess #endif /* PPP_WITH_EAPTLS */ ) { dbglog("EAP unexpected success message in state %s (%d)", eap_state_name(esp->es_client.ea_state), esp->es_client.ea_state); return; } #ifdef PPP_WITH_EAPTLS if(esp->es_client.ea_using_eaptls && esp->es_client.ea_state != eapTlsRecvSuccess) { dbglog("EAP-TLS unexpected success message in state %s (%d)", eap_state_name(esp->es_client.ea_state), esp->es_client.ea_state); return; } #endif /* PPP_WITH_EAPTLS */ if (esp->es_client.ea_timeout > 0) { UNTIMEOUT(eap_client_timeout, (void *)esp); } if (len > 0) { /* This is odd. The spec doesn't allow for this. */ PRINTMSG(inp, len); } #ifdef PPP_WITH_PEAP peap_finish(&esp->ea_peap); #endif esp->es_client.ea_state = eapOpen; auth_withpeer_success(esp->es_unit, PPP_EAP, 0); } /* * eap_failure - Receive EAP Failure message (client mode). */ static void eap_failure(eap_state *esp, u_char *inp, int id, int len) { /* * Ignore failure messages if we're not open */ if (esp->es_client.ea_state <= eapClosed) return; if (!eap_client_active(esp)) { dbglog("EAP unexpected failure message in state %s (%d)", eap_state_name(esp->es_client.ea_state), esp->es_client.ea_state); } if (esp->es_client.ea_timeout > 0) { UNTIMEOUT(eap_client_timeout, (void *)esp); } if (len > 0) { /* This is odd. The spec doesn't allow for this. */ PRINTMSG(inp, len); } esp->es_client.ea_state = eapBadAuth; error("EAP: peer reports authentication failure"); #ifdef PPP_WITH_PEAP peap_finish(&esp->ea_peap); #endif auth_withpeer_fail(esp->es_unit, PPP_EAP); } /* * eap_input - Handle received EAP message. */ static void eap_input(int unit, u_char *inp, int inlen) { eap_state *esp = &eap_states[unit]; u_char code, id; int len; /* * Parse header (code, id and length). If packet too short, * drop it. */ if (inlen < EAP_HEADERLEN) { error("EAP: packet too short: %d < %d", inlen, EAP_HEADERLEN); return; } GETCHAR(code, inp); GETCHAR(id, inp); GETSHORT(len, inp); if (len < EAP_HEADERLEN || len > inlen) { error("EAP: packet has illegal length field %d (%d..%d)", len, EAP_HEADERLEN, inlen); return; } len -= EAP_HEADERLEN; /* Dispatch based on message code */ switch (code) { case EAP_REQUEST: eap_request(esp, inp, id, len); break; case EAP_RESPONSE: eap_response(esp, inp, id, len); break; case EAP_SUCCESS: eap_success(esp, inp, id, len); break; case EAP_FAILURE: eap_failure(esp, inp, id, len); break; default: /* XXX Need code reject */ /* Note: it's not legal to send EAP Nak here. */ warn("EAP: unknown code %d received", code); break; } } /* * eap_printpkt - print the contents of an EAP packet. */ static char *eap_codenames[] = { "Request", "Response", "Success", "Failure" }; static char *eap_typenames[] = { "Identity", "Notification", "Nak", "MD5-Challenge", "OTP", "Generic-Token", NULL, NULL, "RSA", "DSS", "KEA", "KEA-Validate", "TLS", "Defender", "Windows 2000", "Arcot", "Cisco", "Nokia", "SRP", NULL, "TTLS", "RAS", "AKA", "3COM", "PEAP", "MSCHAPv2" }; static int eap_printpkt(u_char *inp, int inlen, void (*printer) (void *, char *, ...), void *arg) { int code, id, len, rtype, vallen; u_char *pstart; u_int32_t uval; #ifdef PPP_WITH_EAPTLS u_char flags; #endif /* PPP_WITH_EAPTLS */ #ifdef PPP_WITH_CHAPMS u_char opcode; #endif /* PPP_WITH_CHAPMS */ if (inlen < EAP_HEADERLEN) return (0); pstart = inp; GETCHAR(code, inp); GETCHAR(id, inp); GETSHORT(len, inp); if (len < EAP_HEADERLEN || len > inlen) return (0); if (code >= 1 && code <= sizeof(eap_codenames) / sizeof(char *)) printer(arg, " %s", eap_codenames[code-1]); else printer(arg, " code=0x%x", code); printer(arg, " id=0x%x", id); len -= EAP_HEADERLEN; switch (code) { case EAP_REQUEST: if (len < 1) { printer(arg, " "); break; } GETCHAR(rtype, inp); len--; if (rtype >= 1 && rtype <= sizeof (eap_typenames) / sizeof (char *)) printer(arg, " %s", eap_typenames[rtype-1]); else printer(arg, " type=0x%x", rtype); switch (rtype) { case EAPT_IDENTITY: case EAPT_NOTIFICATION: if (len > 0) { printer(arg, " "); INCPTR(len, inp); len = 0; } else { printer(arg, " "); } break; case EAPT_MD5CHAP: if (len <= 0) break; GETCHAR(vallen, inp); len--; if (vallen > len) goto truncated; printer(arg, " ", vallen, inp); INCPTR(vallen, inp); len -= vallen; if (len > 0) { printer(arg, " "); INCPTR(len, inp); len = 0; } else { printer(arg, " "); } break; #ifdef PPP_WITH_CHAPMS case EAPT_MSCHAPV2: if (len <= 0) break; GETCHAR(opcode, inp); len--; switch (opcode) { case CHAP_CHALLENGE: INCPTR(3, inp); len -= 3; GETCHAR(vallen, inp); len--; if (vallen > len) goto truncated; len -= vallen; printer(arg, " Challenge <"); for (; vallen > 0; --vallen) { u_char val; GETCHAR(val, inp); printer(arg, "%.2x", val); } printer(arg, ">"); if (len > 0) { printer(arg, ", "); INCPTR(len, inp); len = 0; } else { printer(arg, ", "); } break; case CHAP_SUCCESS: INCPTR(3, inp); len -= 3; printer(arg, " Success "); break; case CHAP_FAILURE: INCPTR(3, inp); len -= 3; printer(arg, " Failure "); break; default: INCPTR(3, inp); len -= 3; printer(arg, " opcode=0x%x <%.*B>", opcode, len, inp); break; } break; #endif /* PPP_WITH_CHAPMS */ #ifdef PPP_WITH_EAPTLS case EAPT_TLS: if (len < 1) break; GETCHAR(flags, inp); len--; if(flags == 0 && len == 0){ printer(arg, " Ack"); break; } printer(arg, flags & EAP_TLS_FLAGS_LI ? " L":" -"); printer(arg, flags & EAP_TLS_FLAGS_MF ? "M":"-"); printer(arg, flags & EAP_TLS_FLAGS_START ? "S":"- "); break; #endif /* PPP_WITH_EAPTLS */ #ifdef PPP_WITH_SRP case EAPT_SRP: if (len < 3) goto truncated; GETCHAR(vallen, inp); len--; printer(arg, "-%d", vallen); switch (vallen) { case EAPSRP_CHALLENGE: GETCHAR(vallen, inp); len--; if (vallen >= len) goto truncated; if (vallen > 0) { printer(arg, " "); } else { printer(arg, " "); } INCPTR(vallen, inp); len -= vallen; GETCHAR(vallen, inp); len--; if (vallen >= len) goto truncated; printer(arg, " ", vallen, inp); INCPTR(vallen, inp); len -= vallen; GETCHAR(vallen, inp); len--; if (vallen > len) goto truncated; if (vallen == 0) { printer(arg, " "); } else { printer(arg, " ", vallen, inp); } INCPTR(vallen, inp); len -= vallen; if (len == 0) { printer(arg, " "); } else { printer(arg, " ", len, inp); INCPTR(len, inp); len = 0; } break; case EAPSRP_SKEY: printer(arg, " ", len, inp); INCPTR(len, inp); len = 0; break; case EAPSRP_SVALIDATOR: if (len < sizeof (u_int32_t)) break; GETLONG(uval, inp); len -= sizeof (u_int32_t); if (uval & SRPVAL_EBIT) { printer(arg, " E"); uval &= ~SRPVAL_EBIT; } if (uval != 0) { printer(arg, " f<%X>", uval); } if ((vallen = len) > SHA_DIGEST_LENGTH) vallen = SHA_DIGEST_LENGTH; printer(arg, " ", len, inp, len < SHA_DIGEST_LENGTH ? "?" : ""); INCPTR(vallen, inp); len -= vallen; if (len > 0) { printer(arg, " ", len, inp); INCPTR(len, inp); len = 0; } break; case EAPSRP_LWRECHALLENGE: printer(arg, " ", len, inp); INCPTR(len, inp); len = 0; break; } break; #endif /* PPP_WITH_SRP */ } break; case EAP_RESPONSE: if (len < 1) break; GETCHAR(rtype, inp); len--; if (rtype >= 1 && rtype <= sizeof (eap_typenames) / sizeof (char *)) printer(arg, " %s", eap_typenames[rtype-1]); else printer(arg, " type=0x%x", rtype); switch (rtype) { case EAPT_IDENTITY: if (len > 0) { printer(arg, " "); INCPTR(len, inp); len = 0; } break; #ifdef PPP_WITH_EAPTLS case EAPT_TLS: if (len < 1) break; GETCHAR(flags, inp); len--; if(flags == 0 && len == 0){ printer(arg, " Ack"); break; } printer(arg, flags & EAP_TLS_FLAGS_LI ? " L":" -"); printer(arg, flags & EAP_TLS_FLAGS_MF ? "M":"-"); printer(arg, flags & EAP_TLS_FLAGS_START ? "S":"- "); break; #endif /* PPP_WITH_EAPTLS */ case EAPT_NAK: if (len <= 0) { printer(arg, " "); break; } GETCHAR(rtype, inp); len--; printer(arg, " = 1 && rtype <= sizeof (eap_typenames) / sizeof (char *)) printer(arg, " (%s)", eap_typenames[rtype-1]); printer(arg, ">"); break; case EAPT_MD5CHAP: if (len <= 0) { printer(arg, " "); break; } GETCHAR(vallen, inp); len--; if (vallen > len) goto truncated; printer(arg, " ", vallen, inp); INCPTR(vallen, inp); len -= vallen; if (len > 0) { printer(arg, " "); INCPTR(len, inp); len = 0; } else { printer(arg, " "); } break; #ifdef PPP_WITH_CHAPMS case EAPT_MSCHAPV2: if (len <= 0) break; GETCHAR(opcode, inp); len--; switch (opcode) { case CHAP_RESPONSE: INCPTR(3, inp); len -= 3; GETCHAR(vallen, inp); len--; if (vallen > len) goto truncated; len -= vallen; printer(arg, " Response <"); for (; vallen > 0; --vallen) { u_char val; GETCHAR(val, inp); printer(arg, "%.2x", val); } printer(arg, ">"); if (len > 0) { printer(arg, ", "); INCPTR(len, inp); len = 0; } else { printer(arg, ", "); } break; case CHAP_SUCCESS: printer(arg, " Success"); break; case CHAP_FAILURE: printer(arg, " Failure"); break; default: printer(arg, " opcode=0x%x <%.*B>", opcode, len, inp); break; } break; #endif /* PPP_WITH_CHAPMS */ #ifdef PPP_WITH_SRP case EAPT_SRP: if (len < 1) goto truncated; GETCHAR(vallen, inp); len--; printer(arg, "-%d", vallen); switch (vallen) { case EAPSRP_CKEY: printer(arg, " ", len, inp); INCPTR(len, inp); len = 0; break; case EAPSRP_CVALIDATOR: if (len < sizeof (u_int32_t)) break; GETLONG(uval, inp); len -= sizeof (u_int32_t); if (uval & SRPVAL_EBIT) { printer(arg, " E"); uval &= ~SRPVAL_EBIT; } if (uval != 0) { printer(arg, " f<%X>", uval); } printer(arg, " ", len, inp, len == SHA_DIGEST_LENGTH ? "" : "?"); INCPTR(len, inp); len = 0; break; case EAPSRP_ACK: break; case EAPSRP_LWRECHALLENGE: printer(arg, " ", len, inp, len == SHA_DIGEST_LENGTH ? "" : "?"); if ((vallen = len) > SHA_DIGEST_LENGTH) vallen = SHA_DIGEST_LENGTH; INCPTR(vallen, inp); len -= vallen; break; } break; #endif /* PPP_WITH_SRP */ } break; case EAP_SUCCESS: /* No payload expected for these! */ case EAP_FAILURE: break; truncated: printer(arg, " "); break; } if (len > 8) printer(arg, "%8B...", inp); else if (len > 0) printer(arg, "%.*B", len, inp); INCPTR(len, inp); return (inp - pstart); }