Make daily-digest work for more users than just me
[blerg.git] / common / auth.c
1 /* Blerg is (C) 2011 The Dominion of Awesome, and is distributed under a
2  * BSD-style license.  Please see the COPYING file for details.
3  */
4 #include <sys/file.h>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <fcntl.h>
8 #include <string.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <crypto_scrypt.h>
13 #include "config.h"
14 #include "configuration.h"
15 #include "database.h"
16 #include "auth.h"
17 #include "util.h"
18 #include "stringring.h"
19 #include "md5.h"
20
21 int auth_set_password(const char *username, const char *password) {
22         char filename[FILENAME_MAX];
23         struct auth_v2 auth;
24         int fd, n, r;
25
26         if (!valid_name(username) || !blerg_exists(username))
27                 return 0;
28
29         n = strlen(password);
30         if (n > MAX_PASSWORD_LENGTH)
31                 return 0;
32
33         /* Clear the auth structure */
34         memset(&auth, 0, sizeof(struct auth_v2));
35
36         /* Set the auth version */
37         auth.header.version = 2;
38
39         /* Gather some salt */
40         fd = open("/dev/urandom", O_RDONLY);
41         if (fd == -1) {
42                 perror("Could not open /dev/urandom");
43                 return 0;
44         }
45         read(fd, auth.salt, SCRYPT_SALT_SIZE);
46         close(fd);
47
48         r = crypto_scrypt((const uint8_t *)password, n, auth.salt, SCRYPT_SALT_SIZE, SCRYPT_N, SCRYPT_r, SCRYPT_p, auth.password, SCRYPT_OUTPUT_SIZE);
49         if (r != 0) {
50                 fprintf(stderr, "Failure in scrypt for %s\n", username);
51                 return 0;
52         }
53
54         /* Write the data */
55         snprintf(filename, FILENAME_MAX, "%s/%s/auth", blergconf.data_path, username);
56         fd = open(filename, O_WRONLY | O_CREAT, 0600);
57         flock(fd, LOCK_EX);
58         write(fd, &auth, sizeof(struct auth_v2));
59         flock(fd, LOCK_UN);
60         close(fd);
61
62         return 1;
63 }
64
65 int auth_get_password_version(const char *username) {
66         char filename[FILENAME_MAX];
67         int fd;
68         char str[4];
69         struct auth_header ah;
70         int len = 0;
71
72         snprintf(filename, FILENAME_MAX, "%s/%s/auth", blergconf.data_path, username);
73         if (access(filename, F_OK) == 0) {
74                 fd = open(filename, O_RDONLY);
75                 if (fd == -1)
76                         return -1;
77                 len = read(fd, &ah, sizeof(struct auth_header));
78                 close(fd);
79
80                 if (len < 0) {
81                         perror("reading auth file");
82                         return -1;
83                 } else if (len < sizeof(struct auth_header)) {
84                         fprintf(stderr, "Short read on while determining auth version for %s", username);
85                         return -1;
86                 }
87
88                 return ah.version;
89         }
90
91         snprintf(filename, FILENAME_MAX, "%s/%s/password_version", blergconf.data_path, username);
92         if (access(filename, F_OK) != 0) {
93                 return 0;
94         }
95
96         fd = open(filename, O_RDONLY);
97         if (fd == -1)
98                 return -1;
99         len = read(fd, str, 4);
100         close(fd);
101
102         if (len < 0) {
103                 perror("auth_get_password_version");
104                 return -1;
105         }
106
107         str[len] = 0;
108
109         /* strtol returns zero if there isn't a number */
110         return strtol(str, NULL, 10);
111 }
112
113 int auth_get_password(const char *username, char *password) {
114         char filename[FILENAME_MAX];
115         int fd;
116         int read_size;
117         int len = 0;
118
119         if (!valid_name(username))
120                 return 0;
121
122         switch(auth_get_password_version(username)) {
123         case 0:
124                 read_size = MD5_DIGEST_SIZE;
125                 break;
126         case 1:
127                 read_size = SCRYPT_OUTPUT_SIZE;
128                 break;
129         default:
130                 return 0;
131         }
132
133         snprintf(filename, FILENAME_MAX, "%s/%s/password", blergconf.data_path, username);
134         fd = open(filename, O_RDONLY);
135         if (fd == -1)
136                 return 0;
137         len = read(fd, password, read_size);
138         close(fd);
139
140         if (len < 0) {
141                 perror("auth_get_password");
142                 return 0;
143         } else if (len < read_size) {
144                 fprintf(stderr, "Short read getting password\n");
145                 return 0;
146         }
147
148         password[len] = 0;
149
150         return 1;
151 }
152
153 int auth_get_salt(const char *username, uint8_t *salt) {
154         char filename[FILENAME_MAX];
155         int fd;
156         int len = 0;
157
158         if (!valid_name(username))
159                 return 0;
160
161         snprintf(filename, FILENAME_MAX, "%s/%s/password_salt", blergconf.data_path, username);
162         fd = open(filename, O_RDONLY);
163         if (fd == -1)
164                 return 0;
165         len = read(fd, salt, SCRYPT_SALT_SIZE);
166         close(fd);
167
168         if (len < 0) {
169                 perror("auth_get_salt");
170                 return 0;
171         } else if (len < SCRYPT_SALT_SIZE) {
172                 fprintf(stderr, "Short read getting salt\n");
173                 return 0;
174         }
175
176         return 1;
177 }
178
179 int auth_check_password_v0(const char *username, const char *password) {
180         char epw[MD5_DIGEST_SIZE + 1];
181         char givenpw[MD5_DIGEST_SIZE];
182         struct MD5Context ctx;
183
184         if (auth_get_password(username, epw) == 0)
185                 return 0;
186
187         MD5Init(&ctx);
188         MD5Update(&ctx, username, strlen(username));
189         MD5Update(&ctx, password, strlen(password));
190         MD5Final((unsigned char *)givenpw, &ctx);
191
192         if (strncmp(givenpw, epw, MD5_DIGEST_SIZE) == 0)
193                 return 1;
194         else
195                 return 0;
196 }
197
198 int auth_get_data(const char *username, void *data, size_t data_len) {
199         char filename[FILENAME_MAX];
200         int fd;
201         int len = 0;
202
203         if (!valid_name(username))
204                 return 0;
205
206         snprintf(filename, FILENAME_MAX, "%s/%s/auth", blergconf.data_path, username);
207         fd = open(filename, O_RDONLY);
208         if (fd == -1)
209                 return 0;
210         flock(fd, LOCK_SH);
211         len = read(fd, data, data_len);
212         flock(fd, LOCK_UN);
213         close(fd);
214
215         if (len < 0) {
216                 perror("auth_get_data");
217                 return 0;
218         } else if (len < data_len) {
219                 fprintf(stderr, "Short read getting auth data\n");
220                 return 0;
221         }
222
223         return 1;
224 }
225
226 int auth_check_scrypt(struct auth_v2 *auth, const char *username, const char *password) {
227         unsigned char givenpw[SCRYPT_OUTPUT_SIZE];
228         int r;
229
230         r = crypto_scrypt((const uint8_t *)password, strlen(password), auth->salt, SCRYPT_SALT_SIZE, SCRYPT_N, SCRYPT_r, SCRYPT_p, givenpw, SCRYPT_OUTPUT_SIZE);
231         if (r != 0) {
232                 fprintf(stderr, "Failure in scrypt for %s\n", username);
233                 return 0;
234         }
235
236         if (memcmp(givenpw, auth->password, SCRYPT_OUTPUT_SIZE) == 0)
237                 return 1;
238         else
239                 return 0;
240 }
241
242 int auth_check_password_v1(const char *username, const char *password) {
243         struct auth_v2 auth;
244
245         if (auth_get_password(username, (char *)auth.password) == 0)
246                 return 0;
247
248         if (auth_get_salt(username, auth.salt) == 0)
249                 return 0;
250
251         return auth_check_scrypt(&auth, username, password);
252 }
253
254 int auth_check_password_v2(const char *username, const char *password) {
255         struct auth_v2 auth;
256
257         if (auth_get_data(username, (void *) &auth, sizeof(struct auth_v2)) == 0)
258                 return 0;
259
260         return auth_check_scrypt(&auth, username, password);
261 }
262
263 int auth_check_password(const char *username, const char *password) {
264         int version = auth_get_password_version(username);
265
266         switch(version) {
267         case 0:
268                 if (auth_check_password_v0(username, password)) {
269                         /* Refresh to the newest version */
270                         auth_set_password(username, password);
271                         return 1;
272                 } else {
273                         return 0;
274                 }
275                 break;
276         case 1:
277                 if (auth_check_password_v1(username, password)) {
278                         /* Refresh to the newest version */
279                         auth_set_password(username, password);
280                         return 1;
281                 } else {
282                         return 0;
283                 }
284                 break;
285         case 2:
286                 return auth_check_password_v2(username, password);
287         }
288         fprintf(stderr, "auth_check_password fell through. Bad password version?\n");
289         return 0;
290 }
291
292 void hexify(char *dst, char *src, int len) {
293         static char hex[] = "0123456789abcdef";
294         int i;
295
296         for (i = 0; i < len; i++) {
297                 dst[i * 2]     = hex[(src[i] & 0xF0) >> 4];
298                 dst[i * 2 + 1] = hex[src[i] & 0xF];
299         }
300 }
301
302 char *create_random_token() {
303         char buf[TOKEN_SIZE];
304         char *token;
305         int rand_fd;
306
307         rand_fd = open("/dev/urandom", O_RDONLY);
308         if (rand_fd == -1) {
309                 perror("Could not open /dev/urandom\n");
310                 return 0;
311         }
312         read(rand_fd, buf, TOKEN_SIZE);
313         close(rand_fd);
314
315         token = malloc(TOKEN_SIZE * 2 + 1);
316         hexify(token, buf, TOKEN_SIZE);
317         token[TOKEN_SIZE * 2] = 0;
318
319         return token;
320 }
321
322 char * auth_login(const char *username, const char *password) {
323         char filename[FILENAME_MAX];
324         struct stringring *sr;
325         char *token;
326
327
328         if (!auth_check_password(username, password))
329                 return NULL;
330
331         snprintf(filename, FILENAME_MAX, "%s/%s/tokens", blergconf.data_path, username);
332         sr = stringring_open(filename);
333         if (sr == NULL) {
334                 return NULL;
335         }
336         token = create_random_token();
337         if (!stringring_add(sr, token)) {
338                 free(token);
339                 stringring_close(sr);
340                 return NULL;
341         }
342         stringring_close(sr);
343
344         return token;
345 }
346
347 int auth_logout(const char *username, const char *token) {
348         char filename[FILENAME_MAX];
349         struct stringring *sr;
350         int ret;
351
352         if (!valid_name(username))
353                 return 0;
354
355         snprintf(filename, FILENAME_MAX, "%s/%s/tokens", blergconf.data_path, username);
356         if (access(filename, F_OK) != 0) {
357                 return 0;
358         }
359         sr = stringring_open(filename);
360         if (sr == NULL) {
361                 return 0;
362         }
363         ret = stringring_remove(sr, token);
364         stringring_close(sr);
365
366         return ret;
367 }
368
369 int auth_check_token(const char *username, const char *given_token) {
370         char filename[FILENAME_MAX];
371         struct stringring *sr;
372         int ret;
373
374         snprintf(filename, FILENAME_MAX, "%s/%s/tokens", blergconf.data_path, username);
375         if (access(filename, F_OK) != 0) {
376                 return 0;
377         }
378         sr = stringring_open(filename);
379         if (sr == NULL) {
380                 return 0;
381         }
382         ret = (stringring_find(sr, given_token, AUTHENTICATION_TIMEOUT) != -1);
383         if (ret == 1) {
384                 /* Update token timestamp */
385                 stringring_touch(sr, given_token);
386         }
387         stringring_close(sr);
388
389         return ret;
390 }
391
392 /* Return a 32-bit integer "counter" that will change when the password is
393  * updated.  Used to invalidate password recovery schemes after the password is
394  * updated.  Returns the counter in the "counter" argument, and returns
395  * true/false on success/failure. */
396 int auth_get_counter(const char *username, uint32_t *counter) {
397         struct auth_v2 auth;
398         struct MD5Context ctx;
399         uint8_t md5hash[MD5_DIGEST_SIZE];
400
401         if (auth_get_data(username, (void *) &auth, sizeof(struct auth_v2)) == 0)
402                 return 0;
403
404         /* There's probably going to be some question about using MD5 here.
405          * All I really need is to quickly and repeatably scramble some bits.
406          * MD5 can still do that. */
407         MD5Init(&ctx);
408         MD5Update(&ctx, auth.password, SCRYPT_OUTPUT_SIZE);
409         MD5Update(&ctx, auth.salt, SCRYPT_SALT_SIZE);
410         MD5Final((unsigned char *)md5hash, &ctx);
411
412         *counter = ((uint32_t *)md5hash)[0];
413
414         return 1;
415 }