Claude Code for pwd: Python Unix Password Database — Claude Skills 360 Blog
Blog / AI / Claude Code for pwd: Python Unix Password Database
AI

Claude Code for pwd: Python Unix Password Database

Published: October 23, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s pwd module reads the Unix password database (/etc/passwd), returning struct_passwd named tuples. import pwd. getpwnam: pwd.getpwnam(name)struct_passwd; raises KeyError if not found. getpwuid: pwd.getpwuid(uid)struct_passwd. getpwall: pwd.getpwall()list[struct_passwd]. struct_passwd fields: pw_name (username str), pw_passwd (str, usually "x"), pw_uid (int), pw_gid (int primary group), pw_gecos (str, GECOS/full name), pw_dir (str home directory), pw_shell (str login shell). Current user: pwd.getpwuid(os.getuid()) or pwd.getpwnam(os.environ["USER"]). Home dir: pw.pw_dir or use pathlib.Path.home(). Drop privileges: call os.setgid(pw.pw_gid) then os.setuid(pw.pw_uid) (order matters). Windows: not available; use os.environ["USERNAME"] and pathlib.Path.home(). Claude Code generates user identity resolvers, home directory locators, privilege drop helpers, and user account auditors.

CLAUDE.md for pwd

## pwd Stack
- Stdlib: import pwd, os
- By name: entry = pwd.getpwnam("alice")       # KeyError if missing
- By UID:  entry = pwd.getpwuid(os.getuid())   # current user
- All:     entries = pwd.getpwall()
- Fields:  .pw_name .pw_passwd .pw_uid .pw_gid .pw_gecos .pw_dir .pw_shell
- Drop:    os.setgid(pw.pw_gid); os.setuid(pw.pw_uid)  # root only

pwd Unix Password Database Pipeline

# app/pwdutil.py — lookup, current user, home dir, privilege drop, audit
from __future__ import annotations

import os
import platform
from dataclasses import dataclass
from pathlib import Path

_PWD_AVAILABLE = platform.system() != "Windows"
if _PWD_AVAILABLE:
    import pwd


# ─────────────────────────────────────────────────────────────────────────────
# 1. Basic lookups
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class UserEntry:
    name:    str
    uid:     int
    gid:     int
    gecos:   str
    home:    Path
    shell:   str

    @property
    def full_name(self) -> str:
        """Extract the full name from the GECOS field (first comma-separated value)."""
        return self.gecos.split(",")[0].strip() if self.gecos else ""

    def __str__(self) -> str:
        return (f"uid={self.uid:5d}  gid={self.gid:5d}  "
                f"{self.name:<16s}  {self.home}  {self.shell}")


def _from_struct(e) -> UserEntry:
    return UserEntry(
        name=e.pw_name,
        uid=e.pw_uid,
        gid=e.pw_gid,
        gecos=e.pw_gecos,
        home=Path(e.pw_dir),
        shell=e.pw_shell,
    )


def lookup_by_name(name: str) -> UserEntry | None:
    """
    Look up a user by login name. Returns None if not found.

    Example:
        u = lookup_by_name("alice")
        if u: print(u.home)
    """
    if not _PWD_AVAILABLE:
        return None
    try:
        return _from_struct(pwd.getpwnam(name))
    except KeyError:
        return None


def lookup_by_uid(uid: int) -> UserEntry | None:
    """
    Look up a user by UID. Returns None if not found.

    Example:
        u = lookup_by_uid(os.getuid())
        if u: print(u.name)
    """
    if not _PWD_AVAILABLE:
        return None
    try:
        return _from_struct(pwd.getpwuid(uid))
    except KeyError:
        return None


def all_users() -> list[UserEntry]:
    """
    Return all user entries from the password database.

    Example:
        for u in all_users():
            if u.uid >= 1000: print(u)
    """
    if not _PWD_AVAILABLE:
        return []
    return [_from_struct(e) for e in pwd.getpwall()]


# ─────────────────────────────────────────────────────────────────────────────
# 2. Current user helpers
# ─────────────────────────────────────────────────────────────────────────────

def current_user() -> UserEntry | None:
    """
    Return the UserEntry for the currently running process (real UID).

    Example:
        u = current_user()
        print(u.name, u.home)
    """
    return lookup_by_uid(os.getuid())


def effective_user() -> UserEntry | None:
    """
    Return the UserEntry for the effective UID of the current process.
    Differs from current_user() when setuid is in effect.

    Example:
        u = effective_user()
        print(f"running as {u.name}")
    """
    return lookup_by_uid(os.geteuid())


def is_root() -> bool:
    """Return True if the effective UID is 0 (root)."""
    return os.geteuid() == 0


def home_dir(username: str | None = None) -> Path:
    """
    Return the home directory for a user (default: current user).
    Falls back to pathlib.Path.home() on Windows or lookup failure.

    Example:
        h = home_dir("alice")
        config = h / ".config" / "myapp"
    """
    if username is None:
        if _PWD_AVAILABLE:
            u = current_user()
            return u.home if u else Path.home()
        return Path.home()
    if _PWD_AVAILABLE:
        u = lookup_by_name(username)
        return u.home if u else Path(f"/home/{username}")
    return Path(f"/home/{username}")


def resolve_username(uid: int) -> str:
    """
    Return the username for a UID, or the UID string if not found.

    Example:
        name = resolve_username(stat_result.st_uid)
    """
    u = lookup_by_uid(uid)
    return u.name if u else str(uid)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Privilege management
# ─────────────────────────────────────────────────────────────────────────────

class PrivilegeError(PermissionError):
    """Raised when a privilege operation cannot be completed."""
    pass


def drop_privileges(username: str) -> None:
    """
    Drop root privileges to the named user (irreversibly).
    Must be called as root. Sets GID first, then UID.

    Example:
        drop_privileges("www-data")
        # Process now runs as www-data
    """
    if not _PWD_AVAILABLE:
        raise PrivilegeError("pwd not available on Windows")
    if os.geteuid() != 0:
        raise PrivilegeError("drop_privileges() requires root (UID 0)")
    u = lookup_by_name(username)
    if u is None:
        raise PrivilegeError(f"User '{username}' not found in password database")

    # Set supplementary groups
    try:
        import grp as _grp
        groups = os.getgrouplist(username, u.gid)
        os.setgroups(groups)
    except ImportError:
        os.setgroups([u.gid])

    # GID must be set before UID
    os.setgid(u.gid)
    os.setuid(u.uid)


def assert_not_root() -> None:
    """
    Raise PrivilegeError if the process is running as root.
    Useful at service start to enforce non-root operation.

    Example:
        assert_not_root()
        start_server()
    """
    if os.geteuid() == 0:
        raise PrivilegeError(
            "This process must not run as root. "
            "Use drop_privileges() or restart with a non-root user."
        )


# ─────────────────────────────────────────────────────────────────────────────
# 4. User audit
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class UserAudit:
    total_users:      int
    system_users:     int     # uid < 1000
    human_users:      int     # uid >= 1000
    no_home:          list[UserEntry]
    nologin_users:    list[UserEntry]
    uid_conflicts:    list[tuple[int, list[str]]]   # uid → [names]

    def __str__(self) -> str:
        return (
            f"total={self.total_users}  "
            f"system={self.system_users}  "
            f"human={self.human_users}  "
            f"no_home={len(self.no_home)}  "
            f"nologin={len(self.nologin_users)}  "
            f"uid_conflicts={len(self.uid_conflicts)}"
        )


def audit_users(human_uid_threshold: int = 1000) -> UserAudit:
    """
    Produce a summary audit of the system password database.

    Example:
        audit = audit_users()
        print(audit)
        for u in audit.no_home:
            print(f"  missing home: {u.name}")
    """
    users = all_users()
    if not users:
        return UserAudit(0, 0, 0, [], [], [])

    system = [u for u in users if u.uid < human_uid_threshold]
    human  = [u for u in users if u.uid >= human_uid_threshold]

    no_home = [u for u in users if not u.home.exists() and u.uid > 0]
    nologin = [u for u in users if "nologin" in u.shell or "false" in u.shell]

    # Detect UID collisions (multiple names sharing a UID)
    uid_map: dict[int, list[str]] = {}
    for u in users:
        uid_map.setdefault(u.uid, []).append(u.name)
    conflicts = [(uid, names) for uid, names in uid_map.items() if len(names) > 1]

    return UserAudit(
        total_users=len(users),
        system_users=len(system),
        human_users=len(human),
        no_home=no_home,
        nologin_users=nologin,
        uid_conflicts=conflicts,
    )


def users_with_shell(shell: str) -> list[UserEntry]:
    """
    Return users whose login shell contains the given substring.

    Example:
        bash_users = users_with_shell("/bash")
    """
    return [u for u in all_users() if shell in u.shell]


def users_by_uid_range(low: int, high: int) -> list[UserEntry]:
    """
    Return users whose UID falls in [low, high] (inclusive).

    Example:
        service_accounts = users_by_uid_range(100, 999)
    """
    return [u for u in all_users() if low <= u.uid <= high]


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== pwd demo ===")

    if not _PWD_AVAILABLE:
        print("  pwd not available on Windows — skipping")
        raise SystemExit(0)

    # ── current user ───────────────────────────────────────────────────────────
    print("\n--- current user ---")
    u = current_user()
    print(f"  current: {u}")
    eu = effective_user()
    print(f"  effective: {eu}")
    print(f"  is_root: {is_root()}")
    print(f"  home_dir(): {home_dir()}")

    # ── lookup ─────────────────────────────────────────────────────────────────
    print("\n--- lookups ---")
    for uid in [0, os.getuid()]:
        u = lookup_by_uid(uid)
        print(f"  uid={uid}: {u}")

    for name in ["root", "nobody"]:
        u = lookup_by_name(name)
        print(f"  {name!r}: {'found — ' + str(u) if u else 'not found'}")

    # ── resolve_username ───────────────────────────────────────────────────────
    print("\n--- resolve_username ---")
    for uid in [0, os.getuid(), 65534]:
        print(f"  uid={uid}{resolve_username(uid)!r}")

    # ── shell filter ───────────────────────────────────────────────────────────
    print("\n--- users_with_shell('/bash') ---")
    for u in users_with_shell("/bash")[:5]:
        print(f"  {u}")

    # ── UID range ─────────────────────────────────────────────────────────────
    print("\n--- system users (UID 0–99) ---")
    for u in users_by_uid_range(0, 99)[:8]:
        print(f"  {u}")

    # ── audit ──────────────────────────────────────────────────────────────────
    print("\n--- audit_users ---")
    audit = audit_users()
    print(f"  {audit}")
    if audit.uid_conflicts:
        print(f"  uid_conflicts: {audit.uid_conflicts[:3]}")

    print("\n=== done ===")

For the grp alternative — grp.getgrnam(name) and grp.getgrgid(gid) query the group database and return struct_group entries with gr_name, gr_gid, and gr_mem (member list) — use grp for group-level identity (which users belong to a group); use pwd for user-level identity (UID, home directory, login shell). The two modules are complementary: a typical privilege check uses pwd.getpwnam() to resolve from username to UID/GID, then grp.getgrall() or os.getgrouplist() to enumerate group memberships. For the os alternative — os.getuid()/os.geteuid() return process UIDs directly without a database lookup — use os.getuid() / os.geteuid() when you only need integer UIDs for os.stat() comparisons or os.setuid() calls; use pwd when you need the username string, home path, shell, or GECOS field that the /etc/passwd database provides. The Claude Skills 360 bundle includes pwd skill sets covering lookup_by_name()/lookup_by_uid()/all_users() database readers, current_user()/effective_user()/is_root()/home_dir()/resolve_username() identity helpers, drop_privileges()/assert_not_root() privilege management, and UserAudit with audit_users()/users_with_shell()/users_by_uid_range(). Start with the free tier to try user database patterns and pwd pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free