/*
 * $Id: buildreq.c,v 1.5 2003/04/25 08:10:46 fcusack Exp $
 *
 * Copyright (C) 1995,1997 Lars Fenneberg
 *
 * See the file COPYRIGHT for the respective terms and conditions.
 * If the file is missing contact me at lf@elemental.net
 * and I'll send you a copy.
 *
 */

#include <config.h>
#include <includes.h>
#include <radiusclient.h>

unsigned char rc_get_seqnbr(void);

/*
 * Function: rc_get_nas_id
 *
 * Purpose: fills in NAS-Identifier or NAS-IP-Address in request
 *
 */

int rc_get_nas_id(VALUE_PAIR **sendpairs)
{
	UINT4		client_id;
	char *nasid;

	nasid = rc_conf_str("nas_identifier");
	if (strlen(nasid)) {
		/*
		 * Fill in NAS-Identifier
		 */
		if (rc_avpair_add(sendpairs, PW_NAS_IDENTIFIER, nasid, 0,
				  VENDOR_NONE) == NULL)
			return (ERROR_RC);

		return (OK_RC);
	  
	} else {
		/*
		 * Fill in NAS-IP-Address
		 */
		if ((client_id = rc_own_ipaddress()) == 0)
			return (ERROR_RC);

		if (rc_avpair_add(sendpairs, PW_NAS_IP_ADDRESS, &client_id,
				  0, VENDOR_NONE) == NULL)
			return (ERROR_RC);
	}

	return (OK_RC);
}

/*
 * Function: rc_buildreq
 *
 * Purpose: builds a skeleton RADIUS request using information from the
 *	    config file.
 *
 */

void rc_buildreq(SEND_DATA *data, int code, char *server, unsigned short port,
		 int timeout, int retries)
{
	data->server = server;
	data->svc_port = port;
	data->seq_nbr = rc_get_seqnbr();
	data->timeout = timeout;
	data->retries = retries;
	data->code = code;
}

/*
 * Function: rc_guess_seqnbr
 *
 * Purpose: return a random sequence number
 *
 */

static unsigned char rc_guess_seqnbr(void)
{
	srandom((unsigned int)(time(NULL)+getpid()));
	return (unsigned char)(random() & UCHAR_MAX);
}

/*
 * Function: rc_get_seqnbr
 *
 * Purpose: generate a sequence number
 *
 */

unsigned char rc_get_seqnbr(void)
{
	FILE *sf;
	int tries = 1;
	int seq_nbr, pos;
	char *seqfile = rc_conf_str("seqfile");

	if ((sf = fopen(seqfile, "a+")) == NULL)
	{
		rc_log(LOG_ERR,"rc_get_seqnbr: couldn't open sequence file %s: %s", seqfile, strerror(errno));
		/* well, so guess a sequence number */
		return rc_guess_seqnbr();
	}

	while (do_lock_exclusive(fileno(sf))!= 0)
	{
		if (errno != EWOULDBLOCK) {
			rc_log(LOG_ERR, "rc_get_seqnbr: flock failure: %s: %s", seqfile, strerror(errno));
			fclose(sf);
			return rc_guess_seqnbr();
		}
		tries++;
		if (tries <= 10)
			rc_mdelay(500);
		else
			break;
	}

	if (tries > 10) {
		rc_log(LOG_ERR,"rc_get_seqnbr: couldn't get lock after %d tries: %s", tries-1, seqfile);
		fclose(sf);
		return rc_guess_seqnbr();
	}

	pos = ftell(sf);
	rewind(sf);
	if (fscanf(sf, "%d", &seq_nbr) != 1) {
		if (pos != ftell(sf)) {
			/* file was not empty */
			rc_log(LOG_ERR,"rc_get_seqnbr: fscanf failure: %s", seqfile);
		}
		seq_nbr = rc_guess_seqnbr();
	}

	rewind(sf);
	ftruncate(fileno(sf),0);
	fprintf(sf,"%d\n", (seq_nbr+1) & UCHAR_MAX);

	fflush(sf); /* fflush because a process may read it between the do_unlock and fclose */

	if (do_unlock(fileno(sf)) != 0)
		rc_log(LOG_ERR, "rc_get_seqnbr: couldn't release lock on %s: %s", seqfile, strerror(errno));

	fclose(sf);

	return (unsigned char)seq_nbr;
}

/*
 * Function: rc_auth
 *
 * Purpose: Builds an authentication request for port id client_port
 *	    with the value_pairs send and submits it to a server
 *
 * Returns: received value_pairs in received, messages from the server in msg
 *	    and 0 on success, negative on failure as return value
 *
 */

int rc_auth(UINT4 client_port, VALUE_PAIR *send, VALUE_PAIR **received,
	    char *msg, REQUEST_INFO *info)
{
    SERVER *authserver = rc_conf_srv("authserver");

    if (!authserver) {
	return (ERROR_RC);
    }
    return rc_auth_using_server(authserver, client_port, send, received,
				msg, info);
}

/*
 * Function: rc_auth_using_server
 *
 * Purpose: Builds an authentication request for port id client_port
 *	    with the value_pairs send and submits it to a server.  You
 *          explicitly supply a server list.
 *
 * Returns: received value_pairs in received, messages from the server in msg
 *	    and 0 on success, negative on failure as return value
 *
 */

int rc_auth_using_server(SERVER *authserver,
			 UINT4 client_port,
			 VALUE_PAIR *send,
			 VALUE_PAIR **received,
			 char *msg, REQUEST_INFO *info)
{
	SEND_DATA       data;
	int		result;
	int		i;
	int		timeout = rc_conf_int("radius_timeout");
	int		retries = rc_conf_int("radius_retries");

	data.send_pairs = send;
	data.receive_pairs = NULL;

	/*
	 * Fill in NAS-IP-Address or NAS-Identifier
	 */

	if (rc_get_nas_id(&(data.send_pairs)) == ERROR_RC)
	    return (ERROR_RC);

	/*
	 * Fill in NAS-Port
	 */

	if (rc_avpair_add(&(data.send_pairs), PW_NAS_PORT, &client_port, 0, VENDOR_NONE) == NULL)
		return (ERROR_RC);

	result = ERROR_RC;
	for(i=0; (i<authserver->max) && (result != OK_RC) && (result != BADRESP_RC)
		; i++)
	{
		if (data.receive_pairs != NULL) {
			rc_avpair_free(data.receive_pairs);
			data.receive_pairs = NULL;
		}
		rc_buildreq(&data, PW_ACCESS_REQUEST, authserver->name[i],
			    authserver->port[i], timeout, retries);

		result = rc_send_server (&data, msg, info);
	}

	*received = data.receive_pairs;

	return result;
}

/*
 * Function: rc_auth_proxy
 *
 * Purpose: Builds an authentication request
 *	    with the value_pairs send and submits it to a server.
 *	    Works for a proxy; does not add IP address, and does
 *	    does not rely on config file.
 *
 * Returns: received value_pairs in received, messages from the server in msg
 *	    and 0 on success, negative on failure as return value
 *
 */

int rc_auth_proxy(VALUE_PAIR *send, VALUE_PAIR **received, char *msg)
{
	SEND_DATA       data;
	int		result;
	int		i;
	SERVER		*authserver = rc_conf_srv("authserver");
	int		timeout = rc_conf_int("radius_timeout");
	int		retries = rc_conf_int("radius_retries");

	data.send_pairs = send;
	data.receive_pairs = NULL;

	result = ERROR_RC;
	for(i=0; (i<authserver->max) && (result != OK_RC) && (result != BADRESP_RC)
		; i++)
	{
		if (data.receive_pairs != NULL) {
			rc_avpair_free(data.receive_pairs);
			data.receive_pairs = NULL;
		}
		rc_buildreq(&data, PW_ACCESS_REQUEST, authserver->name[i],
			    authserver->port[i], timeout, retries);

		result = rc_send_server (&data, msg, NULL);
	}

	*received = data.receive_pairs;

	return result;
}


/*
 * Function: rc_acct_using_server
 *
 * Purpose: Builds an accounting request for port id client_port
 *	    with the value_pairs send.  You explicitly supply server list.
 *
 * Remarks: NAS-Identifier/NAS-IP-Address, NAS-Port and Acct-Delay-Time get
 *	    filled in by this function, the rest has to be supplied.
 */

int rc_acct_using_server(SERVER *acctserver,
			 UINT4 client_port,
			 VALUE_PAIR *send)
{
	SEND_DATA       data;
	VALUE_PAIR	*adt_vp;
	int		result;
	time_t		start_time, dtime;
	char		msg[4096];
	int		i;
	int		timeout = rc_conf_int("radius_timeout");
	int		retries = rc_conf_int("radius_retries");

	data.send_pairs = send;
	data.receive_pairs = NULL;

	/*
	 * Fill in NAS-IP-Address or NAS-Identifier
	 */

	if (rc_get_nas_id(&(data.send_pairs)) == ERROR_RC)
	    return (ERROR_RC);

	/*
	 * Fill in NAS-Port
	 */

	if (rc_avpair_add(&(data.send_pairs), PW_NAS_PORT, &client_port, 0, VENDOR_NONE) == NULL)
		return (ERROR_RC);

	/*
	 * Fill in Acct-Delay-Time
	 */

	dtime = 0;
	if ((adt_vp = rc_avpair_add(&(data.send_pairs), PW_ACCT_DELAY_TIME, &dtime, 0, VENDOR_NONE)) == NULL)
		return (ERROR_RC);

	start_time = time(NULL);
	result = ERROR_RC;
	for(i=0; (i<acctserver->max) && (result != OK_RC) && (result != BADRESP_RC)
		; i++)
	{
		if (data.receive_pairs != NULL) {
			rc_avpair_free(data.receive_pairs);
			data.receive_pairs = NULL;
		}
		rc_buildreq(&data, PW_ACCOUNTING_REQUEST, acctserver->name[i],
			    acctserver->port[i], timeout, retries);

		dtime = time(NULL) - start_time;
		rc_avpair_assign(adt_vp, &dtime, 0);

		result = rc_send_server (&data, msg, NULL);
	}

	rc_avpair_free(data.receive_pairs);

	return result;
}

/*
 * Function: rc_acct
 *
 * Purpose: Builds an accounting request for port id client_port
 *	    with the value_pairs send
 *
 * Remarks: NAS-Identifier/NAS-IP-Address, NAS-Port and Acct-Delay-Time get
 *	    filled in by this function, the rest has to be supplied.
 */

int rc_acct(UINT4 client_port, VALUE_PAIR *send)
{
    SERVER *acctserver = rc_conf_srv("acctserver");
    if (!acctserver) return (ERROR_RC);

    return rc_acct_using_server(acctserver, client_port, send);
}

/*
 * Function: rc_acct_proxy
 *
 * Purpose: Builds an accounting request with the value_pairs send
 *
 */

int rc_acct_proxy(VALUE_PAIR *send)
{
	SEND_DATA       data;
	int		result;
	char		msg[4096];
	int		i;
	SERVER		*acctserver = rc_conf_srv("authserver");
	int		timeout = rc_conf_int("radius_timeout");
	int		retries = rc_conf_int("radius_retries");

	data.send_pairs = send;
	data.receive_pairs = NULL;

	result = ERROR_RC;
	for(i=0; (i<acctserver->max) && (result != OK_RC) && (result != BADRESP_RC)
		; i++)
	{
		if (data.receive_pairs != NULL) {
			rc_avpair_free(data.receive_pairs);
			data.receive_pairs = NULL;
		}
		rc_buildreq(&data, PW_ACCOUNTING_REQUEST, acctserver->name[i],
			    acctserver->port[i], timeout, retries);

		result = rc_send_server (&data, msg, NULL);
	}

	rc_avpair_free(data.receive_pairs);

	return result;
}

/*
 * Function: rc_check
 *
 * Purpose: ask the server hostname on the specified port for a
 *	    status message
 *
 */

int rc_check(char *host, unsigned short port, char *msg)
{
	SEND_DATA       data;
	int		result;
	UINT4		service_type;
	int		timeout = rc_conf_int("radius_timeout");
	int		retries = rc_conf_int("radius_retries");

	data.send_pairs = data.receive_pairs = NULL;

	/*
	 * Fill in NAS-IP-Address or NAS-Identifier,
         * although it isn't neccessary
	 */

	if (rc_get_nas_id(&(data.send_pairs)) == ERROR_RC)
	    return (ERROR_RC);

	/*
	 * Fill in Service-Type
	 */

	service_type = PW_ADMINISTRATIVE;
	rc_avpair_add(&(data.send_pairs), PW_SERVICE_TYPE, &service_type, 0, VENDOR_NONE);

	rc_buildreq(&data, PW_STATUS_SERVER, host, port, timeout, retries);
	result = rc_send_server (&data, msg, NULL);

	rc_avpair_free(data.receive_pairs);

	return result;
}
