1163 lines
39 KiB
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;
|
||
|
}
|
||
|
|
||
|
|