| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include "format-util.h" |
| #include "fs-util.h" |
| #include "process-util.h" |
| #include "rlimit-util.h" |
| #include "strv.h" |
| #include "terminal-util.h" |
| #include "user-record-show.h" |
| #include "user-util.h" |
| #include "userdb.h" |
| |
| const char *user_record_state_color(const char *state) { |
| if (STR_IN_SET(state, "unfixated", "absent")) |
| return ansi_grey(); |
| else if (streq(state, "active")) |
| return ansi_highlight_green(); |
| else if (STR_IN_SET(state, "locked", "dirty")) |
| return ansi_highlight_yellow(); |
| |
| return NULL; |
| } |
| |
| void user_record_show(UserRecord *hr, bool show_full_group_info) { |
| const char *hd, *ip, *shell; |
| UserStorage storage; |
| usec_t t; |
| size_t k; |
| int r, b; |
| |
| printf(" User name: %s\n", |
| user_record_user_name_and_realm(hr)); |
| |
| if (hr->state) { |
| const char *color; |
| |
| color = user_record_state_color(hr->state); |
| |
| printf(" State: %s%s%s\n", |
| strempty(color), hr->state, color ? ansi_normal() : ""); |
| } |
| |
| printf(" Disposition: %s\n", user_disposition_to_string(user_record_disposition(hr))); |
| |
| if (hr->last_change_usec != USEC_INFINITY) { |
| char buf[FORMAT_TIMESTAMP_MAX]; |
| printf(" Last Change: %s\n", format_timestamp(buf, sizeof(buf), hr->last_change_usec)); |
| |
| if (hr->last_change_usec > now(CLOCK_REALTIME)) |
| printf(" %sModification time lies in the future, system clock wrong?%s\n", |
| ansi_highlight_yellow(), ansi_normal()); |
| } |
| |
| if (hr->last_password_change_usec != USEC_INFINITY && |
| hr->last_password_change_usec != hr->last_change_usec) { |
| char buf[FORMAT_TIMESTAMP_MAX]; |
| printf(" Last Passw.: %s\n", format_timestamp(buf, sizeof(buf), hr->last_password_change_usec)); |
| } |
| |
| r = user_record_test_blocked(hr); |
| switch (r) { |
| |
| case -ENOLCK: |
| printf(" Login OK: %sno%s (record is locked)\n", ansi_highlight_red(), ansi_normal()); |
| break; |
| |
| case -EL2HLT: |
| printf(" Login OK: %sno%s (record not valid yet))\n", ansi_highlight_red(), ansi_normal()); |
| break; |
| |
| case -EL3HLT: |
| printf(" Login OK: %sno%s (record not valid anymore))\n", ansi_highlight_red(), ansi_normal()); |
| break; |
| |
| case -ESTALE: |
| default: { |
| usec_t y; |
| |
| if (r < 0 && r != -ESTALE) { |
| errno = -r; |
| printf(" Login OK: %sno%s (%m)\n", ansi_highlight_red(), ansi_normal()); |
| break; |
| } |
| |
| if (is_nologin_shell(user_record_shell(hr))) { |
| printf(" Login OK: %sno%s (nologin shell)\n", ansi_highlight_red(), ansi_normal()); |
| break; |
| } |
| |
| y = user_record_ratelimit_next_try(hr); |
| if (y != USEC_INFINITY && y > now(CLOCK_REALTIME)) { |
| printf(" Login OK: %sno%s (ratelimit)\n", ansi_highlight_red(), ansi_normal()); |
| break; |
| } |
| |
| printf(" Login OK: %syes%s\n", ansi_highlight_green(), ansi_normal()); |
| break; |
| }} |
| |
| r = user_record_test_password_change_required(hr); |
| switch (r) { |
| |
| case -EKEYREVOKED: |
| printf(" Password OK: %schange now%s\n", ansi_highlight_yellow(), ansi_normal()); |
| break; |
| |
| case -EOWNERDEAD: |
| printf(" Password OK: %sexpired%s (change now!)\n", ansi_highlight_yellow(), ansi_normal()); |
| break; |
| |
| case -EKEYREJECTED: |
| printf(" Password OK: %sexpired%s (for good)\n", ansi_highlight_red(), ansi_normal()); |
| break; |
| |
| case -EKEYEXPIRED: |
| printf(" Password OK: %sexpires soon%s\n", ansi_highlight_yellow(), ansi_normal()); |
| break; |
| |
| case -ENETDOWN: |
| printf(" Password OK: %sno timestamp%s\n", ansi_highlight_red(), ansi_normal()); |
| break; |
| |
| case -EROFS: |
| printf(" Password OK: %schange not permitted%s\n", ansi_highlight_yellow(), ansi_normal()); |
| break; |
| |
| case -ESTALE: |
| printf(" Password OK: %slast password change in future%s\n", ansi_highlight_yellow(), ansi_normal()); |
| break; |
| |
| default: |
| if (r < 0) { |
| errno = -r; |
| printf(" Password OK: %sno%s (%m)\n", ansi_highlight_yellow(), ansi_normal()); |
| break; |
| } |
| |
| printf(" Password OK: %syes%s\n", ansi_highlight_green(), ansi_normal()); |
| break; |
| } |
| |
| if (uid_is_valid(hr->uid)) |
| printf(" UID: " UID_FMT "\n", hr->uid); |
| if (gid_is_valid(hr->gid)) { |
| if (show_full_group_info) { |
| _cleanup_(group_record_unrefp) GroupRecord *gr = NULL; |
| |
| r = groupdb_by_gid(hr->gid, 0, &gr); |
| if (r < 0) { |
| errno = -r; |
| printf(" GID: " GID_FMT " (unresolvable: %m)\n", hr->gid); |
| } else |
| printf(" GID: " GID_FMT " (%s)\n", hr->gid, gr->group_name); |
| } else |
| printf(" GID: " GID_FMT "\n", hr->gid); |
| } else if (uid_is_valid(hr->uid)) /* Show UID as GID if not separately configured */ |
| printf(" GID: " GID_FMT "\n", (gid_t) hr->uid); |
| |
| if (show_full_group_info) { |
| _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; |
| |
| r = membershipdb_by_user(hr->user_name, 0, &iterator); |
| if (r < 0) { |
| errno = -r; |
| printf(" Aux. Groups: (can't acquire: %m)\n"); |
| } else { |
| const char *prefix = " Aux. Groups:"; |
| |
| for (;;) { |
| _cleanup_free_ char *group = NULL; |
| |
| r = membershipdb_iterator_get(iterator, NULL, &group); |
| if (r == -ESRCH) |
| break; |
| if (r < 0) { |
| errno = -r; |
| printf("%s (can't iterate: %m)\n", prefix); |
| break; |
| } |
| |
| printf("%s %s\n", prefix, group); |
| prefix = " "; |
| } |
| } |
| } |
| |
| if (hr->real_name && !streq(hr->real_name, hr->user_name)) |
| printf(" Real Name: %s\n", hr->real_name); |
| |
| hd = user_record_home_directory(hr); |
| if (hd) |
| printf(" Directory: %s\n", hd); |
| |
| storage = user_record_storage(hr); |
| if (storage >= 0) /* Let's be political, and clarify which storage we like, and which we don't. About CIFS we don't complain. */ |
| printf(" Storage: %s%s\n", user_storage_to_string(storage), |
| storage == USER_LUKS ? " (strong encryption)" : |
| storage == USER_FSCRYPT ? " (weak encryption)" : |
| IN_SET(storage, USER_DIRECTORY, USER_SUBVOLUME) ? " (no encryption)" : ""); |
| |
| ip = user_record_image_path(hr); |
| if (ip && !streq_ptr(ip, hd)) |
| printf(" Image Path: %s\n", ip); |
| |
| b = user_record_removable(hr); |
| if (b >= 0) |
| printf(" Removable: %s\n", yes_no(b)); |
| |
| shell = user_record_shell(hr); |
| if (shell) |
| printf(" Shell: %s\n", shell); |
| |
| if (hr->email_address) |
| printf(" Email: %s\n", hr->email_address); |
| if (hr->location) |
| printf(" Location: %s\n", hr->location); |
| if (hr->password_hint) |
| printf(" Passw. Hint: %s\n", hr->password_hint); |
| if (hr->icon_name) |
| printf(" Icon Name: %s\n", hr->icon_name); |
| |
| if (hr->time_zone) |
| printf(" Time Zone: %s\n", hr->time_zone); |
| |
| if (hr->preferred_language) |
| printf(" Language: %s\n", hr->preferred_language); |
| |
| if (!strv_isempty(hr->environment)) { |
| char **i; |
| |
| STRV_FOREACH(i, hr->environment) { |
| printf(i == hr->environment ? |
| " Environment: %s\n" : |
| " %s\n", *i); |
| } |
| } |
| |
| if (hr->locked >= 0) |
| printf(" Locked: %s\n", yes_no(hr->locked)); |
| |
| if (hr->not_before_usec != UINT64_MAX) { |
| char buf[FORMAT_TIMESTAMP_MAX]; |
| printf(" Not Before: %s\n", format_timestamp(buf, sizeof(buf), hr->not_before_usec)); |
| } |
| |
| if (hr->not_after_usec != UINT64_MAX) { |
| char buf[FORMAT_TIMESTAMP_MAX]; |
| printf(" Not After: %s\n", format_timestamp(buf, sizeof(buf), hr->not_after_usec)); |
| } |
| |
| if (hr->umask != MODE_INVALID) |
| printf(" UMask: 0%03o\n", hr->umask); |
| |
| if (nice_is_valid(hr->nice_level)) |
| printf(" Nice: %i\n", hr->nice_level); |
| |
| for (int j = 0; j < _RLIMIT_MAX; j++) { |
| if (hr->rlimits[j]) |
| printf(" Limit: RLIMIT_%s=%" PRIu64 ":%" PRIu64 "\n", |
| rlimit_to_string(j), (uint64_t) hr->rlimits[j]->rlim_cur, (uint64_t) hr->rlimits[j]->rlim_max); |
| } |
| |
| if (hr->tasks_max != UINT64_MAX) |
| printf(" Tasks Max: %" PRIu64 "\n", hr->tasks_max); |
| |
| if (hr->memory_high != UINT64_MAX) { |
| char buf[FORMAT_BYTES_MAX]; |
| printf(" Memory High: %s\n", format_bytes(buf, sizeof(buf), hr->memory_high)); |
| } |
| |
| if (hr->memory_max != UINT64_MAX) { |
| char buf[FORMAT_BYTES_MAX]; |
| printf(" Memory Max: %s\n", format_bytes(buf, sizeof(buf), hr->memory_max)); |
| } |
| |
| if (hr->cpu_weight != UINT64_MAX) |
| printf(" CPU Weight: %" PRIu64 "\n", hr->cpu_weight); |
| |
| if (hr->io_weight != UINT64_MAX) |
| printf(" IO Weight: %" PRIu64 "\n", hr->io_weight); |
| |
| if (hr->access_mode != MODE_INVALID) |
| printf(" Access Mode: 0%03oo\n", user_record_access_mode(hr)); |
| |
| if (storage == USER_LUKS) { |
| printf("LUKS Discard: online=%s offline=%s\n", yes_no(user_record_luks_discard(hr)), yes_no(user_record_luks_offline_discard(hr))); |
| |
| if (!sd_id128_is_null(hr->luks_uuid)) |
| printf(" LUKS UUID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(hr->luks_uuid)); |
| if (!sd_id128_is_null(hr->partition_uuid)) |
| printf(" Part UUID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(hr->partition_uuid)); |
| if (!sd_id128_is_null(hr->file_system_uuid)) |
| printf(" FS UUID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(hr->file_system_uuid)); |
| |
| if (hr->file_system_type) |
| printf(" File System: %s\n", user_record_file_system_type(hr)); |
| |
| if (hr->luks_cipher) |
| printf(" LUKS Cipher: %s\n", hr->luks_cipher); |
| if (hr->luks_cipher_mode) |
| printf(" Cipher Mode: %s\n", hr->luks_cipher_mode); |
| if (hr->luks_volume_key_size != UINT64_MAX) |
| printf(" Volume Key: %" PRIu64 "bit\n", hr->luks_volume_key_size * 8); |
| |
| if (hr->luks_pbkdf_type) |
| printf(" PBKDF Type: %s\n", hr->luks_pbkdf_type); |
| if (hr->luks_pbkdf_hash_algorithm) |
| printf(" PBKDF Hash: %s\n", hr->luks_pbkdf_hash_algorithm); |
| if (hr->luks_pbkdf_time_cost_usec != UINT64_MAX) { |
| char buf[FORMAT_TIMESPAN_MAX]; |
| printf(" PBKDF Time: %s\n", format_timespan(buf, sizeof(buf), hr->luks_pbkdf_time_cost_usec, 0)); |
| } |
| if (hr->luks_pbkdf_memory_cost != UINT64_MAX) { |
| char buf[FORMAT_BYTES_MAX]; |
| printf(" PBKDF Bytes: %s\n", format_bytes(buf, sizeof(buf), hr->luks_pbkdf_memory_cost)); |
| } |
| if (hr->luks_pbkdf_parallel_threads != UINT64_MAX) |
| printf("PBKDF Thread: %" PRIu64 "\n", hr->luks_pbkdf_parallel_threads); |
| |
| } else if (storage == USER_CIFS) { |
| |
| if (hr->cifs_service) |
| printf("CIFS Service: %s\n", hr->cifs_service); |
| } |
| |
| if (hr->cifs_user_name) |
| printf(" CIFS User: %s\n", user_record_cifs_user_name(hr)); |
| if (hr->cifs_domain) |
| printf(" CIFS Domain: %s\n", hr->cifs_domain); |
| |
| if (storage != USER_CLASSIC) |
| printf(" Mount Flags: %s %s %s\n", |
| hr->nosuid ? "nosuid" : "suid", |
| hr->nodev ? "nodev" : "dev", |
| hr->noexec ? "noexec" : "exec"); |
| |
| if (hr->skeleton_directory) |
| printf(" Skel. Dir.: %s\n", user_record_skeleton_directory(hr)); |
| |
| if (hr->disk_size != UINT64_MAX) { |
| char buf[FORMAT_BYTES_MAX]; |
| printf(" Disk Size: %s\n", format_bytes(buf, sizeof(buf), hr->disk_size)); |
| } |
| |
| if (hr->disk_usage != UINT64_MAX) { |
| char buf[FORMAT_BYTES_MAX]; |
| |
| if (hr->disk_size != UINT64_MAX) { |
| unsigned permille; |
| |
| permille = (unsigned) DIV_ROUND_UP(hr->disk_usage * 1000U, hr->disk_size); /* Round up! */ |
| printf(" Disk Usage: %s (= %u.%01u%%)\n", |
| format_bytes(buf, sizeof(buf), hr->disk_usage), |
| permille / 10, permille % 10); |
| } else |
| printf(" Disk Usage: %s\n", format_bytes(buf, sizeof(buf), hr->disk_usage)); |
| } |
| |
| if (hr->disk_free != UINT64_MAX) { |
| char buf[FORMAT_BYTES_MAX]; |
| |
| if (hr->disk_size != UINT64_MAX) { |
| const char *color_on, *color_off; |
| unsigned permille; |
| |
| permille = (unsigned) ((hr->disk_free * 1000U) / hr->disk_size); /* Round down! */ |
| |
| /* Color the output red or yellow if we are below 10% resp. 25% free. Because 10% and |
| * 25% can be a lot of space still, let's additionally make some absolute |
| * restrictions: 1G and 2G */ |
| if (permille <= 100U && |
| hr->disk_free < 1024U*1024U*1024U /* 1G */) { |
| color_on = ansi_highlight_red(); |
| color_off = ansi_normal(); |
| } else if (permille <= 250U && |
| hr->disk_free < 2U*1024U*1024U*1024U /* 2G */) { |
| color_on = ansi_highlight_yellow(); |
| color_off = ansi_normal(); |
| } else |
| color_on = color_off = ""; |
| |
| printf(" Disk Free: %s%s (= %u.%01u%%)%s\n", |
| color_on, |
| format_bytes(buf, sizeof(buf), hr->disk_free), |
| permille / 10, permille % 10, |
| color_off); |
| } else |
| printf(" Disk Free: %s\n", format_bytes(buf, sizeof(buf), hr->disk_free)); |
| } |
| |
| if (hr->disk_floor != UINT64_MAX) { |
| char buf[FORMAT_BYTES_MAX]; |
| printf(" Disk Floor: %s\n", format_bytes(buf, sizeof(buf), hr->disk_floor)); |
| } |
| |
| if (hr->disk_ceiling != UINT64_MAX) { |
| char buf[FORMAT_BYTES_MAX]; |
| printf("Disk Ceiling: %s\n", format_bytes(buf, sizeof(buf), hr->disk_ceiling)); |
| } |
| |
| if (hr->good_authentication_counter != UINT64_MAX) |
| printf(" Good Auth.: %" PRIu64 "\n", hr->good_authentication_counter); |
| |
| if (hr->last_good_authentication_usec != UINT64_MAX) { |
| char buf[FORMAT_TIMESTAMP_MAX]; |
| printf(" Last Good: %s\n", format_timestamp(buf, sizeof(buf), hr->last_good_authentication_usec)); |
| } |
| |
| if (hr->bad_authentication_counter != UINT64_MAX) |
| printf(" Bad Auth.: %" PRIu64 "\n", hr->bad_authentication_counter); |
| |
| if (hr->last_bad_authentication_usec != UINT64_MAX) { |
| char buf[FORMAT_TIMESTAMP_MAX]; |
| printf(" Last Bad: %s\n", format_timestamp(buf, sizeof(buf), hr->last_bad_authentication_usec)); |
| } |
| |
| t = user_record_ratelimit_next_try(hr); |
| if (t != USEC_INFINITY) { |
| usec_t n = now(CLOCK_REALTIME); |
| |
| if (t <= n) |
| printf(" Next Try: anytime\n"); |
| else { |
| char buf[FORMAT_TIMESPAN_MAX]; |
| printf(" Next Try: %sin %s%s\n", |
| ansi_highlight_red(), |
| format_timespan(buf, sizeof(buf), t - n, USEC_PER_SEC), |
| ansi_normal()); |
| } |
| } |
| |
| if (storage != USER_CLASSIC) { |
| char buf[FORMAT_TIMESPAN_MAX]; |
| printf(" Auth. Limit: %" PRIu64 " attempts per %s\n", user_record_ratelimit_burst(hr), |
| format_timespan(buf, sizeof(buf), user_record_ratelimit_interval_usec(hr), 0)); |
| } |
| |
| if (hr->enforce_password_policy >= 0) |
| printf(" Passwd Pol.: %s\n", yes_no(hr->enforce_password_policy)); |
| |
| if (hr->password_change_min_usec != UINT64_MAX || |
| hr->password_change_max_usec != UINT64_MAX || |
| hr->password_change_warn_usec != UINT64_MAX || |
| hr->password_change_inactive_usec != UINT64_MAX) { |
| |
| char buf[FORMAT_TIMESPAN_MAX]; |
| printf(" Passwd Chg.:"); |
| |
| if (hr->password_change_min_usec != UINT64_MAX) { |
| printf(" min %s", format_timespan(buf, sizeof(buf), hr->password_change_min_usec, 0)); |
| |
| if (hr->password_change_max_usec != UINT64_MAX) |
| printf(" …"); |
| } |
| |
| if (hr->password_change_max_usec != UINT64_MAX) |
| printf(" max %s", format_timespan(buf, sizeof(buf), hr->password_change_max_usec, 0)); |
| |
| if (hr->password_change_warn_usec != UINT64_MAX) |
| printf("/warn %s", format_timespan(buf, sizeof(buf), hr->password_change_warn_usec, 0)); |
| |
| if (hr->password_change_inactive_usec != UINT64_MAX) |
| printf("/inactive %s", format_timespan(buf, sizeof(buf), hr->password_change_inactive_usec, 0)); |
| |
| printf("\n"); |
| } |
| |
| if (hr->password_change_now >= 0) |
| printf("Pas. Ch. Now: %s\n", yes_no(hr->password_change_now)); |
| |
| if (!strv_isempty(hr->ssh_authorized_keys)) |
| printf("SSH Pub. Key: %zu\n", strv_length(hr->ssh_authorized_keys)); |
| |
| if (!strv_isempty(hr->pkcs11_token_uri)) { |
| char **i; |
| |
| STRV_FOREACH(i, hr->pkcs11_token_uri) |
| printf(i == hr->pkcs11_token_uri ? |
| "PKCS11 Token: %s\n" : |
| " %s\n", *i); |
| } |
| |
| if (hr->n_fido2_hmac_credential > 0) |
| printf(" FIDO2 Token: %zu\n", hr->n_fido2_hmac_credential); |
| |
| if (!strv_isempty(hr->recovery_key_type)) |
| printf("Recovery Key: %zu\n", strv_length(hr->recovery_key_type)); |
| |
| k = strv_length(hr->hashed_password); |
| if (k == 0) |
| printf(" Passwords: %snone%s\n", |
| user_record_disposition(hr) == USER_REGULAR ? ansi_highlight_yellow() : ansi_normal(), ansi_normal()); |
| else |
| printf(" Passwords: %zu\n", k); |
| |
| if (hr->signed_locally >= 0) |
| printf(" Local Sig.: %s\n", yes_no(hr->signed_locally)); |
| |
| if (hr->stop_delay_usec != UINT64_MAX) { |
| char buf[FORMAT_TIMESPAN_MAX]; |
| printf(" Stop Delay: %s\n", format_timespan(buf, sizeof(buf), hr->stop_delay_usec, 0)); |
| } |
| |
| if (hr->auto_login >= 0) |
| printf("Autom. Login: %s\n", yes_no(hr->auto_login)); |
| |
| if (hr->kill_processes >= 0) |
| printf(" Kill Proc.: %s\n", yes_no(hr->kill_processes)); |
| |
| if (hr->service) |
| printf(" Service: %s\n", hr->service); |
| } |
| |
| void group_record_show(GroupRecord *gr, bool show_full_user_info) { |
| int r; |
| |
| printf(" Group name: %s\n", |
| group_record_group_name_and_realm(gr)); |
| |
| printf(" Disposition: %s\n", user_disposition_to_string(group_record_disposition(gr))); |
| |
| if (gr->last_change_usec != USEC_INFINITY) { |
| char buf[FORMAT_TIMESTAMP_MAX]; |
| printf(" Last Change: %s\n", format_timestamp(buf, sizeof(buf), gr->last_change_usec)); |
| } |
| |
| if (gid_is_valid(gr->gid)) |
| printf(" GID: " GID_FMT "\n", gr->gid); |
| |
| if (show_full_user_info) { |
| _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; |
| |
| r = membershipdb_by_group(gr->group_name, 0, &iterator); |
| if (r < 0) { |
| errno = -r; |
| printf(" Members: (can't acquire: %m)"); |
| } else { |
| const char *prefix = " Members:"; |
| |
| for (;;) { |
| _cleanup_free_ char *user = NULL; |
| |
| r = membershipdb_iterator_get(iterator, &user, NULL); |
| if (r == -ESRCH) |
| break; |
| if (r < 0) { |
| errno = -r; |
| printf("%s (can't iterate: %m\n", prefix); |
| break; |
| } |
| |
| printf("%s %s\n", prefix, user); |
| prefix = " "; |
| } |
| } |
| } else { |
| const char *prefix = " Members:"; |
| char **i; |
| |
| STRV_FOREACH(i, gr->members) { |
| printf("%s %s\n", prefix, *i); |
| prefix = " "; |
| } |
| } |
| |
| if (!strv_isempty(gr->administrators)) { |
| const char *prefix = " Admins:"; |
| char **i; |
| |
| STRV_FOREACH(i, gr->administrators) { |
| printf("%s %s\n", prefix, *i); |
| prefix = " "; |
| } |
| } |
| |
| if (gr->description && !streq(gr->description, gr->group_name)) |
| printf(" Description: %s\n", gr->description); |
| |
| if (!strv_isempty(gr->hashed_password)) |
| printf(" Passwords: %zu\n", strv_length(gr->hashed_password)); |
| |
| if (gr->service) |
| printf(" Service: %s\n", gr->service); |
| } |