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