Merge "Fix SIGSEV on freeing server domains list" into tizen
[platform/upstream/connman.git] / src / dns-systemd-resolved.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2017  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <gdbus.h>
31 #include <glib.h>
32 #include <connman/dbus.h>
33
34 #include "connman.h"
35
36 #define SYSTEMD_RESOLVED_SERVICE "org.freedesktop.resolve1"
37 #define SYSTEMD_RESOLVED_PATH "/org/freedesktop/resolve1"
38
39 struct mdns_data {
40         int index;
41         bool enabled;
42 };
43
44 static GHashTable *interface_hash;
45 static DBusConnection *connection;
46 static GDBusClient *client;
47 static GDBusProxy *resolved_proxy;
48
49 /* update after a full set of instructions has been received */
50 static guint update_interfaces_source;
51
52 struct dns_interface {
53         GList *domains;
54         GList *servers;
55         int index;
56         bool needs_domain_update;
57         bool needs_server_update;
58 };
59
60 static gboolean compare_index(gconstpointer a, gconstpointer b)
61 {
62         gint ai = GPOINTER_TO_UINT(a);
63         gint bi = GPOINTER_TO_UINT(b);
64
65         return ai == bi;
66 }
67
68 static void free_dns_interface(gpointer data)
69 {
70         struct dns_interface *iface = data;
71
72         if (!iface)
73                 return;
74
75         g_list_free_full(iface->domains, g_free);
76         g_list_free_full(iface->servers, g_free);
77
78         g_free(iface);
79 }
80
81 static void setlinkdns_append(DBusMessageIter *iter, void *user_data)
82 {
83         struct dns_interface *iface = user_data;
84         int result;
85         unsigned int i;
86         int type;
87         char ipv4_bytes[4];
88         char ipv6_bytes[16];
89         GList *list;
90         DBusMessageIter address_array, struct_array, byte_array;
91
92         dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &iface->index);
93
94         dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(iay)",
95                         &address_array);
96
97         for (list = iface->servers; list; list = g_list_next(list)) {
98                 char *server = list->data;
99
100                 DBG("index: %d, server: %s", iface->index, server);
101
102                 dbus_message_iter_open_container(&address_array,
103                                 DBUS_TYPE_STRUCT, NULL, &struct_array);
104
105                 type = connman_inet_check_ipaddress(server);
106
107                 if (type == AF_INET) {
108                         result = inet_pton(type, server, ipv4_bytes);
109                         if (result != 1) {
110                                 DBG("Failed to parse IPv4 address: %s",
111                                                 server);
112                                 return;
113                         }
114
115                         dbus_message_iter_append_basic(&struct_array,
116                                         DBUS_TYPE_INT32, &type);
117
118                         dbus_message_iter_open_container(&struct_array,
119                                         DBUS_TYPE_ARRAY, "y", &byte_array);
120
121                         for (i = 0; i < sizeof(ipv4_bytes); i++) {
122                                 dbus_message_iter_append_basic(&byte_array,
123                                                 DBUS_TYPE_BYTE,
124                                                 &(ipv4_bytes[i]));
125                         }
126
127                         dbus_message_iter_close_container(&struct_array,
128                                         &byte_array);
129                 } else if (type == AF_INET6) {
130                         result = inet_pton(type, server, ipv6_bytes);
131                         if (result != 1) {
132                                 DBG("Failed to parse IPv6 address: %s", server);
133                                 return;
134                         }
135
136                         dbus_message_iter_append_basic(&struct_array,
137                                         DBUS_TYPE_INT32, &type);
138
139                         dbus_message_iter_open_container(&struct_array,
140                                         DBUS_TYPE_ARRAY, "y", &byte_array);
141
142                         for (i = 0; i < sizeof(ipv6_bytes); i++) {
143                                 dbus_message_iter_append_basic(&byte_array,
144                                                 DBUS_TYPE_BYTE,
145                                                 &(ipv6_bytes[i]));
146                         }
147
148                         dbus_message_iter_close_container(&struct_array,
149                                         &byte_array);
150                 }
151
152                 dbus_message_iter_close_container(&address_array,
153                                 &struct_array);
154         }
155
156         dbus_message_iter_close_container(iter, &address_array);
157 }
158
159 static void setlinkdomains_append(DBusMessageIter *iter, void *user_data)
160 {
161         struct dns_interface *iface = user_data;
162         GList *list;
163         DBusMessageIter domain_array, struct_array;
164         gboolean only_routing = FALSE;
165
166         dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &iface->index);
167
168         dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(sb)",
169                                                 &domain_array);
170
171         for (list = iface->domains; list; list = g_list_next(list)) {
172                 char *domain = list->data;
173
174                 DBG("index: %d, domain: %s", iface->index, domain);
175
176                 dbus_message_iter_open_container(&domain_array,
177                                 DBUS_TYPE_STRUCT, NULL, &struct_array);
178
179                 dbus_message_iter_append_basic(&struct_array, DBUS_TYPE_STRING,
180                                 &domain);
181
182                 dbus_message_iter_append_basic(&struct_array, DBUS_TYPE_BOOLEAN,
183                                 &only_routing);
184
185                 dbus_message_iter_close_container(&domain_array, &struct_array);
186         }
187
188         dbus_message_iter_close_container(iter, &domain_array);
189 }
190
191 static int set_systemd_resolved_values(struct dns_interface *iface)
192 {
193         if (!resolved_proxy || !iface)
194                 return -ENOENT;
195
196         /* No async error processing -- just fire and forget */
197
198         if (iface->needs_server_update) {
199                 if (!g_dbus_proxy_method_call(resolved_proxy, "SetLinkDNS",
200                                 setlinkdns_append, NULL, iface, NULL))
201                         return -EINVAL;
202
203                 iface->needs_server_update = FALSE;
204         }
205
206         if (iface->needs_domain_update) {
207                 if (!g_dbus_proxy_method_call(resolved_proxy, "SetLinkDomains",
208                                 setlinkdomains_append, NULL, iface, NULL))
209                         return -EINVAL;
210
211                 iface->needs_domain_update = FALSE;
212         }
213
214         return 0;
215 }
216
217 static bool is_empty(struct dns_interface *iface)
218 {
219         if (!iface)
220                 return FALSE;
221
222         return (!iface->domains && !iface->servers);
223 }
224
225 static void update_interface(gpointer key, gpointer value, gpointer data)
226 {
227         struct dns_interface *iface = value;
228         GList **removed_items = data;
229
230         set_systemd_resolved_values(iface);
231
232         if (is_empty(iface))
233                 *removed_items = g_list_prepend(*removed_items, iface);
234 }
235
236 static int update_systemd_resolved(gpointer data)
237 {
238         GList *removed_items = NULL, *list;
239
240         if (!interface_hash) {
241                 DBG("no interface hash when updating");
242
243                 return G_SOURCE_REMOVE;
244         }
245
246         g_hash_table_foreach(interface_hash, update_interface, &removed_items);
247
248         for (list = removed_items; list; list = g_list_next(list)) {
249                 struct dns_interface *iface = list->data;
250
251                 g_hash_table_remove(interface_hash,
252                                 GUINT_TO_POINTER(iface->index));
253         }
254
255         g_list_free(removed_items);
256
257         update_interfaces_source = 0;
258
259         return G_SOURCE_REMOVE;
260 }
261
262 static GList *remove_string(GList *str_list, const char *str)
263 {
264         GList *match = NULL;
265
266         match = g_list_find_custom(str_list, str,
267                         (GCompareFunc) g_strcmp0);
268         if (match) {
269                 g_free(match->data);
270                         return g_list_delete_link(str_list, match);
271         }
272
273         return str_list;
274 }
275
276 static void remove_values(struct dns_interface *iface, const char *domain,
277                                                         const char *server)
278 {
279         if (!iface)
280                 return;
281
282         if (domain) {
283                 iface->domains = remove_string(iface->domains, domain);
284                 iface->needs_domain_update = TRUE;
285         }
286
287         if (server) {
288                 iface->servers = remove_string(iface->servers, server);
289                 iface->needs_server_update = TRUE;
290         }
291 }
292
293 int __connman_dnsproxy_remove(int index, const char *domain,
294                                                         const char *server)
295 {
296         struct dns_interface *iface;
297
298         DBG("%d, %s, %s", index, domain ? domain : "no domain",
299                         server ? server : "no server");
300
301         if (!interface_hash || index < 0)
302                 return -EINVAL;
303
304         iface = g_hash_table_lookup(interface_hash, GUINT_TO_POINTER(index));
305
306         if (!iface)
307                 return -EINVAL;
308
309         remove_values(iface, domain, server);
310
311         if (!update_interfaces_source)
312                 update_interfaces_source = g_idle_add(update_systemd_resolved,
313                                 NULL);
314
315         return 0;
316 }
317
318 static GList *replace_to_end(GList *str_list, const char *str)
319 {
320         GList *list;
321
322         for (list = str_list; list; list = g_list_next(list)) {
323                 char *orig = list->data;
324
325                 if (g_strcmp0(orig, str) == 0) {
326                         str_list = g_list_remove(str_list, orig);
327                         g_free(orig);
328                         break;
329                 }
330         }
331
332         return g_list_append(str_list, g_strdup(str));
333 }
334
335 int __connman_dnsproxy_append(int index, const char *domain,
336                                                         const char *server)
337 {
338         struct dns_interface *iface;
339
340         DBG("%d, %s, %s", index, domain ? domain : "no domain",
341                         server ? server : "no server");
342
343         if (!interface_hash || index < 0)
344                 return -EINVAL;
345
346         iface = g_hash_table_lookup(interface_hash, GUINT_TO_POINTER(index));
347
348         if (!iface) {
349                 iface = g_new0(struct dns_interface, 1);
350                 if (!iface)
351                         return -ENOMEM;
352
353                 iface->index = index;
354                 g_hash_table_replace(interface_hash, GUINT_TO_POINTER(index), iface);
355         }
356
357         if (domain) {
358                 iface->domains = replace_to_end(iface->domains, domain);
359                 iface->needs_domain_update = TRUE;
360         }
361
362         if (server) {
363                 iface->servers = replace_to_end(iface->servers, server);
364                 iface->needs_server_update = TRUE;
365         }
366
367         if (!update_interfaces_source)
368                 update_interfaces_source = g_idle_add(update_systemd_resolved,
369                                 NULL);
370
371         return 0;
372 }
373
374 int __connman_dnsproxy_add_listener(int index)
375 {
376         DBG("");
377
378         return -ENXIO;
379 }
380
381 void __connman_dnsproxy_remove_listener(int index)
382 {
383         DBG("");
384 }
385
386 static int setup_resolved(void)
387 {
388         connection = connman_dbus_get_connection();
389         if (!connection)
390                 return -ENXIO;
391
392         client = g_dbus_client_new(connection, SYSTEMD_RESOLVED_SERVICE,
393                         SYSTEMD_RESOLVED_PATH);
394
395         if (!client)
396                 return -EINVAL;
397
398         resolved_proxy = g_dbus_proxy_new(client, SYSTEMD_RESOLVED_PATH,
399                         "org.freedesktop.resolve1.Manager");
400
401         if (!resolved_proxy)
402                 return -EINVAL;
403
404         return 0;
405 }
406
407 static void setlinkmulticastdns_append(DBusMessageIter *iter, void *user_data) {
408         struct mdns_data *data = user_data;
409         char *val = "no";
410
411         if (data->enabled)
412                 val = "yes";
413
414         DBG("SetLinkMulticastDNS: %d/%s", data->index, val);
415
416         dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &data->index);
417         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &val);
418 }
419
420 int __connman_dnsproxy_set_mdns(int index, bool enabled)
421 {
422         struct mdns_data data = { .index = index, .enabled = enabled };
423
424         if (!resolved_proxy)
425                 return -ENOENT;
426
427         if (index < 0)
428                 return -EINVAL;
429
430         if (!g_dbus_proxy_method_call(resolved_proxy, "SetLinkMulticastDNS",
431                         setlinkmulticastdns_append, NULL, &data, NULL))
432                 return -EINVAL;
433
434         return 0;
435 }
436
437 int __connman_dnsproxy_init(void)
438 {
439         int ret;
440
441         DBG("");
442
443         ret = setup_resolved();
444         if (ret)
445                 return ret;
446
447         interface_hash = g_hash_table_new_full(g_direct_hash,
448                                                 compare_index,
449                                                 NULL,
450                                                 free_dns_interface);
451         if (!interface_hash)
452                 return -ENOMEM;
453
454         return 0;
455 }
456
457 void __connman_dnsproxy_cleanup(void)
458 {
459         DBG("");
460
461         if (update_interfaces_source) {
462                 /*
463                  * It might be that we don't get to an idle loop anymore, so
464                  * run the update function once more to clean up.
465                  */
466                  g_source_remove(update_interfaces_source);
467                  update_systemd_resolved(NULL);
468                  update_interfaces_source = 0;
469         }
470
471         if (interface_hash) {
472                 g_hash_table_destroy(interface_hash);
473                 interface_hash = NULL;
474         }
475
476         if (resolved_proxy) {
477                 g_dbus_proxy_unref(resolved_proxy);
478                 resolved_proxy = NULL;
479         }
480
481         if (client) {
482                 g_dbus_client_unref(client);
483                 client = NULL;
484         }
485
486         if (connection) {
487                 dbus_connection_unref(connection);
488                 connection = NULL;
489         }
490 }