1 /*
2  *  powwow  --  mud client with telnet protocol
3  *
4  *  Copyright (C) 1998,2000,2002 by Massimiliano Ghilardi
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 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *
12  * History:
13  *
14  * Initially inspired to the Tintin client by Peter Unold,
15  * Powwow contains no Tintin code.
16  * The original program Cancan, written by Mattias Engdeg�rd (Yorick)
17  * (f91-men@nada.kth.se) 1992-94,
18  * was greatly improved upon by Vivriel, Thuzzle and Ilie and then
19  * transformed from Cancan into Powwow by Cosmos who worked
20  * to make it yet more powerful.
21  * AmigaDOS porting attempt by Fror.
22  * Many new features added by Dain.
23  * As usual, all the developers are in debt to countless users
24  * for suggestions and debugging.
25  * Maintance was taken over by Steve Slaven (bpk@hoopajoo.net) in 2005
26  */
27 
28 /*
29  * Set this to whatever you like
30  *
31  * #define POWWOW_DIR	"/home/gustav/powwow"
32  */
33 
34 #ifdef USE_LOCALE
35   #define POWWOW_HACKERS "Yorick, Vivriel, Thuzzle, Ilie, Fr\363r, D\341in"
36   #define COPYRIGHT      "\251"
37 #else
38   #define POWWOW_HACKERS "Yorick, Vivriel, Thuzzle, Ilie, Fror, Dain"
39   #define COPYRIGHT      "Copyright"
40 #endif
41 
42 #define POWWOW_VERSION VERSION                          \
43     ", " COPYRIGHT " 2000-2005 by Cosmos\n"             \
44     COPYRIGHT " 2005 by bpk - http://hoopajoo.net\n"    \
45     "(contributions by " POWWOW_HACKERS ")\n"
46 
47 #define HELPNAME "powwow.help"
48 #define COPYNAME "COPYING"
49 
50 #ifndef POWWOW_DIR
51 # define POWWOW_DIR "./"
52 #endif
53 
54 #include <stdio.h>
55 #include <string.h>
56 #include <stdlib.h>
57 #include <fcntl.h>
58 #include <ctype.h>
59 #include <errno.h>
60 #include <time.h>
61 
62 #include <sys/param.h>
63 #include <sys/stat.h>
64 #include <sys/types.h>
65 #include <sys/time.h>
66 #include <sys/wait.h>
67 #include <memory.h>
68 #include <unistd.h>
69 
70 #ifdef USE_LOCALE
71 #include <locale.h>
72 #endif
73 
74 /* are these really needed? */
75 extern int errno;
76 extern int select();
77 
78 #ifdef USE_REGEXP
79 # include <regex.h>
80 #endif
81 
82 #include "defines.h"
83 #include "main.h"
84 #include "utils.h"
85 #include "beam.h"
86 #include "cmd.h"
87 #include "cmd2.h"
88 #include "edit.h"
89 #include "map.h"
90 #include "list.h"
91 #include "tcp.h"
92 #include "tty.h"
93 #include "eval.h"
94 #include "log.h"
95 
96 /*     local function declarations       */
97 #ifdef MOTDFILE
98 static void printmotd		__P ((void));
99 #endif
100 static void mainloop		__P ((void));
101 static void exec_delays		__P ((void));
102 static void prompt_reset_iac    __P ((void));
103 static void get_remote_input	__P ((void));
104 static void get_user_input	__P ((void));
105 
106 static int  search_action_or_prompt	__P ((char *line, char clearline, char copyprompt));
107 #define search_action(line, clearline) search_action_or_prompt((line), (clearline), 0)
108 #define search_prompt(line, copyprompt) search_action_or_prompt((line), 0, (copyprompt)+1)
109 
110 static void set_params		__P ((char *line, int *match_s, int *match_e));
111 static void parse_commands	__P ((char *command, char *arg));
112 static int  subst_param		__P ((ptr *buf, char *src));
113 static int  jit_subst_vars	__P ((ptr *buf, char *src));
114 
115 
116 /*        GLOBALS                */
117 static char *helpname = HELPNAME;
118 static char *copyname = COPYNAME;
119 
120 long received = 0;	/* amount of data received from remote host */
121 long sent = 0;		/* amount of data sent to remote host */
122 
123 VOLATILE char confirm = 0; /* 1 if just tried to quit */
124 int  history_done = 0;	/* number of recursive #history commands */
125 int  prompt_status = 0;	/* prompt status: 0 = ready -> nothing to do;
126 			 *  1 if internal echo -> must redraw;
127 			 * -1 if something sent to MUD -> waiting for it.
128 			 */
129 int  line_status = 0;	/* input line status: 0 = ready -> nothing to do;
130 			 *  1 if printed something -> must redraw.
131 			 */
132 
133 int limit_mem = 0;	/* if !=0, max len of a string or text */
134 
135 char opt_echo = 1;	/* 1 if text sent to MUD must be echoed */
136 char opt_keyecho = 1;	/* 1 if binds must be echoed */
137 char opt_info = 1;	/* 0 if internal messages are suppressed */
138 char opt_exit = 0;	/* 1 to autoquit when closing last conn. */
139 char opt_history;	/* 1 if to save also history */
140 char opt_words = 0;	/* 1 if to save also word completion list */
141 char opt_compact = 0;	/* 1 if to clear prompt between remote messages */
142 char opt_debug = 0;	/* 1 if to echo every line before executing it */
143 char opt_speedwalk = 0;	/* 1 = speedwalk on */
144 char opt_wrap = 0;	/* 1 = word wrap active */
145 char opt_autoprint = 0;	/* 1 = automatically #print lines matched by actions */
146 char opt_reprint = 0;	/* 1 = reprint sent commands when we get a prompt */
147 char opt_sendsize = 0;	/* 1 = send term size upon connect */
148 char opt_autoclear = 1;	/* 1 = clear input line before executing commands
149 			 * from spawned programs.
150 			 * if 0, spawned progs must #clear before printing
151 			 */
152 
153 char hostname[BUFSIZE];
154 int portnumber;
155 static char powwow_dir[BUFSIZE];   /* default path to definition files */
156 char deffile[BUFSIZE];             /* name and path of definition file */
157 char helpfile[BUFSIZE];            /* name and path of help file */
158 char copyfile[BUFSIZE];            /* name and path of copyright file */
159 aliasnode *aliases[MAX_HASH];      /* head of alias hash list */
160 aliasnode *sortedaliases;          /* head of (ASCII) sorted alias list */
161 actionnode *actions;               /* head of action list */
162 promptnode *prompts;               /* head of prompt list */
163 marknode *markers;                 /* head of mark list */
164 int a_nice = 0;                    /* default priority of new actions/marks */
165 keynode *keydefs;                  /* head of key binding list */
166 delaynode *delays;                 /* head of delayed commands list */
167 delaynode *dead_delays;            /* head of dead-delayed commands list */
168 
169 varnode *named_vars[2][MAX_HASH];  /* head of named variables hash list */
170 varnode *sortednamed_vars[2];	   /* head of (ASCII) sorted named variables list */
171 int max_named_vars = 100;	   /* max number of named vars (grows as needed) */
172 int num_named_vars[2];		   /* number of named variables actually used */
173 
174 static param_stack paramstk;	   /* stack of local unnamed vars */
175 static unnamedvar global_var[NUMTOT]; /* global unnamed vars */
176 
177 vars *var;			   /* vector of all vars */
178 
179 ptr globptr[2];			   /* global ptr buffer */
180 char globptrok = 1|2;		   /* x&i = 0 if globptr[i] is in use */
181 
182 varnode *prompt;		   /* $prompt is always set */
183 ptr marked_prompt;		   /* $prompt with marks added */
184 static varnode *last_line;	   /* $line is always set to
185 				    * the last line processed */
186 
187 vtime now;                         /* current time */
188 int now_updated;		   /* current time is up to date */
189 vtime start_time;                  /* time of powwow timer startup */
190 vtime ref_time;                    /* time corresponding to timer == 0 */
191 
192 function_any last_edit_cmd;	   /* GH: keep track of for repeated cmds */
193 
194 clock_t start_clock, cpu_clock;
195 
196 char initstr[BUFSIZE];             /* initial string to send on connect */
197 
198 int linemode = 0;                  /* line mode flags (LM_* in main.h) */
199 
200 /* for line editing */
201 int cols=80, lines=24;    /* screen size */
202 int cols_1=79;		  /* == cols if tty_wrapglitch, == cols-1 otherwise */
203 int olines;		  /* previous screen size */
204 int col0;                 /* input line offset (= printstrlen of prompt) */
205 int line0;                /* screen line where the input line starts */
206 char edbuf[BUFSIZE];      /* line editing buffer */
207 int edlen;                /* length of current input line */
208 int pos = 0;              /* cursor position in line */
209 char surely_isprompt = 0; /* !=0 if last #prompt set #isprompt */
210 char verbatim = 0;        /* 1 = don't expand aliases or process semicolons */
211 char prefixstr[BUFSIZE];  /* inserted in the editing buffer each time */
212 char inserted_next[BUFSIZE];/* inserted in buffer just once */
213 char flashback = 0;	  /* cursor is on excursion and should be put back */
214 int excursion;		  /* where the excursion is */
215 char edattrbeg[CAPLEN];   /* starting input line attributes */
216 char edattrend[CAPLEN];   /* ending input line attributes */
217 int  edattrbg;		  /* input line attributes do change bg color */
218 
219 /* signals handling */
220 VOLATILE int sig_pending, sig_winch_got, sig_chld_got;
221 
222 
223 /* GH: different ID characters for different action types */
224 /*
225  * Cosmos: they are hardcoded in cmd2.c, function parse_action()
226  * so don't change them.
227  */
228 char action_chars[ACTION_TYPES] = { '>', '%' };
229 
230 /* GH: different delimeter modes */
231 char *delim_list[] = { " ;", " <>!=(),;\"'{}[]+-/*%", 0 };
232 int   delim_len [] = {   2 ,                     21 , 0 };
233 char *delim_name[] = { "normal",    "program", "custom" };
234 int   delim_mode = DELIM_NORMAL;
235 
236 /* Group delimiter */
237 char *group_delim;
238 
__P2(int,argc,char **,argv)239 int main __P2 (int,argc, char **,argv)
240 {
241     char *p;
242     int i;
243     int read_file = 0;	/* GH: if true, powwow was started with
244 			 * a file argument, and initstr shall be ran */
245 
246 #ifdef USE_LOCALE
247     if (!setlocale(LC_ALL, "")) {
248         fprintf(stderr, "Failed setlocale(LC_ALL, \"\")\n");
249     }
250 #endif
251 
252     /* initializations */
253     initstr[0] = 0;
254     memzero(conn_list, sizeof(conn_list));
255     group_delim = my_strdup( "@" );
256 
257     update_now();
258     ref_time = start_time = movie_last = now;
259 
260     start_clock = cpu_clock = clock();
261 #ifndef NO_RANDOM
262     init_random((int)now.tv_sec);
263 #endif
264 
265     initialize_cmd();
266 
267     if ((p = getenv("POWWOWDIR"))) {
268 	strcpy(powwow_dir, p);
269 	if (powwow_dir[strlen(powwow_dir) - 1] != '/')
270 	    strcat(powwow_dir, "/");
271     } else
272 	powwow_dir[0] = '\0';
273 
274     if ((p = getenv("POWWOWHELP")))
275 	strcpy(helpfile, p);
276     else if (powwow_dir[0])
277 	strcpy(helpfile, powwow_dir);
278     else
279 	strcpy(helpfile, POWWOW_DIR);
280 
281     if (helpfile[strlen(helpfile) - 1] != '/')
282 	strcat(helpfile, "/");
283     strcat(helpfile, helpname);
284     if (access(helpfile, R_OK) == -1 && !access(helpname, R_OK))
285 	strcpy(helpfile, helpname);
286 
287     if (powwow_dir[0])
288 	strcpy(copyfile, powwow_dir);
289     else
290 	strcpy(copyfile, POWWOW_DIR);
291     if (copyfile[strlen(copyfile) - 1] != '/')
292 	strcat(copyfile, "/");
293     strcat(copyfile, copyname);
294     if (access(copyfile, R_OK) == -1 && !access(copyname, R_OK))
295 	strcpy(copyfile, copyname);
296 
297     /* initialize variables */
298     if ((var = (vars *)malloc(sizeof(vars)*(NUMTOT+max_named_vars)))) {
299 	for (i=0; i<NUMTOT; i++) {
300 	    var[i].num = &global_var[i].num;
301 	    var[i].str = &global_var[i].str;
302 	}
303     } else
304 	syserr("malloc");
305 
306     /* stack is empty */
307     paramstk.curr = 0;
308 
309     /* allocate permanent variables */
310     if ((prompt = add_varnode("prompt", 1))
311 	&& (prompt->str = ptrnew(PARAMLEN))
312 	&& (marked_prompt = ptrnew(PARAMLEN))
313 	&& (last_line = add_varnode("last_line", 1))
314 	&& (last_line->str = ptrnew(PARAMLEN))
315 	&& (globptr[0] = ptrnew(PARAMLEN))
316 	&& (globptr[1] = ptrnew(PARAMLEN))
317 	&& !MEM_ERROR)
318 	;
319     else
320 	syserr("malloc");
321 
322 
323     /*
324      ptr_bootstrap();
325      utils_bootstrap();
326      beam_bootstrap();
327      cmd_bootstrap();
328      map_bootstrap();
329      eval_bootstrap();
330      list_bootstrap();
331      tcp_bootstrap();
332      */
333     edit_bootstrap();
334     tty_bootstrap();
335 
336 #ifdef MOTDFILE
337     printmotd();
338 #endif
339 
340     printver();
341 
342     if (argc == 1) {
343 	tty_printf(
344 "\nPowwow comes with ABSOLUTELY NO WARRANTY; for details type \"#help warranty\".\n\
345 This is free software, and you are welcome to redistribute it\n\
346 under certain conditions; type \"#help copyright\" for details.\n"
347 		   );
348     }
349 
350     if (argc == 1 || argc == 3) {
351 	tty_add_initial_binds();
352 	tty_add_walk_binds();
353     } else if (argc == 2 || argc == 4) {
354         /*
355          * assuming first arg is definition file name
356          * If three args, first is definition file name,
357          * second and third are hostname and port number
358          * (they overwrite the ones in definition file)
359          */
360         set_deffile(argv[1]);
361 
362         if (access(deffile,R_OK) == -1 || access(deffile,W_OK) == -1) {
363 	    char portnum[INTLEN];
364             tty_printf("Creating %s\nHost name  :", deffile);
365 	    tty_flush();
366 	    tty_gets(hostname, BUFSIZE);
367 	    if (hostname[0] == '\n')
368 		hostname[0] = '\0';
369 	    else
370 		strtok(hostname, "\n");
371             tty_puts("Port number:");
372 	    tty_flush();
373 	    tty_gets(portnum, INTLEN);
374 	    portnumber = atoi(portnum);
375             tty_add_initial_binds();
376 	    tty_add_walk_binds();
377 	    limit_mem = 1048576;
378             if (save_settings() < 0)
379 		exit(0);
380         } else if (read_settings() < 0)
381 	    exit(0);
382 	else
383 	    read_file = 1;
384     }
385     if (argc == 3 || argc == 4) {
386         /* assume last two args are hostname and port number */
387         my_strncpy(hostname, argv[argc - 2], BUFSIZE-1);
388         portnumber = atoi(argv[argc - 1]);
389     }
390 
391     signal_start();
392     tty_start();
393 
394     tty_puts(tty_clreoscr);
395     tty_putc('\n');
396     tty_gotoxy(col0 = 0, lines - 2);
397     tty_puts("Type #help for help.\n");
398     line0 = lines - 1;
399 
400     FD_ZERO(&fdset);
401     FD_SET(tty_read_fd, &fdset);
402 
403     if (*hostname)
404 	tcp_open("main", (*initstr ? initstr : NULL), hostname, portnumber);
405 
406     if (read_file && !*hostname && *initstr) {
407 	parse_instruction(initstr, 0, 0, 1);
408 	history_done = 0;
409     }
410 
411     confirm = 0;
412 
413     mainloop();
414 
415     /* NOTREACHED */
416     return 0;
417 }
418 
419 /*
420  * show current version
421  */
__P0(void)422 void printver __P0 (void)
423 {
424     tty_printf("Powwow version %s\nOptions: %s%s\n", POWWOW_VERSION,
425 #ifdef USE_VT100
426 	       "vt100-only,"
427 #else
428 	       "termcaps,"
429 #endif
430 #ifdef USE_SGTTY
431 	       " BSD sgtty,"
432 #else
433 	       " termios,"
434 #endif
435 #ifdef USE_REGEXP
436 	       " regexp,"
437 #else
438 	       " no regexp,"
439 #endif
440 #ifdef USE_LOCALE
441 	       " locale,"
442 #endif
443 #ifdef HAVE_LIBDL
444 	       " modules,"
445 #endif
446 	       ,
447 #if __STDC__
448 	       " compiled " __TIME__ " " __DATE__
449 #else
450 	       " uknown compile date"
451 #endif
452 	       );
453 }
454 
455 #ifdef MOTDFILE
456 /*
457  * print the message of the day if present
458  */
__P0(void)459 static void printmotd __P0 (void)
460 {
461     char line[BUFSIZE];
462     FILE *f = fopen(MOTDFILE, "r");
463     if (f) {
464         while (fgets(line, BUFSIZE, f))
465 	    tty_puts(line);
466 	fclose(f);
467     }
468 }
469 #endif
470 
__P0(void)471 static void redraw_everything __P0 (void)
472 {
473     if (prompt_status == 1 && line_status == 0)
474 	line_status = 1;
475     if (prompt_status == 1)
476 	draw_prompt();
477     else if (prompt_status == -1) {
478 	promptzero();
479 	col0 = surely_isprompt = '\0';
480     }
481     if (line_status == 1)
482 	draw_input_line();
483 }
484 
485 /* how much can we sleep in select() ? */
__P1(vtime **,timeout)486 static void compute_sleeptime __P1 (vtime **,timeout)
487 {
488     static vtime tbuf;
489     int sleeptime = 0;
490 
491     if (delays) {
492 	update_now();
493 	sleeptime = diff_vtime(&delays->when, &now);
494 	if (!sleeptime)
495 	    sleeptime = 1;    /* if sleeptime is less than 1 millisec,
496 			       * set to 1 millisec */
497     }
498     if (flashback && (!sleeptime || sleeptime > FLASHDELAY))
499 	sleeptime = FLASHDELAY;
500 
501     if (sleeptime) {
502 	tbuf.tv_sec = sleeptime / mSEC_PER_SEC;
503 	tbuf.tv_usec = (sleeptime % mSEC_PER_SEC) * uSEC_PER_mSEC;
504 	*timeout = &tbuf;
505     } else
506 	*timeout = (vtime *)NULL;
507 }
508 
509 /*
510  * main loop.
511  */
__P0(void)512 static void mainloop __P0 (void)
513 {
514     fd_set readfds;
515     int i, err;
516     vtime *timeout;
517 
518     for (;;) {
519 	readfds = fdset;
520 
521 	tcp_fd = tcp_main_fd;
522 	exec_delays();
523 
524 	do {
525 	    if (sig_pending)
526 		sig_bottomhalf(); /* this might set errno... */
527 
528 	    tcp_flush();
529 
530 	    if (!(pos <= edlen)) {
531 		PRINTF("\n#*ARGH* assertion failed (pos <= edlen): mail bpk@hoopajoo.net\n");
532 		pos = edlen;
533 	    }
534 
535 	    redraw_everything();
536 	    tty_flush();
537 
538 	    compute_sleeptime(&timeout);
539 
540 	    error = now_updated = 0;
541 
542 	    err = select(tcp_max_fd+1, &readfds, NULL, NULL, timeout);
543 
544 	    prompt_reset_iac();
545 
546 	} while (err < 0 && errno == EINTR);
547 
548 	if (err < 0 && errno != EINTR)
549 	    syserr("select");
550 
551 	if (flashback) putbackcursor();
552 
553 	/* process subsidiary and spawned connections first */
554 	if (tcp_count > 1 || tcp_attachcount) {
555 	    for (i=0; err && i<conn_max_index; i++) {
556 		if (CONN_INDEX(i).id && CONN_INDEX(i).fd != tcp_main_fd) {
557 		    tcp_fd = CONN_INDEX(i).fd;
558 		    if (FD_ISSET(tcp_fd, &readfds)) {
559 			err--;
560 			get_remote_input();
561 		    }
562 		}
563 	    }
564 	}
565 	/* and main connection last */
566 	if (tcp_main_fd != -1 && FD_ISSET(tcp_main_fd, &readfds)) {
567 	    tcp_fd = tcp_main_fd;
568 	    get_remote_input();
569 	}
570 	if (FD_ISSET(tty_read_fd, &readfds)) {
571 	    tcp_fd = tcp_main_fd;
572 	    confirm = 0;
573 	    get_user_input();
574 	}
575 
576     }
577 }
578 
579 /*
580  * set the new prompt / input line status
581  */
__P1(int,s)582 void status __P1 (int,s)
583 {
584     if (s < 0) {
585 	/* waiting prompt from the MUD */
586 	prompt_status = s;
587 	line_status = 1;
588     } else {
589 	if (prompt_status >= 0)
590 	    prompt_status = s;
591 	if (line_status >= 0)
592 	    line_status = s;
593     }
594 }
595 
596 /*
597  * execute the delayed labels that have expired
598  * and place them in the disabled delays list
599  */
__P0(void)600 static void exec_delays __P0 (void)
601 {
602     delaynode *dying;
603     ptr *pbuf, buf = (ptr)0;
604 
605     if (!delays)
606 	return;
607 
608     update_now();
609 
610     if (cmp_vtime(&delays->when, &now) > 0)
611 	return;
612 
613     /* remember delayed command may modify the prompt and/or input line! */
614     if (prompt_status == 0) {
615 	clear_input_line(opt_compact || !opt_info);
616 	if (!opt_compact && opt_info && prompt_status == 0 && promptlen) {
617 	    tty_putc('\n');
618 	    col0 = 0;
619 	    status(1);
620 	}
621     }
622 
623     TAKE_PTR(pbuf, buf);
624 
625     while (delays && cmp_vtime(&delays->when, &now) <= 0) {
626 	dying = delays;           /* remove delayed command from active list */
627 	delays = dying->next;     /* and put it in the dead one  */
628 
629 	add_node((defnode *)dying, (defnode **)&dead_delays, rev_time_sort);
630 
631 	/* must be moved before executing delay->command
632 	 * and command must be copied in a buffer
633 	 * (can't you imagine why? The command may edit itself...)
634 	 */
635 
636 	if (opt_info)
637 	    tty_printf("#now [%s]\n", dying->command);
638 
639 	if (*dying->command) {
640 
641 	    error = 0;
642 
643 	    *pbuf = ptrmcpy(*pbuf, dying->command, strlen(dying->command));
644 	    if (MEM_ERROR)
645 		errmsg("malloc (#in/#at)");
646 	    else {
647 		parse_instruction(ptrdata(*pbuf), 0, 0, 1);
648 		history_done = 0;
649 	    }
650 	}
651     }
652     DROP_PTR(pbuf);
653 }
654 
655 
656 #define IAC_N 1024
657 static char *iac_v[IAC_N];
658 static int iac_f, iac_l;
659 
__P0(void)660 static void prompt_reset_iac __P0 (void)
661 {
662 	iac_f = iac_l = 0;
663 }
664 
__P1(char *,p)665 void prompt_set_iac __P1 (char *,p)
666 {
667     if (iac_f == iac_l)
668 	iac_f = iac_l = 0;
669 
670     if (iac_l < IAC_N)
671 	iac_v[iac_l++] = p;
672 }
673 
__P0(void)674 static char *prompt_get_iac __P0 (void)
675 {
676     return iac_l > iac_f ? iac_v[iac_f] : NULL;
677 }
678 
__P3(char *,linestart,int,len,int,islast)679 static int grab_prompt __P3 (char *,linestart, int,len, int,islast)
680 {
681     char *p;
682     int is_iac_prompt = surely_isprompt = 0;
683 
684     /* recognize IAC GA as end-of-prompt marker */
685     if ((CONN_LIST(tcp_fd).flags & IDPROMPT)) {
686 	if ((p = prompt_get_iac()) && p > linestart && p <= linestart+len)
687 	    iac_f++, is_iac_prompt = len = p - linestart;
688 	else if (!islast)
689 	    return 0;
690     }
691 
692     /*
693      * We may get a prompt in the middle of a bunch of lines, so
694      * match #prompts. They usually have no #print, so we print manually
695      * if islast is not set and a #prompt matches.
696      */
697     if ((is_iac_prompt || islast || printstrlen(linestart) < cols) &&
698 	((search_prompt(linestart, 1) && surely_isprompt) || is_iac_prompt)) {
699 
700 	char *reprint;
701 	/*
702 	 * the line starts with a prompt.
703 	 * #isprompt placed the actual prompt in $prompt,
704 	 * we must still process the rest of the line.
705 	 */
706 	if (surely_isprompt > 0 && surely_isprompt <= len) {
707 	    len = surely_isprompt;
708 	    prompt_status = 1;
709 	} else if (!surely_isprompt && is_iac_prompt) {
710 	    prompt->str = ptrmcpy(prompt->str, linestart, len = surely_isprompt = is_iac_prompt);
711 	    if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return 0; }
712 	    prompt_status = 1;
713 	}
714 
715 	/*
716 	 * following data may be the reply to a previously sent command,
717 	 * so we may have to reprint that command.
718 	 */
719 	if ((reprint = reprint_getline()) && *reprint) {
720 	    smart_print(promptstr, 0);
721 	    status(-1);
722 	    tty_printf("(%s)\n", reprint);
723 	} else if (!islast)
724 	    smart_print(promptstr, 1);
725     } else if (islast) {
726 	prompt->str = ptrmcpy(prompt->str, linestart, len);
727 	if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return 0; }
728 	prompt_status = 1; /* good, we got what to redraw */
729     } else
730 	len = 0;
731 
732     return len;
733 }
734 
735 
736 /*
737  * process remote input one line at time. stop at "\n".
738  */
__P2(char **,pbuf,int *,psize)739 static void process_singleline __P2 (char **,pbuf, int *,psize)
740 {
741     int size, len = 0;
742     char *wasn = 0, *buf, *linestart = *pbuf, *lineend, *end = *pbuf + *psize;
743 
744     if ((lineend = memchr(linestart, '\n', *psize))) {
745 	/* ok, there is a newline */
746 
747 	*(wasn = lineend) = '\0';
748 	buf = lineend + 1; /* start of next line */
749     }
750 
751     if (!lineend)
752 	/* line continues till end of buffer, no trailing \n */
753 	buf = lineend = end;
754 
755     size = buf - linestart;
756 
757 #ifdef DEBUGCODE_2
758     /* debug code to see in detail what codes come from the server */
759     {
760 	char c;
761 	char *t;
762 	tty_putc('{');
763 	for (t = linestart; t < lineend && (c = *t); t++) {
764 	    if (c < ' ' || c > '~')
765 		tty_printf("[%d]", (int)c);
766 	    else
767 		tty_putc(c);
768 	}
769 	tty_puts("}\n");
770     }
771 #endif
772 
773     /*
774      * Try to guess where is the prompt... really not much more than
775      * a guess. Again, do it only on main connection: we do not want
776      * output from other connections to mess with the prompt
777      * of main connection :)
778      *
779      * Since we now have #prompt, behave more restrictively:
780      * if no #prompts match or a #prompt matches but does not set #isprompt
781      *     (i.e. recognize it for not being a prompt),
782      *     we check for #actions on it when the \n arrives.
783      * if a #prompt matches and sets #isprompt, then it is REALLY a prompt
784      *     so never match #actions on it.
785      */
786     if (lineend == end && tcp_fd == tcp_main_fd) {
787 	/*
788 	 * The last line in the chunk we received has no trailing \n
789 	 * Assume it is a prompt.
790 	 */
791 	if (surely_isprompt && promptlen && prompt_status == 1) {
792 	    draw_prompt(); tty_putc('\n'); col0 = 0;
793 	}
794 	surely_isprompt = 0;
795 	promptzero();
796 	if (lineend > linestart && (len = grab_prompt(linestart, lineend-linestart, 1)))
797 	    size = len;
798     } else {
799 	if (tcp_fd == tcp_main_fd) {
800 	    surely_isprompt = 0;
801 	    promptzero();
802 
803 	    if (linestart[0]) {
804 		/* set $last_line */
805 		last_line->str = ptrmcpy(last_line->str, linestart, strlen(linestart));
806 		if (MEM_ERROR) { print_error(error); return; }
807 
808 		if (lineend > linestart && (len = grab_prompt(linestart, lineend-linestart, 0)))
809 		    size = len;
810 	    }
811 	}
812 	if (!len && ((!search_action(linestart, 0) || opt_autoprint))) {
813 	    if (line0 < lines - 1)
814 		line0++;
815 	    if (tcp_fd != tcp_main_fd)        /* sub connection */
816 		tty_printf("##%s> ", CONN_LIST(tcp_fd).id);
817 
818 	    smart_print(linestart, 1);
819 	}
820     }
821 
822     /*
823      * search_prompt and search_action above
824      * might set error: clear it to avoid troubles.
825      */
826     error = 0;
827     if (wasn) *wasn = '\n';
828     *pbuf += size;
829     *psize -= size;
830 }
831 
832 /*
833  * Code to merge lines from host that were splitted
834  * into different packets: it is a horrible kludge (sigh)
835  * and can be used only on one connection at time.
836  * We currently do it on main connection.
837  *
838  * Note that this code also works for _prompts_ splitted into
839  * different packets, as long as no #prompts execute #isprompt
840  * on an incomplete prompt (as stated in the docs).
841  */
__P2(char *,buf,int,got)842 static int process_first_fragment __P2 (char *,buf, int,got)
843 {
844     int processed = 0;
845 
846     /*
847      * Don't merge if the first part of the line was intercepted
848      * by a #prompt action which executed #isprompt
849      * (to avoid intercepting it twice)
850      */
851     if (*buf == '\n') {
852 	char deleteprompt = 0, matched = 0;
853 
854 	if (opt_compact) {
855 	    /* in compact mode, skip the first \n */
856 	    deleteprompt = 1;
857 	    processed++;
858 	}
859 
860 	/*
861 	 * the prompt was actually a complete line.
862 	 * no need to put it on the top of received data.
863 	 * unless #isprompt was executed, demote it to a regular line,
864 	 * match #actions on it, copy it in last_line.
865 	 */
866 	if (!surely_isprompt) {
867 	    last_line->str = ptrcpy(last_line->str, prompt->str);
868 	    if (MEM_ERROR) { print_error(error); return 0; }
869 
870 	    /*
871 	     * Kludge for kludge: don't delete the old prompt immediately.
872 	     * Instead, match actions on it first.
873 	     * If it matches, clear the line before running the action
874 	     * (done by the "1" in search_action() )
875 	     * If it doesn't match, delete it only if opt_compact != 0
876 	     */
877 
878 	    matched = search_action(promptstr, 1);
879 	}
880 	if (!matched)
881 	    clear_input_line(deleteprompt);
882 	status(-1);
883     } else {
884 	/*
885 	 * try to merge the prompt with the first line in buf
886 	 * (assuming we have a line splitted into those parts)
887 	 * then clear the prompt.
888 	 */
889 	char *lineend, *spinning = NULL;
890 
891 	/* find the end of the first line. include the final newline. */
892 	if ((lineend = strchr(buf, '\n')))
893 	    lineend++;
894 	else
895 	    lineend = buf + got;
896 
897 	if (surely_isprompt || (spinning = memchr(buf, '\b', lineend - buf))) {
898 	    /*
899 	     * either #isprompt _was_ executed,
900 	     * or we got a MUME spinning bar.
901 	     * in both cases, don't try to merge.
902 	     *
903 	     * print a newline (to keep the old prompt on screen)
904 	     * only if !opt_compact and we didn't get a MUME spinning bar.
905 	     */
906 	    clear_input_line(opt_compact);
907 	    if (!spinning && !opt_compact)
908 		tty_putc('\n'), col0 = 0;
909 	    promptzero();
910        	} else {
911 	    ptr *pp, p = (ptr)0;
912 	    char *dummy;
913 	    int dummyint;
914 
915 	    /* ok, merge this junk with the prompt */
916 	    TAKE_PTR(pp, p);
917 	    *pp = ptrcpy(*pp, prompt->str);
918 	    *pp = ptrmcat(*pp, buf, lineend - buf);
919 
920 	    if (MEM_ERROR) { print_error(error); return 0; }
921 	    if (!*pp)
922 		return 0;
923 	    dummy = ptrdata(*pp);
924 	    dummyint = ptrlen(*pp);
925 	    /* this also sets last_line or prompt->str : */
926 	    clear_input_line(1);
927 	    process_singleline(&dummy, &dummyint);
928 
929 	    processed = lineend - buf;
930 	}
931     }
932     return processed;
933 }
934 
935 /*
936  * process input from remote host:
937  * detect special sequences, trigger actions, locate prompt,
938  * word-wrap, print to tty
939  */
__P2(char *,buf,int,size)940 void process_remote_input __P2 (char *,buf, int,size)
941 {
942 
943     if (promptlen && tcp_fd == tcp_main_fd)
944 	promptzero();  /* discard the prompt, we look for another one */
945 
946     status(1);
947 
948     do {
949 	process_singleline(&buf, &size);
950     } while (size > 0);
951 }
952 
__P1(int,newline)953 static void common_clear __P1 (int,newline)
954 {
955     clear_input_line(opt_compact);
956     if (newline) {
957 	tty_putc('\n'); col0 = 0; status(1);
958     }
959 }
960 
961 /*
962  * get data from the socket and process/display it.
963  */
__P0(void)964 static void get_remote_input __P0 (void)
965 {
966     char buffer[BUFSIZE + 2];        /* allow for a terminating \0 later */
967     char *buf = buffer, *newline;
968     int got, otcp_fd, i;
969 
970     if (CONN_LIST(tcp_fd).fragment) {
971 	if ((i = strlen(CONN_LIST(tcp_fd).fragment)) >= BUFSIZE-1) {
972 	    i = 0;
973 	    common_clear(promptlen && !opt_compact);
974 	    tty_printf("#error: ##%s : line too long, discarded\n", CONN_LIST(tcp_fd).id);
975 	} else {
976 	    buf += i;
977 	    memcpy(buffer, CONN_LIST(tcp_fd).fragment, i);
978 	}
979 	free(CONN_LIST(tcp_fd).fragment);
980 	CONN_LIST(tcp_fd).fragment = 0;
981     } else
982 	i = 0;
983 
984     got = tcp_read(tcp_fd, buf, BUFSIZE - i);
985     if (!got)
986 	return;
987 
988     buf[got]='\0';  /* Safe, there is space. Do it now not to forget it later */
989     received += got;
990 
991 #ifdef DEBUGCODE
992     /* debug code to see in detail what strange codes come from the server */
993     {
994 	char c, *t;
995 	newline = buf + got;
996 	tty_printf("%s{", edattrend);
997 	for (t = buf; t < newline; t++) {
998 	    if ((c = *t) < ' ' || c > '~')
999 		tty_printf("[%d]", c);
1000 	    else tty_putc(c);
1001 	}
1002 	tty_puts("}\n");
1003     }
1004 #endif
1005 
1006     if (!(CONN_LIST(tcp_fd).flags & ACTIVE))
1007 	return; /* process only active connections */
1008 
1009     got += (buf - buffer);
1010     buf = buffer;
1011 
1012     if (CONN_LIST(tcp_fd).flags & SPAWN) {
1013 	/* this is data from a spawned child or an attached program.
1014 	 * execute as if typed */
1015 	otcp_fd = tcp_fd;
1016 	tcp_fd = tcp_main_fd;
1017 
1018 	if ((newline = strchr(buf, '\n'))) {
1019 	    /* instead of newline = strtok(buf, "\n") */
1020 	    *newline = '\0';
1021 
1022 	    if (opt_autoclear && line_status == 0) {
1023 		common_clear(!opt_compact);
1024 	    }
1025 	    do {
1026 		if (opt_info) {
1027 		    if (line_status == 0) {
1028 			common_clear(!opt_compact);
1029 		    }
1030 		    tty_printf("##%s [%s]\n", CONN_LIST(otcp_fd).id, buf);
1031 		}
1032 		parse_user_input(buf, 0);
1033 		/*
1034 		 * strtok() may have been used in parse_user_input()...
1035 		 * cannot rely it refers on what we may have set above.
1036 		 * (it causes a bug in #spawned commands if they
1037 		 * evaluate (attr(), noattr) or they #connect ... )
1038 		 * so do it manually.
1039 		 */
1040 		/*
1041 		 * buf = strtok(NULL, "\n");
1042 		 */
1043 		if ((buf = newline) &&
1044 		    (newline = strchr(++buf, '\n')))
1045 			*newline = '\0';
1046 	    } while (buf && newline);
1047 	}
1048 
1049 	if (buf && *buf && !newline) {
1050 	    /*
1051 	     * save last fragment for later, when spawned command will
1052 	     * (hopefully) send the rest of the text
1053 	     */
1054 	    CONN_LIST(otcp_fd).fragment = my_strdup(buf);
1055 
1056 	    if (opt_info) {
1057 		if (line_status == 0) {
1058 		    common_clear(!opt_compact);
1059 		}
1060 		tty_printf("#warning: ##%s : unterminated [%s]\n", CONN_LIST(otcp_fd).id, buf);
1061 	    }
1062 	}
1063 	tcp_fd = otcp_fd;
1064 	return;
1065     }
1066 
1067     if (linemode & LM_CHAR) {
1068 	/* char-by-char mode: just display output, no fuss */
1069 	clear_input_line(0);
1070 	tty_puts(buf);
1071 	return;
1072     }
1073 
1074     /* line-at-a-time mode: process input in a number of ways */
1075 
1076     if (tcp_fd == tcp_main_fd && promptlen) {
1077 	i = process_first_fragment(buf, got);
1078 	buf += i, got -= i;
1079     } else {
1080 	common_clear(promptlen && !opt_compact);
1081     }
1082 
1083     if (got > 0)
1084 	process_remote_input(buf, got);
1085 }
1086 
1087 
1088 #ifdef USE_REGEXP
1089 /*
1090  * GH: matches precompiled regexp, return actual params in param array
1091  *     return 1 if matched, 0 if not
1092  */
__P4(void *,regexp,char *,line,int *,match_s,int *,match_e)1093 static int match_regexp_action __P4 (void *,regexp, char *,line, int *,match_s, int *,match_e)
1094 {
1095     regmatch_t reg_match[NUMPARAM - 1];
1096 
1097     if (!regexec((regex_t *)regexp, line, NUMPARAM - 1, reg_match, 0)) {
1098 	int n;
1099 
1100 	match_s[0] = 0;
1101 	match_e[0] = strlen(line);
1102 	for (n = 1; n < NUMPARAM; n++)
1103 	    match_s[n] = match_e[n] = 0;
1104 	for (n = 0; n <= (int)((regex_t *)regexp)->re_nsub &&
1105 	     n < NUMPARAM - 1; n++) {
1106 	    if (reg_match[n].rm_so == -1) continue;
1107 	    match_s[n+1] = reg_match[n].rm_so;
1108 	    match_e[n+1] = reg_match[n].rm_eo;
1109 	}
1110 	return 1;
1111     }
1112     return 0;
1113 }
1114 #endif
1115 
1116 /*
1117  * match action containing &1..&9 and $1..$9 and return actual params start/end
1118  * in match_s/match_e - return 1 if matched, 0 if not
1119  */
__P4(char *,pat,char *,line,int *,match_s,int *,match_e)1120 static int match_weak_action __P4 (char *,pat, char *,line, int *,match_s, int *,match_e)
1121 {
1122     char mpat[BUFSIZE], *npat=0, *npat2=0, *src=line, *nsrc=0, c;
1123     ptr *pbuf, buf = (ptr)0;
1124     char *tmp, *realpat = pat;
1125     int mbeg = 0, mword = 0, prm = -1, p;
1126 
1127     TAKE_PTR(pbuf, buf);
1128 
1129     if (jit_subst_vars(pbuf, pat))
1130 	pat = ptrdata(*pbuf);
1131     if (REAL_ERROR) {
1132 	print_error(error);
1133 	DROP_PTR(pbuf);
1134 	return 0;
1135     }
1136     unescape(pat);
1137 
1138     for (p = 0; p < NUMPARAM; p++)
1139 	match_s[p] = match_e[p] = 0;
1140     p = 0;
1141 
1142     if (*pat == '^') {
1143 	pat++;
1144 	mbeg = 1;  /* anchor match at line start */
1145     }
1146     if (*pat == '&' || *pat == '$')
1147 	mbeg = - mbeg - 1;  /* pattern starts with '&' or '$' */
1148 
1149     while (pat && *pat) {
1150 	if (((c=*pat) == '&' || c == '$')) {
1151 	    /* &x matches a string */
1152 	    /* $x matches a single word */
1153 	    tmp = pat + 1;
1154 	    if (isdigit(*tmp)) {
1155 		p = 0;
1156 		while (isdigit(*tmp) && p < NUMPARAM) {
1157 		    p *= 10;
1158 		    p += *tmp++ - '0';
1159 		}
1160 		if (p <= 0 || p >= NUMPARAM) {
1161 		    DROP_PTR(pbuf);
1162 		    return 0;
1163 		}
1164 		prm = p;
1165 		pat = tmp;
1166 		if (c == '$')
1167 		    mword = 1;
1168 	    } else {
1169 		PRINTF("#error: bad action pattern \"%s\"\n#missing digit after \"%s\"\n",
1170 		       realpat, pat);
1171 		DROP_PTR(pbuf);
1172 		return 0;
1173 	    }
1174 	}
1175 
1176 	npat  = first_valid(pat, '&');
1177 	npat2 = first_valid(pat, '$');
1178 	if (npat2 < npat) npat = npat2;
1179 	if (!*npat) npat = 0;
1180 
1181 	if (npat) {
1182 	    my_strncpy(mpat, pat, npat-pat);
1183 	    /* mpat[npat - pat] = 0; */
1184 	} else
1185 	    strcpy(mpat, pat);
1186 
1187 	if (*mpat) {
1188 	    nsrc = strstr(src, mpat);
1189 	    if (!nsrc) {
1190 		DROP_PTR(pbuf);
1191 		return 0;
1192 	    }
1193 	    if (mbeg > 0) {
1194 		if (nsrc != src) {
1195 		    DROP_PTR(pbuf);
1196 		    return 0;
1197 		}
1198 		mbeg = 0;  /* reset mbeg to stop further start match */
1199 	    }
1200 	    if (prm != -1) {
1201 		match_s[prm] = src - line;
1202 		match_e[prm] = nsrc - line;
1203 	    }
1204 	} else if (prm != -1) {
1205 	    /* end of pattern space */
1206 	    match_s[prm] = src - line;
1207 	    match_e[prm] = strlen(line);
1208 	}
1209 
1210 	/* post-processing of param */
1211 	if (prm != -1 && match_e[prm] && mword) {
1212 	    if (mbeg == -1) {
1213 		/* unanchored '$' start, take last word */
1214 		if ((tmp = memrchrs(line + match_s[prm],
1215 				    match_e[prm] - match_s[prm],
1216 				    DELIM, DELIM_LEN))) {
1217 		    match_s[prm] = tmp - line + 1;
1218 		}
1219 	    } else if (!*pat) {
1220 		/* '$' at end of pattern, take first word */
1221 		if ((tmp = memchrs(line + match_s[prm],
1222 				   match_e[prm] - match_s[prm],
1223 				   DELIM, DELIM_LEN)))
1224 		    match_e[prm] = tmp - line;
1225 	    } else {
1226 		/* match only if param is single-worded */
1227 		if (memchrs(line + match_s[prm],
1228 			    match_e[prm] - match_s[prm],
1229 			    DELIM, DELIM_LEN)) {
1230 		    DROP_PTR(pbuf);
1231 		    return 0;
1232 		}
1233 	    }
1234 	}
1235 	if (prm != -1 && match_e[prm])
1236 	    mbeg = mword = 0;  /* reset match flags */
1237 	src = nsrc + strlen(mpat);
1238 	pat = npat;
1239     }
1240     DROP_PTR(pbuf);
1241 
1242     match_s[0] = 0; match_e[0] = strlen(line);
1243     return 1;
1244 }
1245 
1246 /*
1247  * Search for #actions or #prompts to trigger on an input line.
1248  * The line can't be trashed since we want to print it on the screen later.
1249  * Return 1 if line matched to some #action, 0 otherwise
1250  *
1251  * Optionally clear the input line before running the trigger command.
1252  */
__P3(char *,line,char,clearline,char,onprompt)1253 static int search_action_or_prompt __P3 (char *,line, char,clearline, char,onprompt)
1254 {
1255     /*
1256      * we need actionnode and promptnode to be the same "triggernode" type
1257      */
1258     triggernode *p;
1259     int ret = 0;
1260     int match_s[NUMPARAM], match_e[NUMPARAM];
1261 
1262     for (p = onprompt ? prompts : actions; p; p = p->next) {
1263 #ifdef USE_REGEXP
1264         if (p->active &&
1265 	    ((p->type == ACTION_WEAK && match_weak_action(p->pattern, line, match_s, match_e))
1266 	     || (p->type == ACTION_REGEXP && match_regexp_action(p->regexp, line, match_s, match_e))
1267 	     ))
1268 #else
1269 	    if (p->active &&
1270 		((p->type == ACTION_WEAK && match_weak_action(p->pattern, line, match_s, match_e))
1271 		 ))
1272 #endif
1273 	{
1274 	    push_params(); if (error) return 0;
1275 	    ret = 1; error = 0;
1276 	    set_params(line, match_s, match_e); if (error) return 0;
1277 	    if (onprompt == 2) {
1278 		prompt->str = ptrmcpy(prompt->str, line, strlen(line));
1279 		if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return 0; }
1280 	    }
1281 	    if (clearline)
1282 		clear_input_line(1);
1283 	    parse_instruction(p->command, 0, 1, 1);
1284 	    history_done = 0;
1285 	    if (error!=DYN_STACK_UND_ERROR && error!=DYN_STACK_OV_ERROR)
1286 		pop_params();
1287             break;
1288 	}
1289     }
1290     if (error) return 0;
1291     return ret;
1292 }
1293 
1294 /*
1295  * read terminal input and send to parser.
1296  * decode keys that send escape sequences
1297  */
__P0(void)1298 static void get_user_input __P0 (void)
1299 {
1300     int i, j, chunk = 1;
1301     static char buf[BUFSIZE+1];   /* allow for terminating \0 */
1302     char *c = buf;
1303     static char typed[CAPLEN];    /* chars typed so far (with partial match) */
1304     static int nchars = 0;	  /* number of them */
1305 
1306     /* We have 4 possible line modes:
1307      * line mode, local echo: line editing functions in effect
1308      * line mode, no echo: sometimes used for passwords, no line editing
1309      * char mode, no echo: send a character directly, no local processing
1310      * char mode, local echo: extremely rare, do as above.
1311      */
1312     if (!(linemode & (LM_NOECHO | LM_CHAR))) /* line mode, local echo */
1313 	chunk = BUFSIZE;
1314 
1315     while ((j = tty_read(c, chunk)) < 0 && errno == EINTR)
1316 	;
1317     if (j == 0)
1318         return;
1319 
1320     if (j < 0 || (chunk == 1 && j != chunk))
1321 	syserr("read from tty");
1322 
1323     c[chunk] = '\0';
1324 
1325     if (linemode & LM_CHAR) {
1326 	/* char mode. chunk == 1 */
1327 	while ((i = write(tcp_fd, c, 1)) < 0 && errno == EINTR)
1328 	    ;
1329 	if (i != 1)
1330 	    syserr("write to socket");
1331 	if (!(linemode & LM_NOECHO))
1332 	    tty_putc(*c);
1333 	last_edit_cmd = (function_any)0;
1334     } else if (linemode & LM_NOECHO) {
1335 	/* sending password (line mode, no echo). chunk == 1 */
1336 	if ((*c != '\n' && *c != '\r') && edlen < BUFSIZE - 2)
1337 	    edbuf[edlen++] = *c;
1338 	else {
1339 	    edbuf[edlen] = '\0';
1340 #ifdef BUG_ANSI
1341 	    if (edattrbg)
1342 		tty_printf("%s\n", edattrend);
1343 	    else
1344 #endif
1345 		tty_putc('\n');
1346 
1347 	    tcp_write(tcp_fd, edbuf);
1348 	    edlen = 0;
1349 	    typed[nchars = 0] = 0;
1350 	}
1351 	edbuf[pos = edlen] = '\0';
1352 	last_edit_cmd = (function_any)0;
1353     } else {
1354 	/* normal mode (line mode, echo). chunk == BUFSIZE */
1355 	int done = 0;
1356 	keynode *p;
1357 
1358 	for (; j > 0; c++, j--) {
1359 
1360 	    /* search function key strings for match */
1361 	    /* GH: support for \0 in sequence */
1362 	    done = 0;
1363 	    typed[nchars++] = *c;
1364 
1365 	    while (!done) {
1366 		done = 1;
1367 		/*
1368 		 * shortcut:
1369 		 * an initial single ASCII char cannot match any #bind
1370 		 */
1371 		if (nchars == 1 && *c >= ' ' && *c <= '~')
1372 		    p = NULL;
1373 		else {
1374 		    for (p = keydefs; (p && (p->seqlen < nchars ||
1375 					     memcmp(typed, p->sequence, nchars)));
1376 			 p = p->next)
1377 			;
1378 		}
1379 
1380 		if (!p) {
1381 		    /*
1382 		     * GH: type the first character and keep processing
1383 		     *     the rest in the input buffer
1384 		     */
1385 		    i = 1;
1386 		    last_edit_cmd = (function_any)0;
1387 		    insert_char(typed[0]);
1388 		    while (i < nchars) {
1389 			typed[i - 1] = typed[i];
1390 			i++;
1391 		    }
1392 		    if (--nchars)
1393 			done = 0;
1394 		} else if (p->seqlen == nchars) {
1395 		    if (flashback)
1396 			putbackcursor();
1397 		    p->funct(p->call_data);
1398 		    last_edit_cmd = (function_any)p->funct; /* GH: keep track of last command */
1399 		    nchars = 0;
1400 		}
1401 	    }
1402 	}
1403     }
1404 }
1405 
1406 /*
1407  * split str into words separated by DELIM, and place in
1408  * VAR[1].str ... VAR[9].str -
1409  * the whole str is put in VAR[0].str
1410  */
__P1(char *,str)1411 static char *split_words __P1 (char *,str)
1412 {
1413     int i;
1414     char *end;
1415     ptr *prm;
1416 
1417     *VAR[0].str = ptrmcpy(*VAR[0].str, str, strlen(str));
1418     for (i = 1; i < NUMPARAM; i++) {
1419 	*VAR[i].num = 0;
1420 	prm = VAR[i].str;
1421 	/* skip multiple span of DELIM */
1422 	while (*str && strchr(DELIM, *str))
1423 	    str++;
1424 	end = str;
1425 	while (*end && !strchr(DELIM, *end))
1426 	    end++;
1427 	*prm = ptrmcpy(*prm, str, end-str);
1428 	str = end;
1429 	if (MEM_ERROR) { print_error(error); return NULL; }
1430     }
1431     return str;
1432 }
1433 
1434 /*
1435  * free the whole stack and reset it to empty
1436  */
__P0(void)1437 static void free_allparams __P0 (void)
1438 {
1439     int i,j;
1440 
1441     paramstk.curr = 0;    /* reset stack to empty */
1442 
1443     for (i=1; i<MAX_STACK; i++) {
1444 	for (j=0; j<NUMPARAM; j++) {
1445 	    ptrdel(paramstk.p[i][j].str);
1446 	    paramstk.p[i][j].str = (ptr)0;
1447 	}
1448     }
1449     for (j=0; j<NUMPARAM; j++) {
1450 	VAR[j].num = &paramstk.p[0][j].num;
1451 	VAR[j].str = &paramstk.p[0][j].str;
1452     }
1453 }
1454 
__P0(void)1455 void push_params __P0 (void)
1456 {
1457     int i;
1458 
1459     if (paramstk.curr < MAX_STACK - 1)
1460 	paramstk.curr++;
1461     else {
1462 	print_error(error=DYN_STACK_OV_ERROR);
1463 	free_allparams();
1464 	return;
1465     }
1466     for (i=0; i<NUMPARAM; i++) {
1467 	        *(VAR[i].num = &paramstk.p[paramstk.curr][i].num) = 0;
1468 	ptrzero(*(VAR[i].str = &paramstk.p[paramstk.curr][i].str));
1469     }
1470 }
1471 
__P0(void)1472 void pop_params __P0 (void)
1473 {
1474     int i;
1475 
1476     if (paramstk.curr > 0)
1477 	paramstk.curr--;
1478     else {
1479 	print_error(error=DYN_STACK_UND_ERROR);
1480 	free_allparams();
1481 	return;
1482     }
1483     for (i=0; i<NUMPARAM; i++) {
1484 	ptrdel(*VAR[i].str); *VAR[i].str = (ptr)0;
1485 	VAR[i].num = &paramstk.p[paramstk.curr][i].num;
1486 	VAR[i].str = &paramstk.p[paramstk.curr][i].str;
1487     }
1488 }
1489 
__P3(char *,line,int *,match_s,int *,match_e)1490 static void set_params __P3 (char *,line, int *,match_s, int *,match_e)
1491 {
1492     int i;
1493 
1494     for (i=0; i<NUMPARAM; i++) {
1495 	*VAR[i].num = 0;
1496 	if (match_e[i] > match_s[i]) {
1497 	    *VAR[i].str = ptrmcpy(*VAR[i].str, line + match_s[i],
1498 				  match_e[i] - match_s[i]);
1499 	    if (MEM_ERROR) {
1500 		print_error(error);
1501 		return;
1502 	    }
1503 	} else
1504 	    ptrzero(*VAR[i].str);
1505     }
1506 }
1507 
__P1(char *,p)1508 char *get_next_instr __P1 (char *,p)
1509 {
1510     int count, is_if;
1511     char *sep, *q;
1512 
1513     p = skipspace(p);
1514 
1515     if (!*p)
1516 	return p;
1517 
1518     count = is_if = !strncmp(p, "#if", 3);
1519 
1520     do {
1521 	sep   = first_regular(p, CMDSEP);
1522 
1523 	q = p;
1524 	if (*q) do {
1525 	    if (*q == '#') q++;
1526 
1527 	    q = first_regular(q, '#');
1528 	} while (*q && strncmp(q, "#if", 3));
1529 
1530 	if (sep<=q) {
1531 	    if (*(p = sep))
1532 		p++;
1533 	}
1534 	else
1535 	    if (*q)
1536 		p = get_next_instr(q);
1537 	else {
1538 	    print_error(error=SYNTAX_ERROR);
1539 	    return NULL;
1540 	}
1541 	sep = skipspace(p);
1542     } while (*p && count-- &&
1543 	     (!is_if || (!strncmp(sep, "#else", 5) &&
1544 			 (*(p = sep + 5)))));
1545 
1546     return p;
1547 }
1548 
__P2(char *,line,char,silent)1549 static void send_line __P2 (char *,line, char,silent)
1550 {
1551     if (!silent && opt_echo) { PRINTF("[%s]\n", line); }
1552     tcp_write(tcp_fd, line);
1553 }
1554 
1555 
1556 /*
1557  * Parse and exec the first instruction in 'line', and return pointer to the
1558  * second instruction in 'line' (if any).
1559  */
__P4(char *,line,char,silent,char,subs,char,jit_subs)1560 char *parse_instruction __P4 (char *,line, char,silent, char,subs, char,jit_subs)
1561 {
1562     aliasnode *np;
1563     char *buf, *arg, *end, *ret;
1564     char last_is_sep = 0;
1565     int len, copied = 0, otcp_fd = -1;
1566     ptr p1 = (ptr)0, p2 = (ptr)0;
1567     ptr *pbuf, *pbusy, *tmp;
1568 
1569     if (error) return NULL;
1570 
1571     ret = get_next_instr(line);
1572 
1573     if (!ret || ret==line)  /* error or empty instruction, bail out */
1574 	return ret;
1575 
1576     /*
1577      * remove the optional ';' after an instruction,
1578      * to have an usable string, ending with \0.
1579      * If it is escaped, DON'T remove it: it is not a separator,
1580      * and the instruction must already end with \0, or we would not be here.
1581      */
1582     if (ret[-1] == CMDSEP) {
1583 	/* instruction is not empty, ret[-1] is allowed */
1584 	if (ret > line + 1 && ret[-2] == ESC) {
1585 	    /* ';' is escaped */
1586 	} else {
1587 	    *--ret = '\0';
1588 	    last_is_sep = 1;
1589 	}
1590     }
1591 
1592     /*
1593      * using two buffers, p1 and p2, for four strings:
1594      * result of subs_param, result of jit_subst_vars, result of
1595      * unescape and first word of line.
1596      *
1597      * So care is required to avoid clashes.
1598      */
1599     TAKE_PTR(pbuf, p1);
1600     TAKE_PTR(pbusy, p2);
1601 
1602     if (subs && subst_param(pbuf, line)) {
1603 	line = *pbuf ? ptrdata(*pbuf) : "";
1604 	SWAP2(pbusy, pbuf, tmp);
1605 	copied = 1;
1606     }
1607     if (jit_subs && jit_subst_vars(pbuf, line)) {
1608 	line = *pbuf ? ptrdata(*pbuf) : "";
1609 	SWAP2(pbusy, pbuf, tmp);
1610 	copied = 1;
1611     }
1612     if (!copied) {
1613 	*pbuf = ptrmcpy(*pbuf, line, strlen(line));
1614 	line = *pbuf ? ptrdata(*pbuf) : "";
1615 	SWAP2(pbusy, pbuf, tmp);
1616     }
1617     if (subs || jit_subs)
1618 	unescape(line);
1619 
1620     /* now line is in (pbusy) and (pbuf) is available */
1621 
1622     /* restore integrity of original line: must still put it in history */
1623     if (last_is_sep)
1624 	*ret++ = CMDSEP;
1625 
1626     if (REAL_ERROR) {
1627 	print_error(error);
1628 	DROP_PTR(pbuf); DROP_PTR(pbusy);
1629 	return NULL;
1630     }
1631     /* run-time debugging */
1632     if (opt_debug) {
1633 	PRINTF("#parsing: %s\n", line);
1634     }
1635 
1636     if (!*line)
1637 	send_line(line, silent);
1638     else do {
1639 	arg = skipspace(line);
1640 
1641 	if (arg[0] == '#' && arg[1] == '#') { /* send to other connection */
1642 	    *pbuf = ptrsetlen(*pbuf, len = strlen(arg));
1643 	    if (REAL_ERROR) { print_error(error); break; }
1644 	    buf = ptrdata(*pbuf);
1645 	    line = split_first_word(buf, len+1, arg + 2);
1646 	    /* now (pbuf) is used too: first word of line */
1647 	    /* line contains the rest */
1648 	    otcp_fd = tcp_fd;
1649 	    if ((tcp_fd = tcp_find(buf))<0) {
1650 		error = OUT_RANGE_ERROR;
1651 		PRINTF("#no connection named \"%s\"\n", buf);
1652 		break;
1653 	    }
1654 	    arg = skipspace(line);
1655 	    if (!*arg) {
1656 		if (CONN_LIST(tcp_fd).flags & SPAWN) {
1657 		    error = OUT_RANGE_ERROR;
1658 		    PRINTF("#only MUD connections can be default ones!\n");
1659 		} else {
1660 		    /* set it as main connection */
1661 		    tcp_set_main(tcp_fd);
1662 		    otcp_fd = -1;
1663 		}
1664 		/* stop parsing, otherwise a newline would be sent to tcp_fd */
1665 		break;
1666 	    }
1667 	    /* now we can trash (pbuf) */
1668 	}
1669 
1670 	if (*arg == '{') {    /* instruction contains a block */
1671 	    end = first_regular(line = arg + 1, '}');
1672 
1673 	    if (*end) {
1674 		*end = '\0';
1675 		parse_user_input(line, silent);
1676 		*end = '}';
1677 	    } else
1678 		print_error(error=MISSING_PAREN_ERROR);
1679 	} else {
1680 	    int oneword;
1681 	    /* initial spaces are NOT skipped this time */
1682 
1683 	    *pbuf = ptrsetlen(*pbuf, len = strlen(line));
1684 	    if (REAL_ERROR) { print_error(error); break; }
1685 
1686 	    buf = ptrdata(*pbuf);
1687 	    arg = split_first_word(buf, len+1, line);
1688 	    /* buf contains the first word, arg points to arguments */
1689 
1690 	    /* now pbuf is used too */
1691 	    if (!*arg) oneword = 1;
1692 	    else oneword = 0;
1693 
1694 	    if ((np = *lookup_alias(buf))&&np->active) {
1695 		push_params();
1696 		if (REAL_ERROR) break;
1697 
1698 		split_words(arg);  /* split argument into words
1699 				    and place them in $0 ... $9 */
1700 		parse_instruction(np->subst, 0, 1, 1);
1701 
1702 		if (error!=DYN_STACK_UND_ERROR && error!=DYN_STACK_OV_ERROR)
1703 		    pop_params();
1704 
1705 		/* now check for internal commands */
1706 		/* placed here to allow also aliases starting with "#" */
1707 	    } else if (*(end = skipspace(line)) == '#') {
1708 
1709 		if (*(end = skipspace(end + 1)) == '(') {    /* execute #() */
1710 		    end++;
1711 		    (void)evaln(&end);
1712 		    if (REAL_ERROR) print_error(error);
1713 		} else
1714 		    parse_commands(buf + 1, arg);
1715 		/* ok, buf contains skipspace(first word) */
1716 	    } else if (!oneword || !map_walk(buf, silent, 0)) {
1717 		/* it is ok, map_walk accepts only one word */
1718 
1719 		if (!subs && !jit_subs)
1720 		    unescape(line);
1721 		send_line(line, silent);
1722 	    }
1723 	}
1724     } while (0);
1725 
1726     if (otcp_fd != -1)
1727 	tcp_fd = otcp_fd;
1728     DROP_PTR(pbuf); DROP_PTR(pbusy);
1729     return !REAL_ERROR ? ret : NULL;
1730 }
1731 
1732 /*
1733  * parse input from user: calls parse_instruction for each instruction
1734  * in cmd_line.
1735  * silent = 1 if the line should not be echoed, 0 otherwise.
1736  */
__P2(char *,line,char,silent)1737 void parse_user_input __P2 (char *,line, char,silent)
1738 {
1739     do {
1740 	line = parse_instruction(line, silent, 0, 0);
1741     } while (!error && line && *line);
1742 }
1743 
1744 /*
1745  * parse powwow's own commands
1746  */
__P2(char *,command,char *,arg)1747 static void parse_commands __P2 (char *,command, char *,arg)
1748 {
1749     int i, j;
1750     cmdstruct *c;
1751 
1752     /* We ALLOW special commands also on subsidiary connections ! */
1753 
1754     /* assume output will be enough to make input line = last screen line */
1755     /* line0 = lines - 1; */
1756     if (isdigit(*command) && (i = atoi(command))) {
1757 	if (i >= 0) {
1758 	    while (i--)
1759 		(void)parse_instruction(arg, 1, 0, 1);
1760 	} else {
1761 	    PRINTF("#bogus repeat count\n");
1762 	}
1763     } else {
1764 	j = strlen(command);
1765 
1766 	if( j == 0 ) {
1767 		/* comment */
1768 		return;
1769 	}
1770 
1771 	for( c = commands; c != NULL; c = c -> next )
1772 	    if (!strncmp(command, c -> name, j)) {
1773 		if (c -> funct) {
1774 		    (*(c -> funct))(arg);
1775 		    return;
1776 		}
1777 	    }
1778 
1779 	PRINTF("#unknown powwow command \"%s\"\n", command);
1780     }
1781 }
1782 
1783 /*
1784  * substitute $0..$9 and @0..@9 in a string
1785  * (unless $ or @ is escaped with backslash)
1786  *
1787  * return 0 if dst not filled. if returned 0 and not error,
1788  * there was nothing to substitute.
1789  */
__P2(ptr *,buf,char *,src)1790 static int subst_param __P2 (ptr *,buf, char *,src)
1791 {
1792     int done, i;
1793     char *dst, *tmp, kind;
1794 
1795     if (!strchr(src, '$') && !strchr(src, '@'))
1796 	return 0;
1797 
1798     i = strlen(src);
1799     if (!*buf || ptrlen(*buf) < i) {
1800 	*buf = ptrsetlen(*buf, i);
1801 	if (REAL_ERROR)
1802 	    return 0;
1803     }
1804     dst = ptrdata(*buf);
1805 
1806     while (*src)  {
1807 	while (*src && *src != '$' && *src != '@' && *src != ESC)
1808 	    *dst++ = *src++;
1809 
1810 	if (*src == ESC) {
1811 	    while (*src == ESC)
1812 		*dst++ = *src++;
1813 
1814 	    if (*src)
1815 		*dst++ = *src++;
1816 	}
1817 
1818 	done = 0;
1819 	if (*src == '$' || *src == '@') {
1820 	    kind = *src == '$' ? 1 : 0;
1821 	    tmp = src + 1;
1822 	    if (isdigit(*tmp)) {
1823 		i = atoi(tmp);
1824 		while (isdigit(*tmp))
1825 		    tmp++;
1826 
1827 		if (i < NUMPARAM) {
1828 		    int max = 0, n;
1829 		    char *data = NULL, buf2[LONGLEN];
1830 
1831 		    done = 1;
1832 		    src = tmp;
1833 
1834 		    /* now the actual substitution */
1835 		    if (kind) {
1836 			if (*VAR[i].str && (data = ptrdata(*VAR[i].str)))
1837 			    max = ptrlen(*VAR[i].str);
1838 		    } else {
1839 			sprintf(data = buf2, "%ld", *VAR[i].num);
1840 			max = strlen(buf2);
1841 		    }
1842 		    if (data && max) {
1843 			n = dst - ptrdata(*buf);
1844 			*buf = ptrpad(*buf, max);
1845 			if (REAL_ERROR)
1846 			    return 0;
1847 			dst = ptrdata(*buf) + n;
1848 			memcpy(dst, data, max);
1849 			dst += max;
1850 		    }
1851 		}
1852 	    }
1853 	}
1854 	if (!done && (*src == '$' || *src == '@'))
1855 	    *dst++ = *src++;
1856     }
1857     *dst = '\0';
1858     return 1;
1859 }
1860 
1861 /*
1862  * just-in-time substitution:
1863  * substitute ${name}, @{name} and #{expression} in a string
1864  * (unless "${", "@{" or "#{" are escaped with backslash)
1865  *
1866  * return 0 if dst not filled. if returned 0 and not error,
1867  * there was nothing to substitute.
1868  */
1869 
__P2(ptr *,buf,char *,src)1870 static int jit_subst_vars __P2 (ptr *,buf, char *,src)
1871 {
1872     int i, done, kind;
1873     char *tmp, *name, *dst, c;
1874     varnode *named_var;
1875 
1876     if (!strstr(src, "${") && !strstr(src, "@{") && !strstr(src, "#{"))
1877 	return 0;
1878 
1879     i = strlen(src);
1880     if (!*buf || ptrlen(*buf) < i) {
1881 	*buf = ptrsetlen(*buf, i);
1882 	if (REAL_ERROR)
1883 	    return 0;
1884     }
1885     dst = ptrdata(*buf);
1886 
1887     while (*src)  {
1888 	while (*src && *src != '$' && *src != '@' && *src != '#' && *src != ESC)
1889 	    *dst++ = *src++;
1890 
1891 	if (*src == ESC) {
1892 	    while (*src == ESC)
1893 		*dst++ = *src++;
1894 
1895 	    if (*src)
1896 		*dst++ = *src++;
1897 	}
1898 
1899 	done = 0;
1900 	if (*src == '$' || *src == '@') {
1901 	    i = 0;
1902 	    kind = *src == '$' ? 1 : 0;
1903 	    tmp = src + 1;
1904 	    if (*tmp == '{') {
1905 		tmp = skipspace(tmp+1);
1906 		if (isdigit(*tmp) || *tmp == '-') {
1907 		    /* numbered variable */
1908 		    i = atoi(tmp);
1909 		    if (i >= -NUMVAR && i < NUMPARAM) {
1910 			if (*tmp == '-')
1911 			    tmp++;
1912 			while (isdigit(*tmp))
1913 			    tmp++;
1914 			done = 1;
1915 		    }
1916 		} else if (isalpha(*tmp) || *tmp == '_') {
1917 		    /* named variable */
1918 		    name = tmp++;
1919 		    while (isalnum(*tmp) || *tmp == '_')
1920 			tmp++;
1921 		    c = *tmp;
1922 		    *tmp = '\0';
1923 		    named_var = *lookup_varnode(name, kind);
1924 		    *tmp = c;
1925 		    if (named_var) {
1926 			i = named_var->index;
1927 			done = 1;
1928 		    }
1929 		}
1930 		tmp = skipspace(tmp);
1931 		if (done) {
1932 		    int max = 0, n;
1933 		    char *data = NULL, buf2[LONGLEN];
1934 
1935 		    src = tmp + 1; /* skip the '}' */
1936 
1937 		    /* now the actual substitution */
1938 		    if (kind == 1) {
1939 			if (*VAR[i].str && (data = ptrdata(*VAR[i].str)))
1940 			    max = ptrlen(*VAR[i].str);
1941 		    } else {
1942 			sprintf(data = buf2, "%ld", *VAR[i].num);
1943 			max = strlen(buf2);
1944 		    }
1945 		    if (data && max) {
1946 			n = dst - ptrdata(*buf);
1947 			*buf = ptrpad(*buf, max);
1948 			if (REAL_ERROR)
1949 			    return 0;
1950 			dst = ptrdata(*buf) + n;
1951 			memcpy(dst, data, max);
1952 			dst += max;
1953 		    }
1954 		} else if (*tmp == '}')
1955 		    /* met an undefined variable, consider empty */
1956 		    src = tmp + 1;
1957 
1958 		/* else syntax error, do nothing */
1959 	    }
1960 	} else if (src[0] == '#' && src[1] == '{') {
1961 	    int max, n;
1962 	    ptr pbuf = (ptr)0;
1963 
1964 	    src += 2;
1965 	    (void)evalp(&pbuf, &src);
1966 	    if (REAL_ERROR) {
1967 		ptrdel(pbuf);
1968 		return 0;
1969 	    }
1970 	    if (pbuf) {
1971 		max = ptrlen(pbuf);
1972 		n = dst - ptrdata(*buf);
1973 		*buf = ptrpad(*buf, max);
1974 		if (REAL_ERROR) {
1975 		    ptrdel(pbuf);
1976 		    return 0;
1977 		}
1978 		dst = ptrdata(*buf) + n;
1979 		memcpy(dst, ptrdata(pbuf), max);
1980 		dst += max;
1981 	    }
1982 	    ptrdel(pbuf);
1983 
1984 	    if (*src)
1985 		src = skipspace(src);
1986 	    if (*src != '}') {
1987 		PRINTF("#{}: ");
1988 		print_error(error=MISSING_PAREN_ERROR);
1989 		return 0;
1990 	    }
1991 	    done = 1;
1992 	    if (*src)
1993 		src++;
1994 	}
1995 
1996 	if (!done && (*src == '$' || *src == '@' || *src == '#'))
1997 	    /* not matched, just copy */
1998 	    *dst++ = *src++;
1999     }
2000     *dst = '\0';
2001     ptrtrunc(*buf, dst - ptrdata(*buf));
2002     return 1;
2003 }
2004 
2005 /*
2006  * set definition file:
2007  * rules: if powwow_dir is set it is searched first.
2008  *        if file doesn't exist, it is created there.
2009  * If a slash appears in the name, the powwow_dir isn't used.
2010  */
__P1(char *,arg)2011 void set_deffile __P1 (char *,arg)
2012 {
2013     if (!strchr(arg, '/') && *powwow_dir) {
2014 	strcpy(deffile, powwow_dir);
2015 	strcat(deffile, arg);
2016 	if ((access(deffile, R_OK) == -1 || access(deffile, W_OK) == -1)
2017 	    && !access(arg,R_OK) && !access(arg, W_OK))
2018 	    strcpy(deffile, arg);
2019     } else
2020 	strcpy(deffile, arg);
2021 }
2022 
2023 /*
2024  * GH: return true if var is one of the permanent variables
2025  */
__P1(varnode *,v)2026 int is_permanent_variable __P1 (varnode *,v)
2027 {
2028     return (v == prompt || v == last_line);
2029 }
2030