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