From 06785ea4e1ace20e014acdfb17634a09cd1c87c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Mon, 15 Sep 2014 00:56:06 +0200 Subject: [PATCH] JSON output support This was rather simple. --- Makefile | 2 +- README | 8 +++- ponymap.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 128 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 3c816de..209ba14 100644 --- a/Makefile +++ b/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: diff --git a/README b/README index 4186f3f..efc6d2e 100644 --- a/README +++ b/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. diff --git a/ponymap.c b/ponymap.c index a8ff167..de93024 100644 --- a/ponymap.c +++ b/ponymap.c @@ -22,10 +22,13 @@ #include "plugin-api.h" #include #include +#include #include #include +#include + // --- 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; }