/service_scan.cc
C++ | 2762 lines | 2181 code | 202 blank | 379 comment | 446 complexity | f7a696193f9f59efe543f5a3d3ba30bb MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, LGPL-2.0, LGPL-2.1
Large files files are truncated, but you can click here to view the full file
- /***************************************************************************
- * 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-2013 Insecure.Com LLC. Nmap is *
- * also a registered trademark of Insecure.Com LLC. This program is free *
- * software; you may redistribute and/or modify it under the terms of the *
- * GNU General Public License as published by the Free Software *
- * Foundation; Version 2 ("GPL"), BUT ONLY WITH ALL OF THE CLARIFICATIONS *
- * AND EXCEPTIONS DESCRIBED HEREIN. This guarantees your right to use, *
- * modify, and redistribute this software under certain conditions. If *
- * you wish to embed Nmap technology into proprietary software, we sell *
- * alternative licenses (contact sales@nmap.com). Dozens of software *
- * vendors already license Nmap technology such as host discovery, port *
- * scanning, OS detection, version detection, and the Nmap Scripting *
- * Engine. *
- * *
- * Note that the GPL places important restrictions on "derivative works", *
- * yet it does not provide a detailed definition of that term. To avoid *
- * misunderstandings, we interpret that term as broadly as copyright law *
- * allows. For example, we consider an application to constitute a *
- * derivative work for the purpose of this license if it does any of the *
- * following with any software or content covered by this license *
- * ("Covered Software"): *
- * *
- * o Integrates source code from Covered Software. *
- * *
- * o Reads or includes copyrighted data files, such as Nmap's nmap-os-db *
- * or nmap-service-probes. *
- * *
- * o Is designed specifically to execute Covered Software and parse the *
- * results (as opposed to typical shell or execution-menu apps, which will *
- * execute anything you tell them to). *
- * *
- * o Includes Covered Software in a proprietary executable installer. The *
- * installers produced by InstallShield are an example of this. Including *
- * Nmap with other software in compressed or archival form does not *
- * trigger this provision, provided appropriate open source decompression *
- * or de-archiving software is widely available for no charge. For the *
- * purposes of this license, an installer is considered to include Covered *
- * Software even if it actually retrieves a copy of Covered Software from *
- * another source during runtime (such as by downloading it from the *
- * Internet). *
- * *
- * o Links (statically or dynamically) to a library which does any of the *
- * above. *
- * *
- * o Executes a helper program, module, or script to do any of the above. *
- * *
- * This list is not exclusive, but is meant to clarify our interpretation *
- * of derived works with some common examples. Other people may interpret *
- * the plain GPL differently, so we consider this a special exception to *
- * the GPL that we apply to Covered Software. Works which meet any of *
- * these conditions must conform to all of the terms of this license, *
- * particularly including the GPL Section 3 requirements of providing *
- * source code and allowing free redistribution of the work as a whole. *
- * *
- * As another special exception to the GPL terms, Insecure.Com LLC grants *
- * permission to link the code of this program with any version of the *
- * OpenSSL library which is distributed under a license identical to that *
- * listed in the included docs/licenses/OpenSSL.txt file, and distribute *
- * linked combinations including the two. *
- * *
- * Any redistribution of Covered Software, including any derived works, *
- * must obey and carry forward all of the terms of this license, including *
- * obeying all GPL rules and restrictions. For example, source code of *
- * the whole work must be provided and free redistribution must be *
- * allowed. All GPL references to "this License", are to be treated as *
- * including the terms and conditions of this license text as well. *
- * *
- * Because this license imposes special exceptions to the GPL, Covered *
- * Work may not be combined (even as part of a larger work) with plain GPL *
- * software. The terms, conditions, and exceptions of this license must *
- * be included as well. This license is incompatible with some other open *
- * source licenses as well. In some cases we can relicense portions of *
- * Nmap or grant special permissions to use it in other open source *
- * software. Please contact fyodor@nmap.org with any such requests. *
- * Similarly, we don't incorporate incompatible open source software into *
- * Covered Software without special permission from the copyright holders. *
- * *
- * If you have any questions about the licensing restrictions on using *
- * Nmap in other works, are happy to help. As mentioned above, we also *
- * offer alternative license to integrate Nmap into proprietary *
- * applications and appliances. These contracts have been sold to dozens *
- * of software vendors, and generally include a perpetual license as well *
- * as providing for priority support and updates. They also fund the *
- * continued development of Nmap. Please email sales@nmap.com for further *
- * information. *
- * *
- * If you have received a written license agreement or contract for *
- * Covered Software stating terms other than these, you may choose to use *
- * and redistribute Covered Software under those terms instead of these. *
- * *
- * 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 (none *
- * have been found so far). *
- * *
- * Source code also allows you to port Nmap to new platforms, fix bugs, *
- * and add new features. You are highly encouraged to send your changes *
- * to the dev@nmap.org mailing list for possible incorporation into the *
- * main distribution. By sending these changes to Fyodor or one of the *
- * Insecure.Org development mailing lists, or checking them into the Nmap *
- * source code repository, it is understood (unless you specify otherwise) *
- * that you are offering the Nmap Project (Insecure.Com LLC) the *
- * unlimited, non-exclusive right to reuse, modify, and relicense the *
- * code. Nmap will always be available Open Source, but this is important *
- * because the inability to relicense code has caused devastating problems *
- * for other Free Software projects (such as KDE and NASM). We also *
- * occasionally relicense the code to third parties as discussed above. *
- * If you wish to specify special license conditions of your *
- * contributions, just say so when you send them. *
- * *
- * This program 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. See the Nmap *
- * license file for more details (it's in a COPYING file included with *
- * Nmap, and also available from https://svn.nmap.org/nmap/COPYING *
- * *
- ***************************************************************************/
- /* $Id$ */
- #include "service_scan.h"
- #include "timing.h"
- #include "NmapOps.h"
- #include "nsock.h"
- #include "Target.h"
- #include "utils.h"
- #include "protocols.h"
- #include "nmap_tty.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
- #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 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;
-
- 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(nsock_pool nsp, enum serviceprobestate probe_state, ServiceGroup *SG, ServiceNFO *svc, nsock_iod nsi);
- 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;
- regex_extra = NULL;
- isInitialized = false;
- matchops_ignorecase = false;
- matchops_dotall = false;
- isSoft = false;
- }
- ServiceProbeMatch::~ServiceProbeMatch() {
- std::vector<char *>::iterator it;
- if (!isInitialized) return;
- if (servicename) free(servicename);
- 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);
- matchstrlen = 0;
- if (regex_compiled) pcre_free(regex_compiled);
- if (regex_extra) pcre_free(regex_extra);
- isInitialized = false;
- matchops_anchor = -1;
- }
- /* Realloc a malloc-allocated string and put a given prefix at the front. */
- static char *string_prefix(char *string, const char *prefix)
- {
- size_t slen, plen;
- slen = strlen(string);
- plen = strlen(prefix);
- string = (char *) safe_realloc(string, plen + slen + 1);
- memmove(string + plen, string, slen + 1);
- memmove(string, prefix, plen);
- return string;
- }
- /* Read the next tmplt from *matchtext and update *matchtext. Return true iff
- a template was read. For example, after
- matchtext = "p/123/ d/456/";
- next_template(&matchtext, &modestr, &flags, &tmplt);
- then
- matchtext == " d/456/"
- modestr == "p"
- tmplt == "123"
- flags == ""
- *modestr and *tmplt must be freed if the return value is true. */
- static bool next_template(const char **matchtext, char **modestr, char **tmplt,
- char **flags, int lineno) {
- const char *p, *q;
- char delimchar;
- p = *matchtext;
- while(isspace((int) (unsigned char) *p))
- p++;
- if (*p == '\0')
- return false;
- q = p;
- while (isalpha(*q) || *q == ':')
- q++;
- if (*q == '\0' || isspace(*q))
- fatal("%s: parse error on line %d of nmap-service-probes", __func__, lineno);
- *modestr = mkstr(p, q);
- delimchar = *q;
- p = q + 1;
- q = strchr(p, delimchar);
- if (q == NULL)
- fatal("%s: parse error on line %d of nmap-service-probes", __func__, lineno);
- *tmplt = mkstr(p, q);
- p = q + 1;
- q = p;
- while (isalpha(*q))
- q++;
- *flags = mkstr(p, q);
- /* 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 *modestr, *tmptemplate, *flags;
- int pcre_compile_ops = 0;
- const char *pcre_errptr = NULL;
- int pcre_erroffset = 0;
- 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 = (char *) safe_malloc(p - matchtext + 1);
- memcpy(servicename, matchtext, p - matchtext);
- servicename[p - matchtext] = '\0';
- // 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 delieates 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);
- matchtype = SERVICEMATCH_REGEX;
- // 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)
- pcre_compile_ops |= PCRE_CASELESS;
- if (matchops_dotall)
- pcre_compile_ops |= PCRE_DOTALL;
- regex_compiled = pcre_compile(matchstr, pcre_compile_ops, &pcre_errptr,
- &pcre_erroffset, NULL);
- if (regex_compiled == NULL)
- fatal("%s: illegal regexp on line %d of nmap-service-probes (at regexp offset %d): %s\n", __func__, lineno, pcre_erroffset, pcre_errptr);
- // Now study the regexp for greater efficiency
- regex_extra = pcre_study(regex_compiled, 0, &pcre_errptr);
- if (pcre_errptr != NULL)
- fatal("%s: failed to pcre_study regexp on line %d of nmap-service-probes: %s\n", __func__, lineno, pcre_errptr);
- free(modestr);
- free(flags);
- /* 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)) {
- if (strcmp(modestr, "p") == 0)
- curr_tmp = &product_template;
- else if (strcmp(modestr, "v") == 0)
- curr_tmp = &version_template;
- else if (strcmp(modestr, "i") == 0)
- curr_tmp = &info_template;
- else if (strcmp(modestr, "h") == 0)
- curr_tmp = &hostname_template;
- else if (strcmp(modestr, "o") == 0)
- curr_tmp = &ostype_template;
- else if (strcmp(modestr, "d") == 0)
- curr_tmp = &devicetype_template;
- else if (strcmp(modestr, "cpe:") == 0) {
- tmptemplate = string_prefix(tmptemplate, "cpe:/");
- cpe_templates.push_back(NULL);
- curr_tmp = &cpe_templates.back();
- } else
- fatal("%s: Unknown template specifier '%s' on line %d of nmap-service-probes", __func__, modestr, lineno);
- /* 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;
- free(modestr);
- free(flags);
- }
- 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 sructure returned is
- // only valid until the next time this function is called. The only
- // exception is that the serviceName field can be saved throughought
- // 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;
- int ovector[150]; // allows 50 substring matches (including the overall match)
- assert(isInitialized);
- assert (matchtype == SERVICEMATCH_REGEX);
- // Clear out the output struct
- memset(&MD_return, 0, sizeof(MD_return));
- MD_return.isSoft = isSoft;
- rc = pcre_exec(regex_compiled, regex_extra, bufc, buflen, 0, 0, ovector, sizeof(ovector) / sizeof(*ovector));
- if (rc < 0) {
- #ifdef PCRE_ERROR_MATCHLIMIT // earlier PCRE versions lack this
- if (rc == PCRE_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
- #endif // PCRE_ERROR_MATCHLIMIT
- if (rc != PCRE_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, ovector, rc, 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 (*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, ovector, and nummatches mean the same as in
- // dotmplsubst().
- static char *substvar(char *tmplvar, char **tmplvarend,
- const u8 *subject, int subjectlen, int *ovector,
- int nummatches) {
- char substcommand[16];
- char *p = NULL;
- char *p_end;
- int subnum = 0;
- int offstart, offend;
- int rc;
- int i;
- 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;
- 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];
- offend = ovector[subnum * 2 + 1];
- assert(offstart >= 0 && offstart < subjectlen);
- assert(offend >= 0 && 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];
- offend = ovector[subnum * 2 + 1];
- assert(offstart >= 0 && offstart < subjectlen);
- assert(offend >= 0 && 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(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];
- offend = ovector[subnum * 2 + 1];
- assert(offstart >= 0 && offstart < subjectlen);
- assert(offend >= 0 && 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(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 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 'nummatches'
- // matches in ovector. 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, int subjectlen,
- int *ovector, int nummatches, 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; // fuck this!
-
- 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, ovector, nummatches);
- 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, int subjectlen,
- int *ovector, int nummatches, char *product, int productlen,
- char *version, int versionlen, char *info, int infolen,
- char *hostname, int hostnamelen, char *ostype, int ostypelen,
- char *devicetype, int devicetypelen,
- char *cpe_a, int cpe_alen,
- char *cpe_h, int cpe_hlen,
- char *cpe_o, int cpe_olen) {
- 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, ovector, nummatches, product_template, product, productlen);
- if (rc != 0) {
- error("Warning: Servicescan failed to fill product_template (subjectlen: %d, productlen: %d). 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, ovector, nummatches, version_template, version, versionlen);
- if (rc != 0) {
- error("Warning: Servicescan failed to fill version_template (subjectlen: %d, versionlen: %d). 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, ovector, nummatches, info_template, info, infolen);
- if (rc != 0) {
- error("Warning: Servicescan failed to fill info_template (subjectlen: %d, infolen: %d). 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, ovector, nummatches, hostname_template, hostname, hostnamelen);
- if (rc != 0) {
- error("Warning: Servicescan failed to fill hostname_template (subjectlen: %d, hostnamelen: %d). 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, ovector, nummatches, ostype_template, ostype, ostypelen);
- if (rc != 0) {
- error("Warning: Servicescan failed to fill ostype_template (subjectlen: %d, ostypelen: %d). 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, ovector, nummatches, devicetype_template, devicetype, devicetypelen);
- if (rc != 0) {
- error("Warning: Servicescan failed to fill devicetype_template (subjectlen: %d, devicetypelen: %d). 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;
- int 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, ovector, nummatches, cpe_templates[i], cpe, cpelen, transform_cpe);
- if (rc != 0) {
- error("Warning: Servicescan failed to fill cpe_%c (subjectlen: %d, devicetypelen: %d). Too long? Match string was line %d: d/%s/", part, subjectlen, devicetypelen, deflineno,
- (devicetype_template)? devicetype_template : "");
- if (devicetypelen > 0) *devicetype = '\0';
- retval = -1;
- }
- }
-
- return retval;
- }
- ServiceProbe::ServiceProbe() {
- int i;
- probename = NULL;
- probestring = NULL;
- totalwaitms = DEFAULT_SERVICEWAITMS;
- 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;
- fallbackStr = NULL;
- for (i=0; i<MAXFALLBACKS+1; i++) fallbacks[i] = NULL;
- }
- ServiceProbe::~ServiceProbe() {
- std::vector<ServiceProbeMatch *>::iterator vi;
- if (probename) free(probename);
- if (probestring) free(probestring);
- 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);
- len = p - pd;
- probename = (char *) safe_malloc(len + 1);
- memcpy(probename, pd, len);
- probename[len] = '\0';
- // 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);
- }
- void ServiceProbe::setProbeString(const u8 *ps, int stringlen) {
- if (probestringlen) free(probestring);
- probestringlen = stringlen;
- if (stringlen > 0) {
- probestring = (u8 *) safe_malloc(stringlen + 1);
- memcpy(probestring, ps, stringlen);
- probestring[stringlen] = '\0'; // but note that other \0 may be in string
- } 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) {
- 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) {
- std::vector<const char *>::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;…
Large files files are truncated, but you can click here to view the full file