validate_recipients() - completed removal of unused param
[citadel.git] / citadel / server / user_ops.c
index 5ee1da4a6c56b7d2e467e2a0ddd4bacc4e39c620..e35b00a27e71b222b20c5f3772b9b7aaac255552 100644 (file)
@@ -1,6 +1,6 @@
 // Server functions which perform operations on user objects.
 //
-// Copyright (c) 1987-2022 by the citadel.org team
+// Copyright (c) 1987-2023 by the citadel.org team
 //
 // This program is open source software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License, version 3.
@@ -18,6 +18,8 @@
 #include "citadel_ldap.h"
 #include "ctdl_module.h"
 #include "user_ops.h"
+#include "room_ops.h"
+#include "makeuserkey.h"
 #include "internet_addressing.h"
 
 // These pipes are used to talk to the chkpwd daemon, which is forked during startup
@@ -25,28 +27,6 @@ int chkpwd_write_pipe[2];
 int chkpwd_read_pipe[2];
 
 
-// makeuserkey() - convert a username into the format used as a database key
-//             "key" must be a buffer of at least USERNAME_SIZE
-//             (Key format is the username with all non-alphanumeric characters removed, and converted to lower case.)
-void makeuserkey(char *key, const char *username) {
-       int i;
-       int keylen = 0;
-
-       if (IsEmptyStr(username)) {
-               key[0] = 0;
-               return;
-       }
-
-       int len = strlen(username);
-       for (i=0; ((i<=len) && (i<USERNAME_SIZE-1)); ++i) {
-               if (isalnum((username[i]))) {
-                       key[keylen++] = tolower(username[i]);
-               }
-       }
-       key[keylen++] = 0;
-}
-
-
 // Compare two usernames to see if they are the same user after being keyed for the database
 // Usage is identical to strcmp()
 int CtdlUserCmp(char *s1, char *s2) {
@@ -63,7 +43,7 @@ int CtdlUserCmp(char *s1, char *s2) {
 //                     returns 0 on success
 int CtdlGetUser(struct ctdluser *usbuf, char *name) {
        char usernamekey[USERNAME_SIZE];
-       struct cdbdata *cdbus;
+       struct cdbdata cdbus;
 
        if (usbuf != NULL) {
                memset(usbuf, 0, sizeof(struct ctdluser));
@@ -75,13 +55,12 @@ int CtdlGetUser(struct ctdluser *usbuf, char *name) {
        }
        cdbus = cdb_fetch(CDB_USERS, usernamekey, strlen(usernamekey));
 
-       if (cdbus == NULL) {    // user not found
+       if (cdbus.ptr == NULL) {        // user not found
                return(1);
        }
        if (usbuf != NULL) {
-               memcpy(usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ?  sizeof(struct ctdluser) : cdbus->len));
+               memcpy(usbuf, cdbus.ptr, ((cdbus.len > sizeof(struct ctdluser)) ?  sizeof(struct ctdluser) : cdbus.len));
        }
-       cdb_free(cdbus);
        return(0);
 }
 
@@ -199,7 +178,7 @@ void reindex_user_928(char *username, void *out_data) {
 
        char oldkey[USERNAME_SIZE];
        char newkey[USERNAME_SIZE];
-       struct cdbdata *cdbus;
+       struct cdbdata cdbus;
        struct ctdluser usbuf;
 
        makeuserkey_pre928(oldkey, username);
@@ -209,12 +188,11 @@ void reindex_user_928(char *username, void *out_data) {
 
        // Fetch the user record using the old index format
        cdbus = cdb_fetch(CDB_USERS, oldkey, strlen(oldkey));
-       if (cdbus == NULL) {
+       if (cdbus.ptr == NULL) {
                syslog(LOG_INFO, "user_ops: <%s> not found, were they already reindexed?", username);
                return;
        }
-       memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len));
-       cdb_free(cdbus);
+       memcpy(&usbuf, cdbus.ptr, ((cdbus.len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus.len));
 
        // delete the old record
        cdb_delete(CDB_USERS, oldkey, strlen(oldkey));
@@ -225,17 +203,8 @@ void reindex_user_928(char *username, void *out_data) {
 
 
 // Index-generating function used by Ctdl[Get|Set]Relationship
-int GenerateRelationshipIndex(char *IndexBuf,
-                             long RoomID,
-                             long RoomGen,
-                             long UserID
-) {
-       struct {
-               long iRoomID;
-               long iRoomGen;
-               long iUserID;
-       } TheIndex;
-
+int GenerateRelationshipIndex(char *IndexBuf, long RoomID, long RoomGen, long UserID) {
+       struct visit_index TheIndex;
        TheIndex.iRoomID = RoomID;
        TheIndex.iRoomGen = RoomGen;
        TheIndex.iUserID = UserID;
@@ -244,20 +213,9 @@ int GenerateRelationshipIndex(char *IndexBuf,
        return(sizeof(TheIndex));
 }
 
-
 // Back end for CtdlSetRelationship()
 void put_visit(struct visit *newvisit) {
-       char IndexBuf[32];
-       int IndexLen = 0;
-
-       memset (IndexBuf, 0, sizeof (IndexBuf));
-       // Generate an index
-       IndexLen = GenerateRelationshipIndex(IndexBuf, newvisit->v_roomnum, newvisit->v_roomgen, newvisit->v_usernum);
-
-       // Store the record
-       cdb_store(CDB_VISIT, IndexBuf, IndexLen,
-                 newvisit, sizeof(struct visit)
-       );
+       cdb_store(CDB_VISIT, newvisit, (sizeof(long)*3), newvisit, sizeof(struct visit));
 }
 
 
@@ -269,26 +227,26 @@ void CtdlSetRelationship(struct visit *newvisit, struct ctdluser *rel_user, stru
        newvisit->v_roomgen = rel_room->QRgen;
        newvisit->v_usernum = rel_user->usernum;
 
+       // Store the record
        put_visit(newvisit);
 }
 
 
 // Locate a relationship between a user and a room
 void CtdlGetRelationship(struct visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
-       char IndexBuf[32];
-       int IndexLen;
-       struct cdbdata *cdbvisit;
-
-       // Generate an index
-       IndexLen = GenerateRelationshipIndex(IndexBuf, rel_room->QRnumber, rel_room->QRgen, rel_user->usernum);
+       struct cdbdata cdbvisit;
 
        // Clear out the buffer
        memset(vbuf, 0, sizeof(struct visit));
 
-       cdbvisit = cdb_fetch(CDB_VISIT, IndexBuf, IndexLen);
-       if (cdbvisit != NULL) {
-               memcpy(vbuf, cdbvisit->ptr, ((cdbvisit->len > sizeof(struct visit)) ?  sizeof(struct visit) : cdbvisit->len));
-               cdb_free(cdbvisit);
+       // Fill out the first three fields; they are also the index
+       vbuf->v_roomnum = rel_room->QRnumber;
+       vbuf->v_roomgen = rel_room->QRgen;
+       vbuf->v_usernum = rel_user->usernum;
+
+       cdbvisit = cdb_fetch(CDB_VISIT, vbuf, (sizeof(long)*3));
+       if (cdbvisit.ptr != NULL) {
+               memcpy(vbuf, cdbvisit.ptr, ((cdbvisit.len > sizeof(struct visit)) ? sizeof(struct visit) : cdbvisit.len));
        }
        else {
                // If this is the first time the user has seen this room, set the view to be the default for the room.
@@ -396,18 +354,17 @@ int is_room_aide(void) {
 // CtdlGetUserByNumber() - get user by number, returns 0 if user was found
 // Note: fetching a user this way requires one additional database operation.
 int CtdlGetUserByNumber(struct ctdluser *usbuf, long number) {
-       struct cdbdata *cdbun;
+       struct cdbdata cdbun;
        int r;
 
        cdbun = cdb_fetch(CDB_USERSBYNUMBER, &number, sizeof(long));
-       if (cdbun == NULL) {
+       if (cdbun.ptr == NULL) {
                syslog(LOG_INFO, "user_ops: %ld not found", number);
                return(-1);
        }
 
-       syslog(LOG_INFO, "user_ops: %ld maps to %s", number, cdbun->ptr);
-       r = CtdlGetUser(usbuf, cdbun->ptr);
-       cdb_free(cdbun);
+       syslog(LOG_INFO, "user_ops: %ld maps to %s", number, cdbun.ptr);
+       r = CtdlGetUser(usbuf, cdbun.ptr);
        return(r);
 }
 
@@ -432,28 +389,29 @@ void rebuild_usersbynumber(void) {
 
 // getuserbyuid()      Get user by system uid (for PAM mode authentication)
 //                     Returns 0 if user was found
-//                     This now uses an extauth index.
 int getuserbyuid(struct ctdluser *usbuf, uid_t number) {
-       struct cdbdata *cdbextauth;
-       long usernum = 0;
-       StrBuf *claimed_id;
-
-       claimed_id = NewStrBuf();
-       StrBufPrintf(claimed_id, "uid:%d", number);
-       cdbextauth = cdb_fetch(CDB_EXTAUTH, ChrPtr(claimed_id), StrLength(claimed_id));
-       FreeStrBuf(&claimed_id);
-       if (cdbextauth == NULL) {
-               return(-1);
-       }
 
-       memcpy(&usernum, cdbextauth->ptr, sizeof(long));
-       cdb_free(cdbextauth);
+       struct cdbkeyval cdbus;
+       struct ctdluser *usptr;
+       int return_value = (-1);
 
-       if (!CtdlGetUserByNumber(usbuf, usernum)) {
-               return(0);
+       // Yes, we do this the long way.  Someday we might want to try an index.
+       // No, we don't use CtdlForEachUser() because that requires multiple reads for each record
+       cdb_rewind(CDB_USERS);
+       while (cdbus = cdb_next_item(CDB_USERS), cdbus.val.ptr!=NULL) {         // always read through to the end
+               usptr = (struct ctdluser *) cdbus.val.ptr;
+
+               if (usptr->uid == number) {
+                       syslog(LOG_DEBUG, "user_ops: found uid=%d username=%s", usptr->uid, usptr->fullname);
+                       memcpy(usbuf, usptr, sizeof(struct ctdluser));
+                       return_value = 0;       // success
+               }
        }
 
-       return(-1);
+       if (return_value != 0) {
+               syslog(LOG_DEBUG, "user_ops: no user found with uid=%d", number);
+       }
+       return(return_value);
 }
 
 
@@ -547,7 +505,7 @@ int CtdlLoginExistingUser(const char *trythisname) {
        
                // If that didn't work, try to log in as if the supplied name * is an e-mail address
                if (found_user != 0) {
-                       valid = validate_recipients(username, NULL, 0);
+                       valid = validate_recipients(username, 0);
                        if (valid != NULL) {
                                if (valid->num_local == 1) {
                                        found_user = CtdlGetUser(&CC->user, valid->recp_local);
@@ -583,7 +541,6 @@ void do_login(void) {
        syslog(LOG_NOTICE, "user_ops: <%s> logged in", CC->curr_user);
 
        CtdlGetUserLock(&CC->user, CC->curr_user);
-       ++(CC->user.timescalled);
        CC->previous_login = CC->user.lastcall;
        time(&CC->user.lastcall);
 
@@ -649,9 +606,8 @@ void do_login(void) {
 
 
 void logged_in_response(void) {
-       cprintf("%d %s|%d|%ld|%ld|%u|%ld|%ld\n",
+       cprintf("%d %s|%d|0|0|%u|%ld|%ld\n",
                CIT_OK, CC->user.fullname, CC->user.axlevel,
-               CC->user.timescalled, CC->user.posted,
                CC->user.flags, CC->user.usernum,
                CC->previous_login
        );
@@ -741,21 +697,21 @@ void start_chkpwd_daemon(void) {
 
        if ((stat(file_chkpwd, &filestats)==-1) || (filestats.st_size==0)) {
                syslog(LOG_ERR, "user_ops: %s: %m", file_chkpwd);
-               abort();
+               exit(CTDLEXIT_CHKPWD);
        }
        if (pipe(chkpwd_write_pipe) != 0) {
                syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
-               abort();
+               exit(CTDLEXIT_CHKPWD);
        }
        if (pipe(chkpwd_read_pipe) != 0) {
                syslog(LOG_ERR, "user_ops: unable to create pipe for chkpwd daemon: %m");
-               abort();
+               exit(CTDLEXIT_CHKPWD);
        }
 
        chkpwd_pid = fork();
        if (chkpwd_pid < 0) {
                syslog(LOG_ERR, "user_ops: unable to fork chkpwd daemon: %m");
-               abort();
+               exit(CTDLEXIT_CHKPWD);
        }
        if (chkpwd_pid == 0) {
                dup2(chkpwd_write_pipe[0], 0);
@@ -763,7 +719,6 @@ void start_chkpwd_daemon(void) {
                for (i=2; i<256; ++i) close(i);
                execl(file_chkpwd, file_chkpwd, NULL);
                syslog(LOG_ERR, "user_ops: unable to exec chkpwd daemon: %m");
-               abort();
                exit(errno);
        }
 }
@@ -889,7 +844,11 @@ int purge_user(char pname[]) {
        PerformUserHooks(&usbuf, EVT_PURGEUSER);
 
        // delete any existing user/room relationships
-       cdb_delete(CDB_VISIT, &usbuf.usernum, sizeof(long));
+       // Commenting out since it was assuming the user number was
+       //   at the top of the index.  Room number is actually first.
+       //   Auto-Purge will clean up the records later, so not worth
+       //   scanning all the records here.
+       //cdb_delete(CDB_VISIT, &usbuf.usernum, sizeof(long));
 
        // delete the users-by-number index record
        cdb_delete(CDB_USERSBYNUMBER, &usbuf.usernum, sizeof(long));
@@ -915,9 +874,6 @@ int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid) {
 
        // These are the default flags on new accounts
        usbuf->flags = US_LASTOLD | US_DISAPPEAR | US_PAGINATOR | US_FLOORS;
-
-       usbuf->timescalled = 0;
-       usbuf->posted = 0;
        usbuf->axlevel = CtdlGetConfigInt("c_initax");
        usbuf->lastcall = time(NULL);
 
@@ -932,7 +888,6 @@ int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid) {
        if ((usbuf->uid > 0) && (usbuf->uid != NATIVE_AUTH_UID)) {
                StrBuf *claimed_id = NewStrBuf();
                StrBufPrintf(claimed_id, "uid:%d", usbuf->uid);
-               attach_extauth(usbuf, claimed_id);
                FreeStrBuf(&claimed_id);
        }
 
@@ -1075,46 +1030,32 @@ int CtdlForgetThisRoom(void) {
 // Traverse the user file and perform a callback for each user record.
 // (New improved version that runs in two phases so that callbacks can perform writes without having a r/o cursor open)
 void ForEachUser(void (*CallBack) (char *, void *out_data), void *in_data) {
-       struct cdbdata *cdbus;
+       struct cdbkeyval cdbus;
        struct ctdluser *usptr;
 
-       struct feu {
-               struct feu *next;
-               char username[USERNAME_SIZE];
-       };
-       struct feu *ufirst = NULL;
-       struct feu *ulast = NULL;
-       struct feu *f = NULL;
-
-       cdb_rewind(CDB_USERS);
+       Array *all_users = array_new(USERNAME_SIZE);
+       if (all_users == NULL) {
+               syslog(LOG_ERR, "user_ops: alloc failed, ForEachUser() exiting");
+               return;
+       }
 
-       // Phase 1 : build a linked list of all our user account names
-       while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
-               usptr = (struct ctdluser *) cdbus->ptr;
 
+       // Phase 1 : build an array of all our user account names
+       cdb_rewind(CDB_USERS);
+       while (cdbus = cdb_next_item(CDB_USERS), cdbus.val.ptr!=NULL) {         // always read through to the end
+               usptr = (struct ctdluser *) cdbus.val.ptr;
                if (strlen(usptr->fullname) > 0) {
-                       f = malloc(sizeof(struct feu));
-                       f->next = NULL;
-                       strncpy(f->username, usptr->fullname, USERNAME_SIZE);
-
-                       if (ufirst == NULL) {
-                               ufirst = f;
-                               ulast = f;
-                       }
-                       else {
-                               ulast->next = f;
-                               ulast = f;
-                       }
+                       array_append(all_users, usptr->fullname);
                }
        }
 
        // Phase 2 : perform the callback for each user while de-allocating the list
-       while (ufirst != NULL) {
-               (*CallBack) (ufirst->username, in_data);
-               f = ufirst;
-               ufirst = ufirst->next;
-               free(f);
+       for (int i=0; i<array_len(all_users); ++i) {
+               (*CallBack) (array_get_element_at(all_users, i), in_data);
        }
+
+       // Phase 3 : free the array
+       array_free(all_users);
 }
 
 
@@ -1135,24 +1076,17 @@ int InitialMailCheck() {
        char mailboxname[ROOMNAMELEN];
        struct ctdlroom mailbox;
        struct visit vbuf;
-       struct cdbdata *cdbfr;
        long *msglist = NULL;
        int num_msgs = 0;
 
        CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM);
-       if (CtdlGetRoom(&mailbox, mailboxname) != 0)
+       if (CtdlGetRoom(&mailbox, mailboxname) != 0) {
                return(0);
+       }
        CtdlGetRelationship(&vbuf, &CC->user, &mailbox);
 
-       cdbfr = cdb_fetch(CDB_MSGLISTS, &mailbox.QRnumber, sizeof(long));
-
-       if (cdbfr != NULL) {
-               msglist = malloc(cdbfr->len);
-               memcpy(msglist, cdbfr->ptr, cdbfr->len);
-               num_msgs = cdbfr->len / sizeof(long);
-               cdb_free(cdbfr);
-       }
-       if (num_msgs > 0)
+       num_msgs = CtdlFetchMsgList(mailbox.QRnumber, &msglist);
+       if (num_msgs > 0) {
                for (a = 0; a < num_msgs; ++a) {
                        if (msglist[a] > 0L) {
                                if (msglist[a] > vbuf.v_lastseen) {
@@ -1160,8 +1094,22 @@ int InitialMailCheck() {
                                }
                        }
                }
-       if (msglist != NULL)
-               free(msglist);
-
+       }
+       free(msglist);
        return(num_newmsgs);
 }
+
+
+// This is an undocumented rescue mode that can be used to rewrite an admin user account that has been corrupted.
+// To prevent abuse, it can only run as a one-shot when the server is not otherwise operational.
+// Don't use this without asking about it.
+void undocumented_rescue_mode(char *rescue_string) {
+       struct ctdluser usbuf;
+       memset(&usbuf, 0, sizeof(struct ctdluser));
+       extract_token(usbuf.fullname, rescue_string, 0, '|', sizeof usbuf.password);
+       extract_token(usbuf.password, rescue_string, 1, '|', sizeof usbuf.password);
+       usbuf.usernum = extract_long(rescue_string, 2);
+       usbuf.axlevel = 6;
+       usbuf.lastcall = time(NULL);
+       CtdlPutUser(&usbuf);
+}