/* $Header: /CVSROOT/multiproxy/socks5.h,v 1.5 2008-10-20 23:44:33 tino Exp $ * * RFC1928 socks5 implementation * RFC1961 (GSSAPI) not yet implemented * * Yes, this is crude. And still too complex to understand. * * Copyright (C)2006-2007 Valentin Hilbig * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Log: socks5.h,v $ * Revision 1.5 2008-10-20 23:44:33 tino * tino/auxbuf bugfix release * * Revision 1.4 2007-05-08 16:18:52 tino * Internal overhaul to new routines, and see ChangeLog * * Revision 1.3 2006/10/21 01:56:37 tino * Checkin for save * * Revision 1.2 2006/01/26 12:38:48 tino * deleted some debugging code * * Revision 1.1 2006/01/24 07:32:24 tino * First version. It is working as expected. */ #undef MULTIPROXY_SOCKS5 #define MULTIPROXY_SOCKS5 || socks5() static int socks_ver; enum socks5_auth { SOCKS5_AUTH_ERR = 0xff, SOCKS5_AUTH_NONE= 0, SOCKS5_AUTH_GSSAPI, /*rfc1961*/ SOCKS5_AUTH_USER, /*rfc1929*/ }; /* RFC1928 +----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ Where: o VER protocol version: X'05' o REP Reply field: o X'00' succeeded o X'01' general SOCKS server failure o X'02' connection not allowed by ruleset o X'03' Network unreachable o X'04' Host unreachable o X'05' Connection refused o X'06' TTL expired o X'07' Command not supported o X'08' Address type not supported o X'09' to X'FF' unassigned o RSV RESERVED o ATYP address type of following address o IP V4 address: X'01' o DOMAINNAME: X'03' o IP V6 address: X'04' o BND.ADDR server bound address o BND.PORT server bound port in network octet order */ static void socks5_reply_fail(int code, const char *err, ...) { va_list list; outbuf[0] = socks_ver; outbuf[1] = code; outbuf[2] = 0; outbuf[3] = 1; outbuf[4] = 0; outbuf[5] = 0; outbuf[6] = 0; outbuf[7] = 0; outbuf[8] = 0; outbuf[9] = 0; commit(0, 10); linger(10); va_start(list, err); vend(err, list); /* never reached */ } /* Hunt for a method */ static int socks5_checkmethod(int meth) { int i; xDP(("check(%d)", meth)); for (i=0; iserver): +------+------+------+.......................+ + ver | mtyp | len | token | +------+------+------+.......................+ + 0x01 | 0x01 | 0x02 | up to 2^16 - 1 octets | +------+------+------+.......................+ For the case where a client successfully sends a token emitted by gss_init_sec_context() to the server, the server must pass the client-supplied token to gss_accept_sec_context as input_token. When calling gss_accept_sec_context() for the first time, the context_handle argument is initially set to GSS_C_NO_CONTEXT. For portability, verifier_cred_handle is set to GSS_C_NO_CREDENTIAL to specify default credentials (for acceptor usage). If gss_accept_sec_context returns GSS_CONTINUE_NEEDED, the server should return the generated output_token to the client, and subsequently pass the resulting client supplied token to another call to gss_accept_sec_context. If gss_accept_sec_context returns GSS_S_COMPLETE, then, if an output_token is returned, the server should return it to the client. If no token is returned, a zero length token should be sent by the server to signal to the client that it is ready to receive the client's request.' Failure (server->client): +------+------+ + ver | mtyp | +------+------+ + 0x01 | 0xff | +------+------+ */ static void socks5_auth_gssapi(void) { #ifdef MULTIPROXY_GSSAPI #error "not yet implemented" #else err("gssapi: not yet implemented"); #endif } /* RFC1929 +----+------+----------+------+----------+ |VER | ULEN | UNAME | PLEN | PASSWD | +----+------+----------+------+----------+ | 1 | 1 | 1 to 255 | 1 | 1 to 255 | +----+------+----------+------+----------+ The VER field contains the current version of the subnegotiation, which is X'01'. The ULEN field contains the length of the UNAME field that follows. The UNAME field contains the username as known to the source operating system. The PLEN field contains the length of the PASSWD field that follows. The PASSWD field contains the password association with the given UNAME. The server verifies the supplied UNAME and PASSWD, and sends the following response: +----+--------+ |VER | STATUS | +----+--------+ | 1 | 1 | +----+--------+ A STATUS field of X'00' indicates success. If the server returns a `failure' (STATUS value other than X'00') status, it MUST close the connection. */ static void socks5_auth_user(void) { unsigned char *pass; int status; int tot; fetch(3); if (inbuf[0]!=1) { /* XXX TODO * * How to send error to client? RFC does not tell this. */ err("wrong authentication version: %d", inbuf[0]); } fetch(3+inbuf[1]); tot = 3+inbuf[1]+inbuf[2+inbuf[1]]; fetch(tot); pass = inbuf+3+inbuf[1]; if (pass[-1]) { pass[pass[-1]] = 0; pass[-1] = 0; } else pass--; /* no password */ status = auth_user("socks", inbuf+2, pass); outbuf[0] = 1; outbuf[1] = status ? 1 : 0; commit(tot, 2); if (status) { linger(10); end("authorization failed"); } info("authorized as user %.*s", inbuf[1], inbuf+2); } /* RFC1928 The SOCKS request is formed as follows: +----+-----+-------+------+----------+----------+ |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ Where: o VER protocol version: X'05' o CMD o CONNECT X'01' o BIND X'02' o UDP ASSOCIATE X'03' o RSV RESERVED o ATYP address type of following address o IP V4 address: X'01' o DOMAINNAME: X'03' o IP V6 address: X'04' o DST.ADDR desired destination address o DST.PORT desired destination port in network octet order */ static int socks5_request(struct addrinfo **adrlist) { int cmd, len; const char *cmdname, *adrname; char portstr[10]; unsigned char adrbuf[50], *adr; struct addrinfo hints, *ap; unsigned port; fetch(4); if (inbuf[0]!=socks_ver) err("wrong socks version"); memset(&hints, 0, sizeof hints); switch (cmd=inbuf[1]) { default: socks5_reply_fail(7, "unsupported command %d", cmd); case 1: cmdname = "connect"; /* preset hints as old libc does not fill this correctly */ hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; break; #if 0 case 2: cmdname = "bind"; break; case 3: cmdname = "udp"; break; #endif } switch (inbuf[3]) { default: socks5_reply_fail(8, "unsupported addresstype %d", inbuf[3]); case 1: adrname = "v4"; len = 4; break; case 3: adrname = "domain"; fetch(5); len = inbuf[4]+1; break; #ifdef MULTIPROXY_IPv6 case 4: adrname = "v6"; len = 16; break; #endif } fetch(6+len); port = network_order_2(4+len); adr = adrbuf; switch (inbuf[3]) { default: internal("socks5_request"); case 1: sprintf((char *)adr, "%d.%d.%d.%d", inbuf[4], inbuf[5], inbuf[6], inbuf[7]); hints.ai_flags = AI_NUMERICHOST; break; case 3: adr = inbuf+5; adr[adr[-1]] = 0; break; #ifdef MULTIPROXY_IPv6 case 4: sprintf((char *)adr, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", inbuf[4], inbuf[5], inbuf[6], inbuf[7], inbuf[8], inbuf[9], inbuf[10], inbuf[11], inbuf[12], inbuf[13], inbuf[14], inbuf[15], inbuf[16], inbuf[17], inbuf[18], inbuf[19]); break; #endif } sprintf(portstr, "%u", port); if (auth("socks", cmdname, adrname, adr, portstr, NULL)) socks5_reply_fail(2, "command %s not permitted", cmdname); if (getaddrinfo((char *)adr, NULL, &hints, adrlist)) socks5_reply_fail(1, "getaddrinfo failed"); commit(6+len, 0); /* Patch in the port into the destination */ port = htons(port); for (ap= *adrlist; ap; ap=ap->ai_next) switch (ap->ai_family) { case PF_INET: ((struct sockaddr_in *)ap->ai_addr)->sin_port = port; break; #ifdef MULTIPROXY_IPv6 /* Don't do this if we pretend to not know about IPv6 */ case PF_INET6: ((struct sockaddr_in6 *)ap->ai_addr)->sin6_port = port; break; #endif } return cmd; } static void socks5_connect(struct addrinfo *adr) { #if 0 struct protoent *tcp; tcp = getprotobyname("tcp"); #endif for (; adr; adr=adr->ai_next) { #if 0 fprintf(stderr, "%s\n", dumpaddr(adr)); #endif /* buggy old libc does not fill this parameters */ if (!adr->ai_socktype && !adr->ai_protocol) { adr->ai_socktype = SOCK_STREAM; adr->ai_protocol = IPPROTO_TCP; } if (adr->ai_socktype==SOCK_STREAM) { int sock; sock = connecter(adr); if (sock==-1) continue; outbuf[0] = socks_ver; outbuf[1] = 0; outbuf[2] = 0; outbuf[3] = 1; outbuf[4] = 0; outbuf[5] = 0; outbuf[6] = 0; outbuf[7] = 0; outbuf[8] = 0; outbuf[9] = 0; commit(0, 10); copy_exit(sock); } } switch (errno) { case ECONNREFUSED: socks5_reply_fail(5, "no address left"); } /* better logging, else "command returned", which is confusing */ socks5_reply_fail(1, "cannot connect"); } static int socks5(void) { struct addrinfo *adr; socks_ver = 5; switch (socks5_method()) { case SOCKS5_AUTH_ERR: return 1; case SOCKS5_AUTH_NONE: break; case SOCKS5_AUTH_USER: socks5_auth_user(); break; case SOCKS5_AUTH_GSSAPI: socks5_auth_gssapi(); break; } switch (socks5_request(&adr)) { default: internal("socks5"); case 1: socks5_connect(adr); break; } socks5_reply_fail(1, "socks5: command returned"); return 1; }