diff options
Diffstat (limited to '')
-rw-r--r-- | service_scan.cc | 2856 |
1 files changed, 2856 insertions, 0 deletions
diff --git a/service_scan.cc b/service_scan.cc new file mode 100644 index 0000000..7d99e5f --- /dev/null +++ b/service_scan.cc @@ -0,0 +1,2856 @@ + +/*************************************************************************** + * service_scan.cc -- Routines used for service fingerprinting to determine * + * what application-level protocol is listening on a given port * + * (e.g. snmp, http, ftp, smtp, etc.) * + * * + ***********************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 "service_scan.h" +#include "timing.h" +#include "NmapOps.h" +#include "nsock.h" +#include "Target.h" +#include "utils.h" +#include "nmap_error.h" +#include "payload.h" +#include "protocols.h" +#include "scan_lists.h" +#include "charpool.h" + +#include "nmap_tty.h" + +#include <errno.h> + +#if HAVE_OPENSSL +/* OpenSSL 1.0.0 needs _WINSOCKAPI_ to be defined, otherwise it loads + <windows.h> (through openssl/dtls1.h), which is incompatible with the + <winsock2.h> that we use. (It creates errors with the redefinition of struct + timeval, for example.) _WINSOCKAPI_ should be defined by our inclusion of + <winsock2.h>, but it appears to be undefined somewhere, possibly in + libpcap. */ +#define _WINSOCKAPI_ +#include <openssl/ssl.h> +#endif + +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +#ifndef IPPROTO_SCTP +#include "libnetutil/netutil.h" +#endif + +#include <algorithm> +#include <list> + +extern NmapOps o; + +// Details on a particular service (open port) we are trying to match +class ServiceNFO { +public: + ServiceNFO(AllProbes *AP); + ~ServiceNFO(); + + // If a service response to a given probeName, this function adds + // the response the the fingerprint for that service. The + // fingerprint can be printed when nothing matches the service. You + // can obtain the fingerprint (if any) via getServiceFingerprint(); + void addToServiceFingerprint(const char *probeName, const u8 *resp, + int resplen); + + // Get the service fingerprint. It is NULL if there is none, such + // as if there was a match before any other probes were finished (or + // if no probes gave back data). Note that this is plain + // NUL-terminated ASCII data, although the length is optionally + // available anyway. This function terminates the service fingerprint + // with a semi-colon + const char *getServiceFingerprint(int *flen); + + // Note that the next 2 members are for convenience and are not destroyed w/the ServiceNFO + Target *target; // the port belongs to this target host + // if a match is found, it is placed here. Otherwise NULL + const char *probe_matched; + // If a match is found, any product/version/info/hostname/ostype/devicetype + // is placed in these 6 strings. Otherwise the string will be 0 length. + char product_matched[80]; + char version_matched[80]; + char extrainfo_matched[256]; + char hostname_matched[80]; + char ostype_matched[32]; + char devicetype_matched[32]; + char cpe_a_matched[80]; + char cpe_h_matched[80]; + char cpe_o_matched[80]; + enum service_tunnel_type tunnel; /* SERVICE_TUNNEL_NONE, SERVICE_TUNNEL_SSL */ + // This stores our SSL session id, which will help speed up subsequent + // SSL connections. It's overwritten each time. void* is used so we don't + // need to #ifdef HAVE_OPENSSL all over. We'll cast later as needed. + void *ssl_session; + // if a match was found (see above), this tells whether it was a "soft" + // or hard match. It is always false if no match has been found. + bool softMatchFound; + // most recent probe executed (or in progress). If there has been a match + // (probe_matched != NULL), this will be the corresponding ServiceProbe. + ServiceProbe *currentProbe(); + // computes the next probe to test, and ALSO CHANGES currentProbe() to + // that! If newresp is true, the old response info will be lost and + // invalidated. Otherwise it remains as if it had been received by + // the current probe (useful after a NULL probe). + ServiceProbe *nextProbe(bool newresp); + // Resets the probes back to the first one. One case where this is useful is + // when SSL is detected -- we redo all probes through SSL. If freeFP, any + // service fingerprint is freed too. + void resetProbes(bool freefp); + // Number of milliseconds used so far to complete the present probe. Timeval + // can omitted, it is just there as an optimization in case you have it handy. + int probe_timemsused(const ServiceProbe *probe, const struct timeval *now = NULL); + // Number of milliseconds left to complete the present probe, or 0 if + // the probe is already expired. Timeval can omitted, it is just there + // as an optimization in case you have it handy. + int probe_timemsleft(const ServiceProbe *probe, const struct timeval *now = NULL); + enum serviceprobestate probe_state; // defined in portlist.h + nsock_iod niod; // The IO Descriptor being used in this probe (or NULL) + u16 portno; // in host byte order + u8 proto; // IPPROTO_TCP or IPPROTO_UDP + // The time that the current probe was executed (meaning TCP connection + // made or first UDP packet sent + struct timeval currentprobe_exec_time; + // Append newly-received data to the current response string (if any) + void appendtocurrentproberesponse(const u8 *respstr, int respstrlen); + // Get the full current response string. Note that this pointer is + // INVALIDATED if you call appendtocurrentproberesponse() or nextProbe() + u8 *getcurrentproberesponse(int *respstrlen); + AllProbes *AP; + // Is it possible this service is tcpwrapped? Not if a probe times out or + // gets a real response. + bool tcpwrap_possible; + +private: + // Adds a character to servicefp. Takes care of word wrapping if + // necessary at the given (wrapat) column. Chars will only be + // written if there is enough space. Otherwise it exits. + void addServiceChar(char c, int wrapat); + // Like addServiceChar, but for a whole zero-terminated string + void addServiceString(const char *s, int wrapat); + std::vector<ServiceProbe *>::iterator current_probe; + u8 *currentresp; + int currentresplen; + char *servicefp; + int servicefplen; + int servicefpalloc; +}; + +// This holds the service information for a group of Targets being service scanned. +class ServiceGroup { +public: + ServiceGroup(std::vector<Target *> &Targets, AllProbes *AP); + ~ServiceGroup(); + std::list<ServiceNFO *> services_finished; // Services finished (discovered or not) + std::list<ServiceNFO *> services_in_progress; // Services currently being probed + std::list<ServiceNFO *> services_remaining; // Probes not started yet + unsigned int ideal_parallelism; // Max (and desired) number of probes out at once. + ScanProgressMeter *SPM; + int num_hosts_timedout; // # of hosts timed out during (or before) scan +}; + +#define SUBSTARGS_MAX_ARGS 5 +#define SUBSTARGS_STRLEN 128 +#define SUBSTARGS_ARGTYPE_NONE 0 +#define SUBSTARGS_ARGTYPE_STRING 1 +#define SUBSTARGS_ARGTYPE_INT 2 +struct substargs { + int num_args; // Total number of arguments found + char str_args[SUBSTARGS_MAX_ARGS][SUBSTARGS_STRLEN]; + // This is the length of each string arg, since they can contain zeros. + // The str_args[] are zero-terminated for convenience in the cases where + // you know they won't contain zero. + int str_args_len[SUBSTARGS_MAX_ARGS]; + int int_args[SUBSTARGS_MAX_ARGS]; + // The type of each argument -- see #define's above. + int arg_types[SUBSTARGS_MAX_ARGS]; +}; + +/******************** PROTOTYPES *******************/ +static void servicescan_read_handler(nsock_pool nsp, nsock_event nse, void *mydata); +static void servicescan_write_handler(nsock_pool nsp, nsock_event nse, void *mydata); +static void servicescan_connect_handler(nsock_pool nsp, nsock_event nse, void *mydata); +static void end_svcprobe(enum serviceprobestate probe_state, ServiceGroup *SG, ServiceNFO *svc, nsock_iod nsi); +static int scanThroughTunnel(ServiceNFO *svc); +static bool processMatch(const struct MatchDetails *MD, ServiceNFO *svc, + const char *probeName, const char *fallbackName); + +ServiceProbeMatch::ServiceProbeMatch() { + deflineno = -1; + servicename = NULL; + matchstr = NULL; + product_template = version_template = info_template = NULL; + hostname_template = ostype_template = devicetype_template = NULL; + regex_compiled = NULL; + match_data = NULL; + isInitialized = false; + matchops_ignorecase = false; + matchops_dotall = false; + isSoft = false; +} + +ServiceProbeMatch::~ServiceProbeMatch() { + std::vector<char *>::iterator it; + if (!isInitialized) return; + if (matchstr) free(matchstr); + if (product_template) free(product_template); + if (version_template) free(version_template); + if (info_template) free(info_template); + if (hostname_template) free(hostname_template); + if (ostype_template) free(ostype_template); + if (devicetype_template) free(devicetype_template); + for (it = cpe_templates.begin(); it != cpe_templates.end(); it++) + free(*it); + if (regex_compiled) + { + pcre2_code_free(regex_compiled); + regex_compiled=NULL; + } + if (match_data) + { + pcre2_match_data_free(match_data); + match_data=NULL; + } + if (match_context) + { + pcre2_match_context_free(match_context); + match_context=NULL; + } + isInitialized = false; +} + +/* Read the next tmplt from *matchtext and update *matchtext. Return true iff + a template was read. modestr and flags must each point to a char[4]. For example, after + matchtext = "p/123/ d/456/"; + next_template(&matchtext, modestr, flags, &tmplt); + then + matchtext == " d/456/" + modestr == "p" + tmplt == "123" + flags == "" + *tmplt must be freed if the return value is true. + Special handling for cpe:/txt/ => modestr == "cpe" tmplt == "cpe:/txt" */ +static bool next_template(const char **matchtext, char modestr[4], char **tmplt, + char flags[4], int lineno) { + const char *p, *q; + char delimchar; + int i; + + p = *matchtext; + while(isspace((int) (unsigned char) *p)) + p++; + if (*p == '\0') + return false; + + for (i=0; i < 3 && isalpha(p[i]); i++) + modestr[i] = p[i]; + q = p + i; + modestr[i] = '\0'; + if (*q == ':' && 0 == strcmp(modestr, "cpe")) { + q++; + if (*q != '/') + fatal("%s: parse error (cpe delimiter not '/') on line %d of nmap-service-probes", __func__, lineno); + // p == "cpe:/..." + } + else { + if (*q == '\0' || isspace(*q)) + fatal("%s: parse error (bare word) on line %d of nmap-service-probes", __func__, lineno); + // p == start of template + p = q + 1; + } + + delimchar = *q; + + q = strchr(q + 1, delimchar); + if (q == NULL) + fatal("%s: parse error (missing end delimiter) on line %d of nmap-service-probes", __func__, lineno); + + *tmplt = mkstr(p, q); + + + // *q == delimchar; + p = q + 1; + for (i=0; i < 3 && isalpha(p[i]); i++) + flags[i] = p[i]; + flags[i] = '\0'; + + q = p + i; + if (!isspace(*q)) + fatal("%s: parse error (flags too long) on line %d of nmap-service-probes", __func__, lineno); + + /* Update pointer for caller. */ + *matchtext = q; + + return true; +} + +// match text from the nmap-service-probes file. This must be called +// before you try and do anything with this match. This function +// should be passed the whole line starting with "match" or +// "softmatch" in nmap-service-probes. The line number that the text +// is provided so that it can be reported in error messages. This +// function will abort the program if there is a syntax problem. +void ServiceProbeMatch::InitMatch(const char *matchtext, int lineno) { + const char *p; + char *tmptemplate; + char modestr[4]; + char flags[4]; + int pcre2_compile_ops = 0; + int pcre2_errcode; + PCRE2_SIZE pcre2_erroffset; + char **curr_tmp = NULL; + + if (isInitialized) fatal("Sorry ... %s does not yet support reinitializion", __func__); + if (!matchtext || !*matchtext) + fatal("%s: no matchtext passed in (line %d of nmap-service-probes)", __func__, lineno); + isInitialized = true; + + deflineno = lineno; + while(isspace((int) (unsigned char) *matchtext)) matchtext++; + + // first we find whether this is a "soft" or normal match + if (strncmp(matchtext, "softmatch ", 10) == 0) { + isSoft = true; + matchtext += 10; + } else if (strncmp(matchtext, "match ", 6) == 0) { + isSoft = false; + matchtext += 6; + } else + fatal("%s: parse error on line %d of nmap-service-probes - must begin with \"match\" or \"softmatch\"", __func__, lineno); + + // next comes the service name + p = strchr(matchtext, ' '); + if (!p) fatal("%s: parse error on line %d of nmap-service-probes: could not find service name", __func__, lineno); + + servicename = cp_strndup(matchtext, p - matchtext); + + // The next part is a perl style regular expression specifier, like: + // m/^220 .*smtp/i Where 'm' means a normal regular expressions is + // used, the char after m can be anything (within reason, slash in + // this case) and tells us what delineates the end of the regex. + // After the delineating character are any single-character + // options. ('i' means "case insensitive", 's' means that . matches + // newlines (both are just as in perl) + matchtext = p; + if (!next_template(&matchtext, modestr, &matchstr, flags, lineno)) + fatal("%s: parse error on line %d of nmap-service-probes", __func__, lineno); + + if (strcmp(modestr, "m") != 0) + fatal("%s: parse error on line %d of nmap-service-probes: matchtext must begin with 'm'", __func__, lineno); + + // any options? + for (p = flags; *p != '\0'; p++) { + if (*p == 'i') + matchops_ignorecase = true; + else if (*p == 's') + matchops_dotall = true; + else + fatal("%s: illegal regexp option on line %d of nmap-service-probes", __func__, lineno); + } + + // Next we compile and study the regular expression to match + if (matchops_ignorecase) + pcre2_compile_ops |= PCRE2_CASELESS; + + if (matchops_dotall) + pcre2_compile_ops |= PCRE2_DOTALL; + + regex_compiled = pcre2_compile((PCRE2_SPTR)matchstr,PCRE2_ZERO_TERMINATED, pcre2_compile_ops, &pcre2_errcode, + &pcre2_erroffset, NULL); + + if (regex_compiled == NULL) + fatal("%s: illegal regexp on line %d of nmap-service-probes (at regexp offset %ld): %d\n", __func__, lineno, pcre2_erroffset, pcre2_errcode); + + // creates a new match data block for holding the result of a match + match_data = pcre2_match_data_create_from_pattern( + regex_compiled,NULL + ); + + if (!match_data) { + fatal("%s: failed to allocate match_data\n", __func__); + } + + match_context = pcre2_match_context_create(NULL); + + if (!match_context) { + fatal("%s: failed to allocate match_context\n", __func__); + } + // Set some limits to avoid evil match cases. + // These are flexible; if they cause problems, increase them. + pcre2_set_match_limit(match_context, 100000); +#ifdef pcre2_set_depth_limit + // Changed name in PCRE2 10.30. PCRE2 uses macro definitions for function + // names, so we don't have to add this to configure.ac. + pcre2_set_depth_limit(match_context, 10000); +#else + pcre2_set_recursion_limit(match_context, 10000); +#endif + + /* OK! Now we look for any templates of the form ?/.../ + * where ? is either p, v, i, h, o, or d. / is any + * delimiter character and ... is a template */ + + while (next_template(&matchtext, modestr, &tmptemplate, flags, lineno)) { + switch (modestr[0] + (modestr[1] << 8)) { + case 'p': + curr_tmp = &product_template; + break; + case 'v': + curr_tmp = &version_template; + break; + case 'i': + curr_tmp = &info_template; + break; + case 'h': + curr_tmp = &hostname_template; + break; + case 'o': + curr_tmp = &ostype_template; + break; + case 'd': + curr_tmp = &devicetype_template; + break; + case 'c' + ('p' << 8): + if (modestr[2] == 'e' && modestr[3] == '\0') { + cpe_templates.push_back(NULL); + curr_tmp = &cpe_templates.back(); + break; + } + default: + fatal("%s: Unknown template specifier '%s' on line %d of nmap-service-probes", __func__, modestr, lineno); + break; + } + + /* This one already defined? */ + if (*curr_tmp) { + if (o.debugging) { + error("WARNING: Template \"%s/%s/\" replaced with \"%s/%s/\" on line %d of nmap-service-probes", + modestr, *curr_tmp, modestr, tmptemplate, lineno); + } + free(*curr_tmp); + } + + *curr_tmp = tmptemplate; + } + + isInitialized = 1; +} + + // If the buf (of length buflen) match the regex in this + // ServiceProbeMatch, returns the details of the match (service + // name, version number if applicable, and whether this is a "soft" + // match. If the buf doesn't match, the serviceName field in the + // structure will be NULL. The MatchDetails structure returned is + // only valid until the next time this function is called. The only + // exception is that the serviceName field can be saved throughout + // program execution. If no version matched, that field will be + // NULL. +const struct MatchDetails *ServiceProbeMatch::testMatch(const u8 *buf, int buflen) { + int rc; + static char product[80]; + static char version[80]; + static char info[256]; /* We will truncate with ... later */ + static char hostname[80]; + static char ostype[32]; + static char devicetype[32]; + static char cpe_a[80], cpe_h[80], cpe_o[80]; + char *bufc = (char *) buf; + assert(isInitialized); + + // Clear out the output struct + memset(&MD_return, 0, sizeof(MD_return)); + MD_return.isSoft = isSoft; + + rc = pcre2_match(regex_compiled, (PCRE2_SPTR8)bufc, buflen, 0, 0, match_data, match_context); + if (rc < 0) { + if (rc == PCRE2_ERROR_MATCHLIMIT) { + if (o.debugging || o.verbose > 1) + error("Warning: Hit PCRE_ERROR_MATCHLIMIT when probing for service %s with the regex '%s'", servicename, matchstr); + } else + if (rc == PCRE2_ERROR_RECURSIONLIMIT) { + if (o.debugging || o.verbose > 1) + error("Warning: Hit PCRE_ERROR_RECURSIONLIMIT when probing for service %s with the regex '%s'", servicename, matchstr); + } else + if (rc != PCRE2_ERROR_NOMATCH) { + fatal("Unexpected PCRE error (%d) when probing for service %s with the regex '%s'", rc, servicename, matchstr); + } + } else { + // Yeah! Match apparently succeeded. + // Now lets get the version number if available + getVersionStr(buf, buflen, product, sizeof(product), version, sizeof(version), info, sizeof(info), + hostname, sizeof(hostname), ostype, sizeof(ostype), devicetype, sizeof(devicetype), + cpe_a, sizeof(cpe_a), cpe_h, sizeof(cpe_h), cpe_o, sizeof(cpe_o)); + if (*product) MD_return.product = product; + if (*version) MD_return.version = version; + if (*info) MD_return.info = info; + if (*hostname) MD_return.hostname = hostname; + if (*ostype) MD_return.ostype = ostype; + if (*devicetype) MD_return.devicetype = devicetype; + if (*cpe_a) MD_return.cpe_a = cpe_a; + if (*cpe_h) MD_return.cpe_h = cpe_h; + if (*cpe_o) MD_return.cpe_o = cpe_o; + + MD_return.serviceName = servicename; + MD_return.lineno = getLineNo(); + } + + return &MD_return; +} + +// This simple function parses arguments out of a string. The string +// starts with the first argument. Each argument can be a string or +// an integer. Strings must be enclosed in double quotes (""). Most +// standard C-style escapes are supported. If this is successful, the +// number of args found is returned, args is filled appropriately, and +// args_end (if non-null) is set to the character after the closing +// ')'. Otherwise we return -1 and the values of args and args_end +// are undefined. +static int getsubstcommandargs(struct substargs *args, char *args_start, + char **args_end) { + char *p; + unsigned int len; + if (!args || !args_start) return -1; + + memset(args, 0, sizeof(*args)); + + while(*args_start && *args_start != ')') { + // Find the next argument. + while(isspace((int) (unsigned char) *args_start)) args_start++; + if (*args_start == ')') + break; + else if (*args_start == '"') { + // OK - it is a string + // Do we have space for another arg? + if (args->num_args == SUBSTARGS_MAX_ARGS) + return -1; + do { + args_start++; + if (*args_start == '"' && (*(args_start - 1) != '\\' || *(args_start - 2) == '\\')) + break; + len = args->str_args_len[args->num_args]; + if (len >= SUBSTARGS_STRLEN - 1) + return -1; + args->str_args[args->num_args][len] = *args_start; + args->str_args_len[args->num_args]++; + } while(*args_start); + len = args->str_args_len[args->num_args]; + args->str_args[args->num_args][len] = '\0'; + // Now handle escaped characters and such + if (!cstring_unescape(args->str_args[args->num_args], &len)) + return -1; + args->str_args_len[args->num_args] = len; + args->arg_types[args->num_args] = SUBSTARGS_ARGTYPE_STRING; + args->num_args++; + args_start++; + args_start = strpbrk(args_start, ",)"); + if (!args_start) return -1; + if (*args_start == ',') args_start++; + } else { + // Must be an integer argument + args->int_args[args->num_args] = (int) strtol(args_start, &p, 0); + if (p <= args_start) return -1; + args_start = p; + args->arg_types[args->num_args] = SUBSTARGS_ARGTYPE_INT; + args->num_args++; + args_start = strpbrk(args_start, ",)"); + if (!args_start) return -1; + if (*args_start == ',') args_start++; + } + } + + if (*args_start == ')') args_start++; + if (args_end) *args_end = args_start; + return args->num_args; +} + +/* These three functions manage a growing string buffer, appended to at the end. + Begin with strbuf_init, follow with any number of strbuf_append, and end with + strbuf_finish. */ +static void strbuf_init(char **buf, size_t *n, size_t *len) { + *buf = NULL; + *n = 0; + *len = 0; +} + +static void strbuf_append(char **buf, size_t *n, size_t *len, + const char *from, size_t fromlen) { + /* Double the size of the buffer if necessary. */ + if (*len == 0 || *len + fromlen > *n) { + *n = (*len + fromlen) * 2; + *buf = (char *) safe_realloc(*buf, *n + 1); + } + memcpy(*buf + *len, from, fromlen); + *len += fromlen; +} + +/* Trim to length. (Also does initial allocation when *buf is empty.) */ +static void strbuf_finish(char **buf, size_t *n, size_t *len) { + *buf = (char *) safe_realloc(*buf, *len + 1); + (*buf)[*len] = '\0'; +} + +/* Transform a string so that it is safe to insert into the middle of a CPE URL. */ +static char *transform_cpe(const char *s) { + char *result; + size_t n, len, repllen; + const char *p; + + strbuf_init(&result, &n, &len); + for (p = s; *p != '\0'; p++) { + const char *repl; + char buf[32]; + + /* Section 5.4 of the CPE specification lists these characters to be + escaped. */ + if (strchr(":/?#[]@!$&'()*+,;=%<>\"", *p) != NULL) { + Snprintf(buf, sizeof(buf), "%%%02X", *p); + repl = buf; + /* Replacing spaces with underscores is also a convention. */ + } else if (isspace(*p)) { + repl = "_"; + /* Otherwise just make lower-case. */ + } else { + buf[0] = tolower(*p); + buf[1] = '\0'; + repl = buf; + } + + repllen = strlen(repl); + strbuf_append(&result, &n, &len, repl, repllen); + } + strbuf_finish(&result, &n, &len); + + return result; +} + +// This function does the substitution of a placeholder like $2 or $P(4). It +// returns a newly allocated string, or NULL if it fails. tmplvar is a template +// variable, such as "$P(2)". We set *tmplvarend to the character after the +// variable. subject, subjectlen, and match_data mean the same as in +// dotmplsubst(). +static char *substvar(char *tmplvar, char **tmplvarend, + const u8 *subject, size_t subjectlen, pcre2_match_data *match_data + ) { + char substcommand[16]; + char *p = NULL; + char *p_end; + u8 subnum = 0; + PCRE2_SIZE offstart, offend; + int rc; + struct substargs command_args; + char *result; + size_t n, len; + + // skip the '$' + if (*tmplvar != '$') return NULL; + tmplvar++; + + if (!isdigit((int) (unsigned char) *tmplvar)) { + int commandlen; + /* This is a command like $P(1). */ + p = strchr(tmplvar, '('); + if (!p) return NULL; + commandlen = p - tmplvar; + if (!commandlen || commandlen >= (int) sizeof(substcommand)) + return NULL; + memcpy(substcommand, tmplvar, commandlen); + substcommand[commandlen] = '\0'; + tmplvar = p+1; + // Now we grab the arguments. + rc = getsubstcommandargs(&command_args, tmplvar, &p_end); + if (rc <= 0) return NULL; + tmplvar = p_end; + } else { + /* This is a placeholder like $2. */ + substcommand[0] = '\0'; + subnum = *tmplvar - '0'; + tmplvar++; + } + + if (tmplvarend) *tmplvarend = tmplvar; + u32 nummatches = pcre2_get_ovector_count(match_data); + PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); + + strbuf_init(&result, &n, &len); + if (!*substcommand) { + /* Handler for a placeholder like $2. */ + if (subnum > 9 || subnum <= 0) return NULL; + if (subnum >= nummatches) return NULL; + offstart = ovector[subnum * 2]; + if (offstart == PCRE2_UNSET) return NULL; + offend = ovector[subnum * 2 + 1]; + assert(offstart <= subjectlen); + assert(offend != PCRE2_UNSET && offend <= subjectlen); + // A plain-jane copy + strbuf_append(&result, &n, &len, (const char *) subject + offstart, offend - offstart); + } else if (strcmp(substcommand, "P") == 0) { + if (command_args.num_args != 1 || + command_args.arg_types[0] != SUBSTARGS_ARGTYPE_INT) { + return NULL; + } + subnum = command_args.int_args[0]; + if (subnum > 9 || subnum <= 0) return NULL; + if (subnum >= nummatches) return NULL; + offstart = ovector[subnum * 2]; + if (offstart == PCRE2_UNSET) return NULL; + offend = ovector[subnum * 2 + 1]; + assert(offstart <= subjectlen); + assert(offend != PCRE2_UNSET && offend <= subjectlen); + // This filter only includes printable characters. It is particularly + // useful for collapsing unicode text that looks like + // "W\0O\0R\0K\0G\0R\0O\0U\0P\0" + for(PCRE2_SIZE i=offstart; i < offend; i++) { + if (isprint((int) subject[i])) + strbuf_append(&result, &n, &len, (const char *) subject + i, 1); + } + } else if (strcmp(substcommand, "SUBST") == 0) { + char *findstr, *replstr; + int findstrlen, replstrlen; + if (command_args.num_args != 3 || + command_args.arg_types[0] != SUBSTARGS_ARGTYPE_INT || + command_args.arg_types[1] != SUBSTARGS_ARGTYPE_STRING || + command_args.arg_types[2] != SUBSTARGS_ARGTYPE_STRING) { + return NULL; + } + subnum = command_args.int_args[0]; + if (subnum > 9 || subnum <= 0) return NULL; + if (subnum >= nummatches) return NULL; + offstart = ovector[subnum * 2]; + if (offstart == PCRE2_UNSET) return NULL; + offend = ovector[subnum * 2 + 1]; + assert(offstart <= subjectlen); + assert(offend != PCRE2_UNSET && offend <= subjectlen); + findstr = command_args.str_args[1]; + findstrlen = command_args.str_args_len[1]; + replstr = command_args.str_args[2]; + replstrlen = command_args.str_args_len[2]; + for(PCRE2_SIZE i=offstart; i < offend; ) { + if (memcmp(subject + i, findstr, findstrlen) != 0) { + strbuf_append(&result, &n, &len, (const char *) subject + i, 1); // no match + i++; + } else { + // The find string was found, copy it to newstring + strbuf_append(&result, &n, &len, replstr, replstrlen); + i += findstrlen; + } + } + } else if (strcmp(substcommand, "I") == 0 ){ + // Parse an unsigned int + long long unsigned val = 0; + bool bigendian = true; + char buf[24]; //0xffffffffffffffff = 18446744073709551615, 20 chars + int buflen; + if (command_args.num_args != 2 || + command_args.arg_types[0] != SUBSTARGS_ARGTYPE_INT || + command_args.arg_types[1] != SUBSTARGS_ARGTYPE_STRING || + command_args.str_args_len[1] != 1) { + return NULL; + } + subnum = command_args.int_args[0]; + if (subnum > 9 || subnum <= 0) return NULL; + if (subnum >= nummatches) return NULL; + offstart = ovector[subnum * 2]; + if (offstart == PCRE2_UNSET) return NULL; + offend = ovector[subnum * 2 + 1]; + assert(offend != PCRE2_UNSET && offstart <= subjectlen); + + // overflow + if (offend - offstart > 8) { + return NULL; + } + switch (command_args.str_args[1][0]) { + case '>': + bigendian = true; + break; + case '<': + bigendian = false; + break; + default: + return NULL; + break; + } + if (bigendian) { + for(PCRE2_SIZE i=offstart; i < offend; i++) { + val = (val<<8) + subject[i]; + } + } else { + for(PCRE2_SIZE i=offend - 1; i > offstart - 1; i--) { + val = (val<<8) + subject[i]; + } + } + buflen = Snprintf(buf, sizeof(buf), "%llu", val); + if (buflen < 0 || buflen >= (int) sizeof(buf)) { + return NULL; + } + strbuf_append(&result, &n, &len, buf, buflen); + } else return NULL; // Unknown command + + strbuf_finish(&result, &n, &len); + return result; +} + + + +// This function takes a template string (tmpl) which can have +// placeholders in it such as $1 for substring matches in a regexp +// that was run against subject, and subjectlen, with the +// matches in match_data. The NUL-terminated newly composted string is +// placed into 'newstr', as long as it doesn't exceed 'newstrlen' +// bytes. Trailing whitespace and commas are removed. Returns zero for success +// +// The transform argument is a function pointer. If not NULL, the given +// function is applied to all substitutions before they are inserted +// into the result string. +static int dotmplsubst(const u8 *subject, size_t subjectlen, + pcre2_match_data *match_data, char *tmpl, char *newstr, + int newstrlen, + char *(*transform)(const char *) = NULL) { + int newlen; + char *srcstart=tmpl, *srcend; + char *dst = newstr; + char *newstrend = newstr + newstrlen; // Right after the final char + char *subst; + + if (!newstr || !tmpl) return -1; + if (newstrlen < 3) return -1; // Have a nice day! + + while(*srcstart) { + // First do any literal text before '$' + srcend = strchr(srcstart, '$'); + if (!srcend) { + // Only literal text remain! + while(*srcstart) { + if (dst >= newstrend - 1) + return -1; + *dst++ = *srcstart++; + } + *dst = '\0'; + while (--dst >= newstr) { + if (isspace((int) (unsigned char) *dst) || *dst == ',') + *dst = '\0'; + else break; + } + return 0; + } else { + // Copy the literal text up to the '$', then do the substitution + newlen = srcend - srcstart; + if (newlen > 0) { + if (newstrend - dst <= newlen - 1) + return -1; + memcpy(dst, srcstart, newlen); + dst += newlen; + } + srcstart = srcend; + subst = substvar(srcstart, &srcend, subject, subjectlen, match_data); + if (subst == NULL) + return -1; + /* Apply transformation if requested. */ + if (transform != NULL) { + char *tmp = subst; + subst = transform(subst); + free(tmp); + if (subst == NULL) + return -1; + } + newlen = strlen(subst); + if (dst + newlen >= newstrend - 1) { + free(subst); + return -1; + } + memcpy(dst, subst, newlen); + free(subst); + dst += newlen; + srcstart = srcend; + } + } + + if (dst >= newstrend - 1) + return -1; + *dst = '\0'; + while (--dst >= newstr) { + if (isspace((int) (unsigned char) *dst) || *dst == ',') + *dst = '\0'; + else break; + } + return 0; + +} + + +// Use the version templates and the match data included here +// to put the version info into the given strings, (as long as the sizes +// are sufficient). Returns zero for success. If no template is available +// for a string, that string will have zero length after the function +// call (assuming the corresponding length passed in is at least 1) + +int ServiceProbeMatch::getVersionStr(const u8 *subject, size_t subjectlen, + char *product, size_t productlen, + char *version, size_t versionlen, char *info, size_t infolen, + char *hostname, size_t hostnamelen, char *ostype, size_t ostypelen, + char *devicetype, size_t devicetypelen, + char *cpe_a, size_t cpe_alen, + char *cpe_h, size_t cpe_hlen, + char *cpe_o, size_t cpe_olen) const { + + int rc; + assert(productlen >= 0 && versionlen >= 0 && infolen >= 0 && + hostnamelen >= 0 && ostypelen >= 0 && devicetypelen >= 0); + + if (productlen > 0) *product = '\0'; + if (versionlen > 0) *version = '\0'; + if (infolen > 0) *info = '\0'; + if (hostnamelen > 0) *hostname = '\0'; + if (ostypelen > 0) *ostype = '\0'; + if (devicetypelen > 0) *devicetype = '\0'; + if (cpe_alen > 0) *cpe_a = '\0'; + if (cpe_hlen > 0) *cpe_h = '\0'; + if (cpe_olen > 0) *cpe_o = '\0'; + int retval = 0; + + // Now lets get this started! We begin with the product name + if (product_template) { + rc = dotmplsubst(subject, subjectlen, match_data, product_template, product, productlen); + if (rc != 0) { + error("Warning: Servicescan failed to fill product_template (subjectlen: %lu, productlen: %lu). Capture exceeds length? Match string was line %d: p/%s/%s/%s", subjectlen, productlen, deflineno, + (product_template)? product_template : "", + (version_template)? version_template : "", + (info_template)? info_template : ""); + if (productlen > 0) *product = '\0'; + retval = -1; + } + } + + if (version_template) { + rc = dotmplsubst(subject, subjectlen, match_data, version_template, version, versionlen); + if (rc != 0) { + error("Warning: Servicescan failed to fill version_template (subjectlen: %lu, versionlen: %lu). Capture exceeds length? Match string was line %d: v/%s/%s/%s", subjectlen, versionlen, deflineno, + (product_template)? product_template : "", + (version_template)? version_template : "", + (info_template)? info_template : ""); + if (versionlen > 0) *version = '\0'; + retval = -1; + } + } + + if (info_template) { + rc = dotmplsubst(subject, subjectlen, match_data, info_template, info, infolen); + if (rc != 0) { + error("Warning: Servicescan failed to fill info_template (subjectlen: %lu, infolen: %lu). Capture exceeds length? Match string was line %d: i/%s/%s/%s", subjectlen, infolen, deflineno, + (product_template)? product_template : "", + (version_template)? version_template : "", + (info_template)? info_template : ""); + if (infolen > 0) *info = '\0'; + retval = -1; + } + } + + if (hostname_template) { + rc = dotmplsubst(subject, subjectlen, match_data, hostname_template, hostname, hostnamelen); + if (rc != 0) { + error("Warning: Servicescan failed to fill hostname_template (subjectlen: %lu, hostnamelen: %lu). Capture exceeds length? Match string was line %d: h/%s/", subjectlen, hostnamelen, deflineno, + (hostname_template)? hostname_template : ""); + if (hostnamelen > 0) *hostname = '\0'; + retval = -1; + } + } + + if (ostype_template) { + rc = dotmplsubst(subject, subjectlen, match_data, ostype_template, ostype, ostypelen); + if (rc != 0) { + error("Warning: Servicescan failed to fill ostype_template (subjectlen: %lu, ostypelen: %lu). Capture exceeds length? Match string was line %d: o/%s/", subjectlen, ostypelen, deflineno, + (ostype_template)? ostype_template : ""); + if (ostypelen > 0) *ostype = '\0'; + retval = -1; + } + } + + if (devicetype_template) { + rc = dotmplsubst(subject, subjectlen, match_data, devicetype_template, devicetype, devicetypelen); + if (rc != 0) { + error("Warning: Servicescan failed to fill devicetype_template (subjectlen: %lu, devicetypelen: %lu). Too long? Match string was line %d: d/%s/", subjectlen, devicetypelen, deflineno, + (devicetype_template)? devicetype_template : ""); + if (devicetypelen > 0) *devicetype = '\0'; + retval = -1; + } + } + + /* There may be multiple cpe templates. We peek at the first character and + store in cpe_a, cpe_h, or cpe_o as appropriate. */ + for (unsigned int i = 0; i < cpe_templates.size(); i++) { + char *cpe; + size_t cpelen; + int part; + + part = cpe_get_part(cpe_templates[i]); + switch (part) { + case 'a': + cpe = cpe_a; + cpelen = cpe_alen; + break; + case 'h': + cpe = cpe_h; + cpelen = cpe_hlen; + break; + case 'o': + cpe = cpe_o; + cpelen = cpe_olen; + break; + default: + error("Warning: ignoring cpe:// template with unknown part '%c' (0x%02X)", + isprint(part) ? part : '.', part); + continue; + break; + } + rc = dotmplsubst(subject, subjectlen, match_data, cpe_templates[i], cpe, cpelen, transform_cpe); + if (rc != 0) { + error("Warning: Servicescan failed to fill cpe_%c (subjectlen: %lu, cpelen: %lu). Too long? Match string was line %d: %s", part, subjectlen, cpelen, deflineno, + (cpe_templates[i])? cpe_templates[i] : ""); + if (cpelen > 0) *cpe = '\0'; + retval = -1; + } + } + + return retval; +} + + +ServiceProbe::ServiceProbe() { + int i; + probename = NULL; + probestring = NULL; + totalwaitms = DEFAULT_SERVICEWAITMS; + tcpwrappedms = DEFAULT_TCPWRAPPEDMS; + probestringlen = 0; probeprotocol = -1; + // The default rarity level for a probe without a rarity + // directive - should almost never have to be relied upon. + rarity = 5; + notForPayload = false; + fallbackStr = NULL; + for (i=0; i<MAXFALLBACKS+1; i++) fallbacks[i] = NULL; +} + +ServiceProbe::~ServiceProbe() { + std::vector<ServiceProbeMatch *>::iterator vi; + + for(vi = matches.begin(); vi != matches.end(); vi++) { + delete *vi; + } + + if (fallbackStr) free(fallbackStr); +} + + // Parses the "probe " line in the nmap-service-probes file. Pass the rest of the line + // after "probe ". The format better be: + // [TCP|UDP] [probename] q|probetext| + // Note that the delimiter (|) of the probetext can be anything (within reason) + // the lineno is requested because this function will bail with an error + // (giving the line number) if it fails to parse the string. +void ServiceProbe::setProbeDetails(char *pd, int lineno) { + char *p; + unsigned int len; + char delimiter; + + if (!pd || !*pd) + fatal("Parse error on line %d of nmap-service-probes: no arguments found!", lineno); + + // First the protocol + if (strncmp(pd, "TCP ", 4) == 0) + probeprotocol = IPPROTO_TCP; + else if (strncmp(pd, "UDP ", 4) == 0) + probeprotocol = IPPROTO_UDP; + else fatal("Parse error on line %d of nmap-service-probes: invalid protocol", lineno); + pd += 4; + + // Next the service name + if (!isalnum((int) (unsigned char) *pd)) fatal("Parse error on line %d of nmap-service-probes - bad probe name", lineno); + p = strchr(pd, ' '); + if (!p) fatal("Parse error on line %d of nmap-service-probes - nothing after probe name", lineno); + probename = cp_strndup(pd, p - pd); + + // Now for the probe itself + pd = p+1; + + if (*pd != 'q') fatal("Parse error on line %d of nmap-service-probes - probe string must begin with 'q'", lineno); + delimiter = *(++pd); + p = strchr(++pd, delimiter); + if (!p) fatal("Parse error on line %d of nmap-service-probes -- no ending delimiter for probe string", lineno); + *p = '\0'; + if (!cstring_unescape(pd, &len)) { + fatal("Parse error on line %d of nmap-service-probes: bad probe string escaping", lineno); + } + setProbeString((const u8 *)pd, len); + // Optional extensible flags + pd = p+1; + while (*pd != '\0' && *pd != '\n') { + while(*pd && isspace((int) (unsigned char) *pd)) pd++; + if (0 == strncmp(pd, "no-payload", 10)) { + notForPayload = true; + break; // Remove this if we handle more than 1 flag in the future + } + while (*pd && !isspace((int) (unsigned char) *pd)) pd++; + } +} + +void ServiceProbe::setProbeString(const u8 *ps, int stringlen) { + probestringlen = stringlen; + if (stringlen > 0) { + probestring = (const u8 *)cp_strndup((const char *)ps, stringlen); + } else probestring = NULL; +} + +void ServiceProbe::setPortVector(std::vector<u16> *portv, const char *portstr, + int lineno) { + const char *current_range; + char *endptr; + long int rangestart = 0, rangeend = 0; + + current_range = portstr; + + do { + while(*current_range && isspace((int) (unsigned char) *current_range)) current_range++; + if (isdigit((int) (unsigned char) *current_range)) { + rangestart = strtol(current_range, &endptr, 10); + if (rangestart < 0 || rangestart > 65535) { + fatal("Parse error on line %d of nmap-service-probes: Ports must be between 0 and 65535 inclusive", lineno); + } + current_range = endptr; + while(isspace((int) (unsigned char) *current_range)) current_range++; + } else { + fatal("Parse error on line %d of nmap-service-probes: An example of proper portlist form is \"21-25,53,80\"", lineno); + } + + /* Now I have a rangestart, time to go after rangeend */ + if (!*current_range || *current_range == ',') { + /* Single port specification */ + rangeend = rangestart; + } else if (*current_range == '-') { + current_range++; + if (isdigit((int) (unsigned char) *current_range)) { + rangeend = strtol(current_range, &endptr, 10); + if (rangeend < 0 || rangeend > 65535 || rangeend < rangestart) { + fatal("Parse error on line %d of nmap-service-probes: Ports must be between 0 and 65535 inclusive", lineno); + } + current_range = endptr; + } else { + fatal("Parse error on line %d of nmap-service-probes: An example of proper portlist form is \"21-25,53,80\"", lineno); + } + } else { + fatal("Parse error on line %d of nmap-service-probes: An example of proper portlist form is \"21-25,53,80\"", lineno); + } + + /* Now I have a rangestart and a rangeend, so I can add these ports */ + while(rangestart <= rangeend) { + portv->push_back(rangestart); + rangestart++; + } + + /* Find the next range */ + while(isspace((int) (unsigned char) *current_range)) current_range++; + if (*current_range && *current_range != ',') { + fatal("Parse error on line %d of nmap-service-probes: An example of proper portlist form is \"21-25,53,80\"", lineno); + } + if (*current_range == ',') + current_range++; + } while(current_range && *current_range); +} + + // Takes a string as given in the 'ports '/'sslports ' line of + // nmap-service-probes. Pass in the list from the appropriate + // line. For 'sslports', tunnel should be specified as + // SERVICE_TUNNEL_SSL. Otherwise use SERVICE_TUNNEL_NONE. The line + // number is requested because this function will bail with an error + // (giving the line number) if it fails to parse the string. Ports + // are a comma separated list of ports and ranges + // (e.g. 53,80,6000-6010). +void ServiceProbe::setProbablePorts(enum service_tunnel_type tunnel, + const char *portstr, int lineno) { + if (tunnel == SERVICE_TUNNEL_NONE) + setPortVector(&probableports, portstr, lineno); + else { + assert(tunnel == SERVICE_TUNNEL_SSL); + setPortVector(&probablesslports, portstr, lineno); + } +} + + /* Returns true if the passed in port is on the list of probable + ports for this probe and tunnel type. Use a tunnel of + SERVICE_TUNNEL_SSL or SERVICE_TUNNEL_NONE as appropriate */ +bool ServiceProbe::portIsProbable(enum service_tunnel_type tunnel, u16 portno) const { + const std::vector<u16> *portv; + + portv = (tunnel == SERVICE_TUNNEL_SSL)? &probablesslports : &probableports; + + if (find(portv->begin(), portv->end(), portno) == portv->end()) + return false; + return true; +} + + // Returns true if the passed in service name is among those that can + // be detected by the matches in this probe; +bool ServiceProbe::serviceIsPossible(const char *sname) const { + std::vector<const char *>::const_iterator vi; + + for(vi = detectedServices.begin(); vi != detectedServices.end(); vi++) { + if (strcmp(*vi, sname) == 0) + return true; + } + return false; +} + + +// Takes a string following a Rarity directive in the probes file. +// The string should contain a single integer between 1 and 9. The +// default rarity is 5. This function will bail if the string is invalid. +void ServiceProbe::setRarity(const char *portstr, int lineno) { + int tp; + + tp = atoi(portstr); + + if (tp < 1 || tp > 9) + fatal("%s: Rarity directive on line %d of nmap-service-probes must be between 1 and 9", __func__, lineno); + + rarity = tp; +} + + + // Takes a match line in a probe description and adds it to the + // list of matches for this probe. This function should be passed + // the whole line starting with "match" or "softmatch" in + // nmap-service-probes. The line number is requested because this + // function will bail with an error (giving the line number) if it + // fails to parse the string. +void ServiceProbe::addMatch(const char *match, int lineno) { + const char *sname; + ServiceProbeMatch *newmatch = new ServiceProbeMatch(); + newmatch->InitMatch(match, lineno); + sname = newmatch->getName(); + if (!serviceIsPossible(sname)) + detectedServices.push_back(sname); + matches.push_back(newmatch); +} + +/* Parses the given nmap-service-probes file into the AP class Must + NOT be made static because I have external maintenance tools + (servicematch) which use this */ +void parse_nmap_service_probe_file(AllProbes *AP, const char *filename) { + ServiceProbe *newProbe = NULL; + char line[2048]; + int lineno = 0; + FILE *fp; + + // We better start by opening the file + fp = fopen(filename, "r"); + if (!fp) + pfatal("Failed to open nmap-service-probes file %s for reading", filename); + + while(fgets(line, sizeof(line), fp)) { + lineno++; + + if (*line == '\n' || *line == '#') + continue; + + if (strncmp(line, "Exclude ", 8) == 0) { + if (AP->excluded_seen) + fatal("Only 1 Exclude directive is allowed in the nmap-service-probes file"); + getpts(line+8, &AP->excludedports); + AP->excluded_seen = true; + continue; + } + + anotherprobe: + + if (strncmp(line, "Probe ", 6) != 0) + fatal("Parse error on line %d of nmap-service-probes file: %s -- line was expected to begin with \"Probe \" or \"Exclude \"", lineno, filename); + + newProbe = new ServiceProbe(); + newProbe->setProbeDetails(line + 6, lineno); + + // Now we read the rest of the probe info + while(fgets(line, sizeof(line), fp)) { + lineno++; + if (*line == '\n' || *line == '#') + continue; + + if (strncmp(line, "Probe ", 6) == 0) { + if (newProbe->isNullProbe()) { + assert(!AP->nullProbe); + AP->nullProbe = newProbe; + } else { + AP->probes.push_back(newProbe); + } + goto anotherprobe; + } else if (strncmp(line, "ports ", 6) == 0) { + newProbe->setProbablePorts(SERVICE_TUNNEL_NONE, line + 6, lineno); + } else if (strncmp(line, "sslports ", 9) == 0) { + newProbe->setProbablePorts(SERVICE_TUNNEL_SSL, line + 9, lineno); + } else if (strncmp(line, "rarity ", 7) == 0) { + newProbe->setRarity(line + 7, lineno); + } else if (strncmp(line, "fallback ", 9) == 0) { + newProbe->fallbackStr = strdup(line + 9); + } else if (strncmp(line, "totalwaitms ", 12) == 0) { + long waitms = strtol(line + 12, NULL, 10); + if (waitms < 100 || waitms > 300000) + fatal("Error on line %d of nmap-service-probes file (%s): bad totalwaitms value. Must be between 100 and 300000 milliseconds", lineno, filename); + newProbe->totalwaitms = waitms; + } else if (strncmp(line, "tcpwrappedms ", 13) == 0) { + long waitms = strtol(line + 13, NULL, 10); + if (waitms < 100 || waitms > 300000) + fatal("Error on line %d of nmap-service-probes file (%s): bad tcpwrappedms value. Must be between 100 and 300000 milliseconds", lineno, filename); + newProbe->tcpwrappedms = waitms; + } else if (strncmp(line, "match ", 6) == 0 || strncmp(line, "softmatch ", 10) == 0) { + newProbe->addMatch(line, lineno); + } else if (strncmp(line, "Exclude ", 8) == 0) { + fatal("The Exclude directive must precede all Probes in nmap-service-probes"); + } else fatal("Parse error on line %d of nmap-service-probes file: %s -- unknown directive", lineno, filename); + } + } + + if (newProbe != NULL) { + if (newProbe->isNullProbe()) { + assert(!AP->nullProbe); + AP->nullProbe = newProbe; + } else { + AP->probes.push_back(newProbe); + } + } + fclose(fp); + + AP->compileFallbacks(); +} + +// Parses the nmap-service-probes file, and adds each probe to +// the already-created 'probes' vector. +static void parse_nmap_service_probes(AllProbes *AP) { + char filename[256]; + + if (nmap_fetchfile(filename, sizeof(filename), "nmap-service-probes") != 1){ + fatal("Service scan requested but I cannot find nmap-service-probes file."); + } + + parse_nmap_service_probe_file(AP, filename); + /* Record where this data file was found. */ + o.loaded_data_files["nmap-service-probes"] = filename; +} + +AllProbes *AllProbes::global_AP; +AllProbes *AllProbes::service_scan_init(void) +{ + if(global_AP) + return global_AP; + global_AP = new AllProbes(); + parse_nmap_service_probes(global_AP); + + return global_AP; +} + +void AllProbes::service_scan_free(void) +{ + if(global_AP){ + delete global_AP; + global_AP = NULL; + } +} + +// Function that calls isExcluded() function to check if the port +// is in the excludedports list. +int AllProbes::check_excluded_port(unsigned short portno, int proto) +{ + int excluded; + + // Check if the -sV version scan option was specified + // or if the --allports option was used + if (!o.servicescan || o.override_excludeports) + return 0; + + if (global_AP == NULL) + fatal("Failed to check the list of excluded ports: %s", __func__); + + if ((excluded = global_AP->isExcluded(portno, proto))) { + if (o.debugging) + log_write(LOG_PLAIN, "EXCLUDING %d/%s\n", + portno, IPPROTO2STR(proto)); + } + + return excluded; +} + +// If the buf (of length buflen) matches one of the regexes in this +// ServiceProbe, returns the details of nth match (service name, +// version number if applicable, and whether this is a "soft" match. +// If the buf doesn't match, the serviceName field in the structure +// will be NULL. The MatchDetails returned is only valid until the +// next time this function is called. The only exception is that the +// serviceName field can be saved throughout program execution. If +// no version matched, that field will be NULL. This function may +// return NULL if there are no match lines at all in this probe. +const struct MatchDetails *ServiceProbe::testMatch(const u8 *buf, int buflen, int n = 0) { + std::vector<ServiceProbeMatch *>::iterator vi; + const struct MatchDetails *MD; + + for(vi = matches.begin(); vi != matches.end(); vi++) { + MD = (*vi)->testMatch(buf, buflen); + if (MD->serviceName) { + if (n == 0) + return MD; + n--; + } + } + + return NULL; +} + +AllProbes::AllProbes() { + nullProbe = NULL; + excluded_seen = false; + memset(&excludedports, 0, sizeof(excludedports)); +} + +AllProbes::~AllProbes() { + std::vector<ServiceProbe *>::iterator vi; + + // Delete all the ServiceProbe's inside the probes vector + for(vi = probes.begin(); vi != probes.end(); vi++) { + delete *vi; + } + if(nullProbe) + delete nullProbe; + free_scan_lists(&excludedports); +} + + // Tries to find the probe in this AllProbes class which have the + // given name and protocol. If no match is found for the requested + // protocol it will try to find matches on any protocol. + // It can return the NULL probe. +ServiceProbe *AllProbes::getProbeByName(const char *name, int proto) const { + std::vector<ServiceProbe *>::const_iterator vi; + + if (proto == IPPROTO_TCP && nullProbe && strcmp(nullProbe->getName(), name) == 0) + return nullProbe; + + for(vi = probes.begin(); vi != probes.end(); vi++) { + if ((*vi)->getProbeProtocol() == proto && + strcmp(name, (*vi)->getName()) == 0) + return *vi; + } + + // Since the probe wasn't matched for the requested protocol, now try to + // find a match regardless of protocol + for(vi = probes.begin(); vi != probes.end(); vi++) { + if (strcmp(name, (*vi)->getName()) == 0) + return *vi; + } + + return NULL; +} + + + +// Returns nonzero if port was specified in the excludeports +// directive in nmap-service-probes. Zero otherwise. +// Proto should be IPPROTO_TCP for TCP and IPPROTO_UDP for UDP +// Note that although getpts() can set protocols (for protocol +// scanning), this is ignored here because you can't version +// scan protocols. +int AllProbes::isExcluded(unsigned short port, int proto) const { + unsigned short *p=NULL; + int count=-1,i; + + if (!excluded_seen) return 0; + + if (proto == IPPROTO_TCP) { + p = excludedports.tcp_ports; + count = excludedports.tcp_count; + } else if (proto == IPPROTO_UDP) { + p = excludedports.udp_ports; + count = excludedports.udp_count; + } else if (proto == IPPROTO_SCTP) { + p = excludedports.sctp_ports; + count = excludedports.sctp_count; + } else { + fatal("Bad proto number (%d) specified in %s", proto, __func__); + } + + for (i=0; i<count; i++) + if (p[i] == port) + return 1; + + return 0; +} + + +// Before this function is called, the fallbacks exist as unparsed +// comma-separated strings in the fallbackStr field of each probe. +// This function fills out the fallbacks array in each probe with +// an ordered list of pointers to which probes to try. This is both for +// efficiency and to deal with odd cases like the NULL probe and falling +// back to probes later in the file. This function also free()s all the +// fallbackStrs. +void AllProbes::compileFallbacks() { + std::vector<ServiceProbe *>::iterator curr; + char *tp; + int i; + + curr = probes.begin(); + + // The NULL probe is a special case: + if (nullProbe != NULL) + nullProbe->fallbacks[0] = nullProbe; + + while (curr != probes.end()) { + + if ((*curr)->fallbackStr == NULL) { + // A non-NULL probe without a fallback directive. We + // just use "Itself,NULL" unless it's UDP, then just "Itself". + + (*curr)->fallbacks[0] = *curr; + if ((*curr)->getProbeProtocol() == IPPROTO_TCP) + (*curr)->fallbacks[1] = nullProbe; + } else { + // A non-NULL probe *with* a fallback directive. We use: + // TCP: "Itself,<directive1>,...,<directiveN>,NULL" + // UDP: "Itself,<directive1>,...,<directiveN>" + + (*curr)->fallbacks[0] = *curr; + i = 1; + tp = strtok((*curr)->fallbackStr, ",\r\n\t "); // \r and \n because string will be terminated with them + + while (tp != NULL && i<(MAXFALLBACKS-1)) { + (*curr)->fallbacks[i] = getProbeByName(tp, (*curr)->getProbeProtocol()); + if ((*curr)->fallbacks[i] == NULL) + fatal("%s: Unknown fallback specified in Probe %s: '%s'", __func__, (*curr)->getName(), tp); + i++; + tp = strtok(NULL, ",\r\n\t "); + } + + if (i == MAXFALLBACKS-1) + fatal("%s: MAXFALLBACKS exceeded on probe '%s'", __func__, (*curr)->getName()); + + if ((*curr)->getProbeProtocol() == IPPROTO_TCP) + (*curr)->fallbacks[i] = nullProbe; + } + + if ((*curr)->fallbackStr) free((*curr)->fallbackStr); + (*curr)->fallbackStr = NULL; + + curr++; + } + +} + + + +ServiceNFO::ServiceNFO(AllProbes *newAP) { + target = NULL; + probe_matched = NULL; + niod = NULL; + probe_state = PROBESTATE_INITIAL; + portno = proto = 0; + AP = newAP; + currentresp = NULL; + currentresplen = 0; + product_matched[0] = version_matched[0] = extrainfo_matched[0] = '\0'; + hostname_matched[0] = ostype_matched[0] = devicetype_matched[0] = '\0'; + cpe_a_matched[0] = cpe_h_matched[0] = cpe_o_matched[0] = '\0'; + tunnel = SERVICE_TUNNEL_NONE; + ssl_session = NULL; + softMatchFound = false; + servicefplen = servicefpalloc = 0; + servicefp = NULL; + tcpwrap_possible = true; + memset(¤tprobe_exec_time, 0, sizeof(currentprobe_exec_time)); +} + +ServiceNFO::~ServiceNFO() { + if (currentresp) free(currentresp); + if (servicefp) free(servicefp); + servicefp = NULL; + servicefpalloc = servicefplen = 0; +#if HAVE_OPENSSL + if (ssl_session) + SSL_SESSION_free((SSL_SESSION*)ssl_session); + ssl_session=NULL; +#endif +} + + // Adds a character to servicefp. Takes care of word wrapping if + // necessary at the given (wrapat) column. Chars will only be + // written if there is enough space. Otherwise it exits. +void ServiceNFO::addServiceChar(const char c, int wrapat) { + + if (servicefpalloc - servicefplen < 6) + fatal("%s - out of space for servicefp", __func__); + + if (servicefplen % (wrapat+1) == wrapat) { + // we need to start a new line + memcpy(servicefp + servicefplen, "\nSF:", 4); + servicefplen += 4; + } + + servicefp[servicefplen++] = c; +} + +// Like addServiceChar, but for a whole zero-terminated string +void ServiceNFO::addServiceString(const char *s, int wrapat) { + while(*s) + addServiceChar(*s++, wrapat); +} + +// If a service responds to a given probeName, this function adds the +// response to the fingerprint for that service. The fingerprint can +// be printed when nothing matches the service. You can obtain the +// fingerprint (if any) via getServiceFingerprint(); +void ServiceNFO::addToServiceFingerprint(const char *probeName, const u8 *resp, + int resplen) { + int spaceleft = servicefpalloc - servicefplen; + int servicewrap=74; // Wrap after 74 chars / line + int respused = MIN(resplen, (o.debugging)? 1300 : 900); // truncate to reasonable size + // every char could require \xHH escape, plus there is the matter of + // "\nSF:" for each line, plus "%r(probename,probelen,"") Oh, and + // the SF-PortXXXX-TCP stuff, etc + int spaceneeded = respused * 5 + strlen(probeName) + 128; + int srcidx; + struct tm ltime; + time_t timep; + int err; + char buf[128]; + + assert(resplen); + assert(probeName); + + if (servicefplen > (o.debugging? 10000 : 2200)) + return; // it is large enough. + + if (spaceneeded >= spaceleft) { + spaceneeded = MAX(spaceneeded, 512); // No point in tiny allocations + spaceneeded += servicefpalloc; + + servicefp = (char *) safe_realloc(servicefp, spaceneeded); + servicefpalloc = spaceneeded; + } + spaceleft = servicefpalloc - servicefplen; + + if (servicefplen == 0) { + timep = time(NULL); + err = n_localtime(&timep, <ime); + if (err) + error("Error in localtime: %s", strerror(err)); + Snprintf(buf, sizeof(buf), "SF-Port%hu-%s:V=%s%s%%I=%d%%D=%d/%d%%Time=%X%%P=%s", + portno, proto2ascii_uppercase(proto), NMAP_VERSION, + (tunnel == SERVICE_TUNNEL_SSL)? "%T=SSL" : "", o.version_intensity, + err ? 0 : ltime.tm_mon + 1, err ? 0 : ltime.tm_mday, (int) timep, NMAP_PLATFORM); + addServiceString(buf, servicewrap); + } + + // Note that we give the total length of the response, even though we + // may truncate + Snprintf(buf, sizeof(buf), "%%r(%s,%X,\"", probeName, resplen); + addServiceString(buf, servicewrap); + + // Now for the probe response itself ... + for(srcidx=0; srcidx < respused; srcidx++) { + // A run of this can take up to 8 chars: "\n \x20" + assert(servicefpalloc - servicefplen > 8); + + if (isalnum((int)resp[srcidx])) + addServiceChar((char) resp[srcidx], servicewrap); + else if (resp[srcidx] == '\0') { + /* We need to be careful with this, because if it is followed by + an ASCII number, PCRE will treat it differently. */ + if (srcidx + 1 >= respused || !isdigit((int) resp[srcidx + 1])) + addServiceString("\\0", servicewrap); + else addServiceString("\\x00", servicewrap); + } else if (strchr("\\?\"[]().*+$^|", resp[srcidx])) { + addServiceChar('\\', servicewrap); + addServiceChar(resp[srcidx], servicewrap); + } else if (ispunct((int)resp[srcidx])) { + addServiceChar((char) resp[srcidx], servicewrap); + } else if (resp[srcidx] == '\r') { + addServiceString("\\r", servicewrap); + } else if (resp[srcidx] == '\n') { + addServiceString("\\n", servicewrap); + } else if (resp[srcidx] == '\t') { + addServiceString("\\t", servicewrap); + } else { + addServiceChar('\\', servicewrap); + addServiceChar('x', servicewrap); + Snprintf(buf, sizeof(buf), "%02x", resp[srcidx]); + addServiceChar(*buf, servicewrap); + addServiceChar(*(buf+1), servicewrap); + } + } + + addServiceChar('"', servicewrap); + addServiceChar(')', servicewrap); + assert(servicefpalloc - servicefplen > 1); + servicefp[servicefplen] = '\0'; +} + +// Get the service fingerprint. It is NULL if there is none, such +// as if there was a match before any other probes were finished (or +// if no probes gave back data). Note that this is plain +// NUL-terminated ASCII data, although the length is optionally +// available anyway. This function terminates the service fingerprint +// with a semi-colon +const char *ServiceNFO::getServiceFingerprint(int *flen) { + + if (servicefplen == 0) { + if (flen) *flen = 0; + return NULL; + } + + // Ensure we have enough space for the terminating semi-colon and \0 + if (servicefplen + 2 > servicefpalloc) { + servicefpalloc = servicefplen + 20; + servicefp = (char *) safe_realloc(servicefp, servicefpalloc); + } + + if (flen) *flen = servicefplen + 1; + // We terminate with a semi-colon, which is never wrapped. + servicefp[servicefplen] = ';'; + servicefp[servicefplen + 1] = '\0'; + return servicefp; +} + +ServiceProbe *ServiceNFO::currentProbe() { + if (probe_state == PROBESTATE_INITIAL) { + return nextProbe(true); + } else if (probe_state == PROBESTATE_NULLPROBE) { + assert(AP->nullProbe); + return AP->nullProbe; + } else if (probe_state == PROBESTATE_MATCHINGPROBES || + probe_state == PROBESTATE_NONMATCHINGPROBES) { + return *current_probe; + } + return NULL; +} + +// computes the next probe to test, and ALSO CHANGES currentProbe() to +// that! If newresp is true, the old response info will be lost and +// invalidated. Otherwise it remains as if it had been received by +// the current probe (useful after a NULL probe). +ServiceProbe *ServiceNFO::nextProbe(bool newresp) { +bool dropdown = false; + +// This invalidates the probe response string if any + if (newresp) { + if (currentresp) free(currentresp); + currentresp = NULL; currentresplen = 0; + } + + if (probe_state == PROBESTATE_INITIAL) { + probe_state = PROBESTATE_NULLPROBE; + // This is the very first probe -- so we try to use the NULL probe + // but obviously NULL probe only works with TCP + if (proto == IPPROTO_TCP && AP->nullProbe) + return AP->nullProbe; + + // No valid NULL probe -- we'll drop to the next state + } + + if (probe_state == PROBESTATE_NULLPROBE) { + // There can only be one (or zero) NULL probe. So now we go through the + // list looking for matching probes + probe_state = PROBESTATE_MATCHINGPROBES; + dropdown = true; + current_probe = AP->probes.begin(); + } + + if (probe_state == PROBESTATE_MATCHINGPROBES) { + if (!dropdown && current_probe != AP->probes.end()) current_probe++; + while (current_probe != AP->probes.end()) { + // For the first run, we only do probes that match this port number + if ((proto == (*current_probe)->getProbeProtocol()) && + (*current_probe)->portIsProbable(tunnel, portno) && + // Skip the probe if we softmatched and the service isn't available via this probe. + // --version-all avoids this optimization here and in PROBESTATE_NONMATCHINGPROBES below. + (!softMatchFound || o.version_intensity >= 9 || (*current_probe)->serviceIsPossible(probe_matched))) { + // This appears to be a valid probe. Let's do it! + return *current_probe; + } + current_probe++; + } + // Tried all MATCHINGPROBES -- now we must move to nonmatching + probe_state = PROBESTATE_NONMATCHINGPROBES; + dropdown = true; + current_probe = AP->probes.begin(); + } + + if (probe_state == PROBESTATE_NONMATCHINGPROBES) { + if (!dropdown && current_probe != AP->probes.end()) current_probe++; + while (current_probe != AP->probes.end()) { + // The protocol must be right, it must be a nonmatching port ('cause we did those), + // and we better either have no soft match yet, or the soft service match must + // be available via this probe. Also, the Probe's rarity must be <= to our + // version detection intensity level. + if ((proto == (*current_probe)->getProbeProtocol()) && + !(*current_probe)->portIsProbable(tunnel, portno) && + // No softmatch so obey intensity, or + ((!softMatchFound && (*current_probe)->getRarity() <= o.version_intensity) || + // Softmatch, so only require service match (no rarity check) + (softMatchFound && (o.version_intensity >= 9 || (*current_probe)->serviceIsPossible(probe_matched))))) { + // Valid, probe. Let's do it! + return *current_probe; + } + current_probe++; + } + + // Tried all NONMATCHINGPROBES -- we're finished + probe_state = (softMatchFound)? PROBESTATE_FINISHED_SOFTMATCHED : PROBESTATE_FINISHED_NOMATCH; + return NULL; + } + + fatal("%s called for probe in state (%d)", __func__, (int) probe_state); + return NULL; +} + + // Resets the probes back to the first one. One case where this is useful is + // when SSL is detected -- we redo all probes through SSL. If freeFP, any + // service fingerprint is freed too. +void ServiceNFO::resetProbes(bool freefp) { + + if (currentresp) free(currentresp); + + if (freefp) { + if (servicefp) { free(servicefp); servicefp = NULL; } + servicefplen = servicefpalloc = 0; + } + + currentresp = NULL; currentresplen = 0; + + probe_state = PROBESTATE_INITIAL; +} + +int ServiceNFO::probe_timemsused(const ServiceProbe *probe, const struct timeval *now) { + int timeused; + + if (now) + timeused = TIMEVAL_MSEC_SUBTRACT(*now, currentprobe_exec_time); + else { + struct timeval tv; + gettimeofday(&tv, NULL); + timeused = TIMEVAL_MSEC_SUBTRACT(tv, currentprobe_exec_time); + } + + // Historically this function was always called with the assumption that + // probe == currentProbe(). Check that this remains the case. + assert(probe == currentProbe()); + + return timeused; +} + +int ServiceNFO::probe_timemsleft(const ServiceProbe *probe, const struct timeval *now) { + + // Historically this function was always called with the assumption that + // probe == currentProbe(). Check that this remains the case. + assert(probe == currentProbe()); + + int timeleft = probe->totalwaitms - probe_timemsused(probe, now); + return (timeleft < 0)? 0 : timeleft; +} + +void ServiceNFO::appendtocurrentproberesponse(const u8 *respstr, int respstrlen) { + currentresp = (u8 *) safe_realloc(currentresp, currentresplen + respstrlen); + memcpy(currentresp + currentresplen, respstr, respstrlen); + currentresplen += respstrlen; +} + +// Get the full current response string. Note that this pointer is +// INVALIDATED if you call appendtocurrentproberesponse() or nextProbe() +u8 *ServiceNFO::getcurrentproberesponse(int *respstrlen) { + *respstrlen = currentresplen; + return currentresp; +} + + +ServiceGroup::ServiceGroup(std::vector<Target *> &Targets, AllProbes *AP) { + unsigned int targetno; + ServiceNFO *svc; + Port *nxtport; + Port port; + int desired_par; + struct timeval now; + num_hosts_timedout = 0; + gettimeofday(&now, NULL); + + SPM = new ScanProgressMeter("Service scan"); + for(targetno = 0 ; targetno < Targets.size(); targetno++) { + Target *target = Targets[targetno]; + assert(target); + nxtport = NULL; + if (target->timedOut(&now)) { + num_hosts_timedout++; + continue; + } + while((nxtport = target->ports.nextPort(nxtport, &port, TCPANDUDPANDSCTP, PORT_OPEN))) { + svc = new ServiceNFO(AP); + svc->target = target; + svc->portno = nxtport->portno; + svc->proto = nxtport->proto; + services_remaining.push_back(svc); + } + + /* Use a whole new loop for PORT_OPENFILTERED so that we try all the + known open ports first before bothering with this speculative + stuff */ + while((nxtport = target->ports.nextPort(nxtport, &port, TCPANDUDPANDSCTP, PORT_OPENFILTERED))) { + svc = new ServiceNFO(AP); + svc->target = target; + svc->portno = nxtport->portno; + svc->proto = nxtport->proto; + services_remaining.push_back(svc); + } + + /* Check if any early responses can help */ + for (std::vector<EarlySvcResponse *>::iterator it = target->earlySvcResponses.begin(); + it != target->earlySvcResponses.end(); it++) { + EarlySvcResponse *esr = *it; + assert(esr); + const struct MatchDetails *MD = payload_service_match(esr->pspec.pd.udp.dport, + esr->data, esr->len); + if (MD) { + // Find the appropriate ServiceNFO and process it. + for (std::list<ServiceNFO *>::iterator i = services_remaining.begin(); + i != services_remaining.end(); i++) { + svc = *i; + if (svc->proto == IPPROTO_UDP && svc->portno == esr->pspec.pd.udp.dport) { + if (processMatch(MD, svc, "port scan", "udp payload") + && !scanThroughTunnel(svc)) { + end_svcprobe(PROBESTATE_FINISHED_HARDMATCHED, this, svc, NULL); + } + break; + } + } + } + } + } + + desired_par = 1; + if (o.timing_level == 3) desired_par = 20; + if (o.timing_level == 4) desired_par = 30; + if (o.timing_level >= 5) desired_par = 40; + // TODO: Come up with better ways to determine ideal_parallelism + int min_par, max_par; + min_par = o.min_parallelism; + max_par = MAX(min_par, o.max_parallelism ? o.max_parallelism : 100); + ideal_parallelism = box(min_par, max_par, desired_par); +} + +ServiceGroup::~ServiceGroup() { + std::list<ServiceNFO *>::iterator i; + + for(i = services_finished.begin(); i != services_finished.end(); i++) + delete *i; + + for(i = services_in_progress.begin(); i != services_in_progress.end(); i++) + delete *i; + + for(i = services_remaining.begin(); i != services_remaining.end(); i++) + delete *i; + + delete SPM; +} + +/* Called if data is read for a service or a TCP connection made. Sets the port + state to PORT_OPEN. */ +static void adjustPortStateIfNecessary(ServiceNFO *svc) { + int oldstate; + char host[128]; + + oldstate = svc->target->ports.getPortState(svc->portno, svc->proto); + if (oldstate != PORT_OPEN) { + svc->target->ports.setPortState(svc->portno, svc->proto, PORT_OPEN); + if (svc->proto == IPPROTO_TCP) + svc->target->ports.setStateReason(svc->portno, svc->proto, ER_TCPRESPONSE, 0, NULL); + if (svc->proto == IPPROTO_UDP) + svc->target->ports.setStateReason(svc->portno, svc->proto, ER_UDPRESPONSE, 0, NULL); + + if (o.verbose || o.debugging > 1) { + svc->target->NameIP(host, sizeof(host)); + + log_write(LOG_STDOUT, "Discovered %s port %hu/%s on %s is actually open\n", + statenum2str(oldstate), svc->portno, proto2ascii_lowercase(svc->proto), host); + log_flush(LOG_STDOUT); + } + } + + return; +} + + // Sends probe text to an open connection. In the case of a NULL probe, there + // may be no probe text + static int send_probe_text(nsock_pool nsp, nsock_iod nsi, ServiceNFO *svc, + ServiceProbe *probe) { + const u8 *probestring; + int probestringlen; + + // Report data as probes are sent if --version-trace has been requested + if (o.debugging > 1 || o.versionTrace()) { + log_write(LOG_PLAIN, "Service scan sending probe %s to %s:%hu (%s)\n", probe->getName(), svc->target->targetipstr(), svc->portno, proto2ascii_lowercase(svc->proto)); + } + + assert(probe); + if (probe->isNullProbe()) + return 0; // No need to send anything for a NULL probe; + probestring = probe->getProbeString(&probestringlen); + assert(probestringlen > 0); + // Now we write the string to the IOD + nsock_write(nsp, nsi, servicescan_write_handler, svc->probe_timemsleft(probe), svc, + (const char *) probestring, probestringlen); + return 0; + } + +// This simple helper function is used to start the next probe. If +// the probe exists, execution begins (and the previous one is cleaned +// up if necessary) . Otherwise, the service is listed as finished +// and moved to the finished list. If you pass 'true' for alwaysrestart, a +// new connection will be made even if the previous probe was the NULL probe. +// You would do this, for example, if the other side has closed the connection. +static void startNextProbe(nsock_pool nsp, nsock_iod nsi, ServiceGroup *SG, + ServiceNFO *svc, bool alwaysrestart) { + bool isInitial = svc->probe_state == PROBESTATE_INITIAL; + ServiceProbe *probe = svc->currentProbe(); + struct sockaddr_storage ss; + size_t ss_len; + + if (!alwaysrestart && probe->isNullProbe()) { + // The difference here is that we can reuse the same (TCP) connection + // if the last probe was the NULL probe. + probe = svc->nextProbe(false); + if (probe) { + svc->currentprobe_exec_time = *nsock_gettimeofday(); + send_probe_text(nsp, nsi, svc, probe); + nsock_read(nsp, nsi, servicescan_read_handler, + svc->probe_timemsleft(probe, nsock_gettimeofday()), svc); + } else { + // Should only happen if someone has a highly perverse nmap-service-probes + // file. Null scan should generally never be the only probe. + end_svcprobe((svc->softMatchFound)? PROBESTATE_FINISHED_SOFTMATCHED : PROBESTATE_FINISHED_NOMATCH, SG, svc, NULL); + } + } else { + // The finished probe was not a NULL probe. So we close the + // connection, and if further probes are available, we launch the + // next one. + if (nsi) + nsock_iod_delete(nsi, NSOCK_PENDING_SILENT); + if (!isInitial) + probe = svc->nextProbe(true); // if was initial, currentProbe() returned the right one to execute. + if (probe) { + if ((svc->niod = nsock_iod_new(nsp, svc)) == NULL) { + fatal("Failed to allocate Nsock I/O descriptor in %s()", __func__); + } + if (o.spoofsource) { + o.SourceSockAddr(&ss, &ss_len); + nsock_iod_set_localaddr(svc->niod, &ss, ss_len); + } + if (o.ipoptionslen) + nsock_iod_set_ipoptions(svc->niod, o.ipoptions, o.ipoptionslen); + if (svc->target->TargetName()) { + if (nsock_iod_set_hostname(svc->niod, svc->target->TargetName()) == -1) + fatal("nsock_iod_set_hostname(\"%s\" failed in %s()", + svc->target->TargetName(), __func__); + } + svc->target->TargetSockAddr(&ss, &ss_len); + if (svc->tunnel == SERVICE_TUNNEL_NONE) { + if (svc->proto == IPPROTO_TCP) { + nsock_connect_tcp(nsp, svc->niod, servicescan_connect_handler, + DEFAULT_CONNECT_TIMEOUT, svc, + (struct sockaddr *) &ss, ss_len, + svc->portno); + } + else { + nsock_connect_udp(nsp, svc->niod, servicescan_connect_handler, + svc, (struct sockaddr *) &ss, ss_len, + svc->portno); + } + } else { + assert(svc->tunnel == SERVICE_TUNNEL_SSL); + nsock_connect_ssl(nsp, svc->niod, servicescan_connect_handler, + DEFAULT_CONNECT_SSL_TIMEOUT, svc, + (struct sockaddr *) &ss, + ss_len, svc->proto, svc->portno, svc->ssl_session); + } + } else { + // No more probes remaining! Failed to match + end_svcprobe((svc->softMatchFound)? PROBESTATE_FINISHED_SOFTMATCHED : + PROBESTATE_FINISHED_NOMATCH, + SG, svc, NULL); + } + } + return; +} + +/* Sometimes the normal service scan will detect a + tunneling/encryption protocol such as SSL. Instead of just + reporting "ssl", we can make an SSL connection and try to determine + the service that is really sitting behind the SSL. This function + will take a service that has just been detected (hard match only), + and see if we can dig deeper through tunneling. Nonzero is + returned if we can do more. Otherwise 0 is returned and the caller + should end the service with its successful match. If the tunnel + results can be determined with no more effort, 0 is also returned. + For example, a service that already matched as "ssl/ldap" will be + changed to "ldap" with the tunnel being SSL and 0 will be returned. + That is a special case. +*/ + +static int scanThroughTunnel(ServiceNFO *svc) { + + if (svc->probe_matched && strncmp(svc->probe_matched, "ssl/", 4) == 0) { + /* The service has been detected without having to make an SSL connection */ + svc->tunnel = SERVICE_TUNNEL_SSL; + svc->probe_matched += 4; + return 0; + } + +#ifdef HAVE_OPENSSL + if (svc->tunnel != SERVICE_TUNNEL_NONE) { + // Another tunnel type has already been tried. Let's not go recursive. + return 0; + } + + if (!svc->probe_matched || + (strcmp(svc->probe_matched, "ssl") != 0 && + strcmp(svc->probe_matched, "dtls") != 0)) + return 0; // Not SSL + + // Alright! We are going to start the tests over using SSL + // printf("DBG: Found SSL service on %s:%hu - starting SSL scan\n", svc->target->NameIP(), svc->portno); + svc->tunnel = SERVICE_TUNNEL_SSL; + svc->probe_matched = NULL; + svc->product_matched[0] = svc->version_matched[0] = svc->extrainfo_matched[0] = '\0'; + svc->hostname_matched[0] = svc->ostype_matched[0] = svc->devicetype_matched[0] = '\0'; + svc->cpe_a_matched[0] = svc->cpe_h_matched[0] = svc->cpe_o_matched[0] = '\0'; + svc->softMatchFound = false; + svc->resetProbes(true); + return 1; +#else + return 0; +#endif +} + +/* Prints completion estimates and the like when appropriate */ +static void considerPrintingStats(ServiceGroup *SG) { + /* Check for status requests */ + if (keyWasPressed()) { + nmap_adjust_loglevel(o.versionTrace()); + SG->SPM->printStats(SG->services_finished.size() / + ((double)SG->services_remaining.size() + SG->services_in_progress.size() + + SG->services_finished.size()), nsock_gettimeofday()); + } + + + /* Perhaps this should be made more complex, but I suppose it should be + good enough for now. */ + if (SG->SPM->mayBePrinted(nsock_gettimeofday())) { + SG->SPM->printStatsIfNecessary(SG->services_finished.size() / ((double)SG->services_remaining.size() + SG->services_in_progress.size() + SG->services_finished.size()), nsock_gettimeofday()); + } +} + +/* Check if target is done (no more probes remaining for it in service group), + and responds appropriately if so */ +static void handleHostIfDone(ServiceGroup *SG, Target *target) { + std::list<ServiceNFO *>::const_iterator svcI; + bool found = false; + + for(svcI = SG->services_in_progress.begin(); + svcI != SG->services_in_progress.end(); svcI++) { + if ((*svcI)->target == target) { + found = true; + break; + } + } + + for(svcI = SG->services_remaining.begin(); + !found && svcI != SG->services_remaining.end(); svcI++) { + if ((*svcI)->target == target) { + found = true; + break; + } + } + + if (!found) { + if (target->timeOutClockRunning()) + target->stopTimeOutClock(nsock_gettimeofday()); + if (target->timedOut(NULL)) { + SG->num_hosts_timedout++; + } + } +} + +// A simple helper function to cancel further work on a service and +// set it to the given probe_state pass NULL for nsi if you don't want +// it to be deleted (for example, if you already have done so). +static void end_svcprobe(enum serviceprobestate probe_state, ServiceGroup *SG, ServiceNFO *svc, nsock_iod nsi) { + std::list<ServiceNFO *>::iterator member; + Target *target = svc->target; + + svc->probe_state = svc->tcpwrap_possible ? PROBESTATE_FINISHED_TCPWRAPPED : probe_state; + member = find(SG->services_in_progress.begin(), SG->services_in_progress.end(), + svc); + if (member != SG->services_in_progress.end()) { + assert(*member == svc); + SG->services_in_progress.erase(member); + } else { + /* A probe can finish from services_remaining if the host times out before the + probe has even started */ + member = find(SG->services_remaining.begin(), SG->services_remaining.end(), + svc); + assert(member != SG->services_remaining.end()); + assert(*member == svc); + SG->services_remaining.erase(member); + } + + SG->services_finished.push_back(svc); + + considerPrintingStats(SG); + + if (nsi) + nsock_iod_delete(nsi, NSOCK_PENDING_SILENT); + + handleHostIfDone(SG, target); + return; +} + +// This function consults the ServiceGroup to determine whether any +// more probes can be launched at this time. If so, it determines the +// appropriate ones and then starts them up. +static int launchSomeServiceProbes(nsock_pool nsp, ServiceGroup *SG) { + ServiceNFO *svc; + static int warn_no_scanning=1; + + while (SG->services_in_progress.size() < SG->ideal_parallelism && + !SG->services_remaining.empty()) { + // Start executing a probe from the new list and move it to in_progress + svc = SG->services_remaining.front(); + if (svc->target->timedOut(nsock_gettimeofday())) { + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, NULL); + continue; + } + else if (!svc->target->timeOutClockRunning()) { + svc->target->startTimeOutClock(nsock_gettimeofday()); + } + + // Launch it! If there were no probes, we'll get a NOMATCH immediately. + startNextProbe(nsp, NULL, SG, svc, true); + + if (svc->probe_state == PROBESTATE_FINISHED_NOMATCH) { + if (warn_no_scanning && o.debugging) { + log_write(LOG_PLAIN, "Service scan: Not probing some ports due to low intensity\n"); + warn_no_scanning=0; + } + continue; + } + + // Check that the service is still where we left it. + // servicescan_connect_handler can call end_svcprobe before this point, + // putting it into services_finished already. + if (!SG->services_remaining.empty() && SG->services_remaining.front() == svc) { + // Now remove it from the remaining service list + SG->services_remaining.pop_front(); + // And add it to the in progress list + SG->services_in_progress.push_back(svc); + } + } + return 0; +} + + +static void servicescan_connect_handler(nsock_pool nsp, nsock_event nse, void *mydata) { + nsock_iod nsi = nse_iod(nse); + enum nse_status status = nse_status(nse); + enum nse_type type = nse_type(nse); + ServiceNFO *svc = (ServiceNFO *) mydata; + ServiceProbe *probe = svc->currentProbe(); + ServiceGroup *SG = (ServiceGroup *) nsock_pool_get_udata(nsp); + + assert(type == NSE_TYPE_CONNECT || type == NSE_TYPE_CONNECT_SSL); + + if (svc->target->timedOut(nsock_gettimeofday())) { + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, nsi); + } else if (status == NSE_STATUS_SUCCESS) { + +#if HAVE_OPENSSL + // Snag our SSL_SESSION from the nsi for use in subsequent connections. + if (nsock_iod_check_ssl(nsi)) { + if (svc->ssl_session) { + if (svc->ssl_session == (SSL_SESSION *)(nsock_iod_get_ssl_session(nsi, 0))) { + //nada + } else { + SSL_SESSION_free((SSL_SESSION*)svc->ssl_session); + svc->ssl_session = (SSL_SESSION *)(nsock_iod_get_ssl_session(nsi, 1)); + } + } else { + svc->ssl_session = (SSL_SESSION *)(nsock_iod_get_ssl_session(nsi, 1)); + } + } +#endif + + /* If the port is TCP, it is now known to be open rather than openfiltered */ + if (svc->proto == IPPROTO_TCP) + adjustPortStateIfNecessary(svc); + + // Yeah! Connection made to the port. Send the appropriate probe + // text (if any is needed -- might be NULL probe) + svc->currentprobe_exec_time = *nsock_gettimeofday(); + send_probe_text(nsp, nsi, svc, probe); + // Now let us read any results + nsock_read(nsp, nsi, servicescan_read_handler, svc->probe_timemsleft(probe, nsock_gettimeofday()), svc); + } else { + switch(status) { + case NSE_STATUS_TIMEOUT: + case NSE_STATUS_ERROR: + case NSE_STATUS_PROXYERROR: + // This is not good. The connect() really shouldn't generally + // be timing out like that. We'll mark this svc as incomplete + // and move it to the finished bin. + if (o.debugging) + error("Got nsock CONNECT response with status %s - aborting this service", nse_status2str(status)); + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, nsi); + break; + + case NSE_STATUS_KILL: + /* User probably specified host_timeout and so the service scan is + * shutting down */ + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, nsi); + return; + + default: + fatal("Unexpected nsock status (%d) returned for connection attempt", (int)status); + } + } + // We may have room for more probes! + launchSomeServiceProbes(nsp, SG); + return; +} + +static void servicescan_write_handler(nsock_pool nsp, nsock_event nse, void *mydata) { + enum nse_status status = nse_status(nse); + nsock_iod nsi; + ServiceNFO *svc = (ServiceNFO *)mydata; + ServiceGroup *SG; + int err; + + SG = (ServiceGroup *) nsock_pool_get_udata(nsp); + nsi = nse_iod(nse); + + // Check if a status message was requested + if (keyWasPressed()) { + SG->SPM->printStats(SG->services_finished.size() / + ((double)SG->services_remaining.size() + SG->services_in_progress.size() + + SG->services_finished.size()), nsock_gettimeofday()); + } + + + if (svc->target->timedOut(nsock_gettimeofday())) { + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, nsi); + return; + } + + if (status == NSE_STATUS_SUCCESS) + return; + + if (status == NSE_STATUS_KILL) { + /* User probably specified host_timeout and so the service scan is + shutting down */ + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, nsi); + return; + } + + if (status == NSE_STATUS_ERROR || status == NSE_STATUS_PROXYERROR) { + err = nse_errorcode(nse); + error("Got nsock WRITE error #%d (%s)", err, strerror(err)); + } + + // Uh-oh. Some sort of write failure ... maybe the connection closed + // on us unexpectedly? + if (o.debugging) + error("Got nsock WRITE response with status %s - aborting this service", nse_status2str(status)); + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, nsi); + + // We may have room for more probes! + launchSomeServiceProbes(nsp, SG); + + return; +} + +/* Returns true if this is a new hard match, false if not a match or if a softmatch */ +static bool processMatch(const struct MatchDetails *MD, ServiceNFO *svc, + const char *probeName, const char *fallbackName) { + if (!MD || !MD->serviceName) { + return false; + } + // WOO HOO!!!!!! MATCHED! But might be soft + if (MD->isSoft && svc->probe_matched) { + if (strcmp(svc->probe_matched, MD->serviceName) != 0) + error("WARNING: Service %s:%hu had already soft-matched %s, but now soft-matched %s; ignoring second value", svc->target->targetipstr(), svc->portno, svc->probe_matched, MD->serviceName); + // No error if its the same - that happens frequently. For + // example, if we read more data for the same probe response + // it will probably still match. + return false; + } + if (o.debugging > 1 || o.versionTrace()) { + log_write(LOG_PLAIN, "Service scan %s match (Probe %s matched with %s line %d): %s:%hu is %s%s. Version: |%s|%s|%s|\n", + (MD->isSoft)? "soft" : "hard", + probeName, fallbackName, + MD->lineno, + svc->target->targetipstr(), svc->portno, (svc->tunnel == SERVICE_TUNNEL_SSL)? "SSL/" : "", + MD->serviceName, (MD->product)? MD->product : "", (MD->version)? MD->version : "", + (MD->info)? MD->info : ""); + } + svc->probe_matched = MD->serviceName; + svc->tcpwrap_possible = false; + if (MD->product) + Strncpy(svc->product_matched, MD->product, sizeof(svc->product_matched)); + if (MD->version) + Strncpy(svc->version_matched, MD->version, sizeof(svc->version_matched)); + if (MD->info) + Strncpy(svc->extrainfo_matched, MD->info, sizeof(svc->extrainfo_matched)); + if (MD->hostname) + Strncpy(svc->hostname_matched, MD->hostname, sizeof(svc->hostname_matched)); + if (MD->ostype) + Strncpy(svc->ostype_matched, MD->ostype, sizeof(svc->ostype_matched)); + if (MD->devicetype) + Strncpy(svc->devicetype_matched, MD->devicetype, sizeof(svc->devicetype_matched)); + if (MD->cpe_a) + Strncpy(svc->cpe_a_matched, MD->cpe_a, sizeof(svc->cpe_a_matched)); + if (MD->cpe_h) + Strncpy(svc->cpe_h_matched, MD->cpe_h, sizeof(svc->cpe_h_matched)); + if (MD->cpe_o) + Strncpy(svc->cpe_o_matched, MD->cpe_o, sizeof(svc->cpe_o_matched)); + svc->softMatchFound = MD->isSoft; + return !MD->isSoft; +} + +static void servicescan_read_handler(nsock_pool nsp, nsock_event nse, void *mydata) { + nsock_iod nsi = nse_iod(nse); + enum nse_status status = nse_status(nse); + enum nse_type type = nse_type(nse); + ServiceNFO *svc = (ServiceNFO *) mydata; + ServiceProbe *probe = svc->currentProbe(); + ServiceGroup *SG = (ServiceGroup *) nsock_pool_get_udata(nsp); + const u8 *readstr; + int readstrlen; + + assert(type == NSE_TYPE_READ); + + if (svc->target->timedOut(nsock_gettimeofday())) { + svc->tcpwrap_possible = false; + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, nsi); + } else if (status == NSE_STATUS_SUCCESS) { + // w00p, w00p, we read something back from the port. + svc->tcpwrap_possible = false; + readstr = (u8 *) nse_readbuf(nse, &readstrlen); + adjustPortStateIfNecessary(svc); /* A response means PORT_OPENFILTERED is really PORT_OPEN */ + svc->appendtocurrentproberesponse(readstr, readstrlen); + // now get the full version + readstr = svc->getcurrentproberesponse(&readstrlen); + + const struct MatchDetails *MD = NULL; + ServiceProbe *fallback = NULL; + for (int fallbackDepth=0; fallbackDepth < MAXFALLBACKS + 1; fallbackDepth++) { + fallback = probe->fallbacks[fallbackDepth]; + if (fallback == NULL) + break; + MD = fallback->testMatch(readstr, readstrlen); + if (MD && MD->serviceName) break; // Found one! + } + + if (fallback && processMatch(MD, svc, probe->getName(), fallback->getName())) { + // hard match! + // We might be able to continue scan through a tunnel protocol + // like SSL + if (scanThroughTunnel(svc)) { + startNextProbe(nsp, nsi, SG, svc, true); + } + else { + end_svcprobe(PROBESTATE_FINISHED_HARDMATCHED, SG, svc, nsi); + } + } + else { + // Didn't match... maybe reading more until timeout will help + // TODO: For efficiency I should be able to test if enough data + // has been received rather than always waiting for the reading + // to timeout. For now I'll limit it to 4096 bytes just to + // avoid reading megs from services like chargen. But better + // approach is needed. + if (svc->probe_timemsleft(probe) > 0 && readstrlen < 4096) { + nsock_read(nsp, nsi, servicescan_read_handler, svc->probe_timemsleft(probe), svc); + } else { + // Failed -- lets go to the next probe. + if (readstrlen > 0) + svc->addToServiceFingerprint(probe->getName(), readstr, readstrlen); + startNextProbe(nsp, nsi, SG, svc, false); + } + } + } else if (status == NSE_STATUS_TIMEOUT) { + // Failed to read enough to make a match in the given amount of time. So we + // move on to the next probe. If this was a NULL probe, we can simply + // send the new probe text immediately. Otherwise we make a new connection. + + svc->tcpwrap_possible = false; + readstr = svc->getcurrentproberesponse(&readstrlen); + if (readstrlen > 0) + svc->addToServiceFingerprint(svc->currentProbe()->getName(), readstr, + readstrlen); + startNextProbe(nsp, nsi, SG, svc, false); + + } else if (status == NSE_STATUS_EOF) { + // The jerk closed on us during read request! + // If this was during the NULL probe, let's (for now) assume + // the port is TCP wrapped. Otherwise, we'll treat it as a nomatch + readstr = svc->getcurrentproberesponse(&readstrlen); + if (readstrlen > 0) { + svc->addToServiceFingerprint(svc->currentProbe()->getName(), readstr, + readstrlen); + svc->tcpwrap_possible = false; + } + if (svc->tcpwrap_possible && probe->isNullProbe() && readstrlen == 0 && svc->probe_timemsused(probe) < probe->tcpwrappedms) { + // TODO: Perhaps should do further verification before making this assumption + end_svcprobe(PROBESTATE_FINISHED_TCPWRAPPED, SG, svc, nsi); + } else { + // Perhaps this service didn't like the particular probe text. + // We'll try the next one + startNextProbe(nsp, nsi, SG, svc, true); + } + } else if (status == NSE_STATUS_ERROR) { + // Errors might happen in some cases ... I'll worry about later + int err = nse_errorcode(nse); + switch(err) { + case ECONNRESET: + case ECONNREFUSED: // weird to get this on a connected socket (shrug) but + // BSD sometimes gives it + case ECONNABORTED: + // Jerk hung up on us. Probably didn't like our probe. We treat it as with EOF above. + if (svc->tcpwrap_possible && probe->isNullProbe() && svc->probe_timemsused(probe) < probe->tcpwrappedms) { + // TODO: Perhaps should do further verification before making this assumption + end_svcprobe(PROBESTATE_FINISHED_TCPWRAPPED, SG, svc, nsi); + } else { + // Perhaps this service didn't like the particular probe text. We'll try the + // next one + startNextProbe(nsp, nsi, SG, svc, true); + } + break; +#ifdef EHOSTDOWN + case EHOSTDOWN: // ICMP_HOST_UNKNOWN +#endif +#ifdef ENONET + case ENONET: // ICMP_HOST_ISOLATED +#endif + /* EHOSTDOWN and ENONET can be the result of forged ICMP responses. + * We should probably give up on this port. + */ + case ENETUNREACH: + case EHOSTUNREACH: + // That is funny. The port scanner listed the port as open. Maybe it got unplugged, or firewalled us, or did + // something else nasty during the scan. Shrug. I'll give up on this port + svc->tcpwrap_possible = false; + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, nsi); + break; +#ifdef ENOPROTOOPT + case ENOPROTOOPT: // ICMP_PROT_UNREACH +#endif + case EMSGSIZE: // ICMP_FRAG_NEEDED + case EOPNOTSUPP: // ICMP_SR_FAILED + /* EPROTOOPT has been reported in the wild. EMSGSIZE and EOPNOTSUPP are theoretically + * possible responses due to forged ICMP responses. + * These seem packet-specific, not a result of the host shutting us out completely. + * We'll try some other probes. + */ +#ifndef WIN32 + case EPIPE: +#endif + + case ENETRESET: + //This error (same as WSAENETRESET according to nbase_winunix) is Microsoft only error, where the connected host crashes and then resets during the communication + //More information can be found at http://www.sockets.com/err_lst1.htm#WSAENETRESET. + //I assume that we shouldn't bother doing anything beyond catching it, and then going on to the next probe. + +#ifdef EPROTO + case EPROTO: + // EPROTO is suspected to be caused by an active IDS/IPS that forges ICMP + // type-12 errors ("Parameter problem"). It's been seen in response to the + // Sqlping probe. +#endif + case EIO: + // Usually an SSL error of some sort (those are presently + // hardcoded to EIO). I'll just try the next probe. + startNextProbe(nsp, nsi, SG, svc, true); + break; + default: + fatal("Unexpected error in NSE_TYPE_READ callback. Error code: %d (%s)", err, + socket_strerror(err)); + } + } else if (status == NSE_STATUS_KILL) { + /* User probably specified host_timeout and so the service scan is + shutting down */ + svc->tcpwrap_possible = false; + end_svcprobe(PROBESTATE_INCOMPLETE, SG, svc, nsi); + return; + } else { + fatal("Unexpected status (%d) in NSE_TYPE_READ callback.", (int) status); + } + + // We may have room for more probes! + launchSomeServiceProbes(nsp, SG); + return; +} + + +// This is used in processResults to determine whether a FP +// should be printed based on type of match, version intensity, etc. +static int shouldWePrintFingerprint(ServiceNFO *svc) { + // Never print FP if hardmatched + if (svc->probe_state == PROBESTATE_FINISHED_HARDMATCHED) + return 0; + + // If we were called with a version_intensity less than + // the default, don't bother printing. + if (o.version_intensity < 7) return 0; + + return 1; +} + +// This is passed a completed ServiceGroup which contains the scanning results for every service. +// The function iterates through each finished service and adds the results to Target structure for +// Nmap to output later. + +static void processResults(ServiceGroup *SG) { +std::list<ServiceNFO *>::iterator svc; + + for(svc = SG->services_finished.begin(); svc != SG->services_finished.end(); svc++) { + if ((*svc)->probe_state != PROBESTATE_FINISHED_NOMATCH) { + std::vector<const char *> cpe; + + if (*(*svc)->cpe_a_matched) + cpe.push_back((*svc)->cpe_a_matched); + if (*(*svc)->cpe_h_matched) + cpe.push_back((*svc)->cpe_h_matched); + if (*(*svc)->cpe_o_matched) + cpe.push_back((*svc)->cpe_o_matched); + + (*svc)->target->ports.setServiceProbeResults((*svc)->portno, (*svc)->proto, + (*svc)->probe_state, + (*svc)->probe_matched, + (*svc)->tunnel, + *(*svc)->product_matched? (*svc)->product_matched : NULL, + *(*svc)->version_matched? (*svc)->version_matched : NULL, + *(*svc)->extrainfo_matched? (*svc)->extrainfo_matched : NULL, + *(*svc)->hostname_matched? (*svc)->hostname_matched : NULL, + *(*svc)->ostype_matched? (*svc)->ostype_matched : NULL, + *(*svc)->devicetype_matched? (*svc)->devicetype_matched : NULL, + (cpe.size() > 0) ? &cpe : NULL, + shouldWePrintFingerprint(*svc) ? (*svc)->getServiceFingerprint(NULL) : NULL); + } else { + (*svc)->target->ports.setServiceProbeResults((*svc)->portno, (*svc)->proto, + (*svc)->probe_state, NULL, + (*svc)->tunnel, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, + (*svc)->getServiceFingerprint(NULL)); + } + } +} + + +// We iterate through SG->services_remaining and remove any with port/protocol +// pairs that are excluded. We use AP->isExcluded() to determine which ports +// are excluded. +static void remove_excluded_ports(AllProbes *AP, ServiceGroup *SG) { + std::list<ServiceNFO *>::iterator i, nxt; + ServiceNFO *svc; + + for(i = SG->services_remaining.begin(); i != SG->services_remaining.end(); i=nxt) { + nxt = i; + nxt++; + + svc = *i; + if (AP->isExcluded(svc->portno, svc->proto)) { + + if (o.debugging) log_write(LOG_PLAIN, "EXCLUDING %d/%s\n", svc->portno, + IPPROTO2STR(svc->proto)); + + svc->target->ports.setServiceProbeResults(svc->portno, svc->proto, + PROBESTATE_EXCLUDED, NULL, + SERVICE_TUNNEL_NONE, + "Excluded from version scan", NULL, + NULL, NULL, NULL, NULL, NULL, NULL); + + SG->services_remaining.erase(i); + SG->services_finished.push_back(svc); + } + } + +} + + +/* Execute a service fingerprinting scan against all open ports of the + Targets specified. */ +int service_scan(std::vector<Target *> &Targets) { + // int service_scan(Target *targets[], int num_targets) + AllProbes *AP; + ServiceGroup *SG; + nsock_pool nsp; + struct timeval now; + int timeout; + enum nsock_loopstatus looprc; + struct timeval starttv; + + if (Targets.size() == 0) + return 1; + + AP = AllProbes::service_scan_init(); + + + // Now I convert the targets into a new ServiceGroup + SG = new ServiceGroup(Targets, AP); + + if (o.override_excludeports) { + if (o.debugging || o.verbose) log_write(LOG_PLAIN, "Overriding exclude ports option! Some undesirable ports may be version scanned!\n"); + } else { + remove_excluded_ports(AP, SG); + } + + if (SG->services_remaining.size() == 0) { + processResults(SG); + delete SG; + return 1; + } + + gettimeofday(&starttv, NULL); + if (o.verbose) { + char targetstr[128]; + bool plural = (Targets.size() != 1); + if (!plural) { + (*(Targets.begin()))->NameIP(targetstr, sizeof(targetstr)); + } else Snprintf(targetstr, sizeof(targetstr), "%u hosts", (unsigned) Targets.size()); + + log_write(LOG_STDOUT, "Scanning %u %s on %s\n", + (unsigned) SG->services_remaining.size(), + (SG->services_remaining.size() == 1)? "service" : "services", + targetstr); + } + + // Lets create a nsock pool for managing all the concurrent probes + // Store the servicegroup in there for availability in callbacks + if ((nsp = nsock_pool_new(SG)) == NULL) { + fatal("%s() failed to create new nsock pool.", __func__); + } + nmap_set_nsock_logger(); + nmap_adjust_loglevel(o.versionTrace()); + + nsock_pool_set_device(nsp, o.device); + + if (o.proxy_chain) { + nsock_pool_set_proxychain(nsp, o.proxy_chain); + } + +#if HAVE_OPENSSL + /* We don't care about connection security in version detection. */ + nsock_pool_ssl_init(nsp, NSOCK_SSL_MAX_SPEED); + nsock_pool_dtls_init(nsp, NSOCK_SSL_MAX_SPEED); +#endif + + launchSomeServiceProbes(nsp, SG); + + // How long do we have before timing out? + gettimeofday(&now, NULL); + timeout = -1; + + // OK! Lets start our main loop! + looprc = nsock_loop(nsp, timeout); + if (looprc == NSOCK_LOOP_ERROR) { + int err = nsock_pool_get_error(nsp); + fatal("Unexpected nsock_loop error. Error code %d (%s)", err, socket_strerror(err)); + } + + nsock_pool_delete(nsp); + + if (o.verbose) { + char additional_info[128]; + if (SG->num_hosts_timedout == 0) + Snprintf(additional_info, sizeof(additional_info), "%u %s on %u %s", + (unsigned) SG->services_finished.size(), + (SG->services_finished.size() == 1)? "service" : "services", + (unsigned) Targets.size(), (Targets.size() == 1)? "host" : "hosts"); + else Snprintf(additional_info, sizeof(additional_info), "%u %s timed out", + SG->num_hosts_timedout, + (SG->num_hosts_timedout == 1)? "host" : "hosts"); + SG->SPM->endTask(NULL, additional_info); + } + + // Yeah - done with the service scan. Now I go through the results + // discovered, store the important info away, and free up everything + // else. + processResults(SG); + + delete SG; + + return 0; +} |