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