parent
7d3f0ca4c8
commit
06785ea4e1
2
Makefile
2
Makefile
|
@ -4,7 +4,7 @@ CC = clang
|
||||||
CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-function -ggdb
|
CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-function -ggdb
|
||||||
# -lpthread is only there for debugging (gdb & errno)
|
# -lpthread is only there for debugging (gdb & errno)
|
||||||
# -lrt is only for glibc < 2.17
|
# -lrt is only for glibc < 2.17
|
||||||
LDFLAGS = `pkg-config --libs libssl` -lpthread -lrt -ldl -lcurses
|
LDFLAGS = `pkg-config --libs libssl jansson` -lpthread -lrt -ldl -lcurses
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
|
|
8
README
8
README
|
@ -3,12 +3,16 @@ ponymap
|
||||||
|
|
||||||
`ponymap' is an experimental network scanner, not even in the alpha stage yet.
|
`ponymap' is an experimental network scanner, not even in the alpha stage yet.
|
||||||
|
|
||||||
|
Replacing nmap is not the goal, even though it would be rather very nice to
|
||||||
|
have a non-copyleft licensed network scanner.
|
||||||
|
|
||||||
The ultimate purpose of this scanner is bruteforcing hosts and ports in search
|
The ultimate purpose of this scanner is bruteforcing hosts and ports in search
|
||||||
of running services. Replacing nmap is not the goal.
|
of running services of a kind. It should be very simple and straight-forward
|
||||||
|
to write your own service detection plugins.
|
||||||
|
|
||||||
Building and Running
|
Building and Running
|
||||||
--------------------
|
--------------------
|
||||||
Build dependencies: openssl, clang, pkg-config, GNU make, awk, sh
|
Build dependencies: openssl, clang, pkg-config, GNU make, Jansson
|
||||||
|
|
||||||
If you don't have Clang, you can edit the Makefile to use GCC or TCC, they work
|
If you don't have Clang, you can edit the Makefile to use GCC or TCC, they work
|
||||||
just as good. But there's no CMake support yet, so I force it in the Makefile.
|
just as good. But there's no CMake support yet, so I force it in the Makefile.
|
||||||
|
|
125
ponymap.c
125
ponymap.c
|
@ -22,10 +22,13 @@
|
||||||
#include "plugin-api.h"
|
#include "plugin-api.h"
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#include <curses.h>
|
#include <curses.h>
|
||||||
#include <term.h>
|
#include <term.h>
|
||||||
|
|
||||||
|
#include <jansson.h>
|
||||||
|
|
||||||
// --- Configuration (application-specific) ------------------------------------
|
// --- Configuration (application-specific) ------------------------------------
|
||||||
|
|
||||||
#define DEFAULT_CONNECT_TIMEOUT 10
|
#define DEFAULT_CONNECT_TIMEOUT 10
|
||||||
|
@ -313,6 +316,9 @@ struct app_context
|
||||||
unsigned connect_timeout; ///< Hard timeout for connect()
|
unsigned connect_timeout; ///< Hard timeout for connect()
|
||||||
unsigned scan_timeout; ///< Hard timeout for service scans
|
unsigned scan_timeout; ///< Hard timeout for service scans
|
||||||
|
|
||||||
|
json_t *json_results; ///< The results as a JSON value
|
||||||
|
const char *json_filename; ///< The filename to write JSON to
|
||||||
|
|
||||||
SSL_CTX *ssl_ctx; ///< OpenSSL context
|
SSL_CTX *ssl_ctx; ///< OpenSSL context
|
||||||
|
|
||||||
struct str_map svc_list; ///< List of services to scan for
|
struct str_map svc_list; ///< List of services to scan for
|
||||||
|
@ -376,6 +382,8 @@ app_context_free (struct app_context *self)
|
||||||
|
|
||||||
if (self->ssl_ctx)
|
if (self->ssl_ctx)
|
||||||
SSL_CTX_free (self->ssl_ctx);
|
SSL_CTX_free (self->ssl_ctx);
|
||||||
|
if (self->json_results)
|
||||||
|
json_decref (self->json_results);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Progress indicator ------------------------------------------------------
|
// --- Progress indicator ------------------------------------------------------
|
||||||
|
@ -1052,6 +1060,107 @@ initialize_tls (struct app_context *ctx)
|
||||||
|
|
||||||
// --- Job generation and result aggregation -----------------------------------
|
// --- Job generation and result aggregation -----------------------------------
|
||||||
|
|
||||||
|
struct target_dump_data
|
||||||
|
{
|
||||||
|
char address[INET_ADDRSTRLEN]; ///< The IP address as a string
|
||||||
|
|
||||||
|
struct unit **results; ///< Results sorted by service
|
||||||
|
size_t results_len; ///< Number of results
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
target_dump_json (struct target *self, struct target_dump_data *data)
|
||||||
|
{
|
||||||
|
json_t *o = json_object ();
|
||||||
|
json_array_append_new (self->ctx->json_results, o);
|
||||||
|
|
||||||
|
json_object_set_new (o, "address", json_string (data->address));
|
||||||
|
if (self->hostname)
|
||||||
|
json_object_set_new (o, "hostname", json_string (self->hostname));
|
||||||
|
if (self->ctx->quitting)
|
||||||
|
json_object_set_new (o, "partial", json_boolean (true));
|
||||||
|
|
||||||
|
json_t *services = json_array ();
|
||||||
|
json_object_set_new (o, "services", services);
|
||||||
|
|
||||||
|
struct service *last_service = NULL;
|
||||||
|
json_t *service, *ports;
|
||||||
|
for (size_t i = 0; i < data->results_len; i++)
|
||||||
|
{
|
||||||
|
struct unit *u = data->results[i];
|
||||||
|
if (u->service != last_service)
|
||||||
|
{
|
||||||
|
service = json_object ();
|
||||||
|
json_array_append_new (services, service);
|
||||||
|
json_object_set_new (service, "name",
|
||||||
|
json_string (u->service->name));
|
||||||
|
json_object_set_new (service, "transport",
|
||||||
|
json_string (u->transport->name));
|
||||||
|
json_object_set_new (service, "ports", ports);
|
||||||
|
|
||||||
|
last_service = u->service;
|
||||||
|
ports = json_array ();
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t *port = json_object ();
|
||||||
|
json_array_append_new (ports, port);
|
||||||
|
json_object_set_new (port, "port", json_integer (u->port));
|
||||||
|
|
||||||
|
json_t *info = json_array ();
|
||||||
|
json_object_set_new (port, "info", info);
|
||||||
|
for (size_t k = 0; k < u->info.len; k++)
|
||||||
|
json_array_append_new (info, json_string (u->info.vector[k]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
target_dump_terminal (struct target *self, struct target_dump_data *data)
|
||||||
|
{
|
||||||
|
// TODO: hide the indicator -> ncurses
|
||||||
|
// TODO: present the results; if we've been interrupted by the user,
|
||||||
|
// self->ctx->quitting, state that they're only partial
|
||||||
|
// TODO: show the indicator again
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
unit_cmp_by_service (const void *ax, const void *bx)
|
||||||
|
{
|
||||||
|
const struct unit *a = ax, *b = bx;
|
||||||
|
return strcmp (a->service->name, b->service->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
target_dump_results (struct target *self)
|
||||||
|
{
|
||||||
|
struct app_context *ctx = self->ctx;
|
||||||
|
struct target_dump_data data;
|
||||||
|
|
||||||
|
uint32_t address = htonl (self->ip);
|
||||||
|
if (!inet_ntop (AF_INET, &address, data.address, sizeof data.address))
|
||||||
|
{
|
||||||
|
print_error ("%s: %s", "inet_ntop", strerror (errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = 0;
|
||||||
|
for (struct unit *iter = self->results; iter; iter = iter->next)
|
||||||
|
len++;
|
||||||
|
|
||||||
|
struct unit *sorted[len];
|
||||||
|
data.results = sorted;
|
||||||
|
data.results_len = len;
|
||||||
|
|
||||||
|
for (struct unit *iter = self->results; iter; iter = iter->next)
|
||||||
|
sorted[--len] = iter;
|
||||||
|
|
||||||
|
// Sort them by service name so that they can be grouped
|
||||||
|
qsort (sorted, N_ELEMENTS (sorted), sizeof *sorted, unit_cmp_by_service);
|
||||||
|
|
||||||
|
if (ctx->json_results)
|
||||||
|
target_dump_json (self, &data);
|
||||||
|
target_dump_terminal (self, &data);
|
||||||
|
}
|
||||||
|
|
||||||
static struct target *
|
static struct target *
|
||||||
target_ref (struct target *self)
|
target_ref (struct target *self)
|
||||||
{
|
{
|
||||||
|
@ -1065,10 +1174,8 @@ target_unref (struct target *self)
|
||||||
if (!self || --self->ref_count)
|
if (!self || --self->ref_count)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// TODO: hide the indicator -> ncurses
|
if (self->results)
|
||||||
// TODO: present the results; if we've been interrupted by the user,
|
target_dump_results (self);
|
||||||
// self->ctx->quitting, state that they're only partial
|
|
||||||
// TODO: show the indicator again
|
|
||||||
|
|
||||||
// These must have been aborted already (although we could do that in here)
|
// These must have been aborted already (although we could do that in here)
|
||||||
hard_assert (!self->running_units);
|
hard_assert (!self->running_units);
|
||||||
|
@ -1451,6 +1558,8 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv)
|
||||||
{ 'T', "scan-timeout", "TIMEOUT", 0,
|
{ 'T', "scan-timeout", "TIMEOUT", 0,
|
||||||
"timeout for service scans, in seconds"
|
"timeout for service scans, in seconds"
|
||||||
" (default: " XSTRINGIFY (DEFAULT_SCAN_TIMEOUT) ")" },
|
" (default: " XSTRINGIFY (DEFAULT_SCAN_TIMEOUT) ")" },
|
||||||
|
{ 'j', "json-output", "FILENAME", OPT_LONG_ONLY,
|
||||||
|
"write the results as JSON" },
|
||||||
{ 'w', "write-default-cfg", "FILENAME",
|
{ 'w', "write-default-cfg", "FILENAME",
|
||||||
OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
|
OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
|
||||||
"write a default configuration file and exit" },
|
"write a default configuration file and exit" },
|
||||||
|
@ -1499,6 +1608,10 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv)
|
||||||
}
|
}
|
||||||
ctx->scan_timeout = ul;
|
ctx->scan_timeout = ul;
|
||||||
break;
|
break;
|
||||||
|
case 'j':
|
||||||
|
ctx->json_results = json_array ();
|
||||||
|
ctx->json_filename = optarg;
|
||||||
|
break;
|
||||||
case 'w':
|
case 'w':
|
||||||
call_write_default_config (optarg, g_config_table);
|
call_write_default_config (optarg, g_config_table);
|
||||||
exit (EXIT_SUCCESS);
|
exit (EXIT_SUCCESS);
|
||||||
|
@ -1591,6 +1704,10 @@ main (int argc, char *argv[])
|
||||||
while (ctx.polling)
|
while (ctx.polling)
|
||||||
poller_run (&ctx.poller);
|
poller_run (&ctx.poller);
|
||||||
|
|
||||||
|
if (ctx.json_results && !json_dump_file (ctx.json_results,
|
||||||
|
ctx.json_filename, JSON_INDENT (2) | JSON_SORT_KEYS | JSON_ENCODE_ANY))
|
||||||
|
print_error ("failed to write JSON output");
|
||||||
|
|
||||||
app_context_free (&ctx);
|
app_context_free (&ctx);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue