masscan-mark-ii/src/read-service-probes.c

1163 lines
39 KiB
C

#include "read-service-probes.h"
#include "util-malloc.h"
#include "massip-port.h"
#include "unusedparm.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _MSC_VER
#pragma warning(disable:4996)
#endif
#if defined(WIN32)
#define strncasecmp _strnicmp
#endif
/*****************************************************************************
* Translate string name into enumerated type
*****************************************************************************/
static enum SvcP_RecordType
parse_type(const char *line, size_t *r_offset, size_t line_length)
{
static const struct {
const char *name;
size_t length;
enum SvcP_RecordType type;
} name_to_types[] = {
{"exclude", 7, SvcP_Exclude},
{"probe", 5, SvcP_Probe},
{"match", 5, SvcP_Match},
{"softmatch", 9, SvcP_Softmatch},
{"ports", 5, SvcP_Ports},
{"sslports", 8, SvcP_Sslports},
{"totalwaitms", 11, SvcP_Totalwaitms},
{"tcpwrappedms",12, SvcP_Tcpwrappedms},
{"rarity", 6, SvcP_Rarity},
{"fallback", 8, SvcP_Fallback},
{0, SvcP_Unknown}
};
size_t i;
size_t offset = *r_offset;
size_t name_length;
size_t name_offset;
enum SvcP_RecordType result;
/* find length of command name */
name_offset = offset;
while (offset < line_length && !isspace(line[offset]))
offset++; /* name = all non-space chars until first space */
name_length = offset - name_offset;
while (offset < line_length && isspace(line[offset]))
offset++; /* trim whitespace after name */
*r_offset = offset;
/* Lookup the command name */
for (i=0; name_to_types[i].name; i++) {
if (name_length != name_to_types[i].length)
continue;
if (strncasecmp(line+name_offset, name_to_types[i].name, name_length) == 0) {
break;
}
}
result = name_to_types[i].type;
/* return the type */
return result;
}
/*****************************************************************************
*****************************************************************************/
static int
is_hexchar(int c)
{
switch (c) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
return 1;
default:
return 0;
}
}
/*****************************************************************************
*****************************************************************************/
static unsigned
hexval(int c)
{
switch (c) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
return c - '0';
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
return c - 'a' + 10;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
return c - 'A' + 10;
default:
return (unsigned)~0;
}
}
/*****************************************************************************
*****************************************************************************/
static struct RangeList
parse_ports(struct NmapServiceProbeList *list, const char *line, size_t offset, size_t line_length)
{
/* Examples:
Exclude 53,T:9100,U:30000-40000
ports 21,43,110,113,199,505,540,1248,5432,30444
ports 111,4045,32750-32810,38978
sslports 443
*/
unsigned is_error = 0;
const char *p;
struct RangeList ranges = {0};
UNUSEDPARM(line_length);
p = rangelist_parse_ports(&ranges, line + offset, &is_error, 0);
if (is_error) {
fprintf(stderr, "%s:%u:%u: bad port spec\n", list->filename, list->line_number, (unsigned)(p-line));
rangelist_remove_all(&ranges);
}
return ranges;
}
/*****************************************************************************
*****************************************************************************/
static unsigned
parse_number(struct NmapServiceProbeList *list, const char *line, size_t offset, size_t line_length)
{
/* Examples:
totalwaitms 6000
tcpwrappedms 3000
rarity 6
*/
unsigned number = 0;
while (offset < line_length && isdigit(line[offset])) {
number = number * 10;
number = number + (line[offset] - '0');
offset++;
}
while (offset < line_length && isspace(line[offset]))
offset++;
if (offset != line_length) {
fprintf(stderr, "%s:%u:%u: unexpected character '%c'\n", list->filename, list->line_number, (unsigned)offset, isprint(line[offset])?line[offset]:'.');
}
return number;
}
/*****************************************************************************
*****************************************************************************/
static char *
parse_name(const char *line, size_t *r_offset, size_t line_length)
{
size_t name_offset = *r_offset;
size_t name_length;
char *result;
/* grab all characters until first space */
while (*r_offset < line_length && !isspace(line[*r_offset]))
(*r_offset)++;
name_length = *r_offset - name_offset;
if (name_length == 0)
return 0;
/* trim trailing white space */
while (*r_offset < line_length && isspace(line[*r_offset]))
(*r_offset)++;
/* allocate result string */
result = MALLOC(name_length+1);
memcpy(result, line + name_offset, name_length+1);
result[name_length] = '\0';
return result;
}
/*****************************************************************************
*****************************************************************************/
static struct ServiceProbeFallback *
parse_fallback(struct NmapServiceProbeList *list, const char *line, size_t offset, size_t line_length)
{
/* Examples:
fallback GetRequest,GenericLines
*/
struct ServiceProbeFallback *result = 0;
while (offset < line_length) {
size_t name_offset;
size_t name_length;
struct ServiceProbeFallback *fallback;
struct ServiceProbeFallback **r_fallback;
/* grab all characters until first space */
name_offset = offset;
while (offset < line_length && !isspace(line[offset]) && line[offset] != ',')
offset++;
name_length = offset - name_offset;
while (offset < line_length && (isspace(line[offset]) || line[offset] == ','))
offset++; /* trim trailing whitespace */
if (name_length == 0) {
fprintf(stderr, "%s:%u:%u: name too short\n", list->filename, list->line_number, (unsigned)name_offset);
break;
}
/* Allocate a record */
fallback = CALLOC(1, sizeof(*fallback));
fallback->name = MALLOC(name_length+1);
memcpy(fallback->name, line+name_offset, name_length+1);
fallback->name[name_length] = '\0';
/* append to end of list */
for (r_fallback=&result; *r_fallback; r_fallback = &(*r_fallback)->next)
;
fallback->next = *r_fallback;
*r_fallback = fallback;
}
return result;
}
/*****************************************************************************
*****************************************************************************/
static void
parse_probe(struct NmapServiceProbeList *list, const char *line, size_t offset, size_t line_length)
{
/* Examples:
Probe TCP GetRequest q|GET / HTTP/1.0\r\n\r\n|
Probe UDP DNSStatusRequest q|\0\0\x10\0\0\0\0\0\0\0\0\0|
Probe TCP NULL q||
*/
const char *filename = list->filename;
unsigned line_number = list->line_number;
struct NmapServiceProbe *probe;
/*
* We have a new 'Probe', so append a blank record to the end of
* our list
*/
probe = CALLOC(1, sizeof(*probe));
if (list->count + 1 >= list->max) {
list->max = list->max * 2 + 1;
list->list = REALLOCARRAY(list->list, sizeof(list->list[0]), list->max);
}
list->list[list->count++] = probe;
/*
* <protocol>
*/
if (line_length - offset <= 3) {
fprintf(stderr, "%s:%u:%u: line too short\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
if (memcmp(line+offset, "TCP", 3) == 0)
probe->protocol = 6;
else if (memcmp(line+offset, "UDP", 3) == 0)
probe->protocol = 17;
else {
fprintf(stderr, "%s:%u:%u: unknown protocol\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
offset += 3;
if (!isspace(line[offset])) {
fprintf(stderr, "%s:%u:%u: unexpected character\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
while (offset < line_length && isspace(line[offset]))
offset++;
/*
* <probename>
*/
probe->name = parse_name(line, &offset, line_length);
if (probe->name == 0) {
fprintf(stderr, "%s:%u:%u: probename parse error\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
/*
* <probestring>
* - must start with a 'q' character
* - a delimiter character starts/stop the string, typically '|'
* - Traditional C-style escapes work:
* \\ \0, \a, \b, \f, \n, \r, \t, \v, and \xXX
*/
{
char delimiter;
char *x;
size_t x_offset;
if (line_length - offset <= 2) {
fprintf(stderr, "%s:%u:%u: line too short\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
if (line[offset++] != 'q') {
fprintf(stderr, "%s:%u:%u: expected 'q', found '%c'\n", filename, line_number, (unsigned)offset, isprint(line[offset-1])?line[offset-1]:'.');
goto parse_error;
}
/* The next character is a 'delimiter' that starts and stops the next
* string of characters, it it usually '|' but may be anything, like '/',
* as long as the delimiter itself is not contained inside the string */
delimiter = line[offset++];
/* allocate a buffer at least as long as the remainder of the line. This is
* probably too large, but cannot be too small. It's okay if we waste a
* few characters. */
x = CALLOC(1, line_length - offset + 1);
probe->hellostring = x;
/* Grab all the characters until the next delimiter, translating escaped
* characters as needed */
x_offset = 0;
while (offset < line_length && line[offset] != delimiter) {
/* Normal case: unescaped characters */
if (line[offset] != '\\') {
x[x_offset++] = line[offset++];
continue;
}
/* skip escape character '\\' */
offset++;
if (offset >= line_length || line[offset] == delimiter) {
fprintf(stderr, "%s:%u:%u: premature end of field\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
/* Handled escape sequence */
switch (line[offset++]) {
default:
fprintf(stderr, "%s:%u: %.*s\n", filename, line_number, (unsigned)line_length, line);
fprintf(stderr, "%s:%u:%u: unexpected escape character '%c'\n", filename, line_number, (unsigned)offset-1, isprint(line[offset-1])?line[offset-1]:'.');
goto parse_error;
case '\\':
x[x_offset++] = '\\';
break;
case '0':
x[x_offset++] = '\0';
break;
case 'a':
x[x_offset++] = '\a';
break;
case 'b':
x[x_offset++] = '\b';
break;
case 'f':
x[x_offset++] = '\f';
break;
case 'n':
x[x_offset++] = '\n';
break;
case 'r':
x[x_offset++] = '\r';
break;
case 't':
x[x_offset++] = '\t';
break;
case 'v':
x[x_offset++] = '\v';
break;
case 'x':
/* make sure at least 2 characters exist in input, either due
* to line-length or the delimiter */
if (offset + 2 >= line_length || line[offset+0] == delimiter || line[offset+1] == delimiter) {
fprintf(stderr, "%s:%u:%u: line too short\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
/* make sure those two characters are hex digits */
if (!is_hexchar(line[offset+0]) || !is_hexchar(line[offset+1])) {
fprintf(stderr, "%s:%u:%u: expected hex, found '%c%c'\n", filename, line_number, (unsigned)offset,
isprint(line[offset+1])?line[offset+1]:'.',
isprint(line[offset+2])?line[offset+2]:'.'
);
goto parse_error;
}
/* parse those two hex digits */
x[x_offset++] = (char)(hexval(line[offset+0])<< 4 | hexval(line[offset+1]));
offset += 2;
break;
}
}
probe->hellolength = x_offset;
if (offset >= line_length || line[offset] != delimiter) {
fprintf(stderr, "%s:%u:%u: missing end delimiter '%c'\n", filename, line_number, (unsigned)offset, isprint(delimiter)?delimiter:'.');
goto parse_error;
}
//offset++;
}
return;
parse_error:
if (probe->name != 0)
free(probe->name);
if (probe->hellostring != 0)
free(probe->hellostring);
probe->hellostring = 0;
free(probe);
list->count--;
}
/*****************************************************************************
*****************************************************************************/
static struct ServiceProbeMatch *
parse_match(struct NmapServiceProbeList *list, const char *line, size_t offset, size_t line_length)
{
/* Examples:
match ftp m/^220.*Welcome to .*Pure-?FTPd (\d\S+\s*)/ p/Pure-FTPd/ v/$1/ cpe:/a:pureftpd:pure-ftpd:$1/
match ssh m/^SSH-([\d.]+)-OpenSSH[_-]([\w.]+)\r?\n/i p/OpenSSH/ v/$2/ i/protocol $1/ cpe:/a:openbsd:openssh:$2/
match mysql m|^\x10\0\0\x01\xff\x13\x04Bad handshake$| p/MySQL/ cpe:/a:mysql:mysql/
match chargen m|@ABCDEFGHIJKLMNOPQRSTUVWXYZ|
match uucp m|^login: login: login: $| p/NetBSD uucpd/ o/NetBSD/ cpe:/o:netbsd:netbsd/a
match printer m|^([\w-_.]+): lpd: Illegal service request\n$| p/lpd/ h/$1/
match afs m|^[\d\D]{28}\s*(OpenAFS)([\d\.]{3}[^\s\0]*)\0| p/$1/ v/$2/
*/
const char *filename = list->filename;
unsigned line_number = list->line_number;
struct ServiceProbeMatch *match;
match = CALLOC(1, sizeof(*match));
/*
* <servicename>
*/
match->service = parse_name(line, &offset, line_length);
if (match->service == 0) {
fprintf(stderr, "%s:%u:%u: servicename is empty\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
/*
* <pattern>
* - must start with a 'm' character
* - a delimiter character starts/stop the string, typically '/' or '|'
* - contents are PCRE regex
*/
{
char delimiter;
size_t regex_offset;
size_t regex_length;
/* line must start with 'm' */
if (line_length - offset <= 2) {
fprintf(stderr, "%s:%u:%u: line too short\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
if (line[offset] != 'm') {
fprintf(stderr, "%s:%u:%u: expected 'm', found '%c'\n", filename, line_number, (unsigned)offset, isprint(line[offset])?line[offset]:'.');
goto parse_error;
}
offset++;
/* next character is the delimiter */
delimiter = line[offset++];
/* Find the length of the regex */
regex_offset = offset;
while (offset < line_length && line[offset] != delimiter)
offset++;
regex_length = offset - regex_offset;
if (offset >= line_length || line[offset] != delimiter) {
fprintf(stderr, "%s:%u:%u: missing ending delimiter '%c'\n", filename, line_number, (unsigned)offset, isprint(delimiter)?delimiter:'.');
goto parse_error;
} else
offset++;
/* add regex pattern to record */
match->regex_length = regex_length;
match->regex = MALLOC(regex_length + 1);
memcpy(match->regex, line+regex_offset, regex_length + 1);
match->regex[regex_length] = '\0';
/* Verify the regex options characters */
while (offset<line_length && !isspace(line[offset])) {
switch (line[offset]) {
case 'i':
match->is_case_insensitive = 1;
break;
case 's':
match->is_include_newlines = 1;
break;
default:
fprintf(stderr, "%s:%u:%u: unknown regex pattern option '%c'\n", filename, line_number, (unsigned)offset, isprint(line[offset])?line[offset]:'.');
goto parse_error;
}
offset++;
}
while (offset<line_length && isspace(line[offset]))
offset++;
}
/*
* <versioninfo>
* - several optional fields
* - each file starts with identifier (p v i h o d cpe:)
* - next comes the delimiter character (preferably '/' slash)
* - next comes data
* - ends with delimiter
*/
while (offset < line_length) {
char id;
char delimiter;
size_t value_length;
size_t value_offset;
int is_a = 0;
enum SvcV_InfoType type;
/* Make sure we have enough characters for a versioninfo string */
if (offset >= line_length)
break;
if (offset + 2 >= line_length) {
fprintf(stderr, "%s:%u:%u: unexpected character at end of line '%c'\n", filename, line_number, (unsigned)offset, isprint(line[offset])?line[offset]:'.');
goto parse_error;
}
/* grab the 'id' character, which is either singe letter or the string 'cpe:' */
id = line[offset++];
if (id == 'c') {
if (offset + 3 >= line_length) {
fprintf(stderr, "%s:%u:%u: unexpected character at end of line '%c'\n", filename, line_number, (unsigned)offset, isprint(line[offset])?line[offset]:'.');
goto parse_error;
}
if (memcmp(line+offset, "pe:", 3) != 0) {
fprintf(stderr, "%s:%u:%u: expected string 'cpe:'\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
offset += 3;
}
switch (id) {
case 'p':
type = SvcV_ProductName;
break;
case 'v':
type = SvcV_Version;
break;
case 'i':
type = SvcV_Info;
break;
case 'h':
type = SvcV_Hostname;
break;
case 'o':
type = SvcV_OperatingSystem;
break;
case 'd':
type = SvcV_DeviceType;
break;
case 'c':
type = SvcV_CpeName;
break;
default:
fprintf(stderr, "%s:%u:%u: versioninfo unknown identifier '%c'\n", filename, line_number, (unsigned)offset, isprint(id)?id:'.');
goto parse_error;
}
/* grab the delimiter */
if (offset + 2 >= line_length) {
fprintf(stderr, "%s:%u:%u: line too short\n", filename, line_number, (unsigned)offset);
goto parse_error;
}
delimiter = line[offset++];
/* Grab the contents of this string */
value_offset = offset;
while (offset < line_length && line[offset] != delimiter)
offset++;
value_length = offset - value_offset;
if (offset >= line_length || line[offset] != delimiter) {
fprintf(stderr, "%s:%u:%u: missing ending delimiter '%c'\n", filename, line_number, (unsigned)offset, isprint(delimiter)?delimiter:'.');
goto parse_error;
} else
offset++;
if (id == 'c' && offset + 1 <= line_length && line[offset] == 'a') {
is_a = 1;
offset++;
}
if (offset < line_length && !isspace(line[offset])) {
fprintf(stderr, "%s:%u:%u: unexpected character after delimiter '%c'\n", filename, line_number, (unsigned)offset, isprint(delimiter)?delimiter:'.');
goto parse_error;
}
while (offset < line_length && isspace(line[offset]))
offset++;
/* Create a versioninfo record */
{
struct ServiceVersionInfo *v;
struct ServiceVersionInfo **r_v;
v = CALLOC(1, sizeof(*v));
v->type = type;
v->value = MALLOC(value_length + 1);
memcpy(v->value, line+value_offset, value_length+1);
v->value[value_length] = '\0';
v->is_a = is_a;
/* insert at end of list */
for (r_v = &match->versioninfo; *r_v; r_v = &(*r_v)->next)
;
v->next = *r_v;
*r_v = v;
}
}
return match;
parse_error:
free(match->regex);
free(match->service);
while (match->versioninfo) {
struct ServiceVersionInfo *v = match->versioninfo;
match->versioninfo = v->next;
if (v->value)
free(v->value);
free(v);
}
free(match);
return 0;
}
/*****************************************************************************
*****************************************************************************/
static void
parse_line(struct NmapServiceProbeList *list, const char *line)
{
const char *filename = list->filename;
unsigned line_number = list->line_number;
size_t line_length;
size_t offset;
enum SvcP_RecordType type;
struct RangeList ranges = {0};
struct NmapServiceProbe *probe;
/* trim whitespace */
offset = 0;
line_length = strlen(line);
while (offset && isspace(line[offset]))
offset++;
while (line_length && isspace(line[line_length-1]))
line_length--;
/* Ignore comment lines */
if (ispunct(line[offset]))
return;
/* Ignore empty lines */
if (offset >= line_length)
return;
/* parse the type field field */
type = parse_type(line, &offset, line_length);
/* parse the remainder of the line, depending upon the type */
switch ((int)type) {
case SvcP_Unknown:
fprintf(stderr, "%s:%u:%u: unknown type: '%.*s'\n", filename, line_number, (unsigned)offset, (int)offset-0, line);
return;
case SvcP_Exclude:
if (list->count) {
/* The 'Exclude' directive is only valid at the top of the file,
* before any Probes */
fprintf(stderr, "%s:%u:%u: 'Exclude' directive only valid before any 'Probe'\n", filename, line_number, (unsigned)offset);
} else {
ranges = parse_ports(list, line, offset, line_length);
if (ranges.count == 0) {
fprintf(stderr, "%s:%u:%u: 'Exclude' bad format\n", filename, line_number, (unsigned)offset);
} else {
rangelist_merge(&list->exclude, &ranges);
rangelist_remove_all(&ranges);
}
}
return;
case SvcP_Probe:
/* Creates a new probe record, all the other types (except 'Exclude') operate
* on the current probe record */
parse_probe(list, line, offset, line_length);
return;
}
/*
* The remaining items only work in the context of the current 'Probe'
* directive
*/
if (list->count == 0) {
fprintf(stderr, "%s:%u:%u: 'directive only valid after a 'Probe'\n", filename, line_number, (unsigned)offset);
return;
}
probe = list->list[list->count-1];
switch ((int)type) {
case SvcP_Ports:
ranges = parse_ports(list, line, offset, line_length);
if (ranges.count == 0) {
fprintf(stderr, "%s:%u:%u: bad ports format\n", filename, line_number, (unsigned)offset);
} else {
rangelist_merge(&probe->ports, &ranges);
rangelist_remove_all(&ranges);
}
break;
case SvcP_Sslports:
ranges = parse_ports(list, line, offset, line_length);
if (ranges.count == 0) {
fprintf(stderr, "%s:%u:%u: bad ports format\n", filename, line_number, (unsigned)offset);
} else {
rangelist_merge(&probe->sslports, &ranges);
rangelist_remove_all(&ranges);
}
break;
case SvcP_Match:
case SvcP_Softmatch:
{
struct ServiceProbeMatch *match;
match = parse_match(list, line, offset, line_length);
if (match) {
struct ServiceProbeMatch **r_match;
/* put at end of list */
for (r_match = &probe->match; *r_match; r_match = &(*r_match)->next)
;
match->next = *r_match;
*r_match = match;
match->is_softmatch = (type == SvcP_Softmatch);
}
}
break;
case SvcP_Totalwaitms:
probe->totalwaitms = parse_number(list, line, offset, line_length);
break;
case SvcP_Tcpwrappedms:
probe->tcpwrappedms = parse_number(list, line, offset, line_length);
break;
case SvcP_Rarity:
probe->rarity = parse_number(list, line, offset, line_length);
break;
case SvcP_Fallback:
{
struct ServiceProbeFallback *fallback;
fallback = parse_fallback(list, line, offset, line_length);
if (fallback) {
fallback->next = probe->fallback;
probe->fallback = fallback;
}
}
break;
}
}
/*****************************************************************************
*****************************************************************************/
static struct NmapServiceProbeList *
nmapserviceprobes_new(const char *filename)
{
struct NmapServiceProbeList *result;
result = CALLOC(1, sizeof(*result));
result->filename = filename;
return result;
}
/*****************************************************************************
*****************************************************************************/
struct NmapServiceProbeList *
nmapserviceprobes_read_file(const char *filename)
{
FILE *fp;
char line[32768];
struct NmapServiceProbeList *result;
/*
* Open the file
*/
fp = fopen(filename, "rt");
if (fp == NULL) {
perror(filename);
return 0;
}
/*
* Create the result structure
*/
result = nmapserviceprobes_new(filename);
/*
* parse all lines in the text file
*/
while (fgets(line, sizeof(line), fp)) {
/* Track line number for error messages */
result->line_number++;
/* Parse this string into a record */
parse_line(result, line);
}
fclose(fp);
result->filename = 0; /* name no longer valid after this point */
result->line_number = (unsigned)~0; /* line number no longer valid after this point */
nmapserviceprobes_print(result, stdout);
return result;
}
/*****************************************************************************
*****************************************************************************/
static void
nmapserviceprobes_free_record(struct NmapServiceProbe *probe)
{
if (probe->name)
free(probe->name);
if (probe->hellostring)
free(probe->hellostring);
rangelist_remove_all(&probe->ports);
rangelist_remove_all(&probe->sslports);
while (probe->match) {
struct ServiceProbeMatch *match = probe->match;
probe->match = match->next;
free(match->regex);
free(match->service);
while (match->versioninfo) {
struct ServiceVersionInfo *v = match->versioninfo;
match->versioninfo = v->next;
if (v->value)
free(v->value);
free(v);
}
free(match);
}
while (probe->fallback) {
struct ServiceProbeFallback *fallback;
fallback = probe->fallback;
probe->fallback = fallback->next;
if (fallback->name)
free(fallback->name);
free(fallback);
}
free(probe);
}
/*****************************************************************************
*****************************************************************************/
static void
nmapserviceprobes_print_ports(const struct RangeList *ranges, FILE *fp, const char *prefix, int default_proto)
{
unsigned i;
/* don't print anything if no ports */
if (ranges == NULL || ranges->count == 0)
return;
/* 'Exclude', 'ports', 'sslports' */
fprintf(fp, "%s ", prefix);
/* print all ports */
for (i=0; i<ranges->count; i++) {
int proto;
int begin = ranges->list[i].begin;
int end = ranges->list[i].end;
if (Templ_TCP <= begin && begin < Templ_UDP)
proto = Templ_TCP;
else if (Templ_UDP <= begin && begin < Templ_SCTP)
proto = Templ_UDP;
else
proto = Templ_SCTP;
/* If UDP, shift down */
begin -= proto;
end -= proto;
/* print comma between ports, but not for first port */
if (i)
fprintf(fp, ",");
/* Print either one number for a single port, or two numbers for a range */
if (default_proto != proto) {
default_proto = proto;
switch (proto) {
case Templ_TCP: fprintf(fp, "T:"); break;
case Templ_UDP: fprintf(fp, "U:"); break;
case Templ_SCTP: fprintf(fp, "S"); break;
case Templ_ICMP_echo: fprintf(fp, "e"); break;
case Templ_ICMP_timestamp: fprintf(fp, "t"); break;
case Templ_ARP: fprintf(fp, "A"); break;
case Templ_VulnCheck: fprintf(fp, "v"); break;
}
}
fprintf(fp, "%u", begin);
if (end > begin)
fprintf(fp, "-%u", end);
}
fprintf(fp, "\n");
}
/*****************************************************************************
*****************************************************************************/
static int
contains_char(const char *string, size_t length, int c)
{
size_t i;
for (i=0; i<length; i++) {
if (string[i] == c)
return 1;
}
return 0;
}
/*****************************************************************************
*****************************************************************************/
static void
nmapserviceprobes_print_dstring(FILE *fp, const char *string, size_t length, int delimiter)
{
size_t i;
/* If the string contains the preferred delimiter, then choose a different
* delimiter */
if (contains_char(string, length, delimiter)) {
static const char *delimiters = "|/\"'#*+-!@$%^&()_=";
for (i=0; delimiters[i]; i++) {
delimiter = delimiters[i];
if (!contains_char(string, length, delimiter))
break;
}
}
/* print start delimiter */
fprintf(fp, "%c", delimiter);
/* print the string */
for (i=0; i<length; i++) {
char c = string[i];
fprintf(fp, "%c", c);
}
/* print end delimiter */
fprintf(fp, "%c", delimiter);
}
/*****************************************************************************
*****************************************************************************/
static void
nmapserviceprobes_print_hello(FILE *fp, const char *string, size_t length, int delimiter)
{
size_t i;
/* If the string contains the preferred delimiter, then choose a different
* delimiter */
if (contains_char(string, length, delimiter)) {
static const char *delimiters = "|/\"'#*+-!@$%^&()_=";
for (i=0; delimiters[i]; i++) {
delimiter = delimiters[i];
if (!contains_char(string, length, delimiter))
break;
}
}
/* print start delimiter */
fprintf(fp, "%c", delimiter);
/* print the string */
for (i=0; i<length; i++) {
char c = string[i];
switch (c) {
case '\\':
fprintf(fp, "\\\\");
break;
case '\0':
fprintf(fp, "\\0");
break;
case '\a':
fprintf(fp, "\\a");
break;
case '\b':
fprintf(fp, "\\b");
break;
case '\f':
fprintf(fp, "\\f");
break;
case '\n':
fprintf(fp, "\\n");
break;
case '\r':
fprintf(fp, "\\r");
break;
case '\t':
fprintf(fp, "\\t");
break;
case '\v':
fprintf(fp, "\\v");
break;
default:
if (isprint(c))
fprintf(fp, "%c", c);
else
fprintf(fp, "\\x%02x", ((unsigned)c)&0xFF);
break;
}
}
/* print end delimiter */
fprintf(fp, "%c", delimiter);
}
/*****************************************************************************
*****************************************************************************/
void
nmapserviceprobes_print(const struct NmapServiceProbeList *list, FILE *fp)
{
unsigned i;
if (list == NULL)
return;
nmapserviceprobes_print_ports(&list->exclude, fp, "Exclude", ~0);
for (i=0; i<list->count; i++) {
struct NmapServiceProbe *probe = list->list[i];
struct ServiceProbeMatch *match;
/* print the first part of the probe */
fprintf(fp, "Probe %s %s q",
(probe->protocol==6)?"TCP":"UDP",
probe->name);
/* print the query/hello string */
nmapserviceprobes_print_hello(fp, probe->hellostring, probe->hellolength, '|');
fprintf(fp, "\n");
if (probe->rarity)
fprintf(fp, "rarity %u\n", probe->rarity);
if (probe->totalwaitms)
fprintf(fp, "totalwaitms %u\n", probe->totalwaitms);
if (probe->tcpwrappedms)
fprintf(fp, "tcpwrappedms %u\n", probe->tcpwrappedms);
nmapserviceprobes_print_ports(&probe->ports, fp, "ports", (probe->protocol==6)?Templ_TCP:Templ_UDP);
nmapserviceprobes_print_ports(&probe->sslports, fp, "sslports", (probe->protocol==6)?Templ_TCP:Templ_UDP);
for (match=probe->match; match; match = match->next) {
struct ServiceVersionInfo *vi;
fprintf(fp, "match %s m", match->service);
nmapserviceprobes_print_dstring(fp, match->regex, match->regex_length, '/');
if (match->is_case_insensitive)
fprintf(fp, "i");
if (match->is_include_newlines)
fprintf(fp, "s");
fprintf(fp, " ");
for (vi=match->versioninfo; vi; vi=vi->next) {
const char *tag;
switch (vi->type) {
case SvcV_Unknown: tag = "u"; break;
case SvcV_ProductName: tag = "p"; break;
case SvcV_Version: tag = "v"; break;
case SvcV_Info: tag = "i"; break;
case SvcV_Hostname: tag = "h"; break;
case SvcV_OperatingSystem: tag = "o"; break;
case SvcV_DeviceType: tag = "e"; break;
case SvcV_CpeName: tag = "cpe:"; break;
default: tag = "";
}
fprintf(fp, "%s", tag);
nmapserviceprobes_print_dstring(fp, vi->value, strlen(vi->value), '/');
if (vi->is_a)
fprintf(fp, "a");
fprintf(fp, " ");
}
fprintf(fp, "\n");
}
}
}
/*****************************************************************************
*****************************************************************************/
void
nmapserviceprobes_free(struct NmapServiceProbeList *list)
{
unsigned i;
if (list == NULL)
return;
for (i=0; list->count; i++) {
nmapserviceprobes_free_record(list->list[i]);
}
if (list->list)
free(list->list);
free(list);
}
/*****************************************************************************
*****************************************************************************/
int
nmapserviceprobes_selftest(void)
{
const char *lines[] = {
"Exclude 53,T:9100,U:30000-40000\n",
"Probe UDP DNSStatusRequest q|\\0\\0\\x10\\0\\0\\0\\0\\0\\0\\0\\0\\0|\n",
"Probe TCP GetRequest q|GET / HTTP/1.0\r\n\r\n|\n",
"ports 80\n",
"sslports 443\n",
"Probe TCP NULL q||\n",
"ports 21,43,110,113,199,505,540,1248,5432,30444\n",
"match ftp m/^220.*Welcome to .*Pure-?FTPd (\\d\\S+\\s*)/ p/Pure-FTPd/ v/$1/ cpe:/a:pureftpd:pure-ftpd:$1/\n",
"match ssh m/^SSH-([\\d.]+)-OpenSSH[_-]([\\w.]+)\\r?\\n/i p/OpenSSH/ v/$2/ i/protocol $1/ cpe:/a:openbsd:openssh:$2/\n",
"match mysql m|^\\x10\\0\\0\\x01\\xff\\x13\\x04Bad handshake$| p/MySQL/ cpe:/a:mysql:mysql/\n",
"match chargen m|@ABCDEFGHIJKLMNOPQRSTUVWXYZ|\n",
"match uucp m|^login: login: login: $| p/NetBSD uucpd/ o/NetBSD/ cpe:/o:netbsd:netbsd/a\n",
"match printer m|^([\\w-_.]+): lpd: Illegal service request\\n$| p/lpd/ h/$1/\n",
"match afs m|^[\\d\\D]{28}\\s*(OpenAFS)([\\d\\.]{3}[^\\s\\0]*)\\0| p/$1/ v/$2/\n",
0
};
unsigned i;
struct NmapServiceProbeList *list = nmapserviceprobes_new("<selftest>");
for (i=0; lines[i]; i++) {
list->line_number = i;
parse_line(list, lines[i]);
}
//nmapserviceprobes_print(list, stdout);
return 0;
}