diff options
Diffstat (limited to 'libtac/lib/connect.c')
-rw-r--r-- | libtac/lib/connect.c | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/libtac/lib/connect.c b/libtac/lib/connect.c new file mode 100644 index 0000000..8641ae2 --- /dev/null +++ b/libtac/lib/connect.c @@ -0,0 +1,236 @@ +/* connect.c - Open connection to server. + * + * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and + * Jeroen Nijhof <jeroen@jeroennijhof.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program - see the file COPYING. + * + * See `CHANGES' file for revision history. + */ + +#include <signal.h> +#include <arpa/inet.h> +#include <time.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef _AIX +#include <sys/socket.h> +#endif + +#include "libtac.h" + +/* Pointer to TACACS+ connection timeout */ +int tac_timeout = 5; + +/* Returns file descriptor of open connection + to the first available server from list passed + in server table. + + * return value: + * >= 0 : valid fd + * < 0 : error status code, see LIBTAC_STATUS_... + * iface is used to bind to an interface or VRF if non-null + */ +int tac_connect(struct addrinfo **server, char **key, int servers, char *iface) { + int tries; + int fd=-1; + + if(servers == 0 || server == NULL) { + TACSYSLOG((LOG_ERR, "%s: no TACACS+ servers defined", __func__)) + } else { + for ( tries = 0; tries < servers; tries++ ) { + if((fd=tac_connect_single(server[tries], key[tries], NULL, iface)) >= 0 ) { + /* tac_secret was set in tac_connect_single on success */ + break; + } + } + } + + /* all attempts failed if fd is still < 0 */ + TACDEBUG((LOG_DEBUG, "%s: exit status=%d",__func__, fd)) + return fd; +} /* tac_connect */ + + +/* return value: + * >= 0 : valid fd + * < 0 : error status code, see LIBTAC_STATUS_... + * If iface is non-null, try to setsockopt SO_BINDTODEVICE to that iface + * to support specific routing, including VRF. + * if srcaddr is non-null, try to bind() to that address to support + * specifying source IP addres + */ +int tac_connect_single(struct addrinfo *server, const char *key, + struct addrinfo *srcaddr, char *iface) { + int retval = LIBTAC_STATUS_CONN_ERR; /* default retval */ + int fd = -1; + int flags, rc; + fd_set readfds, writefds; + struct timeval tv; + socklen_t len; + struct sockaddr_storage addr; + char *ip; + + if(server == NULL) { + TACSYSLOG((LOG_ERR, "%s: no TACACS+ server defined", __func__)) + return LIBTAC_STATUS_CONN_ERR; + } + + /* format server address into a string for use in messages */ + ip = tac_ntop(server->ai_addr); + + if((fd=socket(server->ai_family, server->ai_socktype, server->ai_protocol)) < 0) { + TACSYSLOG((LOG_ERR,"%s: socket creation error: %s", __func__, + strerror(errno))) + retval = LIBTAC_STATUS_CONN_ERR; + goto error; + } + + if (iface) { + /* do not fail if the bind fails, connection may still succeed */ + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface, + strlen(iface)+1) < 0) + TACSYSLOG((LOG_WARNING, "%s: Binding socket to device %s failed: %m", + __func__, iface)) + } + + /* get flags for restoration later */ + flags = fcntl(fd, F_GETFL, 0); + + /* put socket in non blocking mode for timeout support */ + if( fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1 ) { + TACSYSLOG((LOG_ERR, "%s: cannot set socket non blocking",\ + __func__)) + retval = LIBTAC_STATUS_CONN_ERR; + goto error; + } + + /* bind if source address got explicity defined */ + if (srcaddr) { + if (bind(fd, srcaddr->ai_addr, srcaddr->ai_addrlen) < 0) { + TACSYSLOG((LOG_ERR, "%s: Failed to bind source address: %s", + __func__, strerror(errno))) + retval = LIBTAC_STATUS_CONN_ERR; + goto error; + } + } + + rc = connect(fd, server->ai_addr, server->ai_addrlen); + /* FIX this..for some reason errno = 0 on AIX... */ + if((rc == -1) && (errno != EINPROGRESS) && (errno != 0)) { + TACSYSLOG((LOG_ERR,\ + "%s: connection to %s failed: %m", __func__, ip)) + retval = LIBTAC_STATUS_CONN_ERR; + goto error; + } + + /* set fds for select */ + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_SET(fd, &readfds); + FD_SET(fd, &writefds); + + /* set timeout seconds */ + tv.tv_sec = tac_timeout; + tv.tv_usec = 0; + + /* check if socket is ready for read and write */ + rc = select(fd+1, &readfds, &writefds, NULL, &tv); + + /* timeout */ + if ( rc == 0 ) { + retval = LIBTAC_STATUS_CONN_TIMEOUT; + goto error; + } + + /* some other error or interrupt before timeout */ + if ( rc < 0 ) { + TACSYSLOG((LOG_ERR,\ + "%s: connection failed with %s: %m", __func__, ip)) + retval = LIBTAC_STATUS_CONN_ERR; + goto error; + } + + /* check with getpeername if we have a valid connection */ + len = sizeof addr; + if(getpeername(fd, (struct sockaddr*)&addr, &len) == -1) { + TACSYSLOG((LOG_ERR,\ + "%s: getpeername failed with %s: %m", __func__, ip)) + retval = LIBTAC_STATUS_CONN_ERR; + goto error; + } + + /* restore flags on socket */ + if (flags & O_NONBLOCK) { + flags &= ~O_NONBLOCK; + } + if(fcntl(fd, F_SETFL, flags) == -1) { + TACSYSLOG((LOG_ERR, "%s: cannot restore socket flags: %m",\ + __func__)) + retval = LIBTAC_STATUS_CONN_ERR; + goto error; + } + + /* connected ok */ + TACDEBUG((LOG_DEBUG, "%s: connected to %s", __func__, ip)) + retval = fd; + + /* set current tac_secret */ + tac_encryption = 0; + if (key != NULL && *key) { + tac_encryption = 1; + tac_secret = key; + } + +error: + if (retval < 0 && fd >= 0) /* we had an error, don't leak fd */ + close(fd); + TACDEBUG((LOG_DEBUG, "%s: exit status=%d (fd=%d)",\ + __func__, retval < 0 ? retval:0, fd)) + return retval; +} /* tac_connect_single */ + + +/* return value: + * ptr to char* with format IP address + * warning: returns a static buffer + * (which some ppl don't like, but it's robust and at last no more memory leaks) + */ +char *tac_ntop(const struct sockaddr *sa) { + static char server_address[INET6_ADDRSTRLEN+16]; + + switch(sa->sa_family) { + case AF_INET: + inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), + server_address, INET_ADDRSTRLEN); + + snprintf(server_address + strlen(server_address), 14, ":%hu", + htons(((struct sockaddr_in *)sa)->sin_port)); + break; + + case AF_INET6: + inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), + server_address, INET6_ADDRSTRLEN); + + snprintf(server_address + strlen(server_address), 14, ":%hu", + htons(((struct sockaddr_in6 *)sa)->sin6_port)); + break; + + default: + strcpy(server_address, "Unknown AF"); + } + return server_address; +} /* tac_ntop */ + |