]> ruderich.org/simon Gitweb - tlsproxy/tlsproxy.git/blob - src/connection.c
7ea6eb24a7573dd14d5d21facf89a06dc2546c9d
[tlsproxy/tlsproxy.git] / src / connection.c
1 /*
2  * Handle connections.
3  *
4  * Copyright (C) 2011  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
23 /* close() */
24 #include <unistd.h>
25 /* getaddrinfo() */
26 #include <netdb.h>
27 /* poll() */
28 #include <poll.h>
29 /* errno */
30 #include <errno.h>
31
32
33 /* Maximum line of a HTTP request line. Longer request lines are aborted with
34  * an error. The standard doesn't specify a maximum line length but this
35  * should be a good limit to make processing simpler. */
36 #define MAX_REQUEST_LINE 4096
37
38 /* Paths to necessary TLS files: the CA and the server key. */
39 #define PROXY_CA_FILE  "proxy-ca.pem"
40 #define PROXY_KEY_FILE "proxy-key.pem"
41
42
43 static int initialize_tls_session_client(int peer_socket,
44         const char *hostname,
45         gnutls_session_t *session,
46         gnutls_certificate_credentials_t *x509_cred);
47 static int initialize_tls_session_server(int peer_socket,
48         gnutls_session_t *session,
49         gnutls_certificate_credentials_t *x509_cred);
50
51 static int read_http_request(FILE *client_fd, char *request, size_t length);
52 static void send_bad_request(FILE *client_fd);
53 static void send_forwarding_failure(FILE *client_fd);
54
55 #if 0
56 static void transfer_data(int client, int server);
57 static int read_from_write_to(int from, int to);
58 #endif
59 static void transfer_data_tls(int client, int server,
60                               gnutls_session_t client_session,
61                               gnutls_session_t server_session);
62 static int read_from_write_to_tls(gnutls_session_t from, gnutls_session_t to);
63
64 static int connect_to_host(const char *hostname, const char *port);
65
66 static int parse_request(const char *buffer, char *host, char *port,
67                                              int *version_minor);
68
69
70 void handle_connection(int client_socket) {
71     int server_socket;
72     FILE *client_fd, *server_fd;
73
74     char buffer[MAX_REQUEST_LINE];
75     char host[MAX_REQUEST_LINE];
76     char port[5 + 1];
77
78     int version_minor;
79     int result;
80
81     /* client_x509_cred is used when talking to the client (acting as a TSL
82      * server), server_x509_cred is used when talking to the server (acting as
83      * a TSL client). */
84     gnutls_certificate_credentials_t client_x509_cred, server_x509_cred;
85
86     gnutls_session_t client_session, server_session;
87     /* initialize_tls_session_*() called? - used for goto out */
88     int client_session_init, server_session_init;
89     /* gnutls_handshake() called? - used for goto out */
90     int client_session_started, server_session_started;
91
92     LOG(LOG_DEBUG, "new connection");
93
94     server_socket = -1;
95     client_fd = NULL;
96     server_fd = NULL;
97     client_session_init = 0;
98     server_session_init = 0;
99     client_session_started = 0;
100     server_session_started = 0;
101
102     client_fd = fdopen(client_socket, "a+");
103     if (NULL == client_fd) {
104         LOG_PERROR(LOG_WARNING, "fdopen(): client failed");
105         goto out;
106     }
107
108     /* Read request line (CONNECT ..) and headers (they are discarded). */
109     result = read_http_request(client_fd, buffer, sizeof(buffer));
110     if (-1 == result) {
111         /* Read error. */
112         LOG(LOG_WARNING, "read_http_request(): client read error");
113         goto out;
114     } else if (-2 == result) {
115         /* EOF */
116         LOG(LOG_WARNING, "read_http_request(): client EOF");
117         send_bad_request(client_fd);
118         goto out;
119     }
120
121     if (0 != parse_request(buffer, host, port, &version_minor)) {
122         LOG(LOG_WARNING, "bad request: %s", buffer);
123         send_bad_request(client_fd);
124         goto out;
125     }
126
127     LOG(LOG_DEBUG, "target: %s:%s (HTTP 1.%d)", host, port, version_minor);
128
129     /* Connect to proxy server or directly to server. */
130     if (NULL != global_proxy_host && NULL != global_proxy_port) {
131         LOG(LOG_DEBUG, "connecting to %s:%s", global_proxy_host,
132                                               global_proxy_port);
133         server_socket = connect_to_host(global_proxy_host, global_proxy_port);
134     } else {
135         LOG(LOG_DEBUG, "connecting to %s:%s", host, port);
136         server_socket = connect_to_host(host, port);
137     }
138
139     if (-1 == server_socket) {
140         LOG(LOG_WARNING, "failed to connect to server");
141         send_forwarding_failure(client_fd);
142         goto out;
143     }
144     server_fd = fdopen(server_socket, "a+");
145     if (NULL == server_fd) {
146         LOG_PERROR(LOG_WARNING, "fdopen(): server failed");
147         send_forwarding_failure(client_fd);
148         goto out;
149     }
150
151     /* Connect to proxy if requested (command line option). */
152     if (NULL != global_proxy_host && NULL != global_proxy_port) {
153         fprintf(server_fd, "CONNECT %s:%s HTTP/1.0\r\n", host, port);
154         fprintf(server_fd, "\r\n");
155
156         /* Read response line from proxy server. */
157         result = read_http_request(server_fd, buffer, sizeof(buffer));
158         if (-1 == result) {
159             /* Read error. */
160             LOG(LOG_WARNING, "read_http_request(): proxy read error");
161             send_forwarding_failure(client_fd);
162             goto out;
163         } else if (-2 == result) {
164             /* EOF */
165             LOG(LOG_WARNING, "read_http_request(): proxy EOF");
166             send_forwarding_failure(client_fd);
167             goto out;
168         }
169
170         /* Check response of proxy server. */
171         if (0 != strncmp(buffer, "HTTP/1.0 200", 12)) {
172             LOG(LOG_WARNING, "bad proxy response: %s", buffer);
173             send_forwarding_failure(client_fd);
174             goto out;
175         }
176     }
177
178     LOG(LOG_DEBUG, "connection to server established");
179
180     /* We've established a connection, tell the client. */
181     fprintf(client_fd, "HTTP/1.0 200 Connection established\r\n");
182     fprintf(client_fd, "\r\n");
183     fflush(client_fd);
184
185     /* Initialize TLS server credentials to talk to the client. */
186     result = initialize_tls_session_client(client_socket, host,
187                                            &client_session,
188                                            &client_x509_cred);
189     if (0 != result) {
190         LOG(LOG_WARNING, "initialize_tls_session_client() failed");
191         send_forwarding_failure(client_fd);
192         goto out;
193     }
194     client_session_init = 1;
195
196     LOG(LOG_DEBUG, "starting client TLS handshake");
197
198     /* Try to establish TLS handshake between client and us. */
199     result = gnutls_handshake(client_session);
200     if (GNUTLS_E_SUCCESS != result) {
201         LOG(LOG_WARNING, "client TLS handshake failed: %s",
202                          gnutls_strerror(result));
203         send_forwarding_failure(client_fd);
204         goto out;
205     }
206     client_session_started = 1;
207
208     LOG(LOG_DEBUG, "client TLS handshake finished");
209
210     result = initialize_tls_session_server(server_socket, &server_session,
211                                                           &server_x509_cred);
212     /* Initialize TLS client credentials to talk to the server. */
213     if (0 != result) {
214         LOG(LOG_WARNING, "initialize_tls_session_server() failed");
215         send_forwarding_failure(client_fd);
216         goto out;
217     }
218     server_session_init = 1;
219
220     LOG(LOG_DEBUG, "starting server TLS handshake");
221
222     /* Try to establish TLS handshake between us and server. */
223     result = gnutls_handshake(server_session);
224     if (GNUTLS_E_SUCCESS != result) {
225         LOG(LOG_WARNING, "server TLS handshake failed: %s",
226                          gnutls_strerror(result));
227         send_forwarding_failure(client_fd);
228         goto out;
229     }
230     server_session_started = 1;
231
232     LOG(LOG_DEBUG, "server TLS handshake finished, transferring data");
233
234     /* FIXME: verify server's fingerprint */
235
236     /* Proxy data between client and server until one suite is done (EOF or
237      * error). */
238     transfer_data_tls(client_socket, server_socket,
239                       client_session, server_session);
240
241     LOG(LOG_DEBUG, "finished transferring data");
242
243 out:
244     /* Close TLS sessions if necessary. */
245     if (0 != server_session_started) {
246         gnutls_bye(server_session, GNUTLS_SHUT_WR);
247     }
248     if (0 != client_session_started) {
249         gnutls_bye(client_session, GNUTLS_SHUT_WR);
250     }
251     if (0 != server_session_init) {
252         gnutls_deinit(server_session);
253         gnutls_certificate_free_credentials(server_x509_cred);
254     }
255     if (0 != client_session_init) {
256         gnutls_deinit(client_session);
257         gnutls_certificate_free_cas(client_x509_cred);
258         gnutls_certificate_free_keys(client_x509_cred);
259         gnutls_certificate_free_credentials(client_x509_cred);
260     }
261
262     /* Close connection to server/proxy. */
263     if (NULL != server_fd) {
264         fclose(server_fd);
265     } else if (-1 != server_socket) {
266         close(server_socket);
267     }
268     LOG(LOG_DEBUG, "connection to server closed");
269     /* Close connection to client. */
270     if (NULL != client_fd) {
271         fclose(client_fd);
272     } else {
273         close(client_socket);
274     }
275     LOG(LOG_DEBUG, "connection to client closed");
276
277     LOG(LOG_DEBUG, "connection finished");
278 }
279
280
281 static int initialize_tls_session_client(int peer_socket,
282         const char *hostname,
283         gnutls_session_t *session,
284         gnutls_certificate_credentials_t *x509_cred) {
285     int result;
286     char path[1024];
287     /* The server certificate for the given hostname is stored in
288      * "./certificate-hostname-proxy.pem". */
289 #define PATH_FORMAT "./certificate-%s-proxy.pem"
290
291     /* Hostname too long. */
292     if (sizeof(path) - strlen(PATH_FORMAT) <= strlen(hostname)) {
293         LOG(LOG_WARNING,
294             "initialize_tls_session_client(): hostname too long: '%s'",
295             hostname);
296         return -1;
297     }
298     /* Try to prevent path traversals in hostnames. */
299     if (NULL != strstr(hostname, "..")) {
300         LOG(LOG_WARNING,
301             "initialize_tls_session_client(): possible path traversal: '%s'",
302             hostname);
303         return -1;
304     }
305     snprintf(path, sizeof(path), PATH_FORMAT, hostname);
306 #undef PATH_FORMAT
307
308     result = gnutls_certificate_allocate_credentials(x509_cred);
309     if (GNUTLS_E_SUCCESS != result) {
310         LOG(LOG_ERROR,
311             "initialize_tls_session_client(): \
312 gnutls_certificate_allocate_credentials(): %s",
313             gnutls_strerror(result));
314         return -1;
315     }
316
317     /* Load proxy CA file, this CA "list" is send to the client. */
318     result = gnutls_certificate_set_x509_trust_file(*x509_cred,
319                                                     PROXY_CA_FILE,
320                                                     GNUTLS_X509_FMT_PEM);
321     if (0 >= result) {
322         LOG(LOG_ERROR,
323             "initialize_tls_session_client(): can't read CA file: '%s'",
324             PROXY_CA_FILE);
325         return -1;
326     }
327     /* And certificate for this website and proxy's private key. */
328     result = gnutls_certificate_set_x509_key_file(*x509_cred,
329                                                   path, PROXY_KEY_FILE,
330                                                   GNUTLS_X509_FMT_PEM);
331     if (GNUTLS_E_SUCCESS != result) {
332         LOG(LOG_ERROR,
333             "initialize_tls_session_client(): \
334 can't read server certificate ('%s') or key file ('%s'): %s",
335             path, PROXY_KEY_FILE, gnutls_strerror(result));
336         /* Could be a missing certificate. */
337         return -2;
338     }
339
340     gnutls_certificate_set_dh_params(*x509_cred, tls_dh_params);
341
342     result = gnutls_init(session, GNUTLS_SERVER);
343     if (GNUTLS_E_SUCCESS != result) {
344         LOG(LOG_ERROR,
345             "initialize_tls_session_client(): gnutls_init(): %s",
346             gnutls_strerror(result));
347         return -1;
348     }
349     result = gnutls_priority_set(*session, tls_priority_cache);
350     if (GNUTLS_E_SUCCESS != result) {
351         LOG(LOG_ERROR,
352             "initialize_tls_session_client(): gnutls_priority_set(): %s",
353             gnutls_strerror(result));
354         return -1;
355     }
356     result = gnutls_credentials_set(*session,
357                                     GNUTLS_CRD_CERTIFICATE, *x509_cred);
358     if (GNUTLS_E_SUCCESS != result) {
359         LOG(LOG_ERROR,
360             "initialize_tls_session_client(): gnutls_credentials_set(): %s",
361             gnutls_strerror(result));
362         return -1;
363     }
364
365     gnutls_transport_set_ptr(*session, (gnutls_transport_ptr_t)peer_socket);
366
367     return 0;
368 }
369 static int initialize_tls_session_server(int peer_socket,
370         gnutls_session_t *session,
371         gnutls_certificate_credentials_t *x509_cred) {
372     int result;
373
374     result = gnutls_certificate_allocate_credentials(x509_cred);
375     if (GNUTLS_E_SUCCESS != result) {
376         LOG(LOG_ERROR,
377             "initialize_tls_session_server(): \
378 gnutls_certificate_allocate_credentials(): %s",
379             gnutls_strerror(result));
380         return -1;
381     }
382
383     result = gnutls_init(session, GNUTLS_CLIENT);
384     if (GNUTLS_E_SUCCESS != result) {
385         LOG(LOG_ERROR,
386             "initialize_tls_session_server(): gnutls_init(): %s",
387             gnutls_strerror(result));
388         return -1;
389     }
390     gnutls_priority_set(*session, tls_priority_cache);
391     if (GNUTLS_E_SUCCESS != result) {
392         LOG(LOG_ERROR,
393             "initialize_tls_session_server(): gnutls_priority_set(): %s",
394             gnutls_strerror(result));
395         return -1;
396     }
397     result = gnutls_credentials_set(*session,
398                                     GNUTLS_CRD_CERTIFICATE, *x509_cred);
399     if (GNUTLS_E_SUCCESS != result) {
400         LOG(LOG_ERROR,
401             "initialize_tls_session_server(): gnutls_credentials_set(): %s",
402             gnutls_strerror(result));
403         return -1;
404     }
405
406     gnutls_transport_set_ptr(*session, (gnutls_transport_ptr_t)peer_socket);
407
408     return 0;
409 }
410
411
412 /* Read HTTP request line and headers (ignored).
413  *
414  * On success 0 is returned, -1 on client error, -2 on unexpected EOF.
415  */
416 static int read_http_request(FILE *client_fd, char *request, size_t length) {
417     char buffer[MAX_REQUEST_LINE];
418
419     if (NULL == fgets(request, (int)length, client_fd)) {
420         if (ferror(client_fd)) {
421             LOG_PERROR(LOG_WARNING, "read_http_request(): fgets()");
422             return -1;
423         }
424
425         return -2;
426     }
427
428     while (NULL != fgets(buffer, MAX_REQUEST_LINE, client_fd)) {
429         /* End of header. */
430         if (0 == strcmp(buffer, "\n") || 0 == strcmp(buffer, "\r\n")) {
431             break;
432         }
433     }
434     if (ferror(client_fd)) {
435         LOG_PERROR(LOG_WARNING, "read_http_request(): fgets()");
436         return -1;
437     }
438
439     return 0;
440 }
441
442 static void send_bad_request(FILE *client_fd) {
443     fprintf(client_fd, "HTTP/1.0 400 Bad Request\r\n");
444     fprintf(client_fd, "\r\n");
445 }
446 static void send_forwarding_failure(FILE *client_fd) {
447     fprintf(client_fd, "HTTP/1.0 503 Forwarding failure\r\n");
448     fprintf(client_fd, "\r\n");
449 }
450
451
452 #if 0
453 /* Transfer data between client and server sockets until one closes the
454  * connection. */
455 static void transfer_data(int client, int server) {
456     struct pollfd fds[2];
457     fds[0].fd      = client;
458     fds[0].events  = POLLIN | POLLPRI | POLLHUP | POLLERR;
459     fds[0].revents = 0;
460     fds[1].fd      = server;
461     fds[1].events  = POLLIN | POLLPRI | POLLHUP | POLLERR;
462     fds[1].revents = 0;
463
464     for (;;) {
465         int result = poll(fds, 2, -1 /* no timeout */);
466         if (result < 0) {
467             LOG_PERROR(LOG_ERROR, "transfer_data(): poll()");
468             return;
469         }
470
471         /* Data available from client. */
472         if (fds[0].revents & POLLIN || fds[0].revents & POLLPRI) {
473             if (0 != read_from_write_to(client, server)) {
474                 /* EOF (or other error) */
475                 break;
476             }
477         }
478         /* Data available from server. */
479         if (fds[1].revents & POLLIN || fds[1].revents & POLLPRI) {
480             if (0 != read_from_write_to(server, client)) {
481                 /* EOF (or other error) */
482                 break;
483             }
484         }
485
486         /* Client closed connection. */
487         if (fds[0].revents & POLLERR || fds[0].revents & POLLHUP) {
488             break;
489         }
490         /* Server closed connection. */
491         if (fds[1].revents & POLLERR || fds[1].revents & POLLHUP) {
492             break;
493         }
494     }
495 }
496
497 /* Read available data from socket from and write it to socket to. At maximum
498  * 4096 bytes are read/written. */
499 static int read_from_write_to(int from, int to) {
500     ssize_t size_read;
501     ssize_t size_written;
502     char buffer[4096];
503
504     size_read = read(from, buffer, sizeof(buffer));
505     if (0 > size_read) {
506         LOG_PERROR(LOG_WARNING, "read_from_write_to(): read()");
507         return -1;
508     }
509     /* EOF */
510     if (0 == size_read) {
511         return -1;
512     }
513
514     size_written = write(to, buffer, (size_t)size_read);
515     if (0 > size_written) {
516         LOG_PERROR(LOG_WARNING, "read_from_write_to(): write()");
517         return -1;
518     }
519     if (size_read != size_written) {
520         LOG(LOG_ERROR, "read_from_write_to(): only written %ld of %ld bytes!",
521                        (long int)size_written, (long int)size_read);
522         return -1;
523     }
524
525     return 0;
526 }
527 #endif
528
529 /* Transfer data between client and server TLS connection until one closes the
530  * connection. */
531 static void transfer_data_tls(int client, int server,
532                               gnutls_session_t client_session,
533                               gnutls_session_t server_session) {
534     struct pollfd fds[2];
535     fds[0].fd      = client;
536     fds[0].events  = POLLIN | POLLPRI | POLLHUP | POLLERR;
537     fds[0].revents = 0;
538     fds[1].fd      = server;
539     fds[1].events  = POLLIN | POLLPRI | POLLHUP | POLLERR;
540     fds[1].revents = 0;
541
542     for (;;) {
543         int result = poll(fds, 2, -1 /* no timeout */);
544         if (result < 0) {
545             LOG_PERROR(LOG_ERROR, "transfer_data(): poll()");
546             return;
547         }
548
549         /* Data available from client. */
550         if (fds[0].revents & POLLIN || fds[0].revents & POLLPRI) {
551             if (0 != read_from_write_to_tls(client_session, server_session)) {
552                 /* EOF (or other error) */
553                 break;
554             }
555         }
556         /* Data available from server. */
557         if (fds[1].revents & POLLIN || fds[1].revents & POLLPRI) {
558             if (0 != read_from_write_to_tls(server_session, client_session)) {
559                 /* EOF (or other error) */
560                 break;
561             }
562         }
563
564         /* Client closed connection. */
565         if (fds[0].revents & POLLERR || fds[0].revents & POLLHUP) {
566             break;
567         }
568         /* Server closed connection. */
569         if (fds[1].revents & POLLERR || fds[1].revents & POLLHUP) {
570             break;
571         }
572     }
573 }
574
575 /* Read available data from session from and write to session to. */
576 static int read_from_write_to_tls(gnutls_session_t from,
577                                   gnutls_session_t to) {
578     size_t size;
579     ssize_t size_read;
580     ssize_t size_written;
581     char buffer[16384];
582
583     /* Get maximum possible buffer size. */
584     size = gnutls_record_get_max_size(from);
585     LOG(LOG_DEBUG, "read_from_write_to_tls(): suggested buffer size: %ld",
586                    (long int)size);
587     if (size > gnutls_record_get_max_size(to)) {
588         size = gnutls_record_get_max_size(to);
589     }
590     if (size > sizeof(buffer)) {
591         size = sizeof(buffer);
592     }
593     LOG(LOG_DEBUG, "read_from_write_to_tls(): used buffer size: %ld",
594                    (long int)size);
595
596     size_read = gnutls_record_recv(from, buffer, size);
597     if (0 > size_read) {
598         LOG(LOG_WARNING, "read_from_write_to_tls(): gnutls_record_recv(): %s",
599                          gnutls_strerror((int)size_read));
600         return -1;
601     }
602     /* EOF */
603     if (0 == size_read) {
604         return -1;
605     }
606
607     size_written = gnutls_record_send(to, buffer, (size_t)size_read);
608     if (0 > size_written) {
609         LOG(LOG_WARNING, "read_from_write_to_tls(): gnutls_record_send(): %s",
610                          gnutls_strerror((int)size_written));
611         return -1;
612     }
613     if (size_read != size_written) {
614         LOG(LOG_ERROR, "read_from_write_to_tls(): only written %ld of %ld bytes!",
615                        (long int)size_written, (long int)size_read);
616         return -1;
617     }
618
619     return 0;
620 }
621
622
623 static int connect_to_host(const char *hostname, const char *port) {
624     struct addrinfo gai_hints;
625     struct addrinfo *gai_result;
626     int gai_return;
627
628     int server_socket;
629     struct addrinfo *server;
630
631     if (NULL == hostname || NULL == port) {
632         return -1;
633     }
634
635     /* Get IP of hostname server. */
636     memset(&gai_hints, 0, sizeof(gai_hints));
637     gai_hints.ai_family   = AF_UNSPEC;
638     gai_hints.ai_socktype = SOCK_STREAM;
639     gai_hints.ai_protocol = 0;
640     gai_hints.ai_flags    = AI_NUMERICSERV /* given port is numeric */
641                           | AI_ADDRCONFIG  /* supported by this computer */
642                           | AI_V4MAPPED;   /* support IPv4 through IPv6 */
643     gai_return = getaddrinfo(hostname, port, &gai_hints, &gai_result);
644     if (0 != gai_return) {
645         LOG_PERROR(LOG_WARNING, "connect_to_host(): getaddrinfo()");
646         return -1;
647     }
648
649     /* Now try to connect to each server returned by getaddrinfo(), use the
650      * first successful connect. */
651     for (server = gai_result; NULL != server; server = server->ai_next) {
652         server_socket = socket(server->ai_family,
653                                server->ai_socktype,
654                                server->ai_protocol);
655         if (-1 == server_socket) {
656             LOG_PERROR(LOG_DEBUG, "connect_to_host(): socket(), trying next");
657             continue;
658         }
659
660         if (-1 != connect(server_socket, server->ai_addr,
661                                          server->ai_addrlen)) {
662             break;
663         }
664         LOG_PERROR(LOG_DEBUG, "connect_to_host(): connect(), trying next");
665
666         close(server_socket);
667     }
668     /* Make sure we free the result from getaddrinfo(). */
669     freeaddrinfo(gai_result);
670
671     if (NULL == server) {
672         LOG_PERROR(LOG_WARNING, "connect_to_host(): no server found, abort");
673         return -1;
674     }
675
676     return server_socket;
677 }
678
679
680 /* Parse HTTP CONNECT request string and save its parameters.
681  *
682  * The following format is expected: "CONNECT host:port HTTP/1.x".
683  *
684  * request and host must have the same size! port must be at least 6 bytes
685  * long (5 + '\0').
686  */
687 static int parse_request(const char *request, char *host, char *port,
688                                               int *version_minor) {
689     int port_unused; /* just used to verify the port is numeric */
690     char *position;
691
692     /* scanf() doesn't check spaces. */
693     if (0 != strncmp(request, "CONNECT ", 8)) {
694         return -1;
695     }
696     /* Check request and extract data, "host:port" is not yet separated. */
697     if (2 != sscanf(request, "CONNECT %s HTTP/1.%d",
698                              host, version_minor)) {
699         return -1;
700     }
701     /* Make sure ":port" is there. */
702     if (NULL == (position = strchr(host, ':'))) {
703         return -1;
704     }
705     /* Make sure port is numeric. */
706     if (1 != sscanf(position + 1, "%d", &port_unused)) {
707         return -1;
708     }
709     /* Store it in *port. */
710     strncpy(port, position + 1, 5);
711     port[5] = '\0';
712     /* And remove port from host. */
713     *position = '\0';
714
715     return 0;
716 }