963cbca163712c6515b728fa3bfe627ddd387c39
[claws.git] / src / plugins / clamd / libclamd / clamd-plugin.c
1 /* vim: set textwidth=80 tabstop=4: */
2
3 /*
4  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
5  * Copyright (C) 1999-2017 Michael Rasmussen and the Claws Mail Team
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  * 
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #       include "config.h"
24 #endif
25
26 #include "defs.h"
27
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
31 #include <gtk/gtkutils.h>
32
33 #include <stdio.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <netinet/in.h>
42 #include <sys/un.h>
43 #include <arpa/inet.h>
44 #include <netdb.h>
45 #include <errno.h>
46
47 #include "common/claws.h"
48 #include "common/version.h"
49 #include "plugin.h"
50 #include "utils.h"
51 #include "prefs.h"
52 #include "folder.h"
53 #include "prefs_gtk.h"
54 #include "foldersel.h"
55 #include "statusbar.h"
56 #include "alertpanel.h"
57 #include "clamd-plugin.h"
58
59 #ifndef UNIX_PATH_MAX
60 #define UNIX_PATH_MAX 108
61 #endif
62
63 /* needs to be generic */
64 static const gchar* config_dirs[] = { 
65         "/etc", 
66         "/usr/local/etc",
67         "/etc/clamav",
68         "/usr/local/etc/clamav",
69         NULL };
70
71 static const gchar* clamd_tokens[] = {
72         "LocalSocket",
73         "TCPSocket",
74         "TCPAddr",
75         NULL };
76
77 static Clamd_Socket* Socket = NULL;
78 static Config* config = NULL;
79
80 /**
81  *  clamd commands used
82  *  prefixing with either z or n is recommended
83  *  z <=> null terminated command
84  *  n <=> newline terminated command
85  */
86 static const gchar ping[] = "nPING\n";
87 static const gchar version[] = "nVERSION\n";
88 static const gchar scan[] = "nSCAN";
89 static const gchar contscan[] = "nCONTSCAN";
90 static const gchar instream[10] = "zINSTREAM\0";
91
92 void clamd_create_config_automatic(const gchar* path) {
93         FILE* conf;
94         char buf[1024];
95         gchar* key = NULL;
96         gchar* value = NULL;
97
98         /*debug_set_mode(TRUE);*/
99         /*debug_print("%s : %s\n", folder, path);*/
100         if (! path) {
101                 g_warning("Missing path");
102                 return;
103         }
104         if (config && config->ConfigType == AUTOMATIC &&
105                         config->automatic.folder &&
106                         strcmp(config->automatic.folder, path) == 0) {
107                 debug_print("%s : %s - Identical. No need to read again\n",
108                         config->automatic.folder, path);
109                 return;
110         }
111         if (config)
112                 clamd_config_free(config);
113         config = clamd_config_new();
114         
115         config->ConfigType = AUTOMATIC;
116         config->automatic.folder = g_strdup(path);
117         debug_print("Opening %s to parse config file\n", path);
118         conf = fopen(path, "r");
119         if (!conf) {
120                 /*g_error("%s: Unable to open", path);*/
121                 alertpanel_error(_("%s: Unable to open\nclamd will be disabled"), path);
122                 return;
123         }
124         while (fgets(buf, sizeof(buf), conf)) {
125                 g_strstrip(buf);
126                 if (buf[0] == '#')
127                         continue;
128                 const gchar** tokens = clamd_tokens;
129                 while (*tokens) {
130                         const gchar* token = *tokens++;
131                         if ((key = g_strstr_len(buf, strlen(buf), token)) != NULL) {
132                                 gchar* tmp = &(*(key + strlen(token)));
133                                 tmp = g_strchug(tmp);
134                                 gchar* end = index(tmp, '#');
135                                 if (end)
136                                         value = g_strndup(tmp, end - tmp);
137                                 else
138                                         value = g_strdup(g_strchomp(tmp));
139                                 if (strcmp(clamd_tokens[0], token) == 0) {
140                                         /* UNIX socket */
141                                         Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket));
142                                         if (Socket) {
143                                                 Socket->socket.host = NULL;
144                                                 Socket->socket.port = -1;
145                                                 Socket->type = UNIX_SOCKET;
146                                                 Socket->socket.path = g_strdup(value);
147                                                 g_free(value);
148                                                 value = NULL;
149                                                 fclose(conf);
150                                                 debug_print("clamctl: %s\n", Socket->socket.path);
151                                                 return;
152                                         }
153                                 }
154                                 else if (strcmp(clamd_tokens[1], token) == 0) {
155                                         /* INET socket */
156                                         if (! Socket) {
157                                                 Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket));
158                                                 if (Socket) {
159                                                         Socket->socket.path = NULL;
160                                                         Socket->socket.port = -1;
161                                                         Socket->type = INET_SOCKET;
162                                                         Socket->socket.port = atoi(value);
163                                                         Socket->socket.host = g_strdup("localhost");
164                                                         g_free(value);
165                                                         value = NULL;
166                                                         debug_print("clamctl: %s:%d\n", 
167                                                                 Socket->socket.host, Socket->socket.port);
168                                                 }
169                                         }
170                                         else {
171                                                 Socket->type = INET_SOCKET;
172                                                 Socket->socket.port = atoi(value);
173                                                 g_free(value);
174                                                 value = NULL;
175                                                 if (! Socket->socket.host)
176                                                         Socket->socket.host = g_strdup("localhost");
177                                                 debug_print("clamctl: %s:%d\n", 
178                                                         Socket->socket.host, Socket->socket.port);
179                                         }
180                                         /* We must continue since TCPAddr could also be configured */
181                                 }
182                                 else if (strcmp(clamd_tokens[2], token) == 0) {
183                                         if (! Socket) {
184                                                 Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket));
185                                                 if (Socket) {
186                                                         Socket->socket.path = NULL;
187                                                         Socket->socket.port = 3310; /* default port */
188                                                         Socket->type = INET_SOCKET;
189                                                         Socket->socket.host = g_strdup(value);
190                                                         g_free(value);
191                                                         value = NULL;
192                                                         debug_print("clamctl: %s:%d\n", 
193                                                                 Socket->socket.host, Socket->socket.port);
194                                                 }
195                                         }
196                                         else {
197                                                 Socket->type = INET_SOCKET;
198                                                 if (Socket->socket.host)
199                                                         g_free(Socket->socket.host);
200                                                 Socket->socket.host = g_strdup(value);
201                                                 g_free(value);
202                                                 value = NULL;
203                                                 if (Socket->socket.port == -1)
204                                                         Socket->socket.port = 3310;
205                                                 debug_print("clamctl: %s:%d\n", 
206                                                         Socket->socket.host, Socket->socket.port);
207                                         }
208                                         /* We must continue since TCPSocket could also be configured */
209                                 }
210                         }
211                 }
212         }
213         fclose(conf);
214         if (! (Socket && (Socket->socket.port || Socket->socket.path))) {
215                 /*g_error("%s: Not able to find required information", path);*/
216                 alertpanel_error(_("%s: Not able to find required information\nclamd will be disabled"), path);
217         }
218         /*debug_set_mode(FALSE);*/
219 }
220
221 void clamd_create_config_manual(const gchar* host, int port) {
222         if (! host || port < 1) {
223                 g_warning("Missing host or port < 1");
224                 return;
225         }
226         if (config && config->ConfigType == MANUAL &&
227                         config->manual.host && config->manual.port == port &&
228                         strcmp(config->manual.host, host) == 0) {
229                 debug_print("%s : %s and %d : %d - Identical. No need to read again\n",
230                         config->manual.host, host, config->manual.port, port);
231                 return;
232         }
233
234         if (config)
235                 clamd_config_free(config);
236         config = clamd_config_new();
237         
238         config->ConfigType = MANUAL;
239         config->manual.host = g_strdup(host);
240         config->manual.port = port;
241         /* INET socket */
242         Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket));
243         if (Socket) {
244                 Socket->type = INET_SOCKET;
245                 Socket->socket.port = port;
246                 Socket->socket.host = g_strdup(host);
247         }
248         else {
249                 /*g_error("%s: Not able to find required information", path);*/
250                 alertpanel_error(_("Could not create socket"));
251         }
252 }
253
254 gboolean clamd_find_socket() {
255         const gchar** config_dir = config_dirs;
256         gchar *clamd_conf = NULL;
257         
258         while (*config_dir) {
259                 clamd_conf = g_strdup_printf("%s/clamd.conf", *config_dir++);
260                 debug_print("Looking for %s\n", clamd_conf);
261                 if (g_file_test(clamd_conf, G_FILE_TEST_EXISTS))
262                         break;
263                 g_free(clamd_conf);
264                 clamd_conf = NULL;
265         }
266         if (! clamd_conf)
267                 return FALSE;
268
269         debug_print("Using %s to find configuration\n", clamd_conf);
270         clamd_create_config_automatic(clamd_conf);
271         g_free(clamd_conf);
272
273         return TRUE;
274 }
275
276 Config* clamd_get_config() {
277         return config;
278 }
279
280 Clamd_Socket* clamd_get_socket() {
281         return Socket;
282 }
283
284 static int create_socket() {
285         struct sockaddr_un addr_u;
286         struct sockaddr_in addr_i;
287         struct hostent *hp;
288
289         int new_sock = -1;
290
291         /*debug_set_mode(TRUE);*/
292         if (! Socket) {
293                 return -1;
294         }
295         memset(&addr_u, 0, sizeof(addr_u));
296         memset(&addr_i, 0, sizeof(addr_i));
297         debug_print("socket->type: %d\n", Socket->type);
298         switch (Socket->type) {
299                 case UNIX_SOCKET:
300                         debug_print("socket path: %s\n", Socket->socket.path);
301                         new_sock = socket(PF_UNIX, SOCK_STREAM, 0);
302                         if (new_sock < 0) {
303                                 perror("create socket");
304                                 return new_sock;
305                         }
306                         debug_print("socket file (create): %d\n", new_sock);
307                         addr_u.sun_family = AF_UNIX;
308                         if (strlen(Socket->socket.path) > UNIX_PATH_MAX) {
309                                 g_error("socket path longer than %d-char: %s",
310                                         UNIX_PATH_MAX, Socket->socket.path);
311                                 new_sock = -2;
312                         } else {
313                                 memcpy(addr_u.sun_path, Socket->socket.path, 
314                                                 strlen(Socket->socket.path));
315                                 if (connect(new_sock, (struct sockaddr *) &addr_u, sizeof(addr_u)) < 0) {
316                                         perror("connect socket");
317                                         close(new_sock);
318                                         new_sock = -2;
319                                 }
320                         }
321                         debug_print("socket file (connect): %d\n", new_sock);
322                         break;
323                 case INET_SOCKET:
324                         addr_i.sin_family = AF_INET;
325                         addr_i.sin_port = htons(Socket->socket.port);
326                         hp = gethostbyname(Socket->socket.host);
327                         if (!hp) {
328                                 g_error("fail to get host by: %s", Socket->socket.host);
329                                 return new_sock;
330                         }
331                         debug_print("IP socket host: %s:%d\n",
332                                         Socket->socket.host, Socket->socket.port);
333                         bcopy((void *)hp->h_addr, (void *)&addr_i.sin_addr, hp->h_length);
334                         new_sock = socket(PF_INET, SOCK_STREAM, 0);
335                         if (new_sock < 0) {
336                                 perror("create socket");
337                                 return new_sock;
338                         }
339                         debug_print("IP socket (create): %d\n", new_sock);
340                         if (connect(new_sock, (struct sockaddr *)&addr_i, sizeof(addr_i)) < 0) {
341                                 perror("connect socket");
342                                 close(new_sock);
343                                 new_sock = -2;
344                         }
345                         debug_print("IP socket (connect): %d\n", new_sock);
346                         break;
347         }
348
349         return new_sock;
350 }
351
352 static void copy_socket(Clamd_Socket* sock) {
353         Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket));
354         Socket->type = sock->type;
355         if (Socket->type == UNIX_SOCKET) {
356                 Socket->socket.path = g_strdup(sock->socket.path);
357                 Socket->socket.host = NULL;
358         }
359         else {
360                 Socket->socket.path = NULL;
361                 Socket->socket.host = g_strdup(sock->socket.host);
362                 Socket->socket.port = sock->socket.port;
363         }
364 }
365
366 Clamd_Stat clamd_init(Clamd_Socket* config) {
367         gchar buf[BUFSIZ];
368         int n_read;
369         gboolean connect = FALSE;
370         int sock;
371
372         /*debug_set_mode(TRUE);*/
373         if (config != NULL && Socket != NULL)
374                 return NO_SOCKET;
375         if (config) {
376                 debug_print("socket: %s\n", config->socket.path);
377                 copy_socket(config);
378         }
379         sock = create_socket();
380         if (sock < 0) {
381                 debug_print("no connection\n");
382                 return NO_CONNECTION;
383         }
384         if (write(sock, ping, strlen(ping)) == -1) {
385                 debug_print("write error %d\n", errno);
386                 close(sock);
387                 return NO_CONNECTION;
388         }
389         memset(buf, '\0', sizeof(buf));
390         while ((n_read = read(sock, buf, BUFSIZ - 1)) > 0) {
391                 buf[n_read] = '\0';
392                 if (buf[n_read - 1] == '\n')
393                         buf[n_read - 1] = '\0';
394                 debug_print("Ping result: %s\n", buf);
395                 if (strcmp("PONG", buf) == 0)
396                         connect = TRUE;
397         }
398         close(sock);
399         sock = create_socket();
400         if (sock < 0) {
401             debug_print("no connection\n");
402             return NO_CONNECTION;
403         }
404         if (write(sock, version, strlen(version)) == -1) {
405             debug_print("write error %d\n", errno);
406             close(sock);
407             return NO_CONNECTION;
408         }
409         memset(buf, '\0', sizeof(buf));
410         while ((n_read = read(sock, buf, BUFSIZ - 1)) > 0) {
411                 buf[n_read] = '\0';
412                 if (buf[n_read - 1] == '\n')
413                         buf[n_read - 1] = '\0';
414             debug_print("Version: %s\n", buf);
415         }
416         close(sock);
417         /*debug_set_mode(FALSE);*/
418         return (connect) ? OK : NO_CONNECTION;
419 }
420
421 static Clamd_Stat clamd_stream_scan(int sock,
422                 const gchar* path, gchar** res, ssize_t size) {
423         int fd;
424         ssize_t count;
425         gchar buf[BUFSIZ];
426         int n_read;
427         int32_t chunk;
428         
429         debug_print("Scanning: %s\n", path);
430
431         memset(buf, '\0', sizeof(buf));
432
433         if (! res || size < 1) {
434                 return SCAN_ERROR;
435         }
436         if (! *res)
437                 *res = g_new(gchar, size);
438         memset(*res, '\0', size);
439         
440         if (! g_file_test(path, G_FILE_TEST_EXISTS)) {
441                 *res = g_strconcat("ERROR -> ", path, _(": File does not exist"), NULL);
442                 debug_print("res: %s\n", *res);
443                 return SCAN_ERROR;
444         }
445
446 #ifdef _LARGE_FILES
447         fd = open(path, O_RDONLY, O_LARGEFILE);
448 #else
449         fd = open(path, O_RDONLY);
450 #endif
451
452         if (fd < 0) {
453                 /*g_error("%s: Unable to open", path);*/
454                 *res = g_strconcat("ERROR -> ", path, _(": Unable to open"), NULL);
455                 return SCAN_ERROR;
456         }
457         
458         debug_print("command: %s\n", instream);
459         if (write(sock, instream, strlen(instream) + 1) == -1) {
460                 close(fd);
461                 return NO_CONNECTION;
462         }
463
464         while ((count = read(fd, (void *) buf, BUFSIZ - 1)) > 0) {
465                 buf[count] = '\0';
466                 if (buf[count - 1] == '\n')
467                         buf[count - 1] = '\0';
468                 
469                 debug_print("chunk size: %ld\n", count);
470                 chunk = htonl(count);
471                 if (write(sock, &chunk, 4) == -1) {
472                         close(fd);
473                         *res = g_strconcat("ERROR -> ", _("Socket write error"), NULL);
474                         return SCAN_ERROR;
475                 }
476                 if (write(sock, buf, count) == -1) {
477                         close(fd);
478                         *res = g_strconcat("ERROR -> ", _("Socket write error"), NULL);
479                         return SCAN_ERROR;
480                 }
481                 memset(buf, '\0', BUFSIZ - 1);
482         }
483         if (count == -1) {
484                 close(fd);
485                 *res = g_strconcat("ERROR -> ", path, _("%s: Error reading"), NULL);
486                 return SCAN_ERROR;
487         }
488         close(fd);
489         
490         chunk = htonl(0);
491         if (write(sock, &chunk, 4) == -1) {
492                 *res = g_strconcat("ERROR -> ", _("Socket write error"), NULL);
493                 return SCAN_ERROR;
494         }
495         
496         debug_print("reading from socket\n");
497         n_read = read(sock, *res, size);
498         if (n_read < 0) {
499                 *res = g_strconcat("ERROR -> ", _("Socket read error"), NULL);
500                 return SCAN_ERROR;
501         }
502         (*res)[n_read] = '\0';
503         debug_print("received: %s\n", *res);
504         return OK;
505 }
506
507 Clamd_Stat clamd_verify_email(const gchar* path, response* result) {
508         gchar buf[BUFSIZ];
509         int n_read;
510         gchar* command;
511         Clamd_Stat stat;
512         int sock;
513
514         /*debug_set_mode(TRUE);*/
515         if (!result) {
516                 return SCAN_ERROR;
517         }
518         sock = create_socket();
519         if (sock < 0) {
520                 debug_print("no connection (socket create)\n");
521                 return NO_CONNECTION;
522         }
523         memset(buf, '\0', sizeof(buf));
524         if (Socket->type == INET_SOCKET) {
525                 gchar* tmp = g_new0(gchar, BUFSIZ);
526                 stat = clamd_stream_scan(sock, path, &tmp, BUFSIZ);
527                 if (stat != OK) {
528                         close(sock);
529                         result->msg = g_strdup(tmp);
530                         g_free(tmp);
531                         debug_print("result: %s\n", result->msg);
532                         return stat;
533                 }
534                 debug_print("copy to buf: %s\n", tmp);
535                 memcpy(&buf, tmp, BUFSIZ);
536                 g_free(tmp);
537                 debug_print("response: %s\n", buf);
538         }
539         else {
540                 command = g_strconcat(scan, " ", path, "\n", NULL);
541                 debug_print("command: %s\n", command);
542                 if (write(sock, command, strlen(command)) == -1) {
543                         debug_print("no connection (socket write)\n");
544                         g_free(command);
545                         return NO_CONNECTION;
546                 }
547                 g_free(command);
548                 memset(buf, '\0', sizeof(buf));
549                 /* shouldn't we read only once here? we're checking the last response line anyway */
550                 while ((n_read = read(sock, buf, BUFSIZ - 1)) > 0) {
551                         buf[n_read] = '\0';
552                         if (buf[n_read - 1] == '\n')
553                                 buf[n_read - 1] = '\0';
554                         debug_print("response: %s\n", buf);
555                 }
556                 if (n_read == 0) {
557                         buf[n_read] = '\0';
558                         debug_print("response: %s\n", buf);
559                 }
560         }
561         if (strstr(buf, "ERROR")) {
562                 stat = SCAN_ERROR;
563                 result->msg = g_strdup(buf);
564         }               
565         else if (strstr(buf, "FOUND")) {
566                 stat = VIRUS;
567                 result->msg = g_strdup(buf);
568         }               
569         else {
570                 stat = OK;
571                 result->msg = NULL;
572         }
573         close(sock);
574         /*debug_set_mode(FALSE);*/
575
576         return stat;
577 }
578
579 GSList* clamd_verify_dir(const gchar* path) {
580         gchar buf[BUFSIZ];
581         int n_read;
582         gchar* command;
583         GSList *list = NULL;
584         int sock;
585
586         if (Socket->type == INET_SOCKET)
587                 return list;
588
589         sock = create_socket();
590         if (sock < 0) {
591                 debug_print("No socket\n");
592                 return list;
593         }
594         command = g_strconcat(contscan, path, "\n", NULL);
595         debug_print("command: %s\n", command);
596         if (write(sock, command, strlen(command)) == -1) {
597                 debug_print("write error %d\n", errno);
598                 close(sock);
599                 return list;
600         }
601         g_free(command);
602         memset(buf, '\0', sizeof(buf));
603         while ((n_read = read(sock, buf, BUFSIZ - 1)) > 0) {
604                 gchar** tmp = g_strsplit(buf, "\n", 0);
605                 gchar** head = tmp;
606                 while (*tmp) {
607                         gchar* file = *tmp++;
608                         debug_print("%s\n", file);
609                         if (strstr(file, "ERROR")) {
610                                 g_warning("%s", file);
611                                 /* dont report files with errors */
612                         }
613                         else if (strstr(file, "FOUND")) {
614                                 list = g_slist_append(list, g_strdup(file));
615                         }
616                 }
617                 g_strfreev(head);
618         }
619         close(sock);
620         return list;
621 }
622
623 void clamd_free_gslist(GSList* list) {
624         GSList* tmp = list;
625         while(tmp) {
626                 g_free(tmp->data);
627                 tmp = g_slist_next(tmp);
628         }
629         g_slist_free(list);
630 }
631
632 gchar* clamd_get_virus_name(gchar* msg) {
633         gchar *head, *tail, *name;
634
635         tail = g_strrstr_len(msg, strlen(msg), "FOUND");
636         if (! tail)
637                 return NULL;
638         head = g_strstr_len(msg, strlen(msg), ":");
639         ++head;
640         name = g_strndup(head, tail - head);
641         g_strstrip(name);
642         return name;
643 }
644
645 void clamd_free() {
646         if (Socket) {
647                 switch (Socket->type) {
648                     case UNIX_SOCKET:
649                         if (Socket->socket.path) {
650                             g_free(Socket->socket.path);
651                             Socket->socket.path = NULL;
652                         }
653                         break;
654                     case INET_SOCKET:
655                         if (Socket->socket.host) {
656                             g_free(Socket->socket.host);
657                             Socket->socket.host = NULL;
658                         }
659                         break;
660                 }
661                 g_free(Socket);
662                 Socket = NULL;
663         }
664         if (config) {
665             clamd_config_free(config);
666             config = NULL;
667         }
668 }
669
670 Config* clamd_config_new() {
671         return g_new0(Config, 1);
672 }
673
674 void clamd_config_free(Config* c) {
675         if (c->ConfigType == AUTOMATIC) {
676                 g_free(c->automatic.folder);
677                 c->automatic.folder = NULL;
678         }
679         else {
680                 g_free(c->manual.host);
681                 c->manual.host = NULL;
682         }
683         g_free(c);
684 }
685
686 gchar* int2char(int i) {
687         gchar* s = g_new0(gchar, 5);
688
689         sprintf(s, "%d", i);
690         
691         return s;
692 }
693
694 gchar* long2char(long l) {
695         gchar* s = g_new0(gchar, 5);
696
697         debug_print("l: %ld\n", l);
698         sprintf(s, "%ld", l);
699         debug_print("s: %s\n", s);
700         
701         return s;
702 }