2 * Support rsync daemon authentication.
4 * Copyright (C) 1998-2000 Andrew Tridgell
5 * Copyright (C) 2002-2007 Wayne Davison
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, visit the http://fsf.org website.
23 extern char *password_file
;
24 extern int log_got_error
;
26 /***************************************************************************
27 encode a buffer using base64 - simple and slow algorithm. null terminates
29 ***************************************************************************/
30 void base64_encode(const char *buf
, int len
, char *out
, int pad
)
32 char *b64
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
33 int bit_offset
, byte_offset
, idx
, i
;
34 const uchar
*d
= (const uchar
*)buf
;
35 int bytes
= (len
*8 + 5)/6;
37 for (i
= 0; i
< bytes
; i
++) {
38 byte_offset
= (i
*6)/8;
41 idx
= (d
[byte_offset
] >> (2-bit_offset
)) & 0x3F;
43 idx
= (d
[byte_offset
] << (bit_offset
-2)) & 0x3F;
44 if (byte_offset
+1 < len
) {
45 idx
|= (d
[byte_offset
+1] >> (8-(bit_offset
-2)));
51 while (pad
&& (i
% 4))
57 /* Generate a challenge buffer and return it base64-encoded. */
58 static void gen_challenge(const char *addr
, char *challenge
)
61 char digest
[MAX_DIGEST_LEN
];
65 memset(input
, 0, sizeof input
);
67 strlcpy(input
, addr
, 17);
68 sys_gettimeofday(&tv
);
69 SIVAL(input
, 16, tv
.tv_sec
);
70 SIVAL(input
, 20, tv
.tv_usec
);
71 SIVAL(input
, 24, getpid());
74 sum_update(input
, sizeof input
);
75 len
= sum_end(digest
);
77 base64_encode(digest
, len
, challenge
, 0);
81 /* Return the secret for a user from the secret file, null terminated.
82 * Maximum length is len (not counting the null). */
83 static int get_secret(int module
, const char *user
, char *secret
, int len
)
85 const char *fname
= lp_secrets_file(module
);
91 if (!fname
|| !*fname
)
94 if ((fd
= open(fname
, O_RDONLY
)) < 0)
97 if (do_stat(fname
, &st
) == -1) {
98 rsyserr(FLOG
, errno
, "stat(%s)", fname
);
100 } else if (lp_strict_modes(module
)) {
101 if ((st
.st_mode
& 06) != 0) {
102 rprintf(FLOG
, "secrets file must not be other-accessible (see strict modes option)\n");
104 } else if (MY_UID() == 0 && st
.st_uid
!= 0) {
105 rprintf(FLOG
, "secrets file must be owned by root when running as root (see strict modes)\n");
110 rprintf(FLOG
, "continuing without secrets file\n");
116 /* Reject attempt to match a comment. */
121 /* Try to find a line that starts with the user name and a ':'. */
124 if (read(fd
, &ch
, 1) != 1) {
133 else if (!*p
&& ch
== ':')
140 /* Slurp the secret into the "secret" buffer. */
143 if (read(fd
, s
, 1) != 1 || *s
== '\n')
156 static const char *getpassf(const char *filename
)
159 char buffer
[512], *p
;
161 const char *envpw
= getenv("RSYNC_PASSWORD");
166 if ((fd
= open(filename
,O_RDONLY
)) < 0) {
167 rsyserr(FERROR
, errno
, "could not open password file \"%s\"",
170 rprintf(FINFO
, "falling back to RSYNC_PASSWORD environment variable.\n");
174 if (do_stat(filename
, &st
) == -1) {
175 rsyserr(FERROR
, errno
, "stat(%s)", filename
);
177 } else if ((st
.st_mode
& 06) != 0) {
178 rprintf(FERROR
, "password file must not be other-accessible\n");
180 } else if (MY_UID() == 0 && st
.st_uid
!= 0) {
181 rprintf(FERROR
, "password file must be owned by root when running as root\n");
186 rprintf(FERROR
, "continuing without password file\n");
188 rprintf(FINFO
, "falling back to RSYNC_PASSWORD environment variable.\n");
192 n
= read(fd
, buffer
, sizeof buffer
- 1);
196 if ((p
= strtok(buffer
, "\n\r")) != NULL
)
203 /* Generate an MD4 hash created from the combination of the password
204 * and the challenge string and return it base64-encoded. */
205 static void generate_hash(const char *in
, const char *challenge
, char *out
)
207 char buf
[MAX_DIGEST_LEN
];
211 sum_update(in
, strlen(in
));
212 sum_update(challenge
, strlen(challenge
));
215 base64_encode(buf
, len
, out
, 0);
218 /* Possibly negotiate authentication with the client. Use "leader" to
219 * start off the auth if necessary.
221 * Return NULL if authentication failed. Return "" if anonymous access.
222 * Otherwise return username.
224 char *auth_server(int f_in
, int f_out
, int module
, const char *host
,
225 const char *addr
, const char *leader
)
227 char *users
= lp_auth_users(module
);
228 char challenge
[MAX_DIGEST_LEN
*2];
229 char line
[BIGPATHBUFLEN
];
231 char pass2
[MAX_DIGEST_LEN
*2];
234 /* if no auth list then allow anyone in! */
235 if (!users
|| !*users
)
238 gen_challenge(addr
, challenge
);
240 io_printf(f_out
, "%s%s\n", leader
, challenge
);
242 if (!read_line_old(f_in
, line
, sizeof line
)
243 || (pass
= strchr(line
, ' ')) == NULL
) {
244 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
245 "invalid challenge response\n",
246 lp_name(module
), host
, addr
);
251 if (!(users
= strdup(users
)))
252 out_of_memory("auth_server");
254 for (tok
= strtok(users
, " ,\t"); tok
; tok
= strtok(NULL
, " ,\t")) {
255 if (wildmatch(tok
, line
))
261 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
262 "unauthorized user\n",
263 lp_name(module
), host
, addr
);
267 memset(secret
, 0, sizeof secret
);
268 if (!get_secret(module
, line
, secret
, sizeof secret
- 1)) {
269 memset(secret
, 0, sizeof secret
);
270 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
271 "missing secret for user \"%s\"\n",
272 lp_name(module
), host
, addr
, line
);
276 generate_hash(secret
, challenge
, pass2
);
277 memset(secret
, 0, sizeof secret
);
279 if (strcmp(pass
, pass2
) != 0) {
280 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
281 "password mismatch\n",
282 lp_name(module
), host
, addr
);
289 void auth_client(int fd
, const char *user
, const char *challenge
)
292 char pass2
[MAX_DIGEST_LEN
*2];
297 if (!(pass
= getpassf(password_file
))
298 && !(pass
= getenv("RSYNC_PASSWORD"))) {
299 /* XXX: cyeoh says that getpass is deprecated, because
300 * it may return a truncated password on some systems,
301 * and it is not in the LSB.
303 * Andrew Klein says that getpassphrase() is present
304 * on Solaris and reads up to 256 characters.
306 * OpenBSD has a readpassphrase() that might be more suitable.
308 pass
= getpass("Password: ");
311 /* Any errors output during password handling aren't transfer errors. */
317 generate_hash(pass
, challenge
, pass2
);
318 io_printf(fd
, "%s %s\n", user
, pass2
);