/* * Simple LD_PRELOAD wrapper for connect() which uses tlsproxy as proxy for * programs which don't support setting a TLS proxy. * * The following environment variables can be used: * * - TLSPROXYHELPER_PORT: port where tlsproxy is running (default 9000) * * If an error occurs -1 is returned and errno is set (either EAFNOSUPPORT if * another protocol besides IPv4/IPv6 was used, or EINVAL if another error * occurred). The error is written to stderr with "tlsproxyhelper:" as prefix. */ /* * Copyright (C) 2011-2014 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)\n"); 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 (mode == 1) { fds[0].events |= POLLIN | POLLPRI; } else if (mode == 0) { 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) { fprintf(stderr, LOG_PREFIX "poll(): POLLERR\n"); return -1; } if (fds[0].revents & POLLHUP) { fprintf(stderr, LOG_PREFIX "poll(): POLLHUP\n"); return -1; } return 0; }