Claude Code for nis: Python NIS Network Information Service — Claude Skills 360 Blog
Blog / AI / Claude Code for nis: Python NIS Network Information Service
AI

Claude Code for nis: Python NIS Network Information Service

Published: January 20, 2029
Read time: 5 min read
By: Claude Skills 360

Python’s nis module (Unix only, deprecated Python 3.11, removed Python 3.13) is a thin wrapper around the NIS (Network Information Service, also called Yellow Pages) client library. import nis. Default domain: nis.get_default_domain()str. List maps: nis.maps() or nis.maps(domain)list[str]. Lookup: nis.match(key, map)str; e.g. nis.match("alice", "passwd.byname"). Full map dump: nis.cat(map)dict[str, str]; e.g. nis.cat("hosts.byname"). With domain: nis.match(key, map, domain) and nis.cat(map, domain). Exception: nis.error — raised on YPERR_KEY (key not found), YPERR_DOMAIN (domain not bound), YPERR_YPBIND (ypbind not running), etc. Common NIS maps: passwd.byname, passwd.byuid, group.byname, group.bygid, hosts.byname, hosts.byaddr, netgroup, auto.master, auto.home. NIS is legacy technology (1980s Sun Microsystems), largely superseded by LDAP/Active Directory, but still found in older Unix data centres. Claude Code generates NIS-to-LDAP migration tools, network user enumerators, Unix group importers, hostname resolvers, and NIS map auditors.

CLAUDE.md for nis

## nis Stack
- Stdlib: import nis   (Unix only, deprecated 3.11, removed 3.13)
- Domain: nis.get_default_domain()
- Maps:   nis.maps()               # list all maps in default domain
-         nis.maps(domain)          # list maps in given domain
- Lookup: nis.match(key, map)      # key → value; raises nis.error on miss
-         nis.match(key, map, domain)
- Dump:   nis.cat(map)             # full map → dict[str, str]
-         nis.cat(map, domain)
- Err:    nis.error               # base exception for all NIS failures
- Maps:   passwd.byname / passwd.byuid / group.byname / hosts.byname

nis NIS Client Pipeline

# app/nisutil.py — lookup, dump, user/group/host helpers, migration, audit
from __future__ import annotations

import pwd
import grp
import socket
from dataclasses import dataclass, field
from typing import Any

_NIS_AVAILABLE = False
try:
    import nis as _nis
    _NIS_AVAILABLE = True
except ImportError:
    pass


# ─────────────────────────────────────────────────────────────────────────────
# 1. Basic lookup wrappers
# ─────────────────────────────────────────────────────────────────────────────

def get_domain() -> str:
    """
    Return the default NIS domain name, or '' if NIS is unavailable.

    Example:
        domain = get_domain()   # e.g. "corp.example.com"
    """
    if not _NIS_AVAILABLE:
        return ""
    try:
        return _nis.get_default_domain()
    except Exception:
        return ""


def nis_match(key: str, map_name: str,
              domain: str | None = None) -> str | None:
    """
    Look up a key in a NIS map. Returns None if not found or NIS unavailable.

    Example:
        entry = nis_match("alice", "passwd.byname")
        host_ip = nis_match("fileserver", "hosts.byname")
    """
    if not _NIS_AVAILABLE:
        return None
    try:
        if domain:
            return _nis.match(key, map_name, domain)
        return _nis.match(key, map_name)
    except Exception:
        return None


def nis_cat(map_name: str, domain: str | None = None) -> dict[str, str]:
    """
    Return all key-value pairs from a NIS map as a dict.
    Returns {} if NIS is unavailable or the map doesn't exist.

    Example:
        users = nis_cat("passwd.byname")
        groups = nis_cat("group.byname")
    """
    if not _NIS_AVAILABLE:
        return {}
    try:
        if domain:
            return _nis.cat(map_name, domain)
        return _nis.cat(map_name)
    except Exception:
        return {}


def nis_list_maps(domain: str | None = None) -> list[str]:
    """
    Return a sorted list of available NIS map names.

    Example:
        maps = nis_list_maps()
        print("\\n".join(maps))
    """
    if not _NIS_AVAILABLE:
        return []
    try:
        if domain:
            return sorted(_nis.maps(domain))
        return sorted(_nis.maps())
    except Exception:
        return []


# ─────────────────────────────────────────────────────────────────────────────
# 2. NIS user / group helpers
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class NisUser:
    username:  str
    uid:       int
    gid:       int
    gecos:     str
    home_dir:  str
    shell:     str


def parse_passwd_entry(entry: str) -> NisUser | None:
    """
    Parse a passwd(5) format entry string into a NisUser.

    Example:
        user = parse_passwd_entry("alice:x:1001:1001:Alice:/home/alice:/bin/bash")
    """
    parts = entry.split(":")
    if len(parts) < 7:
        return None
    try:
        return NisUser(
            username=parts[0],
            uid=int(parts[2]),
            gid=int(parts[3]),
            gecos=parts[4],
            home_dir=parts[5],
            shell=parts[6].rstrip("\x00"),
        )
    except (ValueError, IndexError):
        return None


def list_nis_users() -> list[NisUser]:
    """
    Return all NIS users from the passwd.byname map.

    Example:
        for user in list_nis_users():
            print(user.username, user.uid, user.shell)
    """
    raw = nis_cat("passwd.byname")
    users: list[NisUser] = []
    for _name, entry in raw.items():
        user = parse_passwd_entry(entry)
        if user:
            users.append(user)
    return sorted(users, key=lambda u: u.uid)


@dataclass
class NisGroup:
    name:    str
    gid:     int
    members: list[str]


def parse_group_entry(key: str, entry: str) -> NisGroup | None:
    """
    Parse a group(5) format entry into a NisGroup.

    Example:
        g = parse_group_entry("admins", "admins:x:100:alice,bob")
    """
    parts = entry.split(":")
    if len(parts) < 4:
        return None
    try:
        members = [m for m in parts[3].rstrip("\x00").split(",") if m]
        return NisGroup(name=parts[0], gid=int(parts[2]), members=members)
    except (ValueError, IndexError):
        return None


def list_nis_groups() -> list[NisGroup]:
    """
    Return all NIS groups from the group.byname map.

    Example:
        for g in list_nis_groups():
            print(g.name, g.gid, g.members)
    """
    raw = nis_cat("group.byname")
    groups: list[NisGroup] = []
    for name, entry in raw.items():
        g = parse_group_entry(name, entry)
        if g:
            groups.append(g)
    return sorted(groups, key=lambda g: g.gid)


# ─────────────────────────────────────────────────────────────────────────────
# 3. NIS host resolver
# ─────────────────────────────────────────────────────────────────────────────

def resolve_host_via_nis(hostname: str) -> str | None:
    """
    Look up a hostname in the NIS hosts.byname map.
    Returns the IP address string or None.

    Example:
        ip = resolve_host_via_nis("fileserver")
    """
    entry = nis_match(hostname, "hosts.byname")
    if entry is None:
        return None
    # hosts.byname value format: "ip\thostname [aliases...]"
    parts = entry.split()
    return parts[0] if parts else None


# ─────────────────────────────────────────────────────────────────────────────
# 4. NIS map auditor
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class NisAuditReport:
    domain:         str
    maps_available: list[str]
    user_count:     int = 0
    group_count:    int = 0
    host_count:     int = 0
    issues:         list[str] = field(default_factory=list)


def audit_nis(domain: str | None = None) -> NisAuditReport:
    """
    Audit the current NIS domain: list maps, count users/groups/hosts,
    flag any users without a valid shell, groups with no members, etc.

    Example:
        report = audit_nis()
        print(report.domain, report.user_count, report.issues)
    """
    dom = domain or get_domain()
    maps = nis_list_maps(domain)
    report = NisAuditReport(domain=dom, maps_available=maps)

    users = list_nis_users()
    report.user_count = len(users)
    for u in users:
        if not u.shell or u.shell in ("/sbin/nologin", "/bin/false"):
            pass  # system/service accounts — not an issue
        elif u.uid == 0 and u.username != "root":
            report.issues.append(
                f"UID 0 assigned to non-root user: {u.username!r}")

    groups = list_nis_groups()
    report.group_count = len(groups)

    host_map = nis_cat("hosts.byname", domain)
    report.host_count = len(host_map)

    return report


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

if __name__ == "__main__":
    print("=== nis demo ===")
    print(f"  nis available  : {_NIS_AVAILABLE}")
    print(f"  default domain : {get_domain()!r}")

    if _NIS_AVAILABLE:
        print("\n--- available maps ---")
        for m in nis_list_maps()[:10]:
            print(f"  {m}")

        print("\n--- list_nis_users (first 5) ---")
        for user in list_nis_users()[:5]:
            print(f"  uid={user.uid:5d}  {user.username:20s}  {user.shell}")

        print("\n--- list_nis_groups (first 5) ---")
        for g in list_nis_groups()[:5]:
            print(f"  gid={g.gid:5d}  {g.name:20s}  members={g.members[:3]}")

        print("\n--- audit_nis ---")
        report = audit_nis()
        print(f"  domain  : {report.domain}")
        print(f"  maps    : {len(report.maps_available)}")
        print(f"  users   : {report.user_count}")
        print(f"  groups  : {report.group_count}")
        print(f"  hosts   : {report.host_count}")
        print(f"  issues  : {report.issues or 'none'}")
    else:
        print("\n  (NIS client library not available on this system)")
        print("  Falling back to local /etc/passwd via pwd module:")
        for entry in list(pwd.getpwall())[:3]:
            print(f"  uid={entry.pw_uid:5d}  {entry.pw_name:20s}  {entry.pw_shell}")

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

For the ldap3 (PyPI) alternative — ldap3.Connection(server, user, password).search(base_dn, filter, attributes=['uid','cn','mail']) queries LDAP/Active Directory, the standard replacement for NIS in modern Unix environments — use ldap3 for all new directory service lookups; NIS is a 1980s-era protocol with no encryption or authentication, deprecated in Python 3.11 and removed in 3.13. For the subprocess + getent fallback — subprocess.run(["getent", "passwd", "alice"], capture_output=True) calls the system getent utility which queries NSS (Name Service Switch) including NIS, LDAP, and local files — use subprocess + getent for a portable, NSS-aware lookup that works regardless of whether the underlying directory is NIS, LDAP, or /etc/passwd files. The Claude Skills 360 bundle includes nis skill sets covering get_domain()/nis_match()/nis_cat()/nis_list_maps() primitives, NisUser/parse_passwd_entry()/list_nis_users() user helpers, NisGroup/parse_group_entry()/list_nis_groups() group helpers, resolve_host_via_nis() host resolver, and NisAuditReport/audit_nis() domain auditor. Start with the free tier to try NIS client patterns and nis 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