| /* source: xio-socks.c */ |
| /* Copyright Gerhard Rieger and contributors (see file CHANGES) */ |
| /* Published under the GNU General Public License V.2, see file COPYING */ |
| |
| /* this file contains the source for opening addresses of socks4 type */ |
| |
| #include "xiosysincludes.h" |
| |
| #if WITH_SOCKS4 || WITH_SOCKS4A |
| |
| #include "xioopen.h" |
| #include "xio-ascii.h" |
| #include "xio-socket.h" |
| #include "xio-ip.h" |
| #include "xio-ipapp.h" |
| |
| #include "xio-socks.h" |
| |
| |
| enum { |
| SOCKS_CD_GRANTED = 90, |
| SOCKS_CD_FAILED, |
| SOCKS_CD_NOIDENT, |
| SOCKS_CD_IDENTFAILED |
| } ; |
| |
| #define SOCKSPORT "1080" |
| #define BUFF_LEN (SIZEOF_STRUCT_SOCKS4+512) |
| |
| static int xioopen_socks4_connect(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *fd, const struct addrdesc *addrdesc); |
| |
| const struct optdesc opt_socksport = { "socksport", NULL, OPT_SOCKSPORT, GROUP_IP_SOCKS, PH_LATE, TYPE_STRING, OFUNC_SPEC }; |
| const struct optdesc opt_socksuser = { "socksuser", NULL, OPT_SOCKSUSER, GROUP_IP_SOCKS, PH_LATE, TYPE_NAME, OFUNC_SPEC }; |
| |
| 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_SOCKS|GROUP_CHILD|GROUP_RETRY, 0, 0, 0 HELP(":<socks-server>:<host>:<port>") }; |
| |
| 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_SOCKS|GROUP_CHILD|GROUP_RETRY, 1, 0, 0 HELP(":<socks-server>:<host>:<port>") }; |
| |
| static int xioopen_socks4_connect( |
| int argc, |
| const char *argv[], |
| struct opt *opts, |
| int xioflags, |
| xiofile_t *xxfd, |
| const struct addrdesc *addrdesc) |
| { |
| /* we expect the form: host:host:port */ |
| struct single *sfd = &xxfd->stream; |
| int socks4a = addrdesc->arg1; |
| struct opt *opts0 = NULL; |
| const char *sockdname; char *socksport; |
| const char *targetname, *targetport; |
| int pf = PF_UNSPEC; |
| int ipproto = IPPROTO_TCP; |
| bool dofork = false; |
| union sockaddr_union us_sa, *us = &us_sa; |
| socklen_t uslen = sizeof(us_sa); |
| struct addrinfo **themarr, *themp; |
| int i; |
| bool needbind = false; |
| bool lowport = false; |
| char infobuff[256]; |
| unsigned char buff[BUFF_LEN]; |
| struct socks4 *sockhead = (struct socks4 *)buff; |
| size_t buflen = sizeof(buff); |
| int socktype = SOCK_STREAM; |
| int level; |
| int result; |
| |
| if (argc != 4) { |
| xio_syntax(argv[0], 3, argc-1, addrdesc->syntax); |
| return STAT_NORETRY; |
| } |
| sockdname = argv[1]; |
| targetname = argv[2]; |
| targetport = argv[3]; |
| |
| if (sfd->howtoend == END_UNSPEC) |
| sfd->howtoend = END_SHUTDOWN; |
| if (applyopts_single(sfd, opts, PH_INIT) < 0) return -1; |
| applyopts(sfd, 1, opts, PH_INIT); |
| |
| retropt_int(opts, OPT_SO_TYPE, &socktype); |
| |
| retropt_bool(opts, OPT_FORK, &dofork); |
| |
| result = _xioopen_socks4_prepare(targetport, opts, &socksport, sockhead, &buflen); |
| if (result != STAT_OK) |
| return result; |
| |
| Notice5("opening connection to %s:%u via socks4 server %s:%s as user \"%s\"", |
| targetname, |
| ntohs(sockhead->port), |
| sockdname, socksport, sockhead->userid); |
| |
| i = 0; |
| do { /* loop over retries (failed connect and socks-request attempts) */ |
| |
| level = E_INFO; |
| |
| result = |
| _xioopen_ipapp_prepare(opts, &opts0, sockdname, socksport, |
| &pf, ipproto, |
| sfd->para.socket.ip.ai_flags, |
| &themarr, us, &uslen, |
| &needbind, &lowport, socktype); |
| |
| /* we try to resolve the target address _before_ connecting to the socks |
| server: this avoids unnecessary socks connects and timeouts */ |
| result = |
| _xioopen_socks4_connect0(sfd, targetname, socks4a, sockhead, |
| (ssize_t *)&buflen, level); |
| switch (result) { |
| case STAT_OK: break; |
| #if WITH_RETRY |
| case STAT_RETRYLATER: |
| case STAT_RETRYNOW: |
| if (sfd->forever || sfd->retry--) { |
| if (result == STAT_RETRYLATER) |
| Nanosleep(&sfd->intervall, NULL); |
| continue; |
| } |
| #endif /* WITH_RETRY */ |
| default: |
| return result; |
| } |
| |
| /* loop over themarr */ |
| i = 0; |
| themp = themarr[i++]; |
| while (themp != NULL) { |
| Notice1("opening connection to %s", |
| sockaddr_info(themp->ai_addr, themp->ai_addrlen, |
| infobuff, sizeof(infobuff))); |
| #if WITH_RETRY |
| if (sfd->forever || sfd->retry || themarr[i] != NULL) { |
| level = E_INFO; |
| } else |
| #endif /* WITH_RETRY */ |
| level = E_ERROR; |
| |
| /* this cannot fork because we retrieved fork option above */ |
| result = |
| _xioopen_connect(sfd, |
| needbind?us:NULL, uslen, |
| themp->ai_addr, themp->ai_addrlen, |
| opts, pf?pf:themp->ai_family, socktype, IPPROTO_TCP, lowport, level); |
| if (result == STAT_OK) |
| break; |
| themp = themarr[i++]; |
| if (themp == NULL) |
| result = STAT_RETRYLATER; |
| } |
| switch (result) { |
| case STAT_OK: break; |
| #if WITH_RETRY |
| case STAT_RETRYLATER: |
| case STAT_RETRYNOW: |
| if (sfd->forever || sfd->retry) { |
| --sfd->retry; |
| if (result == STAT_RETRYLATER) |
| Nanosleep(&sfd->intervall, NULL); |
| continue; |
| } |
| #endif /* WITH_RETRY */ |
| default: |
| xiofreeaddrinfo(themarr); |
| return result; |
| } |
| xiofreeaddrinfo(themarr); |
| applyopts(sfd, -1, opts, PH_ALL); |
| |
| if ((result = _xio_openlate(sfd, opts)) < 0) |
| return result; |
| |
| result = _xioopen_socks4_connect(sfd, sockhead, buflen, level); |
| switch (result) { |
| case STAT_OK: break; |
| #if WITH_RETRY |
| case STAT_RETRYLATER: |
| case STAT_RETRYNOW: |
| if (sfd->forever || sfd->retry--) { |
| if (result == STAT_RETRYLATER) Nanosleep(&sfd->intervall, NULL); |
| continue; |
| } |
| #endif /* WITH_RETRY */ |
| default: |
| return result; |
| } |
| |
| if (dofork) { |
| xiosetchilddied(); /* set SIGCHLD handler */ |
| } |
| |
| #if WITH_RETRY |
| if (dofork) { |
| pid_t pid; |
| int level = E_ERROR; |
| if (sfd->forever || sfd->retry) { |
| level = E_WARN; /* most users won't expect a problem here, |
| so Notice is too weak */ |
| } |
| while ((pid = xio_fork(false, level, sfd->shutup)) < 0) { |
| if (sfd->forever || --sfd->retry) { |
| Nanosleep(&sfd->intervall, NULL); |
| continue; |
| } |
| return STAT_RETRYLATER; |
| } |
| |
| if (pid == 0) { /* child process */ |
| sfd->forever = false; |
| sfd->retry = 0; |
| break; |
| } |
| |
| /* parent process */ |
| Close(sfd->fd); |
| Nanosleep(&sfd->intervall, NULL); |
| dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL); |
| continue; |
| } else |
| #endif /* WITH_RETRY */ |
| { |
| break; |
| } |
| |
| } while (true); /* end of complete open loop - drop out on success */ |
| return 0; |
| } |
| |
| |
| int _xioopen_opt_socksport( |
| struct opt *opts, |
| char **socksport) |
| { |
| struct servent *se; |
| |
| if (retropt_string(opts, OPT_SOCKSPORT, socksport) < 0) { |
| if ((se = getservbyname("socks", "tcp")) != NULL) { |
| Debug1("\"socks/tcp\" resolves to %u", ntohs(se->s_port)); |
| if ((*socksport = Malloc(6)) == NULL) { |
| return STAT_NORETRY; |
| } |
| sprintf(*socksport, "%u", ntohs(se->s_port)); |
| } else { |
| Debug1("cannot resolve service \"socks/tcp\", using %s", SOCKSPORT); |
| if ((*socksport = strdup(SOCKSPORT)) == NULL) { |
| return STAT_NORETRY; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| |
| int _xioopen_socks4_prepare(const char *targetport, struct opt *opts, char **socksport, struct socks4 *sockhead, size_t *headlen) { |
| char *userid; |
| |
| /* generate socks header - points to final target */ |
| sockhead->version = 4; |
| sockhead->action = 1; |
| sockhead->port = parseport(targetport, IPPROTO_TCP); /* network byte |
| order */ |
| if (_xioopen_opt_socksport(opts, socksport) < 0) { |
| return STAT_NORETRY; |
| } |
| |
| if (retropt_string(opts, OPT_SOCKSUSER, &userid) < 0) { |
| if ((userid = getenv("LOGNAME")) == NULL) { |
| if ((userid = getenv("USER")) == NULL) { |
| userid = "anonymous"; |
| } |
| } |
| } |
| sockhead->userid[0] = '\0'; strncat(sockhead->userid, userid, *headlen-SIZEOF_STRUCT_SOCKS4-1); |
| *headlen = SIZEOF_STRUCT_SOCKS4+strlen(userid)+1; |
| return STAT_OK; |
| } |
| |
| |
| /* called within retry/fork loop, before connect() */ |
| int |
| _xioopen_socks4_connect0(struct single *sfd, |
| const char *hostname, /* socks target host */ |
| int socks4a, |
| struct socks4 *sockhead, |
| ssize_t *headlen, /* get available space, |
| return used length*/ |
| int level) { |
| int result; |
| |
| if (!socks4a) { |
| union sockaddr_union sau; |
| socklen_t saulen = sizeof(sau); |
| |
| if ((result = xioresolve(hostname, NULL, |
| PF_INET, SOCK_STREAM, IPPROTO_TCP, |
| &sau, &saulen, |
| sfd->para.socket.ip.ai_flags)) |
| != STAT_OK) { |
| return result; /*! STAT_RETRY? */ |
| } |
| memcpy(&sockhead->dest, &sau.ip4.sin_addr, 4); |
| } |
| #if WITH_SOCKS4A |
| else { |
| /*! noresolve */ |
| sockhead->dest = htonl(0x00000001); /* three bytes zero */ |
| } |
| #endif /* WITH_SOCKS4A */ |
| #if WITH_SOCKS4A |
| if (socks4a) { |
| /* SOCKS4A requires us to append the host name to resolve |
| after the user name's trailing 0 byte. */ |
| char* insert_position = (char*) sockhead + *headlen; |
| |
| insert_position[0] = '\0'; strncat(insert_position, hostname, BUFF_LEN-*headlen-1); |
| ((char *)sockhead)[BUFF_LEN-1] = 0; |
| *headlen += strlen(hostname) + 1; |
| if (*headlen > BUFF_LEN) { |
| *headlen = BUFF_LEN; |
| } |
| } |
| #endif /* WITH_SOCKS4A */ |
| return STAT_OK; |
| } |
| |
| |
| /* perform socks4 client dialog on existing FD. |
| Called within fork/retry loop, after connect() */ |
| int _xioopen_socks4_connect(struct single *sfd, |
| struct socks4 *sockhead, |
| size_t headlen, |
| int level) { |
| ssize_t bytes; |
| int result; |
| unsigned char buff[SIZEOF_STRUCT_SOCKS4]; |
| struct socks4 *replyhead = (struct socks4 *)buff; |
| char *destdomname = NULL; |
| |
| /* send socks header (target addr+port, +auth) */ |
| #if WITH_MSGLEVEL <= E_INFO |
| if (ntohl(sockhead->dest) <= 0x000000ff) { |
| destdomname = strchr(sockhead->userid, '\0')+1; |
| } |
| Info11("sending socks4%s request VN=%d DC=%d DSTPORT=%d DSTIP=%d.%d.%d.%d USERID=%s%s%s", |
| destdomname?"a":"", |
| sockhead->version, sockhead->action, ntohs(sockhead->port), |
| ((unsigned char *)&sockhead->dest)[0], |
| ((unsigned char *)&sockhead->dest)[1], |
| ((unsigned char *)&sockhead->dest)[2], |
| ((unsigned char *)&sockhead->dest)[3], |
| sockhead->userid, |
| destdomname?" DESTNAME=":"", |
| destdomname?destdomname:""); |
| #endif /* WITH_MSGLEVEL <= E_INFO */ |
| #if WITH_MSGLEVEL <= E_DEBUG |
| { |
| char *msgbuff; |
| if ((msgbuff = Malloc(3*headlen)) != NULL) { |
| xiohexdump((const unsigned char *)sockhead, headlen, msgbuff); |
| Debug1("sending socks4(a) request data %s", msgbuff); |
| } |
| } |
| #endif /* WITH_MSGLEVEL <= E_DEBUG */ |
| if (writefull(sfd->fd, sockhead, headlen) < 0) { |
| Msg4(level, "write(%d, %p, "F_Zu"): %s", |
| sfd->fd, sockhead, headlen, strerror(errno)); |
| if (Close(sfd->fd) < 0) { |
| Info2("close(%d): %s", sfd->fd, strerror(errno)); |
| } |
| return STAT_RETRYLATER; /* retry complete open cycle */ |
| } |
| |
| bytes = 0; |
| Info("waiting for socks reply"); |
| while (bytes >= 0) { /* loop over answer chunks until complete or error */ |
| /* receive socks answer */ |
| do { |
| result = Read(sfd->fd, buff+bytes, SIZEOF_STRUCT_SOCKS4-bytes); |
| } while (result < 0 && errno == EINTR); |
| if (result < 0) { |
| Msg4(level, "read(%d, %p, "F_Zu"): %s", |
| sfd->fd, buff+bytes, SIZEOF_STRUCT_SOCKS4-bytes, |
| strerror(errno)); |
| if (Close(sfd->fd) < 0) { |
| Info2("close(%d): %s", sfd->fd, strerror(errno)); |
| } |
| } |
| if (result == 0) { |
| Msg(level, "read(): EOF during read of socks reply, peer might not be a socks4 server"); |
| if (Close(sfd->fd) < 0) { |
| Info2("close(%d): %s", sfd->fd, strerror(errno)); |
| } |
| return STAT_RETRYLATER; |
| } |
| #if WITH_MSGLEVEL <= E_DEBUG |
| { |
| char msgbuff[3*SIZEOF_STRUCT_SOCKS4]; |
| * xiohexdump((const unsigned char *)replyhead+bytes, result, msgbuff) |
| = '\0'; |
| Debug2("received socks4 reply data (offset "F_Zd"): %s", bytes, msgbuff); |
| } |
| #endif /* WITH_MSGLEVEL <= E_DEBUG */ |
| bytes += result; |
| if (bytes == SIZEOF_STRUCT_SOCKS4) { |
| Debug1("received all "F_Zd" bytes", bytes); |
| break; |
| } |
| Debug2("received %d bytes, waiting for "F_Zu" more bytes", |
| result, SIZEOF_STRUCT_SOCKS4-bytes); |
| } |
| if (result <= 0) { /* we had a problem while reading socks answer */ |
| return STAT_RETRYLATER; /* retry complete open cycle */ |
| } |
| |
| Info7("received socks reply VN=%u CD=%u DSTPORT=%u DSTIP=%u.%u.%u.%u", |
| replyhead->version, replyhead->action, ntohs(replyhead->port), |
| ((uint8_t *)&replyhead->dest)[0], |
| ((uint8_t *)&replyhead->dest)[1], |
| ((uint8_t *)&replyhead->dest)[2], |
| ((uint8_t *)&replyhead->dest)[3]); |
| if (replyhead->version != 0) { |
| Warn1("socks: reply code version is not 0 (%d)", |
| replyhead->version); |
| } |
| |
| switch (replyhead->action) { |
| case SOCKS_CD_GRANTED: |
| /* Notice("socks: connect request succeeded"); */ |
| #if 0 |
| if (Getsockname(sfd->fd, (struct sockaddr *)&us, &uslen) < 0) { |
| Warn4("getsockname(%d, %p, {%d}): %s", |
| sfd->fd, &us, uslen, strerror(errno)); |
| } |
| Notice1("successfully connected from %s via socks4", |
| sockaddr_info((struct sockaddr *)&us, infobuff, sizeof(infobuff))); |
| #else |
| Notice("successfully connected via socks4"); |
| #endif |
| break; |
| |
| case SOCKS_CD_FAILED: |
| Msg(level, "socks: connect request rejected or failed"); |
| return STAT_RETRYLATER; |
| |
| case SOCKS_CD_NOIDENT: |
| Msg(level, "socks: ident refused by client"); |
| return STAT_RETRYLATER; |
| |
| case SOCKS_CD_IDENTFAILED: |
| Msg(level, "socks: ident failed"); |
| return STAT_RETRYLATER; |
| |
| default: |
| Msg1(level, "socks: undefined status %u", replyhead->action); |
| } |
| |
| return STAT_OK; |
| } |
| #endif /* WITH_SOCKS4 || WITH_SOCKS4A */ |
| |