diff options
author | Lennart Poettering <lennart@poettering.net> | 2019-08-05 18:21:30 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2020-01-15 15:27:23 +0100 |
commit | 9b2d907877ab10202fdfb7db7a5ce6e8b32c70b8 (patch) | |
tree | 6fafb8decf2a399bf5c19a7808e93e7a683ad8f7 | |
parent | shared: add generic user/group record structures and JSON parsers (diff) | |
download | systemd-9b2d907877ab10202fdfb7db7a5ce6e8b32c70b8.tar.xz systemd-9b2d907877ab10202fdfb7db7a5ce6e8b32c70b8.zip |
shared: add helpers for converting NSS passwd/group structures to new JSON objects
These new calls may be used to convert classic UNIX/glibc NSS struct
passwd and struct group records into new-style JSON-based user/group
objects.
-rw-r--r-- | src/shared/group-record-nss.c | 203 | ||||
-rw-r--r-- | src/shared/group-record-nss.h | 15 | ||||
-rw-r--r-- | src/shared/meson.build | 4 | ||||
-rw-r--r-- | src/shared/user-record-nss.c | 288 | ||||
-rw-r--r-- | src/shared/user-record-nss.h | 15 |
5 files changed, 525 insertions, 0 deletions
diff --git a/src/shared/group-record-nss.c b/src/shared/group-record-nss.c new file mode 100644 index 0000000000..77924f1c40 --- /dev/null +++ b/src/shared/group-record-nss.c @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "errno-util.h" +#include "group-record-nss.h" +#include "libcrypt-util.h" +#include "strv.h" + +int nss_group_to_group_record( + const struct group *grp, + const struct sgrp *sgrp, + GroupRecord **ret) { + + _cleanup_(group_record_unrefp) GroupRecord *g = NULL; + int r; + + assert(grp); + assert(ret); + + if (isempty(grp->gr_name)) + return -EINVAL; + + if (sgrp && !streq_ptr(sgrp->sg_namp, grp->gr_name)) + return -EINVAL; + + g = group_record_new(); + if (!g) + return -ENOMEM; + + g->group_name = strdup(grp->gr_name); + if (!g->group_name) + return -ENOMEM; + + g->members = strv_copy(grp->gr_mem); + if (!g->members) + return -ENOMEM; + + g->gid = grp->gr_gid; + + if (sgrp) { + if (hashed_password_valid(sgrp->sg_passwd)) { + g->hashed_password = strv_new(sgrp->sg_passwd); + if (!g->hashed_password) + return -ENOMEM; + } + + r = strv_extend_strv(&g->members, sgrp->sg_mem, 1); + if (r < 0) + return r; + + g->administrators = strv_copy(sgrp->sg_adm); + if (!g->administrators) + return -ENOMEM; + } + + r = json_build(&g->json, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g->group_name)), + JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(g->gid)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->members), "members", JSON_BUILD_STRV(g->members)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->hashed_password), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(g->hashed_password)))), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->administrators), "administrators", JSON_BUILD_STRV(g->administrators)))); + if (r < 0) + return r; + + g->mask = USER_RECORD_REGULAR | + (!strv_isempty(g->hashed_password) ? USER_RECORD_PRIVILEGED : 0); + + *ret = TAKE_PTR(g); + return 0; +} + +int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer) { + size_t buflen = 4096; + int r; + + assert(grp); + assert(ret_sgrp); + assert(ret_buffer); + + for (;;) { + _cleanup_free_ char *buf = NULL; + struct sgrp sgrp, *result; + + buf = malloc(buflen); + if (!buf) + return -ENOMEM; + + r = getsgnam_r(grp->gr_name, &sgrp, buf, buflen, &result); + if (r == 0) { + if (!result) + return -ESRCH; + + *ret_sgrp = *result; + *ret_buffer = TAKE_PTR(buf); + return 0; + } + if (r < 0) + return -EIO; /* Weird, this should not return negative! */ + if (r != ERANGE) + return -r; + + if (buflen > SIZE_MAX / 2) + return -ERANGE; + + buflen *= 2; + buf = mfree(buf); + } +} + +int nss_group_record_by_name(const char *name, GroupRecord **ret) { + _cleanup_free_ char *buf = NULL, *sbuf = NULL; + struct group grp, *result; + bool incomplete = false; + size_t buflen = 4096; + struct sgrp sgrp; + int r; + + assert(name); + assert(ret); + + for (;;) { + buf = malloc(buflen); + if (!buf) + return -ENOMEM; + + r = getgrnam_r(name, &grp, buf, buflen, &result); + if (r == 0) { + if (!result) + return -ESRCH; + + break; + } + + if (r < 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value"); + if (r != ERANGE) + return -r; + if (buflen > SIZE_MAX / 2) + return -ERANGE; + + buflen *= 2; + buf = mfree(buf); + } + + r = nss_sgrp_for_group(result, &sgrp, &sbuf); + if (r < 0) { + log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name); + incomplete = ERRNO_IS_PRIVILEGE(r); + } + + r = nss_group_to_group_record(result, r >= 0 ? &sgrp : NULL, ret); + if (r < 0) + return r; + + (*ret)->incomplete = incomplete; + return 0; +} + +int nss_group_record_by_gid(gid_t gid, GroupRecord **ret) { + _cleanup_free_ char *buf = NULL, *sbuf = NULL; + struct group grp, *result; + bool incomplete = false; + size_t buflen = 4096; + struct sgrp sgrp; + int r; + + assert(ret); + + for (;;) { + buf = malloc(buflen); + if (!buf) + return -ENOMEM; + + r = getgrgid_r(gid, &grp, buf, buflen, &result); + if (r == 0) { + if (!result) + return -ESRCH; + break; + } + + if (r < 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value"); + if (r != ERANGE) + return -r; + if (buflen > SIZE_MAX / 2) + return -ERANGE; + + buflen *= 2; + buf = mfree(buf); + } + + r = nss_sgrp_for_group(result, &sgrp, &sbuf); + if (r < 0) { + log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name); + incomplete = ERRNO_IS_PRIVILEGE(r); + } + + r = nss_group_to_group_record(result, r >= 0 ? &sgrp : NULL, ret); + if (r < 0) + return r; + + (*ret)->incomplete = incomplete; + return 0; +} diff --git a/src/shared/group-record-nss.h b/src/shared/group-record-nss.h new file mode 100644 index 0000000000..38b2995178 --- /dev/null +++ b/src/shared/group-record-nss.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <grp.h> +#include <gshadow.h> + +#include "group-record.h" + +/* Synthesize GroupRecord objects from NSS data */ + +int nss_group_to_group_record(const struct group *grp, const struct sgrp *sgrp, GroupRecord **ret); +int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer); + +int nss_group_record_by_name(const char *name, GroupRecord **ret); +int nss_group_record_by_gid(gid_t gid, GroupRecord **ret); diff --git a/src/shared/meson.build b/src/shared/meson.build index f9799fee1d..d99aaa62c6 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -86,6 +86,8 @@ shared_sources = files(''' generator.c generator.h gpt.h + group-record-nss.c + group-record-nss.h group-record.c group-record.h id128-print.c @@ -191,6 +193,8 @@ shared_sources = files(''' uid-range.h unit-file.c unit-file.h + user-record-nss.c + user-record-nss.h user-record.c user-record.h utmp-wtmp.h diff --git a/src/shared/user-record-nss.c b/src/shared/user-record-nss.c new file mode 100644 index 0000000000..11c085d63d --- /dev/null +++ b/src/shared/user-record-nss.c @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "errno-util.h" +#include "format-util.h" +#include "libcrypt-util.h" +#include "strv.h" +#include "user-record-nss.h" + +int nss_passwd_to_user_record( + const struct passwd *pwd, + const struct spwd *spwd, + UserRecord **ret) { + + _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + int r; + + assert(pwd); + assert(ret); + + if (isempty(pwd->pw_name)) + return -EINVAL; + + if (spwd && !streq_ptr(spwd->sp_namp, pwd->pw_name)) + return -EINVAL; + + hr = user_record_new(); + if (!hr) + return -ENOMEM; + + r = free_and_strdup(&hr->user_name, pwd->pw_name); + if (r < 0) + return r; + + if (isempty(pwd->pw_gecos) || streq_ptr(pwd->pw_gecos, hr->user_name)) + hr->real_name = mfree(hr->real_name); + else { + r = free_and_strdup(&hr->real_name, pwd->pw_gecos); + if (r < 0) + return r; + } + + if (isempty(pwd->pw_dir)) + hr->home_directory = mfree(hr->home_directory); + else { + r = free_and_strdup(&hr->home_directory, pwd->pw_dir); + if (r < 0) + return r; + } + + if (isempty(pwd->pw_shell)) + hr->shell = mfree(hr->shell); + else { + r = free_and_strdup(&hr->shell, pwd->pw_shell); + if (r < 0) + return r; + } + + hr->uid = pwd->pw_uid; + hr->gid = pwd->pw_gid; + + if (spwd) { + if (hashed_password_valid(spwd->sp_pwdp)) { + strv_free_erase(hr->hashed_password); + hr->hashed_password = strv_new(spwd->sp_pwdp); + if (!hr->hashed_password) + return -ENOMEM; + } else + hr->hashed_password = strv_free_erase(hr->hashed_password); + + /* shadow-utils suggests using "chage -E 0" (or -E 1, depending on which man page you check) + * for locking a whole account, hence check for that. Note that it also defines a way to lock + * just a password instead of the whole account, but that's mostly pointless in times of + * password-less authorization, hence let's not bother. */ + + if (spwd->sp_expire >= 0) + hr->locked = spwd->sp_expire <= 1; + else + hr->locked = -1; + + if (spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY) + hr->not_after_usec = spwd->sp_expire * USEC_PER_DAY; + else + hr->not_after_usec = UINT64_MAX; + + if (spwd->sp_lstchg >= 0) + hr->password_change_now = spwd->sp_lstchg == 0; + else + hr->password_change_now = -1; + + if (spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY) + hr->last_password_change_usec = spwd->sp_lstchg * USEC_PER_DAY; + else + hr->last_password_change_usec = UINT64_MAX; + + if (spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY) + hr->password_change_min_usec = spwd->sp_min * USEC_PER_DAY; + else + hr->password_change_min_usec = UINT64_MAX; + + if (spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY) + hr->password_change_max_usec = spwd->sp_max * USEC_PER_DAY; + else + hr->password_change_max_usec = UINT64_MAX; + + if (spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY) + hr->password_change_warn_usec = spwd->sp_warn * USEC_PER_DAY; + else + hr->password_change_warn_usec = UINT64_MAX; + + if (spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY) + hr->password_change_inactive_usec = spwd->sp_inact * USEC_PER_DAY; + else + hr->password_change_inactive_usec = UINT64_MAX; + } else { + hr->hashed_password = strv_free_erase(hr->hashed_password); + hr->locked = -1; + hr->not_after_usec = UINT64_MAX; + hr->password_change_now = -1, + hr->last_password_change_usec = UINT64_MAX; + 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; + } + + hr->json = json_variant_unref(hr->json); + r = json_build(&hr->json, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(hr->user_name)), + JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(hr->uid)), + JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(hr->gid)), + JSON_BUILD_PAIR_CONDITION(hr->real_name, "realName", JSON_BUILD_STRING(hr->real_name)), + JSON_BUILD_PAIR_CONDITION(hr->home_directory, "homeDirectory", JSON_BUILD_STRING(hr->home_directory)), + JSON_BUILD_PAIR_CONDITION(hr->shell, "shell", JSON_BUILD_STRING(hr->shell)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(hr->hashed_password), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(hr->hashed_password)))), + JSON_BUILD_PAIR_CONDITION(hr->locked >= 0, "locked", JSON_BUILD_BOOLEAN(hr->locked)), + JSON_BUILD_PAIR_CONDITION(hr->not_after_usec != UINT64_MAX, "notAfterUSec", JSON_BUILD_UNSIGNED(hr->not_after_usec)), + JSON_BUILD_PAIR_CONDITION(hr->password_change_now >= 0, "passwordChangeNow", JSON_BUILD_BOOLEAN(hr->password_change_now)), + JSON_BUILD_PAIR_CONDITION(hr->last_password_change_usec != UINT64_MAX, "lastPasswordChangeUSec", JSON_BUILD_UNSIGNED(hr->last_password_change_usec)), + JSON_BUILD_PAIR_CONDITION(hr->password_change_min_usec != UINT64_MAX, "passwordChangeMinUSec", JSON_BUILD_UNSIGNED(hr->password_change_min_usec)), + JSON_BUILD_PAIR_CONDITION(hr->password_change_max_usec != UINT64_MAX, "passwordChangeMaxUSec", JSON_BUILD_UNSIGNED(hr->password_change_max_usec)), + JSON_BUILD_PAIR_CONDITION(hr->password_change_warn_usec != UINT64_MAX, "passwordChangeWarnUSec", JSON_BUILD_UNSIGNED(hr->password_change_warn_usec)), + JSON_BUILD_PAIR_CONDITION(hr->password_change_inactive_usec != UINT64_MAX, "passwordChangeInactiveUSec", JSON_BUILD_UNSIGNED(hr->password_change_inactive_usec)))); + + if (r < 0) + return r; + + hr->mask = USER_RECORD_REGULAR | + (!strv_isempty(hr->hashed_password) ? USER_RECORD_PRIVILEGED : 0); + + *ret = TAKE_PTR(hr); + return 0; +} + +int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer) { + size_t buflen = 4096; + int r; + + assert(pwd); + assert(ret_spwd); + assert(ret_buffer); + + for (;;) { + _cleanup_free_ char *buf = NULL; + struct spwd spwd, *result; + + buf = malloc(buflen); + if (!buf) + return -ENOMEM; + + r = getspnam_r(pwd->pw_name, &spwd, buf, buflen, &result); + if (r == 0) { + if (!result) + return -ESRCH; + + *ret_spwd = *result; + *ret_buffer = TAKE_PTR(buf); + return 0; + } + if (r < 0) + return -EIO; /* Weird, this should not return negative! */ + if (r != ERANGE) + return -r; + + if (buflen > SIZE_MAX / 2) + return -ERANGE; + + buflen *= 2; + buf = mfree(buf); + } +} + +int nss_user_record_by_name(const char *name, UserRecord **ret) { + _cleanup_free_ char *buf = NULL, *sbuf = NULL; + struct passwd pwd, *result; + bool incomplete = false; + size_t buflen = 4096; + struct spwd spwd; + int r; + + assert(name); + assert(ret); + + for (;;) { + buf = malloc(buflen); + if (!buf) + return -ENOMEM; + + r = getpwnam_r(name, &pwd, buf, buflen, &result); + if (r == 0) { + if (!result) + return -ESRCH; + + break; + } + + if (r < 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value"); + if (r != ERANGE) + return -r; + + if (buflen > SIZE_MAX / 2) + return -ERANGE; + + buflen *= 2; + buf = mfree(buf); + } + + r = nss_spwd_for_passwd(result, &spwd, &sbuf); + if (r < 0) { + log_debug_errno(r, "Failed to do shadow lookup for user %s, ignoring: %m", name); + incomplete = ERRNO_IS_PRIVILEGE(r); + } + + r = nss_passwd_to_user_record(result, r >= 0 ? &spwd : NULL, ret); + if (r < 0) + return r; + + (*ret)->incomplete = incomplete; + return 0; +} + +int nss_user_record_by_uid(uid_t uid, UserRecord **ret) { + _cleanup_free_ char *buf = NULL, *sbuf = NULL; + struct passwd pwd, *result; + bool incomplete = false; + size_t buflen = 4096; + struct spwd spwd; + int r; + + assert(ret); + + for (;;) { + buf = malloc(buflen); + if (!buf) + return -ENOMEM; + + r = getpwuid_r(uid, &pwd, buf, buflen, &result); + if (r == 0) { + if (!result) + return -ESRCH; + + break; + } + if (r < 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value"); + if (r != ERANGE) + return -r; + + if (buflen > SIZE_MAX / 2) + return -ERANGE; + + buflen *= 2; + buf = mfree(buf); + } + + r = nss_spwd_for_passwd(result, &spwd, &sbuf); + if (r < 0) { + log_debug_errno(r, "Failed to do shadow lookup for UID " UID_FMT ", ignoring: %m", uid); + incomplete = ERRNO_IS_PRIVILEGE(r); + } + + r = nss_passwd_to_user_record(result, r >= 0 ? &spwd : NULL, ret); + if (r < 0) + return r; + + (*ret)->incomplete = incomplete; + return 0; +} diff --git a/src/shared/user-record-nss.h b/src/shared/user-record-nss.h new file mode 100644 index 0000000000..d5fb23ad2a --- /dev/null +++ b/src/shared/user-record-nss.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <pwd.h> +#include <shadow.h> + +#include "user-record.h" + +/* Synthesizes a UserRecord object from NSS data */ + +int nss_passwd_to_user_record(const struct passwd *pwd, const struct spwd *spwd, UserRecord **ret); +int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer); + +int nss_user_record_by_name(const char *name, UserRecord **ret); +int nss_user_record_by_uid(uid_t uid, UserRecord **ret); |