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