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
;
25 /***************************************************************************
26 encode a buffer using base64 - simple and slow algorithm. null terminates
28 ***************************************************************************/
29 void base64_encode(const char *buf
, int len
, char *out
, int pad
)
31 char *b64
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
32 int bit_offset
, byte_offset
, idx
, i
;
33 const uchar
*d
= (const uchar
*)buf
;
34 int bytes
= (len
*8 + 5)/6;
36 for (i
= 0; i
< bytes
; i
++) {
37 byte_offset
= (i
*6)/8;
40 idx
= (d
[byte_offset
] >> (2-bit_offset
)) & 0x3F;
42 idx
= (d
[byte_offset
] << (bit_offset
-2)) & 0x3F;
43 if (byte_offset
+1 < len
) {
44 idx
|= (d
[byte_offset
+1] >> (8-(bit_offset
-2)));
50 while (pad
&& (i
% 4))
56 /* Generate a challenge buffer and return it base64-encoded. */
57 static void gen_challenge(const char *addr
, char *challenge
)
60 char digest
[MAX_DIGEST_LEN
];
64 memset(input
, 0, sizeof input
);
66 strlcpy(input
, addr
, 17);
67 sys_gettimeofday(&tv
);
68 SIVAL(input
, 16, tv
.tv_sec
);
69 SIVAL(input
, 20, tv
.tv_usec
);
70 SIVAL(input
, 24, getpid());
73 sum_update(input
, sizeof input
);
74 len
= sum_end(digest
);
76 base64_encode(digest
, len
, challenge
, 0);
80 /* Return the secret for a user from the secret file, null terminated.
81 * Maximum length is len (not counting the null). */
82 static int get_secret(int module
, const char *user
, char *secret
, int len
)
84 const char *fname
= lp_secrets_file(module
);
90 if (!fname
|| !*fname
)
93 if ((fd
= open(fname
, O_RDONLY
)) < 0)
96 if (do_stat(fname
, &st
) == -1) {
97 rsyserr(FLOG
, errno
, "stat(%s)", fname
);
99 } else if (lp_strict_modes(module
)) {
100 if ((st
.st_mode
& 06) != 0) {
101 rprintf(FLOG
, "secrets file must not be other-accessible (see strict modes option)\n");
103 } else if (MY_UID() == 0 && st
.st_uid
!= 0) {
104 rprintf(FLOG
, "secrets file must be owned by root when running as root (see strict modes)\n");
109 rprintf(FLOG
, "continuing without secrets file\n");
115 /* Reject attempt to match a comment. */
120 /* Try to find a line that starts with the user name and a ':'. */
123 if (read(fd
, &ch
, 1) != 1) {
132 else if (!*p
&& ch
== ':')
139 /* Slurp the secret into the "secret" buffer. */
142 if (read(fd
, s
, 1) != 1 || *s
== '\n')
155 static const char *getpassf(const char *filename
)
158 char buffer
[512], *p
;
160 const char *envpw
= getenv("RSYNC_PASSWORD");
165 if ((fd
= open(filename
,O_RDONLY
)) < 0) {
166 rsyserr(FERROR
, errno
, "could not open password file \"%s\"",
169 rprintf(FINFO
, "falling back to RSYNC_PASSWORD environment variable.\n");
173 if (do_stat(filename
, &st
) == -1) {
174 rsyserr(FERROR
, errno
, "stat(%s)", filename
);
176 } else if ((st
.st_mode
& 06) != 0) {
177 rprintf(FERROR
, "password file must not be other-accessible\n");
179 } else if (MY_UID() == 0 && st
.st_uid
!= 0) {
180 rprintf(FERROR
, "password file must be owned by root when running as root\n");
185 rprintf(FERROR
, "continuing without password file\n");
187 rprintf(FINFO
, "falling back to RSYNC_PASSWORD environment variable.\n");
191 n
= read(fd
, buffer
, sizeof buffer
- 1);
195 if ((p
= strtok(buffer
, "\n\r")) != NULL
)
202 /* Generate an MD4 hash created from the combination of the password
203 * and the challenge string and return it base64-encoded. */
204 static void generate_hash(const char *in
, const char *challenge
, char *out
)
206 char buf
[MAX_DIGEST_LEN
];
210 sum_update(in
, strlen(in
));
211 sum_update(challenge
, strlen(challenge
));
214 base64_encode(buf
, len
, out
, 0);
217 /* Possibly negotiate authentication with the client. Use "leader" to
218 * start off the auth if necessary.
220 * Return NULL if authentication failed. Return "" if anonymous access.
221 * Otherwise return username.
223 char *auth_server(int f_in
, int f_out
, int module
, const char *host
,
224 const char *addr
, const char *leader
)
226 char *users
= lp_auth_users(module
);
227 char challenge
[MAX_DIGEST_LEN
*2];
228 char line
[BIGPATHBUFLEN
];
230 char pass2
[MAX_DIGEST_LEN
*2];
233 /* if no auth list then allow anyone in! */
234 if (!users
|| !*users
)
237 gen_challenge(addr
, challenge
);
239 io_printf(f_out
, "%s%s\n", leader
, challenge
);
241 if (!read_line_old(f_in
, line
, sizeof line
)
242 || (pass
= strchr(line
, ' ')) == NULL
) {
243 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
244 "invalid challenge response\n",
245 lp_name(module
), host
, addr
);
250 if (!(users
= strdup(users
)))
251 out_of_memory("auth_server");
253 for (tok
= strtok(users
, " ,\t"); tok
; tok
= strtok(NULL
, " ,\t")) {
254 if (wildmatch(tok
, line
))
260 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
261 "unauthorized user\n",
262 lp_name(module
), host
, addr
);
266 memset(secret
, 0, sizeof secret
);
267 if (!get_secret(module
, line
, secret
, sizeof secret
- 1)) {
268 memset(secret
, 0, sizeof secret
);
269 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
270 "missing secret for user \"%s\"\n",
271 lp_name(module
), host
, addr
, line
);
275 generate_hash(secret
, challenge
, pass2
);
276 memset(secret
, 0, sizeof secret
);
278 if (strcmp(pass
, pass2
) != 0) {
279 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
280 "password mismatch\n",
281 lp_name(module
), host
, addr
);
288 void auth_client(int fd
, const char *user
, const char *challenge
)
291 char pass2
[MAX_DIGEST_LEN
*2];
296 if (!(pass
= getpassf(password_file
))
297 && !(pass
= getenv("RSYNC_PASSWORD"))) {
298 /* XXX: cyeoh says that getpass is deprecated, because
299 * it may return a truncated password on some systems,
300 * and it is not in the LSB.
302 * Andrew Klein says that getpassphrase() is present
303 * on Solaris and reads up to 256 characters.
305 * OpenBSD has a readpassphrase() that might be more suitable.
307 pass
= getpass("Password: ");
313 generate_hash(pass
, challenge
, pass2
);
314 io_printf(fd
, "%s %s\n", user
, pass2
);