#include "sd-bus.h"
#include "ask-password-api.h"
+#include "build.h"
#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-locator.h"
+#include "cap-list.h"
+#include "capability-util.h"
#include "cgroup-util.h"
#include "dns-domain.h"
#include "env-util.h"
#include "spawn-polkit-agent.h"
#include "terminal-util.h"
#include "uid-alloc-range.h"
+#include "user-record.h"
#include "user-record-pwquality.h"
#include "user-record-show.h"
#include "user-record-util.h"
-#include "user-record.h"
#include "user-util.h"
#include "verbs.h"
static char **arg_pkcs11_token_uri = NULL;
static char **arg_fido2_device = NULL;
static Fido2EnrollFlags arg_fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP;
+#if HAVE_LIBFIDO2
+static int arg_fido2_cred_alg = COSE_ES256;
+#else
+static int arg_fido2_cred_alg = 0;
+#endif
static bool arg_recovery_key = false;
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static bool arg_and_resize = false;
EXPORT_FORMAT_STRIPPED, /* strip "state" + "binding", but leave signature in place */
EXPORT_FORMAT_MINIMAL, /* also strip signature */
} arg_export_format = EXPORT_FORMAT_FULL;
+static uint64_t arg_capability_bounding_set = UINT64_MAX;
+static uint64_t arg_capability_ambient_set = UINT64_MAX;
STATIC_DESTRUCTOR_REGISTER(arg_identity_extra, json_variant_unrefp);
STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_this_machine, json_variant_unrefp);
AskPasswordFlags flags) {
_cleanup_(strv_free_erasep) char **password = NULL;
+ _cleanup_(erase_and_freep) char *envpw = NULL;
_cleanup_free_ char *question = NULL;
- char *e;
int r;
assert(user_name);
assert(hr);
- e = getenv("PASSWORD");
- if (e) {
+ r = getenv_steal_erase("PASSWORD", &envpw);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire password from environment: %m");
+ if (r > 0) {
/* People really shouldn't use environment variables for passing passwords. We support this
* only for testing purposes, and do not document the behaviour, so that people won't
* actually use this outside of testing. */
- r = user_record_set_password(hr, STRV_MAKE(e), true);
+ r = user_record_set_password(hr, STRV_MAKE(envpw), true);
if (r < 0)
return log_error_errno(r, "Failed to store password: %m");
- assert_se(unsetenv_erase("PASSWORD") >= 0);
return 1;
}
AskPasswordFlags flags) {
_cleanup_(strv_free_erasep) char **recovery_key = NULL;
+ _cleanup_(erase_and_freep) char *envpw = NULL;
_cleanup_free_ char *question = NULL;
- char *e;
int r;
assert(user_name);
assert(hr);
- e = getenv("RECOVERY_KEY");
- if (e) {
+ r = getenv_steal_erase("PASSWORD", &envpw);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire password from environment: %m");
+ if (r > 0) {
/* People really shouldn't use environment variables for passing secrets. We support this
* only for testing purposes, and do not document the behaviour, so that people won't
* actually use this outside of testing. */
- r = user_record_set_password(hr, STRV_MAKE(e), true); /* recovery keys are stored in the record exactly like regular passwords! */
+ r = user_record_set_password(hr, STRV_MAKE(envpw), true); /* recovery keys are stored in the record exactly like regular passwords! */
if (r < 0)
return log_error_errno(r, "Failed to store recovery key: %m");
- assert_se(unsetenv_erase("RECOVERY_KEY") >= 0);
return 1;
}
AskPasswordFlags flags) {
_cleanup_(strv_free_erasep) char **pin = NULL;
+ _cleanup_(erase_and_freep) char *envpin = NULL;
_cleanup_free_ char *question = NULL;
- char *e;
int r;
assert(user_name);
assert(hr);
- e = getenv("PIN");
- if (e) {
- r = user_record_set_token_pin(hr, STRV_MAKE(e), false);
+ r = getenv_steal_erase("PIN", &envpin);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire PIN from environment: %m");
+ if (r > 0) {
+ r = user_record_set_token_pin(hr, STRV_MAKE(envpin), false);
if (r < 0)
return log_error_errno(r, "Failed to store token PIN: %m");
- assert_se(unsetenv_erase("PIN") >= 0);
return 1;
}
else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT))
return log_error_errno(SYNTHETIC_ERRNO(ETOOMANYREFS),
- "Too frequent unsuccessful login attempts for user %s, try again later.", user_name);
+ "Too frequent login attempts for user %s, try again later.", user_name);
else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD)) {
static int activate_home(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, ret = 0;
- char **i;
r = acquire_bus(&bus);
if (r < 0)
static int deactivate_home(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, ret = 0;
- char **i;
r = acquire_bus(&bus);
if (r < 0)
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(strv_freep) char **mangled_list = NULL;
int r, ret = 0;
- char **items, **i;
+ char **items;
pager_open(arg_pager_flags);
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(strv_freep) char **mangled_list = NULL;
int r, ret = 0;
- char **i, **items;
+ char **items;
items = mangle_user_list(strv_skip(argv, 1), &mangled_list);
if (!items)
static int acquire_new_home_record(UserRecord **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
- char **i;
int r;
assert(ret);
}
STRV_FOREACH(i, arg_fido2_device) {
- r = identity_add_fido2_parameters(&v, *i, arg_fido2_lock_with);
+ r = identity_add_fido2_parameters(&v, *i, arg_fido2_lock_with, arg_fido2_cred_alg);
if (r < 0)
return r;
}
bool suggest,
char **ret) {
+ _cleanup_(erase_and_freep) char *envpw = NULL;
unsigned i = 5;
- char *e;
int r;
assert(user_name);
assert(hr);
- e = getenv("NEWPASSWORD");
- if (e) {
- _cleanup_(erase_and_freep) char *copy = NULL;
-
+ r = getenv_steal_erase("NEWPASSWORD", &envpw);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire password from environment: %m");
+ if (r > 0) {
/* As above, this is not for use, just for testing */
- if (ret) {
- copy = strdup(e);
- if (!copy)
- return log_oom();
- }
-
- r = user_record_set_password(hr, STRV_MAKE(e), /* prepend = */ true);
+ r = user_record_set_password(hr, STRV_MAKE(envpw), /* prepend = */ true);
if (r < 0)
return log_error_errno(r, "Failed to store password: %m");
- assert_se(unsetenv_erase("NEWPASSWORD") >= 0);
-
if (ret)
- *ret = TAKE_PTR(copy);
+ *ret = TAKE_PTR(envpw);
return 0;
}
static int remove_home(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, ret = 0;
- char **i;
r = acquire_bus(&bus);
if (r < 0)
_cleanup_(json_variant_unrefp) JsonVariant *json = NULL;
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
- char **i;
int r;
assert(ret);
}
STRV_FOREACH(i, arg_fido2_device) {
- r = identity_add_fido2_parameters(&json, *i, arg_fido2_lock_with);
+ r = identity_add_fido2_parameters(&json, *i, arg_fido2_lock_with, arg_fido2_cred_alg);
if (r < 0)
return r;
}
int r;
if (arg_pkcs11_token_uri)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "To change the PKCS#11 security token use 'homectl update --pkcs11-token-uri=…'.");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "To change the PKCS#11 security token use 'homectl update --pkcs11-token-uri=%s'.",
+ special_glyph(SPECIAL_GLYPH_ELLIPSIS));
if (arg_fido2_device)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "To change the FIDO2 security token use 'homectl update --fido2-device=…'.");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "To change the FIDO2 security token use 'homectl update --fido2-device=%s'.",
+ special_glyph(SPECIAL_GLYPH_ELLIPSIS));
if (identity_properties_specified())
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The 'passwd' verb does not permit changing other record properties at the same time.");
static int lock_home(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, ret = 0;
- char **i;
r = acquire_bus(&bus);
if (r < 0)
static int unlock_home(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, ret = 0;
- char **i;
r = acquire_bus(&bus);
if (r < 0)
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
- _cleanup_close_ int acquired_fd = -1;
+ _cleanup_close_ int acquired_fd = -EBADF;
_cleanup_strv_free_ char **cmdline = NULL;
const char *home;
int r, ret;
" -d --home-dir=PATH Home directory\n"
" -u --uid=UID Numeric UID for user\n"
" -G --member-of=GROUP Add user to group\n"
+ " --capability-bounding-set=CAPS\n"
+ " Bounding POSIX capability set\n"
+ " --capability-ambient-set=CAPS\n"
+ " Ambient POSIX capability set\n"
" --skel=PATH Skeleton directory to use\n"
" --shell=PATH Shell for account\n"
" --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n"
" Memory cost for PBKDF in bytes\n"
" --luks-pbkdf-parallel-threads=NUMBER\n"
" Number of parallel threads for PKBDF\n"
+ " --luks-sector-size=BYTES\n"
+ " Sector size for LUKS encryption in bytes\n"
" --luks-extra-mount-options=OPTIONS\n"
" LUKS extra mount options\n"
" --auto-resize-mode=MODE Automatically grow/shrink home on login/logout\n"
ARG_IO_WEIGHT,
ARG_LUKS_PBKDF_TYPE,
ARG_LUKS_PBKDF_HASH_ALGORITHM,
+ ARG_LUKS_PBKDF_FORCE_ITERATIONS,
ARG_LUKS_PBKDF_TIME_COST,
ARG_LUKS_PBKDF_MEMORY_COST,
ARG_LUKS_PBKDF_PARALLEL_THREADS,
+ ARG_LUKS_SECTOR_SIZE,
ARG_RATE_LIMIT_INTERVAL,
ARG_RATE_LIMIT_BURST,
ARG_STOP_DELAY,
ARG_LUKS_EXTRA_MOUNT_OPTIONS,
ARG_AUTO_RESIZE_MODE,
ARG_REBALANCE_WEIGHT,
+ ARG_FIDO2_CRED_ALG,
+ ARG_CAPABILITY_BOUNDING_SET,
+ ARG_CAPABILITY_AMBIENT_SET,
};
static const struct option options[] = {
{ "luks-volume-key-size", required_argument, NULL, ARG_LUKS_VOLUME_KEY_SIZE },
{ "luks-pbkdf-type", required_argument, NULL, ARG_LUKS_PBKDF_TYPE },
{ "luks-pbkdf-hash-algorithm", required_argument, NULL, ARG_LUKS_PBKDF_HASH_ALGORITHM },
+ { "luks-pbkdf-force-iterations", required_argument, NULL, ARG_LUKS_PBKDF_FORCE_ITERATIONS },
{ "luks-pbkdf-time-cost", required_argument, NULL, ARG_LUKS_PBKDF_TIME_COST },
{ "luks-pbkdf-memory-cost", required_argument, NULL, ARG_LUKS_PBKDF_MEMORY_COST },
{ "luks-pbkdf-parallel-threads", required_argument, NULL, ARG_LUKS_PBKDF_PARALLEL_THREADS },
+ { "luks-sector-size", required_argument, NULL, ARG_LUKS_SECTOR_SIZE },
{ "nosuid", required_argument, NULL, ARG_NOSUID },
{ "nodev", required_argument, NULL, ARG_NODEV },
{ "noexec", required_argument, NULL, ARG_NOEXEC },
{ "json", required_argument, NULL, ARG_JSON },
{ "export-format", required_argument, NULL, ARG_EXPORT_FORMAT },
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
+ { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG },
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
{ "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN },
{ "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP },
{ "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS },
{ "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE },
{ "rebalance-weight", required_argument, NULL, ARG_REBALANCE_WEIGHT },
+ { "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET },
+ { "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET },
{}
};
case ARG_DISK_SIZE:
if (isempty(optarg)) {
- const char *prop;
-
FOREACH_STRING(prop, "diskSize", "diskSizeRelative", "rebalanceWeight") {
r = drop_from_identity(prop);
if (r < 0)
break;
case ARG_LUKS_VOLUME_KEY_SIZE:
+ case ARG_LUKS_PBKDF_FORCE_ITERATIONS:
case ARG_LUKS_PBKDF_PARALLEL_THREADS:
case ARG_RATE_LIMIT_BURST: {
const char *field =
c == ARG_LUKS_VOLUME_KEY_SIZE ? "luksVolumeKeySize" :
+ c == ARG_LUKS_PBKDF_FORCE_ITERATIONS ? "luksPbkdfForceIterations" :
c == ARG_LUKS_PBKDF_PARALLEL_THREADS ? "luksPbkdfParallelThreads" :
c == ARG_RATE_LIMIT_BURST ? "rateLimitBurst" : NULL;
unsigned n;
break;
}
+ case ARG_LUKS_SECTOR_SIZE: {
+ uint64_t ss;
+
+ if (isempty(optarg)) {
+ r = drop_from_identity("luksSectorSize");
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ r = parse_sector_size(optarg, &ss);
+ if (r < 0)
+ return r;
+
+ r = json_variant_set_field_unsigned(&arg_identity_extra, "luksSectorSize", ss);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set sector size field: %m");
+
+ break;
+ }
+
case ARG_UMASK: {
mode_t m;
break;
}
- case ARG_PKCS11_TOKEN_URI: {
- const char *p;
-
+ case ARG_PKCS11_TOKEN_URI:
if (streq(optarg, "list"))
return pkcs11_list_tokens();
strv_uniq(arg_pkcs11_token_uri);
break;
- }
- case ARG_FIDO2_DEVICE: {
- const char *p;
+ case ARG_FIDO2_CRED_ALG:
+ r = parse_fido2_algorithm(optarg, &arg_fido2_cred_alg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse COSE algorithm: %s", optarg);
+ break;
+ case ARG_FIDO2_DEVICE:
if (streq(optarg, "list"))
return fido2_list_devices();
strv_uniq(arg_fido2_device);
break;
- }
case ARG_FIDO2_WITH_PIN: {
bool lock_with_pin;
break;
}
- case ARG_RECOVERY_KEY: {
- const char *p;
-
+ case ARG_RECOVERY_KEY:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg);
}
break;
- }
case ARG_AUTO_RESIZE_MODE:
if (isempty(optarg)) {
r = drop_from_identity("rebalanceWeight");
if (r < 0)
return r;
+ break;
}
if (streq(optarg, "off"))
return log_error_errno(r, "Failed to parse --rebalance-weight= argument: %s", optarg);
if (u < REBALANCE_WEIGHT_MIN || u > REBALANCE_WEIGHT_MAX)
- return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Rebalancing weight out of valid range %" PRIu64 "…%" PRIu64 ": %s",
- REBALANCE_WEIGHT_MIN, REBALANCE_WEIGHT_MAX, optarg);
+ return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Rebalancing weight out of valid range %" PRIu64 "%s%" PRIu64 ": %s",
+ REBALANCE_WEIGHT_MIN, special_glyph(SPECIAL_GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX, optarg);
}
/* Drop from per machine stuff and everywhere */
r = drop_from_identity("dropCaches");
if (r < 0)
return r;
+ break;
}
r = parse_boolean_argument("--drop-caches=", optarg, &drop_caches);
break;
}
+ case ARG_CAPABILITY_AMBIENT_SET:
+ case ARG_CAPABILITY_BOUNDING_SET: {
+ _cleanup_strv_free_ char **l = NULL;
+ bool subtract = false;
+ uint64_t parsed, *which, updated;
+ const char *p, *field;
+
+ if (c == ARG_CAPABILITY_AMBIENT_SET) {
+ which = &arg_capability_ambient_set;
+ field = "capabilityAmbientSet";
+ } else {
+ assert(c == ARG_CAPABILITY_BOUNDING_SET);
+ which = &arg_capability_bounding_set;
+ field = "capabilityBoundingSet";
+ }
+
+ if (isempty(optarg)) {
+ r = drop_from_identity(field);
+ if (r < 0)
+ return r;
+
+ *which = UINT64_MAX;
+ break;
+ }
+
+ p = optarg;
+ if (*p == '~') {
+ subtract = true;
+ p++;
+ }
+
+ r = capability_set_from_string(p, &parsed);
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capabilities in capability string '%s'.", p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse capability string '%s': %m", p);
+
+ if (*which == UINT64_MAX)
+ updated = subtract ? all_capabilities() & ~parsed : parsed;
+ else if (subtract)
+ updated = *which & ~parsed;
+ else
+ updated = *which | parsed;
+
+ if (capability_set_to_strv(updated, &l) < 0)
+ return log_oom();
+
+ r = json_variant_set_field_strv(&arg_identity_extra, field, l);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set %s field: %m", field);
+
+ *which = updated;
+ break;
+ }
+
case '?':
return -EINVAL;