radlib.c revision 43400
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 43400 1999-01-29 22:44:47Z brian $
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 int	 is_valid_response(struct rad_handle *, int,
52		    const struct sockaddr_in *);
53static int	 put_password_attr(struct rad_handle *, int,
54		    const void *, size_t);
55static int	 put_raw_attr(struct rad_handle *, int,
56		    const void *, size_t);
57static int	 split(char *, char *[], int, char *, size_t);
58
59static void
60clear_password(struct rad_handle *h)
61{
62	if (h->pass_len != 0) {
63		memset(h->pass, 0, h->pass_len);
64		h->pass_len = 0;
65		h->pass_pos = 0;
66	}
67}
68
69static void
70generr(struct rad_handle *h, const char *format, ...)
71{
72	va_list		 ap;
73
74	va_start(ap, format);
75	vsnprintf(h->errmsg, ERRSIZE, format, ap);
76	va_end(ap);
77}
78
79static void
80insert_scrambled_password(struct rad_handle *h, int srv)
81{
82	MD5_CTX ctx;
83	unsigned char md5[16];
84	const struct rad_server *srvp;
85	int padded_len;
86	int pos;
87
88	srvp = &h->servers[srv];
89	padded_len = h->pass_len == 0 ? 16 : (h->pass_len+15) & ~0xf;
90
91	memcpy(md5, &h->request[POS_AUTH], LEN_AUTH);
92	for (pos = 0;  pos < padded_len;  pos += 16) {
93		int i;
94
95		/* Calculate the new scrambler */
96		MD5Init(&ctx);
97		MD5Update(&ctx, srvp->secret, strlen(srvp->secret));
98		MD5Update(&ctx, md5, 16);
99		MD5Final(md5, &ctx);
100
101		/*
102		 * Mix in the current chunk of the password, and copy
103		 * the result into the right place in the request.  Also
104		 * modify the scrambler in place, since we will use this
105		 * in calculating the scrambler for next time.
106		 */
107		for (i = 0;  i < 16;  i++)
108			h->request[h->pass_pos + pos + i] =
109			    md5[i] ^= h->pass[pos + i];
110	}
111}
112
113/*
114 * Return true if the current response is valid for a request to the
115 * specified server.
116 */
117static int
118is_valid_response(struct rad_handle *h, int srv,
119    const struct sockaddr_in *from)
120{
121	MD5_CTX ctx;
122	unsigned char md5[16];
123	const struct rad_server *srvp;
124	int len;
125
126	srvp = &h->servers[srv];
127
128	/* Check the source address */
129	if (from->sin_family != srvp->addr.sin_family ||
130	    from->sin_addr.s_addr != srvp->addr.sin_addr.s_addr ||
131	    from->sin_port != srvp->addr.sin_port)
132		return 0;
133
134	/* Check the message length */
135	if (h->resp_len < POS_ATTRS)
136		return 0;
137	len = h->response[POS_LENGTH] << 8 | h->response[POS_LENGTH+1];
138	if (len > h->resp_len)
139		return 0;
140
141	/* Check the response authenticator */
142	MD5Init(&ctx);
143	MD5Update(&ctx, &h->response[POS_CODE], POS_AUTH - POS_CODE);
144	MD5Update(&ctx, &h->request[POS_AUTH], LEN_AUTH);
145	MD5Update(&ctx, &h->response[POS_ATTRS], len - POS_ATTRS);
146	MD5Update(&ctx, srvp->secret, strlen(srvp->secret));
147	MD5Final(md5, &ctx);
148	if (memcmp(&h->response[POS_AUTH], md5, sizeof md5) != 0)
149		return 0;
150
151	return 1;
152}
153
154static int
155put_password_attr(struct rad_handle *h, int type, const void *value, size_t len)
156{
157	int padded_len;
158	int pad_len;
159
160	if (h->pass_pos != 0) {
161		generr(h, "Multiple User-Password attributes specified");
162		return -1;
163	}
164	if (len > PASSSIZE)
165		len = PASSSIZE;
166	padded_len = len == 0 ? 16 : (len+15) & ~0xf;
167	pad_len = padded_len - len;
168
169	/*
170	 * Put in a place-holder attribute containing all zeros, and
171	 * remember where it is so we can fill it in later.
172	 */
173	clear_password(h);
174	put_raw_attr(h, type, h->pass, padded_len);
175	h->pass_pos = h->req_len - padded_len;
176
177	/* Save the cleartext password, padded as necessary */
178	memcpy(h->pass, value, len);
179	h->pass_len = len;
180	memset(h->pass + len, 0, pad_len);
181	return 0;
182}
183
184static int
185put_raw_attr(struct rad_handle *h, int type, const void *value, size_t len)
186{
187	if (len > 253) {
188		generr(h, "Attribute too long");
189		return -1;
190	}
191	if (h->req_len + 2 + len > MSGSIZE) {
192		generr(h, "Maximum message length exceeded");
193		return -1;
194	}
195	h->request[h->req_len++] = type;
196	h->request[h->req_len++] = len + 2;
197	memcpy(&h->request[h->req_len], value, len);
198	h->req_len += len;
199	return 0;
200}
201
202int
203rad_add_server(struct rad_handle *h, const char *host, int port,
204    const char *secret, int timeout, int tries)
205{
206	struct rad_server *srvp;
207
208	if (h->num_servers >= MAXSERVERS) {
209		generr(h, "Too many RADIUS servers specified");
210		return -1;
211	}
212	srvp = &h->servers[h->num_servers];
213
214	memset(&srvp->addr, 0, sizeof srvp->addr);
215	srvp->addr.sin_len = sizeof srvp->addr;
216	srvp->addr.sin_family = AF_INET;
217	if (!inet_aton(host, &srvp->addr.sin_addr)) {
218		struct hostent *hent;
219
220		if ((hent = gethostbyname(host)) == NULL) {
221			generr(h, "%s: host not found", host);
222			return -1;
223		}
224		memcpy(&srvp->addr.sin_addr, hent->h_addr,
225		    sizeof srvp->addr.sin_addr);
226	}
227	if (port != 0)
228		srvp->addr.sin_port = htons(port);
229	else {
230		struct servent *sent;
231
232		srvp->addr.sin_port =
233		    (sent = getservbyname("radius", "udp")) != NULL ?
234			sent->s_port : htons(RADIUS_PORT);
235	}
236	if ((srvp->secret = strdup(secret)) == NULL) {
237		generr(h, "Out of memory");
238		return -1;
239	}
240	srvp->timeout = timeout;
241	srvp->max_tries = tries;
242	srvp->num_tries = 0;
243	h->num_servers++;
244	return 0;
245}
246
247void
248rad_close(struct rad_handle *h)
249{
250	int srv;
251
252	if (h->fd != -1)
253		close(h->fd);
254	for (srv = 0;  srv < h->num_servers;  srv++) {
255		memset(h->servers[srv].secret, 0,
256		    strlen(h->servers[srv].secret));
257		free(h->servers[srv].secret);
258	}
259	clear_password(h);
260	free(h);
261}
262
263int
264rad_config(struct rad_handle *h, const char *path)
265{
266	FILE *fp;
267	char buf[MAXCONFLINE];
268	int linenum;
269	int retval;
270
271	if (path == NULL)
272		path = PATH_RADIUS_CONF;
273	if ((fp = fopen(path, "r")) == NULL) {
274		generr(h, "Cannot open \"%s\": %s", path, strerror(errno));
275		return -1;
276	}
277	retval = 0;
278	linenum = 0;
279	while (fgets(buf, sizeof buf, fp) != NULL) {
280		int len;
281		char *fields[4];
282		int nfields;
283		char msg[ERRSIZE];
284		char *host;
285		char *port_str;
286		char *secret;
287		char *timeout_str;
288		char *maxtries_str;
289		char *end;
290		unsigned long timeout;
291		unsigned long maxtries;
292		int port;
293
294		linenum++;
295		len = strlen(buf);
296		/* We know len > 0, else fgets would have returned NULL. */
297		if (buf[len - 1] != '\n') {
298			if (len == sizeof buf - 1)
299				generr(h, "%s:%d: line too long", path,
300				    linenum);
301			else
302				generr(h, "%s:%d: missing newline", path,
303				    linenum);
304			retval = -1;
305			break;
306		}
307		buf[len - 1] = '\0';
308
309		/* Extract the fields from the line. */
310		nfields = split(buf, fields, 4, msg, sizeof msg);
311		if (nfields == -1) {
312			generr(h, "%s:%d: %s", path, linenum, msg);
313			retval = -1;
314			break;
315		}
316		if (nfields == 0)
317			continue;
318		if (nfields < 2) {
319			generr(h, "%s:%d: missing shared secret", path,
320			    linenum);
321			retval = -1;
322			break;
323		}
324		host = fields[0];
325		secret = fields[1];
326		timeout_str = fields[2];
327		maxtries_str = fields[3];
328
329		/* Parse and validate the fields. */
330		host = strtok(host, ":");
331		port_str = strtok(NULL, ":");
332		if (port_str != NULL) {
333			port = strtoul(port_str, &end, 10);
334			if (*end != '\0') {
335				generr(h, "%s:%d: invalid port", path,
336				    linenum);
337				retval = -1;
338				break;
339			}
340		} else
341			port = 0;
342		if (timeout_str != NULL) {
343			timeout = strtoul(timeout_str, &end, 10);
344			if (*end != '\0') {
345				generr(h, "%s:%d: invalid timeout", path,
346				    linenum);
347				retval = -1;
348				break;
349			}
350		} else
351			timeout = TIMEOUT;
352		if (maxtries_str != NULL) {
353			maxtries = strtoul(maxtries_str, &end, 10);
354			if (*end != '\0') {
355				generr(h, "%s:%d: invalid maxtries", path,
356				    linenum);
357				retval = -1;
358				break;
359			}
360		} else
361			maxtries = MAXTRIES;
362
363		if (rad_add_server(h, host, port, secret, timeout, maxtries) ==
364		    -1) {
365			char msg[ERRSIZE];
366
367			strcpy(msg, h->errmsg);
368			generr(h, "%s:%d: %s", path, linenum, msg);
369			retval = -1;
370			break;
371		}
372	}
373	/* Clear out the buffer to wipe a possible copy of a shared secret */
374	memset(buf, 0, sizeof buf);
375	fclose(fp);
376	return retval;
377}
378
379int
380rad_create_request(struct rad_handle *h, int code)
381{
382	int i;
383
384	h->request[POS_CODE] = code;
385	h->request[POS_IDENT] = ++h->ident;
386	/* Create a random authenticator */
387	for (i = 0;  i < LEN_AUTH;  i += 2) {
388		long r;
389		r = random();
390		h->request[POS_AUTH+i] = r;
391		h->request[POS_AUTH+i+1] = r >> 8;
392	}
393	h->req_len = POS_ATTRS;
394	clear_password(h);
395	return 0;
396}
397
398struct in_addr
399rad_cvt_addr(const void *data)
400{
401	struct in_addr value;
402
403	memcpy(&value.s_addr, data, sizeof value.s_addr);
404	return value;
405}
406
407u_int32_t
408rad_cvt_int(const void *data)
409{
410	u_int32_t value;
411
412	memcpy(&value, data, sizeof value);
413	return ntohl(value);
414}
415
416char *
417rad_cvt_string(const void *data, size_t len)
418{
419	char *s;
420
421	s = malloc(len + 1);
422	if (s != NULL) {
423		memcpy(s, data, len);
424		s[len] = '\0';
425	}
426	return s;
427}
428
429/*
430 * Returns the attribute type.  If none are left, returns 0.  On failure,
431 * returns -1.
432 */
433int
434rad_get_attr(struct rad_handle *h, const void **value, size_t *len)
435{
436	int type;
437
438	if (h->resp_pos >= h->resp_len)
439		return 0;
440	if (h->resp_pos + 2 > h->resp_len) {
441		generr(h, "Malformed attribute in response");
442		return -1;
443	}
444	type = h->response[h->resp_pos++];
445	*len = h->response[h->resp_pos++] - 2;
446	if (h->resp_pos + *len > h->resp_len) {
447		generr(h, "Malformed attribute in response");
448		return -1;
449	}
450	*value = &h->response[h->resp_pos];
451	h->resp_pos += *len;
452	return type;
453}
454
455/*
456 * Create and initialize a rad_handle structure, and return it to the
457 * caller.  Can fail only if the necessary memory cannot be allocated.
458 * In that case, it returns NULL.
459 */
460struct rad_handle *
461rad_open(void)
462{
463	struct rad_handle *h;
464
465	h = (struct rad_handle *)malloc(sizeof(struct rad_handle));
466	if (h != NULL) {
467		srandomdev();
468		h->fd = -1;
469		h->num_servers = 0;
470		h->ident = random();
471		h->errmsg[0] = '\0';
472		memset(h->pass, 0, sizeof h->pass);
473		h->pass_len = 0;
474		h->pass_pos = 0;
475		h->chap_pass = 0;
476	}
477	return h;
478}
479
480int
481rad_put_addr(struct rad_handle *h, int type, struct in_addr addr)
482{
483	return rad_put_attr(h, type, &addr.s_addr, sizeof addr.s_addr);
484}
485
486int
487rad_put_attr(struct rad_handle *h, int type, const void *value, size_t len)
488{
489	int result;
490
491	if (type == RAD_USER_PASSWORD)
492		result = put_password_attr(h, type, value, len);
493	else {
494		result = put_raw_attr(h, type, value, len);
495		if (result == 0 && type == RAD_CHAP_PASSWORD)
496			h->chap_pass = 1;
497	}
498
499	return result;
500}
501
502int
503rad_put_int(struct rad_handle *h, int type, u_int32_t value)
504{
505	u_int32_t nvalue;
506
507	nvalue = htonl(value);
508	return rad_put_attr(h, type, &nvalue, sizeof nvalue);
509}
510
511int
512rad_put_string(struct rad_handle *h, int type, const char *str)
513{
514	return rad_put_attr(h, type, str, strlen(str));
515}
516
517/*
518 * Returns the response type code on success, or -1 on failure.
519 */
520int
521rad_send_request(struct rad_handle *h)
522{
523	int total_tries;
524	int try;
525	int srv;
526	int n;
527	int got_valid_response;
528
529	/* Make sure we have a socket to use */
530	if (h->fd == -1) {
531		struct sockaddr_in sin;
532
533		if ((h->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
534			generr(h, "Cannot create socket: %s", strerror(errno));
535			return -1;
536		}
537		memset(&sin, 0, sizeof sin);
538		sin.sin_len = sizeof sin;
539		sin.sin_family = AF_INET;
540		sin.sin_addr.s_addr = INADDR_ANY;
541		sin.sin_port = htons(0);
542		if (bind(h->fd, (const struct sockaddr *)&sin,
543		    sizeof sin) == -1) {
544			generr(h, "bind: %s", strerror(errno));
545			close(h->fd);
546			h->fd = -1;
547			return -1;
548		}
549	}
550
551	/* Make sure the user gave us a password */
552	if (h->pass_pos == 0 && !h->chap_pass) {
553		generr(h, "No User or Chap Password attributes given");
554		return -1;
555	}
556
557	if (h->pass_pos != 0 && h->chap_pass) {
558		generr(h, "Both User and Chap Password attributes given");
559		return -1;
560	}
561
562	/* Fill in the length field in the message */
563	h->request[POS_LENGTH] = h->req_len >> 8;
564	h->request[POS_LENGTH+1] = h->req_len;
565
566	/*
567	 * Count the total number of tries we will make, and zero the
568	 * counter for each server.
569	 */
570	total_tries = 0;
571	for (srv = 0;  srv < h->num_servers;  srv++) {
572		total_tries += h->servers[srv].max_tries;
573		h->servers[srv].num_tries = 0;
574	}
575	if (total_tries == 0) {
576		generr(h, "No RADIUS servers specified");
577		return -1;
578	}
579
580	srv = 0;
581	got_valid_response = 0;
582	for (try = 0;  try < total_tries;  try++) {
583		struct timeval timelimit;
584		struct timeval tv;
585
586		/*
587                 * Scan round-robin to the next server that has some
588                 * tries left.  There is guaranteed to be one, or we
589                 * would have exited this loop by now.
590		 */
591		while (h->servers[srv].num_tries >=
592		    h->servers[srv].max_tries)
593			if (++srv >= h->num_servers)
594				srv = 0;
595
596		/* Insert the scrambled password into the request */
597		if (h->pass_pos != 0)
598			insert_scrambled_password(h, srv);
599
600		/* Send the request */
601		n = sendto(h->fd, h->request, h->req_len, 0,
602		    (const struct sockaddr *)&h->servers[srv].addr,
603		    sizeof h->servers[srv].addr);
604		if (n != h->req_len) {
605			if (n == -1)
606				generr(h, "sendto: %s", strerror(errno));
607			else
608				generr(h, "sendto: short write");
609			return -1;
610		}
611		h->servers[srv].num_tries++;
612
613		/* Wait for a valid response */
614		gettimeofday(&timelimit, NULL);
615		timelimit.tv_sec += h->servers[srv].timeout;
616
617		tv.tv_sec = h->servers[srv].timeout;
618		tv.tv_usec = 0;
619		for ( ; ; ) {
620			fd_set readfds;
621
622			FD_ZERO(&readfds);
623			FD_SET(h->fd, &readfds);
624			n = select(h->fd + 1, &readfds, NULL, NULL, &tv);
625			if (n == -1) {
626				generr(h, "select: %s", strerror(errno));
627				return -1;
628			}
629			if (n == 0)	/* Timed out */
630				break;
631			if (FD_ISSET(h->fd, &readfds)) {
632				struct sockaddr_in from;
633				int fromlen;
634
635				fromlen = sizeof from;
636				h->resp_len = recvfrom(h->fd, h->response,
637				    MSGSIZE, MSG_WAITALL,
638				    (struct sockaddr *)&from, &fromlen);
639				if (h->resp_len == -1) {
640					generr(h, "recvfrom: %s",
641					    strerror(errno));
642					return -1;
643				}
644				if (is_valid_response(h, srv, &from)) {
645					got_valid_response = 1;
646					break;
647				}
648			}
649			/* Compute a new timeout */
650			gettimeofday(&tv, NULL);
651			timersub(&timelimit, &tv, &tv);
652			if (tv.tv_sec < 0)	/* Still poll once more */
653				timerclear(&tv);
654		}
655		if (got_valid_response)
656			break;
657		/* Advance to the next server */
658		if (++srv >= h->num_servers)
659			srv = 0;
660	}
661	if (!got_valid_response) {
662		generr(h, "No valid RADIUS responses received");
663		return -1;
664	}
665	h->resp_len = h->response[POS_LENGTH] << 8 | h->response[POS_LENGTH+1];
666	h->resp_pos = POS_ATTRS;
667	return h->response[POS_CODE];
668}
669
670const char *
671rad_strerror(struct rad_handle *h)
672{
673	return h->errmsg;
674}
675
676/*
677 * Destructively split a string into fields separated by white space.
678 * `#' at the beginning of a field begins a comment that extends to the
679 * end of the string.  Fields may be quoted with `"'.  Inside quoted
680 * strings, the backslash escapes `\"' and `\\' are honored.
681 *
682 * Pointers to up to the first maxfields fields are stored in the fields
683 * array.  Missing fields get NULL pointers.
684 *
685 * The return value is the actual number of fields parsed, and is always
686 * <= maxfields.
687 *
688 * On a syntax error, places a message in the msg string, and returns -1.
689 */
690static int
691split(char *str, char *fields[], int maxfields, char *msg, size_t msglen)
692{
693	char *p;
694	int i;
695	static const char ws[] = " \t";
696
697	for (i = 0;  i < maxfields;  i++)
698		fields[i] = NULL;
699	p = str;
700	i = 0;
701	while (*p != '\0') {
702		p += strspn(p, ws);
703		if (*p == '#' || *p == '\0')
704			break;
705		if (i >= maxfields) {
706			snprintf(msg, msglen, "line has too many fields");
707			return -1;
708		}
709		if (*p == '"') {
710			char *dst;
711
712			dst = ++p;
713			fields[i] = dst;
714			while (*p != '"') {
715				if (*p == '\\') {
716					p++;
717					if (*p != '"' && *p != '\\' &&
718					    *p != '\0') {
719						snprintf(msg, msglen,
720						    "invalid `\\' escape");
721						return -1;
722					}
723				}
724				if (*p == '\0') {
725					snprintf(msg, msglen,
726					    "unterminated quoted string");
727					return -1;
728				}
729				*dst++ = *p++;
730			}
731			*dst = '\0';
732			p++;
733			if (*fields[i] == '\0') {
734				snprintf(msg, msglen,
735				    "empty quoted string not permitted");
736				return -1;
737			}
738			if (*p != '\0' && strspn(p, ws) == 0) {
739				snprintf(msg, msglen, "quoted string not"
740				    " followed by white space");
741				return -1;
742			}
743		} else {
744			fields[i] = p;
745			p += strcspn(p, ws);
746			if (*p != '\0')
747				*p++ = '\0';
748		}
749		i++;
750	}
751	return i;
752}
753