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