device: renew dhcp leases on awake for software devices
[NetworkManager.git] / libnm-core / nm-vpn-plugin-info.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /*
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 2 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library; if not, write to the
15  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  * Boston, MA 02110-1301 USA.
17  *
18  * Copyright 2015 Red Hat, Inc.
19  */
20
21 #include "nm-default.h"
22
23 #include "nm-vpn-plugin-info.h"
24
25 #include <string.h>
26 #include <errno.h>
27 #include <sys/stat.h>
28
29 #include "nm-errors.h"
30 #include "nm-core-internal.h"
31
32 #define DEFAULT_DIR_ETC     NMCONFDIR"/VPN"
33 #define DEFAULT_DIR_LIB     NMLIBDIR"/VPN"
34
35 enum {
36         PROP_0,
37         PROP_NAME,
38         PROP_FILENAME,
39         PROP_KEYFILE,
40
41         LAST_PROP,
42 };
43
44 typedef struct {
45         char *filename;
46         char *name;
47         char *service;
48         char **aliases;
49         GKeyFile *keyfile;
50
51         /* It is convenient for nm_vpn_plugin_info_lookup_property() to return a const char *,
52          * contrary to what g_key_file_get_string() does. Hence we must cache the returned
53          * value somewhere... let's put it in an internal hash table.
54          * This contains a clone of all the strings in keyfile. */
55         GHashTable *keys;
56
57         gboolean editor_plugin_loaded;
58         NMVpnEditorPlugin *editor_plugin;
59 } NMVpnPluginInfoPrivate;
60
61 static void nm_vpn_plugin_info_initable_iface_init (GInitableIface *iface);
62
63 G_DEFINE_TYPE_WITH_CODE (NMVpnPluginInfo, nm_vpn_plugin_info, G_TYPE_OBJECT,
64                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, nm_vpn_plugin_info_initable_iface_init);
65                          )
66
67 #define NM_VPN_PLUGIN_INFO_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_PLUGIN_INFO, NMVpnPluginInfoPrivate))
68
69 /*********************************************************************/
70
71 /**
72  * nm_vpn_plugin_info_validate_filename:
73  * @filename: the filename to check
74  *
75  * Regular name files have a certain pattern. That basically means
76  * they have the file extension "name". Check if @filename
77  * is valid according to that pattern.
78  *
79  * Since: 1.2
80  */
81 gboolean
82 nm_vpn_plugin_info_validate_filename (const char *filename)
83 {
84         if (!filename || !g_str_has_suffix (filename, ".name"))
85                 return FALSE;
86
87         /* originally, we didn't do further checks... but here we go. */
88         if (filename[0] == '.') {
89                 /* this also rejects name ".name" alone. */
90                 return FALSE;
91         }
92         return TRUE;
93 }
94
95 static gboolean
96 nm_vpn_plugin_info_check_file_full (const char *filename,
97                                     gboolean check_absolute,
98                                     gboolean do_validate_filename,
99                                     gint64 check_owner,
100                                     NMUtilsCheckFilePredicate check_file,
101                                     gpointer user_data,
102                                     struct stat *out_st,
103                                     GError **error)
104 {
105         if (!filename || !*filename) {
106                 g_set_error (error,
107                              NM_VPN_PLUGIN_ERROR,
108                              NM_VPN_PLUGIN_ERROR_FAILED,
109                              _("missing filename"));
110                 return FALSE;
111         }
112
113         if (check_absolute && !g_path_is_absolute (filename)) {
114                 g_set_error (error,
115                              NM_VPN_PLUGIN_ERROR,
116                              NM_VPN_PLUGIN_ERROR_FAILED,
117                              _("filename must be an absolute path (%s)"), filename);
118                 return FALSE;
119         }
120
121         if (   do_validate_filename
122             && !nm_vpn_plugin_info_validate_filename (filename)) {
123                 g_set_error (error,
124                              NM_VPN_PLUGIN_ERROR,
125                              NM_VPN_PLUGIN_ERROR_FAILED,
126                              _("filename has invalid format (%s)"), filename);
127                 return FALSE;
128         }
129
130         return _nm_utils_check_file (filename,
131                                      check_owner,
132                                      check_file,
133                                      user_data,
134                                      out_st,
135                                      error);
136 }
137
138 /**
139  * _nm_vpn_plugin_info_check_file:
140  * @filename: the file to check
141  * @check_absolute: if %TRUE, only allow absolute path names.
142  * @do_validate_filename: if %TRUE, only accept the filename if
143  *   nm_vpn_plugin_info_validate_filename() succeeds.
144  * @check_owner: if non-negative, only accept the file if the
145  *   owner UID is equal to @check_owner or if the owner is 0.
146  *   In this case, also check that the file is not writable by
147  *   other users.
148  * @check_file: pass a callback to do your own validation.
149  * @user_data: user data for @check_file.
150  * @error: (allow-none): (out): the error reason if the check fails.
151  *
152  * Check whether the file exists and is a valid name file (in keyfile format).
153  * Additionally, also check for file permissions.
154  *
155  * Returns: %TRUE if a file @filename exists and has valid permissions.
156  *
157  * Since: 1.2
158  */
159 gboolean
160 _nm_vpn_plugin_info_check_file (const char *filename,
161                                 gboolean check_absolute,
162                                 gboolean do_validate_filename,
163                                 gint64 check_owner,
164                                 NMUtilsCheckFilePredicate check_file,
165                                 gpointer user_data,
166                                 GError **error)
167 {
168         return nm_vpn_plugin_info_check_file_full (filename, check_absolute, do_validate_filename, check_owner, check_file, user_data, NULL, error);
169 }
170
171 typedef struct {
172         NMVpnPluginInfo *plugin_info;
173         struct stat stat;
174 } LoadDirInfo;
175
176 static int
177 _sort_files (LoadDirInfo *a, LoadDirInfo *b)
178 {
179         time_t ta, tb;
180
181         ta = MAX (a->stat.st_mtime, a->stat.st_ctime);
182         tb = MAX (b->stat.st_mtime, b->stat.st_ctime);
183         if (ta < tb)
184                 return 1;
185         if (ta > tb)
186                 return -1;
187         return g_strcmp0 (nm_vpn_plugin_info_get_filename (a->plugin_info),
188                           nm_vpn_plugin_info_get_filename (b->plugin_info));
189 }
190
191 /**
192  * _nm_vpn_plugin_info_get_default_dir_etc:
193  *
194  * Returns: (transfer none): compile time constant of the default
195  *   VPN plugin directory.
196  */
197 const char *
198 _nm_vpn_plugin_info_get_default_dir_etc ()
199 {
200         return DEFAULT_DIR_ETC;
201 }
202
203 /**
204  * _nm_vpn_plugin_info_get_default_dir_lib:
205  *
206  * Returns: (transfer none): compile time constant of the default
207  *   VPN plugin directory.
208  */
209 const char *
210 _nm_vpn_plugin_info_get_default_dir_lib ()
211 {
212         return DEFAULT_DIR_LIB;
213 }
214
215 /**
216  * _nm_vpn_plugin_info_get_default_dir_user:
217  *
218  * Returns: The user can specify a different directory for VPN plugins
219  * by setting NM_VPN_PLUGIN_DIR environment variable. Return
220  * that directory.
221  */
222 const char *
223 _nm_vpn_plugin_info_get_default_dir_user ()
224 {
225         return g_getenv ("NM_VPN_PLUGIN_DIR");
226 }
227
228 /**
229  * _nm_vpn_plugin_info_list_load_dir:
230  * @dirname: the name of the directory to load.
231  * @do_validate_filename: only consider filenames that have a certain
232  *   pattern (i.e. end with ".name").
233  * @check_owner: if set to a non-negative number, check that the file
234  *   owner is either the same uid or 0. In that case, also check
235  *   that the file is not writable by group or other.
236  * @check_file: (allow-none): callback to check whether the file is valid.
237  * @user_data: data for @check_file
238  *
239  * Iterate over the content of @dirname and load name files.
240  *
241  * Returns: (transfer full) (element-type NMVpnPluginInfo): list of loaded plugin infos.
242  */
243 GSList *
244 _nm_vpn_plugin_info_list_load_dir (const char *dirname,
245                                    gboolean do_validate_filename,
246                                    gint64 check_owner,
247                                    NMUtilsCheckFilePredicate check_file,
248                                    gpointer user_data)
249 {
250         GDir *dir;
251         const char *fn;
252         GArray *array;
253         GSList *res = NULL;
254         guint i;
255
256         g_return_val_if_fail (dirname && dirname[0], NULL);
257
258         dir = g_dir_open (dirname, 0, NULL);
259         if (!dir)
260                 return NULL;
261
262         array = g_array_new (FALSE, FALSE, sizeof (LoadDirInfo));
263
264         while ((fn = g_dir_read_name (dir))) {
265                 gs_free char *filename = NULL;
266                 LoadDirInfo info = { 0 };
267
268                 filename = g_build_filename (dirname, fn, NULL);
269                 if (nm_vpn_plugin_info_check_file_full (filename,
270                                                         FALSE,
271                                                         do_validate_filename,
272                                                         check_owner,
273                                                         check_file,
274                                                         user_data,
275                                                         &info.stat,
276                                                         NULL)) {
277                         info.plugin_info = nm_vpn_plugin_info_new_from_file (filename, NULL);
278                         if (info.plugin_info) {
279                                 g_array_append_val (array, info);
280                                 continue;
281                         }
282                 }
283         }
284         g_dir_close (dir);
285
286         /* sort the files so that we have a stable behavior. The directory might contain
287          * duplicate VPNs, so while nm_vpn_plugin_info_list_load() would load them all, the
288          * caller probably wants to reject duplicates. Having a stable order means we always
289          * reject the same files in face of duplicates. */
290         g_array_sort (array, (GCompareFunc) _sort_files);
291
292         for (i = 0; i < array->len; i++)
293                 res = g_slist_prepend (res, g_array_index (array, LoadDirInfo, i).plugin_info);
294
295         g_array_unref (array);
296
297         return g_slist_reverse (res);
298 }
299
300 /**
301  * nm_vpn_plugin_info_list_load:
302  *
303  * Returns: (element-type NMVpnPluginInfo) (transfer full): list of plugins
304  * loaded from the default directories rejecting duplicates.
305  *
306  * Since: 1.2
307  */
308 GSList *
309 nm_vpn_plugin_info_list_load ()
310 {
311         int i;
312         gint64 uid;
313         GSList *list = NULL;
314         GSList *infos, *info;
315         const char *dir[] = {
316                 /* We load plugins from NM_VPN_PLUGIN_DIR *and* DEFAULT_DIR*, with
317                  * preference to the former.
318                  *
319                  * load user directory with highest priority. */
320                 _nm_vpn_plugin_info_get_default_dir_user (),
321
322                 /* lib directory has higher priority then etc. The reason is that
323                  * etc is deprecated and used by old plugins. We expect newer plugins
324                  * to install their file in lib, where they have higher priority.
325                  *
326                  * Optimally, there are no duplicates anyway, so it doesn't really matter. */
327                 _nm_vpn_plugin_info_get_default_dir_lib (),
328                 _nm_vpn_plugin_info_get_default_dir_etc (),
329         };
330
331         uid = getuid ();
332
333         for (i = 0; i < G_N_ELEMENTS (dir); i++) {
334                 if (   !dir[i]
335                     || _nm_utils_strv_find_first ((char **) dir, i, dir[i]) >= 0)
336                         continue;
337
338                 infos = _nm_vpn_plugin_info_list_load_dir (dir[i], TRUE, uid, NULL, NULL);
339
340                 for (info = infos; info; info = info->next)
341                         nm_vpn_plugin_info_list_add (&list, info->data, NULL);
342
343                 g_slist_free_full (infos, g_object_unref);
344         }
345         return list;
346 }
347
348 /*********************************************************************/
349
350 static gboolean
351 _check_no_conflict (NMVpnPluginInfo *i1, NMVpnPluginInfo *i2, GError **error)
352 {
353         NMVpnPluginInfoPrivate *priv1, *priv2;
354         uint i;
355         struct {
356                 const char *group;
357                 const char *key;
358         } check_list[] = {
359                 { NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "service" },
360                 { NM_VPN_PLUGIN_INFO_KF_GROUP_LIBNM,      "plugin" },
361                 { NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME,      "properties" },
362         };
363
364         priv1 = NM_VPN_PLUGIN_INFO_GET_PRIVATE (i1);
365         priv2 = NM_VPN_PLUGIN_INFO_GET_PRIVATE (i2);
366
367         for (i = 0; i < G_N_ELEMENTS (check_list); i++) {
368                 gs_free NMUtilsStrStrDictKey *k = NULL;
369                 const char *s1, *s2;
370
371                 k = _nm_utils_strstrdictkey_create (check_list[i].group, check_list[i].key);
372                 s1 = g_hash_table_lookup (priv1->keys, k);
373                 if (!s1)
374                         continue;
375                 s2 = g_hash_table_lookup (priv2->keys, k);
376                 if (!s2)
377                         continue;
378
379                 if (strcmp (s1, s2) == 0) {
380                         g_set_error (error,
381                                      NM_VPN_PLUGIN_ERROR,
382                                      NM_VPN_PLUGIN_ERROR_FAILED,
383                                      _("there exists a conflicting plugin (%s) that has the same %s.%s value"),
384                                      priv2->name,
385                                      check_list[i].group, check_list[i].key);
386                         return FALSE;
387                 }
388         }
389         return TRUE;
390 }
391
392 /**
393  * nm_vpn_plugin_info_list_add:
394  * @list: (element-type NMVpnPluginInfo): list of plugins
395  * @plugin_info: instance to add
396  * @error: failure reason
397  *
398  * Returns: %TRUE if the plugin was added to @list. This will fail
399  * to add duplicate plugins.
400  *
401  * Since: 1.2
402  */
403 gboolean
404 nm_vpn_plugin_info_list_add (GSList **list, NMVpnPluginInfo *plugin_info, GError **error)
405 {
406         GSList *iter;
407         const char *name;
408
409         g_return_val_if_fail (list, FALSE);
410         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (plugin_info), FALSE);
411
412         name = nm_vpn_plugin_info_get_name (plugin_info);
413         for (iter = *list; iter; iter = iter->next) {
414                 if (iter->data == plugin_info)
415                         return TRUE;
416
417                 if (strcmp (nm_vpn_plugin_info_get_name (iter->data), name) == 0) {
418                         g_set_error (error,
419                                      NM_VPN_PLUGIN_ERROR,
420                                      NM_VPN_PLUGIN_ERROR_FAILED,
421                                      _("there exists a conflicting plugin with the same name (%s)"),
422                                      name);
423                         return FALSE;
424                 }
425
426                 /* the plugin must have unique values for certain properties. E.g. two different
427                  * plugins cannot share the same service name. */
428                 if (!_check_no_conflict (plugin_info, iter->data, error))
429                         return FALSE;
430         }
431
432         *list = g_slist_append (*list, g_object_ref (plugin_info));
433         return TRUE;
434 }
435
436 /**
437  * nm_vpn_plugin_info_list_remove:
438  * @list: (element-type NMVpnPluginInfo): list of plugins
439  * @plugin_info: instance
440  *
441  * Remove @plugin_info from @list.
442  *
443  * Returns: %TRUE if @plugin_info was in @list and successfully removed.
444  *
445  * Since: 1.2
446  */
447 gboolean
448 nm_vpn_plugin_info_list_remove (GSList **list, NMVpnPluginInfo *plugin_info)
449 {
450         g_return_val_if_fail (list, FALSE);
451         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (plugin_info), FALSE);
452
453         if (!g_slist_find (*list, plugin_info))
454                 return FALSE;
455
456         *list = g_slist_remove (*list, plugin_info);
457         g_object_unref (plugin_info);
458         return TRUE;
459 }
460
461 /**
462  * nm_vpn_plugin_info_list_find_by_name:
463  * @list: (element-type NMVpnPluginInfo): list of plugins
464  * @name: name to search
465  *
466  * Returns: (transfer none): the first plugin with a matching @name (or %NULL).
467  *
468  * Since: 1.2
469  */
470 NMVpnPluginInfo *
471 nm_vpn_plugin_info_list_find_by_name (GSList *list, const char *name)
472 {
473         GSList *iter;
474
475         if (!name)
476                 g_return_val_if_reached (NULL);
477
478         for (iter = list; iter; iter = iter->next) {
479                 if (strcmp (nm_vpn_plugin_info_get_name (iter->data), name) == 0)
480                         return iter->data;
481         }
482         return NULL;
483 }
484
485 /**
486  * nm_vpn_plugin_info_list_find_by_filename:
487  * @list: (element-type NMVpnPluginInfo): list of plugins
488  * @filename: filename to search
489  *
490  * Returns: (transfer none): the first plugin with a matching @filename (or %NULL).
491  *
492  * Since: 1.2
493  */
494 NMVpnPluginInfo *
495 nm_vpn_plugin_info_list_find_by_filename (GSList *list, const char *filename)
496 {
497         GSList *iter;
498
499         if (!filename)
500                 g_return_val_if_reached (NULL);
501
502         for (iter = list; iter; iter = iter->next) {
503                 if (g_strcmp0 (nm_vpn_plugin_info_get_filename (iter->data), filename) == 0)
504                         return iter->data;
505         }
506         return NULL;
507 }
508
509 /**
510  * nm_vpn_plugin_info_list_find_by_service:
511  * @list: (element-type NMVpnPluginInfo): list of plugins
512  * @service: service to search
513  *
514  * Returns: (transfer none): the first plugin with a matching @service (or %NULL).
515  *
516  * Since: 1.2
517  */
518 NMVpnPluginInfo *
519 nm_vpn_plugin_info_list_find_by_service (GSList *list, const char *service)
520 {
521         GSList *iter;
522
523         if (!service)
524                 g_return_val_if_reached (NULL);
525
526         /* First, consider the primary service name. */
527         for (iter = list; iter; iter = iter->next) {
528                 if (strcmp (NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data)->service, service) == 0)
529                         return iter->data;
530         }
531
532         /* Then look into the aliases. */
533         for (iter = list; iter; iter = iter->next) {
534                 char **aliases = (NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data))->aliases;
535
536                 if (!aliases)
537                         continue;
538                 if (_nm_utils_strv_find_first (aliases, -1, service) >= 0)
539                         return iter->data;
540         }
541         return NULL;
542 }
543
544 /*********************************************************************/
545
546 /**
547  * nm_vpn_plugin_info_get_filename:
548  * @self: plugin info instance
549  *
550  * Returns: (transfer none): the filename. Can be %NULL.
551  *
552  * Since: 1.2
553  */
554 const char *
555 nm_vpn_plugin_info_get_filename (NMVpnPluginInfo *self)
556 {
557         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
558
559         return NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->filename;
560 }
561
562 /**
563  * nm_vpn_plugin_info_get_name:
564  * @self: plugin info instance
565  *
566  * Returns: (transfer none): the name. Cannot be %NULL.
567  *
568  * Since: 1.2
569  */
570 const char *
571 nm_vpn_plugin_info_get_name (NMVpnPluginInfo *self)
572 {
573         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
574
575         return NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->name;
576 }
577
578 /**
579  * nm_vpn_plugin_info_get_plugin:
580  * @self: plugin info instance
581  *
582  * Returns: (transfer none): the plugin. Can be %NULL.
583  *
584  * Since: 1.2
585  */
586 const char *
587 nm_vpn_plugin_info_get_plugin (NMVpnPluginInfo *self)
588 {
589         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
590
591         return g_hash_table_lookup (NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->keys,
592                                     _nm_utils_strstrdictkey_static (NM_VPN_PLUGIN_INFO_KF_GROUP_LIBNM, "plugin"));
593 }
594
595 /**
596  * nm_vpn_plugin_info_get_program:
597  * @self: plugin info instance
598  *
599  * Returns: (transfer none): the program. Can be %NULL.
600  *
601  * Since: 1.2
602  */
603 const char *
604 nm_vpn_plugin_info_get_program (NMVpnPluginInfo *self)
605 {
606         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
607
608         return g_hash_table_lookup (NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->keys,
609                                     _nm_utils_strstrdictkey_static (NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "program"));
610 }
611
612 /**
613  * nm_vpn_plugin_info_supports_multiple:
614  * @self: plugin info instance
615  *
616  * Returns: %TRUE if the service supports multiple instances with different bus names, otherwise %FALSE
617  *
618  * Since: 1.2
619  */
620 gboolean
621 nm_vpn_plugin_info_supports_multiple (NMVpnPluginInfo *self)
622 {
623         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), FALSE);
624
625         return g_key_file_get_boolean (NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->keyfile,
626                                        NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION,
627                                        "supports-multiple-connections",
628                                        NULL);
629 }
630
631
632 /**
633  * nm_vpn_plugin_info_lookup_property:
634  * @self: plugin info instance
635  * @group: group name
636  * @key: name of the property
637  *
638  * Returns: (transfer none): #NMVpnPluginInfo is internally a #GKeyFile. Returns the matching
639  * property.
640  *
641  * Since: 1.2
642  */
643 const char *
644 nm_vpn_plugin_info_lookup_property (NMVpnPluginInfo *self, const char *group, const char *key)
645 {
646         NMVpnPluginInfoPrivate *priv;
647         gs_free NMUtilsStrStrDictKey *k = NULL;
648
649         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
650         g_return_val_if_fail (group, NULL);
651         g_return_val_if_fail (key, NULL);
652
653         priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
654
655         k = _nm_utils_strstrdictkey_create (group, key);
656         return g_hash_table_lookup (priv->keys, k);
657 }
658
659 /*********************************************************************/
660
661 /**
662  * nm_vpn_plugin_info_get_editor_plugin:
663  * @self: plugin info instance
664  *
665  * Returns: (transfer none): the cached #NMVpnEditorPlugin instance.
666  *
667  * Since: 1.2
668  */
669 NMVpnEditorPlugin *
670 nm_vpn_plugin_info_get_editor_plugin (NMVpnPluginInfo *self)
671 {
672         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
673
674         return NM_VPN_PLUGIN_INFO_GET_PRIVATE (self)->editor_plugin;
675 }
676
677 /**
678  * nm_vpn_plugin_info_set_editor_plugin:
679  * @self: plugin info instance
680  * @plugin: (allow-none): plugin instance
681  *
682  * Set the internal plugin instance. If %NULL, only clear the previous instance.
683  *
684  * Since: 1.2
685  */
686 void
687 nm_vpn_plugin_info_set_editor_plugin (NMVpnPluginInfo *self, NMVpnEditorPlugin *plugin)
688 {
689         NMVpnPluginInfoPrivate *priv;
690         NMVpnEditorPlugin *old;
691
692         g_return_if_fail (NM_IS_VPN_PLUGIN_INFO (self));
693         g_return_if_fail (!plugin || G_IS_OBJECT (plugin));
694
695         priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
696
697         if (!plugin) {
698                 priv->editor_plugin_loaded = FALSE;
699                 g_clear_object (&priv->editor_plugin);
700         } else {
701                 old = priv->editor_plugin;
702                 priv->editor_plugin = g_object_ref (plugin);
703                 priv->editor_plugin_loaded = TRUE;
704                 if (old)
705                         g_object_unref (old);
706         }
707 }
708
709 /**
710  * nm_vpn_plugin_info_load_editor_plugin:
711  * @self: plugin info instance
712  * @error: error reason on failure
713  *
714  * Returns: (transfer none): loads the plugin and returns the newly created
715  *   instance. The plugin is owned by @self and can be later retrieved again
716  *   via nm_vpn_plugin_info_get_editor_plugin(). You can load the
717  *   plugin only once, unless you reset the state via
718  *   nm_vpn_plugin_info_set_editor_plugin().
719  *
720  * Since: 1.2
721  */
722 NMVpnEditorPlugin *
723 nm_vpn_plugin_info_load_editor_plugin (NMVpnPluginInfo *self, GError **error)
724 {
725         NMVpnPluginInfoPrivate *priv;
726         const char *plugin_filename;
727
728         g_return_val_if_fail (NM_IS_VPN_PLUGIN_INFO (self), NULL);
729
730         priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
731
732         if (priv->editor_plugin)
733                 return priv->editor_plugin;
734
735         plugin_filename = nm_vpn_plugin_info_get_plugin (self);
736         if (!plugin_filename || !*plugin_filename) {
737                 g_set_error (error,
738                              NM_VPN_PLUGIN_ERROR,
739                              NM_VPN_PLUGIN_ERROR_FAILED,
740                              _("missing \"plugin\" setting"));
741                 return NULL;
742         }
743
744         /* We only try once to load the plugin. If we previously tried and it was
745          * unsuccessful, error out immediately. */
746         if (priv->editor_plugin_loaded) {
747                 g_set_error (error,
748                              NM_VPN_PLUGIN_ERROR,
749                              NM_VPN_PLUGIN_ERROR_FAILED,
750                              _("%s: don't retry loading plugin which already failed previously"), priv->name);
751                 return NULL;
752         }
753
754         priv->editor_plugin_loaded = TRUE;
755         priv->editor_plugin = nm_vpn_editor_plugin_load_from_file (plugin_filename,
756                                                                    priv->service,
757                                                                    getuid (),
758                                                                    NULL,
759                                                                    NULL,
760                                                                    error);
761         return priv->editor_plugin;
762 }
763
764 /*********************************************************************/
765
766 /**
767  * nm_vpn_plugin_info_new_from_file:
768  * @filename: filename to read.
769  * @error: on failure, the error reason.
770  *
771  * Read the plugin info from file @filename. Does not do
772  * any further verification on the file. You might want to check
773  * file permissions and ownership of the file.
774  *
775  * Returns: %NULL if there is any error or a newly created
776  * #NMVpnPluginInfo instance.
777  *
778  * Since: 1.2
779  */
780 NMVpnPluginInfo *
781 nm_vpn_plugin_info_new_from_file (const char *filename,
782                                   GError **error)
783 {
784         g_return_val_if_fail (filename, NULL);
785
786         return NM_VPN_PLUGIN_INFO (g_initable_new (NM_TYPE_VPN_PLUGIN_INFO,
787                                                    NULL,
788                                                    error,
789                                                    NM_VPN_PLUGIN_INFO_FILENAME, filename,
790                                                    NULL));
791 }
792
793 /**
794  * nm_vpn_plugin_info_new_with_data:
795  * @filename: optional filename.
796  * @keyfile: inject data for the plugin info instance.
797  * @error: construction may fail if the keyfile lacks mandatory fields.
798  *   In this case, return the error reason.
799  *
800  * This constructor does not read any data from file but
801  * takes instead a @keyfile argument.
802  *
803  * Returns: new plugin info instance.
804  *
805  * Since: 1.2
806  */
807 NMVpnPluginInfo *
808 nm_vpn_plugin_info_new_with_data (const char *filename,
809                                   GKeyFile *keyfile,
810                                   GError **error)
811 {
812         g_return_val_if_fail (keyfile, NULL);
813
814         return NM_VPN_PLUGIN_INFO (g_initable_new (NM_TYPE_VPN_PLUGIN_INFO,
815                                                    NULL,
816                                                    error,
817                                                    NM_VPN_PLUGIN_INFO_FILENAME, filename,
818                                                    NM_VPN_PLUGIN_INFO_KEYFILE, keyfile,
819                                                    NULL));
820 }
821
822 /*********************************************************************/
823
824 static void
825 nm_vpn_plugin_info_init (NMVpnPluginInfo *plugin)
826 {
827 }
828
829 static gboolean
830 init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
831 {
832         NMVpnPluginInfo *self = NM_VPN_PLUGIN_INFO (initable);
833         NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
834         gs_strfreev char **groups = NULL;
835         guint i, j;
836
837         if (!priv->keyfile) {
838                 if (!priv->filename) {
839                         g_set_error_literal (error,
840                                              NM_VPN_PLUGIN_ERROR,
841                                              NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
842                                              _("missing filename to load VPN plugin info"));
843                         return FALSE;
844                 }
845                 priv->keyfile = g_key_file_new ();
846                 if (!g_key_file_load_from_file (priv->keyfile, priv->filename, G_KEY_FILE_NONE, error))
847                         return FALSE;
848         }
849
850         /* we reqire at least a "name" */
851         priv->name = g_key_file_get_string (priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "name", NULL);
852         if (!priv->name || !priv->name[0]) {
853                 g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
854                                      _("missing name for VPN plugin info"));
855                 return FALSE;
856         }
857
858         /* we also require "service", because that how we associate NMSettingVpn:service-type with the
859          * NMVpnPluginInfo. */
860         priv->service = g_key_file_get_string (priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "service", NULL);
861         if (!priv->service || !*priv->service) {
862                 g_set_error_literal (error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS,
863                                      _("missing service for VPN plugin info"));
864                 return FALSE;
865         }
866
867         priv->aliases = g_key_file_get_string_list (priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "aliases", NULL, NULL);
868
869         priv->keys = g_hash_table_new_full (_nm_utils_strstrdictkey_hash,
870                                             _nm_utils_strstrdictkey_equal,
871                                             g_free, g_free);
872         groups = g_key_file_get_groups (priv->keyfile, NULL);
873         for (i = 0; groups && groups[i]; i++) {
874                 gs_strfreev char **keys = NULL;
875
876                 keys = g_key_file_get_keys (priv->keyfile, groups[i], NULL, NULL);
877                 for (j = 0; keys && keys[j]; j++) {
878                         char *s;
879
880                         /* Lookup the value via get_string(). We want that behavior.
881                          * You could still lookup the original values via g_key_file_get_value()
882                          * based on priv->keyfile. */
883                         s = g_key_file_get_string (priv->keyfile, groups[i], keys[j], NULL);
884                         if (s)
885                                 g_hash_table_insert (priv->keys, _nm_utils_strstrdictkey_create (groups[i], keys[j]), s);
886                 }
887         }
888
889         return TRUE;
890 }
891
892 static void
893 set_property (GObject *object, guint prop_id,
894               const GValue *value, GParamSpec *pspec)
895 {
896         NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (object);
897
898         switch (prop_id) {
899         case PROP_FILENAME:
900                 priv->filename = g_value_dup_string (value);
901                 break;
902         case PROP_KEYFILE:
903                 priv->keyfile = g_value_dup_boxed (value);
904                 break;
905         default:
906                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
907                 break;
908         }
909 }
910
911 static void
912 get_property (GObject *object, guint prop_id,
913               GValue *value, GParamSpec *pspec)
914 {
915         NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (object);
916
917         switch (prop_id) {
918         case PROP_NAME:
919                 g_value_set_string (value, priv->name);
920                 break;
921         case PROP_FILENAME:
922                 g_value_set_string (value, priv->filename);
923                 break;
924         default:
925                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
926                 break;
927         }
928 }
929
930 static void
931 dispose (GObject *object)
932 {
933         NMVpnPluginInfo *self = NM_VPN_PLUGIN_INFO (object);
934         NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
935
936         g_clear_object (&priv->editor_plugin);
937
938         G_OBJECT_CLASS (nm_vpn_plugin_info_parent_class)->dispose (object);
939 }
940
941 static void
942 finalize (GObject *object)
943 {
944         NMVpnPluginInfo *self = NM_VPN_PLUGIN_INFO (object);
945         NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (self);
946
947         g_free (priv->name);
948         g_free (priv->service);
949         g_strfreev (priv->aliases);
950         g_free (priv->filename);
951         g_key_file_unref (priv->keyfile);
952         g_hash_table_unref (priv->keys);
953
954         G_OBJECT_CLASS (nm_vpn_plugin_info_parent_class)->finalize (object);
955 }
956
957 static void
958 nm_vpn_plugin_info_class_init (NMVpnPluginInfoClass *plugin_class)
959 {
960         GObjectClass *object_class = G_OBJECT_CLASS (plugin_class);
961
962         g_type_class_add_private (object_class, sizeof (NMVpnPluginInfoPrivate));
963
964         /* virtual methods */
965         object_class->set_property = set_property;
966         object_class->get_property = get_property;
967         object_class->dispose      = dispose;
968         object_class->finalize     = finalize;
969
970         /* properties */
971
972         /**
973          * NMVpnPluginInfo:name:
974          *
975          * The name of the VPN plugin.
976          *
977          * Since: 1.2
978          */
979         g_object_class_install_property
980             (object_class, PROP_NAME,
981              g_param_spec_string (NM_VPN_PLUGIN_INFO_NAME, "", "",
982                                   NULL,
983                                   G_PARAM_READABLE |
984                                   G_PARAM_STATIC_STRINGS));
985
986         /**
987          * NMVpnPluginInfo:filename:
988          *
989          * The filename from which the info was loaded.
990          * Can be %NULL if the instance was not loaded from
991          * a file (i.e. the keyfile instance was passed to the
992          * constructor).
993          *
994          * Since: 1.2
995          */
996         g_object_class_install_property
997             (object_class, PROP_FILENAME,
998              g_param_spec_string (NM_VPN_PLUGIN_INFO_FILENAME, "", "",
999                                   NULL,
1000                                   G_PARAM_READWRITE |
1001                                   G_PARAM_CONSTRUCT_ONLY |
1002                                   G_PARAM_STATIC_STRINGS));
1003
1004         /**
1005          * NMVpnPluginInfo:keyfile:
1006          *
1007          * Initalize the instance with a different keyfile instance.
1008          * When passing a keyfile instance, the constructor will not
1009          * try to read from filename.
1010          *
1011          * Since: 1.2
1012          */
1013         g_object_class_install_property
1014             (object_class, PROP_KEYFILE,
1015              g_param_spec_boxed (NM_VPN_PLUGIN_INFO_KEYFILE, "", "",
1016                                  G_TYPE_KEY_FILE,
1017                                  G_PARAM_WRITABLE |
1018                                  G_PARAM_CONSTRUCT_ONLY |
1019                                  G_PARAM_STATIC_STRINGS));
1020 }
1021
1022 static void
1023 nm_vpn_plugin_info_initable_iface_init (GInitableIface *iface)
1024 {
1025         iface->init = init_sync;
1026 }
1027