When rsync encountered an empty file list, it behaved differently
[rsync.git] / authenticate.c
1 /* -*- c-file-style: "linux"; -*-
2
3    Copyright (C) 1998-2000 by Andrew Tridgell
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 /* support rsync authentication */
21 #include "rsync.h"
22
23 extern char *password_file;
24 extern int am_root;
25
26 /***************************************************************************
27 encode a buffer using base64 - simple and slow algorithm. null terminates
28 the result.
29   ***************************************************************************/
30 void base64_encode(char *buf, int len, char *out)
31 {
32         char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
33         int bit_offset, byte_offset, idx, i;
34         unsigned char *d = (unsigned char *)buf;
35         int bytes = (len*8 + 5)/6;
36
37         memset(out, 0, bytes+1);
38
39         for (i = 0; i < bytes; i++) {
40                 byte_offset = (i*6)/8;
41                 bit_offset = (i*6)%8;
42                 if (bit_offset < 3) {
43                         idx = (d[byte_offset] >> (2-bit_offset)) & 0x3F;
44                 } else {
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)));
48                         }
49                 }
50                 out[i] = b64[idx];
51         }
52 }
53
54 /* Generate a challenge buffer and return it base64-encoded. */
55 static void gen_challenge(char *addr, char *challenge)
56 {
57         char input[32];
58         char md4_out[MD4_SUM_LENGTH];
59         struct timeval tv;
60
61         memset(input, 0, sizeof input);
62
63         strlcpy((char *)input, addr, 17);
64         sys_gettimeofday(&tv);
65         SIVAL(input, 16, tv.tv_sec);
66         SIVAL(input, 20, tv.tv_usec);
67         SIVAL(input, 24, getpid());
68
69         sum_init(0);
70         sum_update(input, sizeof input);
71         sum_end(md4_out);
72
73         base64_encode(md4_out, MD4_SUM_LENGTH, challenge);
74 }
75
76
77 /* Return the secret for a user from the secret file, null terminated.
78  * Maximum length is len (not counting the null). */
79 static int get_secret(int module, char *user, char *secret, int len)
80 {
81         char *fname = lp_secrets_file(module);
82         STRUCT_STAT st;
83         int fd, ok = 1;
84         char ch, *p;
85
86         if (!fname || !*fname)
87                 return 0;
88
89         if ((fd = open(fname, O_RDONLY)) < 0)
90                 return 0;
91
92         if (do_stat(fname, &st) == -1) {
93                 rsyserr(FLOG, errno, "stat(%s)", safe_fname(fname));
94                 ok = 0;
95         } else if (lp_strict_modes(module)) {
96                 if ((st.st_mode & 06) != 0) {
97                         rprintf(FLOG, "secrets file must not be other-accessible (see strict modes option)\n");
98                         ok = 0;
99                 } else if (am_root && (st.st_uid != 0)) {
100                         rprintf(FLOG, "secrets file must be owned by root when running as root (see strict modes)\n");
101                         ok = 0;
102                 }
103         }
104         if (!ok) {
105                 rprintf(FLOG, "continuing without secrets file\n");
106                 close(fd);
107                 return 0;
108         }
109
110         if (*user == '#') {
111                 /* Reject attempt to match a comment. */
112                 close(fd);
113                 return 0;
114         }
115
116         /* Try to find a line that starts with the user name and a ':'. */
117         p = user;
118         while (1) {
119                 if (read(fd, &ch, 1) != 1) {
120                         close(fd);
121                         return 0;
122                 }
123                 if (ch == '\n')
124                         p = user;
125                 else if (p) {
126                         if (*p == ch)
127                                 p++;
128                         else if (!*p && ch == ':')
129                                 break;
130                         else
131                                 p = NULL;
132                 }
133         }
134
135         /* Slurp the secret into the "secret" buffer. */
136         p = secret;
137         while (len > 0) {
138                 if (read(fd, p, 1) != 1 || *p == '\n')
139                         break;
140                 if (*p == '\r')
141                         continue;
142                 p++;
143                 len--;
144         }
145         *p = '\0';
146         close(fd);
147
148         return 1;
149 }
150
151 static char *getpassf(char *filename)
152 {
153         STRUCT_STAT st;
154         char buffer[512], *p;
155         int fd, n, ok = 1;
156         char *envpw = getenv("RSYNC_PASSWORD");
157
158         if (!filename)
159                 return NULL;
160
161         if ((fd = open(filename,O_RDONLY)) < 0) {
162                 rsyserr(FERROR, errno, "could not open password file \"%s\"",
163                         safe_fname(filename));
164                 if (envpw)
165                         rprintf(FERROR, "falling back to RSYNC_PASSWORD environment variable.\n");
166                 return NULL;
167         }
168
169         if (do_stat(filename, &st) == -1) {
170                 rsyserr(FERROR, errno, "stat(%s)", safe_fname(filename));
171                 ok = 0;
172         } else if ((st.st_mode & 06) != 0) {
173                 rprintf(FERROR,"password file must not be other-accessible\n");
174                 ok = 0;
175         } else if (am_root && st.st_uid != 0) {
176                 rprintf(FERROR,"password file must be owned by root when running as root\n");
177                 ok = 0;
178         }
179         if (!ok) {
180                 rprintf(FERROR,"continuing without password file\n");
181                 if (envpw)
182                         rprintf(FERROR, "using RSYNC_PASSWORD environment variable.\n");
183                 close(fd);
184                 return NULL;
185         }
186
187         if (envpw)
188                 rprintf(FERROR, "RSYNC_PASSWORD environment variable ignored\n");
189
190         n = read(fd, buffer, sizeof buffer - 1);
191         close(fd);
192         if (n > 0) {
193                 buffer[n] = '\0';
194                 if ((p = strtok(buffer, "\n\r")) != NULL)
195                         return strdup(p);
196         }
197
198         return NULL;
199 }
200
201 /* Generate an MD4 hash created from the combination of the password
202  * and the challenge string and return it base64-encoded. */
203 static void generate_hash(char *in, char *challenge, char *out)
204 {
205         char buf[MD4_SUM_LENGTH];
206
207         sum_init(0);
208         sum_update(in, strlen(in));
209         sum_update(challenge, strlen(challenge));
210         sum_end(buf);
211
212         base64_encode(buf, MD4_SUM_LENGTH, out);
213 }
214
215 /* Possibly negotiate authentication with the client.  Use "leader" to
216  * start off the auth if necessary.
217  *
218  * Return NULL if authentication failed.  Return "" if anonymous access.
219  * Otherwise return username.
220  */
221 char *auth_server(int f_in, int f_out, int module, char *host, char *addr,
222                   char *leader)
223 {
224         char *users = lp_auth_users(module);
225         char challenge[MD4_SUM_LENGTH*2];
226         char line[MAXPATHLEN];
227         char secret[512];
228         char pass2[MD4_SUM_LENGTH*2];
229         char *tok, *pass;
230
231         /* if no auth list then allow anyone in! */
232         if (!users || !*users)
233                 return "";
234
235         gen_challenge(addr, challenge);
236
237         io_printf(f_out, "%s%s\n", leader, challenge);
238
239         if (!read_line(f_in, line, sizeof line - 1)
240          || (pass = strchr(line, ' ')) == NULL) {
241                 rprintf(FLOG, "auth failed on module %s from %s (%s): "
242                         "invalid challenge response\n",
243                         lp_name(module), host, addr);
244                 return NULL;
245         }
246         *pass++ = '\0';
247
248         if (!(users = strdup(users)))
249                 out_of_memory("auth_server");
250
251         for (tok = strtok(users, " ,\t"); tok; tok = strtok(NULL, " ,\t")) {
252                 if (wildmatch(tok, line))
253                         break;
254         }
255         free(users);
256
257         if (!tok) {
258                 rprintf(FLOG, "auth failed on module %s from %s (%s): "
259                         "unauthorized user\n",
260                         lp_name(module), host, addr);
261                 return NULL;
262         }
263
264         memset(secret, 0, sizeof secret);
265         if (!get_secret(module, line, secret, sizeof secret - 1)) {
266                 memset(secret, 0, sizeof secret);
267                 rprintf(FLOG, "auth failed on module %s from %s (%s): "
268                         "missing secret for user \"%s\"\n",
269                         lp_name(module), host, addr, line);
270                 return NULL;
271         }
272
273         generate_hash(secret, challenge, pass2);
274         memset(secret, 0, sizeof secret);
275
276         if (strcmp(pass, pass2) != 0) {
277                 rprintf(FLOG, "auth failed on module %s from %s (%s): "
278                         "password mismatch\n",
279                         lp_name(module), host, addr);
280                 return NULL;
281         }
282
283         return strdup(line);
284 }
285
286
287 void auth_client(int fd, char *user, char *challenge)
288 {
289         char *pass;
290         char pass2[MD4_SUM_LENGTH*2];
291
292         if (!user || !*user)
293                 user = "nobody";
294
295         if (!(pass = getpassf(password_file))
296          && !(pass = getenv("RSYNC_PASSWORD"))) {
297                 /* XXX: cyeoh says that getpass is deprecated, because
298                  * it may return a truncated password on some systems,
299                  * and it is not in the LSB.
300                  *
301                  * Andrew Klein says that getpassphrase() is present
302                  * on Solaris and reads up to 256 characters.
303                  *
304                  * OpenBSD has a readpassphrase() that might be more suitable.
305                  */
306                 pass = getpass("Password: ");
307         }
308
309         if (!pass)
310                 pass = "";
311
312         generate_hash(pass, challenge, pass2);
313         io_printf(fd, "%s %s\n", user, pass2);
314 }