taclib.c revision 84222
141120Sjdp/*- 241120Sjdp * Copyright 1998 Juniper Networks, Inc. 341120Sjdp * All rights reserved. 441120Sjdp * 541120Sjdp * Redistribution and use in source and binary forms, with or without 641120Sjdp * modification, are permitted provided that the following conditions 741120Sjdp * are met: 841120Sjdp * 1. Redistributions of source code must retain the above copyright 941120Sjdp * notice, this list of conditions and the following disclaimer. 1041120Sjdp * 2. Redistributions in binary form must reproduce the above copyright 1141120Sjdp * notice, this list of conditions and the following disclaimer in the 1241120Sjdp * documentation and/or other materials provided with the distribution. 1341120Sjdp * 1441120Sjdp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 1541120Sjdp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1641120Sjdp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1741120Sjdp * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 1841120Sjdp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 1941120Sjdp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2041120Sjdp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2141120Sjdp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2241120Sjdp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2341120Sjdp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2441120Sjdp * SUCH DAMAGE. 2541120Sjdp */ 2641120Sjdp 2784222Sdillon#include <sys/cdefs.h> 2884222Sdillon__FBSDID("$FreeBSD: head/lib/libtacplus/taclib.c 84222 2001-09-30 22:29:19Z dillon $"); 2984222Sdillon 3041120Sjdp#include <sys/types.h> 3141120Sjdp#include <sys/socket.h> 3241120Sjdp#include <sys/time.h> 3341120Sjdp#include <netinet/in.h> 3441120Sjdp#include <arpa/inet.h> 3541120Sjdp 3641120Sjdp#include <assert.h> 3741120Sjdp#include <errno.h> 3841120Sjdp#include <fcntl.h> 3941120Sjdp#include <md5.h> 4041120Sjdp#include <netdb.h> 4141120Sjdp#include <stdarg.h> 4241120Sjdp#include <stddef.h> 4341120Sjdp#include <stdio.h> 4441120Sjdp#include <stdlib.h> 4541120Sjdp#include <string.h> 4641120Sjdp#include <unistd.h> 4741120Sjdp 4841120Sjdp#include "taclib_private.h" 4941120Sjdp 5041120Sjdpstatic int add_str_8(struct tac_handle *, u_int8_t *, 5141120Sjdp struct clnt_str *); 5241120Sjdpstatic int add_str_16(struct tac_handle *, u_int16_t *, 5341120Sjdp struct clnt_str *); 5441120Sjdpstatic int authen_version(int, int); 5541120Sjdpstatic void close_connection(struct tac_handle *); 5641120Sjdpstatic int conn_server(struct tac_handle *); 5741120Sjdpstatic void crypt_msg(struct tac_handle *, struct tac_msg *); 5841120Sjdpstatic void *dup_str(struct tac_handle *, const struct srvr_str *, 5941120Sjdp size_t *); 6041120Sjdpstatic int establish_connection(struct tac_handle *); 6141120Sjdpstatic void free_str(struct clnt_str *); 6241120Sjdpstatic void generr(struct tac_handle *, const char *, ...) 6341120Sjdp __printflike(2, 3); 6441120Sjdpstatic void gen_session_id(struct tac_msg *); 6541120Sjdpstatic int get_srvr_end(struct tac_handle *); 6641120Sjdpstatic int get_srvr_str(struct tac_handle *, struct srvr_str *, 6741120Sjdp size_t); 6841120Sjdpstatic void init_clnt_str(struct clnt_str *); 6941120Sjdpstatic void init_srvr_str(struct srvr_str *); 7041120Sjdpstatic int read_timed(struct tac_handle *, void *, size_t, 7141120Sjdp const struct timeval *); 7241120Sjdpstatic int recv_msg(struct tac_handle *); 7341120Sjdpstatic int save_str(struct tac_handle *, struct clnt_str *, 7441120Sjdp const void *, size_t); 7541120Sjdpstatic int send_msg(struct tac_handle *); 7641120Sjdpstatic int split(char *, char *[], int, char *, size_t); 7741120Sjdpstatic void *xmalloc(struct tac_handle *, size_t); 7841120Sjdpstatic char *xstrdup(struct tac_handle *, const char *); 7941120Sjdp 8041120Sjdp/* 8141120Sjdp * Append some optional data to the current request, and store its 8241120Sjdp * length into the 8-bit field referenced by "fld". Returns 0 on 8341120Sjdp * success, or -1 on failure. 8441120Sjdp * 8541120Sjdp * This function also frees the "cs" string data and initializes it 8641120Sjdp * for the next time. 8741120Sjdp */ 8841120Sjdpstatic int 8941120Sjdpadd_str_8(struct tac_handle *h, u_int8_t *fld, struct clnt_str *cs) 9041120Sjdp{ 9141120Sjdp u_int16_t len; 9241120Sjdp 9341120Sjdp if (add_str_16(h, &len, cs) == -1) 9441120Sjdp return -1; 9541120Sjdp len = ntohs(len); 9641120Sjdp if (len > 0xff) { 9741120Sjdp generr(h, "Field too long"); 9841120Sjdp return -1; 9941120Sjdp } 10041120Sjdp *fld = len; 10141120Sjdp return 0; 10241120Sjdp} 10341120Sjdp 10441120Sjdp/* 10541120Sjdp * Append some optional data to the current request, and store its 10641120Sjdp * length into the 16-bit field (network byte order) referenced by 10741120Sjdp * "fld". Returns 0 on success, or -1 on failure. 10841120Sjdp * 10941120Sjdp * This function also frees the "cs" string data and initializes it 11041120Sjdp * for the next time. 11141120Sjdp */ 11241120Sjdpstatic int 11341120Sjdpadd_str_16(struct tac_handle *h, u_int16_t *fld, struct clnt_str *cs) 11441120Sjdp{ 11541120Sjdp size_t len; 11641120Sjdp 11741120Sjdp len = cs->len; 11841120Sjdp if (cs->data == NULL) 11941120Sjdp len = 0; 12041120Sjdp if (len != 0) { 12141120Sjdp int offset; 12241120Sjdp 12341120Sjdp if (len > 0xffff) { 12441120Sjdp generr(h, "Field too long"); 12541120Sjdp return -1; 12641120Sjdp } 12741120Sjdp offset = ntohl(h->request.length); 12841120Sjdp if (offset + len > BODYSIZE) { 12941120Sjdp generr(h, "Message too long"); 13041120Sjdp return -1; 13141120Sjdp } 13241120Sjdp memcpy(h->request.u.body + offset, cs->data, len); 13341120Sjdp h->request.length = htonl(offset + len); 13441120Sjdp } 13541120Sjdp *fld = htons(len); 13641120Sjdp free_str(cs); 13741120Sjdp return 0; 13841120Sjdp} 13941120Sjdp 14041120Sjdpstatic int 14141120Sjdpauthen_version(int action, int type) 14241120Sjdp{ 14341120Sjdp int minor; 14441120Sjdp 14541120Sjdp switch (action) { 14641120Sjdp 14741120Sjdp case TAC_AUTHEN_LOGIN: 14841120Sjdp switch (type) { 14941120Sjdp 15041120Sjdp case TAC_AUTHEN_TYPE_PAP: 15141120Sjdp case TAC_AUTHEN_TYPE_CHAP: 15241120Sjdp case TAC_AUTHEN_TYPE_MSCHAP: 15341120Sjdp case TAC_AUTHEN_TYPE_ARAP: 15441120Sjdp minor = 1; 15541120Sjdp break; 15641120Sjdp 15741120Sjdp default: 15841120Sjdp minor = 0; 15941120Sjdp break; 16041120Sjdp } 16141120Sjdp break; 16241120Sjdp 16341120Sjdp case TAC_AUTHEN_SENDAUTH: 16441120Sjdp minor = 1; 16541120Sjdp break; 16641120Sjdp 16741120Sjdp default: 16841120Sjdp minor = 0; 16941120Sjdp break; 17041120Sjdp }; 17141120Sjdp 17241120Sjdp return TAC_VER_MAJOR << 4 | minor; 17341120Sjdp} 17441120Sjdp 17541120Sjdpstatic void 17641120Sjdpclose_connection(struct tac_handle *h) 17741120Sjdp{ 17841120Sjdp if (h->fd != -1) { 17941120Sjdp close(h->fd); 18041120Sjdp h->fd = -1; 18141120Sjdp } 18241120Sjdp} 18341120Sjdp 18441120Sjdpstatic int 18541120Sjdpconn_server(struct tac_handle *h) 18641120Sjdp{ 18741120Sjdp const struct tac_server *srvp = &h->servers[h->cur_server]; 18841120Sjdp int flags; 18941120Sjdp 19041120Sjdp if ((h->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 19141120Sjdp generr(h, "Cannot create socket: %s", strerror(errno)); 19241120Sjdp return -1; 19341120Sjdp } 19441120Sjdp if ((flags = fcntl(h->fd, F_GETFL, 0)) == -1 || 19541120Sjdp fcntl(h->fd, F_SETFL, flags | O_NONBLOCK) == -1) { 19641120Sjdp generr(h, "Cannot set non-blocking mode on socket: %s", 19741120Sjdp strerror(errno)); 19841120Sjdp close(h->fd); 19941120Sjdp h->fd = -1; 20041120Sjdp return -1; 20141120Sjdp } 20241120Sjdp if (connect(h->fd, (struct sockaddr *)&srvp->addr, 20341120Sjdp sizeof srvp->addr) == 0) 20441120Sjdp return 0; 20541120Sjdp 20641120Sjdp if (errno == EINPROGRESS) { 20741120Sjdp fd_set wfds; 20841120Sjdp struct timeval tv; 20941120Sjdp int nfds; 21041120Sjdp struct sockaddr peer; 21141120Sjdp int peerlen; 21241120Sjdp int err; 21341120Sjdp int errlen; 21441120Sjdp 21541120Sjdp /* Wait for the connection to complete. */ 21641120Sjdp FD_ZERO(&wfds); 21741120Sjdp FD_SET(h->fd, &wfds); 21841120Sjdp tv.tv_sec = srvp->timeout; 21941120Sjdp tv.tv_usec = 0; 22041120Sjdp nfds = select(h->fd + 1, NULL, &wfds, NULL, &tv); 22141120Sjdp if (nfds == -1) { 22241120Sjdp generr(h, "select: %s", strerror(errno)); 22341120Sjdp close(h->fd); 22441120Sjdp h->fd = -1; 22541120Sjdp return -1; 22641120Sjdp } 22741120Sjdp if (nfds == 0) { 22841120Sjdp generr(h, "connect: timed out"); 22941120Sjdp close(h->fd); 23041120Sjdp h->fd = -1; 23141120Sjdp return -1; 23241120Sjdp } 23341120Sjdp 23441120Sjdp /* See whether we are connected now. */ 23541120Sjdp peerlen = sizeof peer; 23641120Sjdp if (getpeername(h->fd, &peer, &peerlen) == 0) 23741120Sjdp return 0; 23841120Sjdp 23941120Sjdp if (errno != ENOTCONN) { 24041120Sjdp generr(h, "getpeername: %s", strerror(errno)); 24141120Sjdp close(h->fd); 24241120Sjdp h->fd = -1; 24341120Sjdp return -1; 24441120Sjdp } 24541120Sjdp 24641120Sjdp /* Find out why the connect failed. */ 24741120Sjdp errlen = sizeof err; 24841120Sjdp getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &err, &errlen); 24941120Sjdp errno = err; 25041120Sjdp } 25141120Sjdp generr(h, "connect: %s", strerror(errno)); 25241120Sjdp close(h->fd); 25341120Sjdp h->fd = -1; 25441120Sjdp return -1; 25541120Sjdp} 25641120Sjdp 25741120Sjdp/* 25841120Sjdp * Encrypt or decrypt a message. The operations are symmetrical. 25941120Sjdp */ 26041120Sjdpstatic void 26141120Sjdpcrypt_msg(struct tac_handle *h, struct tac_msg *msg) 26241120Sjdp{ 26341120Sjdp const char *secret; 26441120Sjdp MD5_CTX base_ctx; 26541120Sjdp MD5_CTX ctx; 26641120Sjdp unsigned char md5[16]; 26741120Sjdp int chunk; 26841120Sjdp int msg_len; 26941120Sjdp 27041120Sjdp secret = h->servers[h->cur_server].secret; 27141120Sjdp if (secret[0] == '\0') 27241120Sjdp msg->flags |= TAC_UNENCRYPTED; 27341120Sjdp if (msg->flags & TAC_UNENCRYPTED) 27441120Sjdp return; 27541120Sjdp 27641120Sjdp msg_len = ntohl(msg->length); 27741120Sjdp 27841120Sjdp MD5Init(&base_ctx); 27941120Sjdp MD5Update(&base_ctx, msg->session_id, sizeof msg->session_id); 28041120Sjdp MD5Update(&base_ctx, secret, strlen(secret)); 28141120Sjdp MD5Update(&base_ctx, &msg->version, sizeof msg->version); 28241120Sjdp MD5Update(&base_ctx, &msg->seq_no, sizeof msg->seq_no); 28341120Sjdp 28441120Sjdp ctx = base_ctx; 28541120Sjdp for (chunk = 0; chunk < msg_len; chunk += sizeof md5) { 28641120Sjdp int chunk_len; 28741120Sjdp int i; 28841120Sjdp 28941120Sjdp MD5Final(md5, &ctx); 29041120Sjdp 29141120Sjdp if ((chunk_len = msg_len - chunk) > sizeof md5) 29241120Sjdp chunk_len = sizeof md5; 29341120Sjdp for (i = 0; i < chunk_len; i++) 29441120Sjdp msg->u.body[chunk + i] ^= md5[i]; 29541120Sjdp 29641120Sjdp ctx = base_ctx; 29741120Sjdp MD5Update(&ctx, md5, sizeof md5); 29841120Sjdp } 29941120Sjdp} 30041120Sjdp 30141120Sjdp/* 30241120Sjdp * Return a dynamically allocated copy of the given server string. 30341120Sjdp * The copy is null-terminated. If "len" is non-NULL, the length of 30441120Sjdp * the string (excluding the terminating null byte) is stored via it. 30541120Sjdp * Returns NULL on failure. Empty strings are still allocated even 30641120Sjdp * though they have no content. 30741120Sjdp */ 30841120Sjdpstatic void * 30941120Sjdpdup_str(struct tac_handle *h, const struct srvr_str *ss, size_t *len) 31041120Sjdp{ 31141120Sjdp unsigned char *p; 31241120Sjdp 31341120Sjdp if ((p = (unsigned char *)xmalloc(h, ss->len + 1)) == NULL) 31441120Sjdp return NULL; 31541120Sjdp if (ss->data != NULL && ss->len != 0) 31641120Sjdp memcpy(p, ss->data, ss->len); 31741120Sjdp p[ss->len] = '\0'; 31841120Sjdp if (len != NULL) 31941120Sjdp *len = ss->len; 32041120Sjdp return p; 32141120Sjdp} 32241120Sjdp 32341120Sjdpstatic int 32441120Sjdpestablish_connection(struct tac_handle *h) 32541120Sjdp{ 32641120Sjdp int i; 32741120Sjdp 32841120Sjdp if (h->fd >= 0) /* Already connected. */ 32941120Sjdp return 0; 33041120Sjdp if (h->num_servers == 0) { 33141120Sjdp generr(h, "No TACACS+ servers specified"); 33241120Sjdp return -1; 33341120Sjdp } 33441120Sjdp /* 33541120Sjdp * Try the servers round-robin. We begin with the one that 33641120Sjdp * worked for us the last time. That way, once we find a good 33741120Sjdp * server, we won't waste any more time trying the bad ones. 33841120Sjdp */ 33941120Sjdp for (i = 0; i < h->num_servers; i++) { 34041120Sjdp if (conn_server(h) == 0) { 34141120Sjdp h->single_connect = (h->servers[h->cur_server].flags & 34241120Sjdp TAC_SRVR_SINGLE_CONNECT) != 0; 34341120Sjdp return 0; 34441120Sjdp } 34541120Sjdp if (++h->cur_server >= h->num_servers) /* Wrap around */ 34641120Sjdp h->cur_server = 0; 34741120Sjdp } 34841120Sjdp /* Just return whatever error was last reported by conn_server(). */ 34941120Sjdp return -1; 35041120Sjdp} 35141120Sjdp 35241120Sjdp/* 35341120Sjdp * Free a client string, obliterating its contents first for security. 35441120Sjdp */ 35541120Sjdpstatic void 35641120Sjdpfree_str(struct clnt_str *cs) 35741120Sjdp{ 35841120Sjdp if (cs->data != NULL) { 35941120Sjdp memset(cs->data, 0, cs->len); 36041120Sjdp free(cs->data); 36141120Sjdp cs->data = NULL; 36241120Sjdp cs->len = 0; 36341120Sjdp } 36441120Sjdp} 36541120Sjdp 36641120Sjdpstatic void 36741120Sjdpgenerr(struct tac_handle *h, const char *format, ...) 36841120Sjdp{ 36941120Sjdp va_list ap; 37041120Sjdp 37141120Sjdp va_start(ap, format); 37241120Sjdp vsnprintf(h->errmsg, ERRSIZE, format, ap); 37341120Sjdp va_end(ap); 37441120Sjdp} 37541120Sjdp 37641120Sjdpstatic void 37741120Sjdpgen_session_id(struct tac_msg *msg) 37841120Sjdp{ 37941120Sjdp int r; 38041120Sjdp 38141120Sjdp r = random(); 38241120Sjdp msg->session_id[0] = r >> 8; 38341120Sjdp msg->session_id[1] = r; 38441120Sjdp r = random(); 38541120Sjdp msg->session_id[2] = r >> 8; 38641120Sjdp msg->session_id[3] = r; 38741120Sjdp} 38841120Sjdp 38941120Sjdp/* 39041120Sjdp * Verify that we are exactly at the end of the response message. 39141120Sjdp * Returns 0 on success, -1 on failure. 39241120Sjdp */ 39341120Sjdpstatic int 39441120Sjdpget_srvr_end(struct tac_handle *h) 39541120Sjdp{ 39641120Sjdp if (h->srvr_pos != ntohl(h->response.length)) { 39741120Sjdp generr(h, "Invalid length field in response from server"); 39841120Sjdp return -1; 39941120Sjdp } 40041120Sjdp return 0; 40141120Sjdp} 40241120Sjdp 40341120Sjdpstatic int 40441120Sjdpget_srvr_str(struct tac_handle *h, struct srvr_str *ss, size_t len) 40541120Sjdp{ 40641120Sjdp if (h->srvr_pos + len > ntohl(h->response.length)) { 40741120Sjdp generr(h, "Invalid length field in response from server"); 40841120Sjdp return -1; 40941120Sjdp } 41041120Sjdp ss->data = len != 0 ? h->response.u.body + h->srvr_pos : NULL; 41141120Sjdp ss->len = len; 41241120Sjdp h->srvr_pos += len; 41341120Sjdp return 0; 41441120Sjdp} 41541120Sjdp 41641120Sjdpstatic void 41741120Sjdpinit_clnt_str(struct clnt_str *cs) 41841120Sjdp{ 41941120Sjdp cs->data = NULL; 42041120Sjdp cs->len = 0; 42141120Sjdp} 42241120Sjdp 42341120Sjdpstatic void 42441120Sjdpinit_srvr_str(struct srvr_str *ss) 42541120Sjdp{ 42641120Sjdp ss->data = NULL; 42741120Sjdp ss->len = 0; 42841120Sjdp} 42941120Sjdp 43041120Sjdpstatic int 43141120Sjdpread_timed(struct tac_handle *h, void *buf, size_t len, 43241120Sjdp const struct timeval *deadline) 43341120Sjdp{ 43441120Sjdp char *ptr; 43541120Sjdp 43641120Sjdp ptr = (char *)buf; 43741120Sjdp while (len > 0) { 43841120Sjdp int n; 43941120Sjdp 44041120Sjdp n = read(h->fd, ptr, len); 44141120Sjdp if (n == -1) { 44241120Sjdp struct timeval tv; 44341120Sjdp int nfds; 44441120Sjdp 44541120Sjdp if (errno != EAGAIN) { 44641120Sjdp generr(h, "Network read error: %s", 44741120Sjdp strerror(errno)); 44841120Sjdp return -1; 44941120Sjdp } 45041120Sjdp 45141120Sjdp /* Wait until we can read more data. */ 45241120Sjdp gettimeofday(&tv, NULL); 45341120Sjdp timersub(deadline, &tv, &tv); 45441120Sjdp if (tv.tv_sec >= 0) { 45541120Sjdp fd_set rfds; 45641120Sjdp 45741120Sjdp FD_ZERO(&rfds); 45841120Sjdp FD_SET(h->fd, &rfds); 45941120Sjdp nfds = 46041120Sjdp select(h->fd + 1, &rfds, NULL, NULL, &tv); 46141120Sjdp if (nfds == -1) { 46241120Sjdp generr(h, "select: %s", 46341120Sjdp strerror(errno)); 46441120Sjdp return -1; 46541120Sjdp } 46641120Sjdp } else 46741120Sjdp nfds = 0; 46841120Sjdp if (nfds == 0) { 46941120Sjdp generr(h, "Network read timed out"); 47041120Sjdp return -1; 47141120Sjdp } 47241120Sjdp } else if (n == 0) { 47341120Sjdp generr(h, "unexpected EOF from server"); 47441120Sjdp return -1; 47541120Sjdp } else { 47641120Sjdp ptr += n; 47741120Sjdp len -= n; 47841120Sjdp } 47941120Sjdp } 48041120Sjdp return 0; 48141120Sjdp} 48241120Sjdp 48341120Sjdp/* 48441120Sjdp * Receive a response from the server and decrypt it. Returns 0 on 48541120Sjdp * success, or -1 on failure. 48641120Sjdp */ 48741120Sjdpstatic int 48841120Sjdprecv_msg(struct tac_handle *h) 48941120Sjdp{ 49041120Sjdp struct timeval deadline; 49141120Sjdp struct tac_msg *msg; 49241120Sjdp size_t len; 49341120Sjdp 49441120Sjdp msg = &h->response; 49541120Sjdp gettimeofday(&deadline, NULL); 49641120Sjdp deadline.tv_sec += h->servers[h->cur_server].timeout; 49741120Sjdp 49841120Sjdp /* Read the message header and make sure it is reasonable. */ 49941120Sjdp if (read_timed(h, msg, HDRSIZE, &deadline) == -1) 50041120Sjdp return -1; 50141120Sjdp if (memcmp(msg->session_id, h->request.session_id, 50241120Sjdp sizeof msg->session_id) != 0) { 50341120Sjdp generr(h, "Invalid session ID in received message"); 50441120Sjdp return -1; 50541120Sjdp } 50641120Sjdp if (msg->type != h->request.type) { 50741120Sjdp generr(h, "Invalid type in received message"); 50841120Sjdp return -1; 50941120Sjdp } 51041120Sjdp len = ntohl(msg->length); 51141120Sjdp if (len > BODYSIZE) { 51241120Sjdp generr(h, "Received message too large"); 51341120Sjdp return -1; 51441120Sjdp } 51541120Sjdp if (msg->seq_no != ++h->last_seq_no) { 51641120Sjdp generr(h, "Invalid sequence number in received message"); 51741120Sjdp return -1; 51841120Sjdp } 51941120Sjdp 52041120Sjdp /* Read the message body. */ 52141120Sjdp if (read_timed(h, msg->u.body, len, &deadline) == -1) 52241120Sjdp return -1; 52341120Sjdp 52441120Sjdp /* Decrypt it. */ 52541120Sjdp crypt_msg(h, msg); 52641120Sjdp 52741120Sjdp /* 52841120Sjdp * Turn off single-connection mode if the server isn't amenable 52941120Sjdp * to it. 53041120Sjdp */ 53141120Sjdp if (!(msg->flags & TAC_SINGLE_CONNECT)) 53241120Sjdp h->single_connect = 0; 53341120Sjdp return 0; 53441120Sjdp} 53541120Sjdp 53641120Sjdpstatic int 53741120Sjdpsave_str(struct tac_handle *h, struct clnt_str *cs, const void *data, 53841120Sjdp size_t len) 53941120Sjdp{ 54041120Sjdp free_str(cs); 54141120Sjdp if (data != NULL && len != 0) { 54241120Sjdp if ((cs->data = xmalloc(h, len)) == NULL) 54341120Sjdp return -1; 54441120Sjdp cs->len = len; 54541120Sjdp memcpy(cs->data, data, len); 54641120Sjdp } 54741120Sjdp return 0; 54841120Sjdp} 54941120Sjdp 55041120Sjdp/* 55141120Sjdp * Send the current request, after encrypting it. Returns 0 on success, 55241120Sjdp * or -1 on failure. 55341120Sjdp */ 55441120Sjdpstatic int 55541120Sjdpsend_msg(struct tac_handle *h) 55641120Sjdp{ 55741120Sjdp struct timeval deadline; 55841120Sjdp struct tac_msg *msg; 55941120Sjdp char *ptr; 56041120Sjdp int len; 56141120Sjdp 56241120Sjdp if (h->last_seq_no & 1) { 56341120Sjdp generr(h, "Attempt to send message out of sequence"); 56441120Sjdp return -1; 56541120Sjdp } 56641120Sjdp 56741120Sjdp msg = &h->request; 56841120Sjdp msg->seq_no = ++h->last_seq_no; 56941120Sjdp if (msg->seq_no == 1) 57041120Sjdp gen_session_id(msg); 57141120Sjdp crypt_msg(h, msg); 57241120Sjdp 57341120Sjdp if (establish_connection(h) == -1) 57441120Sjdp return -1; 57541120Sjdp 57641120Sjdp if (h->single_connect) 57741120Sjdp msg->flags |= TAC_SINGLE_CONNECT; 57841120Sjdp else 57941120Sjdp msg->flags &= ~TAC_SINGLE_CONNECT; 58041120Sjdp gettimeofday(&deadline, NULL); 58141120Sjdp deadline.tv_sec += h->servers[h->cur_server].timeout; 58241120Sjdp len = HDRSIZE + ntohl(msg->length); 58341120Sjdp ptr = (char *)msg; 58441120Sjdp while (len > 0) { 58541120Sjdp int n; 58641120Sjdp 58741120Sjdp n = write(h->fd, ptr, len); 58841120Sjdp if (n == -1) { 58941120Sjdp struct timeval tv; 59041120Sjdp int nfds; 59141120Sjdp 59241120Sjdp if (errno != EAGAIN) { 59341120Sjdp generr(h, "Network write error: %s", 59441120Sjdp strerror(errno)); 59541120Sjdp return -1; 59641120Sjdp } 59741120Sjdp 59841120Sjdp /* Wait until we can write more data. */ 59941120Sjdp gettimeofday(&tv, NULL); 60041120Sjdp timersub(&deadline, &tv, &tv); 60141120Sjdp if (tv.tv_sec >= 0) { 60241120Sjdp fd_set wfds; 60341120Sjdp 60441120Sjdp FD_ZERO(&wfds); 60541120Sjdp FD_SET(h->fd, &wfds); 60641120Sjdp nfds = 60741120Sjdp select(h->fd + 1, NULL, &wfds, NULL, &tv); 60841120Sjdp if (nfds == -1) { 60941120Sjdp generr(h, "select: %s", 61041120Sjdp strerror(errno)); 61141120Sjdp return -1; 61241120Sjdp } 61341120Sjdp } else 61441120Sjdp nfds = 0; 61541120Sjdp if (nfds == 0) { 61641120Sjdp generr(h, "Network write timed out"); 61741120Sjdp return -1; 61841120Sjdp } 61941120Sjdp } else { 62041120Sjdp ptr += n; 62141120Sjdp len -= n; 62241120Sjdp } 62341120Sjdp } 62441120Sjdp return 0; 62541120Sjdp} 62641120Sjdp 62741120Sjdp/* 62841120Sjdp * Destructively split a string into fields separated by white space. 62941120Sjdp * `#' at the beginning of a field begins a comment that extends to the 63041120Sjdp * end of the string. Fields may be quoted with `"'. Inside quoted 63141120Sjdp * strings, the backslash escapes `\"' and `\\' are honored. 63241120Sjdp * 63341120Sjdp * Pointers to up to the first maxfields fields are stored in the fields 63441120Sjdp * array. Missing fields get NULL pointers. 63541120Sjdp * 63641120Sjdp * The return value is the actual number of fields parsed, and is always 63741120Sjdp * <= maxfields. 63841120Sjdp * 63941120Sjdp * On a syntax error, places a message in the msg string, and returns -1. 64041120Sjdp */ 64141120Sjdpstatic int 64241120Sjdpsplit(char *str, char *fields[], int maxfields, char *msg, size_t msglen) 64341120Sjdp{ 64441120Sjdp char *p; 64541120Sjdp int i; 64641120Sjdp static const char ws[] = " \t"; 64741120Sjdp 64841120Sjdp for (i = 0; i < maxfields; i++) 64941120Sjdp fields[i] = NULL; 65041120Sjdp p = str; 65141120Sjdp i = 0; 65241120Sjdp while (*p != '\0') { 65341120Sjdp p += strspn(p, ws); 65441120Sjdp if (*p == '#' || *p == '\0') 65541120Sjdp break; 65641120Sjdp if (i >= maxfields) { 65741120Sjdp snprintf(msg, msglen, "line has too many fields"); 65841120Sjdp return -1; 65941120Sjdp } 66041120Sjdp if (*p == '"') { 66141120Sjdp char *dst; 66241120Sjdp 66341120Sjdp dst = ++p; 66441120Sjdp fields[i] = dst; 66541120Sjdp while (*p != '"') { 66641120Sjdp if (*p == '\\') { 66741120Sjdp p++; 66841120Sjdp if (*p != '"' && *p != '\\' && 66941120Sjdp *p != '\0') { 67041120Sjdp snprintf(msg, msglen, 67141120Sjdp "invalid `\\' escape"); 67241120Sjdp return -1; 67341120Sjdp } 67441120Sjdp } 67541120Sjdp if (*p == '\0') { 67641120Sjdp snprintf(msg, msglen, 67741120Sjdp "unterminated quoted string"); 67841120Sjdp return -1; 67941120Sjdp } 68041120Sjdp *dst++ = *p++; 68141120Sjdp } 68241120Sjdp *dst = '\0'; 68341120Sjdp p++; 68441120Sjdp if (*p != '\0' && strspn(p, ws) == 0) { 68541120Sjdp snprintf(msg, msglen, "quoted string not" 68641120Sjdp " followed by white space"); 68741120Sjdp return -1; 68841120Sjdp } 68941120Sjdp } else { 69041120Sjdp fields[i] = p; 69141120Sjdp p += strcspn(p, ws); 69241120Sjdp if (*p != '\0') 69341120Sjdp *p++ = '\0'; 69441120Sjdp } 69541120Sjdp i++; 69641120Sjdp } 69741120Sjdp return i; 69841120Sjdp} 69941120Sjdp 70041120Sjdpint 70141120Sjdptac_add_server(struct tac_handle *h, const char *host, int port, 70241120Sjdp const char *secret, int timeout, int flags) 70341120Sjdp{ 70441120Sjdp struct tac_server *srvp; 70541120Sjdp 70641120Sjdp if (h->num_servers >= MAXSERVERS) { 70756141Sjdp generr(h, "Too many TACACS+ servers specified"); 70841120Sjdp return -1; 70941120Sjdp } 71041120Sjdp srvp = &h->servers[h->num_servers]; 71141120Sjdp 71241120Sjdp memset(&srvp->addr, 0, sizeof srvp->addr); 71341120Sjdp srvp->addr.sin_len = sizeof srvp->addr; 71441120Sjdp srvp->addr.sin_family = AF_INET; 71541120Sjdp if (!inet_aton(host, &srvp->addr.sin_addr)) { 71641120Sjdp struct hostent *hent; 71741120Sjdp 71841120Sjdp if ((hent = gethostbyname(host)) == NULL) { 71941120Sjdp generr(h, "%s: host not found", host); 72041120Sjdp return -1; 72141120Sjdp } 72241120Sjdp memcpy(&srvp->addr.sin_addr, hent->h_addr, 72341120Sjdp sizeof srvp->addr.sin_addr); 72441120Sjdp } 72541120Sjdp srvp->addr.sin_port = htons(port != 0 ? port : TACPLUS_PORT); 72641120Sjdp if ((srvp->secret = xstrdup(h, secret)) == NULL) 72741120Sjdp return -1; 72841120Sjdp srvp->timeout = timeout; 72941120Sjdp srvp->flags = flags; 73041120Sjdp h->num_servers++; 73141120Sjdp return 0; 73241120Sjdp} 73341120Sjdp 73441120Sjdpvoid 73541120Sjdptac_close(struct tac_handle *h) 73641120Sjdp{ 73741120Sjdp int srv; 73841120Sjdp 73941120Sjdp if (h->fd != -1) 74041120Sjdp close(h->fd); 74141120Sjdp for (srv = 0; srv < h->num_servers; srv++) { 74241120Sjdp memset(h->servers[srv].secret, 0, 74341120Sjdp strlen(h->servers[srv].secret)); 74441120Sjdp free(h->servers[srv].secret); 74541120Sjdp } 74641120Sjdp free_str(&h->user); 74741120Sjdp free_str(&h->port); 74841120Sjdp free_str(&h->rem_addr); 74941120Sjdp free_str(&h->data); 75041120Sjdp free_str(&h->user_msg); 75141120Sjdp free(h); 75241120Sjdp} 75341120Sjdp 75441120Sjdpint 75541120Sjdptac_config(struct tac_handle *h, const char *path) 75641120Sjdp{ 75741120Sjdp FILE *fp; 75841120Sjdp char buf[MAXCONFLINE]; 75941120Sjdp int linenum; 76041120Sjdp int retval; 76141120Sjdp 76241120Sjdp if (path == NULL) 76341120Sjdp path = PATH_TACPLUS_CONF; 76441120Sjdp if ((fp = fopen(path, "r")) == NULL) { 76541120Sjdp generr(h, "Cannot open \"%s\": %s", path, strerror(errno)); 76641120Sjdp return -1; 76741120Sjdp } 76841120Sjdp retval = 0; 76941120Sjdp linenum = 0; 77041120Sjdp while (fgets(buf, sizeof buf, fp) != NULL) { 77141120Sjdp int len; 77241120Sjdp char *fields[4]; 77341120Sjdp int nfields; 77441120Sjdp char msg[ERRSIZE]; 77565222Sache char *host, *res; 77641120Sjdp char *port_str; 77741120Sjdp char *secret; 77841120Sjdp char *timeout_str; 77941120Sjdp char *options_str; 78041120Sjdp char *end; 78141120Sjdp unsigned long timeout; 78241120Sjdp int port; 78341120Sjdp int options; 78441120Sjdp 78541120Sjdp linenum++; 78641120Sjdp len = strlen(buf); 78741120Sjdp /* We know len > 0, else fgets would have returned NULL. */ 78841120Sjdp if (buf[len - 1] != '\n') { 78941120Sjdp if (len == sizeof buf - 1) 79041120Sjdp generr(h, "%s:%d: line too long", path, 79141120Sjdp linenum); 79241120Sjdp else 79341120Sjdp generr(h, "%s:%d: missing newline", path, 79441120Sjdp linenum); 79541120Sjdp retval = -1; 79641120Sjdp break; 79741120Sjdp } 79841120Sjdp buf[len - 1] = '\0'; 79941120Sjdp 80041120Sjdp /* Extract the fields from the line. */ 80141120Sjdp nfields = split(buf, fields, 4, msg, sizeof msg); 80241120Sjdp if (nfields == -1) { 80341120Sjdp generr(h, "%s:%d: %s", path, linenum, msg); 80441120Sjdp retval = -1; 80541120Sjdp break; 80641120Sjdp } 80741120Sjdp if (nfields == 0) 80841120Sjdp continue; 80941120Sjdp if (nfields < 2) { 81041120Sjdp generr(h, "%s:%d: missing shared secret", path, 81141120Sjdp linenum); 81241120Sjdp retval = -1; 81341120Sjdp break; 81441120Sjdp } 81541120Sjdp host = fields[0]; 81641120Sjdp secret = fields[1]; 81741120Sjdp timeout_str = fields[2]; 81841120Sjdp options_str = fields[3]; 81941120Sjdp 82041120Sjdp /* Parse and validate the fields. */ 82165222Sache res = host; 82265222Sache host = strsep(&res, ":"); 82365222Sache port_str = strsep(&res, ":"); 82441120Sjdp if (port_str != NULL) { 82541120Sjdp port = strtoul(port_str, &end, 10); 82641120Sjdp if (port_str[0] == '\0' || *end != '\0') { 82741120Sjdp generr(h, "%s:%d: invalid port", path, 82841120Sjdp linenum); 82941120Sjdp retval = -1; 83041120Sjdp break; 83141120Sjdp } 83241120Sjdp } else 83341120Sjdp port = 0; 83441120Sjdp if (timeout_str != NULL) { 83541120Sjdp timeout = strtoul(timeout_str, &end, 10); 83641120Sjdp if (timeout_str[0] == '\0' || *end != '\0') { 83741120Sjdp generr(h, "%s:%d: invalid timeout", path, 83841120Sjdp linenum); 83941120Sjdp retval = -1; 84041120Sjdp break; 84141120Sjdp } 84241120Sjdp } else 84341120Sjdp timeout = TIMEOUT; 84441120Sjdp options = 0; 84541120Sjdp if (options_str != NULL) { 84641120Sjdp if (strcmp(options_str, "single-connection") == 0) 84741120Sjdp options |= TAC_SRVR_SINGLE_CONNECT; 84841120Sjdp else { 84941120Sjdp generr(h, "%s:%d: invalid option \"%s\"", 85041120Sjdp path, linenum, options_str); 85141120Sjdp retval = -1; 85241120Sjdp break; 85341120Sjdp } 85441120Sjdp }; 85541120Sjdp 85641120Sjdp if (tac_add_server(h, host, port, secret, timeout, 85741120Sjdp options) == -1) { 85841120Sjdp char msg[ERRSIZE]; 85941120Sjdp 86041120Sjdp strcpy(msg, h->errmsg); 86141120Sjdp generr(h, "%s:%d: %s", path, linenum, msg); 86241120Sjdp retval = -1; 86341120Sjdp break; 86441120Sjdp } 86541120Sjdp } 86641120Sjdp /* Clear out the buffer to wipe a possible copy of a shared secret */ 86741120Sjdp memset(buf, 0, sizeof buf); 86841120Sjdp fclose(fp); 86941120Sjdp return retval; 87041120Sjdp} 87141120Sjdp 87241120Sjdpint 87341120Sjdptac_create_authen(struct tac_handle *h, int action, int type, int service) 87441120Sjdp{ 87541120Sjdp struct tac_msg *msg; 87641120Sjdp struct tac_authen_start *as; 87741120Sjdp 87841120Sjdp h->last_seq_no = 0; 87941120Sjdp 88041120Sjdp msg = &h->request; 88141120Sjdp msg->type = TAC_AUTHEN; 88241120Sjdp msg->version = authen_version(action, type); 88341120Sjdp msg->flags = 0; 88441120Sjdp 88541120Sjdp as = &msg->u.authen_start; 88641120Sjdp as->action = action; 88741120Sjdp as->priv_lvl = TAC_PRIV_LVL_USER; 88841120Sjdp as->authen_type = type; 88941120Sjdp as->service = service; 89041120Sjdp 89141120Sjdp free_str(&h->user); 89241120Sjdp free_str(&h->port); 89341120Sjdp free_str(&h->rem_addr); 89441120Sjdp free_str(&h->data); 89541120Sjdp free_str(&h->user_msg); 89641120Sjdp 89741120Sjdp /* XXX - more to do */ 89841120Sjdp return 0; 89941120Sjdp} 90041120Sjdp 90141120Sjdpvoid * 90241120Sjdptac_get_data(struct tac_handle *h, size_t *len) 90341120Sjdp{ 90441120Sjdp return dup_str(h, &h->srvr_data, len); 90541120Sjdp} 90641120Sjdp 90741120Sjdpchar * 90841120Sjdptac_get_msg(struct tac_handle *h) 90941120Sjdp{ 91041120Sjdp return (char *)dup_str(h, &h->srvr_msg, NULL); 91141120Sjdp} 91241120Sjdp 91341120Sjdp/* 91441120Sjdp * Create and initialize a tac_handle structure, and return it to the 91541120Sjdp * caller. Can fail only if the necessary memory cannot be allocated. 91641120Sjdp * In that case, it returns NULL. 91741120Sjdp */ 91841120Sjdpstruct tac_handle * 91941120Sjdptac_open(void) 92041120Sjdp{ 92141120Sjdp struct tac_handle *h; 92241120Sjdp 92341120Sjdp h = (struct tac_handle *)malloc(sizeof(struct tac_handle)); 92441120Sjdp if (h != NULL) { 92541120Sjdp h->fd = -1; 92641120Sjdp h->num_servers = 0; 92741120Sjdp h->cur_server = 0; 92841120Sjdp h->errmsg[0] = '\0'; 92941120Sjdp init_clnt_str(&h->user); 93041120Sjdp init_clnt_str(&h->port); 93141120Sjdp init_clnt_str(&h->rem_addr); 93241120Sjdp init_clnt_str(&h->data); 93341120Sjdp init_clnt_str(&h->user_msg); 93441120Sjdp init_srvr_str(&h->srvr_msg); 93541120Sjdp init_srvr_str(&h->srvr_data); 93641120Sjdp srandomdev(); 93741120Sjdp } 93841120Sjdp return h; 93941120Sjdp} 94041120Sjdp 94141120Sjdpint 94241120Sjdptac_send_authen(struct tac_handle *h) 94341120Sjdp{ 94441120Sjdp struct tac_authen_reply *ar; 94541120Sjdp 94641120Sjdp if (h->last_seq_no == 0) { /* Authentication START packet */ 94741120Sjdp struct tac_authen_start *as; 94841120Sjdp 94941120Sjdp as = &h->request.u.authen_start; 95041120Sjdp h->request.length = 95141120Sjdp htonl(offsetof(struct tac_authen_start, rest[0])); 95241120Sjdp if (add_str_8(h, &as->user_len, &h->user) == -1 || 95341120Sjdp add_str_8(h, &as->port_len, &h->port) == -1 || 95441120Sjdp add_str_8(h, &as->rem_addr_len, &h->rem_addr) == -1 || 95541120Sjdp add_str_8(h, &as->data_len, &h->data) == -1) 95641120Sjdp return -1; 95741120Sjdp } else { /* Authentication CONTINUE packet */ 95841120Sjdp struct tac_authen_cont *ac; 95941120Sjdp 96041120Sjdp ac = &h->request.u.authen_cont; 96141120Sjdp ac->flags = 0; 96241120Sjdp h->request.length = 96341120Sjdp htonl(offsetof(struct tac_authen_cont, rest[0])); 96441120Sjdp if (add_str_16(h, &ac->user_msg_len, &h->user_msg) == -1 || 96541120Sjdp add_str_16(h, &ac->data_len, &h->data) == -1) 96641120Sjdp return -1; 96741120Sjdp } 96841120Sjdp 96941120Sjdp /* Send the message and retrieve the reply. */ 97041120Sjdp if (send_msg(h) == -1 || recv_msg(h) == -1) 97141120Sjdp return -1; 97241120Sjdp 97341120Sjdp /* Scan the optional fields in the reply. */ 97441120Sjdp ar = &h->response.u.authen_reply; 97541120Sjdp h->srvr_pos = offsetof(struct tac_authen_reply, rest[0]); 97641120Sjdp if (get_srvr_str(h, &h->srvr_msg, ntohs(ar->msg_len)) == -1 || 97741120Sjdp get_srvr_str(h, &h->srvr_data, ntohs(ar->data_len)) == -1 || 97841120Sjdp get_srvr_end(h) == -1) 97941120Sjdp return -1; 98041120Sjdp 98141120Sjdp if (!h->single_connect && 98241120Sjdp ar->status != TAC_AUTHEN_STATUS_GETDATA && 98341120Sjdp ar->status != TAC_AUTHEN_STATUS_GETUSER && 98441120Sjdp ar->status != TAC_AUTHEN_STATUS_GETPASS) 98541120Sjdp close_connection(h); 98641120Sjdp 98741120Sjdp return ar->flags << 8 | ar->status; 98841120Sjdp} 98941120Sjdp 99041120Sjdpint 99141120Sjdptac_set_rem_addr(struct tac_handle *h, const char *addr) 99241120Sjdp{ 99341120Sjdp return save_str(h, &h->rem_addr, addr, addr != NULL ? strlen(addr) : 0); 99441120Sjdp} 99541120Sjdp 99641120Sjdpint 99741120Sjdptac_set_data(struct tac_handle *h, const void *data, size_t data_len) 99841120Sjdp{ 99941120Sjdp return save_str(h, &h->data, data, data_len); 100041120Sjdp} 100141120Sjdp 100241120Sjdpint 100341120Sjdptac_set_msg(struct tac_handle *h, const char *msg) 100441120Sjdp{ 100541120Sjdp return save_str(h, &h->user_msg, msg, msg != NULL ? strlen(msg) : 0); 100641120Sjdp} 100741120Sjdp 100841120Sjdpint 100941120Sjdptac_set_port(struct tac_handle *h, const char *port) 101041120Sjdp{ 101141120Sjdp return save_str(h, &h->port, port, port != NULL ? strlen(port) : 0); 101241120Sjdp} 101341120Sjdp 101441120Sjdpint 101541120Sjdptac_set_priv(struct tac_handle *h, int priv) 101641120Sjdp{ 101741120Sjdp if (!(TAC_PRIV_LVL_MIN <= priv && priv <= TAC_PRIV_LVL_MAX)) { 101841120Sjdp generr(h, "Attempt to set invalid privilege level"); 101941120Sjdp return -1; 102041120Sjdp } 102141120Sjdp h->request.u.authen_start.priv_lvl = priv; 102241120Sjdp return 0; 102341120Sjdp} 102441120Sjdp 102541120Sjdpint 102641120Sjdptac_set_user(struct tac_handle *h, const char *user) 102741120Sjdp{ 102841120Sjdp return save_str(h, &h->user, user, user != NULL ? strlen(user) : 0); 102941120Sjdp} 103041120Sjdp 103141120Sjdpconst char * 103241120Sjdptac_strerror(struct tac_handle *h) 103341120Sjdp{ 103441120Sjdp return h->errmsg; 103541120Sjdp} 103641120Sjdp 103741120Sjdpstatic void * 103841120Sjdpxmalloc(struct tac_handle *h, size_t size) 103941120Sjdp{ 104041120Sjdp void *r; 104141120Sjdp 104241120Sjdp if ((r = malloc(size)) == NULL) 104341120Sjdp generr(h, "Out of memory"); 104441120Sjdp return r; 104541120Sjdp} 104641120Sjdp 104741120Sjdpstatic char * 104841120Sjdpxstrdup(struct tac_handle *h, const char *s) 104941120Sjdp{ 105041120Sjdp char *r; 105141120Sjdp 105241120Sjdp if ((r = strdup(s)) == NULL) 105341120Sjdp generr(h, "Out of memory"); 105441120Sjdp return r; 105541120Sjdp} 1056