#!/usr/bin/env python3 import argparse import json from pathlib import Path def main(): parser = argparse.ArgumentParser( description="Convert a passwd/group/shadow file to a directory of systemd JSON user records.", ) parser.add_argument( "passwd_file", type=Path, help="Path to the passwd file.", ) parser.add_argument( "group_file", type=Path, help="Path to the group file.", ) parser.add_argument( "shadow_file", type=Path, help="Path to the shadow file.", ) parser.add_argument( "output_dir", type=Path, help="Path to a directory to store json user records.", ) args = parser.parse_args() parsed_users = parse_passwd_file(args.passwd_file) parsed_groups = parse_group_file(args.group_file) parsed_shadow = parse_shadow_file(args.shadow_file) for user in parsed_users: print(json.dumps(user, indent=2)) for group in parsed_groups: print(json.dumps(group, indent=2)) for shadow_entry in parsed_shadow: print(json.dumps(shadow_entry, indent=2)) def parse_passwd_file(passwd_file: Path) -> list[dict]: users = [] with passwd_file.open("r") as f: for line in f: if line.startswith("#") or not line.strip(): continue parts = line.strip().split(":") if len(parts) < 7: print(f"Warning: Skipping malformed line: {line.strip()}") continue user = { "name": parts[0], "uid": int(parts[2]), "gid": int(parts[3]), "gecos": parse_passwd_gecos(parts[4]), "home": parts[5], "shell": parts[6], } users.append(user) return users def parse_passwd_gecos(gecos: str) -> dict: parts = gecos.split(",") return { "full_name": parts[0] if len(parts) > 0 else "", "room_number": parts[1] if len(parts) > 1 else "", "work_phone": parts[2] if len(parts) > 2 else "", "home_phone": parts[3] if len(parts) > 3 else "", "other": parts[4] if len(parts) > 4 else "", } def parse_group_file(group_file: Path) -> list[dict]: groups = [] with group_file.open("r") as f: for line in f: if line.startswith("#") or not line.strip(): continue parts = line.strip().split(":") if len(parts) < 4: print(f"Warning: Skipping malformed line: {line.strip()}") continue group = { "name": parts[0], "gid": int(parts[2]), "members": parts[3].split(",") if parts[3] else [], } groups.append(group) return groups def parse_shadow_file(shadow_file: Path) -> list[dict]: shadow_entries = [] with shadow_file.open("r") as f: for line in f: if line.startswith("#") or not line.strip(): continue parts = line.strip().split(":") if len(parts) < 9: print(f"Warning: Skipping malformed line: {line.strip()}") continue shadow_entry = { "name": parts[0], "password": parts[1], "last_change": int(parts[2]) if parts[2].isdigit() else None, "min_days": int(parts[3]) if parts[3].isdigit() else None, "max_days": int(parts[4]) if parts[4].isdigit() else None, "warn_days": int(parts[5]) if parts[5].isdigit() else None, "inactive_days": int(parts[6]) if parts[6].isdigit() else None, "expire_date": int(parts[7]) if parts[7].isdigit() else None, "reserved": parts[8], } shadow_entries.append(shadow_entry) return shadow_entries if __name__ == "__main__": main()