device: renew dhcp leases on awake for software devices
[NetworkManager.git] / callouts / nm-dispatcher-utils.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) 2008 - 2011 Red Hat, Inc.
19  */
20
21 #include "nm-default.h"
22
23 #include <string.h>
24
25 #include "nm-dbus-interface.h"
26 #include "nm-connection.h"
27 #include "nm-setting-ip4-config.h"
28 #include "nm-setting-ip6-config.h"
29 #include "nm-setting-connection.h"
30
31 #include "nm-dispatcher-api.h"
32 #include "nm-utils.h"
33
34 #include "nm-dispatcher-utils.h"
35
36 static GSList *
37 construct_basic_items (GSList *list,
38                        const char *uuid,
39                        const char *id,
40                        const char *iface,
41                        const char *ip_iface)
42 {
43         if (uuid)
44                 list = g_slist_prepend (list, g_strdup_printf ("CONNECTION_UUID=%s", uuid));
45         if (id)
46                 list = g_slist_prepend (list, g_strdup_printf ("CONNECTION_ID=%s", id));
47         if (iface)
48                 list = g_slist_prepend (list, g_strdup_printf ("DEVICE_IFACE=%s", iface));
49         if (ip_iface)
50                 list = g_slist_prepend (list, g_strdup_printf ("DEVICE_IP_IFACE=%s", ip_iface));
51         return list;
52 }
53
54 static GSList *_list_append_val_strv (GSList *items, char **values, const char *format, ...) G_GNUC_PRINTF(3, 4);
55
56 static GSList *
57 _list_append_val_strv (GSList *items, char **values, const char *format, ...)
58 {
59         if (!values)
60                 g_return_val_if_reached (items);
61
62         /*  Only add an item if the list of @values is not empty */
63         if (values[0]) {
64                 va_list args;
65                 guint i;
66                 GString *str = g_string_new (NULL);
67
68                 va_start (args, format);
69                 g_string_append_vprintf (str, format, args);
70                 va_end (args);
71
72                 g_string_append (str, values[0]);
73                 for (i = 1; values[i]; i++) {
74                         g_string_append_c (str, ' ');
75                         g_string_append (str, values[i]);
76                 }
77                 items = g_slist_prepend (items, g_string_free (str, FALSE));
78         }
79
80         /* we take ownership of the values array and free it. */
81         g_strfreev (values);
82         return items;
83 }
84
85 static GSList *
86 add_domains (GSList *items,
87              GVariant *dict,
88              const char *prefix,
89              const char four_or_six)
90 {
91         GVariant *val;
92
93         /* Search domains */
94         val = g_variant_lookup_value (dict, "domains", G_VARIANT_TYPE_STRING_ARRAY);
95         if (val) {
96                 items = _list_append_val_strv (items, g_variant_dup_strv (val, NULL),
97                                                "%sIP%c_DOMAINS=", prefix, four_or_six);
98                 g_variant_unref (val);
99         }
100         return items;
101 }
102
103 static GSList *
104 construct_ip4_items (GSList *items, GVariant *ip4_config, const char *prefix)
105 {
106         GPtrArray *addresses, *routes;
107         char *gateway;
108         GVariant *val;
109         int i;
110
111         if (ip4_config == NULL)
112                 return items;
113
114         if (prefix == NULL)
115                 prefix = "";
116
117         /* IP addresses */
118         val = g_variant_lookup_value (ip4_config, "addresses", G_VARIANT_TYPE ("aau"));
119         if (val) {
120                 addresses = nm_utils_ip4_addresses_from_variant (val, &gateway);
121                 if (!gateway)
122                         gateway = g_strdup ("0.0.0.0");
123
124                 for (i = 0; i < addresses->len; i++) {
125                         NMIPAddress *addr = addresses->pdata[i];
126                         char *addrtmp;
127
128                         addrtmp = g_strdup_printf ("%sIP4_ADDRESS_%d=%s/%d %s", prefix, i,
129                                                    nm_ip_address_get_address (addr),
130                                                    nm_ip_address_get_prefix (addr),
131                                                    gateway);
132                         items = g_slist_prepend (items, addrtmp);
133                 }
134                 if (addresses->len)
135                         items = g_slist_prepend (items, g_strdup_printf ("%sIP4_NUM_ADDRESSES=%d", prefix, addresses->len));
136
137                 /* Write gateway to a separate variable, too. */
138                 items = g_slist_prepend (items, g_strdup_printf ("%sIP4_GATEWAY=%s", prefix, gateway));
139
140                 g_ptr_array_unref (addresses);
141                 g_free (gateway);
142                 g_variant_unref (val);
143         }
144
145         /* DNS servers */
146         val = g_variant_lookup_value (ip4_config, "nameservers", G_VARIANT_TYPE ("au"));
147         if (val) {
148                 items = _list_append_val_strv (items, nm_utils_ip4_dns_from_variant (val),
149                                                "%sIP4_NAMESERVERS=", prefix);
150                 g_variant_unref (val);
151         }
152
153         /* Search domains */
154         items = add_domains (items, ip4_config, prefix, '4');
155
156         /* WINS servers */
157         val = g_variant_lookup_value (ip4_config, "wins-servers", G_VARIANT_TYPE ("au"));
158         if (val) {
159                 items = _list_append_val_strv (items, nm_utils_ip4_dns_from_variant (val),
160                                                "%sIP4_WINS_SERVERS=", prefix);
161                 g_variant_unref (val);
162         }
163
164         /* Static routes */
165         val = g_variant_lookup_value (ip4_config, "routes", G_VARIANT_TYPE ("aau"));
166         if (val) {
167                 routes = nm_utils_ip4_routes_from_variant (val);
168
169                 for (i = 0; i < routes->len; i++) {
170                         NMIPRoute *route = routes->pdata[i];
171                         const char *next_hop;
172                         char *routetmp;
173
174                         next_hop = nm_ip_route_get_next_hop (route);
175                         if (!next_hop)
176                                 next_hop = "0.0.0.0";
177
178                         routetmp = g_strdup_printf ("%sIP4_ROUTE_%d=%s/%d %s %u", prefix, i,
179                                                     nm_ip_route_get_dest (route),
180                                                     nm_ip_route_get_prefix (route),
181                                                     next_hop,
182                                                     (guint32) MAX (0, nm_ip_route_get_metric (route)));
183                         items = g_slist_prepend (items, routetmp);
184                 }
185                 items = g_slist_prepend (items, g_strdup_printf ("%sIP4_NUM_ROUTES=%d", prefix, routes->len));
186                 g_ptr_array_unref (routes);
187                 g_variant_unref (val);
188         } else
189                 items = g_slist_prepend (items, g_strdup_printf ("%sIP4_NUM_ROUTES=0", prefix));
190
191         return items;
192 }
193
194 static GSList *
195 construct_device_dhcp4_items (GSList *items, GVariant *dhcp4_config)
196 {
197         GVariantIter iter;
198         const char *key, *tmp;
199         GVariant *val;
200         char *ucased;
201
202         if (dhcp4_config == NULL)
203                 return items;
204
205         g_variant_iter_init (&iter, dhcp4_config);
206         while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) {
207                 ucased = g_ascii_strup (key, -1);
208                 tmp = g_variant_get_string (val, NULL);
209                 items = g_slist_prepend (items, g_strdup_printf ("DHCP4_%s=%s", ucased, tmp));
210                 g_free (ucased);
211                 g_variant_unref (val);
212         }
213         return items;
214 }
215
216 static GSList *
217 construct_ip6_items (GSList *items, GVariant *ip6_config, const char *prefix)
218 {
219         GPtrArray *addresses, *routes;
220         char *gateway = NULL;
221         GVariant *val;
222         int i;
223
224         if (ip6_config == NULL)
225                 return items;
226
227         if (prefix == NULL)
228                 prefix = "";
229
230         /* IP addresses */
231         val = g_variant_lookup_value (ip6_config, "addresses", G_VARIANT_TYPE ("a(ayuay)"));
232         if (val) {
233                 addresses = nm_utils_ip6_addresses_from_variant (val, &gateway);
234                 if (!gateway)
235                         gateway = g_strdup ("::");
236
237                 for (i = 0; i < addresses->len; i++) {
238                         NMIPAddress *addr = addresses->pdata[i];
239                         char *addrtmp;
240
241                         addrtmp = g_strdup_printf ("%sIP6_ADDRESS_%d=%s/%d %s", prefix, i,
242                                                    nm_ip_address_get_address (addr),
243                                                    nm_ip_address_get_prefix (addr),
244                                                    gateway);
245                         items = g_slist_prepend (items, addrtmp);
246                 }
247                 if (addresses->len)
248                         items = g_slist_prepend (items, g_strdup_printf ("%sIP6_NUM_ADDRESSES=%d", prefix, addresses->len));
249
250                 /* Write gateway to a separate variable, too. */
251                 items = g_slist_prepend (items, g_strdup_printf ("%sIP6_GATEWAY=%s", prefix, gateway));
252
253                 g_ptr_array_unref (addresses);
254                 g_free (gateway);
255                 g_variant_unref (val);
256         }
257
258         /* DNS servers */
259         val = g_variant_lookup_value (ip6_config, "nameservers", G_VARIANT_TYPE ("aay"));
260         if (val) {
261                 items = _list_append_val_strv (items, nm_utils_ip6_dns_from_variant (val),
262                                                "%sIP6_NAMESERVERS=", prefix);
263                 g_variant_unref (val);
264         }
265
266         /* Search domains */
267         items = add_domains (items, ip6_config, prefix, '6');
268
269         /* Static routes */
270         val = g_variant_lookup_value (ip6_config, "routes", G_VARIANT_TYPE ("a(ayuayu)"));
271         if (val) {
272                 routes = nm_utils_ip6_routes_from_variant (val);
273
274                 for (i = 0; i < routes->len; i++) {
275                         NMIPRoute *route = routes->pdata[i];
276                         const char *next_hop;
277                         char *routetmp;
278
279                         next_hop = nm_ip_route_get_next_hop (route);
280                         if (!next_hop)
281                                 next_hop = "::";
282
283                         routetmp = g_strdup_printf ("%sIP6_ROUTE_%d=%s/%d %s %u", prefix, i,
284                                                     nm_ip_route_get_dest (route),
285                                                     nm_ip_route_get_prefix (route),
286                                                     next_hop,
287                                                     (guint32) MAX (0, nm_ip_route_get_metric (route)));
288                         items = g_slist_prepend (items, routetmp);
289                 }
290                 if (routes->len)
291                         items = g_slist_prepend (items, g_strdup_printf ("%sIP6_NUM_ROUTES=%d", prefix, routes->len));
292                 g_ptr_array_unref (routes);
293                 g_variant_unref (val);
294         }
295
296         return items;
297 }
298
299 static GSList *
300 construct_device_dhcp6_items (GSList *items, GVariant *dhcp6_config)
301 {
302         GVariantIter iter;
303         const char *key, *tmp;
304         GVariant *val;
305         char *ucased;
306
307         if (dhcp6_config == NULL)
308                 return items;
309
310         g_variant_iter_init (&iter, dhcp6_config);
311         while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) {
312                 ucased = g_ascii_strup (key, -1);
313                 tmp = g_variant_get_string (val, NULL);
314                 items = g_slist_prepend (items, g_strdup_printf ("DHCP6_%s=%s", ucased, tmp));
315                 g_free (ucased);
316                 g_variant_unref (val);
317         }
318         return items;
319 }
320
321 char **
322 nm_dispatcher_utils_construct_envp (const char *action,
323                                     GVariant *connection_dict,
324                                     GVariant *connection_props,
325                                     GVariant *device_props,
326                                     GVariant *device_ip4_props,
327                                     GVariant *device_ip6_props,
328                                     GVariant *device_dhcp4_props,
329                                     GVariant *device_dhcp6_props,
330                                     const char *vpn_ip_iface,
331                                     GVariant *vpn_ip4_props,
332                                     GVariant *vpn_ip6_props,
333                                     char **out_iface,
334                                     const char **out_error_message)
335 {
336         const char *iface = NULL, *ip_iface = NULL;
337         const char *uuid = NULL, *id = NULL, *path = NULL;
338         const char *filename = NULL;
339         gboolean external;
340         NMDeviceState dev_state = NM_DEVICE_STATE_UNKNOWN;
341         GVariant *value;
342         char **envp = NULL, *path_item;
343         GSList *items = NULL, *iter;
344         guint i;
345         GVariant *con_setting;
346         const char *error_message_backup;
347
348         if (!out_error_message)
349                 out_error_message = &error_message_backup;
350
351         g_return_val_if_fail (action != NULL, NULL);
352         g_return_val_if_fail (out_iface != NULL, NULL);
353         g_return_val_if_fail (*out_iface == NULL, NULL);
354
355         /* Hostname changes don't require a device nor contain a connection */
356         if (!strcmp (action, NMD_ACTION_HOSTNAME))
357                 goto done;
358
359         /* Connection properties */
360         if (!g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_PATH, "&o", &path)) {
361                 *out_error_message = "Missing or invalid required value " NMD_CONNECTION_PROPS_PATH "!";
362                 return NULL;
363         }
364         items = g_slist_prepend (items, g_strdup_printf ("CONNECTION_DBUS_PATH=%s", path));
365
366         if (g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_EXTERNAL, "b", &external) && external)
367                 items = g_slist_prepend (items, g_strdup ("CONNECTION_EXTERNAL=1"));
368
369         if (g_variant_lookup (connection_props, NMD_CONNECTION_PROPS_FILENAME, "&s", &filename))
370                 items = g_slist_prepend (items, g_strdup_printf ("CONNECTION_FILENAME=%s", filename));
371
372
373         /* Canonicalize the VPN interface name; "" is used when passing it through
374          * D-Bus so make sure that's fixed up here.
375          */
376         if (vpn_ip_iface && !strlen (vpn_ip_iface))
377                 vpn_ip_iface = NULL;
378
379         /* interface name */
380         if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_INTERFACE, "&s", &iface)) {
381                 *out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_INTERFACE "!";
382                 return NULL;
383         }
384         if (!*iface)
385                 iface = NULL;
386
387         /* IP interface name */
388         value = g_variant_lookup_value (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, NULL);
389         if (value) {
390                 if (!g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) {
391                         *out_error_message = "Invalid value " NMD_DEVICE_PROPS_IP_INTERFACE "!";
392                         return NULL;
393                 }
394                 g_variant_unref (value);
395                 (void) g_variant_lookup (device_props, NMD_DEVICE_PROPS_IP_INTERFACE, "&s", &ip_iface);
396         }
397
398         /* Device type */
399         if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_TYPE, "u", NULL)) {
400                 *out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_TYPE "!";
401                 return NULL;
402         }
403
404         /* Device state */
405         value = g_variant_lookup_value (device_props, NMD_DEVICE_PROPS_STATE, G_VARIANT_TYPE_UINT32);
406         if (!value) {
407                 *out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_STATE "!";
408                 return NULL;
409         }
410         dev_state = g_variant_get_uint32 (value);
411         g_variant_unref (value);
412
413         /* device itself */
414         if (!g_variant_lookup (device_props, NMD_DEVICE_PROPS_PATH, "o", NULL)) {
415                 *out_error_message = "Missing or invalid required value " NMD_DEVICE_PROPS_PATH "!";
416                 return NULL;
417         }
418
419         /* UUID and ID */
420         con_setting = g_variant_lookup_value (connection_dict, NM_SETTING_CONNECTION_SETTING_NAME, NM_VARIANT_TYPE_SETTING);
421         if (!con_setting) {
422                 *out_error_message = "Failed to read connection setting";
423                 return NULL;
424         }
425
426         if (!g_variant_lookup (con_setting, NM_SETTING_CONNECTION_UUID, "&s", &uuid)) {
427                 *out_error_message = "Connection hash did not contain the UUID";
428                 g_variant_unref (con_setting);
429                 return NULL;
430         }
431
432         if (!g_variant_lookup (con_setting, NM_SETTING_CONNECTION_ID, "&s", &id)) {
433                 *out_error_message = "Connection hash did not contain the ID";
434                 g_variant_unref (con_setting);
435                 return NULL;
436         }
437
438         items = construct_basic_items (items, uuid, id, iface, ip_iface);
439         g_variant_unref (con_setting);
440
441         /* Device it's aren't valid if the device isn't activated */
442         if (iface && (dev_state == NM_DEVICE_STATE_ACTIVATED)) {
443                 items = construct_ip4_items (items, device_ip4_props, NULL);
444                 items = construct_ip6_items (items, device_ip6_props, NULL);
445                 items = construct_device_dhcp4_items (items, device_dhcp4_props);
446                 items = construct_device_dhcp6_items (items, device_dhcp6_props);
447         }
448
449         if (vpn_ip_iface) {
450                 items = g_slist_prepend (items, g_strdup_printf ("VPN_IP_IFACE=%s", vpn_ip_iface));
451                 items = construct_ip4_items (items, vpn_ip4_props, "VPN_");
452                 items = construct_ip6_items (items, vpn_ip6_props, "VPN_");
453         }
454
455         /* Backwards compat: 'iface' is set in this order:
456          * 1) VPN interface name
457          * 2) Device IP interface name
458          * 3) Device interface anme
459          */
460         if (vpn_ip_iface)
461                 *out_iface = g_strdup (vpn_ip_iface);
462         else if (ip_iface)
463                 *out_iface = g_strdup (ip_iface);
464         else
465                 *out_iface = g_strdup (iface);
466
467  done:
468         path = g_getenv ("PATH");
469         if (path) {
470                 path_item = g_strdup_printf ("PATH=%s", path);
471                 items = g_slist_prepend (items, path_item);
472         }
473
474         /* Convert the list to an environment pointer */
475         envp = g_new0 (char *, g_slist_length (items) + 1);
476         for (iter = items, i = 0; iter; iter = g_slist_next (iter), i++)
477                 envp[i] = (char *) iter->data;
478         g_slist_free (items);
479
480         *out_error_message = NULL;
481         return envp;
482 }
483