Update password storage to a more compact form
[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/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7 #include <string.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <crypto_scrypt.h>
12 #include "config.h"
13 #include "configuration.h"
14 #include "database.h"
15 #include "auth.h"
16 #include "util.h"
17 #include "md5.h"
18
19 int auth_set_password(const char *username, const char *password) {
20         char filename[FILENAME_MAX];
21         struct auth_v2 auth;
22         int fd, n, r;
23
24         if (!valid_name(username) || !blerg_exists(username))
25                 return 0;
26
27         n = strlen(password);
28         if (n > MAX_PASSWORD_LENGTH)
29                 return 0;
30
31         /* Clear the auth structure */
32         memset(&auth, 0, sizeof(struct auth_v2));
33
34         /* Set the auth version */
35         auth.header.version = 2;
36
37         /* Gather some salt */
38         fd = open("/dev/urandom", O_RDONLY);
39         if (fd == -1) {
40                 perror("Could not open /dev/urandom");
41                 return 0;
42         }
43         read(fd, auth.salt, SCRYPT_SALT_SIZE);
44         close(fd);
45
46         r = crypto_scrypt((const uint8_t *)password, n, auth.salt, SCRYPT_SALT_SIZE, SCRYPT_N, SCRYPT_r, SCRYPT_p, auth.password, SCRYPT_OUTPUT_SIZE);
47         if (r != 0) {
48                 fprintf(stderr, "Failure in scrypt for %s\n", username);
49                 return 0;
50         }
51
52         /* Write the data */
53         snprintf(filename, FILENAME_MAX, "%s/%s/auth", blergconf.data_path, username);
54         fd = open(filename, O_WRONLY | O_CREAT, 0600);
55         flock(fd, LOCK_EX);
56         write(fd, &auth, sizeof(struct auth_v2));
57         flock(fd, LOCK_UN);
58         close(fd);
59
60         return 1;
61 }
62
63 int auth_get_password_version(const char *username) {
64         char filename[FILENAME_MAX];
65         int fd;
66         char str[4];
67         struct auth_header ah;
68         int len = 0;
69
70         snprintf(filename, FILENAME_MAX, "%s/%s/auth", blergconf.data_path, username);
71         if (access(filename, F_OK) == 0) {
72                 fd = open(filename, O_RDONLY);
73                 if (fd == -1)
74                         return -1;
75                 len = read(fd, &ah, sizeof(struct auth_header));
76                 close(fd);
77
78                 if (len < 0) {
79                         perror("reading auth file");
80                         return -1;
81                 } else if (len < sizeof(struct auth_header)) {
82                         fprintf(stderr, "Short read on while determining auth version for %s", username);
83                         return -1;
84                 }
85
86                 return ah.version;
87         }
88
89         snprintf(filename, FILENAME_MAX, "%s/%s/password_version", blergconf.data_path, username);
90         if (access(filename, F_OK) != 0) {
91                 return 0;
92         }
93
94         fd = open(filename, O_RDONLY);
95         if (fd == -1)
96                 return -1;
97         len = read(fd, str, 4);
98         close(fd);
99
100         if (len < 0) {
101                 perror("auth_get_password_version");
102                 return -1;
103         }
104
105         str[len] = 0;
106
107         /* strtol returns zero if there isn't a number */
108         return strtol(str, NULL, 10);
109 }
110
111 int auth_get_password(const char *username, char *password) {
112         char filename[FILENAME_MAX];
113         int fd;
114         int read_size;
115         int len = 0;
116
117         if (!valid_name(username))
118                 return 0;
119
120         switch(auth_get_password_version(username)) {
121         case 0:
122                 read_size = MD5_DIGEST_SIZE;
123                 break;
124         case 1:
125                 read_size = SCRYPT_OUTPUT_SIZE;
126                 break;
127         default:
128                 return 0;
129         }
130
131         snprintf(filename, FILENAME_MAX, "%s/%s/password", blergconf.data_path, username);
132         fd = open(filename, O_RDONLY);
133         if (fd == -1)
134                 return 0;
135         len = read(fd, password, read_size);
136         close(fd);
137
138         if (len < 0) {
139                 perror("auth_get_password");
140                 return 0;
141         } else if (len < read_size) {
142                 fprintf(stderr, "Short read getting password\n");
143                 return 0;
144         }
145
146         password[len] = 0;
147
148         return 1;
149 }
150
151 int auth_get_salt(const char *username, uint8_t *salt) {
152         char filename[FILENAME_MAX];
153         int fd;
154         int len = 0;
155
156         if (!valid_name(username))
157                 return 0;
158
159         snprintf(filename, FILENAME_MAX, "%s/%s/password_salt", blergconf.data_path, username);
160         fd = open(filename, O_RDONLY);
161         if (fd == -1)
162                 return 0;
163         len = read(fd, salt, SCRYPT_SALT_SIZE);
164         close(fd);
165
166         if (len < 0) {
167                 perror("auth_get_salt");
168                 return 0;
169         } else if (len < SCRYPT_SALT_SIZE) {
170                 fprintf(stderr, "Short read getting salt\n");
171                 return 0;
172         }
173
174         return 1;
175 }
176
177 int auth_check_password_v0(const char *username, const char *password) {
178         char epw[MD5_DIGEST_SIZE + 1];
179         char givenpw[MD5_DIGEST_SIZE];
180         struct MD5Context ctx;
181
182         if (auth_get_password(username, epw) == 0)
183                 return 0;
184
185         MD5Init(&ctx);
186         MD5Update(&ctx, username, strlen(username));
187         MD5Update(&ctx, password, strlen(password));
188         MD5Final((unsigned char *)givenpw, &ctx);
189
190         if (strncmp(givenpw, epw, MD5_DIGEST_SIZE) == 0)
191                 return 1;
192         else
193                 return 0;
194 }
195
196 int auth_get_data(const char *username, void *data, size_t data_len) {
197         char filename[FILENAME_MAX];
198         int fd;
199         int len = 0;
200
201         if (!valid_name(username))
202                 return 0;
203
204         snprintf(filename, FILENAME_MAX, "%s/%s/auth", blergconf.data_path, username);
205         fd = open(filename, O_RDONLY);
206         if (fd == -1)
207                 return 0;
208         flock(fd, LOCK_SH);
209         len = read(fd, data, data_len);
210         flock(fd, LOCK_UN);
211         close(fd);
212
213         if (len < 0) {
214                 perror("auth_get_data");
215                 return 0;
216         } else if (len < data_len) {
217                 fprintf(stderr, "Short read getting auth data\n");
218                 return 0;
219         }
220
221         return 1;
222 }
223
224 int auth_check_scrypt(struct auth_v2 *auth, const char *username, const char *password) {
225         unsigned char givenpw[SCRYPT_OUTPUT_SIZE];
226         int r;
227
228         r = crypto_scrypt(password, strlen(password), auth->salt, SCRYPT_SALT_SIZE, SCRYPT_N, SCRYPT_r, SCRYPT_p, givenpw, SCRYPT_OUTPUT_SIZE);
229         if (r != 0) {
230                 fprintf(stderr, "Failure in scrypt for %s\n", username);
231                 return 0;
232         }
233
234         if (memcmp(givenpw, auth->password, SCRYPT_OUTPUT_SIZE) == 0)
235                 return 1;
236         else
237                 return 0;
238 }
239
240 int auth_check_password_v1(const char *username, const char *password) {
241         struct auth_v2 auth;
242         int r;
243
244         if (auth_get_password(username, (char *)auth.password) == 0)
245                 return 0;
246
247         if (auth_get_salt(username, auth.salt) == 0)
248                 return 0;
249
250         return auth_check_scrypt(&auth, username, password);
251 }
252
253 int auth_check_password_v2(const char *username, const char *password) {
254         struct auth_v2 auth;
255         int r;
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         int token_fd;
325
326         if (!auth_check_password(username, password))
327                 return NULL;
328
329         char *token = create_random_token();
330
331         snprintf(filename, FILENAME_MAX, "%s/%s/tokens", blergconf.data_path, username);
332         if (access(filename, F_OK) != 0) {
333                 if (mkdir(filename, 0700) == -1) {
334                         perror("Could not create auth token dir");
335                         return NULL;
336                 }
337         }
338
339         snprintf(filename, FILENAME_MAX, "%s/%s/tokens/%s", blergconf.data_path, username, token);
340         token_fd = open(filename, O_WRONLY | O_CREAT, 0600);
341         if (token_fd == -1) {
342                 perror("Could not open token");
343                 return NULL;
344         }
345         close(token_fd);
346
347         return token;
348 }
349
350 int auth_logout(const char *username, const char *token) {
351         char filename[FILENAME_MAX];
352
353         if (!valid_name(username))
354                 return 0;
355
356         snprintf(filename, FILENAME_MAX, "%s/%s/tokens", blergconf.data_path, username);
357         if (access(filename, F_OK) != 0) {
358                 return 0;
359         }
360
361         snprintf(filename, FILENAME_MAX, "%s/%s/tokens/%s", blergconf.data_path, username, token);
362         if (unlink(filename) == -1)
363                 return 0;
364
365         return 1;
366 }
367
368 int auth_check_token(const char *username, const char *given_token) {
369         char filename[FILENAME_MAX];
370
371         snprintf(filename, FILENAME_MAX, "%s/%s/tokens/%s", blergconf.data_path, username, given_token);
372
373         return (access(filename, F_OK) == 0);
374 }