2 * Support rsync daemon authentication.
4 * Copyright (C) 1998-2000 Andrew Tridgell
5 * Copyright (C) 2002-2009 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.
25 extern char *password_file
;
27 /***************************************************************************
28 encode a buffer using base64 - simple and slow algorithm. null terminates
30 ***************************************************************************/
31 void base64_encode(const char *buf
, int len
, char *out
, int pad
)
33 char *b64
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
34 int bit_offset
, byte_offset
, idx
, i
;
35 const uchar
*d
= (const uchar
*)buf
;
36 int bytes
= (len
*8 + 5)/6;
38 for (i
= 0; i
< bytes
; i
++) {
39 byte_offset
= (i
*6)/8;
42 idx
= (d
[byte_offset
] >> (2-bit_offset
)) & 0x3F;
44 idx
= (d
[byte_offset
] << (bit_offset
-2)) & 0x3F;
45 if (byte_offset
+1 < len
) {
46 idx
|= (d
[byte_offset
+1] >> (8-(bit_offset
-2)));
52 while (pad
&& (i
% 4))
58 /* Generate a challenge buffer and return it base64-encoded. */
59 static void gen_challenge(const char *addr
, char *challenge
)
62 char digest
[MAX_DIGEST_LEN
];
66 memset(input
, 0, sizeof input
);
68 strlcpy(input
, addr
, 17);
69 sys_gettimeofday(&tv
);
70 SIVAL(input
, 16, tv
.tv_sec
);
71 SIVAL(input
, 20, tv
.tv_usec
);
72 SIVAL(input
, 24, getpid());
75 sum_update(input
, sizeof input
);
76 len
= sum_end(digest
);
78 base64_encode(digest
, len
, challenge
, 0);
81 /* Generate an MD4 hash created from the combination of the password
82 * and the challenge string and return it base64-encoded. */
83 static void generate_hash(const char *in
, const char *challenge
, char *out
)
85 char buf
[MAX_DIGEST_LEN
];
89 sum_update(in
, strlen(in
));
90 sum_update(challenge
, strlen(challenge
));
93 base64_encode(buf
, len
, out
, 0);
96 /* Return the secret for a user from the secret file, null terminated.
97 * Maximum length is len (not counting the null). */
98 static const char *check_secret(int module
, const char *user
, const char *group
,
99 const char *challenge
, const char *pass
)
102 char pass2
[MAX_DIGEST_LEN
*2];
103 const char *fname
= lp_secrets_file(module
);
106 int user_len
= strlen(user
);
107 int group_len
= group
? strlen(group
) : 0;
110 if (!fname
|| !*fname
|| (fd
= open(fname
, O_RDONLY
)) < 0)
111 return "no secrets file";
113 if (do_fstat(fd
, &st
) == -1) {
114 rsyserr(FLOG
, errno
, "fstat(%s)", fname
);
116 } else if (lp_strict_modes(module
)) {
117 if ((st
.st_mode
& 06) != 0) {
118 rprintf(FLOG
, "secrets file must not be other-accessible (see strict modes option)\n");
120 } else if (MY_UID() == 0 && st
.st_uid
!= 0) {
121 rprintf(FLOG
, "secrets file must be owned by root when running as root (see strict modes)\n");
127 return "ignoring secrets file";
131 /* Reject attempt to match a comment. */
133 return "invalid username";
136 /* Try to find a line that starts with the user (or @group) name and a ':'. */
137 err
= "secret not found";
138 while ((user
|| group
) && read_line_old(fd
, line
, sizeof line
, 1)) {
139 const char **ptr
, *s
;
150 if (!*ptr
|| strncmp(s
, *ptr
, len
) != 0 || s
[len
] != ':')
152 generate_hash(s
+len
+1, challenge
, pass2
);
153 if (strcmp(pass
, pass2
) == 0) {
157 err
= "password mismatch";
158 *ptr
= NULL
; /* Don't look for name again. */
163 memset(line
, 0, sizeof line
);
164 memset(pass2
, 0, sizeof pass2
);
169 static const char *getpassf(const char *filename
)
172 char buffer
[512], *p
;
174 const char *envpw
= getenv("RSYNC_PASSWORD");
179 if ((fd
= open(filename
,O_RDONLY
)) < 0) {
180 rsyserr(FWARNING
, errno
, "could not open password file \"%s\"",
183 rprintf(FINFO
, "falling back to RSYNC_PASSWORD environment variable.\n");
187 if (do_stat(filename
, &st
) == -1) {
188 rsyserr(FWARNING
, errno
, "stat(%s)", filename
);
190 } else if ((st
.st_mode
& 06) != 0) {
191 rprintf(FWARNING
, "password file must not be other-accessible\n");
193 } else if (MY_UID() == 0 && st
.st_uid
!= 0) {
194 rprintf(FWARNING
, "password file must be owned by root when running as root\n");
199 rprintf(FWARNING
, "continuing without password file\n");
201 rprintf(FINFO
, "falling back to RSYNC_PASSWORD environment variable.\n");
205 n
= read(fd
, buffer
, sizeof buffer
- 1);
209 if ((p
= strtok(buffer
, "\n\r")) != NULL
)
216 /* Possibly negotiate authentication with the client. Use "leader" to
217 * start off the auth if necessary.
219 * Return NULL if authentication failed. Return "" if anonymous access.
220 * Otherwise return username.
222 char *auth_server(int f_in
, int f_out
, int module
, const char *host
,
223 const char *addr
, const char *leader
)
225 char *users
= lp_auth_users(module
);
226 char challenge
[MAX_DIGEST_LEN
*2];
227 char line
[BIGPATHBUFLEN
];
228 char **auth_uid_groups
= NULL
;
229 int auth_uid_groups_cnt
= -1;
230 const char *err
= NULL
;
231 int group_match
= -1;
235 /* if no auth list then allow anyone in! */
236 if (!users
|| !*users
)
239 gen_challenge(addr
, challenge
);
241 io_printf(f_out
, "%s%s\n", leader
, challenge
);
243 if (!read_line_old(f_in
, line
, sizeof line
, 0)
244 || (pass
= strchr(line
, ' ')) == NULL
) {
245 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
246 "invalid challenge response\n",
247 lp_name(module
), host
, addr
);
252 if (!(users
= strdup(users
)))
253 out_of_memory("auth_server");
255 for (tok
= strtok(users
, " ,\t"); tok
; tok
= strtok(NULL
, " ,\t")) {
257 /* See if the user appended :deny, :ro, or :rw. */
258 if ((opts
= strchr(tok
, ':')) != NULL
) {
260 opt_ch
= isUpper(opts
) ? toLower(opts
) : *opts
;
261 if (opt_ch
== 'r') { /* handle ro and rw */
262 opt_ch
= isUpper(opts
+1) ? toLower(opts
+1) : opts
[1];
265 else if (opt_ch
!= 'w')
267 } else if (opt_ch
!= 'd') /* if it's not deny, ignore it */
272 /* Match the username */
273 if (wildmatch(tok
, line
))
276 #ifdef HAVE_GETGROUPLIST
278 /* See if authorizing user is a real user, and if so, see
279 * if it is in a group that matches tok+1 wildmat. */
280 if (auth_uid_groups_cnt
< 0) {
283 auth_uid_groups_cnt
= sizeof gid_list
/ sizeof (gid_t
);
284 if (!user_to_uid(line
, &auth_uid
, False
)
285 || getallgroups(auth_uid
, gid_list
, &auth_uid_groups_cnt
) != NULL
)
286 auth_uid_groups_cnt
= 0;
288 if ((auth_uid_groups
= new_array(char *, auth_uid_groups_cnt
)) == NULL
)
289 out_of_memory("auth_server");
290 for (j
= 0; j
< auth_uid_groups_cnt
; j
++)
291 auth_uid_groups
[j
] = gid_to_group(gid_list
[j
]);
294 for (j
= 0; j
< auth_uid_groups_cnt
; j
++) {
295 if (auth_uid_groups
[j
] && wildmatch(tok
+1, auth_uid_groups
[j
])) {
300 if (group_match
>= 0)
303 rprintf(FLOG
, "your computer doesn't support getgrouplist(), so no @group authorization is possible.\n");
311 err
= "no matching rule";
312 else if (opt_ch
== 'd')
313 err
= "denied by rule";
315 char *group
= group_match
>= 0 ? auth_uid_groups
[group_match
] : NULL
;
316 err
= check_secret(module
, line
, group
, challenge
, pass
);
319 memset(challenge
, 0, sizeof challenge
);
320 memset(pass
, 0, strlen(pass
));
322 if (auth_uid_groups
) {
324 for (j
= 0; j
< auth_uid_groups_cnt
; j
++) {
325 if (auth_uid_groups
[j
])
326 free(auth_uid_groups
[j
]);
328 free(auth_uid_groups
);
332 rprintf(FLOG
, "auth failed on module %s from %s (%s) for %s: %s\n",
333 lp_name(module
), host
, addr
, line
, err
);
339 else if (opt_ch
== 'w')
345 void auth_client(int fd
, const char *user
, const char *challenge
)
348 char pass2
[MAX_DIGEST_LEN
*2];
353 if (!(pass
= getpassf(password_file
))
354 && !(pass
= getenv("RSYNC_PASSWORD"))) {
355 /* XXX: cyeoh says that getpass is deprecated, because
356 * it may return a truncated password on some systems,
357 * and it is not in the LSB.
359 * Andrew Klein says that getpassphrase() is present
360 * on Solaris and reads up to 256 characters.
362 * OpenBSD has a readpassphrase() that might be more suitable.
364 pass
= getpass("Password: ");
370 generate_hash(pass
, challenge
, pass2
);
371 io_printf(fd
, "%s %s\n", user
, pass2
);