// 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.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
#include <stdlib.h>
#include <unistd.h>
#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
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) {
// 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));
}
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);
}
char oldkey[USERNAME_SIZE];
char newkey[USERNAME_SIZE];
- struct cdbdata *cdbus;
+ struct cdbdata cdbus;
struct ctdluser usbuf;
makeuserkey_pre928(oldkey, username);
// 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));
// 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;
return(sizeof(TheIndex));
}
-
// Back end for CtdlSetRelationship()
-void put_visit(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(visit)
- );
+void put_visit(struct visit *newvisit) {
+ cdb_store(CDB_VISIT, newvisit, (sizeof(long)*3), newvisit, sizeof(struct visit));
}
// Define a relationship between a user and a room
-void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
+void CtdlSetRelationship(struct visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
// We don't use these in Citadel because they're implicit by the
// index, but they must be present if the database is exported.
newvisit->v_roomnum = rel_room->QRnumber;
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(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);
+void CtdlGetRelationship(struct visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room) {
+ struct cdbdata cdbvisit;
// Clear out the buffer
- memset(vbuf, 0, sizeof(visit));
+ memset(vbuf, 0, sizeof(struct visit));
+
+ // 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, IndexBuf, IndexLen);
- if (cdbvisit != NULL) {
- memcpy(vbuf, cdbvisit->ptr, ((cdbvisit->len > sizeof(visit)) ? sizeof(visit) : cdbvisit->len));
- cdb_free(cdbvisit);
+ 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.
// 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);
}
// 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);
}
// Continue attempting user validation...
safestrncpy(username, trythisname, sizeof (username));
- striplt(username);
+ string_trim(username);
if (IsEmptyStr(username)) {
return login_not_found;
syslog(LOG_DEBUG, "user_ops: asking host about <%s>", username);
#ifdef HAVE_GETPWNAM_R
-#ifdef SOLARIS_GETPWUID
- syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
- tempPwdPtr = getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer);
-#else // SOLARIS_GETPWUID
syslog(LOG_DEBUG, "user_ops: calling getpwnam_r()");
getpwnam_r(username, &pd, pwdbuffer, sizeof pwdbuffer, &tempPwdPtr);
-#endif // SOLARIS_GETPWUID
#else // HAVE_GETPWNAM_R
syslog(LOG_DEBUG, "user_ops: SHOULD NEVER GET HERE!!!");
tempPwdPtr = NULL;
// 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);
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);
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
);
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);
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);
}
}
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));
// 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);
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);
}
// Set iuser to the name of the user, and op to 1=invite or 0=kick
int CtdlInvtKick(char *iuser, int op) {
struct ctdluser USscratch;
- visit vbuf;
+ struct visit vbuf;
char bbb[SIZ];
if (CtdlGetUser(&USscratch, iuser) != 0) {
// Forget (Zap) the current room (API call)
// Returns 0 on success
int CtdlForgetThisRoom(void) {
- visit vbuf;
+ struct visit vbuf;
// On some systems, Admins are not allowed to forget rooms
if (is_aide() && (CtdlGetConfigInt("c_aide_zap") == 0)
// 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);
}
-// Count the number of new mail messages the user has
+// Return the number of new messages that have arrived in the user's inbox while they were logged in.
+// Resets to zero when called.
int NewMailCount() {
int num_newmsgs = 0;
num_newmsgs = CC->newmail;
int a;
char mailboxname[ROOMNAMELEN];
struct ctdlroom mailbox;
- visit vbuf;
- struct cdbdata *cdbfr;
+ struct visit vbuf;
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) {
}
}
}
- 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);
+}