radlib.c revision 52709
1/*-
2 * Copyright 1998 Juniper Networks, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 *	$FreeBSD: head/lib/libradius/radlib.c 52709 1999-10-31 04:47:59Z jdp $
27 */
28
29#include <sys/types.h>
30#include <sys/socket.h>
31#include <sys/time.h>
32#include <netinet/in.h>
33#include <arpa/inet.h>
34
35#include <errno.h>
36#include <md5.h>
37#include <netdb.h>
38#include <stdarg.h>
39#include <stddef.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <unistd.h>
44
45#include "radlib_private.h"
46
47static void	 clear_password(struct rad_handle *);
48static void	 generr(struct rad_handle *, const char *, ...)
49		    __printflike(2, 3);
50static void	 insert_scrambled_password(struct rad_handle *, int);
51static void	 insert_request_authenticator(struct rad_handle *, int);
52static int	 is_valid_response(struct rad_handle *, int,
53		    const struct sockaddr_in *);
54static int	 put_password_attr(struct rad_handle *, int,
55		    const void *, size_t);
56static int	 put_raw_attr(struct rad_handle *, int,
57		    const void *, size_t);
58static int	 split(char *, char *[], int, char *, size_t);
59
60static void
61clear_password(struct rad_handle *h)
62{
63	if (h->pass_len != 0) {
64		memset(h->pass, 0, h->pass_len);
65		h->pass_len = 0;
66		h->pass_pos = 0;
67	}
68}
69
70static void
71generr(struct rad_handle *h, const char *format, ...)
72{
73	va_list		 ap;
74
75	va_start(ap, format);
76	vsnprintf(h->errmsg, ERRSIZE, format, ap);
77	va_end(ap);
78}
79
80static void
81insert_scrambled_password(struct rad_handle *h, int srv)
82{
83	MD5_CTX ctx;
84	unsigned char md5[16];
85	const struct rad_server *srvp;
86	int padded_len;
87	int pos;
88
89	srvp = &h->servers[srv];
90	padded_len = h->pass_len == 0 ? 16 : (h->pass_len+15) & ~0xf;
91
92	memcpy(md5, &h->request[POS_AUTH], LEN_AUTH);
93	for (pos = 0;  pos < padded_len;  pos += 16) {
94		int i;
95
96		/* Calculate the new scrambler */
97		MD5Init(&ctx);
98		MD5Update(&ctx, srvp->secret, strlen(srvp->secret));
99		MD5Update(&ctx, md5, 16);
100		MD5Final(md5, &ctx);
101
102		/*
103		 * Mix in the current chunk of the password, and copy
104		 * the result into the right place in the request.  Also
105		 * modify the scrambler in place, since we will use this
106		 * in calculating the scrambler for next time.
107		 */
108		for (i = 0;  i < 16;  i++)
109			h->request[h->pass_pos + pos + i] =
110			    md5[i] ^= h->pass[pos + i];
111	}
112}
113
114static void
115insert_request_authenticator(struct rad_handle *h, int srv)
116{
117	MD5_CTX ctx;
118	const struct rad_server *srvp;
119
120	srvp = &h->servers[srv];
121
122	/* Create the request authenticator */
123	MD5Init(&ctx);
124	MD5Update(&ctx, &h->request[POS_CODE], POS_AUTH - POS_CODE);
125	MD5Update(&ctx, memset(&h->request[POS_AUTH], 0, LEN_AUTH), LEN_AUTH);
126	MD5Update(&ctx, &h->request[POS_ATTRS], h->req_len - POS_ATTRS);
127	MD5Update(&ctx, srvp->secret, strlen(srvp->secret));
128	MD5Final(&h->request[POS_AUTH], &ctx);
129}
130
131/*
132 * Return true if the current response is valid for a request to the
133 * specified server.
134 */
135static int
136is_valid_response(struct rad_handle *h, int srv,
137    const struct sockaddr_in *from)
138{
139	MD5_CTX ctx;
140	unsigned char md5[16];
141	const struct rad_server *srvp;
142	int len;
143
144	srvp = &h->servers[srv];
145
146	/* Check the source address */
147	if (from->sin_family != srvp->addr.sin_family ||
148	    from->sin_addr.s_addr != srvp->addr.sin_addr.s_addr ||
149	    from->sin_port != srvp->addr.sin_port)
150		return 0;
151
152	/* Check the message length */
153	if (h->resp_len < POS_ATTRS)
154		return 0;
155	len = h->response[POS_LENGTH] << 8 | h->response[POS_LENGTH+1];
156	if (len > h->resp_len)
157		return 0;
158
159	/* Check the response authenticator */
160	MD5Init(&ctx);
161	MD5Update(&ctx, &h->response[POS_CODE], POS_AUTH - POS_CODE);
162	MD5Update(&ctx, &h->request[POS_AUTH], LEN_AUTH);
163	MD5Update(&ctx, &h->response[POS_ATTRS], len - POS_ATTRS);
164	MD5Update(&ctx, srvp->secret, strlen(srvp->secret));
165	MD5Final(md5, &ctx);
166	if (memcmp(&h->response[POS_AUTH], md5, sizeof md5) != 0)
167		return 0;
168
169	return 1;
170}
171
172static int
173put_password_attr(struct rad_handle *h, int type, const void *value, size_t len)
174{
175	int padded_len;
176	int pad_len;
177
178	if (h->pass_pos != 0) {
179		generr(h, "Multiple User-Password attributes specified");
180		return -1;
181	}
182	if (len > PASSSIZE)
183		len = PASSSIZE;
184	padded_len = len == 0 ? 16 : (len+15) & ~0xf;
185	pad_len = padded_len - len;
186
187	/*
188	 * Put in a place-holder attribute containing all zeros, and
189	 * remember where it is so we can fill it in later.
190	 */
191	clear_password(h);
192	put_raw_attr(h, type, h->pass, padded_len);
193	h->pass_pos = h->req_len - padded_len;
194
195	/* Save the cleartext password, padded as necessary */
196	memcpy(h->pass, value, len);
197	h->pass_len = len;
198	memset(h->pass + len, 0, pad_len);
199	return 0;
200}
201
202static int
203put_raw_attr(struct rad_handle *h, int type, const void *value, size_t len)
204{
205	if (len > 253) {
206		generr(h, "Attribute too long");
207		return -1;
208	}
209	if (h->req_len + 2 + len > MSGSIZE) {
210		generr(h, "Maximum message length exceeded");
211		return -1;
212	}
213	h->request[h->req_len++] = type;
214	h->request[h->req_len++] = len + 2;
215	memcpy(&h->request[h->req_len], value, len);
216	h->req_len += len;
217	return 0;
218}
219
220int
221rad_add_server(struct rad_handle *h, const char *host, int port,
222    const char *secret, int timeout, int tries)
223{
224	struct rad_server *srvp;
225
226	if (h->num_servers >= MAXSERVERS) {
227		generr(h, "Too many RADIUS servers specified");
228		return -1;
229	}
230	srvp = &h->servers[h->num_servers];
231
232	memset(&srvp->addr, 0, sizeof srvp->addr);
233	srvp->addr.sin_len = sizeof srvp->addr;
234	srvp->addr.sin_family = AF_INET;
235	if (!inet_aton(host, &srvp->addr.sin_addr)) {
236		struct hostent *hent;
237
238		if ((hent = gethostbyname(host)) == NULL) {
239			generr(h, "%s: host not found", host);
240			return -1;
241		}
242		memcpy(&srvp->addr.sin_addr, hent->h_addr,
243		    sizeof srvp->addr.sin_addr);
244	}
245	if (port != 0)
246		srvp->addr.sin_port = htons(port);
247	else {
248		struct servent *sent;
249
250		if (h->type == RADIUS_AUTH)
251			srvp->addr.sin_port =
252			    (sent = getservbyname("radius", "udp")) != NULL ?
253				sent->s_port : htons(RADIUS_PORT);
254		else
255			srvp->addr.sin_port =
256			    (sent = getservbyname("radacct", "udp")) != NULL ?
257				sent->s_port : htons(RADACCT_PORT);
258	}
259	if ((srvp->secret = strdup(secret)) == NULL) {
260		generr(h, "Out of memory");
261		return -1;
262	}
263	srvp->timeout = timeout;
264	srvp->max_tries = tries;
265	srvp->num_tries = 0;
266	h->num_servers++;
267	return 0;
268}
269
270void
271rad_close(struct rad_handle *h)
272{
273	int srv;
274
275	if (h->fd != -1)
276		close(h->fd);
277	for (srv = 0;  srv < h->num_servers;  srv++) {
278		memset(h->servers[srv].secret, 0,
279		    strlen(h->servers[srv].secret));
280		free(h->servers[srv].secret);
281	}
282	clear_password(h);
283	free(h);
284}
285
286int
287rad_config(struct rad_handle *h, const char *path)
288{
289	FILE *fp;
290	char buf[MAXCONFLINE];
291	int linenum;
292	int retval;
293
294	if (path == NULL)
295		path = PATH_RADIUS_CONF;
296	if ((fp = fopen(path, "r")) == NULL) {
297		generr(h, "Cannot open \"%s\": %s", path, strerror(errno));
298		return -1;
299	}
300	retval = 0;
301	linenum = 0;
302	while (fgets(buf, sizeof buf, fp) != NULL) {
303		int len;
304		char *fields[5];
305		int nfields;
306		char msg[ERRSIZE];
307		char *type;
308		char *host;
309		char *port_str;
310		char *secret;
311		char *timeout_str;
312		char *maxtries_str;
313		char *end;
314		char *wanttype;
315		unsigned long timeout;
316		unsigned long maxtries;
317		int port;
318		int i;
319
320		linenum++;
321		len = strlen(buf);
322		/* We know len > 0, else fgets would have returned NULL. */
323		if (buf[len - 1] != '\n') {
324			if (len == sizeof buf - 1)
325				generr(h, "%s:%d: line too long", path,
326				    linenum);
327			else
328				generr(h, "%s:%d: missing newline", path,
329				    linenum);
330			retval = -1;
331			break;
332		}
333		buf[len - 1] = '\0';
334
335		/* Extract the fields from the line. */
336		nfields = split(buf, fields, 5, msg, sizeof msg);
337		if (nfields == -1) {
338			generr(h, "%s:%d: %s", path, linenum, msg);
339			retval = -1;
340			break;
341		}
342		if (nfields == 0)
343			continue;
344		/*
345		 * The first field should contain "auth" or "acct" for
346		 * authentication or accounting, respectively.  But older
347		 * versions of the file didn't have that field.  Default
348		 * it to "auth" for backward compatibility.
349		 */
350		if (strcmp(fields[0], "auth") != 0 &&
351		    strcmp(fields[0], "acct") != 0) {
352			if (nfields >= 5) {
353				generr(h, "%s:%d: invalid service type", path,
354				    linenum);
355				retval = -1;
356				break;
357			}
358			nfields++;
359			for (i = nfields;  --i > 0;  )
360				fields[i] = fields[i - 1];
361			fields[0] = "auth";
362		}
363		if (nfields < 3) {
364			generr(h, "%s:%d: missing shared secret", path,
365			    linenum);
366			retval = -1;
367			break;
368		}
369		type = fields[0];
370		host = fields[1];
371		secret = fields[2];
372		timeout_str = fields[3];
373		maxtries_str = fields[4];
374
375		/* Ignore the line if it is for the wrong service type. */
376		wanttype = h->type == RADIUS_AUTH ? "auth" : "acct";
377		if (strcmp(type, wanttype) != 0)
378			continue;
379
380		/* Parse and validate the fields. */
381		host = strtok(host, ":");
382		port_str = strtok(NULL, ":");
383		if (port_str != NULL) {
384			port = strtoul(port_str, &end, 10);
385			if (*end != '\0') {
386				generr(h, "%s:%d: invalid port", path,
387				    linenum);
388				retval = -1;
389				break;
390			}
391		} else
392			port = 0;
393		if (timeout_str != NULL) {
394			timeout = strtoul(timeout_str, &end, 10);
395			if (*end != '\0') {
396				generr(h, "%s:%d: invalid timeout", path,
397				    linenum);
398				retval = -1;
399				break;
400			}
401		} else
402			timeout = TIMEOUT;
403		if (maxtries_str != NULL) {
404			maxtries = strtoul(maxtries_str, &end, 10);
405			if (*end != '\0') {
406				generr(h, "%s:%d: invalid maxtries", path,
407				    linenum);
408				retval = -1;
409				break;
410			}
411		} else
412			maxtries = MAXTRIES;
413
414		if (rad_add_server(h, host, port, secret, timeout, maxtries) ==
415		    -1) {
416			strcpy(msg, h->errmsg);
417			generr(h, "%s:%d: %s", path, linenum, msg);
418			retval = -1;
419			break;
420		}
421	}
422	/* Clear out the buffer to wipe a possible copy of a shared secret */
423	memset(buf, 0, sizeof buf);
424	fclose(fp);
425	return retval;
426}
427
428/*
429 * rad_init_send_request() must have previously been called.
430 * Returns:
431 *   0     The application should select on *fd with a timeout of tv before
432 *         calling rad_continue_send_request again.
433 *   < 0   Failure
434 *   > 0   Success
435 */
436int
437rad_continue_send_request(struct rad_handle *h, int selected, int *fd,
438                          struct timeval *tv)
439{
440	int n;
441
442	if (selected) {
443		struct sockaddr_in from;
444		int fromlen;
445
446		fromlen = sizeof from;
447		h->resp_len = recvfrom(h->fd, h->response,
448		    MSGSIZE, MSG_WAITALL, (struct sockaddr *)&from, &fromlen);
449		if (h->resp_len == -1) {
450			generr(h, "recvfrom: %s", strerror(errno));
451			return -1;
452		}
453		if (is_valid_response(h, h->srv, &from)) {
454			h->resp_len = h->response[POS_LENGTH] << 8 |
455			    h->response[POS_LENGTH+1];
456			h->resp_pos = POS_ATTRS;
457			return h->response[POS_CODE];
458		}
459	}
460
461	if (h->try == h->total_tries) {
462		generr(h, "No valid RADIUS responses received");
463		return -1;
464	}
465
466	/*
467         * Scan round-robin to the next server that has some
468         * tries left.  There is guaranteed to be one, or we
469         * would have exited this loop by now.
470	 */
471	while (h->servers[h->srv].num_tries >= h->servers[h->srv].max_tries)
472		if (++h->srv >= h->num_servers)
473			h->srv = 0;
474
475	if (h->request[POS_CODE] == RAD_ACCOUNTING_REQUEST)
476		/* Insert the request authenticator into the request */
477		insert_request_authenticator(h, h->srv);
478	else
479		/* Insert the scrambled password into the request */
480		if (h->pass_pos != 0)
481			insert_scrambled_password(h, h->srv);
482
483	/* Send the request */
484	n = sendto(h->fd, h->request, h->req_len, 0,
485	    (const struct sockaddr *)&h->servers[h->srv].addr,
486	    sizeof h->servers[h->srv].addr);
487	if (n != h->req_len) {
488		if (n == -1)
489			generr(h, "sendto: %s", strerror(errno));
490		else
491			generr(h, "sendto: short write");
492		return -1;
493	}
494
495	h->try++;
496	h->servers[h->srv].num_tries++;
497	tv->tv_sec = h->servers[h->srv].timeout;
498	tv->tv_usec = 0;
499	*fd = h->fd;
500
501	return 0;
502}
503
504int
505rad_create_request(struct rad_handle *h, int code)
506{
507	int i;
508
509	h->request[POS_CODE] = code;
510	h->request[POS_IDENT] = ++h->ident;
511	/* Create a random authenticator */
512	for (i = 0;  i < LEN_AUTH;  i += 2) {
513		long r;
514		r = random();
515		h->request[POS_AUTH+i] = r;
516		h->request[POS_AUTH+i+1] = r >> 8;
517	}
518	h->req_len = POS_ATTRS;
519	clear_password(h);
520	return 0;
521}
522
523struct in_addr
524rad_cvt_addr(const void *data)
525{
526	struct in_addr value;
527
528	memcpy(&value.s_addr, data, sizeof value.s_addr);
529	return value;
530}
531
532u_int32_t
533rad_cvt_int(const void *data)
534{
535	u_int32_t value;
536
537	memcpy(&value, data, sizeof value);
538	return ntohl(value);
539}
540
541char *
542rad_cvt_string(const void *data, size_t len)
543{
544	char *s;
545
546	s = malloc(len + 1);
547	if (s != NULL) {
548		memcpy(s, data, len);
549		s[len] = '\0';
550	}
551	return s;
552}
553
554/*
555 * Returns the attribute type.  If none are left, returns 0.  On failure,
556 * returns -1.
557 */
558int
559rad_get_attr(struct rad_handle *h, const void **value, size_t *len)
560{
561	int type;
562
563	if (h->resp_pos >= h->resp_len)
564		return 0;
565	if (h->resp_pos + 2 > h->resp_len) {
566		generr(h, "Malformed attribute in response");
567		return -1;
568	}
569	type = h->response[h->resp_pos++];
570	*len = h->response[h->resp_pos++] - 2;
571	if (h->resp_pos + *len > h->resp_len) {
572		generr(h, "Malformed attribute in response");
573		return -1;
574	}
575	*value = &h->response[h->resp_pos];
576	h->resp_pos += *len;
577	return type;
578}
579
580/*
581 * Returns -1 on error, 0 to indicate no event and >0 for success
582 */
583int
584rad_init_send_request(struct rad_handle *h, int *fd, struct timeval *tv)
585{
586	int srv;
587
588	/* Make sure we have a socket to use */
589	if (h->fd == -1) {
590		struct sockaddr_in sin;
591
592		if ((h->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
593			generr(h, "Cannot create socket: %s", strerror(errno));
594			return -1;
595		}
596		memset(&sin, 0, sizeof sin);
597		sin.sin_len = sizeof sin;
598		sin.sin_family = AF_INET;
599		sin.sin_addr.s_addr = INADDR_ANY;
600		sin.sin_port = htons(0);
601		if (bind(h->fd, (const struct sockaddr *)&sin,
602		    sizeof sin) == -1) {
603			generr(h, "bind: %s", strerror(errno));
604			close(h->fd);
605			h->fd = -1;
606			return -1;
607		}
608	}
609
610	if (h->request[POS_CODE] == RAD_ACCOUNTING_REQUEST) {
611		/* Make sure no password given */
612		if (h->pass_pos || h->chap_pass) {
613			generr(h, "User or Chap Password in accounting request");
614			return -1;
615		}
616	} else {
617		/* Make sure the user gave us a password */
618		if (h->pass_pos == 0 && !h->chap_pass) {
619			generr(h, "No User or Chap Password attributes given");
620			return -1;
621		}
622		if (h->pass_pos != 0 && h->chap_pass) {
623			generr(h, "Both User and Chap Password attributes given");
624			return -1;
625		}
626	}
627
628	/* Fill in the length field in the message */
629	h->request[POS_LENGTH] = h->req_len >> 8;
630	h->request[POS_LENGTH+1] = h->req_len;
631
632	/*
633	 * Count the total number of tries we will make, and zero the
634	 * counter for each server.
635	 */
636	h->total_tries = 0;
637	for (srv = 0;  srv < h->num_servers;  srv++) {
638		h->total_tries += h->servers[srv].max_tries;
639		h->servers[srv].num_tries = 0;
640	}
641	if (h->total_tries == 0) {
642		generr(h, "No RADIUS servers specified");
643		return -1;
644	}
645
646	h->try = h->srv = 0;
647
648	return rad_continue_send_request(h, 0, fd, tv);
649}
650
651/*
652 * Create and initialize a rad_handle structure, and return it to the
653 * caller.  Can fail only if the necessary memory cannot be allocated.
654 * In that case, it returns NULL.
655 */
656struct rad_handle *
657rad_auth_open(void)
658{
659	struct rad_handle *h;
660
661	h = (struct rad_handle *)malloc(sizeof(struct rad_handle));
662	if (h != NULL) {
663		srandomdev();
664		h->fd = -1;
665		h->num_servers = 0;
666		h->ident = random();
667		h->errmsg[0] = '\0';
668		memset(h->pass, 0, sizeof h->pass);
669		h->pass_len = 0;
670		h->pass_pos = 0;
671		h->chap_pass = 0;
672		h->type = RADIUS_AUTH;
673	}
674	return h;
675}
676
677struct rad_handle *
678rad_acct_open(void)
679{
680	struct rad_handle *h;
681
682	h = rad_open();
683	if (h != NULL)
684	        h->type = RADIUS_ACCT;
685	return h;
686}
687
688struct rad_handle *
689rad_open(void)
690{
691    return rad_auth_open();
692}
693
694int
695rad_put_addr(struct rad_handle *h, int type, struct in_addr addr)
696{
697	return rad_put_attr(h, type, &addr.s_addr, sizeof addr.s_addr);
698}
699
700int
701rad_put_attr(struct rad_handle *h, int type, const void *value, size_t len)
702{
703	int result;
704
705	if (type == RAD_USER_PASSWORD)
706		result = put_password_attr(h, type, value, len);
707	else {
708		result = put_raw_attr(h, type, value, len);
709		if (result == 0 && type == RAD_CHAP_PASSWORD)
710			h->chap_pass = 1;
711	}
712
713	return result;
714}
715
716int
717rad_put_int(struct rad_handle *h, int type, u_int32_t value)
718{
719	u_int32_t nvalue;
720
721	nvalue = htonl(value);
722	return rad_put_attr(h, type, &nvalue, sizeof nvalue);
723}
724
725int
726rad_put_string(struct rad_handle *h, int type, const char *str)
727{
728	return rad_put_attr(h, type, str, strlen(str));
729}
730
731/*
732 * Returns the response type code on success, or -1 on failure.
733 */
734int
735rad_send_request(struct rad_handle *h)
736{
737	struct timeval timelimit;
738	struct timeval tv;
739	int fd;
740	int n;
741
742	n = rad_init_send_request(h, &fd, &tv);
743
744	if (n != 0)
745		return n;
746
747	gettimeofday(&timelimit, NULL);
748	timeradd(&tv, &timelimit, &timelimit);
749
750	for ( ; ; ) {
751		fd_set readfds;
752
753		FD_ZERO(&readfds);
754		FD_SET(fd, &readfds);
755
756		n = select(fd + 1, &readfds, NULL, NULL, &tv);
757
758		if (n == -1) {
759			generr(h, "select: %s", strerror(errno));
760			return -1;
761		}
762
763		if (!FD_ISSET(fd, &readfds)) {
764			/* Compute a new timeout */
765			gettimeofday(&tv, NULL);
766			timersub(&timelimit, &tv, &tv);
767			if (tv.tv_sec > 0 || (tv.tv_sec == 0 && tv.tv_usec > 0))
768				/* Continue the select */
769				continue;
770		}
771
772		n = rad_continue_send_request(h, n, &fd, &tv);
773
774		if (n != 0)
775			return n;
776
777		gettimeofday(&timelimit, NULL);
778		timeradd(&tv, &timelimit, &timelimit);
779	}
780}
781
782const char *
783rad_strerror(struct rad_handle *h)
784{
785	return h->errmsg;
786}
787
788/*
789 * Destructively split a string into fields separated by white space.
790 * `#' at the beginning of a field begins a comment that extends to the
791 * end of the string.  Fields may be quoted with `"'.  Inside quoted
792 * strings, the backslash escapes `\"' and `\\' are honored.
793 *
794 * Pointers to up to the first maxfields fields are stored in the fields
795 * array.  Missing fields get NULL pointers.
796 *
797 * The return value is the actual number of fields parsed, and is always
798 * <= maxfields.
799 *
800 * On a syntax error, places a message in the msg string, and returns -1.
801 */
802static int
803split(char *str, char *fields[], int maxfields, char *msg, size_t msglen)
804{
805	char *p;
806	int i;
807	static const char ws[] = " \t";
808
809	for (i = 0;  i < maxfields;  i++)
810		fields[i] = NULL;
811	p = str;
812	i = 0;
813	while (*p != '\0') {
814		p += strspn(p, ws);
815		if (*p == '#' || *p == '\0')
816			break;
817		if (i >= maxfields) {
818			snprintf(msg, msglen, "line has too many fields");
819			return -1;
820		}
821		if (*p == '"') {
822			char *dst;
823
824			dst = ++p;
825			fields[i] = dst;
826			while (*p != '"') {
827				if (*p == '\\') {
828					p++;
829					if (*p != '"' && *p != '\\' &&
830					    *p != '\0') {
831						snprintf(msg, msglen,
832						    "invalid `\\' escape");
833						return -1;
834					}
835				}
836				if (*p == '\0') {
837					snprintf(msg, msglen,
838					    "unterminated quoted string");
839					return -1;
840				}
841				*dst++ = *p++;
842			}
843			*dst = '\0';
844			p++;
845			if (*fields[i] == '\0') {
846				snprintf(msg, msglen,
847				    "empty quoted string not permitted");
848				return -1;
849			}
850			if (*p != '\0' && strspn(p, ws) == 0) {
851				snprintf(msg, msglen, "quoted string not"
852				    " followed by white space");
853				return -1;
854			}
855		} else {
856			fields[i] = p;
857			p += strcspn(p, ws);
858			if (*p != '\0')
859				*p++ = '\0';
860		}
861		i++;
862	}
863	return i;
864}
865