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