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