]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nss-systemd: synthesize NSS shadow/gshadow records from userdb, as well 19545/head
authorLennart Poettering <lennart@poettering.net>
Wed, 5 May 2021 14:11:26 +0000 (16:11 +0200)
committerLennart Poettering <lennart@poettering.net>
Sat, 8 May 2021 12:35:28 +0000 (14:35 +0200)
This ensures we not only synthesize regular paswd/group records of
userdb records, but shadow records as well. This should make sure that
userdb can be used as comprehensive superset of the classic
passwd/group/shadow/gshadow functionality.

factory/etc/nsswitch.conf
man/nss-myhostname.xml
man/nss-mymachines.xml
man/nss-resolve.xml
man/nss-systemd.xml
src/basic/nss-util.h
src/nss-systemd/nss-systemd.c
src/nss-systemd/nss-systemd.sym
src/nss-systemd/userdb-glue.c
src/nss-systemd/userdb-glue.h

index d87f8811ecb36bb5faf8025eae8961c1b20a8241..acfa8474d2d7a7291246f847ebae01cd2f6e3085 100644 (file)
@@ -2,7 +2,8 @@
 
 passwd:         compat systemd
 group:          compat [SUCCESS=merge] systemd
-shadow:         compat
+shadow:         compat systemd
+gshadow:        files systemd
 
 hosts:          mymachines resolve [!UNAVAIL=return] files myhostname dns
 networks:       files
index 8a603fb9ce4ce3e1e690c0af138c9c36f30b2958..98eb0ec77edaedbbcf7361753bd74c0d3f1cdbe7 100644 (file)
@@ -91,7 +91,9 @@
     <!-- synchronize with other nss-* man pages and factory/etc/nsswitch.conf -->
 <programlisting>passwd:         compat systemd
 group:          compat [SUCCESS=merge] systemd
-shadow:         compat
+shadow:         compat systemd
+gshadow:        files systemd
+
 
 # Either (untrusted network, see above):
 hosts:          mymachines resolve [!UNAVAIL=return] files <command>myhostname</command> dns
index b2785df410c19992e840fd7e7eca5fe50864a2fc..03fcd4308e0faa62dc09e68e3fb204234c907c79 100644 (file)
@@ -57,7 +57,8 @@
     <!-- synchronize with other nss-* man pages and factory/etc/nsswitch.conf -->
     <programlisting>passwd:         compat systemd
 group:          compat [SUCCESS=merge] systemd
-shadow:         compat
+shadow:         compat systemd
+gshadow:        files systemd
 
 hosts:          <command>mymachines</command> resolve [!UNAVAIL=return] files myhostname dns
 networks:       files
index 78c92030aca4cc9cecb462b603447c72b12c943f..97c3768100bacac9ef1fc51e3ff67f59e2e7a785 100644 (file)
@@ -63,7 +63,8 @@
     <!-- synchronize with other nss-* man pages and factory/etc/nsswitch.conf -->
 <programlisting>passwd:         compat systemd
 group:          compat [SUCCESS=merge] systemd
-shadow:         compat
+shadow:         compat systemd
+gshadow:        files systemd
 
 hosts:          mymachines <command>resolve [!UNAVAIL=return]</command> files myhostname dns
 networks:       files
index 1fee8cc8ba051bbf1e9841adcc3139b98f750d6c..d42a866b0ff8862577ac195cceb0583620c3450c 100644 (file)
     for resolving users and groups, but also works without the service running.</para>
 
     <para>To activate the NSS module, add <literal>systemd</literal> to the lines starting with
-    <literal>passwd:</literal> and <literal>group:</literal> in <filename>/etc/nsswitch.conf</filename>.</para>
+    <literal>passwd:</literal>, <literal>group:</literal>, <literal>shadow:</literal> and
+    <literal>gshadow:</literal> in <filename>/etc/nsswitch.conf</filename>.</para>
 
     <para>It is recommended to place <literal>systemd</literal> after the <literal>files</literal> or
     <literal>compat</literal> entry of the <filename>/etc/nsswitch.conf</filename> lines so that
-    <filename>/etc/passwd</filename> and <filename>/etc/group</filename> based mappings take precedence.</para>
+    <filename>/etc/passwd</filename>, <filename>/etc/group</filename>, <filename>/etc/shadow</filename> and
+    <filename>/etc/gshadow</filename> based mappings take precedence.</para>
   </refsect1>
 
   <refsect1>
@@ -63,7 +65,8 @@
     <!-- synchronize with other nss-* man pages and factory/etc/nsswitch.conf -->
     <programlisting>passwd:         compat <command>systemd</command>
 group:          compat [SUCCESS=merge] <command>systemd</command>
-shadow:         compat
+shadow:         compat <command>systemd</command>
+gshadow:        files <command>systemd</command>
 
 hosts:          mymachines resolve [!UNAVAIL=return] files myhostname dns
 networks:       files
index 3c59dcc03c363b5106c4a8624c9b3d9aef65171b..579e2c0bded657c0673f906cc76e5f805f17b402 100644 (file)
@@ -127,6 +127,20 @@ enum nss_status _nss_##module##_getpwuid_r(             \
                 char *buffer, size_t buflen,            \
                 int *errnop) _public_
 
+#define NSS_GETSP_PROTOTYPES(module)                    \
+enum nss_status _nss_##module##_getspnam_r(             \
+                const char *name,                       \
+                struct spwd *spwd,                      \
+                char *buffer, size_t buflen,            \
+                int *errnop) _public_
+
+#define NSS_GETSG_PROTOTYPES(module)                    \
+enum nss_status _nss_##module##_getsgnam_r(             \
+                const char *name,                       \
+                struct sgrp *sgrp,                      \
+                char *buffer, size_t buflen,            \
+                int *errnop) _public_
+
 #define NSS_GETGR_PROTOTYPES(module)                    \
 enum nss_status _nss_##module##_getgrnam_r(             \
                 const char *name,                       \
@@ -150,6 +164,17 @@ enum nss_status _nss_##module##_getpwent_r(             \
                 size_t buflen,                          \
                 int *errnop) _public_;
 
+#define NSS_SPENT_PROTOTYPES(module)                    \
+enum nss_status _nss_##module##_endspent(               \
+                void) _public_;                         \
+enum nss_status _nss_##module##_setspent(               \
+                int stayopen) _public_;                 \
+enum nss_status _nss_##module##_getspent_r(             \
+                struct spwd *spwd,                      \
+                char *buffer,                           \
+                size_t buflen,                          \
+                int *errnop) _public_;
+
 #define NSS_GRENT_PROTOTYPES(module)                    \
 enum nss_status _nss_##module##_endgrent(               \
                 void) _public_;                         \
@@ -161,6 +186,17 @@ enum nss_status _nss_##module##_getgrent_r(             \
                 size_t buflen,                          \
                 int *errnop) _public_;
 
+#define NSS_SGENT_PROTOTYPES(module)                    \
+enum nss_status _nss_##module##_endsgent(               \
+                void) _public_;                         \
+enum nss_status _nss_##module##_setsgent(               \
+                int stayopen) _public_;                 \
+enum nss_status _nss_##module##_getsgent_r(             \
+                struct sgrp *sgrp,                      \
+                char *buffer,                           \
+                size_t buflen,                          \
+                int *errnop) _public_;
+
 #define NSS_INITGROUPS_PROTOTYPE(module)                \
 enum nss_status _nss_##module##_initgroups_dyn(         \
                 const char *user,                       \
index 14712f8735d2a0adba763bb3954030a0f66dab9c..1b0866109ab071d117fd674779fd4753042c9de4 100644 (file)
@@ -28,6 +28,18 @@ static const struct passwd root_passwd = {
         .pw_shell = (char*) "/bin/sh",
 };
 
+static const struct spwd root_spwd = {
+        .sp_namp = (char*) "root",
+        .sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID,
+        .sp_lstchg = -1,
+        .sp_min = -1,
+        .sp_max = -1,
+        .sp_warn = -1,
+        .sp_inact = -1,
+        .sp_expire = -1,
+        .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
+};
+
 static const struct passwd nobody_passwd = {
         .pw_name = (char*) NOBODY_USER_NAME,
         .pw_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
@@ -38,6 +50,18 @@ static const struct passwd nobody_passwd = {
         .pw_shell = (char*) NOLOGIN,
 };
 
+static const struct spwd nobody_spwd = {
+        .sp_namp = (char*) NOBODY_USER_NAME,
+        .sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID,
+        .sp_lstchg = -1,
+        .sp_min = -1,
+        .sp_max = -1,
+        .sp_warn = -1,
+        .sp_inact = -1,
+        .sp_expire = -1,
+        .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
+};
+
 static const struct group root_group = {
         .gr_name = (char*) "root",
         .gr_gid = 0,
@@ -45,6 +69,11 @@ static const struct group root_group = {
         .gr_mem = (char*[]) { NULL },
 };
 
+static const struct sgrp root_sgrp = {
+        .sg_namp = (char*) "root",
+        .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
+};
+
 static const struct group nobody_group = {
         .gr_name = (char*) NOBODY_GROUP_NAME,
         .gr_gid = GID_NOBODY,
@@ -52,6 +81,11 @@ static const struct group nobody_group = {
         .gr_mem = (char*[]) { NULL },
 };
 
+static const struct sgrp nobody_sgrp = {
+        .sg_namp = (char*) NOBODY_GROUP_NAME,
+        .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
+};
+
 typedef struct GetentData {
         /* As explained in NOTES section of getpwent_r(3) as 'getpwent_r() is not really reentrant since it
          * shares the reading position in the stream with all other threads', we need to protect the data in
@@ -66,11 +100,19 @@ typedef struct GetentData {
 } GetentData;
 
 static GetentData getpwent_data = {
-        .mutex = PTHREAD_MUTEX_INITIALIZER
+        .mutex = PTHREAD_MUTEX_INITIALIZER,
 };
 
 static GetentData getgrent_data = {
-        .mutex = PTHREAD_MUTEX_INITIALIZER
+        .mutex = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static GetentData getspent_data = {
+        .mutex = PTHREAD_MUTEX_INITIALIZER,
+};
+
+static GetentData getsgent_data = {
+        .mutex = PTHREAD_MUTEX_INITIALIZER,
 };
 
 static void setup_logging(void) {
@@ -88,9 +130,13 @@ static void setup_logging_once(void) {
         setup_logging_once()
 
 NSS_GETPW_PROTOTYPES(systemd);
+NSS_GETSP_PROTOTYPES(systemd);
 NSS_GETGR_PROTOTYPES(systemd);
+NSS_GETSG_PROTOTYPES(systemd);
 NSS_PWENT_PROTOTYPES(systemd);
+NSS_SPENT_PROTOTYPES(systemd);
 NSS_GRENT_PROTOTYPES(systemd);
+NSS_SGENT_PROTOTYPES(systemd);
 NSS_INITGROUPS_PROTOTYPE(systemd);
 
 enum nss_status _nss_systemd_getpwnam_r(
@@ -191,6 +237,54 @@ enum nss_status _nss_systemd_getpwuid_r(
         return status;
 }
 
+enum nss_status _nss_systemd_getspnam_r(
+                const char *name,
+                struct spwd *spwd,
+                char *buffer, size_t buflen,
+                int *errnop) {
+
+        enum nss_status status;
+        int e;
+
+        PROTECT_ERRNO;
+        NSS_ENTRYPOINT_BEGIN;
+
+        assert(name);
+        assert(spwd);
+        assert(errnop);
+
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
+                return NSS_STATUS_NOTFOUND;
+
+        /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
+        if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
+
+                if (streq(name, root_spwd.sp_namp)) {
+                        *spwd = root_spwd;
+                        return NSS_STATUS_SUCCESS;
+                }
+
+                if (streq(name, nobody_spwd.sp_namp)) {
+                        if (!synthesize_nobody())
+                                return NSS_STATUS_NOTFOUND;
+
+                        *spwd = nobody_spwd;
+                        return NSS_STATUS_SUCCESS;
+                }
+
+        } else if (STR_IN_SET(name, root_spwd.sp_namp, nobody_spwd.sp_namp))
+                return NSS_STATUS_NOTFOUND;
+
+        status = userdb_getspnam(name, spwd, buffer, buflen, &e);
+        if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
+                UNPROTECT_ERRNO;
+                *errnop = e;
+                return status;
+        }
+
+        return status;
+}
+
 #pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
 
 enum nss_status _nss_systemd_getgrnam_r(
@@ -288,6 +382,54 @@ enum nss_status _nss_systemd_getgrgid_r(
         return status;
 }
 
+enum nss_status _nss_systemd_getsgnam_r(
+                const char *name,
+                struct sgrp *sgrp,
+                char *buffer, size_t buflen,
+                int *errnop) {
+
+        enum nss_status status;
+        int e;
+
+        PROTECT_ERRNO;
+        NSS_ENTRYPOINT_BEGIN;
+
+        assert(name);
+        assert(sgrp);
+        assert(errnop);
+
+        if (!valid_user_group_name(name, VALID_USER_RELAX))
+                return NSS_STATUS_NOTFOUND;
+
+        /* Synthesize records for root and nobody, in case they are missing from /etc/group */
+        if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
+
+                if (streq(name, root_sgrp.sg_namp)) {
+                        *sgrp = root_sgrp;
+                        return NSS_STATUS_SUCCESS;
+                }
+
+                if (streq(name, nobody_sgrp.sg_namp)) {
+                        if (!synthesize_nobody())
+                                return NSS_STATUS_NOTFOUND;
+
+                        *sgrp = nobody_sgrp;
+                        return NSS_STATUS_SUCCESS;
+                }
+
+        } else if (STR_IN_SET(name, root_sgrp.sg_namp, nobody_sgrp.sg_namp))
+                return NSS_STATUS_NOTFOUND;
+
+        status = userdb_getsgnam(name, sgrp, buffer, buflen, &e);
+        if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
+                UNPROTECT_ERRNO;
+                *errnop = e;
+                return status;
+        }
+
+        return status;
+}
+
 static enum nss_status nss_systemd_endent(GetentData *p) {
         PROTECT_ERRNO;
         NSS_ENTRYPOINT_BEGIN;
@@ -307,10 +449,18 @@ enum nss_status _nss_systemd_endpwent(void) {
         return nss_systemd_endent(&getpwent_data);
 }
 
+enum nss_status _nss_systemd_endspent(void) {
+        return nss_systemd_endent(&getspent_data);
+}
+
 enum nss_status _nss_systemd_endgrent(void) {
         return nss_systemd_endent(&getgrent_data);
 }
 
+enum nss_status _nss_systemd_endsgent(void) {
+        return nss_systemd_endent(&getsgent_data);
+}
+
 enum nss_status _nss_systemd_setpwent(int stayopen) {
         int r;
 
@@ -355,6 +505,46 @@ enum nss_status _nss_systemd_setgrent(int stayopen) {
         return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
 }
 
+enum nss_status _nss_systemd_setspent(int stayopen) {
+        int r;
+
+        PROTECT_ERRNO;
+        NSS_ENTRYPOINT_BEGIN;
+
+        if (_nss_systemd_is_blocked())
+                return NSS_STATUS_NOTFOUND;
+
+        _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getspent_data.mutex);
+        (void) _l; /* make llvm shut up about _l not being used. */
+
+        getspent_data.iterator = userdb_iterator_free(getspent_data.iterator);
+        getspent_data.by_membership = false;
+
+        /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
+        r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getspent_data.iterator);
+        return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_setsgent(int stayopen) {
+        int r;
+
+        PROTECT_ERRNO;
+        NSS_ENTRYPOINT_BEGIN;
+
+        if (_nss_systemd_is_blocked())
+                return NSS_STATUS_NOTFOUND;
+
+        _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getsgent_data.mutex);
+        (void) _l; /* make llvm shut up about _l not being used. */
+
+        getsgent_data.iterator = userdb_iterator_free(getsgent_data.iterator);
+        getsgent_data.by_membership = false;
+
+        /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
+        r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getsgent_data.iterator);
+        return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
+}
+
 enum nss_status _nss_systemd_getpwent_r(
                 struct passwd *result,
                 char *buffer, size_t buflen,
@@ -527,6 +717,110 @@ enum nss_status _nss_systemd_getgrent_r(
         return NSS_STATUS_SUCCESS;
 }
 
+enum nss_status _nss_systemd_getspent_r(
+                struct spwd *result,
+                char *buffer, size_t buflen,
+                int *errnop) {
+
+        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+        int r;
+
+        PROTECT_ERRNO;
+        NSS_ENTRYPOINT_BEGIN;
+
+        assert(result);
+        assert(errnop);
+
+        if (_nss_systemd_is_blocked())
+                return NSS_STATUS_NOTFOUND;
+
+        _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getspent_data.mutex);
+        (void) _l; /* make llvm shut up about _l not being used. */
+
+        if (!getspent_data.iterator) {
+                UNPROTECT_ERRNO;
+                *errnop = EHOSTDOWN;
+                return NSS_STATUS_UNAVAIL;
+        }
+
+        for (;;) {
+                r = userdb_iterator_get(getspent_data.iterator, &ur);
+                if (r == -ESRCH)
+                        return NSS_STATUS_NOTFOUND;
+                if (r < 0) {
+                        UNPROTECT_ERRNO;
+                        *errnop = -r;
+                        return NSS_STATUS_UNAVAIL;
+                }
+
+                if (!ur->incomplete) /* don't synthesize shadow records for records where we couldn't read shadow data */
+                        break;
+
+                ur = user_record_unref(ur);
+        }
+
+        r = nss_pack_user_record_shadow(ur, result, buffer, buflen);
+        if (r < 0) {
+                UNPROTECT_ERRNO;
+                *errnop = -r;
+                return NSS_STATUS_TRYAGAIN;
+        }
+
+        return NSS_STATUS_SUCCESS;
+}
+
+enum nss_status _nss_systemd_getsgent_r(
+                struct sgrp *result,
+                char *buffer, size_t buflen,
+                int *errnop) {
+
+        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
+        int r;
+
+        PROTECT_ERRNO;
+        NSS_ENTRYPOINT_BEGIN;
+
+        assert(result);
+        assert(errnop);
+
+        if (_nss_systemd_is_blocked())
+                return NSS_STATUS_NOTFOUND;
+
+        _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getsgent_data.mutex);
+        (void) _l; /* make llvm shut up about _l not being used. */
+
+        if (!getsgent_data.iterator) {
+                UNPROTECT_ERRNO;
+                *errnop = EHOSTDOWN;
+                return NSS_STATUS_UNAVAIL;
+        }
+
+        for (;;) {
+                r = groupdb_iterator_get(getsgent_data.iterator, &gr);
+                if (r == -ESRCH)
+                        return NSS_STATUS_NOTFOUND;
+                if (r < 0) {
+                        UNPROTECT_ERRNO;
+                        *errnop = -r;
+                        return NSS_STATUS_UNAVAIL;
+                }
+
+                if (!gr->incomplete) /* don't synthesize shadow records for records where we couldn't read shadow data */
+                        break;
+
+                gr = group_record_unref(gr);
+        }
+
+        r = nss_pack_group_record_shadow(gr, result, buffer, buflen);
+        if (r < 0) {
+                UNPROTECT_ERRNO;
+                *errnop = -r;
+                return NSS_STATUS_TRYAGAIN;
+        }
+
+        return NSS_STATUS_SUCCESS;
+}
+
 enum nss_status _nss_systemd_initgroups_dyn(
                 const char *user_name,
                 gid_t gid,
index 7caf21714482d565c8f9efbd7acddcd2f58cb31c..5602f0064cb80cb4610fd8095128b9ad84abd133 100644 (file)
 {
 global:
         _nss_systemd_getpwnam_r;
+        _nss_systemd_getspnam_r;
         _nss_systemd_getpwuid_r;
         _nss_systemd_endpwent;
         _nss_systemd_setpwent;
         _nss_systemd_getpwent_r;
+        _nss_systemd_endspent;
+        _nss_systemd_setspent;
+        _nss_systemd_getspent_r;
         _nss_systemd_getgrnam_r;
+        _nss_systemd_getsgnam_r;
         _nss_systemd_getgrgid_r;
         _nss_systemd_endgrent;
         _nss_systemd_setgrent;
         _nss_systemd_getgrent_r;
+        _nss_systemd_endsgent;
+        _nss_systemd_setsgent;
+        _nss_systemd_getsgent_r;
         _nss_systemd_initgroups_dyn;
 
         /* These two are not used by glibc, but can be used by apps to explicitly disable nss-systemd for the calling thread. */
index 4b04d03c95fb0c7c4d8b8a016d20d0f6de456fd5..f7df30f906c8c868a17070922a7f348c3e56c8e7 100644 (file)
@@ -32,7 +32,7 @@ int nss_pack_user_record(
         assert(hr);
         assert(pwd);
 
-        assert_se(hr->user_name);
+        assert(hr->user_name);
         required = strlen(hr->user_name) + 1;
 
         assert_se(rn = user_record_real_name(hr));
@@ -129,6 +129,85 @@ enum nss_status userdb_getpwuid(
         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 pasword 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_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_user_record_shadow(hr, spwd, buffer, buflen);
+        if (r < 0) {
+                *errnop = -r;
+                return NSS_STATUS_TRYAGAIN;
+        }
+
+        return NSS_STATUS_SUCCESS;
+}
+
 int nss_pack_group_record(
                 GroupRecord *g,
                 char **extra_members,
@@ -142,7 +221,7 @@ int nss_pack_group_record(
         assert(g);
         assert(gr);
 
-        assert_se(g->group_name);
+        assert(g->group_name);
         required = strlen(g->group_name) + 1;
 
         STRV_FOREACH(m, g->members) {
@@ -327,3 +406,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;
+}
index cb0dcb9302413d84c3ab1d3d44411179533bc5e1..386cc88955d24dca3f23ebe89f3bd6571dc39828 100644 (file)
@@ -13,8 +13,15 @@ UserDBFlags nss_glue_userdb_flags(void);
 int nss_pack_user_record(UserRecord *hr, struct passwd *pwd, char *buffer, size_t buflen);
 int nss_pack_group_record(GroupRecord *g, char **extra_members, struct group *gr, char *buffer, size_t buflen);
 
+int nss_pack_user_record_shadow(UserRecord *hr, struct spwd *spwd, char *buffer, size_t buflen);
+int nss_pack_group_record_shadow(GroupRecord *hr, struct sgrp *sgrp, char *buffer,size_t buflen);
+
 enum nss_status userdb_getpwnam(const char *name, struct passwd *pwd, char *buffer, size_t buflen, int *errnop);
 enum nss_status userdb_getpwuid(uid_t uid, struct passwd *pwd, char *buffer, size_t buflen, int *errnop);
 
+enum nss_status userdb_getspnam(const char *name, struct spwd *spwd, char *buffer, size_t buflen, int *errnop);
+
 enum nss_status userdb_getgrnam(const char *name, struct group *gr, char *buffer, size_t buflen, int *errnop);
 enum nss_status userdb_getgrgid(gid_t gid, struct group *gr, char *buffer, size_t buflen, int *errnop);
+
+enum nss_status userdb_getsgnam(const char *name, struct sgrp *sgrp, char *buffer, size_t buflen, int *errnop);