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