1 /* Citadel/UX room-oriented routines */
11 #include <sys/types.h>
19 #define IFEXPERT if (userflags&US_EXPERT)
20 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
21 #define IFAIDE if (axlevel>=6)
22 #define IFNAIDE if (axlevel<6)
25 void sttybbs(int cmd);
26 void extract(char *dest, char *source, int parmnum);
27 int extract_int(char *source, int parmnum);
28 void hit_any_key(void);
31 void strprompt(char *prompt, char *str, int len);
32 void newprompt(char *prompt, char *str, int len);
33 int struncmp(char *lstr, char *rstr, int len);
34 void dotgoto(char *towhere, int display_name);
35 long extract_long(char *source, int parmnum);
36 void serv_read(char *buf, int bytes);
37 void formout(char *name);
39 int fmout(int width, FILE *fp, char pagin, int height, int starting_lp, char subst);
40 void citedit(FILE *fp, long int base_pos);
41 void progress(long int curr, long int cmax);
42 int pattern(char *search, char *patn);
43 int file_checksum(char *filename);
44 int nukedir(char *dirname);
45 void color(int colornum);
47 extern unsigned room_flags;
48 extern char room_name[];
50 extern char tempdir[];
51 extern int editor_pid;
52 extern char editor_path[];
53 extern int screenwidth;
54 extern int screenheight;
55 extern char fullname[];
57 extern char sigcaught;
58 extern char floor_mode;
59 extern char curr_floor;
66 extern char floorlist[128][256];
69 void load_floorlist(void) {
73 for (a=0; a<128; ++a) floorlist[a][0] = 0;
78 strcpy(floorlist[0],"Main Floor");
81 while (serv_gets(buf), strcmp(buf,"000")) {
82 extract(floorlist[extract_int(buf,0)],buf,1);
86 void listrms(char *variety)
94 if (buf[0]!='1') return;
98 while (serv_gets(buf), strcmp(buf,"000")) if (sigcaught==0) {
99 extract(rmname,buf,0);
100 if ((c + strlen(rmname) + 4) > screenwidth) {
104 f = extract_int(buf,1);
105 if (f & QR_MAILBOX) {
108 else if (f & QR_PRIVATE) {
115 if ((f & QR_DIRECTORY) && (f & QR_NETWORK)) printf("} ");
116 else if (f & QR_DIRECTORY) printf("] ");
117 else if (f & QR_NETWORK) printf(") ");
119 c = c + strlen(rmname) + 3;
126 void list_other_floors(void) {
130 for (a=0; a<128; ++a) if ((strlen(floorlist[a])>0)&&(a!=curr_floor)) {
131 if ((c + strlen(floorlist[a]) + 4) > screenwidth) {
135 printf("%s: ",floorlist[a]);
136 c = c + strlen(floorlist[a]) + 3;
142 * List known rooms. kn_floor_mode should be set to 0 for a 'flat' listing,
143 * 1 to list rooms on the current floor, or 1 to list rooms on all floors.
145 void knrooms(int kn_floor_mode)
152 if (kn_floor_mode == 0) {
154 printf("\n Rooms with unread messages:\n");
157 printf("\n\n No unseen messages in:\n");
162 if (kn_floor_mode == 1) {
164 printf("\n Rooms with unread messages on %s:\n",
165 floorlist[(int)curr_floor]);
166 sprintf(buf,"LKRN %d",curr_floor);
169 printf("\n\n Rooms with no new messages on %s:\n",
170 floorlist[(int)curr_floor]);
171 sprintf(buf,"LKRO %d",curr_floor);
174 printf("\n\n Other floors:\n");
179 if (kn_floor_mode == 2) {
180 for (a=0; a<128; ++a) if (floorlist[a][0]!=0) {
182 printf("\n Rooms on %s:\n",floorlist[a]);
183 sprintf(buf,"LKRA %d",a);
190 IFNEXPERT hit_any_key();
194 void listzrooms(void) { /* list public forgotten rooms */
196 printf("\n Forgotten public rooms:\n");
200 IFNEXPERT hit_any_key();
204 int set_room_attr(int ibuf, char *prompt, unsigned int sbit)
208 printf("%s [%s]? ",prompt,((ibuf&sbit) ? "Yes":"No"));
209 a=yesno_d(ibuf&sbit);
211 if (!a) ibuf=(ibuf^sbit);
218 * Select a floor (used in several commands)
219 * The supplied argument is the 'default' floor number.
220 * This function returns the selected floor number.
222 int select_floor(int rfloor)
227 if (floor_mode == 1) {
228 if (floorlist[(int)curr_floor][0]==0) load_floorlist();
232 safestrncpy(floorstr,floorlist[rfloor],sizeof floorstr);
233 strprompt("Which floor",floorstr,256);
234 for (a=0; a<128; ++a) {
235 if (!strucmp(floorstr,&floorlist[a][0]))
237 if ((newfloor<0)&&(!struncmp(floorstr,
238 &floorlist[a][0],strlen(floorstr))))
240 if ((newfloor<0)&&(pattern(&floorlist[a][0],
241 floorstr)>=0)) newfloor = a;
244 printf("\n One of:\n");
245 for (a=0; a<128; ++a)
246 if (floorlist[a][0]!=0)
250 } while(newfloor < 0);
260 * .<A>ide <E>dit room
262 void editthisroom(void) {
263 char rname[ROOMNAMELEN];
272 int expire_value = 0;
274 /* Fetch the existing room config */
278 printf("%s\n",&buf[4]);
282 extract(rname,&buf[4],0);
283 extract(rpass,&buf[4],1);
284 extract(rdir, &buf[4],2);
285 rflags = extract_int(&buf[4],3);
286 rfloor = extract_int(&buf[4],4);
289 /* Fetch the name of the current room aide */
292 if (buf[0]=='2') safestrncpy(raide,&buf[4],sizeof raide);
293 else strcpy(raide,"");
294 if (strlen(raide)==0) strcpy(raide,"none");
296 /* Fetch the expire policy (this will silently fail on old servers,
297 * resulting in "default" policy)
299 serv_puts("GPEX room");
302 expire_mode = extract_int(&buf[4], 0);
303 expire_value = extract_int(&buf[4], 1);
306 /* Now interact with the user. */
307 strprompt("Room name",rname,ROOMNAMELEN-1);
309 rfloor = select_floor(rfloor);
310 rflags = set_room_attr(rflags,"Private room",QR_PRIVATE);
311 if (rflags & QR_PRIVATE)
312 rflags = set_room_attr(rflags,
313 "Accessible by guessing room name",QR_GUESSNAME);
315 /* if it's public, clear the privacy classes */
316 if ((rflags & QR_PRIVATE)==0) {
317 if (rflags & QR_GUESSNAME) rflags = rflags - QR_GUESSNAME;
318 if (rflags & QR_PASSWORDED) rflags = rflags - QR_PASSWORDED;
321 /* if it's private, choose the privacy classes */
322 if ( (rflags & QR_PRIVATE)
323 && ( (rflags & QR_GUESSNAME) == 0) ) {
324 rflags = set_room_attr(rflags,
325 "Accessible by entering a password",QR_PASSWORDED);
327 if ( (rflags & QR_PRIVATE)
328 && ((rflags&QR_PASSWORDED)==QR_PASSWORDED) ) {
329 strprompt("Room password",rpass,9);
332 if ((rflags&QR_PRIVATE)==QR_PRIVATE) {
333 printf("Cause current users to forget room [No] ? ");
334 if (yesno_d(0)==1) rbump = 1;
337 rflags = set_room_attr(rflags,"Preferred users only",QR_PREFONLY);
338 rflags = set_room_attr(rflags,"Read-only room",QR_READONLY);
339 rflags = set_room_attr(rflags,"Directory room",QR_DIRECTORY);
340 rflags = set_room_attr(rflags,"Permanent room",QR_PERMANENT);
341 if (rflags & QR_DIRECTORY) {
342 strprompt("Directory name",rdir,14);
343 rflags = set_room_attr(rflags,"Uploading allowed",QR_UPLOAD);
344 rflags = set_room_attr(rflags,"Downloading allowed",
346 rflags = set_room_attr(rflags,"Visible directory",QR_VISDIR);
348 rflags = set_room_attr(rflags,"Network shared room",QR_NETWORK);
349 rflags = set_room_attr(rflags,
350 "Automatically make all messages anonymous",QR_ANONONLY);
351 if ( (rflags & QR_ANONONLY) == 0) {
352 rflags = set_room_attr(rflags,
353 "Ask users whether to make messages anonymous",
358 /* Ask about the room aide */
360 strprompt("Room aide (or 'none')",raide,29);
361 if (!strucmp(raide,"none")) {
366 snprintf(buf,sizeof buf,"QUSR %s",raide);
369 if (buf[0]!='2') printf("%s\n",&buf[4]);
371 } while(buf[0]!='2');
373 if (!strucmp(raide,"none")) strcpy(raide,"");
376 /* Angels and demons dancing in my head... */
378 sprintf(buf, "%d", expire_mode);
379 strprompt("Message expire policy (? for list)", buf, 1);
382 printf("0. Use the default for this floor\n");
383 printf("1. Never automatically expire messages\n");
384 printf("2. Expire by message count\n");
385 printf("3. Expire by message age\n");
387 } while((buf[0]<48)||(buf[0]>51));
388 expire_mode = buf[0] - 48;
390 /* ...lunatics and monsters underneath my bed */
391 if (expire_mode == 2) {
392 sprintf(buf, "%d", expire_value);
393 strprompt("Keep how many messages online?", buf, 10);
394 expire_value = atol(buf);
397 if (expire_mode == 3) {
398 sprintf(buf, "%d", expire_value);
399 strprompt("Keep messages for how many days?", buf, 10);
400 expire_value = atol(buf);
403 /* Give 'em a chance to change their minds */
404 printf("Save changes (y/n)? ");
407 snprintf(buf,sizeof buf,"SETA %s",raide);
410 if (buf[0]!='2') printf("%s\n",&buf[4]);
412 snprintf(buf, sizeof buf, "SPEX room|%d|%d",
413 expire_mode, expire_value);
417 snprintf(buf,sizeof buf,"SETR %s|%s|%s|%d|%d|%d",
418 rname,rpass,rdir,rflags,rbump,rfloor);
421 printf("%s\n",&buf[4]);
422 if (buf[0]=='2') dotgoto(rname,2);
428 * un-goto the previous room
433 if (!strcmp(ugname,"")) return;
434 snprintf(buf,sizeof buf,"GOTO %s",ugname);
438 printf("%s\n",&buf[4]);
441 sprintf(buf,"SLRP %ld",uglsn);
444 if (buf[0]!='2') printf("%s\n",&buf[4]);
445 safestrncpy(buf,ugname,sizeof buf);
452 * download() - download a file or files. The argument passed to this
453 * function determines which protocol to use.
455 void download(int proto)
459 - 0 = paginate, 1 = xmodem, 2 = raw, 3 = ymodem, 4 = zmodem, 5 = save
466 long total_bytes = 0L;
467 long transmitted_bytes = 0L;
476 if ((room_flags & QR_DOWNLOAD) == 0) {
477 printf("*** You cannot download from this room.\n");
481 newprompt("Enter filename: ",filename,255);
483 snprintf(buf,sizeof buf,"OPEN %s",filename);
487 printf("%s\n",&buf[4]);
490 total_bytes = extract_long(&buf[4],0);
493 /* Here's the code for simply transferring the file to the client,
494 * for folks who have their own clientware. It's a lot simpler than
495 * the [XYZ]modem code below...
498 printf("Enter the name of the directory to save '%s'\n",
500 printf("to, or press return for the current directory.\n");
501 newprompt("Directory: ",dbuf,256);
502 if (strlen(dbuf)==0) strcpy(dbuf,".");
504 strcat(dbuf,filename);
506 savefp = fopen(dbuf,"w");
507 if (savefp == NULL) {
508 printf("Cannot open '%s': %s\n",dbuf,strerror(errno));
509 /* close the download file at the server */
513 printf("%s\n",&buf[4]);
517 progress(0,total_bytes);
518 while ( (transmitted_bytes < total_bytes) && (broken == 0) ) {
519 bb = total_bytes - transmitted_bytes;
520 aa = ((bb < 4096) ? bb : 4096);
521 sprintf(buf,"READ %ld|%ld",transmitted_bytes,aa);
525 printf("%s\n",&buf[4]);
528 packet = extract_int(&buf[4],0);
529 serv_read(dbuf,packet);
530 if (fwrite(dbuf,packet,1,savefp) < 1) broken = 1;
531 transmitted_bytes = transmitted_bytes + (long)packet;
532 progress(transmitted_bytes,total_bytes);
535 /* close the download file at the server */
539 printf("%s\n",&buf[4]);
546 snprintf(buf,sizeof buf,"%s/%s",tempdir,filename);
549 /* We do the remainder of this function as a separate process in
550 * order to allow recovery if the transfer is aborted. If the
551 * file transfer program aborts, the first child process receives a
552 * "broken pipe" signal and aborts. We *should* be able to catch
553 * this condition with signal(), but it doesn't seem to work on all
558 /* wait for the download to finish */
559 while (wait(&b)!=a) ;;
561 /* close the download file at the server */
565 printf("%s\n",&buf[4]);
567 /* clean up the temporary directory */
572 snprintf(buf,sizeof buf,"%s/%s",tempdir,filename); /* full pathname */
574 /* The next fork() creates a second child process that is used for
575 * the actual file transfer program (usually sz).
578 if (proto_pid == 0) {
581 signal(SIGINT,SIG_DFL);
582 signal(SIGQUIT,SIG_DFL);
583 snprintf(dbuf,sizeof dbuf,"SHELL=/dev/null; export SHELL; TERM=dumb; export TERM; exec more -d <%s",buf);
589 signal(SIGINT,SIG_DFL);
590 signal(SIGQUIT,SIG_DFL);
591 if (proto==1) execlp("sx","sx",buf,NULL);
592 if (proto==2) execlp("cat","cat",buf,NULL);
593 if (proto==3) execlp("sb","sb",buf,NULL);
594 if (proto==4) execlp("sz","sz",buf,NULL);
595 execlp("cat","cat",buf,NULL);
599 tpipe = fopen(buf,"w");
601 while ( (transmitted_bytes < total_bytes) && (broken == 0) ) {
602 bb = total_bytes - transmitted_bytes;
603 aa = ((bb < 4096) ? bb : 4096);
604 sprintf(buf,"READ %ld|%ld",transmitted_bytes,aa);
608 printf("%s\n",&buf[4]);
611 packet = extract_int(&buf[4],0);
612 serv_read(dbuf,packet);
613 if (fwrite(dbuf,packet,1,tpipe) < 1) broken = 1;
614 transmitted_bytes = transmitted_bytes + (long)packet;
616 if (tpipe!=NULL) fclose(tpipe);
618 /* Hang out and wait for the file transfer program to finish */
619 while (wait(&a) != proto_pid) ;;
623 exit(0); /* transfer control back to the main program */
628 * read directory of this room
639 printf("%s\n",&buf[4]);
643 extract(comment,&buf[4],0);
644 extract(flnm,&buf[4],1);
645 printf("\nDirectory of %s on %s\n",flnm,comment);
646 printf("-----------------------\n");
647 while (serv_gets(buf), strcmp(buf,"000")) {
650 extract(comment,buf,2);
651 if (strlen(flnm)<=14)
652 printf("%-14s %8s %s\n",flnm,flsz,comment);
654 printf("%s\n%14s %8s %s\n",flnm,"",flsz,comment);
660 * add a user to a private room
663 char aaa[31],bbb[256];
665 if ((room_flags & QR_PRIVATE)==0) {
666 printf("This is not a private room.\n");
670 newprompt("Name of user? ",aaa,30);
671 if (aaa[0]==0) return;
673 snprintf(bbb,sizeof bbb,"INVT %s",aaa);
676 printf("%s\n",&bbb[4]);
681 * kick a user out of a room
684 char aaa[31],bbb[256];
686 newprompt("Name of user? ",aaa,30);
687 if (aaa[0]==0) return;
689 snprintf(bbb,sizeof bbb,"KICK %s",aaa);
692 printf("%s\n",&bbb[4]);
697 * aide command: kill the current room
699 void killroom(void) {
705 printf("%s\n",&aaa[4]);
709 printf("Are you sure you want to kill this room? ");
710 if (yesno()==0) return;
714 printf("%s\n",&aaa[4]);
715 if (aaa[0]!='2') return;
716 dotgoto("_BASEROOM_",0);
719 void forget(void) { /* forget the current room */
722 printf("Are you sure you want to forget this room? ");
723 if (yesno()==0) return;
728 printf("%s\n",&cmd[4]);
732 /* now return to the lobby */
733 dotgoto("_BASEROOM_",0);
742 char new_room_name[ROOMNAMELEN];
744 char new_room_pass[10];
752 printf("%s\n",&cmd[4]);
756 newprompt("Name for new room? ",new_room_name,ROOMNAMELEN-1);
757 if (strlen(new_room_name)==0) return;
758 for (a=0; a<strlen(new_room_name); ++a)
759 if (new_room_name[a] == '|') new_room_name[a]='_';
761 new_room_floor = select_floor((int)curr_floor);
763 IFNEXPERT formout("roomaccess");
765 printf("<?>Help\n<1>Public room\n<2>Guess-name room\n");
766 printf("<3>Passworded room\n<4>Invitation-only room\n");
767 printf("Enter room type: ");
770 } while (((b<'1')||(b>'4')) && (b!='?'));
773 formout("roomaccess");
775 } while ((b<'1')||(b>'4'));
778 new_room_type = b - 1;
779 if (new_room_type==2) {
780 newprompt("Enter a room password: ",new_room_pass,9);
781 for (a=0; a<strlen(new_room_pass); ++a)
782 if (new_room_pass[a] == '|') new_room_pass[a]='_';
784 else strcpy(new_room_pass,"");
786 printf("\042%s\042, a",new_room_name);
787 if (b==1) printf(" public room.");
788 if (b==2) printf(" guess-name room.");
789 if (b==3) printf(" passworded room, password: %s",new_room_pass);
790 if (b==4) printf("n invitation-only room.");
791 printf("\nInstall it? (y/n) : ");
795 snprintf(cmd, sizeof cmd, "CRE8 1|%s|%d|%s|%d", new_room_name,
796 new_room_type, new_room_pass, new_room_floor);
800 printf("%s\n",&cmd[4]);
804 /* command succeeded... now GO to the new room! */
805 dotgoto(new_room_name,0);
810 void readinfo(void) { /* read info file for current room */
817 if (cmd[0]!='1') return;
819 fmout(screenwidth,NULL,
820 ((userflags & US_PAGINATOR) ? 1 : 0),
826 * <W>ho knows room...
828 void whoknows(void) {
833 printf("%s\n",&buf[5]);
837 sttybbs(SB_YES_INTR);
838 while (serv_gets(buf), strncmp(buf,"000",3)) {
839 if (sigcaught==0) printf("%s\n",buf);
845 void do_edit(char *desc, char *read_cmd, char *check_cmd, char *write_cmd)
849 int b,cksum,editor_exit;
852 if (strlen(editor_path)==0) {
853 printf("Do you wish to re-enter %s? ",desc);
854 if (yesno()==0) return;
857 fp = fopen(temp,"w");
860 serv_puts(check_cmd);
863 printf("%s\n",&cmd[4]);
867 if (strlen(editor_path)>0) {
871 fp = fopen(temp,"w");
872 while (serv_gets(cmd), strcmp(cmd,"000")) {
873 fprintf(fp,"%s\n",cmd);
879 cksum = file_checksum(temp);
881 if (strlen(editor_path)>0) {
886 execlp(editor_path,editor_path,temp,NULL);
889 if (editor_pid>0) do {
891 b=wait(&editor_exit);
892 } while((b!=editor_pid)&&(b>=0));
894 printf("Executed %s\n", editor_path);
898 printf("Entering %s. ",desc);
899 printf("Press return twice when finished.\n");
905 if (file_checksum(temp) == cksum) {
906 printf("*** Aborted.\n");
910 serv_puts(write_cmd);
913 printf("%s\n",&cmd[4]);
918 while (fgets(cmd,255,fp)!=NULL) {
919 cmd[strlen(cmd)-1] = 0;
930 void enterinfo(void) { /* edit info file for current room */
931 do_edit("the Info file for this room","RINF","EINF 0","EINF 1");
934 void enter_bio(void) {
936 snprintf(cmd,sizeof cmd,"RBIO %s",fullname);
937 do_edit("your Bio",cmd,"NOOP","EBIO");
943 void create_floor(void) {
945 char newfloorname[256];
947 serv_puts("CFLR xx|0");
950 printf("%s\n",&buf[4]);
954 newprompt("Name for new floor: ",newfloorname,255);
955 snprintf(buf,sizeof buf,"CFLR %s|1",newfloorname);
959 printf("Floor has been created.\n");
962 printf("%s\n",&buf[4]);
967 * edit the current floor
969 void edit_floor(void) {
972 int expire_value = 0;
974 if (floorlist[(int)curr_floor][0]==0) load_floorlist();
976 /* Fetch the expire policy (this will silently fail on old servers,
977 * resulting in "default" policy)
979 serv_puts("GPEX floor");
982 expire_mode = extract_int(&buf[4], 0);
983 expire_value = extract_int(&buf[4], 1);
986 /* Interact with the user */
987 strprompt("Floor name",&floorlist[(int)curr_floor][0],255);
989 /* Angels and demons dancing in my head... */
991 sprintf(buf, "%d", expire_mode);
992 strprompt("Floor default essage expire policy (? for list)",
996 printf("0. Use the system default\n");
997 printf("1. Never automatically expire messages\n");
998 printf("2. Expire by message count\n");
999 printf("3. Expire by message age\n");
1001 } while((buf[0]<48)||(buf[0]>51));
1002 expire_mode = buf[0] - 48;
1004 /* ...lunatics and monsters underneath my bed */
1005 if (expire_mode == 2) {
1006 sprintf(buf, "%d", expire_value);
1007 strprompt("Keep how many messages online?", buf, 10);
1008 expire_value = atol(buf);
1011 if (expire_mode == 3) {
1012 sprintf(buf, "%d", expire_value);
1013 strprompt("Keep messages for how many days?", buf, 10);
1014 expire_value = atol(buf);
1018 snprintf(buf, sizeof buf, "SPEX floor|%d|%d",
1019 expire_mode, expire_value);
1023 snprintf(buf,sizeof buf,"EFLR %d|%s",curr_floor,
1024 &floorlist[(int)curr_floor][0]);
1027 printf("%s\n",&buf[4]);
1035 * kill the current floor
1037 void kill_floor(void) {
1038 int floornum_to_delete,a;
1041 if (floorlist[(int)curr_floor][0]==0) load_floorlist();
1043 floornum_to_delete = (-1);
1044 printf("(Press return to abort)\n");
1045 newprompt("Delete which floor? ",buf,255);
1046 if (strlen(buf)==0) return;
1047 for (a=0; a<128; ++a)
1048 if (!strucmp(&floorlist[a][0],buf))
1049 floornum_to_delete = a;
1050 if (floornum_to_delete < 0) {
1051 printf("No such floor. Select one of:\n");
1052 for (a=0; a<128; ++a)
1053 if (floorlist[a][0]!=0)
1054 printf("%s\n",&floorlist[a][0]);
1056 } while (floornum_to_delete < 0);
1057 sprintf(buf,"KFLR %d|1",floornum_to_delete);
1060 printf("%s\n",&buf[4]);