078dc4ab533d41b6660a76d01d5e565511af87f0
[citadel.git] / citadel / room_ops.c
1 // Server functions which perform operations on room 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 modify
6 // 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 <stdio.h>
14 #include <libcitadel.h>
15
16 #include "citserver.h"
17 #include "ctdl_module.h"
18 #include "config.h"
19 #include "control.h"
20 #include "user_ops.h"
21 #include "room_ops.h"
22
23 struct floor *floorcache[MAXFLOORS];
24
25 // Determine whether the currently logged in session has permission to read
26 // messages in the current room.
27 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
28         if (    (!(CC->logged_in))
29                 && (!(CC->internal_pgm))
30                 && (!CtdlGetConfigInt("c_guest_logins"))
31         ) {
32                 return(om_not_logged_in);
33         }
34         return(om_ok);
35 }
36
37
38 // Check to see whether we have permission to post a message in the current
39 // room.  Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
40 // returns 0 on success.
41 int CtdlDoIHavePermissionToPostInThisRoom(
42         char *errmsgbuf, 
43         size_t n, 
44         const char* RemoteIdentifier,
45         PostType PostPublic,
46         int is_reply
47 ) {
48         int ra;
49
50         if (!(CC->logged_in) && (PostPublic == POST_LOGGED_IN)) {
51                 snprintf(errmsgbuf, n, "Not logged in.");
52                 return (ERROR + NOT_LOGGED_IN);
53         }
54         else if (PostPublic == CHECK_EXIST) {
55                 return (0);                                     // evaluate whether a recipient exists
56         }
57         else if (!(CC->logged_in)) {
58                 if ((CC->room.QRflags & QR_READONLY)) {
59                         snprintf(errmsgbuf, n, "Not logged in.");
60                         return (ERROR + NOT_LOGGED_IN);
61                 }
62                 return (0);
63         }
64
65         if ((CC->user.axlevel < AxProbU) && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
66                 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
67                 return (ERROR + HIGHER_ACCESS_REQUIRED);
68         }
69
70         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
71
72         if (ra & UA_POSTALLOWED) {
73                 strcpy(errmsgbuf, "OK to post or reply here");
74                 return(0);
75         }
76
77         if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
78                 // To be thorough, we ought to check to see if the message they are
79                 // replying to is actually a valid one in this room, but unless this
80                 // actually becomes a problem we'll go with high performance instead.
81                 strcpy(errmsgbuf, "OK to reply here");
82                 return(0);
83         }
84
85         if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
86                 // Clarify what happened with a better error message
87                 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
88                 return (ERROR + HIGHER_ACCESS_REQUIRED);
89         }
90
91         snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
92         return (ERROR + HIGHER_ACCESS_REQUIRED);
93
94 }
95
96
97 /*
98  * Check whether the current user has permission to delete messages from
99  * the current room (returns 1 for yes, 0 for no)
100  */
101 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
102         int ra;
103         CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
104         if (ra & UA_DELETEALLOWED) return(1);
105         return(0);
106 }
107
108
109 /*
110  * Retrieve access control information for any user/room pair.
111  * Yes, it has a couple of gotos.  If you don't like that, go die in a car fire.
112  */
113 void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view) {
114         int retval = 0;
115         visit vbuf;
116         int is_me = 0;
117         int is_guest = 0;
118
119         if (userbuf == &CC->user) {
120                 is_me = 1;
121         }
122
123         if ((is_me) && (CtdlGetConfigInt("c_guest_logins")) && (!CC->logged_in)) {
124                 is_guest = 1;
125         }
126
127         /* for internal programs, always do everything */
128         if (((CC->internal_pgm)) && (roombuf->QRflags & QR_INUSE)) {
129                 retval = (UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED);
130                 vbuf.v_view = 0;
131                 goto SKIP_EVERYTHING;
132         }
133
134         /* If guest mode is enabled, always grant access to the Lobby */
135         if ((is_guest) && (!strcasecmp(roombuf->QRname, BASEROOM))) {
136                 retval = (UA_KNOWN | UA_GOTOALLOWED);
137                 vbuf.v_view = 0;
138                 goto SKIP_EVERYTHING;
139         }
140
141         /* Locate any applicable user/room relationships */
142         if (is_guest) {
143                 memset(&vbuf, 0, sizeof vbuf);
144         }
145         else {
146                 CtdlGetRelationship(&vbuf, userbuf, roombuf);
147         }
148
149         /* Force the properties of the Aide room */
150         if (!strcasecmp(roombuf->QRname, CtdlGetConfigStr("c_aideroom"))) {
151                 if (userbuf->axlevel >= AxAideU) {
152                         retval = UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
153                 } else {
154                         retval = 0;
155                 }
156                 goto NEWMSG;
157         }
158
159         /* If this is a public room, it's accessible... */
160         if (    ((roombuf->QRflags & QR_PRIVATE) == 0) 
161                 && ((roombuf->QRflags & QR_MAILBOX) == 0)
162         ) {
163                 retval = retval | UA_KNOWN | UA_GOTOALLOWED;
164         }
165
166         /* If this is a preferred users only room, check access level */
167         if (roombuf->QRflags & QR_PREFONLY) {
168                 if (userbuf->axlevel < AxPrefU) {
169                         retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED;
170                 }
171         }
172
173         /* For private rooms, check the generation number matchups */
174         if (    (roombuf->QRflags & QR_PRIVATE) 
175                 && ((roombuf->QRflags & QR_MAILBOX) == 0)
176         ) {
177
178                 /* An explicit match means the user belongs in this room */
179                 if (vbuf.v_flags & V_ACCESS) {
180                         retval = retval | UA_KNOWN | UA_GOTOALLOWED;
181                 }
182                 /* Otherwise, check if this is a guess-name or passworded
183                  * room.  If it is, a goto may at least be attempted
184                  */
185                 else if (       (roombuf->QRflags & QR_PRIVATE)
186                                 || (roombuf->QRflags & QR_PASSWORDED)
187                 ) {
188                         retval = retval & ~UA_KNOWN;
189                         retval = retval | UA_GOTOALLOWED;
190                 }
191         }
192
193         /* For mailbox rooms, also check the namespace */
194         /* Also, mailbox owners can delete their messages */
195         if ( (roombuf->QRflags & QR_MAILBOX) && (atol(roombuf->QRname) != 0)) {
196                 if (userbuf->usernum == atol(roombuf->QRname)) {
197                         retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
198                 }
199                 /* An explicit match means the user belongs in this room */
200                 if (vbuf.v_flags & V_ACCESS) {
201                         retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED;
202                 }
203         }
204
205         /* For non-mailbox rooms... */
206         else {
207                 // User is allowed to post in the room unless:
208                 // - User is not validated
209                 // - User has no net privileges and it is a shared network room
210                 // - It is a read-only room
211                 // - It is a blog room (in which case we only allow replies to existing messages)
212                 int post_allowed = 1;
213                 int reply_allowed = 1;
214                 if (userbuf->axlevel < AxProbU) {
215                         post_allowed = 0;
216                         reply_allowed = 0;
217                 }
218                 if ((userbuf->axlevel < AxNetU) && (roombuf->QRflags & QR_NETWORK)) {
219                         post_allowed = 0;
220                         reply_allowed = 0;
221                 }
222                 if (roombuf->QRflags & QR_READONLY) {
223                         post_allowed = 0;
224                         reply_allowed = 0;
225                 }
226                 if (roombuf->QRdefaultview == VIEW_BLOG) {
227                         post_allowed = 0;
228                 }
229                 if (post_allowed) {
230                         retval = retval | UA_POSTALLOWED | UA_REPLYALLOWED;
231                 }
232                 if (reply_allowed) {
233                         retval = retval | UA_REPLYALLOWED;
234                 }
235
236                 // If "collaborative deletion" is active for this room, any user who can post
237                 // is also allowed to delete
238                 if (roombuf->QRflags2 & QR2_COLLABDEL) {
239                         if (retval & UA_POSTALLOWED) {
240                                 retval = retval | UA_DELETEALLOWED;
241                         }
242                 }
243
244         }
245
246         /* Check to see if the user has forgotten this room */
247         if (vbuf.v_flags & V_FORGET) {
248                 retval = retval & ~UA_KNOWN;
249                 if (    ( ((roombuf->QRflags & QR_PRIVATE) == 0) 
250                         && ((roombuf->QRflags & QR_MAILBOX) == 0)
251                 ) || (  (roombuf->QRflags & QR_MAILBOX) 
252                         && (atol(roombuf->QRname) == CC->user.usernum))
253                 ) {
254                         retval = retval | UA_ZAPPED;
255                 }
256         }
257
258         /* If user is explicitly locked out of this room, deny everything */
259         if (vbuf.v_flags & V_LOCKOUT) {
260                 retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED & ~UA_POSTALLOWED & ~UA_REPLYALLOWED;
261         }
262
263         /* Aides get access to all private rooms */
264         if (    (userbuf->axlevel >= AxAideU)
265                 && ((roombuf->QRflags & QR_MAILBOX) == 0)
266         ) {
267                 if (vbuf.v_flags & V_FORGET) {
268                         retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
269                 }
270                 else {
271                         retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
272                 }
273         }
274
275         // Aides can gain access to mailboxes as well, but they don't show by default.
276         if (    (userbuf->axlevel >= AxAideU)
277                 && (roombuf->QRflags & QR_MAILBOX)
278         ) {
279                 retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
280         }
281
282         // Aides and Room Aides have admin privileges
283         if (    (userbuf->axlevel >= AxAideU)
284                 || (userbuf->usernum == roombuf->QRroomaide)
285         ) {
286                 retval = retval | UA_ADMINALLOWED | UA_DELETEALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED;
287         }
288
289 NEWMSG: /* By the way, we also check for the presence of new messages */
290         if (is_msg_in_sequence_set(vbuf.v_seen, roombuf->QRhighest) == 0) {
291                 retval = retval | UA_HASNEWMSGS;
292         }
293
294         /* System rooms never show up in the list. */
295         if (roombuf->QRflags2 & QR2_SYSTEM) {
296                 retval = retval & ~UA_KNOWN;
297         }
298
299 SKIP_EVERYTHING:
300         /* Now give the caller the information it wants. */
301         if (result != NULL) *result = retval;
302         if (view != NULL) *view = vbuf.v_view;
303 }
304
305
306 /*
307  * Self-checking stuff for a room record read into memory
308  */
309 void room_sanity_check(struct ctdlroom *qrbuf) {
310         /* Mailbox rooms are always on the lowest floor */
311         if (qrbuf->QRflags & QR_MAILBOX) {
312                 qrbuf->QRfloor = 0;
313         }
314         /* Listing order of 0 is illegal except for base rooms */
315         if (qrbuf->QRorder == 0) {
316                 if (    !(qrbuf->QRflags & QR_MAILBOX)
317                         && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)
318                         && strncasecmp(qrbuf->QRname, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)
319                 ) {
320                         qrbuf->QRorder = 64;
321                 }
322         }
323 }
324
325
326 /*
327  * CtdlGetRoom()  -  retrieve room data from disk
328  */
329 int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name) {
330         struct cdbdata *cdbqr;
331         char lowercase_name[ROOMNAMELEN];
332         char personal_lowercase_name[ROOMNAMELEN];
333         const char *sptr;
334         char *dptr, *eptr;
335
336         dptr = lowercase_name;
337         sptr = room_name;
338         eptr = (dptr + (sizeof lowercase_name - 1));
339         while (!IsEmptyStr(sptr) && (dptr < eptr)) {
340                 *dptr = tolower(*sptr);
341                 sptr++; dptr++;
342         }
343         *dptr = '\0';
344
345         memset(qrbuf, 0, sizeof(struct ctdlroom));
346
347         if (IsEmptyStr(lowercase_name)) {
348                 return(1);                      // empty room name , not valid
349         }
350
351         /* First, try the public namespace */
352         cdbqr = cdb_fetch(CDB_ROOMS, lowercase_name, strlen(lowercase_name));
353
354         /* If that didn't work, try the user's personal namespace */
355         if (cdbqr == NULL) {
356                 snprintf(personal_lowercase_name, sizeof personal_lowercase_name, "%010ld.%s", CC->user.usernum, lowercase_name);
357                 cdbqr = cdb_fetch(CDB_ROOMS, personal_lowercase_name, strlen(personal_lowercase_name));
358         }
359         if (cdbqr != NULL) {
360                 memcpy(qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ?  sizeof(struct ctdlroom) : cdbqr->len));
361                 cdb_free(cdbqr);
362                 room_sanity_check(qrbuf);
363                 return (0);
364         }
365         else {
366                 return (1);
367         }
368 }
369
370
371 /*
372  * CtdlGetRoomLock()  -  same as getroom() but locks the record (if supported)
373  */
374 int CtdlGetRoomLock(struct ctdlroom *qrbuf, const char *room_name) {
375         register int retval;
376         retval = CtdlGetRoom(qrbuf, room_name);
377         if (retval == 0) begin_critical_section(S_ROOMS);
378         return(retval);
379 }
380
381
382 /*
383  * b_putroom()  -  back end to putroom() and b_deleteroom()
384  *              (if the supplied buffer is NULL, delete the room record)
385  */
386 void b_putroom(struct ctdlroom *qrbuf, char *room_name)
387 {
388         char lowercase_name[ROOMNAMELEN];
389         char *aptr, *bptr;
390         long len;
391
392         aptr = room_name;
393         bptr = lowercase_name;
394         while (!IsEmptyStr(aptr)) {
395                 *bptr = tolower(*aptr);
396                 aptr++;
397                 bptr++;
398         }
399         *bptr='\0';
400
401         len = bptr - lowercase_name;
402         if (qrbuf == NULL) {
403                 cdb_delete(CDB_ROOMS, lowercase_name, len);
404         }
405         else {
406                 time(&qrbuf->QRmtime);
407                 cdb_store(CDB_ROOMS, lowercase_name, len, qrbuf, sizeof(struct ctdlroom));
408         }
409 }
410
411
412 /* 
413  * CtdlPutRoom()  -  store room data to disk
414  */
415 void CtdlPutRoom(struct ctdlroom *qrbuf) {
416         b_putroom(qrbuf, qrbuf->QRname);
417 }
418
419
420 /*
421  * b_deleteroom()  -  delete a room record from disk
422  */
423 void b_deleteroom(char *room_name) {
424         b_putroom(NULL, room_name);
425 }
426
427
428 /*
429  * CtdlPutRoomLock()  -  same as CtdlPutRoom() but unlocks the record (if supported)
430  */
431 void CtdlPutRoomLock(struct ctdlroom *qrbuf) {
432         CtdlPutRoom(qrbuf);
433         end_critical_section(S_ROOMS);
434 }
435
436
437 /*
438  * CtdlGetFloorByName()  -  retrieve the number of the named floor
439  * return < 0 if not found else return floor number
440  */
441 int CtdlGetFloorByName(const char *floor_name) {
442         int a;
443         struct floor *flbuf = NULL;
444
445         for (a = 0; a < MAXFLOORS; ++a) {
446                 flbuf = CtdlGetCachedFloor(a);
447
448                 /* check to see if it already exists */
449                 if ((!strcasecmp(flbuf->f_name, floor_name)) && (flbuf->f_flags & F_INUSE)) {
450                         return a;
451                 }
452         }
453         return -1;
454 }
455
456
457 /*
458  * CtdlGetFloorByNameLock()  -  retrieve floor number for given floor and lock the floor list.
459  */
460 int CtdlGetFloorByNameLock(const char *floor_name)
461 {
462         begin_critical_section(S_FLOORTAB);
463         return CtdlGetFloorByName(floor_name);
464 }
465
466
467 /*
468  * CtdlGetAvailableFloor()  -  Return number of first unused floor
469  * return < 0 if none available
470  */
471 int CtdlGetAvailableFloor(void) {
472         int a;
473         struct floor *flbuf = NULL;
474
475         for (a = 0; a < MAXFLOORS; a++) {
476                 flbuf = CtdlGetCachedFloor(a);
477
478                 /* check to see if it already exists */
479                 if ((flbuf->f_flags & F_INUSE) == 0) {
480                         return a;
481                 }
482         }
483         return -1;
484 }
485
486
487 /*
488  * CtdlGetFloor()  -  retrieve floor data from disk
489  */
490 void CtdlGetFloor(struct floor *flbuf, int floor_num) {
491         struct cdbdata *cdbfl;
492
493         memset(flbuf, 0, sizeof(struct floor));
494         cdbfl = cdb_fetch(CDB_FLOORTAB, &floor_num, sizeof(int));
495         if (cdbfl != NULL) {
496                 memcpy(flbuf, cdbfl->ptr, ((cdbfl->len > sizeof(struct floor)) ?  sizeof(struct floor) : cdbfl->len));
497                 cdb_free(cdbfl);
498         } else {
499                 if (floor_num == 0) {
500                         safestrncpy(flbuf->f_name, "Main Floor", sizeof flbuf->f_name);
501                         flbuf->f_flags = F_INUSE;
502                         flbuf->f_ref_count = 3;
503                 }
504         }
505 }
506
507
508 /*
509  * lgetfloor()  -  same as CtdlGetFloor() but locks the record (if supported)
510  */
511 void lgetfloor(struct floor *flbuf, int floor_num) {
512         begin_critical_section(S_FLOORTAB);
513         CtdlGetFloor(flbuf, floor_num);
514 }
515
516
517 /*
518  * CtdlGetCachedFloor()  -  Get floor record from *cache* (loads from disk if needed)
519  *    
520  * This is strictly a performance hack.
521  */
522 struct floor *CtdlGetCachedFloor(int floor_num) {
523         static int initialized = 0;
524         int i;
525         int fetch_new = 0;
526         struct floor *fl = NULL;
527
528         begin_critical_section(S_FLOORCACHE);
529         if (initialized == 0) {
530                 for (i=0; i<MAXFLOORS; ++i) {
531                         floorcache[floor_num] = NULL;
532                 }
533         initialized = 1;
534         }
535         if (floorcache[floor_num] == NULL) {
536                 fetch_new = 1;
537         }
538         end_critical_section(S_FLOORCACHE);
539
540         if (fetch_new) {
541                 fl = malloc(sizeof(struct floor));
542                 CtdlGetFloor(fl, floor_num);
543                 begin_critical_section(S_FLOORCACHE);
544                 if (floorcache[floor_num] != NULL) {
545                         free(floorcache[floor_num]);
546                 }
547                 floorcache[floor_num] = fl;
548                 end_critical_section(S_FLOORCACHE);
549         }
550
551         return(floorcache[floor_num]);
552 }
553
554
555 /*
556  * CtdlPutFloor()  -  store floor data on disk
557  */
558 void CtdlPutFloor(struct floor *flbuf, int floor_num) {
559         /* If we've cached this, clear it out, 'cuz it's WRONG now! */
560         begin_critical_section(S_FLOORCACHE);
561         if (floorcache[floor_num] != NULL) {
562                 free(floorcache[floor_num]);
563                 floorcache[floor_num] = malloc(sizeof(struct floor));
564                 memcpy(floorcache[floor_num], flbuf, sizeof(struct floor));
565         }
566         end_critical_section(S_FLOORCACHE);
567
568         cdb_store(CDB_FLOORTAB, &floor_num, sizeof(int),
569                   flbuf, sizeof(struct floor));
570 }
571
572
573 /*
574  * CtdlPutFloorLock()  -  same as CtdlPutFloor() but unlocks the record (if supported)
575  */
576 void CtdlPutFloorLock(struct floor *flbuf, int floor_num) {
577         CtdlPutFloor(flbuf, floor_num);
578         end_critical_section(S_FLOORTAB);
579
580 }
581
582
583 /*
584  * lputfloor()  -  same as CtdlPutFloor() but unlocks the record (if supported)
585  */
586 void lputfloor(struct floor *flbuf, int floor_num) {
587         CtdlPutFloorLock(flbuf, floor_num);
588 }
589
590
591 /* 
592  * Iterate through the room table, performing a callback for each room.
593  */
594 void CtdlForEachRoom(ForEachRoomCallBack callback_func, void *in_data) {
595         struct ctdlroom qrbuf;
596         struct cdbdata *cdbqr;
597
598         cdb_rewind(CDB_ROOMS);
599
600         while (cdbqr = cdb_next_item(CDB_ROOMS), cdbqr != NULL) {
601                 memset(&qrbuf, 0, sizeof(struct ctdlroom));
602                 memcpy(&qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ?  sizeof(struct ctdlroom) : cdbqr->len) );
603                 cdb_free(cdbqr);
604                 room_sanity_check(&qrbuf);
605                 if (qrbuf.QRflags & QR_INUSE) {
606                         callback_func(&qrbuf, in_data);
607                 }
608         }
609 }
610
611
612 /*
613  * delete_msglist()  -  delete room message pointers
614  */
615 void delete_msglist(struct ctdlroom *whichroom) {
616         struct cdbdata *cdbml;
617
618         /* Make sure the msglist we're deleting actually exists, otherwise
619          * libdb will complain when we try to delete an invalid record
620          */
621         cdbml = cdb_fetch(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
622         if (cdbml != NULL) {
623                 cdb_free(cdbml);
624
625                 /* Go ahead and delete it */
626                 cdb_delete(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long));
627         }
628 }
629
630
631 /*
632  * Message pointer compare function for sort_msglist()
633  */
634 int sort_msglist_cmp(const void *m1, const void *m2) {
635         if ((*(const long *)m1) > (*(const long *)m2)) return(1);
636         if ((*(const long *)m1) < (*(const long *)m2)) return(-1);
637         return(0);
638 }
639
640
641 /*
642  * sort message pointers
643  * (returns new msg count)
644  */
645 int sort_msglist(long listptrs[], int oldcount) {
646         int numitems;
647         int i = 0;
648
649         numitems = oldcount;
650         if (numitems < 2) {
651                 return (oldcount);
652         }
653
654         /* do the sort */
655         qsort(listptrs, numitems, sizeof(long), sort_msglist_cmp);
656
657         /* and yank any nulls */
658         while ((i < numitems) && (listptrs[i] == 0L)) i++;
659
660         if (i > 0) {
661                 memmove(&listptrs[0], &listptrs[i], (sizeof(long) * (numitems - i)));
662                 numitems-=i;
663         }
664
665         return (numitems);
666 }
667
668
669 /*
670  * Determine whether a given room is non-editable.
671  */
672 int CtdlIsNonEditable(struct ctdlroom *qrbuf) {
673
674         /* Mail> rooms are non-editable */
675         if ( (qrbuf->QRflags & QR_MAILBOX) && (!strcasecmp(&qrbuf->QRname[11], MAILROOM)) ) {
676                 return (1);
677         }
678
679         /* Everything else is editable */
680         return (0);
681 }
682
683
684 /*
685  * Make the specified room the current room for this session.  No validation
686  * or access control is done here -- the caller should make sure that the
687  * specified room exists and is ok to access.
688  */
689 void CtdlUserGoto(char *where, int display_result, int transiently,
690                 int *retmsgs, int *retnew, long *retoldest, long *retnewest)
691 {
692         int a;
693         int new_messages = 0;
694         int old_messages = 0;
695         int total_messages = 0;
696         long oldest_message = 0;
697         long newest_message = 0;
698         int info = 0;
699         int rmailflag;
700         int raideflag;
701         int newmailcount = 0;
702         visit vbuf;
703         char truncated_roomname[ROOMNAMELEN];
704         struct cdbdata *cdbfr;
705         long *msglist = NULL;
706         int num_msgs = 0;
707         unsigned int original_v_flags;
708         int num_sets;
709         int s;
710         char setstr[128], lostr[64], histr[64];
711         long lo, hi;
712         int is_trash = 0;
713
714         /* If the supplied room name is NULL, the caller wants us to know that
715          * it has already copied the room record into CC->room, so
716          * we can skip the extra database fetch.
717          */
718         if (where != NULL) {
719                 safestrncpy(CC->room.QRname, where, sizeof CC->room.QRname);
720                 CtdlGetRoom(&CC->room, where);
721         }
722
723         /* Take care of all the formalities. */
724
725         begin_critical_section(S_USERS);
726         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
727         original_v_flags = vbuf.v_flags;
728
729         /* Know the room ... but not if it's the page log room, or if the
730          * caller specified that we're only entering this room transiently.
731          */
732         int add_room_to_known_list = 1;
733         if (transiently == 1) {
734                 add_room_to_known_list = 0;
735         }
736         char *c_logpages = CtdlGetConfigStr("c_logpages");
737         if ( (c_logpages != NULL) && (!strcasecmp(CC->room.QRname, c_logpages)) ) {
738                 add_room_to_known_list = 0;
739         }
740         if (add_room_to_known_list) {
741                 vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
742                 vbuf.v_flags = vbuf.v_flags | V_ACCESS;
743         }
744         
745         /* Only rewrite the database record if we changed something */
746         if (vbuf.v_flags != original_v_flags) {
747                 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
748         }
749         end_critical_section(S_USERS);
750
751         /* Check for new mail */
752         newmailcount = NewMailCount();
753
754         /* Set info to 1 if the room banner is new since our last visit.
755          * Some clients only want to display it when it changes.
756          */
757         if (CC->room.msgnum_info > vbuf.v_lastseen) {
758                 info = 1;
759         }
760
761         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
762         if (cdbfr != NULL) {
763                 msglist = (long *) cdbfr->ptr;
764                 cdbfr->ptr = NULL;      /* CtdlUserGoto() now owns this memory */
765                 num_msgs = cdbfr->len / sizeof(long);
766                 cdb_free(cdbfr);
767         }
768
769         total_messages = 0;
770         for (a=0; a<num_msgs; ++a) {
771                 if (msglist[a] > 0L) ++total_messages;
772         }
773
774         if (total_messages > 0) {
775                 oldest_message = msglist[0];
776                 newest_message = msglist[num_msgs - 1];
777         }
778
779         num_sets = num_tokens(vbuf.v_seen, ',');
780         for (s=0; s<num_sets; ++s) {
781                 extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
782
783                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
784                 if (num_tokens(setstr, ':') >= 2) {
785                         extract_token(histr, setstr, 1, ':', sizeof histr);
786                         if (!strcmp(histr, "*")) {
787                                 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
788                         }
789                 } 
790                 else {
791                         strcpy(histr, lostr);
792                 }
793                 lo = atol(lostr);
794                 hi = atol(histr);
795
796                 for (a=0; a<num_msgs; ++a) if (msglist[a] > 0L) {
797                         if ((msglist[a] >= lo) && (msglist[a] <= hi)) {
798                                 ++old_messages;
799                                 msglist[a] = 0L;
800                         }
801                 }
802         }
803         new_messages = total_messages - old_messages;
804
805         if (msglist != NULL) free(msglist);
806
807         if (CC->room.QRflags & QR_MAILBOX)
808                 rmailflag = 1;
809         else
810                 rmailflag = 0;
811
812         if ((CC->room.QRroomaide == CC->user.usernum) || (CC->user.axlevel >= AxAideU))
813                 raideflag = 1;
814         else
815                 raideflag = 0;
816
817         safestrncpy(truncated_roomname, CC->room.QRname, sizeof truncated_roomname);
818         if ( (CC->room.QRflags & QR_MAILBOX) && (atol(CC->room.QRname) == CC->user.usernum) ) {
819                 safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
820         }
821
822         if (!strcasecmp(truncated_roomname, USERTRASHROOM)) {
823                 is_trash = 1;
824         }
825
826         if (retmsgs != NULL) *retmsgs = total_messages;
827         if (retnew != NULL) *retnew = new_messages;
828         if (retoldest != NULL) *retoldest = oldest_message;
829         if (retnewest != NULL) *retnewest = newest_message;
830         syslog(LOG_DEBUG, "room_ops: %s : %d new of %d total messages, oldest=%ld, newest=%ld",
831                    CC->room.QRname, new_messages, total_messages, oldest_message, newest_message
832         );
833
834         CC->curr_view = (int)vbuf.v_view;
835
836         if (display_result) {
837                 cprintf("%d%c%s|%d|%d|%d|%d|%ld|%ld|%d|%d|%d|%d|%d|%d|%d|%d|%ld|\n",
838                         CIT_OK, CtdlCheckExpress(),
839                         truncated_roomname,
840                         (int)new_messages,
841                         (int)total_messages,
842                         (int)info,
843                         (int)CC->room.QRflags,
844                         (long)CC->room.QRhighest,
845                         (long)vbuf.v_lastseen,
846                         (int)rmailflag,
847                         (int)raideflag,
848                         (int)newmailcount,
849                         (int)CC->room.QRfloor,
850                         (int)vbuf.v_view,
851                         (int)CC->room.QRdefaultview,
852                         (int)is_trash,
853                         (int)CC->room.QRflags2,
854                         (long)CC->room.QRmtime
855                 );
856         }
857 }
858
859
860 /*
861  * Handle some of the macro named rooms
862  */
863 void convert_room_name_macros(char *towhere, size_t maxlen) {
864         if (!strcasecmp(towhere, "_BASEROOM_")) {
865                 safestrncpy(towhere, CtdlGetConfigStr("c_baseroom"), maxlen);
866         }
867         else if (!strcasecmp(towhere, "_MAIL_")) {
868                 safestrncpy(towhere, MAILROOM, maxlen);
869         }
870         else if (!strcasecmp(towhere, "_TRASH_")) {
871                 safestrncpy(towhere, USERTRASHROOM, maxlen);
872         }
873         else if (!strcasecmp(towhere, "_DRAFTS_")) {
874                 safestrncpy(towhere, USERDRAFTROOM, maxlen);
875         }
876         else if (!strcasecmp(towhere, "_BITBUCKET_")) {
877                 safestrncpy(towhere, CtdlGetConfigStr("c_twitroom"), maxlen);
878         }
879         else if (!strcasecmp(towhere, "_CALENDAR_")) {
880                 safestrncpy(towhere, USERCALENDARROOM, maxlen);
881         }
882         else if (!strcasecmp(towhere, "_TASKS_")) {
883                 safestrncpy(towhere, USERTASKSROOM, maxlen);
884         }
885         else if (!strcasecmp(towhere, "_CONTACTS_")) {
886                 safestrncpy(towhere, USERCONTACTSROOM, maxlen);
887         }
888         else if (!strcasecmp(towhere, "_NOTES_")) {
889                 safestrncpy(towhere, USERNOTESROOM, maxlen);
890         }
891 }
892
893
894 /*
895  * Back end function to rename a room.
896  * You can also specify which floor to move the room to, or specify -1 to
897  * keep the room on the same floor it was on.
898  *
899  * If you are renaming a mailbox room, you must supply the namespace prefix
900  * in *at least* the old name!
901  */
902 int CtdlRenameRoom(char *old_name, char *new_name, int new_floor) {
903         int old_floor = 0;
904         struct ctdlroom qrbuf;
905         struct ctdlroom qrtmp;
906         int ret = 0;
907         struct floor *fl;
908         struct floor flbuf;
909         long owner = 0L;
910         char actual_old_name[ROOMNAMELEN];
911
912         syslog(LOG_DEBUG, "room_ops: CtdlRenameRoom(%s, %s, %d)", old_name, new_name, new_floor);
913
914         if (new_floor >= 0) {
915                 fl = CtdlGetCachedFloor(new_floor);
916                 if ((fl->f_flags & F_INUSE) == 0) {
917                         return(crr_invalid_floor);
918                 }
919         }
920
921         begin_critical_section(S_ROOMS);
922
923         if ( (CtdlGetRoom(&qrtmp, new_name) == 0) && (strcasecmp(new_name, old_name)) ) {
924                 ret = crr_already_exists;
925         }
926
927         else if (CtdlGetRoom(&qrbuf, old_name) != 0) {
928                 ret = crr_room_not_found;
929         }
930
931         else if ( (CC->user.axlevel < AxAideU) && (!CC->internal_pgm)
932                   && (CC->user.usernum != qrbuf.QRroomaide)
933                   && ( (((qrbuf.QRflags & QR_MAILBOX) == 0) || (atol(qrbuf.QRname) != CC->user.usernum))) )  {
934                 ret = crr_access_denied;
935         }
936
937         else if (CtdlIsNonEditable(&qrbuf)) {
938                 ret = crr_noneditable;
939         }
940
941         else {
942                 /* Rename it */
943                 safestrncpy(actual_old_name, qrbuf.QRname, sizeof actual_old_name);
944                 if (qrbuf.QRflags & QR_MAILBOX) {
945                         owner = atol(qrbuf.QRname);
946                 }
947                 if ( (owner > 0L) && (atol(new_name) == 0L) ) {
948                         snprintf(qrbuf.QRname, sizeof(qrbuf.QRname),
949                                         "%010ld.%s", owner, new_name);
950                 }
951                 else {
952                         safestrncpy(qrbuf.QRname, new_name,
953                                                 sizeof(qrbuf.QRname));
954                 }
955
956                 /* Reject change of floor for baseroom/aideroom */
957                 if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN) ||
958                     !strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN))
959                 {
960                         new_floor = 0;
961                 }
962
963                 /* Take care of floor stuff */
964                 old_floor = qrbuf.QRfloor;
965                 if (new_floor < 0) {
966                         new_floor = old_floor;
967                 }
968                 qrbuf.QRfloor = new_floor;
969                 CtdlPutRoom(&qrbuf);
970
971                 begin_critical_section(S_CONFIG);
972         
973                 /* If baseroom/aideroom name changes, update config */
974                 if (!strncasecmp(old_name, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
975                         CtdlSetConfigStr("c_baseroom", new_name);
976                 }
977                 if (!strncasecmp(old_name, CtdlGetConfigStr("c_aideroom"), ROOMNAMELEN)) {
978                         CtdlSetConfigStr("c_aideroom", new_name);
979                 }
980         
981                 end_critical_section(S_CONFIG);
982         
983                 /* If the room name changed, then there are now two room
984                  * records, so we have to delete the old one.
985                  */
986                 if (strcasecmp(new_name, old_name)) {
987                         b_deleteroom(actual_old_name);
988                 }
989
990                 ret = crr_ok;
991         }
992
993         end_critical_section(S_ROOMS);
994
995         /* Adjust the floor reference counts if necessary */
996         if (new_floor != old_floor) {
997                 lgetfloor(&flbuf, old_floor);
998                 --flbuf.f_ref_count;
999                 lputfloor(&flbuf, old_floor);
1000                 syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", old_floor, flbuf.f_ref_count);
1001                 lgetfloor(&flbuf, new_floor);
1002                 ++flbuf.f_ref_count;
1003                 lputfloor(&flbuf, new_floor);
1004                 syslog(LOG_DEBUG, "room_ops: reference count for floor %d is now %d", new_floor, flbuf.f_ref_count);
1005         }
1006
1007         /* ...and everybody say "YATTA!" */     
1008         return(ret);
1009 }
1010
1011
1012 /*
1013  * Asynchronously schedule a room for deletion.  By placing the room into an invalid private namespace,
1014  * the room will appear deleted to the user(s), but the session doesn't need to block while waiting for
1015  * database operations to complete.  Instead, the room gets purged when THE DREADED AUTO-PURGER makes
1016  * its next run.  Aren't we so clever?!!
1017  */
1018 void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf) {
1019         char old_name[ROOMNAMELEN];
1020         static int seq = 0;
1021
1022         syslog(LOG_NOTICE, "room_ops: scheduling room <%s> for deletion", qrbuf->QRname);
1023
1024         safestrncpy(old_name, qrbuf->QRname, sizeof old_name);
1025         CtdlGetRoom(qrbuf, qrbuf->QRname);
1026
1027         /* Turn the room into a private mailbox owned by a user who doesn't
1028          * exist.  This will immediately make the room invisible to everyone,
1029          * and qualify the room for purging.
1030          */
1031         snprintf(qrbuf->QRname, sizeof qrbuf->QRname, "9999999999.%08lx.%04d.%s",
1032                 time(NULL),
1033                 ++seq,
1034                 old_name
1035         );
1036         qrbuf->QRflags |= QR_MAILBOX;
1037         time(&qrbuf->QRgen);    /* Use a timestamp as the new generation number  */
1038         CtdlPutRoom(qrbuf);
1039         b_deleteroom(old_name);
1040 }
1041
1042
1043 /*
1044  * Back end processing to delete a room and everything associated with it
1045  * (This one is synchronous and should only get called by THE DREADED
1046  * AUTO-PURGER in serv_expire.c.  All user-facing code should call
1047  * the asynchronous schedule_room_for_deletion() instead.)
1048  */
1049 void CtdlDeleteRoom(struct ctdlroom *qrbuf) {
1050         struct floor flbuf;
1051         char configdbkeyname[25];
1052
1053         syslog(LOG_NOTICE, "room_ops: deleting room <%s>", qrbuf->QRname);
1054
1055         // Delete the room's network configdb entry
1056         netcfg_keyname(configdbkeyname, qrbuf->QRnumber);
1057         CtdlDelConfig(configdbkeyname);
1058
1059         // Delete the messages in the room
1060         // (Careful: this opens an S_ROOMS critical section!)
1061         CtdlDeleteMessages(qrbuf->QRname, NULL, 0, "");
1062
1063         // Flag the room record as not in use
1064         CtdlGetRoomLock(qrbuf, qrbuf->QRname);
1065         qrbuf->QRflags = 0;
1066         CtdlPutRoomLock(qrbuf);
1067
1068         // then decrement the reference count for the floor
1069         lgetfloor(&flbuf, (int) (qrbuf->QRfloor));
1070         flbuf.f_ref_count = flbuf.f_ref_count - 1;
1071         lputfloor(&flbuf, (int) (qrbuf->QRfloor));
1072
1073         // Delete the room record from the database!
1074         b_deleteroom(qrbuf->QRname);
1075 }
1076
1077
1078 /*
1079  * Check access control for deleting a room
1080  */
1081 int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr) {
1082
1083         if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1084                 return(0);
1085         }
1086
1087         if (CtdlIsNonEditable(qr)) {
1088                 return(0);
1089         }
1090
1091         /*
1092          * For mailboxes, check stuff
1093          */
1094         if (qr->QRflags & QR_MAILBOX) {
1095
1096                 if (strlen(qr->QRname) < 12) return(0); /* bad name */
1097
1098                 if (atol(qr->QRname) != CC->user.usernum) {
1099                         return(0);      /* not my room */
1100                 }
1101
1102                 /* Can't delete your Mail> room */
1103                 if (!strcasecmp(&qr->QRname[11], MAILROOM)) return(0);
1104
1105                 /* Otherwise it's ok */
1106                 return(1);
1107         }
1108
1109         /*
1110          * For normal rooms, just check for admin or room admin status.
1111          */
1112         return(is_room_aide());
1113 }
1114
1115
1116 /*
1117  * Internal code to create a new room (returns room flags)
1118  *
1119  * Room types:  0=public, 1=hidden, 2=passworded, 3=invitation-only,
1120  *              4=mailbox, 5=mailbox, but caller supplies namespace
1121  */
1122 unsigned CtdlCreateRoom(char *new_room_name,
1123                      int new_room_type,
1124                      char *new_room_pass,
1125                      int new_room_floor,
1126                      int really_create,
1127                      int avoid_access,
1128                      int new_room_view)
1129 {
1130         struct ctdlroom qrbuf;
1131         struct floor flbuf;
1132         visit vbuf;
1133
1134         syslog(LOG_DEBUG, "room_ops: CtdlCreateRoom(name=%s, type=%d, view=%d)", new_room_name, new_room_type, new_room_view);
1135
1136         if (CtdlGetRoom(&qrbuf, new_room_name) == 0) {
1137                 syslog(LOG_DEBUG, "room_ops: cannot create room <%s> - already exists", new_room_name);
1138                 return(0);
1139         }
1140
1141         memset(&qrbuf, 0, sizeof(struct ctdlroom));
1142         safestrncpy(qrbuf.QRpasswd, new_room_pass, sizeof qrbuf.QRpasswd);
1143         qrbuf.QRflags = QR_INUSE;
1144         if (new_room_type > 0)
1145                 qrbuf.QRflags = (qrbuf.QRflags | QR_PRIVATE);
1146         if (new_room_type == 1)
1147                 qrbuf.QRflags = (qrbuf.QRflags | QR_GUESSNAME);
1148         if (new_room_type == 2)
1149                 qrbuf.QRflags = (qrbuf.QRflags | QR_PASSWORDED);
1150         if ( (new_room_type == 4) || (new_room_type == 5) ) {
1151                 qrbuf.QRflags = (qrbuf.QRflags | QR_MAILBOX);
1152                 /* qrbuf.QRflags2 |= QR2_SUBJECTREQ; */
1153         }
1154
1155         /* If the user is requesting a personal room, set up the room
1156          * name accordingly (prepend the user number)
1157          */
1158         if (new_room_type == 4) {
1159                 CtdlMailboxName(qrbuf.QRname, sizeof qrbuf.QRname, &CC->user, new_room_name);
1160         }
1161         else {
1162                 safestrncpy(qrbuf.QRname, new_room_name, sizeof qrbuf.QRname);
1163         }
1164
1165         /* If the room is private, and the system administrator has elected
1166          * to automatically grant room admin privileges, do so now.
1167          */
1168         if ((qrbuf.QRflags & QR_PRIVATE) && (CREATAIDE == 1)) {
1169                 qrbuf.QRroomaide = CC->user.usernum;
1170         }
1171         /* Blog owners automatically become room admins of their blogs.
1172          * (In the future we will offer a site-wide configuration setting to suppress this behavior.)
1173          */
1174         else if (new_room_view == VIEW_BLOG) {
1175                 qrbuf.QRroomaide = CC->user.usernum;
1176         }
1177         /* Otherwise, set the room admin to undefined.
1178          */
1179         else {
1180                 qrbuf.QRroomaide = (-1L);
1181         }
1182
1183         /* 
1184          * If the caller is only interested in testing whether this will work,
1185          * return now without creating the room.
1186          */
1187         if (!really_create) return (qrbuf.QRflags);
1188
1189         qrbuf.QRnumber = get_new_room_number();
1190         qrbuf.QRhighest = 0L;   /* No messages in this room yet */
1191         time(&qrbuf.QRgen);     /* Use a timestamp as the generation number */
1192         qrbuf.QRfloor = new_room_floor;
1193         qrbuf.QRdefaultview = new_room_view;
1194
1195         /* save what we just did... */
1196         CtdlPutRoom(&qrbuf);
1197
1198         /* bump the reference count on whatever floor the room is on */
1199         lgetfloor(&flbuf, (int) qrbuf.QRfloor);
1200         flbuf.f_ref_count = flbuf.f_ref_count + 1;
1201         lputfloor(&flbuf, (int) qrbuf.QRfloor);
1202
1203         /* Grant the creator access to the room unless the avoid_access
1204          * parameter was specified.
1205          */
1206         if ( (CC->logged_in) && (avoid_access == 0) ) {
1207                 CtdlGetRelationship(&vbuf, &CC->user, &qrbuf);
1208                 vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT;
1209                 vbuf.v_flags = vbuf.v_flags | V_ACCESS;
1210                 CtdlSetRelationship(&vbuf, &CC->user, &qrbuf);
1211         }
1212
1213         /* resume our happy day */
1214         return (qrbuf.QRflags);
1215 }