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 = ¶mstk.p[0][j].num;
1451 VAR[j].str = ¶mstk.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 = ¶mstk.p[paramstk.curr][i].num) = 0;
1468 ptrzero(*(VAR[i].str = ¶mstk.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 = ¶mstk.p[paramstk.curr][i].num;
1486 VAR[i].str = ¶mstk.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