/*
 * RADIUS CLIENT - Sends authentication or accounting requests to RADIUS
 *		   servers and displays the results. I/O is done according to
 * 		   OpenRADIUS' module interface, so this can also be used for
 *		   proxying. Operates fully asynchronously; handles multiple 
 *		   queries and retransmissions for each in parallel.
 *
 * Emile van Bergen, emile@evbergen.xs4all.nl
 *
 * Permission to redistribute an original or modified version of this file
 * in source, intermediate or object code form is hereby granted exclusively
 * under the terms of the GNU General Public License, version 2. Please see the
 * file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html.
 *
 * History:
 * 2002/10/18 - EvB - Started, based on the old radclient test tool, Brian
 *                    Candler's enhancements and my RADIUS client for pppd.
 * 2003/04/01 - EvB - Fixed typo in reply matching code
 * 2004/06/26 - EvB - Added Target-Server attribute support, i.e. ability
 *                    to specify servers in query instead of command line
 * 2004/08/07 - EvB - Split -C from -p option; you do want to reencode PAP
 *                    but don't want to reencode CHAP when proxying.
 */


/*
 * INCLUDES & DEFINES
 */


#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>	/* Standard combo for sockaddr_in, socket, bind etc. */
#include <arpa/inet.h>	/* For inet_addr */
#include <netdb.h>	/* For gethostbyname */

#include <sys/utsname.h> /* For uname */
#include <sys/time.h>	/* For struct timeval */
#include <sys/select.h>	/* For select */
#include <unistd.h> 	/* For getopt */
#include <stdlib.h>	/* For strtoul, getopt for some, malloc */ 
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>

#include <metadict.h>
#include <metaops.h>	/* For META_AV etc. */

#include <ringbuf.h>	/* Ring buffer for stdin */
#include <misc.h>	/* For encrypt_attr_pap(), get_random_data */
#include <md5.h>
#include <debug.h>


/* default RADIUS ports, timeout value and retry count */

#define DEF_AUTHPORT		1812
#define DEF_ACCTPORT		1813
#define DEF_RETRYCOUNT		3
#define DEF_TIMEOUT		5


/* exit codes; bit 6 == 0 means exit code == RAD-Code, 1 means error: */

#define EXIT_USAGE		0x40
#define EXIT_DICT		0x41
#define EXIT_STDIN		0x42
#define EXIT_SOCK		0x43
#define EXIT_TIMEOUT		0x44
#define EXIT_INCOMP		0x45
#define EXIT_INTERN		0x4f


/* if set, always try primary first; continue if reject ("Josh's feature") */

/* #define RADIUS_SPECIAL_PRIMARY	1	*/


/* max. number of servers that can be specified for a query */

#define MAX_SERVERS		4


/* outstanding request table dimensions. Note that one query may generate
   multiple outstanding requests due to retransmissions.

   Don't specify more than 8 bits yet, because RADIUS' ID field isn't wider,
   and using multiple source ports is not implemented yet. The other tricks go
   against the spirit of the standard, if not the letter, and won't be used. 
   
   If you run a busy proxy, simply run multiple instances of radclient in your
   server's proxy interface. */

#define MAX_OUTREQBITS		8
#define MAX_OUTREQS		(1 << (MAX_OUTREQBITS))
#define MAX_OUTREQMASK		((MAX_OUTREQS) - 1)


/* pairs to add to the query or the requests we send on its behalf */

#define A_RAD_CODE		1		/* query */
#define A_USER_NAME		2		/* query */
#define A_CHAP_PASS		4		/* query */
#define A_CHAP_CHAL		8		/* query */
#define A_NAS_IP		0x10		/* query */
#define A_TGT_SRV		0x20		/* query */

#define A_QRY_MASK		0x3f		/* pairs to be added to qry */

#define A_RAD_ID		0x40		/* request */
#define A_RAD_AUTH		0x80		/* request */
#define A_USER_PASS		0x100		/* request (but have plain) */
#define A_ACCT_DLY		0x200		/* request */

#define A_REQ_MASK		0x3c0		/* pairs to be added to req */

#define A_ALL			0x3ff		/* all pairs that we can add */

/* pairs missing from response */

#define A_RAD_LEN		0x400		/* response */


/* utility macros */

#define tmr_ring_maxput(c) ((MAX_OUTREQS + (c)->timer_r - \
					  ((c)->timer_w + 1)) % MAX_OUTREQS)
#define tmr_ring_maxget(c) ((MAX_OUTREQS + (c)->timer_w - (c)->timer_r) % \
			    MAX_OUTREQS)

/*
 * TYPES
 */


struct radsrv {			/* RADIUS server */

	struct sockaddr_in sin;		/* IP address, port */
	unsigned char *secret;		/* shared secret */
	unsigned int secretlen;

	int req_slot;			/* -1 or last request to this server */
};


struct radquery {		/* Unanswered query from stdin */

	struct radquery *prev, *next;	/* linkage */

	time_t timestamp;		/* when query was received */
	int ttl;			/* number of retransmissions to go */

	struct radsrv srv[MAX_SERVERS];	/* servers for this query */
	int srvcnt, cursrv;

	META_AV *reqhead, *reqtail;	/* radius pairs used as-is for query */
	int missing;			/* pairs we need to add to req list */
	META_AV *given_id;		/* radius ID, if given on req list */
	META_AV *given_auth;		/* radius authenticator, if given */
	META_AV *given_pap_pass;	/* pap password, if given */
	int acct;			/* set if accounting, 0 otherwise */

	META_AV *rephead, *reptail;	/* reply list, 0 if none received */
};


struct radreq {			/* Outstanding request */

	struct radquery *query;		/* query to which request belongs */
	struct radsrv *srv;		/* server this request as was for */
	int prev_slot;			/* To find old but different retrans-
					   mits to same server; -1 if none */

	unsigned id;			/* RADIUS ID, for information only */
	unsigned char auth[16];		/* orig. request authenticator */
	time_t time_sent;		/* time this request was last sent */

	unsigned char pkt[C_MAX_PKTSIZE];  /* cached request packet */
	META_ORD pktlen;		/* nonzero means req. slot in use */
};


struct radcfg {

	META *m;

	/* Cached information from dictionary */

	META_SPC *ds_ground, *ds_rad_pkt;
	META_ITEM *di_int, *di_rad_code, *di_rad_id, *di_rad_len, *di_rad_auth;
	META_ITEM *di_user_name, *di_user_pass, *di_chap_pass, *di_chap_chal; 
	META_ITEM *di_nas_ip, *di_acct_dly, *di_query_id, *di_tgt_srv;
	META_VAL *dv_auth_req, *dv_acct_req;
	META_ORD nas_ip_address;
	int max_idbits, max_ids, max_idmask;

	/* Command line options */

	char *raddb;
	struct sockaddr_in sin_src;
	int retrycount, timeout, doacct;
	int nostatus, ioflags, noencpap, noencchap; 
	int flood, outimm, quiet, debug;
	struct radsrv srv[MAX_SERVERS];	/* list of configured servers */
	int srvcnt;

	/* Dynamic per-client data */

	RING *r;			/* ring for stdin */
	META_AV *head, *tail;		/* list of pairs as we get them */
	struct radquery *qryhead, *qrytail;  /* list of unanswered queries */
	struct radreq req[MAX_OUTREQS];	/* ring of requests available by ID */
	int next_slot;			/* next slot to be used in that ring */

	int timer_reqs[MAX_OUTREQS];	/* Timer ring of reqs in ring above */
	int timer_r, timer_w;		/* In, out pointer in timer ring */

	int sfd;			/* socket's fd */
	fd_set rfds;			/* event mask */

	int retcode;			/* associated with last answer */
};


/*********************
 * UTILITY FUNCTIONS *
 *********************/


/* Allocate memory, abort immediately if we cannot */

void *safe_malloc(size_t size)
{
	void *p;

	if (!(p = malloc(size))) {
		perror("FATAL: Could not allocate memory");
		_exit(EXIT_INTERN);
	}
	return p;
}


/***************************************
 * INPUT FROM SOCKET, OUTPUT TO STDOUT *
 ***************************************/


/* Remove query from queue */

void query_del(struct radcfg *c, struct radquery *qry)
{
	int srvnr, req_slot;

	/* Free all possible request slots in use by this query */

	for(srvnr = 0; srvnr < qry->srvcnt; srvnr++) {
		for(req_slot = qry->srv[srvnr].req_slot;
		    req_slot != -1;
		    req_slot = c->req[req_slot].prev_slot) {
			if (c->req[req_slot].pktlen == 0) continue;
			c->req[req_slot].pktlen = 0;
			msg(F_RECV, L_NOTICE, "Done with slot %d, id %d\n",
			    req_slot, c->req[req_slot].id);
		}
	}

	/* Set previous query's next field to query after us, or set head */

	if (qry->prev) qry->prev->next = qry->next;
	else (c->qryhead) = qry->next;

	/* Set next query's previous field to query before us, or set tail */

	if (qry->next) qry->next->prev = qry->prev;
	else (c->qrytail) = qry->prev;

	/* Free query's data and query itself */

	if (qry->reqhead) meta_freeavlist(qry->reqhead);
	if (qry->rephead) meta_freeavlist(qry->rephead);
	free(qry);
}


/* Output response for a query and removes it from the query queue */

#define OUT_BUFLEN	32768

void query_reply(struct radcfg *c, struct radquery *qry, int status)
{
	static char buf[OUT_BUFLEN + 16];
	META_AV av;
	int n;

	msg(F_PROC, L_NOTICE, "Reply, status %d\n", status);
	c->retcode = status;
	n = 0;
	if (c->nostatus == 0) {
		memset(&av, 0, sizeof(av));
		av.i = c->di_int;
		av.l = status;
		n += meta_avtomsg(c->m, &av, buf + n, OUT_BUFLEN - n, c->ioflags | AVMSG_ONESHOT, 0, 0);
	}
	if (qry) {
		if (qry->rephead) n += meta_avtomsg(c->m, qry->rephead, buf + n, OUT_BUFLEN - n, c->ioflags, 0, 0);
		query_del(c, qry);
	}
	if (n >= OUT_BUFLEN) {
		msg(F_PROC, L_ERR, "ERROR: Response too big - discarding!\n");
		c->retcode = EXIT_INTERN;
		return;
	}
	if (n > 0 && write(1, buf, n) != n) {
		msg(F_PROC, L_ERR, "ERROR: Could not write %d bytes to stdout: %s\n", n, strerror(errno));
		c->retcode = EXIT_INTERN;
		return;
	}
}


/* Data available on socket */

void sock_handle_read(struct radcfg *c, time_t t)
{
	static char pkt[C_MAX_PKTSIZE], chkauth[16];
	struct sockaddr_in sin;
	META_AV *head, *tail, *av, *rad_auth;
	int n, got, rad_code, rad_id, rad_len;
	struct radreq *r;
	md5_state_t md5ctx;

	/* Receive packet */

	n = sizeof(sin);
	n = recvfrom(c->sfd, pkt, sizeof(pkt), 0, (struct sockaddr *)&sin, &n);
	if (n == -1) {
	    msg(F_RECV, L_ERR, "ERROR: Could not receive response: %s\n",
		strerror(errno));
	    return;
	}
	msg(F_RECV, L_NOTICE, "Received %d bytes from %s:%d\n",
	    n, inet_ntoa(sin.sin_addr), htons(sin.sin_port));
	if (msg_thresh[F_RECV] >= L_DEBUG) {
	    msg_line(L_DEBUG, "Hexdump:\n");
	    hexdump(pkt, n);
	}

	/* Decode */

	head = meta_decode(c->m, c->ds_ground, 0, pkt, n, &tail);
	if (!head) {
	    msg(F_RECV, L_ERR, "Dropping answer: Could not decode response!\n");
	    return;
	}
	if (msg_thresh[F_RECV] >= L_NOTICE) {
	    msg_line(L_NOTICE, "Full received /response/ A/V list:\n");
	    meta_printavlist(c->m, head, 0);
	}

	/* Gather data */

	rad_code = rad_id = rad_len = 0; rad_auth = 0;	/* 'dumb' compiler */
	got = 0;
	for(av = head; av; av = av->next) {
	    if (av->i == c->di_rad_code) rad_code = av->l, got |= A_RAD_CODE;
	    else if (av->i == c->di_rad_id) rad_id = av->l, got |= A_RAD_ID;
	    else if (av->i == c->di_rad_len) rad_len = av->l, got |= A_RAD_LEN;
	    else if (av->i == c->di_rad_auth) rad_auth = av, got |= A_RAD_AUTH;
	}
	if (got != (A_RAD_CODE | A_RAD_ID | A_RAD_LEN | A_RAD_AUTH)) {
	    msg(F_RECV, L_ERR, "Dropping answer: Missing response elements!\n");
	    return;
	}

	/* Check length */

	if (rad_len != n) {
	    msg(F_RECV, L_NOTICE, "Note: Length in RADIUS packet (%d) differs from that of UDP packet (%d).\n", rad_len, n);
	}

	/* Find the request this answer is for in the ring/hash */

	n = rad_id & MAX_OUTREQMASK;
	r = &c->req[n];
	if (r->pktlen <= 0) {
	    msg(F_RECV, L_ERR, "Dropping answer: slot %d for id %d is empty!\n",
		n, rad_id);
	    return;
	}
	if (rad_id != r->id) {
	    msg(F_RECV, L_ERR, "Dropping answer: ID %d was never used by me!\n",
		rad_id);
	    return;
	}

	/* Check the authenticator by substituting the original request 
	   authenticator for the response authenticator, appending the
	   secret and calculating md5, comparing it to the response 
	   authenticator. This is done in a bit funny way that avoids copying
	   but relies on decoded av->p pointers pointing inside the original
	   packet. 

	   (Having ID, Authenticator and Code at fixed places inside the 
	   packet instead of treating them like other pairs makes a bit more 
	   sense now I'm writing the radius client instead of the server. Oh
	   well, keeping the packet engine's flexibility is still worth it
	   IMHO). */

	md5_init(&md5ctx);
	md5_append(&md5ctx, pkt, rad_auth->p - pkt);
	md5_append(&md5ctx, r->auth, sizeof(r->auth));
	md5_append(&md5ctx, rad_auth->p + rad_auth->l,
			    rad_len - (rad_auth->p + rad_auth->l - pkt));
	md5_append(&md5ctx, r->srv->secret, r->srv->secretlen);
	md5_finish(&md5ctx, chkauth);

	if (rad_auth->l != sizeof(chkauth) ||
	    memcmp(rad_auth->p, chkauth, sizeof(chkauth))) {
	    msg(F_RECV, L_ERR, "Dropping answer: authenticator mismatch!\n");
	    if (msg_thresh[F_RECV] >= L_DEBUG) {
		msg_line(F_RECV, "Expected authenticator:\n");
		hexdump(chkauth, sizeof(chkauth));
		msg_line(F_RECV, "Received authenticator:\n");
		hexdump(rad_auth->p, rad_auth->l);
	    }
	    return;
	}
	msg(F_RECV, L_NOTICE, "Response, ID %d matches request in slot %d.\n",
	    rad_id, n);

	/* See if we want to check the code as well */

#ifdef SPECIAL_PRIMARY
	if (!c->outimm && !r->acct && rad_code == 3 &&
	    r->srv == &r->query->srv[r->query->cursrv] &&
	    --r->query->ttl > 0) {
	    meta_freeavlist(head); 
	    send_qry_req(c, t, r->queryqry);
	    return;
	}
#endif

	/* Attach response to query and reply to it */

	if (head) head->prev = r->query->reptail;
	if (r->query->reptail) r->query->reptail->next = head;
	else r->query->rephead = head;
	r->query->reptail = tail;
	query_reply(c, r->query, rad_code);
}


/**************************************
 * INPUT FROM STDIN, OUTPUT TO SOCKET *
 **************************************/


/* Create a new outstanding request in the ring. Returns ring slot nr. */

int new_req(struct radcfg *c, time_t t, struct radquery *qry,
	    struct radsrv *srv)
{
	struct radreq *r;
	META_AV *av, *head, *tail, *tree;
	META_ORD n;
	md5_state_t md5ctx;
	int ret;

	/* Take given slot or next slot in ring and check if free */

	if (qry->given_id) 
		ret = qry->given_id->l & MAX_OUTREQMASK;
	else {
		ret = c->next_slot;
		c->next_slot++; 
		c->next_slot &= MAX_OUTREQMASK;
	}
	if (c->req[ret].pktlen) {
		msg(F_SEND, L_ERR, "No free request slot - aborting query!\n");
		return -1;
	}

	/* Initialize slot */

	r = &c->req[ret];
	memset(r, 0, sizeof(struct radreq));
	r->query = qry;
	r->srv = srv;
	r->prev_slot = -1;

	/* Add missing per-request attributes that go before attrs from query */

	head = tail = 0;
	av = qry->given_id;
	if (qry->missing & A_RAD_ID) {
		av = safe_malloc(sizeof(META_AV)); 
		memset(av, 0, sizeof(META_AV));
		av->i = c->di_rad_id; 
		av->l = ((getpid() << MAX_OUTREQBITS) | ret) & c->max_idmask;
		meta_addav(&head, &tail, 0, 0, av);
	}						/* Add RADIUS ID */
	r->id = av->l;
	if (qry->missing & A_RAD_AUTH) {		/* (never when acct). */
		av = safe_malloc(sizeof(META_AV)); 
		memset(av, 0, sizeof(META_AV));
		av->i = c->di_rad_auth; 
		av->p = r->auth; av->l = sizeof(r->auth); 
		get_random_data(av->p, av->l);
		meta_addav(&head, &tail, 0, 0, av);
	}						/* Add authenticator */
	else if (qry->given_auth) {
		/* If we didn't need to generate a request authenticator before
		   encoding the packet (because we already have one or we're
		   doing accounting) *and* that is because we were given one in
		   the query, copy it to the request authenticator member to 
		   make sure the response check and encrypt_attr_pap (if done)
		   use the given one. */
		memcpy(r->auth, qry->given_auth->p, MIN(sizeof(r->auth), qry->given_auth->l)); 
	}

	/* Add query list */

	if (tail) tail->next = qry->reqhead; else head = qry->reqhead;
	qry->reqhead->prev = tail;
	tail = qry->reqtail;

	/* Add missing per-request attributes that go after list from query */

	if (qry->missing & A_USER_PASS) {
		av = safe_malloc(sizeof(META_AV));
		memset(av, 0, sizeof(META_AV));
		av->i = c->di_user_pass;
		av->p = safe_malloc((qry->given_pap_pass->l + 15) & ~15);
		encrypt_attr_pap(qry->given_pap_pass->p, qry->given_pap_pass->l,
				 av->p, &n, 
				 srv->secret, srv->secretlen,
				 r->auth, sizeof(r->auth));
		av->l = n;
		av->flags |= AV_FREE_P;
		meta_addav(&head, &tail, 0, 0, av);
	}
	if (qry->missing & A_ACCT_DLY) {
		av = safe_malloc(sizeof(META_AV)); 
		memset(av, 0, sizeof(META_AV));
		av->i = c->di_acct_dly; av->l = t - qry->timestamp;
		meta_addav(&head, &tail, 0, 0, av);
	}

	/* Show request list */

	if (msg_thresh[F_SEND] >= L_NOTICE) {
		msg(F_SEND, L_NOTICE, "Built new %s request for %s:%d in slot %d, id %d:\n", qry->acct ? "accounting" : "authentication", inet_ntoa(srv->sin.sin_addr), ntohs(srv->sin.sin_port),  ret, r->id);
		msg_line(L_NOTICE, "Full /request/ A/V list:\n");
		meta_printavlist(c->m, head, 0);
	}

	/* Build encapsulation tree from list and a packet from that */

	meta_buildtree(head, &tree, c->ds_ground);
	r->pktlen = meta_encode(c->ds_ground, r->pkt, C_MAX_PKTSIZE, tree, 0);

	/* Free temporary tree and list parts */

	meta_freeavtree(tree); tree = 0;	/* Free tree */
	if (qry->reqhead->prev) {
		qry->reqhead->prev->next = 0;
		meta_freeavlist(head); head = 0;	
	}					/* Free list before qry part */
	if (qry->reqtail->next) {
		meta_freeavlist(qry->reqtail->next);
		qry->reqtail->next = 0;
	}					/* Free list after qry part */

	/* Skip if encoding failed for whatever reason */

	if (r->pktlen == -1) {
		msg(F_SEND, L_ERR, "ERROR: Could not build packet!\n");
		r->pktlen = 0;
		return -1;
	}

	/* If we're doing accounting and we didn't already have a signature, 
	   sign the packet now and remember the signature in r->auth so that we
	   can check the response. */

	if (qry->acct && !qry->given_auth) {
		md5_init(&md5ctx);
		md5_append(&md5ctx, r->pkt, r->pktlen);
		md5_append(&md5ctx, srv->secret, srv->secretlen);
		md5_finish(&md5ctx, r->auth);
		memcpy(r->pkt + c->di_rad_auth->val_ofs, r->auth, 
		       c->di_rad_auth->val_size);
	}

	return ret;
}


/* Send a new request or resend a cached request for a certain query */

void send_qry_req(struct radcfg *c, time_t t, struct radquery *qry)
{
	struct radsrv *oldsrv, *srv;
	struct radreq *req;
	int slot;

	/* Keep the last server we sent to in oldsrv, or if we haven't ever 
	   sent a packet for this query, set oldsrv to zero. If we don't use
	   special primary servers, select a random server as the current,
	   otherwise, keep the current server index at its initialised value,
	   -1. Then, we take the server after this (ie. 0, random + 1, or
	   simply the next), wrap around the list, and set srv accordingly. */

	oldsrv = 0;
	if (qry->cursrv != -1) oldsrv = &qry->srv[qry->cursrv];
#ifndef RADIUS_SPECIAL_PRIMARY
	else qry->cursrv = (int)((double)qry->srvcnt * rand() / (RAND_MAX+1.0));
#endif
	qry->cursrv++;	
	if (qry->cursrv >= qry->srvcnt) qry->cursrv = 0;
	srv = &qry->srv[qry->cursrv];

	/* There are three cases in which we don't need to build a new request
	   and occupy an extra request slot, but can reuse an already prepared
	   packet:
	   1. we're accounting and no second has expired between the last
	      transmission to the current or previous server, causing
	      Acct-Delay-Time to be the same;
	   2. we're authenticating and we already have a packet prepared
	      for the current server;
	   3. we're authenticating and we have a packet for the previous
	      server, and either we use a pre-encoded User-Password, or
	      we're not doing PAP at all, or the shared secret is the same. */

	slot = -1;
	if (qry->acct) {
		if (srv->req_slot != -1 && 
		    c->req[srv->req_slot].pktlen &&
		    c->req[srv->req_slot].time_sent == t) {
			slot = srv->req_slot;
		}
		else if (oldsrv && oldsrv->req_slot != -1 && 
		    c->req[oldsrv->req_slot].pktlen &&
		    c->req[oldsrv->req_slot].time_sent == t) {
			slot = oldsrv->req_slot;
		}
	}
	else {
		if (srv->req_slot != -1 && c->req[srv->req_slot].pktlen) {
			slot = srv->req_slot;
		}
		else if (oldsrv && oldsrv->req_slot != -1 &&
		         c->req[oldsrv->req_slot].pktlen &&
			 (c->noencpap || qry->given_pap_pass == 0 ||
			  (oldsrv->secretlen == srv->secretlen &&
			   memcmp(oldsrv->secret, srv->secret, srv->secretlen) == 0))) {
			slot = oldsrv->req_slot;
		}
	}
	if (slot == -1) {
		slot = new_req(c, t, qry, srv);
		if (slot == -1) { query_reply(c, qry, EXIT_INTERN); return; }
	}
	req = &c->req[slot];

	/* If the last request to the current server was in a different slot,
	   link to it from the currently selected slot. Also, set the last
	   request to the current server to the current slot. */

	if (srv->req_slot != slot) req->prev_slot = srv->req_slot;
	srv->req_slot = slot;

	/* Update request timestamp and put at front of timer ring. */

	req->time_sent = t;
	if (tmr_ring_maxput(c) < 1) { 
		msg(F_SEND, L_ERR, "Timer ring full - aborting query!\n");
		query_reply(c, qry, EXIT_INTERN);
		return;
	}
	c->timer_reqs[c->timer_w++] = slot;
	c->timer_w &= MAX_OUTREQMASK;

	/* Send request */

	msg(F_SEND, L_NOTICE, "Sending request in slot %d, id %d, ttl %d, %d bytes to %s:%d\n", slot, req->id, qry->ttl, req->pktlen, inet_ntoa(srv->sin.sin_addr), htons(srv->sin.sin_port));
	if (msg_thresh[F_SEND] >= L_DEBUG) {
		msg_line(L_DEBUG, "Hexdump:\n");
		hexdump(req->pkt, req->pktlen);
	}

	if (sendto(c->sfd, req->pkt, req->pktlen, 0, (struct sockaddr *)&srv->sin, sizeof(struct sockaddr)) == -1) {
		msg(F_SEND, L_ERR, "ERROR: Could not send: %s\n", 
		    strerror(errno));
	}

	return;
}


/* Fix up a query by finding out which attributes are missing and adding the
   attributes that can be added on a per-query basis - i.e. the ones that 
   stay the same across retransmits, possibly to different servers. Returns -1 
   if something is missing for which we cannot make up a value */

int fixup_query(struct radcfg *c, struct radquery *q)
{
	META_AV *av, *nextav, *chap_chal, *chap_pass;
	md5_state_t md5ctx;
	struct radsrv *srv;
	char chap_id, *s;
	int l, n, port;
	U_INT32_T host;

	/* See which attributes we have, i.e. that don't need to be added.
	   Also, save some pointers to pairs we want to reference later. 
	   Further, move all Radclient-Query-Id attributes to the reply list. */

	chap_pass = chap_chal = 0;
	q->missing = A_ALL;
	q->acct = c->doacct;
	for(av = q->reqhead; av; av = nextav) {
		nextav = av->next;
		if (av->i == c->di_rad_code)	   q->missing &= ~ A_RAD_CODE,
			q->acct = (av->l == c->dv_acct_req->nr);
		else if (av->i == c->di_rad_id)    q->missing &= ~ A_RAD_ID,
			q->given_id = av;
		else if (av->i == c->di_rad_auth)  q->missing &= ~ A_RAD_AUTH,
			q->given_auth = av;
		else if (av->i == c->di_nas_ip)    q->missing &= ~ A_NAS_IP;
		else if (av->i == c->di_user_name) q->missing &= ~ A_USER_NAME;
		else if (av->i == c->di_user_pass) q->missing &= ~(A_USER_PASS |
								   A_CHAP_PASS |
								   A_CHAP_CHAL),
			q->given_pap_pass = av;
		else if (av->i == c->di_chap_pass) q->missing &= ~(A_CHAP_PASS |
								   A_USER_PASS),
			chap_pass = av;
		else if (av->i == c->di_chap_chal) q->missing &= ~(A_CHAP_CHAL |
								   A_USER_PASS),
			chap_chal = av;
		else if (av->i == c->di_acct_dly)  q->missing &= ~ A_ACCT_DLY;
		else if (av->i == c->di_query_id) {
			meta_remav(&q->reqhead, &q->reqtail, av);
			meta_addav(&q->rephead, &q->reptail, 0, 0, av);
		}
		else if (av->i == c->di_tgt_srv && q->srvcnt < MAX_SERVERS) { 
			srv = &q->srv[q->srvcnt];

		 	srv->req_slot = -1;
			srv->sin.sin_family = AF_INET;
			srv->sin.sin_port = htons(q->acct ? DEF_ACCTPORT 
							  : DEF_AUTHPORT);
			if ((s = memchr(av->p, '/', av->l)) == 0 || 
			    (l = av->p + av->l - s) <= 1) { msg(F_PROC, L_ERR, "Warning: ignoring malformed Target-Server attribute (no secret)\n"); continue; }
			srv->secret = s + 1; 
			srv->secretlen = l - 1;
			av->l = (s - av->p);
			if ((s = memchr(av->p, ':', av->l))) {
				l = av->p + av->l - s - 1;
				port = meta_atoord(s + 1, l, 0, 0, &n, 10);
				if (l == 0 || l > 5 || n != l || port < 1 || port > 65535) { msg(F_PROC, L_ERR, "Warning: ignoring malformed Target-Server attribute (invalid port)\n"); continue; }
				srv->sin.sin_port = htons(port);
				av->l = (s - av->p);
			}
			host = meta_atoip(av->p, av->l, 0, 0, &n);
			if (n != av->l || host == 0 || host == 0xffffffff) { msg(F_PROC, L_ERR, "Warning: ignoring malformed Target-Server attribute (invalid IP address)\n"); continue; }
			srv->sin.sin_addr.s_addr = htonl(host);

			q->srvcnt++;
			q->missing &= ~ A_TGT_SRV;
		}
	}

	/* Fix up missing RAD-Code (and accounting flag) */

	if (q->missing & A_RAD_CODE) {
		av = safe_malloc(sizeof(META_AV));
		memset(av, 0, sizeof(META_AV));
		av->i = c->di_rad_code; 
		av->l = q->acct ? c->dv_acct_req->nr : c->dv_auth_req->nr;
		meta_addav(&q->reqhead, &q->reqtail, 0, 1, av);
	}

	/* Now that we know for sure wether we're accounting or not, we may 
	   dismiss missing attributes that don't apply for acct/auth, resp. 
           Note: we don't need to generate an authenticator for accounting. */

	if (q->acct) q->missing &= ~(A_USER_PASS | A_CHAP_PASS | 
				     A_CHAP_CHAL | A_RAD_AUTH);
	else q->missing &= ~ A_ACCT_DLY;

	/* If we don't have a CHAP-Password, we don't want a CHAP-Challenge */

	if (q->missing & A_CHAP_PASS) q->missing &= ~ A_CHAP_CHAL;

	/* Warn about missing attributes that are needed according to the RFC,
	   but that we cannot invent a value for */

	if (q->missing & A_USER_NAME) { 
		q->missing &= ~ A_USER_NAME; 
		msg(F_PROC, L_ERR, "Warning: No User-Name - requests won't be RFC2865-compliant\n"); 
	}
	if ((q->missing & (A_USER_PASS | A_CHAP_PASS)) == 
			  (A_USER_PASS | A_CHAP_PASS)) { 
		q->missing &= ~(A_USER_PASS | A_CHAP_PASS | A_CHAP_CHAL);
		msg(F_PROC, L_ERR, "Warning: No PAP or CHAP password - requests won't be RFC2865-compliant\n"); 
	}

	/* Add the other attributes we can add here, i.e. the ones that don't 
	   vary per request */

	if (q->missing & A_NAS_IP) {
		av = safe_malloc(sizeof(META_AV));
		memset(av, 0, sizeof(META_AV));
		av->i = c->di_nas_ip; 
		av->l = c->nas_ip_address;
		meta_addav(&q->reqhead, &q->reqtail, 0, 0, av);
	}
	if (q->missing & A_CHAP_CHAL && c->noencchap == 0) {
		av = safe_malloc(sizeof(META_AV));
		memset(av, 0, sizeof(META_AV));
		av->i = c->di_chap_chal;
		av->p = safe_malloc(av->l = 16); av->flags |= AV_FREE_P;
		get_random_data(av->p, av->l);
		meta_addav(&q->reqhead, &q->reqtail, 0, 0, av);
		chap_chal = av;
	}
	if (q->missing & A_TGT_SRV) {
		if (c->srvcnt == 0) { msg(F_PROC, L_ERR, "ERROR: No target servers specified!\n"); return -1; }
		q->srvcnt = c->srvcnt;	/* Use servers from cmd line */
		memcpy(q->srv, c->srv, sizeof(q->srv));
	}

	/* Encode the CHAP password here if so requested */

	if (c->noencchap == 0 && chap_pass && chap_chal) {
		get_random_data(&chap_id, 1);
		md5_init(&md5ctx);
		md5_append(&md5ctx, &chap_id, 1);
		md5_append(&md5ctx, chap_pass->p, chap_pass->l);
		md5_append(&md5ctx, chap_chal->p, chap_chal->l);
		if (chap_pass->l < 17) {
			meta_freeavdata(chap_pass);
			chap_pass->p = safe_malloc(17);
			chap_pass->flags |= AV_FREE_P;
		}
		chap_pass->l = 17;
		chap_pass->p[0] = chap_id;
		md5_finish(&md5ctx, chap_pass->p + 1);
	}

	/* If we have a User-Password and noencpap == 0, tell new_request that
	   we're missing an encoded User-Password and take the cleartext 
	   version off the list, remembering it only in the query structure. */

	if (c->noencpap == 0 && q->given_pap_pass) {
		q->missing |= A_USER_PASS;
		meta_remav(&q->reqhead, &q->reqtail, q->given_pap_pass);
	}

	return 0;
}


/* Create new query and add to head of query list */

struct radquery *add_new_query(struct radcfg *c, time_t t,
			       META_AV *reqhead, META_AV *reqtail)
{
	struct radquery *ret;

	/* Allocate and initialize new query */

	ret = safe_malloc(sizeof(struct radquery));
	memset(ret, 0, sizeof(struct radquery));

	ret->reqhead = reqhead;
	ret->reqtail = reqtail;
	ret->timestamp = t;
	ret->ttl = c->retrycount;
	ret->cursrv = -1;		/* No current server yet */

	/* Fixup query, reply if we cannot */

	if (fixup_query(c, ret) == -1) {
		query_reply(c, ret, EXIT_INCOMP);
		return 0;
	}

	/* Show full query and add to list */

	if (msg_thresh[F_RECV] >= L_NOTICE) {
		msg_line(L_NOTICE, "Full /query/ A/V list:\n");
		meta_printavlist(c->m, ret->reqhead, 0);
	}

	ret->prev = c->qrytail;
	c->qrytail = ret;
	if (ret->prev) ret->prev->next = ret;
	else c->qryhead = ret;

	return ret;
}


/* Data available on stdin */

void stdin_handle_read(struct radcfg *c, time_t t)
{
    static char line[C_MAX_MSGSIZE + 16];
    struct radquery *qry;
    ssize_t len;

    if (!ring_maxput(c->r)) {
	msg(F_PROC, L_ERR, "ERROR: Input message doesn't fit!\n");
	_exit(EXIT_STDIN);	
    }

    /* Get as much data as we can into the ring */

    ring_read(c->r, 0, &len); 

    /* If EOF on stdin, clear stdin from the fd set and if we were busy
       gathering a list for a query, send it off now. */
     
    if (len == 0) {
	FD_CLR(0, &c->rfds);
	if (c->head) { qry = add_new_query(c, t, c->head, c->tail);
		       if (qry) send_qry_req(c, t, qry); }
	c->head = c->tail = 0;
	return;
    }

    for(;;) {

	/* Process message lines as long as we see data before a real LF or we
	   see data and an EOF */

	while((len = ring_strcspn(c->r, "\n", 1)) > 0 &&
	      len < ring_maxget(c->r)) {

	    ring_get(c->r, line, len); ring_discard(c->r, 1);
	    if (meta_ascmsgtoav(c->m, line, len, &c->head, &c->tail, 
				c->ioflags, 0) != -1) continue;
	    msg(F_PROC, L_ERR, "ERROR: Invalid input line!\n");
	    _exit(EXIT_STDIN);
	}

	/* If we have an incomplete line (0 or more bytes), return till we have
	   received some more data (Mooore Innnput!) */

	if (len == ring_maxget(c->r)) return;

	ring_discard(c->r, 1);
	if (c->head) { qry = add_new_query(c, t, c->head, c->tail);
		       if (qry) send_qry_req(c, t, qry); }
	c->head = c->tail = 0;
    }
}


/*
 * MAIN
 */


void usage(char *msg)
{
	if (msg) fprintf(stderr, "\nERROR: %s\n\n", msg);
	fprintf(stderr, 
		"Usage: radclient [-r raddb] [-i sourceip] [-c retrycount]\n"
		"                 [-t timeout] [-anlpCofqv]\n"
		"                 server[:port] secret [server[:port] secret]...\n"
		"Options:\n\n"
		"  -a	default to Accounting if no RAD-Code given\n"
		"  -n	suppress result code attribute in output\n"
		"  -l	use long attribute names in output\n"
		"  -p	send given User-Password as-is; must be pre-encoded\n"
		"  -C	send given CHAP-Password and CHAP-Challenge as-is\n"
		"  -o	output response immediately, don't retry till valid\n"
/*		"  -f	flood, send retrycount packets as fast as possible\n" */
		"  -q	quiet, no output on stderr\n"
		"  -v	increase verbosity (from ERR to NOTICE to DEBUG)\n"
		"  -h	this help\n");
	_exit(EXIT_USAGE);
}


void parseoptions(int argc, char **argv, struct radcfg *c)
{
	struct radsrv *srv;
	struct hostent *he;
	int o, fac, lvl;
	char *s;

	/* Non-zero defaults for options */

	c->raddb = RADDB; 
	c->sin_src.sin_family = AF_INET;
	c->retrycount = DEF_RETRYCOUNT; 
	c->timeout = DEF_TIMEOUT;
	c->ioflags = AVMSG_ASCII | AVMSG_ADDTAB | AVMSG_ADDSPACES | 
		     AVMSG_NAMEDCONST | AVMSG_SHORTATTR;

	lvl = L_ERR;
	while((o = getopt(argc, argv, "r:i:c:t:anlpCofqvd:D:h")) != -1) {

		switch(o) {
		  case 'r': c->raddb = optarg; break;
		  case 'i':
			c->sin_src.sin_addr.s_addr = inet_addr(optarg);
			if (c->sin_src.sin_addr.s_addr == 0xffffffff) {
				usage("Invalid address!");
			}
			break;
		  case 'c':
			c->retrycount = strtoul(optarg, &s, 10);
			if (!s || *s) usage("Invalid retry count!");
			break;
		  case 't':
			c->timeout = strtoul(optarg, &s, 10);
			if (!s || *s) usage("Invalid timeout value!");
			break;
		  case 'a': c->doacct = 1; break;
		  case 'n': c->nostatus = 1; break;
		  case 'l': c->ioflags &= ~AVMSG_SHORTATTR; break;
		  case 'p': c->noencpap = 1; break;
		  case 'C': c->noencchap = 1; break;
		  case 'o': c->outimm = 1; break;
/*		  case 'f': c->flood = 1; break;		*/
		  case 'q': c->quiet = 1; 
			    break;
		  case 'v': if (lvl == L_ERR) { lvl = L_NOTICE; break; }
			    lvl = L_DEBUG;
			    break;
		  case 'h':
		  case '?':
			usage(0);
		}
	}
	for(fac = 0; fac < F_CNT; fac++) msg_setthresh(fac, lvl);

	while(optind < argc && c->srvcnt < MAX_SERVERS) {

		srv = &c->srv[c->srvcnt++];

		srv->req_slot = -1;
		srv->sin.sin_family = AF_INET;
		srv->sin.sin_port = htons(c->doacct ? DEF_ACCTPORT : 
						      DEF_AUTHPORT);
		if ((s = strchr(argv[optind], ':')) && s[1]) {
			*s = 0; 
			srv->sin.sin_port = htons(strtoul(s + 1, &s, 10));
			if (!s || *s) usage("Invalid port!");
		}

		srv->sin.sin_addr.s_addr = inet_addr(argv[optind]);
		if (srv->sin.sin_addr.s_addr == 0xffffffff) {
			he = gethostbyname(argv[optind]);
			if (!he || he->h_addrtype != AF_INET || he->h_length != 4) { fprintf(stderr, "ERROR: Could not resolve %s!\n", argv[optind]); exit(10); }
			srv->sin.sin_addr.s_addr = *(U_INT32_T *)(he->h_addr);
		}

		if (++optind >= argc || (srv->secret = argv[optind++]) == 0 || 
		    *srv->secret == 0) usage("Missing secret!");
		srv->secretlen = strlen(srv->secret);
	}

	msg_init(-1, c->quiet);
	msg_settag("C", 1);
}


void opendict(struct radcfg *c)
{
	c->m = meta_newfromdict(c->raddb);
	if (!c->m) { 
		msg(F_MISC, L_ERR,"ERROR: Couldn't open dictionary!\n"); 
		_exit(EXIT_DICT); 
	}

	/* We cache some dictionary items for speed and convenience */

	if (!(c->ds_ground    = meta_getspcbynr(c->m, 0)) ||
	    !(c->ds_rad_pkt   = meta_getspcbyname(c->m, "RAD-PKT")) ||
	    !(c->di_int       = meta_getitembyname(c->m, 0, "int", 
						   META_VND_ANY)) ||
	    !(c->di_rad_code  = meta_getitembyname(c->m, c->ds_rad_pkt,
						   "RAD-Code", 
						   META_VND_ANY)) ||
	    !(c->di_rad_id    = meta_getitembyname(c->m, c->ds_rad_pkt,
						   "RAD-Identifier", 
						   META_VND_ANY)) ||
	    !(c->di_rad_len   = meta_getitembyname(c->m, c->ds_rad_pkt,
						   "RAD-Length", 
						   META_VND_ANY)) ||
	    !(c->di_rad_auth  = meta_getitembyname(c->m, c->ds_rad_pkt,
						   "RAD-Authenticator", 
						   META_VND_ANY)) ||
	    !(c->di_user_name = meta_getitembyname(c->m, 0, 
			    			   "User-Name", 
						   META_VND_ANY)) ||
	    !(c->di_user_pass = meta_getitembyname(c->m, 0, 
			    			   "User-Password", 
						   META_VND_ANY)) ||
	    !(c->di_chap_pass = meta_getitembyname(c->m, 0, 
			    			   "CHAP-Password", 
						   META_VND_ANY)) ||
	    !(c->di_chap_chal = meta_getitembyname(c->m, 0, 
			    			   "CHAP-Challenge", 
						   META_VND_ANY)) ||
	    !(c->di_nas_ip    = meta_getitembyname(c->m, 0, 
			    			   "NAS-IP-Address", 
						   META_VND_ANY)) ||
	    !(c->di_acct_dly  = meta_getitembyname(c->m, 0, 
			    			   "Acct-Delay-Time", 
						   META_VND_ANY)) ||
	    !(c->di_query_id  = meta_getitembyname(c->m, 0, 
			    			   "Radclient-Query-Id", 
						   META_VND_ANY)) ||
	    !(c->di_tgt_srv   = meta_getitembyname(c->m, 0, 
			    			   "Target-Server", 
						   META_VND_ANY)) ||
	    !(c->dv_auth_req  = meta_getvalbyname(c->m, c->di_rad_code,
			    			  "Access-Request")) ||
	    !(c->dv_acct_req  = meta_getvalbyname(c->m, c->di_rad_code,
			    			  "Accounting-Request"))) {
		msg(F_MISC, L_ERR, "ERROR: Incomplete dictionary!\n"); 
		_exit(EXIT_DICT);
	}
	c->max_idbits = c->di_rad_id->val_size << 3;
	c->max_ids = 1 << c->max_idbits;
	c->max_idmask = c->max_ids - 1;
}


void getnasip(struct radcfg *c)
{
	struct utsname uts;
	struct hostent *he;

	if (uname(&uts) != -1 &&
	    uts.nodename[0] &&
	    (he = gethostbyname(uts.nodename))) {
		c->nas_ip_address = ntohl(*(U_INT32_T *)(he->h_addr));
		msg(F_MISC, L_DEBUG, "- using %s as default NAS-IP-Address\n",
		    inet_ntoa(*(struct in_addr *)(he->h_addr)));
		return;
	}

	msg(F_MISC, L_ERR, "Warning: could not determine own IP address!\n");
}


void opensocket(struct radcfg *c)
{
	c->sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (c->sfd == -1) { 
		msg(F_SEND, L_ERR, "ERROR: Could not create socket: %s!\n", 
		    strerror(errno)); 
		_exit(EXIT_SOCK); 
	}

	if (c->sin_src.sin_addr.s_addr && 
	    bind(c->sfd, (struct sockaddr *)&c->sin_src, sizeof(c->sin_src)) == -1) {
		msg(F_SEND, L_ERR, "ERROR: Could not bind to %s: %s!\n", 
		    inet_ntoa(c->sin_src.sin_addr), strerror(errno)); 
		_exit(EXIT_SOCK); 
	}

	if (fcntl(c->sfd, F_SETFL, O_NONBLOCK) == -1) {
		msg(F_MISC, L_ERR, "ERROR: Could not make socket nonblocking: %s!\n");
		_exit(EXIT_INTERN);
	}
}


int main(int argc, char **argv)
{
	static struct radcfg radcfg;
	struct radcfg *c = &radcfg;
	struct radquery *qry;
	struct radreq *req;
	struct timeval tv, *tvp;
	fd_set rfds; 
	int n;
	time_t firsttimer, t;

	/*
	 * Initialise
	 */

	memset(c, 0, sizeof(struct radcfg));
	parseoptions(argc, argv, c);
	getnasip(c);
	opendict(c);
	opensocket(c);

	if (fcntl(0, F_SETFL, O_NONBLOCK) == -1) {
		msg(F_MISC, L_ERR, "ERROR: Could not make stdin nonblocking: %s!\n");
		_exit(EXIT_INTERN);
	}

	c->r = ring_new(C_MAX_MSGSIZE);
	if (!c->r) { 
		msg(F_MISC, L_ERR, "ERROR: Could not create input ring!\n");
		_exit(EXIT_INTERN);
	}

	if (!c->quiet && !getenv("RADIUSINTERFACEVERSION")) { 
		fprintf(stderr, "Enter A/V pairs, one per line, terminated by an empty line or ^D.\n"); 
		fflush(stderr); 
	}

	/*
	 * Start the loop
	 */

	time(&t); srand((unsigned)(tv.tv_sec ^ tv.tv_usec));
	FD_ZERO(&c->rfds); FD_SET(0, &c->rfds); FD_SET(c->sfd, &c->rfds);
	firsttimer = 0;
	while(FD_ISSET(0, &c->rfds) || tmr_ring_maxget(c)) {

		rfds = c->rfds; tvp = 0;
		if (firsttimer) {
			tv.tv_sec = firsttimer - t; tv.tv_usec = 0;
			if (tv.tv_sec <= 0) tv.tv_sec = 0, tv.tv_usec = 1;
			tvp = &tv;
		}
		n = 1; 
		if (FD_ISSET(c->sfd, &rfds)) n = c->sfd + 1;

		n = select(n, &rfds, 0, 0, tvp);
		if (n == -1) {
			if (errno == EINTR) continue;
			msg(F_MISC, L_ERR, "ERROR: Select: %s!\n", 
			    strerror(errno)); 
			_exit(EXIT_INTERN);
		}
		time(&t);

		/* Handle I/O readyness */
		
		if (n) {
			if (FD_ISSET(0, &rfds)) stdin_handle_read(c, t);
			if (FD_ISSET(c->sfd, &rfds)) sock_handle_read(c, t);
		}

		/* Handle expired requests in timer ring */

		while (tmr_ring_maxget(c) &&
		       (!(req = &c->req[c->timer_reqs[c->timer_r]])->pktlen ||
		       t >= (firsttimer = req->time_sent + c->timeout))) {

			c->timer_r++; c->timer_r &= MAX_OUTREQMASK;
			if (!req->pktlen) continue; /* Loop if already freed */

			qry = req->query;
			if (--qry->ttl > 0) send_qry_req(c, t, qry);
			else query_reply(c, qry, EXIT_TIMEOUT);
		}
	}

	close(c->sfd);
	ring_del(c->r);
	meta_del(c->m);

	return c->retcode;
}


/*
 * vim:ts=8:softtabstop=4:shiftwidth=4:noexpandtab
 */



syntax highlighted by Code2HTML, v. 0.9.1