dd53ef63f1e39438509be53d88eba70da3b50f34
[claws.git] / src / common / plugin.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2012 Hiroyuki Yamamoto 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
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #include "claws-features.h"
24 #endif
25
26 #include <stdio.h>
27 #ifdef HAVE_VALGRIND
28 #include <valgrind.h>
29 #endif
30
31 #include "defs.h"
32 #include <glib.h>
33 #ifdef ENABLE_NLS
34 #include <glib/gi18n.h>
35 #else
36 #define _(a) (a)
37 #define N_(a) (a)
38 #endif
39 #include <gmodule.h>
40
41 #include "utils.h"
42 #include "plugin.h"
43 #include "prefs.h"
44 #include "claws.h"
45 #include "timing.h"
46
47 struct _Plugin
48 {
49         gchar   *filename;
50         GModule *module;
51         const gchar *(*name) (void);
52         const gchar *(*desc) (void);
53         const gchar *(*version) (void);
54         const gchar *(*type) (void);
55         const gchar *(*licence) (void);
56         struct PluginFeature *(*provides) (void);
57         
58         GSList *rdeps;
59         gchar *error;
60         gboolean unloaded_hidden;
61 };
62
63 const gchar *plugin_feature_names[] =
64         { N_("Nothing"),
65           N_("a viewer"),
66           N_("a MIME parser"),
67           N_("folders"),
68           N_("filtering"),
69           N_("a privacy interface"),
70           N_("a notifier"),
71           N_("an utility"),
72           N_("things"),
73           NULL };
74
75 /* The plugin must be at least under one of these licences and have
76    the corresponding token returned by the plugin_licence function.
77  */
78 const gchar const *plugin_licence_tokens[] = {
79   "LGPL2.1+", "LGPLv2.1+", "LGPL2.1", "LGPLv2.1",
80   "LGPL3+", "LGPLv3+", "LGPL3", "LGPLv3",
81   "GPL3+", "GPLv3+", "GPL3", "GPLv3",
82   "GPL2+", "GPLv2+",
83   "Apache2.0", "Apache 2.0", "Apache v2.0",
84   "2-clause BSD", "Simplified BSD", "FreeBSD",
85   "3-clause BSD", "New BSD", "Modified BSD",
86   NULL
87 };
88
89 /* Dual (or more) licences are allowed, must be separated by one of these.
90  */
91 #define IS_LICENCE_SEP(a) ((a) == ',' || (a) == ';' || (a) == '|' || (a) == '/' || (a) == '\0')
92
93 /**
94  * List of all loaded plugins
95  */
96 GSList *plugins = NULL;
97 GSList *plugin_types = NULL;
98
99 /* 
100  * List of plugins unloaded for some fixable reason
101  */
102 static GSList *unloaded_plugins = NULL;
103
104 static gint list_find_by_string(gconstpointer data, gconstpointer str)
105 {
106         return strcmp((gchar *)data, (gchar *)str) ? TRUE : FALSE;
107 }
108
109 static gint list_find_by_plugin_filename(const Plugin *plugin, const gchar *filename)
110 {
111         /* FIXME: There is a problem in case of symlinks or when a
112            user tries to load a plugin with the same name from a
113            different directory.  I think it would be better to compare
114            only the basename of the filename here (case-insensitive on
115            W32). */
116         cm_return_val_if_fail(plugin, 1);
117         cm_return_val_if_fail(plugin->filename, 1);
118         cm_return_val_if_fail(filename, 1);
119         return strcmp(filename, plugin->filename);
120 }
121
122 void plugin_save_list(void)
123 {
124         gchar *rcpath, *block;
125         PrefFile *pfile;
126         GSList *type_cur, *plugin_cur;
127         Plugin *plugin;
128         
129         for (type_cur = plugin_types; type_cur != NULL; type_cur = g_slist_next(type_cur)) {
130                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
131 #ifdef G_OS_WIN32
132                 block = g_strconcat("PluginsWin32_", type_cur->data, NULL);
133 #else
134                 block = g_strconcat("Plugins_", type_cur->data, NULL);
135 #endif
136                 if ((pfile = prefs_write_open(rcpath)) == NULL ||
137                     (prefs_set_block_label(pfile, block) < 0)) {
138                         g_warning("failed to write plugin list\n");
139                         g_free(rcpath);
140                         return;
141                 }
142                 g_free(block);
143
144                 for (plugin_cur = plugins; plugin_cur != NULL; plugin_cur = g_slist_next(plugin_cur)) {
145                         plugin = (Plugin *) plugin_cur->data;
146                         
147                         if (plugin->unloaded_hidden)
148                                 continue;
149
150                         if (!strcmp(plugin->type(), type_cur->data))
151                                 if (fprintf(pfile->fp, "%s\n", plugin->filename) < 0)
152                                         goto revert;
153                 }
154                 for (plugin_cur = unloaded_plugins; plugin_cur != NULL; plugin_cur = g_slist_next(plugin_cur)) {
155                         plugin = (Plugin *) plugin_cur->data;
156
157                         if (plugin->unloaded_hidden)
158                                 continue;
159                         
160                         if (!strcmp(plugin->type(), type_cur->data))
161                                 if (fprintf(pfile->fp, "%s\n", plugin->filename) < 0)
162                                         goto revert;
163                 }
164                 if (fprintf(pfile->fp, "\n") < 0)
165                         goto revert;
166
167                 if (prefs_file_close(pfile) < 0)
168                         g_warning("failed to write plugin list\n");
169
170                 g_free(rcpath); 
171                 
172                 continue;
173
174 revert:
175                 g_warning("failed to write plugin list\n");
176                 if (prefs_file_close_revert(pfile) < 0)
177                         g_warning("failed to revert plugin list\n");
178
179                 g_free(rcpath); 
180         }
181 }
182
183 static gboolean plugin_is_loaded(const gchar *filename)
184 {
185         return (g_slist_find_custom(plugins, filename, 
186                   (GCompareFunc)list_find_by_plugin_filename) != NULL);
187 }
188
189 static Plugin *plugin_get_by_filename(const gchar *filename)
190 {
191         GSList *cur = plugins;
192         for(; cur; cur = cur->next) {
193                 Plugin *p = (Plugin *)cur->data;
194                 if (!strcmp(p->filename, filename)) {
195                         return p;
196                 } 
197         }
198         return NULL;
199 }
200
201 /** 
202  * Loads a plugin dependancies
203  * 
204  * Plugin dependancies are, optionnaly, listed in a file in
205  * get_plugin_dir()/$pluginname.deps.
206  * \param filename The filename of the plugin for which we have to load deps
207  * \param error The location where an error string can be stored
208  * \return 0 on success, -1 otherwise
209  */
210 static gint plugin_load_deps(const gchar *filename, gchar **error)
211 {
212         gchar *tmp;
213         gchar *deps_file = NULL;
214         FILE *fp = NULL;
215         gchar buf[BUFFSIZE];
216         gchar *p;
217
218         tmp = g_strdup(filename);
219         if( (p = strrchr(tmp, '.')) )
220           *p = '\0';
221         deps_file = g_strconcat(tmp, ".deps", NULL);
222         g_free(tmp);
223         
224         fp = g_fopen(deps_file, "rb");
225         g_free(deps_file);
226         
227         if (!fp)
228                 return 0;
229         
230         while (fgets(buf, sizeof(buf), fp) != NULL) {
231                 Plugin *dep_plugin = NULL;
232                 gchar *path = NULL;
233                 buf[strlen(buf)-1]='\0'; /* chop off \n */
234                 path = g_strconcat(get_plugin_dir(), buf,
235                                 ".", G_MODULE_SUFFIX, NULL);
236                 if ((dep_plugin = plugin_get_by_filename(path)) == NULL) {
237                         debug_print("trying to load %s\n", path);
238                         dep_plugin = plugin_load(path, error);
239                         if (dep_plugin == NULL) {
240                                 g_free(path);
241                                 fclose(fp);
242                                 return -1;
243                         }
244                 }
245                 g_free(path);
246                 if (!g_slist_find_custom(dep_plugin->rdeps, 
247                                 (gpointer) filename, list_find_by_string)) {
248                         debug_print("adding %s to %s rdeps\n",
249                                 filename,
250                                 dep_plugin->filename);
251                         dep_plugin->rdeps = 
252                                 g_slist_append(dep_plugin->rdeps, 
253                                         g_strdup(filename));
254                 }
255         }
256         fclose(fp);
257         return 0;
258 }
259
260 static void plugin_unload_rdeps(Plugin *plugin)
261 {
262         GSList *cur = plugin->rdeps;
263         debug_print("removing %s rdeps\n", plugin->filename);
264         while (cur) {
265                 gchar *file = (gchar *)cur->data;
266                 Plugin *rdep_plugin = file?plugin_get_by_filename(file):NULL;
267                 debug_print(" rdep %s: %p\n", file, rdep_plugin);
268                 if (rdep_plugin) {
269                         plugin_unload(rdep_plugin);
270                 }
271                 g_free(file);
272                 cur = cur->next;
273         }
274         g_slist_free(plugin->rdeps);
275         plugin->rdeps = NULL;
276 }
277
278 static void plugin_remove_from_unloaded_list (const gchar *filename)
279 {
280         GSList *item = g_slist_find_custom(unloaded_plugins, 
281                                 (gpointer) filename, (GCompareFunc)list_find_by_plugin_filename);
282         Plugin *unloaded_plugin = item ? ((Plugin *)item->data):NULL;
283         if (unloaded_plugin != NULL) {
284                 debug_print("removing %s from unloaded list\n", unloaded_plugin->filename);
285                 unloaded_plugins = g_slist_remove(unloaded_plugins, unloaded_plugin);
286                 g_module_close(unloaded_plugin->module);
287                 g_free(unloaded_plugin->filename);
288                 g_free(unloaded_plugin->error);
289                 g_free(unloaded_plugin);
290         }
291 }
292
293 static gchar *plugin_check_features(struct PluginFeature *features) {
294         int i = 0, j = 0;
295         GSList *cur = plugins;
296
297         if (features == NULL)
298                 return NULL;
299         for(; cur; cur = cur->next) {
300                 Plugin *p = (Plugin *)cur->data;
301                 struct PluginFeature *cur_features = p->provides();
302                 if (p->unloaded_hidden)
303                         continue;
304                 for (j = 0; cur_features[j].type != PLUGIN_NOTHING; j++) {
305                         for (i = 0; features[i].type != PLUGIN_NOTHING; i++) {
306                                 if (cur_features[j].type == features[i].type &&
307                                     !strcmp(cur_features[j].subtype, features[i].subtype)) {
308                                         return g_strdup_printf(_(
309                                                 "This plugin provides %s (%s), which is "
310                                                 "already provided by the %s plugin."),
311                                                 _(plugin_feature_names[features[i].type]), 
312                                                 _(features[i].subtype),
313                                                 p->name());
314                                 }
315                         }
316                 }
317         }
318
319         return NULL;
320 }
321
322 static gboolean plugin_licence_check(const gchar *licence) {
323         gint i = 0;
324         gint len = 0;
325         
326         if (licence != NULL) {
327                 len = strlen(licence);
328         }
329         if (len == 0) {
330                 g_warning("plugin licence check failed: empty licence\n");
331                 return FALSE;
332         }
333         while (plugin_licence_tokens[i] != NULL) {
334                 gchar *found = g_strstr_len(licence, len, plugin_licence_tokens[i]);
335                 if (found != NULL) {
336                         gint tlen = strlen(plugin_licence_tokens[i]);
337                         if (len != tlen) { /* not a single license */
338                                 if (((found == licence) &&  (!IS_LICENCE_SEP(licence[tlen])))
339                                                 || (!IS_LICENCE_SEP(*(found - 1))) 
340                                                 || (!IS_LICENCE_SEP(*(found + tlen)))) {
341                                         debug_print("plugin licence check failed: invalid separator\n");
342                                         return FALSE;
343                                 }
344                         }
345                         debug_print("plugin licence check passed: %s found\n", plugin_licence_tokens[i]);
346                         return TRUE;
347                 }
348                 ++i;
349         }
350         debug_print("plugin licence check failed: %s is not a valid licence\n", licence);
351         return FALSE;
352 }
353
354 /**
355  * Loads a plugin
356  *
357  * \param filename The filename of the plugin to load
358  * \param error The location where an error string can be stored
359  * \return the plugin on success, NULL otherwise
360  */
361 Plugin *plugin_load(const gchar *filename, gchar **error)
362 {
363         Plugin *plugin;
364         gint (*plugin_init) (gchar **error);
365         gpointer plugin_name, plugin_desc, plugin_version;
366         const gchar *(*plugin_type)(void);
367         const gchar *(*plugin_licence)(void);
368         struct PluginFeature *(*plugin_provides)(void);
369         gint ok;
370         START_TIMING((filename?filename:"NULL plugin"));
371         cm_return_val_if_fail(filename != NULL, NULL);
372         cm_return_val_if_fail(error != NULL, NULL);
373
374         /* check duplicate plugin path name */
375         if (plugin_is_loaded(filename)) {
376                 plugin = plugin_get_by_filename(filename);
377                 if (plugin->unloaded_hidden) {
378                         /* reshow it */
379                         goto init_plugin;
380                 } else {
381                         *error = g_strdup(_("Plugin already loaded"));
382                         return NULL;            
383                 }
384         }                              
385         
386         plugin_remove_from_unloaded_list(filename);
387         
388         if (plugin_load_deps(filename, error) < 0)
389                 return NULL;
390         plugin = g_new0(Plugin, 1);
391         if (plugin == NULL) {
392                 *error = g_strdup(_("Failed to allocate memory for Plugin"));
393                 return NULL;
394         }
395
396         debug_print("trying to load `%s'\n", filename);
397         plugin->module = g_module_open(filename, 0);
398         if (plugin->module == NULL) {
399                 *error = g_strdup(g_module_error());
400                 g_free(plugin);
401                 return NULL;
402         }
403
404 init_plugin:
405         if (!g_module_symbol(plugin->module, "plugin_name", &plugin_name) ||
406             !g_module_symbol(plugin->module, "plugin_desc", &plugin_desc) ||
407             !g_module_symbol(plugin->module, "plugin_version", &plugin_version) ||
408             !g_module_symbol(plugin->module, "plugin_type", (gpointer)&plugin_type) ||
409             !g_module_symbol(plugin->module, "plugin_licence", (gpointer)&plugin_licence) ||
410             !g_module_symbol(plugin->module, "plugin_provides", (gpointer)&plugin_provides) ||
411             !g_module_symbol(plugin->module, "plugin_init", (gpointer)&plugin_init)) {
412                 *error = g_strdup(g_module_error());
413                 if (plugin->unloaded_hidden)
414                         return NULL;
415                 g_module_close(plugin->module);
416                 g_free(plugin);
417                 return NULL;
418         }
419         
420         if (plugin_licence_check(plugin_licence()) != TRUE) {
421                 *error = g_strdup(_("This module is not licensed under a GPL v3 or later compatible license."));
422                 if (plugin->unloaded_hidden)
423                         return NULL;
424                 g_module_close(plugin->module);
425                 g_free(plugin);
426                 return NULL;
427         }
428
429         if (!strcmp(plugin_type(), "GTK")) {
430                 *error = g_strdup(_("This module is for Claws Mail GTK1."));
431                 if (plugin->unloaded_hidden)
432                         return NULL;
433                 g_module_close(plugin->module);
434                 g_free(plugin);
435                 return NULL;
436         }
437
438         if ((*error = plugin_check_features(plugin_provides())) != NULL) {
439                 if (plugin->unloaded_hidden)
440                         return NULL;
441                 g_module_close(plugin->module);
442                 g_free(plugin);
443                 return NULL;
444         }
445         plugin->name = plugin_name;
446         plugin->desc = plugin_desc;
447         plugin->version = plugin_version;
448         plugin->type = plugin_type;
449         plugin->licence = plugin_licence;
450         plugin->provides = plugin_provides;
451         plugin->filename = g_strdup(filename);
452         plugin->error = NULL;
453
454         if ((ok = plugin_init(error)) < 0) {
455                 if (*error)
456                         plugin->error = g_strdup(*error);
457                 unloaded_plugins = g_slist_append(unloaded_plugins, plugin);
458                 return NULL;
459         }
460
461         if (!plugin->unloaded_hidden)
462                 plugins = g_slist_append(plugins, plugin);
463         plugin->unloaded_hidden = FALSE;
464
465         debug_print("Plugin %s (from file %s) loaded\n", plugin->name(), filename);
466         END_TIMING();
467         return plugin;
468 }
469
470 void plugin_unload(Plugin *plugin)
471 {
472         gboolean (*plugin_done) (void);
473         gboolean can_unload = TRUE;
474
475         plugin_unload_rdeps(plugin);
476
477         if (plugin->unloaded_hidden)
478                 return;
479
480         if (plugin->error) {
481                 plugin_remove_from_unloaded_list(plugin->filename);
482                 return;
483         }
484         if (g_module_symbol(plugin->module, "plugin_done", (gpointer) &plugin_done)) {
485                 can_unload = plugin_done();
486         }
487
488         if (can_unload) {
489 #ifdef HAVE_VALGRIND
490                 if (!RUNNING_ON_VALGRIND) {
491                         g_module_close(plugin->module);
492                 }
493 #else
494                 g_module_close(plugin->module);
495 #endif
496                 plugins = g_slist_remove(plugins, plugin);
497                 g_free(plugin->filename);
498                 g_free(plugin);
499         } else {
500                 plugin->unloaded_hidden = TRUE;
501         }
502
503 }
504
505 void plugin_load_all(const gchar *type)
506 {
507         gchar *rcpath;
508         gchar buf[BUFFSIZE];
509         PrefFile *pfile;
510         gchar *error = NULL, *block;
511
512         plugin_types = g_slist_append(plugin_types, g_strdup(type));
513
514         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL); 
515 #ifdef G_OS_WIN32
516         block = g_strconcat("PluginsWin32_", type, NULL);
517 #else
518         block = g_strconcat("Plugins_", type, NULL);
519 #endif
520         if ((pfile = prefs_read_open(rcpath)) == NULL ||
521             (prefs_set_block_label(pfile, block) < 0)) {
522                 g_free(rcpath);
523                 if (pfile)
524                         prefs_file_close(pfile);
525                 return;
526         }
527         g_free(block);
528
529         while (fgets(buf, sizeof(buf), pfile->fp) != NULL) {
530                 if (buf[0] == '[')
531                         break;
532
533                 g_strstrip(buf);
534                 if ((buf[0] != '\0') && (plugin_load(buf, &error) == NULL)) {
535                         g_warning("plugin loading error: %s\n", error);
536                         g_free(error);
537                 }                                                       
538         }
539         prefs_file_close(pfile);
540
541         g_free(rcpath);
542 }
543
544 void plugin_unload_all(const gchar *type)
545 {
546         GSList *list, *cur;
547
548         list = g_slist_copy(plugins);
549         list = g_slist_reverse(list);
550
551         for(cur = list; cur != NULL; cur = g_slist_next(cur)) {
552                 Plugin *plugin = (Plugin *) cur->data;
553                 
554                 if (!strcmp(type, plugin->type()))
555                         plugin_unload(plugin);
556         }
557         g_slist_free(list);
558
559         cur = g_slist_find_custom(plugin_types, (gpointer) type, list_find_by_string);
560         if (cur) {
561                 g_free(cur->data);
562                 plugin_types = g_slist_remove(plugin_types, cur);
563         }
564 }
565
566
567 /* Load those plugins we always want to use.  No error output; just
568  * try. */
569 void plugin_load_standard_plugins (void)
570 {
571         static const char *names[] = {
572 #ifdef G_OS_WIN32 
573                 "pgpmime",
574                 "pgpinline",
575 #else
576                 /* post-2.5 maybe 
577                 "bogofilter", */
578 #endif
579                 NULL
580         };
581         int i;
582         gchar *error, *filename;
583         
584         for (i=0; names[i]; i++) {
585                 /* Simple hack to check whether the plugin has already
586                  * been loaded but checking only for the basename. */
587                 GSList *cur = plugins;
588                 for(; cur; cur = cur->next) {
589                         Plugin *p = (Plugin *)cur->data;
590                         if (strstr(p->filename, names[i]))
591                                 break;
592                 }
593                 if (!cur) { /* Not yet loaded. */
594                         /* FIXME: get_plugin_dir () returns with a trailing
595                          * (back)slash; this should be fixed so that we can use
596                          * g_module_build_path here. */
597 #ifdef G_OS_WIN32 
598                         filename = g_strconcat (get_plugin_dir(),
599                                                 names[i], NULL);
600 #else
601                         filename = g_strconcat (get_plugin_dir(),
602                                                 names[i], ".", G_MODULE_SUFFIX, NULL);
603 #endif
604                         error = NULL;
605                         plugin_load(filename, &error);
606                         g_free (error);
607                         g_free(filename);
608                 }
609         }
610 }
611
612 GSList *plugin_get_list(void)
613 {
614         GSList *new = NULL;
615         GSList *cur = plugins;
616         for (; cur; cur = cur->next) {
617                 Plugin *p = (Plugin *)cur->data;
618                 if (!p->unloaded_hidden)
619                         new = g_slist_prepend(new, p);
620         }
621         new = g_slist_reverse(new);
622         return new;
623 }
624
625 Plugin *plugin_get_loaded_by_name(const gchar *name) 
626 {
627         Plugin *plugin = NULL;
628         GSList *new, *cur; 
629         new = plugin_get_list();
630         for (cur = new; cur; cur = g_slist_next(cur)) {
631                 plugin = (Plugin *)cur->data;
632                 if (!g_ascii_strcasecmp(plugin->name(), name)) 
633                         break;
634                 else 
635                         plugin = NULL;
636         }
637         g_slist_free(new);
638         return plugin;
639 }
640
641 GSList *plugin_get_unloaded_list(void)
642 {
643         return g_slist_copy(unloaded_plugins);
644 }
645
646 const gchar *plugin_get_name(Plugin *plugin)
647 {
648         return plugin->name();
649 }
650
651 const gchar *plugin_get_desc(Plugin *plugin)
652 {
653         return plugin->desc();
654 }
655
656 const gchar *plugin_get_version(Plugin *plugin)
657 {
658         return plugin->version();
659 }
660
661 const gchar *plugin_get_error(Plugin *plugin)
662 {
663         return plugin->error;
664 }
665
666 /* Generally called in plugin_init() function of each plugin. It check the
667  * minimal and compiled version of claws binary required by the plugin.
668  * If (@minimum_claws_version == 0 || @compiled_claws_version == 0), don't
669  * check the corresponding version.
670  *
671  * If an error occurs {
672  *      If @error == NULL { don't allocate error string. }
673  *      If @error != NULL { error string is allocated and must be freed after 
674  *                              call function. }
675  * }
676  * Returns: FALSE if an error occurs, TRUE if all is OK.
677  */
678 gint check_plugin_version(guint32 minimum_claws_version,
679                          guint32 compiled_claws_version,
680                          const gchar *plugin_name,
681                          gchar **error)
682 {
683         guint32 claws_version = claws_get_version();
684
685         if (compiled_claws_version != 0 && claws_version > compiled_claws_version) {
686                 if (error != NULL) {
687                         *error = (plugin_name && *plugin_name)
688                                 ? g_strdup_printf(_("Your version of Claws Mail is newer than the "
689                                                         "version the '%s' plugin was built with."),
690                                                 plugin_name)
691                                 : g_strdup(_("Your version of Claws Mail is newer than the "
692                                                         "version the plugin was built with."));
693                 }
694                 return FALSE;
695         }
696
697         if (minimum_claws_version != 0 && claws_version < minimum_claws_version) {
698                 if (error != NULL) {
699                         *error = (plugin_name && *plugin_name)
700                                 ? g_strdup_printf(_("Your version of Claws Mail is too old for "
701                                                         "the '%s' plugin."), plugin_name)
702                                 : g_strdup(_("Your version of Claws Mail is too old for the plugin."));
703                 }
704                 return FALSE;
705         }
706         return TRUE;
707 }