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.