remove redundant check. Found by PVS-Studio
[freeradius.git] / src / lib / radius.c
index a7de70c..245b86c 100644 (file)
@@ -65,11 +65,6 @@ static void VP_HEXDUMP(char const *msg, uint8_t const *data, size_t len)
 
 
 /*
- *  The RFC says 4096 octets max, and most packets are less than 256.
- */
-#define MAX_PACKET_LEN 4096
-
-/*
  *     The maximum number of attributes which we allow in an incoming
  *     request.  If there are more attributes than this, the request
  *     is rejected.
@@ -184,13 +179,38 @@ static void print_hex_data(uint8_t const *ptr, int attrlen, int depth)
 }
 
 
-void rad_print_hex(RADIUS_PACKET *packet)
+void rad_print_hex(RADIUS_PACKET const *packet)
 {
        int i;
 
        if (!packet->data || !fr_log_fp) return;
 
-       fprintf(fr_log_fp, "  Code:\t\t%u\n", packet->data[0]);
+       fprintf(fr_log_fp, "  Socket:\t%d\n", packet->sockfd);
+#ifdef WITH_TCP
+       fprintf(fr_log_fp, "  Proto:\t%d\n", packet->proto);
+#endif
+
+       if (packet->src_ipaddr.af == AF_INET) {
+               char buffer[32];
+
+               fprintf(fr_log_fp, "  Src IP:\t%s\n",
+                       inet_ntop(packet->src_ipaddr.af,
+                                 &packet->src_ipaddr.ipaddr,
+                                 buffer, sizeof(buffer)));
+               fprintf(fr_log_fp, "    port:\t%u\n", packet->src_port);
+
+               fprintf(fr_log_fp, "  Dst IP:\t%s\n",
+                       inet_ntop(packet->dst_ipaddr.af,
+                                 &packet->dst_ipaddr.ipaddr,
+                                 buffer, sizeof(buffer)));
+               fprintf(fr_log_fp, "    port:\t%u\n", packet->dst_port);
+       }
+
+       if (packet->data[0] < FR_MAX_PACKET_CODE) {
+               fprintf(fr_log_fp, "  Code:\t\t(%d) %s\n", packet->data[0], fr_packet_codes[packet->data[0]]);
+       } else {
+               fprintf(fr_log_fp, "  Code:\t\t%u\n", packet->data[0]);
+       }
        fprintf(fr_log_fp, "  Id:\t\t%u\n", packet->data[1]);
        fprintf(fr_log_fp, "  Length:\t%u\n", ((packet->data[2] << 8) |
                                   (packet->data[3])));
@@ -339,60 +359,59 @@ ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port,
        struct sockaddr_storage src;
        socklen_t               sizeof_src = sizeof(src);
 
-       data_len = recvfrom(sockfd, header, sizeof(header), MSG_PEEK,
-                           (struct sockaddr *)&src, &sizeof_src);
+       data_len = recvfrom(sockfd, header, sizeof(header), MSG_PEEK, (struct sockaddr *)&src, &sizeof_src);
        if (data_len < 0) {
                if ((errno == EAGAIN) || (errno == EINTR)) return 0;
                return -1;
        }
 
        /*
+        *      Convert AF.  If unknown, discard packet.
+        */
+       if (!fr_sockaddr2ipaddr(&src, sizeof_src, src_ipaddr, src_port)) {
+               FR_DEBUG_STRERROR_PRINTF("Unknown address family");
+               rad_recv_discard(sockfd);
+
+               return 1;
+       }
+
+       /*
         *      Too little data is available, discard the packet.
         */
        if (data_len < 4) {
                FR_DEBUG_STRERROR_PRINTF("Expected at least 4 bytes of header data, got %zu bytes", data_len);
+invalid:
+               FR_DEBUG_STRERROR_PRINTF("Invalid data from %s: %s",
+                                        fr_inet_ntop(src_ipaddr->af, &src_ipaddr->ipaddr),
+                                        fr_strerror());
                rad_recv_discard(sockfd);
 
                return 1;
+       }
 
-       } else {                /* we got 4 bytes of data. */
-               /*
-                *      See how long the packet says it is.
-                */
-               packet_len = (header[2] * 256) + header[3];
-
-               /*
-                *      The length in the packet says it's less than
-                *      a RADIUS header length: discard it.
-                */
-               if (packet_len < RADIUS_HDR_LEN) {
-                       FR_DEBUG_STRERROR_PRINTF("Expected at least " STRINGIFY(RADIUS_HDR_LEN)  " bytes of packet "
-                                                "data, got %zu bytes", packet_len);
-                       rad_recv_discard(sockfd);
-
-                       return 1;
-
-                       /*
-                        *      Enforce RFC requirements, for sanity.
-                        *      Anything after 4k will be discarded.
-                        */
-               } else if (packet_len > MAX_PACKET_LEN) {
-                       FR_DEBUG_STRERROR_PRINTF("Length field value too large, expected maximum of "
-                                                STRINGIFY(MAX_PACKET_LEN) " bytes, got %zu bytes", packet_len);
-                       rad_recv_discard(sockfd);
+       /*
+        *      See how long the packet says it is.
+        */
+       packet_len = (header[2] * 256) + header[3];
 
-                       return 1;
-               }
+       /*
+        *      The length in the packet says it's less than
+        *      a RADIUS header length: discard it.
+        */
+       if (packet_len < RADIUS_HDR_LEN) {
+               FR_DEBUG_STRERROR_PRINTF("Expected at least " STRINGIFY(RADIUS_HDR_LEN)  " bytes of packet "
+                                        "data, got %zu bytes", packet_len);
+               goto invalid;
        }
 
        /*
-        *      Convert AF.  If unknown, discard packet.
+        *      Enforce RFC requirements, for sanity.
+        *      Anything after 4k will be discarded.
         */
-       if (!fr_sockaddr2ipaddr(&src, sizeof_src, src_ipaddr, src_port)) {
-               FR_DEBUG_STRERROR_PRINTF("Unkown address family");
-               rad_recv_discard(sockfd);
-
-               return 1;
+       if (packet_len > MAX_PACKET_LEN) {
+               FR_DEBUG_STRERROR_PRINTF("Length field value too large, expected maximum of "
+                                        STRINGIFY(MAX_PACKET_LEN) " bytes, got %zu bytes", packet_len);
+               goto invalid;
        }
 
        *code = header[0];
@@ -595,62 +614,59 @@ static void make_passwd(uint8_t *output, ssize_t *outlen,
        memcpy(output, passwd, len);
 }
 
+
 static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen,
                               uint8_t const *input, size_t inlen, size_t room,
                               char const *secret, uint8_t const *vector)
 {
        FR_MD5_CTX context, old;
        uint8_t digest[AUTH_VECTOR_LEN];
-       uint8_t passwd[MAX_STRING_LEN + AUTH_VECTOR_LEN];
-       int     i, n;
-       int     len;
+       size_t  i, n;
+       size_t  encrypted_len;
 
        /*
-        *      Be paranoid.
+        *      The password gets encoded with a 1-byte "length"
+        *      field.  Ensure that it doesn't overflow.
         */
        if (room > 253) room = 253;
 
        /*
-        *      Account for 2 bytes of the salt, and round the room
-        *      available down to the nearest multiple of 16.  Then,
-        *      subtract one from that to account for the length byte,
-        *      and the resulting number is the upper bound on the data
-        *      to copy.
-        *
-        *      We could short-cut this calculation just be forcing
-        *      inlen to be no more than 239.  It would work for all
-        *      VSA's, as we don't pack multiple VSA's into one
-        *      attribute.
-        *
-        *      However, this calculation is more general, if a little
-        *      complex.  And it will work in the future for all possible
-        *      kinds of weird attribute packing.
+        *      Limit the maximum size of the input password.  2 bytes
+        *      are taken up by the salt, and one by the encoded
+        *      "length" field.  Note that if we have a tag, the
+        *      "room" will be 252 octets, not 253 octets.
         */
-       room -= 2;
-       room -= (room & 0x0f);
-       room--;
-
-       if (inlen > room) inlen = room;
+       if (inlen > (room - 3)) inlen = room - 3;
 
        /*
-        *      Length of the encrypted data is password length plus
-        *      one byte for the length of the password.
+        *      Length of the encrypted data is the clear-text
+        *      password length plus one byte which encodes the length
+        *      of the password.  We round up to the nearest encoding
+        *      block.  Note that this can result in the encoding
+        *      length being more than 253 octets.
         */
-       len = inlen + 1;
-       if ((len & 0x0f) != 0) {
-               len += 0x0f;
-               len &= ~0x0f;
+       encrypted_len = inlen + 1;
+       if ((encrypted_len & 0x0f) != 0) {
+               encrypted_len += 0x0f;
+               encrypted_len &= ~0x0f;
        }
-       *outlen = len + 2;      /* account for the salt */
 
        /*
-        *      Copy the password over.
+        *      We need 2 octets for the salt, followed by the actual
+        *      encrypted data.
         */
-       memcpy(passwd + 3, input, inlen);
-       memset(passwd + 3 + inlen, 0, sizeof(passwd) - 3 - inlen);
+       if (encrypted_len > (room - 2)) encrypted_len = room - 2;
+
+       *outlen = encrypted_len + 2;    /* account for the salt */
 
        /*
-        *      Generate salt.  The RFC's say:
+        *      Copy the password over, and zero-fill the remainder.
+        */
+       memcpy(output + 3, input, inlen);
+       memset(output + 3 + inlen, 0, *outlen - 3 - inlen);
+
+       /*
+        *      Generate salt.  The RFCs say:
         *
         *      The high bit of salt[0] must be set, each salt in a
         *      packet should be unique, and they should be random
@@ -658,33 +674,40 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen,
         *      So, we set the high bit, add in a counter, and then
         *      add in some CSPRNG data.  should be OK..
         */
-       passwd[0] = (0x80 | ( ((salt_offset++) & 0x0f) << 3) |
+       output[0] = (0x80 | ( ((salt_offset++) & 0x0f) << 3) |
                     (fr_rand() & 0x07));
-       passwd[1] = fr_rand();
-       passwd[2] = inlen;      /* length of the password string */
+       output[1] = fr_rand();
+       output[2] = inlen;      /* length of the password string */
 
        fr_md5_init(&context);
        fr_md5_update(&context, (uint8_t const *) secret, strlen(secret));
        old = context;
 
        fr_md5_update(&context, vector, AUTH_VECTOR_LEN);
-       fr_md5_update(&context, &passwd[0], 2);
+       fr_md5_update(&context, &output[0], 2);
+
+       for (n = 0; n < encrypted_len; n += AUTH_PASS_LEN) {
+               size_t block_len;
 
-       for (n = 0; n < len; n += AUTH_PASS_LEN) {
                if (n > 0) {
                        context = old;
                        fr_md5_update(&context,
-                                      passwd + 2 + n - AUTH_PASS_LEN,
+                                      output + 2 + n - AUTH_PASS_LEN,
                                       AUTH_PASS_LEN);
                }
 
                fr_md5_final(digest, &context);
 
-               for (i = 0; i < AUTH_PASS_LEN; i++) {
-                       passwd[i + 2 + n] ^= digest[i];
+               if ((2 + n + AUTH_PASS_LEN) < room) {
+                       block_len = AUTH_PASS_LEN;
+               } else {
+                       block_len = room - 2 - n;
+               }
+
+               for (i = 0; i < block_len; i++) {
+                       output[i + 2 + n] ^= digest[i];
                }
        }
-       memcpy(output, passwd, len + 2);
 }
 
 static int do_next_tlv(VALUE_PAIR const *vp, VALUE_PAIR const *next, int nest)
@@ -853,10 +876,7 @@ static ssize_t vp2data_any(RADIUS_PACKET const *packet,
        case PW_TYPE_STRING:
        case PW_TYPE_OCTETS:
                data = vp->data.ptr;
-               if (!data) {
-                       fr_strerror_printf("ERROR: Cannot encode NULL data");
-                       return -1;
-               }
+               if (!data) return 0;
                break;
 
        case PW_TYPE_IFID:
@@ -1335,9 +1355,9 @@ static ssize_t vp2attr_rfc(RADIUS_PACKET const *packet,
        ptr[0] = attribute & 0xff;
        ptr[1] = 2;
 
-       if (room > ((unsigned) 255 - ptr[1])) room = 255 - ptr[1];
+       if (room > 255) room = 255;
 
-       len = vp2data_any(packet, original, secret, 0, pvp, ptr + ptr[1], room);
+       len = vp2data_any(packet, original, secret, 0, pvp, ptr + ptr[1], room - ptr[1]);
        if (len <= 0) return len;
 
        ptr[1] += len;
@@ -1422,12 +1442,10 @@ static ssize_t vp2attr_vsa(RADIUS_PACKET const *packet,
 
        }
 
-       if (room > ((unsigned) 255 - (dv->type + dv->length))) {
-               room = 255 - (dv->type + dv->length);
-       }
+       if (room > 255) room = 255;
 
        len = vp2data_any(packet, original, secret, 0, pvp,
-                         ptr + dv->type + dv->length, room);
+                         ptr + dv->type + dv->length, room - (dv->type + dv->length));
        if (len <= 0) return len;
 
        if (dv->length) ptr[dv->type + dv->length - 1] += len;
@@ -1527,11 +1545,11 @@ int rad_vp2vsa(RADIUS_PACKET const *packet, RADIUS_PACKET const *original,
        lvalue = htonl(vp->da->vendor);
        memcpy(ptr + 2, &lvalue, 4);
 
-       if (room > ((unsigned) 255 - ptr[1])) room = 255 - ptr[1];
+       if (room > 255) room = 255;
 
        len = vp2attr_vsa(packet, original, secret, pvp,
                          vp->da->attr, vp->da->vendor,
-                         ptr + ptr[1], room);
+                         ptr + ptr[1], room - ptr[1]);
        if (len < 0) return len;
 
 #ifndef NDEBUG
@@ -1588,7 +1606,7 @@ int rad_vp2rfc(RADIUS_PACKET const *packet,
        /*
         *      Message-Authenticator is hard-coded.
         */
-       if (!vp->da->vendor && (vp->da->attr == PW_MESSAGE_AUTHENTICATOR)) {
+       if (vp->da->attr == PW_MESSAGE_AUTHENTICATOR) {
                if (room < 18) return -1;
 
                ptr[0] = PW_MESSAGE_AUTHENTICATOR;
@@ -1679,7 +1697,10 @@ int rad_vp2attr(RADIUS_PACKET const *packet, RADIUS_PACKET const *original,
         *      RFC format attributes take the fast path.
         */
        if (!vp->da->vendor) {
-               if (vp->da->attr > 255) return 0;
+               if (vp->da->attr > 255) {
+                       *pvp = vp->next;
+                       return 0;
+               }
 
                return rad_vp2rfc(packet, original, secret, pvp,
                                  start, room);
@@ -1787,7 +1808,7 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
         */
        reply = packet->vps;
        while (reply) {
-               size_t last_len;
+               size_t last_len, room;
                char const *last_name = NULL;
 
                VERIFY_VP(reply);
@@ -1816,6 +1837,20 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
                }
 
                /*
+                *      We allow zero-length strings in "unlang", but
+                *      skip them (except for CUI, thanks WiMAX!) on
+                *      all other attributes.
+                */
+               if (reply->vp_length == 0) {
+                       if ((reply->da->vendor != 0) ||
+                           ((reply->da->attr != PW_CHARGEABLE_USER_IDENTITY) &&
+                            (reply->da->attr != PW_MESSAGE_AUTHENTICATOR))) {
+                               reply = reply->next;
+                               continue;
+                       }
+               }
+
+               /*
                 *      Set the Message-Authenticator to the correct
                 *      length and initial value.
                 */
@@ -1831,8 +1866,11 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
                }
                last_name = reply->da->name;
 
-               len = rad_vp2attr(packet, original, secret, &reply, ptr,
-                                 ((uint8_t *) data) + sizeof(data) - ptr);
+               room = ((uint8_t *) data) + sizeof(data) - ptr;
+
+               if (room <= 2) break;
+
+               len = rad_vp2attr(packet, original, secret, &reply, ptr, room);
                if (len < 0) return -1;
 
                /*
@@ -1903,8 +1941,44 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
        }
 
        /*
+        *      Set up the authentication vector with zero, or with
+        *      the original vector, prior to signing.
+        */
+       switch (packet->code) {
+       case PW_CODE_ACCOUNTING_REQUEST:
+       case PW_CODE_DISCONNECT_REQUEST:
+       case PW_CODE_COA_REQUEST:
+               memset(packet->vector, 0, AUTH_VECTOR_LEN);
+               break;
+
+       case PW_CODE_ACCESS_ACCEPT:
+       case PW_CODE_ACCESS_REJECT:
+       case PW_CODE_ACCESS_CHALLENGE:
+       case PW_CODE_ACCOUNTING_RESPONSE:
+       case PW_CODE_DISCONNECT_ACK:
+       case PW_CODE_DISCONNECT_NAK:
+       case PW_CODE_COA_ACK:
+       case PW_CODE_COA_NAK:
+               if (!original) {
+                       fr_strerror_printf("ERROR: Cannot sign response packet without a request packet");
+                       return -1;
+               }
+               memcpy(packet->vector, original->vector, AUTH_VECTOR_LEN);
+               break;
+
+       case PW_CODE_ACCESS_REQUEST:
+       case PW_CODE_STATUS_SERVER:
+       default:
+               break;          /* packet->vector is already random bytes */
+       }
+
+#ifndef NDEBUG
+       if ((fr_debug_lvl > 3) && fr_log_fp) rad_print_hex(packet);
+#endif
+
+       /*
         *      If there's a Message-Authenticator, update it
-        *      now, BEFORE updating the authentication vector.
+        *      now.
         */
        if (packet->offset > 0) {
                uint8_t calc_auth_vector[AUTH_VECTOR_LEN];
@@ -1921,6 +1995,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
                case PW_CODE_DISCONNECT_NAK:
                case PW_CODE_COA_REQUEST:
                case PW_CODE_COA_ACK:
+               case PW_CODE_COA_NAK:
                        memset(hdr->vector, 0, AUTH_VECTOR_LEN);
                        break;
 
@@ -1928,17 +2003,11 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
                case PW_CODE_ACCESS_ACCEPT:
                case PW_CODE_ACCESS_REJECT:
                case PW_CODE_ACCESS_CHALLENGE:
-                       if (!original) {
-                               fr_strerror_printf("ERROR: Cannot sign response packet without a request packet");
-                               return -1;
-                       }
-                       memcpy(hdr->vector, original->vector,
-                              AUTH_VECTOR_LEN);
+                       memcpy(hdr->vector, original->vector, AUTH_VECTOR_LEN);
                        break;
 
-               default:        /* others have vector already set to zero */
+               default:
                        break;
-
                }
 
                /*
@@ -1951,21 +2020,20 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original,
                            (uint8_t const *) secret, strlen(secret));
                memcpy(packet->data + packet->offset + 2,
                       calc_auth_vector, AUTH_VECTOR_LEN);
-
-               /*
-                *      Copy the original request vector back
-                *      to the raw packet.
-                */
-               memcpy(hdr->vector, packet->vector, AUTH_VECTOR_LEN);
        }
 
        /*
+        *      Copy the request authenticator over to the packet.
+        */
+       memcpy(hdr->vector, packet->vector, AUTH_VECTOR_LEN);
+
+       /*
         *      Switch over the packet code, deciding how to
         *      sign the packet.
         */
        switch (packet->code) {
                /*
-                *      Request packets are not signed, bur
+                *      Request packets are not signed, but
                 *      have a random authentication vector.
                 */
        case PW_CODE_ACCESS_REQUEST:
@@ -2276,6 +2344,8 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
        bool                    seen_ma = false;
        uint32_t                num_attributes;
        decode_fail_t           failure = DECODE_FAIL_NONE;
+       bool                    eap = false;
+       bool                    non_eap = false;
 
        /*
         *      Check for packets smaller than the packet header.
@@ -2481,6 +2551,13 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
                         */
                case PW_EAP_MESSAGE:
                        require_ma = true;
+                       eap = true;
+                       break;
+
+               case PW_USER_PASSWORD:
+               case PW_CHAP_PASSWORD:
+               case PW_ARAP_PASSWORD:
+                       non_eap = true;
                        break;
 
                case PW_MESSAGE_AUTHENTICATOR:
@@ -2558,6 +2635,15 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason)
                goto finish;
        }
 
+       if (eap && non_eap) {
+               FR_DEBUG_STRERROR_PRINTF("Bad packet from host %s:  Packet contains EAP-Message and non-EAP authentication attribute",
+                          inet_ntop(packet->src_ipaddr.af,
+                                    &packet->src_ipaddr.ipaddr,
+                                    host_ipaddr, sizeof(host_ipaddr)));
+               failure = DECODE_FAIL_TOO_MANY_AUTH;
+               goto finish;
+       }
+
        /*
         *      Fill RADIUS header fields
         */
@@ -3475,16 +3561,27 @@ ssize_t data2vp(TALLOC_CTX *ctx,
                        buffer[253] = '\0';
 
                        /*
-                        *      Take off trailing zeros from the END.
-                        *      This allows passwords to have zeros in
-                        *      the middle of a field.
-                        *
-                        *      However, if the password has a zero at
-                        *      the end, it will get mashed by this
-                        *      code.  There's really no way around
-                        *      that.
+                        *      MS-CHAP-MPPE-Keys are 24 octets, and
+                        *      encrypted.  Since it's binary, we can't
+                        *      look for trailing zeros.
                         */
-                       while ((datalen > 0) && (buffer[datalen - 1] == '\0')) datalen--;
+                       if (da->flags.length) {
+                               if (datalen > da->flags.length) {
+                                       datalen = da->flags.length;
+                               } /* else leave datalen alone */
+                       } else {
+                               /*
+                                *      Take off trailing zeros from the END.
+                                *      This allows passwords to have zeros in
+                                *      the middle of a field.
+                                *
+                                *      However, if the password has a zero at
+                                *      the end, it will get mashed by this
+                                *      code.  There's really no way around
+                                *      that.
+                                */
+                               while ((datalen > 0) && (buffer[datalen - 1] == '\0')) datalen--;
+                       }
                        break;
 
                /*
@@ -4256,8 +4353,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret,
  * This is per RFC-2868 which adds a two char SALT to the initial intermediate
  * value MD5 hash.
  */
-int rad_tunnel_pwencode(char *passwd, size_t *pwlen, char const *secret,
-                       uint8_t const *vector)
+ssize_t rad_tunnel_pwencode(char *passwd, size_t *pwlen, char const *secret, uint8_t const *vector)
 {
        uint8_t buffer[AUTH_VECTOR_LEN + MAX_STRING_LEN + 3];
        unsigned char   digest[AUTH_VECTOR_LEN];
@@ -4270,14 +4366,15 @@ int rad_tunnel_pwencode(char *passwd, size_t *pwlen, char const *secret,
        if (len > 127) len = 127;
 
        /*
-        * Shift the password 3 positions right to place a salt and original
-        * length, tag will be added automatically on packet send
+        *      Shift the password 3 positions right to place a salt and original
+        *      length, tag will be added automatically on packet send.
         */
-       for (n=len ; n>=0 ; n--) passwd[n+3] = passwd[n];
+       for (n = len ; n >= 0 ; n--) passwd[n + 3] = passwd[n];
        salt = passwd;
        passwd += 2;
+
        /*
-        * save original password length as first password character;
+        *      save original password length as first password character;
         */
        *passwd = len;
        len += 1;
@@ -4338,20 +4435,19 @@ int rad_tunnel_pwencode(char *passwd, size_t *pwlen, char const *secret,
  * initial intermediate value, to differentiate it from the
  * above.
  */
-int rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret,
-                       uint8_t const *vector)
+ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, uint8_t const *vector)
 {
        FR_MD5_CTX  context, old;
        uint8_t         digest[AUTH_VECTOR_LEN];
        int             secretlen;
-       unsigned        i, n, len, reallen;
+       size_t          i, n, encrypted_len, reallen;
 
-       len = *pwlen;
+       encrypted_len = *pwlen;
 
        /*
         *      We need at least a salt.
         */
-       if (len < 2) {
+       if (encrypted_len < 2) {
                fr_strerror_printf("tunnel password is too short");
                return -1;
        }
@@ -4366,13 +4462,13 @@ int rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret,
         *      more data.  So the 'data_len' field may be wrong,
         *      but that's ok...
         */
-       if (len <= 3) {
+       if (encrypted_len <= 3) {
                passwd[0] = 0;
                *pwlen = 0;
                return 0;
        }
 
-       len -= 2;               /* discount the salt */
+       encrypted_len -= 2;             /* discount the salt */
 
        /*
         *      Use the secret to setup the decryption digest
@@ -4392,10 +4488,20 @@ int rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret,
        fr_md5_update(&context, passwd, 2);
 
        reallen = 0;
-       for (n = 0; n < len; n += AUTH_PASS_LEN) {
-               int base = 0;
+       for (n = 0; n < encrypted_len; n += AUTH_PASS_LEN) {
+               size_t base;
+               size_t block_len = AUTH_PASS_LEN;
+
+               /*
+                *      Ensure we don't overflow the input on MD5
+                */
+               if ((n + 2 + AUTH_PASS_LEN) > *pwlen) {
+                       block_len = *pwlen - n - 2;
+               }
 
                if (n == 0) {
+                       base = 1;
+
                        fr_md5_final(digest, &context);
 
                        context = old;
@@ -4406,31 +4512,27 @@ int rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret,
                         *      'data_len' field.  Ensure it's sane.
                         */
                        reallen = passwd[2] ^ digest[0];
-                       if (reallen >len) {
+                       if (reallen > encrypted_len) {
                                fr_strerror_printf("tunnel password is too long for the attribute");
                                return -1;
                        }
 
-                       fr_md5_update(&context, passwd + 2, AUTH_PASS_LEN);
+                       fr_md5_update(&context, passwd + 2, block_len);
 
-                       base = 1;
                } else {
+                       base = 0;
+
                        fr_md5_final(digest, &context);
 
                        context = old;
-                       fr_md5_update(&context, passwd + n + 2, AUTH_PASS_LEN);
+                       fr_md5_update(&context, passwd + n + 2, block_len);
                }
 
-               for (i = base; i < AUTH_PASS_LEN; i++) {
+               for (i = base; i < block_len; i++) {
                        passwd[n + i - 1] = passwd[n + i + 2] ^ digest[i];
                }
        }
 
-       /*
-        *      See make_tunnel_password, above.
-        */
-       if (reallen > 239) reallen = 239;
-
        *pwlen = reallen;
        passwd[reallen] = 0;