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