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