mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-24 09:58:14 +02:00
Whether the length is supplied explicitly or implicitly (via -1), the result is the same. Update the comment.
490 lines
16 KiB
C
490 lines
16 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "libnm-glib-aux/nm-default-glib.h"
|
|
|
|
#include "nm-uuid.h"
|
|
|
|
#include "libnm-glib-aux/nm-random-utils.h"
|
|
#include "libnm-glib-aux/nm-str-buf.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
const NMUuid nm_uuid_ns_zero =
|
|
NM_UUID_INIT(00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00);
|
|
|
|
/* arbitrarily chosen namespace UUID for some uses of nm_uuid_generate_from_strings_old().
|
|
* Try not to re-use this namespace, instead, generate a unique one. */
|
|
const NMUuid nm_uuid_ns_1 =
|
|
NM_UUID_INIT(b4, 25, e9, fb, 75, 98, 44, b4, 9e, 3b, 5a, 2e, 3a, aa, 49, 05);
|
|
|
|
char *
|
|
nm_uuid_unparse_case(const NMUuid *uuid, char out_str[static 37], gboolean upper_case)
|
|
{
|
|
char *s;
|
|
int i;
|
|
|
|
nm_assert(uuid);
|
|
nm_assert(out_str);
|
|
|
|
s = out_str;
|
|
for (i = 0; i < 16; i++) {
|
|
const guint8 c = uuid->uuid[i];
|
|
|
|
if (NM_IN_SET(i, 4, 6, 8, 10))
|
|
*(s++) = '-';
|
|
*(s++) = nm_hexchar(c >> 4, upper_case);
|
|
*(s++) = nm_hexchar(c, upper_case);
|
|
}
|
|
*s = '\0';
|
|
|
|
return out_str;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
gboolean
|
|
nm_uuid_parse_full(const char *str, NMUuid *out_uuid, gboolean *out_is_normalized)
|
|
{
|
|
NMUuid uuid;
|
|
guint8 *p;
|
|
int i;
|
|
gboolean is_normalized = TRUE;
|
|
|
|
nm_assert(str);
|
|
|
|
p = uuid.uuid;
|
|
|
|
for (i = 0; TRUE;) {
|
|
int v0;
|
|
int v1;
|
|
|
|
if (NM_IN_SET(i, 8, 13, 18, 23)) {
|
|
if (str[i] != '-')
|
|
return FALSE;
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (i == 36) {
|
|
if (str[i] != '\0')
|
|
return FALSE;
|
|
|
|
NM_SET_OUT(out_is_normalized, is_normalized);
|
|
NM_SET_OUT(out_uuid, uuid);
|
|
return TRUE;
|
|
}
|
|
|
|
#define _hexchar(ch, out_is_normalized2) \
|
|
({ \
|
|
const char _ch = (ch); \
|
|
int _result = -1; \
|
|
\
|
|
if (_ch >= '0') { \
|
|
if (_ch <= '9') \
|
|
_result = (_ch - '0'); \
|
|
else if (_ch >= 'A') { \
|
|
if (_ch <= 'F') { \
|
|
*(out_is_normalized2) = FALSE; \
|
|
_result = ((int) _ch) + (10 - (int) 'A'); \
|
|
} else if (_ch >= 'a' && _ch <= 'f') \
|
|
_result = ((int) _ch) + (10 - (int) 'a'); \
|
|
} \
|
|
} \
|
|
\
|
|
_result; \
|
|
})
|
|
|
|
v0 = _hexchar(str[i++], &is_normalized);
|
|
if (v0 < 0)
|
|
return FALSE;
|
|
v1 = _hexchar(str[i++], &is_normalized);
|
|
if (v1 < 0)
|
|
return FALSE;
|
|
|
|
*(p++) = (v0 << 4) + v1;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
NMUuid *
|
|
nm_uuid_generate_random(NMUuid *out_uuid)
|
|
{
|
|
nm_assert(out_uuid);
|
|
|
|
/* https://tools.ietf.org/html/rfc4122#section-4.4 */
|
|
|
|
/* See also, systemd's id128_make_v4_uuid() */
|
|
|
|
/* nm_random_get_bytes() is supposed to try hard to give good
|
|
* randomness. If it fails, it still makes an effort to fill
|
|
* random data into the buffer. There is not much we can do about
|
|
* that case, except making sure that it does not happen in the
|
|
* first place. */
|
|
nm_random_get_bytes(out_uuid, sizeof(*out_uuid));
|
|
|
|
/* Set the four most significant bits (bits 12 through 15) of the
|
|
* time_hi_and_version field to the 4-bit version number from
|
|
* Section 4.1.3. */
|
|
out_uuid->uuid[6] = (out_uuid->uuid[6] & 0x0Fu) | 0x40u;
|
|
|
|
/* Set the two most significant bits (bits 6 and 7) of the
|
|
* clock_seq_hi_and_reserved to zero and one, respectively. */
|
|
out_uuid->uuid[8] = (out_uuid->uuid[8] & 0x3Fu) | 0x80u;
|
|
|
|
return out_uuid;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
gboolean
|
|
nm_uuid_is_normalized_full(const char *str)
|
|
{
|
|
/* The only reason why this exists is that nm_uuid_is_normalized() is an inline function.
|
|
* If you need to forward declare the function, that won't work.
|
|
*
|
|
* Usually, you wouldn't use this variant! */
|
|
return nm_uuid_is_normalized(str);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
* nm_uuid_is_valid_nmlegacy()
|
|
* @str: the string to check whether it's a valid UUID.
|
|
*
|
|
* Note that this does not perform a strict check.
|
|
* Instead, it checks a more relaxed format (including
|
|
* non valid UUID strings). This is for backward compatibility,
|
|
* where older code did not perform a strict check.
|
|
*
|
|
* Returns: %TRUE, if the string is a valid legacy format.
|
|
* This may not be a valid UUID!
|
|
*/
|
|
gboolean
|
|
nm_uuid_is_valid_nmlegacy(const char *str)
|
|
{
|
|
const char *p = str;
|
|
int num_dashes = 0;
|
|
|
|
if (!p)
|
|
return FALSE;
|
|
|
|
while (*p) {
|
|
if (*p == '-')
|
|
num_dashes++;
|
|
else if (!g_ascii_isxdigit(*p))
|
|
return FALSE;
|
|
p++;
|
|
}
|
|
|
|
/* While we accept here bogus strings as UUIDs, they must contain only
|
|
* hexdigits and '-', and they must be either 36 or 40 chars long. */
|
|
|
|
if ((num_dashes == 4) && (p - str == 36))
|
|
return TRUE;
|
|
|
|
/* Backwards compat for older configurations */
|
|
if ((num_dashes == 0) && (p - str == 40))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
gboolean
|
|
nm_uuid_is_valid_nm(const char *str,
|
|
gboolean *out_normalized,
|
|
char *out_normalized_str /* [static 37] */)
|
|
{
|
|
NMUuid uuid;
|
|
gboolean is_normalized;
|
|
|
|
/* @out_normalized_str is only set, if normalization was necessary
|
|
* and possible. The caller cannot request @out_normalized_str, without
|
|
* also requesting @out_normalized. Otherwise, they couldn't know whether
|
|
* a normalized string was returned. */
|
|
nm_assert(!out_normalized_str || out_normalized);
|
|
|
|
if (!str)
|
|
return FALSE;
|
|
|
|
if (nm_uuid_parse_full(str, &uuid, &is_normalized)) {
|
|
if (is_normalized) {
|
|
/* @str is already normalized. No need to normalize again, so
|
|
* @out_normalized is FALSE. */
|
|
NM_SET_OUT(out_normalized, FALSE);
|
|
} else {
|
|
NM_SET_OUT(out_normalized, TRUE);
|
|
if (out_normalized_str) {
|
|
/* we need to normalize the UUID */
|
|
nm_uuid_unparse(&uuid, out_normalized_str);
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
if (nm_uuid_is_valid_nmlegacy(str)) {
|
|
/* This is not a valid UUID, but something that we used to
|
|
* accept according to nm_uuid_is_valid_nmlegacy().
|
|
*
|
|
* Normalize it by sha1 hashing the string. Upper case characters
|
|
* are made lower case first. */
|
|
NM_SET_OUT(out_normalized, TRUE);
|
|
if (out_normalized_str) {
|
|
char str_lower[40];
|
|
int i;
|
|
|
|
nm_assert(strlen(str) <= G_N_ELEMENTS(str_lower));
|
|
|
|
/* normalize first to lower-case. */
|
|
for (i = 0; str[i]; i++) {
|
|
nm_assert(i < G_N_ELEMENTS(str_lower));
|
|
str_lower[i] = g_ascii_tolower(str[i]);
|
|
}
|
|
nm_assert(i <= G_N_ELEMENTS(str_lower));
|
|
|
|
/* The namespace UUID is chosen randomly. */
|
|
nm_uuid_generate_from_string(
|
|
&uuid,
|
|
str_lower,
|
|
i,
|
|
NM_UUID_TYPE_VERSION5,
|
|
&NM_UUID_INIT(4e, 72, f7, 09, ca, 95, 44, 05, 90, 53, 1f, 43, 29, 4a, 61, 8c));
|
|
nm_uuid_unparse(&uuid, out_normalized_str);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* UUID is not valid. */
|
|
return FALSE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
gboolean
|
|
nm_uuid_is_null(const NMUuid *uuid)
|
|
{
|
|
int i;
|
|
|
|
if (!uuid)
|
|
return TRUE;
|
|
|
|
for (i = 0; i < (int) G_N_ELEMENTS(uuid->uuid); i++) {
|
|
if (uuid->uuid[i])
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
char *
|
|
nm_uuid_generate_random_str(char buf[static 37])
|
|
{
|
|
NMUuid uuid;
|
|
|
|
nm_assert(buf);
|
|
|
|
nm_uuid_generate_random(&uuid);
|
|
return nm_uuid_unparse(&uuid, buf);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
* nm_uuid_generate_from_string:
|
|
* @uuid: the UUID to update inplace. This function cannot
|
|
* fail to succeed.
|
|
* @s: a string to use as the seed for the UUID
|
|
* @slen: if negative, treat @s as zero terminated C string.
|
|
* Otherwise, assume the length as given (and allow @s to be
|
|
* non-null terminated or contain '\0').
|
|
* @uuid_type: a type identifier which UUID format to generate.
|
|
* @type_args: additional arguments, depending on the uuid_type
|
|
*
|
|
* For a given @s, this function will always return the same UUID.
|
|
*
|
|
* Returns: the input @uuid. This function cannot fail.
|
|
**/
|
|
NMUuid *
|
|
nm_uuid_generate_from_string(NMUuid *uuid,
|
|
const char *s,
|
|
gssize slen,
|
|
NMUuidType uuid_type,
|
|
const NMUuid *type_args)
|
|
{
|
|
nm_auto_free_checksum GChecksum *sum = NULL;
|
|
union {
|
|
guint8 sha1[NM_UTILS_CHECKSUM_LENGTH_SHA1];
|
|
guint8 md5[NM_UTILS_CHECKSUM_LENGTH_MD5];
|
|
NMUuid uuid;
|
|
} digest;
|
|
gsize digest_len;
|
|
GChecksumType checksum_type;
|
|
|
|
G_STATIC_ASSERT_EXPR(sizeof(digest.md5) >= sizeof(digest.uuid));
|
|
G_STATIC_ASSERT_EXPR(sizeof(digest.sha1) >= sizeof(digest.uuid));
|
|
|
|
g_return_val_if_fail(uuid, NULL);
|
|
g_return_val_if_fail(slen <= 0 || s, NULL);
|
|
|
|
if (slen < 0)
|
|
slen = s ? strlen(s) : 0;
|
|
|
|
switch (uuid_type) {
|
|
case NM_UUID_TYPE_LEGACY:
|
|
nm_assert(!type_args);
|
|
type_args = NULL;
|
|
checksum_type = G_CHECKSUM_MD5;
|
|
break;
|
|
case NM_UUID_TYPE_VERSION3:
|
|
if (!type_args)
|
|
type_args = &nm_uuid_ns_zero;
|
|
checksum_type = G_CHECKSUM_MD5;
|
|
break;
|
|
case NM_UUID_TYPE_VERSION5:
|
|
if (!type_args)
|
|
type_args = &nm_uuid_ns_zero;
|
|
checksum_type = G_CHECKSUM_SHA1;
|
|
break;
|
|
default:
|
|
g_return_val_if_reached(NULL);
|
|
}
|
|
|
|
sum = g_checksum_new(checksum_type);
|
|
if (type_args)
|
|
g_checksum_update(sum, (guchar *) type_args, sizeof(*type_args));
|
|
g_checksum_update(sum, (guchar *) s, slen);
|
|
|
|
digest_len = sizeof(digest);
|
|
g_checksum_get_digest(sum, (guint8 *) &digest, &digest_len);
|
|
|
|
nm_assert(digest_len >= sizeof(digest.uuid));
|
|
nm_assert(digest_len
|
|
== ((checksum_type == G_CHECKSUM_MD5 ? NM_UTILS_CHECKSUM_LENGTH_MD5
|
|
: NM_UTILS_CHECKSUM_LENGTH_SHA1)));
|
|
|
|
*uuid = digest.uuid;
|
|
|
|
if (uuid_type != NM_UUID_TYPE_LEGACY) {
|
|
uuid->uuid[6] = (uuid->uuid[6] & 0x0F) | (uuid_type << 4);
|
|
uuid->uuid[8] = (uuid->uuid[8] & 0x3F) | 0x80;
|
|
}
|
|
|
|
return uuid;
|
|
}
|
|
|
|
/**
|
|
* nm_uuid_generate_from_string_str:
|
|
* @s: a string to use as the seed for the UUID
|
|
* @slen: if negative, treat @s as zero terminated C string.
|
|
* Otherwise, assume the length as given (and allow @s to be
|
|
* non-null terminated or contain '\0').
|
|
* @uuid_type: a type identifier which UUID format to generate.
|
|
* @type_args: additional arguments, depending on the uuid_type
|
|
*
|
|
* For a given @s, this function will always return the same UUID.
|
|
*
|
|
* Returns: a newly allocated UUID suitable for use as the #NMSettingConnection
|
|
* object's #NMSettingConnection:id: property
|
|
**/
|
|
char *
|
|
nm_uuid_generate_from_string_str(const char *s,
|
|
gssize slen,
|
|
NMUuidType uuid_type,
|
|
const NMUuid *type_args)
|
|
{
|
|
NMUuid uuid;
|
|
const NMUuid *u;
|
|
|
|
u = nm_uuid_generate_from_string(&uuid, s, slen, uuid_type, type_args);
|
|
|
|
if (G_UNLIKELY(!u))
|
|
return nm_assert_unreachable_val(NULL);
|
|
nm_assert(u == &uuid);
|
|
|
|
return nm_uuid_unparse(&uuid, g_new(char, 37));
|
|
}
|
|
|
|
/**
|
|
* nm_uuid_generate_from_strings_strv:
|
|
* @uuid_type: the UUID type to use. Prefer version 5 unless you have
|
|
* good reasons.
|
|
* @type_args: the namespace UUID.
|
|
* @strv: (nullable): the strv list to hash. Can be NULL, in which
|
|
* case the result is different from an empty array.
|
|
* @len: if negative, @strv is a NULL terminated array. Otherwise,
|
|
* it is the length of the strv array. In the latter case it may
|
|
* also contain NULL strings.
|
|
*
|
|
* Returns a @uuid_type UUID based on the concatenated C strings.
|
|
* It does not simply concatenate them, but also includes the
|
|
* terminating '\0' character. For example "a", "b", gives
|
|
* "a\0b\0".
|
|
* This has the advantage, that the following invocations
|
|
* all give different UUIDs: (NULL), (""), ("",""), ("","a"), ("a",""),
|
|
* ("aa"), ("aa", ""), ("", "aa"), ...
|
|
*/
|
|
char *
|
|
nm_uuid_generate_from_strings_strv(NMUuidType uuid_type,
|
|
const NMUuid *type_args,
|
|
const char *const *strv,
|
|
gssize len)
|
|
{
|
|
nm_auto_str_buf NMStrBuf str = NM_STR_BUF_INIT_A(NM_UTILS_GET_NEXT_REALLOC_SIZE_232, TRUE);
|
|
gsize slen;
|
|
const char *s;
|
|
|
|
if (len > 0) {
|
|
gboolean has_nulls = FALSE;
|
|
gssize i;
|
|
|
|
nm_assert(len == 0 || strv);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (strv[i])
|
|
nm_str_buf_append_len(&str, strv[i], strlen(strv[i]) + 1u);
|
|
else
|
|
has_nulls = TRUE;
|
|
}
|
|
if (has_nulls) {
|
|
/* We either support a NULL terminated strv array, or a ptr array of fixed
|
|
* length (@len argument).
|
|
*
|
|
* If there are no NULLs within the first @len strings, then the result
|
|
* is the same. If there are any NULL strings, we need to encode that
|
|
* in a unique way. We do that by appending a bitmap of the elements
|
|
* whether they were set, plus one 'n' character (without NUL termination).
|
|
* None of the other branches below hashes to that, so this will uniquely
|
|
* encoded the NULL strings.
|
|
*/
|
|
for (i = 0; i < len; i++)
|
|
nm_str_buf_append_c(&str, strv[i] ? '1' : '_');
|
|
nm_str_buf_append_c(&str, 'n');
|
|
}
|
|
slen = str.len;
|
|
s = nm_str_buf_get_str_unsafe(&str);
|
|
} else if (!strv) {
|
|
/* NULL is treated differently from an empty strv. We achieve that
|
|
* by using a non-empty, non-NUL terminated string (which cannot happen
|
|
* in the other cases). */
|
|
slen = 1;
|
|
s = "x";
|
|
} else if (!strv[0] || len == 0) {
|
|
slen = 0;
|
|
s = "";
|
|
} else if (!strv[1]) {
|
|
slen = strlen(strv[0]) + 1u;
|
|
s = strv[0];
|
|
} else {
|
|
/* We concatenate the NUL terminated string, including the NUL
|
|
* character. This way, ("a","a"), ("aa"), ("aa","") all hash
|
|
* differently. */
|
|
for (; strv[0]; strv++)
|
|
nm_str_buf_append_len(&str, strv[0], strlen(strv[0]) + 1u);
|
|
slen = str.len;
|
|
s = nm_str_buf_get_str_unsafe(&str);
|
|
}
|
|
|
|
return nm_uuid_generate_from_string_str(s, slen, uuid_type, type_args);
|
|
}
|