/* * Copyright (c) 2001-2002 Packet Design, LLC. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, * use and redistribution of this software, in source or object code * forms, with or without modifications are expressly permitted by * Packet Design; provided, however, that: * * (i) Any and all reproductions of the source or object code * must include the copyright notice above and the following * disclaimer of warranties; and * (ii) No rights are granted, in any manner or form, to use * Packet Design trademarks, including the mark "PACKET DESIGN" * on advertising, endorsements, or otherwise except as such * appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE, * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * * Author: Archie Cobbs */ #include "ppp/ppp_defs.h" #include "ppp/ppp_log.h" #include "ppp/ppp_fsm_option.h" #include "ppp/ppp_fsm.h" #include "ppp/ppp_auth.h" #include "ppp/ppp_link.h" #include "ppp/ppp_util.h" #include "ppp/ppp_auth_chap.h" #define CHAP_MTYPE "ppp_authtype.chap" #define CHAP_RETRY 3 #define CHAP_MAXTRY 5 #define CHAP_CHALLENGE 1 #define CHAP_RESPONSE 2 #define CHAP_ACK 3 #define CHAP_NAK 4 #define CHAP_MSG_ACK "Authorization successful" #define CHAP_MSG_NAK "Authorization failed" #define CHAP_MSG_BUFSIZE 256 #define MSCHAPV1_MSG_ACK CHAP_MSG_ACK #define MSCHAPV1_MSG_NAK "E=691 R=0" #define MSCHAPV2_MSG_ACK CHAP_MSG_ACK #define MSCHAPV2_MSG_NAK CHAP_MSG_NAK /* CHAP info structure */ struct ppp_auth_chap { struct ppp_link *link; struct ppp_log *log; struct ppp_auth_config aconf; const struct ppp_auth_type *auth; struct ppp_auth_cred cred; struct ppp_auth_resp resp; const struct ppp_auth_chap_type *type; struct pevent_ctx *ev_ctx; struct pevent *timer; pthread_mutex_t *mutex; int dir; int retry; u_char id; }; /* Internal functions */ static void ppp_auth_chap_send_challenge(struct ppp_auth_chap *chap); static void ppp_auth_chap_send_response(struct ppp_auth_chap *chap); static void ppp_auth_chap_send_result(struct ppp_auth_chap *chap, u_char id, int ack); static int ppp_chap_unpack(const u_char *data, size_t len, char *name, u_char *value, int *vlenp); static void ppp_chap_send_value(struct ppp_auth_chap *chap, u_char code, const u_char *value, size_t vlen, const char *name); static ppp_link_auth_finish_t ppp_auth_chap_acquire_finish; static ppp_link_auth_finish_t ppp_auth_chap_check_finish; static pevent_handler_t ppp_auth_chap_timeout; /* Internal variables */ static const char *chap_codes[] = { "zero", "challenge", "response", "ack", "nak" }; /* Macro for logging */ #define LOG(sev, fmt, args...) PPP_LOG(chap->log, sev, fmt , ## args) /* * Start CHAP */ void * ppp_auth_chap_start(struct pevent_ctx *ev_ctx, struct ppp_link *link, pthread_mutex_t *mutex, int dir, u_int16_t *protop, struct ppp_log *log) { struct ppp_auth_chap *chap; /* Create info structure */ if ((chap = MALLOC(CHAP_MTYPE, sizeof(*chap))) == NULL) return (NULL); memset(chap, 0, sizeof(*chap)); chap->ev_ctx = ev_ctx; chap->mutex = mutex; chap->link = link; chap->log = log; chap->dir = dir; chap->retry = CHAP_MAXTRY; /* Get link auth config and auth type */ chap->aconf = *ppp_link_auth_get_config(link); chap->auth = ppp_link_get_auth(link, dir); switch (chap->auth->index) { case PPP_AUTH_CHAP_MD5: chap->type = &ppp_auth_chap_md5; break; case PPP_AUTH_CHAP_MSV1: chap->type = &ppp_auth_chap_msv1; break; case PPP_AUTH_CHAP_MSV2: chap->type = &ppp_auth_chap_msv2; break; default: errno = EPROTONOSUPPORT; FREE(CHAP_MTYPE, chap); return (NULL); } chap->cred.type = chap->auth->index; /* Return protocol */ *protop = PPP_PROTO_CHAP; /* If sending auth, wait for peer's challenge */ if (dir == PPP_PEER) return (chap); /* If receiving auth, send first challenge */ ppp_auth_chap_send_challenge(chap); /* Done */ return (chap); } /* * Cancel CHAP */ void ppp_auth_chap_cancel(void *arg) { struct ppp_auth_chap *chap = arg; pevent_unregister(&chap->timer); ppp_log_close(&chap->log); FREE(CHAP_MTYPE, chap); } /* * Handle timeout event. */ static void ppp_auth_chap_timeout(void *arg) { struct ppp_auth_chap *chap = arg; /* Logging */ LOG(LOG_DEBUG, "%s timeout", chap->dir == PPP_SELF ? chap_codes[CHAP_CHALLENGE] : chap_codes[CHAP_RESPONSE]); /* Cancel timeout event */ pevent_unregister(&chap->timer); /* Send another challenge or response? */ if (chap->retry <= 0) { ppp_link_auth_complete(chap->link, chap->dir, NULL, NULL); return; } /* Send challenge or response again */ if (chap->dir == PPP_SELF) ppp_auth_chap_send_challenge(chap); else ppp_auth_chap_send_response(chap); } /* * Handle CHAP input */ void ppp_auth_chap_input(void *arg, int dir, void *data, size_t len) { struct ppp_auth_chap *chap = arg; struct ppp_fsm_pkt *const pkt = data; u_char value[PPP_MAX_AUTHVALUE]; char name[PPP_MAX_AUTHNAME]; int vlen; if (len < sizeof(*pkt)) return; memcpy(pkt, data, sizeof(*pkt)); pkt->length = ntohs(pkt->length); if (pkt->length > len) return; if (pkt->length < len) len = pkt->length; len -= sizeof(*pkt); switch (pkt->code) { case CHAP_CHALLENGE: { struct ppp_auth_cred_chap *const cred = &chap->cred.u.chap; /* Check direction */ if (dir != PPP_PEER) break; /* Logging */ LOG(LOG_DEBUG, "rec'd %s #%u", chap_codes[pkt->code], pkt->id); /* Parse out packet contents */ if (ppp_chap_unpack(pkt->data, len, name, value, &vlen) == -1) { LOG(LOG_NOTICE, "rec'd malformed %s", chap_codes[pkt->code]); break; } #ifdef notyet /* Don't respond to our own outstanding challenge */ /* * Don't respond to a challenge that looks like it came from * us and has the wrong origination value embedded in it. This * avoids a security hole associated with using the same CHAP * password to authenticate in both directions on a link. */ #endif /* Check challenge length (fixed for MS-CHAP types) */ if (chap->type->cfixed && vlen != chap->type->clen) { LOG(LOG_NOTICE, "wrong %s length %u != %u" " for %s", chap_codes[pkt->code], vlen, chap->type->clen, chap->auth->name); break; } /* Ignore if already handling a previous challenge */ if (ppp_link_auth_in_progress(chap->link, chap->dir)) { LOG(LOG_DEBUG, "ignoring packet, action pending"); break; } /* Partially fill in credentials based on challenge info */ memset(cred, 0, sizeof(*cred)); strlcpy(cred->name, name, sizeof(cred->name)); /* XXX */ cred->chal_len = vlen; memcpy(cred->chal_data, value, cred->chal_len); if (chap->type->set_id != NULL) (*chap->type->set_id)(cred, pkt->id); if (chap->auth->index == PPP_AUTH_CHAP_MSV2) { if (ppp_util_random(cred->u.msv2.peer_chal, sizeof(cred->u.msv2.peer_chal)) == -1) { LOG(LOG_NOTICE, "%s: %m", "ppp_util_random"); break; } } /* Acquire credentials */ if (ppp_link_authorize(chap->link, chap->dir, &chap->cred, ppp_auth_chap_acquire_finish) == -1) { ppp_link_auth_complete(chap->link, chap->dir, NULL, NULL); break; } /* Save peer's id for my response */ chap->id = pkt->id; /* Now wait for credentials acquisition to finish */ break; } case CHAP_RESPONSE: { struct ppp_auth_cred_chap *const cred = &chap->cred.u.chap; int pvlen; /* Check direction */ if (dir != PPP_SELF) break; /* Logging */ LOG(LOG_DEBUG, "rec'd %s #%u", chap_codes[pkt->code], pkt->id); /* Stop timer */ pevent_unregister(&chap->timer); /* Ignore if already checking a previous response */ if (ppp_link_auth_in_progress(chap->link, chap->dir)) { LOG(LOG_DEBUG, "ignoring packet, action pending"); break; } /* Fill out peer credentials using response packet */ if (ppp_chap_unpack(pkt->data, len, cred->name, (u_char *)&cred->u + chap->type->roff, &pvlen) == -1) { LOG(LOG_NOTICE, "rec'd malformed %s", chap_codes[pkt->code]); break; } if (chap->type->set_id != NULL) (*chap->type->set_id)(cred, pkt->id); /* Check credentials */ if (ppp_link_authorize(chap->link, chap->dir, &chap->cred, ppp_auth_chap_check_finish) == -1) { ppp_auth_chap_send_result(chap, pkt->id, 0); ppp_link_auth_complete(chap->link, chap->dir, NULL, NULL); break; } /* Save peer's id for my response */ chap->id = pkt->id; /* Now wait for check to finish */ break; } case CHAP_ACK: case CHAP_NAK: { int valid = (pkt->code == CHAP_ACK); /* Check direction */ if (dir != PPP_PEER) break; /* Logging */ LOG(LOG_DEBUG, "rec'd %s #%u", chap_codes[pkt->code], pkt->id); /* Stop timer */ pevent_unregister(&chap->timer); /* Do final stuff */ if ((*chap->type->final)(&chap->cred.u.chap, chap->log, valid, pkt->data, len, chap->resp.authresp) == -1) { LOG(LOG_NOTICE, "invalid CHAP %s", chap_codes[pkt->code]); valid = 0; } /* Finish up */ if (valid) { ppp_link_auth_complete(chap->link, chap->dir, &chap->cred, &chap->resp.mppe); } else { ppp_link_auth_complete(chap->link, chap->dir, NULL, NULL); } break; } default: break; } } /* * Continue after a successful credentials acquisition. */ static void ppp_auth_chap_acquire_finish(void *arg, const struct ppp_auth_cred *creds, const struct ppp_auth_resp *resp) { struct ppp_auth_chap *const chap = arg; struct ppp_auth_cred_chap *const cred = &chap->cred.u.chap; /* Copy credentials */ chap->cred = *creds; /* Sanitize credentials */ cred->name[sizeof(cred->name) - 1] = '\0'; cred->chal_len = MAX(cred->chal_len, sizeof(cred->chal_data)); /* Send response */ ppp_auth_chap_send_response(chap); } /* * Continue after a successful credentials check. */ static void ppp_auth_chap_check_finish(void *arg, const struct ppp_auth_cred *creds, const struct ppp_auth_resp *resp) { struct ppp_auth_chap *const chap = arg; struct ppp_auth_cred_chap *const cred = &chap->cred.u.chap; int valid = (*resp->errmsg == '\0'); /* Copy response */ chap->resp = *resp; /* Report validity */ if (valid) { LOG(LOG_INFO, "rec'd %s credentials for \"%s\"", "valid", cred->name); } else { LOG(LOG_NOTICE, "rec'd %s credentials for \"%s\": %s", "invalid", cred->name, resp->errmsg); } /* Send result */ ppp_auth_chap_send_result(chap, chap->id, valid); /* Finish up */ ppp_link_auth_complete(chap->link, chap->dir, valid ? &chap->cred : NULL, &chap->resp.mppe); } /* * Send a CHAP challenge */ static void ppp_auth_chap_send_challenge(struct ppp_auth_chap *chap) { struct ppp_auth_cred_chap *cred = &chap->cred.u.chap; /* Create a challenge (first time only) */ if (chap->retry == CHAP_MAXTRY) { /* Generate random challenge bytes */ if (ppp_util_random(cred->chal_data, chap->type->clen) == -1) { LOG(LOG_ERR, "%s: %m", "ppp_util_random"); return; } cred->chal_len = chap->type->clen; /* Set id field (if appropriate) */ if (chap->type->set_id != NULL) (*chap->type->set_id)(&chap->cred.u.chap, ++chap->id); } /* Send packet */ ppp_chap_send_value(chap, CHAP_CHALLENGE, cred->chal_data, cred->chal_len, cred->name); } /* * Send a CHAP response. */ static void ppp_auth_chap_send_response(struct ppp_auth_chap *chap) { struct ppp_auth_cred_chap *const cred = &chap->cred.u.chap; u_char value[PPP_MAX_AUTHVALUE]; /* Send response */ memcpy((u_char *)&cred->u + chap->type->roff, value, chap->type->rlen); ppp_chap_send_value(chap, CHAP_RESPONSE, value, chap->type->rlen, cred->name); } /* * Send a challenge or response packet. */ static void ppp_chap_send_value(struct ppp_auth_chap *chap, u_char code, const u_char *value, size_t vlen, const char *name) { union { u_char buf[sizeof(struct ppp_fsm_pkt) + 1 + PPP_MAX_AUTHVALUE + PPP_MAX_AUTHNAME]; struct ppp_fsm_pkt pkt; } u; struct ppp_fsm_pkt *const pkt = &u.pkt; /* Cancel previous timeout event (if any) and start another */ pevent_unregister(&chap->timer); if (pevent_register(chap->ev_ctx, &chap->timer, 0, chap->mutex, ppp_auth_chap_timeout, chap, PEVENT_TIME, CHAP_RETRY * 1000) == -1) LOG(LOG_ERR, "%s: %m", "pevent_register"); /* Construct packet */ pkt->id = chap->id; pkt->code = code; pkt->length = htons(sizeof(*pkt) + 1 + vlen + strlen(name)); pkt->data[0] = vlen; memcpy(pkt->data + 1, value, vlen); memcpy(pkt->data + 1 + vlen, name, strlen(name)); /* Logging */ LOG(LOG_DEBUG, "xmit %s #%u", chap_codes[code], chap->id); /* Send packet */ ppp_link_write(chap->link, PPP_PROTO_CHAP, pkt, ntohs(pkt->length)); /* Decrement retry counter */ chap->retry--; } /* * Send a CHAP result */ static void ppp_auth_chap_send_result(struct ppp_auth_chap *chap, u_char id, int ack) { union { u_char buf[sizeof(struct ppp_fsm_pkt) + CHAP_MSG_BUFSIZE]; struct ppp_fsm_pkt pkt; } u; struct ppp_fsm_pkt *const pkt = &u.pkt; struct ppp_auth_cred_chap *cred = &chap->cred.u.chap; int i; /* Construct packet */ pkt->id = id; pkt->code = ack ? CHAP_ACK : CHAP_NAK; /* Add response string */ switch (chap->auth->index) { case PPP_AUTH_CHAP_MSV1: strlcpy(pkt->data, ack ? MSCHAPV1_MSG_ACK : MSCHAPV1_MSG_NAK, CHAP_MSG_BUFSIZE); break; case PPP_AUTH_CHAP_MSV2: if (ack) { char hex[(PPP_MSOFTV2_AUTHRESP_LEN * 2) + 1]; for (i = 0; i < PPP_MSOFTV2_AUTHRESP_LEN; i++) { sprintf(hex + (i * 2), "%02X", chap->resp.authresp[i]); } snprintf(pkt->data, CHAP_MSG_BUFSIZE, "S=%s", hex); } else { char cbuf[(2 * PPP_MSOFTV2_CHAL_LEN) + 1]; for (i = 0; i < PPP_MSOFTV2_CHAL_LEN; i++) { sprintf(cbuf + (2 * i), "%02X", cred->u.msv2.peer_chal[i]); } snprintf(pkt->data, CHAP_MSG_BUFSIZE, "E=691 R=0 C=%s V=3 M=%s", cbuf, MSCHAPV2_MSG_NAK); } break; default: strlcpy(pkt->data, ack ? CHAP_MSG_ACK : CHAP_MSG_NAK, CHAP_MSG_BUFSIZE); break; } pkt->length = htons(sizeof(*pkt) + strlen(pkt->data)); /* Logging */ LOG(LOG_DEBUG, "xmit %s #%u", chap_codes[pkt->code], chap->id); /* Send packet */ ppp_link_write(chap->link, PPP_PROTO_CHAP, pkt, ntohs(pkt->length)); } /* * Decode a CHAP challenge or response packet. */ static int ppp_chap_unpack(const u_char *data, size_t len, char *name, u_char *value, int *vlenp) { int nlen; int vlen; /* Check well-formedness */ if (len < 1 || (vlen = data[0]) < 1 || vlen > PPP_MAX_AUTHVALUE || (nlen = len - vlen - 1) < 0 || nlen > PPP_MAX_AUTHNAME - 1) return (-1); /* Get stuff */ memcpy(name, data + 1 + vlen, nlen); name[nlen] = '\0'; memcpy(value, data + 1, vlen); *vlenp = vlen; return (0); }