glibc User Databases

Last update: 2021-02-08 07:06:54 +0800 +0800
tags: Glibc | Linux | Users
Available translations: 日本語 / 中文(简体) / 中文(繁體)

The user database maps login names and numeric user IDs to account records. /etc/passwd is the familiar representation, but an application must not assume that the file is the authoritative source. glibc resolves the passwd database through the Name Service Switch (NSS), whose passwd: entry in /etc/nsswitch.conf can consult local files, systemd, LDAP, NIS, or other installed providers.

PAM does not perform these lookups. PAM handles authentication and account-management policy, whereas NSS answers identity database queries.

Account Records #

<pwd.h> defines the public record:

c
struct passwd {
    char  *pw_name;    /* login name */
    char  *pw_passwd;  /* password field, commonly "x" */
    uid_t  pw_uid;     /* user ID */
    gid_t  pw_gid;     /* primary group ID */
    char  *pw_gecos;   /* user information */
    char  *pw_dir;     /* home directory */
    char  *pw_shell;   /* login shell */
};

The historical password field no longer exposes a reusable password hash on a normal Linux installation. Hashes are kept in the separately protected shadow database; the public passwd record commonly contains x or another marker.

Looking Up a User #

Two functions perform keyed lookups:

c
#include <pwd.h>

struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);

getpwnam() searches by login name, and getpwuid() searches by numeric UID. Both return a pointer to library-managed storage on success. A later password-database call may overwrite that storage, and applications must not free it. NULL can mean either “not found” or an error, so set errno to zero before the call and inspect it afterward when the distinction matters.

NSS defines the lookup semantics. A request may cause local file access, IPC, or network traffic, and NSS policy can stop or continue after each provider result. Consequently, direct parsing of /etc/passwd is not equivalent to calling these interfaces.

Using the Reentrant Interfaces #

Multithreaded code should use getpwnam_r() and getpwuid_r() because they place strings in storage supplied by the caller:

c
struct passwd pwd;
struct passwd *result;
char *buf = malloc(buflen);

int error = getpwnam_r("build", &pwd, buf, buflen, &result);
if (error != 0) {
    /* error is an error number; ERANGE means the buffer was too small */
} else if (result == NULL) {
    /* no matching account */
} else {
    printf("%s has UID %ju\n", pwd.pw_name, (uintmax_t)pwd.pw_uid);
}

The function returns zero when the lookup completed, including the not-found case. *result points to the supplied struct passwd on success and is NULL when no record matched. A positive error number reports failure directly rather than through the return value -1. If the character buffer cannot hold all referenced strings, the function returns ERANGE; increase the buffer and retry. _SC_GETPW_R_SIZE_MAX from sysconf() is an initial sizing hint, not a guaranteed upper bound when it returns -1.

The returned pointers inside pwd refer into buf, so both objects must remain alive while the record is used. Reentrancy protects library storage; it does not make an NSS provider’s external database immutable or make a sequence of separate lookups atomic.

Enumerating the Database #

The stream-style API visits account records in the order exposed by the configured providers:

c
setpwent();

struct passwd *entry;
while ((entry = getpwent()) != NULL)
    printf("%s\n", entry->pw_name);

endpwent();
  • setpwent() rewinds the enumeration and may open provider resources.
  • getpwent() returns the next entry using shared internal storage.
  • endpwent() closes enumeration resources.

These functions maintain process-global enumeration state and are not safe for concurrent use by multiple threads. getpwent_r() supplies caller-owned record and string buffers, but enumeration still has shared stream state; glibc classifies setpwent(), getpwent_r(), and endpwent() as unsafe when another thread manipulates that state. Serialize a complete enumeration if other threads may enumerate concurrently.

Enumeration can also be incomplete or expensive for network-backed directories. Use keyed lookup when an application needs one identity, and do not treat a full scan as a stable snapshot.

References #

Comments

Read or comment on GitHub (sign-in required to comment)