1 /* vim: set textwidth=80 tabstop=4: */
4 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
5 * Copyright (C) 1999-2017 Michael Rasmussen and the Claws Mail Team
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.
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.
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/>.
29 #include <glib/gi18n.h>
31 #include <gtk/gtkutils.h>
37 #include <sys/types.h>
38 #include <sys/socket.h>
41 #include <netinet/in.h>
43 #include <arpa/inet.h>
47 #include "common/claws.h"
48 #include "common/version.h"
53 #include "prefs_gtk.h"
54 #include "foldersel.h"
55 #include "statusbar.h"
56 #include "alertpanel.h"
57 #include "clamd-plugin.h"
61 #define UNIX_PATH_MAX 108
64 /* needs to be generic */
65 static const gchar* config_dirs[] = {
69 "/usr/local/etc/clamav",
72 static const gchar* clamd_tokens[] = {
78 static Clamd_Socket* Socket = NULL;
79 static Config* config = NULL;
83 * prefixing with either z or n is recommended
84 * z <=> null terminated command
85 * n <=> newline terminated command
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";
93 void clamd_create_config_automatic(const gchar* path) {
99 /*debug_set_mode(TRUE);*/
100 /*debug_print("%s : %s\n", folder, path);*/
102 g_warning("Missing path");
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);
113 clamd_config_free(config);
114 config = clamd_config_new();
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");
121 /*g_error("%s: Unable to open", path);*/
122 alertpanel_error(_("%s: Unable to open\nclamd will be disabled"), path);
125 while (claws_fgets(buf, sizeof(buf), conf)) {
129 const gchar** tokens = clamd_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, '#');
137 value = g_strndup(tmp, end - tmp);
139 value = g_strdup(g_strchomp(tmp));
140 if (strcmp(clamd_tokens[0], token) == 0) {
142 Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket));
144 Socket->socket.host = NULL;
145 Socket->socket.port = -1;
146 Socket->type = UNIX_SOCKET;
147 Socket->socket.path = g_strdup(value);
151 debug_print("clamctl: %s\n", Socket->socket.path);
155 else if (strcmp(clamd_tokens[1], token) == 0) {
158 Socket = (Clamd_Socket *) malloc(sizeof(Clamd_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");
167 debug_print("clamctl: %s:%d\n",
168 Socket->socket.host, Socket->socket.port);
172 Socket->type = INET_SOCKET;
173 Socket->socket.port = atoi(value);
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);
181 /* We must continue since TCPAddr could also be configured */
183 else if (strcmp(clamd_tokens[2], token) == 0) {
185 Socket = (Clamd_Socket *) malloc(sizeof(Clamd_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);
193 debug_print("clamctl: %s:%d\n",
194 Socket->socket.host, Socket->socket.port);
198 Socket->type = INET_SOCKET;
199 if (Socket->socket.host)
200 g_free(Socket->socket.host);
201 Socket->socket.host = g_strdup(value);
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);
209 /* We must continue since TCPSocket could also be configured */
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);
219 /*debug_set_mode(FALSE);*/
222 void clamd_create_config_manual(const gchar* host, int port) {
223 if (! host || port < 1) {
224 g_warning("Missing host or port < 1");
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);
236 clamd_config_free(config);
237 config = clamd_config_new();
239 config->ConfigType = MANUAL;
240 config->manual.host = g_strdup(host);
241 config->manual.port = port;
243 Socket = (Clamd_Socket *) malloc(sizeof(Clamd_Socket));
245 Socket->type = INET_SOCKET;
246 Socket->socket.port = port;
247 Socket->socket.host = g_strdup(host);
250 /*g_error("%s: Not able to find required information", path);*/
251 alertpanel_error(_("Could not create socket"));
255 gboolean clamd_find_socket() {
256 const gchar** config_dir = config_dirs;
257 gchar *clamd_conf = NULL;
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))
270 debug_print("Using %s to find configuration\n", clamd_conf);
271 clamd_create_config_automatic(clamd_conf);
277 Config* clamd_get_config() {
281 Clamd_Socket* clamd_get_socket() {
285 static int create_socket() {
286 struct sockaddr_un addr_u;
287 struct sockaddr_in addr_i;
292 /*debug_set_mode(TRUE);*/
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) {
301 debug_print("socket path: %s\n", Socket->socket.path);
302 new_sock = socket(PF_UNIX, SOCK_STREAM, 0);
304 perror("create socket");
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);
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");
322 debug_print("socket file (connect): %d\n", new_sock);
325 addr_i.sin_family = AF_INET;
326 addr_i.sin_port = htons(Socket->socket.port);
327 hp = gethostbyname(Socket->socket.host);
329 g_error("fail to get host by: %s", Socket->socket.host);
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);
337 perror("create socket");
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");
346 debug_print("IP socket (connect): %d\n", new_sock);
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;
361 Socket->socket.path = NULL;
362 Socket->socket.host = g_strdup(sock->socket.host);
363 Socket->socket.port = sock->socket.port;
367 Clamd_Stat clamd_init(Clamd_Socket* config) {
370 gboolean connect = FALSE;
373 /*debug_set_mode(TRUE);*/
374 if (config != NULL && Socket != NULL)
377 debug_print("socket: %s\n", config->socket.path);
380 sock = create_socket();
382 debug_print("no connection\n");
383 return NO_CONNECTION;
385 if (write(sock, ping, strlen(ping)) == -1) {
386 debug_print("write error %d\n", errno);
388 return NO_CONNECTION;
390 memset(buf, '\0', sizeof(buf));
391 while ((n_read = read(sock, buf, BUFSIZ - 1)) > 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)
400 sock = create_socket();
402 debug_print("no connection\n");
403 return NO_CONNECTION;
405 if (write(sock, version, strlen(version)) == -1) {
406 debug_print("write error %d\n", errno);
408 return NO_CONNECTION;
410 memset(buf, '\0', sizeof(buf));
411 while ((n_read = read(sock, buf, BUFSIZ - 1)) > 0) {
413 if (buf[n_read - 1] == '\n')
414 buf[n_read - 1] = '\0';
415 debug_print("Version: %s\n", buf);
418 /*debug_set_mode(FALSE);*/
419 return (connect) ? OK : NO_CONNECTION;
422 static Clamd_Stat clamd_stream_scan(int sock,
423 const gchar* path, gchar** res, ssize_t size) {
430 debug_print("Scanning: %s\n", path);
432 memset(buf, '\0', sizeof(buf));
434 if (! res || size < 1) {
438 *res = g_new(gchar, size);
439 memset(*res, '\0', size);
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);
448 fd = open(path, O_RDONLY, O_LARGEFILE);
450 fd = open(path, O_RDONLY);
454 /*g_error("%s: Unable to open", path);*/
455 *res = g_strconcat("ERROR -> ", path, _(": Unable to open"), NULL);
459 debug_print("command: %s\n", instream);
460 if (write(sock, instream, strlen(instream) + 1) == -1) {
462 return NO_CONNECTION;
465 while ((count = read(fd, (void *) buf, BUFSIZ - 1)) > 0) {
467 if (buf[count - 1] == '\n')
468 buf[count - 1] = '\0';
470 debug_print("chunk size: %ld\n", count);
471 chunk = htonl(count);
472 if (write(sock, &chunk, 4) == -1) {
474 *res = g_strconcat("ERROR -> ", _("Socket write error"), NULL);
477 if (write(sock, buf, count) == -1) {
479 *res = g_strconcat("ERROR -> ", _("Socket write error"), NULL);
482 memset(buf, '\0', BUFSIZ - 1);
486 *res = g_strconcat("ERROR -> ", path, _("%s: Error reading"), NULL);
492 if (write(sock, &chunk, 4) == -1) {
493 *res = g_strconcat("ERROR -> ", _("Socket write error"), NULL);
497 debug_print("reading from socket\n");
498 n_read = read(sock, *res, size);
500 *res = g_strconcat("ERROR -> ", _("Socket read error"), NULL);
503 (*res)[n_read] = '\0';
504 debug_print("received: %s\n", *res);
508 Clamd_Stat clamd_verify_email(const gchar* path, response* result) {
515 /*debug_set_mode(TRUE);*/
519 sock = create_socket();
521 debug_print("no connection (socket create)\n");
522 return NO_CONNECTION;
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);
530 result->msg = g_strdup(tmp);
532 debug_print("result: %s\n", result->msg);
535 debug_print("copy to buf: %s\n", tmp);
536 memcpy(&buf, tmp, BUFSIZ);
538 debug_print("response: %s\n", buf);
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");
546 return NO_CONNECTION;
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) {
553 if (buf[n_read - 1] == '\n')
554 buf[n_read - 1] = '\0';
555 debug_print("response: %s\n", buf);
559 debug_print("response: %s\n", buf);
561 /* in case read() fails */
562 debug_print("read error %d\n", errno);
565 return NO_CONNECTION;
568 if (strstr(buf, "ERROR")) {
570 result->msg = g_strdup(buf);
572 else if (strstr(buf, "FOUND")) {
574 result->msg = g_strdup(buf);
581 /*debug_set_mode(FALSE);*/
586 GSList* clamd_verify_dir(const gchar* path) {
593 if (Socket->type == INET_SOCKET)
596 sock = create_socket();
598 debug_print("No socket\n");
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);
609 memset(buf, '\0', sizeof(buf));
610 while ((n_read = read(sock, buf, BUFSIZ - 1)) > 0) {
611 gchar** tmp = g_strsplit(buf, "\n", 0);
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 */
620 else if (strstr(file, "FOUND")) {
621 list = g_slist_append(list, g_strdup(file));
630 void clamd_free_gslist(GSList* list) {
634 tmp = g_slist_next(tmp);
639 gchar* clamd_get_virus_name(gchar* msg) {
640 gchar *head, *tail, *name;
642 tail = g_strrstr_len(msg, strlen(msg), "FOUND");
645 head = g_strstr_len(msg, strlen(msg), ":");
647 name = g_strndup(head, tail - head);
654 switch (Socket->type) {
656 if (Socket->socket.path) {
657 g_free(Socket->socket.path);
658 Socket->socket.path = NULL;
662 if (Socket->socket.host) {
663 g_free(Socket->socket.host);
664 Socket->socket.host = NULL;
672 clamd_config_free(config);
677 Config* clamd_config_new() {
678 return g_new0(Config, 1);
681 void clamd_config_free(Config* c) {
682 if (c->ConfigType == AUTOMATIC) {
683 g_free(c->automatic.folder);
684 c->automatic.folder = NULL;
687 g_free(c->manual.host);
688 c->manual.host = NULL;
693 gchar* int2char(int i) {
694 gchar* s = g_new0(gchar, 5);
701 gchar* long2char(long l) {
702 gchar* s = g_new0(gchar, 5);
704 debug_print("l: %ld\n", l);
705 sprintf(s, "%ld", l);
706 debug_print("s: %s\n", s);