2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2003-2010 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/>.
22 # include "claws-features.h"
26 #include <glib/gi18n.h>
30 #include "common/claws.h"
31 #include "common/version.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"))
50 static MessageCallback message_callback;
52 static ClamAvConfig config;
54 static PrefParam param[] = {
55 {"clamav_enable", "FALSE", &config.clamav_enable, P_BOOL,
57 /* {"clamav_enable_arc", "FALSE", &config.clamav_enable_arc, P_BOOL,
59 {"clamav_max_size", "1", &config.clamav_max_size, P_USHORT,
61 {"clamav_recv_infected", "TRUE", &config.clamav_recv_infected, P_BOOL,
63 {"clamav_save_folder", NULL, &config.clamav_save_folder, P_STRING,
65 {"clamad_config_type", "TRUE", &config.clamd_config_type, P_BOOL,
67 {"clamd_config_folder", NULL, &config.clamd_config_folder, P_STRING,
69 {"clamd_host", NULL, &config.clamd_host, P_STRING,
71 {"clamd_port", NULL, &config.clamd_port, P_INT,
74 {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
81 static gboolean scan_func(GNode *node, gpointer data)
83 struct clamd_result *result = (struct clamd_result *) data;
84 MimeInfo *mimeinfo = (MimeInfo *) node->data;
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.");
95 max = config.clamav_max_size * 1048576; /* maximum file size */
96 if (stat(outfile, &info) == -1)
97 g_warning("Can't determine file size");
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) {
105 g_warning("[scanning] No socket information");
106 alertpanel_error(_("Scanning\nNo socket information.\nAntivirus disabled."));
109 g_warning("[scanning] Clamd does not respond to ping");
110 alertpanel_warning(_("Scanning\nClamd does not respond to ping.\nIs clamd running?"));
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);
121 alertpanel_warning("%s\n", msg);
126 debug_print("Error: %s\n", buf.msg);
127 alertpanel_error(_("Scanning error:\n%s"), buf.msg);
130 debug_print("No virus detected.\n");
135 debug_print("File: %s. Size (%d) greater than limit (%d)\n",
136 outfile, (int) info.st_size, max);
142 return (result->status == OK) ? FALSE : TRUE;
145 static gboolean mail_filtering_hook(gpointer source, gpointer data)
147 MailFilteringData *mail_filtering_data = (MailFilteringData *) source;
148 MsgInfo *msginfo = mail_filtering_data->msginfo;
151 struct clamd_result result;
153 if (!config.clamav_enable)
156 mimeinfo = procmime_scan_message(msginfo);
157 if (!mimeinfo) return FALSE;
159 debug_print("Scanning message %d for viruses\n", msginfo->msgnum);
160 if (message_callback != NULL)
161 message_callback(_("ClamAV: scanning message..."));
163 debug_print("status: %d\n", result.status);
164 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, scan_func, &result);
166 if (result.status == VIRUS) {
167 if (config.clamav_recv_infected) {
168 FolderItem *clamav_save_folder;
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();
175 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
176 msginfo->filter_op = IS_MOVE;
177 msginfo->to_filter_folder = clamav_save_folder;
179 folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
183 procmime_mimeinfo_free_all(mimeinfo);
185 return (result.status == OK) ? FALSE : TRUE;
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) {
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);
200 else if (config.clamd_config_type || config.clamd_config_folder != NULL) {
201 if (config.clamd_config_folder == NULL) {
205 debug_print("Using clamd.conf: %s\n", config.clamd_config_folder);
206 clamd_create_config_automatic(config.clamd_config_folder);
209 /* Fall back. Try enable anyway */
210 if (! clamd_find_socket())
214 return clamd_init(NULL);
217 ClamAvConfig *clamav_get_config(void)
222 void clamav_save_config(void)
227 debug_print("Saving Clamd Page\n");
229 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
230 pfile = prefs_write_open(rcpath);
232 if (!pfile || (prefs_set_block_label(pfile, "ClamAV") < 0))
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);
240 if (fprintf(pfile->fp, "\n") < 0) {
241 FILE_OP_ERROR(rcpath, "fprintf");
242 prefs_file_close_revert(pfile);
244 prefs_file_close(pfile);
247 void clamav_set_message_callback(MessageCallback callback)
249 message_callback = callback;
252 gint plugin_init(gchar **error)
256 if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
257 VERSION_NUMERIC, PLUGIN_NAME, error))
260 hook_id = hooks_register_hook(MAIL_FILTERING_HOOKLIST, mail_filtering_hook, NULL);
262 *error = g_strdup(_("Failed to register mail filtering hook"));
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);
273 if (config.clamav_enable) {
274 debug_print("Creating socket\n");
275 Clamd_Stat status = clamd_prepare();
278 g_warning("[init] No socket information");
279 alertpanel_error(_("Init\nNo socket information.\nAntivirus disabled."));
282 g_warning("[init] Clamd does not respond to ping");
283 alertpanel_warning(_("Init\nClamd does not respond to ping.\nIs clamd running?"));
290 debug_print("Clamd plugin loaded\n");
296 gboolean plugin_done(void)
298 hooks_unregister_hook(MAIL_FILTERING_HOOKLIST, hook_id);
299 g_free(config.clamav_save_folder);
303 debug_print("Clamd plugin unloaded\n");
307 const gchar *plugin_name(void)
312 const gchar *plugin_desc(void)
314 return _("This plugin uses Clam AntiVirus to scan all messages that are "
315 "received from an IMAP, LOCAL or POP account.\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"
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"
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"
332 "Options can be found in /Configuration/Preferences/Plugins/Clam AntiVirus");
335 const gchar *plugin_type(void)
340 const gchar *plugin_licence(void)
345 const gchar *plugin_version(void)
350 struct PluginFeature *plugin_provides(void)
352 static struct PluginFeature features[] =
353 { {PLUGIN_FILTERING, N_("Virus detection")},
354 {PLUGIN_NOTHING, NULL}};