summaryrefslogtreecommitdiffstats
path: root/scan_engine_connect.cc
diff options
context:
space:
mode:
Diffstat (limited to 'scan_engine_connect.cc')
-rw-r--r--scan_engine_connect.cc560
1 files changed, 560 insertions, 0 deletions
diff --git a/scan_engine_connect.cc b/scan_engine_connect.cc
new file mode 100644
index 0000000..452c3e1
--- /dev/null
+++ b/scan_engine_connect.cc
@@ -0,0 +1,560 @@
+
+/***************************************************************************
+ * scan_engine_connect.cc -- includes helper functions for scan_engine.cc *
+ * that are related to port scanning using connect() system call. *
+ * *
+ ***********************IMPORTANT NMAP LICENSE TERMS************************
+ *
+ * The Nmap Security Scanner is (C) 1996-2023 Nmap Software LLC ("The Nmap
+ * Project"). Nmap is also a registered trademark of the Nmap Project.
+ *
+ * This program is distributed under the terms of the Nmap Public Source
+ * License (NPSL). The exact license text applying to a particular Nmap
+ * release or source code control revision is contained in the LICENSE
+ * file distributed with that version of Nmap or source code control
+ * revision. More Nmap copyright/legal information is available from
+ * https://nmap.org/book/man-legal.html, and further information on the
+ * NPSL license itself can be found at https://nmap.org/npsl/ . This
+ * header summarizes some key points from the Nmap license, but is no
+ * substitute for the actual license text.
+ *
+ * Nmap is generally free for end users to download and use themselves,
+ * including commercial use. It is available from https://nmap.org.
+ *
+ * The Nmap license generally prohibits companies from using and
+ * redistributing Nmap in commercial products, but we sell a special Nmap
+ * OEM Edition with a more permissive license and special features for
+ * this purpose. See https://nmap.org/oem/
+ *
+ * If you have received a written Nmap license agreement or contract
+ * stating terms other than these (such as an Nmap OEM license), you may
+ * choose to use and redistribute Nmap under those terms instead.
+ *
+ * The official Nmap Windows builds include the Npcap software
+ * (https://npcap.com) for packet capture and transmission. It is under
+ * separate license terms which forbid redistribution without special
+ * permission. So the official Nmap Windows builds may not be redistributed
+ * without special permission (such as an Nmap OEM license).
+ *
+ * Source is provided to this software because we believe users have a
+ * right to know exactly what a program is going to do before they run it.
+ * This also allows you to audit the software for security holes.
+ *
+ * Source code also allows you to port Nmap to new platforms, fix bugs, and add
+ * new features. You are highly encouraged to submit your changes as a Github PR
+ * or by email to the dev@nmap.org mailing list for possible incorporation into
+ * the main distribution. Unless you specify otherwise, it is understood that
+ * you are offering us very broad rights to use your submissions as described in
+ * the Nmap Public Source License Contributor Agreement. This is important
+ * because we fund the project by selling licenses with various terms, and also
+ * because the inability to relicense code has caused devastating problems for
+ * other Free Software projects (such as KDE and NASM).
+ *
+ * The free version of Nmap 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. Warranties,
+ * indemnification and commercial support are all available through the
+ * Npcap OEM program--see https://nmap.org/oem/
+ *
+ ***************************************************************************/
+
+/* $Id$ */
+
+#include "nmap_error.h"
+#include "tcpip.h"
+#include "Target.h"
+#include "scan_engine.h"
+#include "scan_engine_connect.h"
+#include "libnetutil/netutil.h" /* for max_sd() */
+#include "NmapOps.h"
+
+#include <errno.h>
+
+extern NmapOps o;
+
+/* Sets this UltraProbe as type UP_CONNECT, preparing to connect to given
+ port number*/
+void UltraProbe::setConnect(u16 portno) {
+ type = UP_CONNECT;
+ probes.CP = new ConnectProbe();
+ mypspec.type = PS_CONNECTTCP;
+ mypspec.proto = IPPROTO_TCP;
+ mypspec.pd.tcp.dport = portno;
+ mypspec.pd.tcp.flags = TH_SYN;
+}
+
+ConnectScanInfo::ConnectScanInfo() {
+ maxValidSD = -1;
+ numSDs = 0;
+ nextSD = -1;
+ if (o.max_parallelism > 0) {
+ maxSocketsAllowed = o.max_parallelism;
+ }
+#ifndef WIN32
+ else {
+ /* Subtracting 10 from max_sd accounts for
+ stdin
+ stdout
+ stderr
+ /dev/tty
+ /var/run/utmpx, which is opened on Mac OS X at least
+ -oG log file
+ -oN log file
+ -oS log file
+ -oX log file
+ perhaps another we've forgotten. */
+ maxSocketsAllowed = max_sd() - 10;
+ if (maxSocketsAllowed < 5)
+ maxSocketsAllowed = 5;
+ }
+ /* We can't issue a FD_SET operation with a socket descriptor greater than
+ * FD_SETSIZE, and we can't stop the OS from handing us ones that are greater
+ * than that, either, so leave a buffer here. */
+ maxSocketsAllowed = MIN(maxSocketsAllowed, FD_SETSIZE - 10);
+#else
+ /* Windows does not have an explicit limit, but we have to keep it below
+ * FD_SETSIZE or select() will fail. Fortunately, it's about the *number* of
+ * sockets, not the socket descriptor number, so we can run right up to that
+ * limit. */
+ maxSocketsAllowed = MIN(maxSocketsAllowed, FD_SETSIZE - 1);
+#endif
+ FD_ZERO(&fds_read);
+ FD_ZERO(&fds_write);
+ FD_ZERO(&fds_except);
+}
+
+/* Nothing really to do here. */
+ConnectScanInfo::~ConnectScanInfo() {}
+
+bool ConnectScanInfo::sendOK() {
+ if (numSDs >= maxSocketsAllowed)
+ return false;
+
+ if (nextSD > 0)
+ return true;
+
+ nextSD = socket(o.af(), SOCK_STREAM, IPPROTO_TCP);
+ if (nextSD == -1)
+ pfatal("Socket creation in %s", __func__);
+#ifndef WIN32
+ /* Check here whether this socket descriptor number will be a problem. If so,
+ * close it and tell the engine to slow down. Windows doesn't have this
+ * limit, only maxSocketsAllowed. */
+ if (nextSD >= FD_SETSIZE) {
+ if (o.debugging) {
+ log_write(LOG_STDOUT, "Socket descriptor %d greater than FD_SETSIZE: slow down.\n", nextSD);
+ }
+ close(nextSD);
+ nextSD = -1;
+ return false;
+ }
+#endif
+ return true;
+}
+
+int ConnectScanInfo::getSocket() {
+ int sd = nextSD;
+ nextSD = -1;
+ return sd;
+}
+
+/* Watch a socket descriptor (add to fd_sets and maxValidSD). Returns
+ true if the SD was absent from the list, false if you tried to
+ watch an SD that was already being watched. */
+bool ConnectScanInfo::watchSD(int sd) {
+ assert(sd >= 0);
+ if (!checked_fd_isset(sd, &fds_read)) {
+ checked_fd_set(sd, &fds_read);
+ checked_fd_set(sd, &fds_write);
+ checked_fd_set(sd, &fds_except);
+ numSDs++;
+ if (sd > maxValidSD)
+ maxValidSD = sd;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/* Clear SD from the fd_sets and maxValidSD. Returns true if the SD
+ was in the list, false if you tried to clear an sd that wasn't
+ there in the first place. */
+bool ConnectScanInfo::clearSD(int sd) {
+ assert(sd >= 0);
+ if (checked_fd_isset(sd, &fds_read)) {
+ checked_fd_clr(sd, &fds_read);
+ checked_fd_clr(sd, &fds_write);
+ checked_fd_clr(sd, &fds_except);
+ assert(numSDs > 0);
+ numSDs--;
+ if (sd == maxValidSD)
+ maxValidSD--;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+ConnectProbe::ConnectProbe() {
+ sd = -1;
+}
+
+ConnectProbe::~ConnectProbe() {
+ if (sd > 0)
+ close(sd);
+ sd = -1;
+}
+
+static void handleConnectResult(UltraScanInfo *USI, HostScanStats *hss,
+ std::list<UltraProbe *>::iterator probeI,
+ int connect_errno,
+ bool destroy_probe=false) {
+ bool adjust_timing = true;
+ int newportstate = PORT_UNKNOWN;
+ int newhoststate = HOST_UNKNOWN;
+ reason_t current_reason = ER_NORESPONSE;
+ UltraProbe *probe = *probeI;
+ struct sockaddr_storage local;
+ socklen_t local_len = sizeof(struct sockaddr_storage);
+ struct sockaddr_storage remote;
+ size_t remote_len;
+
+ if (hss->target->TargetSockAddr(&remote, &remote_len) != 0) {
+ fatal("Failed to get target socket address in %s", __func__);
+ }
+ if (remote.ss_family == AF_INET)
+ ((struct sockaddr_in *) &remote)->sin_port = htons(probe->dport());
+#if HAVE_IPV6
+ else
+ ((struct sockaddr_in6 *) &remote)->sin6_port = htons(probe->dport());
+#endif
+ PacketTrace::traceConnect(IPPROTO_TCP, (sockaddr *) &remote, remote_len,
+ connect_errno, connect_errno, &USI->now);
+ switch (connect_errno) {
+ case 0:
+ newhoststate = HOST_UP;
+ newportstate = PORT_OPEN;
+ current_reason = ER_CONACCEPT;
+ break;
+ case EACCES:
+ /* Apparently this can be caused by dest unreachable admin
+ prohibited messages sent back, at least from IPv6
+ hosts */
+ newhoststate = HOST_DOWN;
+ newportstate = PORT_FILTERED;
+ current_reason = ER_ADMINPROHIBITED;
+ break;
+ /* This can happen on localhost, successful/failing connection immediately
+ in non-blocking mode. */
+ case ECONNREFUSED:
+ if (!o.discovery_ignore_rst) {
+ newhoststate = HOST_UP;
+ }
+ newportstate = PORT_CLOSED;
+ current_reason = ER_CONREFUSED;
+ break;
+ case EAGAIN:
+ log_write(LOG_STDOUT, "Machine %s MIGHT actually be listening on probe port %d\n", hss->target->targetipstr(), USI->ports->syn_ping_ports[probe->dport()]);
+ /* Fall through. */
+#ifdef WIN32
+ case WSAENOTCONN:
+#endif
+ newhoststate = HOST_UP;
+ current_reason = ER_CONACCEPT;
+ break;
+#ifdef ENOPROTOOPT
+ case ENOPROTOOPT:
+ newhoststate = HOST_DOWN;
+ newportstate = PORT_FILTERED;
+ current_reason = ER_PROTOUNREACH;
+ break;
+#endif
+ case EHOSTUNREACH:
+ newhoststate = HOST_DOWN;
+ newportstate = PORT_FILTERED;
+ current_reason = ER_HOSTUNREACH;
+ break;
+#ifdef WIN32
+ case WSAEADDRNOTAVAIL:
+#endif
+ case ETIMEDOUT:
+ case EHOSTDOWN:
+ newhoststate = HOST_DOWN;
+ /* It could be the host is down, or it could be firewalled. We
+ will go on the safe side & assume port is closed ... on second
+ thought, lets go firewalled! and see if it causes any trouble */
+ newportstate = PORT_FILTERED;
+ current_reason = ER_NORESPONSE;
+ break;
+ case ENETUNREACH:
+ newhoststate = HOST_DOWN;
+ newportstate = PORT_FILTERED;
+ current_reason = ER_NETUNREACH;
+ break;
+#ifdef ENONET
+ case ENONET:
+ /* For Linux at least, this means ICMP type 3 code 8, source host isolated */
+ newhoststate = HOST_DOWN;
+ newportstate = PORT_FILTERED;
+ current_reason = ER_DESTUNREACH;
+ break;
+#endif
+ case ENETDOWN:
+ case ENETRESET:
+ case ECONNABORTED:
+ fatal("Strange SO_ERROR from connection to %s (%d - '%s') -- bailing scan", hss->target->targetipstr(), connect_errno, strerror(connect_errno));
+ break;
+ default:
+ error("Strange read error from %s (%d - '%s')", hss->target->targetipstr(), connect_errno, strerror(connect_errno));
+ break;
+ }
+ if (probe->isPing() && newhoststate != HOST_UNKNOWN ) {
+ ultrascan_ping_update(USI, hss, probeI, &USI->now, adjust_timing);
+ } else if (USI->ping_scan && newhoststate != HOST_UNKNOWN) {
+ ultrascan_host_probe_update(USI, hss, probeI, newhoststate, &USI->now, adjust_timing);
+ hss->target->reason.reason_id = current_reason;
+ /* If the host is up, we can forget our other probes. */
+ if (newhoststate == HOST_UP)
+ hss->destroyAllOutstandingProbes();
+ } else if (!USI->ping_scan && newportstate != PORT_UNKNOWN) {
+ /* Save these values so we can use them after
+ ultrascan_port_probe_update deletes probe. */
+ u8 protocol = probe->protocol();
+ u16 dport = probe->dport();
+ /* getsockname can fail on AIX when socket is closed
+ * and we only care about self-connects for open ports anyway
+ */
+ if (newportstate == PORT_OPEN) {
+ /* Check for self-connected probe */
+ if (getsockname(probe->CP()->sd, (struct sockaddr*)&local, &local_len) == 0) {
+ if (sockaddr_storage_cmp(&local, &remote) == 0 && (
+ (local.ss_family == AF_INET &&
+ ((struct sockaddr_in*)&local)->sin_port == htons(dport))
+#if HAVE_IPV6
+ || (local.ss_family == AF_INET6 &&
+ ((struct sockaddr_in6*)&local)->sin6_port == htons(dport))
+#endif
+ )) {
+ if (o.debugging) {
+ log_write(LOG_STDOUT, "Detected likely self-connect on port %d\n", probe->dport());
+ }
+ /* It's not really timed out, but this is a simple way to retry the
+ * probe. It shouldn't affect timing too much, since this is quite
+ * rare (should average one per scan, for localhost -p 0-65535 scans
+ * only) */
+ hss->markProbeTimedout(probeI);
+ }
+ else {
+ ultrascan_port_probe_update(USI, hss, probeI, newportstate, &USI->now, adjust_timing);
+ hss->target->ports.setStateReason(dport, protocol, current_reason, 0, NULL);
+ }
+ }
+ else {
+ gh_perror("getsockname or TargetSockAddr failed");
+ }
+ }
+ else {
+ ultrascan_port_probe_update(USI, hss, probeI, newportstate, &USI->now, adjust_timing);
+ hss->target->ports.setStateReason(dport, protocol, current_reason, 0, NULL);
+ }
+ } else if (destroy_probe) {
+ hss->destroyOutstandingProbe(probeI);
+ }
+ return;
+}
+
+/* Set the socket lingering so we will RST connections instead of wasting
+ bandwidth with the four-step close. Set the source address if needed. Bind to
+ a specific interface if needed. */
+static void init_socket(int sd) {
+ static int bind_failed = 0;
+ struct linger l;
+ struct sockaddr_storage ss;
+ size_t sslen;
+
+ l.l_onoff = 1;
+ l.l_linger = 0;
+
+ if (setsockopt(sd, SOL_SOCKET, SO_LINGER, (const char *) &l, sizeof(l)) != 0) {
+ error("Problem setting socket SO_LINGER, errno: %d", socket_errno());
+ perror("setsockopt");
+ }
+ if (o.spoofsource && !bind_failed) {
+ o.SourceSockAddr(&ss, &sslen);
+ if (::bind(sd, (struct sockaddr*)&ss, sslen) != 0) {
+ error("%s: Problem binding source address (%s), errno: %d", __func__, inet_socktop(&ss), socket_errno());
+ perror("bind");
+ bind_failed = 1;
+ }
+ }
+ errno = 0;
+ if (!socket_bindtodevice(sd, o.device)) {
+ /* EPERM is expected when not running as root. */
+ if (errno != EPERM) {
+ error("Problem binding to interface %s, errno: %d", o.device, socket_errno());
+ perror("socket_bindtodevice");
+ }
+ }
+}
+
+/* If this is NOT a ping probe, set tryno.fields.isPing to 0. Otherwise it will be the
+ ping sequence number (they start at 1). The probe sent is returned. */
+UltraProbe *sendConnectScanProbe(UltraScanInfo *USI, HostScanStats *hss,
+ u16 destport, tryno_t tryno) {
+
+ UltraProbe *probe = new UltraProbe();
+ std::list<UltraProbe *>::iterator probeI;
+ int rc;
+ int connect_errno = 0;
+ struct sockaddr_storage sock;
+ struct sockaddr_in *sin = (struct sockaddr_in *) &sock;
+#if HAVE_IPV6
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &sock;
+#endif
+ size_t socklen;
+ ConnectProbe *CP;
+ ConnectScanInfo *CSI = USI->gstats->CSI;
+
+ probe->tryno = tryno;
+ /* First build the probe */
+ probe->setConnect(destport);
+ CP = probe->CP();
+ /* Initiate the connection */
+ CP->sd = CSI->getSocket();
+ assert(CP->sd > 0);
+ unblock_socket(CP->sd);
+ init_socket(CP->sd);
+ set_ttl(CP->sd, o.ttl);
+ if (o.ipoptionslen)
+ set_ipoptions(CP->sd, o.ipoptions, o.ipoptionslen);
+ if (hss->target->TargetSockAddr(&sock, &socklen) != 0) {
+ fatal("Failed to get target socket address in %s", __func__);
+ }
+ if (sin->sin_family == AF_INET)
+ sin->sin_port = htons(probe->pspec()->pd.tcp.dport);
+#if HAVE_IPV6
+ else sin6->sin6_port = htons(probe->pspec()->pd.tcp.dport);
+#endif
+ probe->sent = USI->now;
+ /* We don't record a byte count for connect probes. */
+ hss->probeSent(0);
+ rc = connect(CP->sd, (struct sockaddr *)&sock, socklen);
+ gettimeofday(&USI->now, NULL);
+ if (rc == -1)
+ connect_errno = socket_errno();
+ /* This counts as probe being sent, so update structures */
+ hss->probes_outstanding.push_back(probe);
+ probeI = hss->probes_outstanding.end();
+ probeI--;
+ USI->gstats->num_probes_active++;
+ hss->num_probes_active++;
+
+ /* It would be convenient if the connect() call would never succeed
+ or permanently fail here, so related code cood all be localized
+ elsewhere. But the reality is that connect() MAY be finished now. */
+
+ if (rc == -1 && (connect_errno == EINPROGRESS || connect_errno == EAGAIN)) {
+ PacketTrace::traceConnect(IPPROTO_TCP, (sockaddr *) &sock, socklen, rc,
+ connect_errno, &USI->now);
+ USI->gstats->CSI->watchSD(CP->sd);
+ } else {
+ handleConnectResult(USI, hss, probeI, connect_errno, true);
+ probe = NULL;
+ }
+ gettimeofday(&USI->now, NULL);
+ return probe;
+}
+
+/* Does a select() call and handles all of the results. This handles both host
+ discovery (ping) scans and port scans. Even if stime is now, it tries a very
+ quick select() just in case. Returns true if at least one good result
+ (generally a port state change) is found, false if it times out instead */
+bool do_one_select_round(UltraScanInfo *USI, struct timeval *stime) {
+ fd_set fds_rtmp, fds_wtmp, fds_xtmp;
+ int selectres;
+ struct timeval timeout;
+ int timeleft;
+ ConnectScanInfo *CSI = USI->gstats->CSI;
+ int sd;
+ std::multiset<HostScanStats *, HssPredicate>::iterator hostI;
+ HostScanStats *host;
+ UltraProbe *probe = NULL;
+ int optval;
+ recvfrom6_t optlen = sizeof(int);
+ int numGoodSD = 0;
+ int err = 0;
+
+ do {
+ timeleft = TIMEVAL_MSEC_SUBTRACT(*stime, USI->now);
+ if (timeleft < 0)
+ timeleft = 0;
+ fds_rtmp = USI->gstats->CSI->fds_read;
+ fds_wtmp = USI->gstats->CSI->fds_write;
+ fds_xtmp = USI->gstats->CSI->fds_except;
+ timeout.tv_sec = timeleft / 1000;
+ timeout.tv_usec = (timeleft % 1000) * 1000;
+
+ if (CSI->numSDs) {
+ selectres = select(CSI->maxValidSD + 1, &fds_rtmp, &fds_wtmp,
+ &fds_xtmp, &timeout);
+ err = socket_errno();
+ } else {
+ /* Apparently Windows returns an WSAEINVAL if you select without watching any SDs. Lame. We'll usleep instead in that case */
+ usleep(timeleft * 1000);
+ selectres = 0;
+ }
+ } while (selectres == -1 && err == EINTR);
+
+ gettimeofday(&USI->now, NULL);
+
+ if (selectres == -1)
+ pfatal("select failed in %s()", __func__);
+
+ if (!selectres)
+ return false;
+
+ /* Yay! Got at least one response back -- loop through outstanding probes
+ and find the relevant ones. Note the peculiar structure of the loop--we
+ iterate through both incompleteHosts and completedHosts, because global
+ timing pings are sent to hosts in completedHosts. */
+ std::multiset<HostScanStats *, HssPredicate>::iterator incompleteHostI, completedHostI;
+ incompleteHostI = USI->incompleteHosts.begin();
+ completedHostI = USI->completedHosts.begin();
+ while ((incompleteHostI != USI->incompleteHosts.end()
+ || completedHostI != USI->completedHosts.end())
+ && numGoodSD < selectres) {
+ if (incompleteHostI != USI->incompleteHosts.end())
+ hostI = incompleteHostI++;
+ else
+ hostI = completedHostI++;
+
+ host = *hostI;
+ if (host->num_probes_active == 0)
+ continue;
+
+ std::list<UltraProbe *>::iterator nextProbeI;
+ for (std::list<UltraProbe *>::iterator probeI = host->probes_outstanding.begin();
+ probeI != host->probes_outstanding.end() && numGoodSD < selectres && host->num_probes_outstanding() > 0; probeI = nextProbeI) {
+ /* handleConnectResult may remove the probe at probeI, which invalidates
+ * the iterator. We copy and increment it here instead of in the for-loop
+ * statement to avoid incrementing an invalid iterator */
+ nextProbeI = probeI;
+ nextProbeI++;
+ probe = *probeI;
+ assert(probe->type == UltraProbe::UP_CONNECT);
+ sd = probe->CP()->sd;
+ /* Let see if anything has happened! */
+ if (sd >= 0 && (checked_fd_isset(sd, &fds_rtmp) ||
+ 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 ... */
+
+ handleConnectResult(USI, host, probeI, optval);
+ }
+ }
+ }
+ return numGoodSD;
+}