In get_reusable_connection(), also log when the timestamp was made, the timeout and...
[privoxy.git] / gateway.c
1 const char gateway_rcs[] = "$Id: gateway.c,v 1.58 2009/08/19 15:22:18 fabiankeil Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/gateway.c,v $
5  *
6  * Purpose     :  Contains functions to connect to a server, possibly
7  *                using a "forwarder" (i.e. HTTP proxy and/or a SOCKS4
8  *                or SOCKS5 proxy).
9  *
10  * Copyright   :  Written by and Copyright (C) 2001-2009 the SourceForge
11  *                Privoxy team. http://www.privoxy.org/
12  *
13  *                Based on the Internet Junkbuster originally written
14  *                by and Copyright (C) 1997 Anonymous Coders and
15  *                Junkbusters Corporation.  http://www.junkbusters.com
16  *
17  *                This program is free software; you can redistribute it
18  *                and/or modify it under the terms of the GNU General
19  *                Public License as published by the Free Software
20  *                Foundation; either version 2 of the License, or (at
21  *                your option) any later version.
22  *
23  *                This program is distributed in the hope that it will
24  *                be useful, but WITHOUT ANY WARRANTY; without even the
25  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
26  *                PARTICULAR PURPOSE.  See the GNU General Public
27  *                License for more details.
28  *
29  *                The GNU General Public License should be included with
30  *                this file.  If not, you can view it at
31  *                http://www.gnu.org/copyleft/gpl.html
32  *                or write to the Free Software Foundation, Inc., 59
33  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
34  *
35  *********************************************************************/
36
37
38 #include "config.h"
39
40 #include <stdio.h>
41 #include <sys/types.h>
42
43 #ifndef _WIN32
44 #include <netinet/in.h>
45 #endif
46
47 #include <errno.h>
48 #include <string.h>
49 #include "assert.h"
50
51 #ifdef _WIN32
52 #include <winsock2.h>
53 #endif /* def _WIN32 */
54
55 #ifdef __BEOS__
56 #include <netdb.h>
57 #endif /* def __BEOS__ */
58
59 #ifdef __OS2__
60 #include <utils.h>
61 #endif /* def __OS2__ */
62
63 #include "project.h"
64 #include "jcc.h"
65 #include "errlog.h"
66 #include "jbsockets.h"
67 #include "gateway.h"
68 #include "miscutil.h"
69 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
70 #ifdef HAVE_POLL
71 #ifdef __GLIBC__ 
72 #include <sys/poll.h>
73 #else
74 #include <poll.h>
75 #endif /* def __GLIBC__ */
76 #endif /* HAVE_POLL */
77 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
78
79 const char gateway_h_rcs[] = GATEWAY_H_VERSION;
80
81 static jb_socket socks4_connect(const struct forward_spec * fwd,
82                                 const char * target_host,
83                                 int target_port,
84                                 struct client_state *csp);
85
86 static jb_socket socks5_connect(const struct forward_spec *fwd,
87                                 const char *target_host,
88                                 int target_port,
89                                 struct client_state *csp);
90
91
92 #define SOCKS_REQUEST_GRANTED          90
93 #define SOCKS_REQUEST_REJECT           91
94 #define SOCKS_REQUEST_IDENT_FAILED     92
95 #define SOCKS_REQUEST_IDENT_CONFLICT   93
96
97 #define SOCKS5_REQUEST_GRANTED             0
98 #define SOCKS5_REQUEST_FAILED              1
99 #define SOCKS5_REQUEST_DENIED              2
100 #define SOCKS5_REQUEST_NETWORK_UNREACHABLE 3
101 #define SOCKS5_REQUEST_HOST_UNREACHABLE    4
102 #define SOCKS5_REQUEST_CONNECTION_REFUSED  5
103 #define SOCKS5_REQUEST_TTL_EXPIRED         6
104 #define SOCKS5_REQUEST_PROTOCOL_ERROR      7
105 #define SOCKS5_REQUEST_BAD_ADDRESS_TYPE    8
106
107 /* structure of a socks client operation */
108 struct socks_op {
109    unsigned char vn;          /* socks version number */
110    unsigned char cd;          /* command code */
111    unsigned char dstport[2];  /* destination port */
112    unsigned char dstip[4];    /* destination address */
113    char userid;               /* first byte of userid */
114    char padding[3];           /* make sure sizeof(struct socks_op) is endian-independent. */
115    /* more bytes of the userid follow, terminated by a NULL */
116 };
117
118 /* structure of a socks server reply */
119 struct socks_reply {
120    unsigned char vn;          /* socks version number */
121    unsigned char cd;          /* command code */
122    unsigned char dstport[2];  /* destination port */
123    unsigned char dstip[4];    /* destination address */
124 };
125
126 static const char socks_userid[] = "anonymous";
127
128 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
129
130 #define MAX_REUSABLE_CONNECTIONS 100
131 static unsigned int keep_alive_timeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
132
133 static struct reusable_connection reusable_connection[MAX_REUSABLE_CONNECTIONS];
134 static int mark_connection_unused(const struct reusable_connection *connection);
135
136 /*********************************************************************
137  *
138  * Function    :  initialize_reusable_connections
139  *
140  * Description :  Initializes the reusable_connection structures.
141  *                Must be called with connection_reuse_mutex locked.
142  *
143  * Parameters  : N/A
144  *
145  * Returns     : void
146  *
147  *********************************************************************/
148 extern void initialize_reusable_connections(void)
149 {
150    unsigned int slot = 0;
151
152 #if !defined(HAVE_POLL) && !defined(_WIN32)
153    log_error(LOG_LEVEL_INFO,
154       "Detecting already dead connections might not work "
155       "correctly on your platform. In case of problems, "
156       "unset the keep-alive-timeout option.");
157 #endif
158
159    for (slot = 0; slot < SZ(reusable_connection); slot++)
160    {
161       mark_connection_closed(&reusable_connection[slot]);
162    }
163
164    log_error(LOG_LEVEL_CONNECT, "Initialized %d socket slots.", slot);
165 }
166
167
168 /*********************************************************************
169  *
170  * Function    :  remember_connection
171  *
172  * Description :  Remembers a connection for reuse later on.
173  *
174  * Parameters  :
175  *          1  :  csp = Current client state (buffers, headers, etc...)
176  *          2  :  fwd = The forwarder settings used.
177  *
178  * Returns     : void
179  *
180  *********************************************************************/
181 void remember_connection(const struct client_state *csp, const struct forward_spec *fwd)
182 {
183    unsigned int slot = 0;
184    int free_slot_found = FALSE;
185    const struct reusable_connection *connection = &csp->server_connection;
186    const struct http_request *http = csp->http;
187
188    assert(connection->sfd != JB_INVALID_SOCKET);
189
190    if (mark_connection_unused(connection))
191    {
192       return;
193    }
194
195    privoxy_mutex_lock(&connection_reuse_mutex);
196
197    /* Find free socket slot. */
198    for (slot = 0; slot < SZ(reusable_connection); slot++)
199    {
200       if (reusable_connection[slot].sfd == JB_INVALID_SOCKET)
201       {
202          assert(reusable_connection[slot].in_use == 0);
203          log_error(LOG_LEVEL_CONNECT,
204             "Remembering socket %d for %s:%d in slot %d.",
205             connection->sfd, http->host, http->port, slot);
206          free_slot_found = TRUE;
207          break;
208       }
209    }
210
211    if (!free_slot_found)
212    {
213       log_error(LOG_LEVEL_CONNECT,
214         "No free slots found to remembering socket for %s:%d. Last slot %d.",
215         http->host, http->port, slot);
216       privoxy_mutex_unlock(&connection_reuse_mutex);
217       close_socket(connection->sfd);
218       return;
219    }
220
221    assert(NULL != http->host);
222    reusable_connection[slot].host = strdup(http->host);
223    if (NULL == reusable_connection[slot].host)
224    {
225       log_error(LOG_LEVEL_FATAL, "Out of memory saving socket.");
226    }
227    reusable_connection[slot].sfd = connection->sfd;
228    reusable_connection[slot].port = http->port;
229    reusable_connection[slot].in_use = 0;
230    reusable_connection[slot].timestamp = connection->timestamp;
231    reusable_connection->request_sent = connection->request_sent;
232    reusable_connection->response_received = connection->response_received;
233    reusable_connection[slot].keep_alive_timeout = connection->keep_alive_timeout;
234
235    assert(NULL != fwd);
236    assert(reusable_connection[slot].gateway_host == NULL);
237    assert(reusable_connection[slot].gateway_port == 0);
238    assert(reusable_connection[slot].forwarder_type == SOCKS_NONE);
239    assert(reusable_connection[slot].forward_host == NULL);
240    assert(reusable_connection[slot].forward_port == 0);
241
242    reusable_connection[slot].forwarder_type = fwd->type;
243    if (NULL != fwd->gateway_host)
244    {
245       reusable_connection[slot].gateway_host = strdup(fwd->gateway_host);
246       if (NULL == reusable_connection[slot].gateway_host)
247       {
248          log_error(LOG_LEVEL_FATAL, "Out of memory saving gateway_host.");
249       }
250    }
251    else
252    {
253       reusable_connection[slot].gateway_host = NULL;
254    }
255    reusable_connection[slot].gateway_port = fwd->gateway_port;
256
257    if (NULL != fwd->forward_host)
258    {
259       reusable_connection[slot].forward_host = strdup(fwd->forward_host);
260       if (NULL == reusable_connection[slot].forward_host)
261       {
262          log_error(LOG_LEVEL_FATAL, "Out of memory saving forward_host.");
263       }
264    }
265    else
266    {
267       reusable_connection[slot].forward_host = NULL;
268    }
269    reusable_connection[slot].forward_port = fwd->forward_port;
270
271    privoxy_mutex_unlock(&connection_reuse_mutex);
272 }
273
274
275 /*********************************************************************
276  *
277  * Function    :  mark_connection_closed
278  *
279  * Description : Marks a reused connection closed.
280  *
281  * Parameters  :
282  *          1  :  closed_connection = The connection to mark as closed.
283  *
284  * Returns     : void
285  *
286  *********************************************************************/
287 void mark_connection_closed(struct reusable_connection *closed_connection)
288 {
289    closed_connection->in_use = FALSE;
290    closed_connection->sfd = JB_INVALID_SOCKET;
291    freez(closed_connection->host);
292    closed_connection->port = 0;
293    closed_connection->timestamp = 0;
294    closed_connection->request_sent = 0;
295    closed_connection->response_received = 0;
296    closed_connection->keep_alive_timeout = 0;
297    closed_connection->forwarder_type = SOCKS_NONE;
298    freez(closed_connection->gateway_host);
299    closed_connection->gateway_port = 0;
300    freez(closed_connection->forward_host);
301    closed_connection->forward_port = 0;
302 }
303
304
305 /*********************************************************************
306  *
307  * Function    :  forget_connection
308  *
309  * Description :  Removes a previously remembered connection from
310  *                the list of reusable connections.
311  *
312  * Parameters  :
313  *          1  :  sfd = The socket belonging to the connection in question.
314  *
315  * Returns     : void
316  *
317  *********************************************************************/
318 void forget_connection(jb_socket sfd)
319 {
320    unsigned int slot = 0;
321
322    assert(sfd != JB_INVALID_SOCKET);
323
324    privoxy_mutex_lock(&connection_reuse_mutex);
325
326    for (slot = 0; slot < SZ(reusable_connection); slot++)
327    {
328       if (reusable_connection[slot].sfd == sfd)
329       {
330          assert(reusable_connection[slot].in_use);
331
332          log_error(LOG_LEVEL_CONNECT,
333             "Forgetting socket %d for %s:%d in slot %d.",
334             sfd, reusable_connection[slot].host,
335             reusable_connection[slot].port, slot);
336          mark_connection_closed(&reusable_connection[slot]);
337          privoxy_mutex_unlock(&connection_reuse_mutex);
338
339          return;
340       }
341    }
342
343    log_error(LOG_LEVEL_CONNECT,
344       "Socket %d already forgotten or never remembered.", sfd);
345
346    privoxy_mutex_unlock(&connection_reuse_mutex);
347 }
348
349
350 /*********************************************************************
351  *
352  * Function    :  connection_destination_matches
353  *
354  * Description :  Determines whether a remembered connection can
355  *                be reused. That is, whether the destination and
356  *                the forwarding settings match.
357  *
358  * Parameters  :
359  *          1  :  connection = The connection to check.
360  *          2  :  http = The destination for the connection.
361  *          3  :  fwd  = The forwarder settings.
362  *
363  * Returns     :  TRUE for yes, FALSE otherwise.
364  *
365  *********************************************************************/
366 int connection_destination_matches(const struct reusable_connection *connection,
367                                    const struct http_request *http,
368                                    const struct forward_spec *fwd)
369 {
370    if ((connection->forwarder_type != fwd->type)
371     || (connection->gateway_port   != fwd->gateway_port)
372     || (connection->forward_port   != fwd->forward_port)
373     || (connection->port           != http->port))
374    {
375       return FALSE;
376    }
377
378    if ((    (NULL != connection->gateway_host)
379          && (NULL != fwd->gateway_host)
380          && strcmpic(connection->gateway_host, fwd->gateway_host))
381        && (connection->gateway_host != fwd->gateway_host))
382    {
383       log_error(LOG_LEVEL_CONNECT, "Gateway mismatch.");
384       return FALSE;
385    }
386
387    if ((    (NULL != connection->forward_host)
388          && (NULL != fwd->forward_host)
389          && strcmpic(connection->forward_host, fwd->forward_host))
390       && (connection->forward_host != fwd->forward_host))
391    {
392       log_error(LOG_LEVEL_CONNECT, "Forwarding proxy mismatch.");
393       return FALSE;
394    }
395
396    return (!strcmpic(connection->host, http->host));
397
398 }
399
400
401 /*********************************************************************
402  *
403  * Function    :  close_unusable_connections
404  *
405  * Description :  Closes remembered connections that have timed
406  *                out or have been closed on the other side.
407  *
408  * Parameters  :  none
409  *
410  * Returns     :  Number of connections that are still alive.
411  *
412  *********************************************************************/
413 int close_unusable_connections(void)
414 {
415    unsigned int slot = 0;
416    int connections_alive = 0;
417
418    privoxy_mutex_lock(&connection_reuse_mutex);
419
420    for (slot = 0; slot < SZ(reusable_connection); slot++)
421    {
422       if (!reusable_connection[slot].in_use
423          && (JB_INVALID_SOCKET != reusable_connection[slot].sfd))
424       {
425          time_t time_open = time(NULL) - reusable_connection[slot].timestamp;
426          time_t latency = reusable_connection[slot].response_received -
427             reusable_connection[slot].request_sent;
428
429          if (reusable_connection[slot].keep_alive_timeout < time_open + latency)
430          {
431             log_error(LOG_LEVEL_CONNECT,
432                "The connection to %s:%d in slot %d timed out. "
433                "Closing socket %d. Timeout is: %d. Assumed latency: %d",
434                reusable_connection[slot].host,
435                reusable_connection[slot].port, slot,
436                reusable_connection[slot].sfd,
437                reusable_connection[slot].keep_alive_timeout,
438                latency);
439             close_socket(reusable_connection[slot].sfd);
440             mark_connection_closed(&reusable_connection[slot]);
441          }
442          else if (!socket_is_still_usable(reusable_connection[slot].sfd))
443          {
444             log_error(LOG_LEVEL_CONNECT,
445                "The connection to %s:%d in slot %d is no longer usable. "
446                "Closing socket %d.", reusable_connection[slot].host,
447                reusable_connection[slot].port, slot,
448                reusable_connection[slot].sfd);
449             close_socket(reusable_connection[slot].sfd);
450             mark_connection_closed(&reusable_connection[slot]);
451          }
452          else
453          {
454             connections_alive++;
455          }
456       }
457    }
458
459    privoxy_mutex_unlock(&connection_reuse_mutex);
460
461    return connections_alive;
462
463 }
464
465
466 /*********************************************************************
467  *
468  * Function    :  get_reusable_connection
469  *
470  * Description :  Returns an open socket to a previously remembered
471  *                open connection (if there is one).
472  *
473  * Parameters  :
474  *          1  :  http = The destination for the connection.
475  *          2  :  fwd  = The forwarder settings.
476  *
477  * Returns     :  JB_INVALID_SOCKET => No reusable connection found,
478  *                otherwise a usable socket.
479  *
480  *********************************************************************/
481 static jb_socket get_reusable_connection(const struct http_request *http,
482                                          const struct forward_spec *fwd)
483 {
484    jb_socket sfd = JB_INVALID_SOCKET;
485    unsigned int slot = 0;
486
487    close_unusable_connections();
488
489    privoxy_mutex_lock(&connection_reuse_mutex);
490
491    for (slot = 0; slot < SZ(reusable_connection); slot++)
492    {
493       if (!reusable_connection[slot].in_use
494          && (JB_INVALID_SOCKET != reusable_connection[slot].sfd))
495       {
496          if (connection_destination_matches(&reusable_connection[slot], http, fwd))
497          {
498             reusable_connection[slot].in_use = TRUE;
499             sfd = reusable_connection[slot].sfd;
500             log_error(LOG_LEVEL_CONNECT,
501                "Found reusable socket %d for %s:%d in slot %d."
502                "Timestamp made %d seconds ago. Timeout: %d. Latency: %d.",
503                sfd, reusable_connection[slot].host, reusable_connection[slot].port,
504                slot, time(NULL) - reusable_connection[slot].timestamp,
505                reusable_connection[slot].keep_alive_timeout,
506                (int)(reusable_connection[slot].response_received -
507                reusable_connection[slot].request_sent));
508             break;
509          }
510       }
511    }
512
513    privoxy_mutex_unlock(&connection_reuse_mutex);
514
515    return sfd;
516
517 }
518
519
520 /*********************************************************************
521  *
522  * Function    :  mark_connection_unused
523  *
524  * Description :  Gives a remembered connection free for reuse.
525  *
526  * Parameters  :
527  *          1  :  connection = The connection in question.
528  *
529  * Returns     :  TRUE => Socket found and marked as unused.
530  *                FALSE => Socket not found.
531  *
532  *********************************************************************/
533 static int mark_connection_unused(const struct reusable_connection *connection)
534 {
535    unsigned int slot = 0;
536    int socket_found = FALSE;
537
538    assert(connection->sfd != JB_INVALID_SOCKET);
539
540    privoxy_mutex_lock(&connection_reuse_mutex);
541
542    for (slot = 0; slot < SZ(reusable_connection); slot++)
543    {
544       if (reusable_connection[slot].sfd == connection->sfd)
545       {
546          assert(reusable_connection[slot].in_use);
547          socket_found = TRUE;
548          log_error(LOG_LEVEL_CONNECT,
549             "Marking open socket %d for %s:%d in slot %d as unused.",
550             connection->sfd, reusable_connection[slot].host,
551             reusable_connection[slot].port, slot);
552          reusable_connection[slot].in_use = 0;
553          reusable_connection[slot].timestamp = connection->timestamp;
554          break;
555       }
556    }
557
558    privoxy_mutex_unlock(&connection_reuse_mutex);
559
560    return socket_found;
561
562 }
563
564
565 /*********************************************************************
566  *
567  * Function    :  set_keep_alive_timeout
568  *
569  * Description :  Sets the timeout after which open
570  *                connections will no longer be reused.
571  *
572  * Parameters  :
573  *          1  :  timeout = The timeout in seconds.
574  *
575  * Returns     :  void
576  *
577  *********************************************************************/
578 void set_keep_alive_timeout(unsigned int timeout)
579 {
580    keep_alive_timeout = timeout;
581 }
582 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
583
584
585 /*********************************************************************
586  *
587  * Function    :  forwarded_connect
588  *
589  * Description :  Connect to a specified web server, possibly via
590  *                a HTTP proxy and/or a SOCKS proxy.
591  *
592  * Parameters  :
593  *          1  :  fwd = the proxies to use when connecting.
594  *          2  :  http = the http request and apropos headers
595  *          3  :  csp = Current client state (buffers, headers, etc...)
596  *
597  * Returns     :  JB_INVALID_SOCKET => failure, else it is the socket file descriptor.
598  *
599  *********************************************************************/
600 jb_socket forwarded_connect(const struct forward_spec * fwd,
601                             struct http_request *http,
602                             struct client_state *csp)
603 {
604    const char * dest_host;
605    int dest_port;
606    jb_socket sfd = JB_INVALID_SOCKET;
607
608 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
609    if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING)
610       && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED))
611    {
612       sfd = get_reusable_connection(http, fwd);
613       if (JB_INVALID_SOCKET != sfd)
614       {
615          return sfd;
616       }
617    }
618 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
619
620    /* Figure out if we need to connect to the web server or a HTTP proxy. */
621    if (fwd->forward_host)
622    {
623       /* HTTP proxy */
624       dest_host = fwd->forward_host;
625       dest_port = fwd->forward_port;
626    }
627    else
628    {
629       /* Web server */
630       dest_host = http->host;
631       dest_port = http->port;
632    }
633
634    /* Connect, maybe using a SOCKS proxy */
635    switch (fwd->type)
636    {
637       case SOCKS_NONE:
638          sfd = connect_to(dest_host, dest_port, csp);
639          break;
640       case SOCKS_4:
641       case SOCKS_4A:
642          sfd = socks4_connect(fwd, dest_host, dest_port, csp);
643          break;
644       case SOCKS_5:
645          sfd = socks5_connect(fwd, dest_host, dest_port, csp);
646          break;
647       default:
648          /* Should never get here */
649          log_error(LOG_LEVEL_FATAL,
650             "SOCKS4 impossible internal error - bad SOCKS type.");
651    }
652
653    if (JB_INVALID_SOCKET != sfd)
654    {
655       log_error(LOG_LEVEL_CONNECT,
656          "Created new connection to %s:%d on socket %d.",
657          http->host, http->port, sfd);
658    }
659
660    return sfd;
661
662 }
663
664
665 /*********************************************************************
666  *
667  * Function    :  socks4_connect
668  *
669  * Description :  Connect to the SOCKS server, and connect through
670  *                it to the specified server.   This handles
671  *                all the SOCKS negotiation, and returns a file
672  *                descriptor for a socket which can be treated as a
673  *                normal (non-SOCKS) socket.
674  *
675  *                Logged error messages are saved to csp->error_message
676  *                and later reused by error_response() for the CGI
677  *                message. strdup allocation failures are handled there.
678  *
679  * Parameters  :
680  *          1  :  fwd = Specifies the SOCKS proxy to use.
681  *          2  :  target_host = The final server to connect to.
682  *          3  :  target_port = The final port to connect to.
683  *          4  :  csp = Current client state (buffers, headers, etc...)
684  *
685  * Returns     :  JB_INVALID_SOCKET => failure, else a socket file descriptor.
686  *
687  *********************************************************************/
688 static jb_socket socks4_connect(const struct forward_spec * fwd,
689                                 const char * target_host,
690                                 int target_port,
691                                 struct client_state *csp)
692 {
693    unsigned long web_server_addr;
694    char buf[BUFFER_SIZE];
695    struct socks_op    *c = (struct socks_op    *)buf;
696    struct socks_reply *s = (struct socks_reply *)buf;
697    size_t n;
698    size_t csiz;
699    jb_socket sfd;
700    int err = 0;
701    char *errstr = NULL;
702
703    if ((fwd->gateway_host == NULL) || (*fwd->gateway_host == '\0'))
704    {
705       /* XXX: Shouldn't the config file parser prevent this? */
706       errstr = "NULL gateway host specified.";
707       err = 1;
708    }
709
710    if (fwd->gateway_port <= 0)
711    {
712       errstr = "invalid gateway port specified.";
713       err = 1;
714    }
715
716    if (err)
717    {
718       log_error(LOG_LEVEL_CONNECT, "socks4_connect: %s", errstr);
719       csp->error_message = strdup(errstr); 
720       errno = EINVAL;
721       return(JB_INVALID_SOCKET);
722    }
723
724    /* build a socks request for connection to the web server */
725
726    strlcpy(&(c->userid), socks_userid, sizeof(buf) - sizeof(struct socks_op));
727
728    csiz = sizeof(*c) + sizeof(socks_userid) - sizeof(c->userid) - sizeof(c->padding);
729
730    switch (fwd->type)
731    {
732       case SOCKS_4:
733          web_server_addr = resolve_hostname_to_ip(target_host);
734          if (web_server_addr == INADDR_NONE)
735          {
736             errstr = "could not resolve target host";
737             log_error(LOG_LEVEL_CONNECT, "socks4_connect: %s %s", errstr, target_host);
738             err = 1;
739          }
740          else
741          {
742             web_server_addr = htonl(web_server_addr);
743          }
744          break;
745       case SOCKS_4A:
746          web_server_addr = 0x00000001;
747          n = csiz + strlen(target_host) + 1;
748          if (n > sizeof(buf))
749          {
750             errno = EINVAL;
751             errstr = "buffer cbuf too small.";
752             log_error(LOG_LEVEL_CONNECT, "socks4_connect: %s", errstr);
753             err = 1;
754          }
755          else
756          {
757             strlcpy(buf + csiz, target_host, sizeof(buf) - sizeof(struct socks_op) - csiz);
758             /*
759              * What we forward to the socks4a server should have the
760              * size of socks_op, plus the length of the userid plus
761              * its \0 byte (which we don't have to add because the
762              * first byte of the userid is counted twice as it's also
763              * part of sock_op) minus the padding bytes (which are part
764              * of the userid as well), plus the length of the target_host
765              * (which is stored csiz bytes after the beginning of the buffer),
766              * plus another \0 byte.
767              */
768             assert(n == sizeof(struct socks_op) + strlen(&(c->userid)) - sizeof(c->padding) + strlen(buf + csiz) + 1);
769             csiz = n;
770          }
771          break;
772       default:
773          /* Should never get here */
774          log_error(LOG_LEVEL_FATAL,
775             "socks4_connect: SOCKS4 impossible internal error - bad SOCKS type.");
776          /* Not reached */
777          return(JB_INVALID_SOCKET);
778    }
779
780    if (err)
781    {
782       csp->error_message = strdup(errstr);
783       return(JB_INVALID_SOCKET);
784    }
785
786    c->vn          = 4;
787    c->cd          = 1;
788    c->dstport[0]  = (unsigned char)((target_port       >> 8  ) & 0xff);
789    c->dstport[1]  = (unsigned char)((target_port             ) & 0xff);
790    c->dstip[0]    = (unsigned char)((web_server_addr   >> 24 ) & 0xff);
791    c->dstip[1]    = (unsigned char)((web_server_addr   >> 16 ) & 0xff);
792    c->dstip[2]    = (unsigned char)((web_server_addr   >>  8 ) & 0xff);
793    c->dstip[3]    = (unsigned char)((web_server_addr         ) & 0xff);
794
795    /* pass the request to the socks server */
796    sfd = connect_to(fwd->gateway_host, fwd->gateway_port, csp);
797
798    if (sfd == JB_INVALID_SOCKET)
799    {
800       /*
801        * XXX: connect_to should fill in the exact reason.
802        * Most likely resolving the IP of the forwarder failed.
803        */
804       errstr = "connect_to failed: see logfile for details";
805       err = 1;
806    }
807    else if (write_socket(sfd, (char *)c, csiz))
808    {
809       errstr = "SOCKS4 negotiation write failed.";
810       log_error(LOG_LEVEL_CONNECT, "socks4_connect: %s", errstr);
811       err = 1;
812       close_socket(sfd);
813    }
814    else if (read_socket(sfd, buf, sizeof(buf)) != sizeof(*s))
815    {
816       errstr = "SOCKS4 negotiation read failed.";
817       log_error(LOG_LEVEL_CONNECT, "socks4_connect: %s", errstr);
818       err = 1;
819       close_socket(sfd);
820    }
821
822    if (err)
823    {
824       csp->error_message = strdup(errstr);      
825       return(JB_INVALID_SOCKET);
826    }
827
828    switch (s->cd)
829    {
830       case SOCKS_REQUEST_GRANTED:
831          return(sfd);
832       case SOCKS_REQUEST_REJECT:
833          errstr = "SOCKS request rejected or failed.";
834          errno = EINVAL;
835          break;
836       case SOCKS_REQUEST_IDENT_FAILED:
837          errstr = "SOCKS request rejected because "
838             "SOCKS server cannot connect to identd on the client.";
839          errno = EACCES;
840          break;
841       case SOCKS_REQUEST_IDENT_CONFLICT:
842          errstr = "SOCKS request rejected because "
843             "the client program and identd report "
844             "different user-ids.";
845          errno = EACCES;
846          break;
847       default:
848          errno = ENOENT;
849          snprintf(buf, sizeof(buf),
850             "SOCKS request rejected for reason code %d.", s->cd);
851          errstr = buf;
852    }
853
854    log_error(LOG_LEVEL_CONNECT, "socks4_connect: %s", errstr);
855    csp->error_message = strdup(errstr);
856    close_socket(sfd);
857
858    return(JB_INVALID_SOCKET);
859
860 }
861
862 /*********************************************************************
863  *
864  * Function    :  translate_socks5_error
865  *
866  * Description :  Translates a SOCKS errors to a string.
867  *
868  * Parameters  :
869  *          1  :  socks_error = The error code to translate.
870  *
871  * Returns     :  The string translation.
872  *
873  *********************************************************************/
874 static const char *translate_socks5_error(int socks_error)
875 {
876    switch (socks_error)
877    {
878       /* XXX: these should be more descriptive */
879       case SOCKS5_REQUEST_FAILED:
880          return "SOCKS5 request failed";
881       case SOCKS5_REQUEST_DENIED:
882          return "SOCKS5 request denied";
883       case SOCKS5_REQUEST_NETWORK_UNREACHABLE:
884          return "SOCKS5 network unreachable";
885       case SOCKS5_REQUEST_HOST_UNREACHABLE:
886          return "SOCKS5 host unreachable";
887       case SOCKS5_REQUEST_CONNECTION_REFUSED:
888          return "SOCKS5 connection refused";
889       case SOCKS5_REQUEST_TTL_EXPIRED:
890          return "SOCKS5 TTL expired";
891       case SOCKS5_REQUEST_PROTOCOL_ERROR:
892          return "SOCKS5 client protocol error";
893       case SOCKS5_REQUEST_BAD_ADDRESS_TYPE:
894          return "SOCKS5 domain names unsupported";
895       case SOCKS5_REQUEST_GRANTED:
896          return "everything's peachy";
897       default:
898          return "SOCKS5 negotiation protocol error";
899    }
900 }
901
902 /*********************************************************************
903  *
904  * Function    :  socks5_connect
905  *
906  * Description :  Connect to the SOCKS server, and connect through
907  *                it to the specified server.   This handles
908  *                all the SOCKS negotiation, and returns a file
909  *                descriptor for a socket which can be treated as a
910  *                normal (non-SOCKS) socket.
911  *
912  * Parameters  :
913  *          1  :  fwd = Specifies the SOCKS proxy to use.
914  *          2  :  target_host = The final server to connect to.
915  *          3  :  target_port = The final port to connect to.
916  *          4  :  csp = Current client state (buffers, headers, etc...)
917  *
918  * Returns     :  JB_INVALID_SOCKET => failure, else a socket file descriptor.
919  *
920  *********************************************************************/
921 static jb_socket socks5_connect(const struct forward_spec *fwd,
922                                 const char *target_host,
923                                 int target_port,
924                                 struct client_state *csp)
925 {
926    int err = 0;
927    char cbuf[300];
928    char sbuf[30];
929    size_t client_pos = 0;
930    int server_size = 0;
931    size_t hostlen = 0;
932    jb_socket sfd;
933    const char *errstr = NULL;
934
935    assert(fwd->gateway_host);
936    if ((fwd->gateway_host == NULL) || (*fwd->gateway_host == '\0'))
937    {
938       errstr = "NULL gateway host specified";
939       err = 1;
940    }
941
942    if (fwd->gateway_port <= 0)
943    {
944       /*
945        * XXX: currently this can't happen because in
946        * case of invalid gateway ports we use the defaults.
947        * Of course we really shouldn't do that.
948        */
949       errstr = "invalid gateway port specified";
950       err = 1;
951    }
952
953    hostlen = strlen(target_host);
954    if (hostlen > (size_t)255)
955    {
956       errstr = "target host name is longer than 255 characters";
957       err = 1;
958    }
959
960    if (fwd->type != SOCKS_5)
961    {
962       /* Should never get here */
963       log_error(LOG_LEVEL_FATAL,
964          "SOCKS5 impossible internal error - bad SOCKS type");
965       err = 1;
966    }
967
968    if (err)
969    {
970       errno = EINVAL;
971       assert(errstr != NULL);
972       log_error(LOG_LEVEL_CONNECT, "socks5_connect: %s", errstr);
973       csp->error_message = strdup(errstr);
974       return(JB_INVALID_SOCKET);
975    }
976
977    /* pass the request to the socks server */
978    sfd = connect_to(fwd->gateway_host, fwd->gateway_port, csp);
979
980    if (sfd == JB_INVALID_SOCKET)
981    {
982       errstr = "socks5 server unreachable";
983       log_error(LOG_LEVEL_CONNECT, "socks5_connect: %s", errstr);
984       csp->error_message = strdup(errstr);
985       return(JB_INVALID_SOCKET);
986    }
987
988    client_pos = 0;
989    cbuf[client_pos++] = '\x05'; /* Version */
990    cbuf[client_pos++] = '\x01'; /* One authentication method supported */
991    cbuf[client_pos++] = '\x00'; /* The no authentication authentication method */
992
993    if (write_socket(sfd, cbuf, client_pos))
994    {
995       errstr = "SOCKS5 negotiation write failed";
996       csp->error_message = strdup(errstr);
997       log_error(LOG_LEVEL_CONNECT, "%s", errstr);
998       close_socket(sfd);
999       return(JB_INVALID_SOCKET);
1000    }
1001
1002    if (read_socket(sfd, sbuf, sizeof(sbuf)) != 2)
1003    {
1004       errstr = "SOCKS5 negotiation read failed";
1005       err = 1;
1006    }
1007
1008    if (!err && (sbuf[0] != '\x05'))
1009    {
1010       errstr = "SOCKS5 negotiation protocol version error";
1011       err = 1;
1012    }
1013
1014    if (!err && (sbuf[1] == '\xff'))
1015    {
1016       errstr = "SOCKS5 authentication required";
1017       err = 1;
1018    }
1019
1020    if (!err && (sbuf[1] != '\x00'))
1021    {
1022       errstr = "SOCKS5 negotiation protocol error";
1023       err = 1;
1024    }
1025
1026    if (err)
1027    {
1028       assert(errstr != NULL);
1029       log_error(LOG_LEVEL_CONNECT, "socks5_connect: %s", errstr);
1030       csp->error_message = strdup(errstr);
1031       close_socket(sfd);
1032       errno = EINVAL;
1033       return(JB_INVALID_SOCKET);
1034    }
1035
1036    client_pos = 0;
1037    cbuf[client_pos++] = '\x05'; /* Version */
1038    cbuf[client_pos++] = '\x01'; /* TCP connect */
1039    cbuf[client_pos++] = '\x00'; /* Reserved, must be 0x00 */
1040    cbuf[client_pos++] = '\x03'; /* Address is domain name */
1041    cbuf[client_pos++] = (char)(hostlen & 0xffu);
1042    assert(sizeof(cbuf) - client_pos > (size_t)255);
1043    /* Using strncpy because we really want the nul byte padding. */
1044    strncpy(cbuf + client_pos, target_host, sizeof(cbuf) - client_pos);
1045    client_pos += (hostlen & 0xffu);
1046    cbuf[client_pos++] = (char)((target_port >> 8) & 0xff);
1047    cbuf[client_pos++] = (char)((target_port     ) & 0xff);
1048
1049    if (write_socket(sfd, cbuf, client_pos))
1050    {
1051       errstr = "SOCKS5 negotiation read failed";
1052       csp->error_message = strdup(errstr);
1053       log_error(LOG_LEVEL_CONNECT, "%s", errstr);
1054       close_socket(sfd);
1055       errno = EINVAL;
1056       return(JB_INVALID_SOCKET);
1057    }
1058
1059    server_size = read_socket(sfd, sbuf, sizeof(sbuf));
1060    if (server_size < 3)
1061    {
1062       errstr = "SOCKS5 negotiation read failed";
1063       err = 1;
1064    }
1065    else if (server_size > 20)
1066    {
1067       /* This is somewhat unexpected but doesn't realy matter. */
1068       log_error(LOG_LEVEL_CONNECT, "socks5_connect: read %d bytes "
1069          "from socks server. Would have accepted up to %d.",
1070          server_size, sizeof(sbuf));
1071    }
1072
1073    if (!err && (sbuf[0] != '\x05'))
1074    {
1075       errstr = "SOCKS5 negotiation protocol version error";
1076       err = 1;
1077    }
1078
1079    if (!err && (sbuf[2] != '\x00'))
1080    {
1081       errstr = "SOCKS5 negotiation protocol error";
1082       err = 1;
1083    }
1084
1085    if (!err)
1086    {
1087       if (sbuf[1] == SOCKS5_REQUEST_GRANTED)
1088       {
1089          return(sfd);
1090       }
1091       errstr = translate_socks5_error(sbuf[1]);
1092    }
1093
1094    assert(errstr != NULL);
1095    csp->error_message = strdup(errstr);
1096    log_error(LOG_LEVEL_CONNECT, "socks5_connect: %s", errstr);
1097    close_socket(sfd);
1098    errno = EINVAL;
1099
1100    return(JB_INVALID_SOCKET);
1101
1102 }
1103
1104 /*
1105   Local Variables:
1106   tab-width: 3
1107   end:
1108 */