summaryrefslogtreecommitdiff
path: root/libtac/lib/connect.c
diff options
context:
space:
mode:
Diffstat (limited to 'libtac/lib/connect.c')
-rw-r--r--libtac/lib/connect.c236
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 */
+