Release notes for 1.9.1
[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         uint8_t pwhash[SCRYPT_OUTPUT_SIZE];
22         uint8_t salt[SCRYPT_SALT_SIZE];
23         int fd, n, r;
24
25         if (!valid_name(username) || !blerg_exists(username))
26                 return 0;
27
28         n = strlen(password);
29         if (n > MAX_PASSWORD_LENGTH)
30                 return 0;
31
32         /* Gather some salt */
33         fd = open("/dev/urandom", O_RDONLY);
34         if (fd == -1) {
35                 perror("Could not open /dev/urandom\n");
36                 return 0;
37         }
38         read(fd, salt, SCRYPT_SALT_SIZE);
39         close(fd);
40
41         r = crypto_scrypt((const uint8_t *)password, n, salt, SCRYPT_SALT_SIZE, SCRYPT_N, SCRYPT_r, SCRYPT_p, pwhash, SCRYPT_OUTPUT_SIZE);
42         if (r != 0) {
43                 fprintf(stderr, "Failure in scrypt for %s\n", username);
44                 return 0;
45         }
46
47         /* Write the password */
48         snprintf(filename, FILENAME_MAX, "%s/%s/password", blergconf.data_path, username);
49         fd = open(filename, O_WRONLY | O_CREAT, 0600);
50         write(fd, pwhash, SCRYPT_OUTPUT_SIZE);
51         close(fd);
52
53         /* Write the salt */
54         snprintf(filename, FILENAME_MAX, "%s/%s/password_salt", blergconf.data_path, username);
55         fd = open(filename, O_WRONLY | O_CREAT, 0600);
56         write(fd, salt, SCRYPT_SALT_SIZE);
57         close(fd);
58
59         /* Mark this as a version 1 password */
60         snprintf(filename, FILENAME_MAX, "%s/%s/password_version", blergconf.data_path, username);
61         fd = open(filename, O_WRONLY | O_CREAT, 0600);
62         write(fd, "1\n", 2);
63         close(fd);
64         
65         return 1;
66 }
67
68 int auth_get_password_version(const char *username) {
69         char filename[FILENAME_MAX];
70         int fd;
71         char str[4];
72         int len = 0;
73
74         snprintf(filename, FILENAME_MAX, "%s/%s/password_version", blergconf.data_path, username);
75         if (access(filename, F_OK) != 0) {
76                 return 0;
77         }
78
79         fd = open(filename, O_RDONLY);
80         if (fd == -1)
81                 return -1;
82         len = read(fd, str, 4);
83         close(fd);
84
85         if (len < -1) {
86                 perror("auth_get_password_version");
87                 return -1;
88         }
89
90         str[len] = 0;
91
92         /* strtol returns zero if there isn't a number */
93         return strtol(str, NULL, 10);
94 }
95
96 int auth_get_password(const char *username, char *password) {
97         char filename[FILENAME_MAX];
98         int fd;
99         int read_size;
100         int len = 0;
101
102         if (!valid_name(username))
103                 return 0;
104
105         switch(auth_get_password_version(username)) {
106         case 0:
107                 read_size = MD5_DIGEST_SIZE;
108                 break;
109         case 1:
110                 read_size = SCRYPT_OUTPUT_SIZE;
111                 break;
112         default:
113                 return 0;
114         }
115
116         snprintf(filename, FILENAME_MAX, "%s/%s/password", blergconf.data_path, username);
117         fd = open(filename, O_RDONLY);
118         if (fd == -1)
119                 return 0;
120         len = read(fd, password, read_size);
121         close(fd);
122
123         if (len < 0) {
124                 perror("auth_get_password");
125                 return 0;
126         } else if (len < read_size) {
127                 fprintf(stderr, "Short read getting password\n");
128                 return 0;
129         }
130
131         password[len] = 0;
132
133         return 1;
134 }
135
136 int auth_get_salt(const char *username, uint8_t *salt) {
137         char filename[FILENAME_MAX];
138         int fd;
139         int len = 0;
140
141         if (!valid_name(username))
142                 return 0;
143
144         snprintf(filename, FILENAME_MAX, "%s/%s/password_salt", blergconf.data_path, username);
145         fd = open(filename, O_RDONLY);
146         if (fd == -1)
147                 return 0;
148         len = read(fd, salt, SCRYPT_SALT_SIZE);
149         close(fd);
150
151         if (len < 0) {
152                 perror("auth_get_salt");
153                 return 0;
154         } else if (len < SCRYPT_SALT_SIZE) {
155                 fprintf(stderr, "Short read getting salt\n");
156                 return 0;
157         }
158
159         return 1;
160 }
161
162 int auth_check_password_v0(const char *username, const char *password) {
163         char epw[MD5_DIGEST_SIZE + 1];
164         char givenpw[MD5_DIGEST_SIZE];
165         struct MD5Context ctx;
166
167         if (auth_get_password(username, epw) == 0)
168                 return 0;
169
170         MD5Init(&ctx);
171         MD5Update(&ctx, username, strlen(username));
172         MD5Update(&ctx, password, strlen(password));
173         MD5Final((unsigned char *)givenpw, &ctx);
174
175         if (strncmp(givenpw, epw, MD5_DIGEST_SIZE) == 0)
176                 return 1;
177         else
178                 return 0;
179 }
180
181 int auth_check_password_v1(const char *username, const char *password) {
182         unsigned char epw[SCRYPT_OUTPUT_SIZE];
183         uint8_t esalt[SCRYPT_SALT_SIZE];
184         unsigned char givenpw[SCRYPT_OUTPUT_SIZE];
185         int r;
186
187         if (auth_get_password(username, (char *)epw) == 0)
188                 return 0;
189
190         if (auth_get_salt(username, esalt) == 0)
191                 return 0;
192
193         r = crypto_scrypt((const uint8_t *)password, strlen(password), esalt, SCRYPT_SALT_SIZE, SCRYPT_N, SCRYPT_r, SCRYPT_p, givenpw, SCRYPT_OUTPUT_SIZE);
194         if (r != 0) {
195                 fprintf(stderr, "Failure in scrypt for %s\n", username);
196                 return 0;
197         }
198
199         if (memcmp(givenpw, epw, SCRYPT_OUTPUT_SIZE) == 0)
200                 return 1;
201         else
202                 return 0;
203 }
204
205 int auth_check_password(const char *username, const char *password) {
206         int version = auth_get_password_version(username);
207
208         switch(version) {
209         case 0:
210                 if (auth_check_password_v0(username, password)) {
211                         /* Refresh to the newest version */
212                         auth_set_password(username, password);
213                         return 1;
214                 } else {
215                         return 0;
216                 }
217                 break;
218         case 1:
219                 return auth_check_password_v1(username, password);
220         }
221         fprintf(stderr, "auth_check_password fell through. Bad password version?\n");
222         return 0;
223 }
224
225 void hexify(char *dst, char *src, int len) {
226         static char hex[] = "0123456789abcdef";
227         int i;
228
229         for (i = 0; i < len; i++) {
230                 dst[i * 2]     = hex[(src[i] & 0xF0) >> 4];
231                 dst[i * 2 + 1] = hex[src[i] & 0xF];
232         }
233 }
234
235 char *create_random_token() {
236         char buf[TOKEN_SIZE];
237         char *token;
238         int rand_fd;
239
240         rand_fd = open("/dev/urandom", O_RDONLY);
241         if (rand_fd == -1) {
242                 perror("Could not open /dev/urandom\n");
243                 return 0;
244         }
245         read(rand_fd, buf, TOKEN_SIZE);
246         close(rand_fd);
247
248         token = malloc(TOKEN_SIZE * 2 + 1);
249         hexify(token, buf, TOKEN_SIZE);
250         token[TOKEN_SIZE * 2] = 0;
251
252         return token;
253 }
254
255 char * auth_login(const char *username, const char *password) {
256         char filename[FILENAME_MAX];
257         int token_fd;
258
259         if (!auth_check_password(username, password))
260                 return NULL;
261
262         char *token = create_random_token();
263
264         snprintf(filename, FILENAME_MAX, "%s/%s/tokens", blergconf.data_path, username);
265         if (access(filename, F_OK) != 0) {
266                 if (mkdir(filename, 0700) == -1) {
267                         perror("Could not create auth token dir");
268                         return NULL;
269                 }
270         }
271
272         snprintf(filename, FILENAME_MAX, "%s/%s/tokens/%s", blergconf.data_path, username, token);
273         token_fd = open(filename, O_WRONLY | O_CREAT, 0600);
274         if (token_fd == -1) {
275                 perror("Could not open token");
276                 return NULL;
277         }
278         close(token_fd);
279
280         return token;
281 }
282
283 int auth_logout(const char *username, const char *token) {
284         char filename[FILENAME_MAX];
285
286         if (!valid_name(username))
287                 return 0;
288
289         snprintf(filename, FILENAME_MAX, "%s/%s/tokens", blergconf.data_path, username);
290         if (access(filename, F_OK) != 0) {
291                 return 0;
292         }
293
294         snprintf(filename, FILENAME_MAX, "%s/%s/tokens/%s", blergconf.data_path, username, token);
295         if (unlink(filename) == -1)
296                 return 0;
297
298         return 1;
299 }
300
301 int auth_check_token(const char *username, const char *given_token) {
302         char filename[FILENAME_MAX];
303
304         snprintf(filename, FILENAME_MAX, "%s/%s/tokens/%s", blergconf.data_path, username, given_token);
305
306         return (access(filename, F_OK) == 0);
307 }