Library Functions for User and Group Info

 

Library Functions for User and Group Info
Chapter 8 Part 3 — getpwnam, getpwuid, getgrnam, getpwent and More
Part 3
of 4
Intermediate
Level
~25 min
Read Time
What You Will Learn

Reading /etc/passwd and /etc/group directly with fopen and fscanf would work, but it is fragile and does not handle NIS or LDAP. The right way is to use the standard POSIX library functions. These functions abstract away the storage backend and always give you the right data regardless of whether users are stored locally or across a network.

In this part you will learn how to look up user info by name and by UID, how to iterate through all users, how to read group records, and critical pitfalls like the static buffer problem and the non-reentrant nature of these functions.

Key Concepts in This Tutorial
getpwnam() getpwuid() struct passwd getgrnam() getgrgid() struct group getpwent() setpwent() endpwent() getspnam() Static Buffer Pitfall Reentrant Functions
Why Not Just Parse /etc/passwd Directly?

You might wonder: if /etc/passwd is a plain text file, why not just open it and parse it yourself? There are three good reasons not to.

NIS/LDAP transparency — users may not be in /etc/passwd at all Format changes — the internal format could change between distros Portability — POSIX functions work on BSD, macOS, and all UNIX variants

The Name Service Switch (NSS) layer sits between your program and the actual storage backend. When you call getpwnam(“alice”), NSS decides whether to look in /etc/passwd, query an LDAP server, or check NIS — based on configuration in /etc/nsswitch.conf. Your code does not need to know.

/* /etc/nsswitch.conf tells NSS where to look */
$ cat /etc/nsswitch.conf

passwd:     files ldap    # Check /etc/passwd first, then LDAP
group:      files ldap
shadow:     files ldap

/* Your code just calls getpwnam — NSS handles the rest */
struct passwd *p = getpwnam("alice");
/* Works whether alice is in /etc/passwd or an LDAP server */
Looking Up a User by Name: getpwnam()

The most common operation: given a username string, get all the account information for that user.

#include <pwd.h>
#include <stdio.h>
#include <errno.h>

int main() {
    struct passwd *pwd;

    errno = 0;
    pwd = getpwnam("alice");

    if (pwd == NULL) {
        if (errno == 0)
            printf("User 'alice' not found\n");
        else
            perror("getpwnam");
        return 1;
    }

    /* Print all fields from the passwd structure */
    printf("Login name  : %s\n",   pwd->pw_name);
    printf("UID         : %d\n",   pwd->pw_uid);
    printf("GID         : %d\n",   pwd->pw_gid);
    printf("Full name   : %s\n",   pwd->pw_gecos);
    printf("Home dir    : %s\n",   pwd->pw_dir);
    printf("Login shell : %s\n",   pwd->pw_shell);

    return 0;
}
/* Output when run */
Login name  : alice
UID         : 1001
GID         : 1001
Full name   : Alice Kumar
Home dir    : /home/alice
Login shell : /bin/bash

Link with no special flags — this function is in the standard C library (glibc).

$ gcc -o lookup lookup.c
$ ./lookup
The struct passwd Fields

Here is the complete structure for reference:

/* Defined in <pwd.h> */
struct passwd {
    char  *pw_name;    /* Login name */
    char  *pw_passwd;  /* Encrypted password (valid only if NO shadow) */
    uid_t  pw_uid;     /* Numeric User ID */
    gid_t  pw_gid;     /* Numeric primary Group ID */
    char  *pw_gecos;   /* Comment / full name */
    char  *pw_dir;     /* Home directory path */
    char  *pw_shell;   /* Login shell path */
};

Important: pw_passwd contains valid data ONLY if shadow passwords are NOT enabled. On modern Linux (shadow enabled), pw_passwd will just be “x”. To get the actual hash you need getspnam() from <shadow.h>.

Looking Up a User by UID: getpwuid()

Sometimes you have a numeric UID (perhaps from stat() on a file) and need to find the human-readable username. getpwuid() does exactly this.

#include <pwd.h>
#include <sys/stat.h>
#include <stdio.h>

/* A simple program that prints the owner name of a file */
int main(int argc, char *argv[]) {
    struct stat sb;
    struct passwd *pwd;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s filename\n", argv[0]);
        return 1;
    }

    /* Get file metadata */
    if (stat(argv[1], &sb) == -1) {
        perror("stat");
        return 1;
    }

    /* UID is in sb.st_uid — look up the name */
    pwd = getpwuid(sb.st_uid);

    if (pwd == NULL)
        printf("Owner UID: %d (name unknown)\n", sb.st_uid);
    else
        printf("Owner: %s (UID=%d)\n", pwd->pw_name, pwd->pw_uid);

    return 0;
}
$ ./owner /etc/hosts
Owner: root (UID=0)

$ ./owner /home/alice/notes.txt
Owner: alice (UID=1001)

This is essentially what the ls -l command does internally for every file it displays.

The Static Buffer Trap — A Critical Pitfall

Both getpwnam() and getpwuid() return a pointer to a statically allocated structure. This means the same memory location is reused on every call. If you call either function twice, the second call overwrites the data from the first call.

/* BUG: This prints the same UID twice — both will be bob's UID */
struct passwd *p1 = getpwnam("alice");
struct passwd *p2 = getpwnam("bob");

/* p1 now points to the SAME struct as p2 — it was overwritten! */
printf("alice UID = %d\n", p1->pw_uid);  /* WRONG: prints bob's UID */
printf("bob   UID = %d\n", p2->pw_uid);  /* Correct */

This is exactly what exercise 8-1 from the chapter is asking about. The fix is to copy the data you need before calling again:

/* CORRECT: Save the data you need before the next call */
struct passwd *p;
uid_t alice_uid, bob_uid;

p = getpwnam("alice");
alice_uid = p->pw_uid;  /* Copy the UID before next call */

p = getpwnam("bob");
bob_uid = p->pw_uid;

printf("alice UID = %d\n", alice_uid);   /* Now correct */
printf("bob   UID = %d\n", bob_uid);     /* Correct */

For multi-threaded programs or reentrant code, use the thread-safe variants:

#include <pwd.h>

/* Reentrant versions — you supply the buffer */
int getpwnam_r(const char *name,
               struct passwd *pwd,      /* caller-provided struct */
               char *buf,               /* caller-provided string buffer */
               size_t buflen,           /* size of buf */
               struct passwd **result); /* points to pwd on success, NULL if not found */

/* Usage example */
struct passwd pwd_store;
char buf[4096];
struct passwd *result;

if (getpwnam_r("alice", &pwd_store, buf, sizeof(buf), &result) == 0) {
    if (result != NULL)
        printf("alice UID = %d\n", result->pw_uid);
}
Converting Between Names and UIDs: Helper Functions

A very common requirement in real programs is to convert a username string to a UID (or vice versa). Here are clean, reusable helper functions that handle the edge cases properly:

#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

/* Returns username for a given UID, or NULL if not found */
char *username_from_uid(uid_t uid) {
    struct passwd *pwd = getpwuid(uid);
    return (pwd == NULL) ? NULL : pwd->pw_name;
}

/* Returns UID for a given username, or -1 if not found.
   Accepts either a name ("alice") or a numeric string ("1001"). */
uid_t uid_from_username(const char *name) {
    struct passwd *pwd;
    char *endptr;
    uid_t u;

    if (name == NULL || *name == '\0')
        return (uid_t) -1;

    /* Allow caller to pass a numeric string directly */
    u = (uid_t) strtol(name, &endptr, 10);
    if (*endptr == '\0')      /* entire string was a valid number */
        return u;

    pwd = getpwnam(name);
    if (pwd == NULL)
        return (uid_t) -1;

    return pwd->pw_uid;
}

/* Example usage */
int main() {
    printf("UID of alice = %d\n",   uid_from_username("alice"));
    printf("UID of 1001  = %d\n",   uid_from_username("1001")); /* numeric string */
    printf("Name of 0    = %s\n",   username_from_uid(0));
    printf("Name of 1001 = %s\n",   username_from_uid(1001));
    return 0;
}
/* Output */
UID of alice = 1001
UID of 1001  = 1001
Name of 0    = root
Name of 1001 = alice
Group Lookup Functions: getgrnam() and getgrgid()

The group equivalents work identically to the password functions, but they use <grp.h> and return a struct group.

#include <grp.h>
#include <stdio.h>

int main() {
    struct group *grp;

    /* Look up group by name */
    grp = getgrnam("developers");
    if (grp == NULL) {
        printf("Group 'developers' not found\n");
        return 1;
    }

    printf("Group name : %s\n",  grp->gr_name);
    printf("Group GID  : %d\n",  grp->gr_gid);
    printf("Members    : ");

    /* gr_mem is a NULL-terminated array of strings */
    char **member = grp->gr_mem;
    while (*member != NULL) {
        printf("%s ", *member);
        member++;
    }
    printf("\n");

    return 0;
}
/* Output */
Group name : developers
Group GID  : 200
Members    : alice carol dave
/* Look up group by GID */
grp = getgrgid(200);
if (grp != NULL)
    printf("GID 200 is group: %s\n", grp->gr_name);

The same static buffer pitfall applies to group functions. Successive calls overwrite the returned structure.

Scanning All Users: getpwent, setpwent, endpwent

Sometimes you need to iterate over every user on the system — for example, to build a list of all home directories, or to find all accounts that have not logged in for 90 days. The getpwent family does this.

#include <pwd.h>
#include <stdio.h>

/* Print all usernames and their UIDs */
int main() {
    struct passwd *pwd;

    /* No need to open the file manually — getpwent does it */
    while ((pwd = getpwent()) != NULL) {
        printf("%-20s  UID=%-6d  Home=%s\n",
               pwd->pw_name,
               pwd->pw_uid,
               pwd->pw_dir);
    }

    /* ALWAYS call endpwent when done — closes the file */
    endpwent();

    return 0;
}
/* Sample output */
root                  UID=0       Home=/root
daemon                UID=1       Home=/usr/sbin
alice                 UID=1001    Home=/home/alice
bob                   UID=1002    Home=/home/bob
carol                 UID=1003    Home=/home/carol

The three functions and their roles:

setpwent();   /* Rewind to the beginning of the password database */
getpwent();   /* Return next entry, NULL when exhausted */
endpwent();   /* Close the database (free resources) */

/* To restart iteration from the beginning mid-way */
setpwent();

/* Example: Find all users with UID >= 1000 (regular users) */
while ((pwd = getpwent()) != NULL) {
    if (pwd->pw_uid >= 1000)
        printf("Regular user: %s\n", pwd->pw_name);
}
endpwent();
Scanning All Groups: getgrent, setgrent, endgrent

The same pattern applies to groups. Useful for finding all groups a user belongs to, or listing all groups on the system.

#include <grp.h>
#include <stdio.h>
#include <string.h>

/* Print all groups that 'alice' belongs to (via the group file) */
void print_groups_for_user(const char *username) {
    struct group *grp;

    setgrent();    /* Start from beginning */

    while ((grp = getgrent()) != NULL) {
        char **member = grp->gr_mem;
        while (*member != NULL) {
            if (strcmp(*member, username) == 0) {
                printf("  %s (GID=%d)\n", grp->gr_name, grp->gr_gid);
                break;
            }
            member++;
        }
    }

    endgrent();    /* Clean up */
}

int main() {
    printf("Groups for alice:\n");
    print_groups_for_user("alice");
    return 0;
}
/* Output */
Groups for alice:
  developers (GID=200)
  docker (GID=999)
  sudo (GID=27)
Retrieving Shadow Password Records: getspnam()

If you are writing an authentication program (like a custom login screen or a PAM module), you may need to access the shadow password. The getspnam() function retrieves a shadow record by username.

#include <shadow.h>
#include <stdio.h>
#include <errno.h>

/* Must run as root or with appropriate privilege */
int main() {
    struct spwd *sp;

    errno = 0;
    sp = getspnam("alice");

    if (sp == NULL) {
        if (errno == EACCES)
            fprintf(stderr, "Permission denied: run as root\n");
        else
            fprintf(stderr, "User not found in shadow file\n");
        return 1;
    }

    printf("Login name    : %s\n",  sp->sp_namp);
    printf("Encrypted pw  : %s\n",  sp->sp_pwdp);  /* the actual hash */
    printf("Last changed  : %ld days since Jan 1 1970\n", sp->sp_lstchg);
    printf("Max days      : %ld\n",  sp->sp_max);
    printf("Warning days  : %ld\n",  sp->sp_warn);

    return 0;
}
$ sudo ./shadow_read
Login name    : alice
Encrypted pw  : $6$rAnD0mSaLt$VeryLongHashString...
Last changed  : 19500 days since Jan 1 1970
Max days      : 90
Warning days  : 7

There are also setspent(), getspent(), and endspent() for iterating all shadow records, following the same pattern as the password functions.

Practical Project: Who Owns This File?

Here is a small but complete program combining getpwuid and getgrgid to print detailed ownership info for a file, similar to what ls -l does:

#include <stdio.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>

int main(int argc, char *argv[]) {
    struct stat sb;
    struct passwd *pwd;
    struct group *grp;
    const char *filename;

    filename = (argc > 1) ? argv[1] : "/etc/hosts";

    if (stat(filename, &sb) == -1) {
        perror("stat");
        return 1;
    }

    /* Look up owner name */
    pwd = getpwuid(sb.st_uid);
    const char *owner = (pwd != NULL) ? pwd->pw_name : "unknown";

    /* Look up group name */
    grp = getgrgid(sb.st_gid);
    const char *group = (grp != NULL) ? grp->gr_name : "unknown";

    printf("File  : %s\n",    filename);
    printf("Owner : %s (UID=%d)\n", owner, sb.st_uid);
    printf("Group : %s (GID=%d)\n", group, sb.st_gid);
    printf("Size  : %ld bytes\n", (long)sb.st_size);

    return 0;
}
$ gcc -o fileinfo fileinfo.c
$ ./fileinfo /etc/passwd
File  : /etc/passwd
Owner : root (UID=0)
Group : root (GID=0)
Size  : 2345 bytes
Function Reference Summary
getpwnam(name) — lookup passwd by username getpwuid(uid) — lookup passwd by UID getpwent() — next passwd record (sequential scan) setpwent() — rewind to start of passwd database endpwent() — close passwd database getgrnam(name) — lookup group by name getgrgid(gid) — lookup group by GID getgrent() — next group record getspnam(name) — shadow record by username (root only) getpwnam_r() — thread-safe version of getpwnam
/* Headers required */
#include <pwd.h>   /* getpwnam, getpwuid, getpwent, setpwent, endpwent */
#include <grp.h>   /* getgrnam, getgrgid, getgrent, setgrent, endgrent */
#include <shadow.h> /* getspnam, getspent, setspent, endspent */

/* Compile */
$ gcc -o myprogram myprogram.c
/* No special link flags needed for pwd/grp functions */
/* Shadow functions also require no extra -l flag on Linux */
Continue to Part 4

In the final part, we learn how Linux encrypts passwords using crypt() and how to write a program that authenticates a user against the shadow password file.

Part 4: Password Encryption Back to Part 2

Leave a Reply

Your email address will not be published. Required fields are marked *