1 /*
2  *  ClamdTOP
3  *
4  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
5  *  Copyright (C) 2008-2013 Sourcefire, Inc.
6  *
7  *  Authors: Török Edvin
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License version 2 as
11  *  published by the Free Software Foundation.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21  *  MA 02110-1301, USA.
22  */
23 #define _GNU_SOURCE
24 #define __EXTENSIONS
25 #define GCC_PRINTF
26 #define GCC_SCANF
27 
28 #ifdef HAVE_CONFIG_H
29 #include "clamav-config.h"
30 #endif
31 
32 #ifdef HAVE_STDINT_H
33 #include <stdint.h>
34 #endif
35 
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/types.h>
43 #include CURSES_INCLUDE
44 #include <time.h>
45 #include <ctype.h>
46 #include <signal.h>
47 #ifdef _WIN32
48 #include <windows.h>
49 #include <winsock2.h>
50 /* this is not correct, perhaps winsock errors are not mapped on errno */
51 #define herror perror
52 #else
53 #include <netdb.h>
54 #include <arpa/inet.h>
55 #include <netinet/in.h>
56 #include <sys/socket.h>
57 #include <sys/un.h>
58 #include <sys/time.h>
59 #endif
60 #include <assert.h>
61 #include <errno.h>
62 
63 #include "platform.h"
64 
65 // libclamav
66 #include "clamav.h"
67 
68 // common
69 #include "optparser.h"
70 #include "misc.h"
71 
72 /* Types, prototypes and globals*/
73 typedef struct connection {
74     int sd;
75     char *remote;
76     int tcp;
77     struct timeval tv_conn;
78     char *version;
79     int line;
80 } conn_t;
81 
82 struct global_stats {
83     struct task *tasks;
84     ssize_t n;
85     struct stats *all_stats;
86     size_t num_clamd;
87     conn_t *conn;
88 };
89 
90 struct stats {
91     const char *remote;
92     char *engine_version;
93     char *db_version;
94     struct tm db_time;
95     const char *version;
96     int stats_unsupp;
97     uint8_t conn_hr, conn_min, conn_sec;
98     /* threads - primary */
99     unsigned prim_live, prim_idle, prim_max;
100     /* threads - sum */
101     unsigned live, idle, max;
102     /* queue */
103     unsigned biggest_queue, current_q;
104     double mem; /* in megabytes */
105     double heapu, mmapu, totalu, totalf, releasable, pools_used, pools_total;
106     unsigned pools_cnt;
107 };
108 
109 static void cleanup(void);
110 static int send_string_noreconn(conn_t *conn, const char *cmd);
111 static void send_string(conn_t *conn, const char *cmd);
112 static int read_version(conn_t *conn);
113 char *get_ip(const char *ip);
114 char *get_port(const char *ip);
115 char *make_ip(const char *host, const char *port);
116 
117 enum exit_reason {
118     FAIL_CMDLINE = 1,
119     FAIL_INITIAL_CONN,
120     OUT_OF_MEMORY,
121     RECONNECT_FAIL,
122     SIGINT_REASON
123 };
124 
125 static void exit_program(enum exit_reason reason, const char *func, unsigned line);
126 #if __GNUC__ >= 3
127 #define EXIT_PROGRAM(r) exit_program(r, __PRETTY_FUNCTION__, __LINE__);
128 #else
129 #define EXIT_PROGRAM(r) exit_program(r, "<unknown>", __LINE__);
130 #endif
131 #define OOM_CHECK(p)                         \
132     do {                                     \
133         if (!p) EXIT_PROGRAM(OUT_OF_MEMORY); \
134     } while (0)
135 
136 static struct global_stats global;
137 static SCREEN *curses_scr  = NULL;
138 static int curses_inited   = 0;
139 static int maxystats       = 0;
140 static int detail_selected = -1;
141 
detail_exists(void)142 static int detail_exists(void)
143 {
144     return global.num_clamd != 1;
145 }
146 
detail_is_selected(int idx)147 static int detail_is_selected(int idx)
148 {
149     if (!detail_exists()) {
150         assert(idx == 0);
151         return 1;
152     }
153     return idx == detail_selected;
154 }
155 
156 /* ---------------------- NCurses routines -----------------*/
157 enum colors {
158     header_color = 1,
159     version_color,
160     error_color,
161     value_color,
162     descr_color,
163     selected_color,
164     queue_header_color,
165     activ_color,
166     dim_color,
167     red_color,
168 };
169 
170 #define UPDATE_INTERVAL 2
171 #define MIN_INTERVAL 1
172 
173 /* the default color of the terminal in ncurses */
174 #define DEFAULT_COLOR -1
175 
176 #define VALUE_ATTR A_BOLD | COLOR_PAIR(value_color)
177 #define DESCR_ATTR COLOR_PAIR(descr_color)
178 #define ERROR_ATTR A_BOLD | COLOR_PAIR(error_color)
179 
180 static WINDOW *header_window     = NULL;
181 static WINDOW *stats_head_window = NULL;
182 static WINDOW *stats_window      = NULL;
183 static WINDOW *status_bar_window = NULL;
184 static WINDOW *mem_window        = NULL;
185 
186 static const char *status_bar_keys[10];
187 static unsigned maxy = 0, maxx = 0;
188 static char *queue_header       = NULL;
189 static char *multi_queue_header = NULL;
190 static char *clamd_header       = NULL;
191 
192 #define CMDHEAD " COMMAND       QUEUEDSINCE    FILE"
193 #define CMDHEAD2 "NO COMMAND     QUEUEDSINCE    FILE"
194 
195 /*
196  * CLAMD - which local/remote clamd this is
197  * CONNTIM - since when we are connected (TODO: zeroed at reconnect)
198  * QUEUE   - no of items in queue (total)
199  * MAXQUEUE - max no of items in queue observed
200  * LIVETHR - sum of live threads
201  * IDLETHR - sum of idle threads
202  */
203 #define SUMHEAD "NO CONNTIME LIV IDL QUEUE  MAXQ   MEM ENGINE  DBVER DBTIME        HOST"
204 
resize(void)205 static void resize(void)
206 {
207     char *p;
208     unsigned new_maxy, new_maxx;
209     getmaxyx(stdscr, new_maxy, new_maxx);
210     if (new_maxy == maxy && new_maxx == maxx)
211         return;
212     maxx = new_maxx;
213     maxy = new_maxy;
214     free(queue_header);
215     free(clamd_header);
216     queue_header = malloc(maxx + 1);
217     OOM_CHECK(queue_header);
218     clamd_header = malloc(maxx + 1);
219     OOM_CHECK(clamd_header);
220     assert(clamd_header && queue_header);
221     strncpy(queue_header, CMDHEAD, maxx);
222     strncpy(clamd_header, SUMHEAD, maxx);
223     queue_header[maxx] = '\0';
224     clamd_header[maxx] = '\0';
225     p                  = queue_header + strlen(queue_header);
226     while (p < queue_header + maxx)
227         *p++ = ' ';
228     p = clamd_header + strlen(clamd_header);
229     while (p < clamd_header + maxx)
230         *p++ = ' ';
231     if (global.num_clamd > 1) {
232         free(multi_queue_header);
233         multi_queue_header = malloc(maxx + 1);
234         OOM_CHECK(multi_queue_header);
235         assert(multi_queue_header);
236         strncpy(multi_queue_header, CMDHEAD2, maxx);
237         multi_queue_header[maxx] = '\0';
238         p                        = multi_queue_header + strlen(multi_queue_header);
239         while (p < multi_queue_header + maxx)
240             *p++ = ' ';
241     }
242 }
243 
rm_windows(void)244 static void rm_windows(void)
245 {
246     if (header_window) {
247         delwin(header_window);
248         header_window = NULL;
249     }
250     if (mem_window) {
251         delwin(mem_window);
252         mem_window = NULL;
253     }
254     if (stats_window) {
255         delwin(stats_window);
256         stats_window = NULL;
257     }
258     if (stats_head_window) {
259         delwin(stats_head_window);
260         stats_head_window = NULL;
261     }
262     if (status_bar_window) {
263         delwin(status_bar_window);
264         status_bar_window = NULL;
265     }
266 }
267 
init_windows(int num_clamd)268 static void init_windows(int num_clamd)
269 {
270     resize();
271 
272     rm_windows();
273     /* non-overlapping windows */
274     header_window     = subwin(stdscr, 1, maxx, 0, 0);
275     stats_head_window = subwin(stdscr, num_clamd + 1, maxx, 1, 0);
276     maxystats         = maxy - num_clamd - 3;
277     stats_window      = subwin(stdscr, maxystats, maxx, num_clamd + 2, 0);
278     status_bar_window = subwin(stdscr, 1, maxx, maxy - 1, 0);
279     /* memwindow overlaps, used only in details mode */
280     mem_window = derwin(stats_window, 6, 41, 1, maxx - 41);
281     touchwin(stdscr);
282     werase(stdscr);
283     refresh();
284     memset(status_bar_keys, 0, sizeof(status_bar_keys));
285     status_bar_keys[0] = "H - help";
286     status_bar_keys[1] = "Q - quit";
287     status_bar_keys[2] = "R - reset maximums";
288     if (num_clamd > 1) {
289         status_bar_keys[3] = "^ - previous clamd";
290         status_bar_keys[4] = "v - next clamd";
291     }
292 }
293 
init_ncurses(int num_clamd,int use_default)294 static void init_ncurses(int num_clamd, int use_default)
295 {
296     int default_bg = use_default ? DEFAULT_COLOR : COLOR_BLACK;
297     int default_fg = use_default ? DEFAULT_COLOR : COLOR_WHITE;
298 
299     /* newterm() allows us to free curses-allocated memory with delscreen() */
300     if (!(curses_scr = newterm(NULL, stdout, stdin))) {
301         fprintf(stderr, "Failed to initialize curses\n");
302         exit(EXIT_FAILURE);
303     }
304     curses_inited = 1;
305 
306     start_color();
307     keypad(stdscr, TRUE);            /* enable keyboard mapping */
308     nonl();                          /* tell curses not to do NL->CR/NL on output */
309     halfdelay(UPDATE_INTERVAL * 10); /* timeout of 2s when waiting for input*/
310     noecho();                        /* dont echo input */
311     curs_set(0);                     /* turn off cursor */
312     if (use_default)
313         use_default_colors();
314 
315     init_pair(header_color, COLOR_BLACK, COLOR_WHITE);
316     init_pair(version_color, default_fg, default_bg);
317     init_pair(error_color, COLOR_WHITE, COLOR_RED);
318     init_pair(value_color, COLOR_GREEN, default_bg);
319     init_pair(descr_color, COLOR_CYAN, default_bg);
320     init_pair(selected_color, COLOR_BLACK, COLOR_CYAN);
321     init_pair(queue_header_color, COLOR_BLACK, COLOR_GREEN);
322     init_pair(activ_color, COLOR_MAGENTA, default_bg);
323     init_pair(dim_color, COLOR_GREEN, default_bg);
324     init_pair(red_color, COLOR_RED, default_bg);
325 
326     init_windows(num_clamd);
327 }
328 
win_start(WINDOW * win,enum colors col)329 static void win_start(WINDOW *win, enum colors col)
330 {
331     wattrset(win, COLOR_PAIR(col));
332     wbkgd(win, COLOR_PAIR(col));
333     werase(win);
334 }
335 
print_colored(WINDOW * win,const char * p)336 static void print_colored(WINDOW *win, const char *p)
337 {
338     while (*p) {
339         wattron(win, DESCR_ATTR);
340         while (*p && !isdigit(*p))
341             waddch(win, *p++);
342         wattroff(win, DESCR_ATTR);
343         wattron(win, VALUE_ATTR);
344         while (*p && isdigit(*p))
345             waddch(win, *p++);
346         wattroff(win, VALUE_ATTR);
347     }
348 }
349 
header(void)350 static void header(void)
351 {
352     size_t i, x = 0;
353     time_t t;
354 
355     win_start(header_window, header_color);
356     mvwprintw(header_window, 0, 0, "  ClamdTOP version %s   ", get_version());
357     time(&t);
358     wprintw(header_window, "%s", ctime(&t));
359     wrefresh(header_window);
360 
361     /*
362     win_start(version_window, version_color);
363     mvwprintw(version_window, 0, 0, "Connected to: ");
364     print_colored(version_window, clamd_version ? clamd_version : "Unknown");
365     wrefresh(version_window);
366     */
367 
368     werase(status_bar_window);
369     for (i = 0; i < sizeof(status_bar_keys) / sizeof(status_bar_keys[0]); i++) {
370         const char *s = status_bar_keys[i];
371         if (!s)
372             continue;
373         wattron(status_bar_window, A_REVERSE);
374         if (s[0] == '^') {
375             mvwaddch(status_bar_window, 0, x, ACS_UARROW);
376             s++;
377             x++;
378         } else if (s[0] == 'v') {
379             mvwaddch(status_bar_window, 0, x, ACS_DARROW);
380             s++;
381             x++;
382         }
383         mvwprintw(status_bar_window, 0, x, "%s", s);
384         wattroff(status_bar_window, A_REVERSE);
385         x += strlen(s) + 1;
386     }
387     wrefresh(status_bar_window);
388 }
389 
show_bar(WINDOW * win,size_t i,unsigned live,unsigned idle,unsigned max,int blink)390 static void show_bar(WINDOW *win, size_t i, unsigned live, unsigned idle,
391                      unsigned max, int blink)
392 {
393     int y, x, z = 0;
394     unsigned len   = 39;
395     unsigned start = 1;
396     unsigned activ = max ? ((live - idle) * (len - start - 2) + (max / 2)) / max : 0;
397     unsigned dim   = max ? idle * (len - start - 2) / max : 0;
398     unsigned rem   = len - activ - dim - start - 2;
399 
400     assert(activ + 2 < len && activ + dim + 2 < len && activ + dim + rem + 2 < len && "Invalid values");
401     mvwaddch(win, i, start, '[' | A_BOLD);
402     wattron(win, A_BOLD | COLOR_PAIR(activ_color));
403     for (i = 0; i < activ; i++)
404         waddch(win, '|');
405     wattroff(win, A_BOLD | COLOR_PAIR(activ_color));
406     wattron(win, A_DIM | COLOR_PAIR(dim_color));
407     for (i = 0; i < dim; i++)
408         waddch(win, '|');
409     wattroff(win, A_DIM | COLOR_PAIR(dim_color));
410     for (i = 0; i < rem; i++)
411         waddch(win, ' ');
412     waddch(win, ']' | A_BOLD);
413     if (blink) {
414         getyx(win, y, x);
415         if ((x < 0) || (y < 0)) {
416             return; /* if getyx() failed, nevermind the blinking */
417         }
418         if (x >= 2) {
419             z = x - 2;
420         }
421         mvwaddch(win, y, z, '>' | A_BLINK | COLOR_PAIR(red_color));
422         move(y, z);
423     }
424 }
425 
426 /* --------------------- Error handling ---------------------*/
427 static int normal_exit         = 0;
428 static const char *exit_reason = NULL;
429 static const char *exit_func   = NULL;
430 static unsigned exit_line      = 0;
431 
cleanup(void)432 static void cleanup(void)
433 {
434     unsigned i;
435     if (curses_inited) {
436         if (status_bar_window) {
437             werase(status_bar_window);
438             wrefresh(status_bar_window);
439         }
440         rm_windows();
441         endwin();
442         delscreen(curses_scr);
443     }
444     curses_inited = 0;
445     for (i = 0; i < global.num_clamd; i++) {
446         if (global.conn[i].sd && global.conn[i].sd != -1) {
447             (void)send_string_noreconn(&global.conn[i], "nEND\n");
448 #ifndef _WIN32
449             close(global.conn[i].sd);
450 #else
451             closesocket(global.conn[i].sd);
452 #endif
453         }
454         free(global.conn[i].version);
455         free(global.conn[i].remote);
456     }
457     free(global.all_stats);
458     free(global.conn);
459     free(queue_header);
460     if (global.num_clamd > 1)
461         free(multi_queue_header);
462     free(clamd_header);
463     if (!normal_exit) {
464         fprintf(stderr, "Abnormal program termination");
465         if (exit_reason) fprintf(stderr, ": %s", exit_reason);
466         if (exit_func) fprintf(stderr, " in %s", exit_func);
467         if (exit_line) fprintf(stderr, " at line %u", exit_line);
468         fputc('\n', stderr);
469     }
470 }
471 
472 #ifdef __GNUC__
473 #define __noreturn __attribute__((noreturn))
474 #else
475 #define __noreturn
476 #endif
477 
exit_program(enum exit_reason reason,const char * func,unsigned line)478 static void __noreturn exit_program(enum exit_reason reason, const char *func, unsigned line)
479 {
480     switch (reason) {
481         case FAIL_CMDLINE:
482             exit_reason = "Invalid command-line arguments";
483             break;
484         case FAIL_INITIAL_CONN:
485             exit_reason = "Unable to connect to all clamds";
486             break;
487         case OUT_OF_MEMORY:
488             exit_reason = "Out of memory";
489             break;
490         case RECONNECT_FAIL:
491             exit_reason = "Failed to reconnect to clamd after connection was lost";
492             break;
493         case SIGINT_REASON:
494             exit_reason = "User interrupt";
495             break;
496         default:
497             exit_reason = "Unknown";
498             break;
499     }
500     exit_func = func;
501     exit_line = line;
502     exit(reason);
503 }
504 
505 struct task {
506     char *line;
507     double tim;
508     int clamd_no;
509 };
510 
tasks_compare(const void * a,const void * b)511 static int tasks_compare(const void *a, const void *b)
512 {
513     const struct task *ta = a;
514     const struct task *tb = b;
515     if (ta->tim < tb->tim)
516         return 1;
517     if (ta->tim > tb->tim)
518         return -1;
519     return 0;
520 }
521 
522 /* ----------- Socket routines ----------------------- */
523 #ifdef __GNUC__
524 static void print_con_info(conn_t *conn, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
525 #endif
print_con_info(conn_t * conn,const char * fmt,...)526 static void print_con_info(conn_t *conn, const char *fmt, ...)
527 {
528     va_list ap;
529     va_start(ap, fmt);
530     if (stats_head_window) {
531         char *buf = malloc(maxx + 1);
532         char *nl  = NULL;
533         OOM_CHECK(buf);
534         memset(buf, ' ', maxx + 1);
535         vsnprintf(buf, maxx + 1, fmt, ap);
536         if ((nl = strrchr(buf, '\n')) != NULL)
537             *nl = ' ';
538         buf[strlen(buf)] = ' ';
539         buf[maxx]        = '\0';
540         wattron(stats_head_window, ERROR_ATTR);
541         mvwprintw(stats_head_window, conn->line, 0, "%s", buf);
542         wattroff(stats_head_window, ERROR_ATTR);
543         wrefresh(stats_head_window);
544         free(buf);
545     } else
546         vfprintf(stdout, fmt, ap);
547     va_end(ap);
548 }
549 
get_ip(const char * ip)550 char *get_ip(const char *ip)
551 {
552     char *dupip = NULL;
553     char *p1    = NULL;
554     unsigned int i;
555 
556     /*
557      * Expected format of ip:
558      *     1) IPv4
559      *     2) IPv4:Port
560      *     3) IPv6
561      *     4) [IPv6]:Port
562      *
563      * Use of IPv6:Port is incorrect. An IPv6 address must be enclosed in brackets.
564      */
565 
566     dupip = strdup(ip);
567     if (!(dupip))
568         return NULL;
569 
570     if (dupip[0] == '[') {
571         /* IPv6 */
572         p1 = strchr(dupip, ']');
573         if (!(p1)) {
574             free(dupip);
575             return NULL;
576         }
577 
578         *p1 = '\0';
579 
580         p1 = strdup(dupip + 1);
581         free(dupip);
582         dupip = NULL;
583         return p1;
584     }
585 
586     p1 = dupip;
587     i  = 0;
588     while ((p1 = strchr(p1, ':'))) {
589         i++;
590         p1++;
591     }
592 
593     if (i == 1) {
594         p1  = strchr(dupip, ':');
595         *p1 = '\0';
596     }
597 
598     return dupip;
599 }
600 
get_port(const char * ip)601 char *get_port(const char *ip)
602 {
603     char *dupip, *p;
604     unsigned int offset = 0;
605 
606     dupip = get_ip(ip);
607     if (!(dupip))
608         return NULL;
609 
610     if (ip[0] == '[')
611         offset += 2;
612 
613     p = (char *)ip + strlen(dupip) + offset;
614     if (*p == ':') {
615         p = strdup(p + 1);
616         free(dupip);
617         return p;
618     }
619 
620     free(dupip);
621     return NULL;
622 }
623 
make_ip(const char * host,const char * port)624 char *make_ip(const char *host, const char *port)
625 {
626     char *ip;
627     size_t len;
628     int ipv6;
629 
630     if (!host || !port) {
631         return NULL;
632     }
633 
634     len = strlen(host) + strlen(port);
635 
636     ipv6 = (strchr(host, ':') != NULL);
637 
638     len += (ipv6 ? 4 : 3);
639 
640     ip = calloc(1, len);
641     if (!(ip))
642         return NULL;
643 
644     snprintf(ip, len, "%s%s%s:%s", ipv6 ? "[" : "", host, ipv6 ? "]" : "", port);
645 
646     return ip;
647 }
648 
make_connection_real(const char * soname,conn_t * conn)649 static int make_connection_real(const char *soname, conn_t *conn)
650 {
651     int s = -1;
652     struct timeval tv;
653     char *port = NULL;
654     char *pt   = NULL;
655     char *host = pt;
656     struct addrinfo hints, *res = NULL, *p;
657     int err;
658     int ret = 0;
659 
660     pt = strdup(soname);
661     OOM_CHECK(pt);
662 
663     conn->tcp = 0;
664 
665 #ifndef _WIN32
666     if (cli_is_abspath(soname) || (access(soname, F_OK) == 0)) {
667         struct sockaddr_un addr;
668 
669         s = socket(AF_UNIX, SOCK_STREAM, 0);
670         if (s < 0) {
671             perror("socket");
672             ret = -1;
673             goto done;
674         }
675 
676         memset(&addr, 0, sizeof(addr));
677         addr.sun_family = AF_UNIX;
678         strncpy(addr.sun_path, soname, sizeof(addr.sun_path));
679         addr.sun_path[sizeof(addr.sun_path) - 1] = 0x0;
680 
681         print_con_info(conn, "Connecting to: %s\n", soname);
682         if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) {
683             perror("connect");
684             close(s);
685             ret = -1;
686             goto done;
687         }
688 
689         goto end;
690     }
691 #endif
692 
693     memset(&hints, 0x00, sizeof(struct addrinfo));
694     hints.ai_family   = AF_UNSPEC;
695     hints.ai_socktype = SOCK_STREAM;
696     hints.ai_flags    = AI_PASSIVE;
697 
698     host = get_ip(soname);
699     if (!(host)) {
700         ret = -1;
701         goto done;
702     }
703 
704     port = get_port(soname);
705 
706     conn->tcp = 1;
707 
708     print_con_info(conn, "Looking up: %s:%s\n", host, port ? port : "3310");
709     if ((err = getaddrinfo(host, (port != NULL) ? port : "3310", &hints, &res))) {
710         print_con_info(conn, "Could not look up %s:%s, getaddrinfo returned: %s\n",
711                        host, port ? port : "3310", gai_strerror(err));
712         ret = -1;
713         goto done;
714     }
715 
716     for (p = res; p != NULL; p = p->ai_next) {
717         if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
718             perror("socket");
719             continue;
720         }
721 
722         print_con_info(conn, "Connecting to: %s\n", soname);
723         if (connect(s, p->ai_addr, p->ai_addrlen)) {
724             perror("connect");
725 #ifndef _WIN32
726             close(s);
727 #else
728             closesocket(s);
729 #endif
730             continue;
731         }
732 
733         break;
734     }
735 
736     if (p == NULL) {
737         ret = -1;
738         goto done;
739     }
740 
741 end:
742     conn->sd = s;
743     gettimeofday(&conn->tv_conn, NULL);
744     tv.tv_sec  = 30;
745     tv.tv_usec = 0;
746     setsockopt(conn->sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
747 
748     if (conn->remote != soname) {
749         /* when we reconnect, they are the same */
750         if (NULL != conn->remote) {
751             free(conn->remote);
752             conn->remote = NULL;
753         }
754         conn->remote = make_ip(host, (port != NULL) ? port : "3310");
755     }
756 
757 done:
758     if (NULL != res) {
759         freeaddrinfo(res);
760         res = NULL;
761     }
762 
763     if (NULL != pt) {
764         free(pt);
765         pt = NULL;
766     }
767 
768     if (NULL != host) {
769         free(host);
770         host = NULL;
771     }
772 
773     if (NULL != port) {
774         free(port);
775         port = NULL;
776     }
777 
778     return ret;
779 }
780 
make_connection(const char * soname,conn_t * conn)781 static int make_connection(const char *soname, conn_t *conn)
782 {
783     int rc;
784 
785     if (!soname) {
786         return -1;
787     }
788 
789     if ((rc = make_connection_real(soname, conn)))
790         return rc;
791 
792     send_string(conn, "nIDSESSION\nnVERSION\n");
793     free(conn->version);
794     conn->version = NULL;
795     if (!read_version(conn))
796         return 0;
797 
798     /* clamd < 0.95 */
799     if ((rc = make_connection_real(soname, conn)))
800         return rc;
801 
802     send_string(conn, "nSESSION\nnVERSION\n");
803     conn->version = NULL;
804     if (!read_version(conn))
805         return 0;
806 
807     return -1;
808 }
809 
810 static void reconnect(conn_t *conn);
811 
send_string_noreconn(conn_t * conn,const char * cmd)812 static int send_string_noreconn(conn_t *conn, const char *cmd)
813 {
814     assert(cmd);
815     assert(conn && conn->sd > 0);
816     return send(conn->sd, cmd, strlen(cmd), 0);
817 }
818 
send_string(conn_t * conn,const char * cmd)819 static void send_string(conn_t *conn, const char *cmd)
820 {
821     while (send_string_noreconn(conn, cmd) == -1) {
822         reconnect(conn);
823     }
824 }
825 
826 static int tries = 0;
reconnect(conn_t * conn)827 static void reconnect(conn_t *conn)
828 {
829     if (++tries > 3) {
830         EXIT_PROGRAM(RECONNECT_FAIL);
831     }
832     if (conn->sd != -1) {
833 #ifndef _WIN32
834         close(conn->sd);
835 #else
836         closesocket(conn->sd);
837 #endif
838     }
839     if (make_connection(conn->remote, conn) < 0) {
840         print_con_info(conn, "Unable to reconnect to %s: %s", conn->remote, strerror(errno));
841         EXIT_PROGRAM(RECONNECT_FAIL);
842     }
843     tries = 0;
844 }
845 
recv_line(conn_t * conn,char * buf,size_t len)846 static int recv_line(conn_t *conn, char *buf, size_t len)
847 {
848     assert(len > 0);
849     assert(conn);
850     assert(buf);
851 
852     len--;
853     if (!len || conn->sd == -1)
854         return 0;
855     assert(conn->sd > 0);
856     while (len > 0) {
857         ssize_t nread = recv(conn->sd, buf, len, MSG_PEEK);
858         if (nread <= 0) {
859             print_con_info(conn, "%s: %s", conn->remote, strerror(errno));
860             /* it could be a timeout, be nice and send an END */
861             (void)send_string_noreconn(conn, "nEND\n");
862 #ifndef _WIN32
863             close(conn->sd);
864 #else
865             closesocket(conn->sd);
866 #endif
867             conn->sd = -1;
868             return 0;
869         } else {
870             char *p = memchr(buf, '\n', nread);
871             if (p) {
872                 len = p - buf + 1;
873             } else
874                 len = nread;
875             assert(len > 0);
876             assert(len <= (size_t)nread);
877             nread = recv(conn->sd, buf, len, 0);
878             if (nread == -1)
879                 reconnect(conn);
880             else {
881                 assert(nread > 0 && (size_t)nread == len);
882                 buf += nread;
883             }
884             if (p)
885                 break;
886         }
887     }
888     *buf = '\0';
889     return 1;
890 }
891 
output_queue(size_t line,ssize_t max)892 static void output_queue(size_t line, ssize_t max)
893 {
894     ssize_t i, j;
895     int tasks_truncd            = 0;
896     struct task *tasks          = global.tasks;
897     struct task *filtered_tasks = calloc(global.n, sizeof(*filtered_tasks));
898     OOM_CHECK(filtered_tasks);
899     for (i = 0, j = 0; i < global.n; i++) {
900         if (detail_selected == -1 || detail_is_selected(tasks[i].clamd_no - 1)) {
901             filtered_tasks[j++] = tasks[i];
902         }
903     }
904 
905     wattron(stats_window, COLOR_PAIR(queue_header_color));
906     if (detail_selected == -1 && global.num_clamd > 1)
907         mvwprintw(stats_window, line, 0, "%s", multi_queue_header);
908     else
909         mvwprintw(stats_window, line, 0, "%s", queue_header);
910     wattroff(stats_window, COLOR_PAIR(queue_header_color));
911     if (max < j) {
912         --max;
913         tasks_truncd = 1;
914     }
915     if (max < 0) max = 0;
916     for (i = 0; i < j && i < max; i++) {
917         char *cmde;
918         assert(tasks);
919         cmde = strchr(filtered_tasks[i].line, ' ');
920         if (cmde) {
921             char cmd[16];
922             const char *filstart = strchr(cmde + 1, ' ');
923             strncpy(cmd, filtered_tasks[i].line, sizeof(cmd) - 1);
924             cmd[15] = '\0';
925             if (filtered_tasks[i].line + 15 > cmde)
926                 cmd[cmde - filtered_tasks[i].line] = '\0';
927             if (filstart) {
928                 size_t oldline = ++line;
929                 char *nl       = strrchr(++filstart, '\n');
930                 if (nl != NULL)
931                     *nl = '\0';
932                 wattron(stats_window, A_BOLD);
933                 if (detail_selected == -1 && global.num_clamd > 1)
934                     mvwprintw(stats_window, line, 0, "%2u %s", filtered_tasks[i].clamd_no, cmd + 1);
935                 else
936                     mvwprintw(stats_window, line, 0, " %s", cmd + 1);
937                 wattroff(stats_window, A_BOLD);
938                 mvwprintw(stats_window, line, 15, "%10.03fs", filtered_tasks[i].tim);
939                 mvwprintw(stats_window, line, 30, "%s", filstart);
940                 line = getcury(stats_window);
941                 if (line > oldline)
942                     max -= line - oldline;
943                 if (!tasks_truncd && max < j) {
944                     --max;
945                     tasks_truncd = 1;
946                 }
947             }
948         }
949     }
950     if (tasks_truncd) {
951         /* in summary mode we can only show a max amount of tasks */
952         wattron(stats_window, A_DIM | COLOR_PAIR(header_color));
953         mvwprintw(stats_window, maxystats - 1, 0, "*** %u more task(s) not shown ***", (unsigned)(j - i));
954         wattroff(stats_window, A_DIM | COLOR_PAIR(header_color));
955     }
956     free(filtered_tasks);
957 }
958 
959 /* ---------------------- stats parsing routines ------------------- */
960 
parse_queue(conn_t * conn,char * buf,size_t len,unsigned idx)961 static void parse_queue(conn_t *conn, char *buf, size_t len, unsigned idx)
962 {
963     do {
964         double tim;
965         const char *t = strchr(buf, ' ');
966         if (!t)
967             continue;
968         if (sscanf(t, "%lf", &tim) != 1)
969             continue;
970         ++global.n;
971         global.tasks = realloc(global.tasks, sizeof(*global.tasks) * global.n);
972         OOM_CHECK(global.tasks);
973         global.tasks[global.n - 1].line = strdup(buf);
974         OOM_CHECK(global.tasks[global.n - 1].line);
975         global.tasks[global.n - 1].tim      = tim;
976         global.tasks[global.n - 1].clamd_no = idx + 1;
977     } while (recv_line(conn, buf, len) && buf[0] == '\t' && strcmp("END\n", buf) != 0);
978 }
979 
980 static unsigned biggest_mem = 0;
981 
output_memstats(struct stats * stats)982 static void output_memstats(struct stats *stats)
983 {
984     char buf[128];
985     unsigned long totalmem;
986     int blink = 0;
987 
988     werase(mem_window);
989     if (stats->mem > 0 || (stats->mem >= 0 && (stats->pools_total > 0))) {
990         box(mem_window, 0, 0);
991 
992         if (stats->mem > 0)
993             snprintf(buf, sizeof(buf), "heap %4.0fM mmap %4.0fM unused%4.0fM",
994                      stats->heapu, stats->mmapu, stats->releasable);
995         else
996             snprintf(buf, sizeof(buf), "heap   N/A mmap   N/A unused  N/A");
997         mvwprintw(mem_window, 1, 1, "Mem:  ");
998         print_colored(mem_window, buf);
999 
1000         mvwprintw(mem_window, 2, 1, "Libc: ");
1001         if (stats->mem > 0)
1002             snprintf(buf, sizeof(buf), "used %4.0fM free %4.0fM total %4.0fM",
1003                      stats->totalu, stats->totalf, stats->totalu + stats->totalf);
1004         else
1005             snprintf(buf, sizeof(buf), "used   N/A free   N/A total   N/A");
1006         print_colored(mem_window, buf);
1007 
1008         mvwprintw(mem_window, 3, 1, "Pool: ");
1009         snprintf(buf, sizeof(buf), "count %4u used %4.0fM total %4.0fM",
1010                  stats->pools_cnt, stats->pools_used, stats->pools_total);
1011         print_colored(mem_window, buf);
1012 
1013         totalmem = (stats->heapu + stats->mmapu + stats->pools_total) * 1000;
1014         if (totalmem > biggest_mem) {
1015             biggest_mem = totalmem;
1016             blink       = 1;
1017         }
1018         show_bar(mem_window, 4, totalmem,
1019                  (stats->mmapu + stats->releasable + stats->pools_total - stats->pools_used) * 1000,
1020                  biggest_mem, blink);
1021     }
1022     wrefresh(mem_window);
1023 }
1024 
parse_memstats(const char * line,struct stats * stats)1025 static void parse_memstats(const char *line, struct stats *stats)
1026 {
1027     if (sscanf(line, " heap %lfM mmap %lfM used %lfM free %lfM releasable %lfM pools %u pools_used %lfM pools_total %lfM",
1028                &stats->heapu, &stats->mmapu, &stats->totalu, &stats->totalf, &stats->releasable,
1029                &stats->pools_cnt, &stats->pools_used, &stats->pools_total) != 8) {
1030         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",
1031                    &stats->pools_cnt, &stats->pools_used, &stats->pools_total) != 3) {
1032             stats->mem = -1;
1033             return;
1034         }
1035         stats->mem = 0;
1036         return;
1037     }
1038     stats->mem = stats->heapu + stats->mmapu + stats->pools_total;
1039 }
1040 
output_stats(struct stats * stats,unsigned idx)1041 static int output_stats(struct stats *stats, unsigned idx)
1042 {
1043     char buf[128];
1044     char timbuf[14];
1045     int blink = 0;
1046     size_t i  = 0;
1047     char mem[6];
1048     WINDOW *win = stats_head_window;
1049     int sel     = detail_is_selected(idx);
1050     char *line  = malloc(maxx + 1);
1051     int len     = 0;
1052 
1053     OOM_CHECK(line);
1054 
1055     if (stats->mem <= 0 || stats->stats_unsupp) {
1056         strncpy(mem, "N/A", sizeof(mem));
1057         mem[sizeof(mem) - 1] = '\0';
1058     } else {
1059         const char *format;
1060         char c;
1061         double s;
1062         if (stats->mem >= 1024) {
1063             c = 'G';
1064             s = stats->mem / 1024.0;
1065         } else {
1066             c = 'M';
1067             s = stats->mem;
1068         }
1069         if (s >= 99.95)
1070             format = "%.0f%c";
1071         else if (s >= 9.995)
1072             format = "%.1f%c";
1073         else
1074             format = "%.2f%c";
1075 
1076         snprintf(mem, sizeof(mem), format, s, c);
1077         mem[sizeof(mem) - 1] = '\0';
1078     }
1079     i = idx + 1;
1080 
1081     if (!stats->db_time.tm_year) {
1082         strncpy(timbuf, "N/A", sizeof(timbuf));
1083         timbuf[sizeof(timbuf) - 1] = '\0';
1084     } else
1085         snprintf(timbuf, sizeof(timbuf), "%04u-%02u-%02uT%02u",
1086                  1900 + stats->db_time.tm_year,
1087                  stats->db_time.tm_mon + 1,
1088                  stats->db_time.tm_mday,
1089                  stats->db_time.tm_hour);
1090 
1091     memset(line, ' ', maxx + 1);
1092     if (!stats->stats_unsupp) {
1093         len = snprintf(line, maxx + 1, "%2u %02u:%02u:%02u %3u %3u %5u %5u %5s %-7s %5s %-13s %s",
1094                        idx + 1, stats->conn_hr, stats->conn_min, stats->conn_sec,
1095                        stats->live, stats->idle,
1096                        stats->current_q, stats->biggest_queue,
1097                        mem,
1098                        stats->engine_version, stats->db_version, timbuf, stats->remote);
1099     } else {
1100         len = snprintf(line, maxx + 1, "%2u %02u:%02u:%02u N/A N/A   N/A   N/A   N/A %-7s %5s %-13s %s",
1101                        idx + 1, stats->conn_hr, stats->conn_min, stats->conn_sec,
1102                        stats->engine_version, stats->db_version, timbuf, stats->remote);
1103     }
1104     line[maxx]         = '\0';
1105     line[strlen(line)] = ' ';
1106     if (sel) {
1107         wattron(win, COLOR_PAIR(selected_color));
1108     }
1109     mvwprintw(win, i, 0, "%s", line);
1110     if (sel) {
1111         wattroff(win, COLOR_PAIR(selected_color));
1112     }
1113     if ((unsigned)len > maxx) {
1114         wattron(win, A_DIM | COLOR_PAIR(header_color));
1115         mvwprintw(win, i, maxx - 3, "...");
1116         wattroff(win, A_DIM | COLOR_PAIR(header_color));
1117     }
1118     win = stats_window;
1119     i   = 0;
1120     if (sel && !stats->stats_unsupp) {
1121         memset(line, ' ', maxx + 1);
1122         snprintf(line, maxx + 1, "Details for Clamd version: %s", stats->version);
1123         line[maxx]         = '\0';
1124         line[strlen(line)] = ' ';
1125         wattron(win, COLOR_PAIR(queue_header_color));
1126         mvwprintw(win, i++, 0, "%s", line);
1127         wattroff(win, COLOR_PAIR(queue_header_color));
1128         mvwprintw(win, i++, 0, "Primary threads: ");
1129         snprintf(buf, sizeof(buf), "live%3u idle%3u max%3u", stats->prim_live, stats->prim_idle, stats->prim_max);
1130         print_colored(win, buf);
1131         show_bar(win, i++, stats->prim_live, stats->prim_idle, stats->prim_max, 0);
1132         /*
1133         mvwprintw(win, i++, 0, "Multiscan pool : ");
1134         snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->live, stats->idle, stats->max);
1135         print_colored(win, buf);
1136         show_bar(win, i++, stats->live, stats->idle, stats->max, 0);
1137         */
1138 
1139         blink = 0;
1140         if (stats->current_q > stats->biggest_queue) {
1141             stats->biggest_queue = stats->current_q;
1142             blink                = 1;
1143         }
1144         mvwprintw(win, i++, 0, "Queue:");
1145         snprintf(buf, sizeof(buf), "%6u items %6u max", stats->current_q, stats->biggest_queue);
1146         print_colored(win, buf);
1147         show_bar(win, i++, stats->current_q, 0, stats->biggest_queue, blink);
1148         i += 2;
1149         werase(mem_window);
1150         output_memstats(stats);
1151     }
1152     free(line);
1153     return i;
1154 }
1155 
output_all(void)1156 static void output_all(void)
1157 {
1158     unsigned i, stats_line = 0;
1159     werase(stats_head_window);
1160     werase(stats_window);
1161     wattron(stats_head_window, COLOR_PAIR(queue_header_color));
1162     mvwprintw(stats_head_window, 0, 0, "%s", clamd_header);
1163     wattroff(stats_head_window, COLOR_PAIR(queue_header_color));
1164     for (i = 0; i < global.num_clamd; i++) {
1165         unsigned j = output_stats(&global.all_stats[i], i);
1166         if (j > stats_line)
1167             stats_line = j;
1168     }
1169     output_queue(stats_line, maxystats - stats_line - 1);
1170     wrefresh(stats_head_window);
1171     wrefresh(stats_window);
1172     if (detail_exists()) {
1173         /* overlaps, must be done at the end */
1174         wrefresh(mem_window);
1175     }
1176 }
1177 
parse_stats(conn_t * conn,struct stats * stats,unsigned idx)1178 static void parse_stats(conn_t *conn, struct stats *stats, unsigned idx)
1179 {
1180     char buf[1025];
1181     size_t j;
1182     struct timeval tv;
1183     unsigned conn_dt;
1184     int primary = 0;
1185     const char *pstart, *p, *vstart;
1186 
1187     if (conn->tcp)
1188         stats->remote = conn->remote;
1189     else
1190         stats->remote = "local";
1191 
1192     if (!conn->version) {
1193         stats->engine_version = strdup("???");
1194         OOM_CHECK(stats->engine_version);
1195         return;
1196     }
1197     p = pstart = vstart = strchr(conn->version, ' ');
1198     if (!vstart) {
1199         stats->engine_version = strdup("???");
1200         OOM_CHECK(stats->engine_version);
1201         return;
1202     }
1203     /* find digit in version */
1204     while (*p && !isdigit(*p))
1205         p++;
1206     /* rewind to first space or dash */
1207     while (p > pstart && *p && *p != ' ' && *p != '-')
1208         p--;
1209     if (*p) p++;
1210     /* keep only base version, and cut -exp, and -gittags */
1211     pstart = p;
1212     while (*p && *p != ' ' && *p != '-' && *p != '/')
1213         p++;
1214 
1215     stats->engine_version = malloc(p - pstart + 1);
1216     OOM_CHECK(stats->engine_version);
1217 
1218     memcpy(stats->engine_version, pstart, p - pstart);
1219     stats->engine_version[p - pstart] = '\0';
1220 
1221     pstart = strchr(p, '/');
1222     if (!pstart) {
1223         stats->db_version = strdup("????");
1224         OOM_CHECK(stats->db_version);
1225     } else {
1226         pstart++;
1227         p = strchr(pstart, '/');
1228         if (!p)
1229             p = pstart + strlen(pstart);
1230         stats->db_version = malloc(p - pstart + 1);
1231         OOM_CHECK(stats->db_version);
1232         memcpy(stats->db_version, pstart, p - pstart);
1233         stats->db_version[p - pstart] = '\0';
1234         if (*p) p++;
1235         if (!*p || !strptime(p, "%a %b  %d %H:%M:%S %Y", &stats->db_time)) {
1236             memset(&stats->db_time, 0, sizeof(stats->db_time));
1237         }
1238     }
1239     if (maxx > 61 && strlen(stats->db_version) > (maxx - 61)) {
1240         stats->db_version[maxx - 61] = '\0';
1241     }
1242 
1243     stats->version = vstart; /* for details view */
1244     gettimeofday(&tv, NULL);
1245     tv.tv_sec -= conn->tv_conn.tv_sec;
1246     tv.tv_usec -= conn->tv_conn.tv_usec;
1247     conn_dt = tv.tv_sec + tv.tv_usec / 1e6;
1248 
1249     stats->live = stats->idle = stats->max = 0;
1250     stats->conn_hr                         = conn_dt / 3600;
1251     stats->conn_min                        = (conn_dt / 60) % 60;
1252     stats->conn_sec                        = conn_dt % 60;
1253     stats->current_q                       = 0;
1254     buf[sizeof(buf) - 1]                   = 0x0;
1255     while (recv_line(conn, buf, sizeof(buf) - 1) && strcmp("END\n", buf) != 0) {
1256         char *val = strchr(buf, ':');
1257 
1258         if (buf[0] == '\t') {
1259             parse_queue(conn, buf, sizeof(buf) - 1, idx);
1260             continue;
1261         } else if (val)
1262             *val++ = '\0';
1263         if (!strcmp("MEMSTATS", buf)) {
1264             parse_memstats(val, stats);
1265             continue;
1266         }
1267         if (!strncmp("UNKNOWN COMMAND", buf, 15)) {
1268             stats->stats_unsupp = 1;
1269             break;
1270         }
1271         for (j = 1; j < strlen(buf); j++)
1272             buf[j] = tolower(buf[j]);
1273         /*
1274         mvwprintw(win, i, 0, "%s", buf);
1275         if(!val) {
1276             i++;
1277             continue;
1278         }
1279         waddch(win, ':');
1280         print_colored(win, val);
1281         i++;
1282         */
1283         if (!strncmp("State", buf, 5)) {
1284             if (strstr(val, "PRIMARY")) {
1285                 /* primary thread pool */
1286                 primary = 1;
1287             } else {
1288                 /* multiscan pool */
1289                 primary = 0;
1290             }
1291         }
1292         if (!strcmp("Threads", buf)) {
1293             unsigned live, idle, max;
1294             if (sscanf(val, " live %u idle %u max %u", &live, &idle, &max) != 3)
1295                 continue;
1296             if (primary) {
1297                 stats->prim_live = live;
1298                 stats->prim_idle = idle;
1299                 assert(!stats->prim_max && "There can be only one primary pool!");
1300                 stats->prim_max = max;
1301             }
1302             stats->live += live;
1303             stats->idle += idle;
1304             stats->max += max;
1305         } else if (!strcmp("Queue", buf)) {
1306             unsigned len;
1307             if (sscanf(val, "%u", &len) != 1)
1308                 continue;
1309             stats->current_q += len;
1310         }
1311     }
1312 }
1313 
read_version(conn_t * conn)1314 static int read_version(conn_t *conn)
1315 {
1316     char buf[1024];
1317     unsigned i;
1318     if (!recv_line(conn, buf, sizeof(buf)))
1319         return -1;
1320     if (!strcmp(buf, "UNKNOWN COMMAND\n"))
1321         return -2;
1322 
1323     conn->version = strdup(buf);
1324     OOM_CHECK(conn->version);
1325     for (i = 0; i < strlen(conn->version); i++)
1326         if (conn->version[i] == '\n')
1327             conn->version[i] = ' ';
1328     return 0;
1329 }
1330 
sigint(int a)1331 static void sigint(int a)
1332 {
1333     UNUSEDPARAM(a);
1334     EXIT_PROGRAM(SIGINT_REASON);
1335 }
1336 
help(void)1337 static void help(void)
1338 {
1339     printf("\n");
1340     printf("                       Clam AntiVirus: Monitoring Tool %s\n", get_version());
1341     printf("           By The ClamAV Team: https://www.clamav.net/about.html#credits\n");
1342     printf("           (C) 2022 Cisco Systems, Inc.\n");
1343     printf("\n");
1344     printf("    clamdtop [-hVc] [host[:port] /path/to/clamd.socket ...]\n");
1345     printf("\n");
1346     printf("    --help                 -h         Show this help\n");
1347     printf("    --version              -V         Show version\n");
1348     printf("    --config-file=FILE     -c FILE    Read clamd's configuration files from FILE\n");
1349     printf("    --defaultcolors        -d         Use default terminal colors\n");
1350     printf("    host[:port]                       Connect to clamd on host at port (default 3310)\n");
1351     printf("    /path/to/clamd.socket             Connect to clamd over a local socket\n");
1352     printf("\n");
1353     return;
1354 }
1355 static int default_colors = 0;
1356 /* -------------------------- Initialization ---------------- */
setup_connections(int argc,char * argv[])1357 static void setup_connections(int argc, char *argv[])
1358 {
1359     struct optstruct *opts;
1360     struct optstruct *clamd_opts;
1361     unsigned i;
1362     char *conn = NULL;
1363 
1364     opts = optparse(NULL, argc, argv, 1, OPT_CLAMDTOP, 0, NULL);
1365     if (!opts) {
1366         fprintf(stderr, "ERROR: Can't parse command line options\n");
1367         EXIT_PROGRAM(FAIL_CMDLINE);
1368     }
1369 
1370     if (optget(opts, "help")->enabled) {
1371         optfree(opts);
1372         help();
1373         normal_exit = 1;
1374         exit(0);
1375     }
1376 
1377     if (optget(opts, "version")->enabled) {
1378         printf("Clam AntiVirus Monitoring Tool %s\n", get_version());
1379         optfree(opts);
1380         normal_exit = 1;
1381         exit(0);
1382     }
1383 
1384     if (optget(opts, "defaultcolors")->enabled)
1385         default_colors = 1;
1386 
1387     memset(&global, 0, sizeof(global));
1388     if (!opts->filename || !opts->filename[0]) {
1389         const struct optstruct *opt;
1390         const char *clamd_conf = optget(opts, "config-file")->strarg;
1391 
1392         if ((clamd_opts = optparse(clamd_conf, 0, NULL, 1, OPT_CLAMD, 0, NULL)) == NULL) {
1393             fprintf(stderr, "Can't parse clamd configuration file %s\n", clamd_conf);
1394             EXIT_PROGRAM(FAIL_CMDLINE);
1395         }
1396 
1397         if ((opt = optget(clamd_opts, "LocalSocket"))->enabled) {
1398             conn = strdup(opt->strarg);
1399             if (!conn) {
1400                 fprintf(stderr, "Can't strdup LocalSocket value\n");
1401                 EXIT_PROGRAM(FAIL_INITIAL_CONN);
1402             }
1403         } else if ((opt = optget(clamd_opts, "TCPSocket"))->enabled) {
1404             char buf[512];
1405             const struct optstruct *opt_addr;
1406             const char *host = "localhost";
1407             if ((opt_addr = optget(clamd_opts, "TCPAddr"))->enabled) {
1408                 host = opt_addr->strarg;
1409             }
1410             snprintf(buf, sizeof(buf), "%lld", opt->numarg);
1411             conn = make_ip(host, buf);
1412         } else {
1413             fprintf(stderr, "Can't find how to connect to clamd\n");
1414             EXIT_PROGRAM(FAIL_INITIAL_CONN);
1415         }
1416 
1417         optfree(clamd_opts);
1418         global.num_clamd = 1;
1419     } else {
1420         unsigned i = 0;
1421         while (opts->filename[i]) {
1422             i++;
1423         }
1424         global.num_clamd = i;
1425     }
1426 
1427 #ifdef _WIN32
1428     WSADATA wsaData;
1429     if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) {
1430         fprintf(stderr, "Error at WSAStartup(): %d\n", WSAGetLastError());
1431         EXIT_PROGRAM(FAIL_INITIAL_CONN);
1432     }
1433 #endif
1434     /* clamdtop */
1435     puts("        __                    ____");
1436     puts("  _____/ /___ _____ ___  ____/ / /_____  ____");
1437     puts(" / ___/ / __ `/ __ `__ \\/ __  / __/ __ \\/ __ \\");
1438     puts("/ /__/ / /_/ / / / / / / /_/ / /_/ /_/ / /_/ /");
1439     puts("\\___/_/\\__,_/_/ /_/ /_/\\__,_/\\__/\\____/ .___/");
1440     puts("                                     /_/");
1441 
1442     global.all_stats = calloc(global.num_clamd, sizeof(*global.all_stats));
1443     OOM_CHECK(global.all_stats);
1444     global.conn = calloc(global.num_clamd, sizeof(*global.conn));
1445     OOM_CHECK(global.conn);
1446 
1447     for (i = 0; i < global.num_clamd; i++) {
1448         const char *soname;
1449         if (!conn && !opts->filename) {
1450             soname = NULL;
1451         } else {
1452             soname = conn ? conn : opts->filename[i];
1453         }
1454         global.conn[i].line = i + 1;
1455         if (make_connection(soname, &global.conn[i]) < 0) {
1456             EXIT_PROGRAM(FAIL_INITIAL_CONN);
1457         }
1458     }
1459 
1460     optfree(opts);
1461     free(conn);
1462 #ifndef _WIN32
1463     signal(SIGPIPE, SIG_IGN);
1464     signal(SIGINT, sigint);
1465 #endif
1466 }
1467 
free_global_stats(void)1468 static void free_global_stats(void)
1469 {
1470     unsigned i;
1471     for (i = 0; i < (unsigned)global.n; i++) {
1472         free(global.tasks[i].line);
1473     }
1474     for (i = 0; i < global.num_clamd; i++) {
1475         free(global.all_stats[i].engine_version);
1476         free(global.all_stats[i].db_version);
1477     }
1478     free(global.tasks);
1479     global.tasks = NULL;
1480     global.n     = 0;
1481 }
1482 
1483 static int help_line;
explain(const char * abbrev,const char * msg)1484 static void explain(const char *abbrev, const char *msg)
1485 {
1486     wattron(stdscr, A_BOLD);
1487     mvwprintw(stdscr, help_line++, 0, "%-15s", abbrev);
1488     wattroff(stdscr, A_BOLD);
1489     wprintw(stdscr, "  %s", msg);
1490 }
1491 
show_help(void)1492 static int show_help(void)
1493 {
1494     int ch;
1495     werase(stdscr);
1496     help_line = 0;
1497 
1498     explain("NO", "Unique clamd number");
1499     explain("CONNTIME", "How long it is connected");
1500     explain("LIV", "Total number of live threads");
1501     explain("IDL", "Total number of idle threads");
1502     explain("QUEUE", "Number of items in queue");
1503     explain("MAXQ", "Maximum number of items observed in queue");
1504     explain("MEM", "Total memory usage (if available)");
1505     explain("ENGINE", "Engine version");
1506     explain("DBVER", "Database version");
1507     explain("DBTIME", "Database publish time");
1508     explain("HOST", "Which clamd, local means unix socket");
1509     explain("Primary threads", "Threadpool used to receive commands");
1510     /*explain("Multiscan pool", "Threadpool used for multiscan");*/
1511     explain("live", "Executing commands, or scanning");
1512     explain("idle", "Waiting for commands, will exit after idle_timeout");
1513     explain("max", "Maximum number of threads configured for this pool");
1514     explain("Queue", "Tasks queued for processing, but not yet picked up by a thread");
1515     explain("COMMAND", "Command this thread is executing");
1516     explain("QUEUEDSINCE", "How long this task is executing");
1517     explain("FILE", "Which file it is processing (if applicable)");
1518     explain("Mem", "Memory usage reported by libc");
1519     explain("Libc", "Used/free memory reported by libc");
1520     explain("Pool", "Memory usage reported by libclamav's pool");
1521 
1522     wrefresh(stdscr);
1523     werase(status_bar_window);
1524     wattron(status_bar_window, A_REVERSE);
1525     mvwprintw(status_bar_window, 0, 0, "Press any key to exit help");
1526     wattroff(status_bar_window, A_REVERSE);
1527     wrefresh(status_bar_window);
1528     /* getch() times out after a few seconds */
1529     do {
1530         ch = getch();
1531         /* we do need to exit on resize, because the text scroll out of
1532          * view */
1533     } while (ch == -1 /*|| ch == KEY_RESIZE*/);
1534     return ch == KEY_RESIZE ? KEY_RESIZE : -1;
1535 }
1536 
main(int argc,char * argv[])1537 int main(int argc, char *argv[])
1538 {
1539     int ch = 0;
1540     struct timeval tv_last, tv;
1541     unsigned i;
1542 
1543     atexit(cleanup);
1544     setup_connections(argc, argv);
1545     init_ncurses(global.num_clamd, default_colors);
1546 
1547     memset(&tv_last, 0, sizeof(tv_last));
1548     do {
1549         if (toupper(ch) == 'H') {
1550             ch = show_help();
1551         }
1552         switch (ch) {
1553             case KEY_RESIZE:
1554                 resize();
1555                 endwin();
1556                 refresh();
1557                 init_windows(global.num_clamd);
1558                 break;
1559             case 'R':
1560             case 'r':
1561                 for (i = 0; i < global.num_clamd; i++)
1562                     global.all_stats[i].biggest_queue = 1;
1563                 biggest_mem = 0;
1564                 break;
1565             case KEY_UP:
1566                 if (global.num_clamd > 1) {
1567                     if (detail_selected == -1)
1568                         detail_selected = global.num_clamd - 1;
1569                     else
1570                         --detail_selected;
1571                 }
1572                 break;
1573             case KEY_DOWN:
1574                 if (global.num_clamd > 1) {
1575                     if (detail_selected == -1)
1576                         detail_selected = 0;
1577                     else {
1578                         if ((unsigned)++detail_selected >= global.num_clamd)
1579                             detail_selected = -1;
1580                     }
1581                 }
1582                 break;
1583         }
1584         gettimeofday(&tv, NULL);
1585         header();
1586         if (tv.tv_sec - tv_last.tv_sec >= MIN_INTERVAL) {
1587             free_global_stats();
1588             for (i = 0; i < global.num_clamd; i++) {
1589                 unsigned biggest_q;
1590                 struct stats *stats = &global.all_stats[i];
1591                 if (global.conn[i].sd != -1)
1592                     send_string(&global.conn[i], "nSTATS\n");
1593                 biggest_q = stats->biggest_queue;
1594                 memset(stats, 0, sizeof(*stats));
1595                 stats->biggest_queue = biggest_q;
1596                 parse_stats(&global.conn[i], stats, i);
1597             }
1598             if (global.tasks)
1599                 qsort(global.tasks, global.n, sizeof(*global.tasks), tasks_compare);
1600             tv_last = tv;
1601         }
1602         /* always show, so that screen resizes take effect instantly*/
1603         output_all();
1604         for (i = 0; i < global.num_clamd; i++) {
1605             if (global.conn[i].sd == -1)
1606                 reconnect(&global.conn[i]);
1607         }
1608     } while (toupper(ch = getch()) != 'Q');
1609     free_global_stats();
1610     normal_exit = 1;
1611     return 0;
1612 }
1613