ForEachUser() linked list , keep track of last item so we don't have to traverse...
[citadel.git] / citadel / user_ops.c
1 /* 
2  * Server functions which perform operations on user objects.
3  *
4  * Copyright (c) 1987-2019 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License, version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include <stdlib.h>
16 #include <unistd.h>
17 #include "sysdep.h"
18 #include <stdio.h>
19 #include <sys/stat.h>
20 #include <libcitadel.h>
21 #include "control.h"
22 #include "support.h"
23 #include "citserver.h"
24 #include "config.h"
25 #include "citadel_ldap.h"
26 #include "ctdl_module.h"
27 #include "user_ops.h"
28 #include "internet_addressing.h"
29
30 /* These pipes are used to talk to the chkpwd daemon, which is forked during startup */
31 int chkpwd_write_pipe[2];
32 int chkpwd_read_pipe[2];
33
34
35 /*
36  * Trim a string down to the maximum username size and return the new length
37  */
38 long cutusername(char *username) { 
39         long len;
40         len = strlen(username);
41         if (len >= USERNAME_SIZE)
42         {
43                 syslog(LOG_INFO, "Username too long: %s", username);
44                 len = USERNAME_SIZE - 1; 
45                 username[len]='\0';
46         }
47         return len;
48 }
49
50
51 /*
52  * makeuserkey() - convert a username into the format used as a database key
53  *                 (Key format is the username with all non-alphanumeric characters removed, and converted to lower case.)
54  */
55 void makeuserkey(char *key, const char *username, long len) {
56         int i;
57         int keylen = 0;
58
59         if (len >= USERNAME_SIZE) {
60                 syslog(LOG_INFO, "Username too long: %s", username);
61                 len = USERNAME_SIZE - 1; 
62         }
63         for (i=0; i<=len; ++i) {
64                 if (isalnum((username[i]))) {
65                         key[keylen++] = tolower(username[i]);
66                 }
67         }
68         key[keylen++] = 0;
69 }
70
71
72 /*
73  * CtdlGetUser()        retrieve named user into supplied buffer.
74  *                      returns 0 on success
75  */
76 int CtdlGetUser(struct ctdluser *usbuf, char *name)
77 {
78         char usernamekey[USERNAME_SIZE];
79         struct cdbdata *cdbus;
80         long len = cutusername(name);
81
82         if (usbuf != NULL) {
83                 memset(usbuf, 0, sizeof(struct ctdluser));
84         }
85
86         makeuserkey(usernamekey, name, len);
87         cdbus = cdb_fetch(CDB_USERS, usernamekey, strlen(usernamekey));
88
89         if (cdbus == NULL) {    /* user not found */
90                 return(1);
91         }
92         if (usbuf != NULL) {
93                 memcpy(usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ?  sizeof(struct ctdluser) : cdbus->len));
94         }
95         cdb_free(cdbus);
96         return(0);
97 }
98
99
100 int CtdlLockGetCurrentUser(void)
101 {
102         CitContext *CCC = CC;
103         return CtdlGetUser(&CCC->user, CCC->curr_user);
104 }
105
106
107 /*
108  * CtdlGetUserLock()  -  same as getuser() but locks the record
109  */
110 int CtdlGetUserLock(struct ctdluser *usbuf, char *name)
111 {
112         int retcode;
113
114         retcode = CtdlGetUser(usbuf, name);
115         if (retcode == 0) {
116                 begin_critical_section(S_USERS);
117         }
118         return(retcode);
119 }
120
121
122 /*
123  * CtdlPutUser()  -  write user buffer into the correct place on disk
124  */
125 void CtdlPutUser(struct ctdluser *usbuf)
126 {
127         char usernamekey[USERNAME_SIZE];
128
129         makeuserkey(usernamekey, usbuf->fullname, cutusername(usbuf->fullname));
130         usbuf->version = REV_LEVEL;
131         cdb_store(CDB_USERS, usernamekey, strlen(usernamekey), usbuf, sizeof(struct ctdluser));
132 }
133
134
135 void CtdlPutCurrentUserLock()
136 {
137         CtdlPutUser(&CC->user);
138 }
139
140
141 /*
142  * CtdlPutUserLock()  -  same as putuser() but locks the record
143  */
144 void CtdlPutUserLock(struct ctdluser *usbuf)
145 {
146         CtdlPutUser(usbuf);
147         end_critical_section(S_USERS);
148 }
149
150
151 /*
152  * rename_user()  -  this is tricky because the user's display name is the database key
153  *
154  * Returns 0 on success or nonzero if there was an error...
155  *
156  */
157 int rename_user(char *oldname, char *newname) {
158         int retcode = RENAMEUSER_OK;
159         struct ctdluser usbuf;
160
161         char oldnamekey[USERNAME_SIZE];
162         char newnamekey[USERNAME_SIZE];
163
164         /* Create the database keys... */
165         makeuserkey(oldnamekey, oldname, cutusername(oldname));
166         makeuserkey(newnamekey, newname, cutusername(newname));
167
168         /* Lock up and get going */
169         begin_critical_section(S_USERS);
170
171         /* We cannot rename a user who is currently logged in */
172         if (CtdlIsUserLoggedIn(oldname)) {
173                 end_critical_section(S_USERS);
174                 return RENAMEUSER_LOGGED_IN;
175         }
176
177         if (CtdlGetUser(&usbuf, newname) == 0) {
178                 retcode = RENAMEUSER_ALREADY_EXISTS;
179         }
180         else {
181
182                 if (CtdlGetUser(&usbuf, oldname) != 0) {
183                         retcode = RENAMEUSER_NOT_FOUND;
184                 }
185                 else {          /* Sanity checks succeeded.  Now rename the user. */
186                         if (usbuf.usernum == 0) {
187                                 syslog(LOG_DEBUG, "user_ops: can not rename user \"Citadel\".");
188                                 retcode = RENAMEUSER_NOT_FOUND;
189                         } else {
190                                 syslog(LOG_DEBUG, "user_ops: renaming <%s> to <%s>", oldname, newname);
191                                 cdb_delete(CDB_USERS, oldnamekey, strlen(oldnamekey));
192                                 safestrncpy(usbuf.fullname, newname, sizeof usbuf.fullname);
193                                 CtdlPutUser(&usbuf);
194                                 cdb_store(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long), usbuf.fullname, strlen(usbuf.fullname)+1 );
195                                 retcode = RENAMEUSER_OK;
196                         }
197                 }
198         
199         }
200
201         end_critical_section(S_USERS);
202         return(retcode);
203 }
204
205
206 /*
207  * Convert a username into the format used as a database key prior to version 928
208  * This only gets called by reindex_user_928()
209  */
210 void makeuserkey_pre928(char *key, const char *username, long len) {
211         int i;
212
213         if (len >= USERNAME_SIZE) {
214                 syslog(LOG_INFO, "Username too long: %s", username);
215                 len = USERNAME_SIZE - 1; 
216         }
217         for (i=0; i<=len; ++i) {
218                 key[i] = tolower(username[i]);
219         }
220 }
221
222
223 /*
224  * Read a user record using the pre-v928 index format, and write it back using the v928-and-higher index format.
225  * This ONLY gets called during an upgrade from version <928 to version >=928.
226  */
227 void reindex_user_928(char *username, void *out_data) {
228
229         char oldkey[USERNAME_SIZE];
230         char newkey[USERNAME_SIZE];
231         struct cdbdata *cdbus;
232         long len = cutusername(username);
233         struct ctdluser usbuf;
234
235         makeuserkey_pre928(oldkey, username, len);
236         makeuserkey(newkey, username, len);
237
238         syslog(LOG_DEBUG, "user_ops: reindex_user_928: %s <%s> --> <%s>", username, oldkey, newkey);
239
240         // Fetch the user record using the old index format
241         cdbus = cdb_fetch(CDB_USERS, oldkey, strlen(oldkey));
242         if (cdbus == NULL) {
243                 syslog(LOG_INFO, "user_ops: <%s> not found, were they already reindexed?", username);
244                 return;
245         }
246         memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
247         cdb_free(cdbus);
248
249         // delete the old record
250         cdb_delete(CDB_USERS, oldkey, strlen(oldkey));
251
252         // write the new record
253         cdb_store(CDB_USERS, newkey, strlen(newkey), &usbuf, sizeof(struct ctdluser));
254 }
255
256
257 /*
258  * Index-generating function used by Ctdl[Get|Set]Relationship
259  */
260 int GenerateRelationshipIndex(char *IndexBuf,
261                               long RoomID,
262                               long RoomGen,
263                               long UserID)
264 {
265         struct {
266                 long iRoomID;
267                 long iRoomGen;
268                 long iUserID;
269         } TheIndex;
270
271         TheIndex.iRoomID = RoomID;
272         TheIndex.iRoomGen = RoomGen;
273         TheIndex.iUserID = UserID;
274
275         memcpy(IndexBuf, &TheIndex, sizeof(TheIndex));
276         return(sizeof(TheIndex));
277 }
278
279
280 /*
281  * Back end for CtdlSetRelationship()
282  */
283 void put_visit(visit *newvisit)
284 {
285         char IndexBuf[32];
286         int IndexLen = 0;
287
288         memset (IndexBuf, 0, sizeof (IndexBuf));
289         /* Generate an index */
290         IndexLen = GenerateRelationshipIndex(IndexBuf, newvisit->v_roomnum, newvisit->v_roomgen, newvisit->v_usernum);
291
292         /* Store the record */
293         cdb_store(CDB_VISIT, IndexBuf, IndexLen,
294                   newvisit, sizeof(visit)
295         );
296 }
297
298
299 /*
300  * Define a relationship between a user and a room
301  */
302 void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
303         /* We don't use these in Citadel because they're implicit by the
304          * index, but they must be present if the database is exported.
305          */
306         newvisit->v_roomnum = rel_room->QRnumber;
307         newvisit->v_roomgen = rel_room->QRgen;
308         newvisit->v_usernum = rel_user->usernum;
309
310         put_visit(newvisit);
311 }
312
313
314 /*
315  * Locate a relationship between a user and a room
316  */
317 void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
318         char IndexBuf[32];
319         int IndexLen;
320         struct cdbdata *cdbvisit;
321
322         /* Generate an index */
323         IndexLen = GenerateRelationshipIndex(IndexBuf, rel_room->QRnumber, rel_room->QRgen, rel_user->usernum);
324
325         /* Clear out the buffer */
326         memset(vbuf, 0, sizeof(visit));
327
328         cdbvisit = cdb_fetch(CDB_VISIT, IndexBuf, IndexLen);
329         if (cdbvisit != NULL) {
330                 memcpy(vbuf, cdbvisit->ptr, ((cdbvisit->len > sizeof(visit)) ?  sizeof(visit) : cdbvisit->len));
331                 cdb_free(cdbvisit);
332         }
333         else {
334                 /* If this is the first time the user has seen this room,
335                  * set the view to be the default for the room.
336                  */
337                 vbuf->v_view = rel_room->QRdefaultview;
338         }
339
340         /* Set v_seen if necessary */
341         if (vbuf->v_seen[0] == 0) {
342                 snprintf(vbuf->v_seen, sizeof vbuf->v_seen, "*:%ld", vbuf->v_lastseen);
343         }
344 }
345
346
347 void CtdlMailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix)
348 {
349         snprintf(buf, n, "%010ld.%s", who->usernum, prefix);
350 }
351
352
353 void MailboxName(char *buf, size_t n, const struct ctdluser *who, const char *prefix)
354 {
355         snprintf(buf, n, "%010ld.%s", who->usernum, prefix);
356 }
357
358
359 /*
360  * Check to see if the specified user has Internet mail permission
361  * (returns nonzero if permission is granted)
362  */
363 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
364
365         /* Do not allow twits to send Internet mail */
366         if (who->axlevel <= AxProbU) return(0);
367
368         /* Globally enabled? */
369         if (CtdlGetConfigInt("c_restrict") == 0) return(1);
370
371         /* User flagged ok? */
372         if (who->flags & US_INTERNET) return(2);
373
374         /* Admin level access? */
375         if (who->axlevel >= AxAideU) return(3);
376
377         /* No mail for you! */
378         return(0);
379 }
380
381
382 /*
383  * Convenience function.
384  */
385 int CtdlAccessCheck(int required_level)
386 {
387         if (CC->internal_pgm) return(0);
388         if (required_level >= ac_internal) {
389                 cprintf("%d This is not a user-level command.\n", ERROR + HIGHER_ACCESS_REQUIRED);
390                 return(-1);
391         }
392
393         if ((required_level >= ac_logged_in_or_guest) && (CC->logged_in == 0) && (CtdlGetConfigInt("c_guest_logins") == 0)) {
394                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
395                 return(-1);
396         }
397
398         if ((required_level >= ac_logged_in) && (CC->logged_in == 0)) {
399                 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
400                 return(-1);
401         }
402
403         if (CC->user.axlevel >= AxAideU) return(0);
404         if (required_level >= ac_aide) {
405                 cprintf("%d This command requires Admin access.\n",
406                         ERROR + HIGHER_ACCESS_REQUIRED);
407                 return(-1);
408         }
409
410         if (is_room_aide()) return(0);
411         if (required_level >= ac_room_aide) {
412                 cprintf("%d This command requires Admin or Room Admin access.\n",
413                         ERROR + HIGHER_ACCESS_REQUIRED);
414                 return(-1);
415         }
416
417         /* shhh ... succeed quietly */
418         return(0);
419 }
420
421
422 /*
423  * Is the user currently logged in an Admin?
424  */
425 int is_aide(void)
426 {
427         if (CC->user.axlevel >= AxAideU)
428                 return(1);
429         else
430                 return(0);
431 }
432
433
434 /*
435  * Is the user currently logged in an Admin *or* the room Admin for this room?
436  */
437 int is_room_aide(void)
438 {
439
440         if (!CC->logged_in) {
441                 return(0);
442         }
443
444         if ((CC->user.axlevel >= AxAideU) || (CC->room.QRroomaide == CC->user.usernum)) {
445                 return(1);
446         } else {
447                 return(0);
448         }
449 }
450
451
452 /*
453  * CtdlGetUserByNumber() -      get user by number
454  *                      returns 0 if user was found
455  *
456  * Note: fetching a user this way requires one additional database operation.
457  */
458 int CtdlGetUserByNumber(struct ctdluser *usbuf, long number)
459 {
460         struct cdbdata *cdbun;
461         int r;
462
463         cdbun = cdb_fetch(CDB_USERSBYNUMBER, &number, sizeof(long));
464         if (cdbun == NULL) {
465                 syslog(LOG_INFO, "user_ops: %ld not found", number);
466                 return(-1);
467         }
468
469         syslog(LOG_INFO, "user_ops: %ld maps to %s", number, cdbun->ptr);
470         r = CtdlGetUser(usbuf, cdbun->ptr);
471         cdb_free(cdbun);
472         return(r);
473 }
474
475
476 /*
477  * Helper function for rebuild_usersbynumber()
478  */
479 void rebuild_ubn_for_user(char *username, void *data) {
480         struct ctdluser u;
481
482         syslog(LOG_DEBUG, "user_ops: rebuilding usersbynumber index for %s", username);
483         if (CtdlGetUser(&u, username) == 0) {
484                 cdb_store(CDB_USERSBYNUMBER, &(u.usernum), sizeof(long), u.fullname, strlen(u.fullname)+1);
485         }
486 }
487
488
489 /*
490  * Rebuild the users-by-number index
491  */
492 void rebuild_usersbynumber(void) {
493         cdb_trunc(CDB_USERSBYNUMBER);                   /* delete the old indices */
494         ForEachUser(rebuild_ubn_for_user, NULL);        /* enumerate the users */
495 }
496
497
498 /*
499  * getuserbyuid()       Get user by system uid (for PAM mode authentication)
500  *                      Returns 0 if user was found
501  *                      This now uses an extauth index.
502  */
503 int getuserbyuid(struct ctdluser *usbuf, uid_t number)
504 {
505         struct cdbdata *cdbextauth;
506         long usernum = 0;
507         StrBuf *claimed_id;
508
509         claimed_id = NewStrBuf();
510         StrBufPrintf(claimed_id, "uid:%d", number);
511         cdbextauth = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
512         FreeStrBuf(&claimed_id);
513         if (cdbextauth == NULL) {
514                 return(-1);
515         }
516
517         memcpy(&usernum, cdbextauth->ptr, sizeof(long));
518         cdb_free(cdbextauth);
519
520         if (!CtdlGetUserByNumber(usbuf, usernum)) {
521                 return(0);
522         }
523
524         return(-1);
525 }
526
527
528 /*
529  * Back end for cmd_user() and its ilk
530  */
531 int CtdlLoginExistingUser(const char *trythisname)
532 {
533         char username[SIZ];
534         int found_user;
535
536         syslog(LOG_DEBUG, "user_ops: CtdlLoginExistingUser(%s)", trythisname);
537
538         if ((CC->logged_in)) {
539                 return login_already_logged_in;
540         }
541
542         if (trythisname == NULL) return login_not_found;
543         
544         if (!strncasecmp(trythisname, "SYS_", 4))
545         {
546                 syslog(LOG_DEBUG, "user_ops: system user \"%s\" is not allowed to log in.", trythisname);
547                 return login_not_found;
548         }
549
550         /* Continue attempting user validation... */
551         safestrncpy(username, trythisname, sizeof (username));
552         striplt(username);
553
554         if (IsEmptyStr(username)) {
555                 return login_not_found;
556         }
557
558         if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
559
560                 /* host auth mode */
561
562                 struct passwd pd;
563                 struct passwd *tempPwdPtr;
564                 char pwdbuffer[256];
565         
566                 syslog(LOG_DEBUG, "user_ops: asking host about <%s>", username);
567 #ifdef HAVE_GETPWNAM_R
568 #ifdef SOLARIS_GETPWUID
569                 syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
570                 tempPwdPtr = getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer);
571 #else // SOLARIS_GETPWUID
572                 syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
573                 getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer, &tempPwdPtr);
574 #endif // SOLARIS_GETPWUID
575 #else // HAVE_GETPWNAM_R
576                 syslog(LOG_DEBUG, "user_ops: SHOULD NEVER GET HERE!!!");
577                 tempPwdPtr = NULL;
578 #endif // HAVE_GETPWNAM_R
579                 if (tempPwdPtr == NULL) {
580                         syslog(LOG_DEBUG, "user_ops: no such user <%s>", username);
581                         return login_not_found;
582                 }
583         
584                 /* Locate the associated Citadel account.  If not found, make one attempt to create it. */
585                 found_user = getuserbyuid(&CC->user, pd.pw_uid);
586                 if (found_user != 0) {
587                         create_user(username, CREATE_USER_DO_NOT_BECOME_USER, pd.pw_uid);
588                         found_user = getuserbyuid(&CC->user, pd.pw_uid);
589                 }
590                 syslog(LOG_DEBUG, "user_ops: found it: uid=%ld, gecos=%s here: %d", (long)pd.pw_uid, pd.pw_gecos, found_user);
591
592         }
593
594 #ifdef HAVE_LDAP
595         else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
596         
597                 /* LDAP auth mode */
598
599                 uid_t ldap_uid;
600                 char ldap_cn[256];
601                 char ldap_dn[256];
602
603                 found_user = CtdlTryUserLDAP(username, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &ldap_uid);
604                 if (found_user != 0) {
605                         return login_not_found;
606                 }
607
608                 found_user = getuserbyuid(&CC->user, ldap_uid);
609                 if (found_user != 0) {
610                         create_user(ldap_cn, CREATE_USER_DO_NOT_BECOME_USER, ldap_uid);
611                         found_user = getuserbyuid(&CC->user, ldap_uid);
612                 }
613
614                 if (found_user == 0) {
615                         if (CC->ldap_dn != NULL) free(CC->ldap_dn);
616                         CC->ldap_dn = strdup(ldap_dn);
617                 }
618
619         }
620 #endif
621
622         else {
623                 /* native auth mode */
624
625                 recptypes *valid = NULL;
626         
627                 /* First, try to log in as if the supplied name is a display name */
628                 found_user = CtdlGetUser(&CC->user, username);
629         
630                 /* If that didn't work, try to log in as if the supplied name * is an e-mail address */
631                 if (found_user != 0) {
632                         valid = validate_recipients(username, NULL, 0);
633                         if (valid != NULL) {
634                                 if (valid->num_local == 1) {
635                                         found_user = CtdlGetUser(&CC->user, valid->recp_local);
636                                 }
637                                 free_recipients(valid);
638                         }
639                 }
640         }
641
642         /* Did we find something? */
643         if (found_user == 0) {
644                 if (((CC->nologin)) && (CC->user.axlevel < AxAideU)) {
645                         return login_too_many_users;
646                 } else {
647                         safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user);
648                         return login_ok;
649                 }
650         }
651         return login_not_found;
652 }
653
654
655 /*
656  * session startup code which is common to both cmd_pass() and cmd_newu()
657  */
658 void do_login(void)
659 {
660         CC->logged_in = 1;
661         syslog(LOG_NOTICE, "user_ops: <%s> logged in", CC->curr_user);
662
663         CtdlGetUserLock(&CC->user, CC->curr_user);
664         ++(CC->user.timescalled);
665         CC->previous_login = CC->user.lastcall;
666         time(&CC->user.lastcall);
667
668         /* If this user's name is the name of the system administrator
669          * (as specified in setup), automatically assign access level 6.
670          */
671         if ( (!IsEmptyStr(CtdlGetConfigStr("c_sysadm"))) && (!strcasecmp(CC->user.fullname, CtdlGetConfigStr("c_sysadm"))) ) {
672                 CC->user.axlevel = AxAideU;
673         }
674
675         /* If we're authenticating off the host system, automatically give root the highest level of access. */
676         if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
677                 if (CC->user.uid == 0) {
678                         CC->user.axlevel = AxAideU;
679                 }
680         }
681         CtdlPutUserLock(&CC->user);
682
683         /* If we are using LDAP authentication, extract the user's email addresses from the directory. */
684 #ifdef HAVE_LDAP
685         if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
686                 char new_emailaddrs[512];
687                 if (CtdlGetConfigInt("c_ldap_sync_email_addrs") > 0) {
688                         if (extract_email_addresses_from_ldap(CC->ldap_dn, new_emailaddrs) == 0) {
689                                 CtdlSetEmailAddressesForUser(CC->user.fullname, new_emailaddrs);
690                         }
691                 }
692         }
693 #endif
694
695         /* If the user does not have any email addresses assigned, generate one. */
696         if (IsEmptyStr(CC->user.emailaddrs)) {
697                 AutoGenerateEmailAddressForUser(&CC->user);
698         }
699
700         /*
701          * Populate cs_inet_email and cs_inet_other_emails with valid email addresses from the user record
702          */
703         strcpy(CC->cs_inet_email, CC->user.emailaddrs);
704         char *firstsep = strstr(CC->cs_inet_email, "|");
705         if (firstsep) {
706                 strcpy(CC->cs_inet_other_emails, firstsep+1);
707                 *firstsep = 0;
708         }
709         else {
710                 CC->cs_inet_other_emails[0] = 0;
711         }
712
713         /* Create any personal rooms required by the system.
714          * (Technically, MAILROOM should be there already, but just in case...)
715          */
716         CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
717         CtdlCreateRoom(SENTITEMS, 4, "", 0, 1, 0, VIEW_MAILBOX);
718         CtdlCreateRoom(USERTRASHROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
719         CtdlCreateRoom(USERDRAFTROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
720
721         /* Run any startup routines registered by loadable modules */
722         PerformSessionHooks(EVT_LOGIN);
723
724         /* Enter the lobby */
725         CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
726 }
727
728
729 void logged_in_response(void)
730 {
731         cprintf("%d %s|%d|%ld|%ld|%u|%ld|%ld\n",
732                 CIT_OK, CC->user.fullname, CC->user.axlevel,
733                 CC->user.timescalled, CC->user.posted,
734                 CC->user.flags, CC->user.usernum,
735                 CC->previous_login
736         );
737 }
738
739
740 void CtdlUserLogout(void)
741 {
742         CitContext *CCC = MyContext();
743
744         syslog(LOG_DEBUG, "user_ops: CtdlUserLogout() logging out <%s> from session %d", CCC->curr_user, CCC->cs_pid);
745
746         /* Run any hooks registered by modules... */
747         PerformSessionHooks(EVT_LOGOUT);
748         
749         /*
750          * Clear out some session data.  Most likely, the CitContext for this
751          * session is about to get nuked when the session disconnects, but
752          * since it's possible to log in again without reconnecting, we cannot
753          * make that assumption.
754          */
755         CCC->logged_in = 0;
756
757         /* Check to see if the user was deleted while logged in and purge them if necessary */
758         if ((CCC->user.axlevel == AxDeleted) && (CCC->user.usernum)) {
759                 purge_user(CCC->user.fullname);
760         }
761
762         /* Clear out the user record in memory so we don't behave like a ghost */
763         memset(&CCC->user, 0, sizeof(struct ctdluser));
764         CCC->curr_user[0] = 0;
765         CCC->cs_inet_email[0] = 0;
766         CCC->cs_inet_other_emails[0] = 0;
767         CCC->cs_inet_fn[0] = 0;
768
769         /* Free any output buffers */
770         unbuffer_output();
771 }
772
773
774 /*
775  * Validate a password on the host unix system by talking to the chkpwd daemon
776  */
777 static int validpw(uid_t uid, const char *pass)
778 {
779         char buf[256];
780         int rv = 0;
781
782         if (IsEmptyStr(pass)) {
783                 syslog(LOG_DEBUG, "user_ops: refusing to chkpwd for uid=%d with empty password", uid);
784                 return 0;
785         }
786
787         syslog(LOG_DEBUG, "user_ops: validating password for uid=%d using chkpwd...", uid);
788
789         begin_critical_section(S_CHKPWD);
790         rv = write(chkpwd_write_pipe[1], &uid, sizeof(uid_t));
791         if (rv == -1) {
792                 syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
793                 end_critical_section(S_CHKPWD);
794                 return 0;
795         }
796         rv = write(chkpwd_write_pipe[1], pass, 256);
797         if (rv == -1) {
798                 syslog(LOG_ERR, "user_ops: communication with chkpwd broken: %m");
799                 end_critical_section(S_CHKPWD);
800                 return 0;
801         }
802         rv = read(chkpwd_read_pipe[0], buf, 4);
803         if (rv == -1) {
804                 syslog(LOG_ERR, "user_ops: ommunication with chkpwd broken: %m");
805                 end_critical_section(S_CHKPWD);
806                 return 0;
807         }
808         end_critical_section(S_CHKPWD);
809
810         if (!strncmp(buf, "PASS", 4)) {
811                 syslog(LOG_DEBUG, "user_ops: chkpwd pass");
812                 return(1);
813         }
814
815         syslog(LOG_DEBUG, "user_ops: chkpwd fail");
816         return 0;
817 }
818
819
820 /* 
821  * Start up the chkpwd daemon so validpw() has something to talk to
822  */
823 void start_chkpwd_daemon(void) {
824         pid_t chkpwd_pid;
825         struct stat filestats;
826         int i;
827
828         syslog(LOG_DEBUG, "user_ops: starting chkpwd daemon for host authentication mode");
829
830         if ((stat(file_chkpwd, &filestats)==-1) || (filestats.st_size==0)) {
831                 syslog(LOG_ERR, "user_ops: %s: %m", file_chkpwd);
832                 abort();
833         }
834         if (pipe(chkpwd_write_pipe) != 0) {
835                 syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
836                 abort();
837         }
838         if (pipe(chkpwd_read_pipe) != 0) {
839                 syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
840                 abort();
841         }
842
843         chkpwd_pid = fork();
844         if (chkpwd_pid < 0) {
845                 syslog(LOG_ERR, "user_ops: unable to fork chkpwd daemon: %m");
846                 abort();
847         }
848         if (chkpwd_pid == 0) {
849                 dup2(chkpwd_write_pipe[0], 0);
850                 dup2(chkpwd_read_pipe[1], 1);
851                 for (i=2; i<256; ++i) close(i);
852                 execl(file_chkpwd, file_chkpwd, NULL);
853                 syslog(LOG_ERR, "user_ops: unable to exec chkpwd daemon: %m");
854                 abort();
855                 exit(errno);
856         }
857 }
858
859
860 int CtdlTryPassword(const char *password, long len)
861 {
862         int code;
863         CitContext *CCC = CC;
864
865         if ((CCC->logged_in)) {
866                 syslog(LOG_WARNING, "user_ops: CtdlTryPassword: already logged in");
867                 return pass_already_logged_in;
868         }
869         if (!strcmp(CCC->curr_user, NLI)) {
870                 syslog(LOG_WARNING, "user_ops: CtdlTryPassword: no user selected");
871                 return pass_no_user;
872         }
873         if (CtdlGetUser(&CCC->user, CCC->curr_user)) {
874                 syslog(LOG_ERR, "user_ops: CtdlTryPassword: internal error");
875                 return pass_internal_error;
876         }
877         if (password == NULL) {
878                 syslog(LOG_INFO, "user_ops: CtdlTryPassword: NULL password string supplied");
879                 return pass_wrong_password;
880         }
881
882         else if (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_HOST) {
883
884                 /* host auth mode */
885
886                 if (validpw(CCC->user.uid, password)) {
887                         code = 0;
888
889                         /*
890                          * sooper-seekrit hack: populate the password field in the
891                          * citadel database with the password that the user typed,
892                          * if it's correct.  This allows most sites to convert from
893                          * host auth to native auth if they want to.  If you think
894                          * this is a security hazard, comment it out.
895                          */
896
897                         CtdlGetUserLock(&CCC->user, CCC->curr_user);
898                         safestrncpy(CCC->user.password, password, sizeof CCC->user.password);
899                         CtdlPutUserLock(&CCC->user);
900
901                         /*
902                          * (sooper-seekrit hack ends here)
903                          */
904                 }
905                 else {
906                         code = (-1);
907                 }
908         }
909
910 #ifdef HAVE_LDAP
911         else if ((CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP) || (CtdlGetConfigInt("c_auth_mode") == AUTHMODE_LDAP_AD)) {
912
913                 /* LDAP auth mode */
914
915                 if ((CCC->ldap_dn) && (!CtdlTryPasswordLDAP(CCC->ldap_dn, password))) {
916                         code = 0;
917                 }
918                 else {
919                         code = (-1);
920                 }
921         }
922 #endif
923
924         else {
925
926                 /* native auth mode */
927                 char *pw;
928
929                 pw = (char*) malloc(len + 1);
930                 memcpy(pw, password, len + 1);
931                 strproc(pw);
932                 strproc(CCC->user.password);
933                 code = strcasecmp(CCC->user.password, pw);
934                 if (code != 0) {
935                         strproc(pw);
936                         strproc(CCC->user.password);
937                         code = strcasecmp(CCC->user.password, pw);
938                 }
939                 free (pw);
940         }
941
942         if (!code) {
943                 do_login();
944                 return pass_ok;
945         }
946         else {
947                 syslog(LOG_WARNING, "user_ops: bad password specified for <%s> Service <%s> Port <%ld> Remote <%s / %s>",
948                         CCC->curr_user,
949                         CCC->ServiceName,
950                         CCC->tcp_port,
951                         CCC->cs_host,
952                         CCC->cs_addr
953                 );
954                 return pass_wrong_password;
955         }
956 }
957
958
959 /*
960  * Delete a user record *and* all of its related resources.
961  */
962 int purge_user(char pname[])
963 {
964         struct ctdluser usbuf;
965         char usernamekey[USERNAME_SIZE];
966
967         makeuserkey(usernamekey, pname, cutusername(pname));
968
969         /* If the name is empty we can't find them in the DB any way so just return */
970         if (IsEmptyStr(pname)) {
971                 return(ERROR + NO_SUCH_USER);
972         }
973
974         if (CtdlGetUser(&usbuf, pname) != 0) {
975                 syslog(LOG_ERR, "user_ops: cannot purge user <%s> - not found", pname);
976                 return(ERROR + NO_SUCH_USER);
977         }
978
979         /* Don't delete a user who is currently logged in.  Instead, just
980          * set the access level to 0, and let the account get swept up
981          * during the next purge.
982          */
983         if (CtdlIsUserLoggedInByNum(usbuf.usernum)) {
984                 syslog(LOG_WARNING, "user_ops: <%s> is logged in; not deleting", pname);
985                 usbuf.axlevel = AxDeleted;
986                 CtdlPutUser(&usbuf);
987                 return(1);
988         }
989
990         syslog(LOG_NOTICE, "user_ops: deleting <%s>", pname);
991
992         /* Perform any purge functions registered by server extensions */
993         PerformUserHooks(&usbuf, EVT_PURGEUSER);
994
995         /* delete any existing user/room relationships */
996         cdb_delete(CDB_VISIT, &usbuf.usernum, sizeof(long));
997
998         /* delete the users-by-number index record */
999         cdb_delete(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long));
1000
1001         /* delete the userlog entry */
1002         cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey));
1003
1004         return(0);
1005 }
1006
1007
1008 int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid)
1009 {
1010         if (!CtdlGetUser(usbuf, username)) {
1011                 return(ERROR + ALREADY_EXISTS);
1012         }
1013
1014         /* Go ahead and initialize a new user record */
1015         memset(usbuf, 0, sizeof(struct ctdluser));
1016         safestrncpy(usbuf->fullname, username, sizeof usbuf->fullname);
1017         strcpy(usbuf->password, "");
1018         usbuf->uid = uid;
1019
1020         /* These are the default flags on new accounts */
1021         usbuf->flags = US_LASTOLD | US_DISAPPEAR | US_PAGINATOR | US_FLOORS;
1022
1023         usbuf->timescalled = 0;
1024         usbuf->posted = 0;
1025         usbuf->axlevel = CtdlGetConfigInt("c_initax");
1026         usbuf->lastcall = time(NULL);
1027
1028         /* fetch a new user number */
1029         usbuf->usernum = get_new_user_number();
1030
1031         /* add user to the database */
1032         CtdlPutUser(usbuf);
1033         cdb_store(CDB_USERSBYNUMBER, &usbuf->usernum, sizeof(long), usbuf->fullname, strlen(usbuf->fullname)+1);
1034
1035         /* If non-native auth, index by uid */
1036         if ((usbuf->uid > 0) && (usbuf->uid != NATIVE_AUTH_UID)) {
1037                 StrBuf *claimed_id = NewStrBuf();
1038                 StrBufPrintf(claimed_id, "uid:%d", usbuf->uid);
1039                 attach_extauth(usbuf, claimed_id);
1040                 FreeStrBuf(&claimed_id);
1041         }
1042
1043         return(0);
1044 }
1045
1046
1047 /*
1048  * create_user()  -  back end processing to create a new user
1049  *
1050  * Set 'newusername' to the desired account name.
1051  * Set 'become_user' to CREATE_USER_BECOME_USER if this is self-service account creation and we want to
1052  *                   actually log in as the user we just created, otherwise set it to CREATE_USER_DO_NOT_BECOME_USER
1053  * Set 'uid' to some uid_t value to associate the account with an external auth user, or (-1) for native auth
1054  */
1055 int create_user(char *username, int become_user, uid_t uid)
1056 {
1057         struct ctdluser usbuf;
1058         struct ctdlroom qrbuf;
1059         char mailboxname[ROOMNAMELEN];
1060         char buf[SIZ];
1061         int retval;
1062
1063         strproc(username);
1064         if ((retval = internal_create_user(username, &usbuf, uid)) != 0) {
1065                 return retval;
1066         }
1067
1068         /*
1069          * Give the user a private mailbox and a configuration room.
1070          * Make the latter an invisible system room.
1071          */
1072         CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, MAILROOM);
1073         CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_MAILBOX);
1074
1075         CtdlMailboxName(mailboxname, sizeof mailboxname, &usbuf, USERCONFIGROOM);
1076         CtdlCreateRoom(mailboxname, 5, "", 0, 1, 1, VIEW_BBS);
1077         if (CtdlGetRoomLock(&qrbuf, mailboxname) == 0) {
1078                 qrbuf.QRflags2 |= QR2_SYSTEM;
1079                 CtdlPutRoomLock(&qrbuf);
1080         }
1081
1082         /* Perform any create functions registered by server extensions */
1083         PerformUserHooks(&usbuf, EVT_NEWUSER);
1084
1085         /* Everything below this line can be bypassed if administratively
1086          * creating a user, instead of doing self-service account creation
1087          */
1088
1089         if (become_user == CREATE_USER_BECOME_USER) {
1090                 /* Now become the user we just created */
1091                 memcpy(&CC->user, &usbuf, sizeof(struct ctdluser));
1092                 safestrncpy(CC->curr_user, username, sizeof CC->curr_user);
1093                 do_login();
1094         
1095                 /* Check to make sure we're still who we think we are */
1096                 if (CtdlGetUser(&CC->user, CC->curr_user)) {
1097                         return(ERROR + INTERNAL_ERROR);
1098                 }
1099         }
1100         
1101         snprintf(buf, SIZ, 
1102                 "New user account <%s> has been created, from host %s [%s].\n",
1103                 username,
1104                 CC->cs_host,
1105                 CC->cs_addr
1106         );
1107         CtdlAideMessage(buf, "User Creation Notice");
1108         syslog(LOG_NOTICE, "user_ops: <%s> created", username);
1109         return(0);
1110 }
1111
1112
1113 /*
1114  * set password - back end api code
1115  */
1116 void CtdlSetPassword(char *new_pw)
1117 {
1118         CtdlGetUserLock(&CC->user, CC->curr_user);
1119         safestrncpy(CC->user.password, new_pw, sizeof(CC->user.password));
1120         CtdlPutUserLock(&CC->user);
1121         syslog(LOG_INFO, "user_ops: password changed for <%s>", CC->curr_user);
1122         PerformSessionHooks(EVT_SETPASS);
1123 }
1124
1125
1126 /*
1127  * API function for cmd_invt_kick() and anything else that needs to
1128  * invite or kick out a user to/from a room.
1129  * 
1130  * Set iuser to the name of the user, and op to 1=invite or 0=kick
1131  */
1132 int CtdlInvtKick(char *iuser, int op) {
1133         struct ctdluser USscratch;
1134         visit vbuf;
1135         char bbb[SIZ];
1136
1137         if (CtdlGetUser(&USscratch, iuser) != 0) {
1138                 return(1);
1139         }
1140
1141         CtdlGetRelationship(&vbuf, &USscratch, &CC->room);
1142         if (op == 1) {
1143                 vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
1144                 vbuf.v_flags = vbuf.v_flags | V_ACCESS;
1145         }
1146         if (op == 0) {
1147                 vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
1148                 vbuf.v_flags = vbuf.v_flags | V_FORGET | V_LOCKOUT;
1149         }
1150         CtdlSetRelationship(&vbuf, &USscratch, &CC->room);
1151
1152         /* post a message in Aide> saying what we just did */
1153         snprintf(bbb, sizeof bbb, "%s has been %s \"%s\" by %s.\n",
1154                 iuser,
1155                 ((op == 1) ? "invited to" : "kicked out of"),
1156                 CC->room.QRname,
1157                 (CC->logged_in ? CC->user.fullname : "an administrator")
1158         );
1159         CtdlAideMessage(bbb,"User Admin Message");
1160         return(0);
1161 }
1162
1163
1164 /*
1165  * Forget (Zap) the current room (API call)
1166  * Returns 0 on success
1167  */
1168 int CtdlForgetThisRoom(void) {
1169         visit vbuf;
1170
1171         /* On some systems, Admins are not allowed to forget rooms */
1172         if (is_aide() && (CtdlGetConfigInt("c_aide_zap") == 0)
1173            && ((CC->room.QRflags & QR_MAILBOX) == 0)  ) {
1174                 return(1);
1175         }
1176
1177         CtdlGetUserLock(&CC->user, CC->curr_user);
1178         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
1179
1180         vbuf.v_flags = vbuf.v_flags | V_FORGET;
1181         vbuf.v_flags = vbuf.v_flags & ~V_ACCESS;
1182
1183         CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
1184         CtdlPutUserLock(&CC->user);
1185
1186         /* Return to the Lobby, so we don't end up in an undefined room */
1187         CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
1188         return(0);
1189 }
1190
1191
1192 /* 
1193  * Traverse the user file and perform a callback for each user record.
1194  * (New improved version that runs in two phases so that callbacks can perform writes without having a r/o cursor open)
1195  */
1196 void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data)
1197 {
1198         struct cdbdata *cdbus;
1199         struct ctdluser *usptr;
1200
1201         struct feu {
1202                 struct feu *next;
1203                 char username[USERNAME_SIZE];
1204         };
1205         struct feu *ufirst = NULL;
1206         struct feu *ulast = NULL;
1207         struct feu *f = NULL;
1208
1209         cdb_rewind(CDB_USERS);
1210
1211         // Phase 1 : build a linked list of all our user account names
1212         while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
1213                 usptr = (struct ctdluser *) cdbus->ptr;
1214
1215                 if (strlen(usptr->fullname) > 0) {
1216                         f = malloc(sizeof(struct feu));
1217                         f->next = NULL;
1218                         strncpy(f->username, usptr->fullname, USERNAME_SIZE);
1219
1220                         if (ufirst == NULL) {
1221                                 ufirst = f;
1222                                 ulast = f;
1223                         }
1224                         else {
1225                                 ulast->next = f;
1226                                 ulast = f;
1227                         }
1228                 }
1229         }
1230
1231         // Phase 2 : perform the callback for each user while de-allocating the list
1232         while (ufirst != NULL) {
1233                 (*CallBack) (ufirst->username, in_data);
1234                 f = ufirst;
1235                 ufirst = ufirst->next;
1236                 free(f);
1237         }
1238 }
1239
1240
1241 /*
1242  * Count the number of new mail messages the user has
1243  */
1244 int NewMailCount()
1245 {
1246         int num_newmsgs = 0;
1247         num_newmsgs = CC->newmail;
1248         CC->newmail = 0;
1249         return(num_newmsgs);
1250 }
1251
1252
1253 /*
1254  * Count the number of new mail messages the user has
1255  */
1256 int InitialMailCheck()
1257 {
1258         int num_newmsgs = 0;
1259         int a;
1260         char mailboxname[ROOMNAMELEN];
1261         struct ctdlroom mailbox;
1262         visit vbuf;
1263         struct cdbdata *cdbfr;
1264         long *msglist = NULL;
1265         int num_msgs = 0;
1266
1267         CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
1268         if (CtdlGetRoom(&mailbox, mailboxname) != 0)
1269                 return(0);
1270         CtdlGetRelationship(&vbuf, &CC->user, &mailbox);
1271
1272         cdbfr = cdb_fetch(CDB_MSGLISTS, &mailbox.QRnumber, sizeof(long));
1273
1274         if (cdbfr != NULL) {
1275                 msglist = malloc(cdbfr->len);
1276                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
1277                 num_msgs = cdbfr->len / sizeof(long);
1278                 cdb_free(cdbfr);
1279         }
1280         if (num_msgs > 0)
1281                 for (a = 0; a < num_msgs; ++a) {
1282                         if (msglist[a] > 0L) {
1283                                 if (msglist[a] > vbuf.v_lastseen) {
1284                                         ++num_newmsgs;
1285                                 }
1286                         }
1287                 }
1288         if (msglist != NULL)
1289                 free(msglist);
1290
1291         return(num_newmsgs);
1292 }