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