2 * Copyright (C) 2011-2013 Simon Ruderich
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 /* Necessary for RTLD_NEXT. */
24 /* Hack to prevent problems with different declarations of connect(). */
25 #define connect connect_real
27 #include <arpa/inet.h>
34 #include <sys/socket.h>
35 #include <sys/types.h>
41 #define POLL_TIMEOUT 30000 /* 30 seconds */
42 #define LOG_PREFIX "tlsproxyhelper: "
44 /* Load the function name using dlsym() if necessary and store it in pointer.
45 * Terminate program on failure. */
46 #define TLSPROXY_LOAD_FUNCTION(pointer, name) \
47 if ((pointer) == NULL) { \
49 dlerror(); /* Clear possibly existing error. */ \
51 *(void **) (&(pointer)) = dlsym(RTLD_NEXT, (name)); \
53 if ((error = dlerror()) != NULL) { \
54 fprintf(stderr, LOG_PREFIX "%s\n", error); \
60 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
62 static int get_tlsproxy_port(void);
63 static int poll_for(int fd, int read);
66 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
67 static int (*real_connect)(int, const struct sockaddr *, socklen_t);
69 char ip[INET6_ADDRSTRLEN];
72 int tlsproxy_port = 0;
75 struct sockaddr *my_addr;
76 socklen_t my_addr_len;
77 struct sockaddr_in my_addr_ipv4;
78 struct sockaddr_in6 my_addr_ipv6;
80 char buffer[128]; /* response from tlsproxy */
83 TLSPROXY_LOAD_FUNCTION(real_connect, "connect");
85 tlsproxy_port = get_tlsproxy_port();
87 /* Is the socket in non-blocking mode? */
91 if (addr->sa_family == AF_INET) {
92 struct sockaddr_in *ipv4_addr = (struct sockaddr_in *)addr;
94 /* Extract IP and port. */
95 snprintf(port, sizeof(port), "%u", ntohs(ipv4_addr->sin_port));
96 if (inet_ntop(AF_INET, &ipv4_addr->sin_addr, ip, sizeof(ip)) == NULL) {
97 perror(LOG_PREFIX "inet_ntop()");
102 /* Setup connection parameters to connect to tlsproxy. */
103 memset(&my_addr_ipv4, 0, sizeof(my_addr_ipv4));
104 my_addr_ipv4.sin_family = AF_INET;
105 my_addr_ipv4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
106 my_addr_ipv4.sin_port = htons((uint16_t)tlsproxy_port);
108 my_addr = (struct sockaddr *)&my_addr_ipv4;
109 my_addr_len = sizeof(my_addr_ipv4);
112 } else if (addr->sa_family == AF_INET6) {
113 struct sockaddr_in6 *ipv6_addr = (struct sockaddr_in6 *)addr;
115 /* Extract IP and port. */
116 snprintf(port, sizeof(port), "%u", ntohs(ipv6_addr->sin6_port));
117 if (inet_ntop(AF_INET6, &ipv6_addr->sin6_addr, ip, sizeof(ip)) == NULL) {
118 perror(LOG_PREFIX "inet_ntop()");
123 /* Setup connection parameters to connect to tlsproxy. */
124 memset(&my_addr_ipv6, 0, sizeof(my_addr_ipv6));
125 my_addr_ipv6.sin6_family = AF_INET6;
126 my_addr_ipv6.sin6_addr = in6addr_loopback;
127 my_addr_ipv6.sin6_port = htons((uint16_t)tlsproxy_port);
129 my_addr = (struct sockaddr *)&my_addr_ipv6;
130 my_addr_len = sizeof(my_addr_ipv6);
133 fprintf(stderr, LOG_PREFIX "unknown protocol family: %d\n",
135 errno = EAFNOSUPPORT;
139 /* Simple way to pass through DNS requests. */
140 if (!strcmp(port, "53")) {
141 return real_connect(sockfd, addr, addrlen);
144 /* Perform the real connect. */
145 if (real_connect(sockfd, my_addr, my_addr_len) != 0) {
146 /* Handle non-blocking sockets. */
147 if (errno == EINPROGRESS) {
150 perror(LOG_PREFIX "connect()");
151 /* errno is correctly set by real connect(). */
156 /* Make sure we can write. */
161 if (poll_for(sockfd, 0 /* write */) != 0) {
162 /* poll_for() writes the error message */
167 /* Check if connect() was successful. */
168 optlen = sizeof(optval);
169 if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) != 0) {
170 perror(LOG_PREFIX "getsockopt()");
175 fprintf(stderr, LOG_PREFIX "connect() failed (SO_ERROR)");
181 /* Send "CONNECT ip:port HTTP/1.0" to the proxy. */
182 snprintf(buffer, sizeof(buffer),
183 "CONNECT %s:%s HTTP/1.0\r\n\r\n", ip, port);
184 size = write(sockfd, buffer, strlen(buffer));
185 if (size != (int)strlen(buffer)) {
186 perror(LOG_PREFIX "write()");
192 /* Make sure we can read. */
194 if (poll_for(sockfd, 1 /* read */) != 0) {
195 /* poll_for() writes the error message */
201 /* Read the response from the proxy and check if it's fine. */
202 size = read(sockfd, buffer, sizeof(buffer));
204 perror(LOG_PREFIX "read()");
208 if (strncmp(buffer, "HTTP/1.0 200 Connection established\r\n", 37)) {
209 fprintf(stderr, LOG_PREFIX "invalid proxy response: %s", buffer);
214 /* connect() returns 0 on success. */
218 static int get_tlsproxy_port(void) {
222 /* Extract port from the environment variable. */
223 string = getenv("TLSPROXYHELPER_PORT");
224 if (string != NULL) {
227 /* No valid port specified, use the default one. */
228 if (port <= 0 || port > 0xffff) {
235 /* Poll for read (mode = 1) or write (mode = 0) possibility on file descriptor
236 * sockfd. Returns 0 on success, -1 on failure. */
237 static int poll_for(int sockfd, int mode) {
239 struct pollfd fds[1];
242 fds[0].events = POLLERR | POLLHUP;
243 /* Either poll for read or write possibility. */
245 fds[0].events |= POLLIN | POLLPRI;
246 } else if (0 == mode) {
247 fds[0].events |= POLLOUT;
253 result = poll(fds, 1 /* fd count */, POLL_TIMEOUT);
255 perror(LOG_PREFIX "poll()");
257 } else if (result == 0) {
258 fprintf(stderr, LOG_PREFIX "poll() timeout\n");
262 if (fds[0].revents & POLLERR || fds[0].revents & POLLHUP) {
263 fprintf(stderr, LOG_PREFIX "poll(): POLLERR | POLLHUP\n");