.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,
.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,
.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,
.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
} 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) {
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(
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(
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;
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;
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,
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,
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));
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,
assert(g);
assert(gr);
- assert_se(g->group_name);
+ assert(g->group_name);
required = strlen(g->group_name) + 1;
STRV_FOREACH(m, g->members) {
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;
+}