Mercurial > illumos > onarm
diff usr/src/cmd/iscsi/iscsitgtd/radius.c @ 0:c9caec207d52 b86
Initial porting based on b86
author | Koji Uno <koji.uno@sun.com> |
---|---|
date | Tue, 02 Jun 2009 18:56:50 +0900 |
parents | |
children | 1a15d5aaf794 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/iscsi/iscsitgtd/radius.c Tue Jun 02 18:56:50 2009 +0900 @@ -0,0 +1,514 @@ +/* + * 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 "@(#)radius.c 1.1 06/06/20 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); +}