Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ jobs:

- name: Build NetworkManager-ssh
run: |
if [ ! -f configure ]; then
autoreconf -vfi
fi
autoreconf -vfi
./configure --disable-static \
$(if [ -d gtk4 ]; then echo "--with-gtk4"; fi) \
--enable-more-warnings=yes \
Expand Down
3 changes: 2 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ libexec_PROGRAMS += nm-ssh-service
nm_ssh_service_SOURCES = \
src/nm-ssh-service.c \
src/nm-ssh-service.h \
shared/nm-service-defines.h
shared/nm-service-defines.h \
shared/nm-utils/nm-shared-utils.c

nm_ssh_service_LDADD = \
$(LIBNM_LIBS) $(GIO_LIBS)
Expand Down
10 changes: 10 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ PKG_CHECK_MODULES(LIBNM, libnm >= 1.1.0)
LIBNM_CFLAGS="$LIBNM_CFLAGS -DNM_VERSION_MIN_REQUIRED=NM_VERSION_1_2"
LIBNM_CFLAGS="$LIBNM_CFLAGS -DNM_VERSION_MAX_ALLOWED=NM_VERSION_1_2"

saved_LIBS="$LIBS"
saved_CFLAGS="$CFLAGS"
LIBS="$LIBNM_LIBS $LIBS"
CFLAGS="$LIBNM_CFLAGS $CFLAGS"
AC_CHECK_FUNC([nm_utils_copy_cert_as_user],
[AC_DEFINE([HAVE_NM_UTILS_COPY_CERT_AS_USER], [1],
[Define if nm_utils_copy_cert_as_user is available in libnm])])
LIBS="$saved_LIBS"
CFLAGS="$saved_CFLAGS"

NM_VPN_SERVICE_DIR=`$PKG_CONFIG --define-variable prefix='\${prefix}' --variable vpnservicedir libnm`
AC_SUBST(NM_VPN_SERVICE_DIR)

Expand Down
1 change: 1 addition & 0 deletions nm-ssh-service.name.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name=ssh
service=org.freedesktop.NetworkManager.ssh
program=@LIBEXECDIR@/nm-ssh-service
supports-multiple-connections=true
supports-safe-private-file-access=true

[libnm]
plugin=@PLUGINDIR@/libnm-vpn-plugin-ssh.so
Expand Down
266 changes: 266 additions & 0 deletions shared/nm-utils/nm-shared-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
#include "nm-shared-utils.h"

#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

/*****************************************************************************/

Expand Down Expand Up @@ -316,3 +322,263 @@ nm_g_object_set_property (GObject *object,
}

/*****************************************************************************/

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;
}

/*****************************************************************************/

#ifndef HAVE_NM_UTILS_COPY_CERT_AS_USER
/*
* Fallback implementation of nm_utils_copy_cert_as_user() for systems
* with NetworkManager < 1.52.2 (e.g. EL9, older Ubuntu LTS).
*
* Forks a child that drops privileges to @user, reads @filename, and
* pipes the content back to the parent, which writes it to a root-owned
* temporary file under /run/NetworkManager/cert/ (mode 0600).
*/
char *
nm_utils_copy_cert_as_user (const char *filename, const char *user, GError **error)
{
struct passwd *pw;
int pipefd[2];
pid_t pid;
char *tmp_path = NULL;
int tmp_fd = -1;
char buf[4096];
ssize_t n;
gboolean write_error = FALSE;
int status;

g_return_val_if_fail (filename, NULL);
g_return_val_if_fail (user, NULL);
g_return_val_if_fail (!error || !*error, NULL);

pw = getpwnam (user);
if (!pw) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Unknown user '%s'", user);
return NULL;
}

if (pipe (pipefd) < 0) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Failed to create pipe: %s", g_strerror (errno));
return NULL;
}

pid = fork ();
if (pid < 0) {
close (pipefd[0]);
close (pipefd[1]);
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Failed to fork: %s", g_strerror (errno));
return NULL;
}

if (pid == 0) {
/* Child: drop privileges to @user and stream the file to the pipe */
int fd;

close (pipefd[0]);

if (setgid (pw->pw_gid) < 0
|| initgroups (pw->pw_name, pw->pw_gid) < 0
|| setuid (pw->pw_uid) < 0) {
close (pipefd[1]);
_exit (1);
}

fd = open (filename, O_RDONLY);
if (fd < 0) {
close (pipefd[1]);
_exit (1);
}

while ((n = read (fd, buf, sizeof (buf))) > 0) {
if (write (pipefd[1], buf, n) != n) {
close (fd);
close (pipefd[1]);
_exit (1);
}
}
close (fd);
close (pipefd[1]);
_exit (0);
}

/* Parent: write pipe output to a root-only temp file */
close (pipefd[1]);

g_mkdir_with_parents ("/run/NetworkManager/cert", 0700);

tmp_path = g_strdup ("/run/NetworkManager/cert/nm-ssh-XXXXXX");
tmp_fd = mkstemp (tmp_path);
if (tmp_fd < 0) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Failed to create temporary file: %s", g_strerror (errno));
g_free (tmp_path);
close (pipefd[0]);
waitpid (pid, NULL, 0);
return NULL;
}

fchmod (tmp_fd, 0600);

while ((n = read (pipefd[0], buf, sizeof (buf))) > 0) {
if (write (tmp_fd, buf, n) != n) {
write_error = TRUE;
break;
}
}

close (tmp_fd);
close (pipefd[0]);
waitpid (pid, &status, 0);

if (write_error || !WIFEXITED (status) || WEXITSTATUS (status) != 0) {
unlink (tmp_path);
g_free (tmp_path);
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Failed to read '%s' as user '%s'", filename, user);
return NULL;
}

return tmp_path;
}
#endif /* HAVE_NM_UTILS_COPY_CERT_AS_USER */
21 changes: 21 additions & 0 deletions shared/nm-utils/nm-shared-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,25 @@ gboolean nm_g_object_set_property (GObject *object,

/*****************************************************************************/

typedef enum {
NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002,
} NMUtilsStrUtf8SafeFlags;

const char *nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free);
const char *nm_utils_str_utf8safe_unescape (const char *str, char **to_free);

char *nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags);
char *nm_utils_str_utf8safe_unescape_cp (const char *str);

char *nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags);


/*****************************************************************************/

#ifndef HAVE_NM_UTILS_COPY_CERT_AS_USER
char *nm_utils_copy_cert_as_user (const char *filename, const char *user, GError **error);
#endif

#endif /* __NM_SHARED_UTILS_H__ */
Loading
Loading