| /* |
| * 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 <config.h> |
| #endif |
| |
| #include <errno.h> |
| #include <unistd.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <pwd.h> |
| #include <grp.h> |
| |
| #include <glib/gstdio.h> |
| |
| #include <connman/log.h> |
| |
| #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[] = { "/var/run/connman-vpn/", |
| "/var/run/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; |
| } |
| |