From 21b7e3818dab6edad641111a3be12e61ac36f32a Mon Sep 17 00:00:00 2001 From: henri Date: Sun, 9 Feb 2014 14:10:04 +0000 Subject: [PATCH] [Ncat] Added support for socks5 and corresponding regression tests. [Marek Lucaszuk, Petr Stodulka] --- CHANGELOG | 3 + ncat/ncat.h | 48 ++++- ncat/ncat_connect.c | 460 ++++++++++++++++++++++++++++++++++------- ncat/ncat_core.c | 29 ++- ncat/ncat_core.h | 5 +- ncat/ncat_main.c | 102 +++++---- ncat/test/ncat-test.pl | 275 ++++++++++++++++++++++++ 7 files changed, 776 insertions(+), 146 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 941779989..0326b223f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [Ncat] Added support for socks5 and corresponding regression tests. + [Marek Lucaszuk, Petr Stodulka] + o [NSE] Add http-ntlm-info script for getting server information from Web servers that require NTLM authentication. [Justin Cacak] diff --git a/ncat/ncat.h b/ncat/ncat.h index eab90095e..66f046cad 100644 --- a/ncat/ncat.h +++ b/ncat/ncat.h @@ -154,6 +154,8 @@ #endif #endif +#define SOCKS_BUFF_SIZE 512 + /* structs */ #ifdef WIN32 @@ -163,8 +165,27 @@ struct socks4_data { char version; char type; unsigned short port; - unsigned long address; - char username[256]; + uint32_t address; + char data[SOCKS_BUFF_SIZE]; // this has to be able to hold FQDN and username +} __attribute__((packed)); + +struct socks5_connect { + char ver; + char nmethods; + char methods[3]; +} __attribute__((packed)); + +struct socks5_auth { + char ver; // must be always 1 + char data[SOCKS_BUFF_SIZE]; +} __attribute__((packed)); + +struct socks5_request { + char ver; + char cmd; + char rsv; + char atyp; + char dst[SOCKS_BUFF_SIZE]; // addr/name and port info } __attribute__((packed)); #ifdef WIN32 #pragma pack() @@ -192,6 +213,10 @@ struct socks4_data { /* Default port for SOCKS4 */ #define DEFAULT_SOCKS4_PORT 1080 +/* Default port for SOCKS5 */ +#define DEFAULT_SOCKS5_PORT 1080 + + /* The default port Ncat will connect to when trying to connect to an HTTP * proxy server. The current setting is the default for squid and probably * other HTTP proxies. But it may also be 8080, 8888, etc. @@ -217,10 +242,21 @@ struct socks4_data { #define SOCKS4_VERSION 4 #define SOCKS_CONNECT 1 #define SOCKS_BIND 2 -#define SOCKS_CONN_ACC 90 /* woot */ -#define SOCKS_CONN_REF 91 -#define SOCKS_CONN_IDENT 92 -#define SOCKS_CONN_IDENTDIFF 93 +#define SOCKS4_CONN_ACC 90 /* woot */ +#define SOCKS4_CONN_REF 91 +#define SOCKS4_CONN_IDENT 92 +#define SOCKS4_CONN_IDENTDIFF 93 + +/* SOCKS5 protocol */ +#define SOCKS5_VERSION 5 +#define SOCKS5_AUTH_NONE 0 +#define SOCKS5_AUTH_GSSAPI 1 +#define SOCKS5_AUTH_USERPASS 2 +#define SOCKS5_AUTH_FAILED 255 +#define SOCKS5_ATYP_IPv4 1 +#define SOCKS5_ATYP_NAME 3 +#define SOCKS5_ATYP_IPv6 4 + /* Length of IPv6 address */ #ifndef INET6_ADDRSTRLEN diff --git a/ncat/ncat_connect.c b/ncat/ncat_connect.c index 0289882c8..641ec165c 100644 --- a/ncat/ncat_connect.c +++ b/ncat/ncat_connect.c @@ -298,50 +298,48 @@ static void connect_report(nsock_iod nsi) } /* Just like inet_socktop, but it puts IPv6 addresses in square brackets. */ -static const char *sock_to_url(const union sockaddr_u *su) +static const char *sock_to_url(char *host_str, unsigned short port) { - static char buf[INET6_ADDRSTRLEN + 32]; - const char *host_str; - unsigned short port; + static char buf[512]; - host_str = inet_socktop(su); - port = inet_port(su); - if (su->storage.ss_family == AF_INET) - Snprintf(buf, sizeof(buf), "%s:%hu", host_str, port); - else if (su->storage.ss_family == AF_INET6) - Snprintf(buf, sizeof(buf), "[%s]:%hu", host_str, port); - else - bye("Unknown address family in sock_to_url_host."); + switch(getaddrfamily(host_str)) { + case -1: + case 1: + Snprintf(buf, sizeof(buf), "%s:%hu", host_str, port); + break; + case 2: + Snprintf(buf, sizeof(buf), "[%s]:%hu]", host_str, port); + } return buf; } static int append_connect_request_line(char **buf, size_t *size, size_t *offset, - const union sockaddr_u *su) + char* host_str,unsigned short port) { return strbuf_sprintf(buf, size, offset, "CONNECT %s HTTP/1.0\r\n", - sock_to_url(su)); + sock_to_url(host_str,port)); } -static char *http_connect_request(const union sockaddr_u *su, int *n) +static char *http_connect_request(char* host_str, unsigned short port, int *n) { char *buf = NULL; size_t size = 0, offset = 0; - append_connect_request_line(&buf, &size, &offset, su); + append_connect_request_line(&buf, &size, &offset, host_str, port); strbuf_append_str(&buf, &size, &offset, "\r\n"); *n = offset; return buf; } -static char *http_connect_request_auth(const union sockaddr_u *su, int *n, +static char *http_connect_request_auth(char* host_str, unsigned short port, int *n, struct http_challenge *challenge) { char *buf = NULL; size_t size = 0, offset = 0; - append_connect_request_line(&buf, &size, &offset, su); + append_connect_request_line(&buf, &size, &offset, host_str, port); strbuf_append_str(&buf, &size, &offset, "Proxy-Authorization:"); if (challenge->scheme == AUTH_BASIC) { char *auth_str; @@ -364,7 +362,7 @@ static char *http_connect_request_auth(const union sockaddr_u *su, int *n, return NULL; } response_hdr = http_digest_proxy_authorization(challenge, - username, password, "CONNECT", sock_to_url(&httpconnect)); + username, password, "CONNECT", sock_to_url(o.target,o.portno)); if (response_hdr == NULL) { free(proxy_auth); return NULL; @@ -405,7 +403,7 @@ static int do_proxy_http(void) header = NULL; /* First try a request with no authentication. */ - request = http_connect_request(&httpconnect, &n); + request = http_connect_request(o.target,o.portno, &n); if (send(sd, request, n, 0) < 0) { loguser("Error sending proxy request: %s.\n", socket_strerror(socket_errno())); free(request); @@ -455,7 +453,7 @@ static int do_proxy_http(void) goto bail; } - request = http_connect_request_auth(&httpconnect, &n, &challenge); + request = http_connect_request_auth(o.target,o.portno, &n, &challenge); if (request == NULL) { loguser("Error building Proxy-Authorization header.\n"); http_challenge_free(&challenge); @@ -511,6 +509,355 @@ bail: return -1; } + +/* SOCKS4a support + * Return a usable socket descriptor after + * proxy negotiation, or -1 on any error. + */ +static int do_proxy_socks4(void) +{ + struct socket_buffer stateful_buf; + struct socks4_data socks4msg; + char socksbuf[8]; + int sd,len = 9; + + sd = do_connect(SOCK_STREAM); + if (sd == -1) { + loguser("Proxy connection failed: %s.\n", socket_strerror(socket_errno())); + return sd; + } + socket_buffer_init(&stateful_buf, sd); + + if (o.verbose) { + loguser("Connected to proxy %s:%hu\n", inet_socktop(&targetss), + inet_port(&targetss)); + } + + /* Fill the socks4_data struct */ + zmem(&socks4msg, sizeof(socks4msg)); + socks4msg.version = SOCKS4_VERSION; + socks4msg.type = SOCKS_CONNECT; + socks4msg.port = htons(o.portno); + + switch(getaddrfamily(o.target)) { + case 1: // IPv4 address family + socks4msg.address = inet_addr(o.target); + + if (o.proxy_auth){ + memcpy(socks4msg.data, o.proxy_auth, strlen(o.proxy_auth)); + len += strlen(o.proxy_auth); + } + break; + + case 2: // IPv6 address family + + loguser("Error: IPv6 addresses are not supported with Socks4.\n"); + close(sd); + return -1; + + case -1: // fqdn + + socks4msg.address = inet_addr("0.0.0.1"); + + if (strlen(o.target) > SOCKS_BUFF_SIZE-2) { + loguser("Error: host name is too long.\n"); + close(sd); + return -1; + } + + if (o.proxy_auth){ + if (strlen(o.target)+strlen(o.proxy_auth) > SOCKS_BUFF_SIZE-2) { + loguser("Error: host name and username are too long.\n"); + close(sd); + return -1; + } + Strncpy(socks4msg.data,o.proxy_auth,sizeof(socks4msg.data)); + len += strlen(o.proxy_auth); + } + memcpy(socks4msg.data+(len-8), o.target, strlen(o.target)); + len += strlen(o.target)+1; + } + + if (send(sd, (char *) &socks4msg, len, 0) < 0) { + loguser("Error: sending proxy request: %s.\n", socket_strerror(socket_errno())); + close(sd); + return -1; + } + + /* The size of the socks4 response is 8 bytes. So read exactly + 8 bytes from the buffer */ + if (socket_buffer_readcount(&stateful_buf, socksbuf, 8) < 0) { + loguser("Error: short response from proxy.\n"); + close(sd); + return -1; + } + + if (sd != -1 && socksbuf[1] != SOCKS4_CONN_ACC) { + loguser("Proxy connection failed.\n"); + close(sd); + return -1; + } + + return sd; +} + +/* SOCKS5 support + * Return a usable socket descriptor after + * proxy negotiation, or -1 on any error. + */ +static int do_proxy_socks5(void) +{ + + struct socket_buffer stateful_buf; + struct socks5_connect socks5msg; + uint32_t inetaddr; + char inet6addr[16]; + unsigned short proxyport = htons(o.portno); + char socksbuf[8]; + int sd,len,lenfqdn; + + + sd = do_connect(SOCK_STREAM); + if (sd == -1) { + loguser("Proxy connection failed: %s.\n", socket_strerror(socket_errno())); + return sd; + } + + socket_buffer_init(&stateful_buf, sd); + + if (o.verbose) { + loguser("Connected to proxy %s:%hu\n", inet_socktop(&targetss), + inet_port(&targetss)); + } + + zmem(&socks5msg,sizeof(socks5msg)); + socks5msg.ver = SOCKS5_VERSION; + socks5msg.nmethods = 1; + socks5msg.methods[0] = SOCKS5_AUTH_NONE; + len = 3; + + if (o.proxy_auth){ + socks5msg.nmethods ++; + socks5msg.methods[1] = SOCKS5_AUTH_USERPASS; + len ++; + } + + if (send(sd, (char *) &socks5msg, len, 0) < 0) { + loguser("Error: proxy request: %s.\n", socket_strerror(socket_errno())); + close(sd); + return -1; + } + + /* first response just two bytes, version and auth method */ + if (socket_buffer_readcount(&stateful_buf, socksbuf, 2) < 0) { + loguser("Error: malformed first response from proxy.\n"); + close(sd); + return -1; + } + + if (socksbuf[0] != 5){ + loguser("Error: got wrong server version in response.\n"); + close(sd); + return -1; + } + + switch(socksbuf[1]) { + case SOCKS5_AUTH_NONE: + if (o.verbose) + loguser("No authentication needed.\n"); + break; + + case SOCKS5_AUTH_GSSAPI: + loguser("GSSAPI authentication method not supported.\n"); + close(sd); + return -1; + + case SOCKS5_AUTH_USERPASS: + if (o.verbose) + loguser("Doing username and password authentication.\n"); + + if(!o.proxy_auth){ + loguser("Error: proxy requested to do authentication, but no credentials were provided.\n"); + close(sd); + return -1; + } + + if (strlen(o.proxy_auth) > SOCKS_BUFF_SIZE-2){ + loguser("Error: username and password are too long to fit into buffer.\n"); + close(sd); + return -1; + } + + char *proxy_auth; + char *username, *password; + + /* Split up the proxy auth argument. */ + proxy_auth = Strdup(o.proxy_auth); + username = strtok(proxy_auth, ":"); + password = strtok(NULL, ":"); + if (password == NULL || username == NULL) { + free(proxy_auth); + loguser("Error: empty username or password.\n"); + close(sd); + return -1; + } + + /* + * For username/password authentication the client's authentication request is + * field 1: version number, 1 byte (must be 0x01 -- version of subnegotion) + * field 2: username length, 1 byte + * field 3: username + * field 4: password length, 1 byte + * field 5: password + * + * Server response for username/password authentication: + * field 1: version, 1 byte + * field 2: status code, 1 byte. + * 0x00 = success + * any other value = failure, connection must be closed + */ + struct socks5_auth socks5auth; + + socks5auth.ver = 1; + socks5auth.data[0] = strlen(username); + memcpy(socks5auth.data+1,username,strlen(username)); + len = 2 + strlen(username); // (version + strlen) + username + + socks5auth.data[len]=strlen(password); + memcpy(socks5auth.data+len,password,strlen(password)); + len += 1 + strlen(password); + + if (send(sd, (char *) &socks5auth, len, 0) < 0) { + loguser("Error: sending proxy authentication.\n"); + close(sd); + return -1; + } + + if (socket_buffer_readcount(&stateful_buf, socksbuf, 2) < 0) { + loguser("Error: malformed proxy authentication response.\n"); + close(sd); + return -1; + } + + if (socksbuf[0] != 1 || socksbuf[1] != 0) { + loguser("Error: authentication failed.\n"); + close(sd); + return -1; + } + + break; + + default: + loguser("Error - can't choose any authentication method.\n"); + close(sd); + return -1; + } + + struct socks5_request socks5msg2; + + zmem(&socks5msg2,sizeof(socks5msg2)); + socks5msg2.ver = SOCKS5_VERSION; + socks5msg2.cmd = SOCKS_CONNECT; + socks5msg2.rsv = 0; + + switch(getaddrfamily(o.target)) { + + case 1: // IPv4 address family + socks5msg2.atyp = SOCKS5_ATYP_IPv4; + inetaddr = inet_addr(o.target); + memcpy(socks5msg2.dst, &inetaddr, 4); + len = 4; + break; + + case 2: // IPv6 address family + socks5msg2.atyp = SOCKS5_ATYP_IPv6; + inet_pton(AF_INET6,o.target,&inet6addr); + memcpy(socks5msg2.dst, inet6addr,16); + len = 16; + break; + + case -1: // FQDN + socks5msg2.atyp = SOCKS5_ATYP_NAME; + lenfqdn=strlen(o.target); + if (lenfqdn > SOCKS_BUFF_SIZE-5){ + loguser("Error: host name too long.\n"); + close(sd); + return -1; + } + socks5msg2.dst[0]=lenfqdn; + memcpy(socks5msg2.dst+1,o.target,lenfqdn); + len = 1 + lenfqdn; + } + + memcpy(socks5msg2.dst+len, &proxyport, sizeof(proxyport)); + len += 2 + 1 + 3; + + if (len > sizeof(socks5msg2)){ + loguser("Error: address information too large.\n"); + close(sd); + return -1; + } + + if (send(sd, (char *) &socks5msg2, len, 0) < 0) { + loguser("Error: sending proxy request: %s.\n", socket_strerror(socket_errno())); + close(sd); + return -1; + } + + /* TODO just two bytes for now, need to read more for bind */ + if (socket_buffer_readcount(&stateful_buf, socksbuf, 2) < 0) { + loguser("Error: malformed second response from proxy.\n"); + close(sd); + return -1; + } + + switch(socksbuf[1]) { + case 0: + if (o.verbose) + loguser("connection succeeded.\n"); + break; + case 1: + loguser("Error: general SOCKS server failure.\n"); + close(sd); + return -1; + case 2: + loguser("Error: connection not allowed by ruleset.\n"); + close(sd); + return -1; + case 3: + loguser("Error: Network unreachable.\n"); + close(sd); + return -1; + case 4: + loguser("Error: Host unreachable.\n"); + close(sd); + return -1; + case 5: + loguser("Error: Connection refused.\n"); + close(sd); + return -1; + case 6: + loguser("Error: TTL expired.\n"); + close(sd); + return -1; + case 7: + loguser("Error: Command not supported.\n"); + close(sd); + return -1; + case 8: + loguser("Error: Address type not supported.\n"); + close(sd); + return -1; + default: + loguser("Error: unassigned value in the reply.\n"); + close(sd); + return -1; + } + + return(sd); +} + + int ncat_connect(void) { nsock_pool mypool; @@ -543,8 +890,7 @@ int ncat_connect(void) set_ssl_ctx_options((SSL_CTX *) nsp_ssl_init(mypool)); #endif - if (httpconnect.storage.ss_family == AF_UNSPEC - && socksconnect.storage.ss_family == AF_UNSPEC) { + if (!o.proxytype) { /* A non-proxy connection. Create an iod for a new socket. */ cs.sock_nsi = nsi_new(mypool, NULL); if (cs.sock_nsi == NULL) @@ -640,65 +986,23 @@ int ncat_connect(void) } else { /* A proxy connection. */ static int connect_socket; - int len; - char *line; - size_t n; - if (httpconnect.storage.ss_family != AF_UNSPEC) { + if (strcmp(o.proxytype, "http") == 0) { connect_socket = do_proxy_http(); - if (connect_socket == -1) - return 1; - } else if (socksconnect.storage.ss_family != AF_UNSPEC) { - struct socket_buffer stateful_buf; - struct socks4_data socks4msg; - char socksbuf[8]; - - connect_socket = do_connect(SOCK_STREAM); - if (connect_socket == -1) { - loguser("Proxy connection failed: %s.\n", socket_strerror(socket_errno())); - return 1; - } - - socket_buffer_init(&stateful_buf, connect_socket); - - if (o.verbose) { - loguser("Connected to proxy %s:%hu\n", inet_socktop(&targetss), - inet_port(&targetss)); - } - - /* Fill the socks4_data struct */ - zmem(&socks4msg, sizeof(socks4msg)); - socks4msg.version = SOCKS4_VERSION; - socks4msg.type = SOCKS_CONNECT; - socks4msg.port = socksconnect.in.sin_port; - socks4msg.address = socksconnect.in.sin_addr.s_addr; - if (o.proxy_auth) - Strncpy(socks4msg.username, (char *) o.proxy_auth, sizeof(socks4msg.username)); - - len = 8 + strlen(socks4msg.username) + 1; - - if (send(connect_socket, (char *) &socks4msg, len, 0) < 0) { - loguser("Error sending proxy request: %s.\n", socket_strerror(socket_errno())); - return 1; - } - /* The size of the socks4 response is 8 bytes. So read exactly - 8 bytes from the buffer */ - if (socket_buffer_readcount(&stateful_buf, socksbuf, 8) < 0) { - loguser("Error: short reponse from proxy.\n"); - return 1; - } - if (socksbuf[1] != 90) { - loguser("Proxy connection failed.\n"); - return 1; - } - - /* Clear out whatever is left in the socket buffer which may be - already sent by proxy server along with http response headers. */ - line = socket_buffer_remainder(&stateful_buf, &n); - /* Write the leftover data to stdout. */ - Write(STDOUT_FILENO, line, n); + } else if (strcmp(o.proxytype, "socks4") == 0) { + connect_socket = do_proxy_socks4(); + } else if (strcmp(o.proxytype, "socks5") == 0) { + connect_socket = do_proxy_socks5(); } + if (connect_socket == -1) + return 1; + /* Clear out whatever is left in the socket buffer which may be + already sent by proxy server along with http response headers. */ + //line = socket_buffer_remainder(&stateful_buf, &n); + /* Write the leftover data to stdout. */ + //Write(STDOUT_FILENO, line, n); + /* Once the proxy negotiation is done, Nsock takes control of the socket. */ cs.sock_nsi = nsi_new2(mypool, connect_socket, NULL); diff --git a/ncat/ncat_core.c b/ncat/ncat_core.c index e2b71686b..327c010e5 100644 --- a/ncat/ncat_core.c +++ b/ncat/ncat_core.c @@ -150,9 +150,6 @@ size_t srcaddrlen; union sockaddr_u targetss; size_t targetsslen; -union sockaddr_u httpconnect, socksconnect; -size_t httpconnectlen, socksconnectlen; - /* Global options structure. */ struct options o; @@ -536,6 +533,32 @@ static int ncat_hexdump(int logfd, const char *data, int len) return 1; } +/* this function will return in what format the target + * host is specified. It will return: + * 1 - for ipv4, + * 2 - for ipv6, + * -1 - for hostname + * this has to work even if there is no IPv6 support on + * local system, proxy may support it. + */ +int getaddrfamily(const char *addr) +{ + int ret; + + if (strchr(addr,':')) + return 2; + + struct addrinfo hint, *info =0; + zmem(&hint,sizeof(hint)); + hint.ai_family = AF_UNSPEC; + hint.ai_flags = AI_NUMERICHOST; + ret = getaddrinfo(addr, 0, &hint, &info); + if (ret) + return -1; + freeaddrinfo(info); + return 1; +} + void setup_environment(struct fdinfo *info) { union sockaddr_u su; diff --git a/ncat/ncat_core.h b/ncat/ncat_core.h index b19e43652..91dbf15ed 100644 --- a/ncat/ncat_core.h +++ b/ncat/ncat_core.h @@ -139,9 +139,6 @@ extern size_t srcaddrlen; extern union sockaddr_u targetss; extern size_t targetsslen; -extern union sockaddr_u httpconnect, socksconnect; -extern size_t httpconnectlen, socksconnectlen; - enum exec_mode { EXEC_PLAIN, EXEC_SHELL, @@ -197,6 +194,7 @@ struct options { enum exec_mode execmode; char *proxy_auth; char *proxytype; + char *proxyaddr; int ssl; char *sslcert; @@ -265,5 +263,6 @@ extern int ncat_hostaccess(char *matchaddr, char *filename, char *remoteip); Defined in ncat_posix.c and ncat_win.c. */ extern void set_lf_mode(void); +extern int getaddrfamily(const char *addr); extern int setenv_portable(const char *name, const char *value); extern void setup_environment(struct fdinfo *fdinfo); diff --git a/ncat/ncat_main.c b/ncat/ncat_main.c index 3562d8560..d9c6e88e0 100644 --- a/ncat/ncat_main.c +++ b/ncat/ncat_main.c @@ -154,12 +154,10 @@ static int ncat_connect_mode(void); static int ncat_listen_mode(void); /* Determines if it's parsing HTTP or SOCKS by looking at defport */ -static size_t parseproxy(char *str, struct sockaddr_storage *ss, unsigned short defport) +static size_t parseproxy(char *str, struct sockaddr_storage *ss, + size_t *sslen, unsigned short *portno) { char *c = strrchr(str, ':'), *ptr; - int httpproxy = (defport == DEFAULT_PROXY_PORT); - unsigned short portno; - size_t sslen; int rc; ptr = str; @@ -168,19 +166,17 @@ static size_t parseproxy(char *str, struct sockaddr_storage *ss, unsigned short *c = 0; if (c && strlen((c + 1))) - portno = (unsigned short) atoi(c + 1); - else - portno = defport; + *portno = (unsigned short) atoi(c + 1); - rc = resolve(ptr, portno, ss, &sslen, o.af); + rc = resolve(ptr, *portno, ss, sslen, o.af); if (rc != 0) { loguser("Could not resolve proxy \"%s\": %s.\n", ptr, gai_strerror(rc)); - if (o.af == AF_INET6 && httpproxy) + if (o.af == AF_INET6 && *portno) loguser("Did you specify the port number? It's required for IPv6.\n"); exit(EXIT_FAILURE); } - return sslen; + return *sslen; } /* These functions implement a simple linked list to hold allow/deny @@ -259,9 +255,9 @@ int main(int argc, char *argv[]) struct host_list_node *allow_host_list = NULL; struct host_list_node *deny_host_list = NULL; + unsigned short proxyport = DEFAULT_PROXY_PORT; int srcport = -1; char *source = NULL; - char *proxyaddr = NULL; struct option long_options[] = { {"4", no_argument, NULL, '4'}, @@ -457,17 +453,17 @@ int main(int argc, char *argv[]) print_banner(); exit(EXIT_SUCCESS); } else if (strcmp(long_options[option_index].name, "proxy") == 0) { - if (proxyaddr) + if (o.proxyaddr) bye("You can't specify more than one --proxy."); - proxyaddr = Strdup(optarg); + o.proxyaddr = optarg; } else if (strcmp(long_options[option_index].name, "proxy-type") == 0) { if (o.proxytype) bye("You can't specify more than one --proxy-type."); - o.proxytype = Strdup(optarg); + o.proxytype = optarg; } else if (strcmp(long_options[option_index].name, "proxy-auth") == 0) { if (o.proxy_auth) bye("You can't specify more than one --proxy-auth."); - o.proxy_auth = Strdup(optarg); + o.proxy_auth = optarg; } else if (strcmp(long_options[option_index].name, "nsock-engine") == 0) { if (nsock_set_default_engine(optarg) < 0) bye("Unknown or non-available engine: %s.", optarg); @@ -595,8 +591,9 @@ int main(int argc, char *argv[]) " --broker Enable Ncat's connection brokering mode\n" " --chat Start a simple Ncat chat server\n" " --proxy Specify address of host to proxy through\n" -" --proxy-type Specify proxy type (\"http\" or \"socks4\")\n" +" --proxy-type Specify proxy type (\"http\" or \"socks4\" or \"socks5\")\n" " --proxy-auth Authenticate with HTTP or SOCKS proxy server\n" + #ifdef HAVE_OPENSSL " --ssl Connect or listen with SSL\n" " --ssl-cert Specify SSL certificate file (PEM) for listening\n" @@ -634,7 +631,7 @@ int main(int argc, char *argv[]) #if HAVE_SYS_UN_H /* Using Unix domain sockets, so do the checks now */ if (o.af == AF_UNIX) { - if (proxyaddr || o.proxytype) + if (o.proxyaddr || o.proxytype) bye("Proxy option not supported when using Unix domain sockets."); #ifdef HAVE_OPENSSL if (o.ssl) @@ -652,7 +649,7 @@ int main(int argc, char *argv[]) /* Will be AF_INET or AF_INET6 or AF_UNIX when valid */ memset(&targetss.storage, 0, sizeof(targetss.storage)); targetss.storage.ss_family = AF_UNSPEC; - httpconnect.storage = socksconnect.storage = srcaddr.storage = targetss.storage; + srcaddr.storage = targetss.storage; /* Clear the listenaddrs array */ int i; @@ -660,28 +657,32 @@ int main(int argc, char *argv[]) listenaddrs[i].storage = targetss.storage; } - if (proxyaddr) { + if (o.proxyaddr) { if (!o.proxytype) o.proxytype = Strdup("http"); - if (!strcmp(o.proxytype, "http")) { - /* Parse HTTP proxy address and temporarily store it in httpconnect. If - * the proxy server is given as an IPv6 address (not hostname), the port - * number MUST be specified as well or parsing will break (due to the - * colons in the IPv6 address and host:port separator). + if (!strcmp(o.proxytype, "http") || + !strcmp(o.proxytype, "socks4") || !strcmp(o.proxytype, "4") || + !strcmp(o.proxytype, "socks5") || !strcmp(o.proxytype, "5")) { + /* Parse HTTP/SOCKS proxy address and store it in targetss. + * If the proxy server is given as an IPv6 address (not hostname), + * the port number MUST be specified as well or parsing will break + * (due to the colons in the IPv6 address and host:port separator). */ - httpconnectlen = parseproxy(proxyaddr, &httpconnect.storage, DEFAULT_PROXY_PORT); - } else if (!strcmp(o.proxytype, "socks4") || !strcmp(o.proxytype, "4")) { - /* Parse SOCKS proxy address and temporarily store it in socksconnect */ - - socksconnectlen = parseproxy(proxyaddr, &socksconnect.storage, DEFAULT_SOCKS4_PORT); + targetsslen = parseproxy(o.proxyaddr, + &targetss.storage, &targetsslen, &proxyport); + if (o.af == AF_INET) { + targetss.in.sin_port = htons(proxyport); + } else { // might modify to else if and test AF_{INET6|UNIX|UNSPEC} + targetss.in6.sin6_port = htons(proxyport); + } } else { bye("Invalid proxy type \"%s\".", o.proxytype); } - free(o.proxytype); - free(proxyaddr); + if (o.listen) + bye("Invalid option combination: --proxy and -l."); } else { if (o.proxytype) { if (!o.listen) @@ -692,7 +693,10 @@ int main(int argc, char *argv[]) } /* Default port */ - o.portno = DEFAULT_NCAT_PORT; + if (o.listen && o.proxytype && !o.portno && srcport == -1) + o.portno = DEFAULT_PROXY_PORT; + else + o.portno = DEFAULT_NCAT_PORT; /* Resolve the given source address */ if (source) { @@ -753,9 +757,12 @@ int main(int argc, char *argv[]) int rc; o.target = argv[optind]; - /* resolve hostname */ - rc = resolve(o.target, 0, &targetss.storage, &targetsslen, o.af); - if (rc != 0) + /* resolve hostname only if o.proxytype == NULL + * targetss contains data already and you don't want remove them + */ + if( !o.proxytype + && (rc = resolve(o.target, 0, &targetss.storage, &targetsslen, o.af)) != 0) + bye("Could not resolve hostname \"%s\": %s.", o.target, gai_strerror(rc)); optind++; } else { @@ -790,7 +797,9 @@ int main(int argc, char *argv[]) o.portno = (unsigned short) long_port; } - if (targetss.storage.ss_family == AF_INET) + if (o.proxytype && !o.listen) + ; /* Do nothing - port is already set to proxyport */ + else if (targetss.storage.ss_family == AF_INET) targetss.in.sin_port = htons(o.portno); #ifdef HAVE_IPV6 else if (targetss.storage.ss_family == AF_INET6) @@ -831,25 +840,6 @@ int main(int argc, char *argv[]) } } - /* Since the host we're actually *connecting* to is the proxy server, we - * need to reverse these address structures to avoid any further confusion - */ - if (httpconnect.storage.ss_family != AF_UNSPEC) { - union sockaddr_u tmp = targetss; - size_t tmplen = targetsslen; - targetss = httpconnect; - targetsslen = httpconnectlen; - httpconnect = tmp; - httpconnectlen = tmplen; - } else if (socksconnect.storage.ss_family != AF_UNSPEC) { - union sockaddr_u tmp = targetss; - size_t tmplen = targetsslen; - targetss = socksconnect; - targetsslen = socksconnectlen; - socksconnect = tmp; - socksconnectlen = tmplen; - } - if (o.proto == IPPROTO_UDP) { /* Don't allow a false sense of security if someone tries SSL over UDP. */ if (o.ssl) @@ -904,7 +894,7 @@ static int ncat_connect_mode(void) static int ncat_listen_mode(void) { /* Can't 'listen' AND 'connect' to a proxy server at the same time. */ - if (httpconnect.storage.ss_family != AF_UNSPEC || socksconnect.storage.ss_family != AF_UNSPEC) + if (o.proxyaddr != NULL) bye("Invalid option combination: --proxy and -l."); if (o.broker && o.cmdexec != NULL) diff --git a/ncat/test/ncat-test.pl b/ncat/test/ncat-test.pl index 2f982bcb8..bc3e21d8b 100755 --- a/ncat/test/ncat-test.pl +++ b/ncat/test/ncat-test.pl @@ -10,8 +10,10 @@ use File::Temp qw/ tempfile /; use URI::Escape; use Data::Dumper; use Socket; +use Socket6; use Digest::MD5 qw/md5_hex/; use POSIX ":sys_wait_h"; +use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK); use IPC::Open3; use strict; @@ -2767,6 +2769,279 @@ sub { }; kill_children; +# expand IPv6 +sub ipv6_expand { + local($_) = shift; + s/^:/0:/; + s/:$/:0/; + s/(^|:)([^:]{1,3})(?=:|$)/$1.substr("0000$2", -4)/ge; + my $c = tr/:/:/; + s/::/":".("0000:" x (8-$c))/e; + return $_; +} +sub socks5_auth { + my ($pid,$code); + my $buf=""; + my @Barray; + my $auth_data = shift; + my $ipvx = shift; + my $dest_addr = shift; + my $passed = 0; + + my $username= ""; + my $passwd= ""; + my $recv_addr = ""; + my $recv_port; + + my ($pf,$s_addr); + + local $SIG{CHLD} = sub { }; + local *SOCK; + local *S; + + if ($ipvx eq -4) { + $pf = PF_INET; + $s_addr = sockaddr_in($PROXY_PORT, INADDR_ANY); + } else { + $pf = PF_INET6; + $s_addr = sockaddr_in6($PROXY_PORT, inet_pton(PF_INET6, "::1")); + } + + + socket(SOCK, $pf, SOCK_STREAM, getprotobyname("tcp")) or die; + setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die; + bind(SOCK, $s_addr) or die; + listen(SOCK, 1) or die; + + my ($c_pid, $c_out, $c_in) = ncat("--proxy-type", "socks5", "--proxy", "localhost:$PROXY_PORT", @$auth_data, $ipvx, $dest_addr, $PORT); + + accept(S, SOCK) or die "Client not connected"; + binmode(S); + sysread(S, $buf, 10) or die "Connection closed"; + + @Barray = map hex($_), unpack("H*", $buf) =~ /(..)/g; + die "wrong request format" if scalar(@Barray) < 3; + die "wrong protocol version" if $Barray[0] != 5; + + if(scalar(@$auth_data) > 0) { + # subnegotiation for authentication + for(my $i=2; $i < scalar(@Barray); $i++) { + if($Barray[$i] == 2) { + $passed = 1; + } + } + + die "Client did not sent required authentication method x02" if $passed == 0; + + + send(S, "\x05\x02",0) or die "Send: Connection closed"; + sysread(S, $buf, $BUFSIZ) or die "Read: Connection closed"; + + @Barray = map hex($_), unpack("H*", $buf) =~ /(..)/g; + die "wrong request format - small length" if scalar(@Barray) < 5; + die "wrong request format - wrong version" if $Barray[0] != 1; + die "wrong request format - username legth longer then packet size" + if $Barray[1] >= scalar(@Barray); + + # get username + for (my $i=2; $i < $Barray[1]+2; $i++) { + $username .= chr($Barray[$i]); + } + + #get password + for (my $i=3+$Barray[1]; $i < scalar(@Barray); $i++) { + $passwd .= chr($Barray[$i]); + } + + if ($username ne "vasek" or $passwd ne "admin") { + send(S, "\x01\x11", 0); + # do not close connection - we can check if client try continue + } else { + send(S, "\x01\x00",0); + } + } else { + # no authentication + send(S, "\x05\x00",0) or die "Send: Connection closed"; + + } + + sysread(S, $buf, $BUFSIZ) or die "Read: connection closed"; + + @Barray = map hex($_), unpack("H*", $buf) =~ /(..)/g; + die "wrong request length format" if scalar(@Barray) < 10; + die "wrong protocol version after success authentication" if $Barray[0] != 5; + die "expected connect cmd" if $Barray[1] != 1; + + if($Barray[3] == 1) { + # IPv4 + + $recv_addr = $Barray[4] .".". $Barray[5] .".". $Barray[6] .".". $Barray[7]; + die "received wrong destination IPv4" if $recv_addr ne $dest_addr; + } elsif ($Barray[3] == 4) { + #IPv6 + + for(my $i=4; $i<20;$i++) { + if($i > 4 and $i % 2 == 0) { + $recv_addr .= ":"; + } + $recv_addr .= sprintf("%02X",$Barray[$i]); + } + + die "received wrong destination IPv6" if $recv_addr ne ipv6_expand($dest_addr); + } elsif ($Barray[3] == 3) { + # domaint name + + for my $i (@Barray[5..(scalar(@Barray)-3)]) { + $recv_addr .= chr($i); + } + die "received wrong destination domain name" if $recv_addr ne $dest_addr; + die "received wrong length of domain name" if length($recv_addr) != $Barray[4]; + } else { + die "unknown ATYP: $Barray[3]"; + } + + $recv_port = $Barray[-2]*256 + $Barray[-1]; + die "received wrong destination port" if $recv_port ne $PORT; + + send(S, "\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00", 0); + + # check if connection is still open + syswrite($c_in, "abc\n"); + sysread(S, $buf, 10) or die "Connection closed"; + + + close(S); + close(SOCK); +}; + + +test "SOCKS5 client, server require auth username/password (access allowed), IPv4", + sub { socks5_auth(["--proxy-auth","vasek:admin"], "-4", "127.0.0.1"); }; +kill_children; + +test "SOCKS5 client, server require auth username/password (access allowed), IPv6", + sub { socks5_auth(["--proxy-auth","vasek:admin"], "-6", "::1"); }; +kill_children; + +test "SOCKS5 client, server require auth username/password (access allowed), domain", + sub { socks5_auth(["--proxy-auth","vasek:admin"], "-4", "www.seznam.cz"); }; +kill_children; + +test "SOCKS5 client, server allows connection - no auth", + sub { socks5_auth([], "-4", "127.0.0.1")}; +kill_children; +{ +local $xfail = 1; + test "SOCKS5 client, server require auth username/password (access denied)", + sub { socks5_auth(["--proxy-auth","klara:admin"], "-4", "127.0.0.1"); }; + kill_children; + + test "SOCKS5 client, server require auth username/password (too long login)", + sub { socks5_auth(["--proxy-auth",'monika' x 100 . ':admindd'], "-4", "127.0.0.1");}; + kill_children; +} + +{ +local $xfail = 1; +test "SOCKS5 client, server sends short response", +sub { + my ($pid,$code); + my $buf=""; + local $SIG{CHLD} = sub { }; + local *SOCK; + local *S; + + socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die; + setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die; + bind(SOCK, sockaddr_in($PROXY_PORT, INADDR_ANY)) or die; + listen(SOCK, 1) or die; + + my ($c_pid, $c_out, $c_in) = ncat("-4","--proxy-type", "socks5", "--proxy", "$HOST:$PROXY_PORT", "127.0.0.1", $PORT); + + accept(S, SOCK) or die "Client not connected"; + binmode(S); + sysread(S, $buf, 10) or die "Connection closed"; + # not important received data now, + # when we know that's ok from test above + + # we need O_NONBLOCK for read/write actions else + # client block us until we kill process manually + fcntl(S, F_SETFL, O_NONBLOCK) or + die "Can't set flags for the socket: $!\n"; + send(S, "\x05", 0) or die "Send: Connection closed"; + + sysread(S, $buf, $BUFSIZ) or die "Connection closed"; + + close(S); + close(SOCK); +}; +kill_children; +} + +{ +local $xfail = 1; +test "SOCKS5 client, server sends no acceptable auth method", +sub { + my ($pid,$code); + my $buf=""; + my ($my_addr,$recv_addr,$recv_port); + + local $SIG{CHLD} = sub { }; + local *SOCK; + local *S; + + socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die; + setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die; + bind(SOCK, sockaddr_in($PROXY_PORT, INADDR_ANY)) or die; + listen(SOCK, 1) or die; + + my ($c_pid, $c_out, $c_in) = ncat("-4","--proxy-type", "socks5", "--proxy", "$HOST:$PROXY_PORT", "127.0.0.1", $PORT); + + accept(S, SOCK) or die "Client not connected"; + binmode(S); + sysread(S, $buf, 10) or die "Connection closed"; + + send(S, "\x05\xFF",0) or die "Send: Connection closed"; + sysread(S, $buf, $BUFSIZ) or die "Connection closed"; + + close(S); + close(SOCK); +}; +kill_children; +} + +{ + local $xfail = 1; +test "SOCKS5 client, server sends unkown code", + sub { + my ($pid,$code); + my $buf=""; + my ($my_addr,$recv_addr,$recv_port); + + local $SIG{CHLD} = sub { }; + local *SOCK; + local *S; + + socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die; + setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die; + bind(SOCK, sockaddr_in($PROXY_PORT, INADDR_ANY)) or die; + listen(SOCK, 1) or die; + + my ($c_pid, $c_out, $c_in) = ncat("-4","--proxy-type", "socks5", "--proxy", "$HOST:$PROXY_PORT", "127.0.0.1", $PORT); + + accept(S, SOCK) or die "Client not connected"; + binmode(S); + sysread(S, $buf, 10) or die "Connection closed"; + + send(S, "\x05\xAA",0) or die "Send: Connection closed"; + sysread(S, $buf, $BUFSIZ) or die "Connection closed"; + + close(S); + close(SOCK); + }; + kill_children; +} + for my $count (0, 1, 10) { max_conns_test_tcp_sctp_ssl("--max-conns $count --keep-open", ["--keep-open"], [], $count); }