diff options
Diffstat (limited to '')
-rw-r--r-- | output.cc | 2710 |
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, ¤t->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, ¤t->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), ¤ths->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); +} |