announce EAP method initiation once only
[strongswan.git] / src / charon / plugins / eap_mschapv2 / eap_mschapv2.c
index 56b3667..9ddb8fc 100644 (file)
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  * for more details.
- *
- * $Id$
  */
 
 #include "eap_mschapv2.h"
 
+#include <ctype.h>
+#include <unistd.h>
+
 #include <daemon.h>
 #include <library.h>
 #include <utils/enumerator.h>
@@ -141,7 +142,7 @@ ENUM_END(mschapv2_error_names, ERROR_CHANGING_PASSWORD);
 /* Name we send as authenticator */
 #define MSCHAPV2_HOST_NAME "strongSwan"
 /* Message sent on success */
-#define SUCCESS_MESSAGE " M=Welcome to strongSwan"
+#define SUCCESS_MESSAGE " M=Welcome2strongSwan"
 /* Message sent on failure */
 #define FAILURE_MESSAGE "E=691 R=1 C="
 /* Length of the complete failure message */
@@ -318,7 +319,7 @@ static status_t ChallengeResponse(chunk_t challenge_hash, chunk_t password_hash,
        int i;
        crypter_t *crypter;
        chunk_t keys[3], z_password_hash;
-       crypter = lib->crypto->create_crypter(lib->crypto, ENCR_DES_ECB, 64);
+       crypter = lib->crypto->create_crypter(lib->crypto, ENCR_DES_ECB, 8);
        if (crypter == NULL)
        {
                DBG1(DBG_IKE, "EAP-MS-CHAPv2 failed, DES-ECB not supported");
@@ -339,6 +340,7 @@ static status_t ChallengeResponse(chunk_t challenge_hash, chunk_t password_hash,
                crypter->set_key(crypter, expanded);
                crypter->encrypt(crypter, challenge_hash, chunk_empty, &encrypted);
                memcpy(&response->ptr[i * 8], encrypted.ptr, encrypted.len);
+               chunk_clear(&encrypted);
                chunk_clear(&expanded);
        }
        crypter->destroy(crypter);
@@ -365,7 +367,6 @@ static status_t AuthenticatorResponse(chunk_t password_hash_hash,
        static const chunk_t magic1 = chunk_from_buf(magic1_data);
        static const chunk_t magic2 = chunk_from_buf(magic2_data);
        
-       status_t status = FAILED;
        chunk_t digest = chunk_empty, concat;
        hasher_t *hasher;
        
@@ -526,6 +527,24 @@ static chunk_t ascii_to_unicode(chunk_t ascii)
 }
 
 /**
+ * sanitize a string for printing
+ */
+static char* sanitize(char *str)
+{
+       char *pos = str;
+       
+       while (pos && *pos)
+       {
+               if (!isprint(*pos))
+               {
+                       *pos = '?';
+               }
+               pos++;
+       }
+       return str;
+}
+
+/**
  * Returns a chunk of just the username part of the given user identity.
  * Note: the chunk points to internal data of the identification.
  */
@@ -534,7 +553,7 @@ static chunk_t extract_username(identification_t* identification)
        char *has_domain;
        chunk_t id;
        id = identification->get_encoding(identification);
-       has_domain = (char*)memrchr(id.ptr, '\\', id.len);
+       has_domain = (char*)memchr(id.ptr, '\\', id.len);
        if (has_domain)
        {
                int len;
@@ -566,8 +585,6 @@ static status_t initiate_server(private_eap_mschapv2_t *this, eap_payload_t **ou
        const char *name = MSCHAPV2_HOST_NAME;
        u_int16_t len = CHALLENGE_PAYLOAD_LEN + sizeof(MSCHAPV2_HOST_NAME) - 1;
        
-       DBG1(DBG_IKE, "initiating EAP-MS-CHAPv2");
-       
        rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
        if (!rng)
        {
@@ -624,7 +641,8 @@ static status_t process_peer_challenge(private_eap_mschapv2_t *this,
                        
        if (cha->value_size != CHALLENGE_LEN)
        {
-               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message: invalid challenge size");
+               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message: "
+                        "invalid challenge size");
                return FAILED;
        }
                        
@@ -642,11 +660,11 @@ static status_t process_peer_challenge(private_eap_mschapv2_t *this,
        rng->destroy(rng);
                        
        shared = charon->credentials->get_shared(charon->credentials,
-                                                                                        SHARED_EAP, this->server, this->peer);
+                                                                               SHARED_EAP, this->peer, this->server);
        if (shared == NULL)
        {
-               DBG1(DBG_IKE, "no EAP key found for hosts '%D' - '%D'",
-                               this->server, this->peer);
+               DBG1(DBG_IKE, "no EAP key found for hosts '%Y' - '%Y'",
+                        this->server, this->peer);
                return NOT_FOUND;
        }
        
@@ -709,9 +727,9 @@ static status_t process_peer_success(private_eap_mschapv2_t *this,
                return FAILED;
        }
        
-       message_len = data.len - HEADER_LEN + 1; /* + null byte */
-       message = malloc(message_len);
-       memcpy(message, eap->data, message_len - 1);
+       message_len = data.len - HEADER_LEN;
+       message = malloc(message_len + 1);
+       memcpy(message, eap->data, message_len);
        message[message_len] = '\0';
        
        /* S=<auth_string> M=<msg> */
@@ -724,7 +742,8 @@ static status_t process_peer_success(private_eap_mschapv2_t *this,
                        token += 2;
                        if (strlen(token) != AUTH_RESPONSE_LEN - 2)
                        {
-                               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message: invalid auth string");
+                               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message: "
+                                        "invalid auth string");
                                goto error;
                        }
                        hex = chunk_create(token, AUTH_RESPONSE_LEN - 2);
@@ -740,7 +759,8 @@ static status_t process_peer_success(private_eap_mschapv2_t *this,
                        
        if (auth_string.ptr == NULL)    
        {
-               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message: auth string missing");
+               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message: "
+                        "auth string missing");
                goto error;
        }
        
@@ -750,7 +770,7 @@ static status_t process_peer_success(private_eap_mschapv2_t *this,
                goto error;
        }
        
-       DBG1(DBG_IKE, "EAP-MS-CHAPv2 succeeded: '%s'", msg);
+       DBG1(DBG_IKE, "EAP-MS-CHAPv2 succeeded: '%s'", sanitize(msg));
        
        eap = alloca(len);
        eap->code = EAP_RESPONSE;
@@ -779,7 +799,6 @@ static status_t process_peer_failure(private_eap_mschapv2_t *this,
        char *message, *token, *msg = NULL;
        int message_len, error, retryable;
        chunk_t challenge = chunk_empty;
-       u_int16_t len = SHORT_HEADER_LEN;
        
        data = in->get_data(in);
        eap = (eap_mschapv2_header_t*)data.ptr;
@@ -815,7 +834,8 @@ static status_t process_peer_failure(private_eap_mschapv2_t *this,
                        token += 2;
                        if (strlen(token) != 2 * CHALLENGE_LEN)
                        {
-                               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message: invalid challenge");
+                               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message:"
+                                        "invalid challenge");
                                goto error;
                        }
                        hex = chunk_create(token, 2 * CHALLENGE_LEN);
@@ -835,21 +855,23 @@ static status_t process_peer_failure(private_eap_mschapv2_t *this,
        }
        enumerator->destroy(enumerator);
                        
-       DBG1(DBG_IKE, "EAP-MS-CHAPv2 failed with error %N: '%s'", mschapv2_error_names, error, msg);
+       DBG1(DBG_IKE, "EAP-MS-CHAPv2 failed with error %N: '%s'",
+                mschapv2_error_names, error, sanitize(msg));
                        
        /**
-        * at this point, if the error is retryable, we MAY retry the
-        * authentication or MAY send a Change Password packet.
+        * at this point, if the error is retryable, we MAY retry the authentication
+        * or MAY send a Change Password packet.
         * 
-        * if the error is not retryable (or if we do neither of the
-        * above), we SHOULD send a Failure Response packet.
-        * windows clients don't do that, and since windows server 2008 r2
-        * behaves pretty odd if we do send a Failure Response, we just
-        * don't send one either.
+        * if the error is not retryable (or if we do neither of the above), we
+        * SHOULD send a Failure Response packet.
+        * windows clients don't do that, and since windows server 2008 r2 behaves
+        * pretty odd if we do send a Failure Response, we just don't send one
+        * either. windows 7 actually sends a delete notify (which, according to the
+        * logs, results in an error on windows server 2008 r2). 
         * 
-        * btw, windows server 2008 r2 does not send non-retryable errors
-        * for e.g. a disabled account but returns the windows error code in
-        * a notify payload of type 12345.
+        * btw, windows server 2008 r2 does not send non-retryable errors for e.g.
+        * a disabled account but returns the windows error code in a notify payload
+        * of type 12345.
         */
        
        status = FAILED;
@@ -896,8 +918,8 @@ static status_t process_peer(private_eap_mschapv2_t *this, eap_payload_t *in,
                }
                default:
                {
-                       DBG1(DBG_IKE, "EAP-MS-CHAPv2 received packet with unsupported OpCode (%N)!",
-                                       mschapv2_opcode_names, eap->opcode);
+                       DBG1(DBG_IKE, "EAP-MS-CHAPv2 received packet with unsupported "
+                                "OpCode (%N)!", mschapv2_opcode_names, eap->opcode);
                        break;
                }
        }
@@ -914,7 +936,7 @@ static status_t process_server_retry(private_eap_mschapv2_t *this,
        rng_t *rng;
        chunk_t hex;
        char msg[FAILURE_MESSAGE_LEN];
-       u_int16_t len = HEADER_LEN + FAILURE_MESSAGE_LEN - 1;
+       u_int16_t len = HEADER_LEN + FAILURE_MESSAGE_LEN - 1; /* no null byte */
        
        if (++this->retries > MAX_RETRIES)
        {
@@ -923,7 +945,8 @@ static status_t process_server_retry(private_eap_mschapv2_t *this,
                 * so, to clean up our state we just fail with an EAP-Failure.
                 * this gives an unknown error on the windows side, but is also fine
                 * with the standard. */
-               DBG1(DBG_IKE, "EAP-MS-CHAPv2 verification failed: maximum number of retries reached");
+               DBG1(DBG_IKE, "EAP-MS-CHAPv2 verification failed: "
+                        "maximum number of retries reached");
                return FAILED;
        }
        
@@ -938,6 +961,10 @@ static status_t process_server_retry(private_eap_mschapv2_t *this,
        rng->get_bytes(rng, CHALLENGE_LEN, this->challenge.ptr);
        rng->destroy(rng);
        
+       chunk_free(&this->nt_response);
+       chunk_free(&this->auth_response);
+       chunk_free(&this->msk);
+       
        eap = alloca(len);
        eap->code = EAP_REQUEST;
        eap->identifier = ++this->identifier;
@@ -950,7 +977,7 @@ static status_t process_server_retry(private_eap_mschapv2_t *this,
        hex = chunk_to_hex(this->challenge, NULL, TRUE);
        snprintf(msg, FAILURE_MESSAGE_LEN, "%s%s", FAILURE_MESSAGE, hex.ptr);
        chunk_free(&hex);
-       memcpy(eap->data, msg, FAILURE_MESSAGE_LEN - 1);
+       memcpy(eap->data, msg, FAILURE_MESSAGE_LEN - 1); /* no null byte */
        *out = eap_payload_create_data(chunk_create((void*) eap, len));
        
        /* delay the response for some time to make brute-force attacks harder */
@@ -971,6 +998,7 @@ static status_t process_server_response(private_eap_mschapv2_t *this,
        identification_t *userid;
        shared_key_t *shared;
        int name_len;
+       char buf[256];
        
        data = in->get_data(in);
        eap = (eap_mschapv2_header_t*)data.ptr;
@@ -985,16 +1013,16 @@ static status_t process_server_response(private_eap_mschapv2_t *this,
        peer_challenge = chunk_create(res->response.peer_challenge, CHALLENGE_LEN);
        
        name_len = min(data.len - RESPONSE_PAYLOAD_LEN, 255);
-       userid = identification_create_from_encoding(ID_EAP,
-                                                                                                chunk_create(res->name, name_len));
+       snprintf(buf, sizeof(buf), "%.*s", name_len, res->name);
+       userid = identification_create_from_string(buf);
+       DBG2(DBG_IKE, "EAP-MS-CHAPv2 username: '%Y'", userid);
        username = extract_username(userid);
-       DBG2(DBG_IKE, "EAP-MS-CHAPv2 username: '%.*s'", name_len, res->name);
        
        shared = charon->credentials->get_shared(charon->credentials,
                                                                                         SHARED_EAP, this->server, userid);
        if (shared == NULL)
        {
-               DBG1(DBG_IKE, "no EAP key found for hosts '%D' - '%D'",
+               DBG1(DBG_IKE, "no EAP key found for hosts '%Y' - '%Y'",
                                          this->server, userid);
                /* FIXME: windows 7 always sends the username that is first entered in
                 * the username box, even, if the user changes it during retries (probably
@@ -1002,18 +1030,22 @@ static status_t process_server_response(private_eap_mschapv2_t *this,
                 * thus, we could actually fail here, because retries do not make much
                 * sense. on the other hand, an attacker could guess usernames, if the
                 * error messages were different. */
+               userid->destroy(userid);
                return process_server_retry(this, out);
        }
        
        password = ascii_to_unicode(shared->get_key(shared));
        shared->destroy(shared);
        
-       if (GenerateStuff(this, this->challenge, peer_challenge, username, password) != SUCCESS)
+       if (GenerateStuff(this, this->challenge, peer_challenge,
+                                         username, password) != SUCCESS)
        {
                DBG1(DBG_IKE, "EAP-MS-CHAPv2 verification failed");     
+               userid->destroy(userid);
                chunk_clear(&password);
                return FAILED;
        }
+       userid->destroy(userid);
        chunk_clear(&password);
        
        if (memeq(res->response.nt_response, this->nt_response.ptr, this->nt_response.len))
@@ -1054,7 +1086,8 @@ static status_t process_server(private_eap_mschapv2_t *this, eap_payload_t *in,
        
        if (this->identifier != in->get_identifier(in))
        {
-               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message: unexpected identifier");
+               DBG1(DBG_IKE, "received invalid EAP-MS-CHAPv2 message: "
+                        "unexpected identifier");
                return FAILED;
        }
        
@@ -1083,8 +1116,8 @@ static status_t process_server(private_eap_mschapv2_t *this, eap_payload_t *in,
                }
                default:
                {
-                       DBG1(DBG_IKE, "EAP-MS-CHAPv2 received packet with unsupported OpCode (%N)!",
-                                       mschapv2_opcode_names, eap->opcode);
+                       DBG1(DBG_IKE, "EAP-MS-CHAPv2 received packet with unsupported "
+                                "OpCode (%N)!", mschapv2_opcode_names, eap->opcode);
                        break;
                }
        }