2 * Support rsync daemon authentication.
4 * Copyright (C) 1998-2000 Andrew Tridgell
5 * Copyright (C) 2002-2020 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;
111 if (!fname
|| !*fname
|| (fh
= fopen(fname
, "r")) == NULL
)
112 return "no secrets file";
114 if (do_fstat(fileno(fh
), &st
) == -1) {
115 rsyserr(FLOG
, errno
, "fstat(%s)", fname
);
117 } else if (lp_strict_modes(module
)) {
118 if ((st
.st_mode
& 06) != 0) {
119 rprintf(FLOG
, "secrets file must not be other-accessible (see strict modes option)\n");
121 } else if (MY_UID() == 0 && st
.st_uid
!= 0) {
122 rprintf(FLOG
, "secrets file must be owned by root when running as root (see strict modes)\n");
128 return "ignoring secrets file";
132 /* Reject attempt to match a comment. */
134 return "invalid username";
137 /* Try to find a line that starts with the user (or @group) name and a ':'. */
138 err
= "secret not found";
139 while ((user
|| group
) && fgets(line
, sizeof line
, fh
) != NULL
) {
140 const char **ptr
, *s
= strtok(line
, "\n\r");
152 if (!*ptr
|| strncmp(s
, *ptr
, len
) != 0 || s
[len
] != ':')
154 generate_hash(s
+len
+1, challenge
, pass2
);
155 if (strcmp(pass
, pass2
) == 0) {
159 err
= "password mismatch";
160 *ptr
= NULL
; /* Don't look for name again. */
165 force_memzero(line
, sizeof line
);
166 force_memzero(pass2
, sizeof pass2
);
171 static const char *getpassf(const char *filename
)
174 char buffer
[512], *p
;
180 if (strcmp(filename
, "-") == 0) {
181 n
= fgets(buffer
, sizeof buffer
, stdin
) == NULL
? -1 : (int)strlen(buffer
);
185 if ((fd
= open(filename
,O_RDONLY
)) < 0) {
186 rsyserr(FERROR
, errno
, "could not open password file %s", filename
);
187 exit_cleanup(RERR_SYNTAX
);
190 if (do_stat(filename
, &st
) == -1) {
191 rsyserr(FERROR
, errno
, "stat(%s)", filename
);
192 exit_cleanup(RERR_SYNTAX
);
194 if ((st
.st_mode
& 06) != 0) {
195 rprintf(FERROR
, "ERROR: password file must not be other-accessible\n");
196 exit_cleanup(RERR_SYNTAX
);
198 if (MY_UID() == 0 && st
.st_uid
!= 0) {
199 rprintf(FERROR
, "ERROR: password file must be owned by root when running as root\n");
200 exit_cleanup(RERR_SYNTAX
);
203 n
= read(fd
, buffer
, sizeof buffer
- 1);
209 if ((p
= strtok(buffer
, "\n\r")) != NULL
)
213 rprintf(FERROR
, "ERROR: failed to read a password from %s\n", filename
);
214 exit_cleanup(RERR_SYNTAX
);
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
];
229 char **auth_uid_groups
= NULL
;
230 int auth_uid_groups_cnt
= -1;
231 const char *err
= NULL
;
232 int group_match
= -1;
236 /* if no auth list then allow anyone in! */
237 if (!users
|| !*users
)
240 gen_challenge(addr
, challenge
);
242 io_printf(f_out
, "%s%s\n", leader
, challenge
);
244 if (!read_line_old(f_in
, line
, sizeof line
, 0)
245 || (pass
= strchr(line
, ' ')) == NULL
) {
246 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
247 "invalid challenge response\n",
248 lp_name(module
), host
, addr
);
253 if (!(users
= strdup(users
)))
254 out_of_memory("auth_server");
256 for (tok
= strtok(users
, " ,\t"); tok
; tok
= strtok(NULL
, " ,\t")) {
258 /* See if the user appended :deny, :ro, or :rw. */
259 if ((opts
= strchr(tok
, ':')) != NULL
) {
261 opt_ch
= isUpper(opts
) ? toLower(opts
) : *opts
;
262 if (opt_ch
== 'r') { /* handle ro and rw */
263 opt_ch
= isUpper(opts
+1) ? toLower(opts
+1) : opts
[1];
266 else if (opt_ch
!= 'w')
268 } else if (opt_ch
!= 'd') /* if it's not deny, ignore it */
273 /* Match the username */
274 if (wildmatch(tok
, line
))
277 #ifdef HAVE_GETGROUPLIST
279 /* See if authorizing user is a real user, and if so, see
280 * if it is in a group that matches tok+1 wildmat. */
281 if (auth_uid_groups_cnt
< 0) {
282 item_list gid_list
= EMPTY_ITEM_LIST
;
284 if (!user_to_uid(line
, &auth_uid
, False
)
285 || getallgroups(auth_uid
, &gid_list
) != NULL
)
286 auth_uid_groups_cnt
= 0;
288 gid_t
*gid_array
= gid_list
.items
;
289 auth_uid_groups_cnt
= gid_list
.count
;
290 if ((auth_uid_groups
= new_array(char *, auth_uid_groups_cnt
)) == NULL
)
291 out_of_memory("auth_server");
292 for (j
= 0; j
< auth_uid_groups_cnt
; j
++)
293 auth_uid_groups
[j
] = gid_to_group(gid_array
[j
]);
296 for (j
= 0; j
< auth_uid_groups_cnt
; j
++) {
297 if (auth_uid_groups
[j
] && wildmatch(tok
+1, auth_uid_groups
[j
])) {
302 if (group_match
>= 0)
305 rprintf(FLOG
, "your computer doesn't support getgrouplist(), so no @group authorization is possible.\n");
313 err
= "no matching rule";
314 else if (opt_ch
== 'd')
315 err
= "denied by rule";
317 char *group
= group_match
>= 0 ? auth_uid_groups
[group_match
] : NULL
;
318 err
= check_secret(module
, line
, group
, challenge
, pass
);
321 force_memzero(challenge
, sizeof challenge
);
322 force_memzero(pass
, strlen(pass
));
324 if (auth_uid_groups
) {
326 for (j
= 0; j
< auth_uid_groups_cnt
; j
++) {
327 if (auth_uid_groups
[j
])
328 free(auth_uid_groups
[j
]);
330 free(auth_uid_groups
);
334 rprintf(FLOG
, "auth failed on module %s from %s (%s) for %s: %s\n",
335 lp_name(module
), host
, addr
, line
, err
);
341 else if (opt_ch
== 'w')
347 void auth_client(int fd
, const char *user
, const char *challenge
)
350 char pass2
[MAX_DIGEST_LEN
*2];
355 if (!(pass
= getpassf(password_file
))
356 && !(pass
= getenv("RSYNC_PASSWORD"))) {
357 /* XXX: cyeoh says that getpass is deprecated, because
358 * it may return a truncated password on some systems,
359 * and it is not in the LSB.
361 * Andrew Klein says that getpassphrase() is present
362 * on Solaris and reads up to 256 characters.
364 * OpenBSD has a readpassphrase() that might be more suitable.
366 pass
= getpass("Password: ");
372 generate_hash(pass
, challenge
, pass2
);
373 io_printf(fd
, "%s %s\n", user
, pass2
);