2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2014 Colin Leroy <colin@colino.net>
4 * and the Claws Mail Team
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #include "claws-features.h"
30 #include <glib/gi18n.h>
32 #include "common/claws.h"
33 #include "common/version.h"
37 #include "prefs_common.h"
38 #include "file-utils.h"
40 #include "spam_report_prefs.h"
41 #include "statusbar.h"
47 #include "procheader.h"
53 #include <curl/curl.h>
54 #include <curl/curlver.h>
56 struct CurlReadWrite
{
61 static gboolean
check_debian_listid(MsgInfo
*msginfo
);
63 /* this interface struct is probably not enough for the various available
64 * reporting places/methods. It'll be extended as necessary. */
66 #define SSFR_URL "https://www.signal-spam.fr/api/signaler"
67 #define SSFR_BODY "message=%claws_mail_body_b64%"
69 #define DEBL_URL "https://lists.debian.org/cgi-bin/nominate-for-review.pl?Quiet=on&msgid=%claws_mail_msgid%"
71 ReportInterface spam_interfaces
[] = {
72 { "Signal-Spam.fr", INTF_HTTP_AUTH
, SSFR_URL
, SSFR_BODY
, NULL
},
73 { "Spamcop.net", INTF_MAIL
, NULL
, NULL
, NULL
},
74 { "Debian Lists", INTF_HTTP_GET
, DEBL_URL
, NULL
, check_debian_listid
},
75 { NULL
, INTF_NULL
, NULL
, NULL
, NULL
}
78 /* From RSSyl. This should be factorized to the core... */
79 static gchar
*spamreport_strreplace(gchar
*source
, gchar
*pattern
,
82 gchar
*new, *w_new
, *c
;
83 guint count
= 0, final_length
;
84 size_t len_pattern
, len_replacement
;
86 if( source
== NULL
|| pattern
== NULL
) {
87 debug_print("source or pattern is NULL!!!\n");
91 if( !g_utf8_validate(source
, -1, NULL
) ) {
92 debug_print("source is not an UTF-8 encoded text\n");
96 if( !g_utf8_validate(pattern
, -1, NULL
) ) {
97 debug_print("pattern is not an UTF-8 encoded text\n");
101 len_pattern
= strlen(pattern
);
102 len_replacement
= replacement
? strlen(replacement
) : 0;
105 while( ( c
= g_strstr_len(c
, strlen(c
), pattern
) ) ) {
110 final_length
= strlen(source
)
111 - ( count
* len_pattern
)
112 + ( count
* len_replacement
);
114 new = malloc(final_length
+ 1);
116 memset(new, '\0', final_length
+ 1);
120 while( *c
!= '\0' ) {
121 if( !memcmp(c
, pattern
, len_pattern
) ) {
122 gboolean break_after_rep
= FALSE
;
124 if (*(c
+ len_pattern
) == '\0')
125 break_after_rep
= TRUE
;
126 for (i
= 0; i
< len_replacement
; i
++) {
127 *w_new
= replacement
[i
];
142 static gboolean
check_debian_listid(MsgInfo
*msginfo
)
145 if (!procheader_get_header_from_msginfo(msginfo
, &buf
, "List-Id:") && buf
!= NULL
) {
146 if (strstr(buf
, "lists.debian.org")) {
155 static void spamreport_http_response_log(gchar
*url
, long response
)
158 case 400: /* Bad Request */
159 log_error(LOG_PROTOCOL
, "%s: Bad Request\n", url
);
161 case 401: /* Not Authorized */
162 log_error(LOG_PROTOCOL
, "%s: Wrong login or password\n", url
);
164 case 404: /* Not Authorized */
165 log_error(LOG_PROTOCOL
, "%s: Not found\n", url
);
170 static void *myrealloc(void *pointer
, size_t size
) {
172 * There might be a realloc() out there that doesn't like reallocing
173 * NULL pointers, so we take care of it here.
176 return realloc(pointer
, size
);
182 static size_t curl_writefunction_cb(void *pointer
, size_t size
, size_t nmemb
, void *data
) {
183 size_t realsize
= size
* nmemb
;
184 struct CurlReadWrite
*mem
= (struct CurlReadWrite
*)data
;
186 mem
->data
= myrealloc(mem
->data
, mem
->size
+ realsize
+ 1);
188 memcpy(&(mem
->data
[mem
->size
]), pointer
, realsize
);
189 mem
->size
+= realsize
;
190 mem
->data
[mem
->size
] = 0;
195 static void report_spam(gint id
, ReportInterface
*intf
, MsgInfo
*msginfo
, gchar
*contents
)
197 gchar
*reqbody
= NULL
, *tmp
= NULL
, *auth
= NULL
, *b64
= NULL
, *geturl
= NULL
;
202 struct CurlReadWrite chunk
;
207 if (spamreport_prefs
.enabled
[id
] == FALSE
) {
208 debug_print("not reporting via %s (disabled)\n", intf
->name
);
211 if (intf
->should_report
!= NULL
&& (intf
->should_report
)(msginfo
) == FALSE
) {
212 debug_print("not reporting via %s (unsuitable)\n", intf
->name
);
216 debug_print("reporting via %s\n", intf
->name
);
217 tmp
= spamreport_strreplace(intf
->body
, "%claws_mail_body%", contents
);
218 len_contents
= strlen(contents
);
219 b64
= g_base64_encode(contents
, len_contents
);
220 reqbody
= spamreport_strreplace(tmp
, "%claws_mail_body_b64%", b64
);
221 geturl
= spamreport_strreplace(intf
->url
, "%claws_mail_msgid%", msginfo
->msgid
);
227 if (spamreport_prefs
.user
[id
] && *(spamreport_prefs
.user
[id
])) {
228 gchar
*pass
= spamreport_passwd_get(spam_interfaces
[id
].name
);
229 auth
= g_strdup_printf("%s:%s", spamreport_prefs
.user
[id
], (pass
!= NULL
? pass
: ""));
231 memset(pass
, 0, strlen(pass
));
235 curl
= curl_easy_init();
236 curl_easy_setopt(curl
, CURLOPT_URL
, intf
->url
);
237 curl_easy_setopt(curl
, CURLOPT_POSTFIELDS
, reqbody
);
238 curl_easy_setopt(curl
, CURLOPT_USERPWD
, auth
);
239 curl_easy_setopt(curl
, CURLOPT_TIMEOUT
, prefs_common_get_prefs()->io_timeout_secs
);
240 curl_easy_setopt(curl
, CURLOPT_USERAGENT
,
241 SPAM_REPORT_USERAGENT
"(" PLUGINS_URI
")");
243 curl_easy_setopt(curl
, CURLOPT_CAINFO
, claws_ssl_get_cert_file());
245 res
= curl_easy_perform(curl
);
247 debug_print("curl_easy_perfom failed: %s", curl_easy_strerror(res
));
248 curl_easy_getinfo(curl
, CURLINFO_RESPONSE_CODE
, &response
);
249 curl_easy_cleanup(curl
);
250 spamreport_http_response_log(intf
->url
, response
);
255 if (spamreport_prefs
.user
[id
] && *(spamreport_prefs
.user
[id
])) {
256 Compose
*compose
= compose_forward(NULL
, msginfo
, TRUE
, NULL
, TRUE
, TRUE
);
257 compose
->use_signing
= FALSE
;
258 compose_entry_append(compose
, spamreport_prefs
.user
[id
], COMPOSE_TO
, PREF_NONE
);
259 compose_send(compose
);
263 curl
= curl_easy_init();
264 curl_easy_setopt(curl
, CURLOPT_URL
, geturl
);
265 curl_easy_setopt(curl
, CURLOPT_USERAGENT
,
266 SPAM_REPORT_USERAGENT
"(" PLUGINS_URI
")");
267 curl_easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, curl_writefunction_cb
);
268 curl_easy_setopt(curl
, CURLOPT_WRITEDATA
, (void *)&chunk
);
270 curl_easy_setopt(curl
, CURLOPT_CAINFO
, claws_ssl_get_cert_file());
272 res
= curl_easy_perform(curl
);
274 debug_print("curl_easy_perfom failed: %s", curl_easy_strerror(res
));
275 curl_easy_getinfo(curl
, CURLINFO_RESPONSE_CODE
, &response
);
276 curl_easy_cleanup(curl
);
277 spamreport_http_response_log(geturl
, response
);
278 /* On success the page should return "OK: nominated <msgid>" */
279 if (chunk
.size
< 13 || strstr(chunk
.data
, "OK: nominated") == NULL
) {
280 if (chunk
.size
> 0) {
281 log_error(LOG_PROTOCOL
, "%s: response was %s\n", geturl
, chunk
.data
);
284 log_error(LOG_PROTOCOL
, "%s: response was empty\n", geturl
);
289 g_warning("unknown method");
295 static void report_spam_cb_ui(GtkAction
*action
, gpointer data
)
297 MainWindow
*mainwin
= mainwindow_get_mainwindow();
298 SummaryView
*summaryview
= mainwin
->summaryview
;
299 GSList
*msglist
= summary_get_selected_msg_list(summaryview
);
301 gint curnum
=0, total
=0;
302 if (summary_is_locked(summaryview
) || !msglist
) {
304 g_slist_free(msglist
);
307 main_window_cursor_wait(summaryview
->mainwin
);
308 gtk_cmclist_freeze(GTK_CMCLIST(summaryview
->ctree
));
309 folder_item_update_freeze();
312 STATUSBAR_PUSH(mainwin
, _("Reporting spam..."));
313 total
= g_slist_length(msglist
);
315 for (cur
= msglist
; cur
; cur
= cur
->next
) {
316 MsgInfo
*msginfo
= (MsgInfo
*)cur
->data
;
317 gchar
*file
= procmsg_get_message_file(msginfo
);
318 gchar
*contents
= NULL
;
322 debug_print("reporting message %d (%s)\n", msginfo
->msgnum
, file
);
323 statusbar_progress_all(curnum
, total
, 1);
327 contents
= file_read_to_str(file
);
329 for (i
= 0; i
< INTF_LAST
; i
++)
330 report_spam(i
, &(spam_interfaces
[i
]), msginfo
, contents
);
336 statusbar_progress_all(0,0,0);
337 STATUSBAR_POP(mainwin
);
339 folder_item_update_thaw();
340 gtk_cmclist_thaw(GTK_CMCLIST(summaryview
->ctree
));
341 main_window_cursor_normal(summaryview
->mainwin
);
342 g_slist_free(msglist
);
345 static GtkActionEntry spamreport_main_menu
[] = {{
346 "Message/ReportSpam",
347 NULL
, N_("Report spam online..."), NULL
, NULL
, G_CALLBACK(report_spam_cb_ui
)
350 static guint context_menu_id
= 0;
351 static guint main_menu_id
= 0;
353 gint
plugin_init(gchar
**error
)
355 MainWindow
*mainwin
= mainwindow_get_mainwindow();
357 if (!check_plugin_version(MAKE_NUMERIC_VERSION(3,13,2,39),
358 VERSION_NUMERIC
, _("SpamReport"), error
))
361 spamreport_prefs_init();
363 curl_global_init(CURL_GLOBAL_DEFAULT
);
365 gtk_action_group_add_actions(mainwin
->action_group
, spamreport_main_menu
,
366 1, (gpointer
)mainwin
);
367 MENUITEM_ADDUI_ID_MANAGER(mainwin
->ui_manager
, "/Menu/Message", "ReportSpam",
368 "Message/ReportSpam", GTK_UI_MANAGER_MENUITEM
,
370 MENUITEM_ADDUI_ID_MANAGER(mainwin
->ui_manager
, "/Menus/SummaryViewPopup", "ReportSpam",
371 "Message/ReportSpam", GTK_UI_MANAGER_MENUITEM
,
376 gboolean
plugin_done(void)
378 MainWindow
*mainwin
= mainwindow_get_mainwindow();
383 MENUITEM_REMUI_MANAGER(mainwin
->ui_manager
,mainwin
->action_group
, "Message/ReportSpam", main_menu_id
);
386 MENUITEM_REMUI_MANAGER(mainwin
->ui_manager
,mainwin
->action_group
, "Message/ReportSpam", context_menu_id
);
389 spamreport_prefs_done();
394 const gchar
*plugin_name(void)
396 return _("SpamReport");
399 const gchar
*plugin_desc(void)
401 return _("This plugin reports spam to various places.\n"
402 "Currently the following sites or methods are supported:\n\n"
403 " * spam-signal.fr\n"
405 " * lists.debian.org nomination system");
408 const gchar
*plugin_type(void)
413 const gchar
*plugin_licence(void)
418 const gchar
*plugin_version(void)
423 struct PluginFeature
*plugin_provides(void)
425 static struct PluginFeature features
[] =
426 { {PLUGIN_UTILITY
, N_("Spam reporting")},
427 {PLUGIN_NOTHING
, NULL
}};