* New UI for mailing list setup
[citadel.git] / citadel / rooms.c
1 /*
2  * $Id$
3  *
4  * 
5  * Client-side functions which perform room operations
6  *
7  */
8
9 #include "sysdep.h"
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <fcntl.h>
13 #include <stdio.h>
14 #include <ctype.h>
15 #include <string.h>
16 #include <signal.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <sys/wait.h>
20 #include <errno.h>
21 #include <stdarg.h>
22 #include "citadel.h"
23 #include "rooms.h"
24 #include "commands.h"
25 #include "tools.h"
26 #include "messages.h"
27 #ifndef HAVE_SNPRINTF
28 #include "snprintf.h"
29 #endif
30
31 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
32
33
34 void sttybbs(int cmd);
35 void hit_any_key(void);
36 int yesno(void);
37 void strprompt(char *prompt, char *str, int len);
38 void newprompt(char *prompt, char *str, int len);
39 void dotgoto(char *towhere, int display_name);
40 void serv_read(char *buf, int bytes);
41 void formout(char *name);
42 int inkey(void);
43 int fmout(int width, FILE * fp, char pagin, int height, int starting_lp,
44           char subst);
45 void progress(long int curr, long int cmax);
46 int pattern(char *search, char *patn);
47 int file_checksum(char *filename);
48 int nukedir(char *dirname);
49
50 extern unsigned room_flags;
51 extern char room_name[];
52 extern char temp[];
53 extern char tempdir[];
54 extern int editor_pid;
55 extern char editor_path[];
56 extern int screenwidth;
57 extern int screenheight;
58 extern char fullname[];
59 extern int userflags;
60 extern char sigcaught;
61 extern char floor_mode;
62 extern char curr_floor;
63
64
65 extern int ugnum;
66 extern long uglsn;
67 extern char ugname[];
68
69 extern char floorlist[128][SIZ];
70
71
72 void load_floorlist(void)
73 {
74         int a;
75         char buf[SIZ];
76
77         for (a = 0; a < 128; ++a)
78                 floorlist[a][0] = 0;
79
80         serv_puts("LFLR");
81         serv_gets(buf);
82         if (buf[0] != '1') {
83                 strcpy(floorlist[0], "Main Floor");
84                 return;
85         }
86         while (serv_gets(buf), strcmp(buf, "000")) {
87                 extract(floorlist[extract_int(buf, 0)], buf, 1);
88         }
89 }
90
91
92 void room_tree_list(struct roomlisting *rp)
93 {
94         static int c = 0;
95         char rmname[ROOMNAMELEN];
96         int f;
97
98         if (rp == NULL) {
99                 c = 1;
100                 return;
101         }
102
103         if (rp->lnext != NULL) {
104                 room_tree_list(rp->lnext);
105         }
106
107         if (sigcaught == 0) {
108                 strcpy(rmname, rp->rlname);
109                 f = rp->rlflags;
110                 if ((c + strlen(rmname) + 4) > screenwidth) {
111
112                         /* line break, check the paginator */
113                         pprintf("\n");
114                         c = 1;
115                 }
116                 if (f & QR_MAILBOX) {
117                         color(BRIGHT_YELLOW);
118                 } else if (f & QR_PRIVATE) {
119                         color(BRIGHT_RED);
120                 } else {
121                         color(DIM_WHITE);
122                 }
123                 pprintf("%s", rmname);
124                 if ((f & QR_DIRECTORY) && (f & QR_NETWORK))
125                         pprintf("}  ");
126                 else if (f & QR_DIRECTORY)
127                         pprintf("]  ");
128                 else if (f & QR_NETWORK)
129                         pprintf(")  ");
130                 else
131                         pprintf(">  ");
132                 c = c + strlen(rmname) + 3;
133         }
134
135         if (rp->rnext != NULL) {
136                 room_tree_list(rp->rnext);
137         }
138
139         free(rp);
140 }
141
142
143 /* 
144  * Room ordering stuff (compare first by floor, then by order)
145  */
146 int rordercmp(struct roomlisting *r1, struct roomlisting *r2)
147 {
148         if ((r1 == NULL) && (r2 == NULL))
149                 return (0);
150         if (r1 == NULL)
151                 return (-1);
152         if (r2 == NULL)
153                 return (1);
154         if (r1->rlfloor < r2->rlfloor)
155                 return (-1);
156         if (r1->rlfloor > r2->rlfloor)
157                 return (1);
158         if (r1->rlorder < r2->rlorder)
159                 return (-1);
160         if (r1->rlorder > r2->rlorder)
161                 return (1);
162         return (0);
163 }
164
165
166 /*
167  * Common code for all room listings
168  */
169 void listrms(char *variety)
170 {
171         char buf[SIZ];
172
173         struct roomlisting *rl = NULL;
174         struct roomlisting *rp;
175         struct roomlisting *rs;
176
177
178         /* Ask the server for a room list */
179         serv_puts(variety);
180         serv_gets(buf);
181         if (buf[0] != '1') {
182                 return;
183         }
184         while (serv_gets(buf), strcmp(buf, "000")) {
185                 rp = malloc(sizeof(struct roomlisting));
186                 extract(rp->rlname, buf, 0);
187                 rp->rlflags = extract_int(buf, 1);
188                 rp->rlfloor = extract_int(buf, 2);
189                 rp->rlorder = extract_int(buf, 3);
190                 rp->lnext = NULL;
191                 rp->rnext = NULL;
192
193                 rs = rl;
194                 if (rl == NULL) {
195                         rl = rp;
196                 } else {
197                         while (rp != NULL) {
198                                 if (rordercmp(rp, rs) < 0) {
199                                         if (rs->lnext == NULL) {
200                                                 rs->lnext = rp;
201                                                 rp = NULL;
202                                         } else {
203                                                 rs = rs->lnext;
204                                         }
205                                 } else {
206                                         if (rs->rnext == NULL) {
207                                                 rs->rnext = rp;
208                                                 rp = NULL;
209                                         } else {
210                                                 rs = rs->rnext;
211                                         }
212                                 }
213                         }
214                 }
215         }
216
217         room_tree_list(NULL);
218         room_tree_list(rl);
219         color(DIM_WHITE);
220 }
221
222
223 void list_other_floors(void)
224 {
225         int a, c;
226
227         c = 1;
228         for (a = 0; a < 128; ++a) {
229                 if ((strlen(floorlist[a]) > 0) && (a != curr_floor)) {
230                         if ((c + strlen(floorlist[a]) + 4) > screenwidth) {
231                                 pprintf("\n");
232                                 c = 1;
233                         }
234                         pprintf("%s:  ", floorlist[a]);
235                         c = c + strlen(floorlist[a]) + 3;
236                 }
237         }
238 }
239
240
241 /*
242  * List known rooms.  kn_floor_mode should be set to 0 for a 'flat' listing,
243  * 1 to list rooms on the current floor, or 1 to list rooms on all floors.
244  */
245 void knrooms(int kn_floor_mode)
246 {
247         char buf[SIZ];
248         int a;
249
250         load_floorlist();
251
252         if (kn_floor_mode == 0) {
253                 color(BRIGHT_CYAN);
254                 pprintf("\n   Rooms with unread messages:\n");
255                 listrms("LKRN");
256                 color(BRIGHT_CYAN);
257                 pprintf("\n\n   No unseen messages in:\n");
258                 listrms("LKRO");
259                 pprintf("\n");
260         }
261
262         if (kn_floor_mode == 1) {
263                 color(BRIGHT_CYAN);
264                 pprintf("\n   Rooms with unread messages on %s:\n",
265                         floorlist[(int) curr_floor]);
266                 sprintf(buf, "LKRN %d", curr_floor);
267                 listrms(buf);
268                 color(BRIGHT_CYAN);
269                 pprintf("\n\n   Rooms with no new messages on %s:\n",
270                         floorlist[(int) curr_floor]);
271                 sprintf(buf, "LKRO %d", curr_floor);
272                 listrms(buf);
273                 color(BRIGHT_CYAN);
274                 pprintf("\n\n   Other floors:\n");
275                 list_other_floors();
276                 pprintf("\n");
277         }
278
279         if (kn_floor_mode == 2) {
280                 for (a = 0; a < 128; ++a) {
281                         if (floorlist[a][0] != 0) {
282                                 color(BRIGHT_CYAN);
283                                 pprintf("\n   Rooms on %s:\n",
284                                         floorlist[a]);
285                                 sprintf(buf, "LKRA %d", a);
286                                 listrms(buf);
287                                 pprintf("\n");
288                         }
289                 }
290         }
291
292         color(DIM_WHITE);
293         IFNEXPERT hit_any_key();
294 }
295
296
297 void listzrooms(void)
298 {                               /* list public forgotten rooms */
299         color(BRIGHT_CYAN);
300         pprintf("\n   Forgotten public rooms:\n");
301         listrms("LZRM");
302         pprintf("\n");
303         color(DIM_WHITE);
304         IFNEXPERT hit_any_key();
305 }
306
307
308 int set_room_attr(int ibuf, char *prompt, unsigned int sbit)
309 {
310         int a;
311
312         a = boolprompt(prompt, (ibuf & sbit));
313         ibuf = (ibuf | sbit);
314         if (!a) {
315                 ibuf = (ibuf ^ sbit);
316         }
317         return (ibuf);
318 }
319
320
321
322 /*
323  * Select a floor (used in several commands)
324  * The supplied argument is the 'default' floor number.
325  * This function returns the selected floor number.
326  */
327 int select_floor(int rfloor)
328 {
329         int a, newfloor;
330         char floorstr[SIZ];
331
332         if (floor_mode == 1) {
333                 if (floorlist[(int) curr_floor][0] == 0) {
334                         load_floorlist();
335                 }
336
337                 do {
338                         newfloor = (-1);
339                         safestrncpy(floorstr, floorlist[rfloor],
340                                     sizeof floorstr);
341                         strprompt("Which floor", floorstr, SIZ);
342                         for (a = 0; a < 128; ++a) {
343                                 if (!strcasecmp
344                                     (floorstr, &floorlist[a][0]))
345                                         newfloor = a;
346                                 if ((newfloor < 0)
347                                     &&
348                                     (!strncasecmp
349                                      (floorstr, &floorlist[a][0],
350                                       strlen(floorstr))))
351                                         newfloor = a;
352                                 if ((newfloor < 0)
353                                     && (pattern(&floorlist[a][0], floorstr)
354                                         >= 0))
355                                         newfloor = a;
356                         }
357                         if (newfloor < 0) {
358                                 printf("\n One of:\n");
359                                 for (a = 0; a < 128; ++a) {
360                                         if (floorlist[a][0] != 0) {
361                                                 printf("%s\n",
362                                                        &floorlist[a][0]);
363                                         }
364                                 }
365                         }
366                 } while (newfloor < 0);
367                 return (newfloor);
368         }
369         return (rfloor);
370 }
371
372
373
374
375 /*
376  * .<A>ide <E>dit room
377  */
378 void editthisroom(void)
379 {
380         char rname[ROOMNAMELEN];
381         char rpass[10];
382         char rdir[15];
383         unsigned rflags;
384         int rbump;
385         char raide[32];
386         char buf[SIZ];
387         int rfloor;
388         int rorder;
389         int expire_mode = 0;
390         int expire_value = 0;
391
392         /* Fetch the existing room config */
393         serv_puts("GETR");
394         serv_gets(buf);
395         if (buf[0] != '2') {
396                 printf("%s\n", &buf[4]);
397                 return;
398         }
399
400         extract(rname, &buf[4], 0);
401         extract(rpass, &buf[4], 1);
402         extract(rdir, &buf[4], 2);
403         rflags = extract_int(&buf[4], 3);
404         rfloor = extract_int(&buf[4], 4);
405         rorder = extract_int(&buf[4], 5);
406         rbump = 0;
407
408         /* Fetch the name of the current room aide */
409         serv_puts("GETA");
410         serv_gets(buf);
411         if (buf[0] == '2') {
412                 safestrncpy(raide, &buf[4], sizeof raide);
413         }
414         else {
415                 strcpy(raide, "");
416         }
417         if (strlen(raide) == 0) {
418                 strcpy(raide, "none");
419         }
420
421         /* Fetch the expire policy (this will silently fail on old servers,
422          * resulting in "default" policy)
423          */
424         serv_puts("GPEX room");
425         serv_gets(buf);
426         if (buf[0] == '2') {
427                 expire_mode = extract_int(&buf[4], 0);
428                 expire_value = extract_int(&buf[4], 1);
429         }
430
431         /* Now interact with the user. */
432         strprompt("Room name", rname, ROOMNAMELEN - 1);
433
434         rfloor = select_floor(rfloor);
435         rflags = set_room_attr(rflags, "Private room", QR_PRIVATE);
436         if (rflags & QR_PRIVATE) {
437                 rflags = set_room_attr(rflags,
438                                        "Accessible by guessing room name",
439                                        QR_GUESSNAME);
440         }
441
442         /* if it's public, clear the privacy classes */
443         if ((rflags & QR_PRIVATE) == 0) {
444                 if (rflags & QR_GUESSNAME) {
445                         rflags = rflags - QR_GUESSNAME;
446                 }
447                 if (rflags & QR_PASSWORDED) {
448                         rflags = rflags - QR_PASSWORDED;
449                 }
450         }
451
452         /* if it's private, choose the privacy classes */
453         if ((rflags & QR_PRIVATE)
454             && ((rflags & QR_GUESSNAME) == 0)) {
455                 rflags = set_room_attr(rflags,
456                                        "Accessible by entering a password",
457                                        QR_PASSWORDED);
458         }
459         if ((rflags & QR_PRIVATE)
460             && ((rflags & QR_PASSWORDED) == QR_PASSWORDED)) {
461                 strprompt("Room password", rpass, 9);
462         }
463
464         if ((rflags & QR_PRIVATE) == QR_PRIVATE) {
465                 rbump =
466                     boolprompt("Cause current users to forget room", 0);
467         }
468
469         rflags =
470             set_room_attr(rflags, "Preferred users only", QR_PREFONLY);
471         rflags = set_room_attr(rflags, "Read-only room", QR_READONLY);
472         rflags = set_room_attr(rflags, "Directory room", QR_DIRECTORY);
473         rflags = set_room_attr(rflags, "Permanent room", QR_PERMANENT);
474         if (rflags & QR_DIRECTORY) {
475                 strprompt("Directory name", rdir, 14);
476                 rflags =
477                     set_room_attr(rflags, "Uploading allowed", QR_UPLOAD);
478                 rflags =
479                     set_room_attr(rflags, "Downloading allowed",
480                                   QR_DOWNLOAD);
481                 rflags =
482                     set_room_attr(rflags, "Visible directory", QR_VISDIR);
483         }
484         rflags = set_room_attr(rflags, "Network shared room", QR_NETWORK);
485         rflags = set_room_attr(rflags,
486                                "Automatically make all messages anonymous",
487                                QR_ANONONLY);
488         if ((rflags & QR_ANONONLY) == 0) {
489                 rflags = set_room_attr(rflags,
490                                        "Ask users whether to make messages anonymous",
491                                        QR_ANONOPT);
492         }
493         rorder = intprompt("Listing order", rorder, 1, 127);
494
495         /* Ask about the room aide */
496         do {
497                 strprompt("Room aide (or 'none')", raide, 29);
498                 if (!strcasecmp(raide, "none")) {
499                         strcpy(raide, "");
500                         strcpy(buf, "200");
501                 } else {
502                         snprintf(buf, sizeof buf, "QUSR %s", raide);
503                         serv_puts(buf);
504                         serv_gets(buf);
505                         if (buf[0] != '2')
506                                 printf("%s\n", &buf[4]);
507                 }
508         } while (buf[0] != '2');
509
510         if (!strcasecmp(raide, "none")) {
511                 strcpy(raide, "");
512         }
513
514
515         /* Angels and demons dancing in my head... */
516         do {
517                 sprintf(buf, "%d", expire_mode);
518                 strprompt("Message expire policy (? for list)", buf, 1);
519                 if (buf[0] == '?') {
520                         printf("\n");
521                         printf("0. Use the default for this floor\n");
522                         printf("1. Never automatically expire messages\n");
523                         printf("2. Expire by message count\n");
524                         printf("3. Expire by message age\n");
525                 }
526         } while ((buf[0] < 48) || (buf[0] > 51));
527         expire_mode = buf[0] - 48;
528
529         /* ...lunatics and monsters underneath my bed */
530         if (expire_mode == 2) {
531                 sprintf(buf, "%d", expire_value);
532                 strprompt("Keep how many messages online?", buf, 10);
533                 expire_value = atol(buf);
534         }
535
536         if (expire_mode == 3) {
537                 sprintf(buf, "%d", expire_value);
538                 strprompt("Keep messages for how many days?", buf, 10);
539                 expire_value = atol(buf);
540         }
541
542         /* Give 'em a chance to change their minds */
543         printf("Save changes (y/n)? ");
544
545         if (yesno() == 1) {
546                 snprintf(buf, sizeof buf, "SETA %s", raide);
547                 serv_puts(buf);
548                 serv_gets(buf);
549                 if (buf[0] != '2') {
550                         printf("%s\n", &buf[4]);
551                 }
552
553                 snprintf(buf, sizeof buf, "SPEX room|%d|%d",
554                          expire_mode, expire_value);
555                 serv_puts(buf);
556                 serv_gets(buf);
557
558                 snprintf(buf, sizeof buf, "SETR %s|%s|%s|%d|%d|%d|%d",
559                          rname, rpass, rdir, rflags, rbump, rfloor,
560                          rorder);
561                 serv_puts(buf);
562                 serv_gets(buf);
563                 printf("%s\n", &buf[4]);
564                 if (buf[0] == '2')
565                         dotgoto(rname, 2);
566         }
567 }
568
569
570 /*
571  * un-goto the previous room
572  */
573 void ungoto(void)
574 {
575         char buf[SIZ];
576
577         if (!strcmp(ugname, ""))
578                 return;
579         snprintf(buf, sizeof buf, "GOTO %s", ugname);
580         serv_puts(buf);
581         serv_gets(buf);
582         if (buf[0] != '2') {
583                 printf("%s\n", &buf[4]);
584                 return;
585         }
586         sprintf(buf, "SLRP %ld", uglsn);
587         serv_puts(buf);
588         serv_gets(buf);
589         if (buf[0] != '2') {
590                 printf("%s\n", &buf[4]);
591         }
592         safestrncpy(buf, ugname, sizeof buf);
593         strcpy(ugname, "");
594         dotgoto(buf, 0);
595 }
596
597
598 /* Here's the code for simply transferring the file to the client,
599  * for folks who have their own clientware.  It's a lot simpler than
600  * the [XYZ]modem code below...
601  * (This function assumes that a download file is already open on the server)
602  */
603 void download_to_local_disk(char *supplied_filename, long total_bytes)
604 {
605         char buf[SIZ];
606         char dbuf[4096];
607         long transmitted_bytes = 0L;
608         long aa, bb;
609         FILE *savefp;
610         int broken = 0;
611         int packet;
612         char filename[SIZ];
613
614         strcpy(filename, supplied_filename);
615         if (strlen(filename) == 0) {
616                 newprompt("Filename: ", filename, 250);
617         }
618
619         printf("Enter the name of the directory to save '%s'\n", filename);
620         printf("to, or press return for the current directory.\n");
621         newprompt("Directory: ", dbuf, sizeof dbuf);
622         if (strlen(dbuf) == 0)
623                 strcpy(dbuf, ".");
624         strcat(dbuf, "/");
625         strcat(dbuf, filename);
626
627         savefp = fopen(dbuf, "w");
628         if (savefp == NULL) {
629                 printf("Cannot open '%s': %s\n", dbuf, strerror(errno));
630                 /* close the download file at the server */
631                 serv_puts("CLOS");
632                 serv_gets(buf);
633                 if (buf[0] != '2') {
634                         printf("%s\n", &buf[4]);
635                 }
636                 return;
637         }
638         progress(0, total_bytes);
639         while ((transmitted_bytes < total_bytes) && (broken == 0)) {
640                 bb = total_bytes - transmitted_bytes;
641                 aa = ((bb < 4096) ? bb : 4096);
642                 sprintf(buf, "READ %ld|%ld", transmitted_bytes, aa);
643                 serv_puts(buf);
644                 serv_gets(buf);
645                 if (buf[0] != '6') {
646                         printf("%s\n", &buf[4]);
647                         return;
648                 }
649                 packet = extract_int(&buf[4], 0);
650                 serv_read(dbuf, packet);
651                 if (fwrite(dbuf, packet, 1, savefp) < 1)
652                         broken = 1;
653                 transmitted_bytes = transmitted_bytes + (long) packet;
654                 progress(transmitted_bytes, total_bytes);
655         }
656         fclose(savefp);
657         /* close the download file at the server */
658         serv_puts("CLOS");
659         serv_gets(buf);
660         if (buf[0] != '2') {
661                 printf("%s\n", &buf[4]);
662         }
663         return;
664 }
665
666
667 /*
668  * download()  -  download a file or files.  The argument passed to this
669  *                function determines which protocol to use.
670  *  proto - 0 = paginate, 1 = xmodem, 2 = raw, 3 = ymodem, 4 = zmodem, 5 = save
671  */
672 void download(int proto)
673 {
674         char buf[SIZ];
675         char filename[SIZ];
676         char tempname[SIZ];
677         char transmit_cmd[SIZ];
678         long total_bytes = 0L;
679         char dbuf[4096];
680         long transmitted_bytes = 0L;
681         long aa, bb;
682         int packet;
683         FILE *tpipe = NULL;
684         int broken = 0;
685
686         if ((room_flags & QR_DOWNLOAD) == 0) {
687                 printf("*** You cannot download from this room.\n");
688                 return;
689         }
690
691         newprompt("Enter filename: ", filename, 255);
692
693         snprintf(buf, sizeof buf, "OPEN %s", filename);
694         serv_puts(buf);
695         serv_gets(buf);
696         if (buf[0] != '2') {
697                 printf("%s\n", &buf[4]);
698                 return;
699         }
700         total_bytes = extract_long(&buf[4], 0);
701
702         /* Save to local disk, for folks with their own copy of the client */
703         if (proto == 5) {
704                 download_to_local_disk(filename, total_bytes);
705                 return;
706         }
707
708         /* Meta-download for public clients */
709         printf("Fetching file from Citadel server...\n");
710         mkdir(tempdir, 0700);
711         snprintf(tempname, sizeof tempname, "%s/%s", tempdir, filename);
712         tpipe = fopen(tempname, "wb");
713         while ((transmitted_bytes < total_bytes) && (broken == 0)) {
714                 progress(transmitted_bytes, total_bytes);
715                 bb = total_bytes - transmitted_bytes;
716                 aa = ((bb < 4096) ? bb : 4096);
717                 sprintf(buf, "READ %ld|%ld", transmitted_bytes, aa);
718                 serv_puts(buf);
719                 serv_gets(buf);
720                 if (buf[0] != '6') {
721                         printf("%s\n", &buf[4]);
722                 }
723                 packet = extract_int(&buf[4], 0);
724                 serv_read(dbuf, packet);
725                 if (fwrite(dbuf, packet, 1, tpipe) < 1) {
726                         broken = 1;
727                 }
728                 transmitted_bytes = transmitted_bytes + (long) packet;
729         }
730         fclose(tpipe);
731         progress(transmitted_bytes, total_bytes);
732
733         /* close the download file at the server */
734         serv_puts("CLOS");
735         serv_gets(buf);
736         if (buf[0] != '2') {
737                 printf("%s\n", &buf[4]);
738         }
739
740         if (proto == 0) {
741                 sprintf(transmit_cmd,
742                         "SHELL=/dev/null; export SHELL; TERM=dumb; export TERM; exec more -d <%s",
743                         tempname);
744         }
745         else if (proto == 1)
746                 sprintf(transmit_cmd, "exec sx %s", tempname);
747         else if (proto == 3)
748                 sprintf(transmit_cmd, "exec sb %s", tempname);
749         else if (proto == 4)
750                 sprintf(transmit_cmd, "exec sz %s", tempname);
751         else
752                 sprintf(transmit_cmd, "exec cat %s", tempname);
753
754         sttybbs(SB_RESTORE);
755         system(transmit_cmd);
756         sttybbs(SB_NO_INTR);
757
758         /* clean up the temporary directory */
759         nukedir(tempdir);
760         putc(7, stdout);
761 }
762
763
764 /*
765  * read directory of this room
766  */
767 void roomdir(void)
768 {
769         char flnm[SIZ];
770         char flsz[32];
771         char comment[SIZ];
772         char buf[SIZ];
773
774         serv_puts("RDIR");
775         serv_gets(buf);
776         if (buf[0] != '1') {
777                 pprintf("%s\n", &buf[4]);
778                 return;
779         }
780
781         extract(comment, &buf[4], 0);
782         extract(flnm, &buf[4], 1);
783         pprintf("\nDirectory of %s on %s\n", flnm, comment);
784         pprintf("-----------------------\n");
785         while (serv_gets(buf), strcmp(buf, "000")) {
786                 extract(flnm, buf, 0);
787                 extract(flsz, buf, 1);
788                 extract(comment, buf, 2);
789                 if (strlen(flnm) <= 14)
790                         pprintf("%-14s %8s %s\n", flnm, flsz, comment);
791                 else
792                         pprintf("%s\n%14s %8s %s\n", flnm, "", flsz,
793                                 comment);
794         }
795 }
796
797
798 /*
799  * add a user to a private room
800  */
801 void invite(void)
802 {
803         char aaa[31], bbb[SIZ];
804
805         /* Because kicking people out of public rooms now sets a LOCKOUT
806          * flag, we need to be able to invite people into public rooms
807          * in order to let them back in again.
808          *        - cough
809          */
810
811         /*
812          * if ((room_flags & QR_PRIVATE)==0) {
813          *         printf("This is not a private room.\n");
814          *         return;
815          * }
816          */
817
818         newprompt("Name of user? ", aaa, 30);
819         if (aaa[0] == 0)
820                 return;
821
822         snprintf(bbb, sizeof bbb, "INVT %s", aaa);
823         serv_puts(bbb);
824         serv_gets(bbb);
825         printf("%s\n", &bbb[4]);
826 }
827
828
829 /*
830  * kick a user out of a room
831  */
832 void kickout(void)
833 {
834         char username[31], cmd[SIZ];
835
836         newprompt("Name of user? ", username, 30);
837         if (strlen(username) == 0) {
838                 return;
839         }
840
841         snprintf(cmd, sizeof cmd, "KICK %s", username);
842         serv_puts(cmd);
843         serv_gets(cmd);
844         printf("%s\n", &cmd[4]);
845 }
846
847
848 /*
849  * aide command: kill the current room
850  */
851 void killroom(void)
852 {
853         char aaa[100];
854
855         serv_puts("KILL 0");
856         serv_gets(aaa);
857         if (aaa[0] != '2') {
858                 printf("%s\n", &aaa[4]);
859                 return;
860         }
861
862         printf("Are you sure you want to kill this room? ");
863         if (yesno() == 0)
864                 return;
865
866         serv_puts("KILL 1");
867         serv_gets(aaa);
868         printf("%s\n", &aaa[4]);
869         if (aaa[0] != '2')
870                 return;
871         dotgoto("_BASEROOM_", 0);
872 }
873
874 void forget(void)
875 {                               /* forget the current room */
876         char cmd[SIZ];
877
878         printf("Are you sure you want to forget this room? ");
879         if (yesno() == 0)
880                 return;
881
882         serv_puts("FORG");
883         serv_gets(cmd);
884         if (cmd[0] != '2') {
885                 printf("%s\n", &cmd[4]);
886                 return;
887         }
888
889         /* now return to the lobby */
890         dotgoto("_BASEROOM_", 0);
891 }
892
893
894 /*
895  * create a new room
896  */
897 void entroom(void)
898 {
899         char cmd[SIZ];
900         char new_room_name[ROOMNAMELEN];
901         int new_room_type;
902         char new_room_pass[10];
903         int new_room_floor;
904         int a, b;
905
906         serv_puts("CRE8 0");
907         serv_gets(cmd);
908
909         if (cmd[0] != '2') {
910                 printf("%s\n", &cmd[4]);
911                 return;
912         }
913
914         newprompt("Name for new room? ", new_room_name, ROOMNAMELEN - 1);
915         if (strlen(new_room_name) == 0) {
916                 return;
917         }
918         for (a = 0; a < strlen(new_room_name); ++a) {
919                 if (new_room_name[a] == '|') {
920                         new_room_name[a] = '_';
921                 }
922         }
923
924         new_room_floor = select_floor((int) curr_floor);
925
926         IFNEXPERT formout("roomaccess");
927         do {
928                 printf("<?>Help\n<1>Public room\n<2>Guess-name room\n"
929                        "<3>Passworded room\n<4>Invitation-only room\n"
930                        "<5>Personal room\n");
931                 printf("Enter room type: ");
932                 do {
933                         b = inkey();
934                 } while (((b < '1') || (b > '5')) && (b != '?'));
935                 if (b == '?') {
936                         printf("?\n");
937                         formout("roomaccess");
938                 }
939         } while ((b < '1') || (b > '5'));
940         b = b - 48;
941         printf("%d\n", b);
942         new_room_type = b - 1;
943         if (new_room_type == 2) {
944                 newprompt("Enter a room password: ", new_room_pass, 9);
945                 for (a = 0; a < strlen(new_room_pass); ++a)
946                         if (new_room_pass[a] == '|')
947                                 new_room_pass[a] = '_';
948         } else {
949                 strcpy(new_room_pass, "");
950         }
951
952         printf("\042%s\042, a", new_room_name);
953         if (b == 1)
954                 printf(" public room.");
955         if (b == 2)
956                 printf(" guess-name room.");
957         if (b == 3)
958                 printf(" passworded room, password: %s", new_room_pass);
959         if (b == 4)
960                 printf("n invitation-only room.");
961         if (b == 5)
962                 printf(" personal room.");
963         printf("\nInstall it? (y/n) : ");
964         if (yesno() == 0) {
965                 return;
966         }
967
968         snprintf(cmd, sizeof cmd, "CRE8 1|%s|%d|%s|%d", new_room_name,
969                  new_room_type, new_room_pass, new_room_floor);
970         serv_puts(cmd);
971         serv_gets(cmd);
972         if (cmd[0] != '2') {
973                 printf("%s\n", &cmd[4]);
974                 return;
975         }
976
977         /* command succeeded... now GO to the new room! */
978         dotgoto(new_room_name, 0);
979 }
980
981
982
983 void readinfo(void)
984 {                               /* read info file for current room */
985         char cmd[SIZ];
986
987         sprintf(cmd, "RINF");
988         serv_puts(cmd);
989         serv_gets(cmd);
990
991         if (cmd[0] != '1') {
992                 return;
993         }
994
995         fmout(screenwidth, NULL,
996               ((userflags & US_PAGINATOR) ? 1 : 0), screenheight, 0, 1);
997 }
998
999
1000 /*
1001  * <W>ho knows room...
1002  */
1003 void whoknows(void)
1004 {
1005         char buf[SIZ];
1006         serv_puts("WHOK");
1007         serv_gets(buf);
1008         if (buf[0] != '1') {
1009                 pprintf("%s\n", &buf[5]);
1010                 return;
1011         }
1012         while (serv_gets(buf), strncmp(buf, "000", 3)) {
1013                 if (sigcaught == 0)
1014                         pprintf("%s\n", buf);
1015         }
1016 }
1017
1018
1019 void do_edit(char *desc, char *read_cmd, char *check_cmd, char *write_cmd)
1020 {
1021         FILE *fp;
1022         char cmd[SIZ];
1023         int b, cksum, editor_exit;
1024
1025
1026         if (strlen(editor_path) == 0) {
1027                 printf("Do you wish to re-enter %s? ", desc);
1028                 if (yesno() == 0)
1029                         return;
1030         }
1031
1032         fp = fopen(temp, "w");
1033         fclose(fp);
1034
1035         serv_puts(check_cmd);
1036         serv_gets(cmd);
1037         if (cmd[0] != '2') {
1038                 printf("%s\n", &cmd[4]);
1039                 return;
1040         }
1041
1042         if (strlen(editor_path) > 0) {
1043                 serv_puts(read_cmd);
1044                 serv_gets(cmd);
1045                 if (cmd[0] == '1') {
1046                         fp = fopen(temp, "w");
1047                         while (serv_gets(cmd), strcmp(cmd, "000")) {
1048                                 fprintf(fp, "%s\n", cmd);
1049                         }
1050                         fclose(fp);
1051                 }
1052         }
1053
1054         cksum = file_checksum(temp);
1055
1056         if (strlen(editor_path) > 0) {
1057                 editor_pid = fork();
1058                 if (editor_pid == 0) {
1059                         chmod(temp, 0600);
1060                         sttybbs(SB_RESTORE);
1061                         execlp(editor_path, editor_path, temp, NULL);
1062                         exit(1);
1063                 }
1064                 if (editor_pid > 0)
1065                         do {
1066                                 editor_exit = 0;
1067                                 b = wait(&editor_exit);
1068                         } while ((b != editor_pid) && (b >= 0));
1069                 editor_pid = (-1);
1070                 printf("Executed %s\n", editor_path);
1071                 sttybbs(0);
1072         } else {
1073                 printf("Entering %s.  ", desc);
1074                 printf("Press return twice when finished.\n");
1075                 fp = fopen(temp, "r+");
1076                 citedit(fp);
1077                 fclose(fp);
1078         }
1079
1080         if (file_checksum(temp) == cksum) {
1081                 printf("*** Aborted.\n");
1082         }
1083
1084         else {
1085                 serv_puts(write_cmd);
1086                 serv_gets(cmd);
1087                 if (cmd[0] != '4') {
1088                         printf("%s\n", &cmd[4]);
1089                         return;
1090                 }
1091
1092                 fp = fopen(temp, "r");
1093                 while (fgets(cmd, SIZ - 1, fp) != NULL) {
1094                         cmd[strlen(cmd) - 1] = 0;
1095                         serv_puts(cmd);
1096                 }
1097                 fclose(fp);
1098                 serv_puts("000");
1099         }
1100
1101         unlink(temp);
1102 }
1103
1104
1105 void enterinfo(void)
1106 {                               /* edit info file for current room */
1107         do_edit("the Info file for this room", "RINF", "EINF 0", "EINF 1");
1108 }
1109
1110 void enter_bio(void)
1111 {
1112         char cmd[SIZ];
1113         snprintf(cmd, sizeof cmd, "RBIO %s", fullname);
1114         do_edit("your Bio", cmd, "NOOP", "EBIO");
1115 }
1116
1117 /*
1118  * create a new floor
1119  */
1120 void create_floor(void)
1121 {
1122         char buf[SIZ];
1123         char newfloorname[SIZ];
1124
1125         serv_puts("CFLR xx|0");
1126         serv_gets(buf);
1127         if (buf[0] != '2') {
1128                 printf("%s\n", &buf[4]);
1129                 return;
1130         }
1131
1132         newprompt("Name for new floor: ", newfloorname, 255);
1133         snprintf(buf, sizeof buf, "CFLR %s|1", newfloorname);
1134         serv_puts(buf);
1135         serv_gets(buf);
1136         if (buf[0] == '2') {
1137                 printf("Floor has been created.\n");
1138         } else {
1139                 printf("%s\n", &buf[4]);
1140         }
1141 }
1142
1143 /*
1144  * edit the current floor
1145  */
1146 void edit_floor(void)
1147 {
1148         char buf[SIZ];
1149         int expire_mode = 0;
1150         int expire_value = 0;
1151
1152         if (floorlist[(int) curr_floor][0] == 0)
1153                 load_floorlist();
1154
1155         /* Fetch the expire policy (this will silently fail on old servers,
1156          * resulting in "default" policy)
1157          */
1158         serv_puts("GPEX floor");
1159         serv_gets(buf);
1160         if (buf[0] == '2') {
1161                 expire_mode = extract_int(&buf[4], 0);
1162                 expire_value = extract_int(&buf[4], 1);
1163         }
1164
1165         /* Interact with the user */
1166         strprompt("Floor name", &floorlist[(int) curr_floor][0], 255);
1167
1168         /* Angels and demons dancing in my head... */
1169         do {
1170                 sprintf(buf, "%d", expire_mode);
1171                 strprompt
1172                     ("Floor default essage expire policy (? for list)",
1173                      buf, 1);
1174                 if (buf[0] == '?') {
1175                         printf("\n");
1176                         printf("0. Use the system default\n");
1177                         printf("1. Never automatically expire messages\n");
1178                         printf("2. Expire by message count\n");
1179                         printf("3. Expire by message age\n");
1180                 }
1181         } while ((buf[0] < 48) || (buf[0] > 51));
1182         expire_mode = buf[0] - 48;
1183
1184         /* ...lunatics and monsters underneath my bed */
1185         if (expire_mode == 2) {
1186                 sprintf(buf, "%d", expire_value);
1187                 strprompt("Keep how many messages online?", buf, 10);
1188                 expire_value = atol(buf);
1189         }
1190
1191         if (expire_mode == 3) {
1192                 sprintf(buf, "%d", expire_value);
1193                 strprompt("Keep messages for how many days?", buf, 10);
1194                 expire_value = atol(buf);
1195         }
1196
1197         /* Save it */
1198         snprintf(buf, sizeof buf, "SPEX floor|%d|%d",
1199                  expire_mode, expire_value);
1200         serv_puts(buf);
1201         serv_gets(buf);
1202
1203         snprintf(buf, sizeof buf, "EFLR %d|%s", curr_floor,
1204                  &floorlist[(int) curr_floor][0]);
1205         serv_puts(buf);
1206         serv_gets(buf);
1207         printf("%s\n", &buf[4]);
1208         load_floorlist();
1209 }
1210
1211
1212
1213
1214 /*
1215  * kill the current floor 
1216  */
1217 void kill_floor(void)
1218 {
1219         int floornum_to_delete, a;
1220         char buf[SIZ];
1221
1222         if (floorlist[(int) curr_floor][0] == 0)
1223                 load_floorlist();
1224         do {
1225                 floornum_to_delete = (-1);
1226                 printf("(Press return to abort)\n");
1227                 newprompt("Delete which floor? ", buf, 255);
1228                 if (strlen(buf) == 0)
1229                         return;
1230                 for (a = 0; a < 128; ++a)
1231                         if (!strcasecmp(&floorlist[a][0], buf))
1232                                 floornum_to_delete = a;
1233                 if (floornum_to_delete < 0) {
1234                         printf("No such floor.  Select one of:\n");
1235                         for (a = 0; a < 128; ++a)
1236                                 if (floorlist[a][0] != 0)
1237                                         printf("%s\n", &floorlist[a][0]);
1238                 }
1239         } while (floornum_to_delete < 0);
1240         sprintf(buf, "KFLR %d|1", floornum_to_delete);
1241         serv_puts(buf);
1242         serv_gets(buf);
1243         printf("%s\n", &buf[4]);
1244 }