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