JSON output support

This was rather simple.
This commit is contained in:
Přemysl Eric Janouch 2014-09-15 00:56:06 +02:00
parent 7d3f0ca4c8
commit 06785ea4e1
3 changed files with 128 additions and 7 deletions

View File

@ -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
View File

@ -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
View File

@ -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;
} }