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