Merge pull request #21162 from poettering/homed-cifs-improvements

homed: various cifs backend improvements
This commit is contained in:
Lennart Poettering
2021-10-28 08:17:05 +02:00
committed by GitHub
12 changed files with 284 additions and 87 deletions
+76
View File
@@ -12,6 +12,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hostname-util.h"
#include "log.h"
#include "macro.h"
#include "missing_fcntl.h"
@@ -952,3 +953,78 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
return -EINTR;
}
int parse_cifs_service(
const char *s,
char **ret_host,
char **ret_service,
char **ret_path) {
_cleanup_free_ char *h = NULL, *ss = NULL, *x = NULL;
const char *p, *e, *d;
char delimiter;
/* Parses a CIFS service in form of //host/service/path… and splitting it in three parts. The last
* part is optional, in which case NULL is returned there. To maximize compatibility syntax with
* backslashes instead of slashes is accepted too. */
if (!s)
return -EINVAL;
p = startswith(s, "//");
if (!p) {
p = startswith(s, "\\\\");
if (!p)
return -EINVAL;
}
delimiter = s[0];
e = strchr(p, delimiter);
if (!e)
return -EINVAL;
h = strndup(p, e - p);
if (!h)
return -ENOMEM;
if (!hostname_is_valid(h, 0))
return -EINVAL;
e++;
d = strchrnul(e, delimiter);
ss = strndup(e, d - e);
if (!ss)
return -ENOMEM;
if (!filename_is_valid(ss))
return -EINVAL;
if (!isempty(d)) {
x = strdup(skip_leading_chars(d, CHAR_TO_STR(delimiter)));
if (!x)
return -EINVAL;
/* Make sure to convert Windows-style "\" → Unix-style / */
for (char *i = x; *i; i++)
if (*i == delimiter)
*i = '/';
if (!path_is_valid(x))
return -EINVAL;
path_simplify(x);
if (!path_is_normalized(x))
return -EINVAL;
}
if (ret_host)
*ret_host = TAKE_PTR(h);
if (ret_service)
*ret_service = TAKE_PTR(ss);
if (ret_path)
*ret_path = TAKE_PTR(x);
return 0;
}
+2
View File
@@ -106,3 +106,5 @@ static inline int conservative_rename(const char *oldpath, const char *newpath)
}
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size);
int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path);
+32 -8
View File
@@ -14,6 +14,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "fs-util.h"
#include "glyph-util.h"
#include "home-util.h"
#include "homectl-fido2.h"
@@ -2160,6 +2161,8 @@ static int help(int argc, char *argv[], void *userdata) {
" --cifs-domain=DOMAIN CIFS (Windows) domain\n"
" --cifs-user-name=USER CIFS (Windows) user name\n"
" --cifs-service=SERVICE CIFS (Windows) service to mount as home area\n"
" --cifs-extra-mount-options=OPTIONS\n"
" CIFS (Windows) extra mount options\n"
"\n%4$sLogin Behaviour User Record Properties:%5$s\n"
" --stop-delay=SECS How long to leave user services running after\n"
" logout\n"
@@ -2216,6 +2219,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_CIFS_DOMAIN,
ARG_CIFS_USER_NAME,
ARG_CIFS_SERVICE,
ARG_CIFS_EXTRA_MOUNT_OPTIONS,
ARG_TASKS_MAX,
ARG_MEMORY_HIGH,
ARG_MEMORY_MAX,
@@ -2308,6 +2312,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "cifs-user-name", required_argument, NULL, ARG_CIFS_USER_NAME },
{ "cifs-domain", required_argument, NULL, ARG_CIFS_DOMAIN },
{ "cifs-service", required_argument, NULL, ARG_CIFS_SERVICE },
{ "cifs-extra-mount-options", required_argument, NULL, ARG_CIFS_EXTRA_MOUNT_OPTIONS },
{ "rate-limit-interval", required_argument, NULL, ARG_RATE_LIMIT_INTERVAL },
{ "rate-limit-burst", required_argument, NULL, ARG_RATE_LIMIT_BURST },
{ "stop-delay", required_argument, NULL, ARG_STOP_DELAY },
@@ -2447,16 +2452,16 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_ICON_NAME:
case ARG_CIFS_USER_NAME:
case ARG_CIFS_DOMAIN:
case ARG_CIFS_SERVICE: {
case ARG_CIFS_EXTRA_MOUNT_OPTIONS: {
const char *field =
c == ARG_EMAIL_ADDRESS ? "emailAddress" :
c == ARG_LOCATION ? "location" :
c == ARG_ICON_NAME ? "iconName" :
c == ARG_CIFS_USER_NAME ? "cifsUserName" :
c == ARG_CIFS_DOMAIN ? "cifsDomain" :
c == ARG_CIFS_SERVICE ? "cifsService" :
NULL;
c == ARG_EMAIL_ADDRESS ? "emailAddress" :
c == ARG_LOCATION ? "location" :
c == ARG_ICON_NAME ? "iconName" :
c == ARG_CIFS_USER_NAME ? "cifsUserName" :
c == ARG_CIFS_DOMAIN ? "cifsDomain" :
c == ARG_CIFS_EXTRA_MOUNT_OPTIONS ? "cifsExtraMountOptions" :
NULL;
assert(field);
@@ -2475,6 +2480,25 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_CIFS_SERVICE:
if (isempty(optarg)) {
r = drop_from_identity("cifsService");
if (r < 0)
return r;
break;
}
r = parse_cifs_service(optarg, NULL, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg);
r = json_variant_set_field_string(&arg_identity_extra, "cifsService", optarg);
if (r < 0)
return log_error_errno(r, "Failed to set cifsService field: %m");
break;
case ARG_PASSWORD_HINT:
if (isempty(optarg)) {
r = drop_from_identity("passwordHint");
+102 -62
View File
@@ -7,6 +7,7 @@
#include "fs-util.h"
#include "homework-cifs.h"
#include "homework-mount.h"
#include "mkdir.h"
#include "mount-util.h"
#include "process-util.h"
#include "stat-util.h"
@@ -18,83 +19,125 @@ int home_setup_cifs(
HomeSetupFlags flags,
HomeSetup *setup) {
_cleanup_free_ char *chost = NULL, *cservice = NULL, *cdir = NULL, *chost_and_service = NULL, *j = NULL;
char **pw;
int r;
assert(h);
assert(setup);
assert(user_record_storage(h) == USER_CIFS);
assert(setup);
assert(!setup->undo_mount);
assert(setup->root_fd < 0);
if (FLAGS_SET(flags, HOME_SETUP_ALREADY_ACTIVATED))
if (FLAGS_SET(flags, HOME_SETUP_ALREADY_ACTIVATED)) {
setup->root_fd = open(user_record_home_directory(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
else {
bool mounted = false;
char **pw;
int r;
if (setup->root_fd < 0)
return log_error_errno(errno, "Failed to open home directory: %m");
r = home_unshare_and_mkdir();
return 0;
}
if (!h->cifs_service)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks CIFS service, refusing.");
r = parse_cifs_service(h->cifs_service, &chost, &cservice, &cdir);
if (r < 0)
return log_error_errno(r, "Failed parse CIFS service specification: %m");
/* Just the host and service part, without the directory */
chost_and_service = strjoin("//", chost, "/", cservice);
if (!chost_and_service)
return log_oom();
r = home_unshare_and_mkdir();
if (r < 0)
return r;
STRV_FOREACH(pw, h->password) {
_cleanup_(unlink_and_freep) char *p = NULL;
_cleanup_free_ char *options = NULL;
_cleanup_(fclosep) FILE *f = NULL;
pid_t mount_pid;
int exit_status;
r = fopen_temporary(NULL, &f, &p);
if (r < 0)
return r;
return log_error_errno(r, "Failed to create temporary credentials file: %m");
STRV_FOREACH(pw, h->password) {
_cleanup_(unlink_and_freep) char *p = NULL;
_cleanup_free_ char *options = NULL;
_cleanup_(fclosep) FILE *f = NULL;
pid_t mount_pid;
int exit_status;
fprintf(f,
"username=%s\n"
"password=%s\n",
user_record_cifs_user_name(h),
*pw);
r = fopen_temporary(NULL, &f, &p);
if (r < 0)
return log_error_errno(r, "Failed to create temporary credentials file: %m");
if (h->cifs_domain)
fprintf(f, "domain=%s\n", h->cifs_domain);
fprintf(f,
"username=%s\n"
"password=%s\n",
user_record_cifs_user_name(h),
*pw);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write temporary credentials file: %m");
if (h->cifs_domain)
fprintf(f, "domain=%s\n", h->cifs_domain);
f = safe_fclose(f);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write temporary credentials file: %m");
if (asprintf(&options, "credentials=%s,uid=" UID_FMT ",forceuid,gid=" GID_FMT ",forcegid,file_mode=0%3o,dir_mode=0%3o",
p, h->uid, user_record_gid(h), user_record_access_mode(h), user_record_access_mode(h)) < 0)
return log_oom();
f = safe_fclose(f);
if (asprintf(&options, "credentials=%s,uid=" UID_FMT ",forceuid,gid=" GID_FMT ",forcegid,file_mode=0%3o,dir_mode=0%3o",
p, h->uid, user_record_gid(h), user_record_access_mode(h), user_record_access_mode(h)) < 0)
if (h->cifs_extra_mount_options)
if (!strextend_with_separator(&options, ",", h->cifs_extra_mount_options))
return log_oom();
r = safe_fork("(mount)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR, &mount_pid);
if (r < 0)
return r;
if (r == 0) {
/* Child */
execl("/bin/mount", "/bin/mount", "-n", "-t", "cifs",
h->cifs_service, HOME_RUNTIME_WORK_DIR,
"-o", options, NULL);
r = safe_fork("(mount)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR, &mount_pid);
if (r < 0)
return r;
if (r == 0) {
/* Child */
execl("/bin/mount", "/bin/mount", "-n", "-t", "cifs",
chost_and_service, HOME_RUNTIME_WORK_DIR,
"-o", options, NULL);
log_error_errno(errno, "Failed to execute mount: %m");
_exit(EXIT_FAILURE);
}
log_error_errno(errno, "Failed to execute mount: %m");
_exit(EXIT_FAILURE);
}
exit_status = wait_for_terminate_and_check("mount", mount_pid, WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS);
if (exit_status < 0)
return exit_status;
if (exit_status != EXIT_SUCCESS)
return -EPROTO;
mounted = true;
exit_status = wait_for_terminate_and_check("mount", mount_pid, WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS);
if (exit_status < 0)
return exit_status;
if (exit_status == EXIT_SUCCESS) {
setup->undo_mount = true;
break;
}
if (!mounted)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
"Failed to mount home directory with supplied password.");
setup->root_fd = open(HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
if (pw[1])
log_info("CIFS mount failed with password #%zu, trying next password.", (size_t) (pw - h->password) + 1);
}
if (!setup->undo_mount)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
"Failed to mount home directory, supplied password(s) possibly wrong.");
/* Adjust MS_SUID and similar flags */
r = mount_nofollow_verbose(LOG_ERR, NULL, HOME_RUNTIME_WORK_DIR, NULL, MS_BIND|MS_REMOUNT|user_record_mount_flags(h), NULL);
if (r < 0)
return r;
if (cdir) {
j = path_join(HOME_RUNTIME_WORK_DIR, cdir);
if (!j)
return log_oom();
if (FLAGS_SET(flags, HOME_SETUP_CIFS_MKDIR)) {
r = mkdir_p(j, 0700);
if (r < 0)
return log_error_errno(r, "Failed to create CIFS subdirectory: %m");
}
}
setup->root_fd = open(j ?: HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
if (setup->root_fd < 0)
return log_error_errno(errno, "Failed to open home directory: %m");
setup->mount_suffix = TAKE_PTR(cdir);
return 0;
}
@@ -104,7 +147,7 @@ int home_activate_cifs(
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL;
const char *hdo, *hd;
int r;
@@ -113,23 +156,20 @@ int home_activate_cifs(
assert(setup);
assert(ret_home);
if (!h->cifs_service)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks CIFS service, refusing.");
assert_se(hdo = user_record_home_directory(h));
hd = strdupa_safe(hdo); /* copy the string out, since it might change later in the home record object */
r = home_setup_cifs(h, 0, setup);
r = home_setup(h, 0, cache, setup, &header_home);
if (r < 0)
return r;
r = home_refresh(h, setup, NULL, cache, NULL, &new_home);
r = home_refresh(h, setup, header_home, cache, NULL, &new_home);
if (r < 0)
return r;
setup->root_fd = safe_close(setup->root_fd);
r = home_move_mount(NULL, hd);
r = home_move_mount(setup->mount_suffix, hd);
if (r < 0)
return r;
@@ -161,7 +201,7 @@ int home_create_cifs(UserRecord *h, HomeSetup *setup, UserRecord **ret_home) {
return log_error_errno(errno, "Unable to detect whether /sbin/mount.cifs exists: %m");
}
r = home_setup_cifs(h, 0, setup);
r = home_setup_cifs(h, HOME_SETUP_CIFS_MKDIR, setup);
if (r < 0)
return r;
+5 -5
View File
@@ -87,17 +87,17 @@ int home_unshare_and_mount(const char *node, const char *fstype, bool discard, u
return home_mount_node(node, fstype, discard, flags);
}
int home_move_mount(const char *user_name_and_realm, const char *target) {
int home_move_mount(const char *mount_suffix, const char *target) {
_cleanup_free_ char *subdir = NULL;
const char *d;
int r;
assert(target);
/* If user_name_and_realm is set, then we'll mount a subdir of the source mount into the host. If
* it's NULL we'll move the mount itself */
if (user_name_and_realm) {
subdir = path_join(HOME_RUNTIME_WORK_DIR, user_name_and_realm);
/* If 'mount_suffix' is set, then we'll mount a subdir of the source mount into the host. If it's
* NULL we'll move the mount itself */
if (mount_suffix) {
subdir = path_join(HOME_RUNTIME_WORK_DIR, mount_suffix);
if (!subdir)
return log_oom();
+2
View File
@@ -379,6 +379,8 @@ int home_setup_done(HomeSetup *setup) {
if (setup->do_drop_caches)
drop_caches_now();
setup->mount_suffix = mfree(setup->mount_suffix);
return r;
}
+5
View File
@@ -37,6 +37,8 @@ typedef struct HomeSetup {
uint64_t partition_offset;
uint64_t partition_size;
char *mount_suffix; /* The directory to use as home dir is this path below /run/systemd/user-home-mount */
} HomeSetup;
typedef struct PasswordCache {
@@ -66,6 +68,9 @@ static inline bool password_cache_contains(const PasswordCache *cache, const cha
/* Various flags for the operation of setting up a home directory */
typedef enum HomeSetupFlags {
HOME_SETUP_ALREADY_ACTIVATED = 1 << 0, /* Open an already activated home, rather than activate it afresh */
/* CIFS backend: */
HOME_SETUP_CIFS_MKDIR = 1 << 1, /* Create CIFS subdir when missing */
} HomeSetupFlags;
int home_setup_done(HomeSetup *setup);
+3
View File
@@ -271,6 +271,7 @@ static UserRecord* user_record_free(UserRecord *h) {
free(h->cifs_service);
free(h->cifs_user_name);
free(h->cifs_domain);
free(h->cifs_extra_mount_options);
free(h->image_path);
free(h->image_path_auto);
@@ -1267,6 +1268,7 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "cifsExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_extra_mount_options), 0 },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
@@ -1612,6 +1614,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "cifsExtraMountOptions", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_extra_mount_options), 0 },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
+1
View File
@@ -304,6 +304,7 @@ typedef struct UserRecord {
char *cifs_domain;
char *cifs_user_name;
char *cifs_service;
char *cifs_extra_mount_options;
char *image_path;
char *image_path_auto; /* when none is configured explicitly, this is where we place the implicit image */
+31
View File
@@ -921,6 +921,36 @@ static void test_rmdir_parents(void) {
assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
}
static void test_parse_cifs_service_one(const char *f, const char *h, const char *s, const char *d, int ret) {
_cleanup_free_ char *a = NULL, *b = NULL, *c = NULL;
assert_se(parse_cifs_service(f, &a, &b, &c) == ret);
assert_se(streq_ptr(a, h));
assert_se(streq_ptr(b, s));
assert_se(streq_ptr(c, d));
}
static void test_parse_cifs_service(void) {
log_info("/* %s */", __func__);
test_parse_cifs_service_one("//foo/bar/baz", "foo", "bar", "baz", 0);
test_parse_cifs_service_one("\\\\foo\\bar\\baz", "foo", "bar", "baz", 0);
test_parse_cifs_service_one("//foo/bar", "foo", "bar", NULL, 0);
test_parse_cifs_service_one("\\\\foo\\bar", "foo", "bar", NULL, 0);
test_parse_cifs_service_one("//foo/bar/baz/uuu", "foo", "bar", "baz/uuu", 0);
test_parse_cifs_service_one("\\\\foo\\bar\\baz\\uuu", "foo", "bar", "baz/uuu", 0);
test_parse_cifs_service_one(NULL, NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("abc", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("abc/cde/efg", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//foo/bar/baz/..", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//foo///", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//foo/.", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//foo/a/.", NULL, NULL, NULL, -EINVAL);
test_parse_cifs_service_one("//./a", NULL, NULL, NULL, -EINVAL);
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_INFO);
@@ -940,6 +970,7 @@ int main(int argc, char *argv[]) {
test_chmod_and_chown();
test_conservative_rename();
test_rmdir_parents();
test_parse_cifs_service();
return 0;
}