summaryrefslogtreecommitdiffstats
path: root/output.cc
diff options
context:
space:
mode:
Diffstat (limited to 'output.cc')
-rw-r--r--output.cc2710
1 files changed, 2710 insertions, 0 deletions
diff --git a/output.cc b/output.cc
new file mode 100644
index 0000000..9fa0d26
--- /dev/null
+++ b/output.cc
@@ -0,0 +1,2710 @@
+
+/***************************************************************************
+ * output.cc -- Handles the Nmap output system. This currently involves *
+ * console-style human readable output, XML output, Script |<iddi3 *
+ * output, and the legacy grepable output (used to be called "machine *
+ * readable"). I expect that future output forms (such as HTML) may be *
+ * created by a different program, library, or script using the XML *
+ * output. *
+ * *
+ ***********************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.h"
+#include "output.h"
+#include "osscan.h"
+#include "osscan2.h"
+#include "NmapOps.h"
+#include "NmapOutputTable.h"
+#include "MACLookup.h"
+#include "portreasons.h"
+#include "protocols.h"
+#include "FingerPrintResults.h"
+#include "tcpip.h"
+#include "Target.h"
+#include "nmap_error.h"
+#include "utils.h"
+#include "xml.h"
+#include "nbase.h"
+#include "libnetutil/netutil.h"
+#include <nsock.h>
+
+#include <math.h>
+
+#include <set>
+#include <vector>
+#include <list>
+#include <sstream>
+
+extern NmapOps o;
+static const char *logtypes[LOG_NUM_FILES] = LOG_NAMES;
+
+/* Used in creating skript kiddie style output. |<-R4d! */
+static void skid_output(char *s) {
+ int i;
+ for (i = 0; s[i]; i++)
+ /* We need a 50/50 chance here, use a random number */
+ if ((get_random_u8() & 0x01) == 0)
+ /* Substitutions commented out are not known to me, but maybe look nice */
+ switch (s[i]) {
+ case 'A':
+ s[i] = '4';
+ break;
+ /* case 'B': s[i]='8'; break;
+ case 'b': s[i]='6'; break;
+ case 'c': s[i]='k'; break;
+ case 'C': s[i]='K'; break; */
+ case 'e':
+ case 'E':
+ s[i] = '3';
+ break;
+ case 'i':
+ case 'I':
+ s[i] = "!|1"[get_random_u8() % 3];
+ break;
+ /* case 'k': s[i]='c'; break;
+ case 'K': s[i]='C'; break; */
+ case 'o':
+ case 'O':
+ s[i] = '0';
+ break;
+ case 's':
+ case 'S':
+ if (s[i + 1] && !isalnum((int) (unsigned char) s[i + 1]))
+ s[i] = 'z';
+ else
+ s[i] = '$';
+ break;
+ case 'z':
+ s[i] = 's';
+ break;
+ case 'Z':
+ s[i] = 'S';
+ break;
+ } else {
+ if (s[i] >= 'A' && s[i] <= 'Z' && (get_random_u8() % 3 == 0)) {
+ s[i] += 'a' - 'A'; /* 1/3 chance of lower-case */
+ } else if (s[i] >= 'a' && s[i] <= 'z' && (get_random_u8() % 3 == 0)) {
+ s[i] -= 'a' - 'A'; /* 1/3 chance of upper-case */
+ }
+ }
+}
+
+/* Remove all "\nSF:" from fingerprints */
+static char *servicefp_sf_remove(const char *str) {
+ char *temp = (char *) safe_malloc(strlen(str) + 1);
+ char *dst = temp, *src = (char *) str;
+ char *ampptr = 0;
+
+ while (*src) {
+ if (strncmp(src, "\nSF:", 4) == 0) {
+ src += 4;
+ continue;
+ }
+ /* Needed so "&something;" is not truncated midway */
+ if (*src == '&') {
+ ampptr = dst;
+ } else if (*src == ';') {
+ ampptr = 0;
+ }
+ *dst++ = *src++;
+ }
+ if (ampptr != 0) {
+ *ampptr = '\0';
+ } else {
+ *dst = '\0';
+ }
+ return temp;
+}
+
+// Prints an XML <service> element for the information given in
+// serviceDeduction. This function should only be called if ether
+// the service name or the service fingerprint is non-null.
+static void print_xml_service(const struct serviceDeductions *sd) {
+ xml_open_start_tag("service");
+
+ xml_attribute("name", "%s", sd->name ? sd->name : "unknown");
+ if (sd->product)
+ xml_attribute("product", "%s", sd->product);
+ if (sd->version)
+ xml_attribute("version", "%s", sd->version);
+ if (sd->extrainfo)
+ xml_attribute("extrainfo", "%s", sd->extrainfo);
+ if (sd->hostname)
+ xml_attribute("hostname", "%s", sd->hostname);
+ if (sd->ostype)
+ xml_attribute("ostype", "%s", sd->ostype);
+ if (sd->devicetype)
+ xml_attribute("devicetype", "%s", sd->devicetype);
+ if (sd->service_fp) {
+ char *servicefp = servicefp_sf_remove(sd->service_fp);
+ xml_attribute("servicefp", "%s", servicefp);
+ free(servicefp);
+ }
+
+ if (sd->service_tunnel == SERVICE_TUNNEL_SSL)
+ xml_attribute("tunnel", "ssl");
+ xml_attribute("method", "%s", (sd->dtype == SERVICE_DETECTION_TABLE) ? "table" : "probed");
+ xml_attribute("conf", "%i", sd->name_confidence);
+
+ if (sd->cpe.empty()) {
+ xml_close_empty_tag();
+ } else {
+ unsigned int i;
+
+ xml_close_start_tag();
+ for (i = 0; i < sd->cpe.size(); i++) {
+ xml_start_tag("cpe");
+ xml_write_escaped("%s", sd->cpe[i]);
+ xml_end_tag();
+ }
+ xml_end_tag();
+ }
+}
+
+#ifdef WIN32
+/* Show a fatal error explaining that an interface is not Ethernet and won't
+ work on Windows. Do nothing if --send-ip (PACKET_SEND_IP_STRONG) was used. */
+void win32_fatal_raw_sockets(const char *devname) {
+ if ((o.sendpref & PACKET_SEND_IP_STRONG) != 0)
+ return;
+
+ if (devname != NULL) {
+ fatal("Only ethernet devices can be used for raw scans on Windows, and\n"
+ "\"%s\" is not an ethernet device. Use the --unprivileged option\n"
+ "for this scan.", devname);
+ } else {
+ fatal("Only ethernet devices can be used for raw scans on Windows. Use\n"
+ "the --unprivileged option for this scan.");
+ }
+}
+
+/* Display the mapping from libdnet interface names (like "eth0") to Npcap
+ interface names (like "\Device\NPF_{...}"). This is the same mapping used by
+ eth_open and so can help diagnose connection problems. Additionally display
+ Npcap interface names that are not mapped to by any libdnet name, in other
+ words the names of interfaces Nmap has no way of using.*/
+static void print_iflist_pcap_mapping(const struct interface_info *iflist,
+ int numifs) {
+ pcap_if_t *pcap_ifs = NULL;
+ char errbuf[PCAP_ERRBUF_SIZE];
+ std::list<const pcap_if_t *> leftover_pcap_ifs;
+ std::list<const pcap_if_t *>::iterator leftover_p;
+ int i;
+
+ /* Build a list of "leftover" libpcap interfaces. Initially it contains all
+ the interfaces. */
+ if (o.have_pcap) {
+ if (pcap_findalldevs(&pcap_ifs, errbuf) == -1) {
+ fatal("pcap_findalldevs(): Cannot retrieve pcap interfaces: %s", errbuf);
+ }
+ for (const pcap_if_t *p = pcap_ifs; p != NULL; p = p->next)
+ leftover_pcap_ifs.push_front(p);
+ }
+
+ if (numifs > 0 || !leftover_pcap_ifs.empty()) {
+ NmapOutputTable Tbl(1 + numifs + leftover_pcap_ifs.size(), 2);
+
+ Tbl.addItem(0, 0, false, "DEV");
+ Tbl.addItem(0, 1, false, "WINDEVICE");
+
+ /* Show the libdnet names and what they map to. */
+ for (i = 0; i < numifs; i++) {
+ char pcap_name[1024];
+
+ if (DnetName2PcapName(iflist[i].devname, pcap_name, sizeof(pcap_name))) {
+ /* We got a name. Remove it from the list of leftovers. */
+ std::list<const pcap_if_t *>::iterator next;
+ for (leftover_p = leftover_pcap_ifs.begin();
+ leftover_p != leftover_pcap_ifs.end(); leftover_p = next) {
+ next = leftover_p;
+ next++;
+ if (strcmp((*leftover_p)->name, pcap_name) == 0)
+ leftover_pcap_ifs.erase(leftover_p);
+ }
+ } else {
+ Strncpy(pcap_name, "<none>", sizeof(pcap_name));
+ }
+
+ Tbl.addItem(i + 1, 0, false, iflist[i].devname);
+ Tbl.addItem(i + 1, 1, true, pcap_name);
+ }
+
+ /* Show the "leftover" libpcap interface names (those without a libdnet
+ name that maps to them). */
+ for (leftover_p = leftover_pcap_ifs.begin();
+ leftover_p != leftover_pcap_ifs.end();
+ leftover_p++) {
+ Tbl.addItem(i + 1, 0, false, "<none>");
+ Tbl.addItem(i + 1, 1, false, (*leftover_p)->name);
+ i++;
+ }
+
+ log_write(LOG_PLAIN, "%s\n", Tbl.printableTable(NULL));
+ log_flush_all();
+ }
+
+ if (pcap_ifs) {
+ pcap_freealldevs(pcap_ifs);
+ }
+}
+#endif
+
+/* Print a detailed list of Nmap interfaces and routes to
+ normal/skiddy/stdout output */
+int print_iflist(void) {
+ int numifs = 0, numroutes = 0;
+ struct interface_info *iflist;
+ struct sys_route *routes;
+ NmapOutputTable *Tbl = NULL;
+ char errstr[256];
+ const char *address = NULL;
+ errstr[0]='\0';
+
+ iflist = getinterfaces(&numifs, errstr, sizeof(errstr));
+
+ int i;
+ /* First let's handle interfaces ... */
+ if (iflist==NULL || numifs<=0) {
+ log_write(LOG_PLAIN, "INTERFACES: NONE FOUND(!)\n");
+ if (o.debugging)
+ log_write(LOG_STDOUT, "Reason: %s\n", errstr);
+ } else {
+ int devcol = 0, shortdevcol = 1, ipcol = 2, typecol = 3, upcol = 4, mtucol = 5, maccol = 6;
+ Tbl = new NmapOutputTable(numifs + 1, 7);
+ Tbl->addItem(0, devcol, false, "DEV", 3);
+ Tbl->addItem(0, shortdevcol, false, "(SHORT)", 7);
+ Tbl->addItem(0, ipcol, false, "IP/MASK", 7);
+ Tbl->addItem(0, typecol, false, "TYPE", 4);
+ Tbl->addItem(0, upcol, false, "UP", 2);
+ Tbl->addItem(0, mtucol, false, "MTU", 3);
+ Tbl->addItem(0, maccol, false, "MAC", 3);
+ for (i = 0; i < numifs; i++) {
+ Tbl->addItem(i + 1, devcol, false, iflist[i].devfullname);
+ Tbl->addItemFormatted(i + 1, shortdevcol, false, "(%s)",
+ iflist[i].devname);
+ address = inet_ntop_ez(&(iflist[i].addr), sizeof(iflist[i].addr));
+ Tbl->addItemFormatted(i + 1, ipcol, false, "%s/%d",
+ address == NULL ? "(none)" : address,
+ iflist[i].netmask_bits);
+ if (iflist[i].device_type == devt_ethernet) {
+ Tbl->addItem(i + 1, typecol, false, "ethernet");
+ Tbl->addItemFormatted(i + 1, maccol, false,
+ "%02X:%02X:%02X:%02X:%02X:%02X",
+ iflist[i].mac[0], iflist[i].mac[1],
+ iflist[i].mac[2], iflist[i].mac[3],
+ iflist[i].mac[4], iflist[i].mac[5]);
+ } else if (iflist[i].device_type == devt_loopback)
+ Tbl->addItem(i + 1, typecol, false, "loopback");
+ else if (iflist[i].device_type == devt_p2p)
+ Tbl->addItem(i + 1, typecol, false, "point2point");
+ else
+ Tbl->addItem(i + 1, typecol, false, "other");
+ Tbl->addItem(i + 1, upcol, false,
+ (iflist[i].device_up ? "up" : "down"));
+ Tbl->addItemFormatted(i + 1, mtucol, false, "%d", iflist[i].mtu);
+ }
+ log_write(LOG_PLAIN, "************************INTERFACES************************\n");
+ log_write(LOG_PLAIN, "%s\n", Tbl->printableTable(NULL));
+ log_flush_all();
+ delete Tbl;
+ }
+
+#ifdef WIN32
+ /* Print the libdnet->libpcap interface name mapping. */
+ print_iflist_pcap_mapping(iflist, numifs);
+#endif
+
+ /* OK -- time to handle routes */
+ errstr[0]='\0';
+ routes = getsysroutes(&numroutes, errstr, sizeof(errstr));
+ u16 nbits;
+ if (routes==NULL || numroutes<= 0) {
+ log_write(LOG_PLAIN, "ROUTES: NONE FOUND(!)\n");
+ if (o.debugging)
+ log_write(LOG_STDOUT, "Reason: %s\n", errstr);
+ } else {
+ int dstcol = 0, devcol = 1, metcol = 2, gwcol = 3;
+ Tbl = new NmapOutputTable(numroutes + 1, 4);
+ Tbl->addItem(0, dstcol, false, "DST/MASK", 8);
+ Tbl->addItem(0, devcol, false, "DEV", 3);
+ Tbl->addItem(0, metcol, false, "METRIC", 6);
+ Tbl->addItem(0, gwcol, false, "GATEWAY", 7);
+ for (i = 0; i < numroutes; i++) {
+ nbits = routes[i].netmask_bits;
+ Tbl->addItemFormatted(i + 1, dstcol, false, "%s/%d",
+ inet_ntop_ez(&routes[i].dest, sizeof(routes[i].dest)), nbits);
+ Tbl->addItem(i + 1, devcol, false, routes[i].device->devfullname);
+ Tbl->addItemFormatted(i + 1, metcol, false, "%d", routes[i].metric);
+ if (!sockaddr_equal_zero(&routes[i].gw))
+ Tbl->addItem(i + 1, gwcol, true, inet_ntop_ez(&routes[i].gw, sizeof(routes[i].gw)));
+ }
+ log_write(LOG_PLAIN, "**************************ROUTES**************************\n");
+ log_write(LOG_PLAIN, "%s\n", Tbl->printableTable(NULL));
+ log_flush_all();
+ delete Tbl;
+ }
+ return 0;
+}
+
+#ifndef NOLUA
+/* Escape control characters to make a string safe to display on a terminal. */
+static std::string escape_for_screen(const std::string s) {
+ std::string r;
+
+ for (unsigned int i = 0; i < s.size(); i++) {
+ char buf[5];
+ unsigned char c = s[i];
+ // Printable and some whitespace ok. "\r" not ok because it overwrites the line.
+ if (c == '\t' || c == '\n' || (0x20 <= c && c <= 0x7e)) {
+ r += c;
+ } else {
+ Snprintf(buf, sizeof(buf), "\\x%02X", c);
+ r += buf;
+ }
+ }
+
+ return r;
+}
+
+/* Do something to protect characters that can't appear in XML. This is not a
+ reversible transform, more a last-ditch effort to write readable XML with
+ characters that shouldn't be part of regular output anyway. The escaping that
+ xml_write_escaped is not enough; some characters are not allowed to appear in
+ XML, not even escaped. */
+std::string protect_xml(const std::string s) {
+ std::string r;
+
+ for (unsigned int i = 0; i < s.size(); i++) {
+ char buf[5];
+ unsigned char c = s[i];
+ // Printable and some whitespace ok.
+ if (c == '\t' || c == '\r' || c == '\n' || (0x20 <= c && c <= 0x7e)) {
+ r += c;
+ } else {
+ Snprintf(buf, sizeof(buf), "\\x%02X", c);
+ r += buf;
+ }
+ }
+
+ return r;
+}
+
+static char *formatScriptOutput(const ScriptResult *sr) {
+ std::vector<std::string> lines;
+
+ std::string c_output;
+ const char *p, *q;
+ std::string result;
+ unsigned int i;
+
+ c_output = escape_for_screen(sr->get_output_str());
+ if (c_output.empty())
+ return NULL;
+ p = c_output.c_str();
+
+ while (*p != '\0') {
+ q = strchr(p, '\n');
+ if (q == NULL) {
+ lines.push_back(std::string(p));
+ break;
+ } else {
+ lines.push_back(std::string(p, q - p));
+ p = q + 1;
+ }
+ }
+
+ if (lines.empty())
+ lines.push_back("");
+ for (i = 0; i < lines.size(); i++) {
+ if (i < lines.size() - 1)
+ result += "| ";
+ else
+ result += "|_";
+ if (i == 0)
+ result += std::string(sr->get_id()) + ": ";
+ result += lines[i];
+ if (i < lines.size() - 1)
+ result += "\n";
+ }
+
+ return strdup(result.c_str());
+}
+#endif /* NOLUA */
+
+/* Output a list of ports, compressing ranges like 80-85 */
+static void output_rangelist_given_ports(int logt, const unsigned short *ports, int numports);
+
+/* Prints the familiar Nmap tabular output showing the "interesting"
+ ports found on the machine. It also handles the Machine/Grepable
+ output and the XML output. It is pretty ugly -- in particular I
+ should write helper functions to handle the table creation */
+void printportoutput(const Target *currenths, const PortList *plist) {
+ char protocol[MAX_IPPROTOSTRLEN + 1];
+ char portinfo[64];
+ char grepvers[256];
+ char *p;
+ const char *state;
+ char serviceinfo[64];
+ int i;
+ int first = 1;
+ const struct nprotoent *proto;
+ Port *current;
+ Port port;
+ char hostname[1200];
+ struct serviceDeductions sd;
+ NmapOutputTable *Tbl = NULL;
+ int portcol = -1; // port or IP protocol #
+ int statecol = -1; // port/protocol state
+ int servicecol = -1; // service or protocol name
+ int versioncol = -1;
+ int reasoncol = -1;
+ int colno = 0;
+ unsigned int rowno;
+ int numrows;
+ int numignoredports = plist->numIgnoredPorts();
+ int numports = plist->numPorts();
+ state_reason_summary_t *reasons, *currentr;
+
+ std::vector<const char *> saved_servicefps;
+
+ if (o.noportscan || numports == 0)
+ return;
+
+ xml_start_tag("ports");
+ log_write(LOG_MACHINE, "Host: %s (%s)", currenths->targetipstr(),
+ currenths->HostName());
+
+ if ((o.verbose > 1 || o.debugging) && currenths->StartTime()) {
+ time_t tm_secs, tm_sece;
+ struct tm tm;
+ int err;
+ char tbufs[128];
+ tm_secs = currenths->StartTime();
+ tm_sece = currenths->EndTime();
+ err = n_localtime(&tm_secs, &tm);
+ if (err) {
+ error("Error in localtime: %s", strerror(err));
+ log_write(LOG_PLAIN, "Scanned for %lds\n",
+ (long) (tm_sece - tm_secs));
+ }
+ else {
+ if (strftime(tbufs, sizeof(tbufs), "%Y-%m-%d %H:%M:%S %Z", &tm) <= 0) {
+ error("Unable to properly format host start time");
+ log_write(LOG_PLAIN, "Scanned for %lds\n",
+ (long) (tm_sece - tm_secs));
+ }
+ else {
+ log_write(LOG_PLAIN, "Scanned at %s for %lds\n",
+ tbufs, (long) (tm_sece - tm_secs));
+ }
+ }
+ }
+
+ int prevstate = PORT_UNKNOWN;
+ int istate;
+
+ while ((istate = plist->nextIgnoredState(prevstate)) != PORT_UNKNOWN) {
+ i = plist->getStateCounts(istate);
+ xml_open_start_tag("extraports");
+ xml_attribute("state", "%s", statenum2str(istate));
+ xml_attribute("count", "%d", i);
+ xml_close_start_tag();
+ xml_newline();
+
+ /* Show line like:
+ Not shown: 98 open|filtered udp ports (no-response), 59 closed tcp ports (reset)
+ if appropriate (note that states are reverse-sorted by # of ports) */
+ if (prevstate == PORT_UNKNOWN) {
+ // First time through, check special case
+ if (numignoredports == numports) {
+ log_write(LOG_PLAIN, "All %d scanned ports on %s are in ignored states.\n",
+ numignoredports, currenths->NameIP(hostname, sizeof(hostname)));
+ log_write(LOG_MACHINE, "\t%s: ", (o.ipprotscan) ? "Protocols" : "Ports");
+ /* Grepable output supports only one ignored state. */
+ if (plist->numIgnoredStates() == 1) {
+ log_write(LOG_MACHINE, "\tIgnored State: %s (%d)", statenum2str(istate), i);
+ }
+ }
+ log_write(LOG_PLAIN, "Not shown: ");
+ } else {
+ log_write(LOG_PLAIN, ", ");
+ }
+
+ if((currentr = reasons = get_state_reason_summary(plist, istate)) == NULL) {
+ log_write(LOG_PLAIN, "%d %s %s%s", i, statenum2str(istate),
+ o.ipprotscan ? "protocol" : "port",
+ plist->getStateCounts(istate) == 1 ? "" : "s");
+ prevstate = istate;
+ continue;
+ }
+
+ while(currentr != NULL) {
+ if(currentr->count > 0) {
+ xml_open_start_tag("extrareasons");
+ xml_attribute("reason", "%s", reason_str(currentr->reason_id, SINGULAR));
+ xml_attribute("count", "%d", currentr->count);
+ xml_attribute("proto", "%s", IPPROTO2STR(currentr->proto));
+ xml_write_raw(" ports=\"");
+ output_rangelist_given_ports(LOG_XML, currentr->ports, currentr->count);
+ xml_write_raw("\"");
+ xml_close_empty_tag();
+ xml_newline();
+
+ if (currentr != reasons)
+ log_write(LOG_PLAIN, ", ");
+ log_write(LOG_PLAIN, "%d %s %s %s%s (%s)",
+ currentr->count, statenum2str(istate), IPPROTO2STR(currentr->proto),
+ o.ipprotscan ? "protocol" : "port",
+ plist->getStateCounts(istate) == 1 ? "" : "s",
+ reason_str(currentr->reason_id, SINGULAR));
+ }
+ currentr = currentr->next;
+ }
+ state_reason_summary_dinit(reasons);
+ xml_end_tag();
+ xml_newline();
+ prevstate = istate;
+ }
+
+ log_write(LOG_PLAIN, "\n");
+
+ if (numignoredports == numports) {
+ // Nothing left to show.
+ xml_end_tag(); /* ports */
+ xml_newline();
+ log_flush_all();
+ return;
+ }
+
+ /* OK, now it is time to deal with the service table ... */
+ colno = 0;
+ portcol = colno++;
+ statecol = colno++;
+ servicecol = colno++;
+ if (o.reason)
+ reasoncol = colno++;
+ if (o.servicescan)
+ versioncol = colno++;
+
+ numrows = numports - numignoredports;
+
+#ifndef NOLUA
+ int scriptrows = 0;
+ if (plist->numscriptresults > 0)
+ scriptrows = plist->numscriptresults;
+ numrows += scriptrows;
+#endif
+
+ assert(numrows > 0);
+ numrows++; // The header counts as a row
+
+ Tbl = new NmapOutputTable(numrows, colno);
+
+ // Lets start with the headers
+ if (o.ipprotscan)
+ Tbl->addItem(0, portcol, false, "PROTOCOL", 8);
+ else
+ Tbl->addItem(0, portcol, false, "PORT", 4);
+ Tbl->addItem(0, statecol, false, "STATE", 5);
+ Tbl->addItem(0, servicecol, false, "SERVICE", 7);
+ if (versioncol > 0)
+ Tbl->addItem(0, versioncol, false, "VERSION", 7);
+ if (reasoncol > 0)
+ Tbl->addItem(0, reasoncol, false, "REASON", 6);
+
+ log_write(LOG_MACHINE, "\t%s: ", (o.ipprotscan) ? "Protocols" : "Ports");
+
+ rowno = 1;
+ if (o.ipprotscan) {
+ current = NULL;
+ while ((current = plist->nextPort(current, &port, IPPROTO_IP, 0)) != NULL) {
+ if (!plist->isIgnoredState(current->state, NULL)) {
+ if (!first)
+ log_write(LOG_MACHINE, ", ");
+ else
+ first = 0;
+ if (o.reason) {
+ if (current->reason.ttl)
+ Tbl->addItemFormatted(rowno, reasoncol, false, "%s ttl %d",
+ port_reason_str(current->reason), current->reason.ttl);
+ else
+ Tbl->addItem(rowno, reasoncol, true, port_reason_str(current->reason));
+ }
+ state = statenum2str(current->state);
+ proto = nmap_getprotbynum(current->portno);
+ Snprintf(portinfo, sizeof(portinfo), "%s", proto ? proto->p_name : "unknown");
+ Tbl->addItemFormatted(rowno, portcol, false, "%d", current->portno);
+ Tbl->addItem(rowno, statecol, true, state);
+ Tbl->addItem(rowno, servicecol, true, portinfo);
+ log_write(LOG_MACHINE, "%d/%s/%s/", current->portno, state,
+ (proto) ? proto->p_name : "");
+ xml_open_start_tag("port");
+ xml_attribute("protocol", "ip");
+ xml_attribute("portid", "%d", current->portno);
+ xml_close_start_tag();
+ xml_open_start_tag("state");
+ xml_attribute("state", "%s", state);
+ xml_attribute("reason", "%s", reason_str(current->reason.reason_id, SINGULAR));
+ xml_attribute("reason_ttl", "%d", current->reason.ttl);
+ if (current->reason.ip_addr.sockaddr.sa_family != AF_UNSPEC) {
+ struct sockaddr_storage ss;
+ memcpy(&ss, &current->reason.ip_addr, sizeof(current->reason.ip_addr));
+ xml_attribute("reason_ip", "%s", inet_ntop_ez(&ss, sizeof(ss)));
+ }
+ xml_close_empty_tag();
+
+ if (proto && proto->p_name && *proto->p_name) {
+ xml_newline();
+ xml_open_start_tag("service");
+ xml_attribute("name", "%s", proto->p_name);
+ xml_attribute("conf", "8");
+ xml_attribute("method", "table");
+ xml_close_empty_tag();
+ }
+ xml_end_tag(); /* port */
+ xml_newline();
+ rowno++;
+ }
+ }
+ } else {
+ char fullversion[160];
+
+ current = NULL;
+ while ((current = plist->nextPort(current, &port, TCPANDUDPANDSCTP, 0)) != NULL) {
+ if (!plist->isIgnoredState(current->state, NULL)) {
+ if (!first)
+ log_write(LOG_MACHINE, ", ");
+ else
+ first = 0;
+ strcpy(protocol, IPPROTO2STR(current->proto));
+ Snprintf(portinfo, sizeof(portinfo), "%d/%s", current->portno, protocol);
+ state = statenum2str(current->state);
+ plist->getServiceDeductions(current->portno, current->proto, &sd);
+ if (sd.service_fp && saved_servicefps.size() <= 8)
+ saved_servicefps.push_back(sd.service_fp);
+
+ current->getNmapServiceName(serviceinfo, sizeof(serviceinfo));
+
+ Tbl->addItem(rowno, portcol, true, portinfo);
+ Tbl->addItem(rowno, statecol, false, state);
+ Tbl->addItem(rowno, servicecol, true, serviceinfo);
+ if (o.reason) {
+ if (current->reason.ttl)
+ Tbl->addItemFormatted(rowno, reasoncol, false, "%s ttl %d",
+ port_reason_str(current->reason), current->reason.ttl);
+ else
+ Tbl->addItem(rowno, reasoncol, true, port_reason_str(current->reason));
+ }
+
+ sd.populateFullVersionString(fullversion, sizeof(fullversion));
+ if (*fullversion && versioncol > 0)
+ Tbl->addItem(rowno, versioncol, true, fullversion);
+
+ // How should we escape illegal chars in grepable output?
+ // Well, a reasonably clean way would be backslash escapes
+ // such as \/ and \\ . // But that makes it harder to pick
+ // out fields with awk, cut, and such. So I'm gonna use the
+ // ugly hack (fitting to grepable output) of replacing the '/'
+ // character with '|' in the version field.
+ Strncpy(grepvers, fullversion, sizeof(grepvers) / sizeof(*grepvers));
+ p = grepvers;
+ while ((p = strchr(p, '/'))) {
+ *p = '|';
+ p++;
+ }
+ if (sd.name || sd.service_fp || sd.service_tunnel != SERVICE_TUNNEL_NONE) {
+ p = serviceinfo;
+ while ((p = strchr(p, '/'))) {
+ *p = '|';
+ p++;
+ }
+ }
+ else {
+ serviceinfo[0] = '\0';
+ }
+ log_write(LOG_MACHINE, "%d/%s/%s//%s//%s/", current->portno,
+ state, protocol, serviceinfo, grepvers);
+
+ xml_open_start_tag("port");
+ xml_attribute("protocol", "%s", protocol);
+ xml_attribute("portid", "%d", current->portno);
+ xml_close_start_tag();
+ xml_open_start_tag("state");
+ xml_attribute("state", "%s", state);
+ xml_attribute("reason", "%s", reason_str(current->reason.reason_id, SINGULAR));
+ xml_attribute("reason_ttl", "%d", current->reason.ttl);
+ if (current->reason.ip_addr.sockaddr.sa_family != AF_UNSPEC) {
+ struct sockaddr_storage ss;
+ memcpy(&ss, &current->reason.ip_addr, sizeof(current->reason.ip_addr));
+ xml_attribute("reason_ip", "%s", inet_ntop_ez(&ss, sizeof(ss)));
+ }
+ xml_close_empty_tag();
+
+ if (sd.name || sd.service_fp || sd.service_tunnel != SERVICE_TUNNEL_NONE)
+ print_xml_service(&sd);
+
+ rowno++;
+#ifndef NOLUA
+ if (o.script) {
+ ScriptResults::const_iterator ssr_iter;
+ for (ssr_iter = current->scriptResults.begin();
+ ssr_iter != current->scriptResults.end(); ssr_iter++) {
+ (*ssr_iter)->write_xml();
+
+ char *script_output = formatScriptOutput((*ssr_iter));
+ if (script_output != NULL) {
+ Tbl->addItem(rowno, 0, true, true, script_output);
+ free(script_output);
+ }
+ rowno++;
+ }
+
+ }
+#endif
+
+ xml_end_tag(); /* port */
+ xml_newline();
+ }
+ }
+
+ }
+ /* log_write(LOG_PLAIN,"\n"); */
+ /* Grepable output supports only one ignored state. */
+ if (plist->numIgnoredStates() == 1) {
+ istate = plist->nextIgnoredState(PORT_UNKNOWN);
+ if (plist->getStateCounts(istate) > 0)
+ log_write(LOG_MACHINE, "\tIgnored State: %s (%d)",
+ statenum2str(istate), plist->getStateCounts(istate));
+ }
+ xml_end_tag(); /* ports */
+ xml_newline();
+
+ if (o.defeat_rst_ratelimit && o.TCPScan() && plist->getStateCounts(PORT_FILTERED) > 0) {
+ log_write(LOG_PLAIN, "Some closed ports may be reported as filtered due to --defeat-rst-ratelimit\n");
+ }
+
+ // Now we write the table for the user
+ log_write(LOG_PLAIN, "%s", Tbl->printableTable(NULL));
+ delete Tbl;
+
+ // There may be service fingerprints I would like the user to submit
+ if (saved_servicefps.size() > 0) {
+ int numfps = saved_servicefps.size();
+ log_write(LOG_PLAIN, "%d service%s unrecognized despite returning data."
+ " If you know the service/version, please submit the following"
+ " fingerprint%s at"
+ " https://nmap.org/cgi-bin/submit.cgi?new-service :\n",
+ numfps, (numfps > 1) ? "s" : "", (numfps > 1) ? "s" : "");
+ for (i = 0; i < numfps; i++) {
+ if (numfps > 1)
+ log_write(LOG_PLAIN, "==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============\n");
+ log_write(LOG_PLAIN, "%s\n", saved_servicefps[i]);
+ }
+ }
+ log_flush_all();
+}
+
+
+/* MAX_STRFTIME_EXPANSION is the maximum length that a single %_ escape can
+ * expand to, not including null terminator. If you add another supported
+ * escape, check that it doesn't exceed this value, otherwise increase it.
+ */
+#define MAX_STRFTIME_EXPANSION 10
+char *logfilename(const char *str, struct tm *tm) {
+ char *ret, *end, *p;
+ // Max expansion: "%F" => "YYYY-mm-dd"
+ int retlen = strlen(str) * (MAX_STRFTIME_EXPANSION - 2) + 1;
+ size_t written = 0;
+
+ ret = (char *) safe_malloc(retlen);
+ end = ret + retlen;
+
+ for (p = ret; *str; str++) {
+ if (*str == '%') {
+ str++;
+ written = 0;
+
+ if (!*str)
+ break;
+
+#define FTIME_CASE(_fmt, _fmt_str) case _fmt: \
+ written = strftime(p, end - p, _fmt_str, tm); \
+ break;
+
+ switch (*str) {
+ FTIME_CASE('H', "%H");
+ FTIME_CASE('M', "%M");
+ FTIME_CASE('S', "%S");
+ FTIME_CASE('T', "%H%M%S");
+ FTIME_CASE('R', "%H%M");
+ FTIME_CASE('m', "%m");
+ FTIME_CASE('d', "%d");
+ FTIME_CASE('y', "%y");
+ FTIME_CASE('Y', "%Y");
+ FTIME_CASE('D', "%m%d%y");
+ FTIME_CASE('F', "%Y-%m-%d");
+ default:
+ *p++ = *str;
+ continue;
+ }
+
+ assert(end - p > 1);
+ p += written;
+ } else {
+ *p++ = *str;
+ }
+ }
+
+ *p = 0;
+
+ return (char *) safe_realloc(ret, strlen(ret) + 1);
+}
+
+/* This is the workhorse of the logging functions. Usually it is
+ called through log_write(), but it can be called directly if you are dealing
+ with a vfprintf-style va_list. YOU MUST SANDWICH EACH EXECUTION OF THIS CALL
+ BETWEEN va_start() AND va_end() calls. */
+void log_vwrite(int logt, const char *fmt, va_list ap) {
+ char *writebuf;
+ bool skid_noxlate = false;
+ int rc = 0;
+ int len;
+ int fileidx = 0;
+ int l;
+ int logtype;
+ va_list apcopy;
+
+ for (logtype = 1; logtype <= LOG_MAX; logtype <<= 1) {
+
+ if (!(logt & logtype))
+ continue;
+
+ switch (logtype) {
+ case LOG_STDOUT:
+ vfprintf(o.nmap_stdout, fmt, ap);
+ break;
+
+ case LOG_STDERR:
+ fflush(stdout); // Otherwise some systems will print stderr out of order
+ vfprintf(stderr, fmt, ap);
+ break;
+
+ case LOG_SKID_NOXLT:
+ skid_noxlate = true;
+ /* no break */
+ case LOG_NORMAL:
+ case LOG_MACHINE:
+ case LOG_SKID:
+ case LOG_XML:
+ if (logtype == LOG_SKID_NOXLT)
+ l = LOG_SKID;
+ else
+ l = logtype;
+ fileidx = 0;
+ while ((l & 1) == 0) {
+ fileidx++;
+ l >>= 1;
+ }
+ assert(fileidx < LOG_NUM_FILES);
+ if (o.logfd[fileidx]) {
+ len = alloc_vsprintf(&writebuf, fmt, ap);
+ if (writebuf == NULL)
+ fatal("%s: alloc_vsprintf failed.", __func__);
+ if (len) {
+ if ((logtype & (LOG_SKID|LOG_SKID_NOXLT)) && !skid_noxlate)
+ skid_output(writebuf);
+
+ rc = fwrite(writebuf, len, 1, o.logfd[fileidx]);
+ if (rc != 1) {
+ fatal("Failed to write %d bytes of data to (logt==%d) stream. fwrite returned %d. Quitting.", len, logtype, rc);
+ }
+ va_end(apcopy);
+ }
+ free(writebuf);
+ }
+ break;
+
+ default:
+ /* Unknown log type.
+ * ---
+ * Note that we're not calling fatal() here to avoid infinite call loop
+ * between fatal() and this log_vwrite() function. */
+ assert(0); /* We want people to report it. */
+ }
+ }
+
+ return;
+}
+
+/* Write some information (printf style args) to the given log stream(s).
+ Remember to watch out for format string bugs. */
+void log_write(int logt, const char *fmt, ...) {
+ va_list ap;
+ assert(logt > 0);
+
+ if (!fmt || !*fmt)
+ return;
+
+ for (int l = 1; l <= LOG_MAX; l <<= 1) {
+ if (logt & l) {
+ va_start(ap, fmt);
+ log_vwrite(l, fmt, ap);
+ va_end(ap);
+ }
+ }
+ return;
+}
+
+/* Close the given log stream(s) */
+void log_close(int logt) {
+ int i;
+ if (logt < 0 || logt > LOG_FILE_MASK)
+ return;
+ for (i = 0; logt; logt >>= 1, i++)
+ if (o.logfd[i] && (logt & 1))
+ fclose(o.logfd[i]);
+}
+
+/* Flush the given log stream(s). In other words, all buffered output
+ is written to the log immediately */
+void log_flush(int logt) {
+ int i;
+
+ if (logt & LOG_STDOUT) {
+ fflush(o.nmap_stdout);
+ logt -= LOG_STDOUT;
+ }
+
+ if (logt & LOG_STDERR) {
+ fflush(stderr);
+ logt -= LOG_STDERR;
+ }
+
+ if (logt & LOG_SKID_NOXLT)
+ fatal("You are not allowed to %s() with LOG_SKID_NOXLT", __func__);
+
+ if (logt < 0 || logt > LOG_FILE_MASK)
+ return;
+
+ for (i = 0; logt; logt >>= 1, i++) {
+ if (!o.logfd[i] || !(logt & 1))
+ continue;
+ fflush(o.logfd[i]);
+ }
+
+}
+
+/* Flush every single log stream -- all buffered output is written to the
+ corresponding logs immediately */
+void log_flush_all() {
+ int fileno;
+
+ for (fileno = 0; fileno < LOG_NUM_FILES; fileno++) {
+ if (o.logfd[fileno])
+ fflush(o.logfd[fileno]);
+ }
+ fflush(stdout);
+ fflush(stderr);
+}
+
+/* Open a log descriptor of the type given to the filename given. If
+ append is true, the file will be appended instead of clobbered if
+ it already exists. If the file does not exist, it will be created */
+int log_open(int logt, bool append, const char *filename) {
+ int i = 0;
+ if (logt <= 0 || logt > LOG_FILE_MASK)
+ return -1;
+ while ((logt & 1) == 0) {
+ i++;
+ logt >>= 1;
+ }
+ if (o.logfd[i])
+ fatal("Only one %s output filename allowed", logtypes[i]);
+ if (*filename == '-' && *(filename + 1) == '\0') {
+ o.logfd[i] = stdout;
+ o.nmap_stdout = fopen(DEVNULL, "w");
+ if (!o.nmap_stdout)
+ pfatal("Could not assign %s to stdout for writing", DEVNULL);
+ } else {
+ if (append)
+ o.logfd[i] = fopen(filename, "a");
+ else
+ o.logfd[i] = fopen(filename, "w");
+ if (!o.logfd[i])
+ pfatal("Failed to open %s output file %s for writing", logtypes[i],
+ filename);
+ }
+ return 1;
+}
+
+
+/* The items in ports should be
+ in sequential order for space savings and easier to read output. Outputs the
+ rangelist to the log stream given (such as LOG_MACHINE or LOG_XML) */
+static void output_rangelist_given_ports(int logt, const unsigned short *ports,
+ int numports) {
+ int start, end;
+
+ start = 0;
+ while (start < numports) {
+ end = start;
+ while (end + 1 < numports && ports[end + 1] == ports[end] + 1)
+ end++;
+ if (start > 0)
+ log_write(logt, ",");
+ if (start == end)
+ log_write(logt, "%hu", ports[start]);
+ else
+ log_write(logt, "%hu-%hu", ports[start], ports[end]);
+ start = end + 1;
+ }
+}
+
+/* Output the list of ports scanned to the top of machine parseable
+ logs (in a comment, unfortunately). The items in ports should be
+ in sequential order for space savings and easier to read output */
+void output_ports_to_machine_parseable_output(const struct scan_lists *ports) {
+ int tcpportsscanned = ports->tcp_count;
+ int udpportsscanned = ports->udp_count;
+ int sctpportsscanned = ports->sctp_count;
+ int protsscanned = ports->prot_count;
+ log_write(LOG_MACHINE, "# Ports scanned: TCP(%d;", tcpportsscanned);
+ if (tcpportsscanned)
+ output_rangelist_given_ports(LOG_MACHINE, ports->tcp_ports, tcpportsscanned);
+ log_write(LOG_MACHINE, ") UDP(%d;", udpportsscanned);
+ if (udpportsscanned)
+ output_rangelist_given_ports(LOG_MACHINE, ports->udp_ports, udpportsscanned);
+ log_write(LOG_MACHINE, ") SCTP(%d;", sctpportsscanned);
+ if (sctpportsscanned)
+ output_rangelist_given_ports(LOG_MACHINE, ports->sctp_ports, sctpportsscanned);
+ log_write(LOG_MACHINE, ") PROTOCOLS(%d;", protsscanned);
+ if (protsscanned)
+ output_rangelist_given_ports(LOG_MACHINE, ports->prots, protsscanned);
+ log_write(LOG_MACHINE, ")\n");
+ log_flush_all();
+}
+
+// A simple helper function for doscaninfo handles the c14n of o.scanflags
+static void doscanflags() {
+ struct {
+ unsigned char flag;
+ const char *name;
+ } flags[] = {
+ { TH_FIN, "FIN" },
+ { TH_SYN, "SYN" },
+ { TH_RST, "RST" },
+ { TH_PUSH, "PSH" },
+ { TH_ACK, "ACK" },
+ { TH_URG, "URG" },
+ { TH_ECE, "ECE" },
+ { TH_CWR, "CWR" }
+ };
+
+ if (o.scanflags != -1) {
+ std::string flagstring;
+
+ for (unsigned int i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
+ if (o.scanflags & flags[i].flag)
+ flagstring += flags[i].name;
+ }
+ xml_attribute("scanflags", "%s", flagstring.c_str());
+ }
+}
+
+/* Simple helper function for output_xml_scaninfo_records */
+static void doscaninfo(const char *type, const char *proto,
+ const unsigned short *ports, int numports) {
+ xml_open_start_tag("scaninfo");
+ xml_attribute("type", "%s", type);
+ if (strncmp(proto, "tcp", 3) == 0) {
+ doscanflags();
+ }
+ xml_attribute("protocol", "%s", proto);
+ xml_attribute("numservices", "%d", numports);
+ xml_write_raw(" services=\"");
+ output_rangelist_given_ports(LOG_XML, ports, numports);
+ xml_write_raw("\"");
+ xml_close_empty_tag();
+ xml_newline();
+}
+
+static std::string quote(const char *s) {
+ std::string result("");
+ const char *p;
+ bool space;
+
+ space = false;
+ for (p = s; *p != '\0'; p++) {
+ if (isspace(*p))
+ space = true;
+ if (*p == '"' || *p == '\\')
+ result += "\\";
+ result += *p;
+ }
+
+ if (space)
+ result = "\"" + result + "\"";
+
+ return result;
+}
+
+/* Return a std::string containing all n strings separated by whitespace, and
+ individually quoted if needed. */
+std::string join_quoted(const char * const strings[], unsigned int n) {
+ std::string result("");
+ unsigned int i;
+
+ for (i = 0; i < n; i++) {
+ if (i > 0)
+ result += " ";
+ result += quote(strings[i]);
+ }
+
+ return result;
+}
+
+/* Similar to output_ports_to_machine_parseable_output, this function
+ outputs the XML version, which is scaninfo records of each scan
+ requested and the ports which it will scan for */
+void output_xml_scaninfo_records(const struct scan_lists *scanlist) {
+ if (o.synscan)
+ doscaninfo("syn", "tcp", scanlist->tcp_ports, scanlist->tcp_count);
+ if (o.ackscan)
+ doscaninfo("ack", "tcp", scanlist->tcp_ports, scanlist->tcp_count);
+ if (o.bouncescan)
+ doscaninfo("bounce", "tcp", scanlist->tcp_ports, scanlist->tcp_count);
+ if (o.connectscan)
+ doscaninfo("connect", "tcp", scanlist->tcp_ports, scanlist->tcp_count);
+ if (o.nullscan)
+ doscaninfo("null", "tcp", scanlist->tcp_ports, scanlist->tcp_count);
+ if (o.xmasscan)
+ doscaninfo("xmas", "tcp", scanlist->tcp_ports, scanlist->tcp_count);
+ if (o.windowscan)
+ doscaninfo("window", "tcp", scanlist->tcp_ports, scanlist->tcp_count);
+ if (o.maimonscan)
+ doscaninfo("maimon", "tcp", scanlist->tcp_ports, scanlist->tcp_count);
+ if (o.finscan)
+ doscaninfo("fin", "tcp", scanlist->tcp_ports, scanlist->tcp_count);
+ if (o.udpscan)
+ doscaninfo("udp", "udp", scanlist->udp_ports, scanlist->udp_count);
+ if (o.sctpinitscan)
+ doscaninfo("sctpinit", "sctp", scanlist->sctp_ports, scanlist->sctp_count);
+ if (o.sctpcookieechoscan)
+ doscaninfo("sctpcookieecho", "sctp", scanlist->sctp_ports, scanlist->sctp_count);
+ if (o.ipprotscan)
+ doscaninfo("ipproto", "ip", scanlist->prots, scanlist->prot_count);
+ log_flush_all();
+}
+
+/* Prints the MAC address (if discovered) to XML output */
+static void print_MAC_XML_Info(const Target *currenths) {
+ const u8 *mac = currenths->MACAddress();
+ char macascii[32];
+
+ if (mac) {
+ const char *macvendor = MACPrefix2Corp(mac);
+ Snprintf(macascii, sizeof(macascii), "%02X:%02X:%02X:%02X:%02X:%02X",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ xml_open_start_tag("address");
+ xml_attribute("addr", "%s", macascii);
+ xml_attribute("addrtype", "mac");
+ if (macvendor)
+ xml_attribute("vendor", "%s", macvendor);
+ xml_close_empty_tag();
+ xml_newline();
+ }
+}
+
+/* Helper function to write the status and address/hostname info of a host
+ into the XML log */
+static void write_xml_initial_hostinfo(const Target *currenths,
+ const char *status) {
+ xml_open_start_tag("status");
+ xml_attribute("state", "%s", status);
+ xml_attribute("reason", "%s", reason_str(currenths->reason.reason_id, SINGULAR));
+ xml_attribute("reason_ttl", "%d", currenths->reason.ttl);
+ xml_close_empty_tag();
+ xml_newline();
+ xml_open_start_tag("address");
+ xml_attribute("addr", "%s", currenths->targetipstr());
+ xml_attribute("addrtype", "%s", (o.af() == AF_INET) ? "ipv4" : "ipv6");
+ xml_close_empty_tag();
+ xml_newline();
+ print_MAC_XML_Info(currenths);
+ /* Output a hostnames element whenever we have a name to write or the target
+ is up. */
+ if (currenths->TargetName() != NULL || *currenths->HostName() || strcmp(status, "up") == 0) {
+ xml_start_tag("hostnames");
+ xml_newline();
+ if (currenths->TargetName() != NULL) {
+ xml_open_start_tag("hostname");
+ xml_attribute("name", "%s", currenths->TargetName());
+ xml_attribute("type", "user");
+ xml_close_empty_tag();
+ xml_newline();
+ }
+ if (*currenths->HostName()) {
+ xml_open_start_tag("hostname");
+ xml_attribute("name", "%s", currenths->HostName());
+ xml_attribute("type", "PTR");
+ xml_close_empty_tag();
+ xml_newline();
+ }
+ xml_end_tag();
+ xml_newline();
+ }
+ log_flush_all();
+}
+
+void write_xml_hosthint(const Target *currenths) {
+ xml_start_tag("hosthint");
+ write_xml_initial_hostinfo(currenths, (currenths->flags & HOST_UP) ? "up" : "down");
+ xml_end_tag();
+ xml_newline();
+ log_flush_all();
+}
+
+static void write_xml_osclass(const OS_Classification *osclass, double accuracy) {
+ xml_open_start_tag("osclass");
+ xml_attribute("type", "%s", osclass->Device_Type);
+ xml_attribute("vendor", "%s", osclass->OS_Vendor);
+ xml_attribute("osfamily", "%s", osclass->OS_Family);
+ // Because the OS_Generation field is optional.
+ if (osclass->OS_Generation)
+ xml_attribute("osgen", "%s", osclass->OS_Generation);
+ xml_attribute("accuracy", "%d", (int) (accuracy * 100));
+ if (osclass->cpe.empty()) {
+ xml_close_empty_tag();
+ } else {
+ unsigned int i;
+
+ xml_close_start_tag();
+ for (i = 0; i < osclass->cpe.size(); i++) {
+ xml_start_tag("cpe");
+ xml_write_escaped("%s", osclass->cpe[i]);
+ xml_end_tag();
+ }
+ xml_end_tag();
+ }
+ xml_newline();
+}
+
+static void write_xml_osmatch(const FingerMatch *match, double accuracy) {
+ xml_open_start_tag("osmatch");
+ xml_attribute("name", "%s", match->OS_name);
+ xml_attribute("accuracy", "%d", (int) (accuracy * 100));
+ xml_attribute("line", "%d", match->line);
+ /* When o.deprecated_xml_osclass is true, we don't write osclass elements as
+ children of osmatch but rather as unrelated siblings. */
+ if (match->OS_class.empty() || o.deprecated_xml_osclass) {
+ xml_close_empty_tag();
+ } else {
+ unsigned int i;
+
+ xml_close_start_tag();
+ xml_newline();
+ for (i = 0; i < match->OS_class.size(); i++)
+ write_xml_osclass(&match->OS_class[i], accuracy);
+ xml_end_tag();
+ }
+ xml_newline();
+}
+
+/* Convert a number to a string, keeping the given number of significant digits.
+ The result is returned in a static buffer. */
+static char *num_to_string_sigdigits(double d, int digits) {
+ static char buf[32];
+ int shift;
+ int n;
+
+ assert(digits >= 0);
+ if (d == 0.0) {
+ shift = -digits;
+ } else {
+ shift = (int) floor(log10(fabs(d))) - digits + 1;
+ d = floor(d / pow(10.0, shift) + 0.5);
+ d = d * pow(10.0, shift);
+ }
+
+ n = Snprintf(buf, sizeof(buf), "%.*f", MAX(0, -shift), d);
+ assert(n > 0 && n < (int) sizeof(buf));
+
+ return buf;
+}
+
+/* Writes a heading for a full scan report ("Nmap scan report for..."),
+ including host status and DNS records. */
+void write_host_header(const Target *currenths) {
+ if ((currenths->flags & HOST_UP) || o.verbose || o.always_resolve) {
+ if (currenths->flags & HOST_UP) {
+ log_write(LOG_PLAIN, "Nmap scan report for %s\n", currenths->NameIP());
+ } else if (currenths->flags & HOST_DOWN) {
+ log_write(LOG_PLAIN, "Nmap scan report for %s [host down", currenths->NameIP());
+ if (o.reason)
+ log_write(LOG_PLAIN, ", %s", target_reason_str(currenths));
+ log_write(LOG_PLAIN, "]\n");
+ }
+ }
+ write_host_status(currenths);
+ if (currenths->TargetName() != NULL
+ && !currenths->unscanned_addrs.empty()) {
+
+ log_write(LOG_PLAIN, "Other addresses for %s (not scanned):",
+ currenths->TargetName());
+ for (std::list<struct sockaddr_storage>::const_iterator it = currenths->unscanned_addrs.begin(), end = currenths->unscanned_addrs.end();
+ it != end; it++) {
+ struct sockaddr_storage ss = *it;
+ log_write(LOG_PLAIN, " %s", inet_ntop_ez(&ss, sizeof(ss)));
+ }
+ log_write(LOG_PLAIN, "\n");
+ }
+ /* Print reverse DNS if it differs. */
+ if (currenths->TargetName() != NULL
+ && currenths->HostName() != NULL && currenths->HostName()[0] != '\0'
+ && strcmp(currenths->TargetName(), currenths->HostName()) != 0) {
+ log_write(LOG_PLAIN, "rDNS record for %s: %s\n",
+ currenths->targetipstr(), currenths->HostName());
+ }
+}
+
+/* Writes host status info to the log streams (including STDOUT). An
+ example is "Host: 10.11.12.13 (foo.bar.example.com)\tStatus: Up\n" to
+ machine log. */
+void write_host_status(const Target *currenths) {
+ if (o.listscan) {
+ /* write "unknown" to machine and xml */
+ log_write(LOG_MACHINE, "Host: %s (%s)\tStatus: Unknown\n",
+ currenths->targetipstr(), currenths->HostName());
+ write_xml_initial_hostinfo(currenths, "unknown");
+ } else if (currenths->weird_responses) {
+ /* SMURF ADDRESS */
+ /* Write xml "down" or "up" based on flags and the smurf info */
+ write_xml_initial_hostinfo(currenths,
+ (currenths->
+ flags & HOST_UP) ? "up" : "down");
+ xml_open_start_tag("smurf");
+ xml_attribute("responses", "%d", currenths->weird_responses);
+ xml_close_empty_tag();
+ xml_newline();
+ log_write(LOG_MACHINE, "Host: %s (%s)\tStatus: Smurf (%d responses)\n",
+ currenths->targetipstr(), currenths->HostName(),
+ currenths->weird_responses);
+
+ if (o.noportscan) {
+ log_write(LOG_PLAIN, "Host seems to be a subnet broadcast address (returned %d extra pings).%s\n",
+ currenths->weird_responses,
+ (currenths->flags & HOST_UP) ? " Note -- the actual IP also responded." : "");
+ } else {
+ log_write(LOG_PLAIN, "Host seems to be a subnet broadcast address (returned %d extra pings). %s.\n",
+ currenths->weird_responses,
+ (currenths->flags & HOST_UP) ? " Still scanning it due to ping response from its own IP" : "Skipping host");
+ }
+ } else {
+ /* Ping scan / port scan. */
+
+ write_xml_initial_hostinfo(currenths, (currenths->flags & HOST_UP) ? "up" : "down");
+ if (currenths->flags & HOST_UP) {
+ log_write(LOG_PLAIN, "Host is up");
+ if (o.reason)
+ log_write(LOG_PLAIN, ", %s", target_reason_str(currenths));
+ if (o.reason && currenths->reason.ttl)
+ log_write(LOG_PLAIN, " ttl %d", currenths->reason.ttl);
+ if (currenths->to.srtt != -1)
+ log_write(LOG_PLAIN, " (%ss latency)",
+ num_to_string_sigdigits(currenths->to.srtt / 1000000.0, 2));
+ log_write(LOG_PLAIN, ".\n");
+
+ log_write(LOG_MACHINE, "Host: %s (%s)\tStatus: Up\n",
+ currenths->targetipstr(), currenths->HostName());
+ } else if (currenths->flags & HOST_DOWN) {
+ log_write(LOG_MACHINE, "Host: %s (%s)\tStatus: Down\n",
+ currenths->targetipstr(), currenths->HostName());
+ }
+ }
+}
+
+/* Returns -1 if adding the entry is not possible because it would
+ overflow. Otherwise it returns the new number of entries. Note
+ that only unique entries are added. Also note that *numentries is
+ incremented if the candidate is added. arrsize is the number of
+ char * members that fit into arr */
+static int addtochararrayifnew(const char *arr[], int *numentries, int arrsize,
+ const char *candidate) {
+ int i;
+
+ // First lets see if the member already exists
+ for (i = 0; i < *numentries; i++) {
+ if (strcmp(arr[i], candidate) == 0)
+ return *numentries;
+ }
+
+ // Not already there... do we have room for a new one?
+ if (*numentries >= arrsize)
+ return -1;
+
+ // OK, not already there and we have room, so we'll add it.
+ arr[*numentries] = candidate;
+ (*numentries)++;
+ return *numentries;
+}
+
+/* guess is true if we should print guesses */
+#define MAX_OS_CLASSMEMBERS 8
+static void printosclassificationoutput(const struct
+ OS_Classification_Results *OSR,
+ bool guess) {
+ int classno, cpeno, familyno;
+ unsigned int i;
+ int overflow = 0; /* Whether we have too many devices to list */
+ const char *types[MAX_OS_CLASSMEMBERS];
+ const char *cpes[MAX_OS_CLASSMEMBERS];
+ char fullfamily[MAX_OS_CLASSMEMBERS][128]; // "[vendor] [os family]"
+ double familyaccuracy[MAX_OS_CLASSMEMBERS]; // highest accuracy for this fullfamily
+ char familygenerations[MAX_OS_CLASSMEMBERS][96]; // example: "4.X|5.X|6.X"
+ int numtypes = 0, numcpes = 0, numfamilies = 0;
+ char tmpbuf[1024];
+
+ for (i = 0; i < MAX_OS_CLASSMEMBERS; i++) {
+ familygenerations[i][0] = '\0';
+ familyaccuracy[i] = 0.0;
+ }
+
+ if (OSR->overall_results == OSSCAN_SUCCESS) {
+
+ if (o.deprecated_xml_osclass) {
+ for (classno = 0; classno < OSR->OSC_num_matches; classno++)
+ write_xml_osclass(OSR->OSC[classno], OSR->OSC_Accuracy[classno]);
+ }
+
+ // Now to create the fodder for normal output
+ for (classno = 0; classno < OSR->OSC_num_matches; classno++) {
+ /* We have processed enough if any of the following are true */
+ if ((!guess && classno >= OSR->OSC_num_perfect_matches) ||
+ OSR->OSC_Accuracy[classno] <= OSR->OSC_Accuracy[0] - 0.1 ||
+ (OSR->OSC_Accuracy[classno] < 1.0 && classno > 9))
+ break;
+ if (addtochararrayifnew(types, &numtypes, MAX_OS_CLASSMEMBERS,
+ OSR->OSC[classno]->Device_Type) == -1) {
+ overflow = 1;
+ }
+ for (i = 0; i < OSR->OSC[classno]->cpe.size(); i++) {
+ if (addtochararrayifnew(cpes, &numcpes, MAX_OS_CLASSMEMBERS,
+ OSR->OSC[classno]->cpe[i]) == -1) {
+ overflow = 1;
+ }
+ }
+
+ // If family and vendor names are the same, no point being redundant
+ if (strcmp(OSR->OSC[classno]->OS_Vendor, OSR->OSC[classno]->OS_Family) == 0)
+ Strncpy(tmpbuf, OSR->OSC[classno]->OS_Family, sizeof(tmpbuf));
+ else
+ Snprintf(tmpbuf, sizeof(tmpbuf), "%s %s", OSR->OSC[classno]->OS_Vendor, OSR->OSC[classno]->OS_Family);
+
+
+ // Let's see if it is already in the array
+ for (familyno = 0; familyno < numfamilies; familyno++) {
+ if (strcmp(fullfamily[familyno], tmpbuf) == 0) {
+ // got a match ... do we need to add the generation?
+ if (OSR->OSC[classno]->OS_Generation
+ && !strstr(familygenerations[familyno],
+ OSR->OSC[classno]->OS_Generation)) {
+ int flen = strlen(familygenerations[familyno]);
+ // We add it, preceded by | if something is already there
+ if (flen + 2 + strlen(OSR->OSC[classno]->OS_Generation) >=
+ sizeof(familygenerations[familyno]))
+ fatal("buffer 0verfl0w of familygenerations");
+ if (*familygenerations[familyno])
+ strcat(familygenerations[familyno], "|");
+ strncat(familygenerations[familyno],
+ OSR->OSC[classno]->OS_Generation,
+ sizeof(familygenerations[familyno]) - flen - 1);
+ }
+ break;
+ }
+ }
+
+ if (familyno == numfamilies) {
+ // Looks like the new family is not in the list yet. Do we have room to add it?
+ if (numfamilies >= MAX_OS_CLASSMEMBERS) {
+ overflow = 1;
+ break;
+ }
+ // Have space, time to add...
+ Strncpy(fullfamily[numfamilies], tmpbuf, 128);
+ if (OSR->OSC[classno]->OS_Generation)
+ Strncpy(familygenerations[numfamilies],
+ OSR->OSC[classno]->OS_Generation, 48);
+ familyaccuracy[numfamilies] = OSR->OSC_Accuracy[classno];
+ numfamilies++;
+ }
+ }
+
+ if (!overflow && numfamilies >= 1) {
+ log_write(LOG_PLAIN, "Device type: ");
+ for (classno = 0; classno < numtypes; classno++)
+ log_write(LOG_PLAIN, "%s%s", types[classno], (classno < numtypes - 1) ? "|" : "");
+ log_write(LOG_PLAIN, "\nRunning%s: ", OSR->OSC_num_perfect_matches == 0 ? " (JUST GUESSING)" : "");
+ for (familyno = 0; familyno < numfamilies; familyno++) {
+ if (familyno > 0)
+ log_write(LOG_PLAIN, ", ");
+ log_write(LOG_PLAIN, "%s", fullfamily[familyno]);
+ if (*familygenerations[familyno])
+ log_write(LOG_PLAIN, " %s", familygenerations[familyno]);
+ if (familyno >= OSR->OSC_num_perfect_matches)
+ log_write(LOG_PLAIN, " (%.f%%)",
+ floor(familyaccuracy[familyno] * 100));
+ }
+ log_write(LOG_PLAIN, "\n");
+
+ if (numcpes > 0) {
+ log_write(LOG_PLAIN, "OS CPE:");
+ for (cpeno = 0; cpeno < numcpes; cpeno++)
+ log_write(LOG_PLAIN, " %s", cpes[cpeno]);
+ log_write(LOG_PLAIN, "\n");
+ }
+ }
+ }
+ log_flush_all();
+ return;
+}
+
+/* Prints the MAC address if one was found for the target (generally
+ this means that the target is directly connected on an ethernet
+ network. This only prints to human output -- XML is handled by a
+ separate call ( print_MAC_XML_Info ) because it needs to be printed
+ in a certain place to conform to DTD. */
+void printmacinfo(const Target *currenths) {
+ const u8 *mac = currenths->MACAddress();
+ char macascii[32];
+
+ if (mac) {
+ const char *macvendor = MACPrefix2Corp(mac);
+ Snprintf(macascii, sizeof(macascii), "%02X:%02X:%02X:%02X:%02X:%02X",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ log_write(LOG_PLAIN, "MAC Address: %s (%s)\n", macascii,
+ macvendor ? macvendor : "Unknown");
+ }
+}
+
+
+
+/* A convenience wrapper around mergeFPs. */
+const char *FingerPrintResultsIPv4::merge_fpr(const Target *currenths,
+ bool isGoodFP, bool wrapit) const {
+ return mergeFPs(this->FPs, this->numFPs, isGoodFP, currenths->TargetSockAddr(),
+ currenths->distance,
+ currenths->distance_calculation_method,
+ currenths->MACAddress(), this->osscan_opentcpport,
+ this->osscan_closedtcpport, this->osscan_closedudpport,
+ wrapit);
+}
+
+/* Run-length encode a string in chunks of two bytes. The output sequence
+ AA{n} means to repeat AA n times. The input must not contain '{' or '}'
+ characters. */
+static std::string run_length_encode(const std::string &s) {
+ std::ostringstream result;
+ const char *p, *q;
+ unsigned int reps;
+
+ p = s.c_str();
+ while (*p != '\0' && *(p + 1) != '\0') {
+ for (q = p + 2; *q == *p && *(q + 1) == *(p + 1); q += 2)
+ ;
+ reps = (q - p) / 2;
+ if (reps < 3)
+ result << std::string(p, q);
+ else
+ result << std::string(p, 2) << "{" << reps << "}";
+ p = q;
+ }
+ if (*p != '\0')
+ result << std::string(p);
+
+ return result.str();
+}
+
+static std::string wrap(const std::string &s) {
+ const static char *prefix = "OS:";
+ std::string t, buf;
+ int i, len, prefixlen;
+ size_t p;
+
+ t = s;
+ /* Remove newlines. */
+ p = 0;
+ while ((p = t.find("\n", p)) != std::string::npos)
+ t.erase(p, 1);
+
+ len = t.size();
+ prefixlen = strlen(prefix);
+ assert(FP_RESULT_WRAP_LINE_LEN > prefixlen);
+ for (i = 0; i < len; i += FP_RESULT_WRAP_LINE_LEN - prefixlen) {
+ buf.append(prefix);
+ buf.append(t, i, FP_RESULT_WRAP_LINE_LEN - prefixlen);
+ buf.append("\n");
+ }
+
+ return buf;
+}
+
+static void scrub_packet(PacketElement *pe, unsigned char fill) {
+ unsigned char fillbuf[16];
+
+ memset(fillbuf, fill, sizeof(fillbuf));
+ for (; pe != NULL; pe = pe->getNextElement()) {
+ if (pe->protocol_id() == HEADER_TYPE_IPv6) {
+ IPv6Header *ipv6 = (IPv6Header *) pe;
+ ipv6->setSourceAddress(fillbuf);
+ ipv6->setDestinationAddress(fillbuf);
+ } else if (pe->protocol_id() == HEADER_TYPE_ICMPv6) {
+ ICMPv6Header *icmpv6 = (ICMPv6Header *) pe;
+ in6_addr *addr = (in6_addr *) fillbuf;
+ if (icmpv6->getType() == ICMPV6_NEIGHBOR_ADVERTISEMENT)
+ icmpv6->setTargetAddress(*addr);
+ }
+ }
+}
+
+static std::string get_scrubbed_buffer(const FPResponse *resp) {
+ std::ostringstream result;
+ PacketElement *scrub1, *scrub2;
+ u8 *buf1, *buf2;
+ int len1, len2;
+ unsigned int i;
+
+ scrub1 = PacketParser::split(resp->buf, resp->len);
+ assert(scrub1 != NULL);
+ scrub_packet(scrub1, 0x00);
+
+ scrub2 = PacketParser::split(resp->buf, resp->len);
+ assert(scrub2 != NULL);
+ scrub_packet(scrub2, 0xFF);
+
+ buf1 = scrub1->getBinaryBuffer(&len1);
+ buf2 = scrub2->getBinaryBuffer(&len2);
+
+ assert(resp->len == (unsigned int) len1);
+ assert(resp->len == (unsigned int) len2);
+
+ result.fill('0');
+ result << std::hex;
+ for (i = 0; i < resp->len; i++) {
+ if (resp->buf[i] == buf1[i] && resp->buf[i] == buf2[i]) {
+ result.width(2);
+ result << (unsigned int) resp->buf[i];
+ } else {
+ result << "XX";
+ }
+ }
+
+ free(buf1);
+ free(buf2);
+ PacketParser::freePacketChain(scrub1);
+ PacketParser::freePacketChain(scrub2);
+
+ return result.str();
+}
+
+const char *FingerPrintResultsIPv6::merge_fpr(const Target *currenths,
+ bool isGoodFP, bool wrapit) const {
+ static char str[10240];
+ const FingerPrintResultsIPv6 *FPR;
+ std::ostringstream result;
+ std::string output;
+ unsigned int i;
+
+ /* Write the SCAN line. */
+ WriteSInfo(str, sizeof(str), isGoodFP, "6", currenths->TargetSockAddr(),
+ currenths->distance, currenths->distance_calculation_method,
+ currenths->MACAddress(), this->osscan_opentcpport,
+ this->osscan_closedtcpport, this->osscan_closedudpport);
+ result << str << "\n";
+
+ FPR = (FingerPrintResultsIPv6 *) currenths->FPR;
+ assert(FPR->begin_time.tv_sec != 0);
+ for (i = 0; i < sizeof(FPR->fp_responses) / sizeof(FPR->fp_responses[0]); i++) {
+ const FPResponse *resp;
+ std::string scrubbed;
+
+ resp = this->fp_responses[i];
+ if (resp == NULL)
+ continue;
+ scrubbed = get_scrubbed_buffer(resp);
+ if (wrapit)
+ scrubbed = run_length_encode(scrubbed);
+ result << resp->probe_id << "(P=" << scrubbed;
+ assert(resp->senttime.tv_sec != 0);
+ result << "%ST=" << TIMEVAL_FSEC_SUBTRACT(resp->senttime, FPR->begin_time);
+ assert(resp->rcvdtime.tv_sec != 0);
+ result << "%RT=" << TIMEVAL_FSEC_SUBTRACT(resp->rcvdtime, FPR->begin_time);
+ result << ")\n";
+ }
+
+ result << "EXTRA(";
+ result << "FL=";
+ result.fill('0');
+ result << std::hex;
+ result.width(5);
+ result << FPR->flow_label;
+ result << ")\n";
+
+ output = result.str();
+ if (wrapit) {
+ output = wrap(output);
+ }
+
+ Strncpy(str, output.c_str(), sizeof(str));
+
+ return str;
+}
+
+static void write_merged_fpr(const FingerPrintResults *FPR,
+ const Target *currenths,
+ bool isGoodFP, bool wrapit) {
+ log_write(LOG_NORMAL | LOG_SKID_NOXLT | LOG_STDOUT,
+ "TCP/IP fingerprint:\n%s\n",
+ FPR->merge_fpr(currenths, isGoodFP, wrapit));
+
+ /* Added code here to print fingerprint to XML file any time it would be
+ printed to any other output format */
+ xml_open_start_tag("osfingerprint");
+ xml_attribute("fingerprint", "%s", FPR->merge_fpr(currenths, isGoodFP, wrapit));
+ xml_close_empty_tag();
+ xml_newline();
+}
+
+/* Prints the formatted OS Scan output to stdout, logfiles, etc (but only
+ if an OS Scan was performed).*/
+void printosscanoutput(const Target *currenths) {
+ int i;
+ char numlst[512]; /* For creating lists of numbers */
+ char *p; /* Used in manipulating numlst above */
+ FingerPrintResults *FPR;
+ int osscan_flag;
+
+ if (!(osscan_flag = currenths->osscanPerformed()))
+ return;
+
+ if (currenths->FPR == NULL)
+ return;
+ FPR = currenths->FPR;
+
+ xml_start_tag("os");
+ if (FPR->osscan_opentcpport > 0) {
+ xml_open_start_tag("portused");
+ xml_attribute("state", "open");
+ xml_attribute("proto", "tcp");
+ xml_attribute("portid", "%d", FPR->osscan_opentcpport);
+ xml_close_empty_tag();
+ xml_newline();
+ }
+ if (FPR->osscan_closedtcpport > 0) {
+ xml_open_start_tag("portused");
+ xml_attribute("state", "closed");
+ xml_attribute("proto", "tcp");
+ xml_attribute("portid", "%d", FPR->osscan_closedtcpport);
+ xml_close_empty_tag();
+ xml_newline();
+ }
+ if (FPR->osscan_closedudpport > 0) {
+ xml_open_start_tag("portused");
+ xml_attribute("state", "closed");
+ xml_attribute("proto", "udp");
+ xml_attribute("portid", "%d", FPR->osscan_closedudpport);
+ xml_close_empty_tag();
+ xml_newline();
+ }
+
+ if (osscan_flag == OS_PERF_UNREL &&
+ !(FPR->overall_results == OSSCAN_TOOMANYMATCHES ||
+ (FPR->num_perfect_matches > 8 && !o.debugging)))
+ log_write(LOG_PLAIN, "Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port\n");
+
+ // If the FP can't be submitted anyway, might as well make a guess.
+ const char *reason = FPR->OmitSubmissionFP();
+ printosclassificationoutput(FPR->getOSClassification(), o.osscan_guess || reason);
+
+ if (FPR->overall_results == OSSCAN_SUCCESS &&
+ (FPR->num_perfect_matches <= 8 || o.debugging)) {
+ /* Success, not too many perfect matches. */
+ if (FPR->num_perfect_matches > 0) {
+ /* Some perfect matches. */
+ for (i = 0; i < FPR->num_perfect_matches; i++)
+ write_xml_osmatch(FPR->matches[i], FPR->accuracy[i]);
+
+ log_write(LOG_MACHINE, "\tOS: %s", FPR->matches[0]->OS_name);
+ for (i = 1; i < FPR->num_perfect_matches; i++)
+ log_write(LOG_MACHINE, "|%s", FPR->matches[i]->OS_name);
+
+ unsigned short numprints = FPR->matches[0]->numprints;
+ log_write(LOG_PLAIN, "OS details: %s", FPR->matches[0]->OS_name);
+ for (i = 1; i < FPR->num_perfect_matches; i++) {
+ numprints = MIN(numprints, FPR->matches[i]->numprints);
+ log_write(LOG_PLAIN, ", %s", FPR->matches[i]->OS_name);
+ }
+ log_write(LOG_PLAIN, "\n");
+
+ /* Suggest submission of an already-matching IPv6 fingerprint with
+ * decreasing probability as numprints increases, and never if the group
+ * has 5 or more prints or if the print is unsuitable. */
+ bool suggest_submission = currenths->af() == AF_INET6 && reason == NULL && rand() % 5 >= numprints;
+ if (suggest_submission)
+ log_write(LOG_NORMAL | LOG_SKID_NOXLT | LOG_STDOUT,
+ "Nmap needs more fingerprint submissions of this type. Please submit via https://nmap.org/submit/\n");
+ if (suggest_submission || o.debugging || o.verbose > 1)
+ write_merged_fpr(FPR, currenths, reason == NULL, true);
+ } else {
+ /* No perfect matches. */
+ if ((o.verbose > 1 || o.debugging) && reason)
+ log_write(LOG_NORMAL | LOG_SKID_NOXLT | LOG_STDOUT,
+ "OS fingerprint not ideal because: %s\n", reason);
+
+ for (i = 0; i < 10 && i < FPR->num_matches && FPR->accuracy[i] > FPR->accuracy[0] - 0.10; i++)
+ write_xml_osmatch(FPR->matches[i], FPR->accuracy[i]);
+
+ if ((o.osscan_guess || reason) && FPR->num_matches > 0) {
+ /* Print the best guesses available */
+ log_write(LOG_PLAIN, "Aggressive OS guesses: %s (%.f%%)",
+ FPR->matches[0]->OS_name, floor(FPR->accuracy[0] * 100));
+ for (i = 1; i < 10 && FPR->num_matches > i && FPR->accuracy[i] > FPR->accuracy[0] - 0.10; i++)
+ log_write(LOG_PLAIN, ", %s (%.f%%)", FPR->matches[i]->OS_name, floor(FPR->accuracy[i] * 100));
+
+ log_write(LOG_PLAIN, "\n");
+ }
+
+ if (!reason) {
+ log_write(LOG_NORMAL | LOG_SKID_NOXLT | LOG_STDOUT,
+ "No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).\n");
+ write_merged_fpr(FPR, currenths, true, true);
+ } else {
+ log_write(LOG_NORMAL | LOG_SKID_NOXLT | LOG_STDOUT,
+ "No exact OS matches for host (test conditions non-ideal).\n");
+ if (o.verbose > 1 || o.debugging)
+ write_merged_fpr(FPR, currenths, false, false);
+ }
+ }
+ } else if (FPR->overall_results == OSSCAN_NOMATCHES) {
+ /* No matches at all. */
+ if (!reason) {
+ log_write(LOG_NORMAL | LOG_SKID_NOXLT | LOG_STDOUT,
+ "No OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).\n");
+ write_merged_fpr(FPR, currenths, true, true);
+ } else {
+ log_write(LOG_NORMAL | LOG_SKID_NOXLT | LOG_STDOUT,
+ "OS fingerprint not ideal because: %s\n", reason);
+ log_write(LOG_NORMAL | LOG_SKID_NOXLT | LOG_STDOUT,
+ "No OS matches for host\n");
+ if (o.debugging || o.verbose > 1)
+ write_merged_fpr(FPR, currenths, false, false);
+ }
+ } else if (FPR->overall_results == OSSCAN_TOOMANYMATCHES
+ || (FPR->num_perfect_matches > 8 && !o.debugging)) {
+ /* Too many perfect matches. */
+ log_write(LOG_NORMAL | LOG_SKID_NOXLT | LOG_STDOUT,
+ "Too many fingerprints match this host to give specific OS details\n");
+ if (o.debugging || o.verbose > 1)
+ write_merged_fpr(FPR, currenths, false, false);
+ } else {
+ assert(0);
+ }
+
+ xml_end_tag(); /* os */
+ xml_newline();
+
+ if (currenths->seq.lastboot) {
+ char tmbuf[128];
+ struct timeval tv;
+ double uptime;
+ int err = n_ctime(tmbuf, sizeof(tmbuf), &currenths->seq.lastboot);
+ chomp(tmbuf);
+ gettimeofday(&tv, NULL);
+ uptime = difftime(tv.tv_sec, currenths->seq.lastboot);
+ if (o.verbose) {
+ if (err)
+ log_write(LOG_PLAIN, "Uptime guess: %.3f days\n",
+ uptime / 86400);
+ else
+ log_write(LOG_PLAIN, "Uptime guess: %.3f days (since %s)\n",
+ uptime / 86400,
+ tmbuf);
+ }
+ xml_open_start_tag("uptime");
+ xml_attribute("seconds", "%.0f", uptime);
+ if (!err)
+ xml_attribute("lastboot", "%s", tmbuf);
+ xml_close_empty_tag();
+ xml_newline();
+ }
+
+ if (currenths->distance != -1) {
+ log_write(LOG_PLAIN, "Network Distance: %d hop%s\n",
+ currenths->distance, (currenths->distance == 1) ? "" : "s");
+ xml_open_start_tag("distance");
+ xml_attribute("value", "%d", currenths->distance);
+ xml_close_empty_tag();
+ xml_newline();
+ }
+
+ if (currenths->seq.responses > 3) {
+ p = numlst;
+ for (i = 0; i < currenths->seq.responses; i++) {
+ if (p - numlst > (int) (sizeof(numlst) - 15))
+ fatal("STRANGE ERROR #3877 -- please report to fyodor@nmap.org\n");
+ if (p != numlst)
+ *p++ = ',';
+ sprintf(p, "%X", currenths->seq.seqs[i]);
+ while (*p)
+ p++;
+ }
+
+ xml_open_start_tag("tcpsequence");
+ xml_attribute("index", "%li", (long) currenths->seq.index);
+ xml_attribute("difficulty", "%s", seqidx2difficultystr(currenths->seq.index));
+ xml_attribute("values", "%s", numlst);
+ xml_close_empty_tag();
+ xml_newline();
+ if (o.verbose)
+ log_write(LOG_PLAIN, "TCP Sequence Prediction: Difficulty=%d (%s)\n", currenths->seq.index, seqidx2difficultystr(currenths->seq.index));
+
+ log_write(LOG_MACHINE, "\tSeq Index: %d", currenths->seq.index);
+ }
+
+ if (currenths->seq.responses > 2) {
+ p = numlst;
+ for (i = 0; i < currenths->seq.responses; i++) {
+ if (p - numlst > (int) (sizeof(numlst) - 15))
+ fatal("STRANGE ERROR #3876 -- please report to fyodor@nmap.org\n");
+ if (p != numlst)
+ *p++ = ',';
+ sprintf(p, "%hX", currenths->seq.ipids[i]);
+ while (*p)
+ p++;
+ }
+ xml_open_start_tag("ipidsequence");
+ xml_attribute("class", "%s", ipidclass2ascii(currenths->seq.ipid_seqclass));
+ xml_attribute("values", "%s", numlst);
+ xml_close_empty_tag();
+ xml_newline();
+ if (o.verbose)
+ log_write(LOG_PLAIN, "IP ID Sequence Generation: %s\n",
+ ipidclass2ascii(currenths->seq.ipid_seqclass));
+ log_write(LOG_MACHINE, "\tIP ID Seq: %s",
+ ipidclass2ascii(currenths->seq.ipid_seqclass));
+
+ p = numlst;
+ for (i = 0; i < currenths->seq.responses; i++) {
+ if (p - numlst > (int) (sizeof(numlst) - 15))
+ fatal("STRANGE ERROR #3878 -- please report to fyodor@nmap.org\n");
+ if (p != numlst)
+ *p++ = ',';
+ sprintf(p, "%X", currenths->seq.timestamps[i]);
+ while (*p)
+ p++;
+ }
+
+ xml_open_start_tag("tcptssequence");
+ xml_attribute("class", "%s", tsseqclass2ascii(currenths->seq.ts_seqclass));
+ if (currenths->seq.ts_seqclass != TS_SEQ_UNSUPPORTED) {
+ xml_attribute("values", "%s", numlst);
+ }
+ xml_close_empty_tag();
+ xml_newline();
+ }
+ log_flush_all();
+}
+
+/* An auxiliary function for printserviceinfooutput(). Returns
+ non-zero if a and b are considered the same hostnames. */
+static int hostcmp(const char *a, const char *b) {
+ return strcasecmp(a, b) == 0;
+}
+
+/* Prints the alternate hostname/OS/device information we got from the service
+ scan (if it was performed) */
+void printserviceinfooutput(const Target *currenths) {
+ Port *p = NULL;
+ Port port;
+ struct serviceDeductions sd;
+ int i, numhostnames = 0, numostypes = 0, numdevicetypes = 0, numcpes = 0;
+ char hostname_tbl[MAX_SERVICE_INFO_FIELDS][FQDN_LEN+1];
+ char ostype_tbl[MAX_SERVICE_INFO_FIELDS][64];
+ char devicetype_tbl[MAX_SERVICE_INFO_FIELDS][64];
+ char cpe_tbl[MAX_SERVICE_INFO_FIELDS][80];
+ const char *delim;
+
+ for (i = 0; i < MAX_SERVICE_INFO_FIELDS; i++)
+ hostname_tbl[i][0] = ostype_tbl[i][0] = devicetype_tbl[i][0] = cpe_tbl[i][0] = '\0';
+
+ while ((p = currenths->ports.nextPort(p, &port, TCPANDUDPANDSCTP, PORT_OPEN))) {
+ // The following 2 lines (from portlist.h) tell us that we don't need to
+ // worry about free()ing anything in the serviceDeductions struct. pass in
+ // an allocated struct serviceDeductions (don't worry about initializing, and
+ // you don't have to free any internal ptrs.
+ currenths->ports.getServiceDeductions(p->portno, p->proto, &sd);
+
+ if (sd.hostname && !hostcmp(currenths->HostName(), sd.hostname)) {
+ for (i = 0; i < MAX_SERVICE_INFO_FIELDS; i++) {
+ if (hostname_tbl[i][0] && hostcmp(&hostname_tbl[i][0], sd.hostname))
+ break;
+
+ if (!hostname_tbl[i][0]) {
+ numhostnames++;
+ strncpy(&hostname_tbl[i][0], sd.hostname, sizeof(hostname_tbl[i]));
+ break;
+ }
+ }
+ }
+
+ if (sd.ostype) {
+ for (i = 0; i < MAX_SERVICE_INFO_FIELDS; i++) {
+ if (ostype_tbl[i][0] && !strcmp(&ostype_tbl[i][0], sd.ostype))
+ break;
+
+ if (!ostype_tbl[i][0]) {
+ numostypes++;
+ strncpy(&ostype_tbl[i][0], sd.ostype, sizeof(ostype_tbl[i]));
+ break;
+ }
+ }
+ }
+
+ if (sd.devicetype) {
+ for (i = 0; i < MAX_SERVICE_INFO_FIELDS; i++) {
+ if (devicetype_tbl[i][0] && !strcmp(&devicetype_tbl[i][0], sd.devicetype))
+ break;
+
+ if (!devicetype_tbl[i][0]) {
+ numdevicetypes++;
+ strncpy(&devicetype_tbl[i][0], sd.devicetype, sizeof(devicetype_tbl[i]));
+ break;
+ }
+ }
+ }
+
+ for (std::vector<char *>::const_iterator it = sd.cpe.begin(); it != sd.cpe.end(); it++) {
+ for (i = 0; i < MAX_SERVICE_INFO_FIELDS; i++) {
+ if (cpe_tbl[i][0] && !strcmp(&cpe_tbl[i][0], *it))
+ break;
+ /* Applications (CPE part "a") aren't shown in this summary list in
+ normal output. "a" classifications belong to an individual port, not
+ the entire host, unlike "h" (hardware) and "o" (operating system).
+ There isn't a good place to put the "a" classifications, so they are
+ written to XML only. */
+ if (cpe_get_part(*it) == 'a')
+ break;
+
+ if (!cpe_tbl[i][0]) {
+ numcpes++;
+ strncpy(&cpe_tbl[i][0], *it, sizeof(cpe_tbl[i]));
+ break;
+ }
+ }
+ }
+
+ }
+
+ if (!numhostnames && !numostypes && !numdevicetypes && !numcpes)
+ return;
+
+ log_write(LOG_PLAIN, "Service Info:");
+
+ delim = " ";
+ if (numhostnames) {
+ log_write(LOG_PLAIN, "%sHost%s: %s", delim, numhostnames == 1 ? "" : "s", &hostname_tbl[0][0]);
+ for (i = 1; i < MAX_SERVICE_INFO_FIELDS; i++) {
+ if (hostname_tbl[i][0])
+ log_write(LOG_PLAIN, ", %s", &hostname_tbl[i][0]);
+ }
+ delim = "; ";
+ }
+
+ if (numostypes) {
+ log_write(LOG_PLAIN, "%sOS%s: %s", delim, numostypes == 1 ? "" : "s",
+ &ostype_tbl[0][0]);
+ for (i = 1; i < MAX_SERVICE_INFO_FIELDS; i++) {
+ if (ostype_tbl[i][0])
+ log_write(LOG_PLAIN, ", %s", &ostype_tbl[i][0]);
+ }
+ delim = "; ";
+ }
+
+ if (numdevicetypes) {
+ log_write(LOG_PLAIN, "%sDevice%s: %s", delim,
+ numdevicetypes == 1 ? "" : "s", &devicetype_tbl[0][0]);
+ for (i = 1; i < MAX_SERVICE_INFO_FIELDS; i++) {
+ if (devicetype_tbl[i][0])
+ log_write(LOG_PLAIN, ", %s", &devicetype_tbl[i][0]);
+ }
+ delim = "; ";
+ }
+
+ if (numcpes > 0) {
+ log_write(LOG_PLAIN, "%sCPE: %s", delim, &cpe_tbl[0][0]);
+ for (i = 1; i < MAX_SERVICE_INFO_FIELDS; i++) {
+ if (cpe_tbl[i][0])
+ log_write(LOG_PLAIN, ", %s", &cpe_tbl[i][0]);
+ }
+ delim = "; ";
+ }
+
+ log_write(LOG_PLAIN, "\n");
+ log_flush_all();
+}
+
+#ifndef NOLUA
+void printscriptresults(const ScriptResults *scriptResults, stype scantype) {
+ ScriptResults::const_iterator iter;
+ char *script_output;
+
+ if (scriptResults->size() > 0) {
+ if (scantype == SCRIPT_PRE_SCAN) {
+ xml_start_tag("prescript");
+ log_write(LOG_PLAIN, "Pre-scan script results:\n");
+ } else {
+ xml_start_tag("postscript");
+ log_write(LOG_PLAIN, "Post-scan script results:\n");
+ }
+ for (iter = scriptResults->begin(); iter != scriptResults->end(); iter++) {
+ (*iter)->write_xml();
+ script_output = formatScriptOutput((*iter));
+ if (script_output != NULL) {
+ log_write(LOG_PLAIN, "%s\n", script_output);
+ free(script_output);
+ }
+ }
+ xml_end_tag();
+ }
+}
+
+void printhostscriptresults(const Target *currenths) {
+ ScriptResults::const_iterator iter;
+ char *script_output;
+
+ if (currenths->scriptResults.size() > 0) {
+ xml_start_tag("hostscript");
+ log_write(LOG_PLAIN, "\nHost script results:\n");
+ for (iter = currenths->scriptResults.begin();
+ iter != currenths->scriptResults.end();
+ iter++) {
+ (*iter)->write_xml();
+
+ script_output = formatScriptOutput((*iter));
+ if (script_output != NULL) {
+ log_write(LOG_PLAIN, "%s\n", script_output);
+ free(script_output);
+ }
+ }
+ xml_end_tag();
+ }
+}
+#endif
+
+/* Print a table with traceroute hops. */
+static void printtraceroute_normal(const Target *currenths) {
+ static const int HOP_COL = 0, RTT_COL = 1, HOST_COL = 2;
+ NmapOutputTable Tbl(currenths->traceroute_hops.size() + 1, 3);
+ struct probespec probe;
+ std::list<TracerouteHop>::const_iterator it;
+ int row;
+
+ /* No trace, must be localhost. */
+ if (currenths->traceroute_hops.size() == 0)
+ return;
+
+ /* Print header. */
+ log_write(LOG_PLAIN, "\n");
+ probe = currenths->traceroute_probespec;
+ if (probe.type == PS_TCP) {
+ log_write(LOG_PLAIN, "TRACEROUTE (using port %d/%s)\n",
+ probe.pd.tcp.dport, proto2ascii_lowercase(probe.proto));
+ } else if (probe.type == PS_UDP) {
+ log_write(LOG_PLAIN, "TRACEROUTE (using port %d/%s)\n",
+ probe.pd.udp.dport, proto2ascii_lowercase(probe.proto));
+ } else if (probe.type == PS_SCTP) {
+ log_write(LOG_PLAIN, "TRACEROUTE (using port %d/%s)\n",
+ probe.pd.sctp.dport, proto2ascii_lowercase(probe.proto));
+ } else if (probe.type == PS_ICMP || probe.type == PS_ICMPV6 || probe.type == PS_PROTO) {
+ const struct nprotoent *proto = nmap_getprotbynum(probe.proto);
+ log_write(LOG_PLAIN, "TRACEROUTE (using proto %d/%s)\n",
+ probe.proto, proto ? proto->p_name : "unknown");
+ } else if (probe.type == PS_NONE) {
+ /* "Traces" of directly connected targets don't send any packets. */
+ log_write(LOG_PLAIN, "TRACEROUTE\n");
+ } else {
+ fatal("Unknown probe type %d.", probe.type);
+ }
+
+ row = 0;
+ Tbl.addItem(row, HOP_COL, false, "HOP");
+ Tbl.addItem(row, RTT_COL, false, "RTT");
+ Tbl.addItem(row, HOST_COL, false, "ADDRESS");
+ row++;
+
+ it = currenths->traceroute_hops.begin();
+
+ if (!o.debugging) {
+ /* Consolidate shared hops. */
+ const TracerouteHop *shared_hop = NULL;
+ const struct sockaddr_storage *addr = currenths->TargetSockAddr();
+ while (it != currenths->traceroute_hops.end()
+ && !sockaddr_storage_equal(&it->tag, addr)) {
+ shared_hop = &*it;
+ it++;
+ }
+
+ if (shared_hop != NULL) {
+ Tbl.addItem(row, HOP_COL, false, "-");
+ if (shared_hop->ttl == 1) {
+ Tbl.addItemFormatted(row, RTT_COL, true,
+ "Hop 1 is the same as for %s",
+ inet_ntop_ez(&shared_hop->tag, sizeof(shared_hop->tag)));
+ } else if (shared_hop->ttl > 1) {
+ Tbl.addItemFormatted(row, RTT_COL, true,
+ "Hops 1-%d are the same as for %s", shared_hop->ttl,
+ inet_ntop_ez(&shared_hop->tag, sizeof(shared_hop->tag)));
+ }
+ row++;
+ }
+ }
+
+ while (it != currenths->traceroute_hops.end()) {
+ Tbl.addItemFormatted(row, HOP_COL, false, "%d", it->ttl);
+ if (it->timedout) {
+ if (o.debugging) {
+ Tbl.addItem(row, RTT_COL, false, "...");
+ it++;
+ } else {
+ /* The beginning and end of timeout consolidation. */
+ int begin_ttl, end_ttl;
+ begin_ttl = end_ttl = it->ttl;
+ for (; it != currenths->traceroute_hops.end() && it->timedout; it++)
+ end_ttl = it->ttl;
+ if (begin_ttl == end_ttl)
+ Tbl.addItem(row, RTT_COL, false, "...");
+ else
+ Tbl.addItemFormatted(row, RTT_COL, false, "... %d", end_ttl);
+ }
+ row++;
+ } else {
+ /* Normal hop output. */
+ char namebuf[256];
+
+ it->display_name(namebuf, sizeof(namebuf));
+ if (it->rtt < 0)
+ Tbl.addItem(row, RTT_COL, false, "--");
+ else
+ Tbl.addItemFormatted(row, RTT_COL, false, "%.2f ms", it->rtt);
+ Tbl.addItemFormatted(row, HOST_COL, false, "%s", namebuf);
+ row++;
+ it++;
+ }
+ }
+
+ log_write(LOG_PLAIN, "%s", Tbl.printableTable(NULL));
+
+ log_flush(LOG_PLAIN);
+}
+
+static void printtraceroute_xml(const Target *currenths) {
+ struct probespec probe;
+ std::list<TracerouteHop>::const_iterator it;
+
+ /* No trace, must be localhost. */
+ if (currenths->traceroute_hops.size() == 0)
+ return;
+
+ /* XML traceroute header */
+ xml_open_start_tag("trace");
+
+ probe = currenths->traceroute_probespec;
+ if (probe.type == PS_TCP) {
+ xml_attribute("port", "%d", probe.pd.tcp.dport);
+ xml_attribute("proto", "%s", proto2ascii_lowercase(probe.proto));
+ } else if (probe.type == PS_UDP) {
+ xml_attribute("port", "%d", probe.pd.udp.dport);
+ xml_attribute("proto", "%s", proto2ascii_lowercase(probe.proto));
+ } else if (probe.type == PS_SCTP) {
+ xml_attribute("port", "%d", probe.pd.sctp.dport);
+ xml_attribute("proto", "%s", proto2ascii_lowercase(probe.proto));
+ } else if (probe.type == PS_ICMP || probe.type == PS_PROTO) {
+ const struct nprotoent *proto = nmap_getprotbynum(probe.proto);
+ if (proto == NULL)
+ xml_attribute("proto", "%d", probe.proto);
+ else
+ xml_attribute("proto", "%s", proto->p_name);
+ }
+ xml_close_start_tag();
+ xml_newline();
+
+ for (it = currenths->traceroute_hops.begin();
+ it != currenths->traceroute_hops.end();
+ it++) {
+ if (it->timedout)
+ continue;
+ xml_open_start_tag("hop");
+ xml_attribute("ttl", "%d", it->ttl);
+ xml_attribute("ipaddr", "%s", inet_ntop_ez(&it->addr, sizeof(it->addr)));
+ if (it->rtt < 0)
+ xml_attribute("rtt", "--");
+ else
+ xml_attribute("rtt", "%.2f", it->rtt);
+ if (!it->name.empty())
+ xml_attribute("host", "%s", it->name.c_str());
+ xml_close_empty_tag();
+ xml_newline();
+ }
+
+ /* traceroute XML footer */
+ xml_end_tag();
+ xml_newline();
+ log_flush(LOG_XML);
+}
+
+void printtraceroute(const Target *currenths) {
+ printtraceroute_normal(currenths);
+ printtraceroute_xml(currenths);
+}
+
+void printtimes(const Target *currenths) {
+ if (currenths->to.srtt != -1 || currenths->to.rttvar != -1) {
+ if (o.debugging) {
+ log_write(LOG_STDOUT, "Final times for host: srtt: %d rttvar: %d to: %d\n",
+ currenths->to.srtt, currenths->to.rttvar, currenths->to.timeout);
+ }
+ xml_open_start_tag("times");
+ xml_attribute("srtt", "%d", currenths->to.srtt);
+ xml_attribute("rttvar", "%d", currenths->to.rttvar);
+ xml_attribute("to", "%d", currenths->to.timeout);
+ xml_close_empty_tag();
+ xml_newline();
+ }
+}
+
+/* Prints a status message while the program is running */
+void printStatusMessage() {
+ // Pre-computations
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ int time = (int) (o.TimeSinceStart(&tv));
+
+ log_write(LOG_STDOUT, "Stats: %d:%02d:%02d elapsed; %u hosts completed (%u up), %d undergoing %s\n",
+ time / 60 / 60, time / 60 % 60, time % 60, o.numhosts_scanned,
+ o.numhosts_up, o.numhosts_scanning,
+ scantype2str(o.current_scantype));
+}
+
+/* Prints the beginning of a "finished" start tag, with time, timestr, and
+ elapsed attributes. Leaves the start tag open so you can add more attributes.
+ You have to close the tag with xml_close_empty_tag. */
+void print_xml_finished_open(time_t timep, const struct timeval *tv) {
+ char mytime[128];
+ int err = n_ctime(mytime, sizeof(mytime), &timep);
+
+ chomp(mytime);
+
+ xml_open_start_tag("finished");
+ xml_attribute("time", "%lu", (unsigned long) timep);
+ if (!err) {
+ xml_attribute("timestr", "%s", mytime);
+ xml_attribute("summary",
+ "Nmap done at %s; %u %s (%u %s up) scanned in %.2f seconds",
+ mytime, o.numhosts_scanned,
+ (o.numhosts_scanned == 1) ? "IP address" : "IP addresses",
+ o.numhosts_up, (o.numhosts_up == 1) ? "host" : "hosts",
+ o.TimeSinceStart(tv));
+ }
+ else {
+ xml_attribute("summary",
+ "Nmap done; %u %s (%u %s up) scanned in %.2f seconds",
+ o.numhosts_scanned,
+ (o.numhosts_scanned == 1) ? "IP address" : "IP addresses",
+ o.numhosts_up, (o.numhosts_up == 1) ? "host" : "hosts",
+ o.TimeSinceStart(tv));
+ }
+ xml_attribute("elapsed", "%.2f", o.TimeSinceStart(tv));
+}
+
+void print_xml_hosts() {
+ xml_open_start_tag("hosts");
+ xml_attribute("up", "%u", o.numhosts_up);
+ xml_attribute("down", "%u", o.numhosts_scanned - o.numhosts_up);
+ xml_attribute("total", "%u", o.numhosts_scanned);
+ xml_close_empty_tag();
+}
+
+/* Prints the statistics and other information that goes at the very end
+ of an Nmap run */
+void printfinaloutput() {
+ time_t timep;
+ char mytime[128];
+ int err = 0;
+ struct timeval tv;
+ char statbuf[128];
+
+ gettimeofday(&tv, NULL);
+ timep = time(NULL);
+
+ if (o.numhosts_scanned == 0
+#ifndef NOLUA
+ && !o.scriptupdatedb
+#endif
+ )
+ error("WARNING: No targets were specified, so 0 hosts scanned.");
+ if (o.numhosts_scanned == 1 && o.numhosts_up == 0 && !o.listscan &&
+ o.pingtype != PINGTYPE_NONE)
+ log_write(LOG_STDOUT, "Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn\n");
+ else if (o.numhosts_up > 0) {
+ if (o.osscan && o.servicescan)
+ log_write(LOG_PLAIN, "OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .\n");
+ else if (o.osscan)
+ log_write(LOG_PLAIN, "OS detection performed. Please report any incorrect results at https://nmap.org/submit/ .\n");
+ else if (o.servicescan)
+ log_write(LOG_PLAIN, "Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .\n");
+ else if (o.udpscan && o.defeat_icmp_ratelimit)
+ log_write(LOG_PLAIN, "WARNING: Some ports marked closed|filtered may actually be open. For more accurate results, do not use --defeat-icmp-ratelimit .\n");
+
+ }
+
+ log_write(LOG_STDOUT | LOG_SKID,
+ "Nmap done: %u %s (%u %s up) scanned in %.2f seconds\n",
+ o.numhosts_scanned,
+ (o.numhosts_scanned == 1) ? "IP address" : "IP addresses",
+ o.numhosts_up, (o.numhosts_up == 1) ? "host" : "hosts",
+ o.TimeSinceStart(&tv));
+ if (o.verbose && o.isr00t && o.RawScan())
+ log_write(LOG_STDOUT | LOG_SKID, " %s\n",
+ getFinalPacketStats(statbuf, sizeof(statbuf)));
+
+ xml_start_tag("runstats");
+ print_xml_finished_open(timep, &tv);
+ xml_attribute("exit", "success");
+ xml_close_empty_tag();
+ print_xml_hosts();
+ xml_newline();
+ xml_end_tag();
+ xml_newline();
+
+ err = n_ctime(mytime, sizeof(mytime), &timep);
+ if (!err) {
+ chomp(mytime);
+ log_write(LOG_NORMAL | LOG_MACHINE,
+ "# Nmap done at %s -- %u %s (%u %s up) scanned in %.2f seconds\n",
+ mytime, o.numhosts_scanned,
+ (o.numhosts_scanned == 1) ? "IP address" : "IP addresses",
+ o.numhosts_up, (o.numhosts_up == 1) ? "host" : "hosts",
+ o.TimeSinceStart(&tv));
+ }
+ else {
+ log_write(LOG_NORMAL | LOG_MACHINE,
+ "# Nmap done -- %u %s (%u %s up) scanned in %.2f seconds\n",
+ o.numhosts_scanned,
+ (o.numhosts_scanned == 1) ? "IP address" : "IP addresses",
+ o.numhosts_up, (o.numhosts_up == 1) ? "host" : "hosts",
+ o.TimeSinceStart(&tv));
+ }
+
+ xml_end_tag(); /* nmaprun */
+ xml_newline();
+ log_flush_all();
+}
+
+/* A record consisting of a data file name ("nmap-services", "nmap-os-db",
+ etc.), and the directory and file in which is was found. This is a
+ broken-down version of what is stored in o.loaded_data_files. It is used in
+ printdatafilepaths. */
+struct data_file_record {
+ std::string data_file;
+ std::string dir;
+ std::string file;
+
+ /* Compares this record to another. First compare the directory names, then
+ compare the file names. */
+ bool operator<(const struct data_file_record &other) const {
+ int cmp;
+
+ cmp = dir.compare(other.dir);
+ if (cmp == 0)
+ cmp = file.compare(other.file);
+
+ return cmp < 0;
+ }
+};
+
+/* Prints the names of data files that were loaded and the paths at which they
+ were found. */
+void printdatafilepaths() {
+ std::list<struct data_file_record> df;
+ std::list<struct data_file_record>::const_iterator iter;
+ std::map<std::string, std::string>::const_iterator map_iter;
+ std::string dir;
+ unsigned int num_dirs;
+
+ /* Copy the elements of o.loaded_data_files (each a (data file, path) pair) to
+ a list of data_file_records to make them easier to manipulate. */
+ for (map_iter = o.loaded_data_files.begin();
+ map_iter != o.loaded_data_files.end(); map_iter++) {
+ struct data_file_record r;
+ char *s;
+
+ r.data_file = map_iter->first;
+ s = path_get_dirname(map_iter->second.c_str());
+ if (s == NULL)
+ fatal("%s: failed to allocate temporary memory", __func__);
+ r.dir = std::string(s);
+ free(s);
+ s = path_get_basename(map_iter->second.c_str());
+ if (s == NULL)
+ fatal("%s: failed to allocate temporary memory", __func__);
+ r.file = std::string(s);
+ free(s);
+
+ df.push_back(r);
+ }
+
+ /* Sort the list, first by directory name, then by file name. This ensures
+ that records with the same directory name are contiguous. */
+ df.sort();
+
+ /* Count the number of distinct directories. Normally we print something only
+ if files came from more than one directory. */
+ if (df.empty()) {
+ num_dirs = 0;
+ } else {
+ num_dirs = 1;
+ iter = df.begin();
+ dir = iter->dir;
+ for (iter++; iter != df.end(); iter++) {
+ if (iter->dir != dir) {
+ num_dirs++;
+ dir = iter->dir;
+ }
+ }
+ }
+
+ /* Decide what to print out based on the number of distinct directories and
+ the verbosity and debugging levels. */
+ if (num_dirs == 0) {
+ /* If no files were read, print a message only in debugging mode. */
+ if (o.debugging > 0)
+ log_write(LOG_PLAIN, "No data files read.\n");
+ } else if (num_dirs == 1 && o.verbose && !o.debugging) {
+ /* If all the files were from the same directory and we're in verbose mode,
+ print a brief message unless we are also in debugging mode. */
+ log_write(LOG_PLAIN, "Read data files from: %s\n", dir.c_str());
+ } else if ((num_dirs == 1 && o.debugging) || num_dirs > 1) {
+ /* If files were read from more than one directory, or if they were read
+ from one directory and we are in debugging mode, display all the files
+ grouped by directory. */
+ iter = df.begin();
+ while (iter != df.end()) {
+ dir = iter->dir;
+ /* Write the directory name. */
+ log_write(LOG_PLAIN, "Read from %s:", dir.c_str());
+ /* Write files in that directory on the same line. */
+ while (iter != df.end() && iter->dir == dir) {
+ log_write(LOG_PLAIN, " %s", iter->file.c_str());
+ iter++;
+ }
+ log_write(LOG_PLAIN, ".\n");
+ }
+ }
+}
+
+static inline const char *nslog2str(nsock_loglevel_t loglevel) {
+ switch(loglevel) {
+ case NSOCK_LOG_DBG_ALL:
+ return "DEBUG FULL";
+ case NSOCK_LOG_DBG:
+ return "DEBUG";
+ case NSOCK_LOG_INFO:
+ return "INFO";
+ case NSOCK_LOG_ERROR:
+ return "ERROR";
+ default:
+ return "???";
+ };
+}
+
+void nmap_adjust_loglevel(bool trace) {
+ nsock_loglevel_t nsock_loglevel;
+
+ if (o.debugging >= 7)
+ nsock_loglevel = NSOCK_LOG_DBG_ALL;
+ else if (o.debugging >= 4)
+ nsock_loglevel = NSOCK_LOG_DBG;
+ else if (trace || o.debugging >= 2)
+ nsock_loglevel = NSOCK_LOG_INFO;
+ else
+ nsock_loglevel = NSOCK_LOG_ERROR;
+
+ nsock_set_loglevel(nsock_loglevel);
+}
+
+static void nmap_nsock_stderr_logger(const struct nsock_log_rec *rec) {
+ int elapsed_time;
+
+ elapsed_time = TIMEVAL_MSEC_SUBTRACT(rec->time, *(o.getStartTime()));
+
+ log_write(LOG_STDERR, "NSOCK %s [%.4fs] %s(): %s\n", nslog2str(rec->level),
+ elapsed_time/1000.0, rec->func, rec->msg);
+}
+
+void nmap_set_nsock_logger() {
+ nsock_set_log_function(nmap_nsock_stderr_logger);
+}