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