Esempio n. 1
0
/*
 *	First, look for Exec-Program && Exec-Program-Wait.
 *
 *	Then, call exec_dispatch.
 */
static rlm_rcode_t mod_post_auth(void *instance, REQUEST *request)
{
	int result;
	int exec_wait = 0;
	VALUE_PAIR *vp, *tmp;
	rlm_exec_t *inst = (rlm_exec_t *) instance;

	vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM, 0, TAG_ANY);
	if (vp) {
		exec_wait = 0;

	} else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT, 0, TAG_ANY)) != NULL) {
		exec_wait = 1;
	}
	if (!vp) {
		if (!inst->program) return RLM_MODULE_NOOP;
		
		return exec_dispatch(instance, request);
	}

	tmp = NULL;
	result = radius_exec_program(vp->vp_strvalue, request, exec_wait,
				     NULL, 0, request->packet->vps, &tmp,
				     inst->shell_escape);

	/*
	 *	Always add the value-pairs to the reply.
	 */
	pairmove(request->reply, &request->reply->vps, &tmp);
	pairfree(&tmp);

	if (result < 0) {
		RDEBUGE("Login incorrect (external check failed)");

		request->reply->code = PW_AUTHENTICATION_REJECT;
		return RLM_MODULE_REJECT;
	}
	if (result > 0) {
		/*
		 *	Reject. radius_exec_program() returns >0
		 *	if the exec'ed program had a non-zero
		 *	exit status.
		 */
		request->reply->code = PW_AUTHENTICATION_REJECT;
		
		RDEBUGE("Login incorrect (external check said so)");
		return RLM_MODULE_REJECT;
	}

	return RLM_MODULE_OK;
}
/*
 *	Zap all users on a NAS from the radutmp file.
 */
static int radutmp_zap(REQUEST *request,
		       const char *filename,
		       uint32_t nasaddr,
		       time_t t)
{
	struct radutmp	u;
	int		fd;

	if (t == 0) time(&t);

	fd = open(filename, O_RDWR);
	if (fd < 0) {
		RDEBUGE("Error accessing file %s: %s",
		       filename, strerror(errno));
		return RLM_MODULE_FAIL;
	}

	/*
	 *	Lock the utmp file, prefer lockf() over flock().
	 */
	if (rad_lockfd(fd, LOCK_LEN) < 0) {
		RDEBUGE("Failed to acquire lock on file %s:"
		       " %s", filename, strerror(errno));
		close(fd);
		return RLM_MODULE_FAIL;
	}

	/*
	 *	Find the entry for this NAS / portno combination.
	 */
	while (read(fd, &u, sizeof(u)) == sizeof(u)) {
	  if ((nasaddr != 0 && nasaddr != u.nas_address) ||
	      u.type != P_LOGIN)
	    continue;
	  /*
	   *	Match. Zap it.
	   */
	  if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
	    RDEBUGE("radutmp_zap: negative lseek!");
	    lseek(fd, (off_t)0, SEEK_SET);
	  }
	  u.type = P_IDLE;
	  u.time = t;
	  write(fd, &u, sizeof(u));
	}
	close(fd);	/* and implicitely release the locks */

	return 0;
}
Esempio n. 3
0
/*
 *  Do xlat of strings!
 */
static size_t expr_xlat(UNUSED void *instance, REQUEST *request, const char *fmt,
			char *out, size_t outlen)
{
	int		rcode;
	int64_t		result;
	const		char *p;
	char		buffer[256];

	/*
	 * Do an xlat on the provided string (nice recursive operation).
	 */
	if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
		RDEBUGE("xlat failed.");
		*out = '\0';
		return 0;
	}

	p = buffer;
	rcode = get_number(request, &p, &result);
	if (rcode < 0) {
		return 0;
	}

	/*
	 *  We MUST have eaten the entire input string.
	 */
	if (*p != '\0') {
		RDEBUG2("Failed at %s", p);
		return 0;
	}

	snprintf(out, outlen, "%ld", (long int) result);
	return strlen(out);
}
Esempio n. 4
0
/**
 *  @brief Generate a random integer value
 *
 */
static size_t rand_xlat(UNUSED void *instance, REQUEST *request, const char *fmt,
			char *out, size_t outlen)
{
	int64_t		result;
	char		buffer[256];

	/*
	 * Do an xlat on the provided string (nice recursive operation).
	 */
	if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) {
		RDEBUGE("xlat failed.");
		*out = '\0';
		return 0;
	}

	result = atoi(buffer);

	/*
	 *	Too small or too big.
	 */
	if (result <= 0) return 0;
	if (result >= (1 << 30)) result = (1 << 30);

	result *= fr_rand();	/* 0..2^32-1 */
	result >>= 32;

	snprintf(out, outlen, "%ld", (long int) result);
	return strlen(out);
}
Esempio n. 5
0
/**
 * @brief Convert base64 to hex
 *
 * Example: "%{base64tohex:Zm9v}" == "666f6f"
 */
static size_t base64_to_hex_xlat(UNUSED void *instance, REQUEST *request,
				 const char *fmt, char *out, size_t outlen)
{	
	char buffer[1024];
	uint8_t decbuf[1024], *p;
	
	ssize_t declen;
	size_t freespace = outlen;
	size_t len;

	len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
	
	if (!len) {
		RDEBUGE("xlat failed.");
		*out = '\0';
		return 0;
	}
	
	declen = fr_base64_decode(buffer, len, decbuf, sizeof(decbuf));
	if (declen < 0) {
		RDEBUGE("base64 string invalid");
		*out = '\0';
		return 0;
	}
	
	p = decbuf;
	while ((declen-- > 0) && (--freespace > 0)) {
		if (freespace < 3)
			break;

		snprintf(out, 3, "%02x", *p++);
		
		/* Already decremented */
		freespace -= 1;
		out += 2;
	}

	return outlen - freespace;
}
/*
 *      Check if account has expired, and if user may login now.
 */
static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
{
	rlm_expiration_t *inst = instance;
	VALUE_PAIR *vp, *check_item = NULL;
	char msg[MAX_STRING_LEN];

	if ((check_item = pairfind(request->config_items, PW_EXPIRATION, 0, TAG_ANY)) != NULL){
		/*
		*      Has this user's password expired?
		*
		*      If so, remove ALL reply attributes,
		*      and add our own Reply-Message, saying
		*      why they're being rejected.
		*/
		RDEBUG("Checking Expiration time: '%s'",check_item->vp_strvalue);
		if (((time_t) check_item->vp_date) <= request->timestamp) {
			RDEBUG("Account has expired");

			if (inst->msg && inst->msg[0]){
				if (!radius_xlat(msg, sizeof(msg), inst->msg, request, NULL, NULL)) {
					radlog(L_ERR, "rlm_expiration: xlat failed.");
					return RLM_MODULE_FAIL;
				}

				pairfree(&request->reply->vps);
				pairmake_reply("Reply-Message", msg, T_OP_ADD);
			}

			RDEBUGE("Account has expired [Expiration %s]",check_item->vp_strvalue);
			return RLM_MODULE_USERLOCK;
		}
		/*
		 *	Else the account hasn't expired, but it may do so
		 *	in the future.  Set Session-Timeout.
		 */
		vp = pairfind(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY);
		if (!vp) {
			vp = radius_paircreate(request, &request->reply->vps,
					       PW_SESSION_TIMEOUT, 0);
			vp->vp_date = (uint32_t) (((time_t) check_item->vp_date) - request->timestamp);

		} else if (vp->vp_date > ((uint32_t) (((time_t) check_item->vp_date) - request->timestamp))) {
			vp->vp_date = (uint32_t) (((time_t) check_item->vp_date) - request->timestamp);
		}
	}
	else
		return RLM_MODULE_NOOP;

	return RLM_MODULE_OK;
}
Esempio n. 7
0
void cbtls_info(const SSL *s, int where, int ret)
{
	const char *str, *state;
	int w;
	REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST);
	char buffer[1024];

	w = where & ~SSL_ST_MASK;
	if (w & SSL_ST_CONNECT) str="    TLS_connect";
	else if (w & SSL_ST_ACCEPT) str="    TLS_accept";
	else str="    (other)";

	state = SSL_state_string_long(s);
	state = state ? state : "NULL";
	buffer[0] = '\0';

	if (where & SSL_CB_LOOP) {
		RDEBUG2("%s: %s", str, state);
	} else if (where & SSL_CB_HANDSHAKE_START) {
		RDEBUG2("%s: %s", str, state);
	} else if (where & SSL_CB_HANDSHAKE_DONE) {
		RDEBUG2("%s: %s", str, state);
	} else if (where & SSL_CB_ALERT) {
		str=(where & SSL_CB_READ)?"read":"write";

		snprintf(buffer, sizeof(buffer), "TLS Alert %s:%s:%s",
			 str,
			 SSL_alert_type_string_long(ret),
			 SSL_alert_desc_string_long(ret));
	} else if (where & SSL_CB_EXIT) {
		if (ret == 0) {
			snprintf(buffer, sizeof(buffer), "%s: failed in %s",
				 str, state);

		} else if (ret < 0) {
			if (SSL_want_read(s)) {
				RDEBUG2("%s: Need to read more data: %s",
				       str, state);
			} else {
				snprintf(buffer, sizeof(buffer),
					 "%s: error in %s", str, state);
			}
		}
	}

	if (buffer[0] && request) {
		RDEBUGE("SSL says: %s", buffer);
	}
}
Esempio n. 8
0
/*
 *	Add raw hex data to the reply.
 */
void eap_add_reply(REQUEST *request,
		   const char *name, const uint8_t *value, int len)
{
	VALUE_PAIR *vp;

	vp = pairmake_reply(name, "", T_OP_EQ);
	if (!vp) {
		RDEBUGE("Did not create attribute %s: %s\n",
			name, fr_strerror());
		return;
	}

	memcpy(vp->vp_octets, value, len);
	vp->length = len;
}
Esempio n. 9
0
/**
 * @brief URLencode special characters
 *
 * Example: "%{urlquote:http://example.org/}" == "http%3A%47%47example.org%47"
 */
static size_t urlquote_xlat(UNUSED void *instance, REQUEST *request,
			    const char *fmt, char *out, size_t outlen)
{
	char	*p;
	char 	buffer[1024];
	size_t	freespace = outlen;
	size_t	len;
	
	if (outlen <= 1) return 0;

	len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
	if (!len) {
		RDEBUGE("xlat failed.");
		*out = '\0';
		return 0;
	}

	p = buffer;
	while ((len-- > 0) && (--freespace > 0)) {
		if (isalnum(*p)) {
			*out++ = *p++;
			continue;
		}

		switch (*p) {
			case '-':
			case '_':
			case '.':
			case '~':
				*out++ = *p++;
				break;
			default:
				if (freespace < 3)
					break;
				
				snprintf(out, 4, "%%%02x", *p++); /* %xx */
				
				/* Already decremented */
				freespace -= 2;
				out += 3;
		}
	}

	*out = '\0';

	return outlen - freespace;
}
Esempio n. 10
0
/**
 * @brief Equivalent to the old safe-characters functionality in rlm_sql
 *
 * Example: "%{escape:<img>foo.jpg</img>}" == "=60img=62foo.jpg=60=/img=62"
 */
static size_t escape_xlat(UNUSED void *instance, REQUEST *request,
			  const char *fmt, char *out, size_t outlen)
{
	rlm_expr_t *inst = instance;
	char	*p;
	char 	buffer[1024];
	size_t	freespace = outlen;
	size_t	len;
	
	if (outlen <= 1) return 0;

	len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
	if (!len) {
		RDEBUGE("xlat failed.");
		*out = '\0';
		return 0;
	}

	p = buffer;
	while ((len-- > 0) && (--freespace > 0)) {
		/*
		 *	Non-printable characters get replaced with their
		 *	mime-encoded equivalents.
		 */
		if ((*p > 31) && strchr(inst->allowed_chars, *p)) {
			*out++ = *p++;
			continue;
		}
		
		if (freespace < 3)
			break;

		snprintf(out, 4, "=%02X", *p++);
		
		/* Already decremented */
		freespace -= 2;
		out += 3;
	}
	
	*out = '\0';
	
	return outlen - freespace;
}
Esempio n. 11
0
/**
 * @brief Encode string as base64
 *
 * Example: "%{tobase64:foo}" == "Zm9v"
 */
static size_t base64_xlat(UNUSED void *instance, REQUEST *request,
			  const char *fmt, char *out, size_t outlen)
{
	size_t len;
	char buffer[1024];

	len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
	
	/*
	 *  We can accurately calculate the length of the output string
	 *  if it's larger than outlen, the output would be useless so abort.
	 */
	if (!len || ((FR_BASE64_ENC_LENGTH(len) + 1) > outlen)) {
		RDEBUGE("xlat failed.");
		*out = '\0';
		return 0;
	}
	
	return	fr_base64_encode((uint8_t *) buffer, len, out, outlen);
}
/** Copy packet to multiple servers
 *
 * Create a duplicate of the packet and send it to a list of realms
 * defined by the presence of the Replicate-To-Realm VP in the control
 * list of the current request.
 *
 * This is pretty hacky and is 100% fire and forget. If you're looking
 * to forward authentication requests to multiple realms and process
 * the responses, this function will not allow you to do that.
 *
 * @param[in] instance 	of this module.
 * @param[in] request 	The current request.
 * @param[in] list	of attributes to copy to the duplicate packet.
 * @param[in] code	to write into the code field of the duplicate packet.
 * @return RCODE fail on error, invalid if list does not exist, noop if no
 * 	   replications succeeded, else ok.
 */
static int replicate_packet(UNUSED void *instance, REQUEST *request,
			    pair_lists_t list, unsigned int code)
{
	int rcode = RLM_MODULE_NOOP;
	VALUE_PAIR *vp, **vps, *last;
	home_server *home;
	REALM *realm;
	home_pool_t *pool;
	RADIUS_PACKET *packet = NULL;

	last = request->config_items;

	/*
	 *	Send as many packets as necessary to different
	 *	destinations.
	 */
	while (1) {
		vp = pairfind(last, PW_REPLICATE_TO_REALM, 0, TAG_ANY);
		if (!vp) break;

		last = vp->next;

		realm = realm_find2(vp->vp_strvalue);
		if (!realm) {
			RDEBUG2E("Cannot Replicate to unknown realm %s", realm);
			continue;
		}
		
		/*
		 *	We shouldn't really do this on every loop.
		 */
		switch (request->packet->code) {
		default:
			RDEBUG2E("Cannot replicate unknown packet code %d",
				request->packet->code);
			cleanup(packet);
			return RLM_MODULE_FAIL;
		
		case PW_AUTHENTICATION_REQUEST:
			pool = realm->auth_pool;
			break;
			
#ifdef WITH_ACCOUNTING
			
		case PW_ACCOUNTING_REQUEST:
			pool = realm->acct_pool;
			break;
#endif
			
#ifdef WITH_COA
		case PW_COA_REQUEST:
		case PW_DISCONNECT_REQUEST:
			pool = realm->acct_pool;
			break;
#endif
		}
		
		if (!pool) {
			RDEBUG2W("Cancelling replication to Realm %s, as the realm is local.", realm->name);
			continue;
		}
		
		home = home_server_ldb(realm->name, pool, request);
		if (!home) {
			RDEBUG2E("Failed to find live home server for realm %s",
				realm->name);
			continue;
		}
		
		/*
		 *	For replication to multiple servers we re-use the packet
		 *	we built here.
		 */
		if (!packet) {
			packet = rad_alloc(NULL, 1);
			if (!packet) return RLM_MODULE_FAIL;
			packet->sockfd = -1;
			packet->code = code;
			packet->id = fr_rand() & 0xff;

			packet->sockfd = fr_socket(&home->src_ipaddr, 0);
			if (packet->sockfd < 0) {
				RDEBUGE("Failed opening socket: %s", fr_strerror());
				rcode = RLM_MODULE_FAIL;
				goto done;
			}
			
			vps = radius_list(request, list);
			if (!vps) {
				RDEBUGW("List '%s' doesn't exist for "
				       "this packet", fr_int2str(pair_lists,
				       list, "?unknown?"));
				rcode = RLM_MODULE_INVALID;
				goto done;
			}
			
			/*
			 *	Don't assume the list actually contains any
			 *	attributes.
			 */
			if (*vps) {
				packet->vps = paircopy(packet, *vps);
				if (!packet->vps) {
					rcode = RLM_MODULE_FAIL;
					goto done;
				}
			}
			


			/*
			 *	For CHAP, create the CHAP-Challenge if
			 *	it doesn't exist.
			 */
			if ((code == PW_AUTHENTICATION_REQUEST) &&
			    (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) != NULL) &&
			    (pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL)) {
				vp = radius_paircreate(request, &packet->vps,
						       PW_CHAP_CHALLENGE, 0);
				vp->length = AUTH_VECTOR_LEN;
				memcpy(vp->vp_strvalue, request->packet->vector,
				       AUTH_VECTOR_LEN);
			}
		} else {
			size_t i;

			for (i = 0; i < sizeof(packet->vector); i++) {
				packet->vector[i] = fr_rand() & 0xff;
			}

			packet->id++;
			free(packet->data);
			packet->data = NULL;
			packet->data_len = 0;
		}

		/*
		 *	(Re)-Write these.
		 */
		packet->dst_ipaddr = home->ipaddr;
		packet->dst_port = home->port;
		memset(&packet->src_ipaddr, 0, sizeof(packet->src_ipaddr));
		packet->src_port = 0;
		
		/*
		 *	Encode, sign and then send the packet.
		 */
		RDEBUG("Replicating list '%s' to Realm '%s'",
		       fr_int2str(pair_lists, list, "¿unknown?"),realm->name);
		if (rad_send(packet, NULL, home->secret) < 0) {
			RDEBUGE("Failed replicating packet: %s",
			       fr_strerror());
			rcode = RLM_MODULE_FAIL;
			goto done;
		}

		/*
		 *	We've sent it to at least one destination.
		 */
		rcode = RLM_MODULE_OK;
	}
	
	done:
	
	cleanup(packet);
	return rcode;
}
Esempio n. 13
0
/*
 *	For backwards compatibility.
 */
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
{
	rlm_eap_t		*inst;
	eap_handler_t		*handler;
	eap_packet_raw_t	*eap_packet;
	eap_rcode_t		status;
	rlm_rcode_t		rcode;

	inst = (rlm_eap_t *) instance;

	if (!pairfind(request->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY)) {
		RDEBUGE("You set 'Auth-Type = EAP' for a request that does "
			"not contain an EAP-Message attribute!");
		return RLM_MODULE_INVALID;
	}

	/*
	 *	Get the eap packet  to start with
	 */
	eap_packet = eap_vp2packet(request, request->packet->vps);
	if (!eap_packet) {
		radlog_request(L_ERR, 0, request, "Malformed EAP Message");
		return RLM_MODULE_FAIL;
	}

	/*
	 *	Create the eap handler.  The eap_packet will end up being
	 *	"swallowed" into the handler, so we can't access it after
	 *	this call.
	 */
	handler = eap_handler(inst, &eap_packet, request);
	if (!handler) {
		RDEBUG2("Failed in handler");
		return RLM_MODULE_INVALID;
	}

	/*
	 *	Select the appropriate method or default to the
	 *	configured one
	 */
	status = eap_method_select(inst, handler);

	/*
	 *	If it failed, die.
	 */
	if (status == EAP_INVALID) {
		eap_fail(handler);
		eap_handler_free(inst, handler);
		RDEBUG2("Failed in EAP select");
		return RLM_MODULE_INVALID;
	}

#ifdef WITH_PROXY
	/*
	 *	If we're doing horrible tunneling work, remember it.
	 */
	if ((request->options & RAD_REQUEST_OPTION_PROXY_EAP) != 0) {
		RDEBUG2("  Not-EAP proxy set.  Not composing EAP");
		/*
		 *	Add the handle to the proxied list, so that we
		 *	can retrieve it in the post-proxy stage, and
		 *	send a response.
		 */
		handler->inst_holder = inst;
		status = request_data_add(request,
					  inst, REQUEST_DATA_eap_handler_t,
					  handler, (void *) eap_opaque_free);
		rad_assert(status == 0);
		return RLM_MODULE_HANDLED;
	}
#endif

#ifdef WITH_PROXY
	/*
	 *	Maybe the request was marked to be proxied.  If so,
	 *	proxy it.
	 */
	if (request->proxy != NULL) {
		VALUE_PAIR *vp = NULL;

		rad_assert(!request->proxy_reply);

		/*
		 *	Add the handle to the proxied list, so that we
		 *	can retrieve it in the post-proxy stage, and
		 *	send a response.
		 */
		handler->inst_holder = inst;
		status = request_data_add(request,
					  inst, REQUEST_DATA_eap_handler_t,
					  handler,
					  (void *) eap_opaque_free);
		rad_assert(status == 0);

		/*
		 *	Some simple sanity checks.  These should really
		 *	be handled by the radius library...
		 */
		vp = pairfind(request->proxy->vps, PW_EAP_MESSAGE, 0, TAG_ANY);
		if (vp) {
			vp = pairfind(request->proxy->vps, PW_MESSAGE_AUTHENTICATOR, 0, TAG_ANY);
			if (!vp) {
				pairmake(request->proxy,
					 &request->proxy->vps,
					 "Message-Authenticator",
					 "0x00", T_OP_EQ);
			}
		}

		/*
		 *	Delete the "proxied to" attribute, as it's
		 *	set to 127.0.0.1 for tunneled requests, and
		 *	we don't want to tell the world that...
		 */
		pairdelete(&request->proxy->vps, PW_FREERADIUS_PROXIED_TO, VENDORPEC_FREERADIUS, TAG_ANY);

		RDEBUG2("  Tunneled session will be proxied.  Not doing EAP.");
		return RLM_MODULE_HANDLED;
	}
#endif

	/*
	 *	We are done, wrap the EAP-request in RADIUS to send
	 *	with all other required radius attributes
	 */
	rcode = eap_compose(handler);

	/*
	 *	Add to the list only if it is EAP-Request, OR if
	 *	it's LEAP, and a response.
	 */
	if (((handler->eap_ds->request->code == PW_EAP_REQUEST) &&
	    (handler->eap_ds->request->type.num >= PW_EAP_MD5)) ||

		/*
		 *	LEAP is a little different.  At Stage 4,
		 *	it sends an EAP-Success message, but we still
		 *	need to keep the State attribute & session
		 *	data structure around for the AP Challenge.
		 *
		 *	At stage 6, LEAP sends an EAP-Response, which
		 *	isn't put into the list.
		 */
	    ((handler->eap_ds->response->code == PW_EAP_RESPONSE) &&
	     (handler->eap_ds->response->type.num == PW_EAP_LEAP) &&
	     (handler->eap_ds->request->code == PW_EAP_SUCCESS) &&
	     (handler->eap_ds->request->type.num == 0))) {

		/*
		 *	Return FAIL if we can't remember the handler.
		 *	This is actually disallowed by the
		 *	specification, as unexpected FAILs could have
		 *	been forged.  However, we want to signal to
		 *	everyone else involved that we are
		 *	intentionally failing the session, as opposed
		 *	to accidentally failing it.
		 */
		if (!eaplist_add(inst, handler)) {
			RDEBUG("Failed adding handler to the list");
			eap_fail(handler);
			eap_handler_free(inst, handler);
			return RLM_MODULE_FAIL;
		}

	} else {
		RDEBUG2("Freeing handler");
		/* handler is not required any more, free it now */
		eap_handler_free(inst, handler);
	}

	/*
	 *	If it's an Access-Accept, RFC 2869, Section 2.3.1
	 *	says that we MUST include a User-Name attribute in the
	 *	Access-Accept.
	 */
	if ((request->reply->code == PW_AUTHENTICATION_ACK) &&
	    request->username) {
		VALUE_PAIR *vp;

		/*
		 *	Doesn't exist, add it in.
		 */
		vp = pairfind(request->reply->vps, PW_USER_NAME, 0, TAG_ANY);
		if (!vp) {
			vp = pairmake_reply("User-Name", "",
				      T_OP_EQ);
			strlcpy(vp->vp_strvalue, request->username->vp_strvalue,
				sizeof(vp->vp_strvalue));
			vp->length = request->username->length;
		}

		/*
		 *	Cisco AP1230 has a bug and needs a zero
		 *	terminated string in Access-Accept.
		 */
		if ((inst->mod_accounting_username_bug) &&
		    (vp->length < (int) sizeof(vp->vp_strvalue))) {
			vp->vp_strvalue[vp->length] = '\0';
			vp->length++;
		}
	}

	return rcode;
}
Esempio n. 14
0
/*
 *      Check if account has expired, and if user may login now.
 */
static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
{
    rlm_logintime_t *inst = instance;
    VALUE_PAIR *ends, *timeout;
    int left;

    ends = pairfind(request->config_items, PW_LOGIN_TIME, 0, TAG_ANY);
    if (!ends) {
        return RLM_MODULE_NOOP;
    }

    /*
     *      Authentication is OK. Now see if this user may login at this time of the day.
     */
    RDEBUG("Checking Login-Time");

    /*
     *	Compare the time the request was received with the current Login-Time value
     */
    left = timestr_match(ends->vp_strvalue, request->timestamp);

    /*
     *      Do nothing, login time is not controlled (unendsed).
     */
    if (left == 0) {
        return RLM_MODULE_OK;
    }

    /*
     *      The min_time setting is to deal with NAS that won't allow Session-Timeout values below a certain value
     *	For example some Alcatel Lucent products won't allow a Session-Timeout < 300 (5 minutes).
     *
     *	We don't know were going to get another chance to lock out the user, so we need to do it now.
     */
    if (left < inst->min_time) {
        RDEBUGE("Login outside of allowed time-slot (session end %s, with lockout %i seconds before)",
                ends->vp_strvalue, inst->min_time);

        return RLM_MODULE_USERLOCK;
    }

    /* else left > inst->min_time */

    /*
     *	There's time left in the users session, inform the NAS by including a Session-Timeout
     *	attribute in the reply, or modifying the existing one.
     */
    RDEBUG("Login within allowed time-slot, %i seconds left in this session", left);

    timeout = pairfind(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY);
    if (timeout) {	/* just update... */
        if (timeout->vp_integer > (unsigned int) left) {
            timeout->vp_integer = left;
        }
    } else {
        timeout = radius_paircreate(request, &request->reply->vps, PW_SESSION_TIMEOUT, 0);
        timeout->vp_integer = left;
    }

    RDEBUG("reply:Session-Timeout set to %i", left);

    return RLM_MODULE_OK;
}
Esempio n. 15
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;
}
/*
 *	Do post-proxy processing,
 *	0 = fail
 *	1 = OK.
 *
 *	Called from rlm_eap.c, eap_postproxy().
 */
static int mschap_postproxy(eap_handler_t *handler, UNUSED void *tunnel_data)
{
	VALUE_PAIR *response = NULL;
	mschapv2_opaque_t *data;
	REQUEST *request = handler->request;

	data = (mschapv2_opaque_t *) handler->opaque;
	rad_assert(data != NULL);

	RDEBUG2("Passing reply from proxy back into the tunnel %d.",
		request->reply->code);

	/*
	 *	There is only a limited number of possibilities.
	 */
	switch (request->reply->code) {
	case PW_AUTHENTICATION_ACK:
		RDEBUG2("Proxied authentication succeeded.");

		/*
		 *	Move the attribute, so it doesn't go into
		 *	the reply.
		 */
		pairfilter(data, &response,
			  &request->reply->vps,
			  PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
		break;

	default:
	case PW_AUTHENTICATION_REJECT:
		RDEBUG("Proxied authentication did not succeed.");
		return 0;
	}

	/*
	 *	No response, die.
	 */
	if (!response) {
		RDEBUGE("Proxied reply contained no MS-CHAP-Success or MS-CHAP-Error");
		return 0;
	}

	/*
	 *	Done doing EAP proxy stuff.
	 */
	request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
	eapmschapv2_compose(handler, response);
	data->code = PW_EAP_MSCHAPV2_SUCCESS;

	/*
	 *	Delete MPPE keys & encryption policy
	 *
	 *	FIXME: Use intelligent names...
	 */
	fix_mppe_keys(handler, data);

	/*
	 * save any other attributes for re-use in the final
	 * access-accept e.g. vlan, etc. This lets the PEAP
	 * use_tunneled_reply code work
	 */
	data->reply = paircopy(data, request->reply->vps);

	/*
	 *	And we need to challenge the user, not ack/reject them,
	 *	so we re-write the ACK to a challenge.  Yuck.
	 */
	request->reply->code = PW_ACCESS_CHALLENGE;
	pairfree(&response);

	return 1;
}
Esempio n. 17
0
/** Execute a program.
 *
 * @param cmd Command to execute. This is parsed into argv[] parts,
 * 	then each individual argv part is xlat'ed.
 * @param request current request.
 * @param exec_wait set to 1 if you want to read from or write to child
 * @param user_msg buffer to append plaintext (non valuepair) output.
 * @param msg_len length of user_msg buffer.
 * @param input_pairs list of value pairs - these will be put into
 * 	the environment variables of the child.
 * @param[out] output_pairs list of value pairs - child stdout will be
 * 	parsed and added into this list of value pairs.
 * @param shell_escape
 * @return 0 if exec_wait==0, exit code if exec_wait!=0, -1 on error.
 */
int radius_exec_program(const char *cmd, REQUEST *request,
			int exec_wait,
			char *user_msg, int msg_len,
			VALUE_PAIR *input_pairs,
			VALUE_PAIR **output_pairs,
			int shell_escape)
{
	pid_t pid;
	int from_child;
#ifndef __MINGW32__
	VALUE_PAIR *vp;
	char *p;
	pid_t child_pid;
	int comma = 0;
	int status;
	int n, done;
	char answer[4096];
#endif

	pid = radius_start_program(cmd, request, exec_wait, NULL, &from_child, input_pairs, shell_escape);
	if (pid < 0) {
		return -1;
	}

	if (!exec_wait)
		return 0;

#ifndef __MINGW32__
	done = radius_readfrom_program(request, from_child, pid, 10, answer, sizeof(answer));
	if (done < 0) {
		/*
		 * failure - radius_readfrom_program will
		 * have called close(from_child) for us
		 */
		DEBUG("failed to read from child output");
		return 1;

	}
	answer[done] = 0;


	/*
	 *	Make sure that the writer can't block while writing to
	 *	a pipe that no one is reading from anymore.
	 */
	close(from_child);

	DEBUG2("Exec: Program output is %s", answer);

	/*
	 *	Parse the output, if any.
	 */
	if (done) {
		n = T_OP_INVALID;
		if (output_pairs) {
			/*
			 *	For backwards compatibility, first check
			 *	for plain text (user_msg).
			 */
			vp = NULL;
			n = userparse(answer, &vp);
			if (vp) {
				pairfree(&vp);
			}
		}

		if (n == T_OP_INVALID) {
			if (user_msg) {
				strlcpy(user_msg, answer, msg_len);
			}
		} else {
			/*
			 *	HACK: Replace '\n' with ',' so that
			 *	userparse() can parse the buffer in
			 *	one go (the proper way would be to
			 *	fix userparse(), but oh well).
			 */
			for (p = answer; *p; p++) {
				if (*p == '\n') {
					*p = comma ? ' ' : ',';
					p++;
					comma = 0;
				}
				if (*p == ',') comma++;
			}

			/*
			 *	Replace any trailing comma by a NUL.
			 */
			if (answer[strlen(answer) - 1] == ',') {
				answer[strlen(answer) - 1] = '\0';
			}

			if (userparse(answer, &vp) == T_OP_INVALID) {
				RDEBUGE("Exec: Unparsable reply from '%s'", cmd);

			} else {
				/*
				 *	Tell the caller about the value
				 *	pairs.
				 */
				*output_pairs = vp;
			}
		} /* else the answer was a set of VP's, not a text message */
	} /* else we didn't read anything from the child */

	/*
	 *	Call rad_waitpid (should map to waitpid on non-threaded
	 *	or single-server systems).
	 */
	child_pid = rad_waitpid(pid, &status);
	if (child_pid == 0) {
		RDEBUGE("Exec: Timeout waiting for child");
		return 2;
	}

	if (child_pid == pid) {
		if (WIFEXITED(status)) {
			status = WEXITSTATUS(status);
			RDEBUGE("Exec: child returned %d", status);
			return status;
		}
	}

	RDEBUG("Exec:Abnormal child exit: %s",
	       strerror(errno));
#endif	/* __MINGW32__ */

	return 1;
}
/*
 *	Authenticate a previously sent challenge.
 */
static int mschapv2_authenticate(void *arg, eap_handler_t *handler)
{
	int rcode, ccode;
	mschapv2_opaque_t *data;
	EAP_DS *eap_ds = handler->eap_ds;
	VALUE_PAIR *challenge, *response, *name;
	rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
	REQUEST *request = handler->request;

	rad_assert(request != NULL);
	rad_assert(handler->stage == AUTHENTICATE);

	data = (mschapv2_opaque_t *) handler->opaque;

	/*
	 *	Sanity check the response.
	 */
	if (eap_ds->response->length <= 5) {
		RDEBUGE("corrupted data");
		return 0;
	}

	ccode = eap_ds->response->type.data[0];

	switch (data->code) {
		case PW_EAP_MSCHAPV2_FAILURE:
			if (ccode == PW_EAP_MSCHAPV2_RESPONSE) {
				RDEBUG2("authentication re-try from client after we sent a failure");
				break;
			}

			/*
			 * if we sent error 648 (password expired) to the client
			 * we might get an MSCHAP-CPW packet here; turn it into a
			 * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
			 * (or proxy it, I guess)
			 */
			if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) {
				VALUE_PAIR *cpw;
				int mschap_id = eap_ds->response->type.data[1];
				int copied=0,seq=1;

				RDEBUG2("password change packet received");

				challenge = pairmake_packet("MS-CHAP-Challenge", "0x00", T_OP_EQ);
				if (!challenge) {
					return 0;
				}
				challenge->length = MSCHAPV2_CHALLENGE_LEN;
				memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);

				cpw = pairmake_packet("MS-CHAP2-CPW", "", T_OP_EQ);
				cpw->vp_octets[0] = 7;
				cpw->vp_octets[1] = mschap_id;
				memcpy(cpw->vp_octets+2, eap_ds->response->type.data + 520, 66);
				cpw->length = 68;

				/*
				 * break the encoded password into VPs (3 of them)
				 */
				while (copied < 516) {
					VALUE_PAIR *nt_enc;

					int to_copy = 516 - copied;
					if (to_copy > 243)
						to_copy = 243;

					nt_enc = pairmake_packet("MS-CHAP-NT-Enc-PW", "", T_OP_ADD);
					nt_enc->vp_octets[0] = 6;
					nt_enc->vp_octets[1] = mschap_id;
					nt_enc->vp_octets[2] = 0;
					nt_enc->vp_octets[3] = seq++;

					memcpy(nt_enc->vp_octets + 4, eap_ds->response->type.data + 4 + copied, to_copy);
					copied += to_copy;
					nt_enc->length = 4 + to_copy;
				}

				RDEBUG2("built change password packet");
				debug_pair_list(request->packet->vps);

				/*
				 * jump to "authentication"
				 */
				goto packet_ready;
			}

			/*
			 * we sent a failure and are expecting a failure back
			 */
			if (ccode != PW_EAP_MSCHAPV2_FAILURE) {
				RDEBUGE("Sent FAILURE expecting FAILURE but got %d", ccode);
				return 0;
			}

	failure:
			request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
			eap_ds->request->code = PW_EAP_FAILURE;
			return 1;

		case PW_EAP_MSCHAPV2_SUCCESS:
			/*
			 * we sent a success to the client; some clients send a
			 * success back as-per the RFC, some send an ACK. Permit
			 * both, I guess...
			 */

			switch (ccode) {
				case PW_EAP_MSCHAPV2_SUCCESS:
					eap_ds->request->code = PW_EAP_SUCCESS;

					pairfilter(request->reply,
						  &request->reply->vps,
						  &data->mppe_keys, 0, 0, TAG_ANY);
					/* fall through... */

				case PW_EAP_MSCHAPV2_ACK:
#ifdef WITH_PROXY
					/*
					 *	It's a success.  Don't proxy it.
					 */
					request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
#endif
					pairfilter(request->reply,
						  &request->reply->vps,
						  &data->reply, 0, 0, TAG_ANY);
					return 1;
			}
			RDEBUGE("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
			return 0;

		case PW_EAP_MSCHAPV2_CHALLENGE:
			if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure;

			/*
			 * we sent a challenge, expecting a response
			 */
			if (ccode != PW_EAP_MSCHAPV2_RESPONSE) {
				RDEBUGE("Sent CHALLENGE expecting RESPONSE but got %d", ccode);
				return 0;
			}
			/* authentication happens below */
			break;


		default:
			/* should never happen */
			RDEBUGE("unknown state %d", data->code);
			return 0;
	}


	/*
	 *	Ensure that we have at least enough data
	 *	to do the following checks.
	 *
	 *	EAP header (4), EAP type, MS-CHAP opcode,
	 *	MS-CHAP ident, MS-CHAP data length (2),
	 *	MS-CHAP value length.
	 */
	if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
		RDEBUGE("Response is too short");
		return 0;
	}

	/*
	 *	The 'value_size' is the size of the response,
	 *	which is supposed to be the response (48
	 *	bytes) plus 1 byte of flags at the end.
	 */
	if (eap_ds->response->type.data[4] != 49) {
		RDEBUGE("Response is of incorrect length %d", eap_ds->response->type.data[4]);
		return 0;
	}

	/*
	 *	The MS-Length field is 5 + value_size + length
	 *	of name, which is put after the response.
	 */
	if (((eap_ds->response->type.data[2] << 8) |
	     eap_ds->response->type.data[3]) < (5 + 49)) {
		RDEBUGE("Response contains contradictory length %d %d",
			(eap_ds->response->type.data[2] << 8) |
		       eap_ds->response->type.data[3], 5 + 49);
		return 0;
	}

	/*
	 *	We now know that the user has sent us a response
	 *	to the challenge.  Let's try to authenticate it.
	 *
	 *	We do this by taking the challenge from 'data',
	 *	the response from the EAP packet, and creating VALUE_PAIR's
	 *	to pass to the 'mschap' module.  This is a little wonky,
	 *	but it works.
	 */
	challenge = pairmake_packet("MS-CHAP-Challenge", "0x00", T_OP_EQ);
	if (!challenge) {
		return 0;
	}
	challenge->length = MSCHAPV2_CHALLENGE_LEN;
	memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);

	response = pairmake_packet("MS-CHAP2-Response", "0x00", T_OP_EQ);
	if (!response) {
		return 0;
	}

	response->length = MSCHAPV2_RESPONSE_LEN;
	memcpy(response->vp_strvalue + 2, &eap_ds->response->type.data[5],
	       MSCHAPV2_RESPONSE_LEN - 2);
	response->vp_strvalue[0] = eap_ds->response->type.data[1];
	response->vp_strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];

	name = pairmake_packet("NTLM-User-Name", "", T_OP_EQ);
	if (!name) {
		return 0;
	}
	
	/*
	 *	MS-Length - MS-Value - 5.
	 */
	name->length = (((eap_ds->response->type.data[2] << 8) |
			 eap_ds->response->type.data[3]) -
			eap_ds->response->type.data[4] - 5);
	if (name->length >= sizeof(name->vp_strvalue)) {
		name->length = sizeof(name->vp_strvalue) - 1;
	}

	memcpy(name->vp_strvalue,
	       &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN],
	       name->length);
	name->vp_strvalue[name->length] = '\0';

packet_ready:

#ifdef WITH_PROXY
	/*
	 *	If this options is set, then we do NOT authenticate the
	 *	user here.  Instead, now that we've added the MS-CHAP
	 *	attributes to the request, we STOP, and let the outer
	 *	tunnel code handle it.
	 *
	 *	This means that the outer tunnel code will DELETE the
	 *	EAP attributes, and proxy the MS-CHAP attributes to a
	 *	home server.
	 */
	if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
		char *username = NULL;
		eap_tunnel_data_t *tunnel;

		RDEBUG2("cancelling authentication and letting it be proxied");

		/*
		 *	Set up the callbacks for the tunnel
		 */
		tunnel = talloc_zero(request, eap_tunnel_data_t);

		tunnel->tls_session = arg;
		tunnel->callback = mschap_postproxy;

		/*
		 *	Associate the callback with the request.
		 */
		rcode = request_data_add(request,
					 request->proxy,
					 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
					 tunnel, NULL);
		rad_assert(rcode == 0);

		/*
		 *	The State attribute is NOT supposed to
		 *	go into the proxied packet, it will confuse
		 *	other RADIUS servers, and they will discard
		 *	the request.
		 *
		 *	The PEAP module will take care of adding
		 *	the State attribute back, before passing
		 *	the handler & request back into the tunnel.
		 */
		pairdelete(&request->packet->vps, PW_STATE, 0, TAG_ANY);

		/*
		 *	Fix the User-Name when proxying, to strip off
		 *	the NT Domain, if we're told to, and a User-Name
		 *	exists, and there's a \\, meaning an NT-Domain
		 *	in the user name, THEN discard the user name.
		 */
		if (inst->with_ntdomain_hack &&
		    ((challenge = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) &&
		    ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
			/*
			 *	Wipe out the NT domain.
			 *
			 *	FIXME: Put it into MS-CHAP-Domain?
			 */
			username++; /* skip the \\ */
			memmove(challenge->vp_strvalue,
				username,
				strlen(username) + 1); /* include \0 */
			challenge->length = strlen(challenge->vp_strvalue);
		}

		/*
		 *	Remember that in the post-proxy stage, we've got
		 *	to do the work below, AFTER the call to MS-CHAP
		 *	authentication...
		 */
		return 1;
	}
#endif

	/*
	 *	This is a wild & crazy hack.
	 */
	rcode = process_authenticate(PW_AUTHTYPE_MS_CHAP, request);

	/*
	 *	Delete MPPE keys & encryption policy.  We don't
	 *	want these here.
	 */
	fix_mppe_keys(handler, data);

	/*
	 *	Take the response from the mschap module, and
	 *	return success or failure, depending on the result.
	 */
	response = NULL;
	if (rcode == RLM_MODULE_OK) {
		pairfilter(data, &response, &request->reply->vps,
			 PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
		data->code = PW_EAP_MSCHAPV2_SUCCESS;

	} else if (inst->send_error) {
		pairfilter(data, &response, &request->reply->vps,
			  PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY);
		if (response) {
			int n,err,retry;
			char buf[34];

			RDEBUG2("MSCHAP-Error: %s", response->vp_strvalue);

			/*
			 *	Pxarse the new challenge out of the
			 *	MS-CHAP-Error, so that if the client
			 *	issues a re-try, we will know which
			 *	challenge value that they used.
			 */
			n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]);
			if (n == 3) {
				DEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", err, retry, buf);
				fr_hex2bin(buf, data->challenge, 16);
			} else {
				DEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n);
			}
		}
		data->code = PW_EAP_MSCHAPV2_FAILURE;
	} else {
		eap_ds->request->code = PW_EAP_FAILURE;
		return 1;
	}

	/*
	 *	No response, die.
	 */
	if (!response) {
		RDEBUGE("No MS-CHAP-Success or MS-CHAP-Error was found.");
		return 0;
	}

	/*
	 *	Compose the response (whatever it is),
	 *	and return it to the over-lying EAP module.
	 */
	eapmschapv2_compose(handler, response);
	pairfree(&response);

	return 1;
}
Esempio n. 19
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 mod_authorize(UNUSED void *instance, UNUSED REQUEST *request)
{
    rlm_sqlcounter_t *inst = instance;
    int rcode = RLM_MODULE_NOOP;
    unsigned int counter;
    const DICT_ATTR *dattr;
    VALUE_PAIR *key_vp, *check_vp;
    VALUE_PAIR *reply_item;
    char msg[128];
    char querystr[MAX_QUERY_LEN];
    char sqlxlat[MAX_QUERY_LEN];

    /*
     *	Before doing anything else, see if we have to reset
     *	the counters.
     */
    if (inst->reset_time && (inst->reset_time <= request->timestamp)) {

        /*
         *	Re-set the next time and prev_time for this counters range
         */
        inst->last_reset = inst->reset_time;
        find_next_reset(inst,request->timestamp);
    }


    /*
     *      Look for the key.  User-Name is special.  It means
     *      The REAL username, after stripping.
     */
    DEBUG2("rlm_sqlcounter: Entering module authorize code");
    key_vp = ((inst->key_attr->vendor == 0) && (inst->key_attr->attr == PW_USER_NAME)) ? request->username : pairfind(request->packet->vps, inst->key_attr->attr, inst->key_attr->vendor, TAG_ANY);
    if (!key_vp) {
        DEBUG2("rlm_sqlcounter: Could not find Key value pair");
        return rcode;
    }

    /*
     *      Look for the check item
     */
    if ((dattr = dict_attrbyname(inst->check_name)) == NULL) {
        return rcode;
    }
    /* DEBUG2("rlm_sqlcounter: Found Check item attribute %d", dattr->attr); */
    if ((check_vp= pairfind(request->config_items, dattr->attr, dattr->vendor, TAG_ANY)) == NULL) {
        DEBUG2("rlm_sqlcounter: Could not find Check item value pair");
        return rcode;
    }

    /* first, expand %k, %b and %e in query */
    sqlcounter_expand(querystr, MAX_QUERY_LEN, inst->query, inst);

    /* next, wrap query with sql module & expand */
    snprintf(sqlxlat, sizeof(sqlxlat), "%%{%s:%s}", inst->sqlmod_inst, querystr);

    /* Finally, xlat resulting SQL query */
    radius_xlat(querystr, MAX_QUERY_LEN, sqlxlat, request, NULL, NULL);

    if (sscanf(querystr, "%u", &counter) != 1) {
        DEBUG2("rlm_sqlcounter: No integer found in string \"%s\"",
               querystr);
        return RLM_MODULE_NOOP;
    }

    /*
     * Check if check item > counter
     */
    if (check_vp->vp_integer > counter) {
        unsigned int res = check_vp->vp_integer - counter;

        DEBUG2("rlm_sqlcounter: Check item is greater than query result");
        /*
         *	We are assuming that simultaneous-use=1. But
         *	even if that does not happen then our user
         *	could login at max for 2*max-usage-time Is
         *	that acceptable?
         */

        /*
         *	If we are near a reset then add the next
         *	limit, so that the user will not need to login
         *	again.  Do this only for Session-Timeout.
         */
        if ((inst->reply_attr->attr == PW_SESSION_TIMEOUT) &&
                inst->reset_time &&
                (res >= (inst->reset_time - request->timestamp))) {
            res = inst->reset_time - request->timestamp;
            res += check_vp->vp_integer;
        }

        /*
         *	Limit the reply attribute to the minimum of
         *	the existing value, or this new one.
         */
        reply_item = pairfind(request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor, TAG_ANY);
        if (reply_item) {
            if (reply_item->vp_integer > res)
                reply_item->vp_integer = res;

        } else {
            reply_item = radius_paircreate(request,
                                           &request->reply->vps,
                                           inst->reply_attr->attr,
                                           inst->reply_attr->vendor);
            reply_item->vp_integer = res;
        }

        rcode = RLM_MODULE_OK;

        DEBUG2("rlm_sqlcounter: Authorized user %s, check_item=%u, counter=%u",
               key_vp->vp_strvalue,check_vp->vp_integer,counter);
        DEBUG2("rlm_sqlcounter: Sent Reply-Item for user %s, Type=%s, value=%u",
               key_vp->vp_strvalue,inst->reply_name,reply_item->vp_integer);
    }
    else {
        DEBUG2("rlm_sqlcounter: (Check item - counter) is less than zero");

        /*
         * User is denied access, send back a reply message
         */
        snprintf(msg, sizeof(msg), "Your maximum %s usage time has been reached", inst->reset);
        pairmake_reply("Reply-Message", msg, T_OP_EQ);

        RDEBUGE("Maximum %s usage time reached",
                inst->reset);
        rcode = RLM_MODULE_REJECT;

        DEBUG2("rlm_sqlcounter: Rejected user %s, check_item=%u, counter=%u",
               key_vp->vp_strvalue,check_vp->vp_integer,counter);
    }

    return rcode;
}
Esempio n. 20
0
/*
 *	Verify the response entered by the user.
 */
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
{
	rlm_otp_t *inst = instance;

	const char *username;
	int rc;
	otp_pwe_t pwe;
	VALUE_PAIR *vp;
	
	char challenge[OTP_MAX_CHALLENGE_LEN];	/* cf. authorize() */
	char passcode[OTP_MAX_PASSCODE_LEN + 1];

	challenge[0] = '\0';	/* initialize for otp_pw_valid() */

	/* User-Name attribute required. */
	if (!request->username) {
		RDEBUGW("Attribute \"User-Name\" required "
			"for authentication.");
	  	
		return RLM_MODULE_INVALID;
	}
	
	username = request->username->vp_strvalue;
	
	pwe = otp_pwe_present(request);
	if (pwe == 0) {
		RDEBUGW("Attribute \"User-Password\" "
			"or equivalent required for authentication.");
		
		return RLM_MODULE_INVALID;
	}

	/*
	 *	Retrieve the challenge (from State attribute).
	 */
	vp = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
	if (vp) {
		char	gen_state[OTP_MAX_RADSTATE_LEN]; //!< State as hexits
		uint8_t	bin_state[OTP_MAX_RADSTATE_LEN];
	
		int32_t	then;		//!< State timestamp.
		size_t	elen;		//!< Expected State length.
		size_t	len;

		/*
		 *	Set expected State length (see otp_gen_state())
		 */
		elen = (inst->challenge_len * 2) + 8 + 8 + 32;

		if (vp->length != elen) {
			RDEBUGE("Bad radstate for [%s]: length", username);
			return RLM_MODULE_INVALID;
		}

		/*
		 *	Verify the state.
		 */

		/*
		 *	Convert vp state (ASCII encoded hexits in opaque bin
		 *	string) back to binary.
		 *
		 *	There are notes in otp_radstate as to why the state
		 *	value is encoded as hexits.
		 */
		len = fr_hex2bin(vp->vp_strvalue, bin_state, vp->length);
		if (len != (vp->length / 2)) {
			RDEBUGE("bad radstate for [%s]: not hex", username);
		
			return RLM_MODULE_INVALID;
		}

		/*
		 *	Extract data from State
		 */
		memcpy(challenge, bin_state, inst->challenge_len);

		/*
		 *	Skip flag data
		 */
		memcpy(&then, bin_state + inst->challenge_len + 4, 4);

		/*
		 *	Generate new state from returned input data
		 */
		otp_gen_state(gen_state, challenge, inst->challenge_len, 0,
			      then, inst->hmac_key);

		/*
		 *	Compare generated state (in hex form)
		 *	against generated state (in hex form)
		 *	to verify hmac.
		 */
		if (memcmp(gen_state, vp->vp_octets, vp->length)) {
			RDEBUGE("bad radstate for [%s]: hmac", username);
	
			return RLM_MODULE_REJECT;
		}

		/*
		 *	State is valid, but check expiry.
		 */
		then = ntohl(then);
		if (time(NULL) - then > inst->challenge_delay) {
			RDEBUGE("bad radstate for [%s]: expired",username);

			return RLM_MODULE_REJECT;
		}
	}
	
	/* do it */
	rc = otp_pw_valid(request, pwe, challenge, inst, passcode);

	/* Add MPPE data as needed. */
	if (rc == RLM_MODULE_OK) {
		otp_mppe(request, pwe, inst, passcode);
	}

	return rc;
}
Esempio n. 21
0
/*
 *	Store logins in the RADIUS utmp file.
 */
static rlm_rcode_t mod_accounting(void *instance, REQUEST *request)
{
	struct radutmp	ut, u;
	VALUE_PAIR	*vp;
	int		status = -1;
	int		protocol = -1;
	time_t		t;
	int		fd;
	int		port_seen = 0;
	int		off;
	rlm_radutmp_t	*inst = instance;
	char		buffer[256];
	char		filename[1024];
	char		ip_name[32]; /* 255.255.255.255 */
	const char	*nas;
	NAS_PORT	*cache;
	int		r;

	if (request->packet->src_ipaddr.af != AF_INET) {
		DEBUG("rlm_radutmp: IPv6 not supported!");
		return RLM_MODULE_NOOP;
	}

	/*
	 *	Which type is this.
	 */
	if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) {
		RDEBUG("No Accounting-Status-Type record.");
		return RLM_MODULE_NOOP;
	}
	status = vp->vp_integer;

	/*
	 *	Look for weird reboot packets.
	 *
	 *	ComOS (up to and including 3.5.1b20) does not send
	 *	standard PW_STATUS_ACCOUNTING_XXX messages.
	 *
	 *	Check for:  o no Acct-Session-Time, or time of 0
	 *		    o Acct-Session-Id of "00000000".
	 *
	 *	We could also check for NAS-Port, that attribute
	 *	should NOT be present (but we don't right now).
	 */
	if ((status != PW_STATUS_ACCOUNTING_ON) &&
	    (status != PW_STATUS_ACCOUNTING_OFF)) do {
		int check1 = 0;
		int check2 = 0;

		if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME, 0, TAG_ANY))
		     == NULL || vp->vp_date == 0)
			check1 = 1;
		if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID, 0, TAG_ANY))
		     != NULL && vp->length == 8 &&
		     memcmp(vp->vp_strvalue, "00000000", 8) == 0)
			check2 = 1;
		if (check1 == 0 || check2 == 0) {
			break;
		}
		radlog(L_INFO, "rlm_radutmp: converting reboot records.");
		if (status == PW_STATUS_STOP)
			status = PW_STATUS_ACCOUNTING_OFF;
		if (status == PW_STATUS_START)
			status = PW_STATUS_ACCOUNTING_ON;
	} while(0);

	time(&t);
	memset(&ut, 0, sizeof(ut));
	ut.porttype = 'A';
	ut.nas_address = htonl(INADDR_NONE);

	/*
	 *	First, find the interesting attributes.
	 */
	for (vp = request->packet->vps; vp; vp = vp->next) {
		if (!vp->da->vendor) switch (vp->da->attr) {
			case PW_LOGIN_IP_HOST:
			case PW_FRAMED_IP_ADDRESS:
				ut.framed_address = vp->vp_ipaddr;
				break;
			case PW_FRAMED_PROTOCOL:
				protocol = vp->vp_integer;
				break;
			case PW_NAS_IP_ADDRESS:
				ut.nas_address = vp->vp_ipaddr;
				break;
			case PW_NAS_PORT:
				ut.nas_port = vp->vp_integer;
				port_seen = 1;
				break;
			case PW_ACCT_DELAY_TIME:
				ut.delay = vp->vp_integer;
				break;
			case PW_ACCT_SESSION_ID:
				/*
				 *	If length > 8, only store the
				 *	last 8 bytes.
				 */
				off = vp->length - sizeof(ut.session_id);
				/*
				 * 	Ascend is br0ken - it adds a \0
				 * 	to the end of any string.
				 * 	Compensate.
				 */
				if (vp->length > 0 &&
				    vp->vp_strvalue[vp->length - 1] == 0)
					off--;
				if (off < 0) off = 0;
				memcpy(ut.session_id, vp->vp_strvalue + off,
					sizeof(ut.session_id));
				break;
			case PW_NAS_PORT_TYPE:
				if (vp->vp_integer <= 4)
					ut.porttype = porttypes[vp->vp_integer];
				break;
			case PW_CALLING_STATION_ID:
				if(inst->callerid_ok)
					strlcpy(ut.caller_id,
						(char *)vp->vp_strvalue,
						sizeof(ut.caller_id));
				break;
		}
	}

	/*
	 *	If we didn't find out the NAS address, use the
	 *	originator's IP address.
	 */
	if (ut.nas_address == htonl(INADDR_NONE)) {
		ut.nas_address = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
		nas = request->client->shortname;

	} else if (request->packet->src_ipaddr.ipaddr.ip4addr.s_addr == ut.nas_address) {		/* might be a client, might not be. */
		nas = request->client->shortname;

	} else {
		/*
		 *	The NAS isn't a client, it's behind
		 *	a proxy server.  In that case, just
		 *	get the IP address.
		 */
		nas = ip_ntoa(ip_name, ut.nas_address);
	}

	/*
	 *	Set the protocol field.
	 */
	if (protocol == PW_PPP)
		ut.proto = 'P';
	else if (protocol == PW_SLIP)
		ut.proto = 'S';
	else
		ut.proto = 'T';
	ut.time = t - ut.delay;

	/*
	 *	Get the utmp filename, via xlat.
	 */
	radius_xlat(filename, sizeof(filename), inst->filename, request, NULL, NULL);

	/*
	 *	See if this was a reboot.
	 *
	 *	Hmm... we may not want to zap all of the users when
	 *	the NAS comes up, because of issues with receiving
	 *	UDP packets out of order.
	 */
	if (status == PW_STATUS_ACCOUNTING_ON &&
	    (ut.nas_address != htonl(INADDR_NONE))) {
		radlog(L_INFO, "rlm_radutmp: NAS %s restarted (Accounting-On packet seen)",
		       nas);
		radutmp_zap(request, filename, ut.nas_address, ut.time);
		return RLM_MODULE_OK;
	}

	if (status == PW_STATUS_ACCOUNTING_OFF &&
	    (ut.nas_address != htonl(INADDR_NONE))) {
		radlog(L_INFO, "rlm_radutmp: NAS %s rebooted (Accounting-Off packet seen)",
		       nas);
		radutmp_zap(request, filename, ut.nas_address, ut.time);
		return RLM_MODULE_OK;
	}

	/*
	 *	If we don't know this type of entry pretend we succeeded.
	 */
	if (status != PW_STATUS_START &&
	    status != PW_STATUS_STOP &&
	    status != PW_STATUS_ALIVE) {
		RDEBUGE("NAS %s port %u unknown packet type %d)",
		       nas, ut.nas_port, status);
		return RLM_MODULE_NOOP;
	}

	/*
	 *	Translate the User-Name attribute, or whatever else
	 *	they told us to use.
	 */
	*buffer = '\0';
	radius_xlat(buffer, sizeof(buffer), inst->username, request, NULL, NULL);

	/*
	 *  Copy the previous translated user name.
	 */
	strlcpy(ut.login, buffer, RUT_NAMESIZE);

	/*
	 *	Perhaps we don't want to store this record into
	 *	radutmp. We skip records:
	 *
	 *	- without a NAS-Port (telnet / tcp access)
	 *	- with the username "!root" (console admin login)
	 */
	if (!port_seen) {
		DEBUG2("  rlm_radutmp: No NAS-Port seen.  Cannot do anything.");
		DEBUG2W("checkrad will probably not work!");
		return RLM_MODULE_NOOP;
	}

	if (strncmp(ut.login, "!root", RUT_NAMESIZE) == 0) {
		DEBUG2("  rlm_radutmp: Not recording administrative user");

		return RLM_MODULE_NOOP;
	}

	/*
	 *	Enter into the radutmp file.
	 */
	fd = open(filename, O_RDWR|O_CREAT, inst->permission);
	if (fd < 0) {
		RDEBUGE("Error accessing file %s: %s",
		       filename, strerror(errno));
		return RLM_MODULE_FAIL;
	}

	/*
	 *	Lock the utmp file, prefer lockf() over flock().
	 */
	rad_lockfd(fd, LOCK_LEN);

	/*
	 *	Find the entry for this NAS / portno combination.
	 */
	if ((cache = nas_port_find(inst->nas_port_list, ut.nas_address,
				   ut.nas_port)) != NULL) {
		lseek(fd, (off_t)cache->offset, SEEK_SET);
	}

	r = 0;
	off = 0;
	while (read(fd, &u, sizeof(u)) == sizeof(u)) {
		off += sizeof(u);
		if (u.nas_address != ut.nas_address ||
		    u.nas_port	  != ut.nas_port)
			continue;

		/*
		 *	Don't compare stop records to unused entries.
		 */
		if (status == PW_STATUS_STOP &&
		    u.type == P_IDLE) {
			continue;
		}

		if (status == PW_STATUS_STOP &&
		    strncmp(ut.session_id, u.session_id,
			    sizeof(u.session_id)) != 0) {
			/*
			 *	Don't complain if this is not a
			 *	login record (some clients can
			 *	send _only_ logout records).
			 */
			if (u.type == P_LOGIN)
				RDEBUGW("Logout entry for NAS %s port %u has wrong ID",
				       nas, u.nas_port);
			r = -1;
			break;
		}

		if (status == PW_STATUS_START &&
		    strncmp(ut.session_id, u.session_id,
			    sizeof(u.session_id)) == 0  &&
		    u.time >= ut.time) {
			if (u.type == P_LOGIN) {
				radlog(L_INFO, "rlm_radutmp: Login entry for NAS %s port %u duplicate",
				       nas, u.nas_port);
				r = -1;
				break;
			}
			RDEBUGW("Login entry for NAS %s port %u wrong order",
			       nas, u.nas_port);
			r = -1;
			break;
		}

		/*
		 *	FIXME: the ALIVE record could need
		 *	some more checking, but anyway I'd
		 *	rather rewrite this mess -- miquels.
		 */
		if (status == PW_STATUS_ALIVE &&
		    strncmp(ut.session_id, u.session_id,
			    sizeof(u.session_id)) == 0  &&
		    u.type == P_LOGIN) {
			/*
			 *	Keep the original login time.
			 */
			ut.time = u.time;
		}

		if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
			RDEBUGW("negative lseek!");
			lseek(fd, (off_t)0, SEEK_SET);
			off = 0;
		} else
			off -= sizeof(u);
		r = 1;
		break;
	} /* read the file until we find a match */

	/*
	 *	Found the entry, do start/update it with
	 *	the information from the packet.
	 */
	if (r >= 0 &&  (status == PW_STATUS_START ||
			status == PW_STATUS_ALIVE)) {
		/*
		 *	Remember where the entry was, because it's
		 *	easier than searching through the entire file.
		 */
		if (!cache) {
			cache = talloc_zero(inst, NAS_PORT);
			if (cache) {
				cache->nasaddr = ut.nas_address;
				cache->port = ut.nas_port;
				cache->offset = off;
				cache->next = inst->nas_port_list;
				inst->nas_port_list = cache;
			}
		}

		ut.type = P_LOGIN;
		write(fd, &ut, sizeof(u));
	}

	/*
	 *	The user has logged off, delete the entry by
	 *	re-writing it in place.
	 */
	if (status == PW_STATUS_STOP) {
		if (r > 0) {
			u.type = P_IDLE;
			u.time = ut.time;
			u.delay = ut.delay;
			write(fd, &u, sizeof(u));
		} else if (r == 0) {
			RDEBUGW("Logout for NAS %s port %u, but no Login record",
			       nas, ut.nas_port);
		}
	}
	close(fd);	/* and implicitely release the locks */

	return RLM_MODULE_OK;
}
Esempio n. 22
0
/**
 *  @brief Generate a string of random chars
 *
 *  Build strings of random chars, useful for generating tokens and passcodes
 *  Format similar to String::Random.
 */
static size_t randstr_xlat(UNUSED void *instance, REQUEST *request,
			   const char *fmt, char *out, size_t outlen)
{
	char		*p;
	char		buffer[1024];
	unsigned int	result;
	size_t		freespace = outlen;
	size_t		len;
	
	if (outlen <= 1) return 0;

	/*
	 * Do an xlat on the provided string (nice recursive operation).
	 */
	len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL);
	if (!len) {
		RDEBUGE("xlat failed.");
		*out = '\0';
		return 0;
	}
	
	p = buffer;
	while ((len-- > 0) && (--freespace > 0)) {
		result = fr_rand();
		switch (*p) {
			/*
			 *  Lowercase letters
			 */
			case 'c':
				*out++ = 'a' + (result % 26);
			break;
			
			/*
			 *  Uppercase letters
			 */
			case 'C':
				*out++ = 'A' + (result % 26);
			break;
			
			/*
			 *  Numbers
			 */
			case 'n':
				*out++ = '0' + (result % 10);
			break;
			
			/*
			 *  Alpha numeric
			 */
			case 'a':
				*out++ = randstr_salt[result % (sizeof(randstr_salt) - 3)];
			break;
			
			/*
			 *  Punctuation
			 */
			case '!':
				*out++ = randstr_punc[result % (sizeof(randstr_punc) - 1)];
			break;
			
			/*
			 *  Alpa numeric + punctuation
			 */
			case '.':
				*out++ = '!' + (result % 95);
			break;
			
			/*
			 *  Alpha numeric + salt chars './'
			 */	
			case 's':
				*out++ = randstr_salt[result % (sizeof(randstr_salt) - 1)];
			break;
			
			/*
			 *  Binary data as hexits (we don't really support
			 *  non printable chars).
			 */
			case 'h':
				if (freespace < 2)
					break;
				
				snprintf(out, 3, "%02x", result % 256);
				
				/* Already decremented */
				freespace -= 1;
				out += 2;
			break;
			
			default:
				radlog(L_ERR,
				       "rlm_expr: invalid character class '%c'",
				       *p);
				
				return 0;
			break;
		}
	
		p++;
	}
	
	*out++ = '\0';
	
	return outlen - freespace;
}
Esempio n. 23
0
/*
 *  Dispatch an exec method
 */
static rlm_rcode_t exec_dispatch(void *instance, REQUEST *request)
{
	rlm_exec_t *inst = (rlm_exec_t *) instance;
	int result;
	VALUE_PAIR	**input_pairs, **output_pairs;
	VALUE_PAIR	*answer = NULL;
	char		msg[1024];
	size_t		len;

	/*
	 *	We need a program to execute.
	 */
	if (!inst->program) {
		radlog(L_ERR, "rlm_exec (%s): We require a program to execute",
		       inst->xlat_name);
		return RLM_MODULE_FAIL;
	}

	/*
	 *	See if we're supposed to execute it now.
	 */
	if (!((inst->packet_code == 0) ||
	      (request->packet->code == inst->packet_code) ||
	      (request->reply->code == inst->packet_code)
#ifdef WITH_PROXY
	      || (request->proxy &&
	       (request->proxy->code == inst->packet_code)) ||
	      (request->proxy_reply &&
	       (request->proxy_reply->code == inst->packet_code))
#endif
		    )) {
		RDEBUG2("Packet type is not %s.  Not executing.",
		       inst->packet_type);
		return RLM_MODULE_NOOP;
	}

	/*
	 *	Decide what input/output the program takes.
	 */
	input_pairs = decode_string(request, inst->input);
	output_pairs = decode_string(request, inst->output);

	if (!input_pairs) {
		RDEBUG2W("Possible parse error in %s",
			inst->input);
		return RLM_MODULE_NOOP;
	}

	/*
	 *	It points to the attribute list, but the attribute
	 *	list is empty.
	 */
	if (!*input_pairs) {
		RDEBUG2W("Input pairs are empty.  No attributes will be passed to the script");
	}

	/*
	 *	This function does it's own xlat of the input program
	 *	to execute.
	 *
	 *	FIXME: if inst->program starts with %{, then
	 *	do an xlat ourselves.  This will allow us to do
	 *	program = %{Exec-Program}, which this module
	 *	xlat's into it's string value, and then the
	 *	exec program function xlat's it's string value
	 *	into something else.
	 */
	result = radius_exec_program(inst->program, request,
				     inst->wait, msg, sizeof(msg),
				     *input_pairs, &answer, inst->shell_escape);
	if (result < 0) {
		radlog(L_ERR, "rlm_exec (%s): External script failed",
		       inst->xlat_name);
		return RLM_MODULE_FAIL;
	}

	/*
	 *	Move the answer over to the output pairs.
	 *
	 *	If we're not waiting, then there are no output pairs.
	 */
	if (output_pairs) pairmove(request, output_pairs, &answer);

	pairfree(&answer);

	if (result == 0) {
		return RLM_MODULE_OK;
	}
	if (result > RLM_MODULE_NUMCODES) {
		return RLM_MODULE_FAIL;
	}
	
	/*
	 *	Write any exec output to module failure message
	 */
	if (*msg) {
		/* Trim off returns and newlines */
		len = strlen(msg);
		if (msg[len - 1] == '\n' || msg[len - 1] == '\r') {
			msg[len - 1] = '\0';
		}
		
		RDEBUGE("%s", msg);
	}
	
	return result-1;
}