OpenSolaris_b135/cmd/iscsi/iscsitgtd/iscsi_authglue.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2000 by Cisco Systems, Inc.  All rights reserved.
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * iSCSI Pseudo HBA Driver
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/random.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include <sys/iscsi_protocol.h>
#include <sys/iscsi_authclient.h>
#include <sys/types.h>
#include <iscsitgt_impl.h>
#include "radius.h"
#include "queue.h"
#include "iscsi_sess.h"
#include "target.h"

#define	DEFAULT_RADIUS_PORT 1812

boolean_t
persistent_radius_get(iscsi_radius_props_t *radius)
{
	Boolean_t bRadiusAccess = False;
	char *szRadiusServer = NULL;
	char *szRadiusSecret = NULL;
	char *szRadiusPort = NULL;
	int ret = 0;
	struct addrinfo *res;

	bzero(radius, sizeof (radius));
	radius->r_radius_config_valid = B_FALSE;

	/* Load RADIUS access option: enable/disable */
	if (tgt_find_value_boolean(main_config, XML_ELEMENT_RAD_ACCESS,
		&bRadiusAccess) == False) {
		return (B_FALSE);
	}
	if (bRadiusAccess == False) {
		return (B_FALSE);
	}

	/* Load RADIUS server: ipaddr[:port] */
	if (tgt_find_value_str(main_config, XML_ELEMENT_RAD_SERV,
		&szRadiusServer) == False) {
		return (B_FALSE);
	}

	szRadiusPort = strchr(szRadiusServer, ':');
	if (szRadiusPort == NULL) {
		radius->r_port = DEFAULT_RADIUS_PORT;
	} else {
		radius->r_port = strtoul(szRadiusPort + 1, NULL, 0);
		if (radius->r_port == 0) {
			radius->r_port = DEFAULT_RADIUS_PORT;
		}
		*szRadiusPort = '\0';
	}

	ret = getaddrinfo(szRadiusServer, NULL, NULL, &res);
	free(szRadiusServer);
	if (ret != 0) {
		return (B_FALSE);
	}
	if (res->ai_family == PF_INET) {
		struct sockaddr_in	sa_tmp;

		bcopy(res->ai_addr, &sa_tmp, sizeof (sa_tmp));
		radius->r_insize = sizeof (in_addr_t);
		radius->r_addr.u_in4 = sa_tmp.sin_addr;
	}
	/*
	 * We don't handle IPV6 currently.
	 */

	/* Load RADIUS shared secret */
	if (tgt_find_value_str(main_config, XML_ELEMENT_RAD_SECRET,
		&szRadiusSecret) == False) {
		freeaddrinfo(res);
		return (B_FALSE);
	}
	(void) strncpy((char *)radius->r_shared_secret, szRadiusSecret,
	    MAX_RAD_SHARED_SECRET_LEN);
	radius->r_shared_secret_len = strlen((char *)radius->r_shared_secret);
	free(szRadiusSecret);
	freeaddrinfo(res);

	/* Set RADIUS config flag */
	radius->r_radius_access = B_TRUE;
	radius->r_radius_config_valid = B_TRUE;
	return (B_TRUE);
}

/*
 * Authenticate a target's CHAP response.
 *
 * username - Incoming username from the the target.
 * responseData - Incoming response data from the target.
 */
int
iscsiAuthClientChapAuthRequest(IscsiAuthClient *client,
    char *username, unsigned int id, uchar_t *challengeData,
    unsigned int challengeLength, uchar_t *responseData,
    unsigned int responseLength)
{
	iscsi_sess_t		*isp = (iscsi_sess_t *)client->userHandle;
	IscsiAuthMd5Context	context;
	iscsi_radius_props_t	p_radius_cfg;
	uchar_t			verifyData[iscsiAuthChapResponseLength];
	char			debug[128];

	if (isp == NULL) {
		return (iscsiAuthStatusFail);
	}

	/*
	 * the expected credentials are in the session
	 */
	if (isp->sess_auth.username_in == NULL) {
		(void) snprintf(debug, sizeof (debug),
		    "SES%x iscsi session(%u) failed authentication, "
		    "no incoming username configured to authenticate initiator",
		    isp->s_num);

		queue_str(isp->s_mgmtq, Q_SESS_ERRS, msg_log, debug);
		return (iscsiAuthStatusFail);
	}
	if (strcmp(username, isp->sess_auth.username_in) != 0) {
		(void) snprintf(debug, sizeof (debug),
		    "SES%x iscsi session(%u) failed authentication, "
		    "received incorrect username from initiator",
		    isp->s_num);
		queue_str(isp->s_mgmtq, Q_SESS_ERRS, msg_log, debug);
		return (iscsiAuthStatusFail);
	}

	/* Check if RADIUS access is enabled */
	if (persistent_radius_get(&p_radius_cfg) == B_TRUE &&
		p_radius_cfg.r_radius_access == B_TRUE) {
		chap_validation_status_type chap_valid_status;
		int authStatus;
		RADIUS_CONFIG radius_cfg;

		if (p_radius_cfg.r_radius_config_valid == B_FALSE) {
			/*
			 * Radius enabled but configuration invalid -
			 * invalid condition
			 */
			return (iscsiAuthStatusFail);
		}

		/* Use RADIUS server to authentication target */
		if (p_radius_cfg.r_insize == sizeof (in_addr_t)) {
			/* IPv4 */
			radius_cfg.rad_svr_addr.i_addr.in4.s_addr =
				p_radius_cfg.r_addr.u_in4.s_addr;
			radius_cfg.rad_svr_addr.i_insize
				= sizeof (in_addr_t);
		} else if (p_radius_cfg.r_insize == sizeof (in6_addr_t)) {
			/* IPv6 */
			bcopy(p_radius_cfg.r_addr.u_in6.s6_addr,
				radius_cfg.rad_svr_addr.i_addr.in6.s6_addr,
				16);
			radius_cfg.rad_svr_addr.i_insize = sizeof (in6_addr_t);
		} else {
			return (iscsiAuthStatusFail);
		}

		radius_cfg.rad_svr_port = p_radius_cfg.r_port;
		bcopy(p_radius_cfg.r_shared_secret,
			radius_cfg.rad_svr_shared_secret,
			MAX_RAD_SHARED_SECRET_LEN);
		radius_cfg.rad_svr_shared_secret_len =
			p_radius_cfg.r_shared_secret_len;

		chap_valid_status = radius_chap_validate(
			isp->sess_auth.username_in,
			isp->sess_auth.username,
			challengeData,
			challengeLength,
			responseData,
			responseLength,
			id,
			radius_cfg.rad_svr_addr,
			radius_cfg.rad_svr_port,
			radius_cfg.rad_svr_shared_secret,
			radius_cfg.rad_svr_shared_secret_len);


		switch (chap_valid_status) {
			case CHAP_VALIDATION_PASSED:
				authStatus = iscsiAuthStatusPass;
				break;
			case CHAP_VALIDATION_INVALID_RESPONSE:
				authStatus = iscsiAuthStatusFail;
				break;
			case CHAP_VALIDATION_DUP_SECRET:
				authStatus = iscsiAuthStatusFail;
				break;
			case CHAP_VALIDATION_RADIUS_ACCESS_ERROR:
				authStatus = iscsiAuthStatusFail;
				break;
			case CHAP_VALIDATION_BAD_RADIUS_SECRET:
				authStatus = iscsiAuthStatusFail;
				break;
			default:
				authStatus = iscsiAuthStatusFail;
				break;
		}
		return (authStatus);
	} else {
		/* Use target secret (if defined) to authenticate target */
		if ((isp->sess_auth.password_length_in < 1) ||
		    (isp->sess_auth.password_in == NULL) ||
		    (isp->sess_auth.password_in[0] == '\0')) {
			/* No target secret defined - invalid condition */
			return (iscsiAuthStatusFail);
		}

		/*
		 * challenge length is I->T, and shouldn't need to
		 * be checked
		 */
		if (responseLength != sizeof (verifyData)) {
			(void) snprintf(debug, sizeof (debug),
			    "SES%x iscsi session(%u) failed "
			    "authentication, received incorrect CHAP response "
			    "from initiator", isp->s_num);
			queue_str(isp->s_mgmtq, Q_SESS_ERRS, msg_log, debug);
			return (iscsiAuthStatusFail);
		}

		iscsiAuthMd5Init(&context);

		/*
		 * id byte
		 */
		verifyData[0] = id;
		iscsiAuthMd5Update(&context, verifyData, 1);

		/*
		 * shared secret
		 */
		iscsiAuthMd5Update(&context,
		    (uchar_t *)isp->sess_auth.password_in,
		    isp->sess_auth.password_length_in);

		/*
		 * challenge value
		 */
		iscsiAuthMd5Update(&context,
		    (uchar_t *)challengeData,
		    challengeLength);

		iscsiAuthMd5Final(verifyData, &context);

		if (bcmp(responseData, verifyData,
			sizeof (verifyData)) == 0) {
			return (iscsiAuthStatusPass);
		}

		(void) snprintf(debug, sizeof (debug),
		    "SES%x iscsi session(%u) failed authentication, "
		    "received incorrect CHAP response from initiator",
		    isp->s_num);
		queue_str(isp->s_mgmtq, Q_SESS_ERRS, msg_log, debug);
	}
	return (iscsiAuthStatusFail);
}

int
iscsiAuthClientTextToNumber(const char *text, unsigned long *pNumber)
{
	char *pEnd;
	unsigned long number;

	number = strtoul(text, &pEnd, 0);
	if (*text != '\0' && *pEnd == '\0') {
		*pNumber = number;
		return (0);	/* No error */
	} else {
		return (1);	/* Error */
	}
}

/* ARGSUSED */
void
iscsiAuthClientNumberToText(unsigned long number, char *text,
    unsigned int length)
{
	(void) snprintf(text, length, "%lu", number);
}


void
iscsiAuthRandomSetData(uchar_t *data, unsigned int length)
{
	int fd;
	fd = open("/dev/random", O_RDONLY);
	if (fd == -1)
		return;
	(void) read(fd, data, length);
	(void) close(fd);
}


void
iscsiAuthMd5Init(IscsiAuthMd5Context * context)
{
	MD5Init(context);
}


void
iscsiAuthMd5Update(IscsiAuthMd5Context *context, uchar_t *data,
    unsigned int length)
{
	MD5Update(context, data, length);
}


void
iscsiAuthMd5Final(uchar_t *hash, IscsiAuthMd5Context *context)
{
	MD5Final(hash, context);
}


int
iscsiAuthClientData(uchar_t *outData, unsigned int *outLength,
    uchar_t *inData, unsigned int inLength)
{
	if (*outLength < inLength) {
		return (1);	/* error */
	}
	bcopy(inData, outData, inLength);
	*outLength = inLength;
	return (0);		/* no error */
}