diff --git a/man/userdbctl.xml b/man/userdbctl.xml index a7b438ad91..ff24ef8939 100644 --- a/man/userdbctl.xml +++ b/man/userdbctl.xml @@ -174,6 +174,75 @@ + + + + + When used with the user or group command, do a + fuzzy string search. Any specified arguments will be matched against the user name, the real name of + the user record, the email address, and other descriptive strings of the user or group + record. Moreover, instead of precise matching, a substring match or a match allowing slight + deviations in spelling is applied. + + + + + + + + When used with the user or group command, + filters by disposition of the record. Takes one of intrinsic, + system, regular, dynamic, + container. May be used multiple times, in which case only users matching any of + the specified dispositions are shown. + + + + + + + + + + Shortcuts for , + , , + respectively. + + + + + + + + + When used with the user or group command, + filters the output by UID/GID ranges. Takes numeric minimum resp. maximum UID/GID values. Shows only + records within the specified range. When applied to the user command matches + against UIDs, when applied to the group command against GIDs (despite the name of + the switch). If unspecified defaults to 0 (for the minimum) and 4294967294 (for the maximum), i.e. by + default no filtering is applied as the whole UID/GID range is covered. + + + + + + + + When used with the user or group command, + controls whether to show relevant UID/GID range boundary information in the tabular output. Takes a + boolean. Defaults to true. + + + + + + + + Shortcut for . + + + + diff --git a/src/shared/group-record.c b/src/shared/group-record.c index a297272fab..7b401bf064 100644 --- a/src/shared/group-record.c +++ b/src/shared/group-record.c @@ -326,3 +326,28 @@ int group_record_clone(GroupRecord *h, UserRecordLoadFlags flags, GroupRecord ** *ret = TAKE_PTR(c); return 0; } + +int group_record_match(GroupRecord *h, const UserDBMatch *match) { + assert(h); + assert(match); + + if (h->gid < match->gid_min || h->gid > match->gid_max) + return false; + + if (!FLAGS_SET(match->disposition_mask, UINT64_C(1) << group_record_disposition(h))) + return false; + + if (!strv_isempty(match->fuzzy_names)) { + const char* names[] = { + h->group_name, + group_record_group_name_and_realm(h), + h->description, + }; + + if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names)) + return false; + } + + return true; + +} diff --git a/src/shared/group-record.h b/src/shared/group-record.h index 054849b409..a2cef81c8a 100644 --- a/src/shared/group-record.h +++ b/src/shared/group-record.h @@ -43,5 +43,7 @@ int group_record_load(GroupRecord *h, sd_json_variant *v, UserRecordLoadFlags fl int group_record_build(GroupRecord **ret, ...); int group_record_clone(GroupRecord *g, UserRecordLoadFlags flags, GroupRecord **ret); +int group_record_match(GroupRecord *h, const UserDBMatch *match); + const char* group_record_group_name_and_realm(GroupRecord *h); UserDisposition group_record_disposition(GroupRecord *h); diff --git a/src/shared/user-record-nss.c b/src/shared/user-record-nss.c index a37957ee96..9223a2e6ca 100644 --- a/src/shared/user-record-nss.c +++ b/src/shared/user-record-nss.c @@ -104,37 +104,37 @@ int nss_passwd_to_user_record( * just a password instead of the whole account, but that's mostly pointless in times of * password-less authorization, hence let's not bother. */ - SET_IF(hr->locked, - spwd && spwd->sp_expire >= 0, - spwd->sp_expire <= 1, -1); + SET_IF(hr->locked, + spwd && spwd->sp_expire >= 0, + spwd->sp_expire <= 1, -1); - SET_IF(hr->not_after_usec, - spwd && spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY, - spwd->sp_expire * USEC_PER_DAY, UINT64_MAX); + SET_IF(hr->not_after_usec, + spwd && spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY, + spwd->sp_expire * USEC_PER_DAY, UINT64_MAX); - SET_IF(hr->password_change_now, - spwd && spwd->sp_lstchg >= 0, - spwd->sp_lstchg == 0, -1); + SET_IF(hr->password_change_now, + spwd && spwd->sp_lstchg >= 0, + spwd->sp_lstchg == 0, -1); - SET_IF(hr->last_password_change_usec, - spwd && spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY, - spwd->sp_lstchg * USEC_PER_DAY, UINT64_MAX); + SET_IF(hr->last_password_change_usec, + spwd && spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY, + spwd->sp_lstchg * USEC_PER_DAY, UINT64_MAX); - SET_IF(hr->password_change_min_usec, - spwd && spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY, - spwd->sp_min * USEC_PER_DAY, UINT64_MAX); + SET_IF(hr->password_change_min_usec, + spwd && spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY, + spwd->sp_min * USEC_PER_DAY, UINT64_MAX); - SET_IF(hr->password_change_max_usec, - spwd && spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY, - spwd->sp_max * USEC_PER_DAY, UINT64_MAX); + SET_IF(hr->password_change_max_usec, + spwd && spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY, + spwd->sp_max * USEC_PER_DAY, UINT64_MAX); - SET_IF(hr->password_change_warn_usec, - spwd && spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY, - spwd->sp_warn * USEC_PER_DAY, UINT64_MAX); + SET_IF(hr->password_change_warn_usec, + spwd && spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY, + spwd->sp_warn * USEC_PER_DAY, UINT64_MAX); - SET_IF(hr->password_change_inactive_usec, - spwd && spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY, - spwd->sp_inact * USEC_PER_DAY, UINT64_MAX); + SET_IF(hr->password_change_inactive_usec, + spwd && spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY, + spwd->sp_inact * USEC_PER_DAY, UINT64_MAX); hr->json = sd_json_variant_unref(hr->json); r = sd_json_buildo( diff --git a/src/shared/user-record.c b/src/shared/user-record.c index f14a38e03b..12447a9337 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -2401,6 +2401,72 @@ int suitable_blob_filename(const char *name) { name[0] != '.'; } +bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches) { + assert(names || n_names == 0); + + /* Checks if any of the user record strings in the names[] array matches any of the search strings in + * the matches** strv fuzzily. */ + + FOREACH_ARRAY(n, names, n_names) { + if (!*n) + continue; + + _cleanup_free_ char *lcn = strdup(*n); + if (!lcn) + return -ENOMEM; + + ascii_strlower(lcn); + + STRV_FOREACH(i, matches) { + _cleanup_free_ char *lc = strdup(*i); + if (!lc) + return -ENOMEM; + + ascii_strlower(lc); + + /* First do substring check */ + if (strstr(lcn, lc)) + return true; + + /* Then do some fuzzy string comparison (but only if the needle is non-trivially long) */ + if (strlen(lc) >= 5 && strlevenshtein(lcn, lc) < 3) + return true; + } + } + + return false; +} + +int user_record_match(UserRecord *u, const UserDBMatch *match) { + assert(u); + assert(match); + + if (u->uid < match->uid_min || u->uid > match->uid_max) + return false; + + if (!FLAGS_SET(match->disposition_mask, UINT64_C(1) << user_record_disposition(u))) + return false; + + if (!strv_isempty(match->fuzzy_names)) { + + /* Note this array of names is sparse, i.e. various entries listed in it will be + * NULL. Because of that we are not using a NULL terminated strv here, but a regular + * array. */ + const char* names[] = { + u->user_name, + user_record_user_name_and_realm(u), + u->real_name, + u->email_address, + u->cifs_user_name, + }; + + if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names)) + return false; + } + + return true; +} + static const char* const user_storage_table[_USER_STORAGE_MAX] = { [USER_CLASSIC] = "classic", [USER_LUKS] = "luks", diff --git a/src/shared/user-record.h b/src/shared/user-record.h index 2a0e92d69a..0443820890 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -462,6 +462,24 @@ int user_group_record_mangle(sd_json_variant *v, UserRecordLoadFlags load_flags, #define BLOB_DIR_MAX_SIZE (UINT64_C(64) * U64_MB) int suitable_blob_filename(const char *name); +typedef struct UserDBMatch { + char **fuzzy_names; + uint64_t disposition_mask; + union { + uid_t uid_min; + gid_t gid_min; + }; + union { + uid_t uid_max; + gid_t gid_max; + }; +} UserDBMatch; + +#define USER_DISPOSITION_MASK_MAX ((UINT64_C(1) << _USER_DISPOSITION_MAX) - UINT64_C(1)) + +bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches); +int user_record_match(UserRecord *u, const UserDBMatch *match); + const char* user_storage_to_string(UserStorage t) _const_; UserStorage user_storage_from_string(const char *s) _pure_; diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 5997f9604f..d74251fd86 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -37,6 +37,11 @@ static char** arg_services = NULL; static UserDBFlags arg_userdb_flags = 0; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; static bool arg_chain = false; +static uint64_t arg_disposition_mask = UINT64_MAX; +static uid_t arg_uid_min = 0; +static uid_t arg_uid_max = UID_INVALID-1; +static bool arg_fuzzy = false; +static bool arg_boundaries = true; STATIC_DESTRUCTOR_REGISTER(arg_services, strv_freep); @@ -63,6 +68,10 @@ static const char *user_disposition_to_color(UserDisposition d) { } } +static const char* shell_to_color(const char *shell) { + return !shell || is_nologin_shell(shell) ? ansi_grey() : NULL; +} + static int show_user(UserRecord *ur, Table *table) { int r; @@ -99,10 +108,9 @@ static int show_user(UserRecord *ur, Table *table) { break; case OUTPUT_TABLE: { - UserDisposition d; - assert(table); - d = user_record_disposition(ur); + UserDisposition d = user_record_disposition(ur); + const char *sh = user_record_shell(ur); r = table_add_many( table, @@ -113,8 +121,9 @@ static int show_user(UserRecord *ur, Table *table) { TABLE_UID, ur->uid, TABLE_GID, user_record_gid(ur), TABLE_STRING, empty_to_null(ur->real_name), - TABLE_STRING, user_record_home_directory(ur), - TABLE_STRING, user_record_shell(ur), + TABLE_PATH, user_record_home_directory(ur), + TABLE_PATH, sh, + TABLE_SET_COLOR, shell_to_color(sh), TABLE_INT, 0); if (r < 0) return table_log_add_error(r); @@ -176,6 +185,9 @@ static int table_add_uid_boundaries(Table *table, const UIDRange *p) { FOREACH_ELEMENT(i, uid_range_table) { _cleanup_free_ char *name = NULL, *comment = NULL; + if (!FLAGS_SET(arg_disposition_mask, UINT64_C(1) << i->disposition)) + continue; + if (!uid_range_covers(p, i->first, i->last - i->first + 1)) continue; @@ -346,7 +358,7 @@ static int display_user(int argc, char *argv[], void *userdata) { int ret = 0, r; if (arg_output < 0) - arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE; + arg_output = argc > 1 && !arg_fuzzy ? OUTPUT_FRIENDLY : OUTPUT_TABLE; if (arg_output == OUTPUT_TABLE) { table = table_new(" ", "name", "disposition", "uid", "gid", "realname", "home", "shell", "order"); @@ -357,10 +369,18 @@ static int display_user(int argc, char *argv[], void *userdata) { (void) table_set_align_percent(table, table_get_cell(table, 0, 4), 100); table_set_ersatz_string(table, TABLE_ERSATZ_DASH); (void) table_set_sort(table, (size_t) 3, (size_t) 8); - (void) table_set_display(table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4, (size_t) 5, (size_t) 6, (size_t) 7); + (void) table_hide_column_from_display(table, (size_t) 8); + if (!arg_boundaries) + (void) table_hide_column_from_display(table, (size_t) 0); } - if (argc > 1) + UserDBMatch match = { + .disposition_mask = arg_disposition_mask, + .uid_min = arg_uid_min, + .uid_max = arg_uid_max, + }; + + if (argc > 1 && !arg_fuzzy) STRV_FOREACH(i, argv + 1) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; uid_t uid; @@ -377,8 +397,10 @@ static int display_user(int argc, char *argv[], void *userdata) { else log_error_errno(r, "Failed to find user %s: %m", *i); - if (ret >= 0) - ret = r; + RET_GATHER(ret, r); + } else if (!user_record_match(ur, &match)) { + log_error("User '%s' does not match filter.", *i); + RET_GATHER(ret, -ENOEXEC); } else { if (draw_separator && arg_output == OUTPUT_FRIENDLY) putchar('\n'); @@ -392,6 +414,15 @@ static int display_user(int argc, char *argv[], void *userdata) { } else { _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; + _cleanup_strv_free_ char **names = NULL; + + if (argc > 1) { + names = strv_copy(argv + 1); + if (!names) + return log_oom(); + + match.fuzzy_names = names; + } r = userdb_all(arg_userdb_flags, &iterator); if (r == -ENOLINK) /* ENOLINK → Didn't find answer without Varlink, and didn't try Varlink because was configured to off. */ @@ -412,6 +443,9 @@ static int display_user(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed acquire next user: %m"); + if (!user_record_match(ur, &match)) + continue; + if (draw_separator && arg_output == OUTPUT_FRIENDLY) putchar('\n'); @@ -425,20 +459,23 @@ static int display_user(int argc, char *argv[], void *userdata) { } if (table) { - _cleanup_(uid_range_freep) UIDRange *uid_range = NULL; - int boundary_lines, uid_map_lines; + int boundary_lines = 0, uid_map_lines = 0; - r = uid_range_load_userns(/* path = */ NULL, UID_RANGE_USERNS_INSIDE, &uid_range); - if (r < 0) - log_debug_errno(r, "Failed to load /proc/self/uid_map, ignoring: %m"); + if (arg_boundaries) { + _cleanup_(uid_range_freep) UIDRange *uid_range = NULL; - boundary_lines = table_add_uid_boundaries(table, uid_range); - if (boundary_lines < 0) - return boundary_lines; + r = uid_range_load_userns(/* path = */ NULL, UID_RANGE_USERNS_INSIDE, &uid_range); + if (r < 0) + log_debug_errno(r, "Failed to load /proc/self/uid_map, ignoring: %m"); - uid_map_lines = table_add_uid_map(table, uid_range, add_unavailable_uid); - if (uid_map_lines < 0) - return uid_map_lines; + boundary_lines = table_add_uid_boundaries(table, uid_range); + if (boundary_lines < 0) + return boundary_lines; + + uid_map_lines = table_add_uid_map(table, uid_range, add_unavailable_uid); + if (uid_map_lines < 0) + return uid_map_lines; + } if (!table_isempty(table)) { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); @@ -650,7 +687,7 @@ static int display_group(int argc, char *argv[], void *userdata) { int ret = 0, r; if (arg_output < 0) - arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE; + arg_output = argc > 1 && !arg_fuzzy ? OUTPUT_FRIENDLY : OUTPUT_TABLE; if (arg_output == OUTPUT_TABLE) { table = table_new(" ", "name", "disposition", "gid", "description", "order"); @@ -660,10 +697,18 @@ static int display_group(int argc, char *argv[], void *userdata) { (void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100); table_set_ersatz_string(table, TABLE_ERSATZ_DASH); (void) table_set_sort(table, (size_t) 3, (size_t) 5); - (void) table_set_display(table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4); + (void) table_hide_column_from_display(table, (size_t) 5); + if (!arg_boundaries) + (void) table_hide_column_from_display(table, (size_t) 0); } - if (argc > 1) + UserDBMatch match = { + .disposition_mask = arg_disposition_mask, + .gid_min = arg_uid_min, + .gid_max = arg_uid_max, + }; + + if (argc > 1 && !arg_fuzzy) STRV_FOREACH(i, argv + 1) { _cleanup_(group_record_unrefp) GroupRecord *gr = NULL; gid_t gid; @@ -680,8 +725,10 @@ static int display_group(int argc, char *argv[], void *userdata) { else log_error_errno(r, "Failed to find group %s: %m", *i); - if (ret >= 0) - ret = r; + RET_GATHER(ret, r); + } else if (!group_record_match(gr, &match)) { + log_error("Group '%s' does not match filter.", *i); + RET_GATHER(ret, -ENOEXEC); } else { if (draw_separator && arg_output == OUTPUT_FRIENDLY) putchar('\n'); @@ -695,6 +742,15 @@ static int display_group(int argc, char *argv[], void *userdata) { } else { _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; + _cleanup_strv_free_ char **names = NULL; + + if (argc > 1) { + names = strv_copy(argv + 1); + if (!names) + return log_oom(); + + match.fuzzy_names = names; + } r = groupdb_all(arg_userdb_flags, &iterator); if (r == -ENOLINK) @@ -715,6 +771,9 @@ static int display_group(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed acquire next group: %m"); + if (!group_record_match(gr, &match)) + continue; + if (draw_separator && arg_output == OUTPUT_FRIENDLY) putchar('\n'); @@ -728,20 +787,22 @@ static int display_group(int argc, char *argv[], void *userdata) { } if (table) { - _cleanup_(uid_range_freep) UIDRange *gid_range = NULL; - int boundary_lines, gid_map_lines; + int boundary_lines = 0, gid_map_lines = 0; - r = uid_range_load_userns(/* path = */ NULL, GID_RANGE_USERNS_INSIDE, &gid_range); - if (r < 0) - log_debug_errno(r, "Failed to load /proc/self/gid_map, ignoring: %m"); + if (arg_boundaries) { + _cleanup_(uid_range_freep) UIDRange *gid_range = NULL; + r = uid_range_load_userns(/* path = */ NULL, GID_RANGE_USERNS_INSIDE, &gid_range); + if (r < 0) + log_debug_errno(r, "Failed to load /proc/self/gid_map, ignoring: %m"); - boundary_lines = table_add_gid_boundaries(table, gid_range); - if (boundary_lines < 0) - return boundary_lines; + boundary_lines = table_add_gid_boundaries(table, gid_range); + if (boundary_lines < 0) + return boundary_lines; - gid_map_lines = table_add_uid_map(table, gid_range, add_unavailable_gid); - if (gid_map_lines < 0) - return gid_map_lines; + gid_map_lines = table_add_uid_map(table, gid_range, add_unavailable_gid); + if (gid_map_lines < 0) + return gid_map_lines; + } if (!table_isempty(table)) { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); @@ -1090,6 +1151,15 @@ static int help(int argc, char *argv[], void *userdata) { " --multiplexer=BOOL Control whether to use the multiplexer\n" " --json=pretty|short JSON output mode\n" " --chain Chain another command\n" + " --uid-min=ID Filter by minimum UID/GID (default 0)\n" + " --uid-max=ID Filter by maximum UID/GID (default 4294967294)\n" + " -z --fuzzy Do a fuzzy name search\n" + " --disposition=VALUE Filter by disposition\n" + " -I Equivalent to --disposition=intrinsic\n" + " -S Equivalent to --disposition=system\n" + " -R Equivalent to --disposition=regular\n" + " --boundaries=BOOL Show/hide UID/GID range boundaries in output\n" + " -B Equivalent to --boundaries=no\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -1113,6 +1183,10 @@ static int parse_argv(int argc, char *argv[]) { ARG_MULTIPLEXER, ARG_JSON, ARG_CHAIN, + ARG_UID_MIN, + ARG_UID_MAX, + ARG_DISPOSITION, + ARG_BOUNDARIES, }; static const struct option options[] = { @@ -1129,6 +1203,11 @@ static int parse_argv(int argc, char *argv[]) { { "multiplexer", required_argument, NULL, ARG_MULTIPLEXER }, { "json", required_argument, NULL, ARG_JSON }, { "chain", no_argument, NULL, ARG_CHAIN }, + { "uid-min", required_argument, NULL, ARG_UID_MIN }, + { "uid-max", required_argument, NULL, ARG_UID_MAX }, + { "fuzzy", required_argument, NULL, 'z' }, + { "disposition", required_argument, NULL, ARG_DISPOSITION }, + { "boundaries", required_argument, NULL, ARG_BOUNDARIES }, {} }; @@ -1159,7 +1238,7 @@ static int parse_argv(int argc, char *argv[]) { int c; c = getopt_long(argc, argv, - arg_chain ? "+hjs:N" : "hjs:N", /* When --chain was used disable parsing of further switches */ + arg_chain ? "+hjs:NISRzB" : "hjs:NISRzB", /* When --chain was used disable parsing of further switches */ options, NULL); if (c < 0) break; @@ -1275,6 +1354,65 @@ static int parse_argv(int argc, char *argv[]) { arg_chain = true; break; + case ARG_DISPOSITION: { + UserDisposition d = user_disposition_from_string(optarg); + if (d < 0) + return log_error_errno(d, "Unknown user disposition: %s", optarg); + + if (arg_disposition_mask == UINT64_MAX) + arg_disposition_mask = 0; + + arg_disposition_mask |= UINT64_C(1) << d; + break; + } + + case 'I': + if (arg_disposition_mask == UINT64_MAX) + arg_disposition_mask = 0; + + arg_disposition_mask |= UINT64_C(1) << USER_INTRINSIC; + break; + + case 'S': + if (arg_disposition_mask == UINT64_MAX) + arg_disposition_mask = 0; + + arg_disposition_mask |= UINT64_C(1) << USER_SYSTEM; + break; + + case 'R': + if (arg_disposition_mask == UINT64_MAX) + arg_disposition_mask = 0; + + arg_disposition_mask |= UINT64_C(1) << USER_REGULAR; + break; + + case ARG_UID_MIN: + r = parse_uid(optarg, &arg_uid_min); + if (r < 0) + return log_error_errno(r, "Failed to parse --uid-min= value: %s", optarg); + break; + + case ARG_UID_MAX: + r = parse_uid(optarg, &arg_uid_max); + if (r < 0) + return log_error_errno(r, "Failed to parse --uid-max= value: %s", optarg); + break; + + case 'z': + arg_fuzzy = true; + break; + + case ARG_BOUNDARIES: + r = parse_boolean_argument("boundaries", optarg, &arg_boundaries); + if (r < 0) + return r; + break; + + case 'B': + arg_boundaries = false; + break; + case '?': return -EINVAL; @@ -1283,6 +1421,13 @@ static int parse_argv(int argc, char *argv[]) { } } + if (arg_uid_min > arg_uid_max) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Minimum UID/GID " UID_FMT " is above maximum UID/GID " UID_FMT ", refusing.", arg_uid_min, arg_uid_max); + + /* If not mask was specified, use the all bits on mask */ + if (arg_disposition_mask == UINT64_MAX) + arg_disposition_mask = USER_DISPOSITION_MASK_MAX; + return 1; } diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 61590a1fa4..e858b0c297 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -426,6 +426,13 @@ userdbctl -j --json=short | jq userdbctl --with-varlink=no userdbctl user +userdbctl user -S +userdbctl user -IS +userdbctl user -R +userdbctl user --disposition=regular --disposition=intrinsic +userdbctl user kkkk -z +userdbctl user --uid-min=100 --uid-max=100 +userdbctl user -B userdbctl user testuser userdbctl user root userdbctl user testuser root @@ -448,6 +455,13 @@ userdbctl user --with-nss=no 2000000 (! userdbctl user --with-dropin=no 2000000) userdbctl group +userdbctl group -S +userdbctl group -IS +userdbctl group -R +userdbctl group --disposition=regular --disposition=intrinsic +userdbctl group kkkk -z +userdbctl group --uid-min=100 --uid-max=100 +userdbctl group -B userdbctl group testuser userdbctl group root userdbctl group testuser root