Ejemplo n.º 1
0
/**
 * @brief Convert base64 to hex
 *
 * Example: "%{base64tohex:Zm9v}" == "666f6f"
 */
static size_t base64_to_hex_xlat(UNUSED void *instance, REQUEST *request,
				 char *fmt, char *out, size_t outlen,
				 UNUSED RADIUS_ESCAPE_STRING func)
{
	char buffer[1024];
	char decbuf[1024];

	size_t declen = sizeof(decbuf);
	size_t len;

	while (isspace((int) *fmt)) fmt++;

	len = radius_xlat(buffer, sizeof(buffer), fmt, request, func);
	if (!len) {
		radlog(L_ERR, "rlm_expr: xlat failed.");
		*out = '\0';
		return 0;
	}

	if (!fr_base64_decode(buffer, len, decbuf, &declen)) {
		radlog(L_ERR, "rlm_expr: base64 string invalid");
		*out = '\0';
		return 0;
	}

	if ((size_t)((declen * 2) + 1) > outlen) {
		radlog(L_ERR, "rlm_expr: Base64 conversion failed, "
		       "output buffer exhausted, needed %zd bytes, "
		       "have %zd bytes", (declen * 2) + 1, outlen);
	}

	fr_bin2hex(decbuf, out, declen);

	return declen * 2;
}
Ejemplo n.º 2
0
static ssize_t dhcp_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char **out, size_t freespace)
{
	vp_cursor_t cursor;
	VALUE_PAIR *vp;
	uint8_t binbuf[255];
	ssize_t len;

	while (isspace((int) *fmt)) fmt++;

	if ((radius_copy_vp(request, &vp, request, fmt) < 0) || !vp) return 0;
	fr_cursor_init(&cursor, &vp);

	len = fr_dhcp_encode_option(request, binbuf, sizeof(binbuf), &cursor);
	talloc_free(vp);
	if (len <= 0) {
		REDEBUG("DHCP option encoding failed: %s", fr_strerror());

		return -1;
	}

	if ((size_t)((len * 2) + 1) > freespace) {
		REDEBUG("DHCP option encoding failed: Output buffer exhausted, needed %zd bytes, have %zd bytes",
			(len * 2) + 1, freespace);

		return -1;
	}

	return fr_bin2hex(*out, binbuf, len);
}
Ejemplo n.º 3
0
static int cbtls_new_session(UNUSED SSL *s, SSL_SESSION *sess)
{
	size_t size;
	char buffer[2 * MAX_SESSION_SIZE + 1];

	size = sess->session_id_length;
	if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE;

	fr_bin2hex(sess->session_id, buffer, size);

	DEBUG2("  SSL: adding session %s to cache", buffer);

	return 1;
}
Ejemplo n.º 4
0
static void cbtls_remove_session(UNUSED SSL_CTX *ctx, SSL_SESSION *sess)
{
	size_t size;
	char buffer[2 * MAX_SESSION_SIZE + 1];

	size = sess->session_id_length;
	if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE;

	fr_bin2hex(sess->session_id, buffer, size);

        DEBUG2("  SSL: Removing session %s from the cache", buffer);
        SSL_SESSION_free(sess);

        return;
}
Ejemplo n.º 5
0
static SSL_SESSION *cbtls_get_session(UNUSED SSL *s,
				      unsigned char *data, int len,
				      UNUSED int *copy)
{
	size_t size;
	char buffer[2 * MAX_SESSION_SIZE + 1];

	size = len;
	if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE;

	fr_bin2hex(data, buffer, size);

        DEBUG2("  SSL: Client requested nonexistent cached session %s",
	       buffer);

	return NULL;
}
Ejemplo n.º 6
0
/** Generate the HMAC-SHA1 of a string or attribute
 *
 * Example: "%{hmacsha1:foo bar}" == "Zm9v"
 */
static ssize_t hmac_sha1_xlat(UNUSED void *instance, UNUSED REQUEST *request,
			      char const *fmt, char *out, size_t outlen)
{
	uint8_t const *data, *key;
	char const *p;
	ssize_t data_len, key_len;
	uint8_t digest[SHA1_DIGEST_LENGTH];
	char data_ref[256];

	if (outlen <= (sizeof(digest) * 2)) {
		REDEBUG("Insufficient space to write digest, needed %zu bytes, have %zu bytes",
			(sizeof(digest) * 2) + 1, outlen);
		return -1;
	}

	p = strchr(fmt, ' ');
	if (!p) {
		REDEBUG("HMAC requires exactly two arguments (&data &key)");
		return -1;
	}

	if ((size_t)(p - fmt) >= sizeof(data_ref)) {
		REDEBUG("Insufficient space to store HMAC input data, needed %zu bytes, have %zu bytes",
		        (p - fmt) + 1, sizeof(data_ref));

		return -1;
	}
	strlcpy(data_ref, fmt, (p - fmt) + 1);

	data_len = xlat_fmt_to_ref(&data, request, data_ref);
	if (data_len < 0) return -1;

	while (isspace(*p) && p++);

	key_len = xlat_fmt_to_ref(&key, request, p);
	if (key_len < 0) return -1;

	fr_hmac_sha1(digest, data, data_len, key, key_len);

	return fr_bin2hex(out, digest, sizeof(digest));
}
Ejemplo n.º 7
0
/*
 *	Find the named user in this modules database.  Create the set
 *	of attribute-value pairs to check and reply with for this user
 *	from the database. The authentication code only needs to check
 *	the password, the rest is done here.
 */
static int wimax_authorize(void *instance, REQUEST *request)
{
    VALUE_PAIR *vp;

    /* quiet the compiler */
    instance = instance;
    request = request;

    /*
     *	Fix Calling-Station-Id.  Damn you, WiMAX!
     */
    vp =  pairfind(request->packet->vps, PW_CALLING_STATION_ID);
    if (vp && (vp->length == 6)) {
        int i;
        uint8_t buffer[6];

        memcpy(buffer, vp->vp_octets, 6);

        /*
         *	RFC 3580 Section 3.20 says this is the preferred
         *	format.  Everyone *SANE* is using this format,
         *	so we fix it here.
         */
        for (i = 0; i < 6; i++) {
            fr_bin2hex(&buffer[i], &vp->vp_strvalue[i * 3], 1);
            vp->vp_strvalue[(i * 3) + 2] = '-';
        }

        vp->vp_strvalue[(5*3)+2] = '\0';
        vp->length = (5*3)+2;

        DEBUG2("rlm_wimax: Fixing WiMAX binary Calling-Station-Id to %s",
               vp->vp_strvalue);
    }

    return RLM_MODULE_OK;
}
Ejemplo n.º 8
0
/** Convert base64 to hex
 *
 * Example: "%{base64tohex:Zm9v}" == "666f6f"
 */
static ssize_t base64_to_hex_xlat(UNUSED void *instance, UNUSED REQUEST *request,
				  char const *fmt, char *out, size_t outlen)
{
	uint8_t decbuf[1024];

	ssize_t declen;
	ssize_t len = strlen(fmt);

	*out = '\0';

	declen = fr_base64_decode(decbuf, sizeof(decbuf), fmt, len);
	if (declen < 0) {
		REDEBUG("Base64 string invalid");
		return -1;
	}

	if ((size_t)((declen * 2) + 1) > outlen) {
		REDEBUG("Base64 conversion failed, output buffer exhausted, needed %zd bytes, have %zd bytes",
			(declen * 2) + 1, outlen);
		return -1;
	}

	return fr_bin2hex(out, decbuf, declen);
}
Ejemplo n.º 9
0
/*
 *	Find the named user in this modules database.  Create the set
 *	of attribute-value pairs to check and reply with for this user
 *	from the database. The authentication code only needs to check
 *	the password, the rest is done here.
 */
static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance,
				     REQUEST *request)
{
	VALUE_PAIR *passwd_item, *chap;
	uint8_t pass_str[MAX_STRING_LEN];

	if (!request->username) {
		RWDEBUG("Attribute 'User-Name' is required for authentication.\n");
		return RLM_MODULE_INVALID;
	}

	chap = pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY);
	if (!chap) {
		REDEBUG("You set 'Auth-Type = CHAP' for a request that does not contain a CHAP-Password attribute!");
		return RLM_MODULE_INVALID;
	}

	if (chap->length == 0) {
		REDEBUG("CHAP-Password is empty");
		return RLM_MODULE_INVALID;
	}

	if (chap->length != CHAP_VALUE_LENGTH + 1) {
		REDEBUG("CHAP-Password has invalid length");
		return RLM_MODULE_INVALID;
	}

	/*
	 *	Don't print out the CHAP password here.  It's binary crap.
	 */
	RDEBUG("Login attempt by \"%s\" with CHAP password",
		request->username->vp_strvalue);

	if ((passwd_item = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL){
		if (pairfind(request->config_items, PW_USER_PASSWORD, 0, TAG_ANY) != NULL){
			REDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
			REDEBUG("!!! Please update your configuration so that the \"known !!!");
			REDEBUG("!!! good\" cleartext password is in Cleartext-Password,  !!!");
			REDEBUG("!!! and NOT in User-Password.                            !!!");
			REDEBUG("!!!						          !!!");
			REDEBUG("!!! Authentication will fail because of this.	          !!!");
			REDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
		}

		REDEBUG("Cleartext password is required for authentication");
		return RLM_MODULE_INVALID;
	}

	rad_chap_encode(request->packet, pass_str,
			chap->vp_octets[0], passwd_item);

	if (RDEBUG_ENABLED3) {
		uint8_t const *p;
		size_t length;
		VALUE_PAIR *vp;
		char buffer[CHAP_VALUE_LENGTH * 2 + 1];

		RDEBUG3("Comparing with \"known good\" Cleartext-Password \"%s\"", passwd_item->vp_strvalue);

		vp = pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY);
		if (vp) {
			p = vp->vp_octets;
			length = vp->length;
		} else {
			p = request->packet->vector;
			length = sizeof(request->packet->vector);
		}

		fr_bin2hex(buffer, p, length);
		RINDENT();
		RDEBUG3("CHAP challenge :  %s", buffer);

		fr_bin2hex(buffer, chap->vp_octets + 1, CHAP_VALUE_LENGTH);
		RDEBUG3("Client sent    : %s", buffer);

		fr_bin2hex(buffer, pass_str + 1, CHAP_VALUE_LENGTH);
		RDEBUG3("We calculated  : %s", buffer);
		REXDENT();
	} else {
		RDEBUG2("Comparing with \"known good\" Cleartext-Password");
	}

	if (rad_digest_cmp(pass_str + 1, chap->vp_octets + 1,
			   CHAP_VALUE_LENGTH) != 0) {
		REDEBUG("Password is comparison failed: password is incorrect");
		return RLM_MODULE_REJECT;
	}

	RDEBUG("CHAP user \"%s\" authenticated successfully",
	      request->username->vp_strvalue);

	return RLM_MODULE_OK;
}
Ejemplo n.º 10
0
/*
 *  Print one value into a string.
 *  delimitst will define if strings and dates will be delimited by '"'
 */
int vp_prints_value(char * out, size_t outlen, VALUE_PAIR *vp, int delimitst)
{
	DICT_VALUE  *v;
	char        buf[1024];
	const char  *a = NULL;
	size_t      len;
	time_t      t;
	struct tm   s_tm;

	out[0] = '\0';
	if (!vp) return 0;

	switch (vp->type) {
		case PW_TYPE_STRING:
			if ((delimitst == 1) && vp->flags.has_tag) {
				/* Tagged attribute: print delimter and ignore tag */
				buf[0] = '"';
				fr_print_string(vp->vp_strvalue,
						 vp->length, buf + 1, sizeof(buf) - 2);
				strcat(buf, "\"");
			} else if (delimitst == 1) {
				/* Non-tagged attribute: print delimter */
				buf[0] = '"';
				fr_print_string(vp->vp_strvalue,
						 vp->length, buf + 1, sizeof(buf) - 2);
				strcat(buf, "\"");

			} else if (delimitst < 0) { /* xlat.c */
				strlcpy(out, vp->vp_strvalue, outlen);
				return strlen(out);

			} else {
				/* Non-tagged attribute: no delimiter */
				fr_print_string(vp->vp_strvalue,
						 vp->length, buf, sizeof(buf));
			}
			a = buf;
			break;
		case PW_TYPE_INTEGER:
		        if ( vp->flags.has_tag ) {
			        /* Attribute value has a tag, need to ignore it */
			        if ((v = dict_valbyattr(vp->attribute, (vp->vp_integer & 0xffffff)))
				    != NULL)
				        a = v->name;
				else {
				        snprintf(buf, sizeof(buf), "%u", (vp->vp_integer & 0xffffff));
				        a = buf;
				}
			} else {
		case PW_TYPE_BYTE:
		case PW_TYPE_SHORT:
			        /* Normal, non-tagged attribute */
			        if ((v = dict_valbyattr(vp->attribute, vp->vp_integer))
				    != NULL)
				        a = v->name;
				else {
				        snprintf(buf, sizeof(buf), "%u", vp->vp_integer);
					a = buf;
				}
			}
			break;
		case PW_TYPE_DATE:
			t = vp->vp_date;
			if (delimitst == 1) {
			  len = strftime(buf, sizeof(buf), "\"%b %e %Y %H:%M:%S %Z\"",
					 localtime_r(&t, &s_tm));
			} else {
			  len = strftime(buf, sizeof(buf), "%b %e %Y %H:%M:%S %Z",
					 localtime_r(&t, &s_tm));
			}
			if (len > 0) a = buf;
			break;
		case PW_TYPE_SIGNED: /* Damned code for 1 WiMAX attribute */
			snprintf(buf, sizeof(buf), "%d", vp->vp_signed);
			a = buf;
			break;
		case PW_TYPE_IPADDR:
			a = inet_ntop(AF_INET, &(vp->vp_ipaddr),
				      buf, sizeof(buf));
			break;
		case PW_TYPE_ABINARY:
#ifdef ASCEND_BINARY
			a = buf;
			print_abinary(vp, buf, sizeof(buf));
			break;
#else
		  /* FALL THROUGH */
#endif
		case PW_TYPE_OCTETS:
			if (outlen <= (2 * (vp->length + 1))) return 0;

			strcpy(buf, "0x");

			fr_bin2hex(vp->vp_octets, buf + 2, vp->length);
			a = buf;
		  break;

		case PW_TYPE_IFID:
			a = ifid_ntoa(buf, sizeof(buf), vp->vp_octets);
			break;

		case PW_TYPE_IPV6ADDR:
			a = inet_ntop(AF_INET6,
				      (const struct in6_addr *) vp->vp_strvalue,
				      buf, sizeof(buf));
			break;

		case PW_TYPE_IPV6PREFIX:
		{
			struct in6_addr addr;

			/*
			 *	Alignment issues.
			 */
			memcpy(&addr, vp->vp_strvalue + 2, sizeof(addr));

			a = inet_ntop(AF_INET6, &addr, buf, sizeof(buf));
			if (a) {
				char *p = buf + strlen(buf);
				snprintf(p, buf + sizeof(buf) - p - 1, "/%u",
					 (unsigned int) vp->vp_octets[1]);
			}
		}
			break;

		case PW_TYPE_ETHERNET:
			snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
				 vp->vp_ether[0], vp->vp_ether[1],
				 vp->vp_ether[2], vp->vp_ether[3],
				 vp->vp_ether[4], vp->vp_ether[5]);
			a = buf;
			break;

		case PW_TYPE_TLV:
			if (outlen <= (2 * (vp->length + 1))) return 0;

			strcpy(buf, "0x");

			fr_bin2hex(vp->vp_tlv, buf + 2, vp->length);
			a = buf;
		  break;

		default:
			a = "UNKNOWN-TYPE";
			break;
	}

	if (a != NULL) strlcpy(out, a, outlen);

	return strlen(out);
}
Ejemplo n.º 11
0
/*
 *	Process an EAP request
 */
fr_tls_status_t eaptls_process(eap_handler_t *handler)
{
	tls_session_t *tls_session = (tls_session_t *) handler->opaque;
	EAPTLS_PACKET	*tlspacket;
	fr_tls_status_t	status;
	REQUEST *request = handler->request;

	if (!request) return FR_TLS_FAIL;

	RDEBUG2("Continuing EAP-TLS");

	SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request);

	if (handler->certs) fr_pair_add(&request->packet->vps,
				    fr_pair_list_copy(request->packet, handler->certs));

	/*
	 *	This case is when SSL generates Alert then we
	 *	send that alert to the client and then send the EAP-Failure
	 */
	status = eaptls_verify(handler);
	if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) {
		REDEBUG("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
	} else {
		RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
	}

	switch (status) {
	default:
	case FR_TLS_INVALID:
	case FR_TLS_FAIL:

	/*
	 *	Success means that we're done the initial
	 *	handshake.  For TTLS, this means send stuff
	 *	back to the client, and the client sends us
	 *	more tunneled data.
	 */
	case FR_TLS_SUCCESS:
		goto done;

	/*
	 *	Normal TLS request, continue with the "get rest
	 *	of fragments" phase.
	 */
	case FR_TLS_REQUEST:
		eaptls_request(handler->eap_ds, tls_session);
		status = FR_TLS_HANDLED;
		goto done;

	/*
	 *	The handshake is done, and we're in the "tunnel
	 *	data" phase.
	 */
	case FR_TLS_OK:
		RDEBUG2("Done initial handshake");

	/*
	 *	Get the rest of the fragments.
	 */
	case FR_TLS_FIRST_FRAGMENT:
	case FR_TLS_MORE_FRAGMENTS:
	case FR_TLS_LENGTH_INCLUDED:
		break;
	}

	/*
	 *	Extract the TLS packet from the buffer.
	 */
	if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) {
		status = FR_TLS_FAIL;
		goto done;
	}

	/*
	 *	Get the session struct from the handler
	 *
	 *	update the dirty_in buffer
	 *
	 *	NOTE: This buffer will contain partial data when M bit is set.
	 *
	 * 	CAUTION while reinitializing this buffer, it should be
	 * 	reinitialized only when this M bit is NOT set.
	 */
	if (tlspacket->dlen !=
	    (tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) {
		talloc_free(tlspacket);
		REDEBUG("Exceeded maximum record size");
		status = FR_TLS_FAIL;
		goto done;
	}

	/*
	 *	No longer needed.
	 */
	talloc_free(tlspacket);

	/*
	 *	SSL initalization is done.  Return.
	 *
	 *	The TLS data will be in the tls_session structure.
	 */
	if (SSL_is_init_finished(tls_session->ssl)) {
		/*
		 *	The initialization may be finished, but if
		 *	there more fragments coming, then send ACK,
		 *	and get the caller to continue the
		 *	conversation.
		 */
		if ((status == FR_TLS_MORE_FRAGMENTS) ||
		    (status == FR_TLS_FIRST_FRAGMENT)) {
			/*
			 *	Send the ACK.
			 */
			eaptls_send_ack(handler, tls_session->peap_flag);
			RDEBUG2("Init is done, but tunneled data is fragmented");
			status = FR_TLS_HANDLED;
			goto done;
		}

		status = tls_application_data(tls_session, request);
		goto done;
	}

	/*
	 *	Continue the handshake.
	 */
	status = eaptls_operation(status, handler);
	if (status == FR_TLS_SUCCESS) {
#define MAX_SESSION_SIZE (256)
		size_t size;
		VALUE_PAIR *vps;
		char buffer[2 * MAX_SESSION_SIZE + 1];
		/*
		 *	Restore the cached VPs before processing the
		 *	application data.
		 */
		size = tls_session->ssl->session->session_id_length;
		if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE;

		fr_bin2hex(buffer, tls_session->ssl->session->session_id, size);

		vps = SSL_SESSION_get_ex_data(tls_session->ssl->session, fr_tls_ex_index_vps);
		if (!vps) {
			RWDEBUG("No information in cached session %s", buffer);
		} else {
			vp_cursor_t cursor;
			VALUE_PAIR *vp;

			RDEBUG("Adding cached attributes from session %s", buffer);

			/*
			 *	The cbtls_get_session() function doesn't have
			 *	access to sock->certs or handler->certs, which
			 *	is where the certificates normally live.  So
			 *	the certs are all in the VPS list here, and
			 *	have to be manually extracted.
			 */
			RINDENT();
			for (vp = fr_cursor_init(&cursor, &vps);
			     vp;
			     vp = fr_cursor_next(&cursor)) {
				/*
				 *	TLS-* attrs get added back to
				 *	the request list.
				 */
				if ((vp->da->vendor == 0) &&
				    (vp->da->attr >= PW_TLS_CERT_SERIAL) &&
				    (vp->da->attr <= PW_TLS_CLIENT_CERT_SUBJECT_ALT_NAME_UPN)) {
					/*
					 *	Certs already exist.  Don't re-add them.
					 */
					if (!handler->certs) {
						rdebug_pair(L_DBG_LVL_2, request, vp, "request:");
						fr_pair_add(&request->packet->vps, fr_pair_copy(request->packet, vp));
					}
				} else {
					rdebug_pair(L_DBG_LVL_2, request, vp, "reply:");
					fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp));
				}
			}
			REXDENT();
		}
	}

 done:
	SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, NULL);

	return status;
}
Ejemplo n.º 12
0
/** Allocate a new IP address from a pool
 *
 */
static ippool_rcode_t redis_ippool_allocate(rlm_redis_ippool_t const *inst, REQUEST *request,
					    uint8_t const *key_prefix, size_t key_prefix_len,
					    uint8_t const *device_id, size_t device_id_len,
					    uint8_t const *gateway_id, size_t gateway_id_len,
					    uint32_t expires)
{
	struct			timeval now;
	redisReply		*reply = NULL;

	fr_redis_rcode_t	status;
	ippool_rcode_t		ret = IPPOOL_RCODE_SUCCESS;

	rad_assert(key_prefix);
	rad_assert(device_id);

	gettimeofday(&now, NULL);

	/*
	 *	hiredis doesn't deal well with NULL string pointers
	 */
	if (!gateway_id) gateway_id = (uint8_t const *)"";

	status = ippool_script(&reply, request, inst->cluster,
			       key_prefix, key_prefix_len,
			       inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout),
			       lua_alloc_digest, lua_alloc_cmd,
	 		       "EVALSHA %s 1 %b %u %u %b %b",
	 		       lua_alloc_digest,
			       key_prefix, key_prefix_len,
			       (unsigned int)now.tv_sec, expires,
			       device_id, device_id_len,
			       gateway_id, gateway_id_len);
	if (status != REDIS_RCODE_SUCCESS) {
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}

	rad_assert(reply);
	if (reply->type != REDIS_REPLY_ARRAY) {
		REDEBUG("Expected result to be array got \"%s\"",
			fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>"));
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}

	if (reply->elements == 0) {
		REDEBUG("Got empty result array");
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}

	/*
	 *	Process return code
	 */
	if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
		REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
			fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>"));
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}
	ret = reply->element[0]->integer;
	if (ret < 0) goto finish;

	/*
	 *	Process IP address
	 */
	if (reply->elements > 1) {
		vp_tmpl_t ip_rhs = {
			.type = TMPL_TYPE_DATA,
			.tmpl_value_type = FR_TYPE_STRING
		};
		vp_map_t ip_map = {
			.lhs = inst->allocated_address_attr,
			.op = T_OP_SET,
			.rhs = &ip_rhs
		};

		switch (reply->element[1]->type) {
		/*
		 *	Destination attribute may not be IPv4, in which case
		 *	we want to pre-convert the integer value to an IPv4
		 *	address before casting it once more to the type of
		 *	the destination attribute.
		 */
		case REDIS_REPLY_INTEGER:
		{
			if (ip_map.lhs->tmpl_da->type != FR_TYPE_IPV4_ADDR) {
				fr_value_box_t tmp;

				memset(&tmp, 0, sizeof(tmp));

				tmp.vb_uint32 = ntohl((uint32_t)reply->element[1]->integer);
				tmp.type = FR_TYPE_UINT32;

				if (fr_value_box_cast(NULL, &ip_map.rhs->tmpl_value, FR_TYPE_IPV4_ADDR,
						      NULL, &tmp)) {
					RPEDEBUG("Failed converting integer to IPv4 address");
					ret = IPPOOL_RCODE_FAIL;
					goto finish;
				}
			} else {
				ip_map.rhs->tmpl_value.vb_uint32 = ntohl((uint32_t)reply->element[1]->integer);
				ip_map.rhs->tmpl_value_type = FR_TYPE_UINT32;
			}
		}
			goto do_ip_map;

		case REDIS_REPLY_STRING:
			ip_map.rhs->tmpl_value.vb_strvalue = reply->element[1]->str;
			ip_map.rhs->tmpl_value_length = reply->element[1]->len;
			ip_map.rhs->tmpl_value_type = FR_TYPE_STRING;

		do_ip_map:
			if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) {
				ret = IPPOOL_RCODE_FAIL;
				goto finish;
			}
			break;

		default:
			REDEBUG("Server returned unexpected type \"%s\" for IP element (result[1])",
				fr_int2str(redis_reply_types, reply->element[1]->type, "<UNKNOWN>"));
			ret = IPPOOL_RCODE_FAIL;
			goto finish;
		}
	}

	/*
	 *	Process Range identifier
	 */
	if (reply->elements > 2) {
		switch (reply->element[2]->type) {
		/*
		 *	Add range ID to request
		 */
		case REDIS_REPLY_STRING:
		{
			vp_tmpl_t range_rhs = {
				.name = "",
				.type = TMPL_TYPE_DATA,
				.tmpl_value_type = FR_TYPE_STRING,
				.quote = T_DOUBLE_QUOTED_STRING
			};
			vp_map_t range_map = {
				.lhs = inst->range_attr,
				.op = T_OP_SET,
				.rhs = &range_rhs
			};

			range_map.rhs->tmpl_value.vb_strvalue = reply->element[2]->str;
			range_map.rhs->tmpl_value_length = reply->element[2]->len;
			range_map.rhs->tmpl_value_type = FR_TYPE_STRING;
			if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
				ret = IPPOOL_RCODE_FAIL;
				goto finish;
			}
		}
			break;

		case REDIS_REPLY_NIL:
			break;

		default:
			REDEBUG("Server returned unexpected type \"%s\" for range element (result[2])",
				fr_int2str(redis_reply_types, reply->element[2]->type, "<UNKNOWN>"));
			ret = IPPOOL_RCODE_FAIL;
			goto finish;
		}
	}

	/*
	 *	Process Expiry time
	 */
	if (inst->expiry_attr && (reply->elements > 3)) {
		vp_tmpl_t expiry_rhs = {
			.name = "",
			.type = TMPL_TYPE_DATA,
			.tmpl_value_type = FR_TYPE_STRING,
			.quote = T_DOUBLE_QUOTED_STRING
		};
		vp_map_t expiry_map = {
			.lhs = inst->expiry_attr,
			.op = T_OP_SET,
			.rhs = &expiry_rhs
		};

		if (reply->element[3]->type != REDIS_REPLY_INTEGER) {
			REDEBUG("Server returned unexpected type \"%s\" for expiry element (result[3])",
				fr_int2str(redis_reply_types, reply->element[3]->type, "<UNKNOWN>"));
			ret = IPPOOL_RCODE_FAIL;
			goto finish;
		}

		expiry_map.rhs->tmpl_value.vb_uint32 = reply->element[3]->integer;
		expiry_map.rhs->tmpl_value_type = FR_TYPE_UINT32;
		if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
			ret = IPPOOL_RCODE_FAIL;
			goto finish;
		}
	}
finish:
	fr_redis_reply_free(&reply);
	return ret;
}

/** Update an existing IP address in a pool
 *
 */
static ippool_rcode_t redis_ippool_update(rlm_redis_ippool_t const *inst, REQUEST *request,
					  uint8_t const *key_prefix, size_t key_prefix_len,
					  fr_ipaddr_t *ip,
					  uint8_t const *device_id, size_t device_id_len,
					  uint8_t const *gateway_id, size_t gateway_id_len,
					  uint32_t expires)
{
	struct			timeval now;
	redisReply		*reply = NULL;

	fr_redis_rcode_t	status;
	ippool_rcode_t		ret = IPPOOL_RCODE_SUCCESS;

	vp_tmpl_t		range_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING };
	vp_map_t		range_map = { .lhs = inst->range_attr, .op = T_OP_SET, .rhs = &range_rhs };

	gettimeofday(&now, NULL);

	/*
	 *	hiredis doesn't deal well with NULL string pointers
	 */
	if (!device_id) device_id = (uint8_t const *)"";
	if (!gateway_id) gateway_id = (uint8_t const *)"";

	if ((ip->af == AF_INET) && inst->ipv4_integer) {
		status = ippool_script(&reply, request, inst->cluster,
				       key_prefix, key_prefix_len,
				       inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout),
				       lua_update_digest, lua_update_cmd,
				       "EVALSHA %s 1 %b %u %u %u %b %b",
				       lua_update_digest,
				       key_prefix, key_prefix_len,
				       (unsigned int)now.tv_sec, expires,
				       htonl(ip->addr.v4.s_addr),
				       device_id, device_id_len,
				       gateway_id, gateway_id_len);
	} else {
		char ip_buff[FR_IPADDR_PREFIX_STRLEN];

		IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
		status = ippool_script(&reply, request, inst->cluster,
				       key_prefix, key_prefix_len,
				       inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout),
				       lua_update_digest, lua_update_cmd,
				       "EVALSHA %s 1 %b %u %u %s %b %b",
				       lua_update_digest,
				       key_prefix, key_prefix_len,
				       (unsigned int)now.tv_sec, expires,
				       ip_buff,
				       device_id, device_id_len,
				       gateway_id, gateway_id_len);
	}
	if (status != REDIS_RCODE_SUCCESS) {
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}

	if (reply->type != REDIS_REPLY_ARRAY) {
		REDEBUG("Expected result to be array got \"%s\"",
			fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>"));
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}

	if (reply->elements == 0) {
		REDEBUG("Got empty result array");
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}

	/*
	 *	Process return code
	 */
	if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
		REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
			fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>"));
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}
	ret = reply->element[0]->integer;
	if (ret < 0) goto finish;

	/*
	 *	Process Range identifier
	 */
	if (reply->elements > 1) {
		switch (reply->element[1]->type) {
		/*
		 *	Add range ID to request
		 */
		case REDIS_REPLY_STRING:
			range_map.rhs->tmpl_value.vb_strvalue = reply->element[1]->str;
			range_map.rhs->tmpl_value_length = reply->element[1]->len;
			range_map.rhs->tmpl_value_type = FR_TYPE_STRING;
			if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) {
				ret = IPPOOL_RCODE_FAIL;
				goto finish;
			}
			break;

		case REDIS_REPLY_NIL:
			break;

		default:
			REDEBUG("Server returned unexpected type \"%s\" for range element (result[1])",
				fr_int2str(redis_reply_types, reply->element[0]->type, "<UNKNOWN>"));
			ret = IPPOOL_RCODE_FAIL;
			goto finish;
		}
	}

	/*
	 *	Copy expiry time to expires attribute (if set)
	 */
	if (inst->expiry_attr) {
		vp_tmpl_t expiry_rhs = {
			.name = "",
			.type = TMPL_TYPE_DATA,
			.tmpl_value_type = FR_TYPE_STRING,
			.quote = T_DOUBLE_QUOTED_STRING
		};
		vp_map_t expiry_map = {
			.lhs = inst->expiry_attr,
			.op = T_OP_SET,
			.rhs = &expiry_rhs
		};

		expiry_map.rhs->tmpl_value.vb_uint32 = expires;
		expiry_map.rhs->tmpl_value_type = FR_TYPE_UINT32;
		if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) {
			ret = IPPOOL_RCODE_FAIL;
			goto finish;
		}
	}

finish:
	fr_redis_reply_free(&reply);

	return ret;
}

/** Release an existing IP address in a pool
 *
 */
static ippool_rcode_t redis_ippool_release(rlm_redis_ippool_t const *inst, REQUEST *request,
					   uint8_t const *key_prefix, size_t key_prefix_len,
					   fr_ipaddr_t *ip,
					   uint8_t const *device_id, size_t device_id_len)
{
	struct			timeval now;
	redisReply		*reply = NULL;

	fr_redis_rcode_t	status;
	ippool_rcode_t		ret = IPPOOL_RCODE_SUCCESS;

	gettimeofday(&now, NULL);

	/*
	 *	hiredis doesn't deal well with NULL string pointers
	 */
	if (!device_id) device_id = (uint8_t const *)"";

	if ((ip->af == AF_INET) && inst->ipv4_integer) {
		status = ippool_script(&reply, request, inst->cluster,
				       key_prefix, key_prefix_len,
				       inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout),
				       lua_release_digest, lua_release_cmd,
				       "EVALSHA %s 1 %b %u %u %b",
				       lua_release_digest,
				       key_prefix, key_prefix_len,
				       (unsigned int)now.tv_sec,
				       htonl(ip->addr.v4.s_addr),
				       device_id, device_id_len);
	} else {
		char ip_buff[FR_IPADDR_PREFIX_STRLEN];

		IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix);
		status = ippool_script(&reply, request, inst->cluster,
				       key_prefix, key_prefix_len,
				       inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout),
				       lua_release_digest, lua_release_cmd,
				       "EVALSHA %s 1 %b %u %s %b",
				       lua_release_digest,
				       key_prefix, key_prefix_len,
				       (unsigned int)now.tv_sec,
				       ip_buff,
				       device_id, device_id_len);
	}
	if (status != REDIS_RCODE_SUCCESS) {
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}

	if (reply->type != REDIS_REPLY_ARRAY) {
		REDEBUG("Expected result to be array got \"%s\"",
			fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>"));
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}

	if (reply->elements == 0) {
		REDEBUG("Got empty result array");
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}

	/*
	 *	Process return code
	 */
	if (reply->element[0]->type != REDIS_REPLY_INTEGER) {
		REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])",
			fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>"));
		ret = IPPOOL_RCODE_FAIL;
		goto finish;
	}
	ret = reply->element[0]->integer;
	if (ret < 0) goto finish;

finish:
	fr_redis_reply_free(&reply);

	return ret;
}

/** Find the pool name we'll be allocating from
 *
 * @param[out] out	Where to write the pool name.
 * @param[out] buff	Where to write the pool name (in the case of an expansion).
 * @param[in] bufflen	Size of the output buffer.
 * @param[in] inst	This instance of the rlm_redis_ippool module.
 * @param[in] request	The current request.
 * @return
 *	- < 0 on error.
 *	- 0 if no pool attribute exists, or the pool name is a zero length string.
 *	- > 0 on success (length of data written to out).
 */
static inline ssize_t ippool_pool_name(uint8_t const **out, uint8_t buff[], size_t bufflen,
				       rlm_redis_ippool_t const *inst, REQUEST *request)
{
	ssize_t slen;

	slen = tmpl_expand(out, (char *)buff, bufflen, request, inst->pool_name, NULL, NULL);
	if (slen < 0) {
		if (inst->pool_name->type == TMPL_TYPE_ATTR) {
			RDEBUG2("Pool attribute not present in request.  Doing nothing");
			return 0;
		}
		REDEBUG("Failed expanding pool name");
		return -1;
	}
	if (slen == 0) {
		RDEBUG2("Empty pool name.  Doing nothing");
		return 0;
	}

	if ((*out == buff) && is_truncated((size_t)slen, bufflen)) {
		REDEBUG("Pool name too long.  Expected %zu bytes, got %zu bytes", bufflen, (size_t)slen);
		return -1;
	}

	return slen;
}

static rlm_rcode_t mod_action(rlm_redis_ippool_t const *inst, REQUEST *request, ippool_action_t action)
{
	uint8_t		key_prefix_buff[IPPOOL_MAX_KEY_PREFIX_SIZE], device_id_buff[256], gateway_id_buff[256];
	uint8_t const	*key_prefix, *device_id = NULL, *gateway_id = NULL;
	size_t		key_prefix_len, device_id_len = 0, gateway_id_len = 0;
	ssize_t		slen;
	fr_ipaddr_t	ip;
	char		expires_buff[20];
	char const	*expires_str;
	unsigned long	expires = 0;
	char		*q;

	slen = ippool_pool_name(&key_prefix, (uint8_t *)&key_prefix_buff, sizeof(key_prefix_len), inst, request);
	if (slen < 0) return RLM_MODULE_FAIL;
	if (slen == 0) return RLM_MODULE_NOOP;

	key_prefix_len = (size_t)slen;

	if (inst->device_id) {
		slen = tmpl_expand((char const **)&device_id,
				   (char *)&device_id_buff, sizeof(device_id_buff),
				   request, inst->device_id, NULL, NULL);
		if (slen < 0) {
			REDEBUG("Failed expanding device (%s)", inst->device_id->name);
			return RLM_MODULE_FAIL;
		}
		device_id_len = (size_t)slen;
	}

	if (inst->gateway_id) {
		slen = tmpl_expand((char const **)&gateway_id,
				   (char *)&gateway_id_buff, sizeof(gateway_id_buff),
				   request, inst->gateway_id, NULL, NULL);
		if (slen < 0) {
			REDEBUG("Failed expanding gateway (%s)", inst->gateway_id->name);
			return RLM_MODULE_FAIL;
		}
		gateway_id_len = (size_t)slen;
	}

	switch (action) {
	case POOL_ACTION_ALLOCATE:
		if (tmpl_expand(&expires_str, expires_buff, sizeof(expires_buff),
				request, inst->offer_time, NULL, NULL) < 0) {
			REDEBUG("Failed expanding offer_time (%s)", inst->offer_time->name);
			return RLM_MODULE_FAIL;
		}

		expires = strtoul(expires_str, &q, 10);
		if (q != (expires_str + strlen(expires_str))) {
			REDEBUG("Invalid offer_time.  Must be an integer value");
			return RLM_MODULE_FAIL;
		}

		ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, NULL,
				    device_id, device_id_len, gateway_id, gateway_id_len, expires);
		switch (redis_ippool_allocate(inst, request, key_prefix, key_prefix_len,
					      device_id, device_id_len,
					      gateway_id, gateway_id_len, (uint32_t)expires)) {
		case IPPOOL_RCODE_SUCCESS:
			RDEBUG2("IP address lease allocated");
			return RLM_MODULE_UPDATED;

		case IPPOOL_RCODE_POOL_EMPTY:
			RWDEBUG("Pool contains no free addresses");
			return RLM_MODULE_NOTFOUND;

		default:
			return RLM_MODULE_FAIL;
		}

	case POOL_ACTION_UPDATE:
	{
		char		ip_buff[INET6_ADDRSTRLEN + 4];
		char const	*ip_str;

		if (tmpl_expand(&expires_str, expires_buff, sizeof(expires_buff),
				request, inst->lease_time, NULL, NULL) < 0) {
			REDEBUG("Failed expanding lease_time (%s)", inst->lease_time->name);
			return RLM_MODULE_FAIL;
		}

		expires = strtoul(expires_str, &q, 10);
		if (q != (expires_str + strlen(expires_str))) {
			REDEBUG("Invalid expires.  Must be an integer value");
			return RLM_MODULE_FAIL;
		}

		if (tmpl_expand(&ip_str, ip_buff, sizeof(ip_buff), request, inst->requested_address, NULL, NULL) < 0) {
			REDEBUG("Failed expanding requested_address (%s)", inst->requested_address->name);
			return RLM_MODULE_FAIL;
		}

		if (fr_inet_pton(&ip, ip_str, -1, AF_UNSPEC, false, true) < 0) {
			RPEDEBUG("Failed parsing address");
			return RLM_MODULE_FAIL;
		}

		ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len,
				    ip_str, device_id, device_id_len, gateway_id, gateway_id_len, expires);
		switch (redis_ippool_update(inst, request, key_prefix, key_prefix_len,
					    &ip, device_id, device_id_len,
					    gateway_id, gateway_id_len, (uint32_t)expires)) {
		case IPPOOL_RCODE_SUCCESS:
			RDEBUG2("Requested IP address' \"%s\" lease updated", ip_str);

			/*
			 *	Copy over the input IP address to the reply attribute
			 */
			if (inst->copy_on_update) {
				vp_tmpl_t ip_rhs = {
					.name = "",
					.type = TMPL_TYPE_DATA,
					.quote = T_BARE_WORD,
				};
				vp_map_t ip_map = {
					.lhs = inst->allocated_address_attr,
					.op = T_OP_SET,
					.rhs = &ip_rhs
				};

				ip_rhs.tmpl_value_length = strlen(ip_str);
				ip_rhs.tmpl_value.vb_strvalue = ip_str;
				ip_rhs.tmpl_value_type = FR_TYPE_STRING;

				if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) return RLM_MODULE_FAIL;
			}
			return RLM_MODULE_UPDATED;

		/*
		 *	It's useful to be able to identify the 'not found' case
		 *	as we can relay to a server where the IP address might
		 *	be found.  This extremely useful for migrations.
		 */
		case IPPOOL_RCODE_NOT_FOUND:
			REDEBUG("Requested IP address \"%s\" is not a member of the specified pool", ip_str);
			return RLM_MODULE_NOTFOUND;

		case IPPOOL_RCODE_EXPIRED:
			REDEBUG("Requested IP address' \"%s\" lease already expired at time of renewal", ip_str);
			return RLM_MODULE_INVALID;

		case IPPOOL_RCODE_DEVICE_MISMATCH:
			REDEBUG("Requested IP address' \"%s\" lease allocated to another device", ip_str);
			return RLM_MODULE_INVALID;

		default:
			return RLM_MODULE_FAIL;
		}
	}

	case POOL_ACTION_RELEASE:
	{
		char		ip_buff[INET6_ADDRSTRLEN + 4];
		char const	*ip_str;

		if (tmpl_expand(&ip_str, ip_buff, sizeof(ip_buff), request, inst->requested_address, NULL, NULL) < 0) {
			REDEBUG("Failed expanding requested_address (%s)", inst->requested_address->name);
			return RLM_MODULE_FAIL;
		}

		if (fr_inet_pton(&ip, ip_str, -1, AF_UNSPEC, false, true) < 0) {
			RPEDEBUG("Failed parsing address");
			return RLM_MODULE_FAIL;
		}

		ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len,
				    ip_str, device_id, device_id_len, gateway_id, gateway_id_len, 0);
		switch (redis_ippool_release(inst, request, key_prefix, key_prefix_len,
					     &ip, device_id, device_id_len)) {
		case IPPOOL_RCODE_SUCCESS:
			RDEBUG2("IP address \"%s\" released", ip_str);
			return RLM_MODULE_UPDATED;

		/*
		 *	It's useful to be able to identify the 'not found' case
		 *	as we can relay to a server where the IP address might
		 *	be found.  This extremely useful for migrations.
		 */
		case IPPOOL_RCODE_NOT_FOUND:
			REDEBUG("Requested IP address \"%s\" is not a member of the specified pool", ip_str);
			return RLM_MODULE_NOTFOUND;

		case IPPOOL_RCODE_DEVICE_MISMATCH:
			REDEBUG("Requested IP address' \"%s\" lease allocated to another device", ip_str);
			return RLM_MODULE_INVALID;

		default:
			return RLM_MODULE_FAIL;
		}
	}

	case POOL_ACTION_BULK_RELEASE:
		RDEBUG2("Bulk release not yet implemented");
		return RLM_MODULE_NOOP;

	default:
		rad_assert(0);
		return RLM_MODULE_FAIL;
	}
}

static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull);
static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request)
{
	rlm_redis_ippool_t const	*inst = instance;
	VALUE_PAIR			*vp;

	/*
	 *	Pool-Action override
	 */
	vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY);
	if (vp) return mod_action(inst, request, vp->vp_uint32);

	/*
	 *	Otherwise, guess the action by Acct-Status-Type
	 */
	vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY);
	if (!vp) {
		RDEBUG2("Couldn't find &request:Acct-Status-Type or &control:Pool-Action, doing nothing...");
		return RLM_MODULE_NOOP;
	}

	switch (vp->vp_uint32) {
	case FR_STATUS_START:
	case FR_STATUS_ALIVE:
		return mod_action(inst, request, POOL_ACTION_UPDATE);

	case FR_STATUS_STOP:
		return mod_action(inst, request, POOL_ACTION_RELEASE);

	case FR_STATUS_ACCOUNTING_OFF:
	case FR_STATUS_ACCOUNTING_ON:
		return mod_action(inst, request, POOL_ACTION_BULK_RELEASE);

	default:
		return RLM_MODULE_NOOP;
	}
}

static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull);
static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request)
{
	rlm_redis_ippool_t const	*inst = instance;
	VALUE_PAIR			*vp;

	/*
	 *	Unless it's overridden the default action is to allocate
	 *	when called in Post-Auth.
	 */
	vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY);
	return mod_action(inst, request, vp ? vp->vp_uint32 : POOL_ACTION_ALLOCATE);
}

static rlm_rcode_t mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull);
static rlm_rcode_t mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request)
{
	rlm_redis_ippool_t const	*inst = instance;
	VALUE_PAIR			*vp;
	ippool_action_t			action = POOL_ACTION_ALLOCATE;

	/*
	 *	Unless it's overridden the default action is to allocate
	 *	when called in Post-Auth.
	 */
	vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY);
	if (vp) {
		if ((vp->vp_uint32 > 0) && (vp->vp_uint32 <= POOL_ACTION_BULK_RELEASE)) {
			action = vp->vp_uint32;

		} else {
			RWDEBUG("Ignoring invalid action %d", vp->vp_uint32);
			return RLM_MODULE_NOOP;
		}
#ifdef WITH_DHCP
	} else if (request->dict == dict_dhcpv4) {
		vp = fr_pair_find_by_da(request->control, attr_message_type, TAG_ANY);
		if (!vp) goto run;

		if (vp->vp_uint8 == FR_DHCP_REQUEST) action = POOL_ACTION_UPDATE;
#endif
	}

run:
	return mod_action(inst, request, action);
}

static int mod_instantiate(void *instance, CONF_SECTION *conf)
{
	static bool			done_hash = false;
	CONF_SECTION			*subcs = cf_section_find(conf, "redis", NULL);

	rlm_redis_ippool_t		*inst = instance;

	rad_assert(inst->allocated_address_attr->type == TMPL_TYPE_ATTR);
	rad_assert(subcs);

	inst->cluster = fr_redis_cluster_alloc(inst, subcs, &inst->conf, true, NULL, NULL, NULL);
	if (!inst->cluster) return -1;

	if (!fr_redis_cluster_min_version(inst->cluster, "3.0.2")) {
		PERROR("Cluster error");
		return -1;
	}

	/*
	 *	Pre-Compute the SHA1 hashes of the Lua scripts
	 */
	if (!done_hash) {
		fr_sha1_ctx	sha1_ctx;
		uint8_t		digest[SHA1_DIGEST_LENGTH];

		fr_sha1_init(&sha1_ctx);
		fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_alloc_cmd, sizeof(lua_alloc_cmd) - 1);
		fr_sha1_final(digest, &sha1_ctx);
		fr_bin2hex(lua_alloc_digest, digest, sizeof(digest));

		fr_sha1_init(&sha1_ctx);
		fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_update_cmd, sizeof(lua_update_cmd) - 1);
		fr_sha1_final(digest, &sha1_ctx);
		fr_bin2hex(lua_update_digest, digest, sizeof(digest));

		fr_sha1_init(&sha1_ctx);
		fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_release_cmd, sizeof(lua_release_cmd) - 1);
		fr_sha1_final(digest, &sha1_ctx);
		fr_bin2hex(lua_release_digest, digest, sizeof(digest));
	}

	/*
	 *	If we don't have a separate time specifically for offers
	 *	just use the lease time.
	 */
	if (!inst->offer_time) inst->offer_time = inst->lease_time;

	return 0;
}

static int mod_load(void)
{
	fr_redis_version_print();

	return 0;
}

extern module_t rlm_redis_ippool;
module_t rlm_redis_ippool = {
	.magic		= RLM_MODULE_INIT,
	.name		= "redis",
	.type		= RLM_TYPE_THREAD_SAFE,
	.inst_size	= sizeof(rlm_redis_ippool_t),
	.config		= module_config,
	.onload		= mod_load,
	.instantiate	= mod_instantiate,
	.methods = {
		[MOD_ACCOUNTING]	= mod_accounting,
		[MOD_AUTHORIZE]		= mod_authorize,
		[MOD_POST_AUTH]		= mod_post_auth,
	},
};
Ejemplo n.º 13
0
USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */

/* avoid inclusion of these FR headers which conflict w/ OpenSSL */
#define _FR_MD4_H
#define _FR_SHA1_H

#include "extern.h"

#include <string.h>

#include <openssl/des.h> /* des_cblock */
#include <openssl/md5.h>
#include <openssl/hmac.h>

/*
 * Generate the State attribute, suitable for passing to fr_pair_make().
 * 'challenge' must be a null terminated string, and be sized at least
 * as large as indicated in the function definition.
 *
 * Returns 0 on success, non-zero otherwise.  For successful returns,
 * 'rad_state' (suitable for passing to fr_pair_make()) and 'raw_state',
 * if non-NULL, will be filled in.
 *
 * In the simplest implementation, we would just use the challenge as state.
 * Unfortunately, the RADIUS secret protects only the User-Password
 * attribute; an attacker that can remove packets from the wire and insert
 * new ones can simply insert a replayed state without having to know
 * the secret.  If not for an attacker that can remove packets from the
 * network, I believe trivial state to be secure.
 *
 * So, we have to make up for that deficiency by signing our state with
 * data unique to this specific request.  A NAS would use the Request
 * Authenticator, but we don't know what that will be when the State is
 * returned to us, so we'll use the time.  So our replay prevention
 * is limited to a time interval (inst->challenge_delay).  We could keep
 * track of all challenges issued over that time interval for
 * better protection.
 *
 * Our state, then, is
 *   (challenge + flags + time + hmac(challenge + resync + time, key)),
 * where '+' denotes concatentation, 'challenge' is ... the challenge,
 * 'flags' is a 32-bit value that can be used to record additional info,
 * 'time' is the 32-bit time (LSB if time_t is 64 bits), and 'key' is a
 * random key, generated in mod_instantiate().  'flags' and 'time' are
 * in network byte order.
 *
 * As the signing key is unique to each server, only the server which
 * generates a challenge can verify it; this should be OK if your NAS's
 * load balance across RADIUS servers using a "first available" algorithm.
 * If your NAS's round-robin and don't "stick" to the same server if they
 * see a State attribute (ugh), you could use the RADIUS secret instead,
 * but read RFC 2104 first, and make very sure you really want to do this.
 *
 * Since only the "same server" can verify State, 'flags' and 'time' doesn't
 * really need to be in network byte order, but we do it anyway.
 *
 * The State attribute is an octet string, however some versions of Cisco
 * IOS and Catalyst OS (at least IOS 12.1(26)E4 and CatOS 7.6.12) treat it
 * as an ASCII string (they only return data up to the first NUL byte).
 * So we must handle state as an ASCII string (0x00 -> 0x3030).
 */

/*
 * OTP_MAX_RADSTATE_LEN is composed of:
 *
 *   clen * 2 +			challenge
 *   8 +			flags
 *   8 +			time
 *   sizeof(hmac) * 2 +		hmac
 *   1				\0'
 */

/** Generate an OTP state value
 *
 * Generates an OTP state value (an string of ASCII hexits in an opaque binary
 * string).
 *
 * @param[out] state buffer in which to write the generated state value.
 * @param[in] challenge The challenge value.
 * @param[in] clen The length of the challenge data.
 * @param[in] flags to remember.
 * @param[in] when the challenge was originally generated.
 * @param[in] key HMAC key.
 * @return the amount of data written into the state buffer.
 */
size_t otp_gen_state(char state[OTP_MAX_RADSTATE_LEN],
		     char const challenge[OTP_MAX_CHALLENGE_LEN],
		     size_t clen,
		     int32_t flags, int32_t when, uint8_t const key[16])
{
	HMAC_CTX hmac_ctx;
	uint8_t hmac[MD5_DIGEST_LENGTH];
	char *p;

	/*
	 *	Generate the hmac.  We already have a dependency on openssl for
	 *	DES, so we'll use it's hmac functionality also -- saves us from
	 *	having to collect the data to be signed into one
	 *	contiguous piece.
	 */
	HMAC_Init(&hmac_ctx, key, sizeof(key[0]) * 16, EVP_md5());
	HMAC_Update(&hmac_ctx, (uint8_t const *) challenge, clen);
	HMAC_Update(&hmac_ctx, (uint8_t *) &flags, 4);
	HMAC_Update(&hmac_ctx, (uint8_t *) &when, 4);
	HMAC_Final(&hmac_ctx, hmac, NULL);
	HMAC_cleanup(&hmac_ctx);

	/*
	 *	Generate the state.
	 */
	p = state;

	/*
	 *	Add the challenge (which is already ASCII encoded)
	 */
	p += fr_bin2hex(p, (uint8_t const *) challenge, clen);

	/* Add the flags and time. */
	p += fr_bin2hex(p, (uint8_t *) &flags, 4);
	p += fr_bin2hex(p, (uint8_t *) &when, 4);

	/* Add the hmac. */
	p += fr_bin2hex(p, hmac, 16);

	return p - state;
}
Ejemplo n.º 14
0
/*
 *	Find the named user in this modules database.  Create the set
 *	of attribute-value pairs to check and reply with for this user
 *	from the database. The authentication code only needs to check
 *	the password, the rest is done here.
 */
static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQUEST *request)
{
	VALUE_PAIR *password, *chap;
	uint8_t pass_str[MAX_STRING_LEN];

	if (!request->username) {
		REDEBUG("&request:User-Name attribute is required for authentication");
		return RLM_MODULE_INVALID;
	}

	chap = fr_pair_find_by_num(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY);
	if (!chap) {
		REDEBUG("You set '&control:Auth-Type = CHAP' for a request that "
			"does not contain a CHAP-Password attribute!");
		return RLM_MODULE_INVALID;
	}

	if (chap->vp_length == 0) {
		REDEBUG("&request:CHAP-Password is empty");
		return RLM_MODULE_INVALID;
	}

	if (chap->vp_length != CHAP_VALUE_LENGTH + 1) {
		REDEBUG("&request:CHAP-Password has invalid length");
		return RLM_MODULE_INVALID;
	}

	password = fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY);
	if (password == NULL) {
		if (fr_pair_find_by_num(request->config, PW_USER_PASSWORD, 0, TAG_ANY) != NULL){
			REDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
			REDEBUG("!!! Please update your configuration so that the \"known !!!");
			REDEBUG("!!! good\" cleartext password is in Cleartext-Password,  !!!");
			REDEBUG("!!! and NOT in User-Password.                            !!!");
			REDEBUG("!!!						          !!!");
			REDEBUG("!!! Authentication will fail because of this.	          !!!");
			REDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
		}

		REDEBUG("&control:Cleartext-Password is required for authentication");
		return RLM_MODULE_FAIL;
	}

	rad_chap_encode(request->packet, pass_str, chap->vp_octets[0], password);

	if (RDEBUG_ENABLED3) {
		uint8_t const *p;
		size_t length;
		VALUE_PAIR *vp;
		char buffer[MAX_STRING_LEN * 2 + 1];

		RDEBUG3("Comparing with \"known good\" &control:Cleartext-Password value \"%s\"",
			password->vp_strvalue);

		vp = fr_pair_find_by_num(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY);
		if (vp) {
			RDEBUG2("Using challenge from &request:CHAP-Challenge");
			p = vp->vp_octets;
			length = vp->vp_length;
		} else {
			RDEBUG2("Using challenge from authenticator field");
			p = request->packet->vector;
			length = sizeof(request->packet->vector);
		}

		fr_bin2hex(buffer, p, length);
		RINDENT();
		RDEBUG3("CHAP challenge : %s", buffer);

		fr_bin2hex(buffer, chap->vp_octets + 1, CHAP_VALUE_LENGTH);
		RDEBUG3("Client sent    : %s", buffer);

		fr_bin2hex(buffer, pass_str + 1, CHAP_VALUE_LENGTH);
		RDEBUG3("We calculated  : %s", buffer);
		REXDENT();
	} else {
		RDEBUG2("Comparing with \"known good\" Cleartext-Password");
	}

	if (rad_digest_cmp(pass_str + 1, chap->vp_octets + 1, CHAP_VALUE_LENGTH) != 0) {
		REDEBUG("Password comparison failed: password is incorrect");
		return RLM_MODULE_REJECT;
	}

	RDEBUG("CHAP user \"%s\" authenticated successfully", request->username->vp_strvalue);

	return RLM_MODULE_OK;
}
Ejemplo n.º 15
0
/*
 *	Does dynamic translation of strings.
 *
 *	Pulls NT-Response, LM-Response, or Challenge from MSCHAP
 *	attributes.
 */
static size_t mschap_xlat(void *instance, REQUEST *request,
		       char *fmt, char *out, size_t outlen,
		       RADIUS_ESCAPE_STRING func)
{
	size_t		i, data_len;
	uint8_t		*data = NULL;
	uint8_t		buffer[32];
	VALUE_PAIR	*user_name;
	VALUE_PAIR	*chap_challenge, *response;
	rlm_mschap_t	*inst = instance;

	response = NULL;

	func = func;		/* -Wunused */

	/*
	 *	Challenge means MS-CHAPv1 challenge, or
	 *	hash of MS-CHAPv2 challenge, and peer challenge.
	 */
	if (strncasecmp(fmt, "Challenge", 9) == 0) {
		chap_challenge = pairfind(request->packet->vps,
					  PW_MSCHAP_CHALLENGE);
		if (!chap_challenge) {
			RDEBUG2("No MS-CHAP-Challenge in the request.");
			return 0;
		}

		/*
		 *	MS-CHAP-Challenges are 8 octets,
		 *	for MS-CHAPv2
		 */
		if (chap_challenge->length == 8) {
			RDEBUG2(" mschap1: %02x",
			       chap_challenge->vp_octets[0]);
			data = chap_challenge->vp_octets;
			data_len = 8;

			/*
			 *	MS-CHAP-Challenges are 16 octets,
			 *	for MS-CHAPv2.
			 */
		} else if (chap_challenge->length == 16) {
			char *username_string;

			RDEBUG2(" mschap2: %02x", chap_challenge->vp_octets[0]);
			response = pairfind(request->packet->vps,
					    PW_MSCHAP2_RESPONSE);
			if (!response) {
				RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
				return 0;
			}

			/*
			 *	Responses are 50 octets.
			 */
			if (response->length < 50) {
				radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
				return 0;
			}

			user_name = pairfind(request->packet->vps,
					     PW_USER_NAME);
			if (!user_name) {
				RDEBUG2("User-Name is required to calculateMS-CHAPv1 Challenge.");
				return 0;
			}

			/*
			 *	with_ntdomain_hack moved here, too.
			 */
			if ((username_string = strchr(user_name->vp_strvalue, '\\')) != NULL) {
				if (inst->with_ntdomain_hack) {
					username_string++;
				} else {
					RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
					username_string = user_name->vp_strvalue;
				}
			} else {
				username_string = user_name->vp_strvalue;
			}

			/*
			 *	Get the MS-CHAPv1 challenge
			 *	from the MS-CHAPv2 peer challenge,
			 *	our challenge, and the user name.
			 */
			challenge_hash(response->vp_octets + 2,
				       chap_challenge->vp_octets,
				       username_string, buffer);
			data = buffer;
			data_len = 8;
		} else {
			RDEBUG2("Invalid MS-CHAP challenge length");
			return 0;
		}

		/*
		 *	Get the MS-CHAPv1 response, or the MS-CHAPv2
		 *	response.
		 */
	} else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
		response = pairfind(request->packet->vps,
				    PW_MSCHAP_RESPONSE);
		if (!response) response = pairfind(request->packet->vps,
						   PW_MSCHAP2_RESPONSE);
		if (!response) {
			RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
			return 0;
		}

		/*
		 *	For MS-CHAPv1, the NT-Response exists only
		 *	if the second octet says so.
		 */
		if ((response->attribute == PW_MSCHAP_RESPONSE) &&
		    ((response->vp_octets[1] & 0x01) == 0)) {
			RDEBUG2("No NT-Response in MS-CHAP-Response");
			return 0;
		}

		/*
		 *	MS-CHAP-Response and MS-CHAP2-Response have
		 *	the NT-Response at the same offset, and are
		 *	the same length.
		 */
		data = response->vp_octets + 26;
		data_len = 24;

		/*
		 *	LM-Response is deprecated, and exists only
		 *	in MS-CHAPv1, and not often there.
		 */
	} else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
		response = pairfind(request->packet->vps,
				    PW_MSCHAP_RESPONSE);
		if (!response) {
			RDEBUG2("No MS-CHAP-Response was found in the request.");
			return 0;
		}

		/*
		 *	For MS-CHAPv1, the NT-Response exists only
		 *	if the second octet says so.
		 */
		if ((response->vp_octets[1] & 0x01) != 0) {
			RDEBUG2("No LM-Response in MS-CHAP-Response");
			return 0;
		}
		data = response->vp_octets + 2;
		data_len = 24;

		/*
		 *	Pull the NT-Domain out of the User-Name, if it exists.
		 */
	} else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
		char *p, *q;

		user_name = pairfind(request->packet->vps, PW_USER_NAME);
		if (!user_name) {
			RDEBUG2("No User-Name was found in the request.");
			return 0;
		}

		/*
		 *	First check to see if this is a host/ style User-Name
		 *	(a la Kerberos host principal)
		 */
		if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
			/*
			 *	If we're getting a User-Name formatted in this way,
			 *	it's likely due to PEAP.  The Windows Domain will be
			 *	the first domain component following the hostname,
			 *	or the machine name itself if only a hostname is supplied
			 */
			p = strchr(user_name->vp_strvalue, '.');
			if (!p) {
				RDEBUG2("setting NT-Domain to same as machine name");
				strlcpy(out, user_name->vp_strvalue + 5, outlen);
			} else {
				p++;	/* skip the period */
				q = strchr(p, '.');
				/*
				 * use the same hack as below
				 * only if another period was found
				 */
				if (q) *q = '\0';
				strlcpy(out, p, outlen);
				if (q) *q = '.';
			}
		} else {
			p = strchr(user_name->vp_strvalue, '\\');
			if (!p) {
				RDEBUG2("No NT-Domain was found in the User-Name.");
				return 0;
			}

			/*
			 *	Hack.  This is simpler than the alternatives.
			 */
			*p = '\0';
			strlcpy(out, user_name->vp_strvalue, outlen);
			*p = '\\';
		}

		return strlen(out);

		/*
		 *	Pull the User-Name out of the User-Name...
		 */
	} else if (strncasecmp(fmt, "User-Name", 9) == 0) {
		char *p;

		user_name = pairfind(request->packet->vps, PW_USER_NAME);
		if (!user_name) {
			RDEBUG2("No User-Name was found in the request.");
			return 0;
		}

		/*
		 *	First check to see if this is a host/ style User-Name
		 *	(a la Kerberos host principal)
		 */
		if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
			/*
			 *	If we're getting a User-Name formatted in this way,
			 *	it's likely due to PEAP.  When authenticating this against
			 *	a Domain, Windows will expect the User-Name to be in the
			 *	format of hostname$, the SAM version of the name, so we
			 *	have to convert it to that here.  We do so by stripping
			 *	off the first 5 characters (host/), and copying everything
			 *	from that point to the first period into a string and appending
			 * 	a $ to the end.
			 */
			p = strchr(user_name->vp_strvalue, '.');
			/*
			 * use the same hack as above
			 * only if a period was found
			 */
			if (p) *p = '\0';
			snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
			if (p) *p = '.';
		} else {
			p = strchr(user_name->vp_strvalue, '\\');
			if (p) {
				p++;	/* skip the backslash */
			} else {
				p = user_name->vp_strvalue; /* use the whole User-Name */
			}
			strlcpy(out, p, outlen);
		}

		return strlen(out);

		/*
		 * Return the NT-Hash of the passed string
		 */
	} else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
		char *p;

		p = fmt + 8;	/* 7 is the length of 'NT-Hash' */
		if ((p == '\0')	 || (outlen <= 32))
			return 0;
		RDEBUG("rlm_mschap: NT-Hash: %s",p);
		ntpwdhash(buffer,p);

		fr_bin2hex(buffer, out, 16);
		out[32] = '\0';
		RDEBUG("rlm_mschap: NT-Hash: Result: %s",out);
		return 32;

		/*
		 * Return the LM-Hash of the passed string
		 */
	} else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
		char *p;

		p = fmt + 8;	/* 7 is the length of 'LM-Hash' */
		if ((p == '\0') || (outlen <= 32))
			return 0;

		RDEBUG("rlm_mschap: LM-Hash: %s",p);
		smbdes_lmpwdhash(p, buffer);
		fr_bin2hex(buffer, out, 16);
		out[32] = '\0';
		RDEBUG("rlm_mschap: LM-Hash: Result: %s",out);
		return 32;
	} else {
		RDEBUG2("Unknown expansion string \"%s\"",
		       fmt);
		return 0;
	}

	if (outlen == 0) return 0; /* nowhere to go, don't do anything */

	/*
	 *	Didn't set anything: this is bad.
	 */
	if (!data) {
		RDEBUG2("Failed to do anything intelligent");
		return 0;
	}

	/*
	 *	Check the output length.
	 */
	if (outlen < ((data_len * 2) + 1)) {
		data_len = (outlen - 1) / 2;
	}

	/*
	 *
	 */
	for (i = 0; i < data_len; i++) {
		sprintf(out + (2 * i), "%02x", data[i]);
	}
	out[data_len * 2] = '\0';

	return data_len * 2;
}
Ejemplo n.º 16
0
/** Decrypt a Yubikey OTP AES block
 *
 * @param inst Module configuration.
 * @param passcode string to decrypt.
 * @return one of the RLM_RCODE_* constants.
 */
rlm_rcode_t rlm_yubikey_decrypt(rlm_yubikey_t *inst, REQUEST *request, char const *passcode)
{
	uint32_t counter;
	yubikey_token_st token;

	DICT_ATTR const *da;

	char private_id[(YUBIKEY_UID_SIZE * 2) + 1];
	VALUE_PAIR *key, *vp;

	da = dict_attrbyname("Yubikey-Key");
	if (!da) {
		REDEBUG("Dictionary missing entry for 'Yubikey-Key'");
		return RLM_MODULE_FAIL;
	}

	key = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY);
	if (!key) {
		REDEBUG("Yubikey-Key attribute not found in control list, can't decrypt OTP data");
		return RLM_MODULE_INVALID;
	}

	if (key->length != YUBIKEY_KEY_SIZE) {
		REDEBUG("Yubikey-Key length incorrect, expected %u got %zu", YUBIKEY_KEY_SIZE, key->length);
		return RLM_MODULE_INVALID;
	}

	yubikey_parse((uint8_t const *) passcode + inst->id_len, key->vp_octets, &token);

	/*
	 *	Apparently this just uses byte offsets...
	 */
	if (!yubikey_crc_ok_p((uint8_t *) &token)) {
		REDEBUG("Decrypting OTP token data failed, rejecting");
		return RLM_MODULE_REJECT;
	}

	RDEBUG("Token data decrypted successfully");

	if (request->log.lvl && request->log.func) {
		(void) fr_bin2hex((char *) &private_id, (uint8_t*) &token.uid, YUBIKEY_UID_SIZE);
		RDEBUG2("Private ID	: 0x%s", private_id);
		RDEBUG2("Session counter   : %u", yubikey_counter(token.ctr));
		RDEBUG2("# used in session : %u", token.use);
		RDEBUG2("Token timestamp    : %u",
			(token.tstph << 16) | token.tstpl);
		RDEBUG2("Random data       : %u", token.rnd);
		RDEBUG2("CRC data          : 0x%x", token.crc);
	}

	/*
	 *	Private ID used for validation purposes
	 */
	vp = pairmake(request, &request->packet->vps, "Yubikey-Private-ID", NULL, T_OP_SET);
	if (!vp) {
		REDEBUG("Failed creating Yubikey-Private-ID");

		return RLM_MODULE_FAIL;
	}
	pairmemcpy(vp, token.uid, YUBIKEY_UID_SIZE);

	/*
	 *	Token timestamp
	 */
	vp = pairmake(request, &request->packet->vps, "Yubikey-Timestamp", NULL, T_OP_SET);
	if (!vp) {
		REDEBUG("Failed creating Yubikey-Timestamp");

		return RLM_MODULE_FAIL;
	}
	vp->vp_integer = (token.tstph << 16) | token.tstpl;
	vp->length = 4;

	/*
	 *	Token random
	 */
	vp = pairmake(request, &request->packet->vps, "Yubikey-Random", NULL, T_OP_SET);
	if (!vp) {
		REDEBUG("Failed creating Yubikey-Random");

		return RLM_MODULE_FAIL;
	}
	vp->vp_integer = token.rnd;
	vp->length = 4;

	/*
	 *	Combine the two counter fields together so we can do
	 *	replay attack checks.
	 */
	counter = (yubikey_counter(token.ctr) << 16) | token.use;

	vp = pairmake(request, &request->packet->vps, "Yubikey-Counter", NULL, T_OP_SET);
	if (!vp) {
		REDEBUG("Failed creating Yubikey-Counter");

		return RLM_MODULE_FAIL;
	}
	vp->vp_integer = counter;
	vp->length = 4;

	/*
	 *	Now we check for replay attacks
	 */
	vp = pairfind(request->config_items, vp->da->attr, vp->da->vendor, TAG_ANY);
	if (!vp) {
		RWDEBUG("Yubikey-Counter not found in control list, skipping replay attack checks");
		return RLM_MODULE_OK;
	}

	if (counter <= vp->vp_integer) {
		REDEBUG("Replay attack detected! Counter value %u, is lt or eq to last known counter value %u",
			counter, vp->vp_integer);
		return RLM_MODULE_REJECT;
	}

	return RLM_MODULE_OK;
}
Ejemplo n.º 17
0
/** Escapes the raw string such that it should be safe to use as part of a file path
 *
 * This function is designed to produce a string that's still readable but portable
 * across the majority of file systems.
 *
 * For security reasons it cannot remove characters from the name, and must not allow
 * collisions to occur between different strings.
 *
 * With that in mind '-' has been chosen as the escape character, and will be double
 * escaped '-' -> '--' to avoid collisions.
 *
 * Escaping should be reversible if the original string needs to be extracted.
 *
 * @note function takes additional arguments so that it may be used as an xlat escape
 *	function but it's fine to call it directly.
 *
 * @note OSX/Unix/NTFS/VFAT have a max filename size of 255 bytes.
 *
 * @param request Current request (may be NULL).
 * @param out Output buffer.
 * @param outlen Size of the output buffer.
 * @param in string to escape.
 * @param arg Context arguments (unused, should be NULL).
 */
size_t rad_filename_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
{
	size_t freespace = outlen;

	while (*in != '\0') {
		size_t utf8_len;

		/*
		 *	Encode multibyte UTF8 chars
		 */
		utf8_len = fr_utf8_char((uint8_t const *) in, -1);
		if (utf8_len > 1) {
			if (freespace <= (utf8_len * 3)) break;

			switch (utf8_len) {
			case 2:
				snprintf(out, freespace, "-%x-%x", in[0], in[1]);
				break;

			case 3:
				snprintf(out, freespace, "-%x-%x-%x", in[0], in[1], in[2]);
				break;

			case 4:
				snprintf(out, freespace, "-%x-%x-%x-%x", in[0], in[1], in[2], in[3]);
				break;
			}

			freespace -= (utf8_len * 3);
			out += (utf8_len * 3);
			in += utf8_len;

			continue;
		}

		/*
		 *	Safe chars
		 */
		if (((*in >= 'A') && (*in <= 'Z')) ||
		    ((*in >= 'a') && (*in <= 'z')) ||
		    ((*in >= '0') && (*in <= '9')) ||
		    (*in == '_')) {
		    	if (freespace <= 1) break;

		 	*out++ = *in++;
		 	freespace--;
		 	continue;
		}
		if (freespace <= 2) break;

		/*
		 *	Double escape '-' (like \\)
		 */
		if (*in == '-') {
			*out++ = '-';
			*out++ = '-';

			freespace -= 2;
			in++;
			continue;
		}

		/*
		 *	Unsafe chars
		 */
		*out++ = '-';
		fr_bin2hex(out, (uint8_t const *)in++, 1);
		out += 2;
		freespace -= 3;
	}
	*out = '\0';

	return outlen - freespace;
}
Ejemplo n.º 18
0
/** Unpack data
 *
 *  Example: %{unpack:&Class 0 integer}
 *
 *  Expands Class, treating octet at offset 0 (bytes 0-3) as an "integer".
 */
static ssize_t unpack_xlat(UNUSED void *instance, REQUEST *request, char const *fmt,
			   char *out, size_t outlen)
{
	char *data_name, *data_size, *data_type;
	char *p;
	size_t len, input_len;
	int offset;
	PW_TYPE type;
	DICT_ATTR const *da;
	VALUE_PAIR *vp, *cast;
	uint8_t const *input;
	char buffer[256];
	uint8_t blob[256];

	/*
	 *	FIXME: copy only the fields here, as we parse them.
	 */
	strlcpy(buffer, fmt, sizeof(buffer));

	p = buffer;
	while (isspace((int) *p)) p++; /* skip leading spaces */

	data_name = p;

	while (*p && !isspace((int) *p)) p++;

	if (!*p) {
	error:
		REDEBUG("Format string should be '<data> <offset> <type>' e.g. '&Class 1 integer'");
	nothing:
		*out = '\0';
		return -1;
	}

	while (isspace((int) *p)) *(p++) = '\0';
	if (!*p) GOTO_ERROR;

	data_size = p;

	while (*p && !isspace((int) *p)) p++;
	if (!*p) GOTO_ERROR;

	while (isspace((int) *p)) *(p++) = '\0';
	if (!*p) GOTO_ERROR;

	data_type = p;

	while (*p && !isspace((int) *p)) p++;
	if (*p) GOTO_ERROR;	/* anything after the type is an error */

	/*
	 *	Attribute reference
	 */
	if (*data_name == '&') {
		if (radius_get_vp(&vp, request, data_name) < 0) goto nothing;

		if ((vp->da->type != PW_TYPE_OCTETS) &&
		    (vp->da->type != PW_TYPE_STRING)) {
			REDEBUG("unpack requires the input attribute to be 'string' or 'octets'");
			goto nothing;
		}
		input = vp->vp_octets;
		input_len = vp->vp_length;

	} else if ((data_name[0] == '0') && (data_name[1] == 'x')) {
		/*
		 *	Hex data.
		 */
		len = strlen(data_name + 2);
		if ((len & 0x01) != 0) {
			RDEBUG("Invalid hex string in '%s'", data_name);
			goto nothing;
		}
		input = blob;
		input_len = fr_hex2bin(blob, sizeof(blob), data_name + 2, len);
		vp = NULL;

	} else {
		GOTO_ERROR;
	}

	offset = (int) strtoul(data_size, &p, 10);
	if (*p) {
		REDEBUG("unpack requires a decimal number, not '%s'", data_size);
		goto nothing;
	}

	if ((size_t) offset >= input_len) {
		REDEBUG("Offset is larget then the input.");
		goto nothing;
	}

	/*
	 *	Allow for string(4) or octets(4), which says "take 4
	 *	bytes from the thing.
	 */
	p = strchr(data_type, '(');
	if (p) {
		char *end;
		unsigned long to_copy;

		*p = '\0';

		to_copy = strtoul(p + 1, &end, 10);
		if (to_copy > input_len) {
			REDEBUG("Invalid length at '%s'", p + 1);
			goto nothing;
		}

		if ((end[0] != ')') || (end[1] != '\0')) {
			REDEBUG("Invalid ending at '%s'", end);
			goto nothing;
		}

		type = fr_str2int(dict_attr_types, data_type, PW_TYPE_INVALID);
		if (type == PW_TYPE_INVALID) {
			REDEBUG("Invalid data type '%s'", data_type);
			goto nothing;
		}

		if ((type != PW_TYPE_OCTETS) && (type != PW_TYPE_STRING)) {
			REDEBUG("Cannot take substring of data type '%s'", data_type);
			goto nothing;
		}

		if (input_len < (offset + to_copy)) {
			REDEBUG("Insufficient data to unpack '%s' from '%s'", data_type, data_name);
			goto nothing;
		}

		/*
		 *	Just copy the string over.
		 */
		if (type == PW_TYPE_STRING) {
			if (outlen <= to_copy) {
				REDEBUG("Insufficient buffer space to unpack data");
				goto nothing;
			}

			memcpy(out, input + offset, to_copy);
			out[to_copy] = '\0';
			return to_copy;
		}

		/*
		 *	We hex encode octets.
		 */
		if (outlen <= (to_copy * 2)) {
			REDEBUG("Insufficient buffer space to unpack data");
			goto nothing;
		}

		return fr_bin2hex(out, input + offset, to_copy);
	}

	type = fr_str2int(dict_attr_types, data_type, PW_TYPE_INVALID);
	if (type == PW_TYPE_INVALID) {
		REDEBUG("Invalid data type '%s'", data_type);
		goto nothing;
	}

	/*
	 *	Output must be a non-zero limited size.
	 */
	if ((dict_attr_sizes[type][0] == 0) ||
	    (dict_attr_sizes[type][0] != dict_attr_sizes[type][1])) {
		REDEBUG("unpack requires fixed-size output type, not '%s'", data_type);
		goto nothing;
	}

	if (input_len < (offset + dict_attr_sizes[type][0])) {
		REDEBUG("Insufficient data to unpack '%s' from '%s'", data_type, data_name);
		goto nothing;
	}

	da = dict_attrbyvalue(PW_CAST_BASE + type, 0);
	if (!da) {
		REDEBUG("Cannot decode type '%s'", data_type);
		goto nothing;
	}

	cast = fr_pair_afrom_da(request, da);
	if (!cast) goto nothing;

	memcpy(&(cast->data), input + offset, dict_attr_sizes[type][0]);
	cast->vp_length = dict_attr_sizes[type][0];

	/*
	 *	Hacks
	 */
	switch (type) {
	case PW_TYPE_SIGNED:
	case PW_TYPE_INTEGER:
	case PW_TYPE_DATE:
		cast->vp_integer = ntohl(cast->vp_integer);
		break;

	case PW_TYPE_SHORT:
		cast->vp_short = ((input[offset] << 8) | input[offset + 1]);
		break;

	case PW_TYPE_INTEGER64:
		cast->vp_integer64 = ntohll(cast->vp_integer64);
		break;

	default:
		break;
	}

	len = vp_prints_value(out, outlen, cast, 0);
	talloc_free(cast);

	if (is_truncated(len, outlen)) {
		REDEBUG("Insufficient buffer space to unpack data");
		goto nothing;
	}

	return len;
}
Ejemplo n.º 19
0
/*
 *	Generate the keys after the user has been authenticated.
 */
static int wimax_postauth(void *instance, REQUEST *request)
{
    rlm_wimax_t *inst = instance;
    VALUE_PAIR *msk, *emsk, *vp;
    VALUE_PAIR *mn_nai, *ip, *fa_rk;
    HMAC_CTX hmac;
    unsigned int rk1_len, rk2_len, rk_len;
    int rk_lifetime = 3600;	/* ? */
    uint32_t mip_spi;
    uint8_t usage_data[24];
    uint8_t mip_rk_1[EVP_MAX_MD_SIZE], mip_rk_2[EVP_MAX_MD_SIZE];
    uint8_t mip_rk[2 * EVP_MAX_MD_SIZE];

    msk = pairfind(request->reply->vps, 1129);
    emsk = pairfind(request->reply->vps, 1130);
    if (!msk || !emsk) {
        RDEBUG("No EAP-MSK or EAP-EMSK.  Cannot create WiMAX keys.");
        return RLM_MODULE_NOOP;
    }

    /*
     *	If we delete the MS-MPPE-*-Key attributes, then add in
     *	the WiMAX-MSK so that the client has a key available.
     */
    if (inst->delete_mppe_keys) {
        pairdelete(&request->reply->vps, ((311 << 16) | 16));
        pairdelete(&request->reply->vps, ((311 << 16) | 17));

        vp = radius_pairmake(request, &request->reply->vps, "WiMAX-MSK", "0x00", T_OP_EQ);
        if (vp) {
            memcpy(vp->vp_octets, msk->vp_octets, msk->length);
            vp->length = msk->length;
        }
    }

    /*
     *	Initialize usage data.
     */
    memcpy(usage_data, "*****@*****.**", 21);	/* with trailing \0 */
    usage_data[21] = 0x02;
    usage_data[22] = 0x00;
    usage_data[23] = 0x01;

    /*
     *	MIP-RK-1 = HMAC-SSHA256(EMSK, usage-data | 0x01)
     */
    HMAC_CTX_init(&hmac);
    HMAC_Init_ex(&hmac, emsk->vp_octets, emsk->length, EVP_sha256(), NULL);

    HMAC_Update(&hmac, &usage_data[0], sizeof(usage_data));
    HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len);

    /*
     *	MIP-RK-2 = HMAC-SSHA256(EMSK, MIP-RK-1 | usage-data | 0x01)
     */
    HMAC_CTX_init(&hmac);
    HMAC_Init_ex(&hmac, emsk->vp_octets, emsk->length, EVP_sha256(), NULL);

    HMAC_Update(&hmac, (const uint8_t *) &mip_rk_1, rk1_len);
    HMAC_Update(&hmac, &usage_data[0], sizeof(usage_data));
    HMAC_Final(&hmac, &mip_rk_2[0], &rk2_len);

    vp = pairfind(request->reply->vps, PW_SESSION_TIMEOUT);
    if (vp) rk_lifetime = vp->vp_integer;

    memcpy(mip_rk, mip_rk_1, rk1_len);
    memcpy(mip_rk + rk1_len, mip_rk_2, rk2_len);
    rk_len = rk1_len + rk2_len;

    /*
     *	MIP-SPI = HMAC-SSHA256(MIP-RK, "SPI CMIP PMIP");
     */
    HMAC_CTX_init(&hmac);
    HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha256(), NULL);

    HMAC_Update(&hmac, (const uint8_t *) "SPI CMIP PMIP", 12);
    HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len);

    /*
     *	Take the 4 most significant octets.
     *	If less than 256, add 256.
     */
    mip_spi = ((mip_rk_1[0] << 24) | (mip_rk_1[1] << 16) |
               (mip_rk_1[2] << 8) | mip_rk_1[3]);
    if (mip_spi < 256) mip_spi += 256;

    if (debug_flag) {
        int len = rk_len;
        char buffer[512];

        if (len > 128) len = 128; /* buffer size */

        fr_bin2hex(mip_rk, buffer, len);
        radlog_request(L_DBG, 0, request, "MIP-RK = 0x%s", buffer);
        radlog_request(L_DBG, 0, request, "MIP-SPI = %08x",
                       ntohl(mip_spi));
    }

    /*
     *	FIXME: Perform SPI collision prevention
     */

    /*
     *	Calculate mobility keys
     */
    mn_nai = pairfind(request->packet->vps, 1900);
    if (!mn_nai) mn_nai = pairfind(request->reply->vps, 1900);
    if (!mn_nai) {
        RDEBUG("WARNING: WiMAX-MN-NAI was not found in the request or in the reply.");
        RDEBUG("WARNING: We cannot calculate MN-HA keys.");
    }

    /*
     *	WiMAX-IP-Technology
     */
    vp = NULL;
    if (mn_nai) vp = pairfind(request->reply->vps, WIMAX2ATTR(23));
    if (!vp) {
        RDEBUG("WARNING: WiMAX-IP-Technology not found in reply.");
        RDEBUG("WARNING: Not calculating MN-HA keys");
    }

    if (vp) switch (vp->vp_integer) {
        case 2:			/* PMIP4 */
            /*
             *	Look for WiMAX-hHA-IP-MIP4
             */
            ip = pairfind(request->reply->vps, WIMAX2ATTR(6));
            if (!ip) {
                RDEBUG("WARNING: WiMAX-hHA-IP-MIP4 not found.  Cannot calculate MN-HA-PMIP4 key");
                break;
            }

            /*
             *	MN-HA-PMIP4 =
             *	   H(MIP-RK, "PMIP4 MN HA" | HA-IPv4 | MN-NAI);
             */
            HMAC_CTX_init(&hmac);
            HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha1(), NULL);

            HMAC_Update(&hmac, (const uint8_t *) "PMIP4 MN HA", 11);
            HMAC_Update(&hmac, (const uint8_t *) &ip->vp_ipaddr, 4);
            HMAC_Update(&hmac, (const uint8_t *) &mn_nai->vp_strvalue, mn_nai->length);
            HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len);

            /*
             *	Put MN-HA-PMIP4 into WiMAX-MN-hHA-MIP4-Key
             */
            vp = pairfind(request->reply->vps, WIMAX2ATTR(10));
            if (!vp) {
                vp = radius_paircreate(request, &request->reply->vps,
                                       WIMAX2ATTR(10), PW_TYPE_OCTETS);
            }
            if (!vp) {
                RDEBUG("WARNING: Failed creating WiMAX-MN-hHA-MIP4-Key");
                break;
            }
            memcpy(vp->vp_octets, &mip_rk_1[0], rk1_len);
            vp->length = rk1_len;

            /*
             *	Put MN-HA-PMIP4-SPI into WiMAX-MN-hHA-MIP4-SPI
             */
            vp = pairfind(request->reply->vps, WIMAX2ATTR(11));
            if (!vp) {
                vp = radius_paircreate(request, &request->reply->vps,
                                       WIMAX2ATTR(11), PW_TYPE_INTEGER);
            }
            if (!vp) {
                RDEBUG("WARNING: Failed creating WiMAX-MN-hHA-MIP4-SPI");
                break;
            }
            vp->vp_integer = mip_spi + 1;
            break;

        case 3:			/* CMIP4 */
            /*
             *	Look for WiMAX-hHA-IP-MIP4
             */
            ip = pairfind(request->reply->vps, WIMAX2ATTR(6));
            if (!ip) {
                RDEBUG("WARNING: WiMAX-hHA-IP-MIP4 not found.  Cannot calculate MN-HA-CMIP4 key");
                break;
            }

            /*
             *	MN-HA-CMIP4 =
             *	   H(MIP-RK, "CMIP4 MN HA" | HA-IPv4 | MN-NAI);
             */
            HMAC_CTX_init(&hmac);
            HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha1(), NULL);

            HMAC_Update(&hmac, (const uint8_t *) "CMIP4 MN HA", 11);
            HMAC_Update(&hmac, (const uint8_t *) &ip->vp_ipaddr, 4);
            HMAC_Update(&hmac, (const uint8_t *) &mn_nai->vp_strvalue, mn_nai->length);
            HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len);

            /*
             *	Put MN-HA-CMIP4 into WiMAX-MN-hHA-MIP4-Key
             */
            vp = pairfind(request->reply->vps, WIMAX2ATTR(10));
            if (!vp) {
                vp = radius_paircreate(request, &request->reply->vps,
                                       WIMAX2ATTR(10), PW_TYPE_OCTETS);
            }
            if (!vp) {
                RDEBUG("WARNING: Failed creating WiMAX-MN-hHA-MIP4-Key");
                break;
            }
            memcpy(vp->vp_octets, &mip_rk_1[0], rk1_len);
            vp->length = rk1_len;

            /*
             *	Put MN-HA-CMIP4-SPI into WiMAX-MN-hHA-MIP4-SPI
             */
            vp = pairfind(request->reply->vps, WIMAX2ATTR(11));
            if (!vp) {
                vp = radius_paircreate(request, &request->reply->vps,
                                       WIMAX2ATTR(11), PW_TYPE_INTEGER);
            }
            if (!vp) {
                RDEBUG("WARNING: Failed creating WiMAX-MN-hHA-MIP4-SPI");
                break;
            }
            vp->vp_integer = mip_spi;
            break;

        case 4:			/* CMIP6 */
            /*
             *	Look for WiMAX-hHA-IP-MIP6
             */
            ip = pairfind(request->reply->vps, WIMAX2ATTR(7));
            if (!ip) {
                RDEBUG("WARNING: WiMAX-hHA-IP-MIP6 not found.  Cannot calculate MN-HA-CMIP6 key");
                break;
            }

            /*
             *	MN-HA-CMIP6 =
             *	   H(MIP-RK, "CMIP6 MN HA" | HA-IPv6 | MN-NAI);
             */
            HMAC_CTX_init(&hmac);
            HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha1(), NULL);

            HMAC_Update(&hmac, (const uint8_t *) "CMIP6 MN HA", 11);
            HMAC_Update(&hmac, (const uint8_t *) &ip->vp_ipv6addr, 16);
            HMAC_Update(&hmac, (const uint8_t *) &mn_nai->vp_strvalue, mn_nai->length);
            HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len);

            /*
             *	Put MN-HA-CMIP6 into WiMAX-MN-hHA-MIP6-Key
             */
            vp = pairfind(request->reply->vps, WIMAX2ATTR(12));
            if (!vp) {
                vp = radius_paircreate(request, &request->reply->vps,
                                       WIMAX2ATTR(12), PW_TYPE_OCTETS);
            }
            if (!vp) {
                RDEBUG("WARNING: Failed creating WiMAX-MN-hHA-MIP6-Key");
                break;
            }
            memcpy(vp->vp_octets, &mip_rk_1[0], rk1_len);
            vp->length = rk1_len;

            /*
             *	Put MN-HA-CMIP6-SPI into WiMAX-MN-hHA-MIP6-SPI
             */
            vp = pairfind(request->reply->vps, WIMAX2ATTR(13));
            if (!vp) {
                vp = radius_paircreate(request, &request->reply->vps,
                                       WIMAX2ATTR(13), PW_TYPE_INTEGER);
            }
            if (!vp) {
                RDEBUG("WARNING: Failed creating WiMAX-MN-hHA-MIP6-SPI");
                break;
            }
            vp->vp_integer = mip_spi + 2;
            break;

        default:
            break;		/* do nothing */
        }

    /*
     *	Generate FA-RK, if requested.
     *
     *	FA-RK= H(MIP-RK, "FA-RK")
     */
    fa_rk = pairfind(request->reply->vps, WIMAX2ATTR(14));
    if (fa_rk && (fa_rk->length == 0)) {
        HMAC_CTX_init(&hmac);
        HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha1(), NULL);

        HMAC_Update(&hmac, (const uint8_t *) "FA-RK", 5);

        HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len);

        memcpy(fa_rk->vp_octets, &mip_rk_1[0], rk1_len);
        fa_rk->length = rk1_len;
    }

    /*
     *	Create FA-RK-SPI, which is really SPI-CMIP4, which is
     *	really MIP-SPI.  Clear?  Of course.  This is WiMAX.
     */
    if (fa_rk) {
        vp = pairfind(request->reply->vps, WIMAX2ATTR(61));
        if (!vp) {
            vp = radius_paircreate(request, &request->reply->vps,
                                   WIMAX2ATTR(61), PW_TYPE_INTEGER);
        }
        if (!vp) {
            RDEBUG("WARNING: Failed creating WiMAX-FA-RK-SPI");
        } else {
            vp->vp_integer = mip_spi;
        }
    }

    /*
     *	Generate MN-FA = H(FA-RK, "MN FA" | FA-IP | MN-NAI)
     */
    ip = pairfind(request->reply->vps, 1901);
    if (fa_rk && ip && mn_nai) {
        HMAC_CTX_init(&hmac);
        HMAC_Init_ex(&hmac, fa_rk->vp_octets, fa_rk->length,
                     EVP_sha1(), NULL);

        HMAC_Update(&hmac, (const uint8_t *) "MN FA", 5);
        HMAC_Update(&hmac, (const uint8_t *) &ip->vp_ipaddr, 4);
        HMAC_Update(&hmac, (const uint8_t *) &mn_nai->vp_strvalue, mn_nai->length);

        HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len);

        vp = radius_paircreate(request, &request->reply->vps,
                               1902, PW_TYPE_OCTETS);
        if (!vp) {
            RDEBUG("WARNING: Failed creating WiMAX-MN-FA");
        } else {
            memcpy(vp->vp_octets, &mip_rk_1[0], rk1_len);
            vp->length = rk1_len;
        }
    }

    /*
     *	Give additional information about requests && responses
     *
     *	WiMAX-RRQ-MN-HA-SPI
     */
    vp = pairfind(request->packet->vps, WIMAX2ATTR(20));
    if (vp) {
        RDEBUG("Client requested MN-HA key: Should use SPI to look up key from storage.");
        if (!mn_nai) {
            RDEBUG("WARNING: MN-NAI was not found!");
        }

        /*
         *	WiMAX-RRQ-HA-IP
         */
        if (!pairfind(request->packet->vps, WIMAX2ATTR(18))) {
            RDEBUG("WARNING: HA-IP was not found!");
        }


        /*
         *	WiMAX-HA-RK-Key-Requested
         */
        vp = pairfind(request->packet->vps, WIMAX2ATTR(58));
        if (vp && (vp->vp_integer == 1)) {
            RDEBUG("Client requested HA-RK: Should use IP to look it up from storage.");
        }
    }

    /*
     *	Wipe the context of all sensitive information.
     */
    HMAC_CTX_cleanup(&hmac);

    return RLM_MODULE_UPDATED;
}
Ejemplo n.º 20
0
/*
 *	Authenticate the user with the given password.
 */
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
{
	rlm_yubikey_t *inst = instance;
	
	char *passcode;
	size_t i, len;
	uint32_t counter;

	const DICT_ATTR *da;	
	VALUE_PAIR *key, *vp;
	yubikey_token_st token;
	
	char private_id[(YUBIKEY_UID_SIZE * 2) + 1];
	
	/*
	 *	Can't do yubikey auth if there's no password.
	 */
	if (!request->password || (request->password->da->attr != PW_USER_PASSWORD)) {
		RDEBUGE("No Clear-Text password in the request. Can't do Yubikey authentication.");
		return RLM_MODULE_FAIL;
	}
	
	passcode = request->password->vp_strvalue;
	len = request->password->length;
	/*
	 *	Verify the passcode is the correct length (in its raw
	 *	modhex encoded form).
	 *
	 *	<public_id (6-16 bytes)> + <aes-block (32 bytes)>
	 */
	if (len != (inst->id_len + 32)) {
		RDEBUGE("User-Password value is not the correct length, expected %u, got %zu", inst->id_len + 32, len);
		return RLM_MODULE_FAIL;	
	}

	for (i = inst->id_len; i < len; i++) {
		if (!is_modhex(*passcode)) {
			RDEBUG2("User-Password (aes-block) value contains non modhex chars");
			return RLM_MODULE_FAIL;	
		}
	}
	
	da = dict_attrbyname("Yubikey-Key");
	key = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY);
	if (!key) {
		RDEBUGE("Yubikey-Key attribute not found in control list, can't decrypt OTP data");
		return RLM_MODULE_FAIL;
	}

	if (key->length != YUBIKEY_KEY_SIZE) {
		RDEBUGE("Yubikey-Key length incorrect, expected %u got %zu", YUBIKEY_KEY_SIZE, key->length);
		return RLM_MODULE_FAIL;	
	}
	
	yubikey_parse(request->password->vp_octets + inst->id_len,
		      key->vp_octets, &token);

	/*
	 *	Apparently this just uses byte offsets...
	 */
	if (!yubikey_crc_ok_p((uint8_t *) &token)) {
		RDEBUGE("Decrypting OTP token data failed, rejecting");	
		return RLM_MODULE_REJECT;
	}
	
	RDEBUG("Token data decrypted successfully");
	
	if (request->options && request->radlog) {
		(void) fr_bin2hex((uint8_t*) &token.uid,
				  (char *) &private_id, YUBIKEY_UID_SIZE);
		RDEBUG2("Private ID	: 0x%s", private_id);
		RDEBUG2("Session counter   : %u", yubikey_counter(token.ctr));
		RDEBUG2("# used in session : %u", token.use);
		RDEBUG2("Token timetamp    : %u",
			(token.tstph << 16) | token.tstpl);
		RDEBUG2("Random data       : %u", token.rnd);
		RDEBUG2("CRC data          : 0x%x", token.crc);
	}

	/*
	 *	Private ID used for validation purposes
	 */
	vp = pairmake(request, &request->packet->vps, "Yubikey-Private-ID", NULL, T_OP_SET);	
	memcpy(vp->vp_octets, token.uid, YUBIKEY_UID_SIZE);
	vp->length = YUBIKEY_UID_SIZE;
	
	/*
	 *	Token timestamp
	 */
	vp = pairmake(request, &request->packet->vps, "Yubikey-Timestamp", NULL, T_OP_SET);
	vp->vp_integer = (token.tstph << 16) | token.tstpl;
	vp->length = 4;
	
	/*
	 *	Token random
	 */
	vp = pairmake(request, &request->packet->vps, "Yubikey-Random", NULL, T_OP_SET);
	vp->vp_integer = token.rnd;
	vp->length = 4;
	
	/*
	 *	Combine the two counter fields together so we can do
	 *	replay attack checks.
	 */
	counter = (yubikey_counter(token.ctr) << 16) | token.use;
	
	vp = pairmake(request, &request->packet->vps, "Yubikey-Counter", NULL, T_OP_SET);
	vp->vp_integer = counter;
	vp->length = 4;
	
	/*
	 *	Now we check for replay attacks
	 */
	vp = pairfind(request->config_items, vp->da->attr, vp->da->vendor, TAG_ANY);
	if (!vp) {
		RDEBUGW("Yubikey-Counter not found in control list, skipping replay attack checks");
		return RLM_MODULE_OK;
	}

	if (counter <= vp->vp_integer) {
		RDEBUGE("Replay attack detected! Counter value %u, is lt or eq to last known counter value %u",
			counter, vp->vp_integer);
		return RLM_MODULE_REJECT;	
	}

	return RLM_MODULE_OK;
}