mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
2717 lines
92 KiB
C++
2717 lines
92 KiB
C++
|
|
/***************************************************************************
|
|
* 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-2021 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.org) 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;
|
|
struct protoent *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) {
|
|
struct protoent *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;
|
|
struct sockaddr_storage addr;
|
|
size_t sslen;
|
|
|
|
sslen = sizeof(addr);
|
|
currenths->TargetSockAddr(&addr, &sslen);
|
|
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 protoent *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);
|
|
}
|