ifcfg-rh: allow handling complex routing rules via dispatcher (rh #1160013)
[NetworkManager.git] / src / settings / plugins / ifcfg-rh / nm-ifcfg-connection.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager system settings service
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * Copyright (C) 2008 - 2011 Red Hat, Inc.
19  */
20
21 #include "config.h"
22
23 #include <string.h>
24
25 #include <glib/gstdio.h>
26
27 #include <nm-dbus-interface.h>
28 #include <nm-setting-connection.h>
29 #include <nm-setting-wired.h>
30 #include <nm-setting-wireless.h>
31 #include <nm-setting-gsm.h>
32 #include <nm-setting-cdma.h>
33 #include <nm-setting-pppoe.h>
34 #include <nm-setting-wireless-security.h>
35 #include <nm-setting-8021x.h>
36 #include <nm-platform.h>
37 #include <nm-logging.h>
38
39 #include "common.h"
40 #include "nm-config.h"
41 #include "nm-ifcfg-connection.h"
42 #include "reader.h"
43 #include "writer.h"
44 #include "nm-inotify-helper.h"
45 #include "utils.h"
46
47 G_DEFINE_TYPE (NMIfcfgConnection, nm_ifcfg_connection, NM_TYPE_SETTINGS_CONNECTION)
48
49 #define NM_IFCFG_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionPrivate))
50
51 typedef struct {
52         gulong ih_event_id;
53
54         int file_wd;
55
56         char *keyfile;
57         int keyfile_wd;
58
59         char *routefile;
60         int routefile_wd;
61
62         char *route6file;
63         int route6file_wd;
64
65         char *unmanaged_spec;
66         char *unrecognized_spec;
67
68         gulong devtimeout_link_changed_handler;
69         guint devtimeout_timeout_id;
70 } NMIfcfgConnectionPrivate;
71
72 enum {
73         PROP_0,
74         PROP_UNMANAGED_SPEC,
75         PROP_UNRECOGNIZED_SPEC,
76         LAST_PROP
77 };
78
79 /* Signals */
80 enum {
81         IFCFG_CHANGED,
82         LAST_SIGNAL
83 };
84
85 static guint signals[LAST_SIGNAL] = { 0 };
86
87 static gboolean
88 devtimeout_ready (gpointer user_data)
89 {
90         NMIfcfgConnection *self = user_data;
91         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
92
93         priv->devtimeout_timeout_id = 0;
94         nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), TRUE);
95         return FALSE;
96 }
97
98 static void
99 link_changed (NMPlatform *platform, int ifindex, NMPlatformLink *link,
100               NMPlatformSignalChangeType change_type, NMPlatformReason reason,
101               NMConnection *self)
102 {
103         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
104         const char *ifname;
105
106         ifname = nm_connection_get_interface_name (self);
107         if (g_strcmp0 (link->name, ifname) != 0)
108                 return;
109
110         /* Shouldn't happen, but... */
111         if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
112                 return;
113
114         nm_log_info (LOGD_SETTINGS, "Device %s appeared; connection '%s' now ready",
115                      ifname, nm_connection_get_id (self));
116
117         g_signal_handler_disconnect (platform, priv->devtimeout_link_changed_handler);
118         priv->devtimeout_link_changed_handler = 0;
119         g_source_remove (priv->devtimeout_timeout_id);
120
121         /* Don't declare the connection ready right away, since NMManager may not have
122          * started processing the device yet.
123          */
124         priv->devtimeout_timeout_id = g_idle_add (devtimeout_ready, self);
125 }
126
127 static gboolean
128 devtimeout_expired (gpointer user_data)
129 {
130         NMIfcfgConnection *self = user_data;
131         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
132
133         nm_log_info (LOGD_SETTINGS, "Device for connection '%s' did not appear before timeout",
134                      nm_connection_get_id (NM_CONNECTION (self)));
135
136         g_signal_handler_disconnect (nm_platform_get (), priv->devtimeout_link_changed_handler);
137         priv->devtimeout_link_changed_handler = 0;
138         priv->devtimeout_timeout_id = 0;
139
140         nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), TRUE);
141         return FALSE;
142 }
143
144 static void
145 nm_ifcfg_connection_check_devtimeout (NMIfcfgConnection *self)
146 {
147         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
148         NMSettingConnection *s_con;
149         const char *ifname;
150         guint devtimeout;
151
152         s_con = nm_connection_get_setting_connection (NM_CONNECTION (self));
153
154         if (!nm_setting_connection_get_autoconnect (s_con))
155                 return;
156         ifname = nm_setting_connection_get_interface_name (s_con);
157         if (!ifname)
158                 return;
159         devtimeout = devtimeout_from_file (nm_ifcfg_connection_get_path (self));
160         if (!devtimeout)
161                 return;
162
163         if (nm_platform_link_get_ifindex (ifname) != 0)
164                 return;
165
166         /* ONBOOT=yes, DEVICE and DEVTIMEOUT are set, but device is not present */
167         nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), FALSE);
168
169         nm_log_info (LOGD_SETTINGS, "Waiting %u seconds for %s to appear for connection '%s'",
170                      devtimeout, ifname, nm_connection_get_id (NM_CONNECTION (self)));
171
172         priv->devtimeout_link_changed_handler =
173                 g_signal_connect (nm_platform_get (), NM_PLATFORM_SIGNAL_LINK_CHANGED,
174                                   G_CALLBACK (link_changed), self);
175         priv->devtimeout_timeout_id = g_timeout_add_seconds (devtimeout, devtimeout_expired, self);
176 }
177
178 static void
179 files_changed_cb (NMInotifyHelper *ih,
180                   struct inotify_event *evt,
181                   const char *path,
182                   gpointer user_data)
183 {
184         NMIfcfgConnection *self = NM_IFCFG_CONNECTION (user_data);
185         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
186
187         if (   (evt->wd != priv->file_wd)
188             && (evt->wd != priv->keyfile_wd)
189             && (evt->wd != priv->routefile_wd)
190             && (evt->wd != priv->route6file_wd))
191                 return;
192
193         /* push the event up to the plugin */
194         g_signal_emit (self, signals[IFCFG_CHANGED], 0);
195 }
196
197 NMIfcfgConnection *
198 nm_ifcfg_connection_new (NMConnection *source,
199                          const char *full_path,
200                          GError **error)
201 {
202         GObject *object;
203         NMConnection *tmp;
204         char *unhandled_spec = NULL;
205         const char *unmanaged_spec = NULL, *unrecognized_spec = NULL;
206         gboolean update_unsaved = TRUE;
207
208         g_assert (source || full_path);
209
210         /* If we're given a connection already, prefer that instead of re-reading */
211         if (source)
212                 tmp = g_object_ref (source);
213         else {
214                 tmp = connection_from_file (full_path,
215                                             &unhandled_spec,
216                                             error);
217                 if (!tmp)
218                         return NULL;
219
220                 /* If we just read the connection from disk, it's clearly not Unsaved */
221                 update_unsaved = FALSE;
222         }
223
224         if (unhandled_spec && g_str_has_prefix (unhandled_spec, "unmanaged:"))
225                 unmanaged_spec = unhandled_spec + strlen ("unmanaged:");
226         else if (unhandled_spec && g_str_has_prefix (unhandled_spec, "unrecognized:"))
227                 unrecognized_spec = unhandled_spec + strlen ("unrecognized:");
228
229         object = (GObject *) g_object_new (NM_TYPE_IFCFG_CONNECTION,
230                                            NM_SETTINGS_CONNECTION_FILENAME, full_path,
231                                            NM_IFCFG_CONNECTION_UNMANAGED_SPEC, unmanaged_spec,
232                                            NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, unrecognized_spec,
233                                            NULL);
234         /* Update our settings with what was read from the file */
235         if (nm_settings_connection_replace_settings (NM_SETTINGS_CONNECTION (object),
236                                                      tmp,
237                                                      update_unsaved,
238                                                      error))
239                 nm_ifcfg_connection_check_devtimeout (NM_IFCFG_CONNECTION (object));
240         else
241                 g_clear_object (&object);
242
243         g_object_unref (tmp);
244         g_free (unhandled_spec);
245         return (NMIfcfgConnection *) object;
246 }
247
248 static void
249 path_watch_stop (NMIfcfgConnection *self)
250 {
251         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
252         NMInotifyHelper *ih;
253
254         ih = nm_inotify_helper_get ();
255
256         if (priv->ih_event_id) {
257                 g_signal_handler_disconnect (ih, priv->ih_event_id);
258                 priv->ih_event_id = 0;
259         }
260
261         if (priv->file_wd >= 0) {
262                 nm_inotify_helper_remove_watch (ih, priv->file_wd);
263                 priv->file_wd = -1;
264         }
265
266         g_free (priv->keyfile);
267         priv->keyfile = NULL;
268         if (priv->keyfile_wd >= 0) {
269                 nm_inotify_helper_remove_watch (ih, priv->keyfile_wd);
270                 priv->keyfile_wd = -1;
271         }
272
273         g_free (priv->routefile);
274         priv->routefile = NULL;
275         if (priv->routefile_wd >= 0) {
276                 nm_inotify_helper_remove_watch (ih, priv->routefile_wd);
277                 priv->routefile_wd = -1;
278         }
279
280         g_free (priv->route6file);
281         priv->route6file = NULL;
282         if (priv->route6file_wd >= 0) {
283                 nm_inotify_helper_remove_watch (ih, priv->route6file_wd);
284                 priv->route6file_wd = -1;
285         }
286 }
287
288 static void
289 filename_changed (GObject *object,
290                   GParamSpec *pspec,
291                   gpointer user_data)
292 {
293         NMIfcfgConnection *self = NM_IFCFG_CONNECTION (object);
294         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
295         const char *ifcfg_path;
296
297         path_watch_stop (self);
298
299         ifcfg_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (self));
300         if (!ifcfg_path)
301                 return;
302
303         priv->keyfile = utils_get_keys_path (ifcfg_path);
304         priv->routefile = utils_get_route_path (ifcfg_path);
305         priv->route6file = utils_get_route6_path (ifcfg_path);
306
307         if (nm_config_get_monitor_connection_files (nm_config_get ())) {
308                 NMInotifyHelper *ih = nm_inotify_helper_get ();
309
310                 priv->ih_event_id = g_signal_connect (ih, "event", G_CALLBACK (files_changed_cb), self);
311                 priv->file_wd = nm_inotify_helper_add_watch (ih, ifcfg_path);
312                 priv->keyfile_wd = nm_inotify_helper_add_watch (ih, priv->keyfile);
313                 priv->routefile_wd = nm_inotify_helper_add_watch (ih, priv->routefile);
314                 priv->route6file_wd = nm_inotify_helper_add_watch (ih, priv->route6file);
315         }
316 }
317
318 const char *
319 nm_ifcfg_connection_get_unmanaged_spec (NMIfcfgConnection *self)
320 {
321         g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), NULL);
322
323         return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unmanaged_spec;
324 }
325
326 const char *
327 nm_ifcfg_connection_get_unrecognized_spec (NMIfcfgConnection *self)
328 {
329         g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), NULL);
330
331         return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unrecognized_spec;
332 }
333
334 static void
335 replace_and_commit (NMSettingsConnection *connection,
336                     NMConnection *new_connection,
337                     NMSettingsConnectionCommitFunc callback,
338                     gpointer user_data)
339 {
340         const char *filename;
341         GError *error = NULL;
342
343         filename = nm_settings_connection_get_filename (connection);
344         if (filename && utils_has_complex_routes (filename)) {
345                 if (callback) {
346                         error = g_error_new_literal (NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
347                                                      "Cannot modify a connection that has an associated 'rule-' or 'rule6-' file");
348                         callback (connection, error, user_data);
349                         g_clear_error (&error);
350                 }
351         }
352
353         NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->replace_and_commit (connection, new_connection, callback, user_data);
354 }
355
356 static void
357 commit_changes (NMSettingsConnection *connection,
358                 NMSettingsConnectionCommitFunc callback,
359                 gpointer user_data)
360 {
361         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (connection);
362         GError *error = NULL;
363         NMConnection *reread;
364         gboolean same = FALSE, success = FALSE;
365         char *ifcfg_path = NULL;
366         const char *filename;
367
368         /* To ensure we don't rewrite files that are only changed from other
369          * processes on-disk, read the existing connection back in and only rewrite
370          * it if it's really changed.
371          */
372         filename = nm_settings_connection_get_filename (connection);
373         if (filename) {
374                 reread = connection_from_file (filename, NULL, NULL);
375                 if (reread) {
376                         same = nm_connection_compare (NM_CONNECTION (connection),
377                                                       reread,
378                                                       NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
379                                                          NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS);
380                         g_object_unref (reread);
381
382                         /* Don't bother writing anything out if in-memory and on-disk data are the same */
383                         if (same) {
384                                 /* But chain up to parent to handle success - emits updated signal */
385                                 NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->commit_changes (connection, callback, user_data);
386                                 return;
387                         }
388                 }
389
390                 success = writer_update_connection (NM_CONNECTION (connection),
391                                                     IFCFG_DIR,
392                                                     filename,
393                                                     priv->keyfile,
394                                                     &error);
395         } else {
396                 success = writer_new_connection (NM_CONNECTION (connection),
397                                                  IFCFG_DIR,
398                                                  &ifcfg_path,
399                                                  &error);
400                 if (success) {
401                         nm_settings_connection_set_filename (connection, ifcfg_path);
402                         g_free (ifcfg_path);
403                 }
404         }
405
406         if (success) {
407                 /* Chain up to parent to handle success */
408                 NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->commit_changes (connection, callback, user_data);
409         } else {
410                 /* Otherwise immediate error */
411                 callback (connection, error, user_data);
412                 g_error_free (error);
413         }
414 }
415
416 static void
417 do_delete (NMSettingsConnection *connection,
418                NMSettingsConnectionDeleteFunc callback,
419                gpointer user_data)
420 {
421         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (connection);
422         const char *filename;
423
424         filename = nm_settings_connection_get_filename (connection);
425         if (filename) {
426                 g_unlink (filename);
427                 if (priv->keyfile)
428                         g_unlink (priv->keyfile);
429                 if (priv->routefile)
430                         g_unlink (priv->routefile);
431                 if (priv->route6file)
432                         g_unlink (priv->route6file);
433         }
434
435         NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->delete (connection, callback, user_data);
436 }
437
438 /* GObject */
439
440 static void
441 nm_ifcfg_connection_init (NMIfcfgConnection *connection)
442 {
443         g_signal_connect (connection, "notify::" NM_SETTINGS_CONNECTION_FILENAME,
444                           G_CALLBACK (filename_changed), NULL);
445 }
446
447 static void
448 set_property (GObject *object, guint prop_id,
449                     const GValue *value, GParamSpec *pspec)
450 {
451         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object);
452
453         switch (prop_id) {
454         case PROP_UNMANAGED_SPEC:
455                 priv->unmanaged_spec = g_value_dup_string (value);
456                 break;
457         case PROP_UNRECOGNIZED_SPEC:
458                 priv->unrecognized_spec = g_value_dup_string (value);
459                 break;
460         default:
461                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
462                 break;
463         }
464 }
465
466 static void
467 get_property (GObject *object, guint prop_id,
468                     GValue *value, GParamSpec *pspec)
469 {
470         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object);
471
472         switch (prop_id) {
473         case PROP_UNMANAGED_SPEC:
474                 g_value_set_string (value, priv->unmanaged_spec);
475                 break;
476         case PROP_UNRECOGNIZED_SPEC:
477                 g_value_set_string (value, priv->unrecognized_spec);
478                 break;
479         default:
480                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
481                 break;
482         }
483 }
484
485 static void
486 dispose (GObject *object)
487 {
488         NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (object);
489
490         path_watch_stop (NM_IFCFG_CONNECTION (object));
491
492         if (priv->devtimeout_link_changed_handler) {
493                 g_signal_handler_disconnect (nm_platform_get (),
494                                              priv->devtimeout_link_changed_handler);
495                 priv->devtimeout_link_changed_handler = 0;
496         }
497         if (priv->devtimeout_timeout_id) {
498                 g_source_remove (priv->devtimeout_timeout_id);
499                 priv->devtimeout_timeout_id = 0;
500         }
501
502         G_OBJECT_CLASS (nm_ifcfg_connection_parent_class)->dispose (object);
503 }
504
505 static void
506 nm_ifcfg_connection_class_init (NMIfcfgConnectionClass *ifcfg_connection_class)
507 {
508         GObjectClass *object_class = G_OBJECT_CLASS (ifcfg_connection_class);
509         NMSettingsConnectionClass *settings_class = NM_SETTINGS_CONNECTION_CLASS (ifcfg_connection_class);
510
511         g_type_class_add_private (ifcfg_connection_class, sizeof (NMIfcfgConnectionPrivate));
512
513         /* Virtual methods */
514         object_class->set_property = set_property;
515         object_class->get_property = get_property;
516         object_class->dispose      = dispose;
517         settings_class->delete = do_delete;
518         settings_class->replace_and_commit = replace_and_commit;
519         settings_class->commit_changes = commit_changes;
520
521         /* Properties */
522         g_object_class_install_property
523                 (object_class, PROP_UNMANAGED_SPEC,
524                  g_param_spec_string (NM_IFCFG_CONNECTION_UNMANAGED_SPEC, "", "",
525                                       NULL,
526                                       G_PARAM_READWRITE |
527                                       G_PARAM_STATIC_STRINGS));
528         g_object_class_install_property
529                 (object_class, PROP_UNRECOGNIZED_SPEC,
530                  g_param_spec_string (NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, "", "",
531                                       NULL,
532                                       G_PARAM_READWRITE |
533                                       G_PARAM_STATIC_STRINGS));
534
535         signals[IFCFG_CHANGED] =
536                 g_signal_new ("ifcfg-changed",
537                               G_OBJECT_CLASS_TYPE (object_class),
538                               G_SIGNAL_RUN_LAST,
539                               0, NULL, NULL,
540                               g_cclosure_marshal_VOID__VOID,
541                               G_TYPE_NONE, 0);
542 }
543