]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/nss-systemd/userdb-glue.c
Merge pull request #31524 from poettering/secure-getenv-naming-fix
[thirdparty/systemd.git] / src / nss-systemd / userdb-glue.c
index 81705fa1b6db1acf8099b6443a44be2a26c9a9f7..b02d89a20bc600c8450fcf53f2a2bdac9599b5b5 100644 (file)
@@ -1,19 +1,21 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "env-util.h"
 #include "fd-util.h"
-#include "group-record-nss.h"
+#include "nss-systemd.h"
 #include "strv.h"
+#include "user-record-nss.h"
 #include "user-record.h"
+#include "user-util.h"
 #include "userdb-glue.h"
 #include "userdb.h"
 
 UserDBFlags nss_glue_userdb_flags(void) {
-        UserDBFlags flags = USERDB_AVOID_NSS;
+        UserDBFlags flags = USERDB_EXCLUDE_NSS;
 
         /* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */
-        if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
-                flags |= USERDB_AVOID_DYNAMIC_USER;
+        if (secure_getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
+                flags |= USERDB_EXCLUDE_DYNAMIC_USER;
 
         return flags;
 }
@@ -30,9 +32,11 @@ int nss_pack_user_record(
         assert(hr);
         assert(pwd);
 
-        assert_se(hr->user_name);
+        assert(hr->user_name);
         required = strlen(hr->user_name) + 1;
 
+        required += 2; /* strlen(PASSWORD_SEE_SHADOW) + 1 */
+
         assert_se(rn = user_record_real_name(hr));
         required += strlen(rn) + 1;
 
@@ -49,12 +53,12 @@ int nss_pack_user_record(
                 .pw_name = buffer,
                 .pw_uid = hr->uid,
                 .pw_gid = user_record_gid(hr),
-                .pw_passwd = (char*) "x", /* means: see shadow file */
         };
 
         assert(buffer);
 
-        pwd->pw_gecos = stpcpy(pwd->pw_name, hr->user_name) + 1;
+        pwd->pw_passwd = stpcpy(pwd->pw_name, hr->user_name) + 1;
+        pwd->pw_gecos = stpcpy(pwd->pw_passwd, PASSWORD_SEE_SHADOW) + 1;
         pwd->pw_dir = stpcpy(pwd->pw_gecos, rn) + 1;
         pwd->pw_shell = stpcpy(pwd->pw_dir, hd) + 1;
         strcpy(pwd->pw_shell, shell);
@@ -74,15 +78,10 @@ enum nss_status userdb_getpwnam(
         assert(pwd);
         assert(errnop);
 
-        r = userdb_nss_compat_is_enabled();
-        if (r < 0) {
-                *errnop = -r;
-                return NSS_STATUS_UNAVAIL;
-        }
-        if (!r)
+        if (_nss_systemd_is_blocked())
                 return NSS_STATUS_NOTFOUND;
 
-        r = userdb_by_name(name, nss_glue_userdb_flags(), &hr);
+        r = userdb_by_name(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &hr);
         if (r == -ESRCH)
                 return NSS_STATUS_NOTFOUND;
         if (r < 0) {
@@ -112,15 +111,86 @@ enum nss_status userdb_getpwuid(
         assert(pwd);
         assert(errnop);
 
-        r = userdb_nss_compat_is_enabled();
+        if (_nss_systemd_is_blocked())
+                return NSS_STATUS_NOTFOUND;
+
+        r = userdb_by_uid(uid, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &hr);
+        if (r == -ESRCH)
+                return NSS_STATUS_NOTFOUND;
         if (r < 0) {
                 *errnop = -r;
                 return NSS_STATUS_UNAVAIL;
         }
-        if (!r)
+
+        r = nss_pack_user_record(hr, pwd, buffer, buflen);
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_TRYAGAIN;
+        }
+
+        return NSS_STATUS_SUCCESS;
+}
+
+int nss_pack_user_record_shadow(
+                UserRecord *hr,
+                struct spwd *spwd,
+                char *buffer,
+                size_t buflen) {
+
+        const char *hashed;
+        size_t required;
+
+        assert(hr);
+        assert(spwd);
+
+        assert(hr->user_name);
+        required = strlen(hr->user_name) + 1;
+
+        assert_se(hashed = strv_isempty(hr->hashed_password) ? PASSWORD_LOCKED_AND_INVALID : hr->hashed_password[0]);
+        required += strlen(hashed) + 1;
+
+        if (buflen < required)
+                return -ERANGE;
+
+        *spwd = (struct spwd) {
+                .sp_namp = buffer,
+                .sp_lstchg = hr->last_password_change_usec == 0 ? 1 :               /* map 0 to 1, since 0 means please change password on next login */
+                             hr->last_password_change_usec == UINT64_MAX ? -1 :
+                             (long int) (hr->last_password_change_usec / USEC_PER_DAY),
+                .sp_min = hr->password_change_min_usec != UINT64_MAX ? (long int) (hr->password_change_min_usec / USEC_PER_DAY) : -1,
+                .sp_max = hr->password_change_max_usec != UINT64_MAX ? (long int) (hr->password_change_max_usec / USEC_PER_DAY) : -1,
+                .sp_warn = hr->password_change_warn_usec != UINT64_MAX ? (long int) (hr->password_change_warn_usec / USEC_PER_DAY) : -1,
+                .sp_inact = hr->password_change_inactive_usec != UINT64_MAX ? (long int) (hr->password_change_inactive_usec / USEC_PER_DAY) : -1,
+                .sp_expire = hr->locked > 0 || hr->not_after_usec == 0 ? 1 : /* already expired/locked */
+                             hr->not_after_usec == UINT64_MAX ? -1 :
+                             (long int) (hr->not_after_usec / USEC_PER_DAY),
+                .sp_flag = ULONG_MAX,
+        };
+
+        assert(buffer);
+
+        spwd->sp_pwdp = stpcpy(spwd->sp_namp, hr->user_name) + 1;
+        strcpy(spwd->sp_pwdp, hashed);
+
+        return 0;
+}
+
+enum nss_status userdb_getspnam(
+                const char *name,
+                struct spwd *spwd,
+                char *buffer, size_t buflen,
+                int *errnop) {
+
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        int r;
+
+        assert(spwd);
+        assert(errnop);
+
+        if (_nss_systemd_is_blocked())
                 return NSS_STATUS_NOTFOUND;
 
-        r = userdb_by_uid(uid, nss_glue_userdb_flags(), &hr);
+        r = userdb_by_name(name, nss_glue_userdb_flags(), &hr);
         if (r == -ESRCH)
                 return NSS_STATUS_NOTFOUND;
         if (r < 0) {
@@ -128,7 +198,10 @@ enum nss_status userdb_getpwuid(
                 return NSS_STATUS_UNAVAIL;
         }
 
-        r = nss_pack_user_record(hr, pwd, buffer, buflen);
+        if (hr->incomplete) /* protected records missing? */
+                return NSS_STATUS_NOTFOUND;
+
+        r = nss_pack_user_record_shadow(hr, spwd, buffer, buflen);
         if (r < 0) {
                 *errnop = -r;
                 return NSS_STATUS_TRYAGAIN;
@@ -144,13 +217,13 @@ int nss_pack_group_record(
                 char *buffer,
                 size_t buflen) {
 
-        char **array = NULL, *p, **m;
+        char **array = NULL, *p;
         size_t required, n = 0, i = 0;
 
         assert(g);
         assert(gr);
 
-        assert_se(g->group_name);
+        assert(g->group_name);
         required = strlen(g->group_name) + 1;
 
         STRV_FOREACH(m, g->members) {
@@ -193,7 +266,7 @@ int nss_pack_group_record(
         *gr = (struct group) {
                 .gr_name = strcpy(p, g->group_name),
                 .gr_gid = g->gid,
-                .gr_passwd = (char*) "x", /* means: see shadow file */
+                .gr_passwd = (char*) PASSWORD_SEE_SHADOW,
                 .gr_mem = array,
         };
 
@@ -214,28 +287,23 @@ enum nss_status userdb_getgrnam(
         assert(gr);
         assert(errnop);
 
-        r = userdb_nss_compat_is_enabled();
-        if (r < 0) {
-                *errnop = -r;
-                return NSS_STATUS_UNAVAIL;
-        }
-        if (!r)
+        if (_nss_systemd_is_blocked())
                 return NSS_STATUS_NOTFOUND;
 
-        r = groupdb_by_name(name, nss_glue_userdb_flags(), &g);
+        r = groupdb_by_name(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
         if (r < 0 && r != -ESRCH) {
                 *errnop = -r;
                 return NSS_STATUS_UNAVAIL;
         }
 
-        r = membershipdb_by_group_strv(name, nss_glue_userdb_flags(), &members);
-        if (r < 0) {
+        r = membershipdb_by_group_strv(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &members);
+        if (r < 0 && r != -ESRCH) {
                 *errnop = -r;
                 return NSS_STATUS_UNAVAIL;
         }
 
         if (!g) {
-                _cleanup_close_ int lock_fd = -1;
+                _unused_ _cleanup_(_nss_systemd_unblockp) bool blocked = false;
 
                 if (strv_isempty(members))
                         return NSS_STATUS_NOTFOUND;
@@ -245,13 +313,15 @@ enum nss_status userdb_getgrnam(
                  * acquire it, so that we can extend it (that's because glibc's group merging feature will
                  * merge groups only if both GID and name match and thus we need to have both first). It
                  * sucks behaving recursively likely this, but it's apparently what everybody does. We break
-                 * the recursion for ourselves via the userdb_nss_compat_disable() lock. */
+                 * the recursion for ourselves via the _nss_systemd_block_nss() lock. */
 
-                lock_fd = userdb_nss_compat_disable();
-                if (lock_fd < 0 && lock_fd != -EBUSY)
-                        return lock_fd;
+                r = _nss_systemd_block(true);
+                if (r < 0)
+                        return r;
 
-                r = nss_group_record_by_name(name, &g);
+                blocked = true;
+
+                r = nss_group_record_by_name(name, false, &g);
                 if (r == -ESRCH)
                         return NSS_STATUS_NOTFOUND;
                 if (r < 0) {
@@ -285,35 +355,31 @@ enum nss_status userdb_getgrgid(
         assert(gr);
         assert(errnop);
 
-        r = userdb_nss_compat_is_enabled();
-        if (r < 0) {
-                *errnop = -r;
-                return NSS_STATUS_UNAVAIL;
-        }
-        if (r)
+        if (_nss_systemd_is_blocked())
                 return NSS_STATUS_NOTFOUND;
 
-        r = groupdb_by_gid(gid, nss_glue_userdb_flags(), &g);
+        r = groupdb_by_gid(gid, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
         if (r < 0 && r != -ESRCH) {
                 *errnop = -r;
                 return NSS_STATUS_UNAVAIL;
         }
 
         if (!g) {
-                _cleanup_close_ int lock_fd = -1;
+                _unused_ _cleanup_(_nss_systemd_unblockp) bool blocked = false;
 
                 /* So, quite possibly we have to extend an existing group record with additional members. But
                  * to do this we need to know the group name first. The group didn't exist via non-NSS
                  * queries though, hence let's try to acquire it here recursively via NSS. */
 
-                lock_fd = userdb_nss_compat_disable();
-                if (lock_fd < 0 && lock_fd != -EBUSY)
-                        return lock_fd;
+                r = _nss_systemd_block(true);
+                if (r < 0)
+                        return r;
+
+                blocked = true;
 
-                r = nss_group_record_by_gid(gid, &g);
+                r = nss_group_record_by_gid(gid, false, &g);
                 if (r == -ESRCH)
                         return NSS_STATUS_NOTFOUND;
-
                 if (r < 0) {
                         *errnop = -r;
                         return NSS_STATUS_UNAVAIL;
@@ -323,13 +389,13 @@ enum nss_status userdb_getgrgid(
         } else
                 from_nss = false;
 
-        r = membershipdb_by_group_strv(g->group_name, nss_glue_userdb_flags(), &members);
-        if (r < 0) {
+        r = membershipdb_by_group_strv(g->group_name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &members);
+        if (r < 0 && r != -ESRCH) {
                 *errnop = -r;
                 return NSS_STATUS_UNAVAIL;
         }
 
-        /* If we acquired the record via NSS then there's no reason to respond unless we have to agument the
+        /* If we acquired the record via NSS then there's no reason to respond unless we have to augment the
          * list of members of the group */
         if (from_nss && strv_isempty(members))
                 return NSS_STATUS_NOTFOUND;
@@ -342,3 +408,71 @@ enum nss_status userdb_getgrgid(
 
         return NSS_STATUS_SUCCESS;
 }
+
+int nss_pack_group_record_shadow(
+                GroupRecord *hr,
+                struct sgrp *sgrp,
+                char *buffer,
+                size_t buflen) {
+
+        const char *hashed;
+        size_t required;
+
+        assert(hr);
+        assert(sgrp);
+
+        assert(hr->group_name);
+        required = strlen(hr->group_name) + 1;
+
+        assert_se(hashed = strv_isempty(hr->hashed_password) ? PASSWORD_LOCKED_AND_INVALID : hr->hashed_password[0]);
+        required += strlen(hashed) + 1;
+
+        if (buflen < required)
+                return -ERANGE;
+
+        *sgrp = (struct sgrp) {
+                .sg_namp = buffer,
+        };
+
+        assert(buffer);
+
+        sgrp->sg_passwd = stpcpy(sgrp->sg_namp, hr->group_name) + 1;
+        strcpy(sgrp->sg_passwd, hashed);
+
+        return 0;
+}
+
+enum nss_status userdb_getsgnam(
+                const char *name,
+                struct sgrp *sgrp,
+                char *buffer, size_t buflen,
+                int *errnop) {
+
+        _cleanup_(group_record_unrefp) GroupRecord *hr = NULL;
+        int r;
+
+        assert(sgrp);
+        assert(errnop);
+
+        if (_nss_systemd_is_blocked())
+                return NSS_STATUS_NOTFOUND;
+
+        r = groupdb_by_name(name, nss_glue_userdb_flags(), &hr);
+        if (r == -ESRCH)
+                return NSS_STATUS_NOTFOUND;
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_UNAVAIL;
+        }
+
+        if (hr->incomplete) /* protected records missing? */
+                return NSS_STATUS_NOTFOUND;
+
+        r = nss_pack_group_record_shadow(hr, sgrp, buffer, buflen);
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_TRYAGAIN;
+        }
+
+        return NSS_STATUS_SUCCESS;
+}