1c39b04d6bc8d9ee97968134c49a98bc770c907a
[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         gboolean in_prefix_dir;
62 };
63
64 const gchar *plugin_feature_names[] =
65         { N_("Nothing"),
66           N_("a viewer"),
67           N_("a MIME parser"),
68           N_("folders"),
69           N_("filtering"),
70           N_("a privacy interface"),
71           N_("a notifier"),
72           N_("an utility"),
73           N_("things"),
74           NULL };
75
76 /* The plugin must be at least under one of these licences and have
77    the corresponding token returned by the plugin_licence function.
78  */
79 const gchar const *plugin_licence_tokens[] = {
80   "LGPL2.1+", "LGPLv2.1+", "LGPL2.1", "LGPLv2.1",
81   "LGPL3+", "LGPLv3+", "LGPL3", "LGPLv3",
82   "GPL3+", "GPLv3+", "GPL3", "GPLv3",
83   "GPL2+", "GPLv2+",
84   "Apache2.0", "Apache 2.0", "Apache v2.0",
85   "2-clause BSD", "Simplified BSD", "FreeBSD",
86   "3-clause BSD", "New BSD", "Modified BSD",
87   NULL
88 };
89
90 /* Dual (or more) licences are allowed, must be separated by one of these.
91  */
92 #define IS_LICENCE_SEP(a) ((a) == ',' || (a) == ';' || (a) == '|' || (a) == '/' || (a) == '\0')
93
94 /**
95  * List of all loaded plugins
96  */
97 GSList *plugins = NULL;
98 GSList *plugin_types = NULL;
99
100 /* 
101  * List of plugins unloaded for some fixable reason
102  */
103 static GSList *unloaded_plugins = NULL;
104
105 static gint list_find_by_string(gconstpointer data, gconstpointer str)
106 {
107         return strcmp((gchar *)data, (gchar *)str) ? TRUE : FALSE;
108 }
109
110 static gint list_find_by_plugin_filename(const Plugin *plugin, const gchar *filename)
111 {
112         /* FIXME: There is a problem in case of symlinks or when a
113            user tries to load a plugin with the same name from a
114            different directory.  I think it would be better to compare
115            only the basename of the filename here (case-insensitive on
116            W32). */
117         cm_return_val_if_fail(plugin, 1);
118         cm_return_val_if_fail(plugin->filename, 1);
119         cm_return_val_if_fail(filename, 1);
120         return strcmp(filename, plugin->filename);
121 }
122
123 static gboolean plugin_filename_is_standard_dir(const gchar *filename) {
124         return strncmp(filename, get_plugin_dir(), strlen(get_plugin_dir())) == 0;
125 }
126
127 static gchar * plugin_canonical_name(const Plugin *plugin)
128 {
129         if (plugin->in_prefix_dir == TRUE) {
130                 if (plugin_filename_is_standard_dir(plugin->filename) == TRUE) {
131                         gchar *plugin_name = g_path_get_basename(plugin->filename);
132                         return plugin_name;
133                 }
134         }
135         return g_strdup(plugin->filename);
136 }
137
138 void plugin_save_list(void)
139 {
140         gchar *rcpath, *block;
141         PrefFile *pfile;
142         GSList *type_cur, *plugin_cur;
143         Plugin *plugin;
144         
145         for (type_cur = plugin_types; type_cur != NULL; type_cur = g_slist_next(type_cur)) {
146                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
147 #ifdef G_OS_WIN32
148                 block = g_strconcat("PluginsWin32_", type_cur->data, NULL);
149 #else
150                 block = g_strconcat("Plugins_", type_cur->data, NULL);
151 #endif
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         gint ok;
425         START_TIMING((filename?filename:"NULL plugin"));
426         cm_return_val_if_fail(filename != NULL, NULL);
427         cm_return_val_if_fail(error != NULL, NULL);
428
429         /* check duplicate plugin path name */
430         if (plugin_is_loaded(filename)) {
431                 plugin = plugin_get_by_filename(filename);
432                 if (plugin->unloaded_hidden) {
433                         /* reshow it */
434                         goto init_plugin;
435                 } else {
436                         *error = g_strdup(_("Plugin already loaded"));
437                         return NULL;            
438                 }
439         }                              
440         
441         plugin_remove_from_unloaded_list(filename);
442         
443         if (plugin_load_deps(filename, error) < 0)
444                 return NULL;
445         plugin = g_new0(Plugin, 1);
446         if (plugin == NULL) {
447                 *error = g_strdup(_("Failed to allocate memory for Plugin"));
448                 return NULL;
449         }
450
451         debug_print("trying to load `%s'\n", filename);
452         plugin->module = g_module_open(filename, 0);
453         if (plugin->module == NULL) {
454                 *error = g_strdup(g_module_error());
455                 g_free(plugin);
456                 if (!plugin_filename_is_standard_dir(filename))
457                         return plugin_load_in_default_dir(filename, error);
458                 else
459                         return NULL;
460         } else {
461                 plugin->in_prefix_dir = FALSE;
462         }
463
464 init_plugin:
465         if (!g_module_symbol(plugin->module, "plugin_name", &plugin_name) ||
466             !g_module_symbol(plugin->module, "plugin_desc", &plugin_desc) ||
467             !g_module_symbol(plugin->module, "plugin_version", &plugin_version) ||
468             !g_module_symbol(plugin->module, "plugin_type", (gpointer)&plugin_type) ||
469             !g_module_symbol(plugin->module, "plugin_licence", (gpointer)&plugin_licence) ||
470             !g_module_symbol(plugin->module, "plugin_provides", (gpointer)&plugin_provides) ||
471             !g_module_symbol(plugin->module, "plugin_init", (gpointer)&plugin_init)) {
472                 *error = g_strdup(g_module_error());
473                 if (plugin->unloaded_hidden)
474                         return NULL;
475                 g_module_close(plugin->module);
476                 g_free(plugin);
477                 return NULL;
478         }
479         
480         if (plugin_licence_check(plugin_licence()) != TRUE) {
481                 *error = g_strdup(_("This module is not licensed under a GPL v3 or later compatible license."));
482                 if (plugin->unloaded_hidden)
483                         return NULL;
484                 g_module_close(plugin->module);
485                 g_free(plugin);
486                 return NULL;
487         }
488
489         if (!strcmp(plugin_type(), "GTK")) {
490                 *error = g_strdup(_("This module is for Claws Mail GTK1."));
491                 if (plugin->unloaded_hidden)
492                         return NULL;
493                 g_module_close(plugin->module);
494                 g_free(plugin);
495                 return NULL;
496         }
497
498         if ((*error = plugin_check_features(plugin_provides())) != NULL) {
499                 if (plugin->unloaded_hidden)
500                         return NULL;
501                 g_module_close(plugin->module);
502                 g_free(plugin);
503                 return NULL;
504         }
505         plugin->name = plugin_name;
506         plugin->desc = plugin_desc;
507         plugin->version = plugin_version;
508         plugin->type = plugin_type;
509         plugin->licence = plugin_licence;
510         plugin->provides = plugin_provides;
511         plugin->filename = g_strdup(filename);
512         plugin->error = NULL;
513
514         if ((ok = plugin_init(error)) < 0) {
515                 if (*error)
516                         plugin->error = g_strdup(*error);
517                 unloaded_plugins = g_slist_append(unloaded_plugins, plugin);
518                 return NULL;
519         }
520
521         if (!plugin->unloaded_hidden)
522                 plugins = g_slist_append(plugins, plugin);
523         plugin->unloaded_hidden = FALSE;
524
525         debug_print("Plugin %s (from file %s) loaded\n", plugin->name(), filename);
526         END_TIMING();
527         return plugin;
528 }
529
530 void plugin_unload(Plugin *plugin)
531 {
532         gboolean (*plugin_done) (void);
533         gboolean can_unload = TRUE;
534
535         plugin_unload_rdeps(plugin);
536
537         if (plugin->unloaded_hidden)
538                 return;
539
540         if (plugin->error) {
541                 plugin_remove_from_unloaded_list(plugin->filename);
542                 return;
543         }
544         if (g_module_symbol(plugin->module, "plugin_done", (gpointer) &plugin_done)) {
545                 can_unload = plugin_done();
546         }
547
548         if (can_unload) {
549 #ifdef HAVE_VALGRIND
550                 if (!RUNNING_ON_VALGRIND) {
551                         g_module_close(plugin->module);
552                 }
553 #else
554                 g_module_close(plugin->module);
555 #endif
556                 plugins = g_slist_remove(plugins, plugin);
557                 g_free(plugin->filename);
558                 g_free(plugin);
559         } else {
560                 plugin->unloaded_hidden = TRUE;
561         }
562
563 }
564
565 static void replace_old_plugin_name(gchar *plugin_name)
566 {
567         gchar *old_name_end = g_strconcat("_plugin.", G_MODULE_SUFFIX, NULL);
568         gchar *matches = strstr(plugin_name, old_name_end);
569
570         if (!matches) {
571                 g_free(old_name_end);
572                 return;
573         } else if (plugin_name + strlen(plugin_name) != matches + strlen(matches)) {
574                 g_free(old_name_end);
575                 return;
576         } else {
577                 gchar *new_name_end = g_strconcat(".", G_MODULE_SUFFIX, NULL);
578                 int offset = strlen(plugin_name) - strlen(old_name_end);
579
580                 debug_print("Replacing old plugin name %s\n", plugin_name);
581                 
582                 strncpy(plugin_name + offset, new_name_end, strlen(old_name_end) - 1);
583                 debug_print(" to %s\n", plugin_name);
584         }
585 }
586
587 void plugin_load_all(const gchar *type)
588 {
589         gchar *rcpath;
590         gchar buf[BUFFSIZE];
591         PrefFile *pfile;
592         gchar *error = NULL, *block;
593
594         plugin_types = g_slist_append(plugin_types, g_strdup(type));
595
596         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL); 
597 #ifdef G_OS_WIN32
598         block = g_strconcat("PluginsWin32_", type, NULL);
599 #else
600         block = g_strconcat("Plugins_", type, NULL);
601 #endif
602         if ((pfile = prefs_read_open(rcpath)) == NULL ||
603             (prefs_set_block_label(pfile, block) < 0)) {
604                 g_free(rcpath);
605                 if (pfile)
606                         prefs_file_close(pfile);
607                 return;
608         }
609         g_free(block);
610
611         while (fgets(buf, sizeof(buf), pfile->fp) != NULL) {
612                 if (buf[0] == '[')
613                         break;
614
615                 g_strstrip(buf);
616                 replace_old_plugin_name(buf);
617
618                 if ((buf[0] != '\0') && (plugin_load(buf, &error) == NULL)) {
619                         g_warning("plugin loading error: %s", error);
620                         g_free(error);
621                 }                                                       
622         }
623         prefs_file_close(pfile);
624
625         g_free(rcpath);
626 }
627
628 void plugin_unload_all(const gchar *type)
629 {
630         GSList *list, *cur;
631
632         list = g_slist_copy(plugins);
633         list = g_slist_reverse(list);
634
635         for(cur = list; cur != NULL; cur = g_slist_next(cur)) {
636                 Plugin *plugin = (Plugin *) cur->data;
637                 
638                 if (!strcmp(type, plugin->type()))
639                         plugin_unload(plugin);
640         }
641         g_slist_free(list);
642
643         cur = g_slist_find_custom(plugin_types, (gpointer) type, list_find_by_string);
644         if (cur) {
645                 g_free(cur->data);
646                 plugin_types = g_slist_remove(plugin_types, cur);
647         }
648 }
649
650
651 /* Load those plugins we always want to use.  No error output; just
652  * try. */
653 void plugin_load_standard_plugins (void)
654 {
655         static const char *names[] = {
656 #ifdef G_OS_WIN32 
657                 "pgpmime",
658                 "pgpinline",
659 #else
660                 /* post-2.5 maybe 
661                 "bogofilter", */
662 #endif
663                 NULL
664         };
665         int i;
666         gchar *error, *filename;
667         
668         for (i=0; names[i]; i++) {
669                 /* Simple hack to check whether the plugin has already
670                  * been loaded but checking only for the basename. */
671                 GSList *cur = plugins;
672                 for(; cur; cur = cur->next) {
673                         Plugin *p = (Plugin *)cur->data;
674                         if (strstr(p->filename, names[i]))
675                                 break;
676                 }
677                 if (!cur) { /* Not yet loaded. */
678                         /* FIXME: get_plugin_dir () returns with a trailing
679                          * (back)slash; this should be fixed so that we can use
680                          * g_module_build_path here. */
681 #ifdef G_OS_WIN32 
682                         filename = g_strconcat (get_plugin_dir(),
683                                                 names[i], NULL);
684 #else
685                         filename = g_strconcat (get_plugin_dir(),
686                                                 names[i], ".", G_MODULE_SUFFIX, NULL);
687 #endif
688                         error = NULL;
689                         plugin_load(filename, &error);
690                         g_free (error);
691                         g_free(filename);
692                 }
693         }
694 }
695
696 GSList *plugin_get_list(void)
697 {
698         GSList *new = NULL;
699         GSList *cur = plugins;
700         for (; cur; cur = cur->next) {
701                 Plugin *p = (Plugin *)cur->data;
702                 if (!p->unloaded_hidden)
703                         new = g_slist_prepend(new, p);
704         }
705         new = g_slist_reverse(new);
706         return new;
707 }
708
709 Plugin *plugin_get_loaded_by_name(const gchar *name) 
710 {
711         Plugin *plugin = NULL;
712         GSList *new, *cur; 
713         new = plugin_get_list();
714         for (cur = new; cur; cur = g_slist_next(cur)) {
715                 plugin = (Plugin *)cur->data;
716                 if (!g_ascii_strcasecmp(plugin->name(), name)) 
717                         break;
718                 else 
719                         plugin = NULL;
720         }
721         g_slist_free(new);
722         return plugin;
723 }
724
725 GSList *plugin_get_unloaded_list(void)
726 {
727         return g_slist_copy(unloaded_plugins);
728 }
729
730 const gchar *plugin_get_name(Plugin *plugin)
731 {
732         return plugin->name();
733 }
734
735 const gchar *plugin_get_desc(Plugin *plugin)
736 {
737         return plugin->desc();
738 }
739
740 const gchar *plugin_get_version(Plugin *plugin)
741 {
742         return plugin->version();
743 }
744
745 const gchar *plugin_get_error(Plugin *plugin)
746 {
747         return plugin->error;
748 }
749
750 /* Generally called in plugin_init() function of each plugin. It check the
751  * minimal and compiled version of claws binary required by the plugin.
752  * If (@minimum_claws_version == 0 || @compiled_claws_version == 0), don't
753  * check the corresponding version.
754  *
755  * If an error occurs {
756  *      If @error == NULL { don't allocate error string. }
757  *      If @error != NULL { error string is allocated and must be freed after 
758  *                              call function. }
759  * }
760  * Returns: FALSE if an error occurs, TRUE if all is OK.
761  */
762 gint check_plugin_version(guint32 minimum_claws_version,
763                          guint32 compiled_claws_version,
764                          const gchar *plugin_name,
765                          gchar **error)
766 {
767         guint32 claws_version = claws_get_version();
768
769         if (compiled_claws_version != 0 && claws_version > compiled_claws_version) {
770                 if (error != NULL) {
771                         *error = (plugin_name && *plugin_name)
772                                 ? g_strdup_printf(_("Your version of Claws Mail is newer than the "
773                                                         "version the '%s' plugin was built with."),
774                                                 plugin_name)
775                                 : g_strdup(_("Your version of Claws Mail is newer than the "
776                                                         "version the plugin was built with."));
777                 }
778                 return FALSE;
779         }
780
781         if (minimum_claws_version != 0 && claws_version < minimum_claws_version) {
782                 if (error != NULL) {
783                         *error = (plugin_name && *plugin_name)
784                                 ? g_strdup_printf(_("Your version of Claws Mail is too old for "
785                                                         "the '%s' plugin."), plugin_name)
786                                 : g_strdup(_("Your version of Claws Mail is too old for the plugin."));
787                 }
788                 return FALSE;
789         }
790         return TRUE;
791 }