From de7df6c3a6d533c9702b0c7cb8f337ac8c741a04 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Oct 2021 18:40:39 +0200 Subject: [PATCH 01/10] homework: pass header user record in home_activate_cifs() Of course unlike in the LUKS case there's not actually any user record stored in the LUKS header, so what we pass here will always be NULL. The reason why I am changing is to make this more alike the other home_activate_xyz() calls, and passing this around doesn't hurt. (A later commit will replace all backend-specific home_activate_xyz() calls by a single one) --- src/home/homework-cifs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 896b1bf78b..43e0ad5c1e 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -104,7 +104,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; @@ -119,11 +119,11 @@ int home_activate_cifs( 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; From 812e5876966216388216d31afb0e6367d2a87b4d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Oct 2021 18:45:27 +0200 Subject: [PATCH 02/10] homework: rework home_setup_cifs() to store "mounted" variable in HomeSetup We already have a field for that, let's use it. Let's also reduce the indentation level a bit. No change in behaviour. --- src/home/homework-cifs.c | 129 ++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 62 deletions(-) diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 43e0ad5c1e..399df37b41 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -18,80 +18,85 @@ int home_setup_cifs( HomeSetupFlags flags, HomeSetup *setup) { + 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; + } + + 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 log_error_errno(r, "Failed to create temporary credentials file: %m"); + + fprintf(f, + "username=%s\n" + "password=%s\n", + user_record_cifs_user_name(h), + *pw); + + if (h->cifs_domain) + fprintf(f, "domain=%s\n", h->cifs_domain); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write temporary credentials file: %m"); + + 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) + 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); - 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 log_error_errno(r, "Failed to create temporary credentials file: %m"); - - fprintf(f, - "username=%s\n" - "password=%s\n", - user_record_cifs_user_name(h), - *pw); - - if (h->cifs_domain) - fprintf(f, "domain=%s\n", h->cifs_domain); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write temporary credentials file: %m"); - - 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) - 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); - - 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; - break; + log_error_errno(errno, "Failed to execute mount: %m"); + _exit(EXIT_FAILURE); } - if (!mounted) - return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), - "Failed to mount home directory with supplied password."); + 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; - setup->root_fd = open(HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); + setup->undo_mount = true; + break; } + + if (!setup->undo_mount) + 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 (setup->root_fd < 0) return log_error_errno(errno, "Failed to open home directory: %m"); From 5971c318d43e83698e691cf6bbfc3b8f7771a502 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Oct 2021 18:47:24 +0200 Subject: [PATCH 03/10] homework: move check for CIFS service field initialization to home_setup_cifs() We need this field not only during activation but any kind of setup, hence let's move it into the setup code. --- src/home/homework-cifs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 399df37b41..f0a1d51b8f 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -35,6 +35,9 @@ int home_setup_cifs( return 0; } + if (!h->cifs_service) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks CIFS service, refusing."); + r = home_unshare_and_mkdir(); if (r < 0) return r; @@ -118,9 +121,6 @@ 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 */ From c9080dfb0b31d2a55755b13947812394b4719c44 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Oct 2021 18:48:30 +0200 Subject: [PATCH 04/10] homework: apply mount flags also for CIFS mounts --- src/home/homework-cifs.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index f0a1d51b8f..b920053163 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -99,6 +99,11 @@ int home_setup_cifs( return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to mount home directory with supplied password."); + /* 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; + setup->root_fd = open(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"); From 68def5a975fd8c6dd2061250a0f7aeb8c149ea7b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Oct 2021 15:49:00 +0200 Subject: [PATCH 05/10] fs-util: add helper that can split CIFS services names --- src/basic/fs-util.c | 76 +++++++++++++++++++++++++++++++++++++++++ src/basic/fs-util.h | 2 ++ src/test/test-fs-util.c | 31 +++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index a60ac240ec..65468b51b5 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -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; +} diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index f8a7657a07..0d69fa6edb 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -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); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index cd024ef7f1..41ddec4783 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -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; } From 16b81da684ed20aa94346f4979fa1a4e8cb1abad Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Oct 2021 15:49:42 +0200 Subject: [PATCH 06/10] homectl: validate CIFS service name before accepting it --- src/home/homectl.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 3af1381eb4..502329eedd 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -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" @@ -2446,8 +2447,7 @@ static int parse_argv(int argc, char *argv[]) { case ARG_LOCATION: case ARG_ICON_NAME: case ARG_CIFS_USER_NAME: - case ARG_CIFS_DOMAIN: - case ARG_CIFS_SERVICE: { + case ARG_CIFS_DOMAIN: { const char *field = c == ARG_EMAIL_ADDRESS ? "emailAddress" : @@ -2455,7 +2455,6 @@ static int parse_argv(int argc, char *argv[]) { c == ARG_ICON_NAME ? "iconName" : c == ARG_CIFS_USER_NAME ? "cifsUserName" : c == ARG_CIFS_DOMAIN ? "cifsDomain" : - c == ARG_CIFS_SERVICE ? "cifsService" : NULL; assert(field); @@ -2475,6 +2474,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"); From 2b9855f9d2262f8cc083bf3a372539319a3dd3bd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Oct 2021 15:51:00 +0200 Subject: [PATCH 07/10] homework: make home_move_mount() a bit more generic by renaming first parameter No actual code change, let's just rename the first parameter, to make it more generically useful in case the first argument is an arbitrary path, not necessarily a username/realm. --- src/home/homework-mount.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/home/homework-mount.c b/src/home/homework-mount.c index 7ab503fb7a..5758e85839 100644 --- a/src/home/homework-mount.c +++ b/src/home/homework-mount.c @@ -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(); From bf15879b39b82783de145d84ed87135c140b2be8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 22 Oct 2021 15:52:23 +0200 Subject: [PATCH 08/10] homework: allow specifying a dir component in CIFS services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow specifying CIFS services in the format //host/service/subdir/… to allow multiple homedirs on the same share, and not in the main dir of the share. All other backends allow placing the data store at arbitrary places, let's allow this too for the CIFS backend. This is particularly useful for testing. --- docs/USER_RECORD.md | 4 +++- man/homectl.xml | 7 +++++-- src/home/homework-cifs.c | 32 ++++++++++++++++++++++++++++---- src/home/homework.c | 2 ++ src/home/homework.h | 5 +++++ 5 files changed, 43 insertions(+), 7 deletions(-) diff --git a/docs/USER_RECORD.md b/docs/USER_RECORD.md index 6b607dfd45..6710b00c0c 100644 --- a/docs/USER_RECORD.md +++ b/docs/USER_RECORD.md @@ -411,7 +411,9 @@ useful when `cifs` is used as storage mechanism for the user's home directory, see above. `cifsService` → A string indicating the Windows File Share service (CIFS) to -mount as home directory of the user on login. +mount as home directory of the user on login. Should be in format +`////`. The directory part is optional. If missing +the top-level directory of the CIFS share is used. `imagePath` → A string with an absolute file system path to the file, directory or block device to use for storage backing the home directory. If the `luks` diff --git a/man/homectl.xml b/man/homectl.xml index c2b1ec6c9b..f670566593 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -690,8 +690,11 @@ SERVICE Configures the Windows File Sharing (CIFS) domain and user to associate with the home - directory/user account, as well as the file share ("service") to mount as directory. The latter is used when - cifs storage is selected. + directory/user account, as well as the file share ("service") to mount as directory. The latter is + used when cifs storage is selected. The file share should be specified in format + //host/share/directory/…. The + directory part is optional — if not specified the home directory will be placed in the top-level + directory of the share. diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index b920053163..55b8c2588d 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -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,6 +19,7 @@ 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; @@ -38,6 +40,15 @@ int home_setup_cifs( 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; @@ -78,7 +89,7 @@ int home_setup_cifs( if (r == 0) { /* Child */ execl("/bin/mount", "/bin/mount", "-n", "-t", "cifs", - h->cifs_service, HOME_RUNTIME_WORK_DIR, + chost_and_service, HOME_RUNTIME_WORK_DIR, "-o", options, NULL); log_error_errno(errno, "Failed to execute mount: %m"); @@ -104,10 +115,23 @@ int home_setup_cifs( if (r < 0) return r; - setup->root_fd = open(HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); + 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; } @@ -139,7 +163,7 @@ int home_activate_cifs( 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; @@ -171,7 +195,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; diff --git a/src/home/homework.c b/src/home/homework.c index cfc0c945de..8ffed82742 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -365,6 +365,8 @@ int home_setup_done(HomeSetup *setup) { if (setup->do_drop_caches) drop_caches_now(); + setup->mount_suffix = mfree(setup->mount_suffix); + return r; } diff --git a/src/home/homework.h b/src/home/homework.h index 1fa5a1e37a..08d97b8261 100644 --- a/src/home/homework.h +++ b/src/home/homework.h @@ -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); From 22aba9b2fcb5db1b48abb267f12a1420fd15ea4d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Oct 2021 16:41:31 +0200 Subject: [PATCH 09/10] homework: actually try all supplied passwords Unfortunately mount.cifs doesn't really let us know much about the reason for the failure. Hence, assume it's caused by a bad password, and retry on any failure with additional passwords that we might have. A loop to do this was always in place, but none of the possible codepaths actually allowed to iterate more than once. Fix that. --- src/home/homework-cifs.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 55b8c2588d..6184d7c30e 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -99,16 +99,18 @@ int home_setup_cifs( 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; + if (exit_status == EXIT_SUCCESS) { + setup->undo_mount = true; + break; + } - setup->undo_mount = true; - break; + 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 with supplied password."); + "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); From 4c2ee5c7f26fda41d7eb1250c61c85cc869a90de Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 Oct 2021 16:58:56 +0200 Subject: [PATCH 10/10] homework: allow specifying explicit additional mount options when using CIFS backend This is useful since certain shares can only be mounted with additional mount flags. For example the SMB share in modern AVM Fritz!Boxes requires "noserverino" to be set to work from Linux. --- docs/USER_RECORD.md | 22 +++++++++++++--------- man/homectl.xml | 6 +++++- src/home/homectl.c | 20 +++++++++++++------- src/home/homework-cifs.c | 4 ++++ src/shared/user-record.c | 3 +++ src/shared/user-record.h | 1 + 6 files changed, 39 insertions(+), 17 deletions(-) diff --git a/docs/USER_RECORD.md b/docs/USER_RECORD.md index 6710b00c0c..c134ec42e3 100644 --- a/docs/USER_RECORD.md +++ b/docs/USER_RECORD.md @@ -415,6 +415,9 @@ mount as home directory of the user on login. Should be in format `////`. The directory part is optional. If missing the top-level directory of the CIFS share is used. +`cifsExtraMountOptions` → A string with additional mount options to pass to +`mount.cifs` when mounting the home directory CIFS share. + `imagePath` → A string with an absolute file system path to the file, directory or block device to use for storage backing the home directory. If the `luks` storage is used this refers to the loopback file or block device node to store @@ -707,15 +710,16 @@ that may be used in this section are identical to the equally named ones in the `notAfterUSec`, `storage`, `diskSize`, `diskSizeRelative`, `skeletonDirectory`, `accessMode`, `tasksMax`, `memoryHigh`, `memoryMax`, `cpuWeight`, `ioWeight`, `mountNoDevices`, `mountNoSuid`, `mountNoExecute`, `cifsDomain`, -`cifsUserName`, `cifsService`, `imagePath`, `uid`, `gid`, `memberOf`, -`fileSystemType`, `partitionUuid`, `luksUuid`, `fileSystemUuid`, `luksDiscard`, -`luksOfflineDiscard`, `luksCipher`, `luksCipherMode`, `luksVolumeKeySize`, -`luksPbkdfHashAlgorithm`, `luksPbkdfType`, `luksPbkdfTimeCostUSec`, -`luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`, `rateLimitIntervalUSec`, -`rateLimitBurst`, `enforcePasswordPolicy`, `autoLogin`, `stopDelayUSec`, -`killProcesses`, `passwordChangeMinUSec`, `passwordChangeMaxUSec`, -`passwordChangeWarnUSec`, `passwordChangeInactiveUSec`, `passwordChangeNow`, -`pkcs11TokenUri`, `fido2HmacCredential`. +`cifsUserName`, `cifsService`, `cifsExtraMountOptions`, `imagePath`, `uid`, +`gid`, `memberOf`, `fileSystemType`, `partitionUuid`, `luksUuid`, +`fileSystemUuid`, `luksDiscard`, `luksOfflineDiscard`, `luksCipher`, +`luksCipherMode`, `luksVolumeKeySize`, `luksPbkdfHashAlgorithm`, +`luksPbkdfType`, `luksPbkdfTimeCostUSec`, `luksPbkdfMemoryCost`, +`luksPbkdfParallelThreads`, `rateLimitIntervalUSec`, `rateLimitBurst`, +`enforcePasswordPolicy`, `autoLogin`, `stopDelayUSec`, `killProcesses`, +`passwordChangeMinUSec`, `passwordChangeMaxUSec`, `passwordChangeWarnUSec`, +`passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`, +`fido2HmacCredential`. ## Fields in the `binding` section diff --git a/man/homectl.xml b/man/homectl.xml index f670566593..01e9c3b29b 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -688,13 +688,17 @@ DOMAIN USER SERVICE + OPTIONS Configures the Windows File Sharing (CIFS) domain and user to associate with the home directory/user account, as well as the file share ("service") to mount as directory. The latter is used when cifs storage is selected. The file share should be specified in format //host/share/directory/…. The directory part is optional — if not specified the home directory will be placed in the top-level - directory of the share. + directory of the share. The setting allows specifying + additional mount options when mounting the share, see mount.cifs8 + for details. diff --git a/src/home/homectl.c b/src/home/homectl.c index 502329eedd..4f1aebfe30 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2161,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" @@ -2217,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, @@ -2309,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,15 +2451,17 @@ static int parse_argv(int argc, char *argv[]) { case ARG_LOCATION: case ARG_ICON_NAME: case ARG_CIFS_USER_NAME: - case ARG_CIFS_DOMAIN: { + case ARG_CIFS_DOMAIN: + 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" : - 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); diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 6184d7c30e..6a4431c229 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -83,6 +83,10 @@ int home_setup_cifs( p, h->uid, user_record_gid(h), user_record_access_mode(h), user_record_access_mode(h)) < 0) return log_oom(); + 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; diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 9b2029bfcf..49febade2a 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -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 }, diff --git a/src/shared/user-record.h b/src/shared/user-record.h index 975e3e175b..acf2cdc9d4 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -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 */