64 lines
2.4 KiB
Python
64 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Internal elevated writer: configuration bytes arrive only on stdin.
|
|
|
|
Use a stable directory descriptor, exclusive temporary file, exact input-length
|
|
check and same-directory rename. Never evaluate configuration as a program.
|
|
"""
|
|
import os
|
|
import secrets
|
|
import stat
|
|
import sys
|
|
|
|
|
|
def write_config(path, expected_length, source):
|
|
parent, name = os.path.split(path)
|
|
if not parent or not name or name in (".", "..") or expected_length < 0:
|
|
raise ValueError("invalid destination")
|
|
directory = os.open(parent, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
temporary = ".lnd-config-" + secrets.token_hex(16)
|
|
created = False
|
|
try:
|
|
try:
|
|
info = os.stat(name, dir_fd=directory, follow_symlinks=False)
|
|
if not stat.S_ISREG(info.st_mode) or stat.S_IMODE(info.st_mode) & 0o7000:
|
|
raise ValueError("destination must be an ordinary regular file")
|
|
mode = stat.S_IMODE(info.st_mode)
|
|
except FileNotFoundError:
|
|
info = os.fstat(directory)
|
|
mode = 0o600
|
|
fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
|
|
0o600, dir_fd=directory)
|
|
created = True
|
|
with os.fdopen(fd, "wb") as output:
|
|
remaining = expected_length
|
|
while remaining:
|
|
chunk = source.read(min(65536, remaining))
|
|
if not chunk:
|
|
raise ValueError("incomplete input")
|
|
output.write(chunk)
|
|
remaining -= len(chunk)
|
|
if source.read(1):
|
|
raise ValueError("excess input")
|
|
output.flush()
|
|
os.fchown(output.fileno(), info.st_uid, info.st_gid)
|
|
os.fchmod(output.fileno(), mode)
|
|
os.fsync(output.fileno())
|
|
os.replace(temporary, name, src_dir_fd=directory, dst_dir_fd=directory)
|
|
created = False
|
|
os.fsync(directory)
|
|
finally:
|
|
if created:
|
|
os.unlink(temporary, dir_fd=directory)
|
|
os.close(directory)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
if len(sys.argv) != 3:
|
|
raise ValueError("invalid arguments")
|
|
write_config(sys.argv[1], int(sys.argv[2]), sys.stdin.buffer)
|
|
except Exception:
|
|
# No input contents or parser snippets in privileged process logs.
|
|
print("LND configuration write failed", file=sys.stderr)
|
|
sys.exit(1)
|