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