Release version 976 generated by do-release.sh
[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 = set_room_attr(ipc, attr->QRflags, "Hidden room (accessible to anyone who knows the room name)", QR_GUESSNAME);
471         }
472
473         // if it's public, clear the privacy classes
474         if ((attr->QRflags & QR_PRIVATE) == 0) {
475                 if (attr->QRflags & QR_GUESSNAME) {
476                         attr->QRflags = attr->QRflags - QR_GUESSNAME;
477                 }
478                 if (attr->QRflags & QR_PASSWORDED) {
479                         attr->QRflags = attr->QRflags - QR_PASSWORDED;
480                 }
481         }
482
483         // if it's private, choose the privacy classes
484         if ((attr->QRflags & QR_PRIVATE)
485             && ((attr->QRflags & QR_GUESSNAME) == 0)) {
486                 attr->QRflags = set_room_attr(ipc, attr->QRflags, "Accessible by entering a password", QR_PASSWORDED);
487         }
488         if ((attr->QRflags & QR_PRIVATE)
489             && ((attr->QRflags & QR_PASSWORDED) == QR_PASSWORDED)) {
490                 strprompt("Room password", attr->QRpasswd, 9);
491         }
492
493         if ((attr->QRflags & QR_PRIVATE) == QR_PRIVATE) {
494                 rbump = boolprompt("Cause current users to forget room", 0);
495         }
496
497         attr->QRflags = set_room_attr(ipc, attr->QRflags, "Preferred users only", QR_PREFONLY);
498         attr->QRflags = set_room_attr(ipc, attr->QRflags, "Read-only room", QR_READONLY);
499         attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Allow message deletion by anyone who can post", QR2_COLLABDEL);
500         attr->QRflags = set_room_attr(ipc, attr->QRflags, "Permanent room", QR_PERMANENT);
501         attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Subject Required (Force users to specify a message subject)", QR2_SUBJECTREQ);
502         attr->QRflags = set_room_attr(ipc, attr->QRflags, "Directory room", QR_DIRECTORY);
503         if (attr->QRflags & QR_DIRECTORY) {
504                 strprompt("Directory name", attr->QRdirname, 14);
505                 attr->QRflags = set_room_attr(ipc, attr->QRflags, "Uploading allowed", QR_UPLOAD);
506                 attr->QRflags = set_room_attr(ipc, attr->QRflags, "Downloading allowed", QR_DOWNLOAD);
507                 attr->QRflags = set_room_attr(ipc, attr->QRflags, "Visible directory", QR_VISDIR);
508         }
509         attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Self-service list subscribe/unsubscribe", QR2_SELFLIST);
510         attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Allow non-subscribers to mail to this room", QR2_SMTP_PUBLIC);
511         attr->QRflags2 = set_room_attr(ipc, attr->QRflags2, "Moderated mailing list", QR2_MODERATED);
512         attr->QRflags = set_room_attr(ipc, attr->QRflags, "Automatically make all messages anonymous", QR_ANONONLY);
513         if ((attr->QRflags & QR_ANONONLY) == 0) {
514                 attr->QRflags = set_room_attr(ipc, attr->QRflags, "Ask users whether to make messages anonymous", QR_ANONOPT);
515         }
516         attr->QRorder = intprompt("Listing order", attr->QRorder, 0, 127);
517
518         // Ask about the room admin
519         do {
520                 strprompt("Room admin (or 'none')", room_admin_name, 29);
521                 if (!strcasecmp(room_admin_name, "none")) {
522                         strcpy(room_admin_name, "");
523                         break;
524                 }
525                 else {
526                         r = CtdlIPCQueryUsername(ipc, room_admin_name, buf);
527                         if (r / 100 != 2) {
528                                 scr_printf("%s\n", buf);
529                         }
530                 }
531         } while (r / 100 != 2);
532
533         // Angels and demons dancing in my head...
534         do {
535                 snprintf(buf, sizeof buf, "%d", attr->QRep.expire_mode);
536                 strprompt("Message expire policy (? for list)", buf, 1);
537                 if (buf[0] == '?') {
538                         scr_printf("\n"
539                                    "0. Use the default for this floor\n"
540                                    "1. Never automatically expire messages\n"
541                                    "2. Expire by message count\n" "3. Expire by message age\n");
542                 }
543         } while ((buf[0] < 48) || (buf[0] > 51));
544         attr->QRep.expire_mode = buf[0] - 48;
545
546         // ...lunatics and monsters underneath my bed
547         if (attr->QRep.expire_mode == 2) {
548                 snprintf(buf, sizeof buf, "%d", attr->QRep.expire_value);
549                 strprompt("Keep how many messages online?", buf, 10);
550                 attr->QRep.expire_value = atol(buf);
551         }
552
553         if (attr->QRep.expire_mode == 3) {
554                 snprintf(buf, sizeof buf, "%d", attr->QRep.expire_value);
555                 strprompt("Keep messages for how many days?", buf, 10);
556                 attr->QRep.expire_value = atol(buf);
557         }
558
559         // Give 'em a chance to change their minds
560         scr_printf("Save changes (y/n)? ");
561
562         if (yesno() == 1) {
563                 r = CtdlIPCSetRoomAide(ipc, room_admin_name, buf);
564                 if (r / 100 != 2) {
565                         scr_printf("%s\n", buf);
566                 }
567
568                 r = CtdlIPCSetMessageExpirationPolicy(ipc, 0, eptr, buf);
569                 if (r / 100 != 2) {
570                         scr_printf("%s\n", buf);
571                 }
572
573                 r = CtdlIPCSetRoomAttributes(ipc, rbump, attr, buf);
574                 scr_printf("%s\n", buf);
575                 strncpy(buf, attr->QRname, ROOMNAMELEN);
576                 free(attr);
577                 if (r / 100 == 2) {
578                         dotgoto(ipc, buf, 2, 0);
579                 }
580         }
581         else {
582                 free(attr);
583         }
584 }
585
586
587 // un-goto the previous room, or a specified room
588 void dotungoto(CtdlIPC * ipc, char *towhere) {
589         // Find this 'towhere' room in the list ungoto from this room to
590         // that at the messagepointer position in that room in our ungoto list.
591         // I suppose I could be a real dick and just ungoto that many places
592         // in our list.
593         int found = -1;
594         int lp;
595         char buf[SIZ];
596         struct ctdlipcroom *rret = NULL;        // ignored
597         int r;
598
599         if (uglistsize == 0) {
600                 scr_printf("No rooms to ungoto.\n");
601                 return;
602         }
603         if (towhere == NULL) {
604                 scr_printf("Must specify a room to ungoto.\n");
605                 return;
606         }
607         if (IsEmptyStr(towhere)) {
608                 scr_printf("Must specify a room to ungoto.\n");
609                 return;
610         }
611         for (lp = uglistsize - 1; lp >= 0; lp--) {
612                 if (strcasecmp(towhere, uglist[lp]) == 0) {
613                         found = lp;
614                         break;
615                 }
616         }
617         if (found == -1) {
618                 scr_printf("Room: %s not in ungoto list.\n", towhere);
619                 return;
620         }
621
622         r = CtdlIPCGotoRoom(ipc, uglist[found], "", &rret, buf);
623         if (rret)
624                 free(rret);     // ignored
625         if (r / 100 != 2) {
626                 scr_printf("%s\n", buf);
627                 return;
628         }
629         r = CtdlIPCSetLastRead(ipc, uglistlsn[found] ? uglistlsn[found] : 1, buf);
630         if (r / 100 != 2) {
631                 scr_printf("%s\n", buf);
632         }
633         strncpy(buf, uglist[found], sizeof(buf));
634         // we queue ungoto information here, because we're not really
635         // ungotoing, we're really going to a random spot in some arbitrary
636         // room list.
637         dotgoto(ipc, buf, 0, 0);
638 }
639
640
641 void ungoto(CtdlIPC * ipc) {
642         char buf[SIZ];
643         struct ctdlipcroom *rret = NULL;        // ignored
644         int r;
645
646         if (uglistsize == 0) {
647                 return;
648         }
649
650         r = CtdlIPCGotoRoom(ipc, uglist[uglistsize - 1], "", &rret, buf);
651         if (rret) {
652                 free(rret);     // ignored
653         }
654         if (r / 100 != 2) {
655                 scr_printf("%s\n", buf);
656                 return;
657         }
658         r = CtdlIPCSetLastRead(ipc, uglistlsn[uglistsize - 1] ? uglistlsn[uglistsize - 1] : 1, buf);
659         if (r / 100 != 2) {
660                 scr_printf("%s\n", buf);
661         }
662         strncpy(buf, uglist[uglistsize - 1], sizeof(buf));
663         uglistsize--;
664         free(uglist[uglistsize]);
665         // Don't queue ungoto info or we end up in a loop
666         dotgoto(ipc, buf, 0, 1);
667 }
668
669
670 // saves filelen bytes from file at pathname
671 int save_buffer(void *file, size_t filelen, const char *pathname) {
672         size_t block = 0;
673         size_t bytes_written = 0;
674         FILE *fp;
675
676         fp = fopen(pathname, "w");
677         if (!fp) {
678                 scr_printf("Cannot open '%s': %s\n", pathname, strerror(errno));
679                 return 0;
680         }
681         do {
682                 block = fwrite((char *) file + bytes_written, 1, filelen - bytes_written, fp);
683                 bytes_written += block;
684         } while (errno == EINTR && bytes_written < filelen);
685         fclose(fp);
686
687         if (bytes_written < filelen) {
688                 scr_printf("Trouble saving '%s': %s\n", pathname, strerror(errno));
689                 return 0;
690         }
691         return 1;
692 }
693
694
695 // Save supplied_filename in dest directory; gets the name only
696 void destination_directory(char *dest, const char *supplied_filename) {
697         static char save_dir[SIZ] = { 0 };
698
699         if (IsEmptyStr(save_dir)) {
700                 if (getenv("HOME") == NULL) {
701                         strcpy(save_dir, ".");
702                 }
703                 else {
704                         sprintf(save_dir, "%s/Desktop", getenv("HOME"));
705                         if (access(save_dir, W_OK) != 0) {
706                                 sprintf(save_dir, "%s", getenv("HOME"));
707                                 if (access(save_dir, W_OK) != 0) {
708                                         sprintf(save_dir, ".");
709                                 }
710                         }
711                 }
712         }
713
714         sprintf(dest, "%s/%s", save_dir, supplied_filename);
715         strprompt("Save as", dest, PATH_MAX);
716
717         // Remember the directory for next time
718         strcpy(save_dir, dest);
719         if (strrchr(save_dir, '/') != NULL) {
720                 strcpy(strrchr(save_dir, '/'), "");
721         }
722         else {
723                 strcpy(save_dir, ".");
724         }
725 }
726
727
728 // download()  -  download a file or files.  The argument passed to this
729 //                function determines which protocol to use.
730 //  proto - 0 = paginate, 1 = xmodem, 2 = raw, 3 = ymodem, 4 = zmodem, 5 = save
731 void download(CtdlIPC * ipc, int proto) {
732         char buf[SIZ];
733         char filename[PATH_MAX];
734         char tempname[PATH_MAX];
735         char transmit_cmd[SIZ];
736         FILE *tpipe = NULL;
737
738         int r;
739         int rv = 0;
740         void *file = NULL;      // The downloaded file
741         size_t filelen = 0L;    // The downloaded file length
742
743         if ((room_flags & QR_DOWNLOAD) == 0) {
744                 scr_printf("*** You cannot download from this room.\n");
745                 return;
746         }
747
748         newprompt("Enter filename: ", filename, PATH_MAX);
749
750         // Save to local disk, for folks with their own copy of the client
751         if (proto == 5) {
752                 destination_directory(tempname, filename);
753                 r = CtdlIPCFileDownload(ipc, filename, &file, 0, progress, buf);
754                 if (r / 100 != 2) {
755                         scr_printf("%s\n", buf);
756                         return;
757                 }
758                 save_buffer(file, (size_t) extract_long(buf, 0), tempname);
759                 free(file);
760                 return;
761         }
762
763         r = CtdlIPCFileDownload(ipc, filename, &file, 0, progress, buf);
764         if (r / 100 != 2) {
765                 scr_printf("%s\n", buf);
766                 return;
767         }
768         filelen = extract_unsigned_long(buf, 0);
769
770         // Meta-download for public clients
771         // scr_printf("Fetching file from Citadel server...\n");
772         mkdir(tempdir, 0700);
773         snprintf(tempname, sizeof tempname, "%s/%s", tempdir, filename);
774         tpipe = fopen(tempname, "wb");
775         if (fwrite(file, filelen, 1, tpipe) < filelen) {
776                 // FIXME: restart syscall on EINTR 
777                    // broken = 1;
778         }
779         fclose(tpipe);
780         if (file)
781                 free(file);
782
783         if (proto == 0) {
784                 // FIXME: display internally instead
785                 snprintf(transmit_cmd, sizeof transmit_cmd,
786                          "SHELL=/dev/null; export SHELL; TERM=dumb; export TERM; exec more -d <%s", tempname);
787         }
788         else if (proto == 1)
789                 snprintf(transmit_cmd, sizeof transmit_cmd, "exec sx %s", tempname);
790         else if (proto == 3)
791                 snprintf(transmit_cmd, sizeof transmit_cmd, "exec sb %s", tempname);
792         else if (proto == 4)
793                 snprintf(transmit_cmd, sizeof transmit_cmd, "exec sz %s", tempname);
794         else
795                 // FIXME: display internally instead
796                 snprintf(transmit_cmd, sizeof transmit_cmd, "exec cat %s", tempname);
797
798         stty_ctdl(SB_RESTORE);
799         rv = system(transmit_cmd);
800         if (rv != 0)
801                 scr_printf("failed to download '%s': %d\n", transmit_cmd, rv);
802         stty_ctdl(SB_NO_INTR);
803
804         // clean up the temporary directory
805         nukedir(tempdir);
806         ctdl_beep();            // Beep beep!
807 }
808
809
810 // read directory of this room
811 void roomdir(CtdlIPC * ipc) {
812         char flnm[256];
813         char flsz[32];
814         char comment[256];
815         char mimetype[256];
816         char buf[256];
817         char *listing = NULL;   // Returned directory listing
818         int r;
819
820         r = CtdlIPCReadDirectory(ipc, &listing, buf);
821         if (r / 100 != 1) {
822                 scr_printf("%s\n", buf);
823                 return;
824         }
825
826         extract_token(comment, buf, 0, '|', sizeof comment);
827         extract_token(flnm, buf, 1, '|', sizeof flnm);
828         scr_printf("\nDirectory of %s on %s\n", flnm, comment);
829         scr_printf("-----------------------\n");
830         while (listing && *listing && !IsEmptyStr(listing)) {
831                 extract_token(buf, listing, 0, '\n', sizeof buf);
832                 remove_token(listing, 0, '\n');
833
834                 extract_token(flnm, buf, 0, '|', sizeof flnm);
835                 extract_token(flsz, buf, 1, '|', sizeof flsz);
836                 extract_token(mimetype, buf, 2, '|', sizeof mimetype);
837                 extract_token(comment, buf, 3, '|', sizeof comment);
838                 if (strlen(flnm) <= 14)
839                         scr_printf("%-14s %8s %s [%s]\n", flnm, flsz, comment, mimetype);
840                 else
841                         scr_printf("%s\n%14s %8s %s [%s]\n", flnm, "", flsz, comment, mimetype);
842         }
843         if (listing)
844                 free(listing);
845 }
846
847
848 // add a user to a private room
849 void invite(CtdlIPC * ipc) {
850         char username[USERNAME_SIZE];
851         char buf[SIZ];
852
853         newprompt("Name of user? ", username, USERNAME_SIZE);
854         if (username[0] == 0)
855                 return;
856
857         CtdlIPCInviteUserToRoom(ipc, username, buf);
858         scr_printf("%s\n", buf);
859 }
860
861
862 // kick a user out of a room
863 void kickout(CtdlIPC * ipc) {
864         char username[USERNAME_SIZE];
865         char buf[SIZ];
866
867         newprompt("Name of user? ", username, USERNAME_SIZE);
868         if (username[0] == 0)
869                 return;
870
871         CtdlIPCKickoutUserFromRoom(ipc, username, buf);
872         scr_printf("%s\n", buf);
873 }
874
875
876 // aide command: kill the current room
877 void killroom(CtdlIPC * ipc) {
878         char aaa[100];
879         int r;
880
881         r = CtdlIPCDeleteRoom(ipc, 0, aaa);
882         if (r / 100 != 2) {
883                 scr_printf("%s\n", aaa);
884                 return;
885         }
886
887         scr_printf("Are you sure you want to kill this room? ");
888         if (yesno() == 0)
889                 return;
890
891         r = CtdlIPCDeleteRoom(ipc, 1, aaa);
892         scr_printf("%s\n", aaa);
893         if (r / 100 != 2)
894                 return;
895         dotgoto(ipc, "_BASEROOM_", 0, 0);
896 }
897
898
899 void forget(CtdlIPC * ipc) {    // forget the current room
900         char buf[SIZ];
901
902         scr_printf("Are you sure you want to forget this room? ");
903         if (yesno() == 0)
904                 return;
905
906         remove_march(room_name, 0);
907         if (CtdlIPCForgetRoom(ipc, buf) / 100 != 2) {
908                 scr_printf("%s\n", buf);
909                 return;
910         }
911
912         // now return to the lobby
913         dotgoto(ipc, "_BASEROOM_", 0, 0);
914 }
915
916
917 // create a new room
918 void entroom(CtdlIPC * ipc) {
919         char buf[SIZ];
920         char new_room_name[ROOMNAMELEN];
921         int new_room_type;
922         char new_room_pass[10];
923         int new_room_floor;
924         int a, b;
925         int r;                  // IPC response code
926
927         // Check permission to create room
928         r = CtdlIPCCreateRoom(ipc, 0, "", 1, "", 0, buf);
929         if (r / 100 != 2) {
930                 scr_printf("%s\n", buf);
931                 return;
932         }
933
934         newprompt("Name for new room? ", new_room_name, ROOMNAMELEN - 1);
935         if (IsEmptyStr(new_room_name)) {
936                 return;
937         }
938         for (a = 0; !IsEmptyStr(&new_room_name[a]); ++a) {
939                 if (new_room_name[a] == '|') {
940                         new_room_name[a] = '_';
941                 }
942         }
943
944         new_room_floor = select_floor(ipc, (int) curr_floor);
945
946         IFNEXPERT formout(ipc, "roomaccess");
947         do {
948                 scr_printf("<?>Help\n"
949                            "<1>Public room (shown to all users by default)\n"
950                            "<2>Hidden room (accessible to anyone who knows the room name)\n"
951                            "<3>Passworded room (hidden, plus requires a password to enter)\n"
952                            "<4>Invitation-only room (requires access to be granted by an Admin)\n"
953                            "<5>Personal room (accessible to you only)\n" "Enter room type: ");
954                 do {
955                         b = inkey();
956                 } while (((b < '1') || (b > '5')) && (b != '?'));
957                 if (b == '?') {
958                         scr_printf("?\n");
959                         formout(ipc, "roomaccess");
960                 }
961         } while ((b < '1') || (b > '5'));
962         b -= '0';               // Portable
963         scr_printf("%d\n", b);
964         new_room_type = b - 1;
965         if (new_room_type == 2) {
966                 newprompt("Enter a room password: ", new_room_pass, 9);
967                 for (a = 0; !IsEmptyStr(&new_room_pass[a]); ++a)
968                         if (new_room_pass[a] == '|')
969                                 new_room_pass[a] = '_';
970         }
971         else {
972                 strcpy(new_room_pass, "");
973         }
974
975         scr_printf("\042%s\042, a", new_room_name);
976         if (b == 1)
977                 scr_printf(" public room.");
978         if (b == 2)
979                 scr_printf(" hidden room.");
980         if (b == 3)
981                 scr_printf(" passworded room, password: %s", new_room_pass);
982         if (b == 4)
983                 scr_printf("n invitation-only room.");
984         if (b == 5)
985                 scr_printf(" personal room.");
986         scr_printf("\nInstall it? (y/n) : ");
987         if (yesno() == 0) {
988                 return;
989         }
990
991         r = CtdlIPCCreateRoom(ipc, 1, new_room_name, new_room_type, new_room_pass, new_room_floor, buf);
992         if (r / 100 != 2) {
993                 scr_printf("%s\n", buf);
994                 return;
995         }
996
997         // command succeeded... now GO to the new room!
998         dotgoto(ipc, new_room_name, 0, 0);
999 }
1000
1001
1002
1003 void readinfo(CtdlIPC * ipc) {  // read info file for current room
1004         char buf[SIZ];
1005         char room_admin_name[64];
1006         int r;                  // IPC response code
1007         char *text = NULL;
1008
1009         // Name of currernt room admin
1010         r = CtdlIPCGetRoomAide(ipc, buf);
1011         if (r / 100 == 2)
1012                 strncpy(room_admin_name, buf, sizeof room_admin_name);
1013         else
1014                 strcpy(room_admin_name, "");
1015
1016         if (!IsEmptyStr(room_admin_name))
1017                 scr_printf("Room admin is %s.\n\n", room_admin_name);
1018
1019         r = CtdlIPCRoomInfo(ipc, &text, buf);
1020         if (r / 100 != 1)
1021                 return;
1022
1023         if (text) {
1024                 fmout(screenwidth, NULL, text, NULL, 1);
1025                 free(text);
1026         }
1027 }
1028
1029
1030 // <W>ho knows room...
1031 void whoknows(CtdlIPC * ipc) {
1032         char buf[256];
1033         char *listing = NULL;
1034         int r;
1035
1036         r = CtdlIPCWhoKnowsRoom(ipc, &listing, buf);
1037         if (r / 100 != 1) {
1038                 scr_printf("%s\n", buf);
1039                 return;
1040         }
1041         while (!IsEmptyStr(listing)) {
1042                 extract_token(buf, listing, 0, '\n', sizeof buf);
1043                 remove_token(listing, 0, '\n');
1044                 if (sigcaught == 0)
1045                         scr_printf("%s\n", buf);
1046         }
1047         free(listing);
1048 }
1049
1050
1051 void do_edit(CtdlIPC * ipc, char *desc, char *read_cmd, char *check_cmd, char *write_cmd) {
1052         FILE *fp;
1053         char cmd[SIZ];
1054         int b, cksum, editor_exit;
1055
1056         if (IsEmptyStr(editor_path)) {
1057                 scr_printf("Do you wish to re-enter %s? ", desc);
1058                 if (yesno() == 0)
1059                         return;
1060         }
1061
1062         fp = fopen(temp, "w");
1063         fclose(fp);
1064
1065         CtdlIPC_chat_send(ipc, check_cmd);
1066         CtdlIPC_chat_recv(ipc, cmd);
1067         if (cmd[0] != '2') {
1068                 scr_printf("%s\n", &cmd[4]);
1069                 return;
1070         }
1071
1072         if (!IsEmptyStr(editor_path)) {
1073                 CtdlIPC_chat_send(ipc, read_cmd);
1074                 CtdlIPC_chat_recv(ipc, cmd);
1075                 if (cmd[0] == '1') {
1076                         fp = fopen(temp, "w");
1077                         while (CtdlIPC_chat_recv(ipc, cmd), strcmp(cmd, "000")) {
1078                                 fprintf(fp, "%s\n", cmd);
1079                         }
1080                         fclose(fp);
1081                 }
1082         }
1083
1084         cksum = file_checksum(temp);
1085
1086         if (!IsEmptyStr(editor_path)) {
1087                 char tmp[SIZ];
1088
1089                 snprintf(tmp, sizeof tmp, "WINDOW_TITLE=%s", desc);
1090                 putenv(tmp);
1091                 stty_ctdl(SB_RESTORE);
1092                 editor_pid = fork();
1093                 if (editor_pid == 0) {
1094                         chmod(temp, 0600);
1095                         execlp(editor_path, editor_path, temp, NULL);
1096                         exit(1);
1097                 }
1098                 if (editor_pid > 0)
1099                         do {
1100                                 editor_exit = 0;
1101                                 b = ka_wait(&editor_exit);
1102                         } while ((b != editor_pid) && (b >= 0));
1103                 editor_pid = (-1);
1104                 scr_printf("Executed %s\n", editor_path);
1105                 stty_ctdl(0);
1106         }
1107         else {
1108                 scr_printf("Entering %s.  Press return twice when finished.\n", desc);
1109                 fp = fopen(temp, "r+");
1110                 citedit(fp);
1111                 fclose(fp);
1112         }
1113
1114         if (file_checksum(temp) == cksum) {
1115                 scr_printf("*** Aborted.\n");
1116         }
1117
1118         else {
1119                 CtdlIPC_chat_send(ipc, write_cmd);
1120                 CtdlIPC_chat_recv(ipc, cmd);
1121                 if (cmd[0] != '4') {
1122                         scr_printf("%s\n", &cmd[4]);
1123                         return;
1124                 }
1125
1126                 fp = fopen(temp, "r");
1127                 while (fgets(cmd, SIZ - 1, fp) != NULL) {
1128                         cmd[strlen(cmd) - 1] = 0;
1129                         CtdlIPC_chat_send(ipc, cmd);
1130                 }
1131                 fclose(fp);
1132                 CtdlIPC_chat_send(ipc, "000");
1133         }
1134
1135         unlink(temp);
1136 }
1137
1138
1139 // edit info file for current room
1140 void enterinfo(CtdlIPC * ipc) {
1141         do_edit(ipc, "the Info file for this room", "RINF", "EINF 0", "EINF 1");
1142 }
1143
1144
1145 void enter_bio(CtdlIPC * ipc) {
1146         char cmd[SIZ];
1147         snprintf(cmd, sizeof cmd, "RBIO %s", fullname);
1148         do_edit(ipc, "your Bio", cmd, "NOOP", "EBIO");
1149 }
1150
1151
1152 // create a new floor
1153 void create_floor(CtdlIPC * ipc) {
1154         char buf[SIZ];
1155         char newfloorname[SIZ];
1156         int r;                  // IPC response code
1157
1158         load_floorlist(ipc);
1159
1160         r = CtdlIPCCreateFloor(ipc, 0, "", buf);
1161         if ((r / 100 != 2) && (r != ERROR + ILLEGAL_VALUE)) {
1162                 scr_printf("%s\n", buf);
1163                 return;
1164         }
1165
1166         newprompt("Name for new floor: ", newfloorname, 255);
1167         if (!*newfloorname)
1168                 return;
1169         r = CtdlIPCCreateFloor(ipc, 1, newfloorname, buf);
1170         if (r / 100 == 2) {
1171                 scr_printf("Floor has been created.\n");
1172         }
1173         else {
1174                 scr_printf("%s\n", buf);
1175         }
1176
1177         load_floorlist(ipc);
1178 }
1179
1180
1181 // edit the current floor
1182 void edit_floor(CtdlIPC * ipc) {
1183         char buf[SIZ];
1184         struct ExpirePolicy *ep = NULL;
1185
1186         load_floorlist(ipc);
1187
1188         // Fetch the expire policy (this will silently fail on old servers, resulting in "default" policy)
1189         CtdlIPCGetMessageExpirationPolicy(ipc, 1, &ep, buf);
1190
1191         // Interact with the user
1192         scr_printf("You are editing the floor called \"%s\"\n", &floorlist[(int) curr_floor][0]);
1193         strprompt("Floor name", &floorlist[(int) curr_floor][0], 255);
1194
1195         // Angels and demons dancing in my head...
1196         do {
1197                 snprintf(buf, sizeof buf, "%d", ep->expire_mode);
1198                 strprompt("Floor default message expire policy (? for list)", buf, 1);
1199                 if (buf[0] == '?') {
1200                         scr_printf("\n"
1201                                    "0. Use the system default\n"
1202                                    "1. Never automatically expire messages\n"
1203                                    "2. Expire by message count\n" "3. Expire by message age\n");
1204                 }
1205         } while ((buf[0] < '0') || (buf[0] > '3'));
1206         ep->expire_mode = buf[0] - '0';
1207
1208         // ...lunatics and monsters underneath my bed
1209         if (ep->expire_mode == 2) {
1210                 snprintf(buf, sizeof buf, "%d", ep->expire_value);
1211                 strprompt("Keep how many messages online?", buf, 10);
1212                 ep->expire_value = atol(buf);
1213         }
1214
1215         if (ep->expire_mode == 3) {
1216                 snprintf(buf, sizeof buf, "%d", ep->expire_value);
1217                 strprompt("Keep messages for how many days?", buf, 10);
1218                 ep->expire_value = atol(buf);
1219         }
1220
1221         // Save it
1222         CtdlIPCSetMessageExpirationPolicy(ipc, 1, ep, buf);
1223         CtdlIPCEditFloor(ipc, curr_floor, &floorlist[(int) curr_floor][0], buf);
1224         scr_printf("%s\n", buf);
1225         load_floorlist(ipc);
1226 }
1227
1228
1229 // kill the current floor 
1230 void kill_floor(CtdlIPC * ipc) {
1231         int floornum_to_delete, a;
1232         char buf[SIZ];
1233
1234         load_floorlist(ipc);
1235         do {
1236                 floornum_to_delete = (-1);
1237                 scr_printf("(Press return to abort)\n");
1238                 newprompt("Delete which floor? ", buf, 255);
1239                 if (IsEmptyStr(buf))
1240                         return;
1241                 for (a = 0; a < 128; ++a)
1242                         if (!strcasecmp(&floorlist[a][0], buf))
1243                                 floornum_to_delete = a;
1244                 if (floornum_to_delete < 0) {
1245                         scr_printf("No such floor.  Select one of:\n");
1246                         for (a = 0; a < 128; ++a)
1247                                 if (floorlist[a][0] != 0)
1248                                         scr_printf("%s\n", &floorlist[a][0]);
1249                 }
1250         } while (floornum_to_delete < 0);
1251         CtdlIPCDeleteFloor(ipc, 1, floornum_to_delete, buf);
1252         scr_printf("%s\n", buf);
1253         load_floorlist(ipc);
1254 }