Fix bug #3380. Initialize widget before callback handler for 'clicked'
[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-2010 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 guint hook_id;
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", NULL, &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         struct stat info;
89         gchar* msg;
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 (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                                                 msg = g_strconcat(_("Detected %s virus."),
120                                                         clamd_get_virus_name(buf.msg), NULL);
121                                                 g_warning("%s\n", msg);
122                                                 debug_print("no_recv: %d\n", prefs_common.no_recv_err_panel);
123                                                 if (prefs_common.no_recv_err_panel) {
124                                                     statusbar_print_all("%s", msg);
125                                                 }
126                                                 else {
127                                                     alertpanel_warning("%s\n", msg);
128                                                 }
129                                                 g_free(msg);
130                                                 config.alert_ack = TRUE;
131                                                 break;
132                                         case SCAN_ERROR:
133                                                 debug_print("Error: %s\n", buf.msg);
134                                                 if (config.alert_ack) {
135                                                     alertpanel_error(_("Scanning error:\n%s"), buf.msg);
136                                                     config.alert_ack = FALSE;
137                                                 }
138                                                 break;
139                                         case OK:
140                                                 debug_print("No virus detected.\n");
141                                                 config.alert_ack = TRUE;
142                                                 break;
143                                 }
144                         }
145                         else {
146                                 msg = g_strdup_printf(_("File: %s. Size (%d) greater than limit (%d)\n"), outfile, (int) info.st_size, max);
147                                 statusbar_print_all("%s", msg);
148                                 debug_print("%s", msg);
149                                 g_free(msg);
150                         }
151                 }
152                 g_unlink(outfile);
153         }
154         
155         return (result->status == OK) ? FALSE : TRUE;
156 }
157
158 static gboolean mail_filtering_hook(gpointer source, gpointer data)
159 {
160         MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
161         MsgInfo *msginfo = mail_filtering_data->msginfo;
162         MimeInfo *mimeinfo;
163
164         struct clamd_result result;
165
166         if (!config.clamav_enable)
167                 return FALSE;
168
169         mimeinfo = procmime_scan_message(msginfo);
170         if (!mimeinfo) return FALSE;
171
172         debug_print("Scanning message %d for viruses\n", msginfo->msgnum);
173         if (message_callback != NULL)
174                 message_callback(_("ClamAV: scanning message..."));
175
176         g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, scan_func, &result);
177         debug_print("status: %d\n", result.status);
178
179         if (result.status == VIRUS) {
180                 if (config.clamav_recv_infected) {
181                         FolderItem *clamav_save_folder;
182
183                         if ((!config.clamav_save_folder) ||
184                             (config.clamav_save_folder[0] == '\0') ||
185                             ((clamav_save_folder = folder_find_item_from_identifier(config.clamav_save_folder)) == NULL))
186                                     clamav_save_folder = folder_get_default_trash();
187
188                         procmsg_msginfo_unset_flags(msginfo, ~0, 0);
189                         msginfo->filter_op = IS_MOVE;
190                         msginfo->to_filter_folder = clamav_save_folder;
191                 } else {
192                         folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
193                 }
194         }
195         
196         procmime_mimeinfo_free_all(mimeinfo);
197
198         return (result.status == OK) ? FALSE : TRUE;
199 }
200
201 Clamd_Stat clamd_prepare(void) {
202         debug_print("Creating socket\n");
203         if (!config.clamd_config_type || (config.clamd_host != NULL && config.clamd_port > 0)) {
204                 if (config.clamd_host == NULL || config.clamd_port < 1) {
205                         /* error */
206                         return NO_SOCKET;
207                 }
208                 /* Manual configuration has highest priority */
209                 debug_print("Using user input: %s:%d\n",
210                         config.clamd_host, config.clamd_port);
211                 clamd_create_config_manual(config.clamd_host, config.clamd_port);
212         }
213         else if (config.clamd_config_type || config.clamd_config_folder != NULL) {
214                 if (config.clamd_config_folder == NULL) {
215                         /* error */
216                         return NO_SOCKET;
217                 }
218                 debug_print("Using clamd.conf: %s\n", config.clamd_config_folder);
219                 clamd_create_config_automatic(config.clamd_config_folder);
220         }
221         else {
222                 /* Fall back. Try enable anyway */
223                 if (! clamd_find_socket())
224                         return NO_SOCKET;
225         }
226
227         return clamd_init(NULL);
228 }
229
230 ClamAvConfig *clamav_get_config(void)
231 {
232         return &config;
233 }
234
235 void clamav_save_config(void)
236 {
237         PrefFile *pfile;
238         gchar *rcpath;
239
240         debug_print("Saving Clamd Page\n");
241
242         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
243         pfile = prefs_write_open(rcpath);
244         g_free(rcpath);
245         if (!pfile || (prefs_set_block_label(pfile, "ClamAV") < 0))
246                 return;
247
248         if (prefs_write_param(param, pfile->fp) < 0) {
249                 g_warning("failed to write Clamd configuration to file\n");
250                 prefs_file_close_revert(pfile);
251                 return;
252         }
253     if (fprintf(pfile->fp, "\n") < 0) {
254                 FILE_OP_ERROR(rcpath, "fprintf");
255                 prefs_file_close_revert(pfile);
256         } else
257                 prefs_file_close(pfile);
258 }
259
260 void clamav_set_message_callback(MessageCallback callback)
261 {
262         message_callback = callback;
263 }
264
265 gint plugin_init(gchar **error)
266 {
267         gchar *rcpath;
268
269         if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
270                                 VERSION_NUMERIC, PLUGIN_NAME, error))
271                 return -1;
272
273         hook_id = hooks_register_hook(MAIL_FILTERING_HOOKLIST, mail_filtering_hook, NULL);
274         if (hook_id == -1) {
275                 *error = g_strdup(_("Failed to register mail filtering hook"));
276                 return -1;
277         }
278
279         prefs_set_default(param);
280         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
281         prefs_read_config(param, "ClamAV", rcpath, NULL);
282         g_free(rcpath);
283
284         clamav_gtk_init();
285
286         if (config.clamav_enable) {
287                 debug_print("Creating socket\n");
288                 config.alert_ack = TRUE;
289                 Clamd_Stat status = clamd_prepare();
290                 switch (status) {
291                         case NO_SOCKET: 
292                                 g_warning("[init] No socket information");
293                                 alertpanel_error(_("Init\nNo socket information.\nAntivirus disabled."));
294                                 break;
295                         case NO_CONNECTION:
296                                 g_warning("[init] Clamd does not respond to ping");
297                                 alertpanel_warning(_("Init\nClamd does not respond to ping.\nIs clamd running?"));
298                                 break;
299                         default:
300                                 break;
301                 }
302         }
303
304         debug_print("Clamd plugin loaded\n");
305
306         return 0;
307         
308 }
309
310 gboolean plugin_done(void)
311 {
312         hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, hook_id);
313         g_free(config.clamav_save_folder);
314         clamav_gtk_done();
315         clamd_free();
316
317         debug_print("Clamd plugin unloaded\n");
318         return TRUE;
319 }
320
321 const gchar *plugin_name(void)
322 {
323         return PLUGIN_NAME;
324 }
325
326 const gchar *plugin_desc(void)
327 {
328         return _("This plugin uses Clam AntiVirus to scan all messages that are "
329                "received from an IMAP, LOCAL or POP account.\n"
330                "\n"
331                "When a message attachment is found to contain a virus it can be "
332                "deleted or saved in a specially designated folder.\n"
333                "\n"
334                "Because this plugin communicates with clamd via a\n"
335                "socket then there are some minimum requirements to\n"
336                "the permissions for your home folder and the\n"
337                ".claws-mail folder provided the clamav-daemon is\n"
338                "configured to communicate via a unix socket. All\n"
339                "users at least need to be given execute permissions\n"
340                "on these folders.\n"
341                "\n"
342                "To avoid changing permissions you could configure\n"
343                "the clamav-daemon to communicate via a TCP socket\n"
344                "and choose manual configuration for clamd.\n" 
345                "\n"
346                "Options can be found in /Configuration/Preferences/Plugins/Clam AntiVirus");
347 }
348
349 const gchar *plugin_type(void)
350 {
351         return "GTK2";
352 }
353
354 const gchar *plugin_licence(void)
355 {
356         return "GPL3+";
357 }
358
359 const gchar *plugin_version(void)
360 {
361         return VERSION;
362 }
363
364 struct PluginFeature *plugin_provides(void)
365 {
366         static struct PluginFeature features[] = 
367                 { {PLUGIN_FILTERING, N_("Virus detection")},
368                   {PLUGIN_NOTHING, NULL}};
369         return features;
370 }