blob: 9ef14d38c2352a55811fb61eee594d25cda168d4 [file] [log] [blame]
/*
* 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;
}