Patches submitted by matt:
[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 #include "citadel_dirs.h"
34
35 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
36
37
38 void stty_ctdl(int cmd);
39 void dotgoto(CtdlIPC *ipc, char *towhere, int display_name, int fromungoto);
40 void progress(CtdlIPC* ipc, 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', sizeof buf);
84                 remove_token(listing, 0, '\n');
85                 extract_token(floorlist[extract_int(buf, 0)], buf, 1, '|', SIZ);
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 2 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(ipc);
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(ipc);
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] ? uglistlsn[found] : 1, 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] ? uglistlsn[uglistsize-1] : 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((char *)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         static char save_dir[SIZ] = { 0 };
726
727         if (strlen(save_dir) == 0) {
728                 if (getenv("HOME") == NULL) {
729                         strcpy(save_dir, ".");
730                 }
731                 else {
732                         sprintf(save_dir, "%s/Desktop", getenv("HOME"));
733                         if (access(save_dir, W_OK) != 0) {
734                                 sprintf(save_dir, "%s", getenv("HOME"));
735                                 if (access(save_dir, W_OK) != 0) {
736                                         sprintf(save_dir, ".");
737                                 }
738                         }
739                 }
740         }
741
742         sprintf(dest, "%s/%s", save_dir, supplied_filename);
743         strprompt("Save as", dest, PATH_MAX);
744
745         /* Remember the directory for next time */
746         strcpy(save_dir, dest);
747         if (strrchr(save_dir, '/') != NULL) {
748                 strcpy(strrchr(save_dir, '/'), "");
749         }
750         else {
751                 strcpy(save_dir, ".");
752         }
753 }
754
755
756 /*
757  * download()  -  download a file or files.  The argument passed to this
758  *                function determines which protocol to use.
759  *  proto - 0 = paginate, 1 = xmodem, 2 = raw, 3 = ymodem, 4 = zmodem, 5 = save
760  */
761 void download(CtdlIPC *ipc, int proto)
762 {
763         char buf[SIZ];
764         char filename[PATH_MAX];
765         char tempname[PATH_MAX];
766         char transmit_cmd[SIZ];
767         FILE *tpipe = NULL;
768         int broken = 0;
769         int r;
770         void *file = NULL;      /* The downloaded file */
771         size_t filelen = 0L;    /* The downloaded file length */
772
773         if ((room_flags & QR_DOWNLOAD) == 0) {
774                 scr_printf("*** You cannot download from this room.\n");
775                 return;
776         }
777
778         newprompt("Enter filename: ", filename, PATH_MAX);
779
780         /* Save to local disk, for folks with their own copy of the client */
781         if (proto == 5) {
782                 destination_directory(tempname, filename);
783                 r = CtdlIPCFileDownload(ipc, filename, &file, 0, progress, buf);
784                 if (r / 100 != 2) {
785                         scr_printf("%s\n", buf);
786                         return;
787                 }
788                 save_buffer(file, (size_t)extract_long(buf, 0), tempname);
789                 free(file);
790                 return;
791         }
792
793         r = CtdlIPCFileDownload(ipc, filename, &file, 0, progress, buf);
794         if (r / 100 != 2) {
795                 scr_printf("%s\n", buf);
796                 return;
797         }
798         filelen = extract_unsigned_long(buf, 0);
799
800         /* Meta-download for public clients */
801         /* scr_printf("Fetching file from Citadel server...\n"); */
802         mkdir(tempdir, 0700);
803         snprintf(tempname, sizeof tempname, "%s/%s", tempdir, filename);
804         tpipe = fopen(tempname, "wb");
805         if (fwrite(file, filelen, 1, tpipe) < filelen) {
806                 /* FIXME: restart syscall on EINTR */
807                 broken = 1;
808         }
809         fclose(tpipe);
810         if (file) free(file);
811
812         if (proto == 0) {
813                 /* FIXME: display internally instead */
814                 snprintf(transmit_cmd, sizeof transmit_cmd,
815                         "SHELL=/dev/null; export SHELL; TERM=dumb; export TERM; exec more -d <%s",
816                         tempname);
817         }
818         else if (proto == 1)
819                 snprintf(transmit_cmd, sizeof transmit_cmd, "exec sx %s", tempname);
820         else if (proto == 3)
821                 snprintf(transmit_cmd, sizeof transmit_cmd, "exec sb %s", tempname);
822         else if (proto == 4)
823                 snprintf(transmit_cmd, sizeof transmit_cmd, "exec sz %s", tempname);
824         else
825                 /* FIXME: display internally instead */
826                 snprintf(transmit_cmd, sizeof transmit_cmd, "exec cat %s", tempname);
827
828         screen_reset();
829         stty_ctdl(SB_RESTORE);
830         system(transmit_cmd);
831         stty_ctdl(SB_NO_INTR);
832         screen_set();
833
834         /* clean up the temporary directory */
835         nukedir(tempdir);
836         ctdl_beep();    /* Beep beep! */
837 }
838
839
840 /*
841  * read directory of this room
842  */
843 void roomdir(CtdlIPC *ipc)
844 {
845         char flnm[256];
846         char flsz[32];
847         char comment[256];
848         char buf[256];
849         char *listing = NULL;   /* Returned directory listing */
850         int r;
851
852         r = CtdlIPCReadDirectory(ipc, &listing, buf);
853         if (r / 100 != 1) {
854                 pprintf("%s\n", buf);
855                 return;
856         }
857
858         extract_token(comment, buf, 0, '|', sizeof comment);
859         extract_token(flnm, buf, 1, '|', sizeof flnm);
860         pprintf("\nDirectory of %s on %s\n", flnm, comment);
861         pprintf("-----------------------\n");
862         while (*listing && strlen(listing)) {
863                 extract_token(buf, listing, 0, '\n', sizeof buf);
864                 remove_token(listing, 0, '\n');
865
866                 extract_token(flnm, buf, 0, '|', sizeof flnm);
867                 extract_token(flsz, buf, 1, '|', sizeof flsz);
868                 extract_token(comment, buf, 2, '|', sizeof comment);
869                 if (strlen(flnm) <= 14)
870                         pprintf("%-14s %8s %s\n", flnm, flsz, comment);
871                 else
872                         pprintf("%s\n%14s %8s %s\n", flnm, "", flsz,
873                                 comment);
874         }
875 }
876
877
878 /*
879  * add a user to a private room
880  */
881 void invite(CtdlIPC *ipc)
882 {
883         char username[USERNAME_SIZE];
884         char buf[SIZ];
885         int r;                          /* IPC response code */
886
887         newprompt("Name of user? ", username, USERNAME_SIZE);
888         if (username[0] == 0)
889                 return;
890
891         r = CtdlIPCInviteUserToRoom(ipc, username, buf);
892         scr_printf("%s\n", buf);
893 }
894
895
896 /*
897  * kick a user out of a room
898  */
899 void kickout(CtdlIPC *ipc)
900 {
901         char username[USERNAME_SIZE];
902         char buf[SIZ];
903         int r;                          /* IPC response code */
904
905         newprompt("Name of user? ", username, USERNAME_SIZE);
906         if (username[0] == 0)
907                 return;
908
909         r = CtdlIPCKickoutUserFromRoom(ipc, username, buf);
910         scr_printf("%s\n", buf);
911 }
912
913
914 /*
915  * aide command: kill the current room
916  */
917 void killroom(CtdlIPC *ipc)
918 {
919         char aaa[100];
920         int r;
921
922         r = CtdlIPCDeleteRoom(ipc, 0, aaa);
923         if (r / 100 != 2) {
924                 scr_printf("%s\n", aaa);
925                 return;
926         }
927
928         scr_printf("Are you sure you want to kill this room? ");
929         if (yesno() == 0)
930                 return;
931
932         r = CtdlIPCDeleteRoom(ipc, 1, aaa);
933         scr_printf("%s\n", aaa);
934         if (r / 100 != 2)
935                 return;
936         dotgoto(ipc, "_BASEROOM_", 0, 0);
937 }
938
939 void forget(CtdlIPC *ipc)
940 {                               /* forget the current room */
941         char buf[SIZ];
942
943         scr_printf("Are you sure you want to forget this room? ");
944         if (yesno() == 0)
945                 return;
946
947         remove_march(room_name, 0);
948         if (CtdlIPCForgetRoom(ipc, buf) / 100 != 2) {
949                 scr_printf("%s\n", buf);
950                 return;
951         }
952
953         /* now return to the lobby */
954         dotgoto(ipc, "_BASEROOM_", 0, 0);
955 }
956
957
958 /*
959  * create a new room
960  */
961 void entroom(CtdlIPC *ipc)
962 {
963         char buf[SIZ];
964         char new_room_name[ROOMNAMELEN];
965         int new_room_type;
966         char new_room_pass[10];
967         int new_room_floor;
968         int a, b;
969         int r;                          /* IPC response code */
970
971         /* Check permission to create room */
972         r = CtdlIPCCreateRoom(ipc, 0, "", 1, "", 0, buf);
973         if (r / 100 != 2) {
974                 scr_printf("%s\n", buf);
975                 return;
976         }
977
978         newprompt("Name for new room? ", new_room_name, ROOMNAMELEN - 1);
979         if (strlen(new_room_name) == 0) {
980                 return;
981         }
982         for (a = 0; a < strlen(new_room_name); ++a) {
983                 if (new_room_name[a] == '|') {
984                         new_room_name[a] = '_';
985                 }
986         }
987
988         new_room_floor = select_floor(ipc, (int) curr_floor);
989
990         IFNEXPERT formout(ipc, "roomaccess");
991         do {
992                 scr_printf("<?>Help\n<1>Public room\n<2>Guess-name room\n"
993                        "<3>Passworded room\n<4>Invitation-only room\n"
994                        "<5>Personal room\n"
995                         "Enter room type: ");
996                 do {
997                         b = inkey();
998                 } while (((b < '1') || (b > '5')) && (b != '?'));
999                 if (b == '?') {
1000                         scr_printf("?\n");
1001                         formout(ipc, "roomaccess");
1002                 }
1003         } while ((b < '1') || (b > '5'));
1004         b -= '0';                       /* Portable */
1005         scr_printf("%d\n", b);
1006         new_room_type = b - 1;
1007         if (new_room_type == 2) {
1008                 newprompt("Enter a room password: ", new_room_pass, 9);
1009                 for (a = 0; a < strlen(new_room_pass); ++a)
1010                         if (new_room_pass[a] == '|')
1011                                 new_room_pass[a] = '_';
1012         } else {
1013                 strcpy(new_room_pass, "");
1014         }
1015
1016         scr_printf("\042%s\042, a", new_room_name);
1017         if (b == 1)
1018                 scr_printf(" public room.");
1019         if (b == 2)
1020                 scr_printf(" guess-name room.");
1021         if (b == 3)
1022                 scr_printf(" passworded room, password: %s", new_room_pass);
1023         if (b == 4)
1024                 scr_printf("n invitation-only room.");
1025         if (b == 5)
1026                 scr_printf(" personal room.");
1027         scr_printf("\nInstall it? (y/n) : ");
1028         if (yesno() == 0) {
1029                 return;
1030         }
1031
1032         r = CtdlIPCCreateRoom(ipc, 1, new_room_name, new_room_type,
1033                               new_room_pass, new_room_floor, buf);
1034         if (r / 100 != 2) {
1035                 scr_printf("%s\n", buf);
1036                 return;
1037         }
1038
1039         /* command succeeded... now GO to the new room! */
1040         dotgoto(ipc, new_room_name, 0, 0);
1041 }
1042
1043
1044
1045 void readinfo(CtdlIPC *ipc)
1046 {                               /* read info file for current room */
1047         char buf[SIZ];
1048         char raide[64];
1049         int r;                  /* IPC response code */
1050         char *text = NULL;
1051
1052         /* Name of currernt room aide */
1053         r = CtdlIPCGetRoomAide(ipc, buf);
1054         if (r / 100 == 2)
1055                 safestrncpy(raide, buf, sizeof raide);
1056         else
1057                 strcpy(raide, "");
1058
1059         if (strlen(raide) > 0)
1060                 scr_printf("Room aide is %s.\n\n", raide);
1061
1062         r = CtdlIPCRoomInfo(ipc, &text, buf);
1063         if (r / 100 != 1)
1064                 return;
1065
1066         if (text) {
1067                 fmout(screenwidth, NULL, text, NULL,
1068                       ((userflags & US_PAGINATOR) ? 1 : 0), screenheight, 
1069                       (*raide) ? 2 : 0, 1);
1070                 free(text);
1071         }
1072 }
1073
1074
1075 /*
1076  * <W>ho knows room...
1077  */
1078 void whoknows(CtdlIPC *ipc)
1079 {
1080         char buf[256];
1081         char *listing = NULL;
1082         int r;
1083
1084         r = CtdlIPCWhoKnowsRoom(ipc, &listing, buf);
1085         if (r / 100 != 1) {
1086                 pprintf("%s\n", buf);
1087                 return;
1088         }
1089         while (strlen(listing) > 0) {
1090                 extract_token(buf, listing, 0, '\n', sizeof buf);
1091                 remove_token(listing, 0, '\n');
1092                 if (sigcaught == 0)
1093                         pprintf("%s\n", buf);
1094         }
1095         free(listing);
1096 }
1097
1098
1099 void do_edit(CtdlIPC *ipc,
1100                 char *desc, char *read_cmd, char *check_cmd, char *write_cmd)
1101 {
1102         FILE *fp;
1103         char cmd[SIZ];
1104         int b, cksum, editor_exit;
1105
1106         if (strlen(editor_paths[0]) == 0) {
1107                 scr_printf("Do you wish to re-enter %s? ", desc);
1108                 if (yesno() == 0)
1109                         return;
1110         }
1111
1112         fp = fopen(temp, "w");
1113         fclose(fp);
1114
1115         CtdlIPC_chat_send(ipc, check_cmd);
1116         CtdlIPC_chat_recv(ipc, cmd);
1117         if (cmd[0] != '2') {
1118                 scr_printf("%s\n", &cmd[4]);
1119                 return;
1120         }
1121
1122         if (strlen(editor_paths[0]) > 0) {
1123                 CtdlIPC_chat_send(ipc, read_cmd);
1124                 CtdlIPC_chat_recv(ipc, cmd);
1125                 if (cmd[0] == '1') {
1126                         fp = fopen(temp, "w");
1127                         while (CtdlIPC_chat_recv(ipc, cmd), strcmp(cmd, "000")) {
1128                                 fprintf(fp, "%s\n", cmd);
1129                         }
1130                         fclose(fp);
1131                 }
1132         }
1133
1134         cksum = file_checksum(temp);
1135
1136         if (strlen(editor_paths[0]) > 0) {
1137                 char tmp[SIZ];
1138
1139                 snprintf(tmp, sizeof tmp, "WINDOW_TITLE=%s", desc);
1140                 putenv(tmp);
1141                 screen_reset();
1142                 stty_ctdl(SB_RESTORE);
1143                 editor_pid = fork();
1144                 if (editor_pid == 0) {
1145                         chmod(temp, 0600);
1146                         execlp(editor_paths[0], editor_paths[0], temp, NULL);
1147                         exit(1);
1148                 }
1149                 if (editor_pid > 0)
1150                         do {
1151                                 editor_exit = 0;
1152                                 b = ka_wait(&editor_exit);
1153                         } while ((b != editor_pid) && (b >= 0));
1154                 editor_pid = (-1);
1155                 scr_printf("Executed %s\n", editor_paths[0]);
1156                 stty_ctdl(0);
1157                 screen_set();
1158         } else {
1159                 scr_printf("Entering %s.  "
1160                         "Press return twice when finished.\n", desc);
1161                 fp = fopen(temp, "r+");
1162                 citedit(ipc, fp);
1163                 fclose(fp);
1164         }
1165
1166         if (file_checksum(temp) == cksum) {
1167                 scr_printf("*** Aborted.\n");
1168         }
1169
1170         else {
1171                 CtdlIPC_chat_send(ipc, write_cmd);
1172                 CtdlIPC_chat_recv(ipc, cmd);
1173                 if (cmd[0] != '4') {
1174                         scr_printf("%s\n", &cmd[4]);
1175                         return;
1176                 }
1177
1178                 fp = fopen(temp, "r");
1179                 while (fgets(cmd, SIZ - 1, fp) != NULL) {
1180                         cmd[strlen(cmd) - 1] = 0;
1181                         CtdlIPC_chat_send(ipc, cmd);
1182                 }
1183                 fclose(fp);
1184                 CtdlIPC_chat_send(ipc, "000");
1185         }
1186
1187         unlink(temp);
1188 }
1189
1190
1191 void enterinfo(CtdlIPC *ipc)
1192 {                               /* edit info file for current room */
1193         do_edit(ipc, "the Info file for this room", "RINF", "EINF 0", "EINF 1");
1194 }
1195
1196 void enter_bio(CtdlIPC *ipc)
1197 {
1198         char cmd[SIZ];
1199         snprintf(cmd, sizeof cmd, "RBIO %s", fullname);
1200         do_edit(ipc, "your Bio", cmd, "NOOP", "EBIO");
1201 }
1202
1203 /*
1204  * create a new floor
1205  */
1206 void create_floor(CtdlIPC *ipc)
1207 {
1208         char buf[SIZ];
1209         char newfloorname[SIZ];
1210         int r;                  /* IPC response code */
1211
1212         load_floorlist(ipc);
1213
1214         r = CtdlIPCCreateFloor(ipc, 0, "", buf);
1215         if ( (r / 100 != 2) && (r != ERROR + ILLEGAL_VALUE) ) {
1216                 scr_printf("%s\n", buf);
1217                 return;
1218         }
1219
1220         newprompt("Name for new floor: ", newfloorname, 255);
1221         if (!*newfloorname) return;
1222         r = CtdlIPCCreateFloor(ipc, 1, newfloorname, buf);
1223         if (r / 100 == 2) {
1224                 scr_printf("Floor has been created.\n");
1225         } else {
1226                 scr_printf("%s\n", buf);
1227         }
1228
1229         load_floorlist(ipc);
1230 }
1231
1232 /*
1233  * edit the current floor
1234  */
1235 void edit_floor(CtdlIPC *ipc)
1236 {
1237         char buf[SIZ];
1238         struct ExpirePolicy *ep = NULL;
1239         int r;                          /* IPC response code */
1240
1241         load_floorlist(ipc);
1242
1243         /* Fetch the expire policy (this will silently fail on old servers,
1244          * resulting in "default" policy)
1245          */
1246         r = CtdlIPCGetMessageExpirationPolicy(ipc, 1, &ep, buf);
1247
1248         /* Interact with the user */
1249         scr_printf("You are editing the floor called \"%s\"\n", 
1250                 &floorlist[(int) curr_floor][0] );
1251         strprompt("Floor name", &floorlist[(int) curr_floor][0], 255);
1252
1253         /* Angels and demons dancing in my head... */
1254         do {
1255                 snprintf(buf, sizeof buf, "%d", ep->expire_mode);
1256                 strprompt
1257                     ("Floor default message expire policy (? for list)",
1258                      buf, 1);
1259                 if (buf[0] == '?') {
1260                         scr_printf("\n"
1261                                 "0. Use the system default\n"
1262                                 "1. Never automatically expire messages\n"
1263                                 "2. Expire by message count\n"
1264                                 "3. Expire by message age\n");
1265                 }
1266         } while ((buf[0] < '0') || (buf[0] > '3'));
1267         ep->expire_mode = buf[0] - '0';
1268
1269         /* ...lunatics and monsters underneath my bed */
1270         if (ep->expire_mode == 2) {
1271                 snprintf(buf, sizeof buf, "%d", ep->expire_value);
1272                 strprompt("Keep how many messages online?", buf, 10);
1273                 ep->expire_value = atol(buf);
1274         }
1275
1276         if (ep->expire_mode == 3) {
1277                 snprintf(buf, sizeof buf, "%d", ep->expire_value);
1278                 strprompt("Keep messages for how many days?", buf, 10);
1279                 ep->expire_value = atol(buf);
1280         }
1281
1282         /* Save it */
1283         r = CtdlIPCSetMessageExpirationPolicy(ipc, 1, ep, buf);
1284         r = CtdlIPCEditFloor(ipc, curr_floor, &floorlist[(int)curr_floor][0], buf);
1285         scr_printf("%s\n", buf);
1286         load_floorlist(ipc);
1287 }
1288
1289
1290
1291
1292 /*
1293  * kill the current floor 
1294  */
1295 void kill_floor(CtdlIPC *ipc)
1296 {
1297         int floornum_to_delete, a;
1298         char buf[SIZ];
1299
1300         load_floorlist(ipc);
1301         do {
1302                 floornum_to_delete = (-1);
1303                 scr_printf("(Press return to abort)\n");
1304                 newprompt("Delete which floor? ", buf, 255);
1305                 if (strlen(buf) == 0)
1306                         return;
1307                 for (a = 0; a < 128; ++a)
1308                         if (!strcasecmp(&floorlist[a][0], buf))
1309                                 floornum_to_delete = a;
1310                 if (floornum_to_delete < 0) {
1311                         scr_printf("No such floor.  Select one of:\n");
1312                         for (a = 0; a < 128; ++a)
1313                                 if (floorlist[a][0] != 0)
1314                                         scr_printf("%s\n", &floorlist[a][0]);
1315                 }
1316         } while (floornum_to_delete < 0);
1317         CtdlIPCDeleteFloor(ipc, 1, floornum_to_delete, buf);
1318         scr_printf("%s\n", buf);
1319         load_floorlist(ipc);
1320 }