Skip to content

Commit be12460

Browse files
authored
auth: read only the first password-file line (#1137)
* auth: read only the first password-file line strtok() skips leading delimiters, so an empty first line followed by a password was interpreted as that password. Stop at the first CR/LF instead, keeping an empty first line empty and making file and stdin behavior consistent. Add daemon-auth regression coverage for LF, CRLF, empty input, an empty first line, and a final line without a newline for both input sources. Fixes #1134. Signed-off-by: Eduard Zateev <hackerowskiy@gmail.com> * tests: reject password-file fallback for empty first lines Supply the valid daemon password through RSYNC_PASSWORD in negative cases, then require exit status 1 and the password read diagnostic. Signed-off-by: Eduard Zateev <hackerowskiy@gmail.com> --------- Signed-off-by: Eduard Zateev <hackerowskiy@gmail.com>
1 parent 92a2a09 commit be12460

2 files changed

Lines changed: 74 additions & 2 deletions

File tree

‎authenticate.c‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,10 @@ static const char *getpassf(const char *filename)
271271

272272
if (n > 0) {
273273
buffer[n] = '\0';
274-
if ((p = strtok(buffer, "\n\r")) != NULL)
275-
return strdup(p);
274+
p = buffer + strcspn(buffer, "\n\r");
275+
*p = '\0';
276+
if (buffer[0] != '\0')
277+
return strdup(buffer);
276278
}
277279

278280
rprintf(FERROR, "ERROR: failed to read a password from %s\n", filename);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env python3
2+
"""Only the first line of --password-file is used, for files and stdin."""
3+
4+
import os
5+
import subprocess
6+
7+
from rsyncfns import (
8+
SCRATCHDIR, makepath, rmtree, rsync_argv, start_test_daemon,
9+
test_fail, write_daemon_conf,
10+
)
11+
12+
DAEMON_PORT = 12904
13+
USER = 'tuser'
14+
PASSWORD = b'secretpass'
15+
16+
base = SCRATCHDIR / 'password-file-first-line'
17+
rmtree(base)
18+
src = base / 'src'
19+
dest = base / 'dest'
20+
makepath(src)
21+
(src / 'payload').write_text('regression test\n')
22+
secrets = base / 'secrets'
23+
secrets.write_bytes(USER.encode() + b':' + PASSWORD + b'\n')
24+
secrets.chmod(0o600)
25+
conf = write_daemon_conf([
26+
('auth', {'path': dest, 'read only': 'no',
27+
'auth users': USER, 'secrets file': secrets}),
28+
])
29+
url = start_test_daemon(conf, DAEMON_PORT)
30+
userurl = url.replace('rsync://', f'rsync://{USER}@', 1)
31+
32+
cases = (
33+
('LF', PASSWORD + b'\nignored\n', True),
34+
('CRLF', PASSWORD + b'\r\nignored\r\n', True),
35+
('empty-file', b'', False),
36+
('empty-first-line', b'\n' + PASSWORD + b'\n', False),
37+
('no-final-newline', PASSWORD, True),
38+
)
39+
40+
for source in ('file', 'stdin'):
41+
for name, contents, succeeds in cases:
42+
rmtree(dest)
43+
makepath(dest)
44+
args = ['-a', f'{src}/', f'{userurl}auth/']
45+
if source == 'file':
46+
pwfile = base / f'{name}.password'
47+
pwfile.write_bytes(contents)
48+
pwfile.chmod(0o600)
49+
args.insert(1, f'--password-file={pwfile}')
50+
stdin = subprocess.DEVNULL
51+
else:
52+
args.insert(1, '--password-file=-')
53+
stdin = subprocess.PIPE
54+
proc = subprocess.run(rsync_argv(*args), input=contents if source == 'stdin' else None,
55+
stdin=stdin if source == 'file' else None,
56+
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE,
57+
text=False, timeout=20,
58+
env={**os.environ, 'RSYNC_PASSWORD':
59+
'wrong-fallback' if succeeds else PASSWORD.decode()})
60+
passed = proc.returncode == 0
61+
if passed != succeeds:
62+
test_fail(f'{source}/{name}: expected success={succeeds}, '
63+
f'rc={proc.returncode}, stderr={proc.stderr!r}')
64+
65+
if not succeeds and (proc.returncode != 1
66+
or b'failed to read a password' not in proc.stderr):
67+
test_fail(f'{source}/{name}: expected password read failure, '
68+
f'rc={proc.returncode}, stderr={proc.stderr!r}')
69+
70+
print('password-file: first-line behavior matches for files and stdin')

0 commit comments

Comments
 (0)