127 lines
3.9 KiB
Python
Executable File
127 lines
3.9 KiB
Python
Executable File
#!/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()
|