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