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);
+}