]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - lib/tlsproxyhelper.c
tlsproxyhelper.c: Minor style fix.
[tlsproxy/tlsproxy.git] / lib / tlsproxyhelper.c
1 /*
2  * Simple LD_PRELOAD wrapper for connect() which uses tlsproxy as proxy for
3  * programs which don't support setting a TLS proxy.
4  *
5  * The following environment variables can be used:
6  *
7  * - TLSPROXYHELPER_PORT: port where tlsproxy is running (default 9000)
8  *
9  * If an error occurs -1 is returned and errno is set (either EAFNOSUPPORT if
10  * another protocol besides IPv4/IPv6 was used, or EINVAL if another error
11  * occurred). The error is written to stderr with "tlsproxyhelper:" as prefix.
12  */
13
14 /*
15  * Copyright (C) 2011-2013  Simon Ruderich
16  *
17  * This program is free software: you can redistribute it and/or modify
18  * it under the terms of the GNU General Public License as published by
19  * the Free Software Foundation, either version 3 of the License, or
20  * (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public License
28  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
29  */
30
31
32 #include <config.h>
33
34 /* Necessary for RTLD_NEXT. */
35 #define _GNU_SOURCE
36
37 /* Hack to prevent problems with different declarations of connect(). */
38 #define connect connect_real
39
40 #include <arpa/inet.h>
41 #include <dlfcn.h>
42 #include <errno.h>
43 #include <poll.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <sys/socket.h>
48 #include <sys/types.h>
49 #include <unistd.h>
50
51 #undef connect
52
53
54 #define POLL_TIMEOUT 30000 /* 30 seconds */
55 #define LOG_PREFIX "tlsproxyhelper: "
56
57 /* Load the function name using dlsym() if necessary and store it in pointer.
58  * Terminate program on failure. */
59 #define TLSPROXY_LOAD_FUNCTION(pointer, name) \
60     if ((pointer) == NULL) { \
61         char *error; \
62         dlerror(); /* Clear possibly existing error. */ \
63         \
64         *(void **) (&(pointer)) = dlsym(RTLD_NEXT, (name)); \
65         \
66         if ((error = dlerror()) != NULL) { \
67             fprintf(stderr, LOG_PREFIX "%s\n", error); \
68             exit(EXIT_FAILURE); \
69         } \
70     }
71
72
73 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
74
75 static int get_tlsproxy_port(void);
76 static int poll_for(int fd, int read);
77
78
79 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
80     static int (*real_connect)(int, const struct sockaddr *, socklen_t);
81
82     char ip[INET6_ADDRSTRLEN];
83     char port[5 + 1];
84
85     int tlsproxy_port = 0;
86
87     int non_blocking;
88     struct sockaddr *my_addr;
89     socklen_t        my_addr_len;
90     struct sockaddr_in  my_addr_ipv4;
91     struct sockaddr_in6 my_addr_ipv6;
92
93     char buffer[128]; /* response from tlsproxy */
94     ssize_t size;
95
96     TLSPROXY_LOAD_FUNCTION(real_connect, "connect");
97
98     tlsproxy_port = get_tlsproxy_port();
99
100     /* Is the socket in non-blocking mode? */
101     non_blocking = 0;
102
103     /* IPv4. */
104     if (addr->sa_family == AF_INET) {
105         struct sockaddr_in *ipv4_addr = (struct sockaddr_in *)addr;
106
107         /* Extract IP and port. */
108         snprintf(port, sizeof(port), "%u", ntohs(ipv4_addr->sin_port));
109         if (inet_ntop(AF_INET, &ipv4_addr->sin_addr, ip, sizeof(ip)) == NULL) {
110             perror(LOG_PREFIX "inet_ntop()");
111             errno = EINVAL;
112             return -1;
113         }
114
115         /* Setup connection parameters to connect to tlsproxy. */
116         memset(&my_addr_ipv4, 0, sizeof(my_addr_ipv4));
117         my_addr_ipv4.sin_family      = AF_INET;
118         my_addr_ipv4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
119         my_addr_ipv4.sin_port        = htons((uint16_t)tlsproxy_port);
120
121         my_addr     = (struct sockaddr *)&my_addr_ipv4;
122         my_addr_len = sizeof(my_addr_ipv4);
123
124     /* IPv6. */
125     } else if (addr->sa_family == AF_INET6) {
126         struct sockaddr_in6 *ipv6_addr = (struct sockaddr_in6 *)addr;
127
128         /* Extract IP and port. */
129         snprintf(port, sizeof(port), "%u", ntohs(ipv6_addr->sin6_port));
130         if (inet_ntop(AF_INET6, &ipv6_addr->sin6_addr, ip, sizeof(ip)) == NULL) {
131             perror(LOG_PREFIX "inet_ntop()");
132             errno = EINVAL;
133             return -1;
134         }
135
136         /* Setup connection parameters to connect to tlsproxy. */
137         memset(&my_addr_ipv6, 0, sizeof(my_addr_ipv6));
138         my_addr_ipv6.sin6_family = AF_INET6;
139         my_addr_ipv6.sin6_addr   = in6addr_loopback;
140         my_addr_ipv6.sin6_port   = htons((uint16_t)tlsproxy_port);
141
142         my_addr     = (struct sockaddr *)&my_addr_ipv6;
143         my_addr_len = sizeof(my_addr_ipv6);
144
145     } else {
146         fprintf(stderr, LOG_PREFIX "unknown protocol family: %d\n",
147                         addr->sa_family);
148         errno = EAFNOSUPPORT;
149         return -1;
150     }
151
152     /* Simple way to pass through DNS requests. */
153     if (!strcmp(port, "53")) {
154         return real_connect(sockfd, addr, addrlen);
155     }
156
157     /* Perform the real connect. */
158     if (real_connect(sockfd, my_addr, my_addr_len) != 0) {
159         /* Handle non-blocking sockets. */
160         if (errno == EINPROGRESS) {
161             non_blocking = 1;
162         } else {
163             perror(LOG_PREFIX "connect()");
164             /* errno is correctly set by real connect(). */
165             return -1;
166         }
167     }
168
169     /* Make sure we can write. */
170     if (non_blocking) {
171         int optval;
172         socklen_t optlen;
173
174         if (poll_for(sockfd, 0 /* write */) != 0) {
175             /* poll_for() writes the error message */
176             errno = EINVAL;
177             return -1;
178         }
179
180         /* Check if connect() was successful. */
181         optlen = sizeof(optval);
182         if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) != 0) {
183             perror(LOG_PREFIX "getsockopt()");
184             errno = EINVAL;
185             return -1;
186         }
187         if (optval != 0) {
188             fprintf(stderr, LOG_PREFIX "connect() failed (SO_ERROR)\n");
189             errno = EINVAL;
190             return -1;
191         }
192     }
193
194     /* Send "CONNECT ip:port HTTP/1.0" to the proxy. */
195     snprintf(buffer, sizeof(buffer),
196              "CONNECT %s:%s HTTP/1.0\r\n\r\n", ip, port);
197     size = write(sockfd, buffer, strlen(buffer));
198     if (size != (int)strlen(buffer)) {
199         perror(LOG_PREFIX "write()");
200         errno = EINVAL;
201         return -1;
202     }
203     fsync(sockfd);
204
205     /* Make sure we can read. */
206     if (non_blocking) {
207         if (poll_for(sockfd, 1 /* read */) != 0) {
208             /* poll_for() writes the error message */
209             errno = EINVAL;
210             return -1;
211         }
212     }
213
214     /* Read the response from the proxy and check if it's fine. */
215     size = read(sockfd, buffer, sizeof(buffer));
216     if (size < 0) {
217         perror(LOG_PREFIX "read()");
218         errno = EINVAL;
219         return -1;
220     }
221     if (strncmp(buffer, "HTTP/1.0 200 Connection established\r\n", 37)) {
222         fprintf(stderr, LOG_PREFIX "invalid proxy response: %s", buffer);
223         errno = EINVAL;
224         return -1;
225     }
226
227     /* connect() returns 0 on success. */
228     return 0;
229 }
230
231 static int get_tlsproxy_port(void) {
232     const char *string;
233     int port = 0;
234
235     /* Extract port from the environment variable. */
236     string = getenv("TLSPROXYHELPER_PORT");
237     if (string != NULL) {
238         port = atoi(string);
239     }
240     /* No valid port specified, use the default one. */
241     if (port <= 0 || port > 0xffff) {
242         port = 9000;
243     }
244
245     return port;
246 }
247
248 /* Poll for read (mode = 1) or write (mode = 0) possibility on file descriptor
249  * sockfd. Returns 0 on success, -1 on failure. */
250 static int poll_for(int sockfd, int mode) {
251     int result;
252     struct pollfd fds[1];
253
254     fds[0].fd      = sockfd;
255     fds[0].events  = POLLERR | POLLHUP;
256     /* Either poll for read or write possibility. */
257     if (mode == 1) {
258         fds[0].events |= POLLIN | POLLPRI;
259     } else if (mode == 0) {
260         fds[0].events |= POLLOUT;
261     } else {
262         abort();
263     }
264     fds[0].revents = 0;
265
266     result = poll(fds, 1 /* fd count */, POLL_TIMEOUT);
267     if (result < 0) {
268         perror(LOG_PREFIX "poll()");
269         return -1;
270     } else if (result == 0) {
271         fprintf(stderr, LOG_PREFIX "poll() timeout\n");
272         return -1;
273     }
274
275     if (fds[0].revents & POLLERR) {
276         fprintf(stderr, LOG_PREFIX "poll(): POLLERR\n");
277         return -1;
278     }
279     if (fds[0].revents & POLLHUP) {
280         fprintf(stderr, LOG_PREFIX "poll(): POLLHUP\n");
281         return -1;
282     }
283
284     return 0;
285 }