correctly set modified flag after auto-save
[claws.git] / src / plugins / clamd / clamav_plugin.c
blob34274baa4d75c8f659814e9ac70b662c4c0968d4
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
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.
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.
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/>.
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 # include "claws-features.h"
23 #endif
25 #include <glib.h>
26 #include <glib/gi18n.h>
28 #include "defs.h"
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"
44 #include "clamav_plugin.h"
45 #include "clamd-plugin.h"
47 #define PLUGIN_NAME (_("Clam AntiVirus"))
49 static gulong hook_id = HOOK_NONE;
50 static MessageCallback message_callback;
52 static ClamAvConfig config;
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},
74 {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
77 struct clamd_result {
78 Clamd_Stat status;
81 static gboolean scan_func(GNode *node, gpointer data)
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;
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;
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;
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);
128 else {
129 alertpanel_warning("%s\n", msg);
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;
140 break;
141 case OK:
142 debug_print("No virus detected.\n");
143 config.alert_ack = TRUE;
144 break;
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);
154 if (g_unlink(outfile) < 0)
155 FILE_OP_ERROR(outfile, "g_unlink");
158 return (result->status == OK) ? FALSE : TRUE;
161 static gboolean mail_filtering_hook(gpointer source, gpointer data)
163 MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
164 MsgInfo *msginfo = mail_filtering_data->msginfo;
165 MimeInfo *mimeinfo;
167 struct clamd_result result;
169 if (!config.clamav_enable)
170 return FALSE;
172 mimeinfo = procmime_scan_message(msginfo);
173 if (!mimeinfo) return FALSE;
175 debug_print("Scanning message %d for viruses\n", msginfo->msgnum);
176 if (message_callback != NULL)
177 message_callback(_("ClamAV: scanning message..."));
179 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, scan_func, &result);
180 debug_print("status: %d\n", result.status);
182 if (result.status == VIRUS) {
183 if (config.clamav_recv_infected) {
184 FolderItem *clamav_save_folder;
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();
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);
199 procmime_mimeinfo_free_all(&mimeinfo);
201 return (result.status == OK) ? FALSE : TRUE;
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;
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);
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;
226 debug_print("Using clamd.conf: %s\n", config.clamd_config_folder);
227 clamd_create_config_automatic(config.clamd_config_folder);
229 else {
230 /* Fall back. Try enable anyway */
231 if (! clamd_find_socket())
232 return NO_SOCKET;
235 return clamd_init(NULL);
238 ClamAvConfig *clamav_get_config(void)
240 return &config;
243 void clamav_save_config(void)
245 PrefFile *pfile;
246 gchar *rcpath;
248 debug_print("Saving Clamd Page\n");
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;
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;
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);
268 void clamav_set_message_callback(MessageCallback callback)
270 message_callback = callback;
273 gint plugin_init(gchar **error)
275 gchar *rcpath;
277 if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
278 VERSION_NUMERIC, PLUGIN_NAME, error))
279 return -1;
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;
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);
292 clamav_gtk_init();
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;
312 debug_print("Clamd plugin loaded\n");
314 return 0;
318 gboolean plugin_done(void)
320 hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, hook_id);
321 g_free(config.clamav_save_folder);
322 clamav_gtk_done();
323 clamd_free();
325 debug_print("Clamd plugin unloaded\n");
326 return TRUE;
329 const gchar *plugin_name(void)
331 return PLUGIN_NAME;
334 const gchar *plugin_desc(void)
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");
357 const gchar *plugin_type(void)
359 return "GTK3";
362 const gchar *plugin_licence(void)
364 return "GPL3+";
367 const gchar *plugin_version(void)
369 return VERSION;
372 struct PluginFeature *plugin_provides(void)
374 static struct PluginFeature features[] =
375 { {PLUGIN_FILTERING, N_("Virus detection")},
376 {PLUGIN_NOTHING, NULL}};
377 return features;