Log calling number failed authorization at warn instead of error, to be
[ppp.git] / pppd / chap.c
index 21b0280323577b5bc10896237a5bb267248a7729..a63dde818fa01e0db3fcb14b062541d6180044e1 100644 (file)
@@ -33,7 +33,7 @@
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
-#define RCSID  "$Id: chap.c,v 1.29 2002/03/05 15:14:04 dfs Exp $"
+#define RCSID  "$Id: chap.c,v 1.36 2002/10/12 02:30:21 fcusack Exp $"
 
 /*
  * TODO:
@@ -66,6 +66,14 @@ int (*chap_auth_hook) __P((char *user,
 
 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.
  */
@@ -79,6 +87,12 @@ static option_t chap_option_list[] = {
 #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 }
 };
@@ -443,10 +457,8 @@ ChapReceiveChallenge(cstate, inp, id, len)
     rchallenge = inp;
     INCPTR(rchallenge_len, inp);
 
-    if (len >= sizeof(rhostname))
-       len = sizeof(rhostname) - 1;
-    BCOPY(inp, rhostname, len);
-    rhostname[len] = '\000';
+    /* Null terminate and clean remote name. */
+    slprintf(rhostname, sizeof(rhostname), "%.*v", len, inp);
 
     /* Microsoft doesn't send their name back in the PPP packet */
     if (explicit_remote || (remote_name[0] != 0 && rhostname[0] == 0)) {
@@ -486,13 +498,14 @@ ChapReceiveChallenge(cstate, inp, id, len)
     case CHAP_MICROSOFT:
        ChapMS(cstate, rchallenge, secret, secret_len,
               (MS_ChapResponse *) cstate->response);
-       cstate->resp_length = MS_CHAP_RESPONSE_LEN;
        break;
 
     case CHAP_MICROSOFT_V2:
-       ChapMS2(cstate, rchallenge, NULL, cstate->resp_name, secret, secret_len,
-               (MS_Chap2Response *) cstate->response, cstate->earesponse);
-       cstate->resp_length = MS_CHAP2_RESPONSE_LEN;
+       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);
        break;
 #endif /* CHAPMS */
 
@@ -568,6 +581,11 @@ ChapReceiveResponse(cstate, inp, id, len)
     BCOPY(inp, rhostname, len);
     rhostname[len] = '\000';
 
+#ifdef CHAPMS
+    /* copy the flags into cstate for use elsewhere */
+    if (cstate->chal_type == CHAP_MICROSOFT_V2)
+       cstate->resp_flags = ((MS_Chap2Response *) remmd)->Flags[0];
+#endif /* CHAPMS */
     /*
      * Get secret for authenticating them with us,
      * do the hash ourselves, and compare the result.
@@ -579,6 +597,19 @@ ChapReceiveResponse(cstate, inp, id, len)
        code = (*chap_auth_hook) ( (explicit_remote ? remote_name : rhostname),
                                   remmd, (int) remmd_len,
                                   cstate );
+       /*
+        * Check remote number authorization.  A plugin may have filled in
+        * the remote number or added an allowed number, and rather than
+        * return an authenticate failure, is leaving it for us to verify.
+        */
+       if (code == CHAP_SUCCESS) {
+           if (!auth_number()) {
+               /* We do not want to leak info about the chap result. */
+               code = CHAP_FAILURE; /* XXX exit value will be "wrong" */
+               warn("calling number %q is not authorized", remote_number);
+           }
+       }
+
     } else {
        if (!get_secret(cstate->unit, (explicit_remote? remote_name: rhostname),
                        cstate->chal_name, secret, &secret_len, 1)) {
@@ -631,8 +662,9 @@ ChapReceiveResponse(cstate, inp, id, len)
                ChapMS(cstate, cstate->challenge, secret, secret_len, &md);
 
                /* compare MDs and send the appropriate status */
-               if (memcmp(&md + response_offset,
-                          remmd + response_offset, response_size) == 0)
+               if (memcmp((u_char *) &md + response_offset,
+                          (u_char *) remmd + response_offset,
+                          response_size) == 0)
                    code = CHAP_SUCCESS;        /* they are the same! */
                break;
            }
@@ -649,7 +681,7 @@ ChapReceiveResponse(cstate, inp, id, len)
                ChapMS2(cstate, cstate->challenge, rmd->PeerChallenge,
                        (explicit_remote? remote_name: rhostname),
                        secret, secret_len, &md,
-                       cstate->saresponse);
+                       cstate->saresponse, MS_CHAP2_AUTHENTICATOR);
 
                /* compare MDs and send the appropriate status */
                if (memcmp(md.NTResp, rmd->NTResp, sizeof(md.NTResp)) == 0)
@@ -671,14 +703,15 @@ ChapReceiveResponse(cstate, inp, id, len)
        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);
        notice("CHAP peer authentication succeeded for %q", rhostname);
 
     } else {
-       error("CHAP peer authentication failed for remote host %q", rhostname);
+       warn("CHAP peer authentication failed for %q", rhostname);
        cstate->serverstate = CHAPSS_BADAUTH;
        auth_peer_fail(cstate->unit, PPP_CHAP);
     }
@@ -746,7 +779,8 @@ ChapReceiveSuccess(cstate, inp, id, len)
 
     cstate->clientstate = CHAPCS_OPEN;
 
-    auth_withpeer_success(cstate->unit, PPP_CHAP);
+    notice("CHAP authentication succeeded");
+    auth_withpeer_success(cstate->unit, PPP_CHAP, cstate->resp_type);
 }
 
 
@@ -786,7 +820,7 @@ ChapReceiveFailure(cstate, inp, id, len)
 #ifdef CHAPMS
     if ((cstate->resp_type == CHAP_MICROSOFT_V2) ||
        (cstate->resp_type == CHAP_MICROSOFT)) {
-       long error;
+       int error;
 
        /*
         * Deal with MS-CHAP formatted failure messages; just print the
@@ -794,7 +828,7 @@ ChapReceiveFailure(cstate, inp, id, len)
         * to use M=<message>, but it shouldn't hurt.  See ChapSendStatus().
         */
        if (!strncmp(p, "E=", 2))
-           error = strtol(p, NULL, 10); /* Remember the error code. */
+           error = (int) strtol(p, NULL, 10); /* Remember the error code. */
        else
            goto print_msg; /* Message is badly formatted. */
 
@@ -803,37 +837,37 @@ ChapReceiveFailure(cstate, inp, id, len)
            p += 3;
        } else {
            /* No M=<message>; use the error code. */
-           switch(error - MS_CHAP_ERROR_BASE) {
+           switch(error) {
            case MS_CHAP_ERROR_RESTRICTED_LOGON_HOURS:
-               p = "Restricted logon hours";
+               p = "E=646 Restricted logon hours";
                break;
 
            case MS_CHAP_ERROR_ACCT_DISABLED:
-               p = "Account disabled";
+               p = "E=647 Account disabled";
                break;
 
            case MS_CHAP_ERROR_PASSWD_EXPIRED:
-               p = "Password expired";
+               p = "E=648 Password expired";
                break;
 
            case MS_CHAP_ERROR_NO_DIALIN_PERMISSION:
-               p = "No dialin permission";
+               p = "E=649 No dialin permission";
                break;
 
            case MS_CHAP_ERROR_AUTHENTICATION_FAILURE:
-               p = "Authentication failure";
+               p = "E=691 Authentication failure";
                break;
 
            case MS_CHAP_ERROR_CHANGING_PASSWORD:
                /* Should never see this, we don't support Change Password. */
-               p = "Error changing password";
+               p = "E=709 Error changing password";
                break;
 
            default:
                free(msg);
                p = msg = malloc(len + 33);
                if (!msg) {
-                   notice("Out of memory in ChapReceiveFailure");
+                   novm("ChapReceiveFailure");
                    goto print_msg;
                }
                slprintf(p, len + 33, "Unknown authentication failure: %.*s",
@@ -916,16 +950,30 @@ ChapSendStatus(cstate, code)
 #ifdef CHAPMS
        if (cstate->chal_type == CHAP_MICROSOFT_V2) {
            /*
-            * Success message must be formatted as
+            * Per RFC 2759, 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
+            *
+            * However, some versions of Windows (win98 tested) do not know
+            * about the M=<message> part (required per RFC 2759) and flag
+            * it as an error (reported incorrectly as an encryption error
+            * to the user).  Since the RFC requires it, and it can be
+            * useful information, we supply it if the peer is a conforming
+            * system.  Luckily (?), win98 sets the Flags field to 0x04
+            * (contrary to RFC requirements) so we can use that to
+            * distinguish between conforming and non-conforming systems.
+            *
+            * Special thanks to Alex Swiridov <say@real.kharkov.ua> for
+            * help debugging this.
             */
            slprintf(p, q - p, "S=");
            p += 2;
            slprintf(p, q - p, "%s", cstate->saresponse);
            p += strlen(cstate->saresponse);
+           if (cstate->resp_flags != 0)
+               goto msgdone;
            slprintf(p, q - p, " M=");
            p += 3;
        }
@@ -948,7 +996,16 @@ ChapSendStatus(cstate, code)
             *
             * 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.
+            * (read: lazy) route and include it always.  Neither win2k nor
+            * win98 (others untested) display the message to the user anyway.
+            * They also both ignore the E=e code.
+            *
+            * Note that it's safe to reuse the same challenge as we don't
+            * actually accept another response based on the error message
+            * (and no clients try to resend a response anyway).
+            *
+            * Basically, this whole bit is useless code, even the small
+            * implementation here is only because of overspecification.
             */
            slprintf(p, q - p, "E=691 R=1 C=");
            p += 12;
@@ -962,6 +1019,7 @@ ChapSendStatus(cstate, code)
 
        slprintf(p, q - p, "I don't like you.  Go 'way.");
     }
+msgdone:
     msglen = strlen(msg);
 
     outlen = CHAP_HEADERLEN + msglen;
@@ -1023,9 +1081,15 @@ ChapGenChallenge(cstate)
     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);
 }
 
 /*