aboutsummaryrefslogtreecommitdiffstats
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c1995
1 files changed, 1995 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..547ec72
--- /dev/null
+++ b/main.c
@@ -0,0 +1,1995 @@
+/*
+ * powwow -- mud client with telnet protocol
+ *
+ * Copyright (C) 1998,2000,2002 by Massimiliano Ghilardi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ *
+ * History:
+ *
+ * Initially inspired to the Tintin client by Peter Unold,
+ * Powwow contains no Tintin code.
+ * The original program Cancan, written by Mattias Engdegård (Yorick)
+ * (f91-men@nada.kth.se) 1992-94,
+ * was greatly improved upon by Vivriel, Thuzzle and Ilie and then
+ * transformed from Cancan into Powwow by Cosmos who worked
+ * to make it yet more powerful.
+ * AmigaDOS porting attempt by Fror.
+ * Many new features added by Dain.
+ * As usual, all the developers are in debt to countless users
+ * for suggestions and debugging.
+ */
+
+/*
+ * Set this to whatever you like
+ *
+ * #define POWWOW_DIR "/home/gustav/powwow"
+ */
+
+#define POWWOW_VERSION VERSION ", Copyright 2000-2005 by Cosmos\n" \
+ "(contributions by Yorick, Vivriel, Thuzzle, Ilie, Fror, Dain)\n"
+#define HELPNAME "powwow.help"
+#define COPYNAME "COPYING"
+
+#ifndef POWWOW_DIR
+# define POWWOW_DIR "./"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <memory.h>
+#include <unistd.h>
+
+/* are these really needed? */
+extern int errno;
+extern int select();
+
+#ifdef USE_REGEXP
+# include "malloc.h"
+# include <regex.h>
+#endif
+
+#include "defines.h"
+#include "main.h"
+#include "utils.h"
+#include "beam.h"
+#include "cmd.h"
+#include "cmd2.h"
+#include "edit.h"
+#include "map.h"
+#include "list.h"
+#include "tcp.h"
+#include "tty.h"
+#include "eval.h"
+#include "log.h"
+
+/* local function declarations */
+#ifdef MOTDFILE
+static void printmotd __P ((void));
+#endif
+static void mainloop __P ((void));
+static void exec_delays __P ((void));
+static void prompt_reset_iac __P ((void));
+static void get_remote_input __P ((void));
+static void get_user_input __P ((void));
+
+static int search_action_or_prompt __P ((char *line, char clearline, char copyprompt));
+#define search_action(line, clearline) search_action_or_prompt((line), (clearline), 0)
+#define search_prompt(line, copyprompt) search_action_or_prompt((line), 0, (copyprompt)+1)
+
+static void set_params __P ((char *line, int *match_s, int *match_e));
+static void parse_commands __P ((char *command, char *arg));
+static int subst_param __P ((ptr *buf, char *src));
+static int jit_subst_vars __P ((ptr *buf, char *src));
+
+
+/* GLOBALS */
+static char *helpname = HELPNAME;
+static char *copyname = COPYNAME;
+
+long received = 0; /* amount of data received from remote host */
+long sent = 0; /* amount of data sent to remote host */
+
+char identified = 0; /* 1 after #identify */
+VOLATILE char confirm = 0; /* 1 if just tried to quit */
+int history_done = 0; /* number of recursive #history commands */
+int prompt_status = 0; /* prompt status: 0 = ready -> nothing to do;
+ * 1 if internal echo -> must redraw;
+ * -1 if something sent to MUD -> waiting for it.
+ */
+int line_status = 0; /* input line status: 0 = ready -> nothing to do;
+ * 1 if printed something -> must redraw.
+ */
+
+int limit_mem = 0; /* if !=0, max len of a string or text */
+
+char echo_ext = 1; /* 1 if text sent to MUD must be echoed */
+char echo_key = 1; /* 1 if binds must be echoed */
+char echo_int = 1; /* 0 if internal messages are suppressed */
+char opt_exit = 0; /* 1 to autoquit when closing last conn. */
+char opt_history; /* 1 if to save also history */
+char opt_words = 0; /* 1 if to save also word completion list */
+char opt_compact = 0; /* 1 if to clear prompt between remote messages */
+char opt_debug = 0; /* 1 if to echo every line before executing it */
+char opt_speedwalk = 0; /* 1 = speedwalk on */
+char opt_wrap = 0; /* 1 = word wrap active */
+char opt_autoprint = 0; /* 1 = automatically #print lines matched by actions */
+char opt_reprint = 0; /* 1 = reprint sent commands when we get a prompt */
+char opt_sendsize = 0; /* 1 = send term size upon connect */
+char opt_autoclear = 1; /* 1 = clear input line before executing commands
+ * from spawned programs.
+ * if 0, spawned progs must #clear before printing
+ */
+
+char hostname[BUFSIZE];
+int portnumber;
+static char powwow_dir[BUFSIZE]; /* default path to definition files */
+char deffile[BUFSIZE]; /* name and path of definition file */
+char helpfile[BUFSIZE]; /* name and path of help file */
+char copyfile[BUFSIZE]; /* name and path of copyright file */
+aliasnode *aliases[MAX_HASH]; /* head of alias hash list */
+aliasnode *sortedaliases; /* head of (ASCII) sorted alias list */
+actionnode *actions; /* head of action list */
+promptnode *prompts; /* head of prompt list */
+marknode *markers; /* head of mark list */
+int a_nice = 0; /* default priority of new actions/marks */
+keynode *keydefs; /* head of key binding list */
+delaynode *delays; /* head of delayed commands list */
+delaynode *dead_delays; /* head of dead-delayed commands list */
+
+varnode *named_vars[2][MAX_HASH]; /* head of named variables hash list */
+varnode *sortednamed_vars[2]; /* head of (ASCII) sorted named variables list */
+int max_named_vars = 100; /* max number of named vars (grows as needed) */
+int num_named_vars[2]; /* number of named variables actually used */
+
+static param_stack paramstk; /* stack of local unnamed vars */
+static unnamedvar global_var[NUMTOT]; /* global unnamed vars */
+
+vars *var; /* vector of all vars */
+
+ptr globptr[2]; /* global ptr buffer */
+char globptrok = 1|2; /* x&i = 0 if globptr[i] is in use */
+
+varnode *prompt; /* $prompt is always set */
+ptr marked_prompt; /* $prompt with marks added */
+static varnode *last_line; /* $line is always set to
+ * the last line processed */
+
+vtime now; /* current time */
+int now_updated; /* current time is up to date */
+vtime start_time; /* time of powwow timer startup */
+vtime ref_time; /* time corresponding to timer == 0 */
+
+function_any last_edit_cmd; /* GH: keep track of for repeated cmds */
+
+clock_t start_clock, cpu_clock;
+
+char initstr[BUFSIZE]; /* initial string to send on connect */
+
+int linemode = 0; /* line mode flags (LM_* in main.h) */
+
+/* for line editing */
+int cols=80, lines=24; /* screen size */
+int cols_1=79; /* == cols if tty_wrapglitch, == cols-1 otherwise */
+int olines; /* previous screen size */
+int col0; /* input line offset (= printstrlen of prompt) */
+int line0; /* screen line where the input line starts */
+char edbuf[BUFSIZE]; /* line editing buffer */
+int edlen; /* length of current input line */
+int pos = 0; /* cursor position in line */
+char surely_isprompt = 0; /* !=0 if last #prompt set #isprompt */
+char verbatim = 0; /* 1 = don't expand aliases or process semicolons */
+char prefixstr[BUFSIZE]; /* inserted in the editing buffer each time */
+char inserted_next[BUFSIZE];/* inserted in buffer just once */
+char flashback = 0; /* cursor is on excursion and should be put back */
+int excursion; /* where the excursion is */
+char edattrbeg[CAPLEN]; /* starting input line attributes */
+char edattrend[CAPLEN]; /* ending input line attributes */
+int edattrbg; /* input line attributes do change bg color */
+
+/* signals handling */
+VOLATILE int sig_pending, sig_winch_got, sig_chld_got;
+
+
+/* GH: different ID characters for different action types */
+/*
+ * Cosmos: they are hardcoded in cmd2.c, function parse_action()
+ * so don't change them.
+ */
+char action_chars[ACTION_TYPES] = { '>', '%' };
+
+/* GH: different delimeter modes */
+char *delim_list[] = { " ;", " <>!=(),;\"'{}[]+-/*%", 0 };
+int delim_len [] = { 2 , 21 , 0 };
+char *delim_name[] = { "normal", "program", "custom" };
+int delim_mode = DELIM_NORMAL;
+
+
+int main __P2 (int,argc, char **,argv)
+{
+ char *p;
+ int i;
+ int read_file = 0; /* GH: if true, powwow was started with
+ * a file argument, and initstr shall be ran */
+
+ /* initializations */
+ initstr[0] = 0;
+ memzero(conn_list, sizeof(conn_list));
+
+ update_now();
+ ref_time = start_time = movie_last = now;
+
+ start_clock = cpu_clock = clock();
+#ifndef NO_RANDOM
+ init_random((int)now.tv_sec);
+#endif
+
+ _cmd_init();
+
+ if ((p = getenv("POWWOWDIR"))) {
+ strcpy(powwow_dir, p);
+ if (powwow_dir[strlen(powwow_dir) - 1] != '/')
+ strcat(powwow_dir, "/");
+ } else
+ powwow_dir[0] = '\0';
+
+ if ((p = getenv("POWWOWHELP")))
+ strcpy(helpfile, p);
+ else if (powwow_dir[0])
+ strcpy(helpfile, powwow_dir);
+ else
+ strcpy(helpfile, POWWOW_DIR);
+
+ if (helpfile[strlen(helpfile) - 1] != '/')
+ strcat(helpfile, "/");
+ strcat(helpfile, helpname);
+ if (access(helpfile, R_OK) == -1 && !access(helpname, R_OK))
+ strcpy(helpfile, helpname);
+
+ if (powwow_dir[0])
+ strcpy(copyfile, powwow_dir);
+ else
+ strcpy(copyfile, POWWOW_DIR);
+ if (helpfile[strlen(helpfile) - 1] != '/')
+ strcat(copyfile, "/");
+ strcat(copyfile, copyname);
+ if (access(copyfile, R_OK) == -1 && !access(copyname, R_OK))
+ strcpy(copyfile, copyname);
+
+ /* initialize variables */
+ if ((var = (vars *)malloc(sizeof(vars)*(NUMTOT+max_named_vars)))) {
+ for (i=0; i<NUMTOT; i++) {
+ var[i].num = &global_var[i].num;
+ var[i].str = &global_var[i].str;
+ }
+ } else
+ syserr("malloc");
+
+ /* stack is empty */
+ paramstk.curr = 0;
+
+ /* allocate permanent variables */
+ if ((prompt = add_varnode("prompt", 1))
+ && (prompt->str = ptrnew(PARAMLEN))
+ && (marked_prompt = ptrnew(PARAMLEN))
+ && (last_line = add_varnode("last_line", 1))
+ && (last_line->str = ptrnew(PARAMLEN))
+ && (globptr[0] = ptrnew(PARAMLEN))
+ && (globptr[1] = ptrnew(PARAMLEN))
+ && !MEM_ERROR)
+ ;
+ else
+ syserr("malloc");
+
+
+ /*
+ ptr_bootstrap();
+ utils_bootstrap();
+ beam_bootstrap();
+ cmd_bootstrap();
+ map_bootstrap();
+ eval_bootstrap();
+ list_bootstrap();
+ tcp_bootstrap();
+ */
+ edit_bootstrap();
+ tty_bootstrap();
+
+#ifdef MOTDFILE
+ printmotd();
+#endif
+
+ printver();
+
+ if (argc == 1) {
+ tty_printf(
+"\nPowwow comes with ABSOLUTELY NO WARRANTY; for details type `#help warranty'.\n\
+This is free software, and you are welcome to redistribute it\n\
+under certain conditions; type `#help copyright' for details.\n"
+ );
+ }
+
+ if (argc == 1 || argc == 3) {
+ tty_add_initial_binds();
+ tty_add_walk_binds();
+ } else if (argc == 2 || argc == 4) {
+ /*
+ * assuming first arg is definition file name
+ * If three args, first is definition file name,
+ * second and third are hostname and port number
+ * (they overwrite the ones in definition file)
+ */
+ set_deffile(argv[1]);
+
+ if (access(deffile,R_OK) == -1 || access(deffile,W_OK) == -1) {
+ char portnum[INTLEN];
+ tty_printf("Creating %s\nHost name :", deffile);
+ tty_flush();
+ tty_gets(hostname, BUFSIZE);
+ if (hostname[0] == '\n')
+ hostname[0] = '\0';
+ else
+ strtok(hostname, "\n");
+ tty_puts("Port number:");
+ tty_flush();
+ tty_gets(portnum, INTLEN);
+ portnumber = atoi(portnum);
+ tty_add_initial_binds();
+ tty_add_walk_binds();
+ limit_mem = 1048576;
+ if (save_settings() < 0)
+ exit(0);
+ } else if (read_settings() < 0)
+ exit(0);
+ else
+ read_file = 1;
+ }
+ if (argc == 3 || argc == 4) {
+ /* assume last two args are hostname and port number */
+ my_strncpy(hostname, argv[argc - 2], BUFSIZE-1);
+ portnumber = atoi(argv[argc - 1]);
+ }
+
+ signal_start();
+ tty_start();
+
+ tty_puts(tty_clreoscr);
+ tty_putc('\n');
+ tty_gotoxy(col0 = 0, lines - 2);
+ tty_puts("Type #help for help.\n");
+ line0 = lines - 1;
+
+ FD_ZERO(&fdset);
+ FD_SET(tty_read_fd, &fdset);
+
+ if (*hostname)
+ tcp_open("main", (*initstr ? initstr : NULL), hostname, portnumber);
+
+ if (read_file && !*hostname && *initstr) {
+ parse_instruction(initstr, 0, 0, 1);
+ history_done = 0;
+ }
+
+ confirm = 0;
+
+ mainloop();
+
+ /* NOTREACHED */
+ return 0;
+}
+
+/*
+ * show current version
+ */
+void printver __P0 (void)
+{
+ tty_printf("Powwow version %s\n%s (%s, %s)%s\n", POWWOW_VERSION,
+#if __STDC__
+ "compiled " __TIME__ " " __DATE__,
+#else
+ "",
+#endif
+#ifdef USE_VT100
+ "vt100-only",
+#else
+ "termcaps",
+#endif
+#ifdef USE_SGTTY
+ "BSD sgtty",
+#else
+ "termios",
+#endif
+#ifdef USE_REGEXP
+ ""
+#else
+ " no regexp"
+#endif
+ );
+}
+
+#ifdef MOTDFILE
+/*
+ * print the message of the day if present
+ */
+static void printmotd __P0 (void)
+{
+ char line[BUFSIZE];
+ FILE *f = fopen(MOTDFILE, "r");
+ if (f) {
+ while (fgets(line, BUFSIZE, f))
+ tty_puts(line);
+ fclose(f);
+ }
+}
+#endif
+
+static void redraw_everything __P0 (void)
+{
+ if (prompt_status == 1 && line_status == 0)
+ line_status = 1;
+ if (prompt_status == 1)
+ draw_prompt();
+ else if (prompt_status == -1) {
+ promptzero();
+ col0 = surely_isprompt = '\0';
+ }
+ if (line_status == 1)
+ draw_input_line();
+}
+
+/* how much can we sleep in select() ? */
+static void compute_sleeptime __P1 (vtime **,timeout)
+{
+ static vtime tbuf;
+ int sleeptime = 0;
+
+ if (delays) {
+ update_now();
+ sleeptime = diff_vtime(&delays->when, &now);
+ if (!sleeptime)
+ sleeptime = 1; /* if sleeptime is less than 1 millisec,
+ * set to 1 millisec */
+ }
+ if (flashback && (!sleeptime || sleeptime > FLASHDELAY))
+ sleeptime = FLASHDELAY;
+
+ if (sleeptime) {
+ tbuf.tv_sec = sleeptime / mSEC_PER_SEC;
+ tbuf.tv_usec = (sleeptime % mSEC_PER_SEC) * uSEC_PER_mSEC;
+ *timeout = &tbuf;
+ } else
+ *timeout = (vtime *)NULL;
+}
+
+/*
+ * main loop.
+ */
+static void mainloop __P0 (void)
+{
+ fd_set readfds;
+ int i, err;
+ vtime *timeout;
+
+ for (;;) {
+ readfds = fdset;
+
+ tcp_fd = tcp_main_fd;
+ exec_delays();
+
+ do {
+ if (sig_pending)
+ sig_bottomhalf(); /* this might set errno... */
+
+ tcp_flush();
+
+ if (!(pos <= edlen)) {
+ PRINTF("\n#*ARGH* assertion failed (pos <= edlen): mail max@linuz.sns.it\n");
+ pos = edlen;
+ }
+
+ redraw_everything();
+ tty_flush();
+
+ compute_sleeptime(&timeout);
+
+ error = now_updated = 0;
+
+ err = select(tcp_max_fd+1, &readfds, NULL, NULL, timeout);
+
+ prompt_reset_iac();
+
+ } while (err < 0 && errno == EINTR);
+
+ if (err < 0 && errno != EINTR)
+ syserr("select");
+
+ if (flashback) putbackcursor();
+
+ /* process subsidiary and spawned connections first */
+ if (tcp_count > 1 || tcp_attachcount) {
+ for (i=0; err && i<conn_max_index; i++) {
+ if (CONN_INDEX(i).id && CONN_INDEX(i).fd != tcp_main_fd) {
+ tcp_fd = CONN_INDEX(i).fd;
+ if (FD_ISSET(tcp_fd, &readfds)) {
+ err--;
+ get_remote_input();
+ }
+ }
+ }
+ }
+ /* and main connection last */
+ if (tcp_main_fd != -1 && FD_ISSET(tcp_main_fd, &readfds)) {
+ tcp_fd = tcp_main_fd;
+ get_remote_input();
+ }
+ if (FD_ISSET(tty_read_fd, &readfds)) {
+ tcp_fd = tcp_main_fd;
+ confirm = 0;
+ get_user_input();
+ }
+
+ }
+}
+
+/*
+ * set the new prompt / input line status
+ */
+void status __P1 (int,s)
+{
+ if (s < 0) {
+ /* waiting prompt from the MUD */
+ prompt_status = s;
+ line_status = 1;
+ } else {
+ if (prompt_status >= 0)
+ prompt_status = s;
+ if (line_status >= 0)
+ line_status = s;
+ }
+}
+
+/*
+ * execute the delayed labels that have expired
+ * and place them in the disabled delays list
+ */
+static void exec_delays __P0 (void)
+{
+ delaynode *dying;
+ ptr *pbuf, buf = (ptr)0;
+
+ if (!delays)
+ return;
+
+ update_now();
+
+ if (cmp_vtime(&delays->when, &now) > 0)
+ return;
+
+ /* remember delayed command may modify the prompt and/or input line! */
+ if (prompt_status == 0) {
+ clear_input_line(opt_compact || !echo_int);
+ if (!opt_compact && echo_int && prompt_status == 0 && promptlen) {
+ tty_putc('\n');
+ col0 = 0;
+ status(1);
+ }
+ }
+
+ TAKE_PTR(pbuf, buf);
+
+ while (delays && cmp_vtime(&delays->when, &now) <= 0) {
+ dying = delays; /* remove delayed command from active list */
+ delays = dying->next; /* and put it in the dead one */
+
+ add_node((defnode *)dying, (defnode **)&dead_delays, rev_time_sort);
+
+ /* must be moved before executing delay->command
+ * and command must be copied in a buffer
+ * (can't you imagine why? The command may edit itself...)
+ */
+
+ if (echo_int)
+ tty_printf("#now [%s]\n", dying->command);
+
+ if (*dying->command) {
+
+ error = 0;
+
+ *pbuf = ptrmcpy(*pbuf, dying->command, strlen(dying->command));
+ if (MEM_ERROR)
+ errmsg("malloc (#in/#at)");
+ else {
+ parse_instruction(ptrdata(*pbuf), 0, 0, 1);
+ history_done = 0;
+ }
+ }
+ }
+ DROP_PTR(pbuf);
+}
+
+
+#define IAC_N 1024
+static char *iac_v[IAC_N];
+static int iac_f, iac_l;
+
+static void prompt_reset_iac __P0 (void)
+{
+ iac_f = iac_l = 0;
+}
+
+void prompt_set_iac __P1 (char *,p)
+{
+ if (iac_f == iac_l)
+ iac_f = iac_l = 0;
+
+ if (iac_l < IAC_N)
+ iac_v[iac_l++] = p;
+}
+
+static char *prompt_get_iac __P0 (void)
+{
+ return iac_l > iac_f ? iac_v[iac_f] : NULL;
+}
+
+static int grab_prompt __P3 (char *,linestart, int,len, int,islast)
+{
+ char *p;
+ int is_iac_prompt = surely_isprompt = 0;
+
+ /* recognize IAC GA as end-of-prompt marker */
+ if ((CONN_LIST(tcp_fd).flags & IDPROMPT)) {
+ if ((p = prompt_get_iac()) && p > linestart && p <= linestart+len)
+ iac_f++, is_iac_prompt = len = p - linestart;
+ else if (!islast)
+ return 0;
+ }
+
+ /*
+ * We may get a prompt in the middle of a bunch of lines, so
+ * match #prompts. They usually have no #print, so we print manually
+ * if islast is not set and a #prompt matches.
+ */
+ if ((is_iac_prompt || islast || printstrlen(linestart) < cols) &&
+ ((search_prompt(linestart, 1) && surely_isprompt) || is_iac_prompt)) {
+
+ char *reprint;
+ /*
+ * the line starts with a prompt.
+ * #isprompt placed the actual prompt in $prompt,
+ * we must still process the rest of the line.
+ */
+ if (surely_isprompt > 0 && surely_isprompt <= len) {
+ len = surely_isprompt;
+ prompt_status = 1;
+ } else if (!surely_isprompt && is_iac_prompt) {
+ prompt->str = ptrmcpy(prompt->str, linestart, len = surely_isprompt = is_iac_prompt);
+ if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return 0; }
+ prompt_status = 1;
+ }
+
+ /*
+ * following data may be the reply to a previously sent command,
+ * so we may have to reprint that command.
+ */
+ if ((reprint = reprint_getline()) && *reprint) {
+ smart_print(promptstr, 0);
+ status(-1);
+ tty_printf("(%s)\n", reprint);
+ } else if (!islast)
+ smart_print(promptstr, 1);
+ } else if (islast) {
+ prompt->str = ptrmcpy(prompt->str, linestart, len);
+ if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return 0; }
+ prompt_status = 1; /* good, we got what to redraw */
+ } else
+ len = 0;
+
+ return len;
+}
+
+
+/*
+ * process remote input one line at time. stop at "\n".
+ */
+static void process_singleline __P2 (char **,pbuf, int *,psize)
+{
+ int size, len = 0;
+ char *wasn = 0, *buf, *linestart = *pbuf, *lineend, *end = *pbuf + *psize;
+
+ if ((lineend = memchr(linestart, '\n', *psize))) {
+ /* ok, there is a newline */
+
+ *(wasn = lineend) = '\0';
+ buf = lineend + 1; /* start of next line */
+ }
+
+ if (!lineend)
+ /* line continues till end of buffer, no trailing \n */
+ buf = lineend = end;
+
+ size = buf - linestart;
+
+#ifdef DEBUGCODE_2
+ /* debug code to see in detail what codes come from the server */
+ {
+ char c;
+ char *t;
+ tty_putc('{');
+ for (t = linestart; t < lineend && (c = *t); t++) {
+ if (c < ' ' || c > '~')
+ tty_printf("[%d]", (int)c);
+ else
+ tty_putc(c);
+ }
+ tty_puts("}\n");
+ }
+#endif
+
+ /*
+ * Try to guess where is the prompt... really not much more than
+ * a guess. Again, do it only on main connection: we do not want
+ * output from other connections to mess with the prompt
+ * of main connection :)
+ *
+ * Since we now have #prompt, behave more restrictively:
+ * if no #prompts match or a #prompt matches but does not set #isprompt
+ * (i.e. recognize it for not being a prompt),
+ * we check for #actions on it when the \n arrives.
+ * if a #prompt matches and sets #isprompt, then it is REALLY a prompt
+ * so never match #actions on it.
+ */
+ if (lineend == end && tcp_fd == tcp_main_fd && printstrlen(linestart) < cols) {
+ /*
+ * The last line in the chunk we received has no trailing \n
+ * Assume it is a prompt.
+ */
+ if (surely_isprompt && promptlen && prompt_status == 1) {
+ draw_prompt(); tty_putc('\n'); col0 = 0;
+ }
+ surely_isprompt = 0;
+ promptzero();
+ if (lineend > linestart && (len = grab_prompt(linestart, lineend-linestart, 1)))
+ size = len;
+ } else {
+ if (tcp_fd == tcp_main_fd) {
+ surely_isprompt = 0;
+ promptzero();
+
+ if (linestart[0]) {
+ /* set $last_line */
+ last_line->str = ptrmcpy(last_line->str, linestart, strlen(linestart));
+ if (MEM_ERROR) { print_error(error); return; }
+
+ if (lineend > linestart && (len = grab_prompt(linestart, lineend-linestart, 0)))
+ size = len;
+ }
+ }
+ if (!len && (!*linestart || (!search_action(linestart, 0) || opt_autoprint))) {
+ if (line0 < lines - 1)
+ line0++;
+ if (tcp_fd != tcp_main_fd) /* sub connection */
+ tty_printf("##%s> ", CONN_LIST(tcp_fd).id);
+
+ smart_print(linestart, 1);
+ }
+ }
+
+ /*
+ * search_prompt and search_action above
+ * might set error: clear it to avoid troubles.
+ */
+ error = 0;
+ if (wasn) *wasn = '\n';
+ *pbuf += size;
+ *psize -= size;
+}
+
+/*
+ * Code to merge lines from host that were splitted
+ * into different packets: it is a horrible kludge (sigh)
+ * and can be used only on one connection at time.
+ * We currently do it on main connection.
+ *
+ * Note that this code also works for _prompts_ splitted into
+ * different packets, as long as no #prompts execute #isprompt
+ * on an incomplete prompt (as stated in the docs).
+ */
+static int process_first_fragment __P2 (char *,buf, int,got)
+{
+ int processed = 0;
+
+ /*
+ * Don't merge if the first part of the line was intercepted
+ * by a #prompt action which executed #isprompt
+ * (to avoid intercepting it twice)
+ */
+ if (*buf == '\n') {
+ char deleteprompt = 0, matched = 0;
+
+ if (opt_compact) {
+ /* in compact mode, skip the first \n */
+ deleteprompt = 1;
+ processed++;
+ }
+
+ /*
+ * the prompt was actually a complete line.
+ * no need to put it on the top of received data.
+ * unless #isprompt was executed, demote it to a regular line,
+ * match #actions on it, copy it in last_line.
+ */
+ if (!surely_isprompt) {
+ last_line->str = ptrcpy(last_line->str, prompt->str);
+ if (MEM_ERROR) { print_error(error); return 0; }
+
+ /*
+ * Kludge for kludge: don't delete the old prompt immediately.
+ * Instead, match actions on it first.
+ * If it matches, clear the line before running the action
+ * (done by the `1' in search_action() )
+ * If it doesn't match, delete it only if opt_compact != 0
+ */
+
+ matched = search_action(promptstr, 1);
+ }
+ if (!matched)
+ clear_input_line(deleteprompt);
+ status(-1);
+ } else {
+ /*
+ * try to merge the prompt with the first line in buf
+ * (assuming we have a line splitted into those parts)
+ * then clear the prompt.
+ */
+ char *lineend, *spinning = NULL;
+
+ /* find the end of the first line. include the final newline. */
+ if ((lineend = strchr(buf, '\n')))
+ lineend++;
+ else
+ lineend = buf + got;
+
+ if (surely_isprompt || (spinning = memchr(buf, '\b', lineend - buf))) {
+ /*
+ * either #isprompt _was_ executed,
+ * or we got a MUME spinning bar.
+ * in both cases, don't try to merge.
+ *
+ * print a newline (to keep the old prompt on screen)
+ * only if !opt_compact and we didn't get a MUME spinning bar.
+ */
+ clear_input_line(opt_compact);
+ if (!spinning && !opt_compact)
+ tty_putc('\n'), col0 = 0;
+ promptzero();
+ } else {
+ ptr *pp, p = (ptr)0;
+ char *dummy;
+ int dummyint;
+
+ /* ok, merge this junk with the prompt */
+ TAKE_PTR(pp, p);
+ *pp = ptrcpy(*pp, prompt->str);
+ *pp = ptrmcat(*pp, buf, lineend - buf);
+
+ if (MEM_ERROR) { print_error(error); return 0; }
+ if (!*pp)
+ return 0;
+ dummy = ptrdata(*pp);
+ dummyint = ptrlen(*pp);
+ /* this also sets last_line or prompt->str : */
+ clear_input_line(1);
+ process_singleline(&dummy, &dummyint);
+
+ processed = lineend - buf;
+ }
+ }
+ return processed;
+}
+
+/*
+ * process input from remote host:
+ * detect special sequences, trigger actions, locate prompt,
+ * word-wrap, print to tty
+ */
+void process_remote_input __P2 (char *,buf, int,size)
+{
+
+ if (promptlen && tcp_fd == tcp_main_fd)
+ promptzero(); /* discard the prompt, we look for another one */
+
+ status(1);
+
+ do {
+ process_singleline(&buf, &size);
+ } while (size > 0);
+}
+
+static void common_clear __P1 (int,newline)
+{
+ clear_input_line(opt_compact);
+ if (newline) {
+ tty_putc('\n'); col0 = 0; status(1);
+ }
+}
+
+/*
+ * get data from the socket and process/display it.
+ */
+static void get_remote_input __P0 (void)
+{
+ char buffer[BUFSIZE + 2]; /* allow for a terminating \0 later */
+ char *buf = buffer, *newline;
+ int got, otcp_fd, i;
+
+ if (CONN_LIST(tcp_fd).fragment) {
+ if ((i = strlen(CONN_LIST(tcp_fd).fragment)) >= BUFSIZE-1) {
+ i = 0;
+ common_clear(promptlen && !opt_compact);
+ tty_printf("#error: ##%s : line too long, discarded\n", CONN_LIST(tcp_fd).id);
+ } else {
+ buf += i;
+ memcpy(buffer, CONN_LIST(tcp_fd).fragment, i);
+ }
+ free(CONN_LIST(tcp_fd).fragment);
+ CONN_LIST(tcp_fd).fragment = 0;
+ } else
+ i = 0;
+
+ got = tcp_read(tcp_fd, buf, BUFSIZE - i);
+ if (!got)
+ return;
+
+ buf[got]='\0'; /* Safe, there is space. Do it now not to forget it later */
+ received += got;
+
+#ifdef DEBUGCODE
+ /* debug code to see in detail what strange codes come from the server */
+ {
+ char c, *t;
+ newline = buf + got;
+ tty_printf("%s{", edattrend);
+ for (t = buf; t < newline; t++) {
+ if ((c = *t) < ' ' || c > '~')
+ tty_printf("[%d]", c);
+ else tty_putc(c);
+ }
+ tty_puts("}\n");
+ }
+#endif
+
+ if (!(CONN_LIST(tcp_fd).flags & ACTIVE))
+ return; /* process only active connections */
+
+ got += (buf - buffer);
+ buf = buffer;
+
+ if (CONN_LIST(tcp_fd).flags & SPAWN) {
+ /* this is data from a spawned child or an attached program.
+ * execute as if typed */
+ otcp_fd = tcp_fd;
+ tcp_fd = tcp_main_fd;
+
+ if ((newline = strchr(buf, '\n'))) {
+ /* instead of newline = strtok(buf, "\n") */
+ *newline = '\0';
+
+ if (opt_autoclear && line_status == 0) {
+ common_clear(!opt_compact);
+ }
+ do {
+ if (echo_int) {
+ if (line_status == 0) {
+ common_clear(!opt_compact);
+ }
+ tty_printf("##%s [%s]\n", CONN_LIST(otcp_fd).id, buf);
+ }
+ parse_user_input(buf, 0);
+ /*
+ * strtok() may have been used in parse_user_input()...
+ * cannot rely it refers on what we may have set above.
+ * (it causes a bug in #spawned commands if they
+ * evaluate (attr(), noattr) or they #connect ... )
+ * so do it manually.
+ */
+ /*
+ * buf = strtok(NULL, "\n");
+ */
+ if ((buf = newline) &&
+ (newline = strchr(++buf, '\n')))
+ *newline = '\0';
+ } while (buf && newline);
+ }
+
+ if (buf && *buf && !newline) {
+ /*
+ * save last fragment for later, when spawned command will
+ * (hopefully) send the rest of the text
+ */
+ CONN_LIST(otcp_fd).fragment = my_strdup(buf);
+
+ if (echo_int) {
+ if (line_status == 0) {
+ common_clear(!opt_compact);
+ }
+ tty_printf("#warning: ##%s : unterminated [%s]\n", CONN_LIST(otcp_fd).id, buf);
+ }
+ }
+ tcp_fd = otcp_fd;
+ return;
+ }
+
+ if (linemode & LM_CHAR) {
+ /* char-by-char mode: just display output, no fuss */
+ clear_input_line(0);
+ tty_puts(buf);
+ return;
+ }
+
+ /* line-at-a-time mode: process input in a number of ways */
+
+ if (tcp_fd == tcp_main_fd && promptlen) {
+ i = process_first_fragment(buf, got);
+ buf += i, got -= i;
+ } else {
+ common_clear(promptlen && !opt_compact);
+ }
+
+ if (got > 0)
+ process_remote_input(buf, got);
+}
+
+
+#ifdef USE_REGEXP
+/*
+ * GH: matches precompiled regexp, return actual params in param array
+ * return 1 if matched, 0 if not
+ */
+static int match_regexp_action __P4 (void *,regexp, char *,line, int *,match_s, int *,match_e)
+{
+ regmatch_t reg_match[NUMPARAM - 1];
+
+ if (!regexec((regex_t *)regexp, line, NUMPARAM - 1, reg_match, 0)) {
+ int n;
+
+ match_s[0] = 0;
+ match_e[0] = strlen(line);
+ for (n = 1; n < NUMPARAM; n++)
+ match_s[n] = match_e[n] = 0;
+ for (n = 0; n <= (int)((regex_t *)regexp)->re_nsub &&
+ reg_match[n].rm_so != -1 && n < NUMPARAM - 1; n++) {
+ match_s[n+1] = reg_match[n].rm_so;
+ match_e[n+1] = reg_match[n].rm_eo;
+ }
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+/*
+ * match action containing &1..&9 and $1..$9 and return actual params start/end
+ * in match_s/match_e - return 1 if matched, 0 if not
+ */
+static int match_weak_action __P4 (char *,pat, char *,line, int *,match_s, int *,match_e)
+{
+ char mpat[BUFSIZE], *npat=0, *npat2=0, *src=line, *nsrc=0, c;
+ ptr *pbuf, buf = (ptr)0;
+ char *tmp, *realpat = pat;
+ int mbeg = 0, mword = 0, prm = -1, p;
+
+ TAKE_PTR(pbuf, buf);
+
+ if (jit_subst_vars(pbuf, pat))
+ pat = ptrdata(*pbuf);
+ if (REAL_ERROR) {
+ print_error(error);
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ unescape(pat);
+
+ for (p = 0; p < NUMPARAM; p++)
+ match_s[p] = match_e[p] = 0;
+ p = 0;
+
+ if (*pat == '^') {
+ pat++;
+ mbeg = 1; /* anchor match at line start */
+ }
+ if (*pat == '&' || *pat == '$')
+ mbeg = - mbeg - 1; /* pattern starts with '&' or '$' */
+
+ while (pat && *pat) {
+ if (((c=*pat) == '&' || c == '$')) {
+ /* &x matches a string */
+ /* $x matches a single word */
+ tmp = pat + 1;
+ if (isdigit(*tmp)) {
+ p = 0;
+ while (isdigit(*tmp) && p < NUMPARAM) {
+ p *= 10;
+ p += *tmp++ - '0';
+ }
+ if (p <= 0 || p >= NUMPARAM) {
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ prm = p;
+ pat = tmp;
+ if (c == '$')
+ mword = 1;
+ } else {
+ PRINTF("#error: bad action pattern `%s\'\n#missing digit after `%s\'\n",
+ realpat, pat);
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ }
+
+ npat = first_valid(pat, '&');
+ npat2 = first_valid(pat, '$');
+ if (npat2 < npat) npat = npat2;
+ if (!*npat) npat = 0;
+
+ if (npat) {
+ my_strncpy(mpat, pat, npat-pat);
+ /* mpat[npat - pat] = 0; */
+ } else
+ strcpy(mpat, pat);
+
+ if (*mpat) {
+ nsrc = strstr(src, mpat);
+ if (!nsrc) {
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ if (mbeg > 0) {
+ if (nsrc != src) {
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ mbeg = 0; /* reset mbeg to stop further start match */
+ }
+ if (prm != -1) {
+ match_s[prm] = src - line;
+ match_e[prm] = nsrc - line;
+ }
+ } else if (prm != -1) {
+ /* end of pattern space */
+ match_s[prm] = src - line;
+ match_e[prm] = strlen(line);
+ }
+
+ /* post-processing of param */
+ if (prm != -1 && match_e[prm] && mword) {
+ if (mbeg == -1) {
+ /* unanchored '$' start, take last word */
+ if ((tmp = memrchrs(line + match_s[prm],
+ match_e[prm] - match_s[prm],
+ DELIM, DELIM_LEN))) {
+ match_s[prm] = tmp - line + 1;
+ }
+ } else if (!*pat) {
+ /* '$' at end of pattern, take first word */
+ if ((tmp = memchrs(line + match_s[prm],
+ match_e[prm] - match_s[prm],
+ DELIM, DELIM_LEN)))
+ match_e[prm] = tmp - line;
+ } else {
+ /* match only if param is single-worded */
+ if (memchrs(line + match_s[prm],
+ match_e[prm] - match_s[prm],
+ DELIM, DELIM_LEN)) {
+ DROP_PTR(pbuf);
+ return 0;
+ }
+ }
+ }
+ if (prm != -1 && match_e[prm])
+ mbeg = mword = 0; /* reset match flags */
+ src = nsrc + strlen(mpat);
+ pat = npat;
+ }
+ DROP_PTR(pbuf);
+
+ match_s[0] = 0; match_e[0] = strlen(line);
+ return 1;
+}
+
+/*
+ * Search for #actions or #prompts to trigger on an input line.
+ * The line can't be trashed since we want to print it on the screen later.
+ * Return 1 if line matched to some #action, 0 otherwise
+ *
+ * Optionally clear the input line before running the trigger command.
+ */
+static int search_action_or_prompt __P3 (char *,line, char,clearline, char,onprompt)
+{
+ /*
+ * we need actionnode and promptnode to be the same `triggernode' type
+ */
+ triggernode *p;
+ int ret = 0;
+ int match_s[NUMPARAM], match_e[NUMPARAM];
+
+ for (p = onprompt ? prompts : actions; p; p = p->next) {
+#ifdef USE_REGEXP
+ if (p->active &&
+ ((p->type == ACTION_WEAK && match_weak_action(p->pattern, line, match_s, match_e))
+ || (p->type == ACTION_REGEXP && match_regexp_action(p->regexp, line, match_s, match_e))
+ ))
+#else
+ if (p->active &&
+ ((p->type == ACTION_WEAK && match_weak_action(p->pattern, line, match_s, match_e))
+ ))
+#endif
+ {
+ push_params(); if (error) return 0;
+ ret = 1; error = 0;
+ set_params(line, match_s, match_e); if (error) return 0;
+ if (onprompt == 2) {
+ prompt->str = ptrmcpy(prompt->str, line, strlen(line));
+ if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return 0; }
+ }
+ if (clearline)
+ clear_input_line(1);
+ parse_instruction(p->command, 0, 1, 1);
+ history_done = 0;
+ if (error!=DYN_STACK_UND_ERROR && error!=DYN_STACK_OV_ERROR)
+ pop_params();
+ break;
+ }
+ }
+ if (error) return 0;
+ return ret;
+}
+
+/*
+ * read terminal input and send to parser.
+ * decode keys that send escape sequences
+ */
+static void get_user_input __P0 (void)
+{
+ int i, j, chunk = 1;
+ static char buf[BUFSIZE+1]; /* allow for terminating \0 */
+ char *c = buf;
+ static char typed[CAPLEN]; /* chars typed so far (with partial match) */
+ static int nchars = 0; /* number of them */
+
+ /* We have 4 possible line modes:
+ * line mode, local echo: line editing functions in effect
+ * line mode, no echo: sometimes used for passwords, no line editing
+ * char mode, no echo: send a character directly, no local processing
+ * char mode, local echo: extremely rare, do as above.
+ */
+ if (!(linemode & (LM_NOECHO | LM_CHAR))) /* line mode, local echo */
+ chunk = BUFSIZE;
+
+ while ((j = tty_read(tty_read_fd, c, chunk)) < 0 && errno == EINTR)
+ ;
+ if (j <= 0 || (chunk == 1 && j != chunk))
+ syserr("read from tty");
+
+ c[chunk] = '\0';
+
+ if (linemode & LM_CHAR) {
+ /* char mode. chunk == 1 */
+ while ((i = write(tcp_fd, c, 1)) < 0 && errno == EINTR)
+ ;
+ if (i != 1)
+ syserr("write to socket");
+ if (!(linemode & LM_NOECHO))
+ tty_putc(*c);
+ last_edit_cmd = (function_any)0;
+ } else if (linemode & LM_NOECHO) {
+ /* sending password (line mode, no echo). chunk == 1 */
+ if ((*c != '\n' && *c != '\r') && edlen < BUFSIZE - 2)
+ edbuf[edlen++] = *c;
+ else {
+ edbuf[edlen] = '\0';
+#ifdef BUG_ANSI
+ if (edattrbg)
+ tty_printf("%s\n", edattrend);
+ else
+#endif
+ tty_putc('\n');
+
+ tcp_write(tcp_fd, edbuf);
+ edlen = 0;
+ typed[nchars = 0] = 0;
+ }
+ edbuf[pos = edlen] = '\0';
+ last_edit_cmd = (function_any)0;
+ } else {
+ /* normal mode (line mode, echo). chunk == BUFSIZE */
+ int done = 0;
+ keynode *p;
+
+ for (; j > 0; c++, j--) {
+
+ /* search function key strings for match */
+ /* GH: support for \0 in sequence */
+ done = 0;
+ typed[nchars++] = *c;
+
+ while (!done) {
+ done = 1;
+ /*
+ * shortcut:
+ * an initial single ASCII char cannot match any #bind
+ */
+ if (nchars == 1 && *c >= ' ' && *c <= '~')
+ p = NULL;
+ else {
+ for (p = keydefs; (p && (p->seqlen < nchars ||
+ memcmp(typed, p->sequence, nchars)));
+ p = p->next)
+ ;
+ }
+
+ if (!p) {
+ /*
+ * GH: type the first character and keep processing
+ * the rest in the input buffer
+ */
+ i = 1;
+ last_edit_cmd = (function_any)0;
+ insert_char(typed[0]);
+ while (i < nchars) {
+ typed[i - 1] = typed[i];
+ i++;
+ }
+ if (--nchars)
+ done = 0;
+ } else if (p->seqlen == nchars) {
+ if (flashback)
+ putbackcursor();
+ p->funct(p->call_data);
+ last_edit_cmd = (function_any)p->funct; /* GH: keep track of last command */
+ nchars = 0;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * split str into words separated by DELIM, and place in
+ * VAR[1].str ... VAR[9].str -
+ * the whole str is put in VAR[0].str
+ */
+static char *split_words __P1 (char *,str)
+{
+ int i;
+ char *end;
+ ptr *prm;
+
+ *VAR[0].str = ptrmcpy(*VAR[0].str, str, strlen(str));
+ for (i = 1; i < NUMPARAM; i++) {
+ *VAR[i].num = 0;
+ prm = VAR[i].str;
+ /* skip multiple span of DELIM */
+ while (*str && strchr(DELIM, *str))
+ str++;
+ end = str;
+ while (*end && !strchr(DELIM, *end))
+ end++;
+ *prm = ptrmcpy(*prm, str, end-str);
+ str = end;
+ if (MEM_ERROR) { print_error(error); return NULL; }
+ }
+ return str;
+}
+
+/*
+ * free the whole stack and reset it to empty
+ */
+static void free_allparams __P0 (void)
+{
+ int i,j;
+
+ paramstk.curr = 0; /* reset stack to empty */
+
+ for (i=1; i<MAX_STACK; i++) {
+ for (j=0; j<NUMPARAM; j++) {
+ ptrdel(paramstk.p[i][j].str);
+ paramstk.p[i][j].str = (ptr)0;
+ }
+ }
+ for (j=0; j<NUMPARAM; j++) {
+ VAR[j].num = &paramstk.p[0][j].num;
+ VAR[j].str = &paramstk.p[0][j].str;
+ }
+}
+
+void push_params __P0 (void)
+{
+ int i;
+
+ if (paramstk.curr < MAX_STACK - 1)
+ paramstk.curr++;
+ else {
+ print_error(error=DYN_STACK_OV_ERROR);
+ free_allparams();
+ return;
+ }
+ for (i=0; i<NUMPARAM; i++) {
+ *(VAR[i].num = &paramstk.p[paramstk.curr][i].num) = 0;
+ ptrzero(*(VAR[i].str = &paramstk.p[paramstk.curr][i].str));
+ }
+}
+
+void pop_params __P0 (void)
+{
+ int i;
+
+ if (paramstk.curr > 0)
+ paramstk.curr--;
+ else {
+ print_error(error=DYN_STACK_UND_ERROR);
+ free_allparams();
+ return;
+ }
+ for (i=0; i<NUMPARAM; i++) {
+ ptrdel(*VAR[i].str); *VAR[i].str = (ptr)0;
+ VAR[i].num = &paramstk.p[paramstk.curr][i].num;
+ VAR[i].str = &paramstk.p[paramstk.curr][i].str;
+ }
+}
+
+static void set_params __P3 (char *,line, int *,match_s, int *,match_e)
+{
+ int i;
+
+ for (i=0; i<NUMPARAM; i++) {
+ *VAR[i].num = 0;
+ if (match_e[i] > match_s[i]) {
+ *VAR[i].str = ptrmcpy(*VAR[i].str, line + match_s[i],
+ match_e[i] - match_s[i]);
+ if (MEM_ERROR) {
+ print_error(error);
+ return;
+ }
+ } else
+ ptrzero(*VAR[i].str);
+ }
+}
+
+char *get_next_instr __P1 (char *,p)
+{
+ int count, is_if;
+ char *sep, *q;
+
+ p = skipspace(p);
+
+ if (!*p)
+ return p;
+
+ count = is_if = !strncmp(p, "#if", 3);
+
+ do {
+ sep = first_regular(p, CMDSEP);
+
+ q = p;
+ if (*q) do {
+ if (*q == '#') q++;
+
+ q = first_regular(q, '#');
+ } while (*q && strncmp(q, "#if", 3));
+
+ if (sep<=q) {
+ if (*(p = sep))
+ p++;
+ }
+ else
+ if (*q)
+ p = get_next_instr(q);
+ else {
+ print_error(error=SYNTAX_ERROR);
+ return NULL;
+ }
+ sep = skipspace(p);
+ } while (*p && count-- &&
+ (!is_if || (!strncmp(sep, "#else", 5) &&
+ (*(p = sep + 5)))));
+
+ return p;
+}
+
+static void send_line __P2 (char *,line, char,silent)
+{
+ if (!silent && echo_ext) { PRINTF("[%s]\n", line); }
+ tcp_write(tcp_fd, line);
+}
+
+
+/*
+ * Parse and exec the first instruction in 'line', and return pointer to the
+ * second instruction in 'line' (if any).
+ */
+char *parse_instruction __P4 (char *,line, char,silent, char,subs, char,jit_subs)
+{
+ aliasnode *np;
+ char *buf, *arg, *end, *ret;
+ char last_is_sep = 0;
+ int len, copied = 0, otcp_fd = -1;
+ ptr p1 = (ptr)0, p2 = (ptr)0;
+ ptr *pbuf, *pbusy, *tmp;
+
+ if (error) return NULL;
+
+ ret = get_next_instr(line);
+
+ if (!ret || ret==line) /* error or empty instruction, bail out */
+ return ret;
+
+ /*
+ * remove the optional ';' after an instruction,
+ * to have an usable string, ending with \0.
+ * If it is escaped, DON'T remove it: it is not a separator,
+ * and the instruction must already end with \0, or we would not be here.
+ */
+ if (ret[-1] == CMDSEP) {
+ /* instruction is not empty, ret[-1] is allowed */
+ if (ret > line + 1 && ret[-2] == ESC) {
+ /* ';' is escaped */
+ } else {
+ *--ret = '\0';
+ last_is_sep = 1;
+ }
+ }
+
+ /*
+ * using two buffers, p1 and p2, for four strings:
+ * result of subs_param, result of jit_subst_vars, result of
+ * unescape and first word of line.
+ *
+ * So care is required to avoid clashes.
+ */
+ TAKE_PTR(pbuf, p1);
+ TAKE_PTR(pbusy, p2);
+
+ if (subs && subst_param(pbuf, line)) {
+ line = *pbuf ? ptrdata(*pbuf) : "";
+ SWAP2(pbusy, pbuf, tmp);
+ copied = 1;
+ }
+ if (jit_subs && jit_subst_vars(pbuf, line)) {
+ line = *pbuf ? ptrdata(*pbuf) : "";
+ SWAP2(pbusy, pbuf, tmp);
+ copied = 1;
+ }
+ if (!copied) {
+ *pbuf = ptrmcpy(*pbuf, line, strlen(line));
+ line = *pbuf ? ptrdata(*pbuf) : "";
+ SWAP2(pbusy, pbuf, tmp);
+ }
+ if (subs || jit_subs)
+ unescape(line);
+
+ /* now line is in (pbusy) and (pbuf) is available */
+
+ /* restore integrity of original line: must still put it in history */
+ if (last_is_sep)
+ *ret++ = CMDSEP;
+
+ if (REAL_ERROR) {
+ print_error(error);
+ DROP_PTR(pbuf); DROP_PTR(pbusy);
+ return NULL;
+ }
+ /* run-time debugging */
+ if (opt_debug) {
+ PRINTF("#parsing: %s\n", line);
+ }
+
+ if (!*line)
+ send_line(line, silent);
+ else do {
+ arg = skipspace(line);
+
+ if (arg[0] == '#' && arg[1] == '#') { /* send to other connection */
+ *pbuf = ptrsetlen(*pbuf, len = strlen(arg));
+ if (REAL_ERROR) { print_error(error); break; }
+ buf = ptrdata(*pbuf);
+ line = split_first_word(buf, len+1, arg + 2);
+ /* now (pbuf) is used too: first word of line */
+ /* line contains the rest */
+ otcp_fd = tcp_fd;
+ if ((tcp_fd = tcp_find(buf))<0) {
+ error = OUT_RANGE_ERROR;
+ PRINTF("#no connection named `%s'\n", buf);
+ break;
+ }
+ arg = skipspace(line);
+ if (!*arg) {
+ if (CONN_LIST(tcp_fd).flags & SPAWN) {
+ error = OUT_RANGE_ERROR;
+ PRINTF("#only MUD connections can be default ones!\n");
+ } else {
+ /* set it as main connection */
+ tcp_set_main(tcp_fd);
+ otcp_fd = -1;
+ }
+ /* stop parsing, otherwise a newline would be sent to tcp_fd */
+ break;
+ }
+ /* now we can trash (pbuf) */
+ }
+
+ if (*arg == '{') { /* instruction contains a block */
+ end = first_regular(line = arg + 1, '}');
+
+ if (*end) {
+ *end = '\0';
+ parse_user_input(line, silent);
+ *end = '}';
+ } else
+ print_error(error=MISSING_PAREN_ERROR);
+ } else {
+ int oneword;
+ /* initial spaces are NOT skipped this time */
+
+ *pbuf = ptrsetlen(*pbuf, len = strlen(line));
+ if (REAL_ERROR) { print_error(error); break; }
+
+ buf = ptrdata(*pbuf);
+ arg = split_first_word(buf, len+1, line);
+ /* buf contains the first word, arg points to arguments */
+
+ /* now pbuf is used too */
+ if (!*arg) oneword = 1;
+ else oneword = 0;
+
+ if ((np = *lookup_alias(buf))&&np->active) {
+ push_params();
+ if (REAL_ERROR) break;
+
+ split_words(arg); /* split argument into words
+ and place them in $0 ... $9 */
+ parse_instruction(np->subst, 0, 1, 1);
+
+ if (error!=DYN_STACK_UND_ERROR && error!=DYN_STACK_OV_ERROR)
+ pop_params();
+
+ /* now check for internal commands */
+ /* placed here to allow also aliases starting with `#' */
+ } else if (*(end = skipspace(line)) == '#') {
+
+ if (*(end = skipspace(end + 1)) == '(') { /* execute #() */
+ end++;
+ (void)evaln(&end);
+ if (REAL_ERROR) print_error(error);
+ } else
+ parse_commands(buf + 1, arg);
+ /* ok, buf contains skipspace(first word) */
+ } else if (!oneword || !map_walk(buf, silent, 0)) {
+ /* it is ok, map_walk accepts only one word */
+
+ if (!subs && !jit_subs)
+ unescape(line);
+ send_line(line, silent);
+ }
+ }
+ } while (0);
+
+ if (otcp_fd != -1)
+ tcp_fd = otcp_fd;
+ DROP_PTR(pbuf); DROP_PTR(pbusy);
+ return !REAL_ERROR ? ret : NULL;
+}
+
+/*
+ * parse input from user: calls parse_instruction for each instruction
+ * in cmd_line.
+ * silent = 1 if the line should not be echoed, 0 otherwise.
+ */
+void parse_user_input __P2 (char *,line, char,silent)
+{
+ do {
+ line = parse_instruction(line, silent, 0, 0);
+ } while (!error && line && *line);
+}
+
+/*
+ * parse powwow's own commands
+ */
+static void parse_commands __P2 (char *,command, char *,arg)
+{
+ int i, j;
+ cmdstruct *c;
+
+ /* We ALLOW special commands also on subsidiary connections ! */
+
+ /* assume output will be enough to make input line = last screen line */
+ /* line0 = lines - 1; */
+ if (isdigit(*command) && (i = atoi(command))) {
+ if (i >= 0) {
+ while (i--)
+ (void)parse_instruction(arg, 1, 0, 1);
+ } else {
+ PRINTF("#bogus repeat count\n");
+ }
+ } else {
+ j = strlen(command);
+
+ if( j == 0 ) {
+ /* comment */
+ return;
+ }
+
+ for( c = commands; c != NULL; c = c -> next )
+ if (!strncmp(command, c -> name, j)) {
+ if (c -> funct) {
+ (*(c -> funct))(arg);
+ return;
+ }
+ }
+
+ PRINTF("#unknown powwow command `%s'\n", command);
+ }
+}
+
+/*
+ * substitute $0..$9 and @0..@9 in a string
+ * (unless $ or @ is escaped with backslash)
+ *
+ * return 0 if dst not filled. if returned 0 and not error,
+ * there was nothing to substitute.
+ */
+static int subst_param __P2 (ptr *,buf, char *,src)
+{
+ int done, i;
+ char *dst, *tmp, kind;
+
+ if (!strchr(src, '$') && !strchr(src, '@'))
+ return 0;
+
+ i = strlen(src);
+ if (!*buf || ptrlen(*buf) < i) {
+ *buf = ptrsetlen(*buf, i);
+ if (REAL_ERROR)
+ return 0;
+ }
+ dst = ptrdata(*buf);
+
+ while (*src) {
+ while (*src && *src != '$' && *src != '@' && *src != ESC)
+ *dst++ = *src++;
+
+ if (*src == ESC) {
+ while (*src == ESC)
+ *dst++ = *src++;
+
+ if (*src)
+ *dst++ = *src++;
+ }
+
+ done = 0;
+ if (*src == '$' || *src == '@') {
+ kind = *src == '$' ? 1 : 0;
+ tmp = src + 1;
+ if (isdigit(*tmp)) {
+ i = atoi(tmp);
+ while (isdigit(*tmp))
+ tmp++;
+
+ if (i < NUMPARAM) {
+ int max = 0, n;
+ char *data = NULL, buf2[LONGLEN];
+
+ done = 1;
+ src = tmp;
+
+ /* now the actual substitution */
+ if (kind) {
+ if (*VAR[i].str && (data = ptrdata(*VAR[i].str)))
+ max = ptrlen(*VAR[i].str);
+ } else {
+ sprintf(data = buf2, "%ld", *VAR[i].num);
+ max = strlen(buf2);
+ }
+ if (data && max) {
+ n = dst - ptrdata(*buf);
+ *buf = ptrpad(*buf, max);
+ if (REAL_ERROR)
+ return 0;
+ dst = ptrdata(*buf) + n;
+ memcpy(dst, data, max);
+ dst += max;
+ }
+ }
+ }
+ }
+ if (!done && (*src == '$' || *src == '@'))
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+ return 1;
+}
+
+/*
+ * just-in-time substitution:
+ * substitute ${name}, @{name} and #{expression} in a string
+ * (unless `${', `@{' or `#{' are escaped with backslash)
+ *
+ * return 0 if dst not filled. if returned 0 and not error,
+ * there was nothing to substitute.
+ */
+
+static int jit_subst_vars __P2 (ptr *,buf, char *,src)
+{
+ int i, done, kind;
+ char *tmp, *name, *dst, c;
+ varnode *named_var;
+
+ if (!strstr(src, "${") && !strstr(src, "@{") && !strstr(src, "#{"))
+ return 0;
+
+ i = strlen(src);
+ if (!*buf || ptrlen(*buf) < i) {
+ *buf = ptrsetlen(*buf, i);
+ if (REAL_ERROR)
+ return 0;
+ }
+ dst = ptrdata(*buf);
+
+ while (*src) {
+ while (*src && *src != '$' && *src != '@' && *src != '#' && *src != ESC)
+ *dst++ = *src++;
+
+ if (*src == ESC) {
+ while (*src == ESC)
+ *dst++ = *src++;
+
+ if (*src)
+ *dst++ = *src++;
+ }
+
+ done = 0;
+ if (*src == '$' || *src == '@') {
+ i = 0;
+ kind = *src == '$' ? 1 : 0;
+ tmp = src + 1;
+ if (*tmp == '{') {
+ tmp = skipspace(tmp+1);
+ if (isdigit(*tmp) || *tmp == '-') {
+ /* numbered variable */
+ i = atoi(tmp);
+ if (i >= -NUMVAR && i < NUMPARAM) {
+ if (*tmp == '-')
+ tmp++;
+ while (isdigit(*tmp))
+ tmp++;
+ done = 1;
+ }
+ } else if (isalpha(*tmp) || *tmp == '_') {
+ /* named variable */
+ name = tmp++;
+ while (isalnum(*tmp) || *tmp == '_')
+ tmp++;
+ c = *tmp;
+ *tmp = '\0';
+ named_var = *lookup_varnode(name, kind);
+ *tmp = c;
+ if (named_var) {
+ i = named_var->index;
+ done = 1;
+ }
+ }
+ tmp = skipspace(tmp);
+ if (done) {
+ int max = 0, n;
+ char *data = NULL, buf2[LONGLEN];
+
+ src = tmp + 1; /* skip the '}' */
+
+ /* now the actual substitution */
+ if (kind == 1) {
+ if (*VAR[i].str && (data = ptrdata(*VAR[i].str)))
+ max = ptrlen(*VAR[i].str);
+ } else {
+ sprintf(data = buf2, "%ld", *VAR[i].num);
+ max = strlen(buf2);
+ }
+ if (data && max) {
+ n = dst - ptrdata(*buf);
+ *buf = ptrpad(*buf, max);
+ if (REAL_ERROR)
+ return 0;
+ dst = ptrdata(*buf) + n;
+ memcpy(dst, data, max);
+ dst += max;
+ }
+ } else if (*tmp == '}')
+ /* met an undefined variable, consider empty */
+ src = tmp + 1;
+
+ /* else syntax error, do nothing */
+ }
+ } else if (src[0] == '#' && src[1] == '{') {
+ int max, n;
+ ptr pbuf = (ptr)0;
+
+ src += 2;
+ (void)evalp(&pbuf, &src);
+ if (REAL_ERROR) {
+ ptrdel(pbuf);
+ return 0;
+ }
+ if (pbuf) {
+ max = ptrlen(pbuf);
+ n = dst - ptrdata(*buf);
+ *buf = ptrpad(*buf, max);
+ if (REAL_ERROR) {
+ ptrdel(pbuf);
+ return 0;
+ }
+ dst = ptrdata(*buf) + n;
+ memcpy(dst, ptrdata(pbuf), max);
+ dst += max;
+ }
+ ptrdel(pbuf);
+
+ if (*src)
+ src = skipspace(src);
+ if (*src != '}') {
+ PRINTF("#{}: ");
+ print_error(error=MISSING_PAREN_ERROR);
+ return 0;
+ }
+ done = 1;
+ if (*src)
+ src++;
+ }
+
+ if (!done && (*src == '$' || *src == '@' || *src == '#'))
+ /* not matched, just copy */
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+ ptrtrunc(*buf, dst - ptrdata(*buf));
+ return 1;
+}
+
+/*
+ * set definition file:
+ * rules: if powwow_dir is set it is searched first.
+ * if file doesn't exist, it is created there.
+ * If a slash appears in the name, the powwow_dir isn't used.
+ */
+void set_deffile __P1 (char *,arg)
+{
+ if (!strchr(arg, '/') && *powwow_dir) {
+ strcpy(deffile, powwow_dir);
+ strcat(deffile, arg);
+ if ((access(deffile, R_OK) == -1 || access(deffile, W_OK) == -1)
+ && !access(arg,R_OK) && !access(arg, W_OK))
+ strcpy(deffile, arg);
+ } else
+ strcpy(deffile, arg);
+}
+
+/*
+ * GH: return true if var is one of the permanent variables
+ */
+int is_permanent_variable __P1 (varnode *,v)
+{
+ return (v == prompt || v == last_line);
+}