2 * Support rsync daemon authentication.
4 * Copyright (C) 1998-2000 Andrew Tridgell
5 * Copyright (C) 2002-2022 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
;
27 extern struct name_num_obj valid_auth_checksums
;
29 /***************************************************************************
30 encode a buffer using base64 - simple and slow algorithm. null terminates
32 ***************************************************************************/
33 void base64_encode(const char *buf
, int len
, char *out
, int pad
)
35 char *b64
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
36 int bit_offset
, byte_offset
, idx
, i
;
37 const uchar
*d
= (const uchar
*)buf
;
38 int bytes
= (len
*8 + 5)/6;
40 for (i
= 0; i
< bytes
; i
++) {
41 byte_offset
= (i
*6)/8;
44 idx
= (d
[byte_offset
] >> (2-bit_offset
)) & 0x3F;
46 idx
= (d
[byte_offset
] << (bit_offset
-2)) & 0x3F;
47 if (byte_offset
+1 < len
) {
48 idx
|= (d
[byte_offset
+1] >> (8-(bit_offset
-2)));
54 while (pad
&& (i
% 4))
60 /* Generate a challenge buffer and return it base64-encoded. */
61 static void gen_challenge(const char *addr
, char *challenge
)
64 char digest
[MAX_DIGEST_LEN
];
68 memset(input
, 0, sizeof input
);
70 strlcpy(input
, addr
, 17);
71 sys_gettimeofday(&tv
);
72 SIVAL(input
, 16, tv
.tv_sec
);
73 SIVAL(input
, 20, tv
.tv_usec
);
74 SIVAL(input
, 24, getpid());
76 len
= sum_init(valid_auth_checksums
.negotiated_nni
, 0);
77 sum_update(input
, sizeof input
);
80 base64_encode(digest
, len
, challenge
, 0);
83 /* Generate an MD4 hash created from the combination of the password
84 * and the challenge string and return it base64-encoded. */
85 static void generate_hash(const char *in
, const char *challenge
, char *out
)
87 char buf
[MAX_DIGEST_LEN
];
90 len
= sum_init(valid_auth_checksums
.negotiated_nni
, 0);
91 sum_update(in
, strlen(in
));
92 sum_update(challenge
, strlen(challenge
));
95 base64_encode(buf
, len
, out
, 0);
98 /* Return the secret for a user from the secret file, null terminated.
99 * Maximum length is len (not counting the null). */
100 static const char *check_secret(int module
, const char *user
, const char *group
,
101 const char *challenge
, const char *pass
)
104 char pass2
[MAX_DIGEST_LEN
*2];
105 const char *fname
= lp_secrets_file(module
);
108 int user_len
= strlen(user
);
109 int group_len
= group
? strlen(group
) : 0;
113 if (!fname
|| !*fname
|| (fh
= fopen(fname
, "r")) == NULL
)
114 return "no secrets file";
116 if (do_fstat(fileno(fh
), &st
) == -1) {
117 rsyserr(FLOG
, errno
, "fstat(%s)", fname
);
119 } else if (lp_strict_modes(module
)) {
120 if ((st
.st_mode
& 06) != 0) {
121 rprintf(FLOG
, "secrets file must not be other-accessible (see strict modes option)\n");
123 } else if (MY_UID() == ROOT_UID
&& st
.st_uid
!= ROOT_UID
) {
124 rprintf(FLOG
, "secrets file must be owned by root when running as root (see strict modes)\n");
130 return "ignoring secrets file";
134 /* Reject attempt to match a comment. */
136 return "invalid username";
139 /* Try to find a line that starts with the user (or @group) name and a ':'. */
140 err
= "secret not found";
141 while ((user
|| group
) && fgets(line
, sizeof line
, fh
) != NULL
) {
142 const char **ptr
, *s
= strtok(line
, "\n\r");
154 if (!*ptr
|| strncmp(s
, *ptr
, len
) != 0 || s
[len
] != ':')
156 generate_hash(s
+len
+1, challenge
, pass2
);
157 if (strcmp(pass
, pass2
) == 0) {
161 err
= "password mismatch";
162 *ptr
= NULL
; /* Don't look for name again. */
167 force_memzero(line
, sizeof line
);
168 force_memzero(pass2
, sizeof pass2
);
173 static const char *getpassf(const char *filename
)
176 char buffer
[512], *p
;
182 if (strcmp(filename
, "-") == 0) {
183 n
= fgets(buffer
, sizeof buffer
, stdin
) == NULL
? -1 : (int)strlen(buffer
);
187 if ((fd
= open(filename
,O_RDONLY
)) < 0) {
188 rsyserr(FERROR
, errno
, "could not open password file %s", filename
);
189 exit_cleanup(RERR_SYNTAX
);
192 if (do_stat(filename
, &st
) == -1) {
193 rsyserr(FERROR
, errno
, "stat(%s)", filename
);
194 exit_cleanup(RERR_SYNTAX
);
196 if ((st
.st_mode
& 06) != 0) {
197 rprintf(FERROR
, "ERROR: password file must not be other-accessible\n");
198 exit_cleanup(RERR_SYNTAX
);
200 if (MY_UID() == ROOT_UID
&& st
.st_uid
!= ROOT_UID
) {
201 rprintf(FERROR
, "ERROR: password file must be owned by root when running as root\n");
202 exit_cleanup(RERR_SYNTAX
);
205 n
= read(fd
, buffer
, sizeof buffer
- 1);
211 if ((p
= strtok(buffer
, "\n\r")) != NULL
)
215 rprintf(FERROR
, "ERROR: failed to read a password from %s\n", filename
);
216 exit_cleanup(RERR_SYNTAX
);
219 /* Possibly negotiate authentication with the client. Use "leader" to
220 * start off the auth if necessary.
222 * Return NULL if authentication failed. Return "" if anonymous access.
223 * Otherwise return username.
225 char *auth_server(int f_in
, int f_out
, int module
, const char *host
,
226 const char *addr
, const char *leader
)
228 char *users
= lp_auth_users(module
);
229 char challenge
[MAX_DIGEST_LEN
*2];
230 char line
[BIGPATHBUFLEN
];
231 const char **auth_uid_groups
= NULL
;
232 int auth_uid_groups_cnt
= -1;
233 const char *err
= NULL
;
234 int group_match
= -1;
238 /* if no auth list then allow anyone in! */
239 if (!users
|| !*users
)
242 negotiate_daemon_auth(f_out
, 0);
243 gen_challenge(addr
, challenge
);
245 io_printf(f_out
, "%s%s\n", leader
, challenge
);
247 if (!read_line_old(f_in
, line
, sizeof line
, 0)
248 || (pass
= strchr(line
, ' ')) == NULL
) {
249 rprintf(FLOG
, "auth failed on module %s from %s (%s): "
250 "invalid challenge response\n",
251 lp_name(module
), host
, addr
);
256 users
= strdup(users
);
258 for (tok
= strtok(users
, " ,\t"); tok
; tok
= strtok(NULL
, " ,\t")) {
260 /* See if the user appended :deny, :ro, or :rw. */
261 if ((opts
= strchr(tok
, ':')) != NULL
) {
263 opt_ch
= isUpper(opts
) ? toLower(opts
) : *opts
;
264 if (opt_ch
== 'r') { /* handle ro and rw */
265 opt_ch
= isUpper(opts
+1) ? toLower(opts
+1) : opts
[1];
268 else if (opt_ch
!= 'w')
270 } else if (opt_ch
!= 'd') /* if it's not deny, ignore it */
275 /* Match the username */
276 if (wildmatch(tok
, line
))
279 #ifdef HAVE_GETGROUPLIST
281 /* See if authorizing user is a real user, and if so, see
282 * if it is in a group that matches tok+1 wildmat. */
283 if (auth_uid_groups_cnt
< 0) {
284 item_list gid_list
= EMPTY_ITEM_LIST
;
286 if (!user_to_uid(line
, &auth_uid
, False
)
287 || getallgroups(auth_uid
, &gid_list
) != NULL
)
288 auth_uid_groups_cnt
= 0;
290 gid_t
*gid_array
= gid_list
.items
;
291 auth_uid_groups_cnt
= gid_list
.count
;
292 auth_uid_groups
= new_array(const char *, auth_uid_groups_cnt
);
293 for (j
= 0; j
< auth_uid_groups_cnt
; j
++)
294 auth_uid_groups
[j
] = gid_to_group(gid_array
[j
]);
297 for (j
= 0; j
< auth_uid_groups_cnt
; j
++) {
298 if (auth_uid_groups
[j
] && wildmatch(tok
+1, auth_uid_groups
[j
])) {
303 if (group_match
>= 0)
306 rprintf(FLOG
, "your computer doesn't support getgrouplist(), so no @group authorization is possible.\n");
314 err
= "no matching rule";
315 else if (opt_ch
== 'd')
316 err
= "denied by rule";
318 const char *group
= group_match
>= 0 ? auth_uid_groups
[group_match
] : NULL
;
319 err
= check_secret(module
, line
, group
, challenge
, pass
);
322 force_memzero(challenge
, sizeof challenge
);
323 force_memzero(pass
, strlen(pass
));
325 if (auth_uid_groups
) {
327 for (j
= 0; j
< auth_uid_groups_cnt
; j
++) {
328 if (auth_uid_groups
[j
])
329 free((char*)auth_uid_groups
[j
]);
331 free(auth_uid_groups
);
335 rprintf(FLOG
, "auth failed on module %s from %s (%s) for %s: %s\n",
336 lp_name(module
), host
, addr
, line
, err
);
342 else if (opt_ch
== 'w')
348 void auth_client(int fd
, const char *user
, const char *challenge
)
351 char pass2
[MAX_DIGEST_LEN
*2];
355 negotiate_daemon_auth(-1, 1);
357 if (!(pass
= getpassf(password_file
))
358 && !(pass
= getenv("RSYNC_PASSWORD"))) {
359 /* XXX: cyeoh says that getpass is deprecated, because
360 * it may return a truncated password on some systems,
361 * and it is not in the LSB.
363 * Andrew Klein says that getpassphrase() is present
364 * on Solaris and reads up to 256 characters.
366 * OpenBSD has a readpassphrase() that might be more suitable.
368 pass
= getpass("Password: ");
374 generate_hash(pass
, challenge
, pass2
);
375 io_printf(fd
, "%s %s\n", user
, pass2
);