Even its non-full GNUInstallDirs paths are absolute, which is apparently allowed.
		
			
				
	
	
		
			2118 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2118 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * ponymap.c: the experimental network scanner
 | |
|  *
 | |
|  * Copyright (c) 2014, Přemysl Eric Janouch <p@janouch.name>
 | |
|  *
 | |
|  * Permission to use, copy, modify, and/or distribute this software for any
 | |
|  * purpose with or without fee is hereby granted.
 | |
|  *
 | |
|  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | |
|  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | |
|  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 | |
|  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | |
|  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 | |
|  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 | |
|  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #define LIBERTY_WANT_SSL
 | |
| #define LIBERTY_WANT_POLLER
 | |
| 
 | |
| #include "config.h"
 | |
| #include "liberty/liberty.c"
 | |
| #include "plugin-api.h"
 | |
| 
 | |
| #include <inttypes.h>
 | |
| 
 | |
| #include <dirent.h>
 | |
| #include <dlfcn.h>
 | |
| #include <arpa/inet.h>
 | |
| #include <sys/resource.h>
 | |
| 
 | |
| #include <curses.h>
 | |
| #include <term.h>
 | |
| 
 | |
| #include <jansson.h>
 | |
| 
 | |
| // --- Configuration (application-specific) ------------------------------------
 | |
| 
 | |
| enum
 | |
| {
 | |
| 	DEFAULT_CONNECT_TIMEOUT = 10,
 | |
| 	DEFAULT_SCAN_TIMEOUT    = 10
 | |
| };
 | |
| 
 | |
| static struct simple_config_item g_config_table[] =
 | |
| {
 | |
| 	{ "plugin_dir",      PLUGIN_DIR,        "Where to search for plugins"    },
 | |
| 	{ NULL,              NULL,              NULL                             }
 | |
| };
 | |
| 
 | |
| // --- Fancy terminal output ---------------------------------------------------
 | |
| 
 | |
| static struct
 | |
| {
 | |
| 	bool initialized;                   ///< Terminal is available
 | |
| 	bool stdout_is_tty;                 ///< `stdout' is a terminal
 | |
| 	bool stderr_is_tty;                 ///< `stderr' is a terminal
 | |
| 
 | |
| 	char *color_set[8];                 ///< Codes to set the foreground colour
 | |
| }
 | |
| g_terminal;
 | |
| 
 | |
| static void
 | |
| init_terminal (void)
 | |
| {
 | |
| 	int tty_fd = -1;
 | |
| 	if ((g_terminal.stderr_is_tty = isatty (STDERR_FILENO)))
 | |
| 		tty_fd = STDERR_FILENO;
 | |
| 	if ((g_terminal.stdout_is_tty = isatty (STDOUT_FILENO)))
 | |
| 		tty_fd = STDOUT_FILENO;
 | |
| 
 | |
| 	int err;
 | |
| 	if (tty_fd == -1 || setupterm (NULL, tty_fd, &err) == ERR)
 | |
| 		return;
 | |
| 
 | |
| 	// Make sure all terminal features used by us are supported
 | |
| 	if (!set_a_foreground || !orig_pair
 | |
| 	 || !enter_bold_mode || !exit_attribute_mode
 | |
| 	 || !carriage_return || !cursor_left || !clr_eol)
 | |
| 	{
 | |
| 		del_curterm (cur_term);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	for (size_t i = 0; i < N_ELEMENTS (g_terminal.color_set); i++)
 | |
| 		g_terminal.color_set[i] = xstrdup (tparm (set_a_foreground,
 | |
| 			i, 0, 0, 0, 0, 0, 0, 0, 0));
 | |
| 
 | |
| 	g_terminal.initialized = true;
 | |
| }
 | |
| 
 | |
| static void
 | |
| free_terminal (void)
 | |
| {
 | |
| 	if (!g_terminal.initialized)
 | |
| 		return;
 | |
| 
 | |
| 	for (size_t i = 0; i < N_ELEMENTS (g_terminal.color_set); i++)
 | |
| 		free (g_terminal.color_set[i]);
 | |
| 	del_curterm (cur_term);
 | |
| }
 | |
| 
 | |
| typedef int (*terminal_printer_fn) (int);
 | |
| 
 | |
| static int
 | |
| putchar_stderr (int c)
 | |
| {
 | |
| 	return fputc (c, stderr);
 | |
| }
 | |
| 
 | |
| static terminal_printer_fn
 | |
| get_terminal_printer (FILE *stream)
 | |
| {
 | |
| 	if (!g_terminal.initialized)
 | |
| 		return NULL;
 | |
| 
 | |
| 	if (stream == stdout && g_terminal.stdout_is_tty)
 | |
| 		return putchar;
 | |
| 	if (stream == stderr && g_terminal.stderr_is_tty)
 | |
| 		return putchar_stderr;
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static void
 | |
| print_color (FILE *stream, int color, const char *s)
 | |
| {
 | |
| 	terminal_printer_fn printer = get_terminal_printer (stream);
 | |
| 
 | |
| 	if (printer && color != -1)
 | |
| 		tputs (g_terminal.color_set[color], 1, printer);
 | |
| 
 | |
| 	fputs (s, stream);
 | |
| 
 | |
| 	if (printer && color != -1)
 | |
| 		tputs (orig_pair, 1, printer);
 | |
| }
 | |
| 
 | |
| static void
 | |
| print_bold (FILE *stream, const char *s)
 | |
| {
 | |
| 	terminal_printer_fn printer = get_terminal_printer (stream);
 | |
| 
 | |
| 	if (printer)
 | |
| 		tputs (enter_bold_mode, 1, printer);
 | |
| 
 | |
| 	fputs (s, stream);
 | |
| 
 | |
| 	if (printer)
 | |
| 		tputs (exit_attribute_mode, 1, printer);
 | |
| }
 | |
| 
 | |
| // --- Application data --------------------------------------------------------
 | |
| 
 | |
| // The scan is a cartesian product of: [IP ranges] -> [ports] -> [services]
 | |
| 
 | |
| struct port_range
 | |
| {
 | |
| 	LIST_HEADER (struct port_range)
 | |
| 	uint16_t start;                     ///< The beginning of the range
 | |
| 	uint16_t end;                       ///< The end of the range
 | |
| };
 | |
| 
 | |
| static void
 | |
| port_range_delete (struct port_range *self)
 | |
| {
 | |
| 	free (self);
 | |
| }
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| struct ip_range
 | |
| {
 | |
| 	LIST_HEADER (struct ip_range)
 | |
| 	uint32_t start;                     ///< The beginning of the range
 | |
| 	uint32_t end;                       ///< The end of the range
 | |
| 
 | |
| 	char *original_name;                ///< The name the user typed in
 | |
| 	uint32_t original_address;          ///< The address of `original_name'
 | |
| };
 | |
| 
 | |
| static void
 | |
| ip_range_delete (struct ip_range *self)
 | |
| {
 | |
| 	free (self->original_name);
 | |
| 	free (self);
 | |
| }
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| struct target
 | |
| {
 | |
| 	LIST_HEADER (struct target)
 | |
| 	size_t ref_count;                   ///< Reference count
 | |
| 	struct app_context *ctx;            ///< Application context
 | |
| 
 | |
| 	uint32_t ip;                        ///< IP address
 | |
| 	char ip_string[INET_ADDRSTRLEN];    ///< IP address as a string
 | |
| 	char *hostname;                     ///< Hostname
 | |
| 
 | |
| 	/// All units that have ended, either successfully finding a service, or
 | |
| 	/// just successful in establishing a connection.
 | |
| 	/// These don't hold a reference to us as they're considered a part of
 | |
| 	/// this object; we hold a reference to them.
 | |
| 	struct unit *results;
 | |
| 
 | |
| 	/// All currently running units for this target, holding a reference to us.
 | |
| 	/// They remove themselves from this list upon terminating.  The purpose of
 | |
| 	/// this list is making it possible to stop them forcefully.
 | |
| 	struct unit *running_units;
 | |
| };
 | |
| 
 | |
| static struct target *target_ref (struct target *self);
 | |
| static void target_unref (struct target *self);
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| struct unit
 | |
| {
 | |
| 	LIST_HEADER (struct unit)
 | |
| 	size_t ref_count;                   ///< Reference count
 | |
| 	struct target *target;              ///< Target context
 | |
| 
 | |
| 	struct service *service;            ///< Service
 | |
| 	void *service_data;                 ///< User data for service
 | |
| 
 | |
| 	struct transport *transport;        ///< Transport methods
 | |
| 	void *transport_data;               ///< User data for transport
 | |
| 
 | |
| 	int socket_fd;                      ///< The TCP socket
 | |
| 	uint16_t port;                      ///< The scanned port
 | |
| 	struct str read_buffer;             ///< Unprocessed input
 | |
| 	struct str write_buffer;            ///< Output yet to be sent out
 | |
| 
 | |
| 	struct poller_timer timeout_event;  ///< Timeout event
 | |
| 	struct poller_fd fd_event;          ///< FD event
 | |
| 
 | |
| 	struct strv info;                   ///< Info resulting from the scan
 | |
| 	bool scan_started;                  ///< Whether the scan has been started
 | |
| 	bool stop_requested;                ///< Stopping requested by service
 | |
| 	bool stopped;                       ///< Scan has been stopped
 | |
| 	bool success;                       ///< Service has been found
 | |
| };
 | |
| 
 | |
| static struct unit *unit_ref (struct unit *self);
 | |
| static void unit_unref (struct unit *self);
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| // The only real purpose of this is to abstract away TLS/SSL
 | |
| struct transport
 | |
| {
 | |
| 	LIST_HEADER (struct transport)
 | |
| 
 | |
| 	const char *name;                   ///< Name of the transport
 | |
| 
 | |
| 	/// Initialize the transport
 | |
| 	bool (*init) (struct unit *u);
 | |
| 	/// Destroy the user data pointer
 | |
| 	void (*cleanup) (struct unit *u);
 | |
| 
 | |
| 	/// The underlying socket may have become readable, update `read_buffer';
 | |
| 	/// return false if the connection has failed.
 | |
| 	enum socket_io_result (*on_readable) (struct unit *u);
 | |
| 	/// The underlying socket may have become writeable, flush `write_buffer';
 | |
| 	/// return false if the connection has failed.
 | |
| 	enum socket_io_result (*on_writeable) (struct unit *u);
 | |
| 	/// Return event mask to use in the poller
 | |
| 	int (*get_poll_events) (struct unit *u);
 | |
| };
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| #define INDICATOR_INTERVAL  500
 | |
| 
 | |
| struct indicator
 | |
| {
 | |
| 	struct poller_timer timer;          ///< The animation timer
 | |
| 
 | |
| 	const char *frames;                 ///< All the characters
 | |
| 	size_t frames_len;                  ///< The number of characters
 | |
| 
 | |
| 	char *status;                       ///< The status text
 | |
| 	unsigned position;                  ///< The current animation character
 | |
| 	bool shown;                         ///< The indicator is shown on screen
 | |
| };
 | |
| 
 | |
| static void indicator_init (struct indicator *self, struct poller *poller);
 | |
| 
 | |
| static void
 | |
| indicator_free (struct indicator *self)
 | |
| {
 | |
| 	free (self->status);
 | |
| }
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| struct generator
 | |
| {
 | |
| 	struct target *current_target;      ///< Current target
 | |
| 
 | |
| 	struct ip_range *ip_range_iter;     ///< Current IP range
 | |
| 	struct port_range *port_range_iter; ///< Current port range
 | |
| 	uint32_t ip_iter;                   ///< IP iterator within the range
 | |
| 	uint16_t port_iter;                 ///< Port iterator within the range
 | |
| 
 | |
| 	struct str_map_iter svc_iter;       ///< Service iterator
 | |
| 	struct service *svc;                ///< Current service iterator value
 | |
| 
 | |
| 	struct transport *transport_iter;   ///< Transport iterator
 | |
| };
 | |
| 
 | |
| static bool generator_step (struct app_context *ctx);
 | |
| static void on_generator_step_requested (struct app_context *ctx);
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| struct app_context
 | |
| {
 | |
| 	struct str_map config;              ///< User configuration
 | |
| 	bool list_services;                 ///< Option flag, list all services
 | |
| 	unsigned connect_timeout;           ///< Timeout for connect() in sec.
 | |
| 	unsigned scan_timeout;              ///< Timeout for service scans in sec.
 | |
| 
 | |
| 	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
 | |
| 	struct port_range *port_list;       ///< List of ports to scan on
 | |
| 	struct ip_range *ip_list;           ///< List of IP's to scan
 | |
| 
 | |
| 	struct str_map services;            ///< All registered services
 | |
| 	struct transport *transports;       ///< All available transports
 | |
| 	struct generator generator;         ///< Unit generator
 | |
| 	struct indicator indicator;         ///< Status indicator
 | |
| 
 | |
| 	size_t stats_hosts;                 ///< How many hosts we've scanned
 | |
| 	size_t stats_results;               ///< How many services we've found
 | |
| 
 | |
| 	// We need this list ordered from the oldest running target,
 | |
| 	// therefore we track the tail to allow O(1) appends.
 | |
| 
 | |
| 	struct target *running_targets;     ///< List of currently scanned targets
 | |
| 	struct target *running_tail;        ///< The tail link of `running_targets'
 | |
| 
 | |
| 	struct poller poller;               ///< Manages polled descriptors
 | |
| 	struct poller_idle step_event;      ///< Idle event to make more units
 | |
| 	bool quitting;                      ///< User requested quitting
 | |
| 	bool polling;                       ///< The event loop is running
 | |
| };
 | |
| 
 | |
| static void
 | |
| app_context_init (struct app_context *self)
 | |
| {
 | |
| 	memset (self, 0, sizeof *self);
 | |
| 
 | |
| 	self->config = str_map_make (free);
 | |
| 	simple_config_load_defaults (&self->config, g_config_table);
 | |
| 
 | |
| 	self->connect_timeout = DEFAULT_CONNECT_TIMEOUT;
 | |
| 	self->scan_timeout = DEFAULT_SCAN_TIMEOUT;
 | |
| 
 | |
| 	self->svc_list = str_map_make (NULL);
 | |
| 	self->services = str_map_make (NULL);
 | |
| 	indicator_init (&self->indicator, &self->poller);
 | |
| 	// Ignoring the generator so far
 | |
| 
 | |
| 	poller_init (&self->poller);
 | |
| 	self->quitting = false;
 | |
| 	self->polling = false;
 | |
| 
 | |
| 	self->step_event = poller_idle_make (&self->poller);
 | |
| 	self->step_event.dispatcher = (poller_idle_fn) on_generator_step_requested;
 | |
| 	self->step_event.user_data = self;
 | |
| }
 | |
| 
 | |
| static void
 | |
| app_context_free (struct app_context *self)
 | |
| {
 | |
| 	str_map_free (&self->config);
 | |
| 	str_map_free (&self->svc_list);
 | |
| 	str_map_free (&self->services);
 | |
| 	indicator_free (&self->indicator);
 | |
| 	poller_free (&self->poller);
 | |
| 
 | |
| 	LIST_FOR_EACH (struct ip_range, iter, self->ip_list)
 | |
| 		ip_range_delete (iter);
 | |
| 	LIST_FOR_EACH (struct port_range, iter, self->port_list)
 | |
| 		port_range_delete (iter);
 | |
| 
 | |
| 	if (self->ssl_ctx)
 | |
| 		SSL_CTX_free (self->ssl_ctx);
 | |
| 	if (self->json_results)
 | |
| 		json_decref (self->json_results);
 | |
| }
 | |
| 
 | |
| // --- Progress indicator ------------------------------------------------------
 | |
| 
 | |
| static void
 | |
| indicator_set_timer (struct indicator *self)
 | |
| {
 | |
| 	poller_timer_set (&self->timer, INDICATOR_INTERVAL);
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_indicator_tick (struct indicator *self)
 | |
| {
 | |
| 	if (!self->shown)
 | |
| 		return;
 | |
| 
 | |
| 	if (++self->position >= self->frames_len)
 | |
| 		self->position = 0;
 | |
| 
 | |
| 	tputs (cursor_left, 1, putchar);
 | |
| 	putchar (self->frames[self->position]);
 | |
| 	fflush (stdout);
 | |
| 	indicator_set_timer (self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| indicator_init (struct indicator *self, struct poller *poller)
 | |
| {
 | |
| 	self->timer = poller_timer_make (poller);
 | |
| 	self->timer.dispatcher = (poller_timer_fn) on_indicator_tick;
 | |
| 	self->timer.user_data = self;
 | |
| 
 | |
| 	static const char frames[] = "-\\|/";
 | |
| 	self->position = 0;
 | |
| 	self->frames = frames;
 | |
| 	self->frames_len = sizeof frames - 1;
 | |
| 
 | |
| 	self->status = NULL;
 | |
| 	self->shown = false;
 | |
| }
 | |
| 
 | |
| static void
 | |
| indicator_show (struct indicator *self)
 | |
| {
 | |
| 	if (self->shown || !g_terminal.initialized || !g_terminal.stdout_is_tty)
 | |
| 		return;
 | |
| 
 | |
| 	tputs (carriage_return, 1, putchar);
 | |
| 	printf ("%s... %c", self->status, self->frames[self->position]);
 | |
| 	tputs (clr_eol, 1, putchar);
 | |
| 	fflush (stdout);
 | |
| 
 | |
| 	self->shown = true;
 | |
| 	indicator_set_timer (self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| indicator_hide (struct indicator *self)
 | |
| {
 | |
| 	if (!self->shown)
 | |
| 		return;
 | |
| 
 | |
| 	tputs (carriage_return, 1, putchar);
 | |
| 	tputs (clr_eol, 1, putchar);
 | |
| 	fflush (stdout);
 | |
| 
 | |
| 	self->shown = false;
 | |
| 	poller_timer_reset (&self->timer);
 | |
| }
 | |
| 
 | |
| static void
 | |
| indicator_set_status (struct indicator *self, char *status)
 | |
| {
 | |
| 	bool refresh = self->shown;
 | |
| 	indicator_hide (self);
 | |
| 
 | |
| 	cstr_set (&self->status, status);
 | |
| 
 | |
| 	if (refresh)
 | |
| 		indicator_show (self);
 | |
| }
 | |
| 
 | |
| // --- Scan units --------------------------------------------------------------
 | |
| 
 | |
| static void on_unit_ready (const struct pollfd *pfd, struct unit *u);
 | |
| 
 | |
| static struct unit *
 | |
| unit_ref (struct unit *self)
 | |
| {
 | |
| 	self->ref_count++;
 | |
| 	return self;
 | |
| }
 | |
| 
 | |
| static void
 | |
| unit_unref (struct unit *self)
 | |
| {
 | |
| 	if (!self || --self->ref_count)
 | |
| 		return;
 | |
| 
 | |
| 	target_unref (self->target);
 | |
| 
 | |
| 	str_free (&self->read_buffer);
 | |
| 	str_free (&self->write_buffer);
 | |
| 	strv_free (&self->info);
 | |
| 
 | |
| 	free (self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| unit_stop (struct unit *u)
 | |
| {
 | |
| 	if (u->stopped)
 | |
| 		return;
 | |
| 
 | |
| 	u->stopped = true;
 | |
| 	if (u->scan_started)
 | |
| 	{
 | |
| 		if (u->service->on_stopped)
 | |
| 			u->service->on_stopped (u->service_data);
 | |
| 		u->service->scan_free (u->service_data);
 | |
| 
 | |
| 		if (u->transport->cleanup)
 | |
| 			u->transport->cleanup (u);
 | |
| 	}
 | |
| 
 | |
| 	poller_timer_reset (&u->timeout_event);
 | |
| 
 | |
| 	// This way we avoid a syscall with epoll
 | |
| 	xclose (u->socket_fd);
 | |
| 	// We don't fork any child processes, so we're sure
 | |
| 	u->fd_event.closed = true;
 | |
| 	poller_fd_reset (&u->fd_event);
 | |
| 
 | |
| 	u->transport_data = NULL;
 | |
| 	u->service_data = NULL;
 | |
| 	u->socket_fd = -1;
 | |
| 
 | |
| 	// We're no longer running
 | |
| 	LIST_UNLINK (u->target->running_units, u);
 | |
| 
 | |
| 	// We might have made it possible to launch new units; we cannot run
 | |
| 	// the generator right now, though, as we could spin in a long loop
 | |
| 	poller_idle_set (&u->target->ctx->step_event);
 | |
| 
 | |
| 	// If the scan has been started, we have successfully connected at least
 | |
| 	if (u->scan_started || u->success)
 | |
| 	{
 | |
| 		struct target *target = u->target;
 | |
| 		if (u->success)
 | |
| 			target->ctx->stats_results++;
 | |
| 
 | |
| 		// Now we're a part of the target
 | |
| 		LIST_PREPEND (target->results, u);
 | |
| 		u->target = NULL;
 | |
| 		target_unref (target);
 | |
| 	}
 | |
| 	else
 | |
| 		unit_unref (u);
 | |
| }
 | |
| 
 | |
| static void
 | |
| unit_update_poller (struct unit *u, const struct pollfd *pfd)
 | |
| {
 | |
| 	int new_events = u->transport->get_poll_events (u);
 | |
| 	hard_assert (new_events != 0);
 | |
| 
 | |
| 	if (!pfd || pfd->events != new_events)
 | |
| 		poller_fd_set (&u->fd_event, new_events);
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_unit_ready (const struct pollfd *pfd, struct unit *u)
 | |
| {
 | |
| 	struct service *service = u->service;
 | |
| 	struct transport *transport = u->transport;
 | |
| 	enum socket_io_result result;
 | |
| 	bool got_eof = false;
 | |
| 
 | |
| 	if ((result = transport->on_readable (u)) == SOCKET_IO_ERROR)
 | |
| 		goto error;
 | |
| 	got_eof |= result == SOCKET_IO_EOF;
 | |
| 
 | |
| 	if (u->read_buffer.len)
 | |
| 	{
 | |
| 		struct str *buf = &u->read_buffer;
 | |
| 		service->on_data (u->service_data, buf->str, buf->len);
 | |
| 		str_remove_slice (buf, 0, buf->len);
 | |
| 
 | |
| 		if (u->stop_requested)
 | |
| 			goto stop;
 | |
| 	}
 | |
| 
 | |
| 	if ((result = transport->on_writeable (u)) == SOCKET_IO_ERROR)
 | |
| 		goto error;
 | |
| 	got_eof |= result == SOCKET_IO_EOF;
 | |
| 
 | |
| 	if (got_eof)
 | |
| 	{
 | |
| 		if (service->on_eof)
 | |
| 			service->on_eof (u->service_data);
 | |
| 		if (u->stop_requested || !u->write_buffer.len)
 | |
| 			goto stop;
 | |
| 	}
 | |
| 
 | |
| 	unit_update_poller (u, pfd);
 | |
| 	return;
 | |
| 
 | |
| error:
 | |
| 	if (service->on_error)
 | |
| 		service->on_error (u->service_data);
 | |
| 
 | |
| stop:
 | |
| 	unit_stop (u);
 | |
| }
 | |
| 
 | |
| static void
 | |
| unit_start_scan (struct unit *u)
 | |
| {
 | |
| 	if (u->transport->init && !u->transport->init (u))
 | |
| 	{
 | |
| 		// TODO: maybe print a message with the problem?
 | |
| 		unit_stop (u);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	u->scan_started = true;
 | |
| 	poller_timer_set (&u->timeout_event, u->target->ctx->scan_timeout * 1000);
 | |
| 
 | |
| 	u->service_data = u->service->scan_init (u->service, u);
 | |
| 	u->fd_event.dispatcher = (poller_fd_fn) on_unit_ready;
 | |
| 	unit_update_poller (u, NULL);
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_unit_connected (const struct pollfd *pfd, struct unit *u)
 | |
| {
 | |
| 	(void) pfd;
 | |
| 
 | |
| 	poller_timer_reset (&u->timeout_event);
 | |
| 
 | |
| 	int error;
 | |
| 	socklen_t error_len = sizeof error;
 | |
| 	if (!getsockopt (pfd->fd, SOL_SOCKET, SO_ERROR, &error, &error_len)
 | |
| 	 && error != 0)
 | |
| 	{
 | |
| 		// XXX: what if we get EADDRNOTAVAIL in here?  Can we?  If yes,
 | |
| 		//   we'll have to return the request back to the generator to retry.
 | |
| 		// XXX: we could also call bind separately, with INADDR_ANY, 0.
 | |
| 		//   Then EADDRINUSE (as per man 2 bind) means port exhaustion.
 | |
| 		//   But POSIX seems to say that this can block, too.
 | |
| 		soft_assert (error != EADDRNOTAVAIL);
 | |
| 
 | |
| 		unit_stop (u);
 | |
| 	}
 | |
| 	else
 | |
| 		unit_start_scan (u);
 | |
| }
 | |
| 
 | |
| static struct unit *
 | |
| unit_new (struct target *target, int socket_fd, uint16_t port,
 | |
| 	struct service *service, struct transport *transport)
 | |
| {
 | |
| 	struct unit *u = xcalloc (1, sizeof *u);
 | |
| 	u->ref_count = 1;
 | |
| 	u->target = target_ref (target);
 | |
| 	u->socket_fd = socket_fd;
 | |
| 	u->port = port;
 | |
| 	u->service = service;
 | |
| 	u->transport = transport;
 | |
| 
 | |
| 	u->read_buffer = str_make ();
 | |
| 	u->write_buffer = str_make ();
 | |
| 	u->info = strv_make ();
 | |
| 
 | |
| 	u->timeout_event = poller_timer_make (&target->ctx->poller);
 | |
| 	u->timeout_event.dispatcher = (poller_timer_fn) unit_stop;
 | |
| 	u->timeout_event.user_data = u;
 | |
| 
 | |
| 	u->fd_event = poller_fd_make (&target->ctx->poller, socket_fd);
 | |
| 	u->fd_event.dispatcher = (poller_fd_fn) on_unit_connected;
 | |
| 	u->fd_event.user_data = u;
 | |
| 
 | |
| 	LIST_PREPEND (target->running_units, u);
 | |
| 	return u;
 | |
| }
 | |
| 
 | |
| enum unit_make_result
 | |
| {
 | |
| 	UNIT_MAKE_OK,                       ///< Operation completed successfully
 | |
| 	UNIT_MAKE_ERROR,                    ///< Unspecified error occured
 | |
| 	UNIT_MAKE_TRY_AGAIN                 ///< Try again later
 | |
| };
 | |
| 
 | |
| static enum unit_make_result
 | |
| unit_make (struct target *target, uint32_t ip, uint16_t port,
 | |
| 	struct service *service, struct transport *transport)
 | |
| {
 | |
| 	// TODO: more exhaustive checking of errno
 | |
| 
 | |
| 	struct app_context *ctx = target->ctx;
 | |
| 	int socket_fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
 | |
| 	if (socket_fd == -1)
 | |
| 		return errno == EMFILE
 | |
| 			? UNIT_MAKE_TRY_AGAIN
 | |
| 			: UNIT_MAKE_ERROR;
 | |
| 	set_blocking (socket_fd, false);
 | |
| 
 | |
| 	struct sockaddr_in addr;
 | |
| 	addr.sin_family = AF_INET;
 | |
| 	addr.sin_addr.s_addr = htonl (ip);
 | |
| 	addr.sin_port = htons (port);
 | |
| 
 | |
| 	bool connected;
 | |
| 	if (!connect (socket_fd, (struct sockaddr *) &addr, sizeof addr))
 | |
| 		connected = true;
 | |
| 	else if (errno == EINPROGRESS)
 | |
| 		connected = false;
 | |
| 	else
 | |
| 	{
 | |
| 		xclose (socket_fd);
 | |
| 		return errno == EADDRNOTAVAIL
 | |
| 			? UNIT_MAKE_TRY_AGAIN
 | |
| 			: UNIT_MAKE_ERROR;
 | |
| 	}
 | |
| 
 | |
| 	struct unit *u = unit_new (target, socket_fd, port, service, transport);
 | |
| 	if (connected)
 | |
| 		unit_start_scan (u);
 | |
| 	else
 | |
| 	{
 | |
| 		poller_timer_set (&u->timeout_event, ctx->connect_timeout * 1000);
 | |
| 		poller_fd_set (&u->fd_event, POLLOUT);
 | |
| 	}
 | |
| 
 | |
| 	return UNIT_MAKE_OK;
 | |
| }
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| static void
 | |
| try_finish_quit (struct app_context *ctx)
 | |
| {
 | |
| 	if (!ctx->running_targets && !ctx->generator.current_target)
 | |
| 		ctx->polling = false;
 | |
| }
 | |
| 
 | |
| static void
 | |
| initiate_quit (struct app_context *ctx)
 | |
| {
 | |
| 	ctx->quitting = true;
 | |
| 	indicator_set_status (&ctx->indicator, xstrdup ("Quitting"));
 | |
| 
 | |
| 	// Abort all running units
 | |
| 	struct target *t_iter, *t_next;
 | |
| 	for (t_iter = ctx->running_targets; t_iter; t_iter = t_next)
 | |
| 	{
 | |
| 		t_next = t_iter->next;
 | |
| 
 | |
| 		struct unit *u_iter, *u_next;
 | |
| 		for (u_iter = t_iter->running_units; u_iter; u_iter = u_next)
 | |
| 		{
 | |
| 			u_next = u_iter->next;
 | |
| 			unit_stop (u_iter);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Let the current target die
 | |
| 	target_unref (ctx->generator.current_target);
 | |
| 	ctx->generator.current_target = NULL;
 | |
| 
 | |
| 	try_finish_quit (ctx);
 | |
| }
 | |
| 
 | |
| // --- Plugins -----------------------------------------------------------------
 | |
| 
 | |
| static void
 | |
| plugin_api_register_service (void *app_context, struct service *info)
 | |
| {
 | |
| 	struct app_context *ctx = app_context;
 | |
| 	if (str_map_find (&ctx->services, info->name))
 | |
| 		print_error ("attempt to re-register duplicate service `%s'",
 | |
| 			info->name);
 | |
| 	else
 | |
| 		str_map_set (&ctx->services, info->name, info);
 | |
| }
 | |
| 
 | |
| static const char *
 | |
| plugin_api_get_config (void *app_context, const char *key)
 | |
| {
 | |
| 	struct app_context *ctx = app_context;
 | |
| 	return str_map_find (&ctx->config, key);
 | |
| }
 | |
| 
 | |
| static const char *
 | |
| plugin_api_unit_get_address (struct unit *u)
 | |
| {
 | |
| 	return u->target->ip_string;
 | |
| }
 | |
| 
 | |
| static ssize_t
 | |
| plugin_api_unit_write (struct unit *u, const void *buf, size_t len)
 | |
| {
 | |
| 	if (u->stop_requested || u->stopped)
 | |
| 		return -1;
 | |
| 
 | |
| 	str_append_data (&u->write_buffer, buf, len);
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| static void
 | |
| plugin_api_unit_set_success (struct unit *u, bool success)
 | |
| {
 | |
| 	u->success = success;
 | |
| }
 | |
| 
 | |
| static void
 | |
| plugin_api_unit_add_info (struct unit *u, const char *result)
 | |
| {
 | |
| 	strv_append (&u->info, result);
 | |
| }
 | |
| 
 | |
| static void
 | |
| plugin_api_unit_stop (struct unit *u)
 | |
| {
 | |
| 	u->stop_requested = true;
 | |
| }
 | |
| 
 | |
| static struct plugin_api g_plugin_vtable =
 | |
| {
 | |
| 	.register_service  = plugin_api_register_service,
 | |
| 	.get_config        = plugin_api_get_config,
 | |
| 	.unit_get_address  = plugin_api_unit_get_address,
 | |
| 	.unit_write        = plugin_api_unit_write,
 | |
| 	.unit_set_success  = plugin_api_unit_set_success,
 | |
| 	.unit_add_info     = plugin_api_unit_add_info,
 | |
| 	.unit_stop         = plugin_api_unit_stop
 | |
| };
 | |
| 
 | |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| 
 | |
| static bool
 | |
| load_one_plugin (struct app_context *ctx, const char *name, const char *path)
 | |
| {
 | |
| 	void *table = dlopen (path, RTLD_LAZY | RTLD_LOCAL);
 | |
| 	if (!table)
 | |
| 	{
 | |
| 		print_debug ("%s", path);
 | |
| 		print_error ("could not load `%s': %s", name, dlerror ());
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	struct plugin_info *info = dlsym (table, "ponymap_plugin_info");
 | |
| 	if (!info)
 | |
| 		print_error ("could not load `%s': %s",
 | |
| 			name, "cannot find plugin info");
 | |
| 	else if (info->api_version != API_VERSION)
 | |
| 		print_error ("could not load `%s': %s",
 | |
| 			name, "incompatible API version");
 | |
| 	else if (!info->initialize (ctx, &g_plugin_vtable))
 | |
| 		print_error ("could not load `%s': %s",
 | |
| 			name, "plugin initialization failed");
 | |
| 	else
 | |
| 		return true;
 | |
| 
 | |
| 	dlclose (table);
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| load_plugins (struct app_context *ctx)
 | |
| {
 | |
| 	const char *plugin_dir = str_map_find (&ctx->config, "plugin_dir");
 | |
| 	if (!plugin_dir)
 | |
| 	{
 | |
| 		print_fatal ("no plugin directory defined");
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	DIR *dir = opendir (plugin_dir);
 | |
| 	if (!dir)
 | |
| 	{
 | |
| 		print_debug ("%s", plugin_dir);
 | |
| 		print_fatal ("%s: %s",
 | |
| 			"cannot open plugin directory", strerror (errno));
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	bool success = false;
 | |
| 	struct dirent *iter;
 | |
| 	while ((errno = 0, iter = readdir (dir)))
 | |
| 	{
 | |
| 		char *dot = strrchr (iter->d_name, '.');
 | |
| 		if (!dot || strcmp (dot, ".so"))
 | |
| 			continue;
 | |
| 
 | |
| 		char *path = xstrdup_printf ("%s/%s", plugin_dir, iter->d_name);
 | |
| 		(void) load_one_plugin (ctx, iter->d_name, path);
 | |
| 		free (path);
 | |
| 	}
 | |
| 	if (errno)
 | |
| 		print_fatal ("%s: %s", "readdir", strerror (errno));
 | |
| 	else
 | |
| 		success = true;
 | |
| 
 | |
| 	closedir (dir);
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| // --- Plain transport ---------------------------------------------------------
 | |
| 
 | |
| static enum socket_io_result
 | |
| transport_plain_on_readable (struct unit *u)
 | |
| {
 | |
| 	enum socket_io_result result =
 | |
| 		socket_io_try_read (u->socket_fd, &u->read_buffer);
 | |
| 	if (result == SOCKET_IO_ERROR)
 | |
| 		print_debug ("%s: %s: %s", __func__, "recv", strerror (errno));
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| static enum socket_io_result
 | |
| transport_plain_on_writeable (struct unit *u)
 | |
| {
 | |
| 	enum socket_io_result result =
 | |
| 		socket_io_try_write (u->socket_fd, &u->write_buffer);
 | |
| 	if (result == SOCKET_IO_ERROR)
 | |
| 		print_debug ("%s: %s: %s", __func__, "send", strerror (errno));
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| static int
 | |
| transport_plain_get_poll_events (struct unit *u)
 | |
| {
 | |
| 	if (u->write_buffer.len)
 | |
| 		return POLLIN | POLLOUT;
 | |
| 	return POLLIN;
 | |
| }
 | |
| 
 | |
| static struct transport g_transport_plain =
 | |
| {
 | |
| 	.name             = "plain",
 | |
| 	.on_readable      = transport_plain_on_readable,
 | |
| 	.on_writeable     = transport_plain_on_writeable,
 | |
| 	.get_poll_events  = transport_plain_get_poll_events,
 | |
| };
 | |
| 
 | |
| // --- SSL/TLS transport -------------------------------------------------------
 | |
| 
 | |
| struct transport_tls_data
 | |
| {
 | |
| 	SSL *ssl;                           ///< SSL/TLS connection
 | |
| 	bool ssl_rx_want_tx;                ///< SSL_read() wants to write
 | |
| 	bool ssl_tx_want_rx;                ///< SSL_write() wants to read
 | |
| };
 | |
| 
 | |
| static bool
 | |
| transport_tls_init (struct unit *u)
 | |
| {
 | |
| 	SSL *ssl = SSL_new (u->target->ctx->ssl_ctx);
 | |
| 	if (!ssl || !SSL_set_fd (ssl, u->socket_fd))
 | |
| 	{
 | |
| 		const char *error_info = ERR_error_string (ERR_get_error (), NULL);
 | |
| 		print_debug ("%s: %s",
 | |
| 			"could not initialize SSL/TLS connection", error_info);
 | |
| 		SSL_free (ssl);
 | |
| 		return false;
 | |
| 	}
 | |
| 	SSL_set_connect_state (ssl);
 | |
| 
 | |
| 	struct transport_tls_data *data = xcalloc (1, sizeof *data);
 | |
| 	data->ssl = ssl;
 | |
| 	// XXX: maybe set `ssl_rx_want_tx' to force a handshake?
 | |
| 	u->transport_data = data;
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static void
 | |
| transport_tls_add_certificate_info (struct unit *u, X509 *cert)
 | |
| {
 | |
| 	char *subject = X509_NAME_oneline (X509_get_subject_name (cert), NULL, 0);
 | |
| 	char *issuer  = X509_NAME_oneline (X509_get_issuer_name  (cert), NULL, 0);
 | |
| 
 | |
| 	strv_append_owned (&u->info, xstrdup_printf ("%s: %s",
 | |
| 		"certificate subject", subject));
 | |
| 	strv_append_owned (&u->info, xstrdup_printf ("%s: %s",
 | |
| 		"certificate issuer", issuer));
 | |
| 
 | |
| 	free (subject);
 | |
| 	free (issuer);
 | |
| }
 | |
| 
 | |
| static void
 | |
| transport_tls_cleanup (struct unit *u)
 | |
| {
 | |
| 	struct transport_tls_data *data = u->transport_data;
 | |
| 	if (u->success)
 | |
| 	{
 | |
| 		X509 *cert = SSL_get_peer_certificate (data->ssl);
 | |
| 		if (cert)
 | |
| 		{
 | |
| 			transport_tls_add_certificate_info (u, cert);
 | |
| 			X509_free (cert);
 | |
| 		}
 | |
| 	}
 | |
| 	SSL_free (data->ssl);
 | |
| 	free (data);
 | |
| }
 | |
| 
 | |
| static enum socket_io_result
 | |
| transport_tls_on_readable (struct unit *u)
 | |
| {
 | |
| 	struct transport_tls_data *data = u->transport_data;
 | |
| 	if (data->ssl_tx_want_rx)
 | |
| 		return SOCKET_IO_OK;
 | |
| 
 | |
| 	struct str *buf = &u->read_buffer;
 | |
| 	data->ssl_rx_want_tx = false;
 | |
| 	while (true)
 | |
| 	{
 | |
| 		str_reserve (buf, 4096);
 | |
| 		int n_read = SSL_read (data->ssl, buf->str + buf->len,
 | |
| 			buf->alloc - buf->len - 1 /* null byte */);
 | |
| 
 | |
| 		const char *error_info = NULL;
 | |
| 		switch (xssl_get_error (data->ssl, n_read, &error_info))
 | |
| 		{
 | |
| 		case SSL_ERROR_NONE:
 | |
| 			buf->str[buf->len += n_read] = '\0';
 | |
| 			continue;
 | |
| 		case SSL_ERROR_ZERO_RETURN:
 | |
| 			return SOCKET_IO_EOF;
 | |
| 		case SSL_ERROR_WANT_READ:
 | |
| 			return SOCKET_IO_OK;
 | |
| 		case SSL_ERROR_WANT_WRITE:
 | |
| 			data->ssl_rx_want_tx = true;
 | |
| 			return SOCKET_IO_OK;
 | |
| 		case XSSL_ERROR_TRY_AGAIN:
 | |
| 			continue;
 | |
| 		default:
 | |
| 			print_debug ("%s: %s: %s", __func__, "SSL_read", error_info);
 | |
| 			return SOCKET_IO_ERROR;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static enum socket_io_result
 | |
| transport_tls_on_writeable (struct unit *u)
 | |
| {
 | |
| 	struct transport_tls_data *data = u->transport_data;
 | |
| 	if (data->ssl_rx_want_tx)
 | |
| 		return SOCKET_IO_OK;
 | |
| 
 | |
| 	struct str *buf = &u->write_buffer;
 | |
| 	data->ssl_tx_want_rx = false;
 | |
| 	while (buf->len)
 | |
| 	{
 | |
| 		int n_written = SSL_write (data->ssl, buf->str, buf->len);
 | |
| 
 | |
| 		const char *error_info = NULL;
 | |
| 		switch (xssl_get_error (data->ssl, n_written, &error_info))
 | |
| 		{
 | |
| 		case SSL_ERROR_NONE:
 | |
| 			str_remove_slice (buf, 0, n_written);
 | |
| 			continue;
 | |
| 		case SSL_ERROR_ZERO_RETURN:
 | |
| 			return SOCKET_IO_EOF;
 | |
| 		case SSL_ERROR_WANT_WRITE:
 | |
| 			return SOCKET_IO_OK;
 | |
| 		case SSL_ERROR_WANT_READ:
 | |
| 			data->ssl_tx_want_rx = true;
 | |
| 			return SOCKET_IO_OK;
 | |
| 		case XSSL_ERROR_TRY_AGAIN:
 | |
| 			continue;
 | |
| 		default:
 | |
| 			print_debug ("%s: %s: %s", __func__, "SSL_write", error_info);
 | |
| 			return SOCKET_IO_ERROR;
 | |
| 		}
 | |
| 	}
 | |
| 	return SOCKET_IO_OK;
 | |
| }
 | |
| 
 | |
| static int
 | |
| transport_tls_get_poll_events (struct unit *u)
 | |
| {
 | |
| 	struct transport_tls_data *data = u->transport_data;
 | |
| 
 | |
| 	int events = POLLIN;
 | |
| 	if (u->write_buffer.len || data->ssl_rx_want_tx)
 | |
| 		events |= POLLOUT;
 | |
| 
 | |
| 	// While we're waiting for an opposite event, we ignore the original
 | |
| 	if (data->ssl_rx_want_tx)  events &= ~POLLIN;
 | |
| 	if (data->ssl_tx_want_rx)  events &= ~POLLOUT;
 | |
| 	return events;
 | |
| }
 | |
| 
 | |
| static struct transport g_transport_tls =
 | |
| {
 | |
| 	.name             = "SSL/TLS",
 | |
| 	.init             = transport_tls_init,
 | |
| 	.cleanup          = transport_tls_cleanup,
 | |
| 	.on_readable      = transport_tls_on_readable,
 | |
| 	.on_writeable     = transport_tls_on_writeable,
 | |
| 	.get_poll_events  = transport_tls_get_poll_events,
 | |
| };
 | |
| 
 | |
| static void
 | |
| initialize_tls (struct app_context *ctx)
 | |
| {
 | |
| 	SSL_CTX *ssl_ctx = SSL_CTX_new (SSLv23_client_method ());
 | |
| 	if (!ctx)
 | |
| 	{
 | |
| 		const char *error_info = ERR_error_string (ERR_get_error (), NULL);
 | |
| 		print_error ("%s: %s", "could not initialize SSL/TLS", error_info);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// Fuck off, we're just scanning
 | |
| 	SSL_CTX_set_verify (ssl_ctx, SSL_VERIFY_NONE, NULL);
 | |
| 	SSL_CTX_set_mode (ssl_ctx,
 | |
| 		SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
 | |
| 
 | |
| 	ctx->ssl_ctx = ssl_ctx;
 | |
| 	LIST_PREPEND (ctx->transports, &g_transport_tls);
 | |
| }
 | |
| 
 | |
| // --- Tree printer ------------------------------------------------------------
 | |
| 
 | |
| struct node
 | |
| {
 | |
| 	struct node *next;                  ///< The next sibling in order
 | |
| 	struct node *children;              ///< Children of this node
 | |
| 	char *text;                         ///< Text of this node
 | |
| 	bool bold;                          ///< Whether to print in bold font
 | |
| };
 | |
| 
 | |
| static struct node *
 | |
| node_new (char *text)
 | |
| {
 | |
| 	struct node *self = xcalloc (1, sizeof *self);
 | |
| 	self->text = text;
 | |
| 	return self;
 | |
| }
 | |
| 
 | |
| static void
 | |
| node_delete (struct node *self)
 | |
| {
 | |
| 	LIST_FOR_EACH (struct node, iter, self->children)
 | |
| 		node_delete (iter);
 | |
| 	free (self->text);
 | |
| 	free (self);
 | |
| }
 | |
| 
 | |
| struct node_print_level
 | |
| {
 | |
| 	struct node_print_level *next;      ///< Next print level
 | |
| 	const char *start;                  ///< Starting indentation
 | |
| 	const char *continuation;           ///< Continuation
 | |
| 	bool started;                       ///< Printed starting indentation
 | |
| };
 | |
| 
 | |
| struct node_print_data
 | |
| {
 | |
| 	struct node_print_level *head;      ///< The first level
 | |
| 	struct node_print_level **tail;     ///< Where to place further levels
 | |
| };
 | |
| 
 | |
| static int node_escape (int c) { return (c >= 32 && c < 127) ? c : '.'; }
 | |
| 
 | |
| static void
 | |
| node_print_tree_level (struct node *self, struct node_print_data *data)
 | |
| {
 | |
| 	struct str indent = str_make ();
 | |
| 	for (struct node_print_level *iter = data->head; iter; iter = iter->next)
 | |
| 	{
 | |
| 		bool started = iter->started;
 | |
| 		iter->started = true;
 | |
| 		str_append (&indent, started ? iter->continuation : iter->start);
 | |
| 	}
 | |
| 
 | |
| 	fputs (indent.str, stdout);
 | |
| 	str_free (&indent);
 | |
| 
 | |
| 	char *escaped = xstrdup (self->text);
 | |
| 	cstr_transform (escaped, node_escape);
 | |
| 	if (self->bold)
 | |
| 		print_bold (stdout, escaped);
 | |
| 	else
 | |
| 		fputs (escaped, stdout);
 | |
| 	fputc ('\n', stdout);
 | |
| 	free (escaped);
 | |
| 
 | |
| 	struct node_print_level level;
 | |
| 	level.next = NULL;
 | |
| 	level.start = " |- ";
 | |
| 	level.continuation = " |  ";
 | |
| 	level.started = false;
 | |
| 
 | |
| 	struct node_print_level **prev_tail = data->tail;
 | |
| 	*data->tail = &level;
 | |
| 	data->tail = &level.next;
 | |
| 
 | |
| 	for (struct node *iter = self->children; iter; iter = iter->next)
 | |
| 	{
 | |
| 		if (!iter->next)
 | |
| 		{
 | |
| 			level.start = " '- ";
 | |
| 			level.continuation = "    ";
 | |
| 		}
 | |
| 		level.started = false;
 | |
| 		node_print_tree_level (iter, data);
 | |
| 	}
 | |
| 
 | |
| 	data->tail = prev_tail;
 | |
| 	*data->tail = NULL;
 | |
| }
 | |
| 
 | |
| static void
 | |
| node_print_tree (struct node *self)
 | |
| {
 | |
| 	struct node_print_data data;
 | |
| 	data.head = NULL;
 | |
| 	data.tail = &data.head;
 | |
| 
 | |
| 	node_print_tree_level (self, &data);
 | |
| }
 | |
| 
 | |
| // --- Job generation and result aggregation -----------------------------------
 | |
| 
 | |
| struct target_dump_data
 | |
| {
 | |
| 	struct unit **results;              ///< Results sorted by service
 | |
| 	size_t results_len;                 ///< Number of results
 | |
| 	uint32_t *undetermined;             ///< Bitmap of undetermined services
 | |
| };
 | |
| 
 | |
| 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 (self->ip_string));
 | |
| 	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;
 | |
| 	struct transport *last_transport = 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 || u->transport != last_transport)
 | |
| 		{
 | |
| 			service = json_object ();
 | |
| 			ports = json_array ();
 | |
| 
 | |
| 			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;
 | |
| 			last_transport = u->transport;
 | |
| 		}
 | |
| 
 | |
| 		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]));
 | |
| 	}
 | |
| 
 | |
| 	json_t *undetermined = json_array ();
 | |
| 	json_object_set_new (o, "undetermined", undetermined);
 | |
| 
 | |
| 	if (!data->undetermined)
 | |
| 		return;
 | |
| 
 | |
| 	size_t block = 8 * sizeof *data->undetermined;
 | |
| 	for (size_t i = 0; i < 65536 / block; i++)
 | |
| 	for (size_t k = 0; k < block; k++)
 | |
| 	{
 | |
| 		if (!(data->undetermined[i] & (1 << k)))
 | |
| 			continue;
 | |
| 
 | |
| 		service = json_object ();
 | |
| 		json_object_set_new (service, "port", json_integer (i * block + k));
 | |
| 		json_array_append_new (undetermined, service);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void
 | |
| target_dump_terminal_undetermined (uint16_t port_no, struct node ***tail)
 | |
| {
 | |
| 	struct servent *serv = getservbyport (ntohs (port_no), "tcp");
 | |
| 	struct node *port = **tail = node_new (serv
 | |
| 		? xstrdup_printf ("port %" PRIu16 " (%s)",
 | |
| 			port_no, serv->s_name)
 | |
| 		: xstrdup_printf ("port %" PRIu16, port_no));
 | |
| 	*tail = &port->next;
 | |
| }
 | |
| 
 | |
| static void
 | |
| target_dump_terminal (struct target *self, struct target_dump_data *data)
 | |
| {
 | |
| 	indicator_hide (&self->ctx->indicator);
 | |
| 
 | |
| 	struct str tmp = str_make ();
 | |
| 	str_append (&tmp, self->ip_string);
 | |
| 	if (self->hostname)
 | |
| 		str_append_printf (&tmp, " (%s)", self->hostname);
 | |
| 	if (self->ctx->quitting)
 | |
| 		str_append_printf (&tmp, " (%s)", "partial");
 | |
| 
 | |
| 	struct node *root = node_new (str_steal (&tmp));
 | |
| 	root->bold = true;
 | |
| 
 | |
| 	struct service *last_service = NULL;
 | |
| 	struct transport *last_transport = NULL;
 | |
| 	struct node *service, **s_tail = &root->children, *port, **p_tail;
 | |
| 	for (size_t i = 0; i < data->results_len; i++)
 | |
| 	{
 | |
| 		struct unit *u = data->results[i];
 | |
| 		if (u->service != last_service || u->transport != last_transport)
 | |
| 		{
 | |
| 			*s_tail = service = node_new (xstrdup_printf ("%s (%s)",
 | |
| 				u->service->name, u->transport->name));
 | |
| 			s_tail = &service->next;
 | |
| 			p_tail = &service->children;
 | |
| 
 | |
| 			last_service = u->service;
 | |
| 			last_transport = u->transport;
 | |
| 		}
 | |
| 
 | |
| 		port = *p_tail = node_new (xstrdup_printf ("port %" PRIu16, u->port));
 | |
| 		p_tail = &port->next;
 | |
| 
 | |
| 		struct node *info, **i_tail = &port->children;
 | |
| 		for (size_t k = 0; k < u->info.len; k++)
 | |
| 		{
 | |
| 			info = *i_tail = node_new (xstrdup (u->info.vector[k]));
 | |
| 			i_tail = &info->next;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (data->undetermined)
 | |
| 	{
 | |
| 		*s_tail = service = node_new (xstrdup ("undetermined"));
 | |
| 		p_tail = &service->children;
 | |
| 
 | |
| 		size_t block = 8 * sizeof *data->undetermined;
 | |
| 		for (size_t i = 0; i < 65536 / block; i++)
 | |
| 		for (size_t k = 0; k < block; k++)
 | |
| 			if ((data->undetermined[i] & (1 << k)))
 | |
| 				target_dump_terminal_undetermined (i * block + k, &p_tail);
 | |
| 	}
 | |
| 
 | |
| 	node_print_tree (root);
 | |
| 	node_delete (root);
 | |
| 	putchar ('\n');
 | |
| 
 | |
| 	indicator_show (&self->ctx->indicator);
 | |
| }
 | |
| 
 | |
| static int
 | |
| unit_cmp_by_order (const void *ax, const void *bx)
 | |
| {
 | |
| 	const struct unit **ay = (void *) ax, **by = (void *) bx;
 | |
| 	const struct unit *a = *ay, *b = *by;
 | |
| 	int x = strcmp (a->service->name, b->service->name);
 | |
| 	if (!x) x = strcmp (a->transport->name, b->transport->name);
 | |
| 	if (!x) x = (int) a->port - (int) b->port;
 | |
| 	return x;
 | |
| }
 | |
| 
 | |
| static void
 | |
| target_dump_results (struct target *self)
 | |
| {
 | |
| 	struct app_context *ctx = self->ctx;
 | |
| 	struct target_dump_data data;
 | |
| 
 | |
| 	// Equals { successfully connected } \ { any service detected }
 | |
| 	static uint32_t undetermined[65536 / 32];
 | |
| 	memset (undetermined, 0, sizeof undetermined);
 | |
| 
 | |
| 	size_t len = 0;
 | |
| 	for (struct unit *iter = self->results; iter; iter = iter->next)
 | |
| 	{
 | |
| 		if (iter->success)
 | |
| 			len++;
 | |
| 		else
 | |
| 			undetermined[iter->port / 32] |=   1 << (iter->port % 32);
 | |
| 	}
 | |
| 
 | |
| 	struct unit *sorted[len];
 | |
| 	data.results = sorted;
 | |
| 	data.results_len = len;
 | |
| 
 | |
| 	for (struct unit *iter = self->results; iter; iter = iter->next)
 | |
| 		if (iter->success)
 | |
| 		{
 | |
| 			sorted[--len] = iter;
 | |
| 			undetermined[iter->port / 32] &= ~(1 << (iter->port % 32));
 | |
| 		}
 | |
| 
 | |
| 	// Sort them by service name so that they can be grouped
 | |
| 	qsort (sorted, N_ELEMENTS (sorted), sizeof *sorted, unit_cmp_by_order);
 | |
| 
 | |
| 	// Only set the field if there were any undetermined services at all
 | |
| 	data.undetermined = NULL;
 | |
| 	for (size_t i = 0; i < N_ELEMENTS (undetermined); i++)
 | |
| 		if (undetermined[i])
 | |
| 		{
 | |
| 			data.undetermined = undetermined;
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 	if (ctx->json_results)
 | |
| 		target_dump_json (self, &data);
 | |
| 	target_dump_terminal (self, &data);
 | |
| }
 | |
| 
 | |
| static void
 | |
| target_update_indicator (struct target *self)
 | |
| {
 | |
| 	char *status = xstrdup_printf ("Scanning %s", self->ip_string);
 | |
| 	struct indicator *indicator = &self->ctx->indicator;
 | |
| 	if (!indicator->status || strcmp (status, indicator->status))
 | |
| 		indicator_set_status (&self->ctx->indicator, status);
 | |
| 	else
 | |
| 		free (status);
 | |
| 	indicator_show (&self->ctx->indicator);
 | |
| }
 | |
| 
 | |
| static struct target *
 | |
| target_ref (struct target *self)
 | |
| {
 | |
| 	self->ref_count++;
 | |
| 	return self;
 | |
| }
 | |
| 
 | |
| static void
 | |
| target_unref (struct target *self)
 | |
| {
 | |
| 	if (!self || --self->ref_count)
 | |
| 		return;
 | |
| 
 | |
| 	if (self->results)
 | |
| 		target_dump_results (self);
 | |
| 
 | |
| 	// These must have been stopped already (although we could do that in here)
 | |
| 	hard_assert (!self->running_units);
 | |
| 
 | |
| 	struct unit *iter, *next;
 | |
| 	for (iter = self->results; iter; iter = next)
 | |
| 	{
 | |
| 		next = iter->next;
 | |
| 		unit_unref (iter);
 | |
| 	}
 | |
| 
 | |
| 	struct app_context *ctx = self->ctx;
 | |
| 	LIST_UNLINK_WITH_TAIL (ctx->running_targets, ctx->running_tail, self);
 | |
| 	if (!ctx->running_targets)
 | |
| 		indicator_hide (&ctx->indicator);
 | |
| 	else if (!ctx->quitting && ctx->running_targets)
 | |
| 		target_update_indicator (ctx->running_targets);
 | |
| 
 | |
| 	free (self->hostname);
 | |
| 	free (self);
 | |
| 
 | |
| 	try_finish_quit (ctx);
 | |
| }
 | |
| 
 | |
| static void
 | |
| generator_make_target (struct app_context *ctx)
 | |
| {
 | |
| 	hard_assert (!ctx->quitting);
 | |
| 	struct generator *g = &ctx->generator;
 | |
| 
 | |
| 	struct target *target = xcalloc (1, sizeof *target);
 | |
| 	hard_assert (g->current_target == NULL);
 | |
| 	g->current_target = target;
 | |
| 
 | |
| 	target->ref_count = 1;
 | |
| 	target->ctx = ctx;
 | |
| 	target->ip = g->ip_iter;
 | |
| 
 | |
| 	uint32_t address = htonl (target->ip);
 | |
| 	if (!inet_ntop (AF_INET, &address,
 | |
| 		target->ip_string, sizeof target->ip_string))
 | |
| 	{
 | |
| 		print_error ("%s: %s", "inet_ntop", strerror (errno));
 | |
| 		*target->ip_string = '\0';
 | |
| 	}
 | |
| 
 | |
| 	if (g->ip_iter == g->ip_range_iter->original_address
 | |
| 	 && strcmp (target->ip_string, g->ip_range_iter->original_name))
 | |
| 		target->hostname = xstrdup (g->ip_range_iter->original_name);
 | |
| 
 | |
| 	LIST_APPEND_WITH_TAIL (ctx->running_targets, ctx->running_tail, target);
 | |
| 	target_update_indicator (ctx->running_targets);
 | |
| 
 | |
| 	ctx->stats_hosts++;
 | |
| }
 | |
| 
 | |
| static void
 | |
| generator_init (struct app_context *ctx)
 | |
| {
 | |
| 	struct generator *g = &ctx->generator;
 | |
| 
 | |
| 	g->ip_range_iter = ctx->ip_list;
 | |
| 	g->ip_iter = g->ip_range_iter->start;
 | |
| 	g->current_target = NULL;
 | |
| 	generator_make_target (ctx);
 | |
| 
 | |
| 	g->port_range_iter = ctx->port_list;
 | |
| 	g->port_iter = g->port_range_iter->start;
 | |
| 
 | |
| 	g->svc_iter = str_map_iter_make (&ctx->svc_list);
 | |
| 	g->svc = str_map_iter_next (&g->svc_iter);
 | |
| 
 | |
| 	g->transport_iter = ctx->transports;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| generator_step (struct app_context *ctx)
 | |
| {
 | |
| 	struct generator *g = &ctx->generator;
 | |
| 
 | |
| 	if (ctx->quitting || !g->ip_range_iter)
 | |
| 		return false;
 | |
| 	if (!g->current_target)
 | |
| 		generator_make_target (ctx);
 | |
| 
 | |
| 	switch (unit_make (g->current_target,
 | |
| 		g->ip_iter, g->port_iter, g->svc, g->transport_iter))
 | |
| 	{
 | |
| 	case UNIT_MAKE_OK:
 | |
| 	case UNIT_MAKE_ERROR:
 | |
| 		break;
 | |
| 	case UNIT_MAKE_TRY_AGAIN:
 | |
| 		// TODO: set a timer for a few seconds, we might eventually get lucky
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	// Try to find the next available transport
 | |
| 	while (true)
 | |
| 	{
 | |
| 		if (!(g->transport_iter = g->transport_iter->next))
 | |
| 			break;
 | |
| 		if (g->transport_iter == &g_transport_tls
 | |
| 		 && !(g->svc->flags & SERVICE_SUPPORTS_TLS))
 | |
| 			continue;
 | |
| 		return true;
 | |
| 	}
 | |
| 	g->transport_iter = ctx->transports;
 | |
| 
 | |
| 	// Try to find the next service to scan for
 | |
| 	if ((g->svc = str_map_iter_next (&g->svc_iter)))
 | |
| 		return true;
 | |
| 	g->svc_iter = str_map_iter_make (&ctx->svc_list);
 | |
| 	g->svc = str_map_iter_next (&g->svc_iter);
 | |
| 
 | |
| 	// Try to find the next port to scan
 | |
| 	if (g->port_iter != UINT16_MAX && g->port_iter < g->port_range_iter->end)
 | |
| 	{
 | |
| 		g->port_iter++;
 | |
| 		return true;
 | |
| 	}
 | |
| 	g->port_range_iter = g->port_range_iter->next;
 | |
| 	if (g->port_range_iter)
 | |
| 	{
 | |
| 		g->port_iter = g->port_range_iter->start;
 | |
| 		return true;
 | |
| 	}
 | |
| 	g->port_range_iter = ctx->port_list;
 | |
| 	g->port_iter = g->port_range_iter->start;
 | |
| 
 | |
| 	// Moving on to the next target
 | |
| 	target_unref (g->current_target);
 | |
| 	g->current_target = NULL;
 | |
| 
 | |
| 	// Try to find the next IP to scan
 | |
| 	if (g->ip_iter != UINT32_MAX && g->ip_iter < g->ip_range_iter->end)
 | |
| 	{
 | |
| 		g->ip_iter++;
 | |
| 		return true;
 | |
| 	}
 | |
| 	g->ip_range_iter = g->ip_range_iter->next;
 | |
| 	if (g->ip_range_iter)
 | |
| 	{
 | |
| 		g->ip_iter = g->ip_range_iter->start;
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	// No more jobs to be created
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_generator_step_requested (struct app_context *ctx)
 | |
| {
 | |
| 	poller_idle_reset (&ctx->step_event);
 | |
| 	while (generator_step (ctx))
 | |
| 		;
 | |
| }
 | |
| 
 | |
| // --- Signals -----------------------------------------------------------------
 | |
| 
 | |
| static int g_signal_pipe[2];            ///< A pipe used to signal... signals
 | |
| 
 | |
| /// Program termination has been requested by a signal
 | |
| static volatile sig_atomic_t g_termination_requested;
 | |
| 
 | |
| static void
 | |
| sigterm_handler (int signum)
 | |
| {
 | |
| 	(void) signum;
 | |
| 
 | |
| 	g_termination_requested = true;
 | |
| 
 | |
| 	int original_errno = errno;
 | |
| 	if (write (g_signal_pipe[1], "t", 1) == -1)
 | |
| 		soft_assert (errno == EAGAIN);
 | |
| 	errno = original_errno;
 | |
| }
 | |
| 
 | |
| static void
 | |
| setup_signal_handlers (void)
 | |
| {
 | |
| 	if (pipe (g_signal_pipe) == -1)
 | |
| 		exit_fatal ("%s: %s", "pipe", strerror (errno));
 | |
| 
 | |
| 	set_cloexec (g_signal_pipe[0]);
 | |
| 	set_cloexec (g_signal_pipe[1]);
 | |
| 
 | |
| 	// So that the pipe cannot overflow; it would make write() block within
 | |
| 	// the signal handler, which is something we really don't want to happen.
 | |
| 	// The same holds true for read().
 | |
| 	set_blocking (g_signal_pipe[0], false);
 | |
| 	set_blocking (g_signal_pipe[1], false);
 | |
| 
 | |
| 	signal (SIGPIPE, SIG_IGN);
 | |
| 
 | |
| 	struct sigaction sa;
 | |
| 	sa.sa_flags = SA_RESTART;
 | |
| 	sigemptyset (&sa.sa_mask);
 | |
| 	sa.sa_handler = sigterm_handler;
 | |
| 	if (sigaction (SIGINT, &sa, NULL) == -1
 | |
| 	 || sigaction (SIGTERM, &sa, NULL) == -1)
 | |
| 		exit_fatal ("sigaction: %s", strerror (errno));
 | |
| }
 | |
| 
 | |
| // --- Logging -----------------------------------------------------------------
 | |
| 
 | |
| // The indicator can get in the way of printing information messages
 | |
| 
 | |
| static struct app_context *g_ctx;       ///< Application context
 | |
| 
 | |
| static void
 | |
| log_message_wrapper (void *user_data, const char *quote, const char *fmt,
 | |
| 	va_list ap)
 | |
| {
 | |
| 	bool hide_indicator = g_ctx->indicator.shown;
 | |
| 	if (hide_indicator)
 | |
| 		indicator_hide (&g_ctx->indicator);
 | |
| 
 | |
| 	log_message_stdio (user_data, quote, fmt, ap);
 | |
| 
 | |
| 	if (hide_indicator)
 | |
| 		indicator_show (&g_ctx->indicator);
 | |
| }
 | |
| 
 | |
| // --- Main program ------------------------------------------------------------
 | |
| 
 | |
| typedef bool (*list_foreach_fn) (void *, const char *);
 | |
| 
 | |
| static bool
 | |
| list_foreach (const char *list, list_foreach_fn callback, void *user_data)
 | |
| {
 | |
| 	struct strv items = strv_make ();
 | |
| 
 | |
| 	bool success = false;
 | |
| 	cstr_split (list, ",", true, &items);
 | |
| 	for (size_t i = 0; i < items.len; i++)
 | |
| 		if (!callback (user_data, cstr_strip_in_place (items.vector[i], " ")))
 | |
| 			goto fail;
 | |
| 
 | |
| 	success = true;
 | |
| fail:
 | |
| 	strv_free (&items);
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| parse_port (const char *port, uint16_t *out)
 | |
| {
 | |
| 	unsigned long x;
 | |
| 	struct servent *service;
 | |
| 
 | |
| 	if ((service = getservbyname (port, "tcp")))
 | |
| 		*out = ntohs (service->s_port);
 | |
| 	else if (xstrtoul (&x, port, 10) && x <= UINT16_MAX)
 | |
| 		*out = x;
 | |
| 	else
 | |
| 		return false;
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| add_port_range (struct app_context *ctx, const char *range)
 | |
| {
 | |
| 	uint16_t start_port, end_port;
 | |
| 	const char *hyphen = strchr (range, '-');
 | |
| 	if (hyphen)
 | |
| 	{
 | |
| 		char start[hyphen - range + 1];
 | |
| 		memcpy (start, range, sizeof range - 1);
 | |
| 		start[sizeof start - 1] = '\0';
 | |
| 
 | |
| 		const char *end = hyphen + 1;
 | |
| 
 | |
| 		if (!parse_port (start, &start_port)
 | |
| 		 || !parse_port (end, &end_port))
 | |
| 			goto fail;
 | |
| 	}
 | |
| 	else if (!parse_port (range, &start_port))
 | |
| 		goto fail;
 | |
| 	else
 | |
| 		end_port = start_port;
 | |
| 
 | |
| 	if (start_port > end_port)
 | |
| 		goto fail;
 | |
| 
 | |
| 	struct port_range *pr = xcalloc (1, sizeof *pr);
 | |
| 	pr->start = start_port;
 | |
| 	pr->end = end_port;
 | |
| 	LIST_PREPEND (ctx->port_list, pr);
 | |
| 	return true;
 | |
| 
 | |
| fail:
 | |
| 	print_error ("%s: `%s'", "invalid port range", range);
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| add_service (struct app_context *ctx, const char *name)
 | |
| {
 | |
| 	// To be resolved later
 | |
| 	str_map_set (&ctx->svc_list, name, xstrdup (name));
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| add_target (struct app_context *ctx, const char *target)
 | |
| {
 | |
| 	char host[strlen (target) + 1];
 | |
| 	strcpy (host, target);
 | |
| 
 | |
| 	// Parse as the CIDR notation when a slash is found
 | |
| 	unsigned long mask = 32;
 | |
| 	char *slash = strchr (host, '/');
 | |
| 	if (slash)
 | |
| 	{
 | |
| 		*slash++ = '\0';
 | |
| 		if (!xstrtoul (&mask, slash, 10) || mask > 32)
 | |
| 		{
 | |
| 			print_error ("invalid network mask in `%s'", target);
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	struct addrinfo hints = { .ai_family = AF_INET };
 | |
| 	struct addrinfo *result;
 | |
| 	int err = getaddrinfo (host, NULL, &hints, &result);
 | |
| 	if (err)
 | |
| 	{
 | |
| 		print_error ("cannot resolve `%s': %s", host, gai_strerror (err));
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	struct ip_range *range = xcalloc (1, sizeof *range);
 | |
| 	uint32_t bitmask = ((uint64_t) 1 << (32 - mask)) - 1;
 | |
| 
 | |
| 	hard_assert (result->ai_family == AF_INET);
 | |
| 	hard_assert (result->ai_addr->sa_family == AF_INET);
 | |
| 	uint32_t addr = ntohl (((struct sockaddr_in *)
 | |
| 		result->ai_addr)->sin_addr.s_addr);
 | |
| 	range->start = addr & ~bitmask;
 | |
| 	range->end   = addr |  bitmask;
 | |
| 	freeaddrinfo (result);
 | |
| 
 | |
| 	range->original_name = xstrdup (host);
 | |
| 	range->original_address = addr;
 | |
| 
 | |
| 	LIST_PREPEND (ctx->ip_list, range);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| static void
 | |
| merge_port_ranges (struct app_context *ctx)
 | |
| {
 | |
| 	// Make sure that no port is scanned twice
 | |
| 	struct port_range *i1, *i2, *i2_next;
 | |
| 	for (i1 = ctx->port_list; i1; i1 = i1->next)
 | |
| 	for (i2 = ctx->port_list; i2; i2 = i2_next)
 | |
| 	{
 | |
| 		i2_next = i2->next;
 | |
| 		if (i1 == i2 || i1->end < i2->start || i2->end < i1->start)
 | |
| 			continue;
 | |
| 
 | |
| 		i1->start = MIN (i1->start, i2->start);
 | |
| 		i1->end = MAX (i1->end, i2->end);
 | |
| 		LIST_UNLINK (ctx->port_list, i2);
 | |
| 		free (i2);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void
 | |
| merge_ip_ranges (struct app_context *ctx)
 | |
| {
 | |
| 	// Make sure that no IP is scanned twice
 | |
| 	struct ip_range *i1, *i2, *i2_next;
 | |
| 	for (i1 = ctx->ip_list; i1; i1 = i1->next)
 | |
| 	for (i2 = ctx->ip_list; i2; i2 = i2_next)
 | |
| 	{
 | |
| 		i2_next = i2->next;
 | |
| 		if (i1 == i2 || i1->end < i2->start || i2->end < i1->start)
 | |
| 			continue;
 | |
| 
 | |
| 		i1->start = MIN (i1->start, i2->start);
 | |
| 		i1->end = MAX (i1->end, i2->end);
 | |
| 		LIST_UNLINK (ctx->ip_list, i2);
 | |
| 		free (i2);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static bool
 | |
| resolve_service_names (struct app_context *ctx)
 | |
| {
 | |
| 	struct str_map_iter iter = str_map_iter_make (&ctx->svc_list);
 | |
| 	char *name = NULL;
 | |
| 	bool success = true;
 | |
| 	while (free (name), (name = str_map_iter_next (&iter)))
 | |
| 	{
 | |
| 		struct service *service;
 | |
| 		if ((service = str_map_find (&ctx->services, name)))
 | |
| 		{
 | |
| 			str_map_set (&ctx->svc_list, name, service);
 | |
| 			continue;
 | |
| 		}
 | |
| 		print_error ("unknown service `%s'", name);
 | |
| 		success = false;
 | |
| 	}
 | |
| 	return success;
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
 | |
| {
 | |
| 	char *dummy;
 | |
| 	(void) read (fd->fd, &dummy, 1);
 | |
| 
 | |
| 	if (g_termination_requested && !ctx->quitting)
 | |
| 		initiate_quit (ctx);
 | |
| }
 | |
| 
 | |
| static void
 | |
| parse_program_arguments (struct app_context *ctx, int argc, char **argv)
 | |
| {
 | |
| 	static const struct opt opts[] =
 | |
| 	{
 | |
| 		{ 'd', "debug", NULL, 0, "run in debug mode" },
 | |
| 		{ 'h', "help", NULL, 0, "display this help and exit" },
 | |
| 		{ 'V', "version", NULL, 0, "output version information and exit" },
 | |
| 		{ 'p', "ports", "PORTS", 0,
 | |
| 		  "ports/port ranges, separated by commas" },
 | |
| 		{ 's', "service", "SERVICES", 0,
 | |
| 		  "services to scan for, separated by commas" },
 | |
| 		{ 'l', "list-services", NULL, 0, "list all known services" },
 | |
| 		{ 't', "connect-timeout", "TIMEOUT", 0,
 | |
| 		  "timeout for connect, in seconds"
 | |
| 		  " (default: " XSTRINGIFY (DEFAULT_CONNECT_TIMEOUT) ")" },
 | |
| 		{ '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" },
 | |
| 		{ 0, NULL, NULL, 0, NULL }
 | |
| 	};
 | |
| 
 | |
| 	struct opt_handler oh = opt_handler_make (argc, argv, opts,
 | |
| 		"{ ADDRESS [/MASK] }...", "Experimental network scanner.");
 | |
| 
 | |
| 	int c;
 | |
| 	bool need_no_args = false;
 | |
| 	while ((c = opt_handler_get (&oh)) != -1)
 | |
| 	switch (c)
 | |
| 	{
 | |
| 		unsigned long ul;
 | |
| 	case 'd':
 | |
| 		g_debug_mode = true;
 | |
| 		break;
 | |
| 	case 'h':
 | |
| 		opt_handler_usage (&oh, stdout);
 | |
| 		exit (EXIT_SUCCESS);
 | |
| 	case 'V':
 | |
| 		printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
 | |
| 		exit (EXIT_SUCCESS);
 | |
| 	case 'p':
 | |
| 		if (!list_foreach (optarg, (list_foreach_fn) add_port_range, ctx))
 | |
| 			exit (EXIT_FAILURE);
 | |
| 		break;
 | |
| 	case 's':
 | |
| 		if (!list_foreach (optarg, (list_foreach_fn) add_service, ctx))
 | |
| 			exit (EXIT_FAILURE);
 | |
| 		break;
 | |
| 	case 'l':
 | |
| 		ctx->list_services = true;
 | |
| 		need_no_args = true;
 | |
| 		break;
 | |
| 	case 't':
 | |
| 		if (!xstrtoul (&ul, optarg, 10) || !ul)
 | |
| 		{
 | |
| 			print_error ("invalid value for %s", "connect timeout");
 | |
| 			exit (EXIT_FAILURE);
 | |
| 		}
 | |
| 		ctx->connect_timeout = ul;
 | |
| 		break;
 | |
| 	case 'T':
 | |
| 		if (!xstrtoul (&ul, optarg, 10) || !ul)
 | |
| 		{
 | |
| 			print_error ("invalid value for %s", "scan timeout");
 | |
| 			exit (EXIT_FAILURE);
 | |
| 		}
 | |
| 		ctx->scan_timeout = ul;
 | |
| 		break;
 | |
| 	case 'j':
 | |
| 		ctx->json_results = json_array ();
 | |
| 		ctx->json_filename = optarg;
 | |
| 		break;
 | |
| 	case 'w':
 | |
| 		call_simple_config_write_default (optarg, g_config_table);
 | |
| 		exit (EXIT_SUCCESS);
 | |
| 	default:
 | |
| 		print_error ("wrong options");
 | |
| 		opt_handler_usage (&oh, stderr);
 | |
| 		exit (EXIT_FAILURE);
 | |
| 	}
 | |
| 
 | |
| 	argc -= optind;
 | |
| 	argv += optind;
 | |
| 
 | |
| 	if (!need_no_args && !argc)
 | |
| 	{
 | |
| 		opt_handler_usage (&oh, stderr);
 | |
| 		exit (EXIT_FAILURE);
 | |
| 	}
 | |
| 
 | |
| 	for (int i = 0; i < argc; i++)
 | |
| 		if (!add_target (ctx, argv[i]))
 | |
| 			exit (EXIT_FAILURE);
 | |
| 
 | |
| 	opt_handler_free (&oh);
 | |
| }
 | |
| 
 | |
| static void
 | |
| list_services (struct app_context *ctx)
 | |
| {
 | |
| 	struct node *root = node_new (strdup ("Known services"));
 | |
| 	struct node *s, **s_tail = &root->children;
 | |
| 	root->bold = true;
 | |
| 
 | |
| 	struct str_map_iter iter = str_map_iter_make (&ctx->services);
 | |
| 	struct service *service;
 | |
| 	while ((service = str_map_iter_next (&iter)))
 | |
| 	{
 | |
| 		struct str line = str_make ();
 | |
| 		str_append (&line, service->name);
 | |
| 		if (service->flags & SERVICE_SUPPORTS_TLS)
 | |
| 			str_append (&line, " (supports TLS)");
 | |
| 
 | |
| 		*s_tail = s = node_new (str_steal (&line));
 | |
| 		s_tail = &s->next;
 | |
| 	}
 | |
| 
 | |
| 	node_print_tree (root);
 | |
| 	node_delete (root);
 | |
| 	putchar ('\n');
 | |
| }
 | |
| 
 | |
| int
 | |
| main (int argc, char *argv[])
 | |
| {
 | |
| 	struct app_context ctx;
 | |
| 	app_context_init (&ctx);
 | |
| 	parse_program_arguments (&ctx, argc, argv);
 | |
| 
 | |
| 	setup_signal_handlers ();
 | |
| 	srand (time (NULL));
 | |
| 
 | |
| 	// Set the maximum count of file descriptorts to the hard limit
 | |
| 	struct rlimit limit;
 | |
| 	if (getrlimit (RLIMIT_NOFILE, &limit))
 | |
| 		print_warning ("%s: %s", "getrlimit failed", strerror (errno));
 | |
| 	else
 | |
| 	{
 | |
| 		limit.rlim_cur = limit.rlim_max;
 | |
| 
 | |
| 		// But too much may be seriously detrimental to function
 | |
| 		limit.rlim_cur = MIN (limit.rlim_cur, 1 << 16);
 | |
| 
 | |
| 		if (setrlimit (RLIMIT_NOFILE, &limit))
 | |
| 			print_warning ("%s: %s", "setrlimit failed", strerror (errno));
 | |
| 	}
 | |
| 
 | |
| 	init_terminal ();
 | |
| 	atexit (free_terminal);
 | |
| 
 | |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || LIBRESSL_VERSION_NUMBER
 | |
| 	SSL_library_init ();
 | |
| 	atexit (EVP_cleanup);
 | |
| 	SSL_load_error_strings ();
 | |
| 	atexit (ERR_free_strings);
 | |
| #else
 | |
| 	// Cleanup is done automatically via atexit()
 | |
| 	OPENSSL_init_ssl (0, NULL);
 | |
| #endif
 | |
| 
 | |
| 	struct error *e = NULL;
 | |
| 	if (!simple_config_update_from_file (&ctx.config, &e))
 | |
| 	{
 | |
| 		print_error ("error loading configuration: %s", e->message);
 | |
| 		error_free (e);
 | |
| 		exit (EXIT_FAILURE);
 | |
| 	}
 | |
| 
 | |
| 	struct poller_fd signal_event =
 | |
| 		poller_fd_make (&ctx.poller, g_signal_pipe[0]);
 | |
| 	signal_event.dispatcher = (poller_fd_fn) on_signal_pipe_readable;
 | |
| 	signal_event.user_data = &ctx;
 | |
| 	poller_fd_set (&signal_event, POLLIN);
 | |
| 
 | |
| 	if (!load_plugins (&ctx))
 | |
| 		exit (EXIT_FAILURE);
 | |
| 
 | |
| 	if (ctx.list_services)
 | |
| 	{
 | |
| 		list_services (&ctx);
 | |
| 		exit (EXIT_SUCCESS);
 | |
| 	}
 | |
| 
 | |
| 	// TODO: make the order unimportant; this hopes all services support
 | |
| 	//   the plain transport and that it is the first on the list
 | |
| 	initialize_tls (&ctx);
 | |
| 	LIST_PREPEND (ctx.transports, &g_transport_plain);
 | |
| 
 | |
| 	if (!ctx.port_list)
 | |
| 	{
 | |
| 		struct port_range *range = xcalloc (1, sizeof *range);
 | |
| 		range->start = 1;  // port 0 is reserved, not bothering with it
 | |
| 		range->end = 65535;
 | |
| 		ctx.port_list = range;
 | |
| 	}
 | |
| 
 | |
| 	if (!ctx.svc_list.len)
 | |
| 	{
 | |
| 		struct str_map_iter iter = str_map_iter_make (&ctx.services);
 | |
| 		struct service *service;
 | |
| 		while ((service = str_map_iter_next (&iter)))
 | |
| 			str_map_set (&ctx.svc_list, service->name, service);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// So far we've only stored service _names_ to the hash map;
 | |
| 		// let's try to resolve them to actual services.
 | |
| 		if (!resolve_service_names (&ctx))
 | |
| 			exit (EXIT_FAILURE);
 | |
| 	}
 | |
| 
 | |
| 	merge_port_ranges (&ctx);
 | |
| 	merge_ip_ranges (&ctx);
 | |
| 
 | |
| 	// Wrap the logger so that it is synchronized with the indicator
 | |
| 	g_ctx = &ctx;
 | |
| 	g_log_message_real = log_message_wrapper;
 | |
| 
 | |
| 	// Initate the scan: generate as many units as possible
 | |
| 	generator_init (&ctx);
 | |
| 	while (generator_step (&ctx))
 | |
| 		;
 | |
| 
 | |
| 	ctx.polling = true;
 | |
| 	while (ctx.polling)
 | |
| 		poller_run (&ctx.poller);
 | |
| 
 | |
| 	printf ("Scanned %zu %s, identified %zu %s\n",
 | |
| 		ctx.stats_hosts,   ctx.stats_hosts   == 1 ? "host"    : "hosts",
 | |
| 		ctx.stats_results, ctx.stats_results == 1 ? "service" : "services");
 | |
| 
 | |
| 	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;
 | |
| }
 |