]> git.ozlabs.org Git - ppp.git/blobdiff - pppd/chap.c
Corrected Linux MPPE kernel modules (Frank Cusack)
[ppp.git] / pppd / chap.c
index 189eb20ecc2de6ebbf5a22359c39fd3365e037fe..a9dc78cdc91d887feb06e1bd6c36fd39e2b86332 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * chap.c - Challenge Handshake Authentication Protocol.
+ * chap_ms.c - Challenge Handshake Authentication Protocol.
  *
  * Copyright (c) 1993 The Australian National University.
  * All rights reserved.
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
-#ifndef lint
-static char rcsid[] = "$Id: chap.c,v 1.15 1997/11/27 06:07:48 paulus Exp $";
-#endif
+#define RCSID  "$Id: chap.c,v 1.31 2002/04/02 14:15:07 dfs Exp $"
 
 /*
  * TODO:
  */
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/time.h>
-#include <syslog.h>
 
 #include "pppd.h"
 #include "chap.h"
@@ -54,6 +52,51 @@ static char rcsid[] = "$Id: chap.c,v 1.15 1997/11/27 06:07:48 paulus Exp $";
 #include "chap_ms.h"
 #endif
 
+/* Hook for a plugin to say if we can possibly authenticate a peer using CHAP */
+int (*chap_check_hook) __P((void)) = NULL;
+
+/* Hook for a plugin to get the CHAP password for authenticating us */
+int (*chap_passwd_hook) __P((char *user, char *passwd)) = NULL;
+
+/* Hook for a plugin to validate CHAP challenge */
+int (*chap_auth_hook) __P((char *user,
+                          u_char *remmd,
+                          int remmd_len,
+                          chap_state *cstate)) = NULL;
+
+static const char rcsid[] = RCSID;
+
+#ifdef CHAPMS
+/* For MPPE debug */
+/* Use "[]|}{?/><,`!2&&(" (sans quotes) for RFC 3079 MS-CHAPv2 test value */
+static char *mschap_challenge = NULL;
+/* Use "!@\#$%^&*()_+:3|~" (sans quotes, backslash is to escape #) for ... */
+static char *mschap2_peer_challenge = NULL;
+#endif
+
+/*
+ * Command-line options.
+ */
+static option_t chap_option_list[] = {
+    { "chap-restart", o_int, &chap[0].timeouttime,
+      "Set timeout for CHAP", OPT_PRIO },
+    { "chap-max-challenge", o_int, &chap[0].max_transmits,
+      "Set max #xmits for challenge", OPT_PRIO },
+    { "chap-interval", o_int, &chap[0].chal_interval,
+      "Set interval for rechallenge", OPT_PRIO },
+#ifdef MSLANMAN
+    { "ms-lanman", o_bool, &ms_lanman,
+      "Use LanMan passwd when using MS-CHAP", 1 },
+#endif
+#ifdef DEBUGMPPEKEY
+    { "mschap-challenge", o_string, &mschap_challenge,
+      "specify CHAP challenge" },
+    { "mschap2-peer-challenge", o_string, &mschap2_peer_challenge,
+      "specify CHAP peer challenge" },
+#endif
+    { NULL }
+};
+
 /*
  * Protocol entry points.
  */
@@ -79,6 +122,8 @@ struct protent chap_protent = {
     1,
     "CHAP",
     NULL,
+    chap_option_list,
+    NULL,
     NULL,
     NULL
 };
@@ -143,7 +188,7 @@ ChapAuthWithPeer(unit, our_name, digest)
 
     /*
      * We get here as a result of LCP coming up.
-     * So even if CHAP was open before, we will 
+     * So even if CHAP was open before, we will
      * have to re-authenticate ourselves.
      */
     cstate->clientstate = CHAPCS_LISTEN;
@@ -160,7 +205,7 @@ ChapAuthPeer(unit, our_name, digest)
     int digest;
 {
     chap_state *cstate = &chap[unit];
-  
+
     cstate->chal_name = our_name;
     cstate->chal_type = digest;
 
@@ -185,7 +230,7 @@ ChapChallengeTimeout(arg)
     void *arg;
 {
     chap_state *cstate = (chap_state *) arg;
-  
+
     /* if we aren't sending challenges, don't worry.  then again we */
     /* probably shouldn't be here either */
     if (cstate->serverstate != CHAPSS_INITIAL_CHAL &&
@@ -194,7 +239,7 @@ ChapChallengeTimeout(arg)
 
     if (cstate->chal_transmits >= cstate->max_transmits) {
        /* give up on peer */
-       syslog(LOG_ERR, "Peer failed to respond to CHAP challenge");
+       error("Peer failed to respond to CHAP challenge");
        cstate->serverstate = CHAPSS_BADAUTH;
        auth_peer_fail(cstate->unit, PPP_CHAP);
        return;
@@ -250,7 +295,7 @@ ChapLowerUp(unit)
     int unit;
 {
     chap_state *cstate = &chap[unit];
-  
+
     if (cstate->clientstate == CHAPCS_INITIAL)
        cstate->clientstate = CHAPCS_CLOSED;
     else if (cstate->clientstate == CHAPCS_PENDING)
@@ -276,7 +321,7 @@ ChapLowerDown(unit)
     int unit;
 {
     chap_state *cstate = &chap[unit];
-  
+
     /* Timeout(s) pending?  Cancel if so. */
     if (cstate->serverstate == CHAPSS_INITIAL_CHAL ||
        cstate->serverstate == CHAPSS_RECHALLENGE)
@@ -324,29 +369,29 @@ ChapInput(unit, inpacket, packet_len)
     u_char *inp;
     u_char code, id;
     int len;
-  
+
     /*
      * Parse header (code, id and length).
      * If packet too short, drop it.
      */
     inp = inpacket;
     if (packet_len < CHAP_HEADERLEN) {
-       CHAPDEBUG((LOG_INFO, "ChapInput: rcvd short header."));
+       CHAPDEBUG(("ChapInput: rcvd short header."));
        return;
     }
     GETCHAR(code, inp);
     GETCHAR(id, inp);
     GETSHORT(len, inp);
     if (len < CHAP_HEADERLEN) {
-       CHAPDEBUG((LOG_INFO, "ChapInput: rcvd illegal length."));
+       CHAPDEBUG(("ChapInput: rcvd illegal length."));
        return;
     }
     if (len > packet_len) {
-       CHAPDEBUG((LOG_INFO, "ChapInput: rcvd short packet."));
+       CHAPDEBUG(("ChapInput: rcvd short packet."));
        return;
     }
     len -= CHAP_HEADERLEN;
-  
+
     /*
      * Action depends on code (as in fact it usually does :-).
      */
@@ -354,11 +399,11 @@ ChapInput(unit, inpacket, packet_len)
     case CHAP_CHALLENGE:
        ChapReceiveChallenge(cstate, inp, id, len);
        break;
-    
+
     case CHAP_RESPONSE:
        ChapReceiveResponse(cstate, inp, id, len);
        break;
-    
+
     case CHAP_FAILURE:
        ChapReceiveFailure(cstate, inp, id, len);
        break;
@@ -368,7 +413,7 @@ ChapInput(unit, inpacket, packet_len)
        break;
 
     default:                           /* Need code reject? */
-       syslog(LOG_WARNING, "Unknown CHAP code (%d) received.", code);
+       warn("Unknown CHAP code (%d) received.", code);
        break;
     }
 }
@@ -391,24 +436,22 @@ ChapReceiveChallenge(cstate, inp, id, len)
     char rhostname[256];
     MD5_CTX mdContext;
     u_char hash[MD5_SIGNATURE_SIZE];
-    CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: Rcvd id %d.", id));
+
     if (cstate->clientstate == CHAPCS_CLOSED ||
        cstate->clientstate == CHAPCS_PENDING) {
-       CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: in state %d",
-                  cstate->clientstate));
+       CHAPDEBUG(("ChapReceiveChallenge: in state %d", cstate->clientstate));
        return;
     }
 
     if (len < 2) {
-       CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: rcvd short packet."));
+       CHAPDEBUG(("ChapReceiveChallenge: rcvd short packet."));
        return;
     }
 
     GETCHAR(rchallenge_len, inp);
     len -= sizeof (u_char) + rchallenge_len;   /* now name field length */
     if (len < 0) {
-       CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: rcvd short packet."));
+       CHAPDEBUG(("ChapReceiveChallenge: rcvd short packet."));
        return;
     }
     rchallenge = inp;
@@ -419,14 +462,10 @@ ChapReceiveChallenge(cstate, inp, id, len)
     BCOPY(inp, rhostname, len);
     rhostname[len] = '\000';
 
-    CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: received name field '%s'",
-              rhostname));
-
     /* Microsoft doesn't send their name back in the PPP packet */
-    if (remote_name[0] != 0 && (explicit_remote || rhostname[0] == 0)) {
-       strncpy(rhostname, remote_name, sizeof(rhostname));
-       rhostname[sizeof(rhostname) - 1] = 0;
-       CHAPDEBUG((LOG_INFO, "ChapReceiveChallenge: using '%s' as remote name",
+    if (explicit_remote || (remote_name[0] != 0 && rhostname[0] == 0)) {
+       strlcpy(rhostname, remote_name, sizeof(rhostname));
+       CHAPDEBUG(("ChapReceiveChallenge: using '%q' as remote name",
                   rhostname));
     }
 
@@ -434,8 +473,7 @@ ChapReceiveChallenge(cstate, inp, id, len)
     if (!get_secret(cstate->unit, cstate->resp_name, rhostname,
                    secret, &secret_len, 0)) {
        secret_len = 0;         /* assume null secret if can't find one */
-       syslog(LOG_WARNING, "No CHAP secret found for authenticating us to %s",
-              rhostname);
+       warn("No CHAP secret found for authenticating us to %q", rhostname);
     }
 
     /* cancel response send timeout if necessary */
@@ -446,7 +484,7 @@ ChapReceiveChallenge(cstate, inp, id, len)
     cstate->resp_transmits = 0;
 
     /*  generate MD based on negotiated type */
-    switch (cstate->resp_type) { 
+    switch (cstate->resp_type) {
 
     case CHAP_DIGEST_MD5:
        MD5Init(&mdContext);
@@ -460,12 +498,23 @@ ChapReceiveChallenge(cstate, inp, id, len)
 
 #ifdef CHAPMS
     case CHAP_MICROSOFT:
-       ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len);
+       ChapMS(cstate, rchallenge, secret, secret_len,
+              (MS_ChapResponse *) cstate->response);
+       cstate->resp_length = MS_CHAP_RESPONSE_LEN;
        break;
-#endif
+
+    case CHAP_MICROSOFT_V2:
+       ChapMS2(cstate, rchallenge,
+               mschap2_peer_challenge? mschap2_peer_challenge: NULL,
+               cstate->resp_name, secret, secret_len,
+               (MS_Chap2Response *) cstate->response, cstate->earesponse,
+                MS_CHAP2_AUTHENTICATEE);
+       cstate->resp_length = MS_CHAP2_RESPONSE_LEN;
+       break;
+#endif /* CHAPMS */
 
     default:
-       CHAPDEBUG((LOG_INFO, "unknown digest type %d", cstate->resp_type));
+       CHAPDEBUG(("unknown digest type %d", cstate->resp_type));
        return;
     }
 
@@ -492,12 +541,9 @@ ChapReceiveResponse(cstate, inp, id, len)
     char secret[MAXSECRETLEN];
     u_char hash[MD5_SIGNATURE_SIZE];
 
-    CHAPDEBUG((LOG_INFO, "ChapReceiveResponse: Rcvd id %d.", id));
-
     if (cstate->serverstate == CHAPSS_CLOSED ||
        cstate->serverstate == CHAPSS_PENDING) {
-       CHAPDEBUG((LOG_INFO, "ChapReceiveResponse: in state %d",
-                  cstate->serverstate));
+       CHAPDEBUG(("ChapReceiveResponse: in state %d", cstate->serverstate));
        return;
     }
 
@@ -519,7 +565,7 @@ ChapReceiveResponse(cstate, inp, id, len)
     }
 
     if (len < 2) {
-       CHAPDEBUG((LOG_INFO, "ChapReceiveResponse: rcvd short packet."));
+       CHAPDEBUG(("ChapReceiveResponse: rcvd short packet."));
        return;
     }
     GETCHAR(remmd_len, inp);           /* get length of MD */
@@ -528,7 +574,7 @@ ChapReceiveResponse(cstate, inp, id, len)
 
     len -= sizeof (u_char) + remmd_len;
     if (len < 0) {
-       CHAPDEBUG((LOG_INFO, "ChapReceiveResponse: rcvd short packet."));
+       CHAPDEBUG(("ChapReceiveResponse: rcvd short packet."));
        return;
     }
 
@@ -539,59 +585,119 @@ ChapReceiveResponse(cstate, inp, id, len)
     BCOPY(inp, rhostname, len);
     rhostname[len] = '\000';
 
-    CHAPDEBUG((LOG_INFO, "ChapReceiveResponse: received name field: %s",
-              rhostname));
-
     /*
      * Get secret for authenticating them with us,
      * do the hash ourselves, and compare the result.
      */
     code = CHAP_FAILURE;
-    if (!get_secret(cstate->unit, rhostname, cstate->chal_name,
-                  secret, &secret_len, 1)) {
-       syslog(LOG_WARNING, "No CHAP secret found for authenticating %s",
-              rhostname);
-    } else {
 
-       /*  generate MD based on negotiated type */
-       switch (cstate->chal_type) { 
-
-       case CHAP_DIGEST_MD5:           /* only MD5 is defined for now */
-           if (remmd_len != MD5_SIGNATURE_SIZE)
-               break;                  /* it's not even the right length */
-           MD5Init(&mdContext);
-           MD5Update(&mdContext, &cstate->chal_id, 1);
-           MD5Update(&mdContext, secret, secret_len);
-           MD5Update(&mdContext, cstate->challenge, cstate->chal_len);
-           MD5Final(hash, &mdContext); 
-
-           /* compare local and remote MDs and send the appropriate status */
-           if (memcmp (hash, remmd, MD5_SIGNATURE_SIZE) == 0)
-               code = CHAP_SUCCESS;    /* they are the same! */
-           break;
+    /* If a plugin will verify the response, let the plugin do it. */
+    if (chap_auth_hook) {
+       code = (*chap_auth_hook) ( (explicit_remote ? remote_name : rhostname),
+                                  remmd, (int) remmd_len,
+                                  cstate );
+    } else {
+       if (!get_secret(cstate->unit, (explicit_remote? remote_name: rhostname),
+                       cstate->chal_name, secret, &secret_len, 1)) {
+           warn("No CHAP secret found for authenticating %q", rhostname);
+       } else {
+
+           /*  generate MD based on negotiated type */
+           switch (cstate->chal_type) {
+
+           case CHAP_DIGEST_MD5:
+               if (remmd_len != MD5_SIGNATURE_SIZE)
+                   break;                      /* not even the right length */
+               MD5Init(&mdContext);
+               MD5Update(&mdContext, &cstate->chal_id, 1);
+               MD5Update(&mdContext, secret, secret_len);
+               MD5Update(&mdContext, cstate->challenge, cstate->chal_len);
+               MD5Final(hash, &mdContext);
+
+               /* compare MDs and send the appropriate status */
+               if (memcmp(hash, remmd, MD5_SIGNATURE_SIZE) == 0)
+                   code = CHAP_SUCCESS;        /* they are the same! */
+               break;
 
-       default:
-           CHAPDEBUG((LOG_INFO, "unknown digest type %d", cstate->chal_type));
+#ifdef CHAPMS
+           case CHAP_MICROSOFT:
+           {
+               int response_offset, response_size;
+               MS_ChapResponse *rmd = (MS_ChapResponse *) remmd;
+               MS_ChapResponse md;
+
+               if (remmd_len != MS_CHAP_RESPONSE_LEN)
+                   break;                      /* not even the right length */
+
+               /* Determine which part of response to verify against */
+               if (rmd->UseNT[0]) {
+                   response_offset = offsetof(MS_ChapResponse, NTResp);
+                   response_size = sizeof(rmd->NTResp);
+               } else {
+#ifdef MSLANMAN
+                   response_offset = offsetof(MS_ChapResponse, LANManResp);
+                   response_size = sizeof(rmd->LANManResp);
+#else
+                   /* Should really propagate this into the error packet. */
+                   notice("Peer request for LANMAN auth not supported");
+                   break;
+#endif /* MSLANMAN */
+               }
+
+               /* Generate the expected response. */
+               ChapMS(cstate, cstate->challenge, secret, secret_len, &md);
+
+               /* compare MDs and send the appropriate status */
+               if (memcmp((u_char *) &md + response_offset,
+                          (u_char *) remmd + response_offset,
+                          response_size) == 0)
+                   code = CHAP_SUCCESS;        /* they are the same! */
+               break;
+           }
+
+           case CHAP_MICROSOFT_V2:
+           {
+               MS_Chap2Response *rmd = (MS_Chap2Response *) remmd;
+               MS_Chap2Response md;
+
+               if (remmd_len != MS_CHAP2_RESPONSE_LEN)
+                   break;                      /* not even the right length */
+
+               /* Generate the expected response and our mutual auth. */
+               ChapMS2(cstate, cstate->challenge, rmd->PeerChallenge,
+                       (explicit_remote? remote_name: rhostname),
+                       secret, secret_len, &md,
+                       cstate->saresponse, MS_CHAP2_AUTHENTICATOR);
+
+               /* compare MDs and send the appropriate status */
+               if (memcmp(md.NTResp, rmd->NTResp, sizeof(md.NTResp)) == 0)
+                   code = CHAP_SUCCESS;        /* yay! */
+               break;
+           }
+#endif /* CHAPMS */
+
+           default:
+               CHAPDEBUG(("unknown digest type %d", cstate->chal_type));
+           }
        }
-    }
 
-    BZERO(secret, sizeof(secret));
+       BZERO(secret, sizeof(secret));
+    }
     ChapSendStatus(cstate, code);
 
     if (code == CHAP_SUCCESS) {
        old_state = cstate->serverstate;
        cstate->serverstate = CHAPSS_OPEN;
        if (old_state == CHAPSS_INITIAL_CHAL) {
-           auth_peer_success(cstate->unit, PPP_CHAP, rhostname, len);
+           auth_peer_success(cstate->unit, PPP_CHAP, cstate->chal_type,
+                             rhostname, len);
        }
        if (cstate->chal_interval != 0)
            TIMEOUT(ChapRechallenge, cstate, cstate->chal_interval);
-       syslog(LOG_NOTICE, "CHAP peer authentication succeeded for %s",
-              rhostname);
+       notice("CHAP peer authentication succeeded for %q", rhostname);
 
     } else {
-       syslog(LOG_ERR, "CHAP peer authentication failed for remote host %s",
-              rhostname);
+       error("CHAP peer authentication failed for remote host %q", rhostname);
        cstate->serverstate = CHAPSS_BADAUTH;
        auth_peer_fail(cstate->unit, PPP_CHAP);
     }
@@ -608,21 +714,49 @@ ChapReceiveSuccess(cstate, inp, id, len)
     int len;
 {
 
-    CHAPDEBUG((LOG_INFO, "ChapReceiveSuccess: Rcvd id %d.", id));
-
     if (cstate->clientstate == CHAPCS_OPEN)
        /* presumably an answer to a duplicate response */
        return;
 
     if (cstate->clientstate != CHAPCS_RESPONSE) {
        /* don't know what this is */
-       CHAPDEBUG((LOG_INFO, "ChapReceiveSuccess: in state %d\n",
-                  cstate->clientstate));
+       CHAPDEBUG(("ChapReceiveSuccess: in state %d\n", cstate->clientstate));
        return;
     }
 
     UNTIMEOUT(ChapResponseTimeout, cstate);
 
+#ifdef CHAPMS
+    /*
+     * For MS-CHAPv2, we must verify that the peer knows our secret.
+     */
+    if (cstate->resp_type == CHAP_MICROSOFT_V2) {
+       if ((len >= MS_AUTH_RESPONSE_LENGTH + 2) && !strncmp(inp, "S=", 2)) {
+           inp += 2; len -= 2;
+           if (!memcmp(inp, cstate->earesponse, MS_AUTH_RESPONSE_LENGTH)) {
+               /* Authenticator Response matches. */
+               inp += MS_AUTH_RESPONSE_LENGTH; /* Eat it */
+               len -= MS_AUTH_RESPONSE_LENGTH;
+               if ((len >= 3) && !strncmp(inp, " M=", 3)) {
+                   inp += 3; len -= 3; /* Eat the delimiter */
+               } else if (len) {
+                   /* Packet has extra text which does not begin " M=" */
+                   error("MS-CHAPv2 Success packet is badly formed.");
+                   auth_withpeer_fail(cstate->unit, PPP_CHAP);
+               }
+           } else {
+               /* Authenticator Response did not match expected. */
+               error("MS-CHAPv2 mutual authentication failed.");
+               auth_withpeer_fail(cstate->unit, PPP_CHAP);
+           }
+       } else {
+           /* Packet does not start with "S=" */
+           error("MS-CHAPv2 Success packet is badly formed.");
+           auth_withpeer_fail(cstate->unit, PPP_CHAP);
+       }
+    }
+#endif
+
     /*
      * Print message.
      */
@@ -631,7 +765,7 @@ ChapReceiveSuccess(cstate, inp, id, len)
 
     cstate->clientstate = CHAPCS_OPEN;
 
-    auth_withpeer_success(cstate->unit, PPP_CHAP);
+    auth_withpeer_success(cstate->unit, PPP_CHAP, cstate->resp_type);
 }
 
 
@@ -645,25 +779,103 @@ ChapReceiveFailure(cstate, inp, id, len)
     u_char id;
     int len;
 {
-    CHAPDEBUG((LOG_INFO, "ChapReceiveFailure: Rcvd id %d.", id));
+    u_char *msg;
+    u_char *p = inp;
 
     if (cstate->clientstate != CHAPCS_RESPONSE) {
        /* don't know what this is */
-       CHAPDEBUG((LOG_INFO, "ChapReceiveFailure: in state %d\n",
-                  cstate->clientstate));
+       CHAPDEBUG(("ChapReceiveFailure: in state %d\n", cstate->clientstate));
        return;
     }
 
+#ifdef CHAPMS
+    /* We want a null-terminated string for strxxx(). */
+    msg = malloc(len + 1);
+    if (!msg) {
+       p = NULL;
+       notice("Out of memory in ChapReceiveFailure");
+       goto print_msg;
+    }
+    BCOPY(inp, msg, len);
+    p = msg + len; *p = '\0'; p = msg;
+#endif
+
     UNTIMEOUT(ChapResponseTimeout, cstate);
 
+#ifdef CHAPMS
+    if ((cstate->resp_type == CHAP_MICROSOFT_V2) ||
+       (cstate->resp_type == CHAP_MICROSOFT)) {
+       int error;
+
+       /*
+        * Deal with MS-CHAP formatted failure messages; just print the
+        * M=<message> part (if any).  For MS-CHAP we're not really supposed
+        * to use M=<message>, but it shouldn't hurt.  See ChapSendStatus().
+        */
+       if (!strncmp(p, "E=", 2))
+           error = (int) strtol(p, NULL, 10); /* Remember the error code. */
+       else
+           goto print_msg; /* Message is badly formatted. */
+
+       if (len && ((p = strstr(p, " M=")) != NULL)) {
+           /* M=<message> field found. */
+           p += 3;
+       } else {
+           /* No M=<message>; use the error code. */
+           switch(error) {
+           case MS_CHAP_ERROR_RESTRICTED_LOGON_HOURS:
+               p = "Restricted logon hours";
+               break;
+
+           case MS_CHAP_ERROR_ACCT_DISABLED:
+               p = "Account disabled";
+               break;
+
+           case MS_CHAP_ERROR_PASSWD_EXPIRED:
+               p = "Password expired";
+               break;
+
+           case MS_CHAP_ERROR_NO_DIALIN_PERMISSION:
+               p = "No dialin permission";
+               break;
+
+           case MS_CHAP_ERROR_AUTHENTICATION_FAILURE:
+               p = "Authentication failure";
+               break;
+
+           case MS_CHAP_ERROR_CHANGING_PASSWORD:
+               /* Should never see this, we don't support Change Password. */
+               p = "Error changing password";
+               break;
+
+           default:
+               free(msg);
+               p = msg = malloc(len + 33);
+               if (!msg) {
+                   notice("Out of memory in ChapReceiveFailure");
+                   goto print_msg;
+               }
+               slprintf(p, len + 33, "Unknown authentication failure: %.*s",
+                        len, inp);
+               break;
+           }
+       }
+       len = strlen(p);
+    }
+#endif
+
     /*
      * Print message.
      */
-    if (len > 0)
-       PRINTMSG(inp, len);
+print_msg:
+    if (len > 0 && p != NULL)
+       PRINTMSG(p, len);
 
-    syslog(LOG_ERR, "CHAP authentication failed");
+    error("CHAP authentication failed");
     auth_withpeer_fail(cstate->unit, PPP_CHAP);
+#ifdef CHAPMS
+    if (msg) free(msg);
+#endif
 }
 
 
@@ -696,8 +908,6 @@ ChapSendChallenge(cstate)
     BCOPY(cstate->chal_name, outp, name_len);  /* append hostname */
 
     output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN);
-  
-    CHAPDEBUG((LOG_INFO, "ChapSendChallenge: Sent id %d.", cstate->chal_id));
 
     TIMEOUT(ChapChallengeTimeout, cstate, cstate->timeouttime);
     ++cstate->chal_transmits;
@@ -706,6 +916,7 @@ ChapSendChallenge(cstate)
 
 /*
  * ChapSendStatus - Send a status response (ack or nak).
+ * See RFC 2433 and RFC 2759 for MS-CHAP and MS-CHAPv2 message formats.
  */
 static void
 ChapSendStatus(cstate, code)
@@ -713,28 +924,75 @@ ChapSendStatus(cstate, code)
     int code;
 {
     u_char *outp;
-    int outlen, msglen;
+    int i, outlen, msglen;
     char msg[256];
+    char *p, *q;
 
-    if (code == CHAP_SUCCESS)
-       sprintf(msg, "Welcome to %s.", hostname);
-    else
-       sprintf(msg, "I don't like you.  Go 'way.");
+    p = msg;
+    q = p + sizeof(msg); /* points 1 byte past msg */
+
+    if (code == CHAP_SUCCESS) {
+#ifdef CHAPMS
+       if (cstate->chal_type == CHAP_MICROSOFT_V2) {
+           /*
+            * Success message must be formatted as
+            *     "S=<auth_string> M=<message>"
+            * where
+            *     <auth_string> is the Authenticator Response (mutual auth)
+            *     <message> is a text message
+            */
+           slprintf(p, q - p, "S=");
+           p += 2;
+           slprintf(p, q - p, "%s", cstate->saresponse);
+           p += strlen(cstate->saresponse);
+           slprintf(p, q - p, " M=");
+           p += 3;
+       }
+#endif /* CHAPMS */
+
+       slprintf(p, q - p, "Welcome to %s.", hostname);
+    } else {
+#ifdef CHAPMS
+       if ((cstate->chal_type == CHAP_MICROSOFT_V2) ||
+           (cstate->chal_type == CHAP_MICROSOFT)) {
+           /*
+            * Failure message must be formatted as
+            *     "E=e R=r C=c V=v M=m"
+            * where
+            *     e = error code (we use 691, ERROR_AUTHENTICATION_FAILURE)
+            *     r = retry (we use 1, ok to retry)
+            *     c = challenge to use for next response, we reuse previous
+            *     v = Change Password version supported, we use 0
+            *     m = text message
+            *
+            * The M=m part is only for MS-CHAPv2, but MS-CHAP should ignore
+            * any extra text according to RFC 2433.  So we'll go the easy
+            * (read: lazy) route and include it always.
+            */
+           slprintf(p, q - p, "E=691 R=1 C=");
+           p += 12;
+           for (i = 0; i < cstate->chal_len; i++)
+               sprintf(p + i * 2, "%02X", cstate->challenge[i]);
+           p += cstate->chal_len * 2;
+           slprintf(p, q - p, " V=0 M=");
+           p += 7;
+       }
+#endif /* CHAPMS */
+
+       slprintf(p, q - p, "I don't like you.  Go 'way.");
+    }
     msglen = strlen(msg);
 
     outlen = CHAP_HEADERLEN + msglen;
     outp = outpacket_buf;
 
     MAKEHEADER(outp, PPP_CHAP);        /* paste in a header */
-  
+
     PUTCHAR(code, outp);
     PUTCHAR(cstate->chal_id, outp);
     PUTSHORT(outlen, outp);
     BCOPY(msg, outp, msglen);
     output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN);
-  
-    CHAPDEBUG((LOG_INFO, "ChapSendStatus: Sent code %d, id %d.", code,
-              cstate->chal_id));
 }
 
 /*
@@ -748,22 +1006,51 @@ static void
 ChapGenChallenge(cstate)
     chap_state *cstate;
 {
-    int chal_len;
+    int chal_len = 0; /* Avoid compiler warning */
     u_char *ptr = cstate->challenge;
-    unsigned int i;
+    int i;
+
+    switch (cstate->chal_type) {
+    case CHAP_DIGEST_MD5:
+       /*
+        * pick a random challenge length between MIN_CHALLENGE_LENGTH and
+        * MAX_CHALLENGE_LENGTH
+        */
+       chal_len = (unsigned) ((drand48() *
+                               (MAX_CHALLENGE_LENGTH - MIN_CHALLENGE_LENGTH)) +
+                               MIN_CHALLENGE_LENGTH);
+       break;
+
+#ifdef CHAPMS
+    case CHAP_MICROSOFT:
+       /* MS-CHAP is fixed to an 8 octet challenge. */
+       chal_len = 8;
+       break;
+
+    case CHAP_MICROSOFT_V2:
+       /* MS-CHAPv2 is fixed to a 16 octet challenge. */
+       chal_len = 16;
+       break;
+#endif
+    default:
+       fatal("ChapGenChallenge: Unsupported challenge type %d",
+             (int) cstate->chal_type);
+       break;
+    }
 
-    /* pick a random challenge length between MIN_CHALLENGE_LENGTH and 
-       MAX_CHALLENGE_LENGTH */  
-    chal_len =  (unsigned) ((drand48() *
-                            (MAX_CHALLENGE_LENGTH - MIN_CHALLENGE_LENGTH)) +
-                           MIN_CHALLENGE_LENGTH);
     cstate->chal_len = chal_len;
     cstate->chal_id = ++cstate->id;
     cstate->chal_transmits = 0;
 
-    /* generate a random string */
-    for (i = 0; i < chal_len; i++ )
-       *ptr++ = (char) (drand48() * 0xff);
+#ifdef CHAPMS
+    if (mschap_challenge)
+       for (i = 0; i < chal_len; i++)
+           *ptr++ = mschap_challenge[i];
+    else
+#endif
+       /* generate a random string */
+       for (i = 0; i < chal_len; i++)
+           *ptr++ = (char) (drand48() * 0xff);
 }
 
 /*