xref: /freebsd/lib/libtacplus/taclib.c (revision 41466b50c1d5bfd1cf6adaae547a579a75d7c04e)
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 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <sys/types.h>
31 #include <sys/socket.h>
32 #include <sys/time.h>
33 #include <netinet/in.h>
34 #include <arpa/inet.h>
35 
36 #include <assert.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <md5.h>
40 #include <netdb.h>
41 #include <stdarg.h>
42 #include <stddef.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 
48 #include "taclib_private.h"
49 
50 static int		 add_str_8(struct tac_handle *, u_int8_t *,
51 			    struct clnt_str *);
52 static int		 add_str_16(struct tac_handle *, u_int16_t *,
53 			    struct clnt_str *);
54 static int		 authen_version(int, int);
55 static void		 close_connection(struct tac_handle *);
56 static int		 conn_server(struct tac_handle *);
57 static void		 crypt_msg(struct tac_handle *, struct tac_msg *);
58 static void		*dup_str(struct tac_handle *, const struct srvr_str *,
59 			    size_t *);
60 static int		 establish_connection(struct tac_handle *);
61 static void		 free_str(struct clnt_str *);
62 static void		 generr(struct tac_handle *, const char *, ...)
63 			    __printflike(2, 3);
64 static void		 gen_session_id(struct tac_msg *);
65 static int		 get_srvr_end(struct tac_handle *);
66 static int		 get_srvr_str(struct tac_handle *, struct srvr_str *,
67 			    size_t);
68 static void		 init_clnt_str(struct clnt_str *);
69 static void		 init_srvr_str(struct srvr_str *);
70 static int		 read_timed(struct tac_handle *, void *, size_t,
71 			    const struct timeval *);
72 static int		 recv_msg(struct tac_handle *);
73 static int		 save_str(struct tac_handle *, struct clnt_str *,
74 			    const void *, size_t);
75 static int		 send_msg(struct tac_handle *);
76 static int		 split(char *, char *[], int, char *, size_t);
77 static void		*xmalloc(struct tac_handle *, size_t);
78 static char		*xstrdup(struct tac_handle *, const char *);
79 
80 /*
81  * Append some optional data to the current request, and store its
82  * length into the 8-bit field referenced by "fld".  Returns 0 on
83  * success, or -1 on failure.
84  *
85  * This function also frees the "cs" string data and initializes it
86  * for the next time.
87  */
88 static int
89 add_str_8(struct tac_handle *h, u_int8_t *fld, struct clnt_str *cs)
90 {
91 	u_int16_t len;
92 
93 	if (add_str_16(h, &len, cs) == -1)
94 		return -1;
95 	len = ntohs(len);
96 	if (len > 0xff) {
97 		generr(h, "Field too long");
98 		return -1;
99 	}
100 	*fld = len;
101 	return 0;
102 }
103 
104 /*
105  * Append some optional data to the current request, and store its
106  * length into the 16-bit field (network byte order) referenced by
107  * "fld".  Returns 0 on success, or -1 on failure.
108  *
109  * This function also frees the "cs" string data and initializes it
110  * for the next time.
111  */
112 static int
113 add_str_16(struct tac_handle *h, u_int16_t *fld, struct clnt_str *cs)
114 {
115 	size_t len;
116 
117 	len = cs->len;
118 	if (cs->data == NULL)
119 		len = 0;
120 	if (len != 0) {
121 		int offset;
122 
123 		if (len > 0xffff) {
124 			generr(h, "Field too long");
125 			return -1;
126 		}
127 		offset = ntohl(h->request.length);
128 		if (offset + len > BODYSIZE) {
129 			generr(h, "Message too long");
130 			return -1;
131 		}
132 		memcpy(h->request.u.body + offset, cs->data, len);
133 		h->request.length = htonl(offset + len);
134 	}
135 	*fld = htons(len);
136 	free_str(cs);
137 	return 0;
138 }
139 
140 static int
141 authen_version(int action, int type)
142 {
143 	int minor;
144 
145 	switch (action) {
146 
147 	case TAC_AUTHEN_LOGIN:
148 		switch (type) {
149 
150 		case TAC_AUTHEN_TYPE_PAP:
151 		case TAC_AUTHEN_TYPE_CHAP:
152 		case TAC_AUTHEN_TYPE_MSCHAP:
153 		case TAC_AUTHEN_TYPE_ARAP:
154 			minor = 1;
155 			break;
156 
157 		default:
158 			minor = 0;
159 			break;
160 		}
161 		break;
162 
163 	case TAC_AUTHEN_SENDAUTH:
164 		minor = 1;
165 		break;
166 
167 	default:
168 		minor = 0;
169 		break;
170 	};
171 
172 	return TAC_VER_MAJOR << 4 | minor;
173 }
174 
175 static void
176 close_connection(struct tac_handle *h)
177 {
178 	if (h->fd != -1) {
179 		close(h->fd);
180 		h->fd = -1;
181 	}
182 }
183 
184 static int
185 conn_server(struct tac_handle *h)
186 {
187 	const struct tac_server *srvp = &h->servers[h->cur_server];
188 	int flags;
189 
190 	if ((h->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
191 		generr(h, "Cannot create socket: %s", strerror(errno));
192 		return -1;
193 	}
194 	if ((flags = fcntl(h->fd, F_GETFL, 0)) == -1 ||
195 	    fcntl(h->fd, F_SETFL, flags | O_NONBLOCK) == -1) {
196 		generr(h, "Cannot set non-blocking mode on socket: %s",
197 		    strerror(errno));
198 		close(h->fd);
199 		h->fd = -1;
200 		return -1;
201 	}
202 	if (connect(h->fd, (struct sockaddr *)&srvp->addr,
203 	    sizeof srvp->addr) == 0)
204 		return 0;
205 
206 	if (errno == EINPROGRESS) {
207 		fd_set wfds;
208 		struct timeval tv;
209 		int nfds;
210 		struct sockaddr peer;
211 		int peerlen;
212 		int err;
213 		int errlen;
214 
215 		/* Wait for the connection to complete. */
216 		FD_ZERO(&wfds);
217 		FD_SET(h->fd, &wfds);
218 		tv.tv_sec = srvp->timeout;
219 		tv.tv_usec = 0;
220 		nfds = select(h->fd + 1, NULL, &wfds, NULL, &tv);
221 		if (nfds == -1) {
222 			generr(h, "select: %s", strerror(errno));
223 			close(h->fd);
224 			h->fd = -1;
225 			return -1;
226 		}
227 		if (nfds == 0) {
228 			generr(h, "connect: timed out");
229 			close(h->fd);
230 			h->fd = -1;
231 			return -1;
232 		}
233 
234 		/* See whether we are connected now. */
235 		peerlen = sizeof peer;
236 		if (getpeername(h->fd, &peer, &peerlen) == 0)
237 			return 0;
238 
239 		if (errno != ENOTCONN) {
240 			generr(h, "getpeername: %s", strerror(errno));
241 			close(h->fd);
242 			h->fd = -1;
243 			return -1;
244 		}
245 
246 		/* Find out why the connect failed. */
247 		errlen = sizeof err;
248 		getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &err, &errlen);
249 		errno = err;
250 	}
251 	generr(h, "connect: %s", strerror(errno));
252 	close(h->fd);
253 	h->fd = -1;
254 	return -1;
255 }
256 
257 /*
258  * Encrypt or decrypt a message.  The operations are symmetrical.
259  */
260 static void
261 crypt_msg(struct tac_handle *h, struct tac_msg *msg)
262 {
263 	const char *secret;
264 	MD5_CTX base_ctx;
265 	MD5_CTX ctx;
266 	unsigned char md5[16];
267 	int chunk;
268 	int msg_len;
269 
270 	secret = h->servers[h->cur_server].secret;
271 	if (secret[0] == '\0')
272 		msg->flags |= TAC_UNENCRYPTED;
273 	if (msg->flags & TAC_UNENCRYPTED)
274 		return;
275 
276 	msg_len = ntohl(msg->length);
277 
278 	MD5Init(&base_ctx);
279 	MD5Update(&base_ctx, msg->session_id, sizeof msg->session_id);
280 	MD5Update(&base_ctx, secret, strlen(secret));
281 	MD5Update(&base_ctx, &msg->version, sizeof msg->version);
282 	MD5Update(&base_ctx, &msg->seq_no, sizeof msg->seq_no);
283 
284 	ctx = base_ctx;
285 	for (chunk = 0;  chunk < msg_len;  chunk += sizeof md5) {
286 		int chunk_len;
287 		int i;
288 
289 		MD5Final(md5, &ctx);
290 
291 		if ((chunk_len = msg_len - chunk) > sizeof md5)
292 			chunk_len = sizeof md5;
293 		for (i = 0;  i < chunk_len;  i++)
294 			msg->u.body[chunk + i] ^= md5[i];
295 
296 		ctx = base_ctx;
297 		MD5Update(&ctx, md5, sizeof md5);
298 	}
299 }
300 
301 /*
302  * Return a dynamically allocated copy of the given server string.
303  * The copy is null-terminated.  If "len" is non-NULL, the length of
304  * the string (excluding the terminating null byte) is stored via it.
305  * Returns NULL on failure.  Empty strings are still allocated even
306  * though they have no content.
307  */
308 static void *
309 dup_str(struct tac_handle *h, const struct srvr_str *ss, size_t *len)
310 {
311 	unsigned char *p;
312 
313 	if ((p = (unsigned char *)xmalloc(h, ss->len + 1)) == NULL)
314 		return NULL;
315 	if (ss->data != NULL && ss->len != 0)
316 		memcpy(p, ss->data, ss->len);
317 	p[ss->len] = '\0';
318 	if (len != NULL)
319 		*len = ss->len;
320 	return p;
321 }
322 
323 static int
324 establish_connection(struct tac_handle *h)
325 {
326 	int i;
327 
328 	if (h->fd >= 0)		/* Already connected. */
329 		return 0;
330 	if (h->num_servers == 0) {
331 		generr(h, "No TACACS+ servers specified");
332 		return -1;
333 	}
334 	/*
335          * Try the servers round-robin.  We begin with the one that
336          * worked for us the last time.  That way, once we find a good
337          * server, we won't waste any more time trying the bad ones.
338 	 */
339 	for (i = 0;  i < h->num_servers;  i++) {
340 		if (conn_server(h) == 0) {
341 			h->single_connect = (h->servers[h->cur_server].flags &
342 			    TAC_SRVR_SINGLE_CONNECT) != 0;
343 			return 0;
344 		}
345 		if (++h->cur_server >= h->num_servers)	/* Wrap around */
346 			h->cur_server = 0;
347 	}
348 	/* Just return whatever error was last reported by conn_server(). */
349 	return -1;
350 }
351 
352 /*
353  * Free a client string, obliterating its contents first for security.
354  */
355 static void
356 free_str(struct clnt_str *cs)
357 {
358 	if (cs->data != NULL) {
359 		memset(cs->data, 0, cs->len);
360 		free(cs->data);
361 		cs->data = NULL;
362 		cs->len = 0;
363 	}
364 }
365 
366 static void
367 generr(struct tac_handle *h, const char *format, ...)
368 {
369 	va_list		 ap;
370 
371 	va_start(ap, format);
372 	vsnprintf(h->errmsg, ERRSIZE, format, ap);
373 	va_end(ap);
374 }
375 
376 static void
377 gen_session_id(struct tac_msg *msg)
378 {
379 	int r;
380 
381 	r = random();
382 	msg->session_id[0] = r >> 8;
383 	msg->session_id[1] = r;
384 	r = random();
385 	msg->session_id[2] = r >> 8;
386 	msg->session_id[3] = r;
387 }
388 
389 /*
390  * Verify that we are exactly at the end of the response message.
391  * Returns 0 on success, -1 on failure.
392  */
393 static int
394 get_srvr_end(struct tac_handle *h)
395 {
396 	if (h->srvr_pos != ntohl(h->response.length)) {
397 		generr(h, "Invalid length field in response from server");
398 		return -1;
399 	}
400 	return 0;
401 }
402 
403 static int
404 get_srvr_str(struct tac_handle *h, struct srvr_str *ss, size_t len)
405 {
406 	if (h->srvr_pos + len > ntohl(h->response.length)) {
407 		generr(h, "Invalid length field in response from server");
408 		return -1;
409 	}
410 	ss->data = len != 0 ? h->response.u.body + h->srvr_pos : NULL;
411 	ss->len = len;
412 	h->srvr_pos += len;
413 	return 0;
414 }
415 
416 static void
417 init_clnt_str(struct clnt_str *cs)
418 {
419 	cs->data = NULL;
420 	cs->len = 0;
421 }
422 
423 static void
424 init_srvr_str(struct srvr_str *ss)
425 {
426 	ss->data = NULL;
427 	ss->len = 0;
428 }
429 
430 static int
431 read_timed(struct tac_handle *h, void *buf, size_t len,
432     const struct timeval *deadline)
433 {
434 	char *ptr;
435 
436 	ptr = (char *)buf;
437 	while (len > 0) {
438 		int n;
439 
440 		n = read(h->fd, ptr, len);
441 		if (n == -1) {
442 			struct timeval tv;
443 			int nfds;
444 
445 			if (errno != EAGAIN) {
446 				generr(h, "Network read error: %s",
447 				    strerror(errno));
448 				return -1;
449 			}
450 
451 			/* Wait until we can read more data. */
452 			gettimeofday(&tv, NULL);
453 			timersub(deadline, &tv, &tv);
454 			if (tv.tv_sec >= 0) {
455 				fd_set rfds;
456 
457 				FD_ZERO(&rfds);
458 				FD_SET(h->fd, &rfds);
459 				nfds =
460 				    select(h->fd + 1, &rfds, NULL, NULL, &tv);
461 				if (nfds == -1) {
462 					generr(h, "select: %s",
463 					    strerror(errno));
464 					return -1;
465 				}
466 			} else
467 				nfds = 0;
468 			if (nfds == 0) {
469 				generr(h, "Network read timed out");
470 				return -1;
471 			}
472 		} else if (n == 0) {
473 			generr(h, "unexpected EOF from server");
474 			return -1;
475 		} else {
476 			ptr += n;
477 			len -= n;
478 		}
479 	}
480 	return 0;
481 }
482 
483 /*
484  * Receive a response from the server and decrypt it.  Returns 0 on
485  * success, or -1 on failure.
486  */
487 static int
488 recv_msg(struct tac_handle *h)
489 {
490 	struct timeval deadline;
491 	struct tac_msg *msg;
492 	size_t len;
493 
494 	msg = &h->response;
495 	gettimeofday(&deadline, NULL);
496 	deadline.tv_sec += h->servers[h->cur_server].timeout;
497 
498 	/* Read the message header and make sure it is reasonable. */
499 	if (read_timed(h, msg, HDRSIZE, &deadline) == -1)
500 		return -1;
501 	if (memcmp(msg->session_id, h->request.session_id,
502 	    sizeof msg->session_id) != 0) {
503 		generr(h, "Invalid session ID in received message");
504 		return -1;
505 	}
506 	if (msg->type != h->request.type) {
507 		generr(h, "Invalid type in received message");
508 		return -1;
509 	}
510 	len = ntohl(msg->length);
511 	if (len > BODYSIZE) {
512 		generr(h, "Received message too large");
513 		return -1;
514 	}
515 	if (msg->seq_no != ++h->last_seq_no) {
516 		generr(h, "Invalid sequence number in received message");
517 		return -1;
518 	}
519 
520 	/* Read the message body. */
521 	if (read_timed(h, msg->u.body, len, &deadline) == -1)
522 		return -1;
523 
524 	/* Decrypt it. */
525 	crypt_msg(h, msg);
526 
527 	/*
528 	 * Turn off single-connection mode if the server isn't amenable
529 	 * to it.
530 	 */
531 	if (!(msg->flags & TAC_SINGLE_CONNECT))
532 		h->single_connect = 0;
533 	return 0;
534 }
535 
536 static int
537 save_str(struct tac_handle *h, struct clnt_str *cs, const void *data,
538     size_t len)
539 {
540 	free_str(cs);
541 	if (data != NULL && len != 0) {
542 		if ((cs->data = xmalloc(h, len)) == NULL)
543 			return -1;
544 		cs->len = len;
545 		memcpy(cs->data, data, len);
546 	}
547 	return 0;
548 }
549 
550 /*
551  * Send the current request, after encrypting it.  Returns 0 on success,
552  * or -1 on failure.
553  */
554 static int
555 send_msg(struct tac_handle *h)
556 {
557 	struct timeval deadline;
558 	struct tac_msg *msg;
559 	char *ptr;
560 	int len;
561 
562 	if (h->last_seq_no & 1) {
563 		generr(h, "Attempt to send message out of sequence");
564 		return -1;
565 	}
566 
567 	msg = &h->request;
568 	msg->seq_no = ++h->last_seq_no;
569 	if (msg->seq_no == 1)
570 		gen_session_id(msg);
571 	crypt_msg(h, msg);
572 
573 	if (establish_connection(h) == -1)
574 		return -1;
575 
576 	if (h->single_connect)
577 		msg->flags |= TAC_SINGLE_CONNECT;
578 	else
579 		msg->flags &= ~TAC_SINGLE_CONNECT;
580 	gettimeofday(&deadline, NULL);
581 	deadline.tv_sec += h->servers[h->cur_server].timeout;
582 	len = HDRSIZE + ntohl(msg->length);
583 	ptr = (char *)msg;
584 	while (len > 0) {
585 		int n;
586 
587 		n = write(h->fd, ptr, len);
588 		if (n == -1) {
589 			struct timeval tv;
590 			int nfds;
591 
592 			if (errno != EAGAIN) {
593 				generr(h, "Network write error: %s",
594 				    strerror(errno));
595 				return -1;
596 			}
597 
598 			/* Wait until we can write more data. */
599 			gettimeofday(&tv, NULL);
600 			timersub(&deadline, &tv, &tv);
601 			if (tv.tv_sec >= 0) {
602 				fd_set wfds;
603 
604 				FD_ZERO(&wfds);
605 				FD_SET(h->fd, &wfds);
606 				nfds =
607 				    select(h->fd + 1, NULL, &wfds, NULL, &tv);
608 				if (nfds == -1) {
609 					generr(h, "select: %s",
610 					    strerror(errno));
611 					return -1;
612 				}
613 			} else
614 				nfds = 0;
615 			if (nfds == 0) {
616 				generr(h, "Network write timed out");
617 				return -1;
618 			}
619 		} else {
620 			ptr += n;
621 			len -= n;
622 		}
623 	}
624 	return 0;
625 }
626 
627 /*
628  * Destructively split a string into fields separated by white space.
629  * `#' at the beginning of a field begins a comment that extends to the
630  * end of the string.  Fields may be quoted with `"'.  Inside quoted
631  * strings, the backslash escapes `\"' and `\\' are honored.
632  *
633  * Pointers to up to the first maxfields fields are stored in the fields
634  * array.  Missing fields get NULL pointers.
635  *
636  * The return value is the actual number of fields parsed, and is always
637  * <= maxfields.
638  *
639  * On a syntax error, places a message in the msg string, and returns -1.
640  */
641 static int
642 split(char *str, char *fields[], int maxfields, char *msg, size_t msglen)
643 {
644 	char *p;
645 	int i;
646 	static const char ws[] = " \t";
647 
648 	for (i = 0;  i < maxfields;  i++)
649 		fields[i] = NULL;
650 	p = str;
651 	i = 0;
652 	while (*p != '\0') {
653 		p += strspn(p, ws);
654 		if (*p == '#' || *p == '\0')
655 			break;
656 		if (i >= maxfields) {
657 			snprintf(msg, msglen, "line has too many fields");
658 			return -1;
659 		}
660 		if (*p == '"') {
661 			char *dst;
662 
663 			dst = ++p;
664 			fields[i] = dst;
665 			while (*p != '"') {
666 				if (*p == '\\') {
667 					p++;
668 					if (*p != '"' && *p != '\\' &&
669 					    *p != '\0') {
670 						snprintf(msg, msglen,
671 						    "invalid `\\' escape");
672 						return -1;
673 					}
674 				}
675 				if (*p == '\0') {
676 					snprintf(msg, msglen,
677 					    "unterminated quoted string");
678 					return -1;
679 				}
680 				*dst++ = *p++;
681 			}
682 			*dst = '\0';
683 			p++;
684 			if (*p != '\0' && strspn(p, ws) == 0) {
685 				snprintf(msg, msglen, "quoted string not"
686 				    " followed by white space");
687 				return -1;
688 			}
689 		} else {
690 			fields[i] = p;
691 			p += strcspn(p, ws);
692 			if (*p != '\0')
693 				*p++ = '\0';
694 		}
695 		i++;
696 	}
697 	return i;
698 }
699 
700 int
701 tac_add_server(struct tac_handle *h, const char *host, int port,
702     const char *secret, int timeout, int flags)
703 {
704 	struct tac_server *srvp;
705 
706 	if (h->num_servers >= MAXSERVERS) {
707 		generr(h, "Too many TACACS+ servers specified");
708 		return -1;
709 	}
710 	srvp = &h->servers[h->num_servers];
711 
712 	memset(&srvp->addr, 0, sizeof srvp->addr);
713 	srvp->addr.sin_len = sizeof srvp->addr;
714 	srvp->addr.sin_family = AF_INET;
715 	if (!inet_aton(host, &srvp->addr.sin_addr)) {
716 		struct hostent *hent;
717 
718 		if ((hent = gethostbyname(host)) == NULL) {
719 			generr(h, "%s: host not found", host);
720 			return -1;
721 		}
722 		memcpy(&srvp->addr.sin_addr, hent->h_addr,
723 		    sizeof srvp->addr.sin_addr);
724 	}
725 	srvp->addr.sin_port = htons(port != 0 ? port : TACPLUS_PORT);
726 	if ((srvp->secret = xstrdup(h, secret)) == NULL)
727 		return -1;
728 	srvp->timeout = timeout;
729 	srvp->flags = flags;
730 	h->num_servers++;
731 	return 0;
732 }
733 
734 void
735 tac_close(struct tac_handle *h)
736 {
737 	int srv;
738 
739 	if (h->fd != -1)
740 		close(h->fd);
741 	for (srv = 0;  srv < h->num_servers;  srv++) {
742 		memset(h->servers[srv].secret, 0,
743 		    strlen(h->servers[srv].secret));
744 		free(h->servers[srv].secret);
745 	}
746 	free_str(&h->user);
747 	free_str(&h->port);
748 	free_str(&h->rem_addr);
749 	free_str(&h->data);
750 	free_str(&h->user_msg);
751 	free(h);
752 }
753 
754 int
755 tac_config(struct tac_handle *h, const char *path)
756 {
757 	FILE *fp;
758 	char buf[MAXCONFLINE];
759 	int linenum;
760 	int retval;
761 
762 	if (path == NULL)
763 		path = PATH_TACPLUS_CONF;
764 	if ((fp = fopen(path, "r")) == NULL) {
765 		generr(h, "Cannot open \"%s\": %s", path, strerror(errno));
766 		return -1;
767 	}
768 	retval = 0;
769 	linenum = 0;
770 	while (fgets(buf, sizeof buf, fp) != NULL) {
771 		int len;
772 		char *fields[4];
773 		int nfields;
774 		char msg[ERRSIZE];
775 		char *host, *res;
776 		char *port_str;
777 		char *secret;
778 		char *timeout_str;
779 		char *options_str;
780 		char *end;
781 		unsigned long timeout;
782 		int port;
783 		int options;
784 
785 		linenum++;
786 		len = strlen(buf);
787 		/* We know len > 0, else fgets would have returned NULL. */
788 		if (buf[len - 1] != '\n') {
789 			if (len == sizeof buf - 1)
790 				generr(h, "%s:%d: line too long", path,
791 				    linenum);
792 			else
793 				generr(h, "%s:%d: missing newline", path,
794 				    linenum);
795 			retval = -1;
796 			break;
797 		}
798 		buf[len - 1] = '\0';
799 
800 		/* Extract the fields from the line. */
801 		nfields = split(buf, fields, 4, msg, sizeof msg);
802 		if (nfields == -1) {
803 			generr(h, "%s:%d: %s", path, linenum, msg);
804 			retval = -1;
805 			break;
806 		}
807 		if (nfields == 0)
808 			continue;
809 		if (nfields < 2) {
810 			generr(h, "%s:%d: missing shared secret", path,
811 			    linenum);
812 			retval = -1;
813 			break;
814 		}
815 		host = fields[0];
816 		secret = fields[1];
817 		timeout_str = fields[2];
818 		options_str = fields[3];
819 
820 		/* Parse and validate the fields. */
821 		res = host;
822 		host = strsep(&res, ":");
823 		port_str = strsep(&res, ":");
824 		if (port_str != NULL) {
825 			port = strtoul(port_str, &end, 10);
826 			if (port_str[0] == '\0' || *end != '\0') {
827 				generr(h, "%s:%d: invalid port", path,
828 				    linenum);
829 				retval = -1;
830 				break;
831 			}
832 		} else
833 			port = 0;
834 		if (timeout_str != NULL) {
835 			timeout = strtoul(timeout_str, &end, 10);
836 			if (timeout_str[0] == '\0' || *end != '\0') {
837 				generr(h, "%s:%d: invalid timeout", path,
838 				    linenum);
839 				retval = -1;
840 				break;
841 			}
842 		} else
843 			timeout = TIMEOUT;
844 		options = 0;
845 		if (options_str != NULL) {
846 			if (strcmp(options_str, "single-connection") == 0)
847 				options |= TAC_SRVR_SINGLE_CONNECT;
848 			else {
849 				generr(h, "%s:%d: invalid option \"%s\"",
850 				    path, linenum, options_str);
851 				retval = -1;
852 				break;
853 			}
854 		};
855 
856 		if (tac_add_server(h, host, port, secret, timeout,
857 		    options) == -1) {
858 			char msg[ERRSIZE];
859 
860 			strcpy(msg, h->errmsg);
861 			generr(h, "%s:%d: %s", path, linenum, msg);
862 			retval = -1;
863 			break;
864 		}
865 	}
866 	/* Clear out the buffer to wipe a possible copy of a shared secret */
867 	memset(buf, 0, sizeof buf);
868 	fclose(fp);
869 	return retval;
870 }
871 
872 int
873 tac_create_authen(struct tac_handle *h, int action, int type, int service)
874 {
875 	struct tac_msg *msg;
876 	struct tac_authen_start *as;
877 
878 	h->last_seq_no = 0;
879 
880 	msg = &h->request;
881 	msg->type = TAC_AUTHEN;
882 	msg->version = authen_version(action, type);
883 	msg->flags = 0;
884 
885 	as = &msg->u.authen_start;
886 	as->action = action;
887 	as->priv_lvl = TAC_PRIV_LVL_USER;
888 	as->authen_type = type;
889 	as->service = service;
890 
891 	free_str(&h->user);
892 	free_str(&h->port);
893 	free_str(&h->rem_addr);
894 	free_str(&h->data);
895 	free_str(&h->user_msg);
896 
897 	/* XXX - more to do */
898 	return 0;
899 }
900 
901 void *
902 tac_get_data(struct tac_handle *h, size_t *len)
903 {
904 	return dup_str(h, &h->srvr_data, len);
905 }
906 
907 char *
908 tac_get_msg(struct tac_handle *h)
909 {
910 	return (char *)dup_str(h, &h->srvr_msg, NULL);
911 }
912 
913 /*
914  * Create and initialize a tac_handle structure, and return it to the
915  * caller.  Can fail only if the necessary memory cannot be allocated.
916  * In that case, it returns NULL.
917  */
918 struct tac_handle *
919 tac_open(void)
920 {
921 	struct tac_handle *h;
922 
923 	h = (struct tac_handle *)malloc(sizeof(struct tac_handle));
924 	if (h != NULL) {
925 		h->fd = -1;
926 		h->num_servers = 0;
927 		h->cur_server = 0;
928 		h->errmsg[0] = '\0';
929 		init_clnt_str(&h->user);
930 		init_clnt_str(&h->port);
931 		init_clnt_str(&h->rem_addr);
932 		init_clnt_str(&h->data);
933 		init_clnt_str(&h->user_msg);
934 		init_srvr_str(&h->srvr_msg);
935 		init_srvr_str(&h->srvr_data);
936 		srandomdev();
937 	}
938 	return h;
939 }
940 
941 int
942 tac_send_authen(struct tac_handle *h)
943 {
944 	struct tac_authen_reply *ar;
945 
946 	if (h->last_seq_no == 0) {	/* Authentication START packet */
947 		struct tac_authen_start *as;
948 
949 		as = &h->request.u.authen_start;
950 		h->request.length =
951 		    htonl(offsetof(struct tac_authen_start, rest[0]));
952 		if (add_str_8(h, &as->user_len, &h->user) == -1 ||
953 		    add_str_8(h, &as->port_len, &h->port) == -1 ||
954 		    add_str_8(h, &as->rem_addr_len, &h->rem_addr) == -1 ||
955 		    add_str_8(h, &as->data_len, &h->data) == -1)
956 			return -1;
957 	} else {			/* Authentication CONTINUE packet */
958 		struct tac_authen_cont *ac;
959 
960 		ac = &h->request.u.authen_cont;
961 		ac->flags = 0;
962 		h->request.length =
963 		    htonl(offsetof(struct tac_authen_cont, rest[0]));
964 		if (add_str_16(h, &ac->user_msg_len, &h->user_msg) == -1 ||
965 		    add_str_16(h, &ac->data_len, &h->data) == -1)
966 			return -1;
967 	}
968 
969 	/* Send the message and retrieve the reply. */
970 	if (send_msg(h) == -1 || recv_msg(h) == -1)
971 		return -1;
972 
973 	/* Scan the optional fields in the reply. */
974 	ar = &h->response.u.authen_reply;
975 	h->srvr_pos = offsetof(struct tac_authen_reply, rest[0]);
976 	if (get_srvr_str(h, &h->srvr_msg, ntohs(ar->msg_len)) == -1 ||
977 	    get_srvr_str(h, &h->srvr_data, ntohs(ar->data_len)) == -1 ||
978 	    get_srvr_end(h) == -1)
979 		return -1;
980 
981 	if (!h->single_connect &&
982 	    ar->status != TAC_AUTHEN_STATUS_GETDATA &&
983 	    ar->status != TAC_AUTHEN_STATUS_GETUSER &&
984 	    ar->status != TAC_AUTHEN_STATUS_GETPASS)
985 		close_connection(h);
986 
987 	return ar->flags << 8 | ar->status;
988 }
989 
990 int
991 tac_set_rem_addr(struct tac_handle *h, const char *addr)
992 {
993 	return save_str(h, &h->rem_addr, addr, addr != NULL ? strlen(addr) : 0);
994 }
995 
996 int
997 tac_set_data(struct tac_handle *h, const void *data, size_t data_len)
998 {
999 	return save_str(h, &h->data, data, data_len);
1000 }
1001 
1002 int
1003 tac_set_msg(struct tac_handle *h, const char *msg)
1004 {
1005 	return save_str(h, &h->user_msg, msg, msg != NULL ? strlen(msg) : 0);
1006 }
1007 
1008 int
1009 tac_set_port(struct tac_handle *h, const char *port)
1010 {
1011 	return save_str(h, &h->port, port, port != NULL ? strlen(port) : 0);
1012 }
1013 
1014 int
1015 tac_set_priv(struct tac_handle *h, int priv)
1016 {
1017 	if (!(TAC_PRIV_LVL_MIN <= priv && priv <= TAC_PRIV_LVL_MAX)) {
1018 		generr(h, "Attempt to set invalid privilege level");
1019 		return -1;
1020 	}
1021 	h->request.u.authen_start.priv_lvl = priv;
1022 	return 0;
1023 }
1024 
1025 int
1026 tac_set_user(struct tac_handle *h, const char *user)
1027 {
1028 	return save_str(h, &h->user, user, user != NULL ? strlen(user) : 0);
1029 }
1030 
1031 const char *
1032 tac_strerror(struct tac_handle *h)
1033 {
1034 	return h->errmsg;
1035 }
1036 
1037 static void *
1038 xmalloc(struct tac_handle *h, size_t size)
1039 {
1040 	void *r;
1041 
1042 	if ((r = malloc(size)) == NULL)
1043 		generr(h, "Out of memory");
1044 	return r;
1045 }
1046 
1047 static char *
1048 xstrdup(struct tac_handle *h, const char *s)
1049 {
1050 	char *r;
1051 
1052 	if ((r = strdup(s)) == NULL)
1053 		generr(h, "Out of memory");
1054 	return r;
1055 }
1056