Files
rsync/testsuite/daemon-munge_test.py
Andrew Tridgell 05f30c05c9 testsuite: daemon parameter coverage (loopback)
Drive a loopback daemon (secure stdio-pipe transport by default, also green
under --use-tcp) via the new write_daemon_conf helper and assert the behaviour
of the security-relevant rsyncd.conf parameters, transferring >=3-deep trees:

  daemon-access  path / read only / write only / list, incl. a deep sub-path
                 pull and that a list=no module is hidden yet usable by name.
  daemon-filter  daemon exclude hides matching files everywhere; incoming /
                 outgoing chmod rewrite modes of every transferred file.
  daemon-auth    auth users + secrets file accept the right password, reject a
                 wrong one and an unauthenticated request; strict modes rejects
                 a world-readable secrets file.
  daemon-exec    pre-/post-xfer exec run with RSYNC_MODULE_NAME /
                 RSYNC_EXIT_STATUS; a failing pre-xfer exec aborts the transfer
                 (marker files polled for, since post-xfer exec runs after the
                 client disconnects under TCP).
  daemon-munge   munge symlinks stores incoming links with the /rsyncd-munged/
                 prefix and strips it on the way out.
  daemon-refuse  refuse options: a named option, a wildcard, and the '* !a !v'
                 allow-list idiom.

Green on master under pipe and --use-tcp transports and under --protocol=29.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:31:52 +10:00

54 lines
1.7 KiB
Python

#!/usr/bin/env python3
"""Daemon coverage: munge symlinks.
A module with "munge symlinks = yes" stores incoming symlinks with a
/rsyncd-munged/ prefix (so they can't be used to escape the module) and strips
that prefix from outgoing symlinks. Verify both directions on a symlink several
levels deep.
"""
import os
import subprocess
from rsyncfns import (
FROMDIR, SCRATCHDIR,
assert_is_symlink, make_tree, makepath, rmtree, rsync_argv,
start_test_daemon, test_fail, write_daemon_conf,
)
DAEMON_PORT = 12890
src = FROMDIR
deep = os.path.join('d1', 'd2')
rmtree(src)
make_tree(src, depth=3)
os.symlink('f3', src / deep / 'sl') # deep symlink -> f3
mungedest = SCRATCHDIR / 'mungedest'
pulled = SCRATCHDIR / 'mungepull'
for d in (mungedest, pulled):
rmtree(d)
makepath(mungedest, pulled)
conf = write_daemon_conf([
('munge', {'path': mungedest, 'read only': 'no', 'munge symlinks': 'yes'}),
])
url = start_test_daemon(conf, DAEMON_PORT)
# --- push: the stored symlink is munged with the /rsyncd-munged/ prefix ------
subprocess.run(rsync_argv('-al', f'{src}/', f'{url}munge/'),
stdout=subprocess.DEVNULL)
stored = mungedest / deep / 'sl'
assert_is_symlink(stored, label='munge stored symlink')
target = os.readlink(stored)
if target != '/rsyncd-munged/f3':
test_fail(f"munge symlinks stored {target!r}, expected '/rsyncd-munged/f3'")
# --- pull: the prefix is stripped back off on the way out -------------------
subprocess.run(rsync_argv('-al', f'{url}munge/', f'{pulled}/'),
stdout=subprocess.DEVNULL)
out = pulled / deep / 'sl'
assert_is_symlink(out, target='f3', label='munge stripped on pull')
print("daemon-munge: munge symlinks adds/strips /rsyncd-munged/ at depth")