From 3cb7193721894ec9c064bc22e09078e8c5cd3dd8 Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Sat, 10 Aug 2013 16:46:20 +0200 Subject: [PATCH] Add libtlsproxyhelper.so. libtlsproxyhelper is a simple LD_PRELOAD wrapper for connect() which uses tlsproxy as proxy for programs which don't support setting a TLS proxy. --- .gitignore | 12 ++ Makefile.am | 2 +- configure.ac | 5 +- lib/Makefile.am | 2 + lib/tlsproxyhelper.c | 268 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 lib/Makefile.am create mode 100644 lib/tlsproxyhelper.c diff --git a/.gitignore b/.gitignore index d510d2a..a8a2893 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,18 @@ /config.log /config.status /configure +/lib/.deps/ +/lib/.libs/ +/lib/libtlsproxyhelper.la +/lib/Makefile +/lib/Makefile.in +/lib/tlsproxyhelper.lo +/libtool +/m4/libtool.m4 +/m4/lt~obsolete.m4 +/m4/ltoptions.m4 +/m4/ltsugar.m4 +/m4/ltversion.m4 /Makefile /Makefile.in /man/Makefile diff --git a/Makefile.am b/Makefile.am index 931403b..c1b0b4c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ -SUBDIRS = man src tests +SUBDIRS = man src lib tests ACLOCAL_AMFLAGS = -I m4 diff --git a/configure.ac b/configure.ac index 2e7e638..012b716 100644 --- a/configure.ac +++ b/configure.ac @@ -23,6 +23,7 @@ AC_CONFIG_HEADERS([config.h]) AC_CONFIG_SRCDIR([src/tlsproxy.c]) AM_INIT_AUTOMAKE([foreign -Wall -Werror]) +LT_INIT([disable-static]) AC_PROG_CC @@ -43,6 +44,8 @@ if test "x$GCC" = xyes; then LDFLAGS="-fPIE -pie -Wl,-z,relro -Wl,-z,now $LDFLAGS" fi +AC_SEARCH_LIBS([dlsym], [dl], [], [AC_MSG_ERROR([dlsym() is required])]) + AC_CHECK_LIB([gnutls], [gnutls_certificate_verify_peers2], [], [AC_MSG_ERROR([GnuTLS is required])]) dnl Check for additional GnuTLS functions. @@ -61,5 +64,5 @@ AC_ARG_ENABLE([ipv6], AC_DEFINE([USE_IPV4_ONLY], 1, [Define to not use IPv6.]) fi]) -AC_CONFIG_FILES([Makefile man/Makefile src/Makefile tests/Makefile]) +AC_CONFIG_FILES([Makefile man/Makefile src/Makefile lib/Makefile tests/Makefile]) AC_OUTPUT diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..e71b84c --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,2 @@ +lib_LTLIBRARIES = libtlsproxyhelper.la +libtlsproxyhelper_la_SOURCES = tlsproxyhelper.c diff --git a/lib/tlsproxyhelper.c b/lib/tlsproxyhelper.c new file mode 100644 index 0000000..a37e022 --- /dev/null +++ b/lib/tlsproxyhelper.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2011-2013 Simon Ruderich + * + * 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 3 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. If not, see . + */ + + +#include + +/* Necessary for RTLD_NEXT. */ +#define _GNU_SOURCE + +/* Hack to prevent problems with different declarations of connect(). */ +#define connect connect_real + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef connect + + +#define POLL_TIMEOUT 30000 /* 30 seconds */ +#define LOG_PREFIX "tlsproxyhelper: " + +/* Load the function name using dlsym() if necessary and store it in pointer. + * Terminate program on failure. */ +#define TLSPROXY_LOAD_FUNCTION(pointer, name) \ + if ((pointer) == NULL) { \ + char *error; \ + dlerror(); /* Clear possibly existing error. */ \ + \ + *(void **) (&(pointer)) = dlsym(RTLD_NEXT, (name)); \ + \ + if ((error = dlerror()) != NULL) { \ + fprintf(stderr, LOG_PREFIX "%s\n", error); \ + exit(EXIT_FAILURE); \ + } \ + } + + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + +static int get_tlsproxy_port(void); +static int poll_for(int fd, int read); + + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + static int (*real_connect)(int, const struct sockaddr *, socklen_t); + + char ip[INET6_ADDRSTRLEN]; + char port[5 + 1]; + + int tlsproxy_port = 0; + + int non_blocking; + struct sockaddr *my_addr; + socklen_t my_addr_len; + struct sockaddr_in my_addr_ipv4; + struct sockaddr_in6 my_addr_ipv6; + + char buffer[128]; /* response from tlsproxy */ + ssize_t size; + + TLSPROXY_LOAD_FUNCTION(real_connect, "connect"); + + tlsproxy_port = get_tlsproxy_port(); + + /* Is the socket in non-blocking mode? */ + non_blocking = 0; + + /* IPv4. */ + if (addr->sa_family == AF_INET) { + struct sockaddr_in *ipv4_addr = (struct sockaddr_in *)addr; + + /* Extract IP and port. */ + snprintf(port, sizeof(port), "%u", ntohs(ipv4_addr->sin_port)); + if (inet_ntop(AF_INET, &ipv4_addr->sin_addr, ip, sizeof(ip)) == NULL) { + perror(LOG_PREFIX "inet_ntop()"); + errno = EINVAL; + return -1; + } + + /* Setup connection parameters to connect to tlsproxy. */ + memset(&my_addr_ipv4, 0, sizeof(my_addr_ipv4)); + my_addr_ipv4.sin_family = AF_INET; + my_addr_ipv4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + my_addr_ipv4.sin_port = htons((uint16_t)tlsproxy_port); + + my_addr = (struct sockaddr *)&my_addr_ipv4; + my_addr_len = sizeof(my_addr_ipv4); + + /* IPv6. */ + } else if (addr->sa_family == AF_INET6) { + struct sockaddr_in6 *ipv6_addr = (struct sockaddr_in6 *)addr; + + /* Extract IP and port. */ + snprintf(port, sizeof(port), "%u", ntohs(ipv6_addr->sin6_port)); + if (inet_ntop(AF_INET6, &ipv6_addr->sin6_addr, ip, sizeof(ip)) == NULL) { + perror(LOG_PREFIX "inet_ntop()"); + errno = EINVAL; + return -1; + } + + /* Setup connection parameters to connect to tlsproxy. */ + memset(&my_addr_ipv6, 0, sizeof(my_addr_ipv6)); + my_addr_ipv6.sin6_family = AF_INET6; + my_addr_ipv6.sin6_addr = in6addr_loopback; + my_addr_ipv6.sin6_port = htons((uint16_t)tlsproxy_port); + + my_addr = (struct sockaddr *)&my_addr_ipv6; + my_addr_len = sizeof(my_addr_ipv6); + + } else { + fprintf(stderr, LOG_PREFIX "unknown protocol family: %d\n", + addr->sa_family); + errno = EAFNOSUPPORT; + return -1; + } + + /* Simple way to pass through DNS requests. */ + if (!strcmp(port, "53")) { + return real_connect(sockfd, addr, addrlen); + } + + /* Perform the real connect. */ + if (real_connect(sockfd, my_addr, my_addr_len) != 0) { + /* Handle non-blocking sockets. */ + if (errno == EINPROGRESS) { + non_blocking = 1; + } else { + perror(LOG_PREFIX "connect()"); + /* errno is correctly set by real connect(). */ + return -1; + } + } + + /* Make sure we can write. */ + if (non_blocking) { + int optval; + socklen_t optlen; + + if (poll_for(sockfd, 0 /* write */) != 0) { + /* poll_for() writes the error message */ + errno = EINVAL; + return -1; + } + + /* Check if connect() was successful. */ + optlen = sizeof(optval); + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) != 0) { + perror(LOG_PREFIX "getsockopt()"); + errno = EINVAL; + return -1; + } + if (optval != 0) { + fprintf(stderr, LOG_PREFIX "connect() failed (SO_ERROR)"); + errno = EINVAL; + return -1; + } + } + + /* Send "CONNECT ip:port HTTP/1.0" to the proxy. */ + snprintf(buffer, sizeof(buffer), + "CONNECT %s:%s HTTP/1.0\r\n\r\n", ip, port); + size = write(sockfd, buffer, strlen(buffer)); + if (size != (int)strlen(buffer)) { + perror(LOG_PREFIX "write()"); + errno = EINVAL; + return -1; + } + fsync(sockfd); + + /* Make sure we can read. */ + if (non_blocking) { + if (poll_for(sockfd, 1 /* read */) != 0) { + /* poll_for() writes the error message */ + errno = EINVAL; + return -1; + } + } + + /* Read the response from the proxy and check if it's fine. */ + size = read(sockfd, buffer, sizeof(buffer)); + if (size < 0) { + perror(LOG_PREFIX "read()"); + errno = EINVAL; + return -1; + } + if (strncmp(buffer, "HTTP/1.0 200 Connection established\r\n", 37)) { + fprintf(stderr, LOG_PREFIX "invalid proxy response: %s", buffer); + errno = EINVAL; + return -1; + } + + /* connect() returns 0 on success. */ + return 0; +} + +static int get_tlsproxy_port(void) { + const char *string; + int port = 0; + + /* Extract port from the environment variable. */ + string = getenv("TLSPROXYHELPER_PORT"); + if (string != NULL) { + port = atoi(string); + } + /* No valid port specified, use the default one. */ + if (port <= 0 || port > 0xffff) { + port = 9000; + } + + return port; +} + +/* Poll for read (mode = 1) or write (mode = 0) possibility on file descriptor + * sockfd. Returns 0 on success, -1 on failure. */ +static int poll_for(int sockfd, int mode) { + int result; + struct pollfd fds[1]; + + fds[0].fd = sockfd; + fds[0].events = POLLERR | POLLHUP; + /* Either poll for read or write possibility. */ + if (1 == mode) { + fds[0].events |= POLLIN | POLLPRI; + } else if (0 == mode) { + fds[0].events |= POLLOUT; + } else { + abort(); + } + fds[0].revents = 0; + + result = poll(fds, 1 /* fd count */, POLL_TIMEOUT); + if (result < 0) { + perror(LOG_PREFIX "poll()"); + return -1; + } else if (result == 0) { + fprintf(stderr, LOG_PREFIX "poll() timeout\n"); + return -1; + } + + if (fds[0].revents & POLLERR || fds[0].revents & POLLHUP) { + fprintf(stderr, LOG_PREFIX "poll(): POLLERR | POLLHUP\n"); + return -1; + } + + return 0; +} -- 2.43.2