/* * If we're proxying EAP, then there may be magic we need * to do. */ static rlm_rcode_t mod_post_proxy(void *inst, REQUEST *request) { size_t i; size_t len; VALUE_PAIR *vp; eap_handler_t *handler; /* * Just in case the admin lists EAP in post-proxy-type Fail. */ if (!request->proxy_reply) return RLM_MODULE_NOOP; /* * If there was a handler associated with this request, * then it's a tunneled request which was proxied... */ handler = request_data_get(request, inst, REQUEST_DATA_eap_handler_t); if (handler != NULL) { rlm_rcode_t rcode; eap_tunnel_data_t *data; /* * Grab the tunnel callbacks from the request. */ data = (eap_tunnel_data_t *) request_data_get(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK); if (!data) { radlog_request(L_ERR, 0, request, "Failed to retrieve callback for tunneled session!"); eap_handler_free(inst, handler); return RLM_MODULE_FAIL; } /* * Do the callback... */ RDEBUG2("Doing post-proxy callback"); rcode = data->callback(handler, data->tls_session); free(data); if (rcode == 0) { RDEBUG2("Failed in post-proxy callback"); eap_fail(handler); eap_handler_free(inst, handler); return RLM_MODULE_REJECT; } /* * We are done, wrap the EAP-request in RADIUS to send * with all other required radius attributes */ eap_compose(handler); /* * Add to the list only if it is EAP-Request, OR if * it's LEAP, and a response. */ if ((handler->eap_ds->request->code == PW_EAP_REQUEST) && (handler->eap_ds->request->type.num >= PW_EAP_MD5)) { if (!eaplist_add(inst, handler)) { eap_fail(handler); eap_handler_free(inst, handler); return RLM_MODULE_FAIL; } } else { /* couldn't have been LEAP, there's no tunnel */ RDEBUG2("Freeing handler"); /* handler is not required any more, free it now */ eap_handler_free(inst, handler); } /* * If it's an Access-Accept, RFC 2869, Section 2.3.1 * says that we MUST include a User-Name attribute in the * Access-Accept. */ if ((request->reply->code == PW_AUTHENTICATION_ACK) && request->username) { /* * Doesn't exist, add it in. */ vp = pairfind(request->reply->vps, PW_USER_NAME, 0, TAG_ANY); if (!vp) { pairmake_reply("User-Name", request->username->vp_strvalue, T_OP_EQ); } } return RLM_MODULE_OK; } else { RDEBUG2("No pre-existing handler found"); } /* * There may be more than one Cisco-AVPair. * Ensure we find the one with the LEAP attribute. */ vp = request->proxy_reply->vps; for (;;) { /* * Hmm... there's got to be a better way to * discover codes for vendor attributes. * * This is vendor Cisco (9), Cisco-AVPair * attribute (1) */ vp = pairfind(vp, 1, 9, TAG_ANY); if (!vp) { return RLM_MODULE_NOOP; } /* * If it's "leap:session-key", then stop. * * The format is VERY specific! */ if (strncasecmp(vp->vp_strvalue, "leap:session-key=", 17) == 0) { break; } /* * Not this AV-pair. Go to the next one. */ vp = vp->next; } /* * The format is very specific. */ if (vp->length != 17 + 34) { RDEBUG2("Cisco-AVPair with leap:session-key has incorrect length %d: Expected %d", vp->length, 17 + 34); return RLM_MODULE_NOOP; } /* * Decrypt the session key, using the proxy data. */ i = 34; /* starts off with 34 octets */ len = rad_tunnel_pwdecode(vp->vp_octets + 17, &i, request->home_server->secret, request->proxy->vector); /* * FIXME: Assert that i == 16. */ /* * Encrypt the session key again, using the request data. */ rad_tunnel_pwencode(vp->vp_strvalue + 17, &len, request->client->secret, request->packet->vector); return RLM_MODULE_UPDATED; }
/* * Reply to the request. Also attach * reply attribute value pairs and any user message provided. */ int rad_send(RADIUS_PACKET *packet, const RADIUS_PACKET *original, const char *secret) { VALUE_PAIR *reply; struct sockaddr_in saremote; struct sockaddr_in *sa; const char *what; uint8_t ip_buffer[16]; if ((packet->code > 0) && (packet->code < 52)) { what = packet_codes[packet->code]; } else { what = "Reply"; } /* * First time through, allocate room for the packet */ if (!packet->data) { radius_packet_t *hdr; uint32_t lvalue; uint8_t *ptr, *length_ptr, *vsa_length_ptr; uint8_t digest[16]; int secretlen; int vendorcode, vendorpec; u_short total_length; int len, allowed; int msg_auth_offset = 0; /* * For simplicity in the following logic, we allow * the attributes to "overflow" the 4k maximum * RADIUS packet size, by one attribute. */ uint8_t data[MAX_PACKET_LEN + 256]; /* * Use memory on the stack, until we know how * large the packet will be. */ hdr = (radius_packet_t *) data; /* * Build standard header */ hdr->code = packet->code; hdr->id = packet->id; if ((packet->code == PW_ACCOUNTING_REQUEST) || (packet->code == PW_DISCONNECT_REQUEST)) { memset(hdr->vector, 0, AUTH_VECTOR_LEN); } else { memcpy(hdr->vector, packet->vector, AUTH_VECTOR_LEN); } DEBUG("Sending %s of id %d to %s:%d\n", what, packet->id, ip_ntoa((char *)ip_buffer, packet->dst_ipaddr), packet->dst_port); total_length = AUTH_HDR_LEN; /* * Load up the configuration values for the user */ ptr = hdr->data; vendorcode = 0; vendorpec = 0; vsa_length_ptr = NULL; for (reply = packet->vps; reply; reply = reply->next) { /* * Ignore non-wire attributes */ if ((VENDOR(reply->attribute) == 0) && ((reply->attribute & 0xFFFF) > 0xff)) { continue; } /* * Check that the packet is no more than * 4k in size, AFTER over-flowing the 4k * boundary. Note that the 'data' * buffer, above, is one attribute longer * than necessary, in order to permit * this overflow. */ if (total_length > MAX_PACKET_LEN) { librad_log("ERROR: Too many attributes for packet, result is larger than RFC maximum of 4k"); return -1; } /* * Do stuff for Message-Authenticator */ if (reply->attribute == PW_MESSAGE_AUTHENTICATOR) { /* * Set it to zero! */ reply->length = AUTH_VECTOR_LEN; memset(reply->strvalue, 0, AUTH_VECTOR_LEN); msg_auth_offset = total_length; } /* * Print out ONLY the attributes which * we're sending over the wire, and print * them out BEFORE they're encrypted. */ debug_pair(reply); /* * We have a different vendor. Re-set * the vendor codes. */ if (vendorcode != VENDOR(reply->attribute)) { vendorcode = 0; vendorpec = 0; vsa_length_ptr = NULL; } /* * If the Vendor-Specific attribute is getting * full, then create a new VSA attribute * * FIXME: Multiple VSA's per Vendor-Specific * SHOULD be configurable. When that's done, * the (1), below, can be changed to point to * a configuration variable which is set TRUE * if the NAS cannot understand multiple VSA's * per Vendor-Specific */ if ((1) || /* ALWAYS create a new Vendor-Specific */ (vsa_length_ptr && (reply->length + *vsa_length_ptr) >= MAX_STRING_LEN)) { vendorcode = 0; vendorpec = 0; vsa_length_ptr = NULL; } /* * Maybe we have the start of a set of * (possibly many) VSA attributes from * one vendor. Set a global VSA wrapper */ if ((vendorcode == 0) && ((vendorcode = VENDOR(reply->attribute)) != 0)) { vendorpec = dict_vendorpec(vendorcode); /* * This is a potentially bad error... * we can't find the vendor ID! */ if (vendorpec == 0) { /* FIXME: log an error */ continue; } /* * Build a VSA header. */ *ptr++ = PW_VENDOR_SPECIFIC; vsa_length_ptr = ptr; *ptr++ = 6; lvalue = htonl(vendorpec); memcpy(ptr, &lvalue, 4); ptr += 4; total_length += 6; } if (vendorpec == VENDORPEC_USR) { lvalue = htonl(reply->attribute & 0xFFFF); memcpy(ptr, &lvalue, 4); length_ptr = vsa_length_ptr; total_length += 4; *length_ptr += 4; ptr += 4; /* * Each USR attribute gets it's own * VSA wrapper, so we re-set the * vendor specific information. */ vendorcode = 0; vendorpec = 0; vsa_length_ptr = NULL; } else { /* * All other attributes are as * per the RFC spec. */ *ptr++ = (reply->attribute & 0xFF); length_ptr = ptr; if (vsa_length_ptr) *vsa_length_ptr += 2; *ptr++ = 2; total_length += 2; } switch(reply->type) { /* * Ascend binary attributes are * stored internally in binary form. */ case PW_TYPE_ABINARY: case PW_TYPE_STRING: case PW_TYPE_OCTETS: /* * FIXME: HACK for non-updated dictionaries. * REMOVE in a future release. */ if ((strcmp(reply->name, "Ascend-Send-Secret") == 0) || (strcmp(reply->name, "Ascend-Receive-Secret") == 0)) { reply->flags.encrypt = FLAG_ENCRYPT_ASCEND_SECRET; } if (reply->attribute == PW_USER_PASSWORD) { reply->flags.encrypt = FLAG_ENCRYPT_USER_PASSWORD; } /* * Encrypt the various password styles */ switch (reply->flags.encrypt) { default: break; case FLAG_ENCRYPT_USER_PASSWORD: rad_pwencode((char *)reply->strvalue, &(reply->length), (const char *)secret, (const char *)packet->vector); break; case FLAG_ENCRYPT_TUNNEL_PASSWORD: rad_tunnel_pwencode(reply->strvalue, &(reply->length), secret, packet->vector); break; case FLAG_ENCRYPT_ASCEND_SECRET: make_secret(digest, packet->vector, secret, reply->strvalue); memcpy(reply->strvalue, digest, AUTH_VECTOR_LEN ); reply->length = AUTH_VECTOR_LEN; } /* switch over encryption flags */ len = reply->length; /* * Set the TAG at the beginning * of the string if tagged. If * tag value is not valid for * tagged attribute, make it 0x00 * per RFC 2868. -cparker */ if (reply->flags.has_tag) { if (TAG_VALID(reply->flags.tag)) { len++; *ptr++ = reply->flags.tag; } else if (reply->flags.encrypt == FLAG_ENCRYPT_TUNNEL_PASSWORD) { /* * Tunnel passwords * REQUIRE a tag, * even if we don't * have a valid * tag. */ len++; *ptr++ = 0x00; } /* else don't write a tag */ } /* else the attribute doesn't have a tag */ /* * Ensure we don't go too far. * The 'length' of the attribute * may be 0..255, minus whatever * octets are used in the attribute * header. */ allowed = 255; if (vsa_length_ptr) { allowed -= *vsa_length_ptr; } else { allowed -= *length_ptr; } if (len > allowed) { len = allowed; } *length_ptr += len; if (vsa_length_ptr) *vsa_length_ptr += len; /* * If we have tagged attributes we can't assume that * len == reply->length. Use reply->length for copying * the string data into the packet. Use len for the * true length of the string+tags. */ memcpy(ptr, reply->strvalue, reply->length); ptr += reply->length; total_length += len; break; case PW_TYPE_INTEGER: case PW_TYPE_IPADDR: *length_ptr += 4; if (vsa_length_ptr) *vsa_length_ptr += 4; if (reply->type == PW_TYPE_INTEGER ) { /* If tagged, the tag becomes the MSB of the value */ if(reply->flags.has_tag) { /* Tag must be ( 0x01 -> 0x1F ) OR 0x00 */ if(!TAG_VALID(reply->flags.tag)) { reply->flags.tag = 0x00; } lvalue = htonl((reply->lvalue & 0xffffff) | ((reply->flags.tag & 0xff) << 24)); } else { lvalue = htonl(reply->lvalue); } } else { /* * IP address is already in * network byte order. */ lvalue = reply->lvalue; } memcpy(ptr, &lvalue, 4); ptr += 4; total_length += 4; break; /* * There are no tagged date attributes. */ case PW_TYPE_DATE: *length_ptr += 4; if (vsa_length_ptr) *vsa_length_ptr += 4; lvalue = htonl(reply->lvalue); memcpy(ptr, &lvalue, 4); ptr += 4; total_length += 4; break; default: break; } } /* done looping over all attributes */ /* * Fill in the rest of the fields, and copy * the data over from the local stack to * the newly allocated memory. * * Yes, all this 'memcpy' is slow, but it means * that we only allocate the minimum amount of * memory for a request. */ packet->data_len = total_length; packet->data = (uint8_t *) malloc(packet->data_len); if (!packet->data) { librad_log("Out of memory"); return -1; } memcpy(packet->data, data, packet->data_len); hdr = (radius_packet_t *) packet->data; total_length = htons(total_length); memcpy(hdr->length, &total_length, sizeof(u_short)); /* * If this is not an authentication request, we * need to calculate the md5 hash over the entire packet * and put it in the vector. */ secretlen = strlen(secret); if (packet->code != PW_AUTHENTICATION_REQUEST && packet->code != PW_STATUS_SERVER) { MD5_CTX context; /* * Set the Message-Authenticator attribute, * BEFORE setting the reply authentication vector * for CHALLENGE, ACCEPT and REJECT. */ if (msg_auth_offset) { uint8_t calc_auth_vector[AUTH_VECTOR_LEN]; switch (packet->code) { default: break; case PW_AUTHENTICATION_ACK: case PW_AUTHENTICATION_REJECT: case PW_ACCESS_CHALLENGE: if (original) { memcpy(hdr->vector, original->vector, AUTH_VECTOR_LEN); } break; } memset(packet->data + msg_auth_offset + 2, 0, AUTH_VECTOR_LEN); lrad_hmac_md5(packet->data, packet->data_len, secret, secretlen, calc_auth_vector); memcpy(packet->data + msg_auth_offset + 2, calc_auth_vector, AUTH_VECTOR_LEN); memcpy(hdr->vector, packet->vector, AUTH_VECTOR_LEN); } MD5Init(&context); MD5Update(&context, packet->data, packet->data_len); MD5Update(&context, secret, strlen(secret)); MD5Final(digest, &context); memcpy(hdr->vector, digest, AUTH_VECTOR_LEN); memcpy(packet->vector, digest, AUTH_VECTOR_LEN); } /* * Set the Message-Authenticator attribute, * AFTER setting the authentication vector * only for ACCESS-REQUESTS */ else if (msg_auth_offset) { uint8_t calc_auth_vector[AUTH_VECTOR_LEN]; switch (packet->code) { default: break; case PW_AUTHENTICATION_ACK: case PW_AUTHENTICATION_REJECT: case PW_ACCESS_CHALLENGE: if (original) { memcpy(hdr->vector, original->vector, AUTH_VECTOR_LEN); } break; } memset(packet->data + msg_auth_offset + 2, 0, AUTH_VECTOR_LEN); lrad_hmac_md5(packet->data, packet->data_len, secret, secretlen, calc_auth_vector); memcpy(packet->data + msg_auth_offset + 2, calc_auth_vector, AUTH_VECTOR_LEN); memcpy(hdr->vector, packet->vector, AUTH_VECTOR_LEN); } /* * If packet->data points to data, then we print out * the VP list again only for debugging. */ } else if (librad_debug) { DEBUG("Re-sending %s of id %d to %s:%d\n", what, packet->id, ip_ntoa((char *)ip_buffer, packet->dst_ipaddr), packet->dst_port); for (reply = packet->vps; reply; reply = reply->next) { /* FIXME: ignore attributes > 0xff */ debug_pair(reply); } } /* * And send it on it's way. */ sa = (struct sockaddr_in *) &saremote; memset ((char *) sa, '\0', sizeof (saremote)); sa->sin_family = AF_INET; sa->sin_addr.s_addr = packet->dst_ipaddr; sa->sin_port = htons(packet->dst_port); return sendto(packet->sockfd, packet->data, (int)packet->data_len, 0, (struct sockaddr *)&saremote, sizeof(struct sockaddr_in)); }