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.
26 extern char *password_file
;
28 /***************************************************************************
29 encode a buffer using base64 - simple and slow algorithm. null terminates
31 ***************************************************************************/
32 void base64_encode(const char *buf
, int len
, char *out
, int pad
)
34 char *b64
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
35 int bit_offset
, byte_offset
, idx
, i
;
36 const uchar
*d
= (const uchar
*)buf
;
37 int bytes
= (len
*8 + 5)/6;
39 for (i
= 0; i
< bytes
; i
++) {
40 byte_offset
= (i
*6)/8;
43 idx
= (d
[byte_offset
] >> (2-bit_offset
)) & 0x3F;
45 idx
= (d
[byte_offset
] << (bit_offset
-2)) & 0x3F;
46 if (byte_offset
+1 < len
) {
47 idx
|= (d
[byte_offset
+1] >> (8-(bit_offset
-2)));
53 while (pad
&& (i
% 4))
59 /* Generate a challenge buffer and return it base64-encoded. */
60 static void gen_challenge(const char *addr
, char *challenge
)
63 char digest
[MAX_DIGEST_LEN
];
67 memset(input
, 0, sizeof input
);
69 strlcpy(input
, addr
, 17);
70 sys_gettimeofday(&tv
);
71 SIVAL(input
, 16, tv
.tv_sec
);
72 SIVAL(input
, 20, tv
.tv_usec
);
73 SIVAL(input
, 24, getpid());
76 sum_update(input
, sizeof input
);
77 len
= sum_end(digest
);
79 base64_encode(digest
, len
, challenge
, 0);
82 /* Generate an MD4 hash created from the combination of the password
83 * and the challenge string and return it base64-encoded. */
84 static void generate_hash(const char *in
, const char *challenge
, char *out
)
86 char buf
[MAX_DIGEST_LEN
];
90 sum_update(in
, strlen(in
));
91 sum_update(challenge
, strlen(challenge
));
94 base64_encode(buf
, len
, out
, 0);
97 /* Return the secret for a user from the secret file, null terminated.
98 * Maximum length is len (not counting the null). */
99 static const char *check_secret(int module
, const char *user
, const char *group
,
100 const char *challenge
, const char *pass
)
103 char pass2
[MAX_DIGEST_LEN
*2];
104 const char *fname
= lp_secrets_file(module
);
107 int user_len
= strlen(user
);
108 int group_len
= group
? strlen(group
) : 0;
112 if (!fname
|| !*fname
|| (fh
= fopen(fname
, "r")) == NULL
)
113 return "no secrets file";
115 if (do_fstat(fileno(fh
), &st
) == -1) {
116 rsyserr(FLOG
, errno
, "fstat(%s)", fname
);
118 } else if (lp_strict_modes(module
)) {
119 if ((st
.st_mode
& 06) != 0) {
120 rprintf(FLOG
, "secrets file must not be other-accessible (see strict modes option)\n");
122 } else if (MY_UID() == 0 && st
.st_uid
!= 0) {
123 rprintf(FLOG
, "secrets file must be owned by root when running as root (see strict modes)\n");
129 return "ignoring secrets file";
133 /* Reject attempt to match a comment. */
135 return "invalid username";
138 /* Try to find a line that starts with the user (or @group) name and a ':'. */
139 err
= "secret not found";
140 while ((user
|| group
) && fgets(line
, sizeof line
, fh
) != NULL
) {
141 const char **ptr
, *s
= strtok(line
, "\n\r");
153 if (!*ptr
|| strncmp(s
, *ptr
, len
) != 0 || s
[len
] != ':')
155 generate_hash(s
+len
+1, challenge
, pass2
);
156 if (strcmp(pass
, pass2
) == 0) {
160 err
= "password mismatch";
161 *ptr
= NULL
; /* Don't look for name again. */
166 force_memzero(line
, sizeof line
);
167 force_memzero(pass2
, sizeof pass2
);
172 static const char *getpassf(const char *filename
)
175 char buffer
[512], *p
;
181 if (strcmp(filename
, "-") == 0) {
182 n
= fgets(buffer
, sizeof buffer
, stdin
) == NULL
? -1 : (int)strlen(buffer
);
186 if ((fd
= open(filename
,O_RDONLY
)) < 0) {
187 rsyserr(FERROR
, errno
, "could not open password file %s", filename
);
188 exit_cleanup(RERR_SYNTAX
);
191 if (do_stat(filename
, &st
) == -1) {
192 rsyserr(FERROR
, errno
, "stat(%s)", filename
);
193 exit_cleanup(RERR_SYNTAX
);
195 if ((st
.st_mode
& 06) != 0) {
196 rprintf(FERROR
, "ERROR: password file must not be other-accessible\n");
197 exit_cleanup(RERR_SYNTAX
);
199 if (MY_UID() == 0 && st
.st_uid
!= 0) {
200 rprintf(FERROR
, "ERROR: password file must be owned by root when running as root\n");
201 exit_cleanup(RERR_SYNTAX
);
204 n
= read(fd
, buffer
, sizeof buffer
- 1);
210 if ((p
= strtok(buffer
, "\n\r")) != NULL
)
214 rprintf(FERROR
, "ERROR: failed to read a password from %s\n", filename
);
215 exit_cleanup(RERR_SYNTAX
);
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
];
230 char **auth_uid_groups
= NULL
;
231 int auth_uid_groups_cnt
= -1;
232 const char *err
= NULL
;
233 int group_match
= -1;
237 /* if no auth list then allow anyone in! */
238 if (!users
|| !*users
)
241 gen_challenge(addr
, challenge
);
243 io_printf(f_out
, "%s%s\n", leader
, challenge
);
245 if (!read_line_old(f_in
, line
, sizeof line
, 0)
246 || (pass
= strchr(line
, ' ')) == NULL
) {
247 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
248 "invalid challenge response\n",
249 lp_name(module
), host
, addr
);
254 users
= strdup(users
);
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 auth_uid_groups
= new_array(char *, auth_uid_groups_cnt
);
291 for (j
= 0; j
< auth_uid_groups_cnt
; j
++)
292 auth_uid_groups
[j
] = gid_to_group(gid_array
[j
]);
295 for (j
= 0; j
< auth_uid_groups_cnt
; j
++) {
296 if (auth_uid_groups
[j
] && wildmatch(tok
+1, auth_uid_groups
[j
])) {
301 if (group_match
>= 0)
304 rprintf(FLOG
, "your computer doesn't support getgrouplist(), so no @group authorization is possible.\n");
312 err
= "no matching rule";
313 else if (opt_ch
== 'd')
314 err
= "denied by rule";
316 char *group
= group_match
>= 0 ? auth_uid_groups
[group_match
] : NULL
;
317 err
= check_secret(module
, line
, group
, challenge
, pass
);
320 force_memzero(challenge
, sizeof challenge
);
321 force_memzero(pass
, strlen(pass
));
323 if (auth_uid_groups
) {
325 for (j
= 0; j
< auth_uid_groups_cnt
; j
++) {
326 if (auth_uid_groups
[j
])
327 free(auth_uid_groups
[j
]);
329 free(auth_uid_groups
);
333 rprintf(FLOG
, "auth failed on module %s from %s (%s) for %s: %s\n",
334 lp_name(module
), host
, addr
, line
, err
);
340 else if (opt_ch
== 'w')
346 void auth_client(int fd
, const char *user
, const char *challenge
)
349 char pass2
[MAX_DIGEST_LEN
*2];
354 if (!(pass
= getpassf(password_file
))
355 && !(pass
= getenv("RSYNC_PASSWORD"))) {
356 /* XXX: cyeoh says that getpass is deprecated, because
357 * it may return a truncated password on some systems,
358 * and it is not in the LSB.
360 * Andrew Klein says that getpassphrase() is present
361 * on Solaris and reads up to 256 characters.
363 * OpenBSD has a readpassphrase() that might be more suitable.
365 pass
= getpass("Password: ");
371 generate_hash(pass
, challenge
, pass2
);
372 io_printf(fd
, "%s %s\n", user
, pass2
);