/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include <sys/random.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <strings.h> #include <unistd.h> #include <stdlib.h> #include <netinet/in.h> #include <sys/socket.h> #include <md5.h> #include "target.h" #include "radius.h" /* Forward declaration */ /* * Encode a CHAP-Password attribute. This function basically prepends * the identifier in front of chap_passwd and copy the results to * *result. */ static void encode_chap_password(int identifier, int chap_passwd_len, uint8_t *chap_passwd, uint8_t *result); int snd_radius_request(int sd, iscsi_ipaddr_t rsvr_ip_addr, uint32_t rsvr_port, radius_packet_data_t *req_data); int rcv_radius_response(int sd, uint8_t *shared_secret, uint32_t shared_secret_len, uint8_t *req_authenticator, radius_packet_data_t *resp_data); /* * Annotate the radius_attr_t objects with authentication data. */ static void set_radius_attrs(radius_packet_data_t *req, char *target_chap_name, unsigned char *target_response, uint32_t responseLength, uint8_t *challenge, uint32_t challengeLength); /* * See radius_auth.h. */ /* ARGSUSED */ chap_validation_status_type radius_chap_validate(char *target_chap_name, char *initiator_chap_name, uint8_t *challenge, uint32_t challengeLength, uint8_t *target_response, uint32_t responseLength, uint8_t identifier, iscsi_ipaddr_t rad_svr_ip_addr, uint32_t rad_svr_port, uint8_t *rad_svr_shared_secret, uint32_t rad_svr_shared_secret_len) { chap_validation_status_type validation_status; int rcv_status; int sd; int rc; struct sockaddr_in sockaddr; radius_packet_data_t req; radius_packet_data_t resp; MD5_CTX context; uint8_t md5_digest[16]; /* MD5 digest length 16 */ uint8_t random_number[16]; int fd; if (rad_svr_shared_secret_len == 0) { /* The secret must not be empty (section 3, RFC 2865) */ return (CHAP_VALIDATION_BAD_RADIUS_SECRET); } bzero(&req, sizeof (radius_packet_data_t)); req.identifier = identifier; req.code = RAD_ACCESS_REQ; set_radius_attrs(&req, target_chap_name, target_response, responseLength, challenge, challengeLength); /* Prepare the request authenticator */ MD5Init(&context); bzero(&md5_digest, 16); /* First, the shared secret */ MD5Update(&context, rad_svr_shared_secret, rad_svr_shared_secret_len); /* Then a unique number - use a random number */ fd = open("/dev/random", O_RDONLY); if (fd == -1) return (CHAP_VALIDATION_INTERNAL_ERROR); (void) read(fd, &random_number, sizeof (random_number)); (void) close(fd); MD5Update(&context, random_number, sizeof (random_number)); MD5Final(md5_digest, &context); bcopy(md5_digest, &req.authenticator, RAD_AUTHENTICATOR_LEN); /* Create UDP socket */ sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR); } sockaddr.sin_family = AF_INET; sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); sockaddr.sin_port = htons(0); rc = bind(sd, (struct sockaddr *)&sockaddr, sizeof (sockaddr)); if (rc < 0) { return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR); } /* Send the authentication access request to the RADIUS server */ if (snd_radius_request(sd, rad_svr_ip_addr, rad_svr_port, &req) == -1) { (void) close(sd); return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR); } bzero(&resp, sizeof (radius_packet_data_t)); /* Analyze the response coming through from the same socket. */ rcv_status = rcv_radius_response(sd, rad_svr_shared_secret, rad_svr_shared_secret_len, req.authenticator, &resp); if (rcv_status == RAD_RSP_RCVD_SUCCESS) { if (resp.code == RAD_ACCESS_ACPT) { validation_status = CHAP_VALIDATION_PASSED; } else if (resp.code == RAD_ACCESS_REJ) { validation_status = CHAP_VALIDATION_INVALID_RESPONSE; } else { validation_status = CHAP_VALIDATION_UNKNOWN_RADIUS_CODE; } } else if (rcv_status == RAD_RSP_RCVD_AUTH_FAILED) { validation_status = CHAP_VALIDATION_BAD_RADIUS_SECRET; } else { validation_status = CHAP_VALIDATION_RADIUS_ACCESS_ERROR; } (void) close(sd); return (validation_status); } /* See forward declaration. */ static void set_radius_attrs(radius_packet_data_t *req, char *target_chap_name, unsigned char *target_response, uint32_t responseLength, uint8_t *challenge, uint32_t challengeLength) { req->attrs[0].attr_type_code = RAD_USER_NAME; (void) strncpy((char *)req->attrs[0].attr_value, (const char *)target_chap_name, strlen(target_chap_name)); req->attrs[0].attr_value_len = strlen(target_chap_name); req->attrs[1].attr_type_code = RAD_CHAP_PASSWORD; bcopy(target_response, (char *)req->attrs[1].attr_value, min(responseLength, sizeof (req->attrs[1].attr_value))); /* A target response is an MD5 hash thus its length has to be 16. */ req->attrs[1].attr_value_len = responseLength; req->attrs[2].attr_type_code = RAD_CHAP_CHALLENGE; bcopy(challenge, (char *)req->attrs[2].attr_value, min(challengeLength, sizeof (req->attrs[2].attr_value))); req->attrs[2].attr_value_len = challengeLength; /* 3 attributes associated with each RADIUS packet. */ req->num_of_attrs = 3; } /* * See radius_packet.h. */ int snd_radius_request(int sd, iscsi_ipaddr_t rsvr_ip_addr, uint32_t rsvr_port, radius_packet_data_t *req_data) { int i; /* Loop counter. */ int data_len; int len; ushort_t total_length; /* Has to be 2 octets in size */ uint8_t *ptr; /* Pointer to RADIUS packet data */ uint8_t *length_ptr; /* Points to the Length field of the */ /* packet. */ uint8_t *data; /* RADIUS data to be sent */ radius_attr_t *req_attr; /* Request attributes */ radius_packet_t *packet; /* Outbound RADIUS packet */ union { struct sockaddr_in s_in4; struct sockaddr_in6 s_in6; } sa_rsvr; /* Socket address of the server */ /* * Create a RADIUS packet with minimal length for now. */ total_length = MIN_RAD_PACKET_LEN; data = (uint8_t *)malloc(MAX_RAD_PACKET_LEN); packet = (radius_packet_t *)data; packet->code = req_data->code; packet->identifier = req_data->identifier; bcopy(req_data->authenticator, packet->authenticator, RAD_AUTHENTICATOR_LEN); ptr = packet->data; /* Loop over all attributes of the request. */ for (i = 0; i < req_data->num_of_attrs; i++) { if (total_length > MAX_RAD_PACKET_LEN) { /* The packet has exceed its maximum size. */ free(data); return (-1); } req_attr = &req_data->attrs[i]; *ptr++ = (req_attr->attr_type_code & 0xFF); length_ptr = ptr; /* Length is 2 octets - RFC 2865 section 3 */ *ptr++ = 2; total_length += 2; /* If the attribute is CHAP-Password, encode it. */ if (req_attr->attr_type_code == RAD_CHAP_PASSWORD) { /* * Identifier plus CHAP response. RFC 2865 * section 5.3. */ uint8_t encoded_chap_passwd[RAD_CHAP_PASSWD_STR_LEN + RAD_IDENTIFIER_LEN + 1]; encode_chap_password (req_data->identifier, req_attr->attr_value_len, req_attr->attr_value, encoded_chap_passwd); req_attr->attr_value_len = RAD_CHAP_PASSWD_STR_LEN + RAD_IDENTIFIER_LEN; bcopy(encoded_chap_passwd, req_attr->attr_value, req_attr->attr_value_len); } len = req_attr->attr_value_len; *length_ptr += len; bcopy(req_attr->attr_value, ptr, req_attr->attr_value_len); ptr += req_attr->attr_value_len; total_length += len; } /* Done looping over all attributes */ data_len = total_length; total_length = htons(total_length); bcopy(&total_length, packet->length, sizeof (ushort_t)); /* * Send the packet to the RADIUS server. */ bzero((char *)&sa_rsvr, sizeof (sa_rsvr)); if (rsvr_ip_addr.i_insize == sizeof (in_addr_t)) { int ret; /* IPv4 */ sa_rsvr.s_in4.sin_family = AF_INET; sa_rsvr.s_in4.sin_addr.s_addr = rsvr_ip_addr.i_addr.in4.s_addr; /* * sin_port is of type u_short (or ushort_t - POSIX compliant). */ sa_rsvr.s_in4.sin_port = htons((ushort_t)rsvr_port); ret = sendto(sd, data, data_len, 0, (struct sockaddr *)&sa_rsvr.s_in4, sizeof (struct sockaddr_in)); free(data); return (ret); } else if (rsvr_ip_addr.i_insize == sizeof (in6_addr_t)) { /* IPv6 */ sa_rsvr.s_in6.sin6_family = AF_INET6; bcopy(sa_rsvr.s_in6.sin6_addr.s6_addr, rsvr_ip_addr.i_addr.in6.s6_addr, 16); /* * sin6_port is of type in_port_t (i.e., uint16_t). */ sa_rsvr.s_in6.sin6_port = htons((in_port_t)rsvr_port); free(data); /* No IPv6 support for now. */ return (-1); } else { /* Invalid IP address for RADIUS server. */ free(data); return (-1); } } /* * See radius_packet.h. */ int rcv_radius_response(int sd, uint8_t *shared_secret, uint32_t shared_secret_len, uint8_t *req_authenticator, radius_packet_data_t *resp_data) { int poll_cnt = 0; int rcv_len = 0; radius_packet_t *packet; MD5_CTX context; uint8_t *tmp_data; uint8_t md5_digest[16]; /* MD5 Digest Length 16 */ uint16_t declared_len = 0; ushort_t len; fd_set fdset; struct timeval timeout; tmp_data = (uint8_t *)malloc(MAX_RAD_PACKET_LEN); /* * Poll and receive RADIUS packet. */ poll_cnt = 0; do { timeout.tv_sec = RAD_RCV_TIMEOUT; timeout.tv_usec = 0; FD_ZERO(&fdset); FD_SET(sd, &fdset); if (select(sd+1, &fdset, NULL, NULL, &timeout) < 0) { free(tmp_data); return (RAD_RSP_RCVD_PROTOCOL_ERR); } if (FD_ISSET(sd, &fdset)) { rcv_len = recv(sd, tmp_data, MAX_RAD_PACKET_LEN, 0); break; } else { poll_cnt++; } } while (poll_cnt < RAD_RETRY_MAX); if (poll_cnt >= RAD_RETRY_MAX) { free(tmp_data); return (RAD_RSP_RCVD_TIMEOUT); } if (rcv_len < 0) { /* Socket error. */ free(tmp_data); return (RAD_RSP_RCVD_PROTOCOL_ERR); } packet = (radius_packet_t *)tmp_data; bcopy(packet->length, &len, sizeof (ushort_t)); declared_len = ntohs(len); /* * Check if the received packet length is within allowable range. * RFC 2865 section 3. */ if (rcv_len < MIN_RAD_PACKET_LEN) { free(tmp_data); return (RAD_RSP_RCVD_PROTOCOL_ERR); } else if (rcv_len > MAX_RAD_PACKET_LEN) { free(tmp_data); return (RAD_RSP_RCVD_PROTOCOL_ERR); } /* * Check if the declared packet length is within allowable range. * RFC 2865 section 3. */ if (declared_len < MIN_RAD_PACKET_LEN) { free(tmp_data); return (RAD_RSP_RCVD_PROTOCOL_ERR); } else if (declared_len > MAX_RAD_PACKET_LEN) { free(tmp_data); return (RAD_RSP_RCVD_PROTOCOL_ERR); } /* * Discard packet with received length shorter than declared * length. RFC 2865 section 3. */ if (rcv_len < declared_len) { free(tmp_data); return (RAD_RSP_RCVD_PROTOCOL_ERR); } /* * Authenticate the incoming packet, using the following algorithm * (RFC 2865 section 3): * * MD5(Code+ID+Length+RequestAuth+Attributes+Secret) * * Code = RADIUS packet code * ID = RADIUS packet identifier * Length = Declared length of the packet * RequestAuth = The request authenticator * Attributes = The response attributes * Secret = The shared secret */ MD5Init(&context); bzero(&md5_digest, 16); MD5Update(&context, &packet->code, 1); MD5Update(&context, &packet->identifier, 1); MD5Update(&context, packet->length, 2); MD5Update(&context, req_authenticator, RAD_AUTHENTICATOR_LEN); /* Include response attributes only if there is a payload */ if (declared_len > RAD_PACKET_HDR_LEN) { /* Response Attributes */ MD5Update(&context, packet->data, declared_len - RAD_PACKET_HDR_LEN); } MD5Update(&context, shared_secret, shared_secret_len); MD5Final(md5_digest, &context); if (bcmp(md5_digest, packet->authenticator, RAD_AUTHENTICATOR_LEN) != 0) { free(tmp_data); return (RAD_RSP_RCVD_AUTH_FAILED); } /* * If the received length is greater than the declared length, * trust the declared length and shorten the packet (i.e., to * treat the octets outside the range of the Length field as * padding - RFC 2865 section 3). */ if (rcv_len > declared_len) { /* Clear the padding data. */ bzero(tmp_data + declared_len, rcv_len - declared_len); rcv_len = declared_len; } /* * Annotate the RADIUS packet data with the data we received from * the server. */ resp_data->code = packet->code; resp_data->identifier = packet->identifier; free(tmp_data); return (RAD_RSP_RCVD_SUCCESS); } static void encode_chap_password(int identifier, int chap_passwd_len, uint8_t *chap_passwd, uint8_t *result) { result[0] = (uint8_t)identifier; bcopy(chap_passwd, &result[1], chap_passwd_len); }