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