5 * Client-side functions which perform room operations
17 #include <sys/types.h>
31 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
34 void sttybbs(int cmd);
35 void hit_any_key(void);
37 void strprompt(char *prompt, char *str, int len);
38 void newprompt(char *prompt, char *str, int len);
39 void dotgoto(char *towhere, int display_name);
40 void serv_read(char *buf, int bytes);
41 void formout(char *name);
43 int fmout(int width, FILE *fp, char pagin, int height, int starting_lp, char subst);
44 void progress(long int curr, long int cmax);
45 int pattern(char *search, char *patn);
46 int file_checksum(char *filename);
47 int nukedir(char *dirname);
49 extern unsigned room_flags;
50 extern char room_name[];
52 extern char tempdir[];
53 extern int editor_pid;
54 extern char editor_path[];
55 extern int screenwidth;
56 extern int screenheight;
57 extern char fullname[];
59 extern char sigcaught;
60 extern char floor_mode;
61 extern char curr_floor;
68 extern char floorlist[128][SIZ];
71 void load_floorlist(void) {
75 for (a=0; a<128; ++a) floorlist[a][0] = 0;
80 strcpy(floorlist[0],"Main Floor");
83 while (serv_gets(buf), strcmp(buf,"000")) {
84 extract(floorlist[extract_int(buf,0)],buf,1);
89 void room_tree_list(struct roomlisting *rp) {
91 char rmname[ROOMNAMELEN];
99 if (rp->lnext != NULL) {
100 room_tree_list(rp->lnext);
103 if (sigcaught == 0) {
104 strcpy(rmname, rp->rlname);
106 if ((c + strlen(rmname) + 4) > screenwidth) {
108 /* line break, check the paginator */
112 if (f & QR_MAILBOX) {
113 color(BRIGHT_YELLOW);
115 else if (f & QR_PRIVATE) {
121 pprintf("%s",rmname);
122 if ((f & QR_DIRECTORY) && (f & QR_NETWORK)) pprintf("} ");
123 else if (f & QR_DIRECTORY) pprintf("] ");
124 else if (f & QR_NETWORK) pprintf(") ");
126 c = c + strlen(rmname) + 3;
129 if (rp->rnext != NULL) {
130 room_tree_list(rp->rnext);
138 * Room ordering stuff (compare first by floor, then by order)
140 int rordercmp(struct roomlisting *r1, struct roomlisting *r2)
142 if ((r1==NULL)&&(r2==NULL)) return(0);
143 if (r1==NULL) return(-1);
144 if (r2==NULL) return(1);
145 if (r1->rlfloor < r2->rlfloor) return(-1);
146 if (r1->rlfloor > r2->rlfloor) return(1);
147 if (r1->rlorder < r2->rlorder) return(-1);
148 if (r1->rlorder > r2->rlorder) return(1);
154 * Common code for all room listings
156 void listrms(char *variety)
160 struct roomlisting *rl = NULL;
161 struct roomlisting *rp;
162 struct roomlisting *rs;
165 /* Ask the server for a room list */
168 if (buf[0]!='1') return;
169 while (serv_gets(buf), strcmp(buf, "000")) {
170 rp = malloc(sizeof(struct roomlisting));
171 extract(rp->rlname, buf, 0);
172 rp->rlflags = extract_int(buf, 1);
173 rp->rlfloor = extract_int(buf, 2);
174 rp->rlorder = extract_int(buf, 3);
182 else while (rp != NULL) {
183 if (rordercmp(rp, rs)<0) {
184 if (rs->lnext == NULL) {
193 if (rs->rnext == NULL) {
204 room_tree_list(NULL);
210 void list_other_floors(void) {
214 for (a=0; a<128; ++a) if ((strlen(floorlist[a])>0)&&(a!=curr_floor)) {
215 if ((c + strlen(floorlist[a]) + 4) > screenwidth) {
219 pprintf("%s: ",floorlist[a]);
220 c = c + strlen(floorlist[a]) + 3;
226 * List known rooms. kn_floor_mode should be set to 0 for a 'flat' listing,
227 * 1 to list rooms on the current floor, or 1 to list rooms on all floors.
229 void knrooms(int kn_floor_mode)
236 if (kn_floor_mode == 0) {
238 pprintf("\n Rooms with unread messages:\n");
241 pprintf("\n\n No unseen messages in:\n");
246 if (kn_floor_mode == 1) {
248 pprintf("\n Rooms with unread messages on %s:\n",
249 floorlist[(int)curr_floor]);
250 sprintf(buf,"LKRN %d", curr_floor);
253 pprintf("\n\n Rooms with no new messages on %s:\n",
254 floorlist[(int)curr_floor]);
255 sprintf(buf,"LKRO %d",curr_floor);
258 pprintf("\n\n Other floors:\n");
263 if (kn_floor_mode == 2) {
264 for (a=0; a<128; ++a) if (floorlist[a][0]!=0) {
266 pprintf("\n Rooms on %s:\n",floorlist[a]);
267 sprintf(buf,"LKRA %d",a);
274 IFNEXPERT hit_any_key();
278 void listzrooms(void) { /* list public forgotten rooms */
280 pprintf("\n Forgotten public rooms:\n");
284 IFNEXPERT hit_any_key();
288 int set_room_attr(int ibuf, char *prompt, unsigned int sbit)
292 a = boolprompt(prompt, (ibuf&sbit));
294 if (!a) ibuf=(ibuf^sbit);
301 * Select a floor (used in several commands)
302 * The supplied argument is the 'default' floor number.
303 * This function returns the selected floor number.
305 int select_floor(int rfloor)
310 if (floor_mode == 1) {
311 if (floorlist[(int)curr_floor][0]==0) load_floorlist();
315 safestrncpy(floorstr,floorlist[rfloor],sizeof floorstr);
316 strprompt("Which floor",floorstr,SIZ);
317 for (a=0; a<128; ++a) {
318 if (!strcasecmp(floorstr,&floorlist[a][0]))
320 if ((newfloor<0)&&(!strncasecmp(floorstr,
321 &floorlist[a][0],strlen(floorstr))))
323 if ((newfloor<0)&&(pattern(&floorlist[a][0],
324 floorstr)>=0)) newfloor = a;
327 printf("\n One of:\n");
328 for (a=0; a<128; ++a)
329 if (floorlist[a][0]!=0)
333 } while(newfloor < 0);
343 * .<A>ide <E>dit room
345 void editthisroom(void) {
346 char rname[ROOMNAMELEN];
356 int expire_value = 0;
358 /* Fetch the existing room config */
362 printf("%s\n",&buf[4]);
366 extract(rname,&buf[4],0);
367 extract(rpass,&buf[4],1);
368 extract(rdir, &buf[4],2);
369 rflags = extract_int(&buf[4],3);
370 rfloor = extract_int(&buf[4],4);
371 rorder = extract_int(&buf[4],5);
374 /* Fetch the name of the current room aide */
377 if (buf[0]=='2') safestrncpy(raide,&buf[4],sizeof raide);
378 else strcpy(raide,"");
379 if (strlen(raide)==0) strcpy(raide,"none");
381 /* Fetch the expire policy (this will silently fail on old servers,
382 * resulting in "default" policy)
384 serv_puts("GPEX room");
387 expire_mode = extract_int(&buf[4], 0);
388 expire_value = extract_int(&buf[4], 1);
391 /* Now interact with the user. */
392 strprompt("Room name",rname,ROOMNAMELEN-1);
394 rfloor = select_floor(rfloor);
395 rflags = set_room_attr(rflags,"Private room",QR_PRIVATE);
396 if (rflags & QR_PRIVATE)
397 rflags = set_room_attr(rflags,
398 "Accessible by guessing room name",QR_GUESSNAME);
400 /* if it's public, clear the privacy classes */
401 if ((rflags & QR_PRIVATE)==0) {
402 if (rflags & QR_GUESSNAME) rflags = rflags - QR_GUESSNAME;
403 if (rflags & QR_PASSWORDED) rflags = rflags - QR_PASSWORDED;
406 /* if it's private, choose the privacy classes */
407 if ( (rflags & QR_PRIVATE)
408 && ( (rflags & QR_GUESSNAME) == 0) ) {
409 rflags = set_room_attr(rflags,
410 "Accessible by entering a password",QR_PASSWORDED);
412 if ( (rflags & QR_PRIVATE)
413 && ((rflags&QR_PASSWORDED)==QR_PASSWORDED) ) {
414 strprompt("Room password",rpass,9);
417 if ((rflags&QR_PRIVATE)==QR_PRIVATE) {
418 rbump = boolprompt("Cause current users to forget room", 0);
421 rflags = set_room_attr(rflags,"Preferred users only",QR_PREFONLY);
422 rflags = set_room_attr(rflags,"Read-only room",QR_READONLY);
423 rflags = set_room_attr(rflags,"Directory room",QR_DIRECTORY);
424 rflags = set_room_attr(rflags,"Permanent room",QR_PERMANENT);
425 if (rflags & QR_DIRECTORY) {
426 strprompt("Directory name",rdir,14);
427 rflags = set_room_attr(rflags,"Uploading allowed",QR_UPLOAD);
428 rflags = set_room_attr(rflags,"Downloading allowed",
430 rflags = set_room_attr(rflags,"Visible directory",QR_VISDIR);
432 rflags = set_room_attr(rflags,"Network shared room",QR_NETWORK);
433 rflags = set_room_attr(rflags,
434 "Automatically make all messages anonymous",QR_ANONONLY);
435 if ( (rflags & QR_ANONONLY) == 0) {
436 rflags = set_room_attr(rflags,
437 "Ask users whether to make messages anonymous",
440 rorder = intprompt("Listing order", rorder, 1, 127);
442 /* Ask about the room aide */
444 strprompt("Room aide (or 'none')",raide,29);
445 if (!strcasecmp(raide,"none")) {
450 snprintf(buf,sizeof buf,"QUSR %s",raide);
453 if (buf[0]!='2') printf("%s\n",&buf[4]);
455 } while(buf[0]!='2');
457 if (!strcasecmp(raide,"none")) strcpy(raide,"");
460 /* Angels and demons dancing in my head... */
462 sprintf(buf, "%d", expire_mode);
463 strprompt("Message expire policy (? for list)", buf, 1);
466 printf("0. Use the default for this floor\n");
467 printf("1. Never automatically expire messages\n");
468 printf("2. Expire by message count\n");
469 printf("3. Expire by message age\n");
471 } while((buf[0]<48)||(buf[0]>51));
472 expire_mode = buf[0] - 48;
474 /* ...lunatics and monsters underneath my bed */
475 if (expire_mode == 2) {
476 sprintf(buf, "%d", expire_value);
477 strprompt("Keep how many messages online?", buf, 10);
478 expire_value = atol(buf);
481 if (expire_mode == 3) {
482 sprintf(buf, "%d", expire_value);
483 strprompt("Keep messages for how many days?", buf, 10);
484 expire_value = atol(buf);
487 /* Give 'em a chance to change their minds */
488 printf("Save changes (y/n)? ");
491 snprintf(buf,sizeof buf,"SETA %s",raide);
494 if (buf[0]!='2') printf("%s\n",&buf[4]);
496 snprintf(buf, sizeof buf, "SPEX room|%d|%d",
497 expire_mode, expire_value);
501 snprintf(buf,sizeof buf,"SETR %s|%s|%s|%d|%d|%d|%d",
502 rname,rpass,rdir,rflags,rbump,rfloor,rorder);
505 printf("%s\n",&buf[4]);
506 if (buf[0]=='2') dotgoto(rname,2);
512 * un-goto the previous room
517 if (!strcmp(ugname,"")) return;
518 snprintf(buf,sizeof buf,"GOTO %s",ugname);
522 printf("%s\n",&buf[4]);
525 sprintf(buf,"SLRP %ld",uglsn);
528 if (buf[0]!='2') printf("%s\n",&buf[4]);
529 safestrncpy(buf,ugname,sizeof buf);
535 /* Here's the code for simply transferring the file to the client,
536 * for folks who have their own clientware. It's a lot simpler than
537 * the [XYZ]modem code below...
538 * (This function assumes that a download file is already open on the server)
540 void download_to_local_disk(char *supplied_filename, long total_bytes)
544 long transmitted_bytes = 0L;
551 strcpy(filename, supplied_filename);
552 if (strlen(filename)==0) {
553 newprompt("Filename: ", filename, 250);
556 printf("Enter the name of the directory to save '%s'\n",
558 printf("to, or press return for the current directory.\n");
559 newprompt("Directory: ", dbuf, sizeof dbuf);
560 if (strlen(dbuf)==0) strcpy(dbuf,".");
562 strcat(dbuf,filename);
564 savefp = fopen(dbuf,"w");
565 if (savefp == NULL) {
566 printf("Cannot open '%s': %s\n",dbuf,strerror(errno));
567 /* close the download file at the server */
571 printf("%s\n",&buf[4]);
575 progress(0,total_bytes);
576 while ( (transmitted_bytes < total_bytes) && (broken == 0) ) {
577 bb = total_bytes - transmitted_bytes;
578 aa = ((bb < 4096) ? bb : 4096);
579 sprintf(buf,"READ %ld|%ld",transmitted_bytes,aa);
583 printf("%s\n",&buf[4]);
586 packet = extract_int(&buf[4],0);
587 serv_read(dbuf,packet);
588 if (fwrite(dbuf,packet,1,savefp) < 1) broken = 1;
589 transmitted_bytes = transmitted_bytes + (long)packet;
590 progress(transmitted_bytes,total_bytes);
593 /* close the download file at the server */
597 printf("%s\n",&buf[4]);
604 * download() - download a file or files. The argument passed to this
605 * function determines which protocol to use.
606 * proto - 0 = paginate, 1 = xmodem, 2 = raw, 3 = ymodem, 4 = zmodem, 5 = save
608 void download(int proto)
613 char transmit_cmd[SIZ];
614 long total_bytes = 0L;
616 long transmitted_bytes = 0L;
622 if ((room_flags & QR_DOWNLOAD) == 0) {
623 printf("*** You cannot download from this room.\n");
627 newprompt("Enter filename: ",filename,255);
629 snprintf(buf,sizeof buf,"OPEN %s",filename);
633 printf("%s\n",&buf[4]);
636 total_bytes = extract_long(&buf[4],0);
638 /* Save to local disk, for folks with their own copy of the client */
640 download_to_local_disk(filename, total_bytes);
644 /* Meta-download for public clients */
645 printf("Fetching file from Citadel server...\n");
646 mkdir(tempdir, 0700);
647 snprintf(tempname, sizeof tempname, "%s/%s", tempdir, filename);
648 tpipe = fopen(tempname, "wb");
649 while ( (transmitted_bytes < total_bytes) && (broken == 0) ) {
650 progress(transmitted_bytes, total_bytes);
651 bb = total_bytes - transmitted_bytes;
652 aa = ((bb < 4096) ? bb : 4096);
653 sprintf(buf,"READ %ld|%ld",transmitted_bytes,aa);
657 printf("%s\n",&buf[4]);
659 packet = extract_int(&buf[4],0);
660 serv_read(dbuf,packet);
661 if (fwrite(dbuf,packet,1,tpipe) < 1) broken = 1;
662 transmitted_bytes = transmitted_bytes + (long)packet;
665 progress(transmitted_bytes, total_bytes);
667 /* close the download file at the server */
671 printf("%s\n",&buf[4]);
674 if (proto==0) sprintf(transmit_cmd, "SHELL=/dev/null; export SHELL; TERM=dumb; export TERM; exec more -d <%s",tempname);
675 else if (proto==1) sprintf(transmit_cmd, "exec sx %s", tempname);
676 else if (proto==3) sprintf(transmit_cmd, "exec sb %s", tempname);
677 else if (proto==4) sprintf(transmit_cmd, "exec sz %s", tempname);
678 else sprintf(transmit_cmd, "exec cat %s", tempname);
681 system(transmit_cmd);
684 /* clean up the temporary directory */
691 * read directory of this room
702 pprintf("%s\n",&buf[4]);
706 extract(comment,&buf[4],0);
707 extract(flnm,&buf[4],1);
708 pprintf("\nDirectory of %s on %s\n",flnm,comment);
709 pprintf("-----------------------\n");
710 while (serv_gets(buf), strcmp(buf,"000")) {
713 extract(comment,buf,2);
714 if (strlen(flnm)<=14)
715 pprintf("%-14s %8s %s\n",flnm,flsz,comment);
717 pprintf("%s\n%14s %8s %s\n",flnm,"",flsz,comment);
723 * add a user to a private room
726 char aaa[31],bbb[SIZ];
728 /* Because kicking people out of public rooms now sets a LOCKOUT
729 * flag, we need to be able to invite people into public rooms
730 * in order to let them back in again.
735 * if ((room_flags & QR_PRIVATE)==0) {
736 * printf("This is not a private room.\n");
741 newprompt("Name of user? ",aaa,30);
742 if (aaa[0]==0) return;
744 snprintf(bbb,sizeof bbb,"INVT %s",aaa);
747 printf("%s\n",&bbb[4]);
752 * kick a user out of a room
755 char aaa[31],bbb[SIZ];
757 newprompt("Name of user? ",aaa,30);
758 if (aaa[0]==0) return;
760 snprintf(bbb,sizeof bbb,"KICK %s",aaa);
763 printf("%s\n",&bbb[4]);
768 * aide command: kill the current room
770 void killroom(void) {
776 printf("%s\n",&aaa[4]);
780 printf("Are you sure you want to kill this room? ");
781 if (yesno()==0) return;
785 printf("%s\n",&aaa[4]);
786 if (aaa[0]!='2') return;
787 dotgoto("_BASEROOM_",0);
790 void forget(void) { /* forget the current room */
793 printf("Are you sure you want to forget this room? ");
794 if (yesno()==0) return;
799 printf("%s\n",&cmd[4]);
803 /* now return to the lobby */
804 dotgoto("_BASEROOM_",0);
813 char new_room_name[ROOMNAMELEN];
815 char new_room_pass[10];
823 printf("%s\n",&cmd[4]);
827 newprompt("Name for new room? ",new_room_name,ROOMNAMELEN-1);
828 if (strlen(new_room_name)==0) return;
829 for (a=0; a<strlen(new_room_name); ++a)
830 if (new_room_name[a] == '|') new_room_name[a]='_';
832 new_room_floor = select_floor((int)curr_floor);
834 IFNEXPERT formout("roomaccess");
836 printf( "<?>Help\n<1>Public room\n<2>Guess-name room\n"
837 "<3>Passworded room\n<4>Invitation-only room\n"
838 "<5>Personal room\n");
839 printf("Enter room type: ");
842 } while (((b<'1')||(b>'5')) && (b!='?'));
845 formout("roomaccess");
847 } while ((b<'1')||(b>'5'));
850 new_room_type = b - 1;
851 if (new_room_type==2) {
852 newprompt("Enter a room password: ",new_room_pass,9);
853 for (a=0; a<strlen(new_room_pass); ++a)
854 if (new_room_pass[a] == '|') new_room_pass[a]='_';
856 else strcpy(new_room_pass,"");
858 printf("\042%s\042, a",new_room_name);
859 if (b==1) printf(" public room.");
860 if (b==2) printf(" guess-name room.");
861 if (b==3) printf(" passworded room, password: %s",new_room_pass);
862 if (b==4) printf("n invitation-only room.");
863 if (b==5) printf(" personal room.");
864 printf("\nInstall it? (y/n) : ");
868 snprintf(cmd, sizeof cmd, "CRE8 1|%s|%d|%s|%d", new_room_name,
869 new_room_type, new_room_pass, new_room_floor);
873 printf("%s\n",&cmd[4]);
877 /* command succeeded... now GO to the new room! */
878 dotgoto(new_room_name,0);
883 void readinfo(void) { /* read info file for current room */
890 if (cmd[0]!='1') return;
892 fmout(screenwidth,NULL,
893 ((userflags & US_PAGINATOR) ? 1 : 0),
899 * <W>ho knows room...
901 void whoknows(void) {
906 pprintf("%s\n",&buf[5]);
909 while (serv_gets(buf), strncmp(buf,"000",3)) {
910 if (sigcaught==0) pprintf("%s\n",buf);
915 void do_edit(char *desc, char *read_cmd, char *check_cmd, char *write_cmd)
919 int b,cksum,editor_exit;
922 if (strlen(editor_path)==0) {
923 printf("Do you wish to re-enter %s? ",desc);
924 if (yesno()==0) return;
927 fp = fopen(temp,"w");
930 serv_puts(check_cmd);
933 printf("%s\n",&cmd[4]);
937 if (strlen(editor_path)>0) {
941 fp = fopen(temp,"w");
942 while (serv_gets(cmd), strcmp(cmd,"000")) {
943 fprintf(fp,"%s\n",cmd);
949 cksum = file_checksum(temp);
951 if (strlen(editor_path)>0) {
956 execlp(editor_path,editor_path,temp,NULL);
959 if (editor_pid>0) do {
961 b=wait(&editor_exit);
962 } while((b!=editor_pid)&&(b>=0));
964 printf("Executed %s\n", editor_path);
968 printf("Entering %s. ",desc);
969 printf("Press return twice when finished.\n");
975 if (file_checksum(temp) == cksum) {
976 printf("*** Aborted.\n");
980 serv_puts(write_cmd);
983 printf("%s\n",&cmd[4]);
988 while (fgets(cmd,SIZ-1,fp)!=NULL) {
989 cmd[strlen(cmd)-1] = 0;
1000 void enterinfo(void) { /* edit info file for current room */
1001 do_edit("the Info file for this room","RINF","EINF 0","EINF 1");
1004 void enter_bio(void) {
1006 snprintf(cmd,sizeof cmd,"RBIO %s",fullname);
1007 do_edit("your Bio",cmd,"NOOP","EBIO");
1011 * create a new floor
1013 void create_floor(void) {
1015 char newfloorname[SIZ];
1017 serv_puts("CFLR xx|0");
1020 printf("%s\n",&buf[4]);
1024 newprompt("Name for new floor: ",newfloorname,255);
1025 snprintf(buf,sizeof buf,"CFLR %s|1",newfloorname);
1029 printf("Floor has been created.\n");
1032 printf("%s\n",&buf[4]);
1037 * edit the current floor
1039 void edit_floor(void) {
1041 int expire_mode = 0;
1042 int expire_value = 0;
1044 if (floorlist[(int)curr_floor][0]==0) load_floorlist();
1046 /* Fetch the expire policy (this will silently fail on old servers,
1047 * resulting in "default" policy)
1049 serv_puts("GPEX floor");
1052 expire_mode = extract_int(&buf[4], 0);
1053 expire_value = extract_int(&buf[4], 1);
1056 /* Interact with the user */
1057 strprompt("Floor name",&floorlist[(int)curr_floor][0],255);
1059 /* Angels and demons dancing in my head... */
1061 sprintf(buf, "%d", expire_mode);
1062 strprompt("Floor default essage expire policy (? for list)",
1064 if (buf[0] == '?') {
1066 printf("0. Use the system default\n");
1067 printf("1. Never automatically expire messages\n");
1068 printf("2. Expire by message count\n");
1069 printf("3. Expire by message age\n");
1071 } while((buf[0]<48)||(buf[0]>51));
1072 expire_mode = buf[0] - 48;
1074 /* ...lunatics and monsters underneath my bed */
1075 if (expire_mode == 2) {
1076 sprintf(buf, "%d", expire_value);
1077 strprompt("Keep how many messages online?", buf, 10);
1078 expire_value = atol(buf);
1081 if (expire_mode == 3) {
1082 sprintf(buf, "%d", expire_value);
1083 strprompt("Keep messages for how many days?", buf, 10);
1084 expire_value = atol(buf);
1088 snprintf(buf, sizeof buf, "SPEX floor|%d|%d",
1089 expire_mode, expire_value);
1093 snprintf(buf,sizeof buf,"EFLR %d|%s",curr_floor,
1094 &floorlist[(int)curr_floor][0]);
1097 printf("%s\n",&buf[4]);
1105 * kill the current floor
1107 void kill_floor(void) {
1108 int floornum_to_delete,a;
1111 if (floorlist[(int)curr_floor][0]==0) load_floorlist();
1113 floornum_to_delete = (-1);
1114 printf("(Press return to abort)\n");
1115 newprompt("Delete which floor? ",buf,255);
1116 if (strlen(buf)==0) return;
1117 for (a=0; a<128; ++a)
1118 if (!strcasecmp(&floorlist[a][0],buf))
1119 floornum_to_delete = a;
1120 if (floornum_to_delete < 0) {
1121 printf("No such floor. Select one of:\n");
1122 for (a=0; a<128; ++a)
1123 if (floorlist[a][0]!=0)
1124 printf("%s\n",&floorlist[a][0]);
1126 } while (floornum_to_delete < 0);
1127 sprintf(buf,"KFLR %d|1",floornum_to_delete);
1130 printf("%s\n",&buf[4]);