clamdtop/clamdtop.c
aa22174b
 /*
8db0a346
  *  ClamdTOP
aa22174b
  *
c442ca9c
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2008-2013 Sourcefire, Inc.
aa22174b
  *
  *  Authors: Török Edvin
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
  */
e7fdfa9a
 #define _GNU_SOURCE
 #define __EXTENSIONS
 #define GCC_PRINTF
 #define GCC_SCANF
8db0a346
 
 #ifdef HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e2f3282b
 #ifdef HAVE_STDINT_H
 #include <stdint.h>
 #endif
 
aa22174b
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
8db0a346
 #include CURSES_INCLUDE
aa22174b
 #include <time.h>
 #include <ctype.h>
 #include <signal.h>
ce936887
 #ifdef _WIN32
 #include <windows.h>
 #include <winsock2.h>
 /* this is not correct, perhaps winsock errors are not mapped on errno */
 #define herror perror
 #else
aa22174b
 #include <netdb.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <sys/un.h>
ce936887
 #endif
aa22174b
 #include <sys/time.h>
8ff2ee26
 #include <assert.h>
233732d7
 #include <errno.h>
aa22174b
 
6df13d04
 #include "libclamav/clamav.h"
8db0a346
 #include "shared/optparser.h"
 #include "shared/misc.h"
 
e7fdfa9a
 /* Types, prototypes and globals*/
 typedef struct connection {
 	int sd;
93ecf280
 	char *remote;
e7fdfa9a
 	int tcp;
 	struct timeval tv_conn;
 	char *version;
1615194d
 	int line;
e7fdfa9a
 } conn_t;
 
 struct global_stats {
 	struct task *tasks;
 	ssize_t n;
 	struct stats *all_stats;
 	size_t num_clamd;
 	conn_t *conn;
 };
 
 struct stats {
 	const char *remote;
 	char *engine_version;
 	char *db_version;
 	struct tm db_time;
 	const char *version;
1615194d
 	int stats_unsupp;
e7fdfa9a
 	uint8_t conn_hr, conn_min, conn_sec;
 	/* threads - primary */
 	unsigned prim_live, prim_idle, prim_max;
 	/* threads - sum */
 	unsigned live, idle, max;
 	/* queue */
 	unsigned biggest_queue, current_q;
 	double mem;/* in megabytes */
 	unsigned long lheapu, lmmapu, ltotalu, ltotalf, lreleasable, lpoolu, lpoolt;
 	unsigned pools_cnt;
 };
 
 static void cleanup(void);
 static int send_string_noreconn(conn_t *conn, const char *cmd);
 static void send_string(conn_t *conn, const char *cmd);
e419cad6
 static int read_version(conn_t *conn);
6df13d04
 char *get_ip(const char *ip);
 char *get_port(const char *ip);
 char *make_ip(const char *host, const char *port);
e7fdfa9a
 
1615194d
 enum exit_reason {
8db0a346
         FAIL_CMDLINE=1,
 	FAIL_INITIAL_CONN,
1615194d
 	OUT_OF_MEMORY,
 	RECONNECT_FAIL,
 	SIGINT_REASON
 };
 
 static void exit_program(enum exit_reason reason, const char *func, unsigned line);
ebed1714
 #if __GNUC__ >= 3
1615194d
 #define EXIT_PROGRAM(r) exit_program(r, __PRETTY_FUNCTION__, __LINE__);
ebed1714
 #else
 #define EXIT_PROGRAM(r) exit_program(r, "<unknown>", __LINE__);
 #endif
1615194d
 #define OOM_CHECK(p) do { if (!p) EXIT_PROGRAM(OUT_OF_MEMORY); } while (0)
 
 
e7fdfa9a
 static struct global_stats global;
 static int curses_inited = 1;
 static int maxystats=0;
29d6dd3f
 static int detail_selected = -1;
 
8db0a346
 static int detail_exists(void)
29d6dd3f
 {
 	return global.num_clamd != 1;
 }
 
 static int detail_is_selected(int idx)
 {
 	if (!detail_exists()) {
 		assert(idx == 0);
 		return 1;
 	}
 	return idx == detail_selected;
 }
 
 
233732d7
 /* ---------------------- NCurses routines -----------------*/
aa22174b
 enum colors {
 	header_color=1,
 	version_color,
1615194d
 	error_color,
aa22174b
 	value_color,
 	descr_color,
29d6dd3f
 	selected_color,
aa22174b
 	queue_header_color,
 	activ_color,
 	dim_color,
 	red_color,
 };
 
 #define UPDATE_INTERVAL 2
 #define MIN_INTERVAL 1
 
 /* the default color of the terminal in ncurses */
 #define DEFAULT_COLOR -1
 
 #define VALUE_ATTR A_BOLD | COLOR_PAIR(value_color)
 #define DESCR_ATTR COLOR_PAIR(descr_color)
1615194d
 #define ERROR_ATTR A_BOLD | COLOR_PAIR(error_color)
aa22174b
 
 static WINDOW *header_window = NULL;
 static WINDOW *stats_head_window = NULL;
 static WINDOW *stats_window = NULL;
 static WINDOW *status_bar_window = NULL;
 static WINDOW *mem_window = NULL;
 
 static const char *status_bar_keys[10];
 static unsigned maxy=0, maxx=0;
 static char *queue_header = NULL;
e7fdfa9a
 static char *clamd_header = NULL;
 
55885104
 #define CMDHEAD " COMMAND        QUEUEDSINCE   FILE"
 #define CMDHEAD2 " # COMMAND     QUEUEDSINCE   FILE"
e7fdfa9a
 
 /*
  * CLAMD - which local/remote clamd this is
  * CONNTIM - since when we are connected (TODO: zeroed at reconnect)
  * QUEUE   - no of items in queue (total)
  * MAXQUEUE - max no of items in queue observed
  * LIVETHR - sum of live threads
  * IDLETHR - sum of idle threads
  */
 #define SUMHEAD "NO CONNTIME LIV IDL QUEUE  MAXQ   MEM HOST           ENGINE DBVER DBTIME"
aa22174b
 
 static void resize(void)
 {
 	char *p;
 	unsigned new_maxy, new_maxx;
 	getmaxyx(stdscr, new_maxy, new_maxx);
 	if(new_maxy == maxy && new_maxx == maxx)
 		return;
 	maxx = new_maxx;
 	maxy = new_maxy;
 	free(queue_header);
e7fdfa9a
 	free(clamd_header);
aa22174b
 	queue_header = malloc(maxx + 1);
1615194d
 	OOM_CHECK(queue_header);
e7fdfa9a
 	clamd_header = malloc(maxx + 1);
1615194d
 	OOM_CHECK(clamd_header);
879edca6
 	assert(clamd_header && queue_header);
e7fdfa9a
 	strncpy(queue_header, global.num_clamd>1 ? CMDHEAD2 : CMDHEAD, maxx);
 	strncpy(clamd_header, SUMHEAD, maxx);
aa22174b
 	queue_header[maxx] = '\0';
e7fdfa9a
 	clamd_header[maxx] = '\0';
aa22174b
 	p = queue_header + strlen(queue_header);
 	while(p < queue_header+maxx)
 		*p++ = ' ';
e7fdfa9a
 	p = clamd_header + strlen(clamd_header);
 	while(p < clamd_header+maxx)
 		*p++ = ' ';
aa22174b
 }
 
 static void rm_windows(void)
 {
 	if(header_window) {
 		delwin(header_window);
 		header_window = NULL;
 	}
 	if(mem_window) {
 		delwin(mem_window);
 		mem_window = NULL;
 	}
 	if(stats_window) {
 		delwin(stats_window);
 		stats_window = NULL;
 	}
 	if(stats_head_window) {
 		delwin(stats_head_window);
 		stats_head_window = NULL;
 	}
 	if(status_bar_window) {
 		delwin(status_bar_window);
 		status_bar_window = NULL;
 	}
 }
 
 
e7fdfa9a
 static void init_windows(int num_clamd)
aa22174b
 {
 	resize();
 
 	rm_windows();
e7fdfa9a
 	/* non-overlapping windows */
aa22174b
 	header_window = subwin(stdscr, 1, maxx, 0, 0);
e7fdfa9a
 	stats_head_window = subwin(stdscr, num_clamd+1, maxx, 1, 0);
 	maxystats = maxy-num_clamd-3;
 	stats_window = subwin(stdscr, maxystats, maxx, num_clamd+2, 0);
 	status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0);
 	/* memwindow overlaps, used only in details mode */
29d6dd3f
 	mem_window = derwin(stats_window, 6, 41, 1, maxx-41);
aa22174b
 	touchwin(stdscr);
 	werase(stdscr);
 	refresh();
 	memset(status_bar_keys, 0, sizeof(status_bar_keys));
e419cad6
 	status_bar_keys[0] = "H - help";
55885104
 	status_bar_keys[1] = "Q - quit";
29d6dd3f
 	status_bar_keys[2] = "R - reset maximums";
 	if (num_clamd > 1) {
 		status_bar_keys[3] = "^ - previous clamd";
 		status_bar_keys[4] = "v - next clamd";
 	}
aa22174b
 }
 
43ea5675
 static void init_ncurses(int num_clamd, int use_default)
aa22174b
 {
43ea5675
 	int default_bg = use_default ? DEFAULT_COLOR : COLOR_BLACK;
 	int default_fg = use_default ? DEFAULT_COLOR : COLOR_WHITE;
aa22174b
 	initscr();
e7fdfa9a
 	curses_inited = 1;
 
aa22174b
 	start_color();
 	keypad(stdscr, TRUE);	/* enable keyboard mapping */
 	nonl();			/* tell curses not to do NL->CR/NL on output */
 	halfdelay(UPDATE_INTERVAL*10); /* timeout of 2s when waiting for input*/
 	noecho();		/* dont echo input */
 	curs_set(0);		/* turn off cursor */
43ea5675
 	if (use_default)
 	    use_default_colors();
aa22174b
 
 	init_pair(header_color, COLOR_BLACK, COLOR_WHITE);
43ea5675
 	init_pair(version_color, default_fg, default_bg);
1615194d
 	init_pair(error_color, COLOR_WHITE, COLOR_RED);
43ea5675
 	init_pair(value_color, COLOR_GREEN, default_bg);
 	init_pair(descr_color, COLOR_CYAN, default_bg);
29d6dd3f
 	init_pair(selected_color, COLOR_BLACK, COLOR_CYAN);
aa22174b
 	init_pair(queue_header_color, COLOR_BLACK, COLOR_GREEN);
43ea5675
 	init_pair(activ_color, COLOR_MAGENTA, default_bg);
 	init_pair(dim_color, COLOR_GREEN, default_bg);
 	init_pair(red_color, COLOR_RED, default_bg);
aa22174b
 
e7fdfa9a
 	init_windows(num_clamd);
aa22174b
 }
 
 static void win_start(WINDOW *win, enum colors col)
 {
 	wattrset(win, COLOR_PAIR(col));
 	wbkgd(win, COLOR_PAIR(col));
 	werase(win);
 }
 
 
 static void  print_colored(WINDOW *win, const char *p)
 {
 	while(*p) {
 		wattron(win, DESCR_ATTR);
 		while(*p && !isdigit(*p))
 			waddch(win, *p++);
 		wattroff(win, DESCR_ATTR);
 		wattron(win, VALUE_ATTR);
 		while(*p && isdigit(*p))
 			waddch(win, *p++);
 		wattroff(win, VALUE_ATTR);
 	}
 }
 
 static void header(void)
 {
 	size_t i, x=0;
 	time_t t;
 
e7fdfa9a
 
aa22174b
 	win_start(header_window, header_color);
8db0a346
 	mvwprintw(header_window, 0, 0, "  ClamdTOP version %s   ", get_version());
aa22174b
 	time(&t);
 	wprintw(header_window, "%s", ctime(&t));
 	wrefresh(header_window);
 
e7fdfa9a
 /*	win_start(version_window, version_color);
aa22174b
 	mvwprintw(version_window, 0, 0, "Connected to: ");
 	print_colored(version_window, clamd_version ? clamd_version : "Unknown");
e7fdfa9a
 	wrefresh(version_window);*/
aa22174b
 
 	werase(status_bar_window);
 	for(i=0;i<sizeof(status_bar_keys)/sizeof(status_bar_keys[0]);i++) {
29d6dd3f
 		const char *s = status_bar_keys[i];
 		if(!s)
aa22174b
 			continue;
 		wattron(status_bar_window, A_REVERSE);
29d6dd3f
 		if (s[0] == '^') {
 			mvwaddch(status_bar_window, 0, x, ACS_UARROW);
 			s++;
 			x++;
 		} else if (s[0] == 'v') {
 			mvwaddch(status_bar_window, 0, x, ACS_DARROW);
 			s++;
 			x++;
 		}
 		mvwprintw(status_bar_window, 0, x,  "%s",s);
aa22174b
 		wattroff(status_bar_window, A_REVERSE);
 		x += strlen(status_bar_keys[i]) + 1;
 	}
 	wrefresh(status_bar_window);
 }
 
 static void show_bar(WINDOW *win, size_t i, unsigned live, unsigned idle,
 		unsigned max, int blink)
 {
58403eed
 	int y,x,z = 0;
1615194d
 	unsigned len  = 39;
aa22174b
 	unsigned start = 1;
ce936887
 	unsigned activ = max ? ((live-idle)*(len - start - 2) + (max/2)) / max : 0;
 	unsigned dim   = max ? idle*(len - start - 2) / max : 0;
aa22174b
 	unsigned rem = len - activ - dim - start-2;
e1c57cd9
         
8ff2ee26
 	assert(activ + 2 < len && activ+dim + 2 < len && activ+dim+rem + 2 < len && "Invalid values");
aa22174b
 	mvwaddch(win, i, start, '[' | A_BOLD);
 	wattron(win, A_BOLD | COLOR_PAIR(activ_color));
 	for(i=0;i<activ;i++)
 		waddch(win, '|');
 	wattroff(win, A_BOLD | COLOR_PAIR(activ_color));
 	wattron(win, A_DIM | COLOR_PAIR(dim_color));
 	for(i=0;i<dim;i++)
 		waddch(win, '|');
 	wattroff(win, A_DIM | COLOR_PAIR(dim_color));
 	for(i=0;i<rem;i++)
 		waddch(win, ' ');
 	waddch(win, ']' | A_BOLD);
 	if(blink) {
 		getyx(win, y, x);
737635a4
 		if ((x < 0) || (y < 0)) {
 			return; /* if getyx() failed, nevermind the blinking */
 		}
e1c57cd9
 		if (x >= 2) {
737635a4
 			z = x - 2;
 		}
e1c57cd9
 		mvwaddch(win, y, z, '>' | A_BLINK | COLOR_PAIR(red_color));
58403eed
 		move(y, z);
aa22174b
 	}
 }
 
e7fdfa9a
 /* --------------------- Error handling ---------------------*/
 static int normal_exit = 0;
 static const char *exit_reason = NULL;
 static const char *exit_func = NULL;
 static unsigned exit_line = 0;
 
 static void cleanup(void)
 {
 	unsigned i;
 	if (curses_inited) {
1615194d
 		if (status_bar_window) {
 			werase(status_bar_window);
 			wrefresh(status_bar_window);
 		}
e7fdfa9a
 		rm_windows();
 		endwin();
 	}
 	curses_inited = 0;
 	for (i=0;i<global.num_clamd;i++) {
879edca6
 		if (global.conn[i].sd && global.conn[i].sd != -1) {
b3b8212f
 			send_string_noreconn(&global.conn[i], "nEND\n");
879edca6
 			close(global.conn[i].sd);
 		}
e7fdfa9a
 		free(global.conn[i].version);
93ecf280
 		free(global.conn[i].remote);
e7fdfa9a
 	}
 	free(global.all_stats);
 	free(global.conn);
 	free(queue_header);
 	free(clamd_header);
 	if(!normal_exit) {
 		fprintf(stderr, "Abnormal program termination");
 	        if (exit_reason) fprintf(stderr, ": %s",exit_reason);
 		if (exit_func) fprintf(stderr, " in %s", exit_func);
 		if (exit_line) fprintf(stderr, " at line %u", exit_line);
 		fputc('\n',stderr);
 	}
 }
 
879edca6
 #ifdef __GNUC__
 #define __noreturn __attribute__((noreturn))
 #else
 #define __noreturn
 #endif
e7fdfa9a
 
879edca6
 static void __noreturn exit_program(enum exit_reason reason, const char *func, unsigned line)
e7fdfa9a
 {
 	switch(reason) {
8db0a346
 		case FAIL_CMDLINE:
 			exit_reason = "Invalid command-line arguments";
 			break;
e7fdfa9a
 		case FAIL_INITIAL_CONN:
 			exit_reason = "Unable to connect to all clamds";
 			break;
 		case OUT_OF_MEMORY:
 			exit_reason = "Out of memory";
 			break;
 		case RECONNECT_FAIL:
 			exit_reason = "Failed to reconnect to clamd after connection was lost";
 			break;
1615194d
 		case SIGINT_REASON:
 			exit_reason = "User interrupt";
 			break;
e7fdfa9a
 		default:
 			exit_reason = "Unknown";
 			break;
 	}
 	exit_func = func;
 	exit_line = line;
 	exit(reason);
 }
 
aa22174b
 struct task {
 	char *line;
 	double tim;
e7fdfa9a
 	int clamd_no;
aa22174b
 };
 
 static int tasks_compare(const void *a, const void *b)
 {
 	const struct task *ta = a;
 	const struct task *tb = b;
 	if(ta->tim < tb->tim)
 		return 1;
 	if(ta->tim > tb->tim)
 		return -1;
 	return 0;
 }
 
233732d7
 /* ----------- Socket routines ----------------------- */
8db0a346
 #ifdef __GNUC__
 static void print_con_info(conn_t *conn, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
 #endif
1615194d
 static void print_con_info(conn_t *conn, const char *fmt, ...)
233732d7
 {
 	va_list ap;
 	va_start(ap, fmt);
1615194d
 	if (stats_head_window) {
d737d793
 		char *buf = malloc(maxx);
1615194d
 		OOM_CHECK(buf);
245ca456
 		memset(buf, ' ', maxx);
d737d793
 		vsnprintf(buf, maxx-1, fmt, ap);
1615194d
 		buf[strlen(buf)] = ' ';
d737d793
 		buf[maxx-1] = '\0';
1615194d
 		wattron(stats_head_window, ERROR_ATTR);
 		mvwprintw(stats_head_window, conn->line, 0, "%s", buf);
 		wattroff(stats_head_window, ERROR_ATTR);
 		wrefresh(stats_head_window);
d737d793
 		free(buf);
1615194d
 	} else
 		vfprintf(stdout, fmt, ap);
233732d7
 	va_end(ap);
 }
 
0c81fc85
 char *get_ip(const char *ip)
 {
6df13d04
     char *dupip, *p1;
aad67456
     unsigned int i;
0c81fc85
 
     /*
      * Expected format of ip:
aad67456
      *     1) IPv4
      *     2) IPv4:Port
      *     3) IPv6
      *     4) [IPv6]:Port
0c81fc85
      * 
      * Use of IPv6:Port is incorrect. An IPv6 address must be enclosed in brackets.
      */
 
     dupip = strdup(ip);
     if (!(dupip))
         return NULL;
 
     if (dupip[0] == '[') {
         /* IPv6 */
         p1 = strchr(dupip, ']');
         if (!(p1)) {
             free(dupip);
             return NULL;
         }
 
         *p1 = '\0';
 
aad67456
         p1 = strdup(dupip+1);
         free(dupip);
         return p1;
0c81fc85
     }
 
aad67456
     p1 = dupip;
     i=0;
99af390c
     while ((p1 = strchr(p1, ':'))) {
aad67456
         i++;
         p1++;
0c81fc85
     }
 
aad67456
     if (i == 0 || i > 1)
         return dupip;
0c81fc85
 
aad67456
     if (i == 1) {
         p1 = strchr(dupip, ':');
         *p1 = '\0';
         return dupip;
0c81fc85
     }
 
     return dupip;
 }
 
 char *get_port(const char *ip)
 {
     char *dupip, *p;
aad67456
     unsigned int offset=0;
0c81fc85
 
     dupip = get_ip(ip);
     if (!(dupip))
         return NULL;
 
aad67456
     if (ip[0] == '[')
         offset += 2;
 
99af390c
     p = (char *)ip + strlen(dupip) + offset;
aad67456
     if (*p == ':') {
         p = strdup(p+1);
         free(dupip);
         return p;
     }
0c81fc85
 
aad67456
     return NULL;
0c81fc85
 }
 
 char *make_ip(const char *host, const char *port)
 {
     char *ip;
     size_t len;
     int ipv6;
 
     len = strlen(host) + strlen(port);
 
     ipv6 = (strchr(host, ':') != NULL);
 
884814d6
     len += (ipv6 ? 4 : 3);
0c81fc85
 
     ip = calloc(1, len);
     if (!(ip))
         return NULL;
 
     snprintf(ip, len, "%s%s%s:%s", ipv6 ? "[" : "", host, ipv6 ? "]" : "", port);
 
     return ip;
 }
aa22174b
 
e419cad6
 static int make_connection_real(const char *soname, conn_t *conn)
233732d7
 {
413ffee7
     int s = -1;
33a3a884
     struct timeval tv;
4d035342
     char *port=NULL;
6df13d04
     char *pt = strdup(soname);
33a3a884
     const char *host = pt;
     struct addrinfo hints, *res=NULL, *p;
0c81fc85
     int err;
33a3a884
 
637d2461
     OOM_CHECK(pt);
33a3a884
     conn->tcp = 0;
 
 #ifndef _WIN32
     if(cli_is_abspath(soname) || (access(soname, F_OK) == 0)) {
         struct sockaddr_un addr;
 
         s = socket(AF_UNIX, SOCK_STREAM, 0);
         if(s < 0) {
             perror("socket");
             return -1;
         }
 
         memset(&addr, 0, sizeof(addr));
         addr.sun_family = AF_UNIX;
         strncpy(addr.sun_path, soname, sizeof(addr.sun_path));
         addr.sun_path[sizeof(addr.sun_path) - 1] = 0x0;
 
         print_con_info(conn, "Connecting to: %s\n", soname);
         if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) {
             perror("connect");
8c7ffc98
             close(s);
33a3a884
             return -1;
         }
 
         goto end;
     }
ce936887
 #endif
33a3a884
 
     memset(&hints, 0x00, sizeof(struct addrinfo));
     hints.ai_family = AF_UNSPEC;
     hints.ai_socktype = SOCK_STREAM;
     hints.ai_flags = AI_PASSIVE;
 
0c81fc85
     host = get_ip(soname);
     if (!(host))
         return -1;
 
     port = get_port(soname);
 
33a3a884
     conn->tcp=1;
 
0c81fc85
     print_con_info(conn, "Looking up: %s:%s\n", host, port ? port : "3310");
     if ((err = getaddrinfo(host, (port != NULL) ? port : "3310", &hints, &res))) {
         print_con_info(conn, "Could not look up %s:%s, getaddrinfo returned: %s\n", host, port ? port : "3310", gai_strerror(err));
33a3a884
         return -1;
0c81fc85
     }
33a3a884
 
     for (p = res; p != NULL; p = p->ai_next) {
         if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
             perror("socket");
             continue;
         }
 
0c81fc85
         print_con_info(conn, "Connecting to: %s\n", soname);
33a3a884
         if (connect(s, p->ai_addr, p->ai_addrlen)) {
             perror("connect");
8c7ffc98
             close(s);
33a3a884
             continue;
         }
 
         break;
     }
 
     free(pt);
 
     if (res)
         freeaddrinfo(res);
 
     if (p == NULL)
         return -1;
 
 end:
     if (conn->remote != soname) {
         /* when we reconnect, they are the same */
         if (conn->remote)
             free(conn->remote);
 
0c81fc85
         conn->remote = make_ip(host, (port != NULL) ? port : "3310");
33a3a884
     }
aad67456
     if (port)
         free(port);
33a3a884
     conn->sd = s;
     gettimeofday(&conn->tv_conn, NULL);
     tv.tv_sec = 30;
     tv.tv_usec = 0;
     setsockopt(conn->sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
     return 0;
233732d7
 }
 
e419cad6
 static int make_connection(const char *soname, conn_t *conn)
 {
     int rc;
 
     if ((rc = make_connection_real(soname, conn)))
33a3a884
         return rc;
 
e419cad6
     send_string(conn, "nIDSESSION\nnVERSION\n");
     free(conn->version);
     conn->version = NULL;
     if (!read_version(conn))
33a3a884
         return 0;
e419cad6
 
     /* clamd < 0.95 */
     if ((rc = make_connection_real(soname, conn)))
33a3a884
         return rc;
 
e419cad6
     send_string(conn, "nSESSION\nnVERSION\n");
     conn->version = NULL;
     if (!read_version(conn))
33a3a884
         return 0;
 
e419cad6
     return -1;
 }
 
e7fdfa9a
 static void reconnect(conn_t *conn);
233732d7
 
e7fdfa9a
 static int send_string_noreconn(conn_t *conn, const char *cmd)
233732d7
 {
 	assert(cmd);
 	assert(conn && conn->sd > 0);
e7fdfa9a
 	return send(conn->sd, cmd, strlen(cmd), 0);
 }
233732d7
 
e7fdfa9a
 static void send_string(conn_t *conn, const char *cmd)
 {
 	while(send_string_noreconn(conn, cmd) == -1) {
233732d7
 		reconnect(conn);
 	}
 }
 
e7fdfa9a
 static int tries = 0;
 static void reconnect(conn_t *conn)
 {
 	if(++tries > 3) {
 		EXIT_PROGRAM(RECONNECT_FAIL);
 	}
e419cad6
 	if (conn->sd != -1)
 	    close(conn->sd);
e7fdfa9a
 	if (make_connection(conn->remote, conn) < 0) {
1615194d
 		print_con_info(conn, "Unable to reconnect to %s: %s", conn->remote, strerror(errno));
879edca6
 		EXIT_PROGRAM(RECONNECT_FAIL);
e7fdfa9a
 	}
 	tries = 0;
 }
 
233732d7
 static int recv_line(conn_t *conn, char *buf, size_t len)
 {
 	assert(len > 0);
1615194d
 	assert(conn);
233732d7
 	assert(buf);
 
 	len--;
1615194d
 	if (!len || conn->sd == -1)
233732d7
 		return 0;
1615194d
 	assert(conn->sd > 0);
233732d7
 	while (len > 0) {
 		ssize_t nread = recv(conn->sd, buf, len, MSG_PEEK);
1615194d
 		if (nread  <= 0) {
 			print_con_info(conn, "%s: %s", conn->remote, strerror(errno));
 			/* it could be a timeout, be nice and send an END */
b3b8212f
 			send_string_noreconn(conn, "nEND\n");
1615194d
 			close(conn->sd);
 			conn->sd = -1;
 			return 0;
 		} else {
233732d7
 			char *p = memchr(buf, '\n', nread);
 			if (p) {
 				len = p - buf + 1;
 			} else
 				len = nread;
 			assert(len > 0);
 			assert(len <= (size_t)nread);
 			nread = recv(conn->sd, buf, len, 0);
 			if (nread == -1)
 				reconnect(conn);
 			else {
 				assert(nread >0 && (size_t)nread == len);
 				buf += nread;
 			}
 			if (p)
 				break;
 		}
 	}
 	*buf = '\0';
 	return 1;
 }
 
e7fdfa9a
 static void output_queue(size_t line, ssize_t max)
 {
29d6dd3f
 	ssize_t i, j;
e7fdfa9a
 	struct task *tasks = global.tasks;
29d6dd3f
 	struct task *filtered_tasks = calloc(global.n, sizeof(*filtered_tasks));
 	OOM_CHECK(filtered_tasks);
 	for (i=0,j=0;i<global.n;i++) {
 		if (detail_selected == -1 || detail_is_selected(tasks[i].clamd_no-1)) {
 			filtered_tasks[j++] = tasks[i];
 		}
 	}
 
e7fdfa9a
 	wattron(stats_window, COLOR_PAIR(queue_header_color));
 	mvwprintw(stats_window, line++, 0, "%s", queue_header);
 	wattroff(stats_window, COLOR_PAIR(queue_header_color));
29d6dd3f
 	if (max >= j)
 		max = j;
e7fdfa9a
 	else
 		--max;
 	if (max < 0) max = 0;
 	for(i=0;i<max;i++) {
8db0a346
 		char *cmde;
1615194d
 		assert(tasks);
8db0a346
 		cmde = strchr(filtered_tasks[i].line, ' ');
e7fdfa9a
 		if(cmde) {
 			char cmd[16];
 			const char *filstart = strchr(cmde + 1, ' ');
29d6dd3f
 			strncpy(cmd, filtered_tasks[i].line, sizeof(cmd)-1);
e7fdfa9a
 			cmd[15]='\0';
29d6dd3f
 			if (filtered_tasks[i].line+15 > cmde)
 				cmd[cmde - filtered_tasks[i].line] = '\0';
e7fdfa9a
 			if(filstart) {
 				++filstart;
29d6dd3f
 				if (detail_selected == -1 && global.num_clamd > 1)
 					mvwprintw(stats_window, line + i, 0, "%2u %s", filtered_tasks[i].clamd_no, cmd + 1);
e7fdfa9a
 				else
 					mvwprintw(stats_window, line + i, 0, " %s", cmd + 1);
29d6dd3f
 				mvwprintw(stats_window, line + i, 15, "%10.03fs", filtered_tasks[i].tim);
e7fdfa9a
 				mvwprintw(stats_window, line + i, 30, "%s",filstart);
 			}
 		}
 	}
29d6dd3f
 	if (max < j) {
e7fdfa9a
 		/* in summary mode we can only show a max amount of tasks */
 		wattron(stats_window, A_DIM | COLOR_PAIR(header_color));
29d6dd3f
 		mvwprintw(stats_window, line+i, 0, "*** %u more task(s) not shown ***", (unsigned)(j - max));
e7fdfa9a
 		wattroff(stats_window, A_DIM | COLOR_PAIR(header_color));
 	}
29d6dd3f
 	free(filtered_tasks);
e7fdfa9a
 }
 
233732d7
 /* ---------------------- stats parsing routines ------------------- */
e7fdfa9a
 
 
 static void parse_queue(conn_t *conn, char* buf, size_t len, unsigned idx)
aa22174b
 {
 	do {
 		double tim;
 		const char *t = strchr(buf, ' ');
 		if(!t)
 			continue;
 		if(sscanf(t,"%lf", &tim) != 1)
 			continue;
e7fdfa9a
 		++global.n;
 		global.tasks = realloc(global.tasks, sizeof(*global.tasks)*global.n);
1615194d
 		OOM_CHECK(global.tasks);
e7fdfa9a
 		global.tasks[global.n-1].line = strdup(buf);
1615194d
 		OOM_CHECK(global.tasks[global.n-1].line);
e7fdfa9a
 		global.tasks[global.n-1].tim  = tim;
1615194d
 		global.tasks[global.n-1].clamd_no = idx + 1;
e7fdfa9a
 	} while (recv_line(conn, buf, len) && buf[0] == '\t' && strcmp("END\n", buf) != 0);
aa22174b
 }
 
ca9cb072
 static unsigned biggest_mem = 0;
aa22174b
 
e7fdfa9a
 static void output_memstats(struct stats *stats)
aa22174b
 {
 	char buf[128];
e7fdfa9a
 	unsigned long totalmem;
aa22174b
 	int blink = 0;
 
 	werase(mem_window);
ad6c9656
 	if (stats->mem > 0 || (stats->mem >=0 && (stats->lpoolt > 0))) {
d737d793
 		box(mem_window, 0, 0);
deb30312
 
ad6c9656
 		if (stats->mem > 0)
59deef7d
 		    snprintf(buf, sizeof(buf),"heap %4luM mmap %4luM unused %3luM",
5497369a
 			     stats->lheapu/1000, stats->lmmapu/1000, stats->lreleasable/1000);
59deef7d
 		else
 		    snprintf(buf, sizeof(buf), "heap   N/A mmap   N/A unused  N/A");
d737d793
 		mvwprintw(mem_window, 1, 1, "Mem:  ");
 		print_colored(mem_window, buf);
deb30312
 
d737d793
 		mvwprintw(mem_window, 2, 1, "Libc: ");
ad6c9656
 		if (stats->mem > 0)
59deef7d
 		    snprintf(buf, sizeof(buf),"used %4luM free %4luM total %4luM",
5497369a
 			     stats->ltotalu/1000, stats->ltotalf/1000, (stats->ltotalu+stats->ltotalf)/1000);
59deef7d
 		else
 		    snprintf(buf, sizeof(buf), "used   N/A free   N/A total   N/A");
d737d793
 		print_colored(mem_window, buf);
deb30312
 
d737d793
 		mvwprintw(mem_window, 3, 1, "Pool: ");
59deef7d
 		snprintf(buf, sizeof(buf), "count %4u used %4luM total %4luM",
5497369a
 			stats->pools_cnt, stats->lpoolu/1000, stats->lpoolt/1000);
d737d793
 		print_colored(mem_window, buf);
deb30312
 
d737d793
 		totalmem = stats->lheapu + stats->lmmapu + stats->lpoolt;
 		if(totalmem > biggest_mem) {
 			biggest_mem = totalmem;
 			blink = 1;
 		}
 		show_bar(mem_window, 4, totalmem, stats->lmmapu + stats->lreleasable + stats->lpoolt - stats->lpoolu,
 				biggest_mem, blink);
aa22174b
 	}
 	wrefresh(mem_window);
 }
 
e7fdfa9a
 static void parse_memstats(const char *line, struct stats *stats)
 {
 	double heapu, mmapu, totalu, totalf, releasable, pools_used, pools_total;
 
 	if(sscanf(line, " heap %lfM mmap %lfM used %lfM free %lfM releasable %lfM pools %u pools_used %lfM pools_total %lfM",
59deef7d
 			&heapu, &mmapu, &totalu, &totalf, &releasable, &stats->pools_cnt, &pools_used, &pools_total) != 8) {
 	    if (sscanf(line , " heap N/A mmap N/A used N/A free N/A releasable N/A pools %u pools_used %lfM pools_total %lfM",
 		       &stats->pools_cnt, &pools_used, &pools_total) != 3) {
 		stats->mem = -1;
e7fdfa9a
 		return;
59deef7d
 	    }
 	    stats->lpoolu = pools_used*1000;
 	    stats->lpoolt = pools_total*1000;
 	    stats->mem = 0;
 	    return;
 	}
e7fdfa9a
 	stats->lheapu = heapu*1000;
 	stats->lmmapu = mmapu*1000;
 	stats->ltotalu = totalu*1000;
 	stats->ltotalf = totalf*1000;
 	stats->lreleasable = releasable*1000;
 	stats->lpoolu = pools_used*1000;
 	stats->lpoolt = pools_total*1000;
 	stats->mem = heapu + mmapu + pools_total;
 }
aa22174b
 
e7fdfa9a
 static int output_stats(struct stats *stats, unsigned idx)
 {
 	char buf[128];
 	char timbuf[15];
 	int blink = 0;
 	size_t i= 0;
 	char mem[6];
aa22174b
 	WINDOW *win = stats_head_window;
29d6dd3f
 	int sel = detail_is_selected(idx);
 	char *line = malloc(maxx+1);
 
 	OOM_CHECK(line);
e7fdfa9a
 
43d1be54
 	if (stats->mem <= 0 || stats->stats_unsupp) {
1615194d
 		strncpy(mem, "N/A", sizeof(mem));
43d1be54
 		mem[sizeof(mem)-1]='\0';
 	}
e7fdfa9a
 	else {
 		char c;
 		double s;
 		if (stats->mem > 999.0)  {
 			c = 'G';
 			s = stats->mem / 1024.0;
 		} else {
 			c = 'M';
 			s = stats->mem;
 		}
 		snprintf(mem, sizeof(mem), "%7.3f", s);
 		i = 4;
 		if (mem[i-1] == '.') i--;
 		mem[i++] = c;
 		mem[i] = '\0';
 	}
 	i = idx+1;
 
43d1be54
 	if (!stats->db_time.tm_year) {
1615194d
 		strncpy(timbuf,"N/A",sizeof(timbuf));
55625287
 		timbuf[sizeof(timbuf)-1]='\0';
43d1be54
 	}
e7fdfa9a
 	else
 		snprintf(timbuf, sizeof(timbuf), "%04u-%02u-%02u %02uh",
 				1900 + stats->db_time.tm_year,
e419cad6
 				stats->db_time.tm_mon+1,
e7fdfa9a
 				stats->db_time.tm_mday,
 				stats->db_time.tm_hour);
 
29d6dd3f
 	memset(line, ' ', maxx+1);
1615194d
 	if (!stats->stats_unsupp) {
29d6dd3f
 		snprintf(line, maxx-1, "%2u %02u:%02u:%02u %3u %3u %5u %5u %5s %-14s %-6s %5s %s", idx+1,  stats->conn_hr, stats->conn_min, stats->conn_sec,
e7fdfa9a
 			stats->live, stats->idle,
 			stats->current_q, stats->biggest_queue,
 			mem,
 			stats->remote, stats->engine_version, stats->db_version, timbuf);
1615194d
 	} else {
29d6dd3f
 		snprintf(line, maxx-1, "%2u %02u:%02u:%02u N/A N/A   N/A   N/A   N/A %-14s %-6s %5s %s", idx+1,  stats->conn_hr, stats->conn_min, stats->conn_sec,
1615194d
 			stats->remote, stats->engine_version, stats->db_version, timbuf);
 	}
29d6dd3f
 	line[maxx] = '\0';
 	line[strlen(line)] = ' ';
 	if (sel) {
 		wattron(win,  COLOR_PAIR(selected_color));
 	}
879edca6
 	mvwprintw(win, i, 0, "%s", line);
29d6dd3f
 	if (sel) {
 		wattroff(win, COLOR_PAIR(selected_color));
 	}
e7fdfa9a
 	win = stats_window;
 	i = 0;
29d6dd3f
 	if (sel && !stats->stats_unsupp) {
 		memset(line, ' ', maxx+1);
 		snprintf(line, maxx-1, "Details for Clamd version: %s", stats->version);
 		line[maxx] = '\0';
 		line[strlen(line)]= ' ';
 		wattron(win,  COLOR_PAIR(queue_header_color));
 		mvwprintw(win, i++, 0, "%s", line);
 		wattroff(win, COLOR_PAIR(queue_header_color));
e7fdfa9a
 		mvwprintw(win, i++, 0, "Primary threads: ");
 		snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->prim_live, stats->prim_idle, stats->prim_max);
 		print_colored(win, buf);
 		show_bar(win, i++, stats->prim_live, stats->prim_idle, stats->prim_max, 0);
8db0a346
 /*		mvwprintw(win, i++, 0, "Multiscan pool : ");
e7fdfa9a
 		snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->live, stats->idle, stats->max);
 		print_colored(win, buf);
8db0a346
 		show_bar(win, i++, stats->live, stats->idle, stats->max, 0);*/
e7fdfa9a
 
ca9cb072
 		blink = 0;
 		if(stats->current_q > stats->biggest_queue) {
 			stats->biggest_queue = stats->current_q;
 			blink = 1;
 		}
e7fdfa9a
 		mvwprintw(win, i++, 0, "Queue:");
 		snprintf(buf, sizeof(buf), "%6u items %6u max", stats->current_q, stats->biggest_queue);
 		print_colored(win, buf);
ca9cb072
 		show_bar(win, i++, stats->current_q, 0, stats->biggest_queue, blink);
e419cad6
 		i += 2;
e7fdfa9a
 		werase(mem_window);
 		output_memstats(stats);
 	}
29d6dd3f
 	free(line);
e7fdfa9a
 	return i;
 }
 
 static void output_all(void)
 {
 	unsigned i, stats_line=0;
 	werase(stats_head_window);
 	werase(stats_window);
 	wattron(stats_head_window, COLOR_PAIR(queue_header_color));
 	mvwprintw(stats_head_window, 0, 0, "%s", clamd_header);
 	wattroff(stats_head_window, COLOR_PAIR(queue_header_color));
29d6dd3f
 	for (i=0;i<global.num_clamd;i++) {
 		unsigned  j = output_stats(&global.all_stats[i], i);
 		if (j > stats_line)
 			stats_line = j;
 	}
e7fdfa9a
 	output_queue(stats_line, maxystats - stats_line-1);
 	wrefresh(stats_head_window);
 	wrefresh(stats_window);
29d6dd3f
 	if (detail_exists()) {
e7fdfa9a
 		/* overlaps, must be done at the end */
 		wrefresh(mem_window);
 	}
 }
 
 static void parse_stats(conn_t *conn, struct stats *stats, unsigned idx)
 {
1aa13093
 	char buf[1025];
e7fdfa9a
 	size_t j;
aa22174b
 	struct timeval tv;
 	unsigned conn_dt;
e7fdfa9a
 	int primary = 0;
b3b8212f
 	const char *pstart, *p, *vstart;
e7fdfa9a
 
 	if (conn->tcp)
 		stats->remote = conn->remote;
 	else
 		stats->remote = "local";
 
d737d793
 	if (!conn->version) {
8db0a346
 		stats->engine_version = strdup("???");
637d2461
 		OOM_CHECK(stats->engine_version);
d737d793
 		return;
 	}
b3b8212f
 	p = pstart = vstart = strchr(conn->version, ' ');
 	if (!vstart) {
8db0a346
 	    stats->engine_version = strdup("???");
637d2461
 	    OOM_CHECK(stats->engine_version);
b3b8212f
 	    return;
 	}
e7fdfa9a
 	/* find digit in version */
 	while (*p && !isdigit(*p))
 		p++;
 	/* rewind to first space or dash */
 	while (p > pstart && *p && *p != ' ' && *p != '-')
 		p--;
 	if (*p) p++;
 	/* keep only base version, and cut -exp, and -gittags */
 	pstart = p;
 	while (*p && *p != '-' && *p != '/')
 		p++;
 
 	stats->engine_version = malloc(p - pstart+1);
1615194d
 	OOM_CHECK(stats->engine_version);
aa22174b
 
e7fdfa9a
 	memcpy(stats->engine_version, pstart, p-pstart);
 	stats->engine_version[p-pstart] = '\0';
 
 	pstart = strchr(p, '/');
93842cbd
 	if (!pstart) {
e7fdfa9a
 		stats->db_version = strdup("????");
637d2461
 		OOM_CHECK(stats->db_version);
93842cbd
 	} else {
e7fdfa9a
 		pstart++;
 		p = strchr(pstart, '/');
 		if (!p)
 			p = pstart + strlen(pstart);
 		stats->db_version = malloc(p - pstart + 1);
1615194d
 		OOM_CHECK(stats->db_version);
e7fdfa9a
 		memcpy(stats->db_version, pstart, p-pstart);
 		stats->db_version[p-pstart] = '\0';
 		if(*p) p++;
 		if (!*p || !strptime(p,"%a %b  %d %H:%M:%S %Y", &stats->db_time)) {
 			memset(&stats->db_time, 0, sizeof(stats->db_time));
 		}
 	}
 	if (maxx > 61 && strlen(stats->db_version) > (maxx-61)) {
 		stats->db_version[maxx-61] = '\0';
 	}
 
b3b8212f
 	stats->version = vstart; /* for details view */
aa22174b
 	gettimeofday(&tv, NULL);
e7fdfa9a
 	tv.tv_sec -= conn->tv_conn.tv_sec;
 	tv.tv_usec -= conn->tv_conn.tv_usec;
aa22174b
 	conn_dt = tv.tv_sec + tv.tv_usec/1e6;
e7fdfa9a
 
 	stats->live = stats->idle = stats->max = 0;
 	stats->conn_hr = conn_dt/3600;
 	stats->conn_min = (conn_dt/60)%60;
 	stats->conn_sec = conn_dt%60;
ca9cb072
 	stats->current_q = 0;
3a262e87
 	buf[sizeof(buf) - 1] = 0x0;
1aa13093
 	while(recv_line(conn, buf, sizeof(buf)-1) && strcmp("END\n",buf) != 0) {
aa22174b
 		char *val = strchr(buf, ':');
 
 		if(buf[0] == '\t') {
1aa13093
 			parse_queue(conn, buf, sizeof(buf)-1, idx);
aa22174b
 			continue;
 		} else if(val)
 			*val++ = '\0';
 		if(!strcmp("MEMSTATS", buf)) {
e7fdfa9a
 			parse_memstats(val, stats);
aa22174b
 			continue;
 		}
1615194d
 		if(!strncmp("UNKNOWN COMMAND", buf, 15)) {
 			stats->stats_unsupp = 1;
 			break;
 		}
aa22174b
 		for(j=1;j<strlen(buf);j++)
 			buf[j] = tolower(buf[j]);
e7fdfa9a
 	/*	mvwprintw(win, i, 0, "%s", buf);
aa22174b
 		if(!val) {
 			i++;
 			continue;
 		}
 		waddch(win, ':');
 		print_colored(win, val);
e7fdfa9a
 		i++;*/
 		if(!strncmp("State",buf,5)) {
 			if(strstr(val, "PRIMARY")) {
 				/* primary thread pool */
 				primary = 1;
 			} else {
 				/* multiscan pool */
 				primary = 0;
 			}
 		}
aa22174b
 		if(!strcmp("Threads",buf)) {
 			unsigned live, idle, max;
 			if(sscanf(val, " live %u idle %u max %u", &live, &idle, &max) != 3)
 				continue;
e7fdfa9a
 			if (primary) {
 				stats->prim_live = live;
 				stats->prim_idle = idle;
 				assert(!stats->prim_max && "There can be only one primary pool!");
 				stats->prim_max = max;
 			}
8db0a346
 			stats->live += live;
 			stats->idle += idle;
 			stats->max += max;
aa22174b
 		} else if (!strcmp("Queue",buf)) {
 			unsigned len;
 			if(sscanf(val, "%u", &len) != 1)
 				continue;
ca9cb072
 			stats->current_q += len;
aa22174b
 		}
 	}
 }
 
e419cad6
 static int read_version(conn_t *conn)
aa22174b
 {
 	char buf[1024];
e419cad6
 	unsigned i;
 	if(!recv_line(conn, buf, sizeof(buf)))
 	    return -1;
 	if (!strcmp(buf, "UNKNOWN COMMAND\n"))
 	    return -2;
 
 	conn->version = strdup(buf);
 	OOM_CHECK(conn->version);
 	for (i=0;i<strlen(conn->version);i++)
 		    if (conn->version[i] == '\n')
 			conn->version[i] = ' ';
 	return 0;
aa22174b
 }
 
1615194d
 static void sigint(int a)
 {
6df13d04
     UNUSEDPARAM(a);
1615194d
 	EXIT_PROGRAM(SIGINT_REASON);
 }
 
8db0a346
 static void help(void)
 {
     printf("\n");
e098cdc5
     printf("                       Clam AntiVirus: Monitoring Tool %s\n", get_version());
964a1e73
     printf("           By The ClamAV Team: https://www.clamav.net/about.html#credits\n");
c442ca9c
     printf("           (C) 2019 Cisco Systems, Inc.\n");
e098cdc5
     printf("\n");
     printf("    clamdtop [-hVc] [host[:port] /path/to/clamd.socket ...]\n");
     printf("\n");
     printf("    --help                 -h         Show this help\n");
8db0a346
     printf("    --version              -V         Show version\n");
     printf("    --config-file=FILE     -c FILE    Read clamd's configuration files from FILE\n");
e098cdc5
     printf("    --defaultcolors	       -d         Use default terminal colors\n");
     printf("    host[:port]                       Connect to clamd on host at port (default 3310)\n");
     printf("    /path/to/clamd.socket             Connect to clamd over a local socket\n");
8db0a346
     printf("\n");
     return;
 }
43ea5675
 static int default_colors=0;
e7fdfa9a
 /* -------------------------- Initialization ---------------- */
 static void setup_connections(int argc, char *argv[])
aa22174b
 {
33a3a884
     struct optstruct *opts;
     struct optstruct *clamd_opts;
     unsigned i;
     char *conn = NULL;
 
     opts = optparse(NULL, argc, argv, 1, OPT_CLAMDTOP, 0, NULL);
     if (!opts) {
         fprintf(stderr, "ERROR: Can't parse command line options\n");
         EXIT_PROGRAM(FAIL_CMDLINE);
     }
 
     if(optget(opts, "help")->enabled) {
         optfree(opts);
         help();
         normal_exit = 1;
         exit(0);
     }
 
     if(optget(opts, "version")->enabled) {
         printf("Clam AntiVirus Monitoring Tool %s\n", get_version());
         optfree(opts);
         normal_exit = 1;
         exit(0);
     }
 
     if(optget(opts, "defaultcolors")->enabled)
         default_colors = 1;
 
     memset(&global, 0, sizeof(global));
     if (!opts->filename || !opts->filename[0]) {
         const struct optstruct *opt;
         const char *clamd_conf = optget(opts, "config-file")->strarg;
 
         if ((clamd_opts = optparse(clamd_conf, 0, NULL, 1, OPT_CLAMD, 0, NULL)) == NULL) {
             fprintf(stderr, "Can't parse clamd configuration file %s\n", clamd_conf);
             EXIT_PROGRAM(FAIL_CMDLINE);
         }
 
         if((opt = optget(clamd_opts, "LocalSocket"))->enabled) {
             conn = strdup(opt->strarg);
             if (!conn) {
                 fprintf(stderr, "Can't strdup LocalSocket value\n");
                 EXIT_PROGRAM(FAIL_INITIAL_CONN);
             }
         } else if ((opt = optget(clamd_opts, "TCPSocket"))->enabled) {
             char buf[512];
             const struct optstruct *opt_addr;
             const char *host = "localhost";
             if ((opt_addr = optget(clamd_opts, "TCPAddr"))->enabled) {
                 host = opt_addr->strarg;
             }
0c81fc85
             snprintf(buf, sizeof(buf), "%lld", opt->numarg);
             conn = make_ip(host, buf);
33a3a884
         } else {
             fprintf(stderr, "Can't find how to connect to clamd\n");
             EXIT_PROGRAM(FAIL_INITIAL_CONN);
         }
 
         optfree(clamd_opts);
         global.num_clamd = 1;
     } else {
         unsigned i = 0;
         while (opts->filename[i]) { i++; }
         global.num_clamd = i;
     }
8db0a346
 
ce936887
 #ifdef _WIN32
33a3a884
     WSADATA wsaData;
     if (WSAStartup(MAKEWORD(2,2), &wsaData) != NO_ERROR) {
         fprintf(stderr, "Error at WSAStartup(): %d\n", WSAGetLastError());
         EXIT_PROGRAM(FAIL_INITIAL_CONN);
     }
e7fdfa9a
 #endif
33a3a884
     /* clamdtop */
     puts( "        __                    ____");
     puts("  _____/ /___ _____ ___  ____/ / /_____  ____");
     puts(" / ___/ / __ `/ __ `__ \\/ __  / __/ __ \\/ __ \\");
     puts("/ /__/ / /_/ / / / / / / /_/ / /_/ /_/ / /_/ /");
     puts("\\___/_/\\__,_/_/ /_/ /_/\\__,_/\\__/\\____/ .___/");
     puts("                                     /_/");
 
     global.all_stats = calloc(global.num_clamd, sizeof(*global.all_stats));
     OOM_CHECK(global.all_stats);
     global.conn = calloc(global.num_clamd, sizeof(*global.conn));
     OOM_CHECK(global.conn);
 
     for (i=0;i < global.num_clamd;i++) {
         const char *soname = conn ? conn : opts->filename[i];
         global.conn[i].line = i+1;
         if (make_connection(soname, &global.conn[i]) < 0) {
             EXIT_PROGRAM(FAIL_INITIAL_CONN);
         }
     }
 
     optfree(opts);
     free(conn);
e7fdfa9a
 #ifndef _WIN32
33a3a884
     signal(SIGPIPE, SIG_IGN);
     signal(SIGINT, sigint);
ce936887
 #endif
e7fdfa9a
 }
 
 static void free_global_stats(void)
 {
 	unsigned i;
1615194d
 	for (i=0;i<(unsigned)global.n;i++) {
e7fdfa9a
 		free(global.tasks[i].line);
 	}
 	for (i=0;i<global.num_clamd;i++) {
 		free(global.all_stats[i].engine_version);
 		free(global.all_stats[i].db_version);
 	}
 	free(global.tasks);
 	global.tasks = NULL;
 	global.n=0;
 }
 
55885104
 static int help_line;
 static void explain(const char *abbrev, const char *msg)
 {
 	wattron(stdscr, A_BOLD);
 	mvwprintw(stdscr, help_line++, 0, "%-15s", abbrev);
 	wattroff(stdscr, A_BOLD);
 	wprintw(stdscr,"  %s", msg);
 }
 
 static int show_help(void)
 {
 	int ch;
 	werase(stdscr);
 	help_line = 0;
 
 	explain("NO","Unique clamd number");
 	explain("CONNTIME", "How long it is connected");
 	explain("LIV", "Total number of live threads");
 	explain("IDL", "Total number of idle threads");
 	explain("QUEUE", "Number of items in queue");
 	explain("MAXQ","Maximum number of items observed in queue");
 	explain("MEM", "Total memory usage (if available)");
 	explain("HOST", "Which clamd, local means unix socket");
 	explain("ENGINE", "Engine version");
 	explain("DBVER", "Database version");
 	explain("DBTIME", "Database publish time");
 	explain("Primary threads", "Threadpool used to receive commands");
3b12c12a
 	explain("Multiscan pool","Threadpool used for multiscan");
55885104
 	explain("live","Executing commands, or scanning");
 	explain("idle","Waiting for commands, will exit after idle_timeout");
 	explain("max", "Maximum number of threads configured for this pool");
 	explain("Queue","Tasks queued for processing, but not yet picked up by a thread");
 	explain("COMMAND","Command this thread is executing");
 	explain("QUEUEDSINCE","How long this task is executing");
 	explain("FILE","Which file it is processing (if applicable)");
 	explain("Mem","Memory usage reported by libc");
 	explain("Libc","Used/free memory reported by libc");
 	explain("Pool","Memory usage reported by libclamav's pool");
 
 	wrefresh(stdscr);
 	werase(status_bar_window);
 	wattron(status_bar_window, A_REVERSE);
 	mvwprintw(status_bar_window, 0, 0, "Press any key to exit help");
 	wattroff(status_bar_window, A_REVERSE);
 	wrefresh(status_bar_window);
 	/* getch() times out after a few seconds */
 	do {
 		ch = getch();
 		/* we do need to exit on resize, because the text scroll out of
 		 * view */
 	} while (ch == -1 /*|| ch == KEY_RESIZE*/);
 	return ch == KEY_RESIZE ? KEY_RESIZE : -1;
 }
 
e7fdfa9a
 int main(int argc, char *argv[])
 {
 	int ch = 0;
 	struct timeval tv_last, tv;
 	unsigned i;
 
 	atexit(cleanup);
 	setup_connections(argc, argv);
43ea5675
 	init_ncurses(global.num_clamd, default_colors);
aa22174b
 
 	memset(&tv_last, 0, sizeof(tv_last));
 	do {
e419cad6
 		if (toupper(ch) == 'H') {
879edca6
 			ch = show_help();
 		}
29d6dd3f
 		switch(ch) {
 			case KEY_RESIZE:
 				resize();
 				endwin();
 				refresh();
 				init_windows(global.num_clamd);
 				break;
 			case 'R':
e419cad6
 			case 'r':
ca9cb072
 				for (i=0;i<global.num_clamd;i++)
 					global.all_stats[i].biggest_queue = 1;
29d6dd3f
 				biggest_mem = 0;
 				break;
 			case KEY_UP:
 				if (global.num_clamd > 1) {
 					if (detail_selected == -1)
 						detail_selected = global.num_clamd-1;
 					else
 						--detail_selected;
 				}
 				break;
 			case KEY_DOWN:
 				if (global.num_clamd > 1) {
 					if (detail_selected == -1)
 						detail_selected = 0;
 					else {
 						if((unsigned)++detail_selected >= global.num_clamd)
 							detail_selected = -1;
 					}
 				}
 				break;
aa22174b
 		}
 		gettimeofday(&tv, NULL);
e7fdfa9a
 		header();
aa22174b
 		if(tv.tv_sec - tv_last.tv_sec >= MIN_INTERVAL) {
e7fdfa9a
 			free_global_stats();
 			for(i=0;i<global.num_clamd;i++) {
ca9cb072
 				unsigned biggest_q;
e7fdfa9a
 				struct stats *stats = &global.all_stats[i];
d737d793
 				if (global.conn[i].sd != -1)
b3b8212f
 					send_string(&global.conn[i], "nSTATS\n");
ca9cb072
 				biggest_q = stats->biggest_queue;
e7fdfa9a
 				memset(stats, 0, sizeof(*stats));
ca9cb072
 				stats->biggest_queue = biggest_q;
e7fdfa9a
 				parse_stats(&global.conn[i], stats, i);
aa22174b
 			}
1615194d
 			if (global.tasks)
 				qsort(global.tasks, global.n, sizeof(*global.tasks), tasks_compare);
aa22174b
 			tv_last = tv;
 		}
e7fdfa9a
 		/* always show, so that screen resizes take effect instantly*/
 		output_all();
1615194d
 		for(i=0;i<global.num_clamd;i++) {
 			if (global.conn[i].sd == -1)
 				reconnect(&global.conn[i]);
 		}
aa22174b
 	} while(toupper(ch = getch()) != 'Q');
e7fdfa9a
 	free_global_stats();
aa22174b
 	normal_exit = 1;
 	return 0;
 }