diff options
Diffstat (limited to 'src/nm-core-utils.c')
| -rw-r--r-- | src/nm-core-utils.c | 526 |
1 files changed, 461 insertions, 65 deletions
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c index e9365c0c..6dfbb4ce 100644 --- a/src/nm-core-utils.c +++ b/src/nm-core-utils.c @@ -26,6 +26,7 @@ #include <errno.h> #include <fcntl.h> #include <string.h> +#include <poll.h> #include <unistd.h> #include <stdlib.h> #include <resolv.h> @@ -54,7 +55,7 @@ #endif G_STATIC_ASSERT (sizeof (NMUtilsTestFlags) <= sizeof (int)); -int _nm_utils_testing = 0; +static int _nm_utils_testing = 0; gboolean nm_utils_get_testing_initialized () @@ -199,6 +200,27 @@ nm_ethernet_address_is_valid (gconstpointer addr, gssize len) return TRUE; } +gconstpointer +nm_utils_ipx_address_clear_host_address (int family, gpointer dst, gconstpointer src, guint8 plen) +{ + g_return_val_if_fail (src, NULL); + g_return_val_if_fail (dst, NULL); + + switch (family) { + case AF_INET: + g_return_val_if_fail (plen <= 32, NULL); + *((guint32 *) dst) = nm_utils_ip4_address_clear_host_address (*((guint32 *) src), plen); + break; + case AF_INET6: + g_return_val_if_fail (plen <= 128, NULL); + nm_utils_ip6_address_clear_host_address (dst, src, plen); + break; + default: + g_return_val_if_reached (NULL); + } + return dst; +} + /* nm_utils_ip4_address_clear_host_address: * @addr: source ip6 address * @plen: prefix length of network @@ -445,8 +467,7 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid) gs_strfreev gchar **tokens = NULL; guint num_tokens; gchar *p; - gchar *endp; - char state = '\0'; + char state = ' '; gint64 ppid = 0; start_time = 0; @@ -457,7 +478,7 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid) nm_sprintf_buf (filename, "/proc/%"G_GUINT64_FORMAT"/stat", (guint64) pid); if (!g_file_get_contents (filename, &contents, &length, NULL)) - goto out; + goto fail; /* start time is the token at index 19 after the '(process name)' entry - since only this * field can contain the ')' character, search backwards for this to avoid malicious @@ -465,10 +486,10 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid) */ p = strrchr (contents, ')'); if (p == NULL) - goto out; + goto fail; p += 2; /* skip ') ' */ if (p - contents >= (int) length) - goto out; + goto fail; state = p[0]; @@ -477,23 +498,26 @@ nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid) num_tokens = g_strv_length (tokens); if (num_tokens < 20) - goto out; + goto fail; - if (out_ppid) + if (out_ppid) { ppid = _nm_utils_ascii_str_to_int64 (tokens[1], 10, 1, G_MAXINT, 0); + if (ppid == 0) + goto fail; + } - errno = 0; - start_time = strtoull (tokens[19], &endp, 10); - if (*endp != '\0' || errno != 0) - start_time = 0; - -out: - if (out_state) - *out_state = state; - if (out_ppid) - *out_ppid = ppid; + start_time = _nm_utils_ascii_str_to_int64 (tokens[19], 10, 1, G_MAXINT64, 0); + if (start_time == 0) + goto fail; + NM_SET_OUT (out_state, state); + NM_SET_OUT (out_ppid, ppid); return start_time; + +fail: + NM_SET_OUT (out_state, ' '); + NM_SET_OUT (out_ppid, 0); + return 0; } /******************************************************************************************/ @@ -1248,8 +1272,10 @@ nm_match_spec_hwaddr (const GSList *specs, const char *hwaddr) { const GSList *iter; NMMatchSpecMatchType match = NM_MATCH_SPEC_NO_MATCH; + guint hwaddr_len = 0; + guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX]; - g_return_val_if_fail (hwaddr != NULL, NM_MATCH_SPEC_NO_MATCH); + nm_assert (nm_utils_hwaddr_valid (hwaddr, -1)); for (iter = specs; iter; iter = g_slist_next (iter)) { const char *spec_str = iter->data; @@ -1270,7 +1296,15 @@ nm_match_spec_hwaddr (const GSList *specs, const char *hwaddr) else if (except) continue; - if (nm_utils_hwaddr_matches (spec_str, -1, hwaddr, -1)) { + if (G_UNLIKELY (hwaddr_len == 0)) { + hwaddr_len = _nm_utils_hwaddr_length (hwaddr); + if (!hwaddr_len) + g_return_val_if_reached (NM_MATCH_SPEC_NO_MATCH); + if (!nm_utils_hwaddr_aton (hwaddr, hwaddr_bin, hwaddr_len)) + nm_assert_not_reached (); + } + + if (nm_utils_hwaddr_matches (spec_str, -1, hwaddr_bin, hwaddr_len)) { if (except) return NM_MATCH_SPEC_NEG_MATCH; match = NM_MATCH_SPEC_MATCH; @@ -1900,6 +1934,33 @@ nm_utils_new_vlan_name (const char *parent_iface, guint32 vlan_id) return ifname; } +/* nm_utils_new_infiniband_name: + * @name: the output-buffer where the value will be written. Must be + * not %NULL and point to a string buffer of at least IFNAMSIZ bytes. + * @parent_name: the parent interface name + * @p_key: the partition key. + * + * Returns: the infiniband name will be written to @name and @name + * is returned. + */ +const char * +nm_utils_new_infiniband_name (char *name, const char *parent_name, int p_key) +{ + g_return_val_if_fail (name, NULL); + g_return_val_if_fail (parent_name && parent_name[0], NULL); + g_return_val_if_fail (strlen (parent_name) < IFNAMSIZ, NULL); + + /* technically, p_key of 0x0000 and 0x8000 is not allowed either. But we don't + * want to assert against that in nm_utils_new_infiniband_name(). So be more + * resilient here, and accept those. */ + g_return_val_if_fail (p_key >= 0 && p_key <= 0xffff, NULL); + + /* If parent+suffix is too long, kernel would just truncate + * the name. We do the same. See ipoib_vlan_add(). */ + g_snprintf (name, IFNAMSIZ, "%s.%04x", parent_name, p_key); + return name; +} + /** * nm_utils_read_resolv_conf_nameservers(): * @rc_contents: contents of a resolv.conf; or %NULL to read /etc/resolv.conf @@ -2651,6 +2712,123 @@ nm_utils_machine_id_read (void) /*****************************************************************************/ +/* taken from systemd's fd_wait_for_event(). Note that the timeout + * is here in nano-seconds, not micro-seconds. */ +int +nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns) +{ + struct pollfd pollfd = { + .fd = fd, + .events = event, + }; + struct timespec ts, *pts; + int r; + + if (timeout_ns < 0) + pts = NULL; + else { + ts.tv_sec = (time_t) (timeout_ns / NM_UTILS_NS_PER_SECOND); + ts.tv_nsec = (long int) (timeout_ns % NM_UTILS_NS_PER_SECOND); + pts = &ts; + } + + r = ppoll (&pollfd, 1, pts, NULL); + if (r < 0) + return -errno; + if (r == 0) + return 0; + return pollfd.revents; +} + +/* taken from systemd's loop_read() */ +ssize_t +nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll) +{ + uint8_t *p = buf; + ssize_t n = 0; + + g_return_val_if_fail (fd >= 0, -EINVAL); + g_return_val_if_fail (buf, -EINVAL); + + /* If called with nbytes == 0, let's call read() at least + * once, to validate the operation */ + + if (nbytes > (size_t) SSIZE_MAX) + return -EINVAL; + + do { + ssize_t k; + + k = read (fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN && do_poll) { + + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ + + (void) nm_utils_fd_wait_for_event (fd, POLLIN, -1); + continue; + } + + return n > 0 ? n : -errno; + } + + if (k == 0) + return n; + + g_assert ((size_t) k <= nbytes); + + p += k; + nbytes -= k; + n += k; + } while (nbytes > 0); + + return n; +} + +/* taken from systemd's loop_read_exact() */ +int +nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll) +{ + ssize_t n; + + n = nm_utils_fd_read_loop (fd, buf, nbytes, do_poll); + if (n < 0) + return (int) n; + if ((size_t) n != nbytes) + return -EIO; + + return 0; +} + +/* taken from systemd's dev_urandom(). */ +int +nm_utils_read_urandom (void *p, size_t nbytes) +{ + int fd = -1; + int r; + +again: + fd = open ("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + r = errno; + if (r == EINTR) + goto again; + return r == ENOENT ? -ENOSYS : -r; + } + + r = nm_utils_fd_read_loop_exact (fd, p, nbytes, TRUE); + close (fd); + + return r; +} + +/*****************************************************************************/ + guint8 * nm_utils_secret_key_read (gsize *out_key_len, GError **error) { @@ -2669,34 +2847,28 @@ nm_utils_secret_key_read (gsize *out_key_len, GError **error) key_len = 0; } } else { - int urandom = open ("/dev/urandom", O_RDONLY); + int r; mode_t key_mask; - if (urandom == -1) { - g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, - "Can't open /dev/urandom: %s", strerror (errno)); - key_len = 0; - goto out; - } - /* RFC7217 mandates the key SHOULD be at least 128 bits. * Let's use twice as much. */ key_len = 32; secret_key = g_malloc (key_len); + r = nm_utils_read_urandom (secret_key, key_len); + if (r < 0) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "Can't read /dev/urandom: %s", strerror (-r)); + key_len = 0; + goto out; + } + key_mask = umask (0077); - if (read (urandom, secret_key, key_len) == key_len) { - if (!g_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, error)) { - g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key: "); - key_len = 0; - } - } else { - g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, - "Could not obtain a secret"); + if (!g_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, error)) { + g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key: "); key_len = 0; } umask (key_mask); - close (urandom); } out: @@ -2816,24 +2988,97 @@ nm_utils_get_ipv6_interface_identifier (NMLinkType link_type, } return FALSE; } + +/*****************************************************************************/ + +/** + * nm_utils_ipv6_addr_set_interface_identifier: + * @addr: output token encoded as %in6_addr + * @iid: %NMUtilsIPv6IfaceId interface identifier + * + * Converts the %NMUtilsIPv6IfaceId to an %in6_addr (suitable for use + * with Linux platform). This only copies the lower 8 bytes, ignoring + * the /64 network prefix which is expected to be all-zero for a valid + * token. + */ void -nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr, +nm_utils_ipv6_addr_set_interface_identifier (struct in6_addr *addr, const NMUtilsIPv6IfaceId iid) { memcpy (addr->s6_addr + 8, &iid.id_u8, 8); } +/** + * nm_utils_ipv6_interface_identifier_get_from_addr: + * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token + * @addr: token encoded as %in6_addr + * + * Converts the %in6_addr encoded token (as used by Linux platform) to + * the interface identifier. + */ void -nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid, +nm_utils_ipv6_interface_identifier_get_from_addr (NMUtilsIPv6IfaceId *iid, const struct in6_addr *addr) { memcpy (iid, addr->s6_addr + 8, 8); } +/** + * nm_utils_ipv6_interface_identifier_get_from_token: + * @iid: output %NMUtilsIPv6IfaceId interface identifier set from the token + * @token: token encoded as string + * + * Converts the %in6_addr encoded token (as used in ip6 settings) to + * the interface identifier. + * + * Returns: %TRUE if the @token is a valid token, %FALSE otherwise + */ +gboolean +nm_utils_ipv6_interface_identifier_get_from_token (NMUtilsIPv6IfaceId *iid, + const char *token) +{ + struct in6_addr i6_token; + + g_return_val_if_fail (token, FALSE); + + if (!inet_pton (AF_INET6, token, &i6_token)) + return FALSE; + + if (!_nm_utils_inet6_is_token (&i6_token)) + return FALSE; + + nm_utils_ipv6_interface_identifier_get_from_addr (iid, &i6_token); + return TRUE; +} + +/** + * nm_utils_inet6_interface_identifier_to_token: + * @iid: %NMUtilsIPv6IfaceId interface identifier + * @buf: the destination buffer or %NULL + * + * Converts the interface identifier to a string token. + * If the destination buffer it set, set it is used to store the + * resulting token, otherwise an internal static buffer is used. + * The buffer needs to be %NM_UTILS_INET_ADDRSTRLEN characters long. + * + * Returns: a statically allocated array. Do not g_free(). + */ +const char * +nm_utils_inet6_interface_identifier_to_token (NMUtilsIPv6IfaceId iid, char *buf) +{ + struct in6_addr i6_token = { .s6_addr = { 0, } }; + + nm_utils_ipv6_addr_set_interface_identifier (&i6_token, iid); + return nm_utils_inet6_ntop (&i6_token, buf); +} + +/*****************************************************************************/ + static gboolean -_set_stable_privacy (struct in6_addr *addr, +_set_stable_privacy (guint8 stable_type, + struct in6_addr *addr, const char *ifname, - const char *uuid, + const char *network_id, guint dad_counter, guint8 *secret_key, gsize key_len, @@ -2857,11 +3102,24 @@ _set_stable_privacy (struct in6_addr *addr, key_len = MIN (key_len, G_MAXUINT32); + if (stable_type != NM_UTILS_STABLE_TYPE_UUID) { + /* Preferably, we would always like to include the stable-type, + * but for backward compatibility reasons, we cannot for UUID. + * + * That is no real problem and it is still impossible to + * force a collision here, because of how the remaining + * fields are hashed. That is, as we also hash @key_len + * and the terminating '\0' of @network_id, it is unambigiously + * possible to revert the process and deduce the @stable_type. + */ + g_checksum_update (sum, &stable_type, sizeof (stable_type)); + } + g_checksum_update (sum, addr->s6_addr, 8); g_checksum_update (sum, (const guchar *) ifname, strlen (ifname) + 1); - if (!uuid) - uuid = ""; - g_checksum_update (sum, (const guchar *) uuid, strlen (uuid) + 1); + if (!network_id) + network_id = ""; + g_checksum_update (sum, (const guchar *) network_id, strlen (network_id) + 1); tmp[0] = htonl (dad_counter); tmp[1] = htonl (key_len); g_checksum_update (sum, (const guchar *) tmp, sizeof (tmp)); @@ -2887,15 +3145,20 @@ _set_stable_privacy (struct in6_addr *addr, * Returns: %TRUE on success, %FALSE if the address could not be generated. */ gboolean -nm_utils_ipv6_addr_set_stable_privacy (struct in6_addr *addr, +nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type, + struct in6_addr *addr, const char *ifname, - const char *uuid, + const char *network_id, guint dad_counter, GError **error) { gs_free guint8 *secret_key = NULL; gsize key_len = 0; + nm_assert (NM_IN_SET (stable_type, + NM_UTILS_STABLE_TYPE_UUID, + NM_UTILS_STABLE_TYPE_STABLE_ID)); + if (dad_counter >= RFC7217_IDGEN_RETRIES) { g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "Too many DAD collisions"); @@ -2906,10 +3169,151 @@ nm_utils_ipv6_addr_set_stable_privacy (struct in6_addr *addr, if (!secret_key) return FALSE; - return _set_stable_privacy (addr, ifname, uuid, dad_counter, + return _set_stable_privacy (stable_type, addr, ifname, network_id, dad_counter, secret_key, key_len, error); } +/*****************************************************************************/ + +static void +_hw_addr_eth_complete (struct ether_addr *addr, + const char *current_mac_address, + const char *generate_mac_address_mask) +{ + struct ether_addr mask; + struct ether_addr oui; + struct ether_addr *ouis; + gsize ouis_len; + guint i; + + /* the second LSB of the first octet means + * "globally unique, OUI enforced, BIA (burned-in-address)" + * vs. "locally-administered". By default, set it to + * generate locally-administered addresses. + * + * Maybe be overwritten by a mask below. */ + addr->ether_addr_octet[0] |= 2; + + if (!generate_mac_address_mask || !*generate_mac_address_mask) + goto out; + if (!_nm_utils_generate_mac_address_mask_parse (generate_mac_address_mask, + &mask, + &ouis, + &ouis_len, + NULL)) + goto out; + + nm_assert ((ouis == NULL) ^ (ouis_len != 0)); + if (ouis) { + /* g_random_int() is good enough here. It uses a static GRand instance + * that is seeded from /dev/urandom. */ + oui = ouis[g_random_int () % ouis_len]; + g_free (ouis); + } else { + if (!nm_utils_hwaddr_aton (current_mac_address, &oui, ETH_ALEN)) + goto out; + } + + for (i = 0; i < ETH_ALEN; i++) { + const guint8 a = addr->ether_addr_octet[i]; + const guint8 o = oui.ether_addr_octet[i]; + const guint8 m = mask.ether_addr_octet[i]; + + addr->ether_addr_octet[i] = (a & ~m) | (o & m); + } + +out: + /* The LSB of the first octet must always be cleared, + * it means Unicast vs. Multicast */ + addr->ether_addr_octet[0] &= ~1; +} + +char * +nm_utils_hw_addr_gen_random_eth (const char *current_mac_address, + const char *generate_mac_address_mask) +{ + struct ether_addr bin_addr; + + if (nm_utils_read_urandom (&bin_addr, ETH_ALEN) < 0) + return NULL; + _hw_addr_eth_complete (&bin_addr, current_mac_address, generate_mac_address_mask); + return nm_utils_hwaddr_ntoa (&bin_addr, ETH_ALEN); +} + +static char * +_hw_addr_gen_stable_eth (NMUtilsStableType stable_type, + const char *stable_id, + const guint8 *secret_key, + gsize key_len, + const char *ifname, + const char *current_mac_address, + const char *generate_mac_address_mask) +{ + GChecksum *sum; + guint32 tmp; + guint8 digest[32]; + gsize len = sizeof (digest); + struct ether_addr bin_addr; + guint8 stable_type_uint8; + + nm_assert (stable_id); + nm_assert (NM_IN_SET (stable_type, + NM_UTILS_STABLE_TYPE_UUID, + NM_UTILS_STABLE_TYPE_STABLE_ID)); + nm_assert (secret_key); + + sum = g_checksum_new (G_CHECKSUM_SHA256); + if (!sum) + return NULL; + + key_len = MIN (key_len, G_MAXUINT32); + + stable_type_uint8 = stable_type; + g_checksum_update (sum, (const guchar *) &stable_type_uint8, sizeof (stable_type_uint8)); + + tmp = htonl ((guint32) key_len); + g_checksum_update (sum, (const guchar *) &tmp, sizeof (tmp)); + g_checksum_update (sum, (const guchar *) secret_key, key_len); + g_checksum_update (sum, (const guchar *) (ifname ?: ""), ifname ? (strlen (ifname) + 1) : 1); + g_checksum_update (sum, (const guchar *) stable_id, strlen (stable_id) + 1); + + g_checksum_get_digest (sum, digest, &len); + g_checksum_free (sum); + + g_return_val_if_fail (len == 32, NULL); + + memcpy (&bin_addr, digest, ETH_ALEN); + _hw_addr_eth_complete (&bin_addr, current_mac_address, generate_mac_address_mask); + return nm_utils_hwaddr_ntoa (&bin_addr, ETH_ALEN); +} + +char * +nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type, + const char *stable_id, + const char *ifname, + const char *current_mac_address, + const char *generate_mac_address_mask) +{ + gs_free guint8 *secret_key = NULL; + gsize key_len = 0; + + g_return_val_if_fail (stable_id, NULL); + + secret_key = nm_utils_secret_key_read (&key_len, NULL); + if (!secret_key) + return NULL; + + return _hw_addr_gen_stable_eth (stable_type, + stable_id, + secret_key, + key_len, + ifname, + current_mac_address, + generate_mac_address_mask); +} + +/*****************************************************************************/ + /** * nm_utils_setpgid: * @unused: unused @@ -3120,12 +3524,11 @@ nm_utils_lifetime_get (guint32 timestamp, } const char * -nm_utils_dnsmasq_status_to_string (int status, char *dest, guint size) +nm_utils_dnsmasq_status_to_string (int status, char *dest, gsize size) { - static char buffer[128]; - char *msg, *ret; - gs_free char *msg_free = NULL; - int len; + const char *msg; + + nm_utils_to_string_buffer_init (&dest, &size); if (status == 0) msg = "Success"; @@ -3139,22 +3542,15 @@ nm_utils_dnsmasq_status_to_string (int status, char *dest, guint size) msg = "Memory allocation failure"; else if (status == 5) msg = "Other problem"; - else if (status >= 11) - msg = msg_free = g_strdup_printf ("Lease script failed with error %d", status - 10); + else if (status >= 11) { + g_snprintf (dest, size, "Lease script failed with error %d", status - 10); + return dest; + } else msg = "Unknown problem"; - if (dest) { - ret = dest; - len = size; - } else { - ret = buffer; - len = sizeof (buffer); - } - - g_snprintf (ret, len, "%s (%d)", msg, status); - - return ret; + g_snprintf (dest, size, "%s (%d)", msg, status); + return dest; } /** |