/* * ConnMan VPN daemon utils * * Copyright (C) 2020 Jolla Ltd. All rights reserved. * Copyright (C) 2020 Open Mobile Platform LLC. * Contact: jussi.laakkonen@jolla.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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 General Public License for more details. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "vpn.h" static bool is_string_digits(const char *str) { int i; if (!str || !*str) return false; for (i = 0; str[i]; i++) { if (!g_ascii_isdigit(str[i])) return false; } return true; } static uid_t get_str_id(const char *username) { if (!username || !*username) return 0; return (uid_t)g_ascii_strtoull(username, NULL, 10); } struct passwd *vpn_util_get_passwd(const char *username) { struct passwd *pwd; uid_t uid; if (!username || !*username) return NULL; if (is_string_digits(username)) { uid = get_str_id(username); pwd = getpwuid(uid); } else { pwd = getpwnam(username); } return pwd; } struct group *vpn_util_get_group(const char *groupname) { struct group *grp; gid_t gid; if (!groupname || !*groupname) return NULL; if (is_string_digits(groupname)) { gid = get_str_id(groupname); grp = getgrgid(gid); } else { grp = getgrnam(groupname); } return grp; } /* * These prefixes are used for checking if the requested path for * vpn_util_create_path() is acceptable. Allow only prefixes meant for run-time * or temporary use to limit the access to any system resources. * * VPN core and plugins would need to create only temporary dirs for the * run-time use. The requested dirs can be created for a specific user when * running a VPN plugin as a different user and thus, user specific run dir is * allowed and limitation to access any other system dir is restricted. */ static const char *allowed_prefixes[] = { RUNSTATEDIR "/connman-vpn/", RUNSTATEDIR "/user/", "/tmp/", NULL }; static int is_path_allowed(const char *path) { int err = -EPERM; int i; if (!path || !*path || !g_path_is_absolute(path)) return -EINVAL; if (g_strrstr(path, "..") || g_strrstr(path, "./")) return -EPERM; for (i = 0; allowed_prefixes[i]; i++) { if (g_str_has_prefix(path, allowed_prefixes[i])) { const char *suffix = path + strlen(allowed_prefixes[i]); /* * Don't allow plain prefixes, an additional dir must * be included after the prexix in the requested path. */ if (suffix && *suffix != G_DIR_SEPARATOR && g_strrstr(suffix, G_DIR_SEPARATOR_S)) { DBG("allowed %s, has suffix %s", path, suffix); err = 0; } break; } } return err; } int vpn_util_create_path(const char *path, uid_t uid, gid_t grp, int mode) { mode_t old_umask; char *dir_p; int err; err = is_path_allowed(path); if (err) return err; dir_p = g_path_get_dirname(path); if (!dir_p) return -ENOMEM; err = g_unlink(dir_p); if (err) err = -errno; switch (err) { case 0: /* Removed */ case -ENOENT: /* Did not exist */ break; case -EACCES: /* * Cannot get write access to the containing directory, check * if the path exists. */ if (!g_file_test(dir_p, G_FILE_TEST_EXISTS)) goto out; /* If the dir does not exist new one cannot be created */ if (!g_file_test(dir_p, G_FILE_TEST_IS_DIR)) goto out; /* Do a chmod as the dir exists */ /* fallthrough */ case -EISDIR: /* Exists as dir, just chmod and change owner */ err = g_chmod(dir_p, mode); if (err) { connman_warn("chmod %s failed, err %d", dir_p, err); err = -errno; } goto chown; default: /* Any other error that is not handled here */ connman_warn("remove %s failed, err %d", dir_p, err); goto out; } /* Set dir creation mask to correspond to the mode */ old_umask = umask(~mode & 0777); DBG("mkdir %s", dir_p); err = g_mkdir_with_parents(dir_p, mode); umask(old_umask); if (err) { connman_warn("mkdir %s failed, err %d", dir_p, err); err = -errno; goto out; } chown: if (uid && grp) { err = chown(dir_p, uid, grp); if (err) { err = -errno; connman_warn("chown %s failed for %d/%d, err %d", dir_p, uid, grp, err); } } out: g_free(dir_p); return err; }