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