summary refs log tree commit diff
path: root/src/dhcp/nm-dhcp-dhclient-utils.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-10-05 22:27:18 +0200
committerMichael Biebl <biebl@debian.org>2020-10-05 22:27:18 +0200
commitaafc1dbe4712c86189bbc1d4d54ad8cb4c69be7e (patch)
treea2a9bb4d007339a0b1304540388230ccedac32bd /src/dhcp/nm-dhcp-dhclient-utils.c
parente7b44ef4c80907346ec7492a09c45277459924fc (diff)
New upstream version 1.27.90 upstream/1.27.90
Diffstat (limited to 'src/dhcp/nm-dhcp-dhclient-utils.c')
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c1241
1 files changed, 617 insertions, 624 deletions
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 76fc1a89..f0eed6f5 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+/* SPDX-License-Identifier: GPL-2.0+ */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
@@ -19,702 +19,695 @@
 #include "platform/nm-platform.h"
 #include "NetworkManagerUtils.h"
 
-#define TIMEOUT_TAG      "timeout "
-#define RETRY_TAG        "retry "
-#define CLIENTID_TAG     "send dhcp-client-identifier"
+#define TIMEOUT_TAG  "timeout "
+#define RETRY_TAG    "retry "
+#define CLIENTID_TAG "send dhcp-client-identifier"
 
 #define HOSTNAME4_TAG    "send host-name"
 #define HOSTNAME4_FORMAT HOSTNAME4_TAG " \"%s\"; # added by NetworkManager"
 
-#define FQDN_TAG_PREFIX  "send fqdn."
-#define FQDN_TAG         FQDN_TAG_PREFIX "fqdn"
-#define FQDN_FORMAT      FQDN_TAG " \"%s\"; # added by NetworkManager"
+#define FQDN_TAG_PREFIX "send fqdn."
+#define FQDN_TAG        FQDN_TAG_PREFIX "fqdn"
+#define FQDN_FORMAT     FQDN_TAG " \"%s\"; # added by NetworkManager"
 
 #define ALSOREQ_TAG "also request "
-#define REQ_TAG "request "
+#define REQ_TAG     "request "
 
-#define MUDURLv4_DEF    "option mudurl code 161 = text;\n"
-#define MUDURLv4_FMT    "send mudurl \"%s\";\n"
-
-#define MUDURLv6_DEF    "option dhcp6.mudurl code 112 = text;\n"
-#define MUDURLv6_FMT    "send dhcp6.mudurl \"%s\";\n"
+#define MUDURLv4_DEF "option mudurl code 161 = text;\n"
+#define MUDURLv4_FMT "send mudurl \"%s\";\n"
 
+#define MUDURLv6_DEF "option dhcp6.mudurl code 112 = text;\n"
+#define MUDURLv6_FMT "send dhcp6.mudurl \"%s\";\n"
 
 static void
-add_request (GPtrArray *array, const char *item)
+add_request(GPtrArray *array, const char *item)
 {
-	guint i;
+    guint i;
 
-	for (i = 0; i < array->len; i++) {
-		if (nm_streq (array->pdata[i], item))
-			return;
-	}
-	g_ptr_array_add (array, g_strdup (item));
+    for (i = 0; i < array->len; i++) {
+        if (nm_streq(array->pdata[i], item))
+            return;
+    }
+    g_ptr_array_add(array, g_strdup(item));
 }
 
 static gboolean
-grab_request_options (GPtrArray *store, const char* line)
+grab_request_options(GPtrArray *store, const char *line)
 {
-	gs_free const char **line_v = NULL;
-	gsize i;
-
-	/* Grab each 'request' or 'also request'  option and save for later */
-	line_v = nm_utils_strsplit_set (line, "\t ,");
-	for (i = 0; line_v && line_v[i]; i++) {
-		const char *ss = nm_str_skip_leading_spaces (line_v[i]);
-		gsize l;
-		gboolean end = FALSE;
-
-		if (!ss[0])
-			continue;
-		if (ss[0] == ';') {
-			/* all done */
-			return TRUE;
-		}
-
-		if (!g_ascii_isalnum (ss[0]))
-			continue;
-
-		l = strlen (ss);
-
-		while (   l > 0
-		       && g_ascii_isspace (ss[l - 1])) {
-			((char *) ss)[l - 1] = '\0';
-			l--;
-		}
-		if (   l > 0
-		    && ss[l - 1] == ';') {
-			/* Remove the EOL marker */
-			((char *) ss)[l - 1] = '\0';
-			end = TRUE;
-		}
-
-		if (ss[0])
-			add_request (store, ss);
-
-		if (end)
-			return TRUE;
-	}
-
-	return FALSE;
+    gs_free const char **line_v = NULL;
+    gsize                i;
+
+    /* Grab each 'request' or 'also request'  option and save for later */
+    line_v = nm_utils_strsplit_set(line, "\t ,");
+    for (i = 0; line_v && line_v[i]; i++) {
+        const char *ss = nm_str_skip_leading_spaces(line_v[i]);
+        gsize       l;
+        gboolean    end = FALSE;
+
+        if (!ss[0])
+            continue;
+        if (ss[0] == ';') {
+            /* all done */
+            return TRUE;
+        }
+
+        if (!g_ascii_isalnum(ss[0]))
+            continue;
+
+        l = strlen(ss);
+
+        while (l > 0 && g_ascii_isspace(ss[l - 1])) {
+            ((char *) ss)[l - 1] = '\0';
+            l--;
+        }
+        if (l > 0 && ss[l - 1] == ';') {
+            /* Remove the EOL marker */
+            ((char *) ss)[l - 1] = '\0';
+            end                  = TRUE;
+        }
+
+        if (ss[0])
+            add_request(store, ss);
+
+        if (end)
+            return TRUE;
+    }
+
+    return FALSE;
 }
 
 static void
-add_ip4_config (GString *str,
-                GBytes *client_id,
-                const char *hostname,
-                gboolean use_fqdn,
-                NMDhcpHostnameFlags hostname_flags)
+add_ip4_config(GString *           str,
+               GBytes *            client_id,
+               const char *        hostname,
+               gboolean            use_fqdn,
+               NMDhcpHostnameFlags hostname_flags)
 {
-	if (client_id) {
-		const char *p;
-		gsize l;
-		guint i;
-
-		p = g_bytes_get_data (client_id, &l);
-		nm_assert (p);
-
-		/* Allow type 0 (non-hardware address) to be represented as a string
-		 * as long as all the characters are printable.
-		 */
-		for (i = 1; (p[0] == 0) && i < l; i++) {
-			if (!g_ascii_isprint (p[i]) || p[i] == '\\' || p[i] == '"')
-				break;
-		}
-
-		g_string_append (str, CLIENTID_TAG " ");
-		if (i < l) {
-			/* Unprintable; convert to a hex string */
-			for (i = 0; i < l; i++) {
-				if (i > 0)
-					g_string_append_c (str, ':');
-				g_string_append_printf (str, "%02x", (guint8) p[i]);
-			}
-		} else {
-			/* Printable; just add to the line with type 0 */
-			g_string_append_c (str, '"');
-			g_string_append (str, "\\x00");
-			g_string_append_len (str, p + 1, l - 1);
-			g_string_append_c (str, '"');
-		}
-		g_string_append (str, "; # added by NetworkManager\n");
-	}
-
-	if (hostname) {
-		if (use_fqdn) {
-			g_string_append_printf (str, FQDN_FORMAT "\n", hostname);
-
-			g_string_append_printf (str, FQDN_TAG_PREFIX "encoded %s;\n",
-			                          (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED)
-			                        ? "on"
-			                        : "off");
-
-			g_string_append_printf (str, FQDN_TAG_PREFIX "server-update %s;\n",
-			                          (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE)
-			                        ? "on"
-			                        : "off");
-
-			g_string_append_printf (str, FQDN_TAG_PREFIX "no-client-update %s;\n",
-			                          (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE)
-			                        ? "on"
-			                        : "off");
-		} else
-			g_string_append_printf (str, HOSTNAME4_FORMAT "\n", hostname);
-	}
-
-	g_string_append_c (str, '\n');
-
-	/* Define options for classless static routes */
-	g_string_append (str,
-	                 "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n");
-	g_string_append (str,
-	                 "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n");
-	/* Web Proxy Auto-Discovery option (bgo #368423) */
-	g_string_append (str, "option wpad code 252 = string;\n");
-
-	g_string_append_c (str, '\n');
+    if (client_id) {
+        const char *p;
+        gsize       l;
+        guint       i;
+
+        p = g_bytes_get_data(client_id, &l);
+        nm_assert(p);
+
+        /* Allow type 0 (non-hardware address) to be represented as a string
+         * as long as all the characters are printable.
+         */
+        for (i = 1; (p[0] == 0) && i < l; i++) {
+            if (!g_ascii_isprint(p[i]) || p[i] == '\\' || p[i] == '"')
+                break;
+        }
+
+        g_string_append(str, CLIENTID_TAG " ");
+        if (i < l) {
+            /* Unprintable; convert to a hex string */
+            for (i = 0; i < l; i++) {
+                if (i > 0)
+                    g_string_append_c(str, ':');
+                g_string_append_printf(str, "%02x", (guint8) p[i]);
+            }
+        } else {
+            /* Printable; just add to the line with type 0 */
+            g_string_append_c(str, '"');
+            g_string_append(str, "\\x00");
+            g_string_append_len(str, p + 1, l - 1);
+            g_string_append_c(str, '"');
+        }
+        g_string_append(str, "; # added by NetworkManager\n");
+    }
+
+    if (hostname) {
+        if (use_fqdn) {
+            g_string_append_printf(str, FQDN_FORMAT "\n", hostname);
+
+            g_string_append_printf(str,
+                                   FQDN_TAG_PREFIX "encoded %s;\n",
+                                   (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED) ? "on"
+                                                                                         : "off");
+
+            g_string_append_printf(
+                str,
+                FQDN_TAG_PREFIX "server-update %s;\n",
+                (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE) ? "on" : "off");
+
+            g_string_append_printf(str,
+                                   FQDN_TAG_PREFIX "no-client-update %s;\n",
+                                   (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE) ? "on"
+                                                                                           : "off");
+        } else
+            g_string_append_printf(str, HOSTNAME4_FORMAT "\n", hostname);
+    }
+
+    g_string_append_c(str, '\n');
+
+    /* Define options for classless static routes */
+    g_string_append(
+        str,
+        "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n");
+    g_string_append(str,
+                    "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n");
+    /* Web Proxy Auto-Discovery option (bgo #368423) */
+    g_string_append(str, "option wpad code 252 = string;\n");
+
+    g_string_append_c(str, '\n');
 }
 
 static void
-add_hostname6 (GString *str,
-               const char *hostname,
-               NMDhcpHostnameFlags hostname_flags)
+add_hostname6(GString *str, const char *hostname, NMDhcpHostnameFlags hostname_flags)
 {
-	if (hostname) {
-		g_string_append_printf (str, FQDN_FORMAT "\n", hostname);
-		if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE)
-			g_string_append (str, FQDN_TAG_PREFIX "server-update on;\n");
-		if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE)
-			g_string_append (str, FQDN_TAG_PREFIX "no-client-update on;\n");
-		g_string_append_c (str, '\n');
-	}
+    if (hostname) {
+        g_string_append_printf(str, FQDN_FORMAT "\n", hostname);
+        if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE)
+            g_string_append(str, FQDN_TAG_PREFIX "server-update on;\n");
+        if (hostname_flags & NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE)
+            g_string_append(str, FQDN_TAG_PREFIX "no-client-update on;\n");
+        g_string_append_c(str, '\n');
+    }
 }
 
-static void add_mud_url_config(GString *str, const char *mud_url, int addr_family)
+static void
+add_mud_url_config(GString *str, const char *mud_url, int addr_family)
 {
-	if (mud_url) {
-		if (addr_family == AF_INET) {
-			g_string_append (str, MUDURLv4_DEF);
-			g_string_append_printf (str, MUDURLv4_FMT, mud_url);
-		} else {
-			g_string_append (str, MUDURLv6_DEF);
-			g_string_append_printf (str, MUDURLv6_FMT, mud_url);
-		}
-	}
+    if (mud_url) {
+        if (addr_family == AF_INET) {
+            g_string_append(str, MUDURLv4_DEF);
+            g_string_append_printf(str, MUDURLv4_FMT, mud_url);
+        } else {
+            g_string_append(str, MUDURLv6_DEF);
+            g_string_append_printf(str, MUDURLv6_FMT, mud_url);
+        }
+    }
 }
 
 static GBytes *
-read_client_id (const char *str)
+read_client_id(const char *str)
 {
-	gs_free char *s = NULL;
-	char *p;
-	int i = 0, j = 0;
-
-	nm_assert (!strncmp (str, CLIENTID_TAG, NM_STRLEN (CLIENTID_TAG)));
-	str += NM_STRLEN (CLIENTID_TAG);
-
-	if (!g_ascii_isspace (*str))
-		return NULL;
-	while (g_ascii_isspace (*str))
-		str++;
-
-	if (*str == '"') {
-		/* Parse string literal with escape sequences */
-		s = g_strdup (str + 1);
-		p = strrchr (s, '"');
-		if (p)
-			*p = '\0';
-		else
-			return NULL;
-
-		if (!s[0])
-			return NULL;
-
-		while (s[i]) {
-			if (   s[i] == '\\'
-			    && s[i + 1] == 'x'
-			    && g_ascii_isxdigit (s[i + 2])
-			    && g_ascii_isxdigit (s[i + 3])) {
-				s[j++] =  (g_ascii_xdigit_value (s[i + 2]) << 4)
-				         + g_ascii_xdigit_value (s[i + 3]);
-				i += 4;
-				continue;
-			}
-			if (   s[i] == '\\'
-			    && s[i + 1] >= '0' && s[i + 1] <= '7'
-			    && s[1 + 2] >= '0' && s[i + 2] <= '7'
-			    && s[1 + 3] >= '0' && s[i + 3] <= '7') {
-				s[j++] =    ((s[i + 1] - '0') << 6)
-				          + ((s[i + 2] - '0') << 3)
-				          + ( s[i + 3] - '0');
-				i += 4;
-				continue;
-			}
-			s[j++] = s[i++];
-		}
-		return g_bytes_new_take (g_steal_pointer (&s), j);
-	}
-
-	/* Otherwise, try to read a hexadecimal sequence */
-	s = g_strdup (str);
-	g_strchomp (s);
-	if (s[strlen (s) - 1] == ';')
-		s[strlen (s) - 1] = '\0';
-
-	return nm_utils_hexstr2bin (s);
+    gs_free char *s = NULL;
+    char *        p;
+    int           i = 0, j = 0;
+
+    nm_assert(!strncmp(str, CLIENTID_TAG, NM_STRLEN(CLIENTID_TAG)));
+    str += NM_STRLEN(CLIENTID_TAG);
+
+    if (!g_ascii_isspace(*str))
+        return NULL;
+    while (g_ascii_isspace(*str))
+        str++;
+
+    if (*str == '"') {
+        /* Parse string literal with escape sequences */
+        s = g_strdup(str + 1);
+        p = strrchr(s, '"');
+        if (p)
+            *p = '\0';
+        else
+            return NULL;
+
+        if (!s[0])
+            return NULL;
+
+        while (s[i]) {
+            if (s[i] == '\\' && s[i + 1] == 'x' && g_ascii_isxdigit(s[i + 2])
+                && g_ascii_isxdigit(s[i + 3])) {
+                s[j++] = (g_ascii_xdigit_value(s[i + 2]) << 4) + g_ascii_xdigit_value(s[i + 3]);
+                i += 4;
+                continue;
+            }
+            if (s[i] == '\\' && s[i + 1] >= '0' && s[i + 1] <= '7' && s[1 + 2] >= '0'
+                && s[i + 2] <= '7' && s[1 + 3] >= '0' && s[i + 3] <= '7') {
+                s[j++] = ((s[i + 1] - '0') << 6) + ((s[i + 2] - '0') << 3) + (s[i + 3] - '0');
+                i += 4;
+                continue;
+            }
+            s[j++] = s[i++];
+        }
+        return g_bytes_new_take(g_steal_pointer(&s), j);
+    }
+
+    /* Otherwise, try to read a hexadecimal sequence */
+    s = g_strdup(str);
+    g_strchomp(s);
+    if (s[strlen(s) - 1] == ';')
+        s[strlen(s) - 1] = '\0';
+
+    return nm_utils_hexstr2bin(s);
 }
 
 static gboolean
-read_interface (const char *line, char *interface, guint size)
+read_interface(const char *line, char *interface, guint size)
 {
-	gs_free char *dup = g_strdup (line + NM_STRLEN ("interface"));
-	char *ptr = dup, *end;
-
-	while (g_ascii_isspace (*ptr))
-		ptr++;
-
-	if (*ptr == '"') {
-		ptr++;
-		end = strchr (ptr, '"');
-		if (!end)
-			return FALSE;
-		*end = '\0';
-	} else {
-		end = strchr (ptr, ' ');
-		if (!end)
-			end = strchr (ptr, '{');
-		if (!end)
-			return FALSE;
-		*end = '\0';
-	}
-
-	if (   ptr[0] == '\0'
-	    || strlen (ptr) + 1 > size)
-		return FALSE;
-
-	snprintf (interface, size, "%s", ptr);
-
-	return TRUE;
+    gs_free char *dup = g_strdup(line + NM_STRLEN("interface"));
+    char *        ptr = dup, *end;
+
+    while (g_ascii_isspace(*ptr))
+        ptr++;
+
+    if (*ptr == '"') {
+        ptr++;
+        end = strchr(ptr, '"');
+        if (!end)
+            return FALSE;
+        *end = '\0';
+    } else {
+        end = strchr(ptr, ' ');
+        if (!end)
+            end = strchr(ptr, '{');
+        if (!end)
+            return FALSE;
+        *end = '\0';
+    }
+
+    if (ptr[0] == '\0' || strlen(ptr) + 1 > size)
+        return FALSE;
+
+    snprintf(interface, size, "%s", ptr);
+
+    return TRUE;
 }
 
 char *
-nm_dhcp_dhclient_create_config (const char *interface,
-                                int addr_family,
-                                GBytes *client_id,
-                                const char *anycast_addr,
-                                const char *hostname,
-                                guint32 timeout,
-                                gboolean use_fqdn,
-                                NMDhcpHostnameFlags hostname_flags,
-                                const char *mud_url,
-                                const char *orig_path,
-                                const char *orig_contents,
-                                GBytes **out_new_client_id)
+nm_dhcp_dhclient_create_config(const char *        interface,
+                               int                 addr_family,
+                               GBytes *            client_id,
+                               const char *        anycast_addr,
+                               const char *        hostname,
+                               guint32             timeout,
+                               gboolean            use_fqdn,
+                               NMDhcpHostnameFlags hostname_flags,
+                               const char *        mud_url,
+                               const char *const * reject_servers,
+                               const char *        orig_path,
+                               const char *        orig_contents,
+                               GBytes **           out_new_client_id)
 {
-	nm_auto_free_gstring GString *new_contents = NULL;
-	gs_unref_ptrarray GPtrArray *fqdn_opts = NULL;
-	gs_unref_ptrarray GPtrArray *reqs = NULL;
-	gboolean reset_reqlist = FALSE;
-	int i;
-
-	g_return_val_if_fail (!anycast_addr || nm_utils_hwaddr_valid (anycast_addr, ETH_ALEN), NULL);
-	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NULL);
-	nm_assert (!out_new_client_id || !*out_new_client_id);
-
-	new_contents = g_string_new (_("# Created by NetworkManager\n"));
-	reqs = g_ptr_array_new_full (5, g_free);
-
-	if (orig_contents) {
-		gs_free const char **lines = NULL;
-		gsize line_i;
-		nm_auto_free_gstring GString *blocks_stack = NULL;
-		guint blocks_skip = 0;
-		gboolean in_alsoreq = FALSE;
-		gboolean in_req = FALSE;
-		char intf[IFNAMSIZ];
-
-		blocks_stack = g_string_new (NULL);
-		g_string_append_printf (new_contents, _("# Merged from %s\n\n"), orig_path);
-		intf[0] = '\0';
-
-		lines = nm_utils_strsplit_set (orig_contents, "\n\r");
-		for (line_i = 0; lines && lines[line_i]; line_i++) {
-			const char *line = nm_str_skip_leading_spaces (lines[line_i]);
-			const char *p;
-
-			if (line[0] == '\0')
-				continue;
-
-			g_strchomp ((char *) line);
-
-			p = line;
-			if (in_req) {
-				/* pass */
-			} else if (strchr (p, '{')) {
-				if (   NM_STR_HAS_PREFIX (p, "lease")
-				    || NM_STR_HAS_PREFIX (p, "alias")
-				    || NM_STR_HAS_PREFIX (p, "interface")
-				    || NM_STR_HAS_PREFIX (p, "pseudo")) {
-					/* skip over these blocks, except 'interface' when it
-					 * matches the current interface */
-					blocks_skip++;
-					g_string_append_c (blocks_stack, 'b');
-					if (   !intf[0]
-					    && NM_STR_HAS_PREFIX (p, "interface")) {
-						if (read_interface (p, intf, sizeof (intf)))
-							continue;
-					}
-				} else {
-					/* allow other blocks (conditionals) */
-					if (!strchr (p, '}')) /* '} else {'  */
-						g_string_append_c (blocks_stack, 'c');
-				}
-			} else if (strchr (p, '}')) {
-				if (blocks_stack->len > 0) {
-					if (blocks_stack->str[blocks_stack->len - 1] == 'b') {
-						g_string_truncate (blocks_stack, blocks_stack->len - 1);
-						nm_assert(blocks_skip > 0);
-						blocks_skip--;
-						intf[0] = '\0';
-						continue;
-					}
-					g_string_truncate (blocks_stack, blocks_stack->len - 1);
-				}
-			}
-
-			if (blocks_skip > 0 && !intf[0])
-				continue;
-
-			if (intf[0] && !nm_streq (intf, interface))
-				continue;
-
-			/* Some timing parameters in dhclient should not be imported (timeout, retry).
-			 * The retry parameter will be simply not used as we will exit on first failure.
-			 * The timeout one instead may affect NetworkManager behavior: if the timeout
-			 * elapses before dhcp-timeout dhclient will report failure and cause NM to
-			 * fail the dhcp process before dhcp-timeout. So, always skip importing timeout
-			 * as we will need to add one greater than dhcp-timeout.
-			 */
-			if (   !strncmp (p, TIMEOUT_TAG, strlen (TIMEOUT_TAG))
-			    || !strncmp (p, RETRY_TAG, strlen (RETRY_TAG)))
-				continue;
-
-			if (!strncmp (p, CLIENTID_TAG, strlen (CLIENTID_TAG))) {
-				/* Override config file "dhcp-client-id" and use one from the connection */
-				if (client_id)
-					continue;
-
-				/* Otherwise capture and return the existing client id */
-				if (out_new_client_id)
-					nm_clear_pointer (out_new_client_id, g_bytes_unref);
-				NM_SET_OUT (out_new_client_id, read_client_id (p));
-			}
-
-			/* Override config file hostname and use one from the connection */
-			if (hostname) {
-				if (strncmp (p, HOSTNAME4_TAG, strlen (HOSTNAME4_TAG)) == 0)
-					continue;
-				if (strncmp (p, FQDN_TAG, strlen (FQDN_TAG)) == 0)
-					continue;
-			}
-
-			/* To let user's FQDN options (except "fqdn.fqdn") override the
-			 * default ones set by NM, add them later
-			 */
-			if (!strncmp (p, FQDN_TAG_PREFIX, NM_STRLEN (FQDN_TAG_PREFIX))) {
-				if (!fqdn_opts)
-					fqdn_opts = g_ptr_array_new_full (5, g_free);
-				g_ptr_array_add (fqdn_opts, g_strdup (p + NM_STRLEN (FQDN_TAG_PREFIX)));
-				continue;
-			}
-
-			/* Ignore 'script' since we pass our own */
-			if (g_str_has_prefix (p, "script "))
-				continue;
-
-			/* Check for "request" */
-			if (!strncmp (p, REQ_TAG, strlen (REQ_TAG))) {
-				in_req = TRUE;
-				p += strlen (REQ_TAG);
-				g_ptr_array_set_size (reqs, 0);
-				reset_reqlist = TRUE;
-			}
-
-			/* Save all request options for later use */
-			if (in_req) {
-				in_req = !grab_request_options (reqs, p);
-				continue;
-			}
-
-			/* Check for "also require" */
-			if (!strncmp (p, ALSOREQ_TAG, strlen (ALSOREQ_TAG))) {
-				in_alsoreq = TRUE;
-				p += strlen (ALSOREQ_TAG);
-			}
-
-			if (in_alsoreq) {
-				in_alsoreq = !grab_request_options (reqs, p);
-				continue;
-			}
-
-			/* Existing configuration line is OK, add it to new configuration */
-			g_string_append (new_contents, line);
-			g_string_append_c (new_contents, '\n');
-		}
-	} else
-		g_string_append_c (new_contents, '\n');
-
-	/* ensure dhclient timeout is greater than dhcp-timeout: as dhclient timeout default value is
-	 * 60 seconds, we need this only if dhcp-timeout is greater than 60.
-	 */
-	if (timeout >= 60) {
-		timeout = timeout < G_MAXINT32 ? timeout + 1 : G_MAXINT32;
-		g_string_append_printf (new_contents, "timeout %u;\n", timeout);
-	}
-
-	add_mud_url_config (new_contents, mud_url, addr_family);
-	if (addr_family == AF_INET) {
-		add_ip4_config (new_contents, client_id, hostname, use_fqdn, hostname_flags);
-		add_request (reqs, "rfc3442-classless-static-routes");
-		add_request (reqs, "ms-classless-static-routes");
-		add_request (reqs, "static-routes");
-		add_request (reqs, "wpad");
-		add_request (reqs, "ntp-servers");
-		add_request (reqs, "root-path");
-	} else {
-		add_hostname6 (new_contents, hostname, hostname_flags);
-		add_request (reqs, "dhcp6.name-servers");
-		add_request (reqs, "dhcp6.domain-search");
-
-		/* FIXME: internal client does not support requesting client-id option. Does this even work? */
-		add_request (reqs, "dhcp6.client-id");
-	}
-
-	if (reset_reqlist)
-		g_string_append (new_contents, "request; # override dhclient defaults\n");
-	/* And add it to the dhclient configuration */
-	for (i = 0; i < reqs->len; i++)
-		g_string_append_printf (new_contents, "also request %s;\n", (char *) reqs->pdata[i]);
-
-	if (fqdn_opts) {
-		for (i = 0; i < fqdn_opts->len; i++) {
-			const char *t = fqdn_opts->pdata[i];
-
-			if (i == 0)
-				g_string_append_printf (new_contents, "\n# FQDN options from %s\n", orig_path);
-			g_string_append_printf (new_contents, FQDN_TAG_PREFIX "%s\n", t);
-		}
-	}
-
-	g_string_append_c (new_contents, '\n');
-
-	if (anycast_addr) {
-		g_string_append_printf (new_contents, "interface \"%s\" {\n"
-		                        " initial-interval 1; \n"
-		                        " anycast-mac ethernet %s;\n"
-		                        "}\n",
-		                        interface, anycast_addr);
-	}
-
-	return g_string_free (g_steal_pointer (&new_contents), FALSE);
+    nm_auto_free_gstring GString *new_contents = NULL;
+    gs_unref_ptrarray GPtrArray *fqdn_opts     = NULL;
+    gs_unref_ptrarray GPtrArray *reqs          = NULL;
+    gboolean                     reset_reqlist = FALSE;
+    int                          i;
+
+    g_return_val_if_fail(!anycast_addr || nm_utils_hwaddr_valid(anycast_addr, ETH_ALEN), NULL);
+    g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), NULL);
+    g_return_val_if_fail(!reject_servers || addr_family == AF_INET, NULL);
+    nm_assert(!out_new_client_id || !*out_new_client_id);
+
+    new_contents = g_string_new(_("# Created by NetworkManager\n"));
+    reqs         = g_ptr_array_new_full(5, g_free);
+
+    if (orig_contents) {
+        gs_free const char **lines = NULL;
+        gsize                line_i;
+        nm_auto_free_gstring GString *blocks_stack = NULL;
+        guint                         blocks_skip  = 0;
+        gboolean                      in_alsoreq   = FALSE;
+        gboolean                      in_req       = FALSE;
+        char                          intf[IFNAMSIZ];
+
+        blocks_stack = g_string_new(NULL);
+        g_string_append_printf(new_contents, _("# Merged from %s\n\n"), orig_path);
+        intf[0] = '\0';
+
+        lines = nm_utils_strsplit_set(orig_contents, "\n\r");
+        for (line_i = 0; lines && lines[line_i]; line_i++) {
+            const char *line = nm_str_skip_leading_spaces(lines[line_i]);
+            const char *p;
+
+            if (line[0] == '\0')
+                continue;
+
+            g_strchomp((char *) line);
+
+            p = line;
+            if (in_req) {
+                /* pass */
+            } else if (strchr(p, '{')) {
+                if (NM_STR_HAS_PREFIX(p, "lease") || NM_STR_HAS_PREFIX(p, "alias")
+                    || NM_STR_HAS_PREFIX(p, "interface") || NM_STR_HAS_PREFIX(p, "pseudo")) {
+                    /* skip over these blocks, except 'interface' when it
+                     * matches the current interface */
+                    blocks_skip++;
+                    g_string_append_c(blocks_stack, 'b');
+                    if (!intf[0] && NM_STR_HAS_PREFIX(p, "interface")) {
+                        if (read_interface(p, intf, sizeof(intf)))
+                            continue;
+                    }
+                } else {
+                    /* allow other blocks (conditionals) */
+                    if (!strchr(p, '}')) /* '} else {'  */
+                        g_string_append_c(blocks_stack, 'c');
+                }
+            } else if (strchr(p, '}')) {
+                if (blocks_stack->len > 0) {
+                    if (blocks_stack->str[blocks_stack->len - 1] == 'b') {
+                        g_string_truncate(blocks_stack, blocks_stack->len - 1);
+                        nm_assert(blocks_skip > 0);
+                        blocks_skip--;
+                        intf[0] = '\0';
+                        continue;
+                    }
+                    g_string_truncate(blocks_stack, blocks_stack->len - 1);
+                }
+            }
+
+            if (blocks_skip > 0 && !intf[0])
+                continue;
+
+            if (intf[0] && !nm_streq(intf, interface))
+                continue;
+
+            /* Some timing parameters in dhclient should not be imported (timeout, retry).
+             * The retry parameter will be simply not used as we will exit on first failure.
+             * The timeout one instead may affect NetworkManager behavior: if the timeout
+             * elapses before dhcp-timeout dhclient will report failure and cause NM to
+             * fail the dhcp process before dhcp-timeout. So, always skip importing timeout
+             * as we will need to add one greater than dhcp-timeout.
+             */
+            if (!strncmp(p, TIMEOUT_TAG, strlen(TIMEOUT_TAG))
+                || !strncmp(p, RETRY_TAG, strlen(RETRY_TAG)))
+                continue;
+
+            if (!strncmp(p, CLIENTID_TAG, strlen(CLIENTID_TAG))) {
+                /* Override config file "dhcp-client-id" and use one from the connection */
+                if (client_id)
+                    continue;
+
+                /* Otherwise, capture and return the existing client id */
+                if (out_new_client_id)
+                    nm_clear_pointer(out_new_client_id, g_bytes_unref);
+                NM_SET_OUT(out_new_client_id, read_client_id(p));
+            }
+
+            /* Override config file hostname and use one from the connection */
+            if (hostname) {
+                if (strncmp(p, HOSTNAME4_TAG, strlen(HOSTNAME4_TAG)) == 0)
+                    continue;
+                if (strncmp(p, FQDN_TAG, strlen(FQDN_TAG)) == 0)
+                    continue;
+            }
+
+            /* To let user's FQDN options (except "fqdn.fqdn") override the
+             * default ones set by NM, add them later
+             */
+            if (!strncmp(p, FQDN_TAG_PREFIX, NM_STRLEN(FQDN_TAG_PREFIX))) {
+                if (!fqdn_opts)
+                    fqdn_opts = g_ptr_array_new_full(5, g_free);
+                g_ptr_array_add(fqdn_opts, g_strdup(p + NM_STRLEN(FQDN_TAG_PREFIX)));
+                continue;
+            }
+
+            /* Ignore 'script' since we pass our own */
+            if (g_str_has_prefix(p, "script "))
+                continue;
+
+            /* Check for "request" */
+            if (!strncmp(p, REQ_TAG, strlen(REQ_TAG))) {
+                in_req = TRUE;
+                p += strlen(REQ_TAG);
+                g_ptr_array_set_size(reqs, 0);
+                reset_reqlist = TRUE;
+            }
+
+            /* Save all request options for later use */
+            if (in_req) {
+                in_req = !grab_request_options(reqs, p);
+                continue;
+            }
+
+            /* Check for "also require" */
+            if (!strncmp(p, ALSOREQ_TAG, strlen(ALSOREQ_TAG))) {
+                in_alsoreq = TRUE;
+                p += strlen(ALSOREQ_TAG);
+            }
+
+            if (in_alsoreq) {
+                in_alsoreq = !grab_request_options(reqs, p);
+                continue;
+            }
+
+            /* Existing configuration line is OK, add it to new configuration */
+            g_string_append(new_contents, line);
+            g_string_append_c(new_contents, '\n');
+        }
+    } else
+        g_string_append_c(new_contents, '\n');
+
+    /* ensure dhclient timeout is greater than dhcp-timeout: as dhclient timeout default value is
+     * 60 seconds, we need this only if dhcp-timeout is greater than 60.
+     */
+    if (timeout >= 60) {
+        timeout = timeout < G_MAXINT32 ? timeout + 1 : G_MAXINT32;
+        g_string_append_printf(new_contents, "timeout %u;\n", timeout);
+    }
+
+    add_mud_url_config(new_contents, mud_url, addr_family);
+
+    if (reject_servers && reject_servers[0]) {
+        g_string_append(new_contents, "reject ");
+        for (i = 0; reject_servers[i]; i++) {
+            if (i != 0)
+                g_string_append(new_contents, ", ");
+            g_string_append(new_contents, reject_servers[i]);
+        }
+        g_string_append(new_contents, ";\n");
+    }
+
+    if (addr_family == AF_INET) {
+        add_ip4_config(new_contents, client_id, hostname, use_fqdn, hostname_flags);
+        add_request(reqs, "rfc3442-classless-static-routes");
+        add_request(reqs, "ms-classless-static-routes");
+        add_request(reqs, "static-routes");
+        add_request(reqs, "wpad");
+        add_request(reqs, "ntp-servers");
+        add_request(reqs, "root-path");
+    } else {
+        add_hostname6(new_contents, hostname, hostname_flags);
+        add_request(reqs, "dhcp6.name-servers");
+        add_request(reqs, "dhcp6.domain-search");
+
+        /* FIXME: internal client does not support requesting client-id option. Does this even work? */
+        add_request(reqs, "dhcp6.client-id");
+    }
+
+    if (reset_reqlist)
+        g_string_append(new_contents, "request; # override dhclient defaults\n");
+    /* And add it to the dhclient configuration */
+    for (i = 0; i < reqs->len; i++)
+        g_string_append_printf(new_contents, "also request %s;\n", (char *) reqs->pdata[i]);
+
+    if (fqdn_opts) {
+        for (i = 0; i < fqdn_opts->len; i++) {
+            const char *t = fqdn_opts->pdata[i];
+
+            if (i == 0)
+                g_string_append_printf(new_contents, "\n# FQDN options from %s\n", orig_path);
+            g_string_append_printf(new_contents, FQDN_TAG_PREFIX "%s\n", t);
+        }
+    }
+
+    g_string_append_c(new_contents, '\n');
+
+    if (anycast_addr) {
+        g_string_append_printf(new_contents,
+                               "interface \"%s\" {\n"
+                               " initial-interval 1; \n"
+                               " anycast-mac ethernet %s;\n"
+                               "}\n",
+                               interface,
+                               anycast_addr);
+    }
+
+    return g_string_free(g_steal_pointer(&new_contents), FALSE);
 }
 
 /* Roughly follow what dhclient's quotify_buf() and pretty_escape() functions do */
 char *
-nm_dhcp_dhclient_escape_duid (GBytes *duid)
+nm_dhcp_dhclient_escape_duid(GBytes *duid)
 {
-	char *escaped;
-	const guint8 *s, *s0;
-	gsize len;
-	char *d;
-
-	g_return_val_if_fail (duid, NULL);
-
-	s0 = g_bytes_get_data (duid, &len);
-	s = s0;
-
-	d = escaped = g_malloc ((len * 4) + 1);
-	while (s < (s0 + len)) {
-		if (!g_ascii_isprint (*s)) {
-			*d++ = '\\';
-			*d++ = '0' + ((*s >> 6) & 0x7);
-			*d++ = '0' + ((*s >> 3) & 0x7);
-			*d++ = '0' + (*s++ & 0x7);
-		} else if (*s == '"' || *s == '\'' || *s == '$' ||
-		           *s == '`' || *s == '\\' || *s == '|' ||
-		           *s == '&') {
-			*d++ = '\\';
-			*d++ = *s++;
-		} else
-			*d++ = *s++;
-	}
-	*d++ = '\0';
-	return escaped;
+    char *        escaped;
+    const guint8 *s, *s0;
+    gsize         len;
+    char *        d;
+
+    g_return_val_if_fail(duid, NULL);
+
+    s0 = g_bytes_get_data(duid, &len);
+    s  = s0;
+
+    d = escaped = g_malloc((len * 4) + 1);
+    while (s < (s0 + len)) {
+        if (!g_ascii_isprint(*s)) {
+            *d++ = '\\';
+            *d++ = '0' + ((*s >> 6) & 0x7);
+            *d++ = '0' + ((*s >> 3) & 0x7);
+            *d++ = '0' + (*s++ & 0x7);
+        } else if (*s == '"' || *s == '\'' || *s == '$' || *s == '`' || *s == '\\' || *s == '|'
+                   || *s == '&') {
+            *d++ = '\\';
+            *d++ = *s++;
+        } else
+            *d++ = *s++;
+    }
+    *d++ = '\0';
+    return escaped;
 }
 
 static gboolean
-isoctal (const guint8 *p)
+isoctal(const guint8 *p)
 {
-	return (   p[0] >= '0' && p[0] <= '3'
-	        && p[1] >= '0' && p[1] <= '7'
-	        && p[2] >= '0' && p[2] <= '7');
+    return (p[0] >= '0' && p[0] <= '3' && p[1] >= '0' && p[1] <= '7' && p[2] >= '0' && p[2] <= '7');
 }
 
 GBytes *
-nm_dhcp_dhclient_unescape_duid (const char *duid)
+nm_dhcp_dhclient_unescape_duid(const char *duid)
 {
-	GByteArray *unescaped;
-	const guint8 *p = (const guint8 *) duid;
-	guint i, len;
-	guint8 octal;
-
-	/* FIXME: it's wrong to have an "unescape-duid" function. dhclient
-	 * defines a file format with escaping. So we need a general unescape
-	 * function that can handle dhclient syntax. */
-
-	len = strlen (duid);
-	unescaped = g_byte_array_sized_new (len);
-	for (i = 0; i < len; i++) {
-		if (p[i] == '\\') {
-			i++;
-			if (isdigit (p[i])) {
-				/* Octal escape sequence */
-				if (i + 2 >= len || !isoctal (p + i))
-					goto error;
-				octal = ((p[i] - '0') << 6) + ((p[i + 1] - '0') << 3) + (p[i + 2] - '0');
-				g_byte_array_append (unescaped, &octal, 1);
-				i += 2;
-			} else {
-				/* FIXME: don't warn on untrusted data. Either signal an error, or accept
-				 * it silently. */
-
-				/* One of ", ', $, `, \, |, or & */
-				g_warn_if_fail (p[i] == '"' || p[i] == '\'' || p[i] == '$' ||
-				                p[i] == '`' || p[i] == '\\' || p[i] == '|' ||
-				                p[i] == '&');
-				g_byte_array_append (unescaped, &p[i], 1);
-			}
-		} else
-			g_byte_array_append (unescaped, &p[i], 1);
-	}
-
-	return g_byte_array_free_to_bytes (unescaped);
+    GByteArray *  unescaped;
+    const guint8 *p = (const guint8 *) duid;
+    guint         i, len;
+    guint8        octal;
+
+    /* FIXME: it's wrong to have an "unescape-duid" function. dhclient
+     * defines a file format with escaping. So we need a general unescape
+     * function that can handle dhclient syntax. */
+
+    len       = strlen(duid);
+    unescaped = g_byte_array_sized_new(len);
+    for (i = 0; i < len; i++) {
+        if (p[i] == '\\') {
+            i++;
+            if (isdigit(p[i])) {
+                /* Octal escape sequence */
+                if (i + 2 >= len || !isoctal(p + i))
+                    goto error;
+                octal = ((p[i] - '0') << 6) + ((p[i + 1] - '0') << 3) + (p[i + 2] - '0');
+                g_byte_array_append(unescaped, &octal, 1);
+                i += 2;
+            } else {
+                /* FIXME: don't warn on untrusted data. Either signal an error, or accept
+                 * it silently. */
+
+                /* One of ", ', $, `, \, |, or & */
+                g_warn_if_fail(p[i] == '"' || p[i] == '\'' || p[i] == '$' || p[i] == '`'
+                               || p[i] == '\\' || p[i] == '|' || p[i] == '&');
+                g_byte_array_append(unescaped, &p[i], 1);
+            }
+        } else
+            g_byte_array_append(unescaped, &p[i], 1);
+    }
+
+    return g_byte_array_free_to_bytes(unescaped);
 
 error:
-	g_byte_array_free (unescaped, TRUE);
-	return NULL;
+    g_byte_array_free(unescaped, TRUE);
+    return NULL;
 }
 
 #define DUID_PREFIX "default-duid \""
 
 /* Beware: @error may be unset even if the function returns %NULL. */
 GBytes *
-nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
+nm_dhcp_dhclient_read_duid(const char *leasefile, GError **error)
 {
-	gs_free char *contents = NULL;
-	gs_free const char **contents_v = NULL;
-	gsize i;
+    gs_free char *       contents   = NULL;
+    gs_free const char **contents_v = NULL;
+    gsize                i;
 
-	if (!g_file_test (leasefile, G_FILE_TEST_EXISTS))
-		return NULL;
+    if (!g_file_test(leasefile, G_FILE_TEST_EXISTS))
+        return NULL;
 
-	if (!g_file_get_contents (leasefile, &contents, NULL, error))
-		return NULL;
+    if (!g_file_get_contents(leasefile, &contents, NULL, error))
+        return NULL;
 
-	contents_v = nm_utils_strsplit_set (contents, "\n\r");
-	for (i = 0; contents_v && contents_v[i]; i++) {
-		const char *p = nm_str_skip_leading_spaces (contents_v[i]);
-		GBytes *duid;
+    contents_v = nm_utils_strsplit_set(contents, "\n\r");
+    for (i = 0; contents_v && contents_v[i]; i++) {
+        const char *p = nm_str_skip_leading_spaces(contents_v[i]);
+        GBytes *    duid;
 
-		if (!NM_STR_HAS_PREFIX (p, DUID_PREFIX))
-			continue;
+        if (!NM_STR_HAS_PREFIX(p, DUID_PREFIX))
+            continue;
 
-		p += NM_STRLEN (DUID_PREFIX);
+        p += NM_STRLEN(DUID_PREFIX);
 
-		g_strchomp ((char *) p);
+        g_strchomp((char *) p);
 
-		if (!NM_STR_HAS_SUFFIX (p, "\";"))
-			continue;
+        if (!NM_STR_HAS_SUFFIX(p, "\";"))
+            continue;
 
-		((char *) p)[strlen (p) - 2] = '\0';
+        ((char *) p)[strlen(p) - 2] = '\0';
 
-		duid = nm_dhcp_dhclient_unescape_duid (p);
-		if (duid)
-			return duid;
-	}
+        duid = nm_dhcp_dhclient_unescape_duid(p);
+        if (duid)
+            return duid;
+    }
 
-	return NULL;
+    return NULL;
 }
 
 gboolean
-nm_dhcp_dhclient_save_duid (const char *leasefile,
-                            GBytes *duid,
-                            GError **error)
+nm_dhcp_dhclient_save_duid(const char *leasefile, GBytes *duid, GError **error)
 {
-	gs_free char *escaped_duid = NULL;
-	gs_free const char **lines = NULL;
-	nm_auto_free_gstring GString *s = NULL;
-	const char *const*iter;
-	gsize len = 0;
-
-	g_return_val_if_fail (leasefile != NULL, FALSE);
-	if (!duid) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
-		                            "missing duid");
-		g_return_val_if_reached (FALSE);
-	}
-
-	escaped_duid = nm_dhcp_dhclient_escape_duid (duid);
-	nm_assert (escaped_duid);
-
-	if (g_file_test (leasefile, G_FILE_TEST_EXISTS)) {
-		gs_free char *contents = NULL;
-
-		if (!g_file_get_contents (leasefile, &contents, &len, error)) {
-			g_prefix_error (error, "failed to read lease file %s: ", leasefile);
-			return FALSE;
-		}
-
-		lines = nm_utils_strsplit_set_with_empty (contents, "\n\r");
-	}
-
-	s = g_string_sized_new (len + 50);
-	g_string_append_printf (s, DUID_PREFIX "%s\";\n", escaped_duid);
-
-	/* Preserve existing leasefile contents */
-	if (lines) {
-		for (iter = lines; *iter; iter++) {
-			const char *str = *iter;
-			const char *l;
-
-			/* If we find an uncommented DUID in the file, check if
-			 * equal to the one we are going to write: if so, no need
-			 * to update the lease file, otherwise skip the old DUID.
-			 */
-			l = nm_str_skip_leading_spaces (str);
-			if (g_str_has_prefix (l, DUID_PREFIX)) {
-				gs_strfreev char **split = NULL;
-
-				split = g_strsplit (l, "\"", -1);
-				if (   split[0]
-				    && nm_streq0 (split[1], escaped_duid))
-					return TRUE;
-
-				continue;
-			}
-
-			if (str)
-				g_string_append (s, str);
-			/* avoid to add an extra '\n' at the end of file */
-			if ((iter[1]) != NULL)
-				g_string_append_c (s, '\n');
-		}
-	}
-
-	if (!g_file_set_contents (leasefile, s->str, -1, error)) {
-		g_prefix_error (error, "failed to set DUID in lease file %s: ", leasefile);
-		return FALSE;
-	}
-
-	return TRUE;
+    gs_free char *       escaped_duid = NULL;
+    gs_free const char **lines        = NULL;
+    nm_auto_free_gstring GString *s   = NULL;
+    const char *const *           iter;
+    gsize                         len = 0;
+
+    g_return_val_if_fail(leasefile != NULL, FALSE);
+    if (!duid) {
+        nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "missing duid");
+        g_return_val_if_reached(FALSE);
+    }
+
+    escaped_duid = nm_dhcp_dhclient_escape_duid(duid);
+    nm_assert(escaped_duid);
+
+    if (g_file_test(leasefile, G_FILE_TEST_EXISTS)) {
+        gs_free char *contents = NULL;
+
+        if (!g_file_get_contents(leasefile, &contents, &len, error)) {
+            g_prefix_error(error, "failed to read lease file %s: ", leasefile);
+            return FALSE;
+        }
+
+        lines = nm_utils_strsplit_set_with_empty(contents, "\n\r");
+    }
+
+    s = g_string_sized_new(len + 50);
+    g_string_append_printf(s, DUID_PREFIX "%s\";\n", escaped_duid);
+
+    /* Preserve existing leasefile contents */
+    if (lines) {
+        for (iter = lines; *iter; iter++) {
+            const char *str = *iter;
+            const char *l;
+
+            /* If we find an uncommented DUID in the file, check if
+             * equal to the one we are going to write: if so, no need
+             * to update the lease file, otherwise skip the old DUID.
+             */
+            l = nm_str_skip_leading_spaces(str);
+            if (g_str_has_prefix(l, DUID_PREFIX)) {
+                gs_strfreev char **split = NULL;
+
+                split = g_strsplit(l, "\"", -1);
+                if (split[0] && nm_streq0(split[1], escaped_duid))
+                    return TRUE;
+
+                continue;
+            }
+
+            if (str)
+                g_string_append(s, str);
+            /* avoid to add an extra '\n' at the end of file */
+            if ((iter[1]) != NULL)
+                g_string_append_c(s, '\n');
+        }
+    }
+
+    if (!g_file_set_contents(leasefile, s->str, -1, error)) {
+        g_prefix_error(error, "failed to set DUID in lease file %s: ", leasefile);
+        return FALSE;
+    }
+
+    return TRUE;
 }