diff --git a/docs/USER_RECORD.md b/docs/USER_RECORD.md index 6b607dfd45..c134ec42e3 100644 --- a/docs/USER_RECORD.md +++ b/docs/USER_RECORD.md @@ -411,7 +411,12 @@ 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. + +`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` @@ -705,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 c2b1ec6c9b..01e9c3b29b 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -688,10 +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. + 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. The setting allows specifying + additional mount options when mounting the share, see mount.cifs8 + for details. 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/home/homectl.c b/src/home/homectl.c index 3af1381eb4..4f1aebfe30 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" @@ -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"); diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 896b1bf78b..6a4431c229 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,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; 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(); diff --git a/src/home/homework.c b/src/home/homework.c index 8dc9004c06..6b60fddc2c 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -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; } diff --git a/src/home/homework.h b/src/home/homework.h index 0ce8457727..1b56fbbd8f 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); 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 */ 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; }