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