| /* |
| * chap-new.c - New CHAP implementation. |
| * |
| * Copyright (c) 2003 Paul Mackerras. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. The name(s) of the authors of this software must not be used to |
| * endorse or promote products derived from this software without |
| * prior written permission. |
| * |
| * 3. Redistributions of any form whatsoever must retain the following |
| * acknowledgment: |
| * "This product includes software developed by Paul Mackerras |
| * <paulus@samba.org>". |
| * |
| * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO |
| * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
| * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY |
| * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN |
| * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
| * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| #define RCSID "$Id: chap-new.c,v 1.6 2004/11/04 10:02:26 paulus Exp $" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include "pppd.h" |
| #include "chap-new.h" |
| #include "chap-md5.h" |
| |
| #ifdef CHAPMS |
| #include "chap_ms.h" |
| #define MDTYPE_ALL (MDTYPE_MICROSOFT_V2 | MDTYPE_MICROSOFT | MDTYPE_MD5) |
| #else |
| #define MDTYPE_ALL (MDTYPE_MD5) |
| #endif |
| |
| int chap_mdtype_all = MDTYPE_ALL; |
| |
| /* Hook for a plugin to validate CHAP challenge */ |
| int (*chap_verify_hook)(char *name, char *ourname, int id, |
| struct chap_digest_type *digest, |
| unsigned char *challenge, unsigned char *response, |
| char *message, int message_space) = NULL; |
| |
| /* |
| * Option variables. |
| */ |
| int chap_timeout_time = 3; |
| int chap_max_transmits = 10; |
| int chap_rechallenge_time = 0; |
| |
| /* |
| * Command-line options. |
| */ |
| static option_t chap_option_list[] = { |
| { "chap-restart", o_int, &chap_timeout_time, |
| "Set timeout for CHAP", OPT_PRIO }, |
| { "chap-max-challenge", o_int, &chap_max_transmits, |
| "Set max #xmits for challenge", OPT_PRIO }, |
| { "chap-interval", o_int, &chap_rechallenge_time, |
| "Set interval for rechallenge", OPT_PRIO }, |
| { NULL } |
| }; |
| |
| /* |
| * Internal state. |
| */ |
| static struct chap_client_state { |
| int flags; |
| char *name; |
| struct chap_digest_type *digest; |
| unsigned char priv[64]; /* private area for digest's use */ |
| } client; |
| |
| /* |
| * These limits apply to challenge and response packets we send. |
| * The +4 is the +1 that we actually need rounded up. |
| */ |
| #define CHAL_MAX_PKTLEN (PPP_HDRLEN + CHAP_HDRLEN + 4 + MAX_CHALLENGE_LEN + MAXNAMELEN) |
| #define RESP_MAX_PKTLEN (PPP_HDRLEN + CHAP_HDRLEN + 4 + MAX_RESPONSE_LEN + MAXNAMELEN) |
| |
| static struct chap_server_state { |
| int flags; |
| int id; |
| char *name; |
| struct chap_digest_type *digest; |
| int challenge_xmits; |
| int challenge_pktlen; |
| unsigned char challenge[CHAL_MAX_PKTLEN]; |
| } server; |
| |
| /* Values for flags in chap_client_state and chap_server_state */ |
| #define LOWERUP 1 |
| #define AUTH_STARTED 2 |
| #define AUTH_DONE 4 |
| #define AUTH_FAILED 8 |
| #define TIMEOUT_PENDING 0x10 |
| #define CHALLENGE_VALID 0x20 |
| |
| /* |
| * Prototypes. |
| */ |
| static void chap_init(int unit); |
| static void chap_lowerup(int unit); |
| static void chap_lowerdown(int unit); |
| static void chap_timeout(void *arg); |
| static void chap_generate_challenge(struct chap_server_state *ss); |
| static void chap_handle_response(struct chap_server_state *ss, int code, |
| unsigned char *pkt, int len); |
| static int 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); |
| static void chap_respond(struct chap_client_state *cs, int id, |
| unsigned char *pkt, int len); |
| static void chap_handle_status(struct chap_client_state *cs, int code, int id, |
| unsigned char *pkt, int len); |
| static void chap_protrej(int unit); |
| static void chap_input(int unit, unsigned char *pkt, int pktlen); |
| static int chap_print_pkt(unsigned char *p, int plen, |
| void (*printer) __P((void *, char *, ...)), void *arg); |
| |
| /* List of digest types that we know about */ |
| static struct chap_digest_type *chap_digests; |
| |
| /* |
| * chap_init - reset to initial state. |
| */ |
| static void |
| chap_init(int unit) |
| { |
| memset(&client, 0, sizeof(client)); |
| memset(&server, 0, sizeof(server)); |
| |
| chap_md5_init(); |
| #ifdef CHAPMS |
| chapms_init(); |
| #endif |
| } |
| |
| /* |
| * Add a new digest type to the list. |
| */ |
| void |
| chap_register_digest(struct chap_digest_type *dp) |
| { |
| dp->next = chap_digests; |
| chap_digests = dp; |
| } |
| |
| /* |
| * chap_lowerup - we can start doing stuff now. |
| */ |
| static void |
| chap_lowerup(int unit) |
| { |
| struct chap_client_state *cs = &client; |
| struct chap_server_state *ss = &server; |
| |
| cs->flags |= LOWERUP; |
| ss->flags |= LOWERUP; |
| if (ss->flags & AUTH_STARTED) |
| chap_timeout(ss); |
| } |
| |
| static void |
| chap_lowerdown(int unit) |
| { |
| struct chap_client_state *cs = &client; |
| struct chap_server_state *ss = &server; |
| |
| cs->flags = 0; |
| if (ss->flags & TIMEOUT_PENDING) |
| UNTIMEOUT(chap_timeout, ss); |
| ss->flags = 0; |
| } |
| |
| /* |
| * chap_auth_peer - Start authenticating the peer. |
| * If the lower layer is already up, we start sending challenges, |
| * otherwise we wait for the lower layer to come up. |
| */ |
| void |
| chap_auth_peer(int unit, char *our_name, int digest_code) |
| { |
| struct chap_server_state *ss = &server; |
| struct chap_digest_type *dp; |
| |
| if (ss->flags & AUTH_STARTED) { |
| error("CHAP: peer authentication already started!"); |
| return; |
| } |
| for (dp = chap_digests; dp != NULL; dp = dp->next) |
| if (dp->code == digest_code) |
| break; |
| if (dp == NULL) |
| fatal("CHAP digest 0x%x requested but not available", |
| digest_code); |
| |
| ss->digest = dp; |
| ss->name = our_name; |
| /* Start with a random ID value */ |
| ss->id = (unsigned char)(drand48() * 256); |
| ss->flags |= AUTH_STARTED; |
| if (ss->flags & LOWERUP) |
| chap_timeout(ss); |
| } |
| |
| /* |
| * chap_auth_with_peer - Prepare to authenticate ourselves to the peer. |
| * There isn't much to do until we receive a challenge. |
| */ |
| void |
| chap_auth_with_peer(int unit, char *our_name, int digest_code) |
| { |
| struct chap_client_state *cs = &client; |
| struct chap_digest_type *dp; |
| |
| if (cs->flags & AUTH_STARTED) { |
| error("CHAP: authentication with peer already started!"); |
| return; |
| } |
| for (dp = chap_digests; dp != NULL; dp = dp->next) |
| if (dp->code == digest_code) |
| break; |
| if (dp == NULL) |
| fatal("CHAP digest 0x%x requested but not available", |
| digest_code); |
| |
| cs->digest = dp; |
| cs->name = our_name; |
| cs->flags |= AUTH_STARTED; |
| } |
| |
| /* |
| * chap_timeout - It's time to send another challenge to the peer. |
| * This could be either a retransmission of a previous challenge, |
| * or a new challenge to start re-authentication. |
| */ |
| static void |
| chap_timeout(void *arg) |
| { |
| struct chap_server_state *ss = arg; |
| |
| ss->flags &= ~TIMEOUT_PENDING; |
| if ((ss->flags & CHALLENGE_VALID) == 0) { |
| ss->challenge_xmits = 0; |
| chap_generate_challenge(ss); |
| ss->flags |= CHALLENGE_VALID; |
| } else if (ss->challenge_xmits >= chap_max_transmits) { |
| ss->flags &= ~CHALLENGE_VALID; |
| ss->flags |= AUTH_DONE | AUTH_FAILED; |
| auth_peer_fail(0, PPP_CHAP); |
| return; |
| } |
| |
| output(0, ss->challenge, ss->challenge_pktlen); |
| ++ss->challenge_xmits; |
| ss->flags |= TIMEOUT_PENDING; |
| TIMEOUT(chap_timeout, arg, chap_timeout_time); |
| } |
| |
| /* |
| * chap_generate_challenge - generate a challenge string and format |
| * the challenge packet in ss->challenge_pkt. |
| */ |
| static void |
| chap_generate_challenge(struct chap_server_state *ss) |
| { |
| int clen = 1, nlen, len; |
| unsigned char *p; |
| |
| p = ss->challenge; |
| MAKEHEADER(p, PPP_CHAP); |
| p += CHAP_HDRLEN; |
| ss->digest->generate_challenge(p); |
| clen = *p; |
| nlen = strlen(ss->name); |
| memcpy(p + 1 + clen, ss->name, nlen); |
| |
| len = CHAP_HDRLEN + 1 + clen + nlen; |
| ss->challenge_pktlen = PPP_HDRLEN + len; |
| |
| p = ss->challenge + PPP_HDRLEN; |
| p[0] = CHAP_CHALLENGE; |
| p[1] = ++ss->id; |
| p[2] = len >> 8; |
| p[3] = len; |
| } |
| |
| /* |
| * chap_handle_response - check the response to our challenge. |
| */ |
| static void |
| chap_handle_response(struct chap_server_state *ss, int id, |
| unsigned char *pkt, int len) |
| { |
| int response_len, ok, mlen; |
| unsigned char *response, *p; |
| char *name = NULL; /* initialized to shut gcc up */ |
| int (*verifier)(char *, char *, int, struct chap_digest_type *, |
| unsigned char *, unsigned char *, char *, int); |
| char rname[MAXNAMELEN+1]; |
| char message[256]; |
| |
| if ((ss->flags & LOWERUP) == 0) |
| return; |
| if (id != ss->challenge[PPP_HDRLEN+1] || len < 2) |
| return; |
| if ((ss->flags & AUTH_DONE) == 0) { |
| if ((ss->flags & CHALLENGE_VALID) == 0) |
| return; |
| response = pkt; |
| GETCHAR(response_len, pkt); |
| len -= response_len + 1; /* length of name */ |
| name = (char *)pkt + response_len; |
| if (len < 0) |
| return; |
| |
| ss->flags &= ~CHALLENGE_VALID; |
| if (ss->flags & TIMEOUT_PENDING) { |
| ss->flags &= ~TIMEOUT_PENDING; |
| UNTIMEOUT(chap_timeout, ss); |
| } |
| |
| if (explicit_remote) { |
| name = remote_name; |
| } else { |
| /* Null terminate and clean remote name. */ |
| slprintf(rname, sizeof(rname), "%.*v", len, name); |
| name = rname; |
| } |
| |
| if (chap_verify_hook) |
| verifier = chap_verify_hook; |
| else |
| verifier = chap_verify_response; |
| ok = (*verifier)(name, ss->name, id, ss->digest, |
| ss->challenge + PPP_HDRLEN + CHAP_HDRLEN, |
| response, message, sizeof(message)); |
| if (!ok || !auth_number()) { |
| ss->flags |= AUTH_FAILED; |
| warn("Peer %q failed CHAP authentication", name); |
| } |
| } |
| |
| /* send the response */ |
| p = outpacket_buf; |
| MAKEHEADER(p, PPP_CHAP); |
| mlen = strlen(message); |
| len = CHAP_HDRLEN + mlen; |
| p[0] = (ss->flags & AUTH_FAILED)? CHAP_FAILURE: CHAP_SUCCESS; |
| p[1] = id; |
| p[2] = len >> 8; |
| p[3] = len; |
| if (mlen > 0) |
| memcpy(p + CHAP_HDRLEN, message, mlen); |
| output(0, outpacket_buf, PPP_HDRLEN + len); |
| |
| if ((ss->flags & AUTH_DONE) == 0) { |
| ss->flags |= AUTH_DONE; |
| if (ss->flags & AUTH_FAILED) { |
| auth_peer_fail(0, PPP_CHAP); |
| } else { |
| auth_peer_success(0, PPP_CHAP, ss->digest->code, |
| name, strlen(name)); |
| if (chap_rechallenge_time) { |
| ss->flags |= TIMEOUT_PENDING; |
| TIMEOUT(chap_timeout, ss, |
| chap_rechallenge_time); |
| } |
| } |
| } |
| } |
| |
| /* |
| * 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 |
| 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; |
| } |
| |
| /* |
| * chap_respond - Generate and send a response to a challenge. |
| */ |
| static void |
| chap_respond(struct chap_client_state *cs, int id, |
| unsigned char *pkt, int len) |
| { |
| int clen, nlen; |
| int secret_len; |
| unsigned char *p; |
| unsigned char response[RESP_MAX_PKTLEN]; |
| char rname[MAXNAMELEN+1]; |
| char secret[MAXSECRETLEN+1]; |
| |
| if ((cs->flags & (LOWERUP | AUTH_STARTED)) != (LOWERUP | AUTH_STARTED)) |
| return; /* not ready */ |
| if (len < 2 || len < pkt[0] + 1) |
| return; /* too short */ |
| clen = pkt[0]; |
| nlen = len - (clen + 1); |
| |
| /* Null terminate and clean remote name. */ |
| slprintf(rname, sizeof(rname), "%.*v", nlen, pkt + clen + 1); |
| |
| /* Microsoft doesn't send their name back in the PPP packet */ |
| if (explicit_remote || (remote_name[0] != 0 && rname[0] == 0)) |
| strlcpy(rname, remote_name, sizeof(rname)); |
| |
| /* get secret for authenticating ourselves with the specified host */ |
| if (!get_secret(0, cs->name, rname, secret, &secret_len, 0)) { |
| secret_len = 0; /* assume null secret if can't find one */ |
| warn("No CHAP secret found for authenticating us to %q", rname); |
| } |
| |
| p = response; |
| MAKEHEADER(p, PPP_CHAP); |
| p += CHAP_HDRLEN; |
| |
| cs->digest->make_response(p, id, cs->name, pkt, |
| secret, secret_len, cs->priv); |
| memset(secret, 0, secret_len); |
| |
| clen = *p; |
| nlen = strlen(cs->name); |
| memcpy(p + clen + 1, cs->name, nlen); |
| |
| p = response + PPP_HDRLEN; |
| len = CHAP_HDRLEN + clen + 1 + nlen; |
| p[0] = CHAP_RESPONSE; |
| p[1] = id; |
| p[2] = len >> 8; |
| p[3] = len; |
| |
| output(0, response, PPP_HDRLEN + len); |
| } |
| |
| static void |
| chap_handle_status(struct chap_client_state *cs, int code, int id, |
| unsigned char *pkt, int len) |
| { |
| const char *msg = NULL; |
| |
| if ((cs->flags & (AUTH_DONE|AUTH_STARTED|LOWERUP)) |
| != (AUTH_STARTED|LOWERUP)) |
| return; |
| cs->flags |= AUTH_DONE; |
| |
| if (code == CHAP_SUCCESS) { |
| /* used for MS-CHAP v2 mutual auth, yuck */ |
| if (cs->digest->check_success != NULL) { |
| if (!(*cs->digest->check_success)(pkt, len, cs->priv)) |
| code = CHAP_FAILURE; |
| } else |
| msg = "CHAP authentication succeeded"; |
| } else { |
| if (cs->digest->handle_failure != NULL) |
| (*cs->digest->handle_failure)(pkt, len); |
| else |
| msg = "CHAP authentication failed"; |
| } |
| if (msg) { |
| if (len > 0) |
| info("%s: %.*v", msg, len, pkt); |
| else |
| info("%s", msg); |
| } |
| if (code == CHAP_SUCCESS) |
| auth_withpeer_success(0, PPP_CHAP, cs->digest->code); |
| else { |
| cs->flags |= AUTH_FAILED; |
| auth_withpeer_fail(0, PPP_CHAP); |
| } |
| } |
| |
| static void |
| chap_input(int unit, unsigned char *pkt, int pktlen) |
| { |
| struct chap_client_state *cs = &client; |
| struct chap_server_state *ss = &server; |
| unsigned char code, id; |
| int len; |
| |
| if (pktlen < CHAP_HDRLEN) |
| return; |
| GETCHAR(code, pkt); |
| GETCHAR(id, pkt); |
| GETSHORT(len, pkt); |
| if (len < CHAP_HDRLEN || len > pktlen) |
| return; |
| len -= CHAP_HDRLEN; |
| |
| switch (code) { |
| case CHAP_CHALLENGE: |
| chap_respond(cs, id, pkt, len); |
| break; |
| case CHAP_RESPONSE: |
| chap_handle_response(ss, id, pkt, len); |
| break; |
| case CHAP_FAILURE: |
| case CHAP_SUCCESS: |
| chap_handle_status(cs, code, id, pkt, len); |
| break; |
| } |
| } |
| |
| static void |
| chap_protrej(int unit) |
| { |
| struct chap_client_state *cs = &client; |
| struct chap_server_state *ss = &server; |
| |
| if (ss->flags & TIMEOUT_PENDING) { |
| ss->flags &= ~TIMEOUT_PENDING; |
| UNTIMEOUT(chap_timeout, ss); |
| } |
| if (ss->flags & AUTH_STARTED) { |
| ss->flags = 0; |
| auth_peer_fail(0, PPP_CHAP); |
| } |
| if ((cs->flags & (AUTH_STARTED|AUTH_DONE)) == AUTH_STARTED) { |
| cs->flags &= ~AUTH_STARTED; |
| auth_withpeer_fail(0, PPP_CHAP); |
| } |
| } |
| |
| /* |
| * chap_print_pkt - print the contents of a CHAP packet. |
| */ |
| static char *chap_code_names[] = { |
| "Challenge", "Response", "Success", "Failure" |
| }; |
| |
| static int |
| chap_print_pkt(unsigned char *p, int plen, |
| void (*printer) __P((void *, char *, ...)), void *arg) |
| { |
| int code, id, len; |
| int clen, nlen; |
| unsigned char x; |
| |
| if (plen < CHAP_HDRLEN) |
| return 0; |
| GETCHAR(code, p); |
| GETCHAR(id, p); |
| GETSHORT(len, p); |
| if (len < CHAP_HDRLEN || len > plen) |
| return 0; |
| |
| if (code >= 1 && code <= sizeof(chap_code_names) / sizeof(char *)) |
| printer(arg, " %s", chap_code_names[code-1]); |
| else |
| printer(arg, " code=0x%x", code); |
| printer(arg, " id=0x%x", id); |
| len -= CHAP_HDRLEN; |
| switch (code) { |
| case CHAP_CHALLENGE: |
| case CHAP_RESPONSE: |
| if (len < 1) |
| break; |
| clen = p[0]; |
| if (len < clen + 1) |
| break; |
| ++p; |
| nlen = len - clen - 1; |
| printer(arg, " <"); |
| for (; clen > 0; --clen) { |
| GETCHAR(x, p); |
| printer(arg, "%.2x", x); |
| } |
| printer(arg, ">, name = "); |
| print_string((char *)p, nlen, printer, arg); |
| break; |
| case CHAP_FAILURE: |
| case CHAP_SUCCESS: |
| printer(arg, " "); |
| print_string((char *)p, len, printer, arg); |
| break; |
| default: |
| for (clen = len; clen > 0; --clen) { |
| GETCHAR(x, p); |
| printer(arg, " %.2x", x); |
| } |
| } |
| |
| return len + CHAP_HDRLEN; |
| } |
| |
| struct protent chap_protent = { |
| PPP_CHAP, |
| chap_init, |
| chap_input, |
| chap_protrej, |
| chap_lowerup, |
| chap_lowerdown, |
| NULL, /* open */ |
| NULL, /* close */ |
| chap_print_pkt, |
| NULL, /* datainput */ |
| 1, /* enabled_flag */ |
| "CHAP", /* name */ |
| NULL, /* data_name */ |
| chap_option_list, |
| NULL, /* check_options */ |
| }; |