]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - src/connection.c
Use %zu to print size_t and ssize_t.
[tlsproxy/tlsproxy.git] / src / connection.c
1 /*
2  * Handle connections.
3  *
4  * Copyright (C) 2011-2013  Simon Ruderich
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "tlsproxy.h"
21 #include "connection.h"
22 #include "verify.h"
23
24 #include <assert.h>
25 #include <errno.h>
26 #include <limits.h>
27 #include <netdb.h>
28 #include <poll.h>
29 #include <unistd.h>
30
31 #include <gnutls/x509.h>
32
33
34 /* Maximum length of a HTTP request line. Longer request lines are aborted
35  * with an error. The standard doesn't specify a maximum line length but this
36  * should be a good limit to make processing simpler. As HTTPS is used this
37  * doesn't limit long GET requests. */
38 #define MAX_REQUEST_LINE 4096
39
40 /* Format string used to send HTTP/1.0 error responses to the client.
41  *
42  * %s is used 5 times, first is the error code, then additional headers, next
43  * two are the error code (no %n$s which is not in C98!), the last is the
44  * message. */
45 #define HTTP_RESPONSE_FORMAT "HTTP/1.0 %s\r\n\
46 Content-Type: text/html; charset=US-ASCII\r\n\
47 %s\r\n\
48 <!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n\
49 <html>\n\
50 <head><title>%s</title></head>\n\
51 <body>\n\
52 <h1>%s</h1>\n\
53 <p>%s</p>\n\
54 </body>\n\
55 </html>\n"
56
57
58 static int initialize_tls_session_client(int peer_socket,
59         const char *hostname,
60         gnutls_session_t *session,
61         gnutls_certificate_credentials_t *x509_cred);
62 static int initialize_tls_session_server(int peer_socket,
63         gnutls_session_t *session,
64         gnutls_certificate_credentials_t *x509_cred);
65 static int initialize_tls_session_both(unsigned int flags,
66         int peer_socket,
67         gnutls_session_t *session,
68         gnutls_certificate_credentials_t *x509_cred);
69
70 static int fdopen_read_write(int socket, FILE **read_fd, FILE **write_fd);
71 static int read_http_request(FILE *client_fd, char *request, size_t length);
72 static void send_bad_request(FILE *client_fd);
73 static void send_authentication_required(FILE *client_fd);
74 static void send_forwarding_failure(FILE *client_fd);
75 static void tls_send_invalid_cert_message(gnutls_session_t session);
76
77 static void transfer_data(int client, int server);
78 static int read_from_write_to(int from, int to);
79 static void transfer_data_tls(int client, int server,
80                               gnutls_session_t client_session,
81                               gnutls_session_t server_session);
82 static int read_from_write_to_tls(gnutls_session_t from, gnutls_session_t to,
83                                   size_t buffer_size);
84
85 static int connect_to_host(const char *hostname, const char *port);
86
87 static int parse_request(const char *buffer, char *host, char *port,
88                                              int *version_minor);
89
90 static void log_session_information(gnutls_session_t session);
91
92
93 void handle_connection(int client_socket) {
94     int server_socket;
95     FILE *client_fd_read, *client_fd_write, *server_fd_read, *server_fd_write;
96
97     char buffer[MAX_REQUEST_LINE];
98     char host[MAX_REQUEST_LINE];
99     char port[5 + 1];
100
101     int version_minor; /* x in HTTP/1.x */
102     int result;
103
104     /* client_x509_cred is used when talking to the client (acting as a TSL
105      * server), server_x509_cred is used when talking to the server (acting as
106      * a TSL client). */
107     gnutls_certificate_credentials_t client_x509_cred, server_x509_cred;
108
109     gnutls_session_t client_session, server_session;
110     /* initialize_tls_session_*() called? - used for goto out */
111     int client_session_init, server_session_init;
112     /* gnutls_handshake() called? - used for goto out */
113     int client_session_started, server_session_started;
114     /* Validation failed? If yes we need to send the special "invalid"
115      * certificate. */
116     int validation_failed;
117
118     LOG(DEBUG1, "new connection");
119
120     server_socket = -1;
121     client_fd_read = NULL;
122     client_fd_write = NULL;
123     server_fd_read = NULL;
124     server_fd_write = NULL;
125     client_session_init = 0;
126     server_session_init = 0;
127     client_session_started = 0;
128     server_session_started = 0;
129     validation_failed = 0;
130
131     if (fdopen_read_write(client_socket, &client_fd_read,
132                                          &client_fd_write) != 0) {
133         goto out;
134     }
135
136     /* Read request line (CONNECT ..) and headers (they are discarded). */
137     result = read_http_request(client_fd_read, buffer, sizeof(buffer));
138     if (result == -1) {
139         LOG(WARNING, "read_http_request(): client read error");
140         goto out;
141     } else if (result == -2) {
142         LOG(WARNING, "read_http_request(): client EOF");
143         send_bad_request(client_fd_write);
144         goto out;
145     } else if (result == -3) {
146         LOG(WARNING, "read_http_request(): proxy authentication failed");
147         send_authentication_required(client_fd_write);
148         goto out;
149     }
150
151     if (parse_request(buffer, host, port, &version_minor) != 0) {
152         LOG(WARNING, "bad request: >%s<", buffer);
153         send_bad_request(client_fd_write);
154         goto out;
155     }
156
157     LOG(DEBUG2, "target: %s:%s (HTTP 1.%d)", host, port, version_minor);
158
159     /* Connect to proxy server or directly to server. */
160     if (global_proxy_host != NULL && global_proxy_port != NULL) {
161         LOG(DEBUG1, "connecting to %s:%s", global_proxy_host,
162                                            global_proxy_port);
163         server_socket = connect_to_host(global_proxy_host, global_proxy_port);
164     } else {
165         LOG(DEBUG1, "connecting to %s:%s", host, port);
166         server_socket = connect_to_host(host, port);
167     }
168
169     if (server_socket < 0) {
170         LOG(WARNING, "failed to connect to server");
171         send_forwarding_failure(client_fd_write);
172         goto out;
173     }
174     if (fdopen_read_write(server_socket, &server_fd_read,
175                                          &server_fd_write) != 0) {
176         send_forwarding_failure(client_fd_write);
177         goto out;
178     }
179
180     /* Connect to proxy if requested (command line option). */
181     if (global_proxy_host != NULL && global_proxy_port != NULL) {
182         fprintf(server_fd_write, "CONNECT %s:%s HTTP/1.0\r\n", host, port);
183         fprintf(server_fd_write, "\r\n");
184         fflush(server_fd_write);
185
186         /* Read response line from proxy server. */
187         result = read_http_request(server_fd_read, buffer, sizeof(buffer));
188         if (result == -1) {
189             LOG(WARNING, "read_http_request(): proxy read error");
190             send_forwarding_failure(client_fd_write);
191             goto out;
192         } else if (result == -2) {
193             LOG(WARNING, "read_http_request(): proxy EOF");
194             send_forwarding_failure(client_fd_write);
195             goto out;
196         }
197
198         /* Check response of proxy server. */
199         if (strncmp(buffer, "HTTP/1.0 200", 12)) {
200             LOG(WARNING, "bad proxy response: >%s<", buffer);
201             send_forwarding_failure(client_fd_write);
202             goto out;
203         }
204     }
205
206     LOG(DEBUG1, "connection to server established");
207
208     /* If the -u option is used and we don't know this hostname's server
209      * certificate then just pass through the connection and let the client
210      * verify the server certificate. */
211     if (global_passthrough_unknown) {
212         char path[TLSPROXY_MAX_PATH_LENGTH];
213         FILE *file = NULL;
214
215         if (server_certificate_file(&file, host, path, sizeof(path)) == -2) {
216             /* We've established a connection, tell the client. */
217             fprintf(client_fd_write, "HTTP/1.0 200 Connection established\r\n");
218             fprintf(client_fd_write, "\r\n");
219             fflush(client_fd_write);
220
221             LOG(DEBUG1, "transferring data");
222
223             /* Proxy data between client and server until one side is done
224              * (EOF or error). */
225             transfer_data(client_socket, server_socket);
226
227             LOG(DEBUG1, "finished transferring data");
228
229             goto out;
230         }
231         /* server_certificate_file() may have opened the file, close it. */
232         if (file != NULL) {
233             fclose(file);
234         }
235     }
236
237     /* Initialize TLS client credentials to talk to the server. */
238     result = initialize_tls_session_server(server_socket, &server_session,
239                                                           &server_x509_cred);
240     if (result != 0) {
241         LOG(WARNING, "initialize_tls_session_server() failed");
242         send_forwarding_failure(client_fd_write);
243         goto out;
244     }
245     server_session_init = 1;
246
247     LOG(DEBUG1, "starting server TLS handshake");
248
249     /* Try to establish TLS handshake between us and server. */
250     result = gnutls_handshake(server_session);
251     if (result != GNUTLS_E_SUCCESS) {
252         LOG(WARNING, "server TLS handshake failed: %s",
253                      gnutls_strerror(result));
254         send_forwarding_failure(client_fd_write);
255         goto out;
256     }
257     server_session_started = 1;
258
259     LOG(DEBUG1, "server TLS handshake finished");
260
261     if (global_log_level >= LOG_DEBUG2_LEVEL) {
262         log_session_information(server_session);
263     }
264
265     /* Make sure the server certificate is valid and known. */
266     if (verify_tls_connection(server_session, host) != 0) {
267         LOG(ERROR, "server certificate validation failed!");
268         /* We'll send the error message over our TLS connection to the client,
269          * but with an invalid certificate. No data is transfered from/to the
270          * target server. */
271         validation_failed = 1;
272     }
273
274     /* Initialize TLS server credentials to talk to the client. */
275     result = initialize_tls_session_client(client_socket,
276                                            /* use a special host if the server
277                                             * certificate was invalid */
278                                            (validation_failed) ? "invalid"
279                                                                : host,
280                                            &client_session,
281                                            &client_x509_cred);
282     if (result != 0) {
283         LOG(WARNING, "initialize_tls_session_client() failed");
284         send_forwarding_failure(client_fd_write);
285         goto out;
286     }
287     client_session_init = 1;
288
289     /* We've established a connection, tell the client. */
290     fprintf(client_fd_write, "HTTP/1.0 200 Connection established\r\n");
291     fprintf(client_fd_write, "\r\n");
292     fflush(client_fd_write);
293
294     LOG(DEBUG1, "starting client TLS handshake");
295
296     /* Try to establish TLS handshake between client and us. */
297     result = gnutls_handshake(client_session);
298     if (result != GNUTLS_E_SUCCESS) {
299         LOG(WARNING, "client TLS handshake failed: %s",
300                      gnutls_strerror(result));
301         send_forwarding_failure(client_fd_write);
302         goto out;
303     }
304     client_session_started = 1;
305
306     LOG(DEBUG1, "client TLS handshake finished");
307
308     if (global_log_level >= LOG_DEBUG2_LEVEL) {
309         log_session_information(client_session);
310     }
311
312     /* Tell the client that the verification failed. Shouldn't be necessary as
313      * the client should terminate the connection because he received the
314      * invalid certificate but better be sure. */
315     if (validation_failed) {
316         tls_send_invalid_cert_message(client_session);
317         goto out;
318     }
319
320     LOG(DEBUG1, "transferring TLS data");
321
322     /* Proxy data between client and server until one side is done (EOF or
323      * error). */
324     transfer_data_tls(client_socket, server_socket,
325                       client_session, server_session);
326
327     LOG(DEBUG1, "finished transferring TLS data");
328
329 out:
330     /* Close TLS sessions if necessary. Use GNUTLS_SHUT_RDWR so the data is
331      * reliable transmitted. */
332     if (server_session_started) {
333         /* Recent gnutls-serv (used in the test-suite) won't terminate the
334          * connection when gnutls_bye(session, GNUTLS_SHUT_RDWR) is used
335          * before any other data was received. If the validation failed just
336          * close the connection without waiting for data, we won't read it
337          * anyway.
338          *
339          * For verified connections GNUTLS_SHUT_RDWR is important or we might
340          * lose data. */
341         gnutls_bye(server_session, validation_failed ? GNUTLS_SHUT_WR
342                                                      : GNUTLS_SHUT_RDWR);
343     }
344     if (client_session_started) {
345         gnutls_bye(client_session, GNUTLS_SHUT_RDWR);
346     }
347     if (server_session_init) {
348         gnutls_deinit(server_session);
349         gnutls_certificate_free_credentials(server_x509_cred);
350     }
351     if (client_session_init) {
352         gnutls_deinit(client_session);
353         gnutls_certificate_free_credentials(client_x509_cred);
354     }
355
356     /* Close connection to server/proxy. */
357     if (server_fd_read != NULL) {
358         if (server_fd_write != NULL) {
359             fclose(server_fd_write);
360         }
361         fclose(server_fd_read);
362     } else if (server_socket != -1) {
363         close(server_socket);
364     }
365     LOG(DEBUG1, "connection to server closed");
366     /* Close connection to client. */
367     if (client_fd_read != NULL) {
368         if (client_fd_write != NULL) {
369             fclose(client_fd_write);
370         }
371         fclose(client_fd_read);
372     } else {
373         close(client_socket);
374     }
375     LOG(DEBUG1, "connection to client closed");
376
377     LOG(DEBUG1, "connection finished");
378 }
379
380
381 static int initialize_tls_session_client(int peer_socket,
382         const char *hostname,
383         gnutls_session_t *session,
384         gnutls_certificate_credentials_t *x509_cred) {
385     int result;
386     int use_invalid_cert;
387     char path[TLSPROXY_MAX_PATH_LENGTH];
388
389     /* The "invalid" hostname is special. If it's used we send an invalid
390      * certificate to let the client know something is wrong. */
391     use_invalid_cert = (!strcmp(hostname, "invalid"));
392
393     if (proxy_certificate_path(hostname, path, sizeof(path)) != 0) {
394         LOG(ERROR,
395             "initialize_tls_session_client(): "
396             "failed to get proxy certificate path");
397         return -1;
398     }
399
400     result = gnutls_certificate_allocate_credentials(x509_cred);
401     if (result != GNUTLS_E_SUCCESS) {
402         LOG(ERROR,
403             "initialize_tls_session_client(): "
404             "gnutls_certificate_allocate_credentials(): %s",
405             gnutls_strerror(result));
406         return -1;
407     }
408
409     /* Load proxy CA file, this CA "list" is send to the client. */
410     if (!use_invalid_cert) {
411         result = gnutls_certificate_set_x509_trust_file(*x509_cred,
412                                                         PROXY_CA_PATH,
413                                                         GNUTLS_X509_FMT_PEM);
414         if (result <= 0) {
415             LOG(ERROR,
416                 "initialize_tls_session_client(): can't read CA file: '%s'",
417                 PROXY_CA_PATH);
418             gnutls_certificate_free_credentials(*x509_cred);
419             return -1;
420         } else if (result != 1) {
421             /* Must contain only one CA, our proxy CA. */
422             LOG(ERROR, "initialize_tls_session_client(): multiple CAs found");
423             gnutls_certificate_free_credentials(*x509_cred);
424             return -1;
425         }
426     }
427     /* If the invalid hostname was specified do nothing, we use a self-signed
428      * certificate in this case. */
429
430     /* And certificate for this website and proxy's private key. */
431     if (!use_invalid_cert) {
432         result = gnutls_certificate_set_x509_key_file(*x509_cred,
433                                                       path,
434                                                       PROXY_KEY_PATH,
435                                                       GNUTLS_X509_FMT_PEM);
436     /* If the invalid hostname was specified load our special "invalid"
437      * certificate. */
438     } else {
439         result = gnutls_certificate_set_x509_key_file(*x509_cred,
440                                                       PROXY_INVALID_CERT_PATH,
441                                                       PROXY_KEY_PATH,
442                                                       GNUTLS_X509_FMT_PEM);
443     }
444     if (result != GNUTLS_E_SUCCESS) {
445         LOG(ERROR,
446             "initialize_tls_session_client(): "
447             "can't read server certificate ('%s') or key file ('%s'): %s",
448             path, PROXY_KEY_PATH, gnutls_strerror(result));
449         gnutls_certificate_free_credentials(*x509_cred);
450         /* Could be a missing certificate. */
451         return -2;
452     }
453
454     gnutls_certificate_set_dh_params(*x509_cred, global_tls_dh_params);
455
456     return initialize_tls_session_both(GNUTLS_SERVER,
457                                        peer_socket, session, x509_cred);
458 }
459 static int initialize_tls_session_server(int peer_socket,
460         gnutls_session_t *session,
461         gnutls_certificate_credentials_t *x509_cred) {
462     int result;
463
464     result = gnutls_certificate_allocate_credentials(x509_cred);
465     if (result != GNUTLS_E_SUCCESS) {
466         LOG(ERROR,
467             "initialize_tls_session_server(): "
468             "gnutls_certificate_allocate_credentials(): %s",
469             gnutls_strerror(result));
470         return -1;
471     }
472
473     return initialize_tls_session_both(GNUTLS_CLIENT,
474                                        peer_socket, session, x509_cred);
475 }
476 static int initialize_tls_session_both(unsigned int flags,
477         int peer_socket,
478         gnutls_session_t *session,
479         gnutls_certificate_credentials_t *x509_cred) {
480     int result;
481
482     *session = NULL;
483
484     result = gnutls_init(session, flags);
485     if (result != GNUTLS_E_SUCCESS) {
486         LOG(ERROR,
487             "initialize_tls_session_both(): gnutls_init(): %s",
488             gnutls_strerror(result));
489         goto err;
490     }
491     result = gnutls_priority_set(*session, global_tls_priority_cache);
492     if (result != GNUTLS_E_SUCCESS) {
493         LOG(ERROR,
494             "initialize_tls_session_both(): gnutls_priority_set(): %s",
495             gnutls_strerror(result));
496         goto err;
497     }
498     result = gnutls_credentials_set(*session,
499                                     GNUTLS_CRD_CERTIFICATE, *x509_cred);
500     if (result != GNUTLS_E_SUCCESS) {
501         LOG(ERROR,
502             "initialize_tls_session_both(): gnutls_credentials_set(): %s",
503             gnutls_strerror(result));
504         goto err;
505     }
506
507 #ifdef HAVE_GNUTLS_TRANSPORT_SET_INT2
508     /* gnutls_transport_set_int() is a macro. */
509     gnutls_transport_set_int(*session, peer_socket);
510 #else
511     gnutls_transport_set_ptr(*session, (gnutls_transport_ptr_t)peer_socket);
512 #endif
513
514     return 0;
515
516 err:
517     if (*session) {
518         gnutls_deinit(*session);
519     }
520     gnutls_certificate_free_credentials(*x509_cred);
521     return -1;
522 }
523
524
525 static int fdopen_read_write(int socket, FILE **read_fd, FILE **write_fd) {
526     *read_fd = fdopen(socket, "r");
527     if (*read_fd == NULL) {
528         LOG_PERROR(WARNING, "fdopen_read_write(): fdopen(\"r\") failed");
529         return -1;
530     }
531
532     *write_fd = fdopen(dup(socket), "w");
533     if (*write_fd == NULL) {
534         LOG_PERROR(WARNING, "fdopen_read_write(): fdopen(\"w\") failed");
535         fclose(*read_fd);
536         *read_fd = NULL; /* "tell" caller read_fd is already closed */
537         return -1;
538     }
539
540     return 0;
541 }
542
543 /* Read HTTP request line and headers (ignored).
544  *
545  * On success 0 is returned, -1 on client error, -2 on unexpected EOF.
546  */
547 static int read_http_request(FILE *client_fd, char *request, size_t length) {
548     char buffer[MAX_REQUEST_LINE];
549     int found_proxy_authorization;
550
551     assert(length <= INT_MAX);
552     if (fgets(request, (int)length, client_fd) == NULL) {
553         if (ferror(client_fd)) {
554             LOG_PERROR(WARNING, "read_http_request(): fgets()");
555             return -1;
556         }
557         /* EOF */
558         return -2;
559     }
560
561     found_proxy_authorization = 0;
562     while (fgets(buffer, sizeof(buffer), client_fd) != NULL) {
563         const char *authentication = "Proxy-Authorization: Basic ";
564
565         if (global_http_digest_authorization != NULL
566                 && !strncmp(buffer, authentication, strlen(authentication))) {
567             found_proxy_authorization = 1;
568
569             /* Check if the passphrase matches. */
570             strtok(buffer, "\r\n");
571             if (strcmp(buffer + strlen(authentication),
572                        global_http_digest_authorization)) {
573                 return -3;
574             }
575         }
576
577         /* End of header. */
578         if (!strcmp(buffer, "\n") || !strcmp(buffer, "\r\n")) {
579             break;
580         }
581     }
582     if (ferror(client_fd)) {
583         LOG_PERROR(WARNING, "read_http_request(): fgets()");
584         return -1;
585     } else if (feof(client_fd)) {
586         return -2;
587     }
588
589     if (global_http_digest_authorization != NULL && !found_proxy_authorization) {
590         return -3;
591     }
592
593     return 0;
594 }
595
596 static void send_bad_request(FILE *client_fd) {
597     const char error[] = "400 Bad Request";
598     const char msg[]   = "Your browser sent an invalid request.";
599     fprintf(client_fd, HTTP_RESPONSE_FORMAT, error, "", error, error, msg);
600     fflush(client_fd);
601 }
602 static void send_authentication_required(FILE *client_fd) {
603     const char error[] = "407 Proxy Authentication Required";
604     const char auth[]  = "Proxy-Authenticate: Basic realm=\"tlsproxy\"\r\n";
605     const char msg[]   = "TODO";
606     fprintf(client_fd, HTTP_RESPONSE_FORMAT, error, auth, error, error, msg);
607     fflush(client_fd);
608 }
609 static void send_forwarding_failure(FILE *client_fd) {
610     const char error[] = "503 Forwarding failure";
611     const char msg[]   = "Failed to connect to server, check logs.";
612     fprintf(client_fd, HTTP_RESPONSE_FORMAT, error, "", error, error, msg);
613     fflush(client_fd);
614 }
615 static void tls_send_invalid_cert_message(gnutls_session_t session) {
616     const char error[] = "500 Internal Server Error";
617     const char msg[]   = "Server certificate validation failed, check logs.";
618
619     int result;
620     char buffer[sizeof(HTTP_RESPONSE_FORMAT)
621                 + 3 * sizeof(error) + sizeof(msg)];
622
623     result = snprintf(buffer, sizeof(buffer), HTTP_RESPONSE_FORMAT,
624                                               error, "", error, error, msg);
625     assert(result > 0 && (size_t)result < sizeof(buffer));
626
627     gnutls_record_send(session, buffer, strlen(buffer));
628 }
629
630
631 /* Transfer data between client and server sockets until one closes the
632  * connection. */
633 static void transfer_data(int client, int server) {
634     struct pollfd fds[2];
635     fds[0].fd      = client;
636     fds[0].events  = POLLIN | POLLPRI | POLLHUP | POLLERR;
637     fds[0].revents = 0;
638     fds[1].fd      = server;
639     fds[1].events  = POLLIN | POLLPRI | POLLHUP | POLLERR;
640     fds[1].revents = 0;
641
642     LOG(DEBUG2, "transfer_data(): %d -> %d", client, server);
643
644     for (;;) {
645         int result = poll(fds, 2 /* fd count */, -1 /* no timeout */);
646         if (result < 0) {
647             LOG_PERROR(ERROR, "transfer_data(): poll()");
648             return;
649         }
650
651         /* Data available from client. */
652         if (fds[0].revents & POLLIN || fds[0].revents & POLLPRI) {
653             if (read_from_write_to(client, server) != 0) {
654                 /* EOF (or other error) */
655                 break;
656             }
657         }
658         /* Data available from server. */
659         if (fds[1].revents & POLLIN || fds[1].revents & POLLPRI) {
660             if (read_from_write_to(server, client) != 0) {
661                 /* EOF (or other error) */
662                 break;
663             }
664         }
665
666         /* Client closed connection. */
667         if (fds[0].revents & POLLERR || fds[0].revents & POLLHUP) {
668             break;
669         }
670         /* Server closed connection. */
671         if (fds[1].revents & POLLERR || fds[1].revents & POLLHUP) {
672             break;
673         }
674     }
675 }
676
677 /* Read available data from socket from and write it to socket to. At maximum
678  * 4096 bytes are read/written. */
679 static int read_from_write_to(int from, int to) {
680     ssize_t size_read;
681     ssize_t size_written;
682     char buffer[4096];
683
684     size_read = read(from, buffer, sizeof(buffer));
685     if (size_read < 0) {
686         LOG_PERROR(WARNING, "read_from_write_to(): read()");
687         return -1;
688     /* EOF */
689     } else if (size_read == 0) {
690         return -1;
691     }
692
693     size_written = write(to, buffer, (size_t)size_read);
694     if (size_written < 0) {
695         LOG_PERROR(WARNING, "read_from_write_to(): write()");
696         return -1;
697     }
698     if (size_read != size_written) {
699         LOG(ERROR, "read_from_write_to(): only written %zu of %zu bytes!",
700                    size_written, size_read);
701         return -1;
702     }
703
704     return 0;
705 }
706
707 /* Transfer data between client and server TLS connection until one closes the
708  * connection. */
709 static void transfer_data_tls(int client, int server,
710                               gnutls_session_t client_session,
711                               gnutls_session_t server_session) {
712     size_t buffer_size;
713
714     struct pollfd fds[2];
715     fds[0].fd      = client;
716     fds[0].events  = POLLIN | POLLPRI | POLLHUP | POLLERR;
717     fds[0].revents = 0;
718     fds[1].fd      = server;
719     fds[1].events  = POLLIN | POLLPRI | POLLHUP | POLLERR;
720     fds[1].revents = 0;
721
722     /* Get maximum possible buffer size. */
723     buffer_size = gnutls_record_get_max_size(client_session);
724     if (gnutls_record_get_max_size(server_session) < buffer_size) {
725         buffer_size = gnutls_record_get_max_size(server_session);
726     }
727     LOG(DEBUG2, "transfer_data_tls(): suggested buffer size: %zu",
728                 buffer_size);
729
730     for (;;) {
731         int result = poll(fds, 2 /* fd count */, -1 /* no timeout */);
732         if (result < 0) {
733             LOG_PERROR(ERROR, "transfer_data(): poll()");
734             return;
735         }
736
737         /* Data available from client. */
738         if (fds[0].revents & POLLIN || fds[0].revents & POLLPRI) {
739             if (read_from_write_to_tls(client_session, server_session,
740                                        buffer_size) != 0) {
741                 /* EOF (or other error) */
742                 break;
743             }
744         }
745         /* Data available from server. */
746         if (fds[1].revents & POLLIN || fds[1].revents & POLLPRI) {
747             if (read_from_write_to_tls(server_session, client_session,
748                                        buffer_size) != 0) {
749                 /* EOF (or other error) */
750                 break;
751             }
752         }
753
754         /* Client closed connection. */
755         if (fds[0].revents & POLLERR || fds[0].revents & POLLHUP) {
756             break;
757         }
758         /* Server closed connection. */
759         if (fds[1].revents & POLLERR || fds[1].revents & POLLHUP) {
760             break;
761         }
762     }
763 }
764
765 /* Read available data from session from and write to session to. */
766 static int read_from_write_to_tls(gnutls_session_t from,
767                                   gnutls_session_t to,
768                                   size_t buffer_size) {
769     ssize_t size_read;
770     ssize_t size_written;
771     char buffer[16384]; /* GnuTLS default maximum */
772
773     if (buffer_size > sizeof(buffer)) {
774         LOG(WARNING, "read_from_write_to_tls(): reduced buffer size to %zu",
775                      sizeof(buffer));
776         buffer_size = sizeof(buffer);
777     }
778
779     size_read = gnutls_record_recv(from, buffer, buffer_size);
780     if (size_read < 0) {
781         LOG(WARNING, "read_from_write_to_tls(): gnutls_record_recv(): %s",
782                      gnutls_strerror((int)size_read));
783         return -1;
784     /* EOF */
785     } else if (size_read == 0) {
786         return -1;
787     }
788
789     size_written = gnutls_record_send(to, buffer, (size_t)size_read);
790     if (size_written < 0) {
791         LOG(WARNING, "read_from_write_to_tls(): gnutls_record_send(): %s",
792                      gnutls_strerror((int)size_written));
793         return -1;
794     }
795     if (size_read != size_written) {
796         LOG(ERROR, "read_from_write_to_tls(): only written %zu of %zu bytes!",
797                    size_written, size_read);
798         return -1;
799     }
800
801     return 0;
802 }
803
804
805 static int connect_to_host(const char *hostname, const char *port) {
806     struct addrinfo gai_hints;
807     struct addrinfo *gai_result;
808     int gai_return;
809
810     int server_socket;
811     struct addrinfo *server;
812
813     if (hostname == NULL || port == NULL) {
814         return -1;
815     }
816
817     /* Get IP of hostname server. */
818     memset(&gai_hints, 0, sizeof(gai_hints));
819     gai_hints.ai_family   = AF_UNSPEC;
820     gai_hints.ai_socktype = SOCK_STREAM;
821     gai_hints.ai_protocol = 0;
822     gai_hints.ai_flags    = AI_NUMERICSERV /* given port is numeric */
823                           | AI_ADDRCONFIG  /* supported by this computer */
824                           | AI_V4MAPPED;   /* support IPv4 through IPv6 */
825     gai_return = getaddrinfo(hostname, port, &gai_hints, &gai_result);
826     if (gai_return != 0) {
827         if (gai_return == EAI_SYSTEM) {
828             LOG_PERROR(WARNING, "connect_to_host(): getaddrinfo()");
829         } else {
830             LOG(WARNING, "connect_to_host(): getaddrinfo(): %s",
831                          gai_strerror(gai_return));
832         }
833         return -1;
834     }
835
836     /* Now try to connect to each server returned by getaddrinfo(), use the
837      * first successful connect. */
838     for (server = gai_result; server != NULL; server = server->ai_next) {
839         server_socket = socket(server->ai_family,
840                                server->ai_socktype,
841                                server->ai_protocol);
842         if (server_socket < 0) {
843             LOG_PERROR(DEBUG1, "connect_to_host(): socket(), trying next");
844             continue;
845         }
846
847         if (connect(server_socket, server->ai_addr, server->ai_addrlen) == 0) {
848             break;
849         }
850         LOG_PERROR(DEBUG1, "connect_to_host(): connect(), trying next");
851
852         close(server_socket);
853     }
854     /* Make sure we free the result from getaddrinfo(). */
855     freeaddrinfo(gai_result);
856
857     if (server == NULL) {
858         LOG_PERROR(WARNING, "connect_to_host(): no server found, abort");
859         return -1;
860     }
861
862     return server_socket;
863 }
864
865
866 /* Parse HTTP CONNECT request string and save its parameters.
867  *
868  * The following format is expected: "CONNECT host:port HTTP/1.x".
869  *
870  * request and host must have the same size! port must be at least 6 bytes
871  * long (5 + '\0').
872  */
873 static int parse_request(const char *request, char *host, char *port,
874                                               int *version_minor) {
875     int port_unused; /* just used to verify the port is numeric */
876     char *position;
877
878     /* scanf() doesn't check spaces. */
879     if (strncmp(request, "CONNECT ", 8)) {
880         return -1;
881     }
882     /* Check request and extract data, "host:port" is not yet separated. */
883     if (sscanf(request, "CONNECT %s HTTP/1.%d", host, version_minor) != 2) {
884         return -1;
885     }
886     /* Make sure ":port" is there. */
887     if ((position = strchr(host, ':')) == NULL) {
888         return -1;
889     }
890     /* Make sure port is numeric. */
891     if (sscanf(position + 1, "%d", &port_unused) != 1) {
892         return -1;
893     }
894     /* Store it in *port. */
895     strncpy(port, position + 1, 5);
896     port[5] = '\0';
897     /* And remove port from host. */
898     *position = '\0';
899
900     return 0;
901 }
902
903
904 static void log_session_information(gnutls_session_t session) {
905     /* From doc/examples/ex-session-info.c of GnuTLS 3.2.3's tarball and
906      * modified, thanks. */
907
908     const char *tmp;
909     gnutls_credentials_type_t cred;
910     gnutls_kx_algorithm_t kx;
911     int dhe, ecdh;
912
913     dhe = 0;
914     ecdh = 0;
915
916     /* Key exchange algorithm. */
917     kx = gnutls_kx_get(session);
918     LOG(DEBUG2, "- key exchange: %s", gnutls_kx_get_name(kx));
919
920     /* Authentication type. */
921     cred = gnutls_auth_get_type(session);
922     switch (cred) {
923         case GNUTLS_CRD_CERTIFICATE:
924             if (kx == GNUTLS_KX_DHE_RSA
925                     || kx == GNUTLS_KX_DHE_DSS) {
926                 dhe = 1;
927 #ifdef GNUTLS_KX_ECDHE_RSA
928             } else if (kx == GNUTLS_KX_ECDHE_RSA
929                     || kx == GNUTLS_KX_ECDHE_ECDSA) {
930                 ecdh = 1;
931 #endif
932             }
933             break;
934
935         case GNUTLS_CRD_IA:
936         case GNUTLS_CRD_SRP:
937         case GNUTLS_CRD_PSK:
938         case GNUTLS_CRD_ANON:
939         default:
940             /* This shouldn't occur. */
941             LOG(WARNING, "unexpected authentication method: %d", cred);
942             break;
943     }
944
945     /* Information about key exchange. */
946     if (dhe) {
947         LOG(DEBUG2, "- ephemeral DH using prime of %d bits",
948                     gnutls_dh_get_prime_bits(session));
949     } else if (ecdh) {
950 #ifdef GNUTLS_KX_ECDHE_RSA
951         LOG(DEBUG2, "- ephemeral ECDH using curve %s",
952                     gnutls_ecc_curve_get_name(gnutls_ecc_curve_get(session)));
953 #endif
954     }
955
956     tmp = gnutls_protocol_get_name(gnutls_protocol_get_version(session));
957     LOG(DEBUG2, "- protocol: %s", tmp); /* e.g. TLS 1.0 */
958
959     tmp = gnutls_certificate_type_get_name(gnutls_certificate_type_get(session));
960     LOG(DEBUG2, "- certificate type: %s", tmp);
961
962     tmp = gnutls_compression_get_name(gnutls_compression_get(session));
963     LOG(DEBUG2, "- compression: %s", tmp);
964
965     tmp = gnutls_cipher_get_name(gnutls_cipher_get(session));
966     LOG(DEBUG2, "- cipher: %s", tmp);
967
968     tmp = gnutls_mac_get_name(gnutls_mac_get(session));
969     LOG(DEBUG2, "- MAC: %s", tmp);
970 }