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