diff --git a/nsock/include/nsock.h b/nsock/include/nsock.h index 4c25789f5..9b618c397 100644 --- a/nsock/include/nsock.h +++ b/nsock/include/nsock.h @@ -306,6 +306,20 @@ void nsock_proxychain_delete(nsock_proxychain chain); * nsock_pool_set_proxychain(). Existing nsock_iod will connect as normal. */ int nsock_pool_set_proxychain(nsock_pool nspool, nsock_proxychain chain); +/* Proxy type constants returned by nsock_proxychain_first_node_info(). */ +#define NSOCK_PROXY_TYPE_HTTP 0 +#define NSOCK_PROXY_TYPE_SOCKS4 1 + +/* Retrieve the address, port, and type of the first proxy node in a chain. + * ss and sslen are filled with the proxy server's sockaddr. port is filled with + * the proxy port. proxy_type is set to one of the NSOCK_PROXY_TYPE_* constants. + * Returns 1 on success, 0 if the chain is NULL or empty. */ +int nsock_proxychain_first_node_info(nsock_proxychain chain, + struct sockaddr_storage *ss, + size_t *sslen, + unsigned short *port, + int *proxy_type); + /* nsock_event handles a single event. Its ID is generally returned when the * event is created, and the event itself is included in callbacks * diff --git a/nsock/src/nsock_proxy.c b/nsock/src/nsock_proxy.c index 44630933c..ff10d0bc7 100644 --- a/nsock/src/nsock_proxy.c +++ b/nsock/src/nsock_proxy.c @@ -152,6 +152,30 @@ int nsock_pool_set_proxychain(nsock_pool nspool, nsock_proxychain chain) { return 1; } +int nsock_proxychain_first_node_info(nsock_proxychain chain, + struct sockaddr_storage *ss, + size_t *sslen, + unsigned short *port, + int *proxy_type) { + struct proxy_chain *pchain = (struct proxy_chain *)chain; + gh_lnode_t *first; + struct proxy_node *node; + + if (!pchain) + return 0; + + first = gh_list_first_elem(&pchain->nodes); + if (!first) + return 0; + + node = container_of(first, struct proxy_node, nodeq); + *ss = node->ss; + *sslen = node->sslen; + *port = node->port; + *proxy_type = (int)node->spec->type; + return 1; +} + struct proxy_chain_context *proxy_chain_context_new(nsock_pool nspool) { struct npool *nsp = (struct npool *)nspool; struct proxy_chain_context *ctx; diff --git a/scan_engine.h b/scan_engine.h index 8ec67703b..f1e46ea10 100644 --- a/scan_engine.h +++ b/scan_engine.h @@ -109,6 +109,15 @@ public: ConnectProbe(); ~ConnectProbe(); int sd; /* Socket descriptor used for connection. -1 if not valid. */ + + /* SOCKS4 proxy state for connect scans using --proxy. */ + enum ProxyState { PROXY_NONE = 0, PROXY_CONNECTING, PROXY_READING }; + ProxyState proxy_state; + struct sockaddr_storage proxy_target_ss; /* original target address */ + size_t proxy_target_sslen; + uint16_t proxy_target_port; + uint8_t proxy_resp_buf[8]; /* buffer for 8-byte SOCKS4 response */ + int proxy_resp_len; /* bytes received so far */ }; struct IPExtraProbeData_icmp { diff --git a/scan_engine_connect.cc b/scan_engine_connect.cc index ace0b6d10..481b6c2f3 100644 --- a/scan_engine_connect.cc +++ b/scan_engine_connect.cc @@ -68,6 +68,7 @@ #include "scan_engine_connect.h" #include "libnetutil/netutil.h" /* for max_sd() */ #include "NmapOps.h" +#include #include @@ -207,6 +208,10 @@ bool ConnectScanInfo::clearSD(int sd) { ConnectProbe::ConnectProbe() { sd = -1; + proxy_state = PROXY_NONE; + proxy_target_sslen = 0; + proxy_target_port = 0; + proxy_resp_len = 0; } ConnectProbe::~ConnectProbe() { @@ -488,6 +493,31 @@ UltraProbe *sendConnectScanProbe(UltraScanInfo *USI, HostScanStats *hss, #if HAVE_IPV6 else sin6->sin6_port = htons(probe->pspec()->pd.tcp.dport); #endif + + /* If a SOCKS4 proxy chain is configured, connect to the proxy instead of + * the target directly and perform the SOCKS4 handshake asynchronously. */ + struct sockaddr_storage proxy_ss; + size_t proxy_sslen = 0; + unsigned short proxy_port = 0; + int proxy_type = -1; + if (o.proxy_chain && + nsock_proxychain_first_node_info(o.proxy_chain, &proxy_ss, &proxy_sslen, + &proxy_port, &proxy_type) == 1 && + proxy_type == NSOCK_PROXY_TYPE_SOCKS4 && + sock.ss_family == AF_INET) { + /* Save the real target so we can build the SOCKS4 request later. */ + CP->proxy_state = ConnectProbe::PROXY_CONNECTING; + memcpy(&CP->proxy_target_ss, &sock, socklen); + CP->proxy_target_sslen = socklen; + CP->proxy_target_port = destport; + + /* Point sin/socklen at the proxy address. */ + ((struct sockaddr_in *)&proxy_ss)->sin_port = htons(proxy_port); + memcpy(&sock, &proxy_ss, proxy_sslen); + socklen = proxy_sslen; + sin = (struct sockaddr_in *) &sock; + } + /* We don't record a byte count for connect probes. */ hss->probeSent(0); rc = connect(CP->sd, (struct sockaddr *)&sock, socklen); @@ -510,6 +540,23 @@ UltraProbe *sendConnectScanProbe(UltraScanInfo *USI, HostScanStats *hss, PacketTrace::traceConnect(IPPROTO_TCP, (sockaddr *) &sock, socklen, rc, connect_errno, &USI->now); USI->gstats->CSI->watchSD(CP->sd); + } else if (CP->proxy_state == ConnectProbe::PROXY_CONNECTING && connect_errno == 0) { + /* Immediate synchronous connect to proxy succeeded — send SOCKS4 request + * and watch for the response. */ + uint8_t req[9]; + struct sockaddr_in *tsin = (struct sockaddr_in *)&CP->proxy_target_ss; + req[0] = 4; req[1] = 1; + req[2] = (CP->proxy_target_port >> 8) & 0xff; + req[3] = CP->proxy_target_port & 0xff; + memcpy(&req[4], &tsin->sin_addr.s_addr, 4); + req[8] = 0; /* empty user id */ + if (send(CP->sd, (char *)req, sizeof(req), 0) == (int)sizeof(req)) { + CP->proxy_state = ConnectProbe::PROXY_READING; + USI->gstats->CSI->watchSD(CP->sd); + } else { + handleConnectResult(USI, hss, probeI, ECONNREFUSED, true); + probe = NULL; + } } else { handleConnectResult(USI, hss, probeI, connect_errno, true); probe = NULL; @@ -601,11 +648,60 @@ bool do_one_select_round(UltraScanInfo *USI, struct timeval *stime) { checked_fd_isset(sd, &fds_wtmp) || checked_fd_isset(sd, &fds_xtmp))) { numGoodSD++; - if (getsockopt(sd, SOL_SOCKET, SO_ERROR, (char *) &optval, - &optlen) != 0) - optval = socket_errno(); /* Stupid Solaris ... */ + ConnectProbe *CP = probe->CP(); - handleConnectResult(USI, host, probeI, optval); + if (CP->proxy_state == ConnectProbe::PROXY_CONNECTING) { + /* Connected (or failed) to proxy server. Check result. */ + if (getsockopt(sd, SOL_SOCKET, SO_ERROR, (char *) &optval, + &optlen) != 0) + optval = socket_errno(); + if (optval != 0) { + handleConnectResult(USI, host, probeI, optval); + } else { + /* Send the 9-byte SOCKS4 CONNECT request. */ + uint8_t req[9]; + struct sockaddr_in *tsin = + (struct sockaddr_in *)&CP->proxy_target_ss; + req[0] = 4; req[1] = 1; + req[2] = (CP->proxy_target_port >> 8) & 0xff; + req[3] = CP->proxy_target_port & 0xff; + memcpy(&req[4], &tsin->sin_addr.s_addr, 4); + req[8] = 0; /* empty user id */ + if (send(sd, (char *)req, sizeof(req), 0) == (int)sizeof(req)) { + CP->proxy_state = ConnectProbe::PROXY_READING; + /* Stop watching for writability; keep read + except. */ + checked_fd_clr(sd, &USI->gstats->CSI->fds_write); + } else { + handleConnectResult(USI, host, probeI, ECONNREFUSED); + } + } + } else if (CP->proxy_state == ConnectProbe::PROXY_READING) { + /* Reading the 8-byte SOCKS4 response. */ + int recvd = recv(sd, + (char *)CP->proxy_resp_buf + CP->proxy_resp_len, + 8 - CP->proxy_resp_len, 0); + if (recvd > 0) { + CP->proxy_resp_len += recvd; + if (CP->proxy_resp_len >= 8) { + /* Full response received: byte[1]==90 means granted. */ + if (CP->proxy_resp_buf[1] == 90) + handleConnectResult(USI, host, probeI, 0); + else + handleConnectResult(USI, host, probeI, ECONNREFUSED); + } + /* Partial read: probe stays outstanding, wait for more data. */ + } else if (recvd == 0) { + handleConnectResult(USI, host, probeI, ECONNREFUSED); + } else { + handleConnectResult(USI, host, probeI, socket_errno()); + } + } else { + /* Normal (no proxy) connect result. */ + if (getsockopt(sd, SOL_SOCKET, SO_ERROR, (char *) &optval, + &optlen) != 0) + optval = socket_errno(); /* Stupid Solaris ... */ + handleConnectResult(USI, host, probeI, optval); + } } } }