update copyright year
[claws.git] / src / plugins / clamd / clamav_plugin.c
1 /*
2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3  * Copyright (C) 2003-2017 Michael Rasmussen and the Claws Mail Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #  include "claws-features.h"
23 #endif
24
25 #include <glib.h>
26 #include <glib/gi18n.h>
27
28 #include "defs.h"
29
30 #include "common/claws.h"
31 #include "common/version.h"
32 #include "plugin.h"
33 #include "utils.h"
34 #include "hooks.h"
35 #include "inc.h"
36 #include "mimeview.h"
37 #include "folder.h"
38 #include "prefs.h"
39 #include "prefs_gtk.h"
40 #include "alertpanel.h"
41 #include "prefs_common.h"
42 #include "statusbar.h"
43
44 #include "clamav_plugin.h"
45 #include "clamd-plugin.h"
46
47 #define PLUGIN_NAME (_("Clam AntiVirus"))
48
49 static gulong hook_id = HOOK_NONE;
50 static MessageCallback message_callback;
51
52 static ClamAvConfig config;
53
54 static PrefParam param[] = {
55         {"clamav_enable", "FALSE", &config.clamav_enable, P_BOOL,
56          NULL, NULL, NULL},
57 /*      {"clamav_enable_arc", "FALSE", &config.clamav_enable_arc, P_BOOL,
58          NULL, NULL, NULL},*/
59         {"clamav_max_size", "1", &config.clamav_max_size, P_USHORT,
60          NULL, NULL, NULL},
61         {"clamav_recv_infected", "TRUE", &config.clamav_recv_infected, P_BOOL,
62          NULL, NULL, NULL},
63         {"clamav_save_folder", NULL, &config.clamav_save_folder, P_STRING,
64          NULL, NULL, NULL},
65         {"clamad_config_type", "TRUE", &config.clamd_config_type, P_BOOL,
66          NULL, NULL, NULL},
67         {"clamd_config_folder", NULL, &config.clamd_config_folder, P_STRING,
68          NULL, NULL, NULL},
69         {"clamd_host", NULL, &config.clamd_host, P_STRING,
70          NULL, NULL, NULL},
71         {"clamd_port", "0", &config.clamd_port, P_INT,
72          NULL, NULL, NULL},
73
74         {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
75 };
76
77 struct clamd_result {
78         Clamd_Stat status;
79 };
80
81 static gboolean scan_func(GNode *node, gpointer data)
82 {
83         struct clamd_result *result = (struct clamd_result *) data;
84         MimeInfo *mimeinfo = (MimeInfo *) node->data;
85         gchar *outfile;
86         response buf;
87         int max;
88         GStatBuf info;
89         gchar* msg, *name;
90
91         outfile = procmime_get_tmp_file_name(mimeinfo);
92         if (procmime_get_part(outfile, mimeinfo) < 0)
93                 g_warning("can't get the part of multipart message");
94         else {
95         max = config.clamav_max_size * 1048576; /* maximum file size */
96                 if (g_stat(outfile, &info) == -1)
97                         g_warning("can't determine file size");
98                 else {
99                         if (info.st_size <= max) {
100                                 debug_print("Scanning %s\n", outfile);
101                                 result->status = clamd_verify_email(outfile, &buf);
102                                 debug_print("status: %d\n", result->status);
103                                 switch (result->status) {
104                                         case NO_SOCKET: 
105                                                 g_warning("[scanning] no socket information");
106                                                 if (config.alert_ack) {
107                                                     alertpanel_error(_("Scanning\nNo socket information.\nAntivirus disabled."));
108                                                     config.alert_ack = FALSE;
109                                                 }
110                                                 break;
111                                         case NO_CONNECTION:
112                                                 g_warning("[scanning] Clamd does not respond to ping");
113                                                 if (config.alert_ack) {
114                                                     alertpanel_warning(_("Scanning\nClamd does not respond to ping.\nIs clamd running?"));
115                                                     config.alert_ack = FALSE;
116                                                 }
117                                                 break;
118                                         case VIRUS: 
119                                                 name = clamd_get_virus_name(buf.msg);
120                                                 msg = g_strconcat(_("Detected %s virus."),
121                                                         name, NULL);
122                                                 g_free(name);
123                                                 g_warning("%s", msg);
124                                                 debug_print("no_recv: %d\n", prefs_common_get_prefs()->no_recv_err_panel);
125                                                 if (prefs_common_get_prefs()->no_recv_err_panel) {
126                                                     statusbar_print_all("%s", msg);
127                                                 }
128                                                 else {
129                                                     alertpanel_warning("%s\n", msg);
130                                                 }
131                                                 g_free(msg);
132                                                 config.alert_ack = TRUE;
133                                                 break;
134                                         case SCAN_ERROR:
135                                                 debug_print("Error: %s\n", buf.msg);
136                                                 if (config.alert_ack) {
137                                                     alertpanel_error(_("Scanning error:\n%s"), buf.msg);
138                                                     config.alert_ack = FALSE;
139                                                 }
140                                                 break;
141                                         case OK:
142                                                 debug_print("No virus detected.\n");
143                                                 config.alert_ack = TRUE;
144                                                 break;
145                                 }
146                         }
147                         else {
148                                 msg = g_strdup_printf(_("File: %s. Size (%d) greater than limit (%d)\n"), outfile, (int) info.st_size, max);
149                                 statusbar_print_all("%s", msg);
150                                 debug_print("%s", msg);
151                                 g_free(msg);
152                         }
153                 }
154                 if (g_unlink(outfile) < 0)
155                         FILE_OP_ERROR(outfile, "g_unlink");
156         }
157         
158         return (result->status == OK) ? FALSE : TRUE;
159 }
160
161 static gboolean mail_filtering_hook(gpointer source, gpointer data)
162 {
163         MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
164         MsgInfo *msginfo = mail_filtering_data->msginfo;
165         MimeInfo *mimeinfo;
166
167         struct clamd_result result;
168
169         if (!config.clamav_enable)
170                 return FALSE;
171
172         mimeinfo = procmime_scan_message(msginfo);
173         if (!mimeinfo) return FALSE;
174
175         debug_print("Scanning message %d for viruses\n", msginfo->msgnum);
176         if (message_callback != NULL)
177                 message_callback(_("ClamAV: scanning message..."));
178
179         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, scan_func, &result);
180         debug_print("status: %d\n", result.status);
181
182         if (result.status == VIRUS) {
183                 if (config.clamav_recv_infected) {
184                         FolderItem *clamav_save_folder;
185
186                         if ((!config.clamav_save_folder) ||
187                             (config.clamav_save_folder[0] == '\0') ||
188                             ((clamav_save_folder = folder_find_item_from_identifier(config.clamav_save_folder)) == NULL))
189                                     clamav_save_folder = folder_get_default_trash();
190
191                         procmsg_msginfo_unset_flags(msginfo, ~0, 0);
192                         msginfo->filter_op = IS_MOVE;
193                         msginfo->to_filter_folder = clamav_save_folder;
194                 } else {
195                         folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
196                 }
197         }
198         
199         procmime_mimeinfo_free_all(&mimeinfo);
200
201         return (result.status == OK) ? FALSE : TRUE;
202 }
203
204 Clamd_Stat clamd_prepare(void) {
205         debug_print("Creating socket\n");
206         if (!config.clamd_config_type
207                         || (config.clamd_host != NULL
208                                 && *(config.clamd_host) != '\0'
209                                 && config.clamd_port > 0)) {
210                 if (config.clamd_host == NULL
211                                 || *(config.clamd_host) == '\0'
212                                 || config.clamd_port == 0) {
213                         /* error */
214                         return NO_SOCKET;
215                 }
216                 /* Manual configuration has highest priority */
217                 debug_print("Using user input: %s:%d\n",
218                         config.clamd_host, config.clamd_port);
219                 clamd_create_config_manual(config.clamd_host, config.clamd_port);
220         }
221         else if (config.clamd_config_type || config.clamd_config_folder != NULL) {
222                 if (config.clamd_config_folder == NULL) {
223                         /* error */
224                         return NO_SOCKET;
225                 }
226                 debug_print("Using clamd.conf: %s\n", config.clamd_config_folder);
227                 clamd_create_config_automatic(config.clamd_config_folder);
228         }
229         else {
230                 /* Fall back. Try enable anyway */
231                 if (! clamd_find_socket())
232                         return NO_SOCKET;
233         }
234
235         return clamd_init(NULL);
236 }
237
238 ClamAvConfig *clamav_get_config(void)
239 {
240         return &config;
241 }
242
243 void clamav_save_config(void)
244 {
245         PrefFile *pfile;
246         gchar *rcpath;
247
248         debug_print("Saving Clamd Page\n");
249
250         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
251         pfile = prefs_write_open(rcpath);
252         g_free(rcpath);
253         if (!pfile || (prefs_set_block_label(pfile, "ClamAV") < 0))
254                 return;
255
256         if (prefs_write_param(param, pfile->fp) < 0) {
257                 g_warning("failed to write Clamd configuration to file");
258                 prefs_file_close_revert(pfile);
259                 return;
260         }
261     if (fprintf(pfile->fp, "\n") < 0) {
262                 FILE_OP_ERROR(rcpath, "fprintf");
263                 prefs_file_close_revert(pfile);
264         } else
265                 prefs_file_close(pfile);
266 }
267
268 void clamav_set_message_callback(MessageCallback callback)
269 {
270         message_callback = callback;
271 }
272
273 gint plugin_init(gchar **error)
274 {
275         gchar *rcpath;
276
277         if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
278                                 VERSION_NUMERIC, PLUGIN_NAME, error))
279                 return -1;
280
281         hook_id = hooks_register_hook(MAIL_FILTERING_HOOKLIST, mail_filtering_hook, NULL);
282         if (hook_id == HOOK_NONE) {
283                 *error = g_strdup(_("Failed to register mail filtering hook"));
284                 return -1;
285         }
286
287         prefs_set_default(param);
288         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
289         prefs_read_config(param, "ClamAV", rcpath, NULL);
290         g_free(rcpath);
291
292         clamav_gtk_init();
293
294         if (config.clamav_enable) {
295                 debug_print("Creating socket\n");
296                 config.alert_ack = TRUE;
297                 Clamd_Stat status = clamd_prepare();
298                 switch (status) {
299                         case NO_SOCKET: 
300                                 g_warning("[init] no socket information");
301                                 alertpanel_error(_("Init\nNo socket information.\nAntivirus disabled."));
302                                 break;
303                         case NO_CONNECTION:
304                                 g_warning("[init] Clamd does not respond to ping");
305                                 alertpanel_warning(_("Init\nClamd does not respond to ping.\nIs clamd running?"));
306                                 break;
307                         default:
308                                 break;
309                 }
310         }
311
312         debug_print("Clamd plugin loaded\n");
313
314         return 0;
315         
316 }
317
318 gboolean plugin_done(void)
319 {
320         hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, hook_id);
321         g_free(config.clamav_save_folder);
322         clamav_gtk_done();
323         clamd_free();
324
325         debug_print("Clamd plugin unloaded\n");
326         return TRUE;
327 }
328
329 const gchar *plugin_name(void)
330 {
331         return PLUGIN_NAME;
332 }
333
334 const gchar *plugin_desc(void)
335 {
336         return _("This plugin uses Clam AntiVirus to scan all messages that are "
337                "received from an IMAP, LOCAL or POP account.\n"
338                "\n"
339                "When a message attachment is found to contain a virus it can be "
340                "deleted or saved in a specially designated folder.\n"
341                "\n"
342                "Because this plugin communicates with clamd via a\n"
343                "socket then there are some minimum requirements to\n"
344                "the permissions for your home folder and the\n"
345                ".claws-mail folder provided the clamav-daemon is\n"
346                "configured to communicate via a unix socket. All\n"
347                "users at least need to be given execute permissions\n"
348                "on these folders.\n"
349                "\n"
350                "To avoid changing permissions you could configure\n"
351                "the clamav-daemon to communicate via a TCP socket\n"
352                "and choose manual configuration for clamd.\n" 
353                "\n"
354                "Options can be found in /Configuration/Preferences/Plugins/Clam AntiVirus");
355 }
356
357 const gchar *plugin_type(void)
358 {
359         return "GTK3";
360 }
361
362 const gchar *plugin_licence(void)
363 {
364         return "GPL3+";
365 }
366
367 const gchar *plugin_version(void)
368 {
369         return VERSION;
370 }
371
372 struct PluginFeature *plugin_provides(void)
373 {
374         static struct PluginFeature features[] = 
375                 { {PLUGIN_FILTERING, N_("Virus detection")},
376                   {PLUGIN_NOTHING, NULL}};
377         return features;
378 }