2 * author: rofl0r (C) 2011-2017
3 * License: LGPL 2.1+ with static linking exception
7 * recognized defines: USE_SSL, ROCKSOCK_FILENAME, NO_DNS_SUPPORT
10 #undef _POSIX_C_SOURCE
11 #define _POSIX_C_SOURCE 200809L
21 #include <sys/select.h>
22 #include <netinet/in.h>
25 #warning compiling without SOCK_CLOEXEC support
26 #define SOCK_CLOEXEC 0
30 #include "rocksock_internal.h"
32 /* use this if you have installed libulz systemwide via make install */
33 #include <ulz/strlib.h>
34 #include <ulz/stdio-repl.h>
35 #elif defined(USE_LIBULZ)
36 /* use this if you want to use the extracted libulz sources via RcB2.
37 pass a -I flag pointing to its include dir, like so: -I ../lib/include
40 #include <stdio-repl.h>
42 /* this version of ipv4fromstring was taken from libulz and tuned to be
43 more pedantic than the libulz version, so it can be used for isnumericipv4()
44 as well, as it strictly checks the input for correctness. */
45 static int ipv4fromstring(const char* ipstring
, unsigned char* fourbytesptr
) {
46 const char* start
= ipstring
;
49 if(*ipstring
== '.' || !*ipstring
) {
50 fourbytesptr
[outbyte
] = 0;
53 switch(ipstring
- start
) {
55 tmp
= (start
[b
++]-'0')*100;
56 if(tmp
> 200) return 0;
57 fourbytesptr
[outbyte
] += tmp
;
59 fourbytesptr
[outbyte
] += (start
[b
++]-'0')*10;
61 fourbytesptr
[outbyte
] += (start
[b
++]-'0');
69 if(*ipstring
< '0' || *ipstring
> '9') return 0;
71 if(!*ipstring
&& outbyte
< 4) return 0;
74 if(ipstring
[-1]) return 0;
78 static int isnumericipv4(const char* ipstring
) {
80 return ipv4fromstring(ipstring
, ip
);
84 #ifndef ROCKSOCK_FILENAME
85 #define ROCKSOCK_FILENAME __FILE__
89 #define MSG_NOSIGNAL 0
93 #include "rocksock_ssl_internal.h"
96 int rocksock_seterror(rocksock
* sock
, rs_errorType errortype
, int error
, const char* file
, int line
) {
97 if (!sock
) return RS_E_NULL
;
98 sock
->lasterror
.errortype
= errortype
;
99 sock
->lasterror
.error
= error
;
100 sock
->lasterror
.line
= line
;
101 sock
->lasterror
.file
= file
;
102 sock
->lasterror
.failedProxy
= -1;
106 #define MKOERR(S, X) rocksock_seterror(S, RS_ET_OWN, X, ROCKSOCK_FILENAME, __LINE__)
107 #define NOERR(S) rocksock_seterror(S, RS_ET_OWN, 0, NULL, 0)
108 #define MKSYSERR(S, X) rocksock_seterror(S, RS_ET_SYS, X, ROCKSOCK_FILENAME, __LINE__)
110 //#define NO_DNS_SUPPORT
111 static int rocksock_resolve_host(rocksock
* sock
, rs_hostInfo
* hostinfo
, rs_resolveStorage
* result
) {
112 if (!sock
) return RS_E_NULL
;
113 if (!hostinfo
|| !hostinfo
->host
[0] || !hostinfo
->port
) return MKOERR(sock
, RS_E_NULL
);
115 result
->hostaddr
= &(result
->hostaddr_buf
);
117 #ifndef NO_DNS_SUPPORT
118 struct addrinfo hints
= {.ai_family
= AF_UNSPEC
, .ai_socktype
= SOCK_STREAM
, .ai_flags
= AI_ADDRCONFIG
};
120 struct addrinfo
*best
, *save
;
121 ret
= getaddrinfo(hostinfo
->host
, NULL
, &hints
, &save
);
124 while(best
->ai_addr
->sa_family
== AF_INET6
&& best
->ai_next
) best
= best
->ai_next
;
125 *result
->hostaddr
= *best
;
126 result
->hostaddr
->ai_addr
= (struct sockaddr
*) &(result
->hostaddr_aiaddr_buf
);
127 result
->hostaddr
->ai_next
= 0;
128 *result
->hostaddr
->ai_addr
= *best
->ai_addr
;
130 if(result
->hostaddr
->ai_addr
->sa_family
== AF_INET
)
131 ((struct sockaddr_in
*) result
->hostaddr
->ai_addr
)->sin_port
= htons(hostinfo
->port
);
133 ((struct sockaddr_in6
*) result
->hostaddr
->ai_addr
)->sin6_port
= htons(hostinfo
->port
);
137 return rocksock_seterror(sock
, RS_ET_GAI
, ret
, ROCKSOCK_FILENAME
, __LINE__
);
139 result
->hostaddr
->ai_addr
= (struct sockaddr
*) &(result
->hostaddr_aiaddr_buf
);
141 ((struct sockaddr_in
*) result
->hostaddr
->ai_addr
)->sin_port
= htons(hostinfo
->port
);
142 ((struct sockaddr_in
*) result
->hostaddr
->ai_addr
)->sin_family
= AF_INET
;
143 result
->hostaddr
->ai_addr
->sa_family
= AF_INET
;
144 result
->hostaddr
->ai_addrlen
= sizeof(struct sockaddr_in
);
145 ipv4fromstring(hostinfo
->host
, (unsigned char*) &((struct sockaddr_in
*) result
->hostaddr
->ai_addr
)->sin_addr
);
151 int rocksock_set_timeout(rocksock
* sock
, unsigned long timeout_millisec
) {
152 if (!sock
) return RS_E_NULL
;
153 sock
->timeout
= timeout_millisec
;
157 int rocksock_init(rocksock
* sock
, rs_proxy
*proxies
) {
158 if (!sock
) return RS_E_NULL
;
159 memset(sock
, 0, sizeof(rocksock
));
160 sock
->lastproxy
= -1;
161 sock
->timeout
= 60*1000;
163 sock
->proxies
= proxies
;
167 static struct timeval
* make_timeval(struct timeval
* tv
, unsigned long timeout
) {
169 tv
->tv_sec
= timeout
/ 1000;
170 tv
->tv_usec
= 1000 * (timeout
% 1000);
174 static int do_connect(rocksock
* sock
, rs_resolveStorage
* hostinfo
, unsigned long timeout
) {
179 socklen_t optlen
= sizeof(optval
);
181 sock
->socket
= socket(hostinfo
->hostaddr
->ai_family
, SOCK_STREAM
| SOCK_CLOEXEC
, 0);
182 if(sock
->socket
== -1) return MKSYSERR(sock
, errno
);
184 /* the socket has to be made non-blocking temporarily so we can enforce a connect timeout */
185 flags
= fcntl(sock
->socket
, F_GETFL
);
186 if(flags
== -1) return MKSYSERR(sock
, errno
);
188 if(fcntl(sock
->socket
, F_SETFL
, flags
| O_NONBLOCK
) == -1) return MKSYSERR(sock
, errno
);
190 ret
= connect(sock
->socket
, hostinfo
->hostaddr
->ai_addr
, hostinfo
->hostaddr
->ai_addrlen
);
193 if (!(ret
== EINPROGRESS
|| ret
== EWOULDBLOCK
)) return MKSYSERR(sock
, ret
);
196 if(fcntl(sock
->socket
, F_SETFL
, flags
) == -1) return MKSYSERR(sock
, errno
);
199 FD_SET(sock
->socket
, &wset
);
201 ret
= select(sock
->socket
+1, NULL
, &wset
, NULL
, timeout
? make_timeval(&tv
, timeout
) : NULL
);
203 if(ret
== 1 && FD_ISSET(sock
->socket
, &wset
)) {
204 ret
= getsockopt(sock
->socket
, SOL_SOCKET
, SO_ERROR
, &optval
,&optlen
);
205 if(ret
== -1) return MKSYSERR(sock
, errno
);
206 else if(optval
) return MKSYSERR(sock
, optval
);
208 } else if(ret
== 0) return MKOERR(sock
, RS_E_HIT_CONNECTTIMEOUT
);
210 return MKSYSERR(sock
, errno
);
213 static int rocksock_setup_socks4_header(rocksock
* sock
, int is4a
, char* buffer
, rs_proxy
* proxy
, size_t* bytesused
) {
217 buffer
[2] = proxy
->hostinfo
.port
/ 256;
218 buffer
[3] = proxy
->hostinfo
.port
% 256;
226 rs_resolveStorage stor
;
227 ret
= rocksock_resolve_host(sock
, &proxy
->hostinfo
, &stor
);
229 if(stor
.hostaddr
->ai_family
!= AF_INET
)
230 return MKOERR(sock
, RS_E_SOCKS4_NO_IP6
);
231 buffer
[4] = ((char*) &(((struct sockaddr_in
*) stor
.hostaddr
->ai_addr
)->sin_addr
.s_addr
))[0];
232 buffer
[5] = ((char*) &(((struct sockaddr_in
*) stor
.hostaddr
->ai_addr
)->sin_addr
.s_addr
))[1];
233 buffer
[6] = ((char*) &(((struct sockaddr_in
*) stor
.hostaddr
->ai_addr
)->sin_addr
.s_addr
))[2];
234 buffer
[7] = ((char*) &(((struct sockaddr_in
*) stor
.hostaddr
->ai_addr
)->sin_addr
.s_addr
))[3];
239 char *p
= buffer
+ *bytesused
;
240 size_t l
= strlen(proxy
->hostinfo
.host
) + 1;
241 /* memcpy is safe because all functions accepting a hostname check it's < 255 */
242 memcpy(p
, proxy
->hostinfo
.host
, l
);
248 int rocksock_connect(rocksock
* sock
, const char* host
, unsigned short port
, int useSSL
) {
250 int ret
, trysocksv4a
;
251 rs_hostInfo targethost
;
252 rs_hostInfo
* connector
;
254 rs_proxy
* targetproxy
;
257 size_t socksused
= 0, bytes
;
258 if (!sock
) return RS_E_NULL
;
260 return MKOERR(sock
, RS_E_NULL
);
261 size_t hl
= strlen(host
);
263 return MKOERR(sock
, RS_E_HOSTNAME_TOO_LONG
);
265 if (useSSL
) return MKOERR(sock
, RS_E_NO_SSL
);
267 memcpy(targethost
.host
, host
, hl
+1);
268 targethost
.port
= port
;
270 if(sock
->lastproxy
>= 0)
271 connector
= &sock
->proxies
[0].hostinfo
;
273 connector
= &targethost
;
275 rs_resolveStorage stor
;
277 ret
= rocksock_resolve_host(sock
, connector
, &stor
);
279 check_proxy0_failure
:
284 ret
= do_connect(sock
, &stor
, sock
->timeout
);
285 if(ret
) goto check_proxy0_failure
;
287 for(px
= 0; px
<= sock
->lastproxy
; px
++) {
288 if(px
== sock
->lastproxy
) {
289 targetproxy
= &dummy
;
290 dummy
.hostinfo
= targethost
;
291 dummy
.password
[0] = 0;
292 dummy
.username
[0] = 0;
293 dummy
.proxytype
= RS_PT_NONE
;
295 targetproxy
= &sock
->proxies
[px
+ 1];
297 // send socks connection data
298 switch(sock
->proxies
[px
].proxytype
) {
302 ret
= rocksock_setup_socks4_header(sock
, trysocksv4a
, socksdata
, targetproxy
, &socksused
);
305 sock
->lasterror
.failedProxy
= px
== sock
->lastproxy
? -1 : px
+ 1;
306 if(sock
->socket
!= -1) {
312 ret
= rocksock_send(sock
, socksdata
, socksused
, 0, &bytes
);
313 if(ret
) goto proxyfailure
;
314 ret
= rocksock_recv(sock
, socksdata
, 8, 8, &bytes
);
315 if(ret
) goto proxyfailure
;
316 if(bytes
< 8 || socksdata
[0] != 0) {
318 ret
= MKOERR(sock
, RS_E_PROXY_UNEXPECTED_RESPONSE
);
321 switch(socksdata
[1]) {
330 ret
= MKOERR(sock
, RS_E_TARGETPROXY_CONNECT_FAILED
);
332 case 0x5c: case 0x5d:
334 ret
= MKOERR(sock
, RS_E_PROXY_AUTH_FAILED
);
343 if(sock
->proxies
[px
].username
[0] && sock
->proxies
[px
].password
[0]) {
351 bytes
= p
- socksdata
;
352 ret
= rocksock_send(sock
, socksdata
, bytes
, bytes
, &bytes
);
353 if(ret
) goto proxyfailure
;
354 ret
= rocksock_recv(sock
, socksdata
, 2, 2, &bytes
);
355 if(ret
) goto proxyfailure
;
356 if(bytes
< 2 || socksdata
[0] != 5) goto err_unexpected
;
357 if(socksdata
[1] == '\xff') {
359 } else if (socksdata
[1] == 2) {
360 if(sock
->proxies
[px
].username
[0] && sock
->proxies
[px
].password
[0]) {
362 +----+------+----------+------+----------+
363 |VER | ULEN | UNAME | PLEN | PASSWD |
364 +----+------+----------+------+----------+
365 | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
366 +----+------+----------+------+----------+
370 bytes
= strlen(sock
->proxies
[px
].username
);
372 memcpy(p
, sock
->proxies
[px
].username
, bytes
);
374 bytes
= strlen(sock
->proxies
[px
].password
);
376 memcpy(p
, sock
->proxies
[px
].password
, bytes
);
378 bytes
= p
- socksdata
;
379 ret
= rocksock_send(sock
, socksdata
, bytes
, bytes
, &bytes
);
380 if(ret
) goto proxyfailure
;
381 ret
= rocksock_recv(sock
, socksdata
, 2, 2, &bytes
);
382 if(ret
) goto proxyfailure
;
383 if(bytes
< 2) goto err_unexpected
;
384 else if(socksdata
[1] != 0) goto err_proxyauth
;
393 if(isnumericipv4(targetproxy
->hostinfo
.host
)) {
394 *p
++ = 1; // ipv4 method
396 ipv4fromstring(targetproxy
->hostinfo
.host
, (unsigned char*) p
);
398 *p
++ = 3; //hostname method, requires the server to do dns lookups.
399 bytes
= strlen(targetproxy
->hostinfo
.host
);
401 return MKOERR(sock
, RS_E_SOCKS5_AUTH_EXCEEDSIZE
);
403 memcpy(p
, targetproxy
->hostinfo
.host
, bytes
);
406 *p
++ = targetproxy
->hostinfo
.port
/ 256;
407 *p
++ = targetproxy
->hostinfo
.port
% 256;
408 bytes
= p
- socksdata
;
409 ret
= rocksock_send(sock
, socksdata
, bytes
, bytes
, &bytes
);
410 if(ret
) goto proxyfailure
;
411 ret
= rocksock_recv(sock
, socksdata
, sizeof(socksdata
), sizeof(socksdata
), &bytes
);
412 if(ret
) goto proxyfailure
;
413 if(bytes
< 2) goto err_unexpected
;
414 switch(socksdata
[1]) {
418 ret
= MKOERR(sock
, RS_E_PROXY_GENERAL_FAILURE
);
423 ret
= MKOERR(sock
, RS_E_TARGETPROXY_NET_UNREACHABLE
);
426 ret
= MKOERR(sock
, RS_E_TARGETPROXY_HOST_UNREACHABLE
);
429 ret
= MKOERR(sock
, RS_E_TARGETPROXY_CONN_REFUSED
);
432 ret
= MKOERR(sock
, RS_E_TARGETPROXY_TTL_EXPIRED
);
435 ret
= MKOERR(sock
, RS_E_PROXY_COMMAND_NOT_SUPPORTED
);
438 ret
= MKOERR(sock
, RS_E_PROXY_ADDRESSTYPE_NOT_SUPPORTED
);
445 bytes
= snprintf(socksdata
, sizeof(socksdata
), "CONNECT %s:%d HTTP/1.1\r\n\r\n", targetproxy
->hostinfo
.host
, targetproxy
->hostinfo
.port
);
446 ret
= rocksock_send(sock
, socksdata
, bytes
, bytes
, &bytes
);
447 if(ret
) goto proxyfailure
;
448 ret
= rocksock_recv(sock
, socksdata
, sizeof(socksdata
), sizeof(socksdata
), &bytes
);
449 if(ret
) goto proxyfailure
;
450 if(bytes
< 12) goto err_unexpected
;
451 if(socksdata
[9] != '2') goto err_proxyconnect
;
460 ret
= rocksock_ssl_connect_fd(sock
);
472 static int rocksock_operation(rocksock
* sock
, rs_operationType operation
, char* buffer
, size_t bufsize
, size_t chunksize
, size_t* bytes
) {
473 if (!sock
) return RS_E_NULL
;
474 if (!buffer
|| !bytes
|| (!bufsize
&& operation
== RS_OT_READ
)) return MKOERR(sock
, RS_E_NULL
);
481 size_t bytesleft
= bufsize
? bufsize
: strlen(buffer
);
483 char* bufptr
= buffer
;
485 if (sock
->socket
== -1) return MKOERR(sock
, RS_E_NO_SOCKET
);
486 if(operation
== RS_OT_SEND
) wfd
= &fd
;
490 if(operation
== RS_OT_SEND
)
491 ret
= setsockopt(sock
->socket
, SOL_SOCKET
, SO_SNDTIMEO
, (void*) make_timeval(&tv
, sock
->timeout
), sizeof(tv
));
493 ret
= setsockopt(sock
->socket
, SOL_SOCKET
, SO_RCVTIMEO
, (void*) make_timeval(&tv
, sock
->timeout
), sizeof(tv
));
496 if (ret
== -1) return MKSYSERR(sock
, errno
);
499 byteswanted
= (chunksize
&& chunksize
< bytesleft
) ? chunksize
: bytesleft
;
502 if(operation
== RS_OT_SEND
)
503 ret
= rocksock_ssl_send(sock
, bufptr
, byteswanted
);
505 ret
= rocksock_ssl_recv(sock
, bufptr
, byteswanted
);
508 /* enforce the timeout by using select() before doing the actual recv/send */
509 FD_SET(sock
->socket
, &fd
);
510 ret
=select(sock
->socket
+1, rfd
, wfd
, NULL
, sock
->timeout
? make_timeval(&tv
, sock
->timeout
) : NULL
);
511 if(!FD_ISSET(sock
->socket
, &fd
)) MKOERR(sock
, RS_E_NULL
); // temp test
513 //printf("h: %s, skt: %d, to: %d:%d\n", targethost.host, sock->socket, tv.tv_sec, tv.tv_usec);
514 return MKSYSERR(sock
, errno
);
516 else if(!ret
) return MKOERR(sock
, RS_OT_READ
? RS_E_HIT_READTIMEOUT
: RS_E_HIT_WRITETIMEOUT
);
518 if(operation
== RS_OT_SEND
)
519 ret
= send(sock
->socket
, bufptr
, byteswanted
, MSG_NOSIGNAL
);
521 ret
= recv(sock
->socket
, bufptr
, byteswanted
, 0);
527 if(!ret
) // The return value will be 0 when the peer has performed an orderly shutdown.
528 return MKOERR(sock
, RS_E_REMOTE_DISCONNECTED
);
531 if(ret
== EWOULDBLOCK
|| ret
== EINPROGRESS
) return MKOERR(sock
, RS_OT_READ
? RS_E_HIT_READTIMEOUT
: RS_E_HIT_WRITETIMEOUT
);
532 return MKSYSERR(sock
, errno
);
538 if(operation
== RS_OT_READ
&& (size_t) ret
< byteswanted
) break;
543 int rocksock_send(rocksock
* sock
, char* buffer
, size_t bufsize
, size_t chunksize
, size_t* byteswritten
) {
544 return rocksock_operation(sock
, RS_OT_SEND
, buffer
, bufsize
, chunksize
, byteswritten
);
547 int rocksock_recv(rocksock
* sock
, char* buffer
, size_t bufsize
, size_t chunksize
, size_t* bytesread
) {
548 return rocksock_operation(sock
, RS_OT_READ
, buffer
, bufsize
, chunksize
, bytesread
);
551 int rocksock_disconnect(rocksock
* sock
) {
552 if (!sock
) return RS_E_NULL
;
554 rocksock_ssl_free_context(sock
);
556 if(sock
->socket
!= -1) close(sock
->socket
);
561 int rocksock_clear(rocksock
* sock
) {
562 if (!sock
) return RS_E_NULL
;
563 sock
->lastproxy
= -1;