Version 1.8.0.0
[socat.git] / xio-socks.c
blob5bbb805dd1753efb752a728e028cc0eb427dd7cd
1 /* source: xio-socks.c */
2 /* Copyright Gerhard Rieger and contributors (see file CHANGES) */
3 /* Published under the GNU General Public License V.2, see file COPYING */
5 /* this file contains the source for opening addresses of socks4 type */
7 #include "xiosysincludes.h"
9 #if WITH_SOCKS4 || WITH_SOCKS4A
11 #include "xioopen.h"
12 #include "xio-ascii.h"
13 #include "xio-socket.h"
14 #include "xio-ip.h"
15 #include "xio-ipapp.h"
17 #include "xio-socks.h"
20 enum {
21 SOCKS_CD_GRANTED = 90,
22 SOCKS_CD_FAILED,
23 SOCKS_CD_NOIDENT,
24 SOCKS_CD_IDENTFAILED
25 } ;
27 #define SOCKSPORT "1080"
28 #define BUFF_LEN (SIZEOF_STRUCT_SOCKS4+512)
30 static int xioopen_socks4_connect(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *fd, const struct addrdesc *addrdesc);
32 const struct optdesc opt_socksport = { "socksport", NULL, OPT_SOCKSPORT, GROUP_IP_SOCKS4, PH_LATE, TYPE_STRING, OFUNC_SPEC };
33 const struct optdesc opt_socksuser = { "socksuser", NULL, OPT_SOCKSUSER, GROUP_IP_SOCKS4, PH_LATE, TYPE_NAME, OFUNC_SPEC };
35 const struct addrdesc xioaddr_socks4_connect = { "SOCKS4", 3, xioopen_socks4_connect, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_IP_SOCKS4|GROUP_CHILD|GROUP_RETRY, 0, 0, 0 HELP(":<socks-server>:<host>:<port>") };
37 const struct addrdesc xioaddr_socks4a_connect = { "SOCKS4A", 3, xioopen_socks4_connect, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_IP_SOCKS4|GROUP_CHILD|GROUP_RETRY, 1, 0, 0 HELP(":<socks-server>:<host>:<port>") };
39 static int xioopen_socks4_connect(
40 int argc,
41 const char *argv[],
42 struct opt *opts,
43 int xioflags,
44 xiofile_t *xxfd,
45 const struct addrdesc *addrdesc)
47 /* we expect the form: host:host:port */
48 struct single *sfd = &xxfd->stream;
49 int socks4a = addrdesc->arg1;
50 struct opt *opts0 = NULL;
51 const char *sockdname; char *socksport;
52 const char *targetname, *targetport;
53 int pf = PF_UNSPEC;
54 int ipproto = IPPROTO_TCP;
55 bool dofork = false;
56 union sockaddr_union us_sa, *us = &us_sa;
57 socklen_t uslen = sizeof(us_sa);
58 struct addrinfo *themlist, *themp;
59 struct addrinfo **ai_sorted;
60 int i;
61 bool needbind = false;
62 bool lowport = false;
63 char infobuff[256];
64 unsigned char buff[BUFF_LEN];
65 struct socks4 *sockhead = (struct socks4 *)buff;
66 size_t buflen = sizeof(buff);
67 int socktype = SOCK_STREAM;
68 int level;
69 int result;
71 if (argc != 4) {
72 xio_syntax(argv[0], 3, argc-1, addrdesc->syntax);
73 return STAT_NORETRY;
75 sockdname = argv[1];
76 targetname = argv[2];
77 targetport = argv[3];
79 if (sfd->howtoend == END_UNSPEC)
80 sfd->howtoend = END_SHUTDOWN;
81 if (applyopts_single(sfd, opts, PH_INIT) < 0) return -1;
82 applyopts(sfd, 1, opts, PH_INIT);
84 retropt_int(opts, OPT_SO_TYPE, &socktype);
86 retropt_bool(opts, OPT_FORK, &dofork);
88 result = _xioopen_socks4_prepare(targetport, opts, &socksport, sockhead, &buflen);
89 if (result != STAT_OK)
90 return result;
92 Notice5("opening connection to %s:%u via socks4 server %s:%s as user \"%s\"",
93 targetname,
94 ntohs(sockhead->port),
95 sockdname, socksport, sockhead->userid);
97 i = 0;
98 do { /* loop over retries (failed connect and socks-request attempts) */
100 level = E_INFO;
102 result =
103 _xioopen_ipapp_prepare(opts, &opts0, sockdname, socksport,
104 &pf, ipproto,
105 sfd->para.socket.ip.ai_flags,
106 &themlist, us, &uslen,
107 &needbind, &lowport, socktype);
109 /* Count addrinfo entries */
110 themp = themlist;
111 i = 0;
112 while (themp != NULL) {
113 ++i;
114 themp = themp->ai_next;
116 ai_sorted = Calloc((i+1), sizeof(struct addrinfo *));
117 if (ai_sorted == NULL)
118 return STAT_RETRYLATER;
119 /* Generate a list of addresses sorted by preferred ip version */
120 _xio_sort_ip_addresses(themlist, ai_sorted);
122 /* we try to resolve the target address _before_ connecting to the socks
123 server: this avoids unnecessary socks connects and timeouts */
124 result =
125 _xioopen_socks4_connect0(sfd, targetname, socks4a, sockhead,
126 (ssize_t *)&buflen, level);
127 switch (result) {
128 case STAT_OK: break;
129 #if WITH_RETRY
130 case STAT_RETRYLATER:
131 case STAT_RETRYNOW:
132 if (sfd->forever || sfd->retry--) {
133 if (result == STAT_RETRYLATER)
134 Nanosleep(&sfd->intervall, NULL);
135 continue;
137 #endif /* WITH_RETRY */
138 default:
139 return result;
142 /* loop over themlist */
143 i = 0;
144 themp = ai_sorted[i++];
145 while (themp != NULL) {
146 Notice1("opening connection to %s",
147 sockaddr_info(themp->ai_addr, themp->ai_addrlen,
148 infobuff, sizeof(infobuff)));
149 #if WITH_RETRY
150 if (sfd->forever || sfd->retry || ai_sorted[i] != NULL) {
151 level = E_INFO;
152 } else
153 #endif /* WITH_RETRY */
154 level = E_ERROR;
156 /* this cannot fork because we retrieved fork option above */
157 result =
158 _xioopen_connect(sfd,
159 needbind?us:NULL, uslen,
160 themp->ai_addr, themp->ai_addrlen,
161 opts, pf?pf:themp->ai_family, socktype, IPPROTO_TCP, lowport, level);
162 if (result == STAT_OK)
163 break;
164 themp = ai_sorted[i++];
165 if (themp == NULL)
166 result = STAT_RETRYLATER;
168 switch (result) {
169 case STAT_OK: break;
170 #if WITH_RETRY
171 case STAT_RETRYLATER:
172 case STAT_RETRYNOW:
173 if (sfd->forever || sfd->retry) {
174 --sfd->retry;
175 if (result == STAT_RETRYLATER)
176 Nanosleep(&sfd->intervall, NULL);
177 continue;
179 #endif /* WITH_RETRY */
180 default:
181 free(ai_sorted);
182 xiofreeaddrinfo(themlist);
183 return result;
185 xiofreeaddrinfo(themlist);
186 applyopts(sfd, -1, opts, PH_ALL);
188 if ((result = _xio_openlate(sfd, opts)) < 0)
189 return result;
191 result = _xioopen_socks4_connect(sfd, sockhead, buflen, level);
192 switch (result) {
193 case STAT_OK: break;
194 #if WITH_RETRY
195 case STAT_RETRYLATER:
196 case STAT_RETRYNOW:
197 if (sfd->forever || sfd->retry--) {
198 if (result == STAT_RETRYLATER) Nanosleep(&sfd->intervall, NULL);
199 continue;
201 #endif /* WITH_RETRY */
202 default:
203 return result;
206 if (dofork) {
207 xiosetchilddied(); /* set SIGCHLD handler */
210 #if WITH_RETRY
211 if (dofork) {
212 pid_t pid;
213 int level = E_ERROR;
214 if (sfd->forever || sfd->retry) {
215 level = E_WARN; /* most users won't expect a problem here,
216 so Notice is too weak */
218 while ((pid = xio_fork(false, level, sfd->shutup)) < 0) {
219 if (sfd->forever || --sfd->retry) {
220 Nanosleep(&sfd->intervall, NULL);
221 continue;
223 return STAT_RETRYLATER;
226 if (pid == 0) { /* child process */
227 sfd->forever = false;
228 sfd->retry = 0;
229 break;
232 /* parent process */
233 Close(sfd->fd);
234 Nanosleep(&sfd->intervall, NULL);
235 dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
236 continue;
237 } else
238 #endif /* WITH_RETRY */
240 break;
243 } while (true); /* end of complete open loop - drop out on success */
244 return 0;
248 int _xioopen_socks4_prepare(const char *targetport, struct opt *opts, char **socksport, struct socks4 *sockhead, size_t *headlen) {
249 struct servent *se;
250 char *userid;
252 /* generate socks header - points to final target */
253 sockhead->version = 4;
254 sockhead->action = 1;
255 sockhead->port = parseport(targetport, IPPROTO_TCP); /* network byte
256 order */
258 if (retropt_string(opts, OPT_SOCKSPORT, socksport) < 0) {
259 if ((se = getservbyname("socks", "tcp")) != NULL) {
260 Debug1("\"socks/tcp\" resolves to %u", ntohs(se->s_port));
261 if ((*socksport = Malloc(6)) == NULL) {
262 return -1;
264 sprintf(*socksport, "%u", ntohs(se->s_port));
265 } else {
266 Debug1("cannot resolve service \"socks/tcp\", using %s", SOCKSPORT);
267 if ((*socksport = strdup(SOCKSPORT)) == NULL) {
268 errno = ENOMEM; return -1;
273 if (retropt_string(opts, OPT_SOCKSUSER, &userid) < 0) {
274 if ((userid = getenv("LOGNAME")) == NULL) {
275 if ((userid = getenv("USER")) == NULL) {
276 userid = "anonymous";
280 sockhead->userid[0] = '\0'; strncat(sockhead->userid, userid, *headlen-SIZEOF_STRUCT_SOCKS4-1);
281 *headlen = SIZEOF_STRUCT_SOCKS4+strlen(userid)+1;
282 return STAT_OK;
286 /* called within retry/fork loop, before connect() */
288 _xioopen_socks4_connect0(struct single *sfd,
289 const char *hostname, /* socks target host */
290 int socks4a,
291 struct socks4 *sockhead,
292 ssize_t *headlen, /* get available space,
293 return used length*/
294 int level) {
295 int result;
297 if (!socks4a) {
298 union sockaddr_union sau;
299 socklen_t saulen = sizeof(sau);
301 if ((result = xioresolve(hostname, NULL,
302 PF_INET, SOCK_STREAM, IPPROTO_TCP,
303 &sau, &saulen,
304 sfd->para.socket.ip.ai_flags))
305 != STAT_OK) {
306 return result; /*! STAT_RETRY? */
308 memcpy(&sockhead->dest, &sau.ip4.sin_addr, 4);
310 #if WITH_SOCKS4A
311 else {
312 /*! noresolve */
313 sockhead->dest = htonl(0x00000001); /* three bytes zero */
315 #endif /* WITH_SOCKS4A */
316 #if WITH_SOCKS4A
317 if (socks4a) {
318 /* SOCKS4A requires us to append the host name to resolve
319 after the user name's trailing 0 byte. */
320 char* insert_position = (char*) sockhead + *headlen;
322 insert_position[0] = '\0'; strncat(insert_position, hostname, BUFF_LEN-*headlen-1);
323 ((char *)sockhead)[BUFF_LEN-1] = 0;
324 *headlen += strlen(hostname) + 1;
325 if (*headlen > BUFF_LEN) {
326 *headlen = BUFF_LEN;
329 #endif /* WITH_SOCKS4A */
330 return STAT_OK;
334 /* perform socks4 client dialog on existing FD.
335 Called within fork/retry loop, after connect() */
336 int _xioopen_socks4_connect(struct single *sfd,
337 struct socks4 *sockhead,
338 size_t headlen,
339 int level) {
340 ssize_t bytes;
341 int result;
342 unsigned char buff[SIZEOF_STRUCT_SOCKS4];
343 struct socks4 *replyhead = (struct socks4 *)buff;
344 char *destdomname = NULL;
346 /* send socks header (target addr+port, +auth) */
347 #if WITH_MSGLEVEL <= E_INFO
348 if (ntohl(sockhead->dest) <= 0x000000ff) {
349 destdomname = strchr(sockhead->userid, '\0')+1;
351 Info11("sending socks4%s request VN=%d DC=%d DSTPORT=%d DSTIP=%d.%d.%d.%d USERID=%s%s%s",
352 destdomname?"a":"",
353 sockhead->version, sockhead->action, ntohs(sockhead->port),
354 ((unsigned char *)&sockhead->dest)[0],
355 ((unsigned char *)&sockhead->dest)[1],
356 ((unsigned char *)&sockhead->dest)[2],
357 ((unsigned char *)&sockhead->dest)[3],
358 sockhead->userid,
359 destdomname?" DESTNAME=":"",
360 destdomname?destdomname:"");
361 #endif /* WITH_MSGLEVEL <= E_INFO */
362 #if WITH_MSGLEVEL <= E_DEBUG
364 char *msgbuff;
365 if ((msgbuff = Malloc(3*headlen)) != NULL) {
366 xiohexdump((const unsigned char *)sockhead, headlen, msgbuff);
367 Debug1("sending socks4(a) request data %s", msgbuff);
370 #endif /* WITH_MSGLEVEL <= E_DEBUG */
371 if (writefull(sfd->fd, sockhead, headlen) < 0) {
372 Msg4(level, "write(%d, %p, "F_Zu"): %s",
373 sfd->fd, sockhead, headlen, strerror(errno));
374 if (Close(sfd->fd) < 0) {
375 Info2("close(%d): %s", sfd->fd, strerror(errno));
377 return STAT_RETRYLATER; /* retry complete open cycle */
380 bytes = 0;
381 Info("waiting for socks reply");
382 while (bytes >= 0) { /* loop over answer chunks until complete or error */
383 /* receive socks answer */
384 do {
385 result = Read(sfd->fd, buff+bytes, SIZEOF_STRUCT_SOCKS4-bytes);
386 } while (result < 0 && errno == EINTR);
387 if (result < 0) {
388 Msg4(level, "read(%d, %p, "F_Zu"): %s",
389 sfd->fd, buff+bytes, SIZEOF_STRUCT_SOCKS4-bytes,
390 strerror(errno));
391 if (Close(sfd->fd) < 0) {
392 Info2("close(%d): %s", sfd->fd, strerror(errno));
395 if (result == 0) {
396 Msg(level, "read(): EOF during read of socks reply, peer might not be a socks4 server");
397 if (Close(sfd->fd) < 0) {
398 Info2("close(%d): %s", sfd->fd, strerror(errno));
400 return STAT_RETRYLATER;
402 #if WITH_MSGLEVEL <= E_DEBUG
404 char msgbuff[3*SIZEOF_STRUCT_SOCKS4];
405 * xiohexdump((const unsigned char *)replyhead+bytes, result, msgbuff)
406 = '\0';
407 Debug2("received socks4 reply data (offset "F_Zd"): %s", bytes, msgbuff);
409 #endif /* WITH_MSGLEVEL <= E_DEBUG */
410 bytes += result;
411 if (bytes == SIZEOF_STRUCT_SOCKS4) {
412 Debug1("received all "F_Zd" bytes", bytes);
413 break;
415 Debug2("received %d bytes, waiting for "F_Zu" more bytes",
416 result, SIZEOF_STRUCT_SOCKS4-bytes);
418 if (result <= 0) { /* we had a problem while reading socks answer */
419 return STAT_RETRYLATER; /* retry complete open cycle */
422 Info7("received socks reply VN=%u CD=%u DSTPORT=%u DSTIP=%u.%u.%u.%u",
423 replyhead->version, replyhead->action, ntohs(replyhead->port),
424 ((uint8_t *)&replyhead->dest)[0],
425 ((uint8_t *)&replyhead->dest)[1],
426 ((uint8_t *)&replyhead->dest)[2],
427 ((uint8_t *)&replyhead->dest)[3]);
428 if (replyhead->version != 0) {
429 Warn1("socks: reply code version is not 0 (%d)",
430 replyhead->version);
433 switch (replyhead->action) {
434 case SOCKS_CD_GRANTED:
435 /* Notice("socks: connect request succeeded"); */
436 #if 0
437 if (Getsockname(sfd->fd, (struct sockaddr *)&us, &uslen) < 0) {
438 Warn4("getsockname(%d, %p, {%d}): %s",
439 sfd->fd, &us, uslen, strerror(errno));
441 Notice1("successfully connected from %s via socks4",
442 sockaddr_info((struct sockaddr *)&us, infobuff, sizeof(infobuff)));
443 #else
444 Notice("successfully connected via socks4");
445 #endif
446 break;
448 case SOCKS_CD_FAILED:
449 Msg(level, "socks: connect request rejected or failed");
450 return STAT_RETRYLATER;
452 case SOCKS_CD_NOIDENT:
453 Msg(level, "socks: ident refused by client");
454 return STAT_RETRYLATER;
456 case SOCKS_CD_IDENTFAILED:
457 Msg(level, "socks: ident failed");
458 return STAT_RETRYLATER;
460 default:
461 Msg1(level, "socks: undefined status %u", replyhead->action);
464 return STAT_OK;
466 #endif /* WITH_SOCKS4 || WITH_SOCKS4A */