JSON output support
This was rather simple.
This commit is contained in:
parent
7d3f0ca4c8
commit
06785ea4e1
2
Makefile
2
Makefile
@ -4,7 +4,7 @@ CC = clang
|
||||
CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-function -ggdb
|
||||
# -lpthread is only there for debugging (gdb & errno)
|
||||
# -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
|
||||
.SUFFIXES:
|
||||
|
8
README
8
README
@ -3,12 +3,16 @@ ponymap
|
||||
|
||||
`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
|
||||
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
|
||||
--------------------
|
||||
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
|
||||
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 <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <curses.h>
|
||||
#include <term.h>
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
// --- Configuration (application-specific) ------------------------------------
|
||||
|
||||
#define DEFAULT_CONNECT_TIMEOUT 10
|
||||
@ -313,6 +316,9 @@ struct app_context
|
||||
unsigned connect_timeout; ///< Hard timeout for connect()
|
||||
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
|
||||
|
||||
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)
|
||||
SSL_CTX_free (self->ssl_ctx);
|
||||
if (self->json_results)
|
||||
json_decref (self->json_results);
|
||||
}
|
||||
|
||||
// --- Progress indicator ------------------------------------------------------
|
||||
@ -1052,6 +1060,107 @@ initialize_tls (struct app_context *ctx)
|
||||
|
||||
// --- 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 *
|
||||
target_ref (struct target *self)
|
||||
{
|
||||
@ -1065,10 +1174,8 @@ target_unref (struct target *self)
|
||||
if (!self || --self->ref_count)
|
||||
return;
|
||||
|
||||
// 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
|
||||
if (self->results)
|
||||
target_dump_results (self);
|
||||
|
||||
// These must have been aborted already (although we could do that in here)
|
||||
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,
|
||||
"timeout for service scans, in seconds"
|
||||
" (default: " XSTRINGIFY (DEFAULT_SCAN_TIMEOUT) ")" },
|
||||
{ 'j', "json-output", "FILENAME", OPT_LONG_ONLY,
|
||||
"write the results as JSON" },
|
||||
{ 'w', "write-default-cfg", "FILENAME",
|
||||
OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
|
||||
"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;
|
||||
break;
|
||||
case 'j':
|
||||
ctx->json_results = json_array ();
|
||||
ctx->json_filename = optarg;
|
||||
break;
|
||||
case 'w':
|
||||
call_write_default_config (optarg, g_config_table);
|
||||
exit (EXIT_SUCCESS);
|
||||
@ -1591,6 +1704,10 @@ main (int argc, char *argv[])
|
||||
while (ctx.polling)
|
||||
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);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user