Tweaked the sizeof syntax and some multi-statement lines.
[rsync/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 /* create a 16 byte challenge buffer */
55 static void gen_challenge(char *addr, char *challenge)
56 {
57         char input[32];
58         struct timeval tv;
59
60         memset(input, 0, sizeof input);
61
62         strlcpy((char *)input, addr, 17);
63         sys_gettimeofday(&tv);
64         SIVAL(input, 16, tv.tv_sec);
65         SIVAL(input, 20, tv.tv_usec);
66         SIVAL(input, 24, getpid());
67
68         sum_init();
69         sum_update(input, sizeof input);
70         sum_end(challenge);
71 }
72
73
74 /* Return the secret for a user from the secret file, null terminated.
75  * Maximum length is len (not counting the null). */
76 static int get_secret(int module, char *user, char *secret, int len)
77 {
78         char *fname = lp_secrets_file(module);
79         STRUCT_STAT st;
80         int fd, ok = 1;
81         char ch, *p;
82
83         if (!fname || !*fname)
84                 return 0;
85
86         if ((fd = open(fname, O_RDONLY)) < 0)
87                 return 0;
88
89         if (do_stat(fname, &st) == -1) {
90                 rsyserr(FERROR, errno, "stat(%s)", fname);
91                 ok = 0;
92         } else if (lp_strict_modes(module)) {
93                 if ((st.st_mode & 06) != 0) {
94                         rprintf(FERROR,"secrets file must not be other-accessible (see strict modes option)\n");
95                         ok = 0;
96                 } else if (am_root && (st.st_uid != 0)) {
97                         rprintf(FERROR,"secrets file must be owned by root when running as root (see strict modes)\n");
98                         ok = 0;
99                 }
100         }
101         if (!ok) {
102                 rprintf(FERROR,"continuing without secrets file\n");
103                 close(fd);
104                 return 0;
105         }
106
107         if (*user == '#') {
108                 /* Reject attempt to match a comment. */
109                 close(fd);
110                 return 0;
111         }
112
113         /* Try to find a line that starts with the user name and a ':'. */
114         p = user;
115         while (1) {
116                 if (read(fd, &ch, 1) != 1) {
117                         close(fd);
118                         return 0;
119                 }
120                 if (ch == '\n')
121                         p = user;
122                 else if (p) {
123                         if (*p == ch)
124                                 p++;
125                         else if (!*p && ch == ':')
126                                 break;
127                         else
128                                 p = NULL;
129                 }
130         }
131
132         /* Slurp the secret into the "secret" buffer. */
133         p = secret;
134         while (len > 0) {
135                 if (read(fd, p, 1) != 1 || *p == '\n')
136                         break;
137                 if (*p == '\r')
138                         continue;
139                 p++;
140                 len--;
141         }
142         *p = '\0';
143         close(fd);
144
145         return 1;
146 }
147
148 static char *getpassf(char *filename)
149 {
150         STRUCT_STAT st;
151         char buffer[512], *p;
152         int fd, n, ok = 1;
153         char *envpw = getenv("RSYNC_PASSWORD");
154
155         if (!filename)
156                 return NULL;
157
158         if ((fd = open(filename,O_RDONLY)) < 0) {
159                 rsyserr(FERROR, errno, "could not open password file \"%s\"",filename);
160                 if (envpw)
161                         rprintf(FERROR, "falling back to RSYNC_PASSWORD environment variable.\n");      
162                 return NULL;
163         }
164         
165         if (do_stat(filename, &st) == -1) {
166                 rsyserr(FERROR, errno, "stat(%s)", filename);
167                 ok = 0;
168         } else if ((st.st_mode & 06) != 0) {
169                 rprintf(FERROR,"password file must not be other-accessible\n");
170                 ok = 0;
171         } else if (am_root && st.st_uid != 0) {
172                 rprintf(FERROR,"password file must be owned by root when running as root\n");
173                 ok = 0;
174         }
175         if (!ok) {
176                 rprintf(FERROR,"continuing without password file\n");
177                 if (envpw)
178                         rprintf(FERROR, "using RSYNC_PASSWORD environment variable.\n");
179                 close(fd);
180                 return NULL;
181         }
182
183         if (envpw)
184                 rprintf(FERROR, "RSYNC_PASSWORD environment variable ignored\n");
185
186         n = read(fd, buffer, sizeof buffer - 1);
187         close(fd);
188         if (n > 0) {
189                 buffer[n] = '\0';
190                 if ((p = strtok(buffer, "\n\r")) != NULL)
191                         return strdup(p);
192         }       
193
194         return NULL;
195 }
196
197 /* generate a 16 byte hash from a password and challenge */
198 static void generate_hash(char *in, char *challenge, char *out)
199 {
200         char buf[16];
201
202         sum_init();
203         sum_update(in, strlen(in));
204         sum_update(challenge, strlen(challenge));
205         sum_end(buf);
206
207         base64_encode(buf, 16, out);
208 }
209
210 /* possible negotiate authentication with the client. Use "leader" to
211    start off the auth if necessary 
212
213    return NULL if authentication failed
214
215    return "" if anonymous access
216
217    otherwise return username
218 */
219 char *auth_server(int f_in, int f_out, int module, char *addr, char *leader)
220 {
221         char *users = lp_auth_users(module);
222         char challenge[16];
223         char b64_challenge[30];
224         char line[MAXPATHLEN];
225         static char user[100];
226         char secret[100];
227         char pass[30];
228         char pass2[30];
229         char *tok;
230
231         /* if no auth list then allow anyone in! */
232         if (!users || !*users)
233                 return "";
234
235         gen_challenge(addr, challenge);
236         
237         base64_encode(challenge, 16, b64_challenge);
238
239         io_printf(f_out, "%s%s\n", leader, b64_challenge);
240
241         if (!read_line(f_in, line, sizeof line - 1))
242                 return NULL;
243
244         memset(user, 0, sizeof user);
245         memset(pass, 0, sizeof pass);
246
247         if (sscanf(line,"%99s %29s", user, pass) != 2)
248                 return NULL;
249         
250         users = strdup(users);
251         if (!users)
252                 return NULL;
253
254         for (tok=strtok(users," ,\t"); tok; tok = strtok(NULL," ,\t")) {
255                 if (wildmatch(tok, user))
256                         break;
257         }
258         free(users);
259
260         if (!tok)
261                 return NULL;
262         
263         memset(secret, 0, sizeof secret);
264         if (!get_secret(module, user, secret, sizeof secret - 1)) {
265                 memset(secret, 0, sizeof secret);
266                 return NULL;
267         }
268
269         generate_hash(secret, b64_challenge, pass2);
270         memset(secret, 0, sizeof secret);
271         
272         if (strcmp(pass, pass2) == 0)
273                 return user;
274
275         return NULL;
276 }
277
278
279 void auth_client(int fd, char *user, char *challenge)
280 {
281         char *pass;
282         char pass2[30];
283
284         if (!user || !*user)
285                 user = "nobody";
286
287         if (!(pass = getpassf(password_file))
288          && !(pass = getenv("RSYNC_PASSWORD"))) {
289                 /* XXX: cyeoh says that getpass is deprecated, because
290                  * it may return a truncated password on some systems,
291                  * and it is not in the LSB.
292                  *
293                  * Andrew Klein says that getpassphrase() is present
294                  * on Solaris and reads up to 256 characters.
295                  *
296                  * OpenBSD has a readpassphrase() that might be more suitable.
297                  */
298                 pass = getpass("Password: ");
299         }
300
301         if (!pass)
302                 pass = "";
303
304         generate_hash(pass, challenge, pass2);
305         io_printf(fd, "%s %s\n", user, pass2);
306 }
307
308