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