nms-ifcfg-rh-reader.c 174 KB
Newer Older
1
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 3 4 5 6 7 8 9 10 11 12 13
/* NetworkManager system settings service
 *
 * 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.
 *
 * 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.
 *
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
 *
18
 * Copyright 2008 - 2017 Red Hat, Inc.
19 20
 */

21
#include "nm-default.h"
22

Thomas Haller's avatar
Thomas Haller committed
23
#include "nms-ifcfg-rh-reader.h"
24

25 26 27 28 29
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
30
#include <sys/wait.h>
31
#include <sys/inotify.h>
32 33 34
#include <errno.h>
#include <sys/ioctl.h>
#include <unistd.h>
35

36 37 38 39 40 41 42 43
#include "nm-connection.h"
#include "nm-dbus-interface.h"
#include "nm-setting-connection.h"
#include "nm-setting-ip4-config.h"
#include "nm-setting-vlan.h"
#include "nm-setting-ip6-config.h"
#include "nm-setting-wired.h"
#include "nm-setting-wireless.h"
44
#include "nm-setting-ethtool.h"
45 46 47 48 49 50 51
#include "nm-setting-8021x.h"
#include "nm-setting-bond.h"
#include "nm-setting-team.h"
#include "nm-setting-team-port.h"
#include "nm-setting-bridge.h"
#include "nm-setting-bridge-port.h"
#include "nm-setting-dcb.h"
52
#include "nm-setting-user.h"
53
#include "nm-setting-proxy.h"
54
#include "nm-setting-generic.h"
55
#include "nm-core-internal.h"
56
#include "nm-utils.h"
57
#include "nm-ethtool-utils.h"
58

59
#include "platform/nm-platform.h"
60
#include "NetworkManagerUtils.h"
61

Thomas Haller's avatar
Thomas Haller committed
62 63
#include "nms-ifcfg-rh-common.h"
#include "nms-ifcfg-rh-utils.h"
64
#include "shvar.h"
65

66 67 68 69 70 71
/*****************************************************************************/

#define _NMLOG_DOMAIN      LOGD_SETTINGS
#define _NMLOG_PREFIX_NAME "ifcfg-rh"
#define _NMLOG(level, ...) \
    G_STMT_START { \
72
        nm_log ((level), (_NMLOG_DOMAIN), NULL, NULL, \
73 74 75 76 77 78 79 80
                "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
                _NMLOG_PREFIX_NAME": " \
                _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
    } G_STMT_END

#define PARSE_WARNING(...) _LOGW ("%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), "    " _NM_UTILS_MACRO_REST(__VA_ARGS__))

/*****************************************************************************/
81

82 83 84 85 86 87
static void
check_if_bond_slave (shvarFile *ifcfg,
                     NMSettingConnection *s_con)
{
	gs_free char *value = NULL;
	const char *v;
88
	const char *master;
89 90 91 92 93 94

	v = svGetValueStr (ifcfg, "MASTER_UUID", &value);
	if (!v)
		v = svGetValueStr (ifcfg, "MASTER", &value);

	if (v) {
95 96 97 98 99 100 101
		master = nm_setting_connection_get_master (s_con);
		if (master) {
			PARSE_WARNING ("Already configured as slave of %s. Ignoring MASTER{_UUID}=\"%s\"",
			               master, v);
			return;
		}

102 103 104 105 106 107 108 109 110 111 112
		g_object_set (s_con,
		              NM_SETTING_CONNECTION_MASTER, v,
		              NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_BOND_SETTING_NAME,
		              NULL);
	}

	/* We should be checking for SLAVE=yes as well, but NM used to not set that,
	 * so for backward-compatibility, we don't check.
	 */
}

113
static void
114 115 116 117 118
check_if_team_slave (shvarFile *ifcfg,
                     NMSettingConnection *s_con)
{
	gs_free char *value = NULL;
	const char *v;
119
	const char *master;
120 121 122 123 124

	v = svGetValueStr (ifcfg, "TEAM_MASTER_UUID", &value);
	if (!v)
		v = svGetValueStr (ifcfg, "TEAM_MASTER", &value);
	if (!v)
125 126 127 128 129 130 131 132
		return;

	master = nm_setting_connection_get_master (s_con);
	if (master) {
		PARSE_WARNING ("Already configured as slave of %s. Ignoring TEAM_MASTER{_UUID}=\"%s\"",
		               master, v);
		return;
	}
133 134 135 136 137 138 139

	g_object_set (s_con,
	              NM_SETTING_CONNECTION_MASTER, v,
	              NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_TEAM_SETTING_NAME,
	              NULL);
}

140
static char *
141 142 143 144
make_connection_name (shvarFile *ifcfg,
                      const char *ifcfg_name,
                      const char *suggested,
                      const char *prefix)
145
{
146
	char *full_name = NULL, *name;
147 148

	/* If the ifcfg file already has a NAME, always use that */
149
	name = svGetValueStr_cp (ifcfg, "NAME");
150
	if (name)
151 152 153 154
		return name;

	/* Otherwise construct a new NAME */
	if (!prefix)
155
		prefix = "System";
156 157 158 159 160

	/* For cosmetic reasons, if the suggested name is the same as
	 * the ifcfg files name, don't use it.  Mainly for wifi so that
	 * the SSID is shown in the connection ID instead of just "wlan0".
	 */
161 162 163 164
	if (suggested && strcmp (ifcfg_name, suggested))
		full_name = g_strdup_printf ("%s %s (%s)", prefix, suggested, ifcfg_name);
	else
		full_name = g_strdup_printf ("%s %s", prefix, ifcfg_name);
165 166 167 168

	return full_name;
}

169 170 171 172
static NMSetting *
make_connection_setting (const char *file,
                         shvarFile *ifcfg,
                         const char *type,
173 174
                         const char *suggested,
                         const char *prefix)
175 176
{
	NMSettingConnection *s_con;
177
	NMSettingConnectionLldp lldp;
178
	const char *ifcfg_name = NULL;
179 180 181 182 183
	char *new_id;
	const char *uuid;
	gs_free char *uuid_free = NULL;
	gs_free char *value = NULL;
	const char *v;
184
	gs_free char *stable_id = NULL;
185
	const char *const *iter;
186
	int vint64, i_val;
187

188
	ifcfg_name = utils_get_ifcfg_name (file, TRUE);
189 190 191 192
	if (!ifcfg_name)
		return NULL;

	s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
193

194 195
	new_id = make_connection_name (ifcfg, ifcfg_name, suggested, prefix);
	g_object_set (s_con, NM_SETTING_CONNECTION_ID, new_id, NULL);
196
	g_free (new_id);
197

198
	/* Try for a UUID key before falling back to hashing the file name */
199 200 201 202 203
	uuid = svGetValueStr (ifcfg, "UUID", &uuid_free);
	if (!uuid) {
		uuid_free = nm_utils_uuid_generate_from_string (svFileGetName (ifcfg), -1, NM_UTILS_UUID_TYPE_LEGACY, NULL);
		uuid = uuid_free;
	}
204

205 206 207
	g_object_set (s_con,
	              NM_SETTING_CONNECTION_TYPE, type,
	              NM_SETTING_CONNECTION_UUID, uuid,
208
	              NM_SETTING_CONNECTION_STABLE_ID, svGetValue (ifcfg, "STABLE_ID", &stable_id),
209
	              NULL);
210

211 212
	v = svGetValueStr (ifcfg, "DEVICE", &value);
	if (v) {
213 214
		GError *error = NULL;

215
		if (nm_utils_is_valid_iface_name (v, &error)) {
216
			g_object_set (s_con,
217
			              NM_SETTING_CONNECTION_INTERFACE_NAME, v,
218
			              NULL);
219
		} else {
220
			PARSE_WARNING ("invalid DEVICE name '%s': %s", v, error->message);
221 222
			g_error_free (error);
		}
223 224
	}

225 226 227
	nm_clear_g_free (&value);
	v = svGetValueStr (ifcfg, "LLDP", &value);
	if (nm_streq0 (v, "rx"))
228 229
		lldp = NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
	else
230
		lldp = svParseBoolean (v, NM_SETTING_CONNECTION_LLDP_DEFAULT);
231

232
	/* Missing ONBOOT is treated as "ONBOOT=true" by the old network service */
233 234
	g_object_set (s_con,
	              NM_SETTING_CONNECTION_AUTOCONNECT,
235
	              svGetValueBoolean (ifcfg, "ONBOOT", TRUE),
236
	              NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY,
237
	              (int) svGetValueInt64 (ifcfg, "AUTOCONNECT_PRIORITY", 10,
238 239 240
	                                      NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MIN,
	                                      NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MAX,
	                                      NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT),
241
	              NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES,
242
	              (int) svGetValueInt64 (ifcfg, "AUTOCONNECT_RETRIES", 10,
243
	                                      -1, G_MAXINT32, -1),
244 245 246
	              NM_SETTING_CONNECTION_MULTI_CONNECT,
	              (gint) svGetValueInt64 (ifcfg, "MULTI_CONNECT", 10,
	                                      G_MININT32, G_MAXINT32, NM_CONNECTION_MULTI_CONNECT_DEFAULT),
247
	              NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES,
248
	              svGetValueBoolean (ifcfg, "AUTOCONNECT_SLAVES", NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT),
249
	              NM_SETTING_CONNECTION_LLDP, lldp,
Dan Williams's avatar
Dan Williams committed
250
	              NULL);
251

252 253 254 255
	nm_clear_g_free (&value);
	v = svGetValueStr (ifcfg, "USERS", &value);
	if (v) {
		gs_free const char **items = NULL;
256

257
		items = nm_utils_strsplit_set (v, " ");
258
		for (iter = items; iter && *iter; iter++) {
259 260
			if (!nm_setting_connection_add_permission (s_con, "user", *iter, NULL))
				PARSE_WARNING ("invalid USERS item '%s'", *iter);
261 262 263
		}
	}

264 265 266
	nm_clear_g_free (&value);
	v = svGetValueStr (ifcfg, "ZONE", &value);
	g_object_set (s_con, NM_SETTING_CONNECTION_ZONE, v, NULL);
267

268 269 270 271
	nm_clear_g_free (&value);
	v = svGetValueStr (ifcfg, "SECONDARY_UUIDS", &value);
	if (v) {
		gs_free const char **items = NULL;
272

273
		items = nm_utils_strsplit_set (v, " \t");
274
		for (iter = items; iter && *iter; iter++) {
275 276
			if (!nm_setting_connection_add_secondary (s_con, *iter))
				PARSE_WARNING ("secondary connection UUID '%s' already added", *iter);
277 278 279
		}
	}

280 281 282 283 284
	nm_clear_g_free (&value);
	v = svGetValueStr (ifcfg, "BRIDGE_UUID", &value);
	if (!v)
		v = svGetValueStr (ifcfg, "BRIDGE", &value);
	if (v) {
285
		const char *old_value;
286

287
		if ((old_value = nm_setting_connection_get_master (s_con))) {
288
			PARSE_WARNING ("Already configured as slave of %s. Ignoring BRIDGE=\"%s\"",
289
			               old_value, v);
290
		} else {
291
			g_object_set (s_con, NM_SETTING_CONNECTION_MASTER, v, NULL);
292 293
			g_object_set (s_con, NM_SETTING_CONNECTION_SLAVE_TYPE,
			              NM_SETTING_BRIDGE_SETTING_NAME, NULL);
294 295 296
		}
	}

297 298 299
	check_if_bond_slave (ifcfg, s_con);
	check_if_team_slave (ifcfg, s_con);

300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
	nm_clear_g_free (&value);
	v = svGetValueStr (ifcfg, "OVS_PORT_UUID", &value);
	if (!v)
		v = svGetValueStr (ifcfg, "OVS_PORT", &value);
	if (v) {
		const char *old_value;

		if ((old_value = nm_setting_connection_get_master (s_con))) {
			PARSE_WARNING ("Already configured as slave of %s. Ignoring OVS_PORT=\"%s\"",
			               old_value, v);
		} else {
			g_object_set (s_con, NM_SETTING_CONNECTION_MASTER, v, NULL);
			g_object_set (s_con, NM_SETTING_CONNECTION_SLAVE_TYPE,
			              NM_SETTING_OVS_PORT_SETTING_NAME, NULL);
		}
	}

317 318 319
	nm_clear_g_free (&value);
	v = svGetValueStr (ifcfg, "GATEWAY_PING_TIMEOUT", &value);
	if (v) {
320
		gint64 tmp;
321

322
		tmp = _nm_utils_ascii_str_to_int64 (v, 10, 0, G_MAXINT32 - 1, -1);
323 324 325 326 327
		if (tmp >= 0) {
			if (tmp > 600) {
				tmp = 600;
				PARSE_WARNING ("invalid GATEWAY_PING_TIMEOUT time");
			}
328
			g_object_set (s_con, NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, (guint) tmp, NULL);
329
		} else
330
			PARSE_WARNING ("invalid GATEWAY_PING_TIMEOUT time");
331 332
	}

333
	switch (svGetValueBoolean (ifcfg, "CONNECTION_METERED", -1)) {
334 335 336 337 338 339 340 341
	case TRUE:
		g_object_set (s_con, NM_SETTING_CONNECTION_METERED, NM_METERED_YES, NULL);
		break;
	case FALSE:
		g_object_set (s_con, NM_SETTING_CONNECTION_METERED, NM_METERED_NO, NULL);
		break;
	}

342
	vint64 = svGetValueInt64 (ifcfg, "AUTH_RETRIES", 10, -1, G_MAXINT32, -1);
343
	g_object_set (s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (int) vint64, NULL);
344

345
	i_val = NM_SETTING_CONNECTION_MDNS_DEFAULT;
346 347
	if (!svGetValueEnum (ifcfg, "MDNS",
	                     nm_setting_connection_mdns_get_type (),
348 349
	                     &i_val, NULL))
		PARSE_WARNING ("invalid MDNS setting");
350 351
	g_object_set (s_con, NM_SETTING_CONNECTION_MDNS, i_val, NULL);

352
	return NM_SETTING (s_con);
353 354
}

355
/* Returns TRUE on missing address or valid address */
356 357
static gboolean
read_ip4_address (shvarFile *ifcfg,
358
                  const char *tag,
359 360
                  gboolean *out_has_key,
                  guint32 *out_addr,
361
                  GError **error)
362
{
363 364 365 366 367 368 369 370
	gs_free char *value_to_free = NULL;
	const char *value;
	guint32 a;

	nm_assert (ifcfg);
	nm_assert (tag);
	nm_assert (!error || !*error);

371 372
	value = svGetValueStr (ifcfg, tag, &value_to_free);
	if (!value) {
373 374
		NM_SET_OUT (out_has_key, FALSE);
		NM_SET_OUT (out_addr, 0);
375
		return TRUE;
376
	}
377

378
	if (inet_pton (AF_INET, value, &a) != 1) {
379
		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
380
		             "Invalid %s IP4 address '%s'", tag, value);
381
		return FALSE;
382
	}
383 384 385 386

	NM_SET_OUT (out_has_key, TRUE);
	NM_SET_OUT (out_addr, a);
	return TRUE;
387 388
}

389
static gboolean
390
is_any_ip4_address_defined (shvarFile *ifcfg, int *idx)
391
{
392
	int i, ignore, *ret_idx;
393

394
	ret_idx = idx ?: &ignore;
395 396

	for (i = -1; i <= 2; i++) {
397
		gs_free char *value = NULL;
398
		char tag[256];
399

400
		if (svGetValueStr (ifcfg, numbered_tag (tag, "IPADDR", i), &value)) {
401
			*ret_idx = i;
402 403 404
			return TRUE;
		}

405
		if (svGetValueStr (ifcfg, numbered_tag (tag, "PREFIX", i), &value)) {
406
			*ret_idx = i;
407 408 409
			return TRUE;
		}

410
		if (svGetValueStr (ifcfg, numbered_tag (tag, "NETMASK", i), &value)) {
411
			*ret_idx = i;
412 413 414 415 416 417
			return TRUE;
		}
	}
	return FALSE;
}

418 419
/* Returns TRUE on missing address or valid address */
static gboolean
420
read_full_ip4_address (shvarFile *ifcfg,
421
                       gint32 which,
422 423
                       NMIPAddress *base_addr,
                       NMIPAddress **out_address,
424
                       char **out_gateway,
425 426
                       GError **error)
{
427 428
	char tag[256];
	char prefix_tag[256];
429
	guint32 ipaddr;
430
	gs_free char *value = NULL;
431
	const char *v;
432
	int prefix = 0;
433 434 435
	gboolean has_key;
	guint32 a;
	char inet_buf[NM_UTILS_INET_ADDRSTRLEN];
436

437 438
	g_return_val_if_fail (which >= -1, FALSE);
	g_return_val_if_fail (ifcfg != NULL, FALSE);
439 440
	g_return_val_if_fail (out_address != NULL, FALSE);
	g_return_val_if_fail (*out_address == NULL, FALSE);
441
	g_return_val_if_fail (!error || !*error, FALSE);
442 443

	/* IP address */
444 445 446
	if (!read_ip4_address (ifcfg,
	                       numbered_tag (tag, "IPADDR", which),
	                       &has_key, &ipaddr, error))
447
		return FALSE;
448 449
	if (!has_key) {
		if (!base_addr)
450
			return TRUE;
451
		nm_ip_address_get_address_binary (base_addr, &ipaddr);
452
	}
453 454

	/* Gateway */
455
	if (out_gateway && !*out_gateway) {
456 457 458
		if (!read_ip4_address (ifcfg,
		                       numbered_tag (tag, "GATEWAY", which),
		                       &has_key, &a, error))
459
			return FALSE;
460 461
		if (has_key)
			*out_gateway = g_strdup (nm_utils_inet4_ntop (a, inet_buf));
462 463 464
	}

	/* Prefix */
465
	numbered_tag (prefix_tag, "PREFIX", which);
466 467 468
	v = svGetValueStr (ifcfg, prefix_tag, &value);
	if (v) {
		prefix = _nm_utils_ascii_str_to_int64 (v, 10, 0, 32, -1);
469
		if (prefix < 0) {
470
			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
471
			             "Invalid IP4 prefix '%s'", v);
472
			return FALSE;
473
		}
474 475
	} else {
		/* Fall back to NETMASK if no PREFIX was specified */
476 477 478
		if (!read_ip4_address (ifcfg,
		                       numbered_tag (tag, "NETMASK", which),
		                       &has_key, &a, error))
479
			return FALSE;
480 481 482
		if (has_key)
			prefix = nm_utils_ip4_netmask_to_prefix (a);
		else {
483 484 485 486
			if (base_addr)
				prefix = nm_ip_address_get_prefix (base_addr);
			else {
				/* Try to autodetermine the prefix for the address' class */
487
				prefix = _nm_utils_ip4_get_default_prefix (ipaddr);
488
				PARSE_WARNING ("missing %s, assuming %s/%d", prefix_tag, nm_utils_inet4_ntop (ipaddr, inet_buf), prefix);
489
			}
490
		}
491 492
	}

493
	*out_address = nm_ip_address_new_binary (AF_INET, &ipaddr, prefix, error);
494
	if (*out_address)
495
		return TRUE;
496

497
	return FALSE;
498
}
499

500
/*****************************************************************************/
501 502

static gboolean
503
parse_route_line_is_comment (const char *line)
504
{
505 506 507 508 509
	/* we obtained the line from a legacy route file. Here we skip
	 * empty lines and comments.
	 *
	 * initscripts compares: "$line" =~ '^[[:space:]]*(\#.*)?$'
	 */
510
	while (nm_utils_is_separator (line[0]))
511 512 513 514 515
		line++;
	if (NM_IN_SET (line[0], '\0', '#'))
		return TRUE;
	return FALSE;
}
516

517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
/*****************************************************************************/

typedef struct {
	const char *key;

	/* the element is not available in this case. */
	bool disabled:1;

	/* whether the element is to be ignored. Ignord is different from
	 * "disabled", because we still parse the option, but don't use it. */
	bool ignore:1;

	bool int_base_16:1;

	/* whether the command line option was found, and @v is
	 * initialized. */
	bool has:1;

535 536 537
	/* the type, one of PARSE_LINE_TYPE_* */
	char type;

538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
	union {
		guint8 uint8;
		guint32 uint32;
		struct {
			guint32 uint32;
			bool lock:1;
		} uint32_with_lock;
		struct {
			NMIPAddr addr;
			guint8 plen;
			bool has_plen:1;
		} addr;
	} v;

} ParseLineInfo;

enum {
	/* route attributes */
556
	PARSE_LINE_ATTR_ROUTE_TABLE,
557 558 559
	PARSE_LINE_ATTR_ROUTE_SRC,
	PARSE_LINE_ATTR_ROUTE_FROM,
	PARSE_LINE_ATTR_ROUTE_TOS,
560
	PARSE_LINE_ATTR_ROUTE_ONLINK,
561 562 563 564 565 566 567 568 569 570 571
	PARSE_LINE_ATTR_ROUTE_WINDOW,
	PARSE_LINE_ATTR_ROUTE_CWND,
	PARSE_LINE_ATTR_ROUTE_INITCWND,
	PARSE_LINE_ATTR_ROUTE_INITRWND,
	PARSE_LINE_ATTR_ROUTE_MTU,

	/* iproute2 arguments that only matter when parsing the file. */
	PARSE_LINE_ATTR_ROUTE_TO,
	PARSE_LINE_ATTR_ROUTE_VIA,
	PARSE_LINE_ATTR_ROUTE_METRIC,

572
	/* iproute2 parameters that are well known and that we silently ignore. */
573 574 575 576 577 578 579 580 581
	PARSE_LINE_ATTR_ROUTE_DEV,
};

#define PARSE_LINE_TYPE_UINT8             '8'
#define PARSE_LINE_TYPE_UINT32            'u'
#define PARSE_LINE_TYPE_UINT32_WITH_LOCK  'l'
#define PARSE_LINE_TYPE_ADDR              'a'
#define PARSE_LINE_TYPE_ADDR_WITH_PREFIX  'p'
#define PARSE_LINE_TYPE_IFNAME            'i'
582
#define PARSE_LINE_TYPE_FLAG              'f'
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613

/**
 * parse_route_line:
 * @line: the line to parse. This is either a line from the route-* or route6-* file,
 *   or the numbered OPTIONS setting.
 * @addr_family: the address family.
 * @options_route: (in-out): when line is from the OPTIONS setting, this is a pre-created
 *   route object that is completed with the settings from options. Otherwise,
 *   it shall point to %NULL and a new route is created and returned.
 * @out_route: (out): (transfer-full): (allow-none): the parsed %NMIPRoute instance.
 *   In case a @options_route is passed in, it returns the input route that was modified
 *   in-place. But the caller must unref the returned route in either case.
 * @error: the failure description.
 *
 * Parsing the route options line has two modes: one for the numbered OPTIONS
 * setting, and one for initscript's handle_ip_file(), which takes the lines
 * and passes them to `ip route add`. The modes are similar, but certain properties
 * are not allowed for OPTIONS.
 * The mode is differenciated by having an @options_route argument.
 *
 * Returns: returns a negative errno on failure. On success, it returns 0
 *   and @out_route.
 */
static int
parse_route_line (const char *line,
                  int addr_family,
                  NMIPRoute *options_route,
                  NMIPRoute **out_route,
                  GError **error)
{
	nm_auto_ip_route_unref NMIPRoute *route = NULL;
614 615
	gs_free const char **words_free = NULL;
	const char *const*words;
616 617 618 619 620 621
	const char *s;
	gsize i_words;
	guint i;
	char buf1[256];
	char buf2[256];
	ParseLineInfo infos[] = {
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
		[PARSE_LINE_ATTR_ROUTE_TABLE]     = { .key = NM_IP_ROUTE_ATTRIBUTE_TABLE,
		                                      .type = PARSE_LINE_TYPE_UINT32, },
		[PARSE_LINE_ATTR_ROUTE_SRC]       = { .key = NM_IP_ROUTE_ATTRIBUTE_SRC,
		                                      .type = PARSE_LINE_TYPE_ADDR, },
		[PARSE_LINE_ATTR_ROUTE_FROM]      = { .key = NM_IP_ROUTE_ATTRIBUTE_FROM,
		                                      .type = PARSE_LINE_TYPE_ADDR_WITH_PREFIX,
		                                      .disabled = (addr_family != AF_INET6), },
		[PARSE_LINE_ATTR_ROUTE_TOS]       = { .key = NM_IP_ROUTE_ATTRIBUTE_TOS,
		                                      .type = PARSE_LINE_TYPE_UINT8,
		                                      .int_base_16 = TRUE,
		                                      .ignore = (addr_family != AF_INET), },
		[PARSE_LINE_ATTR_ROUTE_ONLINK]    = { .key = NM_IP_ROUTE_ATTRIBUTE_ONLINK,
		                                      .type = PARSE_LINE_TYPE_FLAG,
		                                      .ignore = (addr_family != AF_INET), },
		[PARSE_LINE_ATTR_ROUTE_WINDOW]    = { .key = NM_IP_ROUTE_ATTRIBUTE_WINDOW,
		                                      .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },
		[PARSE_LINE_ATTR_ROUTE_CWND]      = { .key = NM_IP_ROUTE_ATTRIBUTE_CWND,
		                                      .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },
		[PARSE_LINE_ATTR_ROUTE_INITCWND]  = { .key = NM_IP_ROUTE_ATTRIBUTE_INITCWND,
		                                      .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },
		[PARSE_LINE_ATTR_ROUTE_INITRWND]  = { .key = NM_IP_ROUTE_ATTRIBUTE_INITRWND,
		                                      .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },
		[PARSE_LINE_ATTR_ROUTE_MTU]       = { .key = NM_IP_ROUTE_ATTRIBUTE_MTU,
		                                      .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },

		[PARSE_LINE_ATTR_ROUTE_TO]        = { .key = "to",
		                                      .type = PARSE_LINE_TYPE_ADDR_WITH_PREFIX,
		                                      .disabled = (options_route != NULL), },
		[PARSE_LINE_ATTR_ROUTE_VIA]       = { .key = "via",
		                                      .type = PARSE_LINE_TYPE_ADDR,
		                                      .disabled = (options_route != NULL), },
		[PARSE_LINE_ATTR_ROUTE_METRIC]    = { .key = "metric",
		                                      .type = PARSE_LINE_TYPE_UINT32,
		                                      .disabled = (options_route != NULL), },

		[PARSE_LINE_ATTR_ROUTE_DEV]       = { .key = "dev",
		                                      .type = PARSE_LINE_TYPE_IFNAME,
		                                      .ignore = TRUE,
		                                      .disabled = (options_route != NULL), },
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
	};

	nm_assert (line);
	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
	nm_assert (!options_route || nm_ip_route_get_family (options_route) == addr_family);

	/* initscripts read the legacy route file line-by-line and
	 * use it as `ip route add $line`, thus doing split+glob.
	 * Splitting on IFS (which we consider '<space><tab><newline>')
	 * and globbing (which we obviously don't do).
	 *
	 * I think it's a mess, because it doesn't support escaping or
	 * quoting. In fact, it can only encode benign values.
	 *
	 * We also use the same form for the numbered OPTIONS
	 * variable. I think it's bad not to support any form of
	 * escaping. But do that for now.
	 *
	 * Maybe later we want to support some form of quotation here.
	 * Which of course, would be incompatible with initscripts.
	 */
682
	words_free = nm_utils_strsplit_set (line, " \t\n");
683

684
	words = words_free ?: NM_PTRARRAY_EMPTY (const char *);
685

686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
	for (i_words = 0; words[i_words]; ) {
		const gsize i_words0 = i_words;
		const char *const w = words[i_words0];
		ParseLineInfo *info;
		gboolean unqualified_addr = FALSE;

		for (i = 0; i < G_N_ELEMENTS (infos); i++) {
			info = &infos[i];

			if (info->disabled)
				continue;

			if (!nm_streq (w, info->key))
				continue;

			if (info->has) {
				/* iproute2 for most arguments allows specifying them multiple times.
				 * Let's not do that. */
704
				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
705 706
				             "Duplicate option \"%s\"", w);
				return -EINVAL;
707 708
			}

709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
			info->has = TRUE;
			switch (info->type) {
			case PARSE_LINE_TYPE_UINT8:
				i_words++;
				goto parse_line_type_uint8;
			case PARSE_LINE_TYPE_UINT32:
				i_words++;
				goto parse_line_type_uint32;
			case PARSE_LINE_TYPE_UINT32_WITH_LOCK:
				i_words++;
				goto parse_line_type_uint32_with_lock;
			case PARSE_LINE_TYPE_ADDR:
				i_words++;
				goto parse_line_type_addr;
			case PARSE_LINE_TYPE_ADDR_WITH_PREFIX:
				i_words++;
				goto parse_line_type_addr_with_prefix;
			case PARSE_LINE_TYPE_IFNAME:
				i_words++;
				goto parse_line_type_ifname;
729 730 731
			case PARSE_LINE_TYPE_FLAG:
				i_words++;
				goto next;
732 733
			default:
				nm_assert_not_reached ();
734 735 736
			}
		}

737 738 739 740 741 742 743
		/* "to" is also accepted unqualified... (once) */
		info = &infos[PARSE_LINE_ATTR_ROUTE_TO];
		if (!info->has && !info->disabled) {
			unqualified_addr = TRUE;
			info->has = TRUE;
			goto parse_line_type_addr;
		}
744

745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
		             "Unrecognized argument (\"to\" is duplicate or \"%s\" is garbage)", w);
		return -EINVAL;

parse_line_type_uint8:
		s = words[i_words];
		if (!s)
			goto err_word_missing_argument;
		info->v.uint8 = _nm_utils_ascii_str_to_int64 (s,
		                                              info->int_base_16 ? 16 : 10,
		                                              0,
		                                              G_MAXUINT8,
		                                              0);;
		if (errno) {
			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
			             "Argument for \"%s\" is not a valid number", w);
			return -EINVAL;
		}
		i_words++;
		goto next;

parse_line_type_uint32:
parse_line_type_uint32_with_lock:
		s = words[i_words];
		if (!s)
			goto err_word_missing_argument;
		if (info->type == PARSE_LINE_TYPE_UINT32_WITH_LOCK) {
			if (nm_streq (s, "lock")) {
				s = words[++i_words];
				if (!s)
					goto err_word_missing_argument;
				info->v.uint32_with_lock.lock = TRUE;
			} else
				info->v.uint32_with_lock.lock = FALSE;
			info->v.uint32_with_lock.uint32 = _nm_utils_ascii_str_to_int64 (s, 10, 0, G_MAXUINT32, 0);;
		} else {
			info->v.uint32 = _nm_utils_ascii_str_to_int64 (s, 10, 0, G_MAXUINT32, 0);
		}
		if (errno) {
			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
			             "Argument for \"%s\" is not a valid number", w);
			return -EINVAL;
		}
		i_words++;
		goto next;

parse_line_type_ifname:
		s = words[i_words];
		if (!s)
			goto err_word_missing_argument;
		i_words++;
		goto next;

parse_line_type_addr:
parse_line_type_addr_with_prefix:
		s = words[i_words];
		if (!s)
			goto err_word_missing_argument;
		{
			int prefix = -1;

			if (info->type == PARSE_LINE_TYPE_ADDR) {
807 808
				if (!nm_utils_parse_inaddr_bin (addr_family,
				                                s,
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
				                                &info->v.addr.addr)) {
					if (   info == &infos[PARSE_LINE_ATTR_ROUTE_VIA]
					    && nm_streq (s, "(null)")) {
						/* Due to a bug, would older versions of NM write "via (null)"
						 * (rh#1452648). Workaround that, and accept it.*/
						memset (&info->v.addr.addr, 0, sizeof (info->v.addr.addr));
					} else {
						if (unqualified_addr) {
							g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
							             "Unrecognized argument (inet prefix is expected rather then \"%s\")", w);
							return -EINVAL;
						} else {
							g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
							             "Argument for \"%s\" is not a valid IPv%c address", w,
							             addr_family == AF_INET ? '4' : '6');
						}
						return -EINVAL;
					}
				}
			} else {
				nm_assert (info->type == PARSE_LINE_TYPE_ADDR_WITH_PREFIX);
				if (   info == &infos[PARSE_LINE_ATTR_ROUTE_TO]
				    && nm_streq (s, "default")) {
					memset (&info->v.addr.addr, 0, sizeof (info->v.addr.addr));
					prefix = 0;
834 835 836 837
				} else if (!nm_utils_parse_inaddr_prefix_bin (addr_family,
				                                              s,
				                                              &info->v.addr.addr,
				                                              &prefix)) {
838 839 840 841 842 843 844 845 846 847
					g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
					             "Argument for \"%s\" is not ADDR/PREFIX format", w);
					return -EINVAL;
				}
			}
			if (prefix == -1)
				info->v.addr.has_plen = FALSE;
			else {
				info->v.addr.has_plen = TRUE;
				info->v.addr.plen = prefix;
848
			}
849
		}
850 851 852 853 854 855 856 857 858
		i_words++;
		goto next;

err_word_missing_argument:
		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
		             "Missing argument for \"%s\"", w);
		return -EINVAL;
next:
		;
859 860
	}

861 862 863 864 865 866 867 868 869 870 871 872 873
	if (options_route) {
		route = options_route;
		nm_ip_route_ref (route);
	} else {
		ParseLineInfo *info_to = &infos[PARSE_LINE_ATTR_ROUTE_TO];
		ParseLineInfo *info_via = &infos[PARSE_LINE_ATTR_ROUTE_VIA];
		ParseLineInfo *info_metric = &infos[PARSE_LINE_ATTR_ROUTE_METRIC];
		guint prefix;

		if (!info_to->has) {
			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
			             "Missing destination prefix");
			return -EINVAL;
874 875
		}

876 877 878 879 880 881 882 883
		prefix =   info_to->v.addr.has_plen
		         ? info_to->v.addr.plen
		         : (addr_family == AF_INET ? 32 : 128);

		if (   (   (addr_family == AF_INET  && !info_to->v.addr.addr.addr4)
		        || (addr_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED (&info_to->v.addr.addr.addr6)))
		    && prefix == 0) {
			/* we ignore default routes by returning -ERANGE. */
884
			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
885 886
			             "Ignore manual default route");
			return -ERANGE;
887 888
		}

889 890 891 892 893 894 895 896 897 898 899
		route = nm_ip_route_new_binary (addr_family,
		                                &info_to->v.addr.addr,
		                                prefix,
		                                info_via->has ? &info_via->v.addr.addr : NULL,
		                                info_metric->has ? (gint64) info_metric->v.uint32 : (gint64) -1,
		                                error);
		info_to->has = FALSE;
		info_via->has = FALSE;
		info_metric->has = FALSE;
		if (!route)
			return -EINVAL;
900 901
	}

902 903
	for (i = 0; i < G_N_ELEMENTS (infos); i++) {
		ParseLineInfo *info = &infos[i];
904

905 906 907 908 909 910 911 912 913 914
		if (!info->has)
			continue;
		if (info->ignore || info->disabled)
			continue;
		switch (info->type) {
		case PARSE_LINE_TYPE_UINT8:
			nm_ip_route_set_attribute (route,
			                           info->key,
			                           g_variant_new_byte (info->v.uint8));
			break;
915 916 917 918 919
		case PARSE_LINE_TYPE_UINT32:
			nm_ip_route_set_attribute (route,
			                           info->key,
			                           g_variant_new_uint32 (info->v.uint32));
			break;
920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939
		case PARSE_LINE_TYPE_UINT32_WITH_LOCK:
			if (info->v.uint32_with_lock.lock) {
				nm_ip_route_set_attribute (route,
				                           nm_sprintf_buf (buf1, "lock-%s", info->key),
				                           g_variant_new_boolean (TRUE));
			}
			nm_ip_route_set_attribute (route,
			                           info->key,
			                           g_variant_new_uint32 (info->v.uint32_with_lock.uint32));
			break;
		case PARSE_LINE_TYPE_ADDR:
		case PARSE_LINE_TYPE_ADDR_WITH_PREFIX:
			nm_ip_route_set_attribute (route,
			                           info->key,
			                           g_variant_new_printf ("%s%s",
			                                                 inet_ntop (addr_family, &info->v.addr.addr, buf1, sizeof (buf1)),
			                                                 info->v.addr.has_plen
			                                                    ? nm_sprintf_buf (buf2, "/%u", (unsigned) info->v.addr.plen)
			                                                    : ""));
			break;
940
		case PARSE_LINE_TYPE_FLAG:
941
			/* NOTE: the flag (for "onlink") only allows to explictly set "TRUE".
942 943 944 945 946 947 948
			 * There is no way to express an explicit "FALSE" setting
			 * of this attribute, hence, the file format cannot encode
			 * that configuration. */
			nm_ip_route_set_attribute (route,
			                           info->key,
			                           g_variant_new_boolean (TRUE));
			break;
949 950 951 952 953 954 955 956 957 958
		default:
			nm_assert_not_reached ();
			break;
		}
	}

	nm_assert (_nm_ip_route_attribute_validate_all (route));

	NM_SET_OUT (out_route, g_steal_pointer (&route));
	return 0;
959 960
}

961 962
/* Returns TRUE on missing route or valid route */
static gboolean
963 964
read_one_ip4_route (shvarFile *ifcfg,
                    guint32 which,
965
                    NMIPRoute **out_route,
966 967
                    GError **error)
{
968 969
	char tag[256];
	char netmask_tag[256];
970 971 972 973
	guint32 dest;
	guint32 next_hop;
	guint32 netmask;
	gboolean has_key;
974
	const char *v;
975
	gs_free char *value = NULL;
976
	gint64 prefix, metric;
977
	char inet_buf[NM_UTILS_INET_ADDRSTRLEN];
978

979
	g_return_val_if_fail (ifcfg != NULL, FALSE);
980
	g_return_val_if_fail (out_route && !*out_route, FALSE);
981
	g_return_val_if_fail (!error || !*error, FALSE);
982 983

	/* Destination */
984 985 986
	if (!read_ip4_address (ifcfg,
	                       numbered_tag (tag, "ADDRESS", which),
	                       &has_key, &dest, error))
987
		return FALSE;
988 989 990 991
	if (!has_key) {
		/* missing route = success */
		*out_route = NULL;
		return TRUE;
992 993 994
	}

	/* Next hop */
995 996 997
	if (!read_ip4_address (ifcfg,
	                       numbered_tag (tag, "GATEWAY", which),
	                       NULL, &next_hop, error))
998
		return FALSE;
999
	/* We don't make distinction between missing GATEWAY IP and 0.0.0.0 */
1000 1001

	/* Prefix */
1002 1003 1004
	if (!read_ip4_address (ifcfg,
	                       numbered_tag (netmask_tag, "NETMASK", which),
	                       &has_key, &netmask, error))
1005
		return FALSE;
1006
	if (has_key) {
1007
		prefix = nm_utils_ip4_netmask_to_prefix (netmask);
1008
		if (prefix == 0 || netmask != _nm_utils_ip4_prefix_to_netmask (prefix)) {
1009
			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
1010
			             "Invalid IP4 netmask '%s' \"%s\"", netmask_tag, nm_utils_inet4_ntop (netmask, inet_buf));
1011
			return FALSE;
1012 1013
		}
	} else {
1014
		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
1015
		             "Missing IP4 route element '%s'", netmask_tag);
1016
		return FALSE;
1017 1018 1019
	}

	/* Metric */
1020
	nm_clear_g_free (&value);
1021 1022 1023
	v = svGetValueStr (ifcfg, numbered_tag (tag, "METRIC", which), &value);
	if (v) {
		metric = _nm_utils_ascii_str_to_int64 (v, 10, 0, G_MAXUINT32, -1);
1024
		if (metric < 0) {
1025
			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
1026
			             "Invalid IP4 route metric '%s'", v);
1027
			return FALSE;
1028
		}
1029
	} else
1030
		metric = -1;
1031

1032
	*out_route = nm_ip_route_new_binary (AF_INET, &dest, prefix, &next_hop, metric, error);
1033 1034
	if (!*out_route)
		return FALSE;
1035

1036 1037
	/* Options */
	nm_clear_g_free (&value);
1038 1039
	v = svGetValueStr (ifcfg, numbered_tag (tag, "OPTIONS", which), &value);
	if (v) {
1040
		if (parse_route_line (v, AF_INET, *out_route, NULL, error) < 0) {
1041 1042 1043 1044 1045 1046
			g_clear_pointer (out_route, nm_ip_route_unref);
			return FALSE;
		}
	}

	return TRUE;
1047 1048 1049
}

static gboolean
1050 1051 1052 1053
read_route_file (int addr_family,
                 const char *filename,
                 NMSettingIPConfig *s_ip,
                 GError **error)
1054
{
1055
	gs_free char *contents = NULL;
1056
	char *contents_rest = NULL;
1057
	const char *line;
1058
	gsize len = 0;
1059
	gsize line_num;
1060

1061 1062 1063
	g_return_val_if_fail (filename, FALSE);
	g_return_val_if_fail (   (addr_family == AF_INET  && NM_IS_SETTING_IP4_CONFIG (s_ip))
	                      || (addr_family == AF_INET6 && NM_IS_SETTING_IP6_CONFIG (s_ip)), FALSE);
1064
	g_return_val_if_fail (!error || !*error, FALSE);
1065

1066 1067
	if (   !g_file_get_contents (filename, &contents, &len, NULL)
	    || !len) {
1068
		return TRUE;  /* missing/empty = success */
1069 1070
	}

1071 1072 1073 1074 1075 1076 1077
	line_num = 0;
	for (line = strtok_r (contents, "\n", &contents_rest);
	     line;
	     line = strtok_r (NULL, "\n", &contents_rest)) {
		nm_auto_ip_route_unref NMIPRoute *route = NULL;
		gs_free_error GError *local = NULL;
		int e;
1078

1079 1080 1081
		line_num++;

		if (parse_route_line_is_comment (line))
1082
			continue;
1083

1084
		e = parse_route_line (line, addr_family, NULL, &route, &local);
1085

1086 1087 1088 1089 1090 1091 1092
		if (e < 0) {
			if (e == -ERANGE)
				PARSE_WARNING ("ignoring manual default route: '%s' (%s)", line, filename);
			else {
				/* we accept all unrecognized lines, because otherwise we would reject the
				 * entire connection. */
				PARSE_WARNING ("ignoring invalid route at \"%s\" (%s:%lu): %s", line, filename, (long unsigned) line_num, local->message);
1093
			}
1094
			continue;
1095 1096
		}

1097 1098
		if (!nm_setting_ip_config_add_route (s_ip, route))
			PARSE_WARNING ("duplicate IPv%c route", addr_family == AF_INET ? '4' : '6');
1099 1100
	}

1101
	return TRUE;
1102 1103
}

1104
static void
1105
parse_dns_options (NMSettingIPConfig *ip_config, const char *value)
1106
{
1107 1108
	gs_free const char **options = NULL;
	const char *const *item;
1109 1110 1111 1112 1113 1114

	g_return_if_fail (ip_config);

	if (!value)
		return;

1115 1116 1117
	if (!nm_setting_ip_config_has_dns_options (ip_config))
		nm_setting_ip_config_clear_dns_options (ip_config, TRUE);

1118
	options = nm_utils_strsplit_set (value, " ");
1119 1120
	if (options) {
		for (item = options; *item; item++) {
1121 1122
			if (!nm_setting_ip_config_add_dns_option (ip_config, *item))
				PARSE_WARNING ("can't add DNS option '%s'", *item);
1123 1124 1125 1126
		}
	}
}

1127
static gboolean
1128 1129 1130
parse_full_ip6_address (shvarFile *ifcfg,
                        const char *addr_str,
                        int i,
1131
                        NMIPAddress **out_address,
1132
                        GError **error)
1133 1134
{
	char **list;
1135
	char *ip_val, *prefix_val;
1136
	int prefix;
1137 1138
	gboolean success = FALSE;

1139 1140 1141
	g_return_val_if_fail (addr_str != NULL, FALSE);
	g_return_val_if_fail (out_address != NULL, FALSE);
	g_return_val_if_fail (*out_address == NULL, FALSE);
1142
	g_return_val_if_fail (!error || !*error, FALSE);
1143

1144
	/* Split the address and prefix */
1145 1146
	list = g_strsplit_set (addr_str, "/", 2);
	if (g_strv_length (list) < 1) {
1147
		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
1148 1149 1150 1151
		             "Invalid IP6 address '%s'", addr_str);
		goto error;
	}

1152
	ip_val = list[0];
1153

1154
	prefix_val = list[1];
1155
	if (prefix_val) {
1156 1157
		prefix = _nm_utils_ascii_str_to_int64 (prefix_val, 10, 0, 128, -1);
		if (prefix < 0) {
1158
			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
1159
			             "Invalid IP6 prefix '%s'", prefix_val);
1160 1161 1162 1163
			goto error;
		}
	} else {
		/* Missing prefix is treated as prefix of 64 */
1164
		prefix = 64;
1165 1166
	}

1167
	*out_address = nm_ip_address_new (AF_INET6, ip_val, prefix, error);
1168 1169
	if (*out_address)
		success = TRUE;
1170

1171
error:
1172
	g_strfreev (list);
1173
	return success;
1174 1175
}

1176
static NMSetting *
1177
make_user_setting (shvarFile *ifcfg)
1178 1179 1180 1181 1182 1183 1184 1185
{
	gboolean has_user_data = FALSE;
	gs_unref_object NMSettingUser *s_user = NULL;
	gs_unref_hashtable GHashTable *keys = NULL;
	GHashTableIter iter;
	const char *key;
	nm_auto_free_gstring GString *str = NULL;

1186
	keys = svGetKeys (ifcfg, SV_KEY_TYPE_USER);
1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220
	if (!keys)
		return NULL;

	g_hash_table_iter_init (&iter, keys);
	while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
		const char *value;
		gs_free char *value_to_free = NULL;

		value = svGetValue (ifcfg, key, &value_to_free);

		if (!value)
			continue;

		if (!str)
			str = g_string_sized_new (100);
		else
			g_string_set_size (str, 0);

		if (!nms_ifcfg_rh_utils_user_key_decode (key + NM_STRLEN ("NM_USER_"), str))
			continue;

		if (!s_user)
			s_user = NM_SETTING_USER (nm_setting_user_new ());

		if (nm_setting_user_set_data (s_user, str->str,
		                              value, NULL))
			has_user_data = TRUE;
	}

	return has_user_data
	       ? g_steal_pointer (&s_user)
	       : NULL;
}

1221
static NMSetting *
1222
make_proxy_setting (shvarFile *ifcfg)
1223 1224
{
	NMSettingProxy *s_proxy = NULL;
1225 1226
	gs_free char *value = NULL;
	const char *v;
1227 1228
	NMSettingProxyMethod method;

1229 1230
	v = svGetValueStr (ifcfg, "PROXY_METHOD", &value);
	if (!v)
1231 1232
		return NULL;

1233
	if (!g_ascii_strcasecmp (v, "auto"))
1234 1235 1236 1237 1238 1239 1240 1241 1242
		method = NM_SETTING_PROXY_METHOD_AUTO;
	else
		method = NM_SETTING_PROXY_METHOD_NONE;

	s_proxy = (NMSettingProxy *) nm_setting_proxy_new ();

	switch (method) {
	case NM_SETTING_PROXY_METHOD_AUTO:
		g_object_set (s_proxy,
1243
		              NM_SETTING_PROXY_METHOD, (int) NM_SETTING_PROXY_METHOD_AUTO,
1244 1245
		              NULL);

1246 1247 1248 1249
		nm_clear_g_free (&value);
		v = svGetValueStr (ifcfg, "PAC_URL", &value);
		if (v)
			g_object_set (s_proxy, NM_SETTING_PROXY_PAC_URL, v, NULL);
1250

1251 1252 1253 1254
		nm_clear_g_free (&value);
		v = svGetValueStr (ifcfg, "PAC_SCRIPT", &value);
		if (v)
			g_object_set (s_proxy, NM_SETTING_PROXY_PAC_SCRIPT, v, NULL);
1255 1256 1257 1258

		break;
	case NM_SETTING_PROXY_METHOD_NONE:
		g_object_set (s_proxy,
1259
		              NM_SETTING_PROXY_METHOD, (int) NM_SETTING_PROXY_METHOD_NONE,
1260
		              NULL);
1261
		break;
1262 1263
	}

1264 1265
	if (svGetValueBoolean (ifcfg, "BROWSER_ONLY", FALSE))
		g_object_set (s_proxy, NM_SETTING_PROXY_BROWSER_ONLY, TRUE, NULL);
1266 1267 1268

	return NM_SETTING (s_proxy);
}
1269

1270
static NMSetting *
1271
make_ip4_setting (shvarFile *ifcfg,
1272
                  shvarFile *network_ifcfg,
1273
                  gboolean routes_read,
1274
                  gboolean *out_has_defroute,
1275
                  GError **error)
1276
{
1277 1278
	gs_unref_object NMSettingIPConfig *s_ip4 = NULL;
	gs_free char *route_path = NULL;
1279 1280
	gs_free char *value = NULL;
	const char *v;
1281
	char *method;
1282 1283
	gs_free char *dns_options_free = NULL;
	const char *dns_options = NULL;
1284
	gs_free char *gateway = NULL;
1285
	int i;
1286 1287
	guint32 a;
	gboolean has_key;
1288
	shvarFile *route_ifcfg;
1289
	gboolean never_default;
1290
	gint64 timeout;
1291
	int priority;
1292
	char inet_buf[NM_UTILS_INET_ADDRSTRLEN];
1293
	const char *const *item;
1294
	guint32 route_table;
1295

1296 1297
	nm_assert (out_has_defroute && !*out_has_defroute);

1298
	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
1299

1300 1301 1302 1303 1304
	/* First check if DEFROUTE is set for this device; DEFROUTE has the
	 * opposite meaning from never-default. The default if DEFROUTE is not
	 * specified is DEFROUTE=yes which means that this connection can be used
	 * as a default route
	 */
1305 1306 1307 1308 1309 1310 1311
	i = svGetValueBoolean (ifcfg, "DEFROUTE", -1);
	if (i == -1)
		never_default = FALSE;
	else {
		never_default = !i;
		*out_has_defroute = TRUE;
	}
1312 1313

	/* Then check if GATEWAYDEV; it's global and overrides DEFROUTE */
1314
	if (network_ifcfg) {
1315 1316
		gs_free char *gatewaydev_value = NULL;
		const char *gatewaydev;
1317 1318

		/* Get the connection ifcfg device name and the global gateway device */
1319 1320
		v = svGetValueStr (ifcfg, "DEVICE", &value);
		gatewaydev = svGetValueStr (network_ifcfg, "GATEWAYDEV", &gatewaydev_value);
1321
		dns_options = svGetValue (network_ifcfg, "RES_OPTIONS", &dns_options_free);
1322 1323 1324 1325

		/* If there was a global gateway device specified, then only connections
		 * for that device can be the default connection.
		 */
1326 1327
		if (gatewaydev && v)
			never_default = !!strcmp (v, gatewaydev);
1328

1329
		nm_clear_g_free (&value);
1330
	}
1331