9d59de1a6abe9bf3a91b7e3daae116885b8a7eff
[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 static gboolean plugin_filename_is_standard_dir(const gchar *filename) {
355         return strncmp(filename, get_plugin_dir(), strlen(get_plugin_dir())) == 0;
356 }
357
358 static Plugin *plugin_load_in_default_dir(const gchar *filename, gchar **error)
359 {
360         Plugin *plugin = NULL;
361         gchar *filename_default_dir = NULL;
362         gchar *default_error = NULL;
363         gchar *plugin_name = g_path_get_basename(filename);
364
365         filename_default_dir = g_strconcat(get_plugin_dir(), plugin_name, NULL);
366
367         debug_print("trying to load %s in default plugin directory %s\n",
368                     plugin_name, get_plugin_dir());
369
370         g_free(plugin_name);
371
372         plugin = plugin_load(filename_default_dir, &default_error);
373         
374         g_free(filename_default_dir);
375         
376         if (plugin) {
377                 g_free(*error);
378                 *error = NULL;
379         } else {
380                 g_free(default_error);
381         }
382
383         return plugin;
384 }
385
386 /**
387  * Loads a plugin
388  *
389  * \param filename The filename of the plugin to load
390  * \param error The location where an error string can be stored
391  * \return the plugin on success, NULL otherwise
392  */
393 Plugin *plugin_load(const gchar *filename, gchar **error)
394 {
395         Plugin *plugin;
396         gint (*plugin_init) (gchar **error);
397         gpointer plugin_name, plugin_desc, plugin_version;
398         const gchar *(*plugin_type)(void);
399         const gchar *(*plugin_licence)(void);
400         struct PluginFeature *(*plugin_provides)(void);
401         gint ok;
402         START_TIMING((filename?filename:"NULL plugin"));
403         cm_return_val_if_fail(filename != NULL, NULL);
404         cm_return_val_if_fail(error != NULL, NULL);
405
406         /* check duplicate plugin path name */
407         if (plugin_is_loaded(filename)) {
408                 plugin = plugin_get_by_filename(filename);
409                 if (plugin->unloaded_hidden) {
410                         /* reshow it */
411                         goto init_plugin;
412                 } else {
413                         *error = g_strdup(_("Plugin already loaded"));
414                         return NULL;            
415                 }
416         }                              
417         
418         plugin_remove_from_unloaded_list(filename);
419         
420         if (plugin_load_deps(filename, error) < 0)
421                 return NULL;
422         plugin = g_new0(Plugin, 1);
423         if (plugin == NULL) {
424                 *error = g_strdup(_("Failed to allocate memory for Plugin"));
425                 return NULL;
426         }
427
428         debug_print("trying to load `%s'\n", filename);
429         plugin->module = g_module_open(filename, 0);
430         if (plugin->module == NULL) {
431                 *error = g_strdup(g_module_error());
432                 g_free(plugin);
433                 if (!plugin_filename_is_standard_dir(filename))
434                         return plugin_load_in_default_dir(filename, error);
435                 else
436                         return NULL;
437         }
438
439 init_plugin:
440         if (!g_module_symbol(plugin->module, "plugin_name", &plugin_name) ||
441             !g_module_symbol(plugin->module, "plugin_desc", &plugin_desc) ||
442             !g_module_symbol(plugin->module, "plugin_version", &plugin_version) ||
443             !g_module_symbol(plugin->module, "plugin_type", (gpointer)&plugin_type) ||
444             !g_module_symbol(plugin->module, "plugin_licence", (gpointer)&plugin_licence) ||
445             !g_module_symbol(plugin->module, "plugin_provides", (gpointer)&plugin_provides) ||
446             !g_module_symbol(plugin->module, "plugin_init", (gpointer)&plugin_init)) {
447                 *error = g_strdup(g_module_error());
448                 if (plugin->unloaded_hidden)
449                         return NULL;
450                 g_module_close(plugin->module);
451                 g_free(plugin);
452                 return NULL;
453         }
454         
455         if (plugin_licence_check(plugin_licence()) != TRUE) {
456                 *error = g_strdup(_("This module is not licensed under a GPL v3 or later compatible license."));
457                 if (plugin->unloaded_hidden)
458                         return NULL;
459                 g_module_close(plugin->module);
460                 g_free(plugin);
461                 return NULL;
462         }
463
464         if (!strcmp(plugin_type(), "GTK")) {
465                 *error = g_strdup(_("This module is for Claws Mail GTK1."));
466                 if (plugin->unloaded_hidden)
467                         return NULL;
468                 g_module_close(plugin->module);
469                 g_free(plugin);
470                 return NULL;
471         }
472
473         if ((*error = plugin_check_features(plugin_provides())) != NULL) {
474                 if (plugin->unloaded_hidden)
475                         return NULL;
476                 g_module_close(plugin->module);
477                 g_free(plugin);
478                 return NULL;
479         }
480         plugin->name = plugin_name;
481         plugin->desc = plugin_desc;
482         plugin->version = plugin_version;
483         plugin->type = plugin_type;
484         plugin->licence = plugin_licence;
485         plugin->provides = plugin_provides;
486         plugin->filename = g_strdup(filename);
487         plugin->error = NULL;
488
489         if ((ok = plugin_init(error)) < 0) {
490                 if (*error)
491                         plugin->error = g_strdup(*error);
492                 unloaded_plugins = g_slist_append(unloaded_plugins, plugin);
493                 return NULL;
494         }
495
496         if (!plugin->unloaded_hidden)
497                 plugins = g_slist_append(plugins, plugin);
498         plugin->unloaded_hidden = FALSE;
499
500         debug_print("Plugin %s (from file %s) loaded\n", plugin->name(), filename);
501         END_TIMING();
502         return plugin;
503 }
504
505 void plugin_unload(Plugin *plugin)
506 {
507         gboolean (*plugin_done) (void);
508         gboolean can_unload = TRUE;
509
510         plugin_unload_rdeps(plugin);
511
512         if (plugin->unloaded_hidden)
513                 return;
514
515         if (plugin->error) {
516                 plugin_remove_from_unloaded_list(plugin->filename);
517                 return;
518         }
519         if (g_module_symbol(plugin->module, "plugin_done", (gpointer) &plugin_done)) {
520                 can_unload = plugin_done();
521         }
522
523         if (can_unload) {
524 #ifdef HAVE_VALGRIND
525                 if (!RUNNING_ON_VALGRIND) {
526                         g_module_close(plugin->module);
527                 }
528 #else
529                 g_module_close(plugin->module);
530 #endif
531                 plugins = g_slist_remove(plugins, plugin);
532                 g_free(plugin->filename);
533                 g_free(plugin);
534         } else {
535                 plugin->unloaded_hidden = TRUE;
536         }
537
538 }
539
540 void plugin_load_all(const gchar *type)
541 {
542         gchar *rcpath;
543         gchar buf[BUFFSIZE];
544         PrefFile *pfile;
545         gchar *error = NULL, *block;
546
547         plugin_types = g_slist_append(plugin_types, g_strdup(type));
548
549         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL); 
550 #ifdef G_OS_WIN32
551         block = g_strconcat("PluginsWin32_", type, NULL);
552 #else
553         block = g_strconcat("Plugins_", type, NULL);
554 #endif
555         if ((pfile = prefs_read_open(rcpath)) == NULL ||
556             (prefs_set_block_label(pfile, block) < 0)) {
557                 g_free(rcpath);
558                 if (pfile)
559                         prefs_file_close(pfile);
560                 return;
561         }
562         g_free(block);
563
564         while (fgets(buf, sizeof(buf), pfile->fp) != NULL) {
565                 if (buf[0] == '[')
566                         break;
567
568                 g_strstrip(buf);
569                 if ((buf[0] != '\0') && (plugin_load(buf, &error) == NULL)) {
570                         g_warning("plugin loading error: %s\n", error);
571                         g_free(error);
572                 }                                                       
573         }
574         prefs_file_close(pfile);
575
576         g_free(rcpath);
577 }
578
579 void plugin_unload_all(const gchar *type)
580 {
581         GSList *list, *cur;
582
583         list = g_slist_copy(plugins);
584         list = g_slist_reverse(list);
585
586         for(cur = list; cur != NULL; cur = g_slist_next(cur)) {
587                 Plugin *plugin = (Plugin *) cur->data;
588                 
589                 if (!strcmp(type, plugin->type()))
590                         plugin_unload(plugin);
591         }
592         g_slist_free(list);
593
594         cur = g_slist_find_custom(plugin_types, (gpointer) type, list_find_by_string);
595         if (cur) {
596                 g_free(cur->data);
597                 plugin_types = g_slist_remove(plugin_types, cur);
598         }
599 }
600
601
602 /* Load those plugins we always want to use.  No error output; just
603  * try. */
604 void plugin_load_standard_plugins (void)
605 {
606         static const char *names[] = {
607 #ifdef G_OS_WIN32 
608                 "pgpmime",
609                 "pgpinline",
610 #else
611                 /* post-2.5 maybe 
612                 "bogofilter", */
613 #endif
614                 NULL
615         };
616         int i;
617         gchar *error, *filename;
618         
619         for (i=0; names[i]; i++) {
620                 /* Simple hack to check whether the plugin has already
621                  * been loaded but checking only for the basename. */
622                 GSList *cur = plugins;
623                 for(; cur; cur = cur->next) {
624                         Plugin *p = (Plugin *)cur->data;
625                         if (strstr(p->filename, names[i]))
626                                 break;
627                 }
628                 if (!cur) { /* Not yet loaded. */
629                         /* FIXME: get_plugin_dir () returns with a trailing
630                          * (back)slash; this should be fixed so that we can use
631                          * g_module_build_path here. */
632 #ifdef G_OS_WIN32 
633                         filename = g_strconcat (get_plugin_dir(),
634                                                 names[i], NULL);
635 #else
636                         filename = g_strconcat (get_plugin_dir(),
637                                                 names[i], ".", G_MODULE_SUFFIX, NULL);
638 #endif
639                         error = NULL;
640                         plugin_load(filename, &error);
641                         g_free (error);
642                         g_free(filename);
643                 }
644         }
645 }
646
647 GSList *plugin_get_list(void)
648 {
649         GSList *new = NULL;
650         GSList *cur = plugins;
651         for (; cur; cur = cur->next) {
652                 Plugin *p = (Plugin *)cur->data;
653                 if (!p->unloaded_hidden)
654                         new = g_slist_prepend(new, p);
655         }
656         new = g_slist_reverse(new);
657         return new;
658 }
659
660 Plugin *plugin_get_loaded_by_name(const gchar *name) 
661 {
662         Plugin *plugin = NULL;
663         GSList *new, *cur; 
664         new = plugin_get_list();
665         for (cur = new; cur; cur = g_slist_next(cur)) {
666                 plugin = (Plugin *)cur->data;
667                 if (!g_ascii_strcasecmp(plugin->name(), name)) 
668                         break;
669                 else 
670                         plugin = NULL;
671         }
672         g_slist_free(new);
673         return plugin;
674 }
675
676 GSList *plugin_get_unloaded_list(void)
677 {
678         return g_slist_copy(unloaded_plugins);
679 }
680
681 const gchar *plugin_get_name(Plugin *plugin)
682 {
683         return plugin->name();
684 }
685
686 const gchar *plugin_get_desc(Plugin *plugin)
687 {
688         return plugin->desc();
689 }
690
691 const gchar *plugin_get_version(Plugin *plugin)
692 {
693         return plugin->version();
694 }
695
696 const gchar *plugin_get_error(Plugin *plugin)
697 {
698         return plugin->error;
699 }
700
701 /* Generally called in plugin_init() function of each plugin. It check the
702  * minimal and compiled version of claws binary required by the plugin.
703  * If (@minimum_claws_version == 0 || @compiled_claws_version == 0), don't
704  * check the corresponding version.
705  *
706  * If an error occurs {
707  *      If @error == NULL { don't allocate error string. }
708  *      If @error != NULL { error string is allocated and must be freed after 
709  *                              call function. }
710  * }
711  * Returns: FALSE if an error occurs, TRUE if all is OK.
712  */
713 gint check_plugin_version(guint32 minimum_claws_version,
714                          guint32 compiled_claws_version,
715                          const gchar *plugin_name,
716                          gchar **error)
717 {
718         guint32 claws_version = claws_get_version();
719
720         if (compiled_claws_version != 0 && claws_version > compiled_claws_version) {
721                 if (error != NULL) {
722                         *error = (plugin_name && *plugin_name)
723                                 ? g_strdup_printf(_("Your version of Claws Mail is newer than the "
724                                                         "version the '%s' plugin was built with."),
725                                                 plugin_name)
726                                 : g_strdup(_("Your version of Claws Mail is newer than the "
727                                                         "version the plugin was built with."));
728                 }
729                 return FALSE;
730         }
731
732         if (minimum_claws_version != 0 && claws_version < minimum_claws_version) {
733                 if (error != NULL) {
734                         *error = (plugin_name && *plugin_name)
735                                 ? g_strdup_printf(_("Your version of Claws Mail is too old for "
736                                                         "the '%s' plugin."), plugin_name)
737                                 : g_strdup(_("Your version of Claws Mail is too old for the plugin."));
738                 }
739                 return FALSE;
740         }
741         return TRUE;
742 }