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