Initial version of new room sharing poller. I don't really like this because it...
[citadel.git] / citadel / modules / ctdlproto / serv_rooms.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 <stdlib.h>
16 #include <unistd.h>
17 #include <stdio.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <dirent.h>     /* for cmd_rdir to read contents of the directory */
21 #include <libcitadel.h>
22
23 #include "citserver.h"
24 #include "ctdl_module.h"
25 #include "room_ops.h"
26 #include "config.h"
27
28 /*
29  * Back-back-end for all room listing commands
30  */
31 void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view)
32 {
33         char truncated_roomname[ROOMNAMELEN];
34
35         /* For my own mailbox rooms, chop off the owner prefix */
36         if ( (qrbuf->QRflags & QR_MAILBOX)
37              && (atol(qrbuf->QRname) == CC->user.usernum) ) {
38                 safestrncpy(truncated_roomname, qrbuf->QRname, sizeof truncated_roomname);
39                 safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname);
40                 cprintf("%s", truncated_roomname);
41         }
42         /* For all other rooms, just display the name in its entirety */
43         else {
44                 cprintf("%s", qrbuf->QRname);
45         }
46
47         /* ...and now the other parameters */
48         cprintf("|%u|%d|%d|%d|%d|%d|%d|%ld|\n",
49                 qrbuf->QRflags,
50                 (int) qrbuf->QRfloor,
51                 (int) qrbuf->QRorder,
52                 (int) qrbuf->QRflags2,
53                 ra,
54                 current_view,
55                 default_view,
56                 qrbuf->QRmtime
57         );
58 }
59
60
61 /* 
62  * cmd_lrms()   -  List all accessible rooms, known or forgotten
63  */
64 void cmd_lrms_backend(struct ctdlroom *qrbuf, void *data)
65 {
66         int FloorBeingSearched = (-1);
67         int ra;
68         int view;
69
70         FloorBeingSearched = *(int *)data;
71         CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
72
73         if ((( ra & (UA_KNOWN | UA_ZAPPED)))
74             && ((qrbuf->QRfloor == (FloorBeingSearched))
75                 || ((FloorBeingSearched) < 0)))
76                 list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
77 }
78
79 void cmd_lrms(char *argbuf)
80 {
81         int FloorBeingSearched = (-1);
82         if (!IsEmptyStr(argbuf))
83                 FloorBeingSearched = extract_int(argbuf, 0);
84
85         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
86
87         CtdlGetUser(&CC->user, CC->curr_user);
88         cprintf("%d Accessible rooms:\n", LISTING_FOLLOWS);
89
90         CtdlForEachRoom(cmd_lrms_backend, &FloorBeingSearched);
91         cprintf("000\n");
92 }
93
94
95
96 /* 
97  * cmd_lkra()   -  List all known rooms
98  */
99 void cmd_lkra_backend(struct ctdlroom *qrbuf, void *data)
100 {
101         int FloorBeingSearched = (-1);
102         int ra;
103         int view;
104
105         FloorBeingSearched = *(int *)data;
106         CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
107
108         if ((( ra & (UA_KNOWN)))
109             && ((qrbuf->QRfloor == (FloorBeingSearched))
110                 || ((FloorBeingSearched) < 0)))
111                 list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
112 }
113
114 void cmd_lkra(char *argbuf)
115 {
116         int FloorBeingSearched = (-1);
117         if (!IsEmptyStr(argbuf))
118                 FloorBeingSearched = extract_int(argbuf, 0);
119
120         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
121         
122         CtdlGetUser(&CC->user, CC->curr_user);
123         cprintf("%d Known rooms:\n", LISTING_FOLLOWS);
124
125         CtdlForEachRoom(cmd_lkra_backend, &FloorBeingSearched);
126         cprintf("000\n");
127 }
128
129
130
131 void cmd_lprm_backend(struct ctdlroom *qrbuf, void *data)
132 {
133         int FloorBeingSearched = (-1);
134         int ra;
135         int view;
136
137         FloorBeingSearched = *(int *)data;
138         CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
139
140         if (   ((qrbuf->QRflags & QR_PRIVATE) == 0)
141                 && ((qrbuf->QRflags & QR_MAILBOX) == 0)
142             && ((qrbuf->QRfloor == (FloorBeingSearched))
143                 || ((FloorBeingSearched) < 0)))
144                 list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
145 }
146
147 void cmd_lprm(char *argbuf)
148 {
149         int FloorBeingSearched = (-1);
150         if (!IsEmptyStr(argbuf))
151                 FloorBeingSearched = extract_int(argbuf, 0);
152
153         cprintf("%d Public rooms:\n", LISTING_FOLLOWS);
154
155         CtdlForEachRoom(cmd_lprm_backend, &FloorBeingSearched);
156         cprintf("000\n");
157 }
158
159
160
161 /* 
162  * cmd_lkrn()   -  List all known rooms with new messages
163  */
164 void cmd_lkrn_backend(struct ctdlroom *qrbuf, void *data)
165 {
166         int FloorBeingSearched = (-1);
167         int ra;
168         int view;
169
170         FloorBeingSearched = *(int *)data;
171         CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
172
173         if ((ra & UA_KNOWN)
174             && (ra & UA_HASNEWMSGS)
175             && ((qrbuf->QRfloor == (FloorBeingSearched))
176                 || ((FloorBeingSearched) < 0)))
177                 list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
178 }
179
180 void cmd_lkrn(char *argbuf)
181 {
182         int FloorBeingSearched = (-1);
183         if (!IsEmptyStr(argbuf))
184                 FloorBeingSearched = extract_int(argbuf, 0);
185
186         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
187         
188         CtdlGetUser(&CC->user, CC->curr_user);
189         cprintf("%d Rooms w/ new msgs:\n", LISTING_FOLLOWS);
190
191         CtdlForEachRoom(cmd_lkrn_backend, &FloorBeingSearched);
192         cprintf("000\n");
193 }
194
195
196
197 /* 
198  * cmd_lkro()   -  List all known rooms
199  */
200 void cmd_lkro_backend(struct ctdlroom *qrbuf, void *data)
201 {
202         int FloorBeingSearched = (-1);
203         int ra;
204         int view;
205
206         FloorBeingSearched = *(int *)data;
207         CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
208
209         if ((ra & UA_KNOWN)
210             && ((ra & UA_HASNEWMSGS) == 0)
211             && ((qrbuf->QRfloor == (FloorBeingSearched))
212                 || ((FloorBeingSearched) < 0)))
213                 list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
214 }
215
216 void cmd_lkro(char *argbuf)
217 {
218         int FloorBeingSearched = (-1);
219         if (!IsEmptyStr(argbuf))
220                 FloorBeingSearched = extract_int(argbuf, 0);
221
222         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
223         
224         CtdlGetUser(&CC->user, CC->curr_user);
225         cprintf("%d Rooms w/o new msgs:\n", LISTING_FOLLOWS);
226
227         CtdlForEachRoom(cmd_lkro_backend, &FloorBeingSearched);
228         cprintf("000\n");
229 }
230
231
232
233 /* 
234  * cmd_lzrm()   -  List all forgotten rooms
235  */
236 void cmd_lzrm_backend(struct ctdlroom *qrbuf, void *data)
237 {
238         int FloorBeingSearched = (-1);
239         int ra;
240         int view;
241
242         FloorBeingSearched = *(int *)data;
243         CtdlRoomAccess(qrbuf, &CC->user, &ra, &view);
244
245         if ((ra & UA_GOTOALLOWED)
246             && (ra & UA_ZAPPED)
247             && ((qrbuf->QRfloor == (FloorBeingSearched))
248                 || ((FloorBeingSearched) < 0)))
249                 list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview);
250 }
251
252 void cmd_lzrm(char *argbuf)
253 {
254         int FloorBeingSearched = (-1);
255         if (!IsEmptyStr(argbuf))
256                 FloorBeingSearched = extract_int(argbuf, 0);
257
258         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
259         
260         CtdlGetUser(&CC->user, CC->curr_user);
261         cprintf("%d Zapped rooms:\n", LISTING_FOLLOWS);
262
263         CtdlForEachRoom(cmd_lzrm_backend, &FloorBeingSearched);
264         cprintf("000\n");
265 }
266
267
268 /* 
269  * cmd_goto()  -  goto a new room
270  */
271 void cmd_goto(char *gargs)
272 {
273         struct CitContext *CCC = CC;
274         struct ctdlroom QRscratch;
275         int c;
276         int ok = 0;
277         int ra;
278         char augmented_roomname[ROOMNAMELEN];
279         char towhere[ROOMNAMELEN];
280         char password[32];
281         int transiently = 0;
282
283         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
284
285         extract_token(towhere, gargs, 0, '|', sizeof towhere);
286         extract_token(password, gargs, 1, '|', sizeof password);
287         transiently = extract_int(gargs, 2);
288
289         CtdlGetUser(&CCC->user, CCC->curr_user);
290
291         /*
292          * Handle some of the macro named rooms
293          */
294         convert_room_name_macros(towhere, sizeof towhere);
295
296         /* First try a regular match */
297         c = CtdlGetRoom(&QRscratch, towhere);
298
299         /* Then try a mailbox name match */
300         if (c != 0) {
301                 CtdlMailboxName(augmented_roomname, sizeof augmented_roomname,
302                             &CCC->user, towhere);
303                 c = CtdlGetRoom(&QRscratch, augmented_roomname);
304                 if (c == 0)
305                         safestrncpy(towhere, augmented_roomname, sizeof towhere);
306         }
307
308         /* And if the room was found... */
309         if (c == 0) {
310
311                 /* Let internal programs go directly to any room. */
312                 if (CCC->internal_pgm) {
313                         memcpy(&CCC->room, &QRscratch,
314                                 sizeof(struct ctdlroom));
315                         CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
316                         return;
317                 }
318
319                 /* See if there is an existing user/room relationship */
320                 CtdlRoomAccess(&QRscratch, &CCC->user, &ra, NULL);
321
322                 /* normal clients have to pass through security */
323                 if (ra & UA_GOTOALLOWED) {
324                         ok = 1;
325                 }
326
327                 if (ok == 1) {
328                         if ((QRscratch.QRflags & QR_MAILBOX) &&
329                             ((ra & UA_GOTOALLOWED))) {
330                                 memcpy(&CCC->room, &QRscratch,
331                                         sizeof(struct ctdlroom));
332                                 CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
333                                 return;
334                         } else if ((QRscratch.QRflags & QR_PASSWORDED) &&
335                             ((ra & UA_KNOWN) == 0) &&
336                             (strcasecmp(QRscratch.QRpasswd, password)) &&
337                             (CCC->user.axlevel < AxAideU)
338                             ) {
339                                 cprintf("%d wrong or missing passwd\n",
340                                         ERROR + PASSWORD_REQUIRED);
341                                 return;
342                         } else if ((QRscratch.QRflags & QR_PRIVATE) &&
343                                    ((QRscratch.QRflags & QR_PASSWORDED) == 0) &&
344                                    ((QRscratch.QRflags & QR_GUESSNAME) == 0) &&
345                                    ((ra & UA_KNOWN) == 0) &&
346                                    (CCC->user.axlevel < AxAideU)
347                                   ) {
348                                 syslog(LOG_DEBUG, "rooms: failed to acquire private room");
349                         } else {
350                                 memcpy(&CCC->room, &QRscratch,
351                                         sizeof(struct ctdlroom));
352                                 CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL);
353                                 return;
354                         }
355                 }
356         }
357
358         cprintf("%d room '%s' not found\n", ERROR + ROOM_NOT_FOUND, towhere);
359 }
360
361
362 void cmd_whok(char *cmdbuf)
363 {
364         struct ctdluser temp;
365         struct cdbdata *cdbus;
366         int ra;
367
368         cprintf("%d Who knows room:\n", LISTING_FOLLOWS);
369         cdb_rewind(CDB_USERS);
370         while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) {
371                 memset(&temp, 0, sizeof temp);
372                 memcpy(&temp, cdbus->ptr, sizeof temp);
373                 cdb_free(cdbus);
374
375                 CtdlRoomAccess(&CC->room, &temp, &ra, NULL);
376                 if ((!IsEmptyStr(temp.fullname)) && 
377                     (CC->room.QRflags & QR_INUSE) &&
378                     (ra & UA_KNOWN)
379                         )
380                         cprintf("%s\n", temp.fullname);
381         }
382         cprintf("000\n");
383 }
384
385
386 /*
387  * RDIR command for room directory
388  */
389 void cmd_rdir(char *cmdbuf)
390 {
391         char buf[256];
392         char comment[256];
393         FILE *fd;
394         struct stat statbuf;
395         DIR *filedir = NULL;
396         struct dirent *filedir_entry;
397         int d_namelen;
398         char buf2[SIZ];
399         char mimebuf[64];
400         long len;
401         
402         if (CtdlAccessCheck(ac_logged_in)) return;
403         
404         CtdlGetRoom(&CC->room, CC->room.QRname);
405         CtdlGetUser(&CC->user, CC->curr_user);
406
407         if ((CC->room.QRflags & QR_DIRECTORY) == 0) {
408                 cprintf("%d not here.\n", ERROR + NOT_HERE);
409                 return;
410         }
411         if (((CC->room.QRflags & QR_VISDIR) == 0)
412             && (CC->user.axlevel < AxAideU)
413             && (CC->user.usernum != CC->room.QRroomaide)) {
414                 cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
415                 return;
416         }
417
418         snprintf(buf, sizeof buf, "%s/%s", ctdl_file_dir, CC->room.QRdirname);
419         filedir = opendir (buf);
420         
421         if (filedir == NULL) {
422                 cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED);
423                 return;
424         }
425         cprintf("%d %s|%s/%s\n", LISTING_FOLLOWS, CtdlGetConfigStr("c_fqdn"), ctdl_file_dir, CC->room.QRdirname);
426         
427         snprintf(buf, sizeof buf, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname);
428         fd = fopen(buf, "r");
429         if (fd == NULL)
430                 fd = fopen("/dev/null", "r");
431         while ((filedir_entry = readdir(filedir)))
432         {
433                 if (strcasecmp(filedir_entry->d_name, "filedir") && filedir_entry->d_name[0] != '.')
434                 {
435 #ifdef _DIRENT_HAVE_D_NAMELEN
436                         d_namelen = filedir_entry->d_namlen;
437 #else
438                         d_namelen = strlen(filedir_entry->d_name);
439 #endif
440                         snprintf(buf, sizeof buf, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filedir_entry->d_name);
441                         stat(buf, &statbuf);    /* stat the file */
442                         if (!(statbuf.st_mode & S_IFREG))
443                         {
444                                 snprintf(buf2, sizeof buf2,
445                                         "\"%s\" appears in the file directory for room \"%s\" but is not a regular file.  Directories, named pipes, sockets, etc. are not usable in Citadel room directories.\n",
446                                         buf, CC->room.QRname
447                                 );
448                                 CtdlAideMessage(buf2, "Unusable data found in room directory");
449                                 continue;       /* not a useable file type so don't show it */
450                         }
451                         safestrncpy(comment, "", sizeof comment);
452                         fseek(fd, 0L, 0);       /* rewind descriptions file */
453                         /* Get the description from the descriptions file */
454                         while ((fgets(buf, sizeof buf, fd) != NULL) && (IsEmptyStr(comment))) 
455                         {
456                                 buf[strlen(buf) - 1] = 0;
457                                 if ((!strncasecmp(buf, filedir_entry->d_name, d_namelen)) && (buf[d_namelen] == ' '))
458                                         safestrncpy(comment, &buf[d_namelen + 1], sizeof comment);
459                         }
460                         len = extract_token (mimebuf, comment, 0,' ', 64);
461                         if ((len <0) || strchr(mimebuf, '/') == NULL)
462                         {
463                                 snprintf (mimebuf, 64, "application/octetstream");
464                                 len = 0;
465                         }
466                         cprintf("%s|%ld|%s|%s\n", 
467                                 filedir_entry->d_name, 
468                                 (long)statbuf.st_size, 
469                                 mimebuf, 
470                                 &comment[len]);
471                 }
472         }
473         fclose(fd);
474         closedir(filedir);
475         
476         cprintf("000\n");
477 }
478
479 /*
480  * get room parameters (admin or room admin command)
481  */
482 void cmd_getr(char *cmdbuf)
483 {
484         if (CtdlAccessCheck(ac_room_aide)) return;
485
486         CtdlGetRoom(&CC->room, CC->room.QRname);
487         cprintf("%d%c%s|%s|%s|%d|%d|%d|%d|%d|\n",
488                 CIT_OK,
489                 CtdlCheckExpress(),
490
491                 ((CC->room.QRflags & QR_MAILBOX) ?
492                         &CC->room.QRname[11] : CC->room.QRname),
493
494                 ((CC->room.QRflags & QR_PASSWORDED) ?
495                         CC->room.QRpasswd : ""),
496
497                 ((CC->room.QRflags & QR_DIRECTORY) ?
498                         CC->room.QRdirname : ""),
499
500                 CC->room.QRflags,
501                 (int) CC->room.QRfloor,
502                 (int) CC->room.QRorder,
503
504                 CC->room.QRdefaultview,
505                 CC->room.QRflags2
506                 );
507 }
508
509 /*
510  * set room parameters (admin or room admin command)
511  */
512 void cmd_setr(char *args)
513 {
514         char buf[256];
515         int new_order = 0;
516         int r;
517         int new_floor;
518         char new_name[ROOMNAMELEN];
519
520         if (CtdlAccessCheck(ac_logged_in)) return;
521
522         if (num_parms(args) >= 6) {
523                 new_floor = extract_int(args, 5);
524         } else {
525                 new_floor = (-1);       /* don't change the floor */
526         }
527
528         /* When is a new name more than just a new name?  When the old name
529          * has a namespace prefix.
530          */
531         if (CC->room.QRflags & QR_MAILBOX) {
532                 sprintf(new_name, "%010ld.", atol(CC->room.QRname) );
533         } else {
534                 safestrncpy(new_name, "", sizeof new_name);
535         }
536         extract_token(&new_name[strlen(new_name)], args, 0, '|', (sizeof new_name - strlen(new_name)));
537
538         r = CtdlRenameRoom(CC->room.QRname, new_name, new_floor);
539
540         if (r == crr_room_not_found) {
541                 cprintf("%d Internal error - room not found?\n", ERROR + INTERNAL_ERROR);
542         } else if (r == crr_already_exists) {
543                 cprintf("%d '%s' already exists.\n",
544                         ERROR + ALREADY_EXISTS, new_name);
545         } else if (r == crr_noneditable) {
546                 cprintf("%d Cannot edit this room.\n", ERROR + NOT_HERE);
547         } else if (r == crr_invalid_floor) {
548                 cprintf("%d Target floor does not exist.\n",
549                         ERROR + INVALID_FLOOR_OPERATION);
550         } else if (r == crr_access_denied) {
551                 cprintf("%d You do not have permission to edit '%s'\n",
552                         ERROR + HIGHER_ACCESS_REQUIRED,
553                         CC->room.QRname);
554         } else if (r != crr_ok) {
555                 cprintf("%d Error: CtdlRenameRoom() returned %d\n",
556                         ERROR + INTERNAL_ERROR, r);
557         }
558
559         if (r != crr_ok) {
560                 return;
561         }
562
563         CtdlGetRoom(&CC->room, new_name);
564
565         /* Now we have to do a bunch of other stuff */
566
567         if (num_parms(args) >= 7) {
568                 new_order = extract_int(args, 6);
569                 if (new_order < 1)
570                         new_order = 1;
571                 if (new_order > 127)
572                         new_order = 127;
573         }
574
575         CtdlGetRoomLock(&CC->room, CC->room.QRname);
576
577         /* Directory room */
578         extract_token(buf, args, 2, '|', sizeof buf);
579         buf[15] = 0;
580         safestrncpy(CC->room.QRdirname, buf,
581                 sizeof CC->room.QRdirname);
582
583         /* Default view */
584         if (num_parms(args) >= 8) {
585                 CC->room.QRdefaultview = extract_int(args, 7);
586         }
587
588         /* Second set of flags */
589         if (num_parms(args) >= 9) {
590                 CC->room.QRflags2 = extract_int(args, 8);
591         }
592
593         /* Misc. flags */
594         CC->room.QRflags = (extract_int(args, 3) | QR_INUSE);
595         /* Clean up a client boo-boo: if the client set the room to
596          * guess-name or passworded, ensure that the private flag is
597          * also set.
598          */
599         if ((CC->room.QRflags & QR_GUESSNAME)
600             || (CC->room.QRflags & QR_PASSWORDED))
601                 CC->room.QRflags |= QR_PRIVATE;
602
603         /* Some changes can't apply to BASEROOM */
604         if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
605                 CC->room.QRorder = 0;
606                 CC->room.QRpasswd[0] = '\0';
607                 CC->room.QRflags &= ~(QR_PRIVATE & QR_PASSWORDED &
608                         QR_GUESSNAME & QR_PREFONLY & QR_MAILBOX);
609                 CC->room.QRflags |= QR_PERMANENT;
610         } else {        
611                 /* March order (doesn't apply to AIDEROOM) */
612                 if (num_parms(args) >= 7)
613                         CC->room.QRorder = (char) new_order;
614                 /* Room password */
615                 extract_token(buf, args, 1, '|', sizeof buf);
616                 buf[10] = 0;
617                 safestrncpy(CC->room.QRpasswd, buf,
618                             sizeof CC->room.QRpasswd);
619                 /* Kick everyone out if the client requested it
620                  * (by changing the room's generation number)
621                  */
622                 if (extract_int(args, 4)) {
623                         time(&CC->room.QRgen);
624                 }
625         }
626         /* Some changes can't apply to AIDEROOM */
627         if (!strncasecmp(CC->room.QRname, CtdlGetConfigStr("c_baseroom"), ROOMNAMELEN)) {
628                 CC->room.QRorder = 0;
629                 CC->room.QRflags &= ~QR_MAILBOX;
630                 CC->room.QRflags |= QR_PERMANENT;
631         }
632
633         /* Write the room record back to disk */
634         CtdlPutRoomLock(&CC->room);
635
636         /* Create a room directory if necessary */
637         if (CC->room.QRflags & QR_DIRECTORY) {
638                 snprintf(buf, sizeof buf,"%s/%s",
639                                  ctdl_file_dir,
640                                  CC->room.QRdirname);
641                 mkdir(buf, 0755);
642         }
643         snprintf(buf, sizeof buf, "The room \"%s\" has been edited by %s.\n",
644                 CC->room.QRname,
645                 (CC->logged_in ? CC->curr_user : "an administrator")
646         );
647         CtdlAideMessage(buf, "Room modification Message");
648         cprintf("%d Ok\n", CIT_OK);
649 }
650
651
652
653 /* 
654  * get the name of the room admin for this room
655  */
656 void cmd_geta(char *cmdbuf)
657 {
658         struct ctdluser usbuf;
659
660         if (CtdlAccessCheck(ac_logged_in)) return;
661
662         if (CtdlGetUserByNumber(&usbuf, CC->room.QRroomaide) == 0) {
663                 cprintf("%d %s\n", CIT_OK, usbuf.fullname);
664         } else {
665                 cprintf("%d \n", CIT_OK);
666         }
667 }
668
669
670 /* 
671  * set the room admin for this room
672  */
673 void cmd_seta(char *new_ra)
674 {
675         struct ctdluser usbuf;
676         long newu;
677         char buf[SIZ];
678         int post_notice;
679
680         if (CtdlAccessCheck(ac_room_aide)) return;
681
682         if (CtdlGetUser(&usbuf, new_ra) != 0) {
683                 newu = (-1L);
684         } else {
685                 newu = usbuf.usernum;
686         }
687
688         CtdlGetRoomLock(&CC->room, CC->room.QRname);
689         post_notice = 0;
690         if (CC->room.QRroomaide != newu) {
691                 post_notice = 1;
692         }
693         CC->room.QRroomaide = newu;
694         CtdlPutRoomLock(&CC->room);
695
696         /*
697          * We have to post the change notice _after_ writing changes to 
698          * the room table, otherwise it would deadlock!
699          */
700         if (post_notice == 1) {
701                 if (!IsEmptyStr(usbuf.fullname))
702                         snprintf(buf, sizeof buf,
703                                 "%s is now the room admin for \"%s\".\n",
704                                 usbuf.fullname, CC->room.QRname);
705                 else
706                         snprintf(buf, sizeof buf,
707                                 "There is now no room admin for \"%s\".\n",
708                                 CC->room.QRname);
709                 CtdlAideMessage(buf, "Admin Room Modification");
710         }
711         cprintf("%d Ok\n", CIT_OK);
712 }
713
714 /* 
715  * Retrieve info file for this room (this ought to be upgraded to handle non-plain-text)
716  */
717 void cmd_rinf(char *argbuf)
718 {
719         struct CtdlMessage *msg = CtdlFetchMessage(CC->room.msgnum_info, 1, 1);
720         if (msg != NULL) {
721                 cprintf("%d Info:\n", LISTING_FOLLOWS);
722                 CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0);
723                 CM_Free(msg);
724                 cprintf("000\n");
725         }
726         else {
727                 cprintf("%d No info file.\n", ERROR + FILE_NOT_FOUND);
728         }
729 }
730
731
732 /*
733  * admin command: kill the current room
734  */
735 void cmd_kill(char *argbuf)
736 {
737         char deleted_room_name[ROOMNAMELEN];
738         char msg[SIZ];
739         int kill_ok;
740
741         kill_ok = extract_int(argbuf, 0);
742
743         if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room) == 0) {
744                 cprintf("%d Can't delete this room.\n", ERROR + NOT_HERE);
745                 return;
746         }
747         if (kill_ok) {
748                 if (CC->room.QRflags & QR_MAILBOX) {
749                         safestrncpy(deleted_room_name, &CC->room.QRname[11], sizeof deleted_room_name);
750                 }
751                 else {
752                         safestrncpy(deleted_room_name, CC->room.QRname, sizeof deleted_room_name);
753                 }
754
755                 /* Do the dirty work */
756                 CtdlScheduleRoomForDeletion(&CC->room);
757
758                 /* Return to the Lobby */
759                 CtdlUserGoto(CtdlGetConfigStr("c_baseroom"), 0, 0, NULL, NULL, NULL, NULL);
760
761                 /* tell the world what we did */
762                 snprintf(msg, sizeof msg, "The room \"%s\" has been deleted by %s.\n",
763                          deleted_room_name,
764                         (CC->logged_in ? CC->curr_user : "an administrator")
765                 );
766                 CtdlAideMessage(msg, "Room Purger Message");
767                 cprintf("%d '%s' deleted.\n", CIT_OK, deleted_room_name);
768         } else {
769                 cprintf("%d ok to delete.\n", CIT_OK);
770         }
771 }
772
773
774 /*
775  * create a new room
776  */
777 void cmd_cre8(char *args)
778 {
779         int cre8_ok;
780         char new_room_name[ROOMNAMELEN];
781         int new_room_type;
782         char new_room_pass[32];
783         int new_room_floor;
784         int new_room_view;
785         char *notification_message = NULL;
786         unsigned newflags;
787         struct floor *fl;
788         int avoid_access = 0;
789
790         cre8_ok = extract_int(args, 0);
791         extract_token(new_room_name, args, 1, '|', sizeof new_room_name);
792         new_room_name[ROOMNAMELEN - 1] = 0;
793         new_room_type = extract_int(args, 2);
794         extract_token(new_room_pass, args, 3, '|', sizeof new_room_pass);
795         avoid_access = extract_int(args, 5);
796         new_room_view = extract_int(args, 6);
797         new_room_pass[9] = 0;
798         new_room_floor = 0;
799
800         if ((IsEmptyStr(new_room_name)) && (cre8_ok == 1)) {
801                 cprintf("%d Invalid room name.\n", ERROR + ILLEGAL_VALUE);
802                 return;
803         }
804
805         if (!strcasecmp(new_room_name, MAILROOM)) {
806                 cprintf("%d '%s' already exists.\n",
807                         ERROR + ALREADY_EXISTS, new_room_name);
808                 return;
809         }
810
811         if (num_parms(args) >= 5) {
812                 fl = CtdlGetCachedFloor(extract_int(args, 4));
813                 if (fl == NULL) {
814                         cprintf("%d Invalid floor number.\n",
815                                 ERROR + INVALID_FLOOR_OPERATION);
816                         return;
817                 }
818                 else if ((fl->f_flags & F_INUSE) == 0) {
819                         cprintf("%d Invalid floor number.\n",
820                                 ERROR + INVALID_FLOOR_OPERATION);
821                         return;
822                 } else {
823                         new_room_floor = extract_int(args, 4);
824                 }
825         }
826
827         if (CtdlAccessCheck(ac_logged_in)) return;
828
829         if (CC->user.axlevel < CtdlGetConfigInt("c_createax") && !CC->internal_pgm) {
830                 cprintf("%d You need higher access to create rooms.\n",
831                         ERROR + HIGHER_ACCESS_REQUIRED);
832                 return;
833         }
834
835         if ((IsEmptyStr(new_room_name)) && (cre8_ok == 0)) {
836                 cprintf("%d Ok to create rooms.\n", CIT_OK);
837                 return;
838         }
839
840         if ((new_room_type < 0) || (new_room_type > 5)) {
841                 cprintf("%d Invalid room type.\n", ERROR + ILLEGAL_VALUE);
842                 return;
843         }
844
845         if (new_room_type == 5) {
846                 if (CC->user.axlevel < AxAideU) {
847                         cprintf("%d Higher access required\n", 
848                                 ERROR + HIGHER_ACCESS_REQUIRED);
849                         return;
850                 }
851         }
852
853         /* Check to make sure the requested room name doesn't already exist */
854         newflags = CtdlCreateRoom(new_room_name,
855                                 new_room_type, new_room_pass, new_room_floor,
856                                 0, avoid_access, new_room_view);
857         if (newflags == 0) {
858                 cprintf("%d '%s' already exists.\n",
859                         ERROR + ALREADY_EXISTS, new_room_name);
860                 return;
861         }
862
863         if (cre8_ok == 0) {
864                 cprintf("%d OK to create '%s'\n", CIT_OK, new_room_name);
865                 return;
866         }
867
868         /* If we reach this point, the room needs to be created. */
869
870         newflags = CtdlCreateRoom(new_room_name,
871                            new_room_type, new_room_pass, new_room_floor, 1, 0,
872                            new_room_view);
873
874         /* post a message in Aide> describing the new room */
875         notification_message = malloc(1024);
876         snprintf(notification_message, 1024,
877                 "A new room called \"%s\" has been created by %s%s%s%s%s%s\n",
878                 new_room_name,
879                 (CC->logged_in ? CC->curr_user : "an administrator"),
880                 ((newflags & QR_MAILBOX) ? " [personal]" : ""),
881                 ((newflags & QR_PRIVATE) ? " [private]" : ""),
882                 ((newflags & QR_GUESSNAME) ? " [hidden]" : ""),
883                 ((newflags & QR_PASSWORDED) ? " Password: " : ""),
884                 ((newflags & QR_PASSWORDED) ? new_room_pass : "")
885         );
886         CtdlAideMessage(notification_message, "Room Creation Message");
887         free(notification_message);
888
889         cprintf("%d '%s' has been created.\n", CIT_OK, new_room_name);
890 }
891
892
893 /*
894  * Upload the room banner text for this room.
895  * This should be amended to handle content types other than plain text.
896  */
897 void cmd_einf(char *ok)
898 {                               /* enter info file for current room */
899         char buf[SIZ];
900         unbuffer_output();
901
902         if (CtdlAccessCheck(ac_room_aide)) return;
903
904         if (atoi(ok) == 0) {
905                 cprintf("%d Ok.\n", CIT_OK);
906                 return;
907         }
908
909         StrBuf *NewBanner = NewStrBufPlain("Content-type: text/plain; charset=UTF-8\nContent-transfer-encoding: 8bit\n\n", -1);
910
911         cprintf("%d Transmit new banner in plain text now.\n", SEND_LISTING);
912         while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
913                 StrBufAppendBufPlain(NewBanner, buf, -1, 0);
914                 StrBufAppendBufPlain(NewBanner, HKEY("\n"), 0);
915         }
916
917         // We have read the new banner from the user , now save it
918         long new_msgnum = quickie_message("Citadel", NULL, NULL, SYSCONFIGROOM, ChrPtr(NewBanner), FMT_RFC822, "Banner submitted with EINF command");
919         FreeStrBuf(&NewBanner);
920
921         // Update the room record with a pointer to our new banner
922         CtdlGetRoomLock(&CC->room, CC->room.QRname);
923         long old_msgnum = CC->room.msgnum_info;
924         CC->room.msgnum_info = new_msgnum;
925         CtdlPutRoomLock(&CC->room);
926
927         // Delete the old one
928         CtdlDeleteMessages(SYSCONFIGROOM, &old_msgnum, 1, "");
929 }
930
931
932 /* 
933  * cmd_lflr()   -  List all known floors
934  */
935 void cmd_lflr(char *gargs)
936 {
937         int a;
938         struct floor flbuf;
939
940         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
941
942         cprintf("%d Known floors:\n", LISTING_FOLLOWS);
943
944         for (a = 0; a < MAXFLOORS; ++a) {
945                 CtdlGetFloor(&flbuf, a);
946                 if (flbuf.f_flags & F_INUSE) {
947                         cprintf("%d|%s|%d\n",
948                                 a,
949                                 flbuf.f_name,
950                                 flbuf.f_ref_count);
951                 }
952         }
953         cprintf("000\n");
954 }
955
956
957
958 /*
959  * create a new floor
960  */
961 void cmd_cflr(char *argbuf)
962 {
963         char new_floor_name[256];
964         struct floor flbuf;
965         int cflr_ok;
966         int free_slot = (-1);
967         int a;
968
969         extract_token(new_floor_name, argbuf, 0, '|', sizeof new_floor_name);
970         cflr_ok = extract_int(argbuf, 1);
971
972         if (CtdlAccessCheck(ac_aide)) return;
973
974         if (IsEmptyStr(new_floor_name)) {
975                 cprintf("%d Blank floor name not allowed.\n",
976                         ERROR + ILLEGAL_VALUE);
977                 return;
978         }
979
980         for (a = 0; a < MAXFLOORS; ++a) {
981                 CtdlGetFloor(&flbuf, a);
982
983                 /* note any free slots while we're scanning... */
984                 if (((flbuf.f_flags & F_INUSE) == 0)
985                     && (free_slot < 0))
986                         free_slot = a;
987
988                 /* check to see if it already exists */
989                 if ((!strcasecmp(flbuf.f_name, new_floor_name))
990                     && (flbuf.f_flags & F_INUSE)) {
991                         cprintf("%d Floor '%s' already exists.\n",
992                                 ERROR + ALREADY_EXISTS,
993                                 flbuf.f_name);
994                         return;
995                 }
996         }
997
998         if (free_slot < 0) {
999                 cprintf("%d There is no space available for a new floor.\n",
1000                         ERROR + INVALID_FLOOR_OPERATION);
1001                 return;
1002         }
1003         if (cflr_ok == 0) {
1004                 cprintf("%d ok to create...\n", CIT_OK);
1005                 return;
1006         }
1007         lgetfloor(&flbuf, free_slot);
1008         flbuf.f_flags = F_INUSE;
1009         flbuf.f_ref_count = 0;
1010         safestrncpy(flbuf.f_name, new_floor_name, sizeof flbuf.f_name);
1011         lputfloor(&flbuf, free_slot);
1012         cprintf("%d %d\n", CIT_OK, free_slot);
1013 }
1014
1015
1016
1017 /*
1018  * delete a floor
1019  */
1020 void cmd_kflr(char *argbuf)
1021 {
1022         struct floor flbuf;
1023         int floor_to_delete;
1024         int kflr_ok;
1025         int delete_ok;
1026
1027         floor_to_delete = extract_int(argbuf, 0);
1028         kflr_ok = extract_int(argbuf, 1);
1029
1030         if (CtdlAccessCheck(ac_aide)) return;
1031
1032         lgetfloor(&flbuf, floor_to_delete);
1033
1034         delete_ok = 1;
1035         if ((flbuf.f_flags & F_INUSE) == 0) {
1036                 cprintf("%d Floor %d not in use.\n",
1037                         ERROR + INVALID_FLOOR_OPERATION, floor_to_delete);
1038                 delete_ok = 0;
1039         } else {
1040                 if (flbuf.f_ref_count != 0) {
1041                         cprintf("%d Cannot delete; floor contains %d rooms.\n",
1042                                 ERROR + INVALID_FLOOR_OPERATION,
1043                                 flbuf.f_ref_count);
1044                         delete_ok = 0;
1045                 } else {
1046                         if (kflr_ok == 1) {
1047                                 cprintf("%d Ok\n", CIT_OK);
1048                         } else {
1049                                 cprintf("%d Ok to delete...\n", CIT_OK);
1050                         }
1051
1052                 }
1053
1054         }
1055
1056         if ((delete_ok == 1) && (kflr_ok == 1))
1057                 flbuf.f_flags = 0;
1058         lputfloor(&flbuf, floor_to_delete);
1059 }
1060
1061 /*
1062  * edit a floor
1063  */
1064 void cmd_eflr(char *argbuf)
1065 {
1066         struct floor flbuf;
1067         int floor_num;
1068         int np;
1069
1070         np = num_parms(argbuf);
1071         if (np < 1) {
1072                 cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE);
1073                 return;
1074         }
1075
1076         if (CtdlAccessCheck(ac_aide)) return;
1077
1078         floor_num = extract_int(argbuf, 0);
1079         lgetfloor(&flbuf, floor_num);
1080         if ((flbuf.f_flags & F_INUSE) == 0) {
1081                 lputfloor(&flbuf, floor_num);
1082                 cprintf("%d Floor %d is not in use.\n",
1083                         ERROR + INVALID_FLOOR_OPERATION, floor_num);
1084                 return;
1085         }
1086         if (np >= 2)
1087                 extract_token(flbuf.f_name, argbuf, 1, '|', sizeof flbuf.f_name);
1088         lputfloor(&flbuf, floor_num);
1089
1090         cprintf("%d Ok\n", CIT_OK);
1091 }
1092
1093
1094
1095 /* 
1096  * cmd_stat()  -  return the modification time of the current room (maybe other things in the future)
1097  */
1098 void cmd_stat(char *gargs)
1099 {
1100         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
1101         CtdlGetRoom(&CC->room, CC->room.QRname);
1102         cprintf("%d %s|%ld|\n", CIT_OK, CC->room.QRname, CC->room.QRmtime);
1103 }
1104
1105
1106
1107
1108 /*****************************************************************************/
1109 /*                      MODULE INITIALIZATION STUFF                          */
1110 /*****************************************************************************/
1111
1112 CTDL_MODULE_INIT(rooms)
1113 {
1114         if (!threading) {
1115                 CtdlRegisterProtoHook(cmd_lrms, "LRMS", "List rooms");
1116                 CtdlRegisterProtoHook(cmd_lkra, "LKRA", "List all known rooms");
1117                 CtdlRegisterProtoHook(cmd_lkrn, "LKRN", "List known rooms with new messages");
1118                 CtdlRegisterProtoHook(cmd_lkro, "LKRO", "List known rooms without new messages");
1119                 CtdlRegisterProtoHook(cmd_lzrm, "LZRM", "List zapped rooms");
1120                 CtdlRegisterProtoHook(cmd_lprm, "LPRM", "List public rooms");
1121                 CtdlRegisterProtoHook(cmd_goto, "GOTO", "Goto a named room");
1122                 CtdlRegisterProtoHook(cmd_stat, "STAT", "Get mtime of the current room");
1123                 CtdlRegisterProtoHook(cmd_whok, "WHOK", "List users who know this room");
1124                 CtdlRegisterProtoHook(cmd_rdir, "RDIR", "List files in room directory");
1125                 CtdlRegisterProtoHook(cmd_getr, "GETR", "Get room parameters");
1126                 CtdlRegisterProtoHook(cmd_setr, "SETR", "Set room parameters");
1127                 CtdlRegisterProtoHook(cmd_geta, "GETA", "Get the room admin name");
1128                 CtdlRegisterProtoHook(cmd_seta, "SETA", "Set the room admin for this room");
1129                 CtdlRegisterProtoHook(cmd_rinf, "RINF", "Fetch room info file");
1130                 CtdlRegisterProtoHook(cmd_kill, "KILL", "Kill (delete) the current room");
1131                 CtdlRegisterProtoHook(cmd_cre8, "CRE8", "Create a new room");
1132                 CtdlRegisterProtoHook(cmd_einf, "EINF", "Enter info file for the current room");
1133                 CtdlRegisterProtoHook(cmd_lflr, "LFLR", "List all known floors");
1134                 CtdlRegisterProtoHook(cmd_cflr, "CFLR", "Create a new floor");
1135                 CtdlRegisterProtoHook(cmd_kflr, "KFLR", "Kill a floor");
1136                 CtdlRegisterProtoHook(cmd_eflr, "EFLR", "Edit a floor");
1137         }
1138         /* return our Subversion id for the Log */
1139         return "rooms";
1140 }