summary refs log tree commit diff
path: root/src/dhcp/nm-dhcp-dhclient-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dhcp/nm-dhcp-dhclient-utils.c')
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c330
1 files changed, 273 insertions, 57 deletions
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 52923310..4df90d76 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -125,7 +125,7 @@ add_ip4_config (GString *str, GBytes *client_id, const char *hostname, gboolean
 		 * 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] == '"')
+			if (!g_ascii_isprint (p[i]))
 				break;
 		}
 
@@ -138,9 +138,8 @@ add_ip4_config (GString *str, GBytes *client_id, const char *hostname, gboolean
 				g_string_append_printf (str, "%02x", (guint8) p[i]);
 			}
 		} else {
-			/* Printable; just add to the line with type 0 */
+			/* Printable; just add to the line minus the 'type' */
 			g_string_append_c (str, '"');
-			g_string_append (str, "\\x00");
 			g_string_append_len (str, p + 1, l - 1);
 			g_string_append_c (str, '"');
 		}
@@ -178,60 +177,31 @@ 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;
+	str += NM_STRLEN (CLIENTID_TAG);
 	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;
+	} else
+		s = g_strdup (str);
 
-		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);
+	if (!s[0])
+		return NULL;
+
+	return nm_dhcp_utils_client_id_string_to_bytes (s);
 }
 
 GBytes *
@@ -309,7 +279,6 @@ nm_dhcp_dhclient_create_config (const char *interface,
 
 	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"));
 	fqdn_opts = g_ptr_array_sized_new (5);
@@ -363,8 +332,6 @@ nm_dhcp_dhclient_create_config (const char *interface,
 					continue;
 
 				/* Otherwise capture and return the existing client id */
-				if (out_new_client_id)
-					g_clear_pointer (out_new_client_id, g_bytes_unref);
 				NM_SET_OUT (out_new_client_id, read_client_id (p));
 			}
 
@@ -477,20 +444,14 @@ nm_dhcp_dhclient_create_config (const char *interface,
 
 /* 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 (const GByteArray *duid)
 {
 	char *escaped;
-	const guint8 *s, *s0;
-	gsize len;
+	const guint8 *s = duid->data;
 	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)) {
+	d = escaped = g_malloc0 ((duid->len * 4) + 1);
+	while (s < (duid->data + duid->len)) {
 		if (!g_ascii_isprint (*s)) {
 			*d++ = '\\';
 			*d++ = '0' + ((*s >> 6) & 0x7);
@@ -504,7 +465,6 @@ nm_dhcp_dhclient_escape_duid (GBytes *duid)
 		} else
 			*d++ = *s++;
 	}
-	*d++ = '\0';
 	return escaped;
 }
 
@@ -516,7 +476,7 @@ isoctal (const guint8 *p)
 	        && p[2] >= '0' && p[2] <= '7');
 }
 
-GBytes *
+GByteArray *
 nm_dhcp_dhclient_unescape_duid (const char *duid)
 {
 	GByteArray *unescaped;
@@ -547,7 +507,7 @@ nm_dhcp_dhclient_unescape_duid (const char *duid)
 			g_byte_array_append (unescaped, &p[i], 1);
 	}
 
-	return g_byte_array_free_to_bytes (unescaped);
+	return unescaped;
 
 error:
 	g_byte_array_free (unescaped, TRUE);
@@ -556,10 +516,10 @@ error:
 
 #define DUID_PREFIX "default-duid \""
 
-GBytes *
+GByteArray *
 nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 {
-	GBytes *duid = NULL;
+	GByteArray *duid = NULL;
 	char *contents;
 	char **line, **split, *p, *e;
 
@@ -643,3 +603,259 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
 	g_string_free (s, TRUE);
 	return success;
 }
+
+static void
+add_lease_option (GHashTable *hash, char *line)
+{
+	char *spc;
+	size_t len;
+
+	/* Find the space after "option" */
+	spc = strchr (line, ' ');
+	if (!spc)
+		return;
+
+	/* Find the option tag's data, which is after the second space */
+	if (g_str_has_prefix (line, "option ")) {
+		while (g_ascii_isspace (*spc))
+			spc++;
+		spc = strchr (spc + 1, ' ');
+		if (!spc)
+			return;
+	}
+
+	/* Split the line at the space */
+	*spc = '\0';
+	spc++;
+
+	/* Kill the ';' at the end of the line, if any */
+	len = strlen (spc);
+	if (*(spc + len - 1) == ';')
+		*(spc + len - 1) = '\0';
+
+	/* Strip leading quote */
+	while (g_ascii_isspace (*spc))
+		spc++;
+	if (*spc == '"')
+		spc++;
+
+	/* Strip trailing quote */
+	len = strlen (spc);
+	if (len > 0 && spc[len - 1] == '"')
+		spc[len - 1] = '\0';
+
+	if (spc[0])
+		g_hash_table_insert (hash, g_strdup (line), g_strdup (spc));
+}
+
+#define LEASE_INVALID    G_MININT64
+static GTimeSpan
+lease_validity_span (const char *str_expire, GDateTime *now)
+{
+	GDateTime *expire = NULL;
+	struct tm expire_tm;
+	GTimeSpan span;
+
+	g_return_val_if_fail (now != NULL, LEASE_INVALID);
+	g_return_val_if_fail (str_expire != NULL, LEASE_INVALID);
+
+	/* Skip initial number (day of week?) */
+	if (!isdigit (*str_expire++))
+		return LEASE_INVALID;
+	if (!isspace (*str_expire++))
+		return LEASE_INVALID;
+	/* Read lease expiration (in UTC) */
+	if (!strptime (str_expire, "%t%Y/%m/%d %H:%M:%S", &expire_tm))
+		return LEASE_INVALID;
+
+	expire = g_date_time_new_utc (expire_tm.tm_year + 1900,
+	                              expire_tm.tm_mon + 1,
+	                              expire_tm.tm_mday,
+	                              expire_tm.tm_hour,
+	                              expire_tm.tm_min,
+	                              expire_tm.tm_sec);
+	if (!expire)
+		return LEASE_INVALID;
+
+	span = g_date_time_difference (expire, now);
+	g_date_time_unref (expire);
+
+	/* GDateTime only supports a range of less then 10000 years, so span can
+	 * not overflow or be equal to LEASE_INVALID */
+	return span;
+}
+
+/**
+ * nm_dhcp_dhclient_read_lease_ip_configs:
+ * @multi_idx: the multi index instance for the ip config object
+ * @addr_family: whether to read IPv4 or IPv6 leases
+ * @iface: the interface name to match leases with
+ * @ifindex: interface index of @iface
+ * @route_table: the route table for the default route.
+ * @route_metric: the route metric for the default route.
+ * @contents: the contents of a dhclient leasefile
+ * @now: the current UTC date/time; pass %NULL to automatically use current
+ *  UTC time.  Testcases may need a different value for 'now'
+ *
+ * Reads dhclient leases from @contents and parses them into either
+ * #NMIP4Config or #NMIP6Config objects depending on the value of @addr_family.
+ *
+ * Returns: a #GSList of #NMIP4Config objects (if @addr_family is %AF_INET) or a list of
+ * #NMIP6Config objects (if @addr_family is %AF_INET6) containing the lease data.
+ */
+GSList *
+nm_dhcp_dhclient_read_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                        int addr_family,
+                                        const char *iface,
+                                        int ifindex,
+                                        guint32 route_table,
+                                        guint32 route_metric,
+                                        const char *contents,
+                                        GDateTime *now)
+{
+	GSList *parsed = NULL, *iter, *leases = NULL;
+	char **line, **split = NULL;
+	GHashTable *hash = NULL;
+	gint32 now_monotonic_ts;
+
+	g_return_val_if_fail (contents != NULL, NULL);
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+	split = g_strsplit_set (contents, "\n\r", -1);
+	if (!split)
+		return NULL;
+
+	for (line = split; line && *line; line++) {
+		*line = g_strstrip (*line);
+
+		if (*line[0] == '#') {
+			/* Comment */
+		} else if (!strcmp (*line, "}")) {
+			/* Lease ends */
+			parsed = g_slist_append (parsed, hash);
+			hash = NULL;
+		} else if (!strcmp (*line, "lease {")) {
+			/* Beginning of a new lease */
+			if (hash) {
+				/* Ignore malformed lease that doesn't end before new one starts */
+				g_hash_table_destroy (hash);
+			}
+
+			hash = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
+		} else if (hash && strlen (*line))
+			add_lease_option (hash, *line);
+	}
+	g_strfreev (split);
+
+	/* Check if the last lease in the file was properly ended */
+	if (hash) {
+		/* Ignore malformed lease that doesn't end before new one starts */
+		g_hash_table_destroy (hash);
+		hash = NULL;
+	}
+
+	if (now)
+		g_date_time_ref (now);
+	else
+		now = g_date_time_new_now_utc ();
+	now_monotonic_ts = nm_utils_get_monotonic_timestamp_s ();
+
+	for (iter = parsed; iter; iter = g_slist_next (iter)) {
+		NMIP4Config *ip4;
+		NMPlatformIP4Address address;
+		const char *value;
+		GTimeSpan expiry;
+		guint32 tmp, gw = 0;
+
+		hash = iter->data;
+
+		/* Make sure this lease is for the interface we want */
+		value = g_hash_table_lookup (hash, "interface");
+		if (!value || strcmp (value, iface))
+			continue;
+
+		value = g_hash_table_lookup (hash, "expire");
+		if (!value)
+			continue;
+		expiry = lease_validity_span (value, now);
+		if (expiry == LEASE_INVALID)
+			continue;
+
+		/* scale expiry to seconds (and CLAMP into the range of guint32) */
+		expiry = CLAMP (expiry / G_TIME_SPAN_SECOND, 0, NM_PLATFORM_LIFETIME_PERMANENT-1);
+		if (expiry <= 0) {
+			/* the address is already expired. Don't even add it. */
+			continue;
+		}
+
+		memset (&address, 0, sizeof (address));
+
+		/* IP4 address */
+		value = g_hash_table_lookup (hash, "fixed-address");
+		if (!value)
+			continue;
+		if (!inet_pton (AF_INET, value, &address.address))
+			continue;
+		address.peer_address = address.address;
+
+		/* Gateway */
+		value = g_hash_table_lookup (hash, "option routers");
+		if (!value)
+			continue;
+		if (!inet_pton (AF_INET, value, &gw))
+			continue;
+
+		/* Netmask */
+		value = g_hash_table_lookup (hash, "option subnet-mask");
+		if (value && inet_pton (AF_INET, value, &tmp))
+			address.plen = nm_utils_ip4_netmask_to_prefix (tmp);
+
+		/* Get default netmask for the IP according to appropriate class. */
+		if (!address.plen)
+			address.plen = _nm_utils_ip4_get_default_prefix (address.address);
+
+		address.timestamp = now_monotonic_ts;
+		address.lifetime = address.preferred = expiry;
+		address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
+
+		ip4 = nm_ip4_config_new (multi_idx, ifindex);
+		nm_ip4_config_add_address (ip4, &address);
+
+		{
+			const NMPlatformIP4Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
+				.gateway = gw,
+				.table_coerced = nm_platform_route_table_coerce (route_table),
+				.metric = route_metric,
+			};
+
+			nm_ip4_config_add_route (ip4, &r, NULL);
+		}
+
+		value = g_hash_table_lookup (hash, "option domain-name-servers");
+		if (value) {
+			char **dns, **dns_iter;
+
+			dns = g_strsplit_set (value, ",", -1);
+			for (dns_iter = dns; dns_iter && *dns_iter; dns_iter++) {
+				if (inet_pton (AF_INET, *dns_iter, &tmp))
+					nm_ip4_config_add_nameserver (ip4, tmp);
+			}
+			if (dns)
+				g_strfreev (dns);
+		}
+
+		value = g_hash_table_lookup (hash, "option domain-name");
+		if (value && value[0])
+			nm_ip4_config_add_domain (ip4, value);
+
+		/* FIXME: static routes */
+
+		leases = g_slist_append (leases, ip4);
+	}
+
+	g_date_time_unref (now);
+	g_slist_free_full (parsed, (GDestroyNotify) g_hash_table_destroy);
+	return leases;
+}
+