Files
NetworkManager/shared/nm-utils/nm-shared-utils.c
Thomas Haller 506fca65b3 tui: guess the prefix length (netmask) of private IPv4 addresses and routes based on network class
For RFC1918 private IPv4addresses, guess a better prefix length for
addresses and routes.

nmtui is an interactive program. It makes sense to be a bit smarter
about what the user probably meant.

It would be nice if nmtui would update the entry field immediately when
the cursor leaves the field, to show the guessed prefix length. However,
that is not easily possible, so lets to that another time.

For IPv6 addresses, default to /64 instead of /128.

https://bugzilla.redhat.com/show_bug.cgi?id=1474295
2017-09-05 18:44:04 +02:00

670 lines
18 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
* (C) Copyright 2016 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-shared-utils.h"
#include <errno.h>
#include <arpa/inet.h>
/*****************************************************************************/
const void *const _NM_PTRARRAY_EMPTY[1] = { NULL };
/*****************************************************************************/
void
nm_utils_strbuf_append_c (char **buf, gsize *len, char c)
{
switch (*len) {
case 0:
return;
case 1:
(*buf)[0] = '\0';
*len = 0;
(*buf)++;
return;
default:
(*buf)[0] = c;
(*buf)[1] = '\0';
(*len)--;
(*buf)++;
return;
}
}
void
nm_utils_strbuf_append_str (char **buf, gsize *len, const char *str)
{
gsize src_len;
switch (*len) {
case 0:
return;
case 1:
if (!str || !*str) {
(*buf)[0] = '\0';
return;
}
(*buf)[0] = '\0';
*len = 0;
(*buf)++;
return;
default:
if (!str || !*str) {
(*buf)[0] = '\0';
return;
}
src_len = g_strlcpy (*buf, str, *len);
if (src_len >= *len) {
*buf = &(*buf)[*len];
*len = 0;
} else {
*buf = &(*buf)[src_len];
*len -= src_len;
}
return;
}
}
void
nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...)
{
char *p = *buf;
va_list args;
gint retval;
if (*len == 0)
return;
va_start (args, format);
retval = g_vsnprintf (p, *len, format, args);
va_end (args);
if (retval >= *len) {
*buf = &p[*len];
*len = 0;
} else {
*buf = &p[retval];
*len -= retval;
}
}
/*****************************************************************************/
/**
* _nm_utils_ip4_prefix_to_netmask:
* @prefix: a CIDR prefix
*
* Returns: the netmask represented by the prefix, in network byte order
**/
guint32
_nm_utils_ip4_prefix_to_netmask (guint32 prefix)
{
return prefix < 32 ? ~htonl(0xFFFFFFFF >> prefix) : 0xFFFFFFFF;
}
/**
* _nm_utils_ip4_get_default_prefix:
* @ip: an IPv4 address (in network byte order)
*
* When the Internet was originally set up, various ranges of IP addresses were
* segmented into three network classes: A, B, and C. This function will return
* a prefix that is associated with the IP address specified defining where it
* falls in the predefined classes.
*
* Returns: the default class prefix for the given IP
**/
/* The function is originally from ipcalc.c of Red Hat's initscripts. */
guint32
_nm_utils_ip4_get_default_prefix (guint32 ip)
{
if (((ntohl (ip) & 0xFF000000) >> 24) <= 127)
return 8; /* Class A - 255.0.0.0 */
else if (((ntohl (ip) & 0xFF000000) >> 24) <= 191)
return 16; /* Class B - 255.255.0.0 */
return 24; /* Class C - 255.255.255.0 */
}
gboolean
nm_utils_ip_is_site_local (int addr_family,
const void *address)
{
in_addr_t addr4;
switch (addr_family) {
case AF_INET:
/* RFC1918 private addresses
* 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 */
addr4 = ntohl (*((const in_addr_t *) address));
return (addr4 & 0xff000000) == 0x0a000000
|| (addr4 & 0xfff00000) == 0xac100000
|| (addr4 & 0xffff0000) == 0xc0a80000;
case AF_INET6:
return IN6_IS_ADDR_SITELOCAL (address);
default:
g_return_val_if_reached (FALSE);
}
}
/*****************************************************************************/
gboolean
nm_utils_parse_inaddr (const char *text,
int family,
char **out_addr)
{
union {
in_addr_t v4;
struct in6_addr v6;
} addrbin;
char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
g_return_val_if_fail (text, FALSE);
if (family == AF_UNSPEC)
family = strchr (text, ':') ? AF_INET6 : AF_INET;
else
g_return_val_if_fail (NM_IN_SET (family, AF_INET, AF_INET6), FALSE);
if (inet_pton (family, text, &addrbin) != 1)
return FALSE;
NM_SET_OUT (out_addr, g_strdup (inet_ntop (family, &addrbin, addrstr_buf, sizeof (addrstr_buf))));
return TRUE;
}
gboolean
nm_utils_parse_inaddr_prefix (const char *text,
int family,
char **out_addr,
int *out_prefix)
{
gs_free char *addrstr_free = NULL;
int prefix = -1;
const char *slash;
const char *addrstr;
union {
in_addr_t v4;
struct in6_addr v6;
} addrbin;
char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
g_return_val_if_fail (text, FALSE);
if (family == AF_UNSPEC)
family = strchr (text, ':') ? AF_INET6 : AF_INET;
else
g_return_val_if_fail (NM_IN_SET (family, AF_INET, AF_INET6), FALSE);
slash = strchr (text, '/');
if (slash)
addrstr = addrstr_free = g_strndup (text, slash - text);
else
addrstr = text;
if (inet_pton (family, addrstr, &addrbin) != 1)
return FALSE;
if (slash) {
prefix = _nm_utils_ascii_str_to_int64 (slash + 1, 10,
0,
family == AF_INET ? 32 : 128,
-1);
if (prefix == -1)
return FALSE;
}
NM_SET_OUT (out_addr, g_strdup (inet_ntop (family, &addrbin, addrstr_buf, sizeof (addrstr_buf))));
NM_SET_OUT (out_prefix, prefix);
return TRUE;
}
/*****************************************************************************/
/* _nm_utils_ascii_str_to_int64:
*
* A wrapper for g_ascii_strtoll, that checks whether the whole string
* can be successfully converted to a number and is within a given
* range. On any error, @fallback will be returned and %errno will be set
* to a non-zero value. On success, %errno will be set to zero, check %errno
* for errors. Any trailing or leading (ascii) white space is ignored and the
* functions is locale independent.
*
* The function is guaranteed to return a value between @min and @max
* (inclusive) or @fallback. Also, the parsing is rather strict, it does
* not allow for any unrecognized characters, except leading and trailing
* white space.
**/
gint64
_nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback)
{
gint64 v;
const char *s = NULL;
if (str) {
while (g_ascii_isspace (str[0]))
str++;
}
if (!str || !str[0]) {
errno = EINVAL;
return fallback;
}
errno = 0;
v = g_ascii_strtoll (str, (char **) &s, base);
if (errno != 0)
return fallback;
if (s[0] != '\0') {
while (g_ascii_isspace (s[0]))
s++;
if (s[0] != '\0') {
errno = EINVAL;
return fallback;
}
}
if (v > max || v < min) {
errno = ERANGE;
return fallback;
}
return v;
}
/*****************************************************************************/
/**
* nm_utils_strv_find_first:
* @list: the strv list to search
* @len: the length of the list, or a negative value if @list is %NULL terminated.
* @needle: the value to search for. The search is done using strcmp().
*
* Searches @list for @needle and returns the index of the first match (based
* on strcmp()).
*
* For convenience, @list has type 'char**' instead of 'const char **'.
*
* Returns: index of first occurrence or -1 if @needle is not found in @list.
*/
gssize
nm_utils_strv_find_first (char **list, gssize len, const char *needle)
{
gssize i;
if (len > 0) {
g_return_val_if_fail (list, -1);
if (!needle) {
/* if we search a list with known length, %NULL is a valid @needle. */
for (i = 0; i < len; i++) {
if (!list[i])
return i;
}
} else {
for (i = 0; i < len; i++) {
if (list[i] && !strcmp (needle, list[i]))
return i;
}
}
} else if (len < 0) {
g_return_val_if_fail (needle, -1);
if (list) {
for (i = 0; list[i]; i++) {
if (strcmp (needle, list[i]) == 0)
return i;
}
}
}
return -1;
}
char **
_nm_utils_strv_cleanup (char **strv,
gboolean strip_whitespace,
gboolean skip_empty,
gboolean skip_repeated)
{
guint i, j;
if (!strv || !*strv)
return strv;
if (strip_whitespace) {
for (i = 0; strv[i]; i++)
g_strstrip (strv[i]);
}
if (!skip_empty && !skip_repeated)
return strv;
j = 0;
for (i = 0; strv[i]; i++) {
if ( (skip_empty && !*strv[i])
|| (skip_repeated && nm_utils_strv_find_first (strv, j, strv[i]) >= 0))
g_free (strv[i]);
else
strv[j++] = strv[i];
}
strv[j] = NULL;
return strv;
}
/*****************************************************************************/
gint
_nm_utils_ascii_str_to_bool (const char *str,
gint default_value)
{
gsize len;
char *s = NULL;
if (!str)
return default_value;
while (str[0] && g_ascii_isspace (str[0]))
str++;
if (!str[0])
return default_value;
len = strlen (str);
if (g_ascii_isspace (str[len - 1])) {
s = g_strdup (str);
g_strchomp (s);
str = s;
}
if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1"))
default_value = TRUE;
else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0"))
default_value = FALSE;
if (s)
g_free (s);
return default_value;
}
/*****************************************************************************/
NM_CACHED_QUARK_FCN ("nm-utils-error-quark", nm_utils_error_quark)
void
nm_utils_error_set_cancelled (GError **error,
gboolean is_disposing,
const char *instance_name)
{
if (is_disposing) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING,
"Disposing %s instance",
instance_name && *instance_name ? instance_name : "source");
} else {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
"Request cancelled");
}
}
gboolean
nm_utils_error_is_cancelled (GError *error,
gboolean consider_is_disposing)
{
if (error) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return TRUE;
if ( consider_is_disposing
&& g_error_matches (error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING))
return TRUE;
}
return FALSE;
}
/*****************************************************************************/
/**
* nm_g_object_set_property:
* @object: the target object
* @property_name: the property name
* @value: the #GValue to set
* @error: (allow-none): optional error argument
*
* A reimplementation of g_object_set_property(), but instead
* returning an error instead of logging a warning. All g_object_set*()
* versions in glib require you to not pass invalid types or they will
* log a g_warning() -- without reporting an error. We don't want that,
* so we need to hack error checking around it.
*
* Returns: whether the value was successfully set.
*/
gboolean
nm_g_object_set_property (GObject *object,
const gchar *property_name,
const GValue *value,
GError **error)
{
GParamSpec *pspec;
nm_auto_unset_gvalue GValue tmp_value = G_VALUE_INIT;
GObjectClass *klass;
g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
g_return_val_if_fail (property_name != NULL, FALSE);
g_return_val_if_fail (G_IS_VALUE (value), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
/* g_object_class_find_property() does g_param_spec_get_redirect_target(),
* where we differ from a plain g_object_set_property(). */
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), property_name);
if (!pspec) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
_("object class '%s' has no property named '%s'"),
G_OBJECT_TYPE_NAME (object),
property_name);
return FALSE;
}
if (!(pspec->flags & G_PARAM_WRITABLE)) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
_("property '%s' of object class '%s' is not writable"),
pspec->name,
G_OBJECT_TYPE_NAME (object));
return FALSE;
}
if ((pspec->flags & G_PARAM_CONSTRUCT_ONLY)) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
_("construct property \"%s\" for object '%s' can't be set after construction"),
pspec->name, G_OBJECT_TYPE_NAME (object));
return FALSE;
}
klass = g_type_class_peek (pspec->owner_type);
if (klass == NULL) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
_("'%s::%s' is not a valid property name; '%s' is not a GObject subtype"),
g_type_name (pspec->owner_type), pspec->name, g_type_name (pspec->owner_type));
return FALSE;
}
/* provide a copy to work from, convert (if necessary) and validate */
g_value_init (&tmp_value, pspec->value_type);
if (!g_value_transform (value, &tmp_value)) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
_("unable to set property '%s' of type '%s' from value of type '%s'"),
pspec->name,
g_type_name (pspec->value_type),
G_VALUE_TYPE_NAME (value));
return FALSE;
}
if ( g_param_value_validate (pspec, &tmp_value)
&& !(pspec->flags & G_PARAM_LAX_VALIDATION)) {
gs_free char *contents = g_strdup_value_contents (value);
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
_("value \"%s\" of type '%s' is invalid or out of range for property '%s' of type '%s'"),
contents,
G_VALUE_TYPE_NAME (value),
pspec->name,
g_type_name (pspec->value_type));
return FALSE;
}
g_object_set_property (object, property_name, &tmp_value);
return TRUE;
}
/*****************************************************************************/
static void
_str_append_escape (GString *s, char ch)
{
g_string_append_c (s, '\\');
g_string_append_c (s, '0' + ((((guchar) ch) >> 6) & 07));
g_string_append_c (s, '0' + ((((guchar) ch) >> 3) & 07));
g_string_append_c (s, '0' + ( ((guchar) ch) & 07));
}
/**
* nm_utils_str_utf8safe_escape:
* @str: NUL terminated input string, possibly in utf-8 encoding
* @flags: #NMUtilsStrUtf8SafeFlags flags
* @to_free: (out): return the pointer location of the string
* if a copying was necessary.
*
* Returns the possible non-UTF-8 NUL terminated string @str
* and uses backslash escaping (C escaping, like g_strescape())
* to sanitize non UTF-8 characters. The result is valid
* UTF-8.
*
* The operation can be reverted with g_strcompress() or
* nm_utils_str_utf8safe_unescape().
*
* Depending on @flags, valid UTF-8 characters are not escaped at all
* (except the escape character '\\'). This is the difference to g_strescape(),
* which escapes all non-ASCII characters. This allows to pass on
* valid UTF-8 characters as-is and can be directly shown to the user
* as UTF-8 -- with exception of the backslash escape character,
* invalid UTF-8 sequences, and other (depending on @flags).
*
* Returns: the escaped input string, as valid UTF-8. If no escaping
* is necessary, it returns the input @str. Otherwise, an allocated
* string @to_free is returned which must be freed by the caller
* with g_free. The escaping can be reverted by g_strcompress().
**/
const char *
nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free)
{
const char *p = NULL;
GString *s;
g_return_val_if_fail (to_free, NULL);
*to_free = NULL;
if (!str || !str[0])
return str;
if ( g_utf8_validate (str, -1, &p)
&& !NM_STRCHAR_ANY (str, ch,
( ch == '\\' \
|| ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \
&& ch < ' ') \
|| ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \
&& ((guchar) ch) >= 127))))
return str;
s = g_string_sized_new ((p - str) + strlen (p) + 5);
do {
for (; str < p; str++) {
char ch = str[0];
if (ch == '\\')
g_string_append (s, "\\\\");
else if ( ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \
&& ch < ' ') \
|| ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \
&& ((guchar) ch) >= 127))
_str_append_escape (s, ch);
else
g_string_append_c (s, ch);
}
if (p[0] == '\0')
break;
_str_append_escape (s, p[0]);
str = &p[1];
g_utf8_validate (str, -1, &p);
} while (TRUE);
*to_free = g_string_free (s, FALSE);
return *to_free;
}
const char *
nm_utils_str_utf8safe_unescape (const char *str, char **to_free)
{
g_return_val_if_fail (to_free, NULL);
if (!str || !strchr (str, '\\')) {
*to_free = NULL;
return str;
}
return (*to_free = g_strcompress (str));
}
/**
* nm_utils_str_utf8safe_escape_cp:
* @str: NUL terminated input string, possibly in utf-8 encoding
* @flags: #NMUtilsStrUtf8SafeFlags flags
*
* Like nm_utils_str_utf8safe_escape(), except the returned value
* is always a copy of the input and must be freed by the caller.
*
* Returns: the escaped input string in UTF-8 encoding. The returned
* value should be freed with g_free().
* The escaping can be reverted by g_strcompress().
**/
char *
nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags)
{
char *s;
nm_utils_str_utf8safe_escape (str, flags, &s);
return s ?: g_strdup (str);
}
char *
nm_utils_str_utf8safe_unescape_cp (const char *str)
{
return str ? g_strcompress (str) : NULL;
}
char *
nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags)
{
char *str_to_free;
nm_utils_str_utf8safe_escape (str, flags, &str_to_free);
if (str_to_free) {
g_free (str);
return str_to_free;
}
return str;
}