Dmitriy Sergeyev provided a patch that made the SOCKS[45] code work better as
[platform/upstream/curl.git] / lib / socks.c
1 #include "setup.h"
2
3 #include "urldata.h"
4 #include "sendf.h"
5 #include "strequal.h"
6 #include "select.h"
7 #include "connect.h"
8 #include "timeval.h"
9 #include "socks.h"
10
11 /* The last #include file should be: */
12 #include "memdebug.h"
13
14 /*
15  * Helper read-from-socket functions. Does the same as Curl_read() but it
16  * blocks until all bytes amount of buffersize will be read. No more, no less.
17  *
18  * This is STUPID BLOCKING behaviour which we frown upon, but right now this
19  * is what we have...
20  */
21 static int blockread_all(struct connectdata *conn, /* connection data */
22                          curl_socket_t sockfd,     /* read from this socket */
23                          char *buf,                /* store read data here */
24                          ssize_t buffersize,       /* max amount to read */
25                          ssize_t *n,               /* amount bytes read */
26                          long conn_timeout)        /* timeout for data wait
27                                                       relative to
28                                                       conn->created */
29 {
30   ssize_t nread;
31   ssize_t allread = 0;
32   int result;
33   struct timeval tvnow;
34   long conntime;
35   *n = 0;
36   do {
37     tvnow = Curl_tvnow();
38     /* calculating how long connection is establishing */
39     conntime = Curl_tvdiff(tvnow, conn->created);
40     if(conntime > conn_timeout) {
41       /* we already got the timeout */
42       return -1;
43     }
44     if(Curl_select(sockfd, CURL_SOCKET_BAD,
45                    (int)(conn_timeout - conntime)) <= 0) {
46       return -1;
47     }
48     result = Curl_read(conn, sockfd, buf, buffersize, &nread);
49     if(result)
50       return result;
51
52     if(buffersize == nread) {
53       allread += nread;
54       *n = allread;
55       return CURLE_OK;
56     }
57     buffersize -= nread;
58     buf += nread;
59     allread += nread;
60   } while(1);
61 }
62
63 /*
64 * This function logs in to a SOCKS4 proxy and sends the specifics to the final
65 * destination server.
66 *
67 * Reference :
68 *   http://socks.permeo.com/protocol/socks4.protocol
69 *
70 * Note :
71 *   Nonsupport "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)"
72 *   Nonsupport "Identification Protocol (RFC1413)"
73 */
74 CURLcode Curl_SOCKS4(const char *proxy_name,
75                      struct connectdata *conn)
76 {
77   unsigned char socksreq[262]; /* room for SOCKS4 request incl. user id */
78   int result;
79   CURLcode code;
80   curl_socket_t sock = conn->sock[FIRSTSOCKET];
81   long timeout;
82   struct SessionHandle *data = conn->data;
83
84   /* get timeout */
85   if(data->set.timeout && data->set.connecttimeout) {
86     if (data->set.timeout < data->set.connecttimeout)
87       timeout = data->set.timeout*1000;
88     else
89       timeout = data->set.connecttimeout*1000;
90   }
91   else if(data->set.timeout)
92     timeout = data->set.timeout*1000;
93   else if(data->set.connecttimeout)
94     timeout = data->set.connecttimeout*1000;
95   else
96     timeout = DEFAULT_CONNECT_TIMEOUT;
97
98   Curl_nonblock(sock, FALSE);
99
100   /*
101    * Compose socks4 request
102    *
103    * Request format
104    *
105    *     +----+----+----+----+----+----+----+----+----+----+....+----+
106    *     | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
107    *     +----+----+----+----+----+----+----+----+----+----+....+----+
108    * # of bytes:  1    1      2              4           variable       1
109    */
110
111   socksreq[0] = 4; /* version (SOCKS4) */
112   socksreq[1] = 1; /* connect */
113   *((unsigned short*)&socksreq[2]) = htons(conn->remote_port);
114
115   /* DNS resolve */
116   {
117     struct Curl_dns_entry *dns;
118     Curl_addrinfo *hp=NULL;
119     int rc;
120
121     rc = Curl_resolv(conn, conn->host.name, (int)conn->remote_port, &dns);
122
123     if(rc == CURLRESOLV_ERROR)
124       return CURLE_COULDNT_RESOLVE_PROXY;
125
126     if(rc == CURLRESOLV_PENDING)
127       /* this requires that we're in "wait for resolve" state */
128       rc = Curl_wait_for_resolv(conn, &dns);
129
130     /*
131      * We cannot use 'hostent' as a struct that Curl_resolv() returns.  It
132      * returns a Curl_addrinfo pointer that may not always look the same.
133      */
134     if(dns)
135       hp=dns->addr;
136     if (hp) {
137       char buf[64];
138       unsigned short ip[4];
139       Curl_printable_address(hp, buf, sizeof(buf));
140
141       if(4 == sscanf( buf, "%hu.%hu.%hu.%hu",
142                       &ip[0], &ip[1], &ip[2], &ip[3])) {
143         /* Set DSTIP */
144         socksreq[4] = (unsigned char)ip[0];
145         socksreq[5] = (unsigned char)ip[1];
146         socksreq[6] = (unsigned char)ip[2];
147         socksreq[7] = (unsigned char)ip[3];
148       }
149       else
150         hp = NULL; /* fail! */
151
152       Curl_resolv_unlock(data, dns); /* not used anymore from now on */
153
154     }
155     if(!hp) {
156       failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.",
157             conn->host.name);
158       return CURLE_COULDNT_RESOLVE_HOST;
159     }
160   }
161
162   /*
163    * This is currently not supporting "Identification Protocol (RFC1413)".
164    */
165   socksreq[8] = 0; /* ensure empty userid is NUL-terminated */
166   if (proxy_name)
167     strlcat((char*)socksreq + 8, proxy_name, sizeof(socksreq) - 8);
168
169   /*
170    * Make connection
171    */
172   {
173     ssize_t actualread;
174     ssize_t written;
175     int packetsize = 9 +
176       (int)strlen((char*)socksreq + 8); /* size including NUL */
177
178     /* Send request */
179     code = Curl_write(conn, sock, (char *)socksreq, packetsize, &written);
180     if ((code != CURLE_OK) || (written != packetsize)) {
181       failf(data, "Failed to send SOCKS4 connect request.");
182       return CURLE_COULDNT_CONNECT;
183     }
184
185     packetsize = 8; /* receive data size */
186
187     /* Receive response */
188     result = blockread_all(conn, sock, (char *)socksreq, packetsize,
189                            &actualread, timeout);
190     if ((result != CURLE_OK) || (actualread != packetsize)) {
191       failf(data, "Failed to receive SOCKS4 connect request ack.");
192       return CURLE_COULDNT_CONNECT;
193     }
194
195     /*
196      * Response format
197      *
198      *     +----+----+----+----+----+----+----+----+
199      *     | VN | CD | DSTPORT |      DSTIP        |
200      *     +----+----+----+----+----+----+----+----+
201      * # of bytes:  1    1      2              4
202      *
203      * VN is the version of the reply code and should be 0. CD is the result
204      * code with one of the following values:
205      *
206      * 90: request granted
207      * 91: request rejected or failed
208      * 92: request rejected because SOCKS server cannot connect to
209      *     identd on the client
210      * 93: request rejected because the client program and identd
211      *     report different user-ids
212      */
213
214     /* wrong version ? */
215     if (socksreq[0] != 0) {
216       failf(data,
217             "SOCKS4 reply has wrong version, version should be 4.");
218       return CURLE_COULDNT_CONNECT;
219     }
220
221     /* Result */
222     switch(socksreq[1])
223     {
224     case 90:
225       infof(data, "SOCKS4 request granted.\n");
226       break;
227     case 91:
228       failf(data,
229             "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
230             ", request rejected or failed.",
231             (unsigned char)socksreq[4], (unsigned char)socksreq[5],
232             (unsigned char)socksreq[6], (unsigned char)socksreq[7],
233             (unsigned int)ntohs(*(unsigned short*)(&socksreq[8])),
234             socksreq[1]);
235       return CURLE_COULDNT_CONNECT;
236     case 92:
237       failf(data,
238             "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
239             ", request rejected because SOCKS server cannot connect to "
240             "identd on the client.",
241             (unsigned char)socksreq[4], (unsigned char)socksreq[5],
242             (unsigned char)socksreq[6], (unsigned char)socksreq[7],
243             (unsigned int)ntohs(*(unsigned short*)(&socksreq[8])),
244             socksreq[1]);
245       return CURLE_COULDNT_CONNECT;
246     case 93:
247       failf(data,
248             "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
249             ", request rejected because the client program and identd "
250             "report different user-ids.",
251             (unsigned char)socksreq[4], (unsigned char)socksreq[5],
252             (unsigned char)socksreq[6], (unsigned char)socksreq[7],
253             (unsigned int)ntohs(*(unsigned short*)(&socksreq[8])),
254             socksreq[1]);
255       return CURLE_COULDNT_CONNECT;
256     default:
257       failf(data,
258             "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)"
259             ", Unknown.",
260             (unsigned char)socksreq[4], (unsigned char)socksreq[5],
261             (unsigned char)socksreq[6], (unsigned char)socksreq[7],
262             (unsigned int)ntohs(*(unsigned short*)(&socksreq[8])),
263             socksreq[1]);
264       return CURLE_COULDNT_CONNECT;
265     }
266   }
267
268   Curl_nonblock(sock, TRUE);
269
270   return CURLE_OK; /* Proxy was successful! */
271 }
272
273 /*
274  * This function logs in to a SOCKS5 proxy and sends the specifics to the final
275  * destination server.
276  */
277 CURLcode Curl_SOCKS5(const char *proxy_name,
278                      const char *proxy_password,
279                      struct connectdata *conn)
280 {
281   /*
282     According to the RFC1928, section "6.  Replies". This is what a SOCK5
283     replies:
284
285         +----+-----+-------+------+----------+----------+
286         |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
287         +----+-----+-------+------+----------+----------+
288         | 1  |  1  | X'00' |  1   | Variable |    2     |
289         +----+-----+-------+------+----------+----------+
290
291     Where:
292
293     o  VER    protocol version: X'05'
294     o  REP    Reply field:
295     o  X'00' succeeded
296   */
297
298   unsigned char socksreq[600]; /* room for large user/pw (255 max each) */
299   ssize_t actualread;
300   ssize_t written;
301   int result;
302   CURLcode code;
303   curl_socket_t sock = conn->sock[FIRSTSOCKET];
304   struct SessionHandle *data = conn->data;
305   long timeout;
306
307   /* get timeout */
308   if(data->set.timeout && data->set.connecttimeout) {
309     if (data->set.timeout < data->set.connecttimeout)
310       timeout = data->set.timeout*1000;
311     else
312       timeout = data->set.connecttimeout*1000;
313   }
314   else if(data->set.timeout)
315     timeout = data->set.timeout*1000;
316   else if(data->set.connecttimeout)
317     timeout = data->set.connecttimeout*1000;
318   else
319     timeout = DEFAULT_CONNECT_TIMEOUT;
320
321   Curl_nonblock(sock, TRUE);
322
323   /* wait until socket gets connected */
324   result = Curl_select(CURL_SOCKET_BAD, sock, (int)timeout);
325
326   if(-1 == result) {
327     failf(conn->data, "SOCKS5: no connection here");
328     return CURLE_COULDNT_CONNECT;
329   }
330   else if(0 == result) {
331     failf(conn->data, "SOCKS5: connection timeout");
332     return CURLE_OPERATION_TIMEDOUT;
333   }
334
335   if(result & CSELECT_ERR) {
336     failf(conn->data, "SOCKS5: error occured during connection");
337     return CURLE_COULDNT_CONNECT;
338   }
339
340   socksreq[0] = 5; /* version */
341   socksreq[1] = (char)(proxy_name ? 2 : 1); /* number of methods (below) */
342   socksreq[2] = 0; /* no authentication */
343   socksreq[3] = 2; /* username/password */
344
345   Curl_nonblock(sock, FALSE);
346
347   code = Curl_write(conn, sock, (char *)socksreq, (2 + (int)socksreq[1]),
348                       &written);
349   if ((code != CURLE_OK) || (written != (2 + (int)socksreq[1]))) {
350     failf(data, "Unable to send initial SOCKS5 request.");
351     return CURLE_COULDNT_CONNECT;
352   }
353
354   Curl_nonblock(sock, TRUE);
355
356   result = Curl_select(sock, CURL_SOCKET_BAD, (int)timeout);
357
358   if(-1 == result) {
359     failf(conn->data, "SOCKS5 nothing to read");
360     return CURLE_COULDNT_CONNECT;
361   }
362   else if(0 == result) {
363     failf(conn->data, "SOCKS5 read timeout");
364     return CURLE_OPERATION_TIMEDOUT;
365   }
366
367   if(result & CSELECT_ERR) {
368     failf(conn->data, "SOCKS5 read error occured");
369     return CURLE_RECV_ERROR;
370   }
371
372   Curl_nonblock(sock, FALSE);
373
374   result=blockread_all(conn, sock, (char *)socksreq, 2, &actualread, timeout);
375   if ((result != CURLE_OK) || (actualread != 2)) {
376     failf(data, "Unable to receive initial SOCKS5 response.");
377     return CURLE_COULDNT_CONNECT;
378   }
379
380   if (socksreq[0] != 5) {
381     failf(data, "Received invalid version in initial SOCKS5 response.");
382     return CURLE_COULDNT_CONNECT;
383   }
384   if (socksreq[1] == 0) {
385     /* Nothing to do, no authentication needed */
386     ;
387   }
388   else if (socksreq[1] == 2) {
389     /* Needs user name and password */
390     size_t userlen, pwlen;
391     int len;
392     if(proxy_name && proxy_password) {
393       userlen = strlen(proxy_name);
394       pwlen = proxy_password?strlen(proxy_password):0;
395     }
396     else {
397       userlen = 0;
398       pwlen = 0;
399     }
400
401     /*   username/password request looks like
402      * +----+------+----------+------+----------+
403      * |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
404      * +----+------+----------+------+----------+
405      * | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
406      * +----+------+----------+------+----------+
407      */
408     len = 0;
409     socksreq[len++] = 1;    /* username/pw subnegotiation version */
410     socksreq[len++] = (char) userlen;
411     memcpy(socksreq + len, proxy_name, (int) userlen);
412     len += userlen;
413     socksreq[len++] = (char) pwlen;
414     memcpy(socksreq + len, proxy_password, (int) pwlen);
415     len += pwlen;
416
417     code = Curl_write(conn, sock, (char *)socksreq, len, &written);
418     if ((code != CURLE_OK) || (len != written)) {
419       failf(data, "Failed to send SOCKS5 sub-negotiation request.");
420       return CURLE_COULDNT_CONNECT;
421     }
422
423     result=blockread_all(conn, sock, (char *)socksreq, 2, &actualread,
424                          timeout);
425     if ((result != CURLE_OK) || (actualread != 2)) {
426       failf(data, "Unable to receive SOCKS5 sub-negotiation response.");
427       return CURLE_COULDNT_CONNECT;
428     }
429
430     /* ignore the first (VER) byte */
431     if (socksreq[1] != 0) { /* status */
432       failf(data, "User was rejected by the SOCKS5 server (%d %d).",
433             socksreq[0], socksreq[1]);
434       return CURLE_COULDNT_CONNECT;
435     }
436
437     /* Everything is good so far, user was authenticated! */
438   }
439   else {
440     /* error */
441     if (socksreq[1] == 1) {
442       failf(data,
443             "SOCKS5 GSSAPI per-message authentication is not supported.");
444       return CURLE_COULDNT_CONNECT;
445     }
446     else if (socksreq[1] == 255) {
447       if (!proxy_name || !*proxy_name) {
448         failf(data,
449               "No authentication method was acceptable. (It is quite likely"
450               " that the SOCKS5 server wanted a username/password, since none"
451               " was supplied to the server on this connection.)");
452       }
453       else {
454         failf(data, "No authentication method was acceptable.");
455       }
456       return CURLE_COULDNT_CONNECT;
457     }
458     else {
459       failf(data,
460             "Undocumented SOCKS5 mode attempted to be used by server.");
461       return CURLE_COULDNT_CONNECT;
462     }
463   }
464
465   /* Authentication is complete, now specify destination to the proxy */
466   socksreq[0] = 5; /* version (SOCKS5) */
467   socksreq[1] = 1; /* connect */
468   socksreq[2] = 0; /* must be zero */
469   socksreq[3] = 1; /* IPv4 = 1 */
470
471   {
472     struct Curl_dns_entry *dns;
473     Curl_addrinfo *hp=NULL;
474     int rc = Curl_resolv(conn, conn->host.name, (int)conn->remote_port, &dns);
475
476     if(rc == CURLRESOLV_ERROR)
477       return CURLE_COULDNT_RESOLVE_HOST;
478
479     if(rc == CURLRESOLV_PENDING)
480       /* this requires that we're in "wait for resolve" state */
481       rc = Curl_wait_for_resolv(conn, &dns);
482
483     /*
484      * We cannot use 'hostent' as a struct that Curl_resolv() returns.  It
485      * returns a Curl_addrinfo pointer that may not always look the same.
486      */
487     if(dns)
488       hp=dns->addr;
489     if (hp) {
490       char buf[64];
491       unsigned short ip[4];
492       Curl_printable_address(hp, buf, sizeof(buf));
493
494       if(4 == sscanf( buf, "%hu.%hu.%hu.%hu",
495                       &ip[0], &ip[1], &ip[2], &ip[3])) {
496         socksreq[4] = (unsigned char)ip[0];
497         socksreq[5] = (unsigned char)ip[1];
498         socksreq[6] = (unsigned char)ip[2];
499         socksreq[7] = (unsigned char)ip[3];
500       }
501       else
502         hp = NULL; /* fail! */
503
504       Curl_resolv_unlock(data, dns); /* not used anymore from now on */
505     }
506     if(!hp) {
507       failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.",
508             conn->host.name);
509       return CURLE_COULDNT_RESOLVE_HOST;
510     }
511   }
512
513   *((unsigned short*)&socksreq[8]) = htons(conn->remote_port);
514
515   {
516     const int packetsize = 10;
517
518     code = Curl_write(conn, sock, (char *)socksreq, packetsize, &written);
519     if ((code != CURLE_OK) || (written != packetsize)) {
520       failf(data, "Failed to send SOCKS5 connect request.");
521       return 1;
522     }
523
524     result = blockread_all(conn, sock, (char *)socksreq, packetsize,
525                            &actualread, timeout);
526     if ((result != CURLE_OK) || (actualread != packetsize)) {
527       failf(data, "Failed to receive SOCKS5 connect request ack.");
528       return CURLE_COULDNT_CONNECT;
529     }
530
531     if (socksreq[0] != 5) { /* version */
532       failf(data,
533             "SOCKS5 reply has wrong version, version should be 5.");
534       return CURLE_COULDNT_CONNECT;
535     }
536     if (socksreq[1] != 0) { /* Anything besides 0 is an error */
537         failf(data,
538               "Can't complete SOCKS5 connection to %d.%d.%d.%d:%d. (%d)",
539               (unsigned char)socksreq[4], (unsigned char)socksreq[5],
540               (unsigned char)socksreq[6], (unsigned char)socksreq[7],
541               (unsigned int)ntohs(*(unsigned short*)(&socksreq[8])),
542               socksreq[1]);
543         return CURLE_COULDNT_CONNECT;
544     }
545   }
546
547   Curl_nonblock(sock, TRUE);
548   return CURLE_OK; /* Proxy was successful! */
549 }