device: renew dhcp leases on awake for software devices
[NetworkManager.git] / src / nm-dispatcher.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager -- Network link manager
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) 2004 - 2012 Red Hat, Inc.
19  * Copyright (C) 2005 - 2008 Novell, Inc.
20  */
21
22 #include "nm-default.h"
23
24 #include <string.h>
25 #include <errno.h>
26
27 #include "nm-dispatcher.h"
28 #include "nm-dispatcher-api.h"
29 #include "NetworkManagerUtils.h"
30 #include "nm-utils.h"
31 #include "nm-device.h"
32 #include "nm-dhcp4-config.h"
33 #include "nm-dhcp6-config.h"
34 #include "nm-ip4-config.h"
35 #include "nm-ip6-config.h"
36 #include "nm-settings-connection.h"
37 #include "nm-platform.h"
38 #include "nm-core-internal.h"
39
40 #define CALL_TIMEOUT (1000 * 60 * 10)  /* 10 minutes for all scripts */
41
42 #define _NMLOG_DOMAIN         LOGD_DISPATCH
43 #define _NMLOG_PREFIX_NAME    "dispatcher"
44 #define _NMLOG(level, ...) \
45     G_STMT_START { \
46         nm_log ((level), _NMLOG_DOMAIN, \
47                 "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
48                 _NMLOG_PREFIX_NAME \
49                 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
50     } G_STMT_END
51
52 static GDBusProxy *dispatcher_proxy;
53 static GHashTable *requests = NULL;
54
55 typedef struct {
56         GFileMonitor *monitor;
57         const char *const description;
58         const char *const dir;
59         const guint16 dir_len;
60         char has_scripts;
61 } Monitor;
62
63 enum {
64         MONITOR_INDEX_DEFAULT,
65         MONITOR_INDEX_PRE_UP,
66         MONITOR_INDEX_PRE_DOWN,
67 };
68
69 static Monitor monitors[3] = {
70 #define MONITORS_INIT_SET(INDEX, USE, SCRIPT_DIR)   [INDEX] = { .dir_len = NM_STRLEN (SCRIPT_DIR), .dir = SCRIPT_DIR, .description = ("" USE), .has_scripts = TRUE }
71         MONITORS_INIT_SET (MONITOR_INDEX_DEFAULT,  "default",  NMD_SCRIPT_DIR_DEFAULT),
72         MONITORS_INIT_SET (MONITOR_INDEX_PRE_UP,   "pre-up",   NMD_SCRIPT_DIR_PRE_UP),
73         MONITORS_INIT_SET (MONITOR_INDEX_PRE_DOWN, "pre-down", NMD_SCRIPT_DIR_PRE_DOWN),
74 };
75
76 static const Monitor*
77 _get_monitor_by_action (DispatcherAction action)
78 {
79         switch (action) {
80         case DISPATCHER_ACTION_PRE_UP:
81         case DISPATCHER_ACTION_VPN_PRE_UP:
82                 return &monitors[MONITOR_INDEX_PRE_UP];
83         case DISPATCHER_ACTION_PRE_DOWN:
84         case DISPATCHER_ACTION_VPN_PRE_DOWN:
85                 return &monitors[MONITOR_INDEX_PRE_DOWN];
86         default:
87                 return &monitors[MONITOR_INDEX_DEFAULT];
88         }
89 }
90
91 static void
92 dump_ip4_to_props (NMIP4Config *ip4, GVariantBuilder *builder)
93 {
94         GVariantBuilder int_builder;
95         guint n, i;
96         const NMPlatformIP4Address *addr;
97         const NMPlatformIP4Route *route;
98         guint32 array[4];
99
100         /* Addresses */
101         g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau"));
102         n = nm_ip4_config_get_num_addresses (ip4);
103         for (i = 0; i < n; i++) {
104                 addr = nm_ip4_config_get_address (ip4, i);
105                 array[0] = addr->address;
106                 array[1] = addr->plen;
107                 array[2] = (i == 0) ? nm_ip4_config_get_gateway (ip4) : 0;
108                 g_variant_builder_add (&int_builder, "@au",
109                                        g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
110                                                                   array, 3, sizeof (guint32)));
111         }
112         g_variant_builder_add (builder, "{sv}",
113                                "addresses",
114                                g_variant_builder_end (&int_builder));
115
116         /* DNS servers */
117         g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("au"));
118         n = nm_ip4_config_get_num_nameservers (ip4);
119         for (i = 0; i < n; i++)
120                 g_variant_builder_add (&int_builder, "u", nm_ip4_config_get_nameserver (ip4, i));
121         g_variant_builder_add (builder, "{sv}",
122                                "nameservers",
123                                g_variant_builder_end (&int_builder));
124
125         /* Search domains */
126         g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("as"));
127         n = nm_ip4_config_get_num_domains (ip4);
128         for (i = 0; i < n; i++)
129                 g_variant_builder_add (&int_builder, "s", nm_ip4_config_get_domain (ip4, i));
130         g_variant_builder_add (builder, "{sv}",
131                                "domains",
132                                g_variant_builder_end (&int_builder));
133
134         /* WINS servers */
135         g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("au"));
136         n = nm_ip4_config_get_num_wins (ip4);
137         for (i = 0; i < n; i++)
138                 g_variant_builder_add (&int_builder, "u", nm_ip4_config_get_wins (ip4, i));
139         g_variant_builder_add (builder, "{sv}",
140                                "wins-servers",
141                                g_variant_builder_end (&int_builder));
142
143         /* Static routes */
144         g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau"));
145         n = nm_ip4_config_get_num_routes (ip4);
146         for (i = 0; i < n; i++) {
147                 route = nm_ip4_config_get_route (ip4, i);
148                 array[0] = route->network;
149                 array[1] = route->plen;
150                 array[2] = route->gateway;
151                 array[3] = route->metric;
152                 g_variant_builder_add (&int_builder, "@au",
153                                        g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
154                                                                   array, 4, sizeof (guint32)));
155         }
156         g_variant_builder_add (builder, "{sv}",
157                                "routes",
158                                g_variant_builder_end (&int_builder));
159 }
160
161 static void
162 dump_ip6_to_props (NMIP6Config *ip6, GVariantBuilder *builder)
163 {
164         GVariantBuilder int_builder;
165         guint n, i;
166         const NMPlatformIP6Address *addr;
167         const struct in6_addr *gw_bytes;
168         const NMPlatformIP6Route *route;
169         GVariant *ip, *gw;
170
171         /* Addresses */
172         g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuay)"));
173         n = nm_ip6_config_get_num_addresses (ip6);
174         for (i = 0; i < n; i++) {
175                 addr = nm_ip6_config_get_address (ip6, i);
176                 gw_bytes = nm_ip6_config_get_gateway (ip6);
177                 ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
178                                                 &addr->address,
179                                                 sizeof (struct in6_addr), 1);
180                 gw = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
181                                                 (i == 0 && gw_bytes) ? gw_bytes : &in6addr_any,
182                                                 sizeof (struct in6_addr), 1);
183                 g_variant_builder_add (&int_builder, "(@ayu@ay)", ip, addr->plen, gw);
184         }
185         g_variant_builder_add (builder, "{sv}",
186                                "addresses",
187                                g_variant_builder_end (&int_builder));
188
189         /* DNS servers */
190         g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aay"));
191         n = nm_ip6_config_get_num_nameservers (ip6);
192         for (i = 0; i < n; i++) {
193                 ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
194                                                 nm_ip6_config_get_nameserver (ip6, i),
195                                                 sizeof (struct in6_addr), 1);
196                 g_variant_builder_add (&int_builder, "@ay", ip);
197         }
198         g_variant_builder_add (builder, "{sv}",
199                                "nameservers",
200                                g_variant_builder_end (&int_builder));
201
202         /* Search domains */
203         g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("as"));
204         n = nm_ip6_config_get_num_domains (ip6);
205         for (i = 0; i < n; i++)
206                 g_variant_builder_add (&int_builder, "s", nm_ip6_config_get_domain (ip6, i));
207         g_variant_builder_add (builder, "{sv}",
208                                "domains",
209                                g_variant_builder_end (&int_builder));
210
211         /* Static routes */
212         g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuayu)"));
213         n = nm_ip6_config_get_num_routes (ip6);
214         for (i = 0; i < n; i++) {
215                 route = nm_ip6_config_get_route (ip6, i);
216                 ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
217                                                 &route->network,
218                                                 sizeof (struct in6_addr), 1);
219                 gw = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
220                                                 &route->gateway,
221                                                 sizeof (struct in6_addr), 1);
222                 g_variant_builder_add (&int_builder, "(@ayu@ayu)", ip, route->plen, gw, route->metric);
223         }
224         g_variant_builder_add (builder, "{sv}",
225                                "routes",
226                                g_variant_builder_end (&int_builder));
227 }
228
229 static void
230 fill_device_props (NMDevice *device,
231                    GVariantBuilder *dev_builder,
232                    GVariantBuilder *ip4_builder,
233                    GVariantBuilder *ip6_builder,
234                    GVariant **dhcp4_props,
235                    GVariant **dhcp6_props)
236 {
237         NMIP4Config *ip4_config;
238         NMIP6Config *ip6_config;
239         NMDhcp4Config *dhcp4_config;
240         NMDhcp6Config *dhcp6_config;
241
242         /* If the action is for a VPN, send the VPN's IP interface instead of the device's */
243         g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_IP_INTERFACE,
244                                g_variant_new_string (nm_device_get_ip_iface (device)));
245         g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_INTERFACE,
246                                g_variant_new_string (nm_device_get_iface (device)));
247         g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_TYPE,
248                                g_variant_new_uint32 (nm_device_get_device_type (device)));
249         g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_STATE,
250                                g_variant_new_uint32 (nm_device_get_state (device)));
251         if (nm_exported_object_is_exported (NM_EXPORTED_OBJECT (device)))
252                 g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_PATH,
253                                        g_variant_new_object_path (nm_exported_object_get_path (NM_EXPORTED_OBJECT (device))));
254
255         ip4_config = nm_device_get_ip4_config (device);
256         if (ip4_config)
257                 dump_ip4_to_props (ip4_config, ip4_builder);
258
259         ip6_config = nm_device_get_ip6_config (device);
260         if (ip6_config)
261                 dump_ip6_to_props (ip6_config, ip6_builder);
262
263         dhcp4_config = nm_device_get_dhcp4_config (device);
264         if (dhcp4_config)
265                 *dhcp4_props = nm_dhcp4_config_get_options (dhcp4_config);
266
267         dhcp6_config = nm_device_get_dhcp6_config (device);
268         if (dhcp6_config)
269                 *dhcp6_props = nm_dhcp6_config_get_options (dhcp6_config);
270 }
271
272 static void
273 fill_vpn_props (NMIP4Config *ip4_config,
274                 NMIP6Config *ip6_config,
275                 GVariantBuilder *ip4_builder,
276                 GVariantBuilder *ip6_builder)
277 {
278         if (ip4_config)
279                 dump_ip4_to_props (ip4_config, ip4_builder);
280         if (ip6_config)
281                 dump_ip6_to_props (ip6_config, ip6_builder);
282 }
283
284 typedef struct {
285         DispatcherAction action;
286         guint request_id;
287         DispatcherFunc callback;
288         gpointer user_data;
289         guint idle_id;
290 } DispatchInfo;
291
292 static void
293 dispatcher_info_free (DispatchInfo *info)
294 {
295         if (info->idle_id)
296                 g_source_remove (info->idle_id);
297         g_free (info);
298 }
299
300 static void
301 _ensure_requests (void)
302 {
303         if (G_UNLIKELY (requests == NULL)) {
304                 requests = g_hash_table_new_full (g_direct_hash,
305                                                   g_direct_equal,
306                                                   NULL,
307                                                   (GDestroyNotify) dispatcher_info_free);
308         }
309 }
310
311 static void
312 dispatcher_info_cleanup (DispatchInfo *info)
313 {
314         g_hash_table_remove (requests, GUINT_TO_POINTER (info->request_id));
315 }
316
317 static const char *
318 dispatch_result_to_string (DispatchResult result)
319 {
320         switch (result) {
321         case DISPATCH_RESULT_UNKNOWN:
322                 return "unknown";
323         case DISPATCH_RESULT_SUCCESS:
324                 return "success";
325         case DISPATCH_RESULT_EXEC_FAILED:
326                 return "exec failed";
327         case DISPATCH_RESULT_FAILED:
328                 return "failed";
329         case DISPATCH_RESULT_TIMEOUT:
330                 return "timed out";
331         }
332         g_assert_not_reached ();
333 }
334
335 static void
336 dispatcher_results_process (guint request_id, DispatcherAction action, GVariantIter *results)
337 {
338         const char *script, *err;
339         guint32 result;
340         const Monitor *monitor = _get_monitor_by_action (action);
341
342         g_return_if_fail (results != NULL);
343
344         if (g_variant_iter_n_children (results) == 0) {
345                 _LOGD ("(%u) succeeded but no scripts invoked", request_id);
346                 return;
347         }
348
349         while (g_variant_iter_next (results, "(&su&s)", &script, &result, &err)) {
350                 const char *script_validation_msg = "";
351
352                 if (!*script) {
353                         script_validation_msg = " (path is NULL)";
354                         script = "(unknown)";
355                 } else if (!strncmp (script, monitor->dir, monitor->dir_len)            /* check: prefixed by script directory */
356                     && script[monitor->dir_len] == '/' && script[monitor->dir_len+1]    /* check: with additional "/?" */
357                     && !strchr (&script[monitor->dir_len+1], '/')) {                    /* check: and no further '/' */
358                         /* we expect the script to lie inside monitor->dir. If it does,
359                          * strip the directory name. Otherwise show the full path and a warning. */
360                         script += monitor->dir_len + 1;
361                 } else
362                         script_validation_msg = " (unexpected path)";
363
364                 if (result == DISPATCH_RESULT_SUCCESS) {
365                         _LOGD ("(%u) %s succeeded%s",
366                                request_id,
367                                script, script_validation_msg);
368                 } else {
369                         _LOGW ("(%u) %s failed (%s): %s%s",
370                                request_id,
371                                script,
372                                dispatch_result_to_string (result),
373                                err,
374                                script_validation_msg);
375                 }
376         }
377 }
378
379 static void
380 dispatcher_done_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
381 {
382         DispatchInfo *info = user_data;
383         GVariant *ret;
384         GVariantIter *results;
385         GError *error = NULL;
386
387         ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
388                                           G_VARIANT_TYPE ("(a(sus))"),
389                                           &error);
390         if (ret) {
391                 g_variant_get (ret, "(a(sus))", &results);
392                 dispatcher_results_process (info->request_id, info->action, results);
393                 g_variant_iter_free (results);
394                 g_variant_unref (ret);
395         } else {
396                 if (_nm_dbus_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")) {
397                         g_dbus_error_strip_remote_error (error);
398                         _LOGW ("(%u) failed to call dispatcher scripts: %s",
399                                info->request_id, error->message);
400                 } else {
401                         _LOGD ("(%u) failed to call dispatcher scripts: %s",
402                                info->request_id, error->message);
403                 }
404                 g_clear_error (&error);
405         }
406
407         if (info->callback)
408                 info->callback (info->request_id, info->user_data);
409
410         dispatcher_info_cleanup (info);
411 }
412
413 static const char *action_table[] = {
414         [DISPATCHER_ACTION_HOSTNAME]     = NMD_ACTION_HOSTNAME,
415         [DISPATCHER_ACTION_PRE_UP]       = NMD_ACTION_PRE_UP,
416         [DISPATCHER_ACTION_UP]           = NMD_ACTION_UP,
417         [DISPATCHER_ACTION_PRE_DOWN]     = NMD_ACTION_PRE_DOWN,
418         [DISPATCHER_ACTION_DOWN]         = NMD_ACTION_DOWN,
419         [DISPATCHER_ACTION_VPN_PRE_UP]   = NMD_ACTION_VPN_PRE_UP,
420         [DISPATCHER_ACTION_VPN_UP]       = NMD_ACTION_VPN_UP,
421         [DISPATCHER_ACTION_VPN_PRE_DOWN] = NMD_ACTION_VPN_PRE_DOWN,
422         [DISPATCHER_ACTION_VPN_DOWN]     = NMD_ACTION_VPN_DOWN,
423         [DISPATCHER_ACTION_DHCP4_CHANGE] = NMD_ACTION_DHCP4_CHANGE,
424         [DISPATCHER_ACTION_DHCP6_CHANGE] = NMD_ACTION_DHCP6_CHANGE,
425 };
426
427 static const char *
428 action_to_string (DispatcherAction action)
429 {
430         g_assert ((gsize) action < G_N_ELEMENTS (action_table));
431         return action_table[action];
432 }
433
434 static gboolean
435 dispatcher_idle_cb (gpointer user_data)
436 {
437         DispatchInfo *info = user_data;
438
439         info->idle_id = 0;
440         if (info->callback)
441                 info->callback (info->request_id, info->user_data);
442         dispatcher_info_cleanup (info);
443         return G_SOURCE_REMOVE;
444 }
445
446 static gboolean
447 _dispatcher_call (DispatcherAction action,
448                   gboolean blocking,
449                   NMSettingsConnection *settings_connection,
450                   NMConnection *applied_connection,
451                   NMDevice *device,
452                   const char *vpn_iface,
453                   NMIP4Config *vpn_ip4_config,
454                   NMIP6Config *vpn_ip6_config,
455                   DispatcherFunc callback,
456                   gpointer user_data,
457                   guint *out_call_id)
458 {
459         GVariant *connection_dict;
460         GVariantBuilder connection_props;
461         GVariantBuilder device_props;
462         GVariantBuilder device_ip4_props;
463         GVariantBuilder device_ip6_props;
464         GVariant *device_dhcp4_props = NULL;
465         GVariant *device_dhcp6_props = NULL;
466         GVariantBuilder vpn_ip4_props;
467         GVariantBuilder vpn_ip6_props;
468         DispatchInfo *info = NULL;
469         gboolean success = FALSE;
470         GError *error = NULL;
471         static guint request_counter = 0;
472         guint reqid = ++request_counter;
473
474         if (!dispatcher_proxy)
475                 return FALSE;
476
477         /* Wrapping protection */
478         if (G_UNLIKELY (!reqid))
479                 reqid = ++request_counter;
480
481         g_assert (!blocking || (!callback && !user_data));
482
483         _ensure_requests ();
484
485         /* All actions except 'hostname' require a device */
486         if (action == DISPATCHER_ACTION_HOSTNAME) {
487                 _LOGD ("(%u) dispatching action '%s'%s",
488                        reqid, action_to_string (action),
489                        blocking
490                            ? " (blocking)"
491                            : (callback ? " (with callback)" : ""));
492         } else {
493                 g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
494
495                 _LOGD ("(%u) (%s) dispatching action '%s'%s",
496                        reqid,
497                        vpn_iface ? vpn_iface : nm_device_get_iface (device),
498                        action_to_string (action),
499                        blocking
500                            ? " (blocking)"
501                            : (callback ? " (with callback)" : ""));
502         }
503
504         if (!_get_monitor_by_action(action)->has_scripts) {
505                 if (blocking == FALSE && (out_call_id || callback)) {
506                         info = g_malloc0 (sizeof (*info));
507                         info->action = action;
508                         info->request_id = reqid;
509                         info->callback = callback;
510                         info->user_data = user_data;
511                         info->idle_id = g_idle_add (dispatcher_idle_cb, info);
512                         _LOGD ("(%u) simulate request; no scripts in %s",  reqid, _get_monitor_by_action(action)->dir);
513                 } else
514                         _LOGD ("(%u) ignoring request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir);
515                 success = TRUE;
516                 goto done;
517         }
518
519         if (applied_connection)
520                 connection_dict = nm_connection_to_dbus (applied_connection, NM_CONNECTION_SERIALIZE_NO_SECRETS);
521         else
522                 connection_dict = g_variant_new_array (G_VARIANT_TYPE ("{sa{sv}}"), NULL, 0);
523
524         g_variant_builder_init (&connection_props, G_VARIANT_TYPE_VARDICT);
525         if (settings_connection) {
526                 const char *connection_path;
527                 const char *filename;
528
529                 connection_path = nm_connection_get_path (NM_CONNECTION (settings_connection));
530                 if (connection_path) {
531                         g_variant_builder_add (&connection_props, "{sv}",
532                                                NMD_CONNECTION_PROPS_PATH,
533                                                g_variant_new_object_path (connection_path));
534                 }
535                 filename = nm_settings_connection_get_filename (settings_connection);
536                 if (filename) {
537                         g_variant_builder_add (&connection_props, "{sv}",
538                                                NMD_CONNECTION_PROPS_FILENAME,
539                                                g_variant_new_string (filename));
540                 }
541                 if (nm_settings_connection_get_nm_generated_assumed (settings_connection)) {
542                         g_variant_builder_add (&connection_props, "{sv}",
543                                                NMD_CONNECTION_PROPS_EXTERNAL,
544                                                g_variant_new_boolean (TRUE));
545                 }
546         }
547
548         g_variant_builder_init (&device_props, G_VARIANT_TYPE_VARDICT);
549         g_variant_builder_init (&device_ip4_props, G_VARIANT_TYPE_VARDICT);
550         g_variant_builder_init (&device_ip6_props, G_VARIANT_TYPE_VARDICT);
551         g_variant_builder_init (&vpn_ip4_props, G_VARIANT_TYPE_VARDICT);
552         g_variant_builder_init (&vpn_ip6_props, G_VARIANT_TYPE_VARDICT);
553
554         /* hostname actions only send the hostname */
555         if (action != DISPATCHER_ACTION_HOSTNAME) {
556                 fill_device_props (device,
557                                    &device_props,
558                                    &device_ip4_props,
559                                    &device_ip6_props,
560                                    &device_dhcp4_props,
561                                    &device_dhcp6_props);
562                 if (vpn_ip4_config || vpn_ip6_config) {
563                         fill_vpn_props (vpn_ip4_config,
564                                         vpn_ip6_config,
565                                         &vpn_ip4_props,
566                                         &vpn_ip6_props);
567                 }
568         }
569
570         if (!device_dhcp4_props)
571                 device_dhcp4_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
572         if (!device_dhcp6_props)
573                 device_dhcp6_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
574
575         /* Send the action to the dispatcher */
576         if (blocking) {
577                 GVariant *ret;
578                 GVariantIter *results;
579
580                 ret = _nm_dbus_proxy_call_sync (dispatcher_proxy, "Action",
581                                                 g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}sa{sv}a{sv}b)",
582                                                                action_to_string (action),
583                                                                connection_dict,
584                                                                &connection_props,
585                                                                &device_props,
586                                                                &device_ip4_props,
587                                                                &device_ip6_props,
588                                                                device_dhcp4_props,
589                                                                device_dhcp6_props,
590                                                                vpn_iface ? vpn_iface : "",
591                                                                &vpn_ip4_props,
592                                                                &vpn_ip6_props,
593                                                                nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)),
594                                                 G_VARIANT_TYPE ("(a(sus))"),
595                                                 G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT,
596                                                 NULL, &error);
597                 if (ret) {
598                         g_variant_get (ret, "(a(sus))", &results);
599                         dispatcher_results_process (reqid, action, results);
600                         g_variant_iter_free (results);
601                         g_variant_unref (ret);
602                         success = TRUE;
603                 } else {
604                         g_dbus_error_strip_remote_error (error);
605                         _LOGW ("(%u) failed: %s", reqid, error->message);
606                         g_clear_error (&error);
607                         success = FALSE;
608                 }
609         } else {
610                 info = g_malloc0 (sizeof (*info));
611                 info->action = action;
612                 info->request_id = reqid;
613                 info->callback = callback;
614                 info->user_data = user_data;
615                 g_dbus_proxy_call (dispatcher_proxy, "Action",
616                                    g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}sa{sv}a{sv}b)",
617                                                   action_to_string (action),
618                                                   connection_dict,
619                                                   &connection_props,
620                                                   &device_props,
621                                                   &device_ip4_props,
622                                                   &device_ip6_props,
623                                                   device_dhcp4_props,
624                                                   device_dhcp6_props,
625                                                   vpn_iface ? vpn_iface : "",
626                                                   &vpn_ip4_props,
627                                                   &vpn_ip6_props,
628                                                   nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)),
629                                    G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT,
630                                    NULL, dispatcher_done_cb, info);
631                 success = TRUE;
632         }
633
634         g_variant_unref (device_dhcp4_props);
635         g_variant_unref (device_dhcp6_props);
636
637 done:
638         if (success && info) {
639                 /* Track the request in case of cancelation */
640                 g_hash_table_insert (requests, GUINT_TO_POINTER (info->request_id), info);
641                 if (out_call_id)
642                         *out_call_id = info->request_id;
643         } else if (out_call_id)
644                 *out_call_id = 0;
645
646         return success;
647 }
648
649 /**
650  * nm_dispatcher_call:
651  * @action: the %DispatcherAction
652  * @settings_connection: the #NMSettingsConnection the action applies to
653  * @applied_connection: the currently applied connection
654  * @device: the #NMDevice the action applies to
655  * @callback: a caller-supplied callback to execute when done
656  * @user_data: caller-supplied pointer passed to @callback
657  * @out_call_id: on success, a call identifier which can be passed to
658  * nm_dispatcher_call_cancel()
659  *
660  * This method always invokes the dispatcher action asynchronously.  To ignore
661  * the result, pass %NULL to @callback.
662  *
663  * Returns: %TRUE if the action was dispatched, %FALSE on failure
664  */
665 gboolean
666 nm_dispatcher_call (DispatcherAction action,
667                     NMSettingsConnection *settings_connection,
668                     NMConnection *applied_connection,
669                     NMDevice *device,
670                     DispatcherFunc callback,
671                     gpointer user_data,
672                     guint *out_call_id)
673 {
674         return _dispatcher_call (action, FALSE, settings_connection, applied_connection, device, NULL, NULL,
675                                  NULL, callback, user_data, out_call_id);
676 }
677
678 /**
679  * nm_dispatcher_call_sync():
680  * @action: the %DispatcherAction
681  * @settings_connection: the #NMSettingsConnection the action applies to
682  * @applied_connection: the currently applied connection
683  * @device: the #NMDevice the action applies to
684  *
685  * This method always invokes the dispatcher action synchronously and it may
686  * take a long time to return.
687  *
688  * Returns: %TRUE if the action was dispatched, %FALSE on failure
689  */
690 gboolean
691 nm_dispatcher_call_sync (DispatcherAction action,
692                          NMSettingsConnection *settings_connection,
693                          NMConnection *applied_connection,
694                          NMDevice *device)
695 {
696         return _dispatcher_call (action, TRUE, settings_connection, applied_connection, device, NULL, NULL,
697                                  NULL, NULL, NULL, NULL);
698 }
699
700 /**
701  * nm_dispatcher_call_vpn():
702  * @action: the %DispatcherAction
703  * @settings_connection: the #NMSettingsConnection the action applies to
704  * @applied_connection: the currently applied connection
705  * @parent_device: the parent #NMDevice of the VPN connection
706  * @vpn_iface: the IP interface of the VPN tunnel, if any
707  * @vpn_ip4_config: the #NMIP4Config of the VPN connection
708  * @vpn_ip6_config: the #NMIP6Config of the VPN connection
709  * @callback: a caller-supplied callback to execute when done
710  * @user_data: caller-supplied pointer passed to @callback
711  * @out_call_id: on success, a call identifier which can be passed to
712  * nm_dispatcher_call_cancel()
713  *
714  * This method always invokes the dispatcher action asynchronously.  To ignore
715  * the result, pass %NULL to @callback.
716  *
717  * Returns: %TRUE if the action was dispatched, %FALSE on failure
718  */
719 gboolean
720 nm_dispatcher_call_vpn (DispatcherAction action,
721                         NMSettingsConnection *settings_connection,
722                         NMConnection *applied_connection,
723                         NMDevice *parent_device,
724                         const char *vpn_iface,
725                         NMIP4Config *vpn_ip4_config,
726                         NMIP6Config *vpn_ip6_config,
727                         DispatcherFunc callback,
728                         gpointer user_data,
729                         guint *out_call_id)
730 {
731         return _dispatcher_call (action, FALSE, settings_connection, applied_connection, parent_device, vpn_iface,
732                                  vpn_ip4_config, vpn_ip6_config, callback, user_data, out_call_id);
733 }
734
735 /**
736  * nm_dispatcher_call_vpn_sync():
737  * @action: the %DispatcherAction
738  * @settings_connection: the #NMSettingsConnection the action applies to
739  * @applied_connection: the currently applied connection
740  * @parent_device: the parent #NMDevice of the VPN connection
741  * @vpn_iface: the IP interface of the VPN tunnel, if any
742  * @vpn_ip4_config: the #NMIP4Config of the VPN connection
743  * @vpn_ip6_config: the #NMIP6Config of the VPN connection
744  *
745  * This method always invokes the dispatcher action synchronously and it may
746  * take a long time to return.
747  *
748  * Returns: %TRUE if the action was dispatched, %FALSE on failure
749  */
750 gboolean
751 nm_dispatcher_call_vpn_sync (DispatcherAction action,
752                              NMSettingsConnection *settings_connection,
753                              NMConnection *applied_connection,
754                              NMDevice *parent_device,
755                              const char *vpn_iface,
756                              NMIP4Config *vpn_ip4_config,
757                              NMIP6Config *vpn_ip6_config)
758 {
759         return _dispatcher_call (action, TRUE, settings_connection, applied_connection, parent_device, vpn_iface,
760                                  vpn_ip4_config, vpn_ip6_config, NULL, NULL, NULL);
761 }
762
763 void
764 nm_dispatcher_call_cancel (guint call_id)
765 {
766         DispatchInfo *info;
767
768         _ensure_requests ();
769
770         /* Canceling just means the callback doesn't get called, so set the
771          * DispatcherInfo's callback to NULL.
772          */
773         info = g_hash_table_lookup (requests, GUINT_TO_POINTER (call_id));
774         g_return_if_fail (info);
775
776         if (info && info->callback) {
777                 _LOGD ("(%u) cancelling dispatcher callback action", call_id);
778                 info->callback = NULL;
779         }
780 }
781
782 static void
783 dispatcher_dir_changed (GFileMonitor *monitor,
784                         GFile *file,
785                         GFile *other_file,
786                         GFileMonitorEvent event_type,
787                         Monitor *item)
788 {
789         const char *name;
790         char *full_name;
791         GDir *dir;
792         GError *error = NULL;
793
794         dir = g_dir_open (item->dir, 0, &error);
795         if (dir) {
796                 int errsv = 0;
797
798                 item->has_scripts = FALSE;
799                 errno = 0;
800                 while (!item->has_scripts
801                     && (name = g_dir_read_name (dir))) {
802                         full_name = g_build_filename (item->dir, name, NULL);
803                         item->has_scripts = g_file_test (full_name, G_FILE_TEST_IS_EXECUTABLE);
804                         g_free (full_name);
805                 }
806                 errsv = errno;
807                 g_dir_close (dir);
808                 if (item->has_scripts)
809                         _LOGD ("%s script directory '%s' has scripts", item->description, item->dir);
810                 else if (errsv == 0)
811                         _LOGD ("%s script directory '%s' has no scripts", item->description, item->dir);
812                 else {
813                         _LOGD ("%s script directory '%s' error reading (%s)", item->description, item->dir, strerror (errsv));
814                         item->has_scripts = TRUE;
815                 }
816         } else {
817                 if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
818                         _LOGD ("%s script directory '%s' does not exist", item->description, item->dir);
819                         item->has_scripts = FALSE;
820                 } else {
821                         _LOGD ("%s script directory '%s' error (%s)", item->description, item->dir, error->message);
822                         item->has_scripts = TRUE;
823                 }
824                 g_error_free (error);
825         }
826
827 }
828
829 void
830 nm_dispatcher_init (void)
831 {
832         GFile *file;
833         guint i;
834         GError *error = NULL;
835
836         for (i = 0; i < G_N_ELEMENTS (monitors); i++) {
837                 file = g_file_new_for_path (monitors[i].dir);
838                 monitors[i].monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
839                 if (monitors[i].monitor) {
840                         g_signal_connect (monitors[i].monitor, "changed", G_CALLBACK (dispatcher_dir_changed), &monitors[i]);
841                         dispatcher_dir_changed (monitors[i].monitor, file, NULL, 0, &monitors[i]);
842                 }
843                 g_object_unref (file);
844         }
845
846         dispatcher_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
847                                                           G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
848                                                               G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
849                                                           NULL,
850                                                           NM_DISPATCHER_DBUS_SERVICE,
851                                                           NM_DISPATCHER_DBUS_PATH,
852                                                           NM_DISPATCHER_DBUS_INTERFACE,
853                                                           NULL, &error);
854         if (!dispatcher_proxy) {
855                 _LOGE ("could not get dispatcher proxy! %s", error->message);
856                 g_clear_error (&error);
857         }
858 }
859