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