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