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 <errno.h>
27
#include <fcntl.h>
Dan Winship's avatar
Dan Winship committed
28 29
#include <resolv.h>
#include <stdlib.h>
30
#include <sys/stat.h>
31
#include <sys/ioctl.h>
32 33
#include <sys/types.h>
#include <sys/wait.h>
34 35 36
#include <unistd.h>

#include <linux/fs.h>
37

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

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

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

58
#define HASH_LEN   NM_UTILS_CHECKSUM_LENGTH_SHA1
59

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

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

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

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

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;

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

90 91 92 93 94 95
enum {
	CONFIG_CHANGED,

	LAST_SIGNAL
};

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

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

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

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

	bool ip_config_lst_need_sort:1;

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

117
	char *hostname;
118
	guint updates_queue;
119

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

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

128 129
	NMConfig *config;

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

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

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

146
G_DEFINE_TYPE (NMDnsManager, nm_dns_manager, NM_TYPE_DBUS_OBJECT)
147

148
#define NM_DNS_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDnsManager, NM_IS_DNS_MANAGER)
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 175
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

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

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

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

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

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

200
/*****************************************************************************/
201

202 203
NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_rc_manager_to_string, NMDnsManagerResolvConfManager,
	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
204
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN,        "unknown"),
205 206
	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"),
207 208 209 210
	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"),
211 212
);

213 214
NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_config_type_to_string, NMDnsIPConfigType,
	NM_UTILS_LOOKUP_DEFAULT_WARN ("<unknown>"),
215
	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_REMOVED, "removed"),
216 217 218 219 220
	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"),
);

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

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)
233
{
234 235
	nm_assert (ip_data);
	_ASSERT_config_data (ip_data->data);
236
	nm_assert (NM_IS_IP_CONFIG (ip_data->ip_config, AF_UNSPEC));
237 238 239
	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));
}
240

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

248
	_ASSERT_config_data (data);
249
	nm_assert (NM_IS_IP_CONFIG (ip_config, AF_UNSPEC));
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
	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;
267 268
}

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

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

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

280 281 282 283 284 285
	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);
286 287
}

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

294 295 296 297
	_ASSERT_config_data (data);

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

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

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

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

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

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

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

	return 0;
}

332 333 334 335 336 337 338 339 340 341 342
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;
343 344
}

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

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);
}

/*****************************************************************************/
361

362
static void
363
add_string_item (GPtrArray *array, const char *str, gboolean dup)
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
{
	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 */
379
	g_ptr_array_add (array, dup ? g_strdup (str): (gpointer) str);
380 381
}

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

389
static void
390 391
add_dns_domains (GPtrArray *array, const NMIPConfig *ip_config,
                 gboolean include_routing, gboolean dup)
392 393 394 395 396 397 398 399 400
{
	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);
401 402 403 404 405
		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);
406 407 408 409
	}
	if (num_domains > 1 || !num_searches) {
		for (i = 0; i < num_domains; i++) {
			str = nm_ip_config_get_domain (ip_config, i);
410 411 412 413 414
			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);
415 416 417 418
		}
	}
}

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

428
	addr_family = nm_ip_config_get_addr_family (ip_config);
429

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

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

438
		addr = nm_ip_config_get_nameserver (ip_config, i);
439 440 441 442
		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);
443
		else {
444
			nm_utils_inet6_ntop (&addr->addr6, buf);
445
			if (IN6_IS_ADDR_LINKLOCAL (addr)) {
446 447 448 449 450 451 452
				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));
				}
453
			}
454
		}
455

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

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

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

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

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

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

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

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

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

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

506
	return pid;
507 508 509
}

static void
510
netconfig_construct_str (NMDnsManager *self, GString *str, const char *key, const char *value)
511
{
512 513 514 515 516 517 518 519 520 521 522
	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;
523

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

529
static SpawnResult
530
dispatch_netconfig (NMDnsManager *self,
531 532
                    const char *const*searches,
                    const char *const*nameservers,
533
                    const char *nis_domain,
534
                    const char *const*nis_servers,
535
                    GError **error)
536
{
537
	GPid pid;
538
	int fd;
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 569
	if (!nm_utils_kill_child_sync (pid, 0, LOGD_DNS, "netconfig", &status, 1000, 0)) {
		int errsv = errno;
570

571 572 573
		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
		             "Error waiting for netconfig to exit: %s",
		             strerror (errsv));
574
		return SR_ERROR;
BinLi's avatar
BinLi committed
575
	}
576 577 578 579 580
	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));
581
		return SR_ERROR;
582
	}
583
	return SR_SUCCESS;
584 585
}

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

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

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

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

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

604
		for (i = 0; searches[i]; i++) {
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
			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;
				}
			}

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

636 637
	if (nameservers && nameservers[0]) {
		for (i = 0; nameservers[i]; i++) {
638
			if (i == 3) {
639 640
				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");
641 642 643 644 645 646 647
			}
			g_string_append (str, "nameserver ");
			g_string_append (str, nameservers[i]);
			g_string_append_c (str, '\n');
		}
	}

648 649 650 651 652 653 654 655 656 657
	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);
658 659
}

660 661 662 663 664 665 666 667
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);
}

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

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

686
	return TRUE;
687 688
}

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

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

702
static SpawnResult
703 704
dispatch_resolvconf (NMDnsManager *self,
                     char **searches,
705
                     char **nameservers,
706
                     char **options,
707
                     GError **error)
708
{
709
	gs_free char *cmd = NULL;
710
	FILE *f;
711
	gboolean success = FALSE;
712
	int errnosv, 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 746 747 748
	cmd = g_strconcat (RESOLVCONF_PATH, " -a ", "NetworkManager", NULL);
	if ((f = popen (cmd, "w")) == NULL) {
		g_set_error (error,
		             NM_MANAGER_ERROR,
		             NM_MANAGER_ERROR_FAILED,
749
		             "Could not write to %s: %s",
750 751 752 753 754
		             RESOLVCONF_PATH,
		             g_strerror (errno));
		return SR_ERROR;
	}

755 756 757 758 759
	success = write_resolv_conf (f,
	                             NM_CAST_STRV_CC (searches),
	                             NM_CAST_STRV_CC (nameservers),
	                             NM_CAST_STRV_CC (options),
	                             error);
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775
	err = pclose (f);
	if (err < 0) {
		errnosv = errno;
		g_clear_error (error);
		g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errnosv),
		             "Failed to close pipe to resolvconf: %d", errnosv);
		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;
776 777
}

778 779 780 781 782 783 784 785 786 787 788 789 790 791
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));
}

792 793 794 795 796 797 798 799
#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,
800 801 802
                            const char *const*searches,
                            const char *const*nameservers,
                            const char *const*options)
803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
{
	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);
}
822

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

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

841 842 843
	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))) {
844
		gs_free char *rc_path_syml = NULL;
845 846
		nm_auto_free char *rc_path_real = NULL;
		const char *rc_path = _PATH_RESCONF;
847 848
		GError *local = NULL;

849
		if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) {
850
			rc_path_real = realpath (_PATH_RESCONF, NULL);
851 852
			if (rc_path_real)
				rc_path = rc_path_real;
853 854 855 856 857 858 859 860 861 862 863 864
			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;
			}
865
		}
866

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

882
	if ((f = fopen (MY_RESOLV_CONF_TMP, "we")) == NULL) {
883
		errsv = errno;
884 885 886
		g_set_error (error,
		             NM_MANAGER_ERROR,