Download
jcs
/subtext
/user.c
(View History)
jcs user: Add user_first_sysop_id() | Latest amendment: 562 on 2023-11-27 |
1 | /* |
2 | * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <ctype.h> |
18 | #include <string.h> |
19 | |
20 | #include "bile.h" |
21 | #include "db.h" |
22 | #include "sha2.h" |
23 | #include "subtext.h" |
24 | #include "user.h" |
25 | #include "util.h" |
26 | |
27 | static const char *BANNED_USERNAMES[] = { |
28 | "admin", "administrator", "\"admin", "admin1", |
29 | "client", "contact", |
30 | "daemon", "default", |
31 | "enable", |
32 | "fraud", |
33 | "guest", |
34 | "help", "hostmaster", |
35 | "mailer-daemon", |
36 | "moderator", "moderators", |
37 | "nobody", "notme", |
38 | "postmaster", |
39 | "root", "\"root", |
40 | "security", "server", "support", "sventek", "sysop", "system", |
41 | "test", |
42 | "ubnt", "unknown", |
43 | "webmaster", |
44 | }; |
45 | |
46 | void |
47 | user_cache_usernames(void) |
48 | { |
49 | struct user user; |
50 | struct username_cache *muser, *new_username_cache; |
51 | struct bile_object *o; |
52 | size_t new_nusers, nuser, len; |
53 | |
54 | new_nusers = bile_count_by_type(db->bile, DB_USER_RTYPE); |
55 | new_username_cache = xreallocarray(db->username_cache, |
56 | sizeof(struct username_cache), new_nusers); |
57 | if (new_username_cache == NULL) |
58 | return; |
59 | |
60 | db->nusers = new_nusers; |
61 | db->username_cache = new_username_cache; |
62 | |
63 | nuser = 0; |
64 | while ((o = bile_get_nth_of_type(db->bile, nuser, DB_USER_RTYPE))) { |
65 | muser = &db->username_cache[nuser]; |
66 | muser->id = o->id; |
67 | |
68 | len = bile_read(db->bile, DB_USER_RTYPE, o->id, (char *)&user, |
69 | sizeof(user)); |
70 | if (len == sizeof(user)) |
71 | strncpy(muser->username, user.username, sizeof(muser->username)); |
72 | else { |
73 | warn("user_update_cache_map: user %lu read size %lu != %lu (%d)", |
74 | o->id, len, sizeof(user), bile_error(db->bile)); |
75 | memset(muser->username, 0, sizeof(muser->username)); |
76 | } |
77 | |
78 | xfree(&o); |
79 | nuser++; |
80 | } |
81 | } |
82 | |
83 | bool |
84 | user_save(struct user *user) |
85 | { |
86 | size_t len; |
87 | |
88 | if (!user->id) { |
89 | user->id = bile_next_id(db->bile, DB_USER_RTYPE); |
90 | if (!user->id) |
91 | return false; |
92 | user->created_at = Time; |
93 | } |
94 | |
95 | len = bile_write(db->bile, DB_USER_RTYPE, user->id, |
96 | user, sizeof(struct user)); |
97 | if (len != sizeof(struct user)) |
98 | panic("user_save: failed to write: %d", bile_error(db->bile)); |
99 | |
100 | bile_flush(db->bile, true); |
101 | return true; |
102 | } |
103 | |
104 | struct user * |
105 | user_find(unsigned long id) |
106 | { |
107 | struct user *user; |
108 | char *data; |
109 | size_t len; |
110 | |
111 | len = bile_read_alloc(db->bile, DB_USER_RTYPE, id, &data); |
112 | if (len == 0 || data == NULL) |
113 | return NULL; |
114 | if (len != sizeof(struct user)) |
115 | panic("user_find: bad user record size %lu != %lu", len, |
116 | sizeof(struct user)); |
117 | user = xmalloczero(sizeof(struct user)); |
118 | if (user != NULL) |
119 | memcpy(user, data, sizeof(struct user)); |
120 | xfree(&data); |
121 | return user; |
122 | } |
123 | |
124 | struct user * |
125 | user_find_by_username(const char *username) |
126 | { |
127 | struct user suser; |
128 | struct username_cache *muser; |
129 | short n; |
130 | size_t len; |
131 | |
132 | len = strlen(username); |
133 | if (len > sizeof(suser.username)) |
134 | return NULL; |
135 | |
136 | if (db->nusers == 0) |
137 | user_cache_usernames(); |
138 | |
139 | for (n = 0; n < db->nusers; n++) { |
140 | muser = &db->username_cache[n]; |
141 | |
142 | if (strcasecmp(muser->username, username) == 0) |
143 | return user_find(muser->id); |
144 | } |
145 | |
146 | return NULL; |
147 | } |
148 | |
149 | struct username_cache * |
150 | user_username(unsigned long id) |
151 | { |
152 | struct username_cache *muser; |
153 | size_t n; |
154 | |
155 | for (n = 0; n < db->nusers; n++) { |
156 | muser = &db->username_cache[n]; |
157 | if (muser->id == id) |
158 | return muser; |
159 | } |
160 | |
161 | return NULL; |
162 | } |
163 | |
164 | short |
165 | user_authenticate(struct user *user, const char *password) |
166 | { |
167 | char hash[SHA256_DIGEST_STRING_LENGTH]; |
168 | char *salted; |
169 | size_t plen, slen; |
170 | short n; |
171 | unsigned char res; |
172 | |
173 | plen = strlen(password); |
174 | slen = SHA256_DIGEST_STRING_LENGTH - 1 + plen; |
175 | salted = xmalloc(slen); |
176 | if (salted == NULL) |
177 | return AUTH_USER_FAILED; |
178 | memcpy(salted, user->password_salt, SHA256_DIGEST_STRING_LENGTH - 1); |
179 | memcpy(salted + SHA256_DIGEST_STRING_LENGTH - 1, password, plen); |
180 | |
181 | SHA256Data((const u_int8_t *)salted, slen, hash); |
182 | |
183 | /* timing-safe comparison */ |
184 | for (res = 0, n = 0; n < SHA256_DIGEST_STRING_LENGTH; n++) |
185 | res |= (hash[n] ^ user->password_hash[n]); |
186 | |
187 | memset(&hash, 0, sizeof(hash)); |
188 | memset(salted, 0, slen); |
189 | xfree(&salted); |
190 | |
191 | if (res == 0) |
192 | return AUTH_USER_OK; |
193 | |
194 | return AUTH_USER_FAILED; |
195 | } |
196 | |
197 | bool |
198 | user_set_password(struct user *user, const char *password) |
199 | { |
200 | char hash[SHA256_DIGEST_STRING_LENGTH]; |
201 | char salt[SHA256_DIGEST_STRING_LENGTH]; |
202 | char *salted; |
203 | unsigned long tmp[8]; |
204 | size_t plen, slen; |
205 | short n; |
206 | |
207 | /* make a random salt out of a sha256 of some random longs */ |
208 | for (n = 0; n < nitems(tmp) - 1; n++) |
209 | tmp[n] = xorshift32(); |
210 | |
211 | SHA256Data((const u_int8_t *)tmp, sizeof(tmp), salt); |
212 | memcpy(&user->password_salt, &salt, sizeof(salt)); |
213 | |
214 | plen = strlen(password); |
215 | slen = plen + sizeof(salt) - 1; |
216 | salted = xmalloc(slen); |
217 | if (salted == NULL) |
218 | return false; |
219 | memcpy(salted, salt, sizeof(salt) - 1); /* exclude null */ |
220 | memcpy(salted + sizeof(salt) - 1, password, plen); |
221 | SHA256Data((const u_int8_t *)salted, slen, hash); |
222 | memset(salted, 0, slen); |
223 | xfree(&salted); |
224 | |
225 | memcpy(&user->password_hash, &hash, sizeof(user->password_hash)); |
226 | |
227 | memset(&hash, 0, sizeof(hash)); |
228 | memset(&salt, 0, sizeof(salt)); |
229 | return true; |
230 | } |
231 | |
232 | bool |
233 | user_valid_username(struct user *user, char *username, char **error) |
234 | { |
235 | struct user *ouser; |
236 | unsigned long ouser_id; |
237 | size_t len, n; |
238 | char c; |
239 | |
240 | if (username == NULL) { |
241 | *error = xstrdup("username cannot be empty"); |
242 | return false; |
243 | } |
244 | |
245 | len = strlen(username); |
246 | if (len > DB_USERNAME_LENGTH) { |
247 | *error = xmalloc(61); |
248 | if (*error) |
249 | snprintf(*error, 60, "username cannot be more than %d " |
250 | "characters", DB_USERNAME_LENGTH); |
251 | return false; |
252 | } |
253 | |
254 | for (n = 0; n < len; n++) { |
255 | c = username[n]; |
256 | |
257 | if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || |
258 | (c >= 'a' && c <= 'z') || c == '_')) { |
259 | *error = xstrdup("username cannot contain non-alphanumeric " |
260 | "characters"); |
261 | return false; |
262 | } |
263 | } |
264 | |
265 | if (strcasecmp(username, GUEST_USERNAME) == 0) { |
266 | *error = xstrdup("username is already in use"); |
267 | return false; |
268 | } |
269 | |
270 | if (!(user != NULL && user->is_sysop) && |
271 | user_username_is_banned(username)) { |
272 | *error = xstrdup("username is not permitted"); |
273 | return false; |
274 | } |
275 | |
276 | if ((ouser = user_find_by_username(username))) { |
277 | ouser_id = ouser->id; |
278 | xfree(&ouser); |
279 | |
280 | if (user == NULL || ouser_id != user->id) { |
281 | *error = xstrdup("username is already in use"); |
282 | return false; |
283 | } |
284 | } |
285 | |
286 | return true; |
287 | } |
288 | |
289 | bool |
290 | user_username_is_banned(char *username) |
291 | { |
292 | size_t n; |
293 | |
294 | for (n = 0; n < nitems(BANNED_USERNAMES); n++) { |
295 | if (strcasecmp(username, BANNED_USERNAMES[n]) == 0) |
296 | return true; |
297 | } |
298 | |
299 | return false; |
300 | } |
301 | |
302 | unsigned long |
303 | user_first_sysop_id(void) |
304 | { |
305 | struct user user; |
306 | struct bile_object *o; |
307 | size_t nuser; |
308 | |
309 | nuser = 0; |
310 | while ((o = bile_get_nth_of_type(db->bile, nuser, DB_USER_RTYPE))) { |
311 | bile_read(db->bile, DB_USER_RTYPE, o->id, (char *)&user, |
312 | sizeof(user)); |
313 | xfree(&o); |
314 | if (user.is_sysop) |
315 | return user.id; |
316 | nuser++; |
317 | } |
318 | |
319 | return 0; |
320 | } |
321 | |
322 | void |
323 | user_change_password(struct session *s, struct user *user) |
324 | { |
325 | char *password = NULL, *password_confirm = NULL; |
326 | |
327 | if (!user) { |
328 | session_printf(s, "{{B}}Error{{/B}}: Guest accounts " |
329 | "cannot change passwords\r\n"); |
330 | return; |
331 | } |
332 | |
333 | while (!s->ending) { |
334 | session_printf(s, "{{B}}New Password:{{/B}} "); |
335 | session_flush(s); |
336 | password = session_field_input(s, 64, 64, NULL, false, '*'); |
337 | session_output(s, "\r\n", 2); |
338 | session_flush(s); |
339 | |
340 | if (password == NULL || s->ending) |
341 | break; |
342 | |
343 | if (password[0] == '\0') { |
344 | session_printf(s, "{{B}}Error:{{/B}} " |
345 | "Password cannot be blank\r\n"); |
346 | xfree(&password); |
347 | password = NULL; |
348 | continue; |
349 | } |
350 | |
351 | session_printf(s, "{{B}}New Password (again):{{/B}} "); |
352 | session_flush(s); |
353 | password_confirm = session_field_input(s, 64, 64, NULL, false, '*'); |
354 | session_output(s, "\r\n", 2); |
355 | session_flush(s); |
356 | |
357 | if (password_confirm == NULL || s->ending) |
358 | break; |
359 | |
360 | if (strcmp(password_confirm, password) != 0) { |
361 | session_printf(s, "{{B}}Error:{{/B}} " |
362 | "Passwords do not match\r\n"); |
363 | memset(password, 0, strlen(password)); |
364 | xfree(&password); |
365 | memset(password_confirm, 0, strlen(password_confirm)); |
366 | xfree(&password_confirm); |
367 | continue; |
368 | } |
369 | |
370 | if (!user_set_password(user, password)) { |
371 | session_logf(s, "Failed saving new password"); |
372 | session_printf(s, "{{B}}Error:{{/B}} " |
373 | "Failed saving new password\r\n"); |
374 | break; |
375 | } |
376 | |
377 | if (!user_save(user)) { |
378 | session_logf(s, "Failed saving user account"); |
379 | session_printf(s, "{{B}}Error:{{/B}} " |
380 | "Failed saving user account\r\n"); |
381 | break; |
382 | } |
383 | |
384 | if (strcmp(s->user->username, user->username) == 0) { |
385 | session_logf(s, "User changed password"); |
386 | session_printf(s, "{{B}}Your password has been " |
387 | "changed{{/B}}\r\n"); |
388 | } else { |
389 | session_logf(s, "User %s changed password for %s", |
390 | s->user->username, user->username); |
391 | session_printf(s, "{{B}}Password has been " |
392 | "changed{{/B}}\r\n"); |
393 | } |
394 | |
395 | break; |
396 | } |
397 | |
398 | if (password != NULL) { |
399 | memset(password, 0, strlen(password)); |
400 | xfree(&password); |
401 | } |
402 | if (password_confirm != NULL) { |
403 | memset(password_confirm, 0, strlen(password_confirm)); |
404 | xfree(&password_confirm); |
405 | } |
406 | } |
407 | |
408 | void |
409 | user_delete(struct user *user) |
410 | { |
411 | struct bile_object *o; |
412 | unsigned long msg_user_id, id; |
413 | size_t n; |
414 | |
415 | /* delete the user's mail */ |
416 | n = 0; |
417 | while ((o = bile_get_nth_of_type(db->mail_bile, n, |
418 | MAIL_SPOOL_MESSAGE_RTYPE))) { |
419 | id = o->id; |
420 | bile_read(db->mail_bile, MAIL_SPOOL_MESSAGE_RTYPE, id, |
421 | (char *)&msg_user_id, sizeof(msg_user_id)); |
422 | xfree(&o); |
423 | |
424 | if (msg_user_id != user->id) { |
425 | n++; |
426 | continue; |
427 | } |
428 | |
429 | bile_delete(db->mail_bile, MAIL_SPOOL_MESSAGE_RTYPE, id, |
430 | BILE_DELETE_FLAG_ZERO | BILE_DELETE_FLAG_PURGE); |
431 | |
432 | /* don't increase n, the remaining messages will shift down */ |
433 | } |
434 | |
435 | /* delete the user */ |
436 | bile_delete(db->bile, DB_USER_RTYPE, user->id, |
437 | BILE_DELETE_FLAG_ZERO | BILE_DELETE_FLAG_PURGE); |
438 | } |