nm-dns-manager.c 73.3 KB
Newer Older
Dan Williams's avatar
Dan Williams committed
1 2
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
3
 *
Dan Williams's avatar
Dan Williams committed
4 5 6 7
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
8
 *
Dan Williams's avatar
Dan Williams committed
9 10 11 12
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
13
 *
Dan Williams's avatar
Dan Williams committed
14 15 16
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
 *
Dan Williams's avatar
Dan Williams committed
18
 * Copyright (C) 2004 - 2005 Colin Walters <walters@redhat.com>
19
 * Copyright (C) 2004 - 2017 Red Hat, Inc.
Dan Williams's avatar
Dan Williams committed
20 21
 * Copyright (C) 2005 - 2008 Novell, Inc.
 *   and others
22 23
 */

24
#include "nm-default.h"
25

26
#include <fcntl.h>
Dan Winship's avatar
Dan Winship committed
27 28
#include <resolv.h>
#include <stdlib.h>
29
#include <sys/stat.h>
30
#include <sys/ioctl.h>
31 32
#include <sys/types.h>
#include <sys/wait.h>
33 34 35
#include <unistd.h>

#include <linux/fs.h>
36

37 38 39 40
#if WITH_LIBPSL
#include <libpsl.h>
#endif

41
#include "nm-utils.h"
42
#include "nm-core-internal.h"
43
#include "nm-dns-manager.h"
44
#include "nm-ip4-config.h"
45
#include "nm-ip6-config.h"
46
#include "NetworkManagerUtils.h"
47
#include "nm-config.h"
48
#include "nm-dbus-object.h"
49 50
#include "devices/nm-device.h"
#include "nm-manager.h"
51

52
#include "nm-dns-plugin.h"
53
#include "nm-dns-dnsmasq.h"
54
#include "nm-dns-systemd-resolved.h"
55
#include "nm-dns-unbound.h"
56

57
#define HASH_LEN   NM_UTILS_CHECKSUM_LENGTH_SHA1
58

59
#ifndef RESOLVCONF_PATH
60 61 62
#define RESOLVCONF_PATH "/sbin/resolvconf"
#endif

63
#ifndef NETCONFIG_PATH
64 65 66
#define NETCONFIG_PATH "/sbin/netconfig"
#endif

67 68 69 70
#define PLUGIN_RATELIMIT_INTERVAL    30
#define PLUGIN_RATELIMIT_BURST       5
#define PLUGIN_RATELIMIT_DELAY       300

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
/*****************************************************************************/

typedef enum {
	SR_SUCCESS,
	SR_NOTFOUND,
	SR_ERROR
} SpawnResult;

typedef struct {
	GPtrArray *nameservers;
	GPtrArray *searches;
	GPtrArray *options;
	const char *nis_domain;
	GPtrArray *nis_servers;
} NMResolvConfData;

/*****************************************************************************/

89 90 91 92 93 94
enum {
	CONFIG_CHANGED,

	LAST_SIGNAL
};

95 96 97
NM_GOBJECT_PROPERTIES_DEFINE (NMDnsManager,
	PROP_MODE,
	PROP_RC_MANAGER,
98
	PROP_CONFIGURATION,
99 100
);

101 102 103
static guint signals[LAST_SIGNAL] = { 0 };

typedef struct {
104 105
	GHashTable *configs;
	CList ip_config_lst_head;
106
	GVariant *config_variant;
107

108 109 110 111 112
	NMDnsIPConfigData *best_ip_config_4;
	NMDnsIPConfigData *best_ip_config_6;

	bool ip_config_lst_need_sort:1;

113 114
	bool dns_touched:1;
	bool is_stopped:1;
115

116
	char *hostname;
117
	guint updates_queue;
118

119 120
	guint8 hash[HASH_LEN];  /* SHA1 hash of current DNS config */
	guint8 prev_hash[HASH_LEN];  /* Hash when begin_updates() was called */
121

122
	NMDnsManagerResolvConfManager rc_manager;
123
	char *mode;
124
	NMDnsPlugin *sd_resolve_plugin;
125
	NMDnsPlugin *plugin;
126

127 128
	NMConfig *config;

129 130 131 132 133
	struct {
		guint64 ts;
		guint num_restarts;
		guint timer;
	} plugin_ratelimit;
134
} NMDnsManagerPrivate;
135

136
struct _NMDnsManager {
137
	NMDBusObject parent;
138 139
	NMDnsManagerPrivate _priv;
};
140

141
struct _NMDnsManagerClass {
142
	NMDBusObjectClass parent;
143 144
};

145
G_DEFINE_TYPE (NMDnsManager, nm_dns_manager, NM_TYPE_DBUS_OBJECT)
146

147
#define NM_DNS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDnsManager, NM_IS_DNS_MANAGER)
148

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
NM_DEFINE_SINGLETON_GETTER (NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER);

/*****************************************************************************/

#define _NMLOG_PREFIX_NAME                "dns-mgr"
#define _NMLOG_DOMAIN                     LOGD_DNS
#define _NMLOG(level, ...) \
    G_STMT_START { \
        const NMLogLevel __level = (level); \
        \
        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
            char __prefix[20]; \
            const NMDnsManager *const __self = (self); \
            \
            _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \
                     "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                     _NMLOG_PREFIX_NAME, \
                     ((!__self || __self == singleton_instance) \
                        ? "" \
                        : nm_sprintf_buf (__prefix, "[%p]", __self)) \
                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
        } \
    } G_STMT_END

/*****************************************************************************/

175 176 177 178 179 180
static void _ip_config_dns_priority_changed (gpointer config,
                                             GParamSpec *pspec,
                                             NMDnsIPConfigData *ip_data);

/*****************************************************************************/

181
static gboolean
182
domain_is_valid (const char *domain, gboolean check_public_suffix)
183 184 185 186
{
	if (*domain == '\0')
		return FALSE;
#if WITH_LIBPSL
187
	if (check_public_suffix && psl_is_public_suffix (psl_builtin (), domain))
188 189 190 191 192
		return FALSE;
#endif
	return TRUE;
}

193 194 195 196 197 198
static gboolean
domain_is_routing (const char *domain)
{
	return domain[0] == '~';
}

199
/*****************************************************************************/
200

201 202
NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_rc_manager_to_string, NMDnsManagerResolvConfManager,
	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
203
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN,        "unknown"),
204 205
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED,      "unmanaged"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE,      "immutable"),
206 207 208 209
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK,        "symlink"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE,           "file"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF,     "resolvconf"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG,      "netconfig"),
210 211
);

212 213
NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_config_type_to_string, NMDnsIPConfigType,
	NM_UTILS_LOOKUP_DEFAULT_WARN ("<unknown>"),
214
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_REMOVED, "removed"),
215 216 217 218 219
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_DEFAULT, "default"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE, "best"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_VPN, "vpn"),
);

220 221 222 223 224 225 226 227 228 229 230 231
/*****************************************************************************/

static void
_ASSERT_config_data (const NMDnsConfigData *data)
{
	nm_assert (data);
	nm_assert (NM_IS_DNS_MANAGER (data->self));
	nm_assert (data->ifindex > 0);
}

static void
_ASSERT_ip_config_data (const NMDnsIPConfigData *ip_data)
232
{
233 234
	nm_assert (ip_data);
	_ASSERT_config_data (ip_data->data);
235
	nm_assert (NM_IS_IP_CONFIG (ip_data->ip_config, AF_UNSPEC));
236 237 238
	nm_assert (c_list_contains (&ip_data->data->data_lst_head, &ip_data->data_lst));
	nm_assert (ip_data->data->ifindex == nm_ip_config_get_ifindex (ip_data->ip_config));
}
239

240 241 242 243 244 245
static NMDnsIPConfigData *
_ip_config_data_new (NMDnsConfigData *data,
                     NMIPConfig *ip_config,
                     NMDnsIPConfigType ip_config_type)
{
	NMDnsIPConfigData *ip_data;
246

247
	_ASSERT_config_data (data);
248
	nm_assert (NM_IS_IP_CONFIG (ip_config, AF_UNSPEC));
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
	nm_assert (ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED);

	ip_data = g_slice_new0 (NMDnsIPConfigData);
	ip_data->data = data;
	ip_data->ip_config = g_object_ref (ip_config);
	ip_data->ip_config_type = ip_config_type;
	c_list_link_tail (&data->data_lst_head, &ip_data->data_lst);
	c_list_link_tail (&NM_DNS_MANAGER_GET_PRIVATE (data->self)->ip_config_lst_head, &ip_data->ip_config_lst);

	g_signal_connect (ip_config,
	                  NM_IS_IP4_CONFIG (ip_config)
	                    ? "notify::" NM_IP4_CONFIG_DNS_PRIORITY
	                    : "notify::" NM_IP6_CONFIG_DNS_PRIORITY,
	                  (GCallback) _ip_config_dns_priority_changed, ip_data);

	_ASSERT_ip_config_data (ip_data);
	return ip_data;
266 267
}

268 269
static void
_ip_config_data_free (NMDnsIPConfigData *ip_data)
270
{
271
	_ASSERT_ip_config_data (ip_data);
272

273 274
	c_list_unlink_stale (&ip_data->data_lst);
	c_list_unlink_stale (&ip_data->ip_config_lst);
275

276 277 278
	g_free (ip_data->domains.search);
	g_strfreev (ip_data->domains.reverse);

279 280 281 282 283 284
	g_signal_handlers_disconnect_by_func (ip_data->ip_config,
	                                      _ip_config_dns_priority_changed,
	                                      ip_data);

	g_object_unref (ip_data->ip_config);
	g_slice_free (NMDnsIPConfigData, ip_data);
285 286
}

287 288 289
static NMDnsIPConfigData *
_config_data_find_ip_config (NMDnsConfigData *data,
                             NMIPConfig *ip_config)
290
{
291
	NMDnsIPConfigData *ip_data;
292

293 294 295 296
	_ASSERT_config_data (data);

	c_list_for_each_entry (ip_data, &data->data_lst_head, data_lst) {
		_ASSERT_ip_config_data (ip_data);
297

298 299 300 301
		if (ip_data->ip_config == ip_config)
			return ip_data;
	}
	return NULL;
302 303
}

304
static void
305
_config_data_free (NMDnsConfigData *data)
306
{
307
	_ASSERT_config_data (data);
308

309 310
	nm_assert (c_list_is_empty (&data->data_lst_head));
	g_slice_free (NMDnsConfigData, data);
311 312
}

313
static int
314 315 316
_ip_config_lst_cmp (const CList *a_lst,
                    const CList *b_lst,
                    const void *user_data)
317
{
318 319
	const NMDnsIPConfigData *a = c_list_entry (a_lst, NMDnsIPConfigData, ip_config_lst);
	const NMDnsIPConfigData *b = c_list_entry (b_lst, NMDnsIPConfigData, ip_config_lst);
320 321

	/* Configurations with lower priority value first */
322 323
	NM_CMP_DIRECT (nm_ip_config_get_dns_priority (a->ip_config),
	               nm_ip_config_get_dns_priority (b->ip_config));
324

325 326
	/* Sort according to type (descendingly) */
	NM_CMP_FIELD (b, a, ip_config_type);
327 328 329 330

	return 0;
}

331 332 333 334 335 336 337 338 339 340 341
static CList *
_ip_config_lst_head (NMDnsManager *self)
{
	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);

	if (priv->ip_config_lst_need_sort) {
		priv->ip_config_lst_need_sort = FALSE;
		c_list_sort (&priv->ip_config_lst_head, _ip_config_lst_cmp, NULL);
	}

	return &priv->ip_config_lst_head;
342 343
}

344
/*****************************************************************************/
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359

gboolean
nm_dns_manager_has_systemd_resolved (NMDnsManager *self)
{
	NMDnsManagerPrivate *priv;

	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);

	priv = NM_DNS_MANAGER_GET_PRIVATE (self);

	return    priv->sd_resolve_plugin
	       || NM_IS_DNS_SYSTEMD_RESOLVED (priv->plugin);
}

/*****************************************************************************/
360

361
static void
362
add_string_item (GPtrArray *array, const char *str, gboolean dup)
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
{
	int i;

	g_return_if_fail (array != NULL);
	g_return_if_fail (str != NULL);

	/* Check for dupes before adding */
	for (i = 0; i < array->len; i++) {
		const char *candidate = g_ptr_array_index (array, i);

		if (candidate && !strcmp (candidate, str))
			return;
	}

	/* No dupes, add the new item */
378
	g_ptr_array_add (array, dup ? g_strdup (str): (gpointer) str);
379 380
}

381
static void
382
add_dns_option_item (GPtrArray *array, const char *str)
383 384 385 386 387
{
	if (_nm_utils_dns_option_find_idx (array, str) < 0)
		g_ptr_array_add (array, g_strdup (str));
}

388
static void
389 390
add_dns_domains (GPtrArray *array, const NMIPConfig *ip_config,
                 gboolean include_routing, gboolean dup)
391 392 393 394 395 396 397 398 399
{
	guint num_domains, num_searches, i;
	const char *str;

	num_domains = nm_ip_config_get_num_domains (ip_config);
	num_searches = nm_ip_config_get_num_searches (ip_config);

	for (i = 0; i < num_searches; i++) {
		str = nm_ip_config_get_search (ip_config, i);
400 401 402 403 404
		if (!include_routing && domain_is_routing (str))
			continue;
		if (!domain_is_valid (nm_utils_parse_dns_domain (str, NULL), FALSE))
			continue;
		add_string_item (array, str, dup);
405 406 407 408
	}
	if (num_domains > 1 || !num_searches) {
		for (i = 0; i < num_domains; i++) {
			str = nm_ip_config_get_domain (ip_config, i);
409 410 411 412 413
			if (!include_routing && domain_is_routing (str))
				continue;
			if (!domain_is_valid (nm_utils_parse_dns_domain (str, NULL), FALSE))
				continue;
			add_string_item (array, str, dup);
414 415 416 417
		}
	}
}

418
static void
419
merge_one_ip_config (NMResolvConfData *rc,
420 421
                     int ifindex,
                     const NMIPConfig *ip_config)
422
{
423
	int addr_family;
424
	guint num, i;
425
	char buf[NM_UTILS_INET_ADDRSTRLEN + 50];
426

427
	addr_family = nm_ip_config_get_addr_family (ip_config);
428

429
	nm_assert_addr_family (addr_family);
430 431
	nm_assert (ifindex > 0);
	nm_assert (ifindex == nm_ip_config_get_ifindex (ip_config));
432

433
	num = nm_ip_config_get_num_nameservers (ip_config);
434
	for (i = 0; i < num; i++) {
435
		const NMIPAddr *addr;
436

437
		addr = nm_ip_config_get_nameserver (ip_config, i);
438 439 440 441
		if (addr_family == AF_INET)
			nm_utils_inet_ntop (addr_family, addr, buf);
		else if (IN6_IS_ADDR_V4MAPPED (addr))
			nm_utils_inet4_ntop (addr->addr6.s6_addr32[3], buf);
442
		else {
443
			nm_utils_inet6_ntop (&addr->addr6, buf);
444
			if (IN6_IS_ADDR_LINKLOCAL (addr)) {
445 446 447 448 449 450 451
				const char *ifname;

				ifname = nm_platform_link_get_name (NM_PLATFORM_GET, ifindex);
				if (ifname) {
					g_strlcat (buf, "%", sizeof (buf));
					g_strlcat (buf, ifname, sizeof (buf));
				}
452
			}
453
		}
454

455
		add_string_item (rc->nameservers, buf, TRUE);
456 457
	}

458
	add_dns_domains (rc->searches, ip_config, FALSE, TRUE);
459

460
	num = nm_ip_config_get_num_dns_options (ip_config);
461
	for (i = 0; i < num; i++) {
462
		add_dns_option_item (rc->options,
463
		                     nm_ip_config_get_dns_option (ip_config, i));
464
	}
465

466
	if (addr_family == AF_INET) {
467
		const NMIP4Config *ip4_config = (const NMIP4Config *) ip_config;
468 469

		/* NIS stuff */
470
		num = nm_ip4_config_get_num_nis_servers (ip4_config);
471 472
		for (i = 0; i < num; i++) {
			add_string_item (rc->nis_servers,
473 474
			                 nm_utils_inet4_ntop (nm_ip4_config_get_nis_server (ip4_config, i), buf),
			                 TRUE);
475 476
		}

477
		if (nm_ip4_config_get_nis_domain (ip4_config)) {
478 479
			/* FIXME: handle multiple domains */
			if (!rc->nis_domain)
480
				rc->nis_domain = nm_ip4_config_get_nis_domain (ip4_config);
481 482
		}
	}
483 484
}

485
static GPid
486
run_netconfig (NMDnsManager *self, GError **error, int *stdin_fd)
487
{
488
	char *argv[5];
489
	gs_free char *tmp = NULL;
490
	GPid pid = -1;
491

492
	argv[0] = NETCONFIG_PATH;
493 494 495 496
	argv[1] = "modify";
	argv[2] = "--service";
	argv[3] = "NetworkManager";
	argv[4] = NULL;
497

498 499
	_LOGD ("spawning '%s'",
	       (tmp = g_strjoinv (" ", argv)));
500

501
	if (!g_spawn_async_with_pipes (NULL, argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL,
502
	                               NULL, &pid, stdin_fd, NULL, NULL, error))
503
		return -1;
504

505
	return pid;
506 507 508
}

static void
509
netconfig_construct_str (NMDnsManager *self, GString *str, const char *key, const char *value)
510
{
511 512 513 514 515 516 517 518 519 520 521
	if (value) {
		_LOGD ("writing to netconfig: %s='%s'", key, value);
		g_string_append_printf (str, "%s='%s'\n", key, value);
	}
}

static void
netconfig_construct_strv (NMDnsManager *self, GString *str, const char *key, const char *const*values)
{
	if (values) {
		gs_free char *value = NULL;
522

523 524 525
		value = g_strjoinv (" ", (char **) values);
		netconfig_construct_str (self, str, key, value);
	}
526 527
}

528
static SpawnResult
529
dispatch_netconfig (NMDnsManager *self,
530 531
                    const char *const*searches,
                    const char *const*nameservers,
532
                    const char *nis_domain,
533
                    const char *const*nis_servers,
534
                    GError **error)
535
{
536
	GPid pid;
537
	int fd;
538
	int errsv;
539
	int status;
540 541
	gssize l;
	nm_auto_free_gstring GString *str = NULL;
542

543
	pid = run_netconfig (self, error, &fd);
544
	if (pid <= 0)
545
		return SR_NOTFOUND;
546

547 548
	str = g_string_new ("");

549 550 551
	/* NM is writing already-merged DNS information to netconfig, so it
	 * does not apply to a specific network interface.
	 */
552 553 554 555 556 557 558 559 560 561 562
	netconfig_construct_str (self, str, "INTERFACE", "NetworkManager");
	netconfig_construct_strv (self, str, "DNSSEARCH", searches);
	netconfig_construct_strv (self, str, "DNSSERVERS", nameservers);
	netconfig_construct_str (self, str, "NISDOMAIN", nis_domain);
	netconfig_construct_strv (self, str, "NISSERVERS", nis_servers);

again:
	l = write (fd, str->str, str->len);
	if (l == -1)  {
		if (errno == EINTR)
			goto again;
563 564
	}

Beniamino Galvani's avatar
Beniamino Galvani committed
565
	nm_close (fd);
566

567
	/* Wait until the process exits */
568
	if (!nm_utils_kill_child_sync (pid, 0, LOGD_DNS, "netconfig", &status, 1000, 0)) {
569
		errsv = errno;
570 571 572
		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
		             "Error waiting for netconfig to exit: %s",
		             strerror (errsv));
573
		return SR_ERROR;
BinLi's avatar
BinLi committed
574
	}
575 576 577 578 579
	if (!WIFEXITED (status) || WEXITSTATUS (status) != EXIT_SUCCESS) {
		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
		             "Error calling netconfig: %s %d",
		             WIFEXITED (status) ? "exited with status" : (WIFSIGNALED (status) ? "exited with signal" : "exited with unknown reason"),
		             WIFEXITED (status) ? WEXITSTATUS (status) : (WIFSIGNALED (status) ? WTERMSIG (status) : status));
580
		return SR_ERROR;
581
	}
582
	return SR_SUCCESS;
583 584
}

585
static char *
586 587 588
create_resolv_conf (const char *const*searches,
                    const char *const*nameservers,
                    const char *const*options)
589
{
590
	GString *str;
591
	gsize i;
592

593 594 595
	str = g_string_new_len (NULL, 245);

	g_string_append (str, "# Generated by NetworkManager\n");
596

597
	if (searches && searches[0]) {
598 599
		gsize search_base_idx;

600
		g_string_append (str, "search");
601 602
		search_base_idx = str->len;

603
		for (i = 0; searches[i]; i++) {
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
			const char *s = searches[i];
			gsize l = strlen (s);

			if (   l == 0
			    || NM_STRCHAR_ANY (s, ch, NM_IN_SET (ch, ' ', '\t', '\n'))) {
				/* there should be no such characters in the search entry. Also,
				 * because glibc parser would treat them as line/word separator.
				 *
				 * Skip the value silently. */
				continue;
			}

			if (search_base_idx > 0) {
				if (str->len - search_base_idx + 1 + l > 254) {
					/* this entry crosses the 256 character boundery. Older glibc versions
					 * would truncate the entry at this point.
					 *
					 * Fill the line with spaces to cross the 256 char boundary and continue
					 * afterwards. This way, the truncation happens between two search entries. */
					while (str->len - search_base_idx < 257)
						g_string_append_c (str, ' ');
					search_base_idx = 0;
				}
			}

629
			g_string_append_c (str, ' ');
630
			g_string_append_len (str, s, l);
631 632
		}
		g_string_append_c (str, '\n');
633 634
	}

635 636
	if (nameservers && nameservers[0]) {
		for (i = 0; nameservers[i]; i++) {
637
			if (i == 3) {
638 639
				g_string_append (str, "# NOTE: the libc resolver may not support more than 3 nameservers.\n");
				g_string_append (str, "# The nameservers listed below may not be recognized.\n");
640 641 642 643 644 645 646
			}
			g_string_append (str, "nameserver ");
			g_string_append (str, nameservers[i]);
			g_string_append_c (str, '\n');
		}
	}

647 648 649 650 651 652 653 654 655 656
	if (options && options[0]) {
		g_string_append (str, "options");
		for (i = 0; options[i]; i++) {
			g_string_append_c (str, ' ');
			g_string_append (str, options[i]);
		}
		g_string_append_c (str, '\n');
	}

	return g_string_free (str, FALSE);
657 658
}

659 660 661 662 663 664 665 666
char *
nmtst_dns_create_resolv_conf (const char *const*searches,
                              const char *const*nameservers,
                              const char *const*options)
{
	return create_resolv_conf (searches, nameservers, options);
}

667
static gboolean
Thomas Haller's avatar
Thomas Haller committed
668 669 670
write_resolv_conf_contents (FILE *f,
                            const char *content,
                            GError **error)
671
{
672 673
	int errsv;

674
	if (fprintf (f, "%s", content) < 0) {
675
		errsv = errno;
676 677 678
		g_set_error (error,
		             NM_MANAGER_ERROR,
		             NM_MANAGER_ERROR_FAILED,
679
		             "Could not write " _PATH_RESCONF ": %s",
680 681
		             g_strerror (errsv));
		errno = errsv;
682
		return FALSE;
683
	}
684

685
	return TRUE;
686 687
}

Thomas Haller's avatar
Thomas Haller committed
688 689
static gboolean
write_resolv_conf (FILE *f,
690 691 692
                   const char *const*searches,
                   const char *const*nameservers,
                   const char *const*options,
Thomas Haller's avatar
Thomas Haller committed
693 694 695 696 697 698 699 700
                   GError **error)
{
	gs_free char *content = NULL;

	content = create_resolv_conf (searches, nameservers, options);
	return write_resolv_conf_contents (f, content, error);
}

701
static SpawnResult
702 703
dispatch_resolvconf (NMDnsManager *self,
                     char **searches,
704
                     char **nameservers,
705
                     char **options,
706
                     GError **error)
707
{
708
	gs_free char *cmd = NULL;
709
	FILE *f;
710
	gboolean success = FALSE;
711 712
	int errsv;
	int err;
713 714
	char *argv[] = { RESOLVCONF_PATH, "-d", "NetworkManager", NULL };
	int status;
715

716 717 718 719 720
	if (!g_file_test (RESOLVCONF_PATH, G_FILE_TEST_IS_EXECUTABLE)) {
		g_set_error_literal (error,
		                     NM_MANAGER_ERROR,
		                     NM_MANAGER_ERROR_FAILED,
		                     RESOLVCONF_PATH " is not executable");
721
		return SR_NOTFOUND;
722
	}
723

724
	if (!searches && !nameservers) {
725
		_LOGI ("Removing DNS information from %s", RESOLVCONF_PATH);
726

727 728 729 730 731 732 733 734 735
		if (!g_spawn_sync ("/", argv, NULL, 0, NULL, NULL, NULL, NULL, &status, error))
			return SR_ERROR;

		if (status != 0) {
			g_set_error (error,
			             NM_MANAGER_ERROR,
			             NM_MANAGER_ERROR_FAILED,
			             "%s returned error code",
			             RESOLVCONF_PATH);
736
			return SR_ERROR;
737
		}
738 739

		return SR_SUCCESS;
740 741
	}

742
	_LOGI ("Writing DNS information to %s", RESOLVCONF_PATH);
743

744 745
	cmd = g_strconcat (RESOLVCONF_PATH, " -a ", "NetworkManager", NULL);
	if ((f = popen (cmd, "w")) == NULL) {
746
		errsv = errno;
747 748 749
		g_set_error (error,
		             NM_MANAGER_ERROR,
		             NM_MANAGER_ERROR_FAILED,
750
		             "Could not write to %s: %s",
751
		             RESOLVCONF_PATH,
752
		             g_strerror (errsv));
753 754 755
		return SR_ERROR;
	}

756 757 758 759 760
	success = write_resolv_conf (f,
	                             NM_CAST_STRV_CC (searches),
	                             NM_CAST_STRV_CC (nameservers),
	                             NM_CAST_STRV_CC (options),
	                             error);
761 762
	err = pclose (f);
	if (err < 0) {
763
		errsv = errno;
764
		g_clear_error (error);
765 766
		g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
		             "Failed to close pipe to resolvconf: %d", errsv);
767 768 769 770 771 772 773 774 775 776
		return SR_ERROR;
	} else if (err > 0) {
		_LOGW ("resolvconf failed with status %d", err);
		g_clear_error (error);
		g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
		             "resolvconf failed with status %d", err);
		return SR_ERROR;
	}

	return success ? SR_SUCCESS : SR_ERROR;
777 778
}

779 780 781 782 783 784 785 786 787 788 789 790 791 792
static const char *
_read_link_cached (const char *path, gboolean *is_cached, char **cached)
{
	nm_assert (is_cached);
	nm_assert (cached);

	if (*is_cached)
		return *cached;

	nm_assert (!*cached);
	*is_cached = TRUE;
	return (*cached = g_file_read_link (path, NULL));
}

793 794 795 796 797 798 799 800
#define MY_RESOLV_CONF             NMRUNDIR"/resolv.conf"
#define MY_RESOLV_CONF_TMP         MY_RESOLV_CONF".tmp"
#define RESOLV_CONF_TMP            "/etc/.resolv.conf.NetworkManager"

#define NO_STUB_RESOLV_CONF        NMRUNDIR "/no-stub-resolv.conf"

static void
update_resolv_conf_no_stub (NMDnsManager *self,
801 802 803
                            const char *const*searches,
                            const char *const*nameservers,
                            const char *const*options)
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822
{
	gs_free char *content = NULL;
	GError *local = NULL;

	content = create_resolv_conf (searches, nameservers, options);

	if (!g_file_set_contents (NO_STUB_RESOLV_CONF,
	                          content,
	                          -1,
	                          &local)) {
		_LOGD ("update-resolv-no-stub: failure to write file: %s",
		       local->message);
		g_error_free (local);
		return;
	}

	_LOGT ("update-resolv-no-stub: '%s' successfully written",
	       NO_STUB_RESOLV_CONF);
}
823

824
static SpawnResult
825
update_resolv_conf (NMDnsManager *self,
826 827 828
                    const char *const*searches,
                    const char *const*nameservers,
                    const char *const*options,
829
                    GError **error,
Thomas Haller's avatar
Thomas Haller committed
830
                    NMDnsManagerResolvConfManager rc_manager)
831 832
{
	FILE *f;
833
	gboolean success;
Thomas Haller's avatar
Thomas Haller committed
834 835
	gs_free char *content = NULL;
	SpawnResult write_file_result = SR_SUCCESS;
836
	int errsv;
837 838
	gboolean resconf_link_cached = FALSE;
	gs_free char *resconf_link = NULL;
839

Thomas Haller's avatar
Thomas Haller committed
840 841
	content = create_resolv_conf (searches, nameservers, options);

842 843 844
	if (   rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE
	    || (   rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK
	        && !_read_link_cached (_PATH_RESCONF, &resconf_link_cached, &resconf_link))) {
845
		gs_free char *rc_path_syml = NULL;
846 847
		nm_auto_free char *rc_path_real = NULL;
		const char *rc_path = _PATH_RESCONF;
848 849
		GError *local = NULL;

850
		if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) {
851
			rc_path_real = realpath (_PATH_RESCONF, NULL);
852 853
			if (rc_path_real)
				rc_path = rc_path_real;
854 855 856 857 858 859 860 861 862 863 864 865
			else {
				/* realpath did not resolve a path-name. That either means,
				 * _PATH_RESCONF:
				 *   - does not exist
				 *   - is a plain file
				 *   - is a dangling symlink
				 *
				 * Handle the case, where it is a dangling symlink... */
				rc_path_syml = nm_utils_read_link_absolute (_PATH_RESCONF, NULL);
				if (rc_path_syml)
					rc_path = rc_path_syml;
			}
866
		}
867

Thomas Haller's avatar
Thomas Haller committed
868 869 870
		/* we first write to /etc/resolv.conf directly. If that fails,
		 * we still continue to write to runstatedir but remember the
		 * error. */
871
		if (!g_file_set_contents (rc_path, content, -1, &local)) {
872
			_LOGT ("update-resolv-conf: write to %s failed (rc-manager=%s, %s)",
873
			       rc_path, _rc_manager_to_string (rc_manager), local->message);
Thomas Haller's avatar
Thomas Haller committed
874
			write_file_result = SR_ERROR;
875
			g_propagate_error (error, local);
Thomas Haller's avatar
Thomas Haller committed
876
			error = NULL;
877
		} else {
878
			_LOGT ("update-resolv-conf: write to %s succeeded (rc-manager=%s)",