From 63a6288435c35cd628abd530e0dc3bf84168c2e0 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 27 Dec 2015 15:32:00 +0100 Subject: [PATCH 01/30] client: Fix integer wrap around in ring_peek() Peeking beyond either end of the ring buffer must return EOF. We first compute the index, then check whether it's in range. Unfortunately, the index computation r->prod - -n can wrap around while r->prod is still <= RING_SIZE. If it happens, ring_peek() returns r->buf[(r->prod - -n) % RING_SIZE] instead of EOF. Currently harmless, because no caller peeks out of range. Signed-off-by: Markus Armbruster --- src/client/ringbuf.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/client/ringbuf.c b/src/client/ringbuf.c index c65ff211..1fb6bff6 100644 --- a/src/client/ringbuf.c +++ b/src/client/ringbuf.c @@ -27,7 +27,7 @@ * ringbuf.c: Simple ring buffer * * Known contributors to this file: - * Markus Armbruster, 2007-2009 + * Markus Armbruster, 2007-2015 */ #include @@ -82,9 +82,17 @@ ring_peek(struct ring *r, int n) assert(-RING_SIZE - 1 <= n && n <= RING_SIZE); - idx = n >= 0 ? r->cons + n : r->prod - -n; - if (idx < r->cons && idx >= r->prod) - return EOF; + if (n >= 0) { + idx = r->cons + n; + if (idx >= r->prod) + return EOF; + } else { + /* Beware, r->prod - -n can wrap around, avoid that */ + if (r->prod < r->cons + -n) + return EOF; + idx = r->prod - -n; + } + return r->buf[idx % RING_SIZE]; } From 8fe0221634b69e3caf22495bb440772830e12630 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 27 Dec 2015 15:37:06 +0100 Subject: [PATCH 02/30] client: Drop extra newlines from the client's messages servercmd()'s argument arg ends with a newline already. Broken in commit 8b7d0b9, v4.3.11. Signed-off-by: Markus Armbruster --- src/client/servcmd.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client/servcmd.c b/src/client/servcmd.c index 0c33758f..b8264651 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -30,7 +30,7 @@ * Dave Pare, 1989 * Steve McClure, 1998 * Ron Koenderink, 2005 - * Markus Armbruster, 2005-2010 + * Markus Armbruster, 2005-2015 */ #include @@ -69,7 +69,7 @@ servercmd(int code, char *arg, int len) switch (code) { case C_PROMPT: if (sscanf(arg, "%d %d", &nmin, &nbtu) != 2) { - fprintf(stderr, "prompt: bad server prompt %s\n", arg); + fprintf(stderr, "prompt: bad server prompt %s", arg); } snprintf(the_prompt, sizeof(the_prompt), "[%d:%d] Command : ", nmin, nbtu); @@ -179,7 +179,7 @@ redir_authorized(char *arg, char *attempt, int expected) } if (!seen || (input_to_forget && input_to_forget != seen)) { - fprintf(stderr, "WARNING! Server attempted to %s %s\n", + fprintf(stderr, "WARNING! Server attempted to %s %s", attempt, arg); return 0; } @@ -241,6 +241,9 @@ dopipe(char *p) return; } + /* strip newline */ + p[strlen(p) - 1] = 0; + redir_is_pipe = 1; if ((redir_fp = popen(p, "w")) == NULL) { fprintf(stderr, "Can't redirect to pipe %s: %s\n", From a2f6ea968c3f5a84d0bd5aef7f15aa39b088db18 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 27 Dec 2015 15:54:10 +0100 Subject: [PATCH 03/30] client: Improve the client's messages Use a "Warning: " prefix for server output violating the protocol and for rogue redirections and executes. Don't shout "WARNING!" In redir_authorized(), check for server issues (conflicting redirections, rogue redirections and executes) before enforcing restrictions (restricted mode, executing batch file), so server issues aren't masked. Surprisingly, popen() may not set errno on failure. Avoid reporting a bogus errno in dopipe(). doexecute() complains about an "execute file". We call that a "batch file" elsewhere. Reword for consistency. Signed-off-by: Markus Armbruster --- src/client/servcmd.c | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/client/servcmd.c b/src/client/servcmd.c index b8264651..5bd4fff6 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -69,7 +69,8 @@ servercmd(int code, char *arg, int len) switch (code) { case C_PROMPT: if (sscanf(arg, "%d %d", &nmin, &nbtu) != 2) { - fprintf(stderr, "prompt: bad server prompt %s", arg); + fprintf(stderr, "Warning: server sent malformed prompt %s", + arg); } snprintf(the_prompt, sizeof(the_prompt), "[%d:%d] Command : ", nmin, nbtu); @@ -162,6 +163,19 @@ redir_authorized(char *arg, char *attempt, int expected) { size_t seen = seen_input(arg); + if (!expected) { + fprintf(stderr, "Warning: dropped conflicting %s %s", + attempt, arg); + return 0; + } + + if (!seen || (input_to_forget && input_to_forget != seen)) { + fprintf(stderr, "Warning: server attempted to %s %s", + attempt, arg); + return 0; + } + input_to_forget = seen; + if (restricted) { fprintf(stderr, "Can't %s in restricted mode\n", attempt); return 0; @@ -171,19 +185,6 @@ redir_authorized(char *arg, char *attempt, int expected) fprintf(stderr, "Can't %s in a batch file\n", attempt); return 0; } - - if (!expected) { - fprintf(stderr, "WARNING! Server attempted to %s unexpectedly\n", - attempt); - return 0; - } - - if (!seen || (input_to_forget && input_to_forget != seen)) { - fprintf(stderr, "WARNING! Server attempted to %s %s", - attempt, arg); - return 0; - } - input_to_forget = seen; return 1; } @@ -196,7 +197,7 @@ doredir(char *p) if (!redir_authorized(p, "redirect to file", !redir_fp)) return; if (*p++ != '>') { - fprintf(stderr, "WARNING! Weird redirection %s", p); + fprintf(stderr, "Warning: dropped weird redirection %s", p); return; } @@ -231,7 +232,7 @@ dopipe(char *p) if (!redir_authorized(p, "pipe to shell command", !redir_fp)) return; if (*p++ != '|') { - fprintf(stderr, "WARNING! Weird pipe %s", p); + fprintf(stderr, "Warning: dropped weird pipe %s", p); return; } @@ -245,9 +246,10 @@ dopipe(char *p) p[strlen(p) - 1] = 0; redir_is_pipe = 1; + errno = 0; if ((redir_fp = popen(p, "w")) == NULL) { - fprintf(stderr, "Can't redirect to pipe %s: %s\n", - p, strerror(errno)); + fprintf(stderr, "Can't redirect to pipe %s%s%s\n", + p, errno ? ": " : "", errno ? strerror(errno) : ""); } } @@ -266,7 +268,7 @@ doexecute(char *p) } if ((fd = open(p, O_RDONLY)) < 0) { - fprintf(stderr, "Can't open execute file %s: %s\n", + fprintf(stderr, "Can't open batch file %s: %s\n", p, strerror(errno)); return -1; } From d13950470a470d9c8fb119471d0ea862592f3220 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 27 Dec 2015 18:10:13 +0100 Subject: [PATCH 04/30] client: Simplify rogue redirection and execute protection Redirections let the server write files and run pipelines, and execute lets it read files. Before 4.2.0, the client simply trusted the server. 4.2.0 added fairly complex code to recognize redirections and execute, replace the filenames and pipelines by tag strings, remember tag string and replaced text, and honor redirection and execute only when their text is a known tag string. Tag and replaced text were freed on use. Broken by design because the client cannot know whether a line will actually be read as a command by the server. Issues included: (1) Non-command lines could be messed up. (2) The memory used for remembering their tags was never freed. (3) execute prompting for its argument was incorrectly rejected. (4) A rogue server could use a tag for the wrong purpose. For instance, "execute fire" creates a tag for "fire", which a rogue server could use for a pipeline to command "ire". 4.2.10 dropped the tag strings, and used the actual text as key. This took care of (1). Commit 17d6997 and commit 2456a71 (both v4.3.11) tightened checking of redirections, which took care of (4) for redirections, but not execute. Relatively harmless, because redirection text always starts with '>' or '|', but filenames rarely do. Commit 8b7d0b9 (v4.3.11) replaced the protection code wholesale. Instead of attempting to recognize redirections and execute, we now save everything in a ring buffer, and require redirections and execute to match at a line end in the ring buffer. Much simpler, takes care of issues (2) and (3), but adds new issues: (5) When sent-ahead input exceeds the ring buffer, good redirections and executes get rejected. Could be avoided by limiting send-ahead, or remembering input until its output arrives. However, bogus rejections haven't been a problem in practice even with a tiny 4KiB ring buffer. (6) The protection against rogue execute is *much* weaker, because we now accept any line suffix. Before, we accepted any tag, i.e. anything that looks like a redirection or an execute command. (7) When we find a match in the ring buffer, we used to drop everything up to that line right away. This broke redirected execute commands. Commit 02a9af0 (v4.3.11) fixed it by delaying the drop until the next prompt, but that's overly complicated. This commit addresses (7): don't drop on use, simply let new input push old input out of the ring buffer. The next commit will address (6) and the remainder of (4). Signed-off-by: Markus Armbruster --- src/client/secure.c | 41 ++++------------------------------------- src/client/secure.h | 5 ++--- src/client/servcmd.c | 10 +--------- 3 files changed, 7 insertions(+), 49 deletions(-) diff --git a/src/client/secure.c b/src/client/secure.c index 9760b16c..401eaefd 100644 --- a/src/client/secure.c +++ b/src/client/secure.c @@ -27,7 +27,7 @@ * secure.c: Check redir etc. to protect against tampering deity * * Known contributors to this file: - * Markus Armbruster, 2007 + * Markus Armbruster, 2007-2015 */ #include @@ -38,15 +38,12 @@ #include "secure.h" static struct ring recent_input; -static size_t saved_bytes; /* * Remember line of input @inp for a while. * It must end with a newline. - * Return value is suitable for forget_input(): it makes it forget all - * input up to and including this line. */ -size_t +void save_input(char *inp) { size_t len = strlen(inp); @@ -59,47 +56,17 @@ save_input(char *inp) assert(eol >= 0); ring_discard(&recent_input, eol + 1); } - saved_bytes += len; - return saved_bytes; } /* * Can you still remember a line of input that ends with @tail? * It must end with a newline. - * Return non-zero iff @tail can be remembered. - * Passing that value to forget_input() will forget all input up to - * and including this line. */ -size_t +int seen_input(char *tail) { size_t len = strlen(tail); - size_t remembered = ring_len(&recent_input); - int dist; assert(len && tail[len - 1] == '\n'); - - dist = ring_search(&recent_input, tail); - if (dist < 0) - return 0; - - assert(dist + len <= remembered && remembered <= saved_bytes); - return saved_bytes - remembered + dist + len; -} - -/* - * Forget remembered input up to @seen. - * @seen should be obtained from save_input() or seen_input(). - */ -void -forget_input(size_t seen) -{ - size_t forgotten = saved_bytes - ring_len(&recent_input); - - assert(seen); - - if (seen > forgotten) { - assert(ring_peek(&recent_input, seen - forgotten - 1) == '\n'); - ring_discard(&recent_input, seen - forgotten); - } + return ring_search(&recent_input, tail) >= 0; } diff --git a/src/client/secure.h b/src/client/secure.h index 98322dd2..301a6846 100644 --- a/src/client/secure.h +++ b/src/client/secure.h @@ -35,8 +35,7 @@ #include -extern size_t save_input(char *); -extern size_t seen_input(char *); -extern void forget_input(size_t); +extern void save_input(char *); +extern int seen_input(char *); #endif diff --git a/src/client/servcmd.c b/src/client/servcmd.c index 5bd4fff6..65d029c7 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -52,7 +52,6 @@ int restricted; static FILE *redir_fp; static int redir_is_pipe; static int executing; -static size_t input_to_forget; static void prompt(int, char *, char *); static void doredir(char *p); @@ -81,10 +80,6 @@ servercmd(int code, char *arg, int len) (void)fclose(redir_fp); redir_fp = NULL; } - if (input_to_forget) { - forget_input(input_to_forget); - input_to_forget = 0; - } prompt(code, the_prompt, teles); executing = 0; break; @@ -161,20 +156,17 @@ fname(char *s) static int redir_authorized(char *arg, char *attempt, int expected) { - size_t seen = seen_input(arg); - if (!expected) { fprintf(stderr, "Warning: dropped conflicting %s %s", attempt, arg); return 0; } - if (!seen || (input_to_forget && input_to_forget != seen)) { + if (!seen_input(arg)) { fprintf(stderr, "Warning: server attempted to %s %s", attempt, arg); return 0; } - input_to_forget = seen; if (restricted) { fprintf(stderr, "Can't %s in restricted mode\n", attempt); From 5cb14f508ebf78a9e9bce29be7c2fe462fb73b91 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 27 Dec 2015 20:22:45 +0100 Subject: [PATCH 05/30] client: Fix rogue execute protection To protect against a rogue server reading your files, the client honors C_EXECUTE only when it matches recent player input. This has a somewhat troubled history, detailed in the previous commit. The remaining major issue comes from commit 8b7d0b9 (v4.3.11): any suffix of a recent line of input is accepted as C_EXECUTE text. Before, only text that looked like an argument of an execute command or a redirection was accepted. Fix by again requiring the text to be preceded by something that looks like an execute command. But do it more carefully: don't break execute with a prompted for argument, and prevent abuse of redirections for execute. Signed-off-by: Markus Armbruster --- src/client/ringbuf.c | 9 ++++---- src/client/ringbuf.h | 4 ++-- src/client/secure.c | 43 +++++++++++++++++++++++++++++++++++-- src/client/secure.h | 1 + src/client/servcmd.c | 50 ++++++++++++++++++++++++++++++-------------- 5 files changed, 83 insertions(+), 24 deletions(-) diff --git a/src/client/ringbuf.c b/src/client/ringbuf.c index 1fb6bff6..82e772dc 100644 --- a/src/client/ringbuf.c +++ b/src/client/ringbuf.c @@ -157,16 +157,17 @@ ring_discard(struct ring *r, int n) /* * Search the ring buffer for zero-terminated string S. - * If found, return a non-negative value referring to the beginning of - * S in the buffer when passed to ring_peek(). Else return -1. + * Start at the @(n+1)-th byte to be gotten. + * If found, return the number of bytes in the buffer before S. + * Else return -1. */ int -ring_search(struct ring *r, char *s) +ring_search(struct ring *r, char *s, int n) { size_t len = strlen(s); size_t i, j; - for (i = r->cons; i + len <= r->prod; i++) { + for (i = r->cons + n; i + len <= r->prod; i++) { for (j = 0; s[j] && s[j] == (char)r->buf[(i + j) % RING_SIZE]; j++) ; if (!s[j]) diff --git a/src/client/ringbuf.h b/src/client/ringbuf.h index d2616356..4d44a2b9 100644 --- a/src/client/ringbuf.h +++ b/src/client/ringbuf.h @@ -27,7 +27,7 @@ * ringbuf.h: Simple ring buffer * * Known contributors to this file: - * Markus Armbruster, 2007 + * Markus Armbruster, 2007-2015 */ #ifndef RINGBUF_H @@ -59,7 +59,7 @@ extern int ring_getc(struct ring *); extern int ring_putc(struct ring *, unsigned char); extern int ring_putm(struct ring *, void *, size_t); extern void ring_discard(struct ring *, int); -extern int ring_search(struct ring *, char *); +extern int ring_search(struct ring *, char *, int); extern int ring_from_file(struct ring *, int fd); extern int ring_to_file(struct ring *, int fd); diff --git a/src/client/secure.c b/src/client/secure.c index 401eaefd..330260f7 100644 --- a/src/client/secure.c +++ b/src/client/secure.c @@ -33,6 +33,8 @@ #include #include +#include +#include #include #include "ringbuf.h" #include "secure.h" @@ -52,7 +54,7 @@ save_input(char *inp) assert(len && inp[len - 1] == '\n'); while (ring_putm(&recent_input, inp, len) < 0) { - eol = ring_search(&recent_input, "\n"); + eol = ring_search(&recent_input, "\n", 0); assert(eol >= 0); ring_discard(&recent_input, eol + 1); } @@ -68,5 +70,42 @@ seen_input(char *tail) size_t len = strlen(tail); assert(len && tail[len - 1] == '\n'); - return ring_search(&recent_input, tail) >= 0; + return ring_search(&recent_input, tail, 0) >= 0; +} + +/* + * Can you still remember input that looks like an execute @arg? + * @arg must end with a newline. + */ +int +seen_exec_input(char *arg) +{ + size_t len = strlen(arg); + int n, i, j, ch; + unsigned char buf[RING_SIZE + 1]; + + assert(len && arg[len - 1] == '\n'); + + n = 1; + for (;;) { + /* find next line ending with arg */ + n = ring_search(&recent_input, arg, n + 1); + if (n <= 0) + return 0; + + /* extract command (same or previous line) */ + i = n - 1; + if (ring_peek(&recent_input, i) == '\n') + i--; + j = sizeof(buf); + buf[--j] = 0; + for (; i >= 0 && (ch = ring_peek(&recent_input, i)) != '\n'; i--) + buf[--j] = ch; + + /* compare command */ + for (; isspace(buf[j]); j++) ; + for (i = j; buf[i] && !isspace(buf[i]); i++) ; + if (i - j >= 2 && !strncmp("execute", (char *)buf + j, i - j)) + return 1; + } } diff --git a/src/client/secure.h b/src/client/secure.h index 301a6846..8e4454f9 100644 --- a/src/client/secure.h +++ b/src/client/secure.h @@ -37,5 +37,6 @@ extern void save_input(char *); extern int seen_input(char *); +extern int seen_exec_input(char *); #endif diff --git a/src/client/servcmd.c b/src/client/servcmd.c index 65d029c7..1bb05cbb 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -154,20 +154,8 @@ fname(char *s) } static int -redir_authorized(char *arg, char *attempt, int expected) +common_authorized(char *arg, char *attempt) { - if (!expected) { - fprintf(stderr, "Warning: dropped conflicting %s %s", - attempt, arg); - return 0; - } - - if (!seen_input(arg)) { - fprintf(stderr, "Warning: server attempted to %s %s", - attempt, arg); - return 0; - } - if (restricted) { fprintf(stderr, "Can't %s in restricted mode\n", attempt); return 0; @@ -180,13 +168,43 @@ redir_authorized(char *arg, char *attempt, int expected) return 1; } +static int +redir_authorized(char *arg, char *attempt) +{ + if (redir_fp) { + fprintf(stderr, "Warning: dropped conflicting %s %s", + attempt, arg); + return 0; + } + + if (!seen_input(arg)) { + fprintf(stderr, "Warning: server attempted to %s %s", + attempt, arg); + return 0; + } + + return common_authorized(arg, attempt); +} + +static int +exec_authorized(char *arg) +{ + if (!seen_exec_input(arg)) { + fprintf(stderr, + "Warning: server attempted to execute batch file %s", arg); + return 0; + } + + return common_authorized(arg, "execute batch file"); +} + static void doredir(char *p) { int mode; int fd; - if (!redir_authorized(p, "redirect to file", !redir_fp)) + if (!redir_authorized(p, "redirect to file")) return; if (*p++ != '>') { fprintf(stderr, "Warning: dropped weird redirection %s", p); @@ -221,7 +239,7 @@ doredir(char *p) static void dopipe(char *p) { - if (!redir_authorized(p, "pipe to shell command", !redir_fp)) + if (!redir_authorized(p, "pipe to shell command")) return; if (*p++ != '|') { fprintf(stderr, "Warning: dropped weird pipe %s", p); @@ -250,7 +268,7 @@ doexecute(char *p) { int fd; - if (!redir_authorized(p, "execute batch file", 1)) + if (!exec_authorized(p)) return -1; p = fname(p); From cf7d52fc108c1b1456d5ea3bb7e8cd0ddbb2955f Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 18 Jun 2017 18:57:28 +0200 Subject: [PATCH 06/30] client: Simplify rogue redirection and execute protection further recv_input() passes full lines to save_input(). Pass characters instead. Simpler, and doesn't truncate long lines. Signed-off-by: Markus Armbruster --- src/client/play.c | 15 ++------------- src/client/secure.c | 12 ++++-------- src/client/secure.h | 4 ++-- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/client/play.c b/src/client/play.c index 47e028a8..697fed65 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -404,21 +404,13 @@ recv_output(int sock) static int recv_input(int fd, struct ring *inbuf) { - static struct lbuf cmdbuf; int n, i, ch; - char *line; int res = 1; n = ring_from_file(inbuf, fd); if (n < 0) return -1; if (n == 0) { - /* EOF on input */ - if (lbuf_len(&cmdbuf)) { - /* incomplete line */ - ring_putc(inbuf, '\n'); - n++; - } /* * Can't put EOF cookie into INBUF here, it may not fit. * Leave it to caller. @@ -430,11 +422,8 @@ recv_input(int fd, struct ring *inbuf) for (i = -n; i < 0; i++) { ch = ring_peek(inbuf, i); assert(ch != EOF); - if (ch != '\r' && lbuf_putc(&cmdbuf, ch) > 0) { - line = lbuf_line(&cmdbuf); - save_input(line); - lbuf_init(&cmdbuf); - } + if (ch != '\r') + save_input(ch); if (auxfp) putc(ch, auxfp); } diff --git a/src/client/secure.c b/src/client/secure.c index 330260f7..c2e3cd24 100644 --- a/src/client/secure.c +++ b/src/client/secure.c @@ -27,7 +27,7 @@ * secure.c: Check redir etc. to protect against tampering deity * * Known contributors to this file: - * Markus Armbruster, 2007-2015 + * Markus Armbruster, 2007-2017 */ #include @@ -42,18 +42,14 @@ static struct ring recent_input; /* - * Remember line of input @inp for a while. - * It must end with a newline. + * Remember input @inp for a while. */ void -save_input(char *inp) +save_input(char inp) { - size_t len = strlen(inp); int eol; - assert(len && inp[len - 1] == '\n'); - - while (ring_putm(&recent_input, inp, len) < 0) { + while (ring_putc(&recent_input, inp) < 0) { eol = ring_search(&recent_input, "\n", 0); assert(eol >= 0); ring_discard(&recent_input, eol + 1); diff --git a/src/client/secure.h b/src/client/secure.h index 8e4454f9..c5462f68 100644 --- a/src/client/secure.h +++ b/src/client/secure.h @@ -27,7 +27,7 @@ * secure.h: Check redir etc. to protect against tampering deity * * Known contributors to this file: - * Markus Armbruster, 2007-2009 + * Markus Armbruster, 2007-2017 */ #ifndef SECURE_H @@ -35,7 +35,7 @@ #include -extern void save_input(char *); +extern void save_input(char); extern int seen_input(char *); extern int seen_exec_input(char *); From b6d0f4e3db2406cca6e6237907c8b5d7fa58adfe Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 29 Dec 2015 18:05:56 +0100 Subject: [PATCH 07/30] client: Signal interrupt instead of EOF on batch file error The server doesn't currently care for the difference, but interrupt is more accurate than EOF. The change also enables the next commit. Signed-off-by: Markus Armbruster --- src/client/misc.h | 1 - src/client/play.c | 46 +++++++++++++++++++++++++++++--------------- src/client/servcmd.c | 11 +++++++---- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/client/misc.h b/src/client/misc.h index 1fd9f9bd..3d1ee84f 100644 --- a/src/client/misc.h +++ b/src/client/misc.h @@ -42,7 +42,6 @@ extern char empirehost[]; extern char empireport[]; extern int eight_bit_clean; extern int input_fd; -extern int send_eof; extern FILE *auxfp; extern int restricted; diff --git a/src/client/play.c b/src/client/play.c index 697fed65..93ba6bcb 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -27,7 +27,7 @@ * play.c: Playing the game * * Known contributors to this file: - * Markus Armbruster, 2007-2010 + * Markus Armbruster, 2007-2015 * Ron Koenderink, 2007-2009 */ @@ -304,8 +304,14 @@ w32_ring_from_file_or_bounce_buf(struct ring *r, int fd) #define EOF_COOKIE "ctld\n" #define INTR_COOKIE "aborted\n" +/* + * Player input file descriptor + * 0 while reading interactive input + * >0 while reading a batch file + * <0 during error handling + */ int input_fd; -int send_eof; /* need to send EOF_COOKIE */ + static volatile sig_atomic_t send_intr; /* need to send INTR_COOKIE */ /* @@ -398,7 +404,7 @@ recv_output(int sock) } /* - * Receive command input from @fd into @inbuf. + * Receive player input from @fd into @inbuf. * Return 1 on receipt of input, zero on EOF, -1 on error. */ static int @@ -455,6 +461,7 @@ play(int sock) struct ring inbuf; /* input buffer, draining to SOCK */ int eof_fd0; /* read fd 0 hit EOF? */ int partial_line_sent; /* partial input line sent? */ + int send_eof; /* need to send EOF_COOKIE */ fd_set rdfd, wrfd; int n; @@ -476,10 +483,11 @@ play(int sock) /* * Want to read player input only when we don't need to send - * cookies, and INPUT_FD is still open, and INBUF can accept + * cookies, haven't reached EOF on fd 0, and @inbuf can accept * some. */ - if (!send_intr && !send_eof && input_fd >= 0 && ring_space(&inbuf)) + if (!send_intr && !send_eof && (input_fd || !eof_fd0) + && ring_space(&inbuf)) FD_SET(input_fd, &rdfd); /* Want to send player input only when we have something */ if (send_intr || send_eof || ring_len(&inbuf)) @@ -507,7 +515,7 @@ play(int sock) if (input_fd) { /* execute aborted, switch back to fd 0 */ close(input_fd); - input_fd = eof_fd0 ? -1 : 0; + input_fd = 0; } } @@ -515,23 +523,24 @@ play(int sock) continue; /* read player input */ - if (input_fd >= 0 && FD_ISSET(input_fd, &rdfd)) { + if (FD_ISSET(input_fd, &rdfd)) { n = recv_input(input_fd, &inbuf); - if (n < 0) { - perror("read stdin"); /* FIXME stdin misleading, could be execing */ - n = 0; - } - if (n == 0) { - /* EOF on input */ - send_eof++; + if (n <= 0) { if (input_fd) { /* execute done, switch back to fd 0 */ + if (n < 0) { + perror("read batch file"); + send_intr = 1; + } else + send_eof++; close(input_fd); - input_fd = eof_fd0 ? -1 : 0; + input_fd = 0; } else { /* stop reading input, drain socket ring buffers */ + if (n < 0) + perror("read stdin"); + send_eof++; eof_fd0 = 1; - input_fd = -1; sa.sa_handler = SIG_DFL; sigaction(SIGINT, &sa, NULL); } @@ -557,6 +566,11 @@ play(int sock) } if (n == 0) return 0; + if (input_fd < 0) { + /* execute failed */ + send_intr = 1; + input_fd = 0; + } } } } diff --git a/src/client/servcmd.c b/src/client/servcmd.c index 1bb05cbb..3aab4318 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -41,6 +41,7 @@ #include #include #include +#include #include "misc.h" #include "proto.h" #include "secure.h" @@ -89,12 +90,14 @@ servercmd(int code, char *arg, int len) break; case C_EXECUTE: fd = doexecute(arg); - if (fd < 0) - send_eof++; - else { - input_fd = fd; + if (fd < 0) { + if (input_fd) + close(input_fd); + } else { + assert(!input_fd); executing = 1; } + input_fd = fd; break; case C_EXIT: printf("Exit: %s", arg); From 3135cd39c31cb82714fb8424ba48897cf74af6dd Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 29 Dec 2015 19:26:58 +0100 Subject: [PATCH 08/30] client: Simplify input EOF handling We increment @send_eof only when read() returns zero, and we read() only when it's zero. Therefore, we never increment it beyond one. Change it from counter to flag. This effectively reverts commit 51846ec (v4.3.11). Possible only because the previous commit got rid of the @send_eof increment on failed execute. Signed-off-by: Markus Armbruster --- src/client/play.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/play.c b/src/client/play.c index 93ba6bcb..9881622f 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -508,7 +508,7 @@ play(int sock) partial_line_sent = 0; if (send_eof && !partial_line_sent && ring_putm(&inbuf, EOF_COOKIE, sizeof(EOF_COOKIE) - 1) >= 0) - send_eof--; + send_eof = 0; if (send_intr && !partial_line_sent && ring_putm(&inbuf, INTR_COOKIE, sizeof(INTR_COOKIE) - 1) >= 0) { send_intr = 0; @@ -532,14 +532,14 @@ play(int sock) perror("read batch file"); send_intr = 1; } else - send_eof++; + send_eof = 1; close(input_fd); input_fd = 0; } else { /* stop reading input, drain socket ring buffers */ if (n < 0) perror("read stdin"); - send_eof++; + send_eof = 1; eof_fd0 = 1; sa.sa_handler = SIG_DFL; sigaction(SIGINT, &sa, NULL); From 38097c49868ba81944816a6c99a2fd84fc29e347 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 30 Dec 2015 10:21:18 +0100 Subject: [PATCH 09/30] client: Clear pending interrupt on stdin EOF The client can send an interrupt cookie after the EOF cookie. Harmless, as the server throws away input after the EOF cookie. Clean it up anyway. Signed-off-by: Markus Armbruster --- src/client/play.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/play.c b/src/client/play.c index 9881622f..bc7f629b 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -543,6 +543,7 @@ play(int sock) eof_fd0 = 1; sa.sa_handler = SIG_DFL; sigaction(SIGINT, &sa, NULL); + send_intr = 0; } } else partial_line_sent = ring_peek(&inbuf, -1) != '\n'; From 37e68e5796e156deec7a47427ded2cb7a9ee3270 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sat, 2 Jan 2016 10:11:28 +0100 Subject: [PATCH 10/30] client: Fix obscure misdetection of input EOF recv_input(input_fd, &inbuf) returns zero when @inbuf is full or @input_fd is at EOF. We avoid the former by putting @input_fd in @rdfd only when @inbuf has space, so we can detect EOF easily. But we missed the case where adding a cookie fills up @inbuf. We misinterpret "can't read into full buffer" as "EOF on input" then. Fix by checking for space again. Signed-off-by: Markus Armbruster --- src/client/play.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/play.c b/src/client/play.c index bc7f629b..89c2f66a 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -27,7 +27,7 @@ * play.c: Playing the game * * Known contributors to this file: - * Markus Armbruster, 2007-2015 + * Markus Armbruster, 2007-2016 * Ron Koenderink, 2007-2009 */ @@ -523,7 +523,7 @@ play(int sock) continue; /* read player input */ - if (FD_ISSET(input_fd, &rdfd)) { + if (FD_ISSET(input_fd, &rdfd) && ring_space(&inbuf)) { n = recv_input(input_fd, &inbuf); if (n <= 0) { if (input_fd) { From 8301e0f144c7435d12f2d583ffe78d6b5e287242 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Mon, 10 Apr 2017 22:22:21 +0200 Subject: [PATCH 11/30] client: Lift assignment to @input_fd to recv_output() On successful execute, servercmd() sets @input_fd to the batch file descriptor. Return the file descriptor instead, and let its caller recv_output() set @input_fd. This permits giving @input_fd static linkage. Signed-off-by: Markus Armbruster --- src/client/misc.h | 4 ++-- src/client/play.c | 48 +++++++++++++++++++++++++------------------- src/client/servcmd.c | 16 ++++++--------- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/client/misc.h b/src/client/misc.h index 3d1ee84f..29a548a5 100644 --- a/src/client/misc.h +++ b/src/client/misc.h @@ -28,6 +28,7 @@ * * Known contributors to this file: * Steve McClure, 1998 + * Markus Armbruster, 2004-2017 */ #ifndef MISC_H @@ -41,7 +42,6 @@ extern char empirehost[]; extern char empireport[]; extern int eight_bit_clean; -extern int input_fd; extern FILE *auxfp; extern int restricted; @@ -62,7 +62,7 @@ int tcp_connect(char *, char *); int login(int s, char *uname, char *cname, char *cpass, int kill_proc, int); int play(int); void sendcmd(int s, char *cmd, char *arg); -void servercmd(int, char *, int); +int servercmd(int, char *, int); void outch(char); #ifdef _MSC_VER diff --git a/src/client/play.c b/src/client/play.c index 89c2f66a..da5b2616 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -27,7 +27,7 @@ * play.c: Playing the game * * Known contributors to this file: - * Markus Armbruster, 2007-2016 + * Markus Armbruster, 2007-2017 * Ron Koenderink, 2007-2009 */ @@ -51,6 +51,19 @@ #include "ringbuf.h" #include "secure.h" +#define EOF_COOKIE "ctld\n" +#define INTR_COOKIE "aborted\n" + +/* + * Player input file descriptor + * 0 while reading interactive input + * >0 while reading a batch file + * <0 during error handling + */ +static int input_fd; + +static volatile sig_atomic_t send_intr; /* need to send INTR_COOKIE */ + #ifdef _WIN32 static CRITICAL_SECTION signal_critical_section; static LPCRITICAL_SECTION signal_critical_section_ptr = NULL; @@ -301,19 +314,6 @@ w32_ring_from_file_or_bounce_buf(struct ring *r, int fd) #define sysdep_stdin_init() ((void)0) #endif -#define EOF_COOKIE "ctld\n" -#define INTR_COOKIE "aborted\n" - -/* - * Player input file descriptor - * 0 while reading interactive input - * >0 while reading a batch file - * <0 during error handling - */ -int input_fd; - -static volatile sig_atomic_t send_intr; /* need to send INTR_COOKIE */ - /* * Receive and process server output from @sock. * Return number of characters received on success, -1 on error. @@ -341,7 +341,7 @@ recv_output(int sock) static struct lbuf lbuf; char buf[4096]; ssize_t n; - int i, ch, len; + int i, ch, len, fd; char *line; n = read(sock, buf, sizeof(buf)); @@ -387,7 +387,18 @@ recv_output(int sock) len = lbuf_putc(&lbuf, ch); if (len) { line = lbuf_line(&lbuf); - servercmd(id, line, len); + fd = servercmd(id, line, len); + if (fd < 0) { + /* failed execute */ + if (input_fd) + close(input_fd); + input_fd = 0; + send_intr = 1; + } else if (fd > 0) { + /* successful execute, switch to batch file */ + assert(!input_fd); + input_fd = fd; + } lbuf_init(&lbuf); state = SCANNING_ID; } @@ -567,11 +578,6 @@ play(int sock) } if (n == 0) return 0; - if (input_fd < 0) { - /* execute failed */ - send_intr = 1; - input_fd = 0; - } } } } diff --git a/src/client/servcmd.c b/src/client/servcmd.c index 3aab4318..c02390dc 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -30,7 +30,7 @@ * Dave Pare, 1989 * Steve McClure, 1998 * Ron Koenderink, 2005 - * Markus Armbruster, 2005-2015 + * Markus Armbruster, 2005-2017 */ #include @@ -59,7 +59,7 @@ static void doredir(char *p); static void dopipe(char *p); static int doexecute(char *p); -void +int servercmd(int code, char *arg, int len) { static int nmin, nbtu, fd; @@ -90,15 +90,9 @@ servercmd(int code, char *arg, int len) break; case C_EXECUTE: fd = doexecute(arg); - if (fd < 0) { - if (input_fd) - close(input_fd); - } else { - assert(!input_fd); + if (fd >= 0) executing = 1; - } - input_fd = fd; - break; + return fd; case C_EXIT: printf("Exit: %s", arg); if (auxfp) @@ -129,6 +123,8 @@ servercmd(int code, char *arg, int len) assert(0); break; } + + return 0; } static void From 8fe2b949e69cefe8a5a34af633c78ec3b2692865 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 25 Jun 2017 10:20:53 +0200 Subject: [PATCH 12/30] client: Split ring_to_iovec() off ring_to_file() Signed-off-by: Markus Armbruster --- src/client/ringbuf.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/client/ringbuf.c b/src/client/ringbuf.c index 82e772dc..ec79ef86 100644 --- a/src/client/ringbuf.c +++ b/src/client/ringbuf.c @@ -27,7 +27,7 @@ * ringbuf.c: Simple ring buffer * * Known contributors to this file: - * Markus Armbruster, 2007-2015 + * Markus Armbruster, 2007-2017 */ #include @@ -215,19 +215,15 @@ ring_from_file(struct ring *r, int fd) } /* - * Drain ring buffer to file referred by file descriptor @fd. - * If ring buffer is already empty, do nothing and return 0. - * Else attempt to write complete contents with writev(), and return - * its value. + * Set up @iov[] to describe complete contents of ring buffer. + * @iov[] must have at least two elements. + * Return number of elements used (zero for an empty ring buffer). */ -int -ring_to_file(struct ring *r, int fd) +static int +ring_to_iovec(struct ring *r, struct iovec iov[]) { unsigned cons = r->cons % RING_SIZE; unsigned prod = r->prod % RING_SIZE; - struct iovec iov[2]; - int cnt; - ssize_t res; if (r->cons == r->prod) return 0; @@ -239,15 +235,31 @@ ring_to_file(struct ring *r, int fd) /* r->buf[..prod-1] */ iov[1].iov_base = r->buf; iov[1].iov_len = prod; - cnt = 2; + return 2; } else { /* r->buf[cons..prod-1] */ iov[0].iov_len = prod - cons; - cnt = 1; + return 1; } +} + +/* + * Drain ring buffer to file referred by file descriptor @fd. + * If ring buffer is already empty, do nothing and return 0. + * Else attempt to write complete contents with writev(), and return + * its value. + */ +int +ring_to_file(struct ring *r, int fd) +{ + struct iovec iov[2]; + int cnt; + ssize_t res; + + cnt = ring_to_iovec(r, iov); res = writev(fd, iov, cnt); if (res < 0) return res; - r->cons += res; + ring_discard(r, res); return res; } From 53c8794ef8a30827722bbb8980e98a33494e1fdc Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 25 Jun 2017 10:23:08 +0200 Subject: [PATCH 13/30] client: Rearrange ring_to_iovec() for clarity Signed-off-by: Markus Armbruster --- src/client/ringbuf.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/client/ringbuf.c b/src/client/ringbuf.c index ec79ef86..99d1fe30 100644 --- a/src/client/ringbuf.c +++ b/src/client/ringbuf.c @@ -229,18 +229,17 @@ ring_to_iovec(struct ring *r, struct iovec iov[]) return 0; iov[0].iov_base = r->buf + cons; - if (prod <= cons) { - /* r->buf[cons..] */ - iov[0].iov_len = RING_SIZE - cons; - /* r->buf[..prod-1] */ - iov[1].iov_base = r->buf; - iov[1].iov_len = prod; - return 2; - } else { + if (prod > cons) { /* r->buf[cons..prod-1] */ iov[0].iov_len = prod - cons; return 1; } + /* r->buf[cons..] */ + iov[0].iov_len = RING_SIZE - cons; + /* r->buf[..prod-1] */ + iov[1].iov_base = r->buf; + iov[1].iov_len = prod; + return 2; } /* From 26372eb85d3008c356b20e9eae506dc464dc0856 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 25 Jun 2017 10:27:48 +0200 Subject: [PATCH 14/30] client: Inline ring_to_file() into new send_input() In preparation for the next commit. Signed-off-by: Markus Armbruster --- src/client/play.c | 17 ++++++++++++++++- src/client/ringbuf.c | 3 +-- src/client/ringbuf.h | 4 +++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/client/play.c b/src/client/play.c index da5b2616..2e074233 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -448,6 +448,21 @@ recv_input(int fd, struct ring *inbuf) return res; } +static int +send_input(int fd, struct ring *inbuf) +{ + struct iovec iov[2]; + int cnt; + ssize_t res; + + cnt = ring_to_iovec(inbuf, iov); + res = writev(fd, iov, cnt); + if (res < 0) + return res; + ring_discard(inbuf, res); + return res; +} + static void intr(int sig) { @@ -562,7 +577,7 @@ play(int sock) /* send it to the server */ if (FD_ISSET(sock, &wrfd)) { - n = ring_to_file(&inbuf, sock); + n = send_input(sock, &inbuf); if (n < 0) { perror("write socket"); return -1; diff --git a/src/client/ringbuf.c b/src/client/ringbuf.c index 99d1fe30..638c9e4c 100644 --- a/src/client/ringbuf.c +++ b/src/client/ringbuf.c @@ -35,7 +35,6 @@ #include #include #include -#include #include "ringbuf.h" /* @@ -219,7 +218,7 @@ ring_from_file(struct ring *r, int fd) * @iov[] must have at least two elements. * Return number of elements used (zero for an empty ring buffer). */ -static int +int ring_to_iovec(struct ring *r, struct iovec iov[]) { unsigned cons = r->cons % RING_SIZE; diff --git a/src/client/ringbuf.h b/src/client/ringbuf.h index 4d44a2b9..8d16f0cc 100644 --- a/src/client/ringbuf.h +++ b/src/client/ringbuf.h @@ -27,13 +27,14 @@ * ringbuf.h: Simple ring buffer * * Known contributors to this file: - * Markus Armbruster, 2007-2015 + * Markus Armbruster, 2007-2017 */ #ifndef RINGBUF_H #define RINGBUF_H #include +#include #define RING_SIZE 4096 @@ -61,6 +62,7 @@ extern int ring_putm(struct ring *, void *, size_t); extern void ring_discard(struct ring *, int); extern int ring_search(struct ring *, char *, int); extern int ring_from_file(struct ring *, int fd); +extern int ring_to_iovec(struct ring *, struct iovec[]); extern int ring_to_file(struct ring *, int fd); #endif From b3383c7423aad2daf9ddbd2c1280a6a7795484b7 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 25 Jun 2017 11:13:00 +0200 Subject: [PATCH 15/30] client: Delay additional input processing until after send We need to copy input to @auxfp to implement command line option -2, and pass it to save_input() to enable protection against a rogue server exploiting redirection and execute. We currently do this right when input enters the ring buffer, in recv_input(). Calling save_input() before sending input to the server is sloppy: it can make the client accept "future" redirections and executes. Delay save_input() until after input is sent. For simplicity, delay copying to @auxfp as well. This is actually pretty close to how things worked before commit 8b7d0b9 (v4.3.11). Signed-off-by: Markus Armbruster --- src/client/play.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/client/play.c b/src/client/play.c index 2e074233..210ff733 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -421,7 +421,7 @@ recv_output(int sock) static int recv_input(int fd, struct ring *inbuf) { - int n, i, ch; + int n; int res = 1; n = ring_from_file(inbuf, fd); @@ -435,16 +435,6 @@ recv_input(int fd, struct ring *inbuf) res = 0; } - /* copy input to AUXFP etc. */ - for (i = -n; i < 0; i++) { - ch = ring_peek(inbuf, i); - assert(ch != EOF); - if (ch != '\r') - save_input(ch); - if (auxfp) - putc(ch, auxfp); - } - return res; } @@ -452,14 +442,24 @@ static int send_input(int fd, struct ring *inbuf) { struct iovec iov[2]; - int cnt; + int cnt, i, ch; ssize_t res; cnt = ring_to_iovec(inbuf, iov); res = writev(fd, iov, cnt); if (res < 0) return res; - ring_discard(inbuf, res); + + /* Copy input to @auxfp etc. */ + for (i = 0; i < res; i++) { + ch = ring_getc(inbuf); + assert(ch != EOF); + if (ch != '\r') + save_input(ch); + if (auxfp) + putc(ch, auxfp); + } + return res; } From 594cd20f7629b0d84267f02458963d7586715516 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 25 Jun 2017 12:00:52 +0200 Subject: [PATCH 16/30] client: Remove unused ring_to_file() Signed-off-by: Markus Armbruster --- src/client/ringbuf.c | 21 --------------------- src/client/ringbuf.h | 1 - 2 files changed, 22 deletions(-) diff --git a/src/client/ringbuf.c b/src/client/ringbuf.c index 638c9e4c..2e4c8857 100644 --- a/src/client/ringbuf.c +++ b/src/client/ringbuf.c @@ -240,24 +240,3 @@ ring_to_iovec(struct ring *r, struct iovec iov[]) iov[1].iov_len = prod; return 2; } - -/* - * Drain ring buffer to file referred by file descriptor @fd. - * If ring buffer is already empty, do nothing and return 0. - * Else attempt to write complete contents with writev(), and return - * its value. - */ -int -ring_to_file(struct ring *r, int fd) -{ - struct iovec iov[2]; - int cnt; - ssize_t res; - - cnt = ring_to_iovec(r, iov); - res = writev(fd, iov, cnt); - if (res < 0) - return res; - ring_discard(r, res); - return res; -} diff --git a/src/client/ringbuf.h b/src/client/ringbuf.h index 8d16f0cc..545a2fd3 100644 --- a/src/client/ringbuf.h +++ b/src/client/ringbuf.h @@ -63,6 +63,5 @@ extern void ring_discard(struct ring *, int); extern int ring_search(struct ring *, char *, int); extern int ring_from_file(struct ring *, int fd); extern int ring_to_iovec(struct ring *, struct iovec[]); -extern int ring_to_file(struct ring *, int fd); #endif From f1fc0df03d3350ffe65cd2ff929bcf3ddc958c7c Mon Sep 17 00:00:00 2001 From: Martin Haukeli Date: Sun, 8 Nov 2015 23:57:14 +0100 Subject: [PATCH 17/30] client: Add readline support to empire client Readline provides fancy command line editing such as for previous commands and CTRL+A to jump to the beginning of the line. This patch does not add any completion on key, a TODO, if you will. A new command line flag, -H, turns on saving the history to disk. This may have security implications on shared computers, as all commands are saved as-is. Thus "change re 1234" would be logged directly to the file. Signed-off-by: Martin Haukeli Rebase on top of preparatory work, fix a few bugs, and tidy up: * Update the standalone client build, too. * Fix the Windows build. * Keep command line options sorted case-insensitively. * Error out when $HOME is unset and getpwuid() fails, just like we do for $LOGNAME. * Give @input_from_rl, @has_rl_input static linkage. * @has_rl_input is a flag, not a counter, set and test it accordingly. * Save all input in history, not just commands. Martin's attempt to recognize commands works only as long as the server sends prompts faster than the user sends input. Drop that part, and update commit message accordingly. * Fix recv_input() not to truncate value of strlen() to int, and to use memmove() for updating @input_from_rl in place. * Clean up whitespace in a few places. * Tweak commit message. Signed-off-by: Markus Armbruster --- Make.mk | 2 +- configure.ac | 1 + m4/ax_lib_readline.m4 | 107 ++++++++++++++++++++++++++++++++++++++++ man/empire.6 | 5 ++ src/client/configure.ac | 1 + src/client/main.c | 34 +++++++++++-- src/client/misc.h | 1 + src/client/play.c | 101 ++++++++++++++++++++++++++++++++++--- src/client/servcmd.c | 18 ++++++- 9 files changed, 256 insertions(+), 14 deletions(-) create mode 100644 m4/ax_lib_readline.m4 diff --git a/Make.mk b/Make.mk index b535e9db..c1a6aa92 100644 --- a/Make.mk +++ b/Make.mk @@ -432,5 +432,5 @@ $(srcdir)/src/client/config.h.in: src/client/configure.ac src/client/aclocal.m4 cd $(dir $@) && autoheader touch $@ -$(srcdir)/src/client/aclocal.m4: m4/ax_lib_socket_nsl.m4 m4/my_terminfo.m4 m4/my_windows_api.m4 +$(srcdir)/src/client/aclocal.m4: m4/ax_lib_readline.m4 m4/ax_lib_socket_nsl.m4 m4/my_terminfo.m4 m4/my_windows_api.m4 cat $^ >$@ diff --git a/configure.ac b/configure.ac index 36d358ec..589b06ae 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,7 @@ LIBS_util="$LIBS" LIBS="$LIBS_SOCKETS $LIBS" AX_LIB_SOCKET_NSL LIBS_server="$LIBS" +AX_LIB_READLINE ### Checks for header files diff --git a/m4/ax_lib_readline.m4 b/m4/ax_lib_readline.m4 new file mode 100644 index 00000000..056f25c2 --- /dev/null +++ b/m4/ax_lib_readline.m4 @@ -0,0 +1,107 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_lib_readline.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_LIB_READLINE +# +# DESCRIPTION +# +# Searches for a readline compatible library. If found, defines +# `HAVE_LIBREADLINE'. If the found library has the `add_history' function, +# sets also `HAVE_READLINE_HISTORY'. Also checks for the locations of the +# necessary include files and sets `HAVE_READLINE_H' or +# `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or +# 'HAVE_HISTORY_H' if the corresponding include files exists. +# +# The libraries that may be readline compatible are `libedit', +# `libeditline' and `libreadline'. Sometimes we need to link a termcap +# library for readline to work, this macro tests these cases too by trying +# to link with `libtermcap', `libcurses' or `libncurses' before giving up. +# +# Here is an example of how to use the information provided by this macro +# to perform the necessary includes or declarations in a C file: +# +# #ifdef HAVE_LIBREADLINE +# # if defined(HAVE_READLINE_READLINE_H) +# # include +# # elif defined(HAVE_READLINE_H) +# # include +# # else /* !defined(HAVE_READLINE_H) */ +# extern char *readline (); +# # endif /* !defined(HAVE_READLINE_H) */ +# char *cmdline = NULL; +# #else /* !defined(HAVE_READLINE_READLINE_H) */ +# /* no readline */ +# #endif /* HAVE_LIBREADLINE */ +# +# #ifdef HAVE_READLINE_HISTORY +# # if defined(HAVE_READLINE_HISTORY_H) +# # include +# # elif defined(HAVE_HISTORY_H) +# # include +# # else /* !defined(HAVE_HISTORY_H) */ +# extern void add_history (); +# extern int write_history (); +# extern int read_history (); +# # endif /* defined(HAVE_READLINE_HISTORY_H) */ +# /* no history */ +# #endif /* HAVE_READLINE_HISTORY */ +# +# LICENSE +# +# Copyright (c) 2008 Ville Laurikari +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AU_ALIAS([VL_LIB_READLINE], [AX_LIB_READLINE]) +AC_DEFUN([AX_LIB_READLINE], [ + AC_CACHE_CHECK([for a readline compatible library], + ax_cv_lib_readline, [ + ORIG_LIBS="$LIBS" + for readline_lib in readline edit editline; do + for termcap_lib in "" termcap curses ncurses; do + if test -z "$termcap_lib"; then + TRY_LIB="-l$readline_lib" + else + TRY_LIB="-l$readline_lib -l$termcap_lib" + fi + LIBS="$ORIG_LIBS $TRY_LIB" + AC_TRY_LINK_FUNC(readline, ax_cv_lib_readline="$TRY_LIB") + if test -n "$ax_cv_lib_readline"; then + break + fi + done + if test -n "$ax_cv_lib_readline"; then + break + fi + done + if test -z "$ax_cv_lib_readline"; then + ax_cv_lib_readline="no" + fi + LIBS="$ORIG_LIBS" + ]) + + if test "$ax_cv_lib_readline" != "no"; then + LIBS="$LIBS $ax_cv_lib_readline" + AC_DEFINE(HAVE_LIBREADLINE, 1, + [Define if you have a readline compatible library]) + AC_CHECK_HEADERS(readline.h readline/readline.h) + AC_CACHE_CHECK([whether readline supports history], + ax_cv_lib_readline_history, [ + ax_cv_lib_readline_history="no" + AC_TRY_LINK_FUNC(add_history, ax_cv_lib_readline_history="yes") + ]) + if test "$ax_cv_lib_readline_history" = "yes"; then + AC_DEFINE(HAVE_READLINE_HISTORY, 1, + [Define if your readline library has \`add_history']) + AC_CHECK_HEADERS(history.h readline/history.h) + fi + fi +])dnl diff --git a/man/empire.6 b/man/empire.6 index 4be59941..66460faa 100644 --- a/man/empire.6 +++ b/man/empire.6 @@ -39,6 +39,11 @@ Use UTF-8 rather than ASCII character set. This requires server version 4.2.21 or later, and a terminal that understands UTF-8. .TP +.B \-H +Save readline command history to file. +.IP +Only available when compiled with readline and history support. +.TP .B \-v Print version information and exit. .TP diff --git a/src/client/configure.ac b/src/client/configure.ac index 0854c596..ddd49fc4 100644 --- a/src/client/configure.ac +++ b/src/client/configure.ac @@ -58,6 +58,7 @@ if test "$Windows_API" = yes; then AC_LIBOBJ([w32/w32io]) AC_LIBOBJ([w32/w32sockets]) fi +AX_LIB_READLINE ### Checks for header files. diff --git a/src/client/main.c b/src/client/main.c index 74ab4271..bfd5a0e2 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -40,6 +40,7 @@ #include #ifdef _WIN32 #include +#include #include "sys/socket.h" #else #include @@ -55,6 +56,7 @@ struct passwd { char *pw_name; + char *pw_dir; }; static struct passwd *w32_getpw(void); @@ -72,6 +74,9 @@ print_usage(char *program_name) " -r Restricted mode, no redirections\n" " -s [HOST:]PORT Specify server HOST and PORT\n" " -u Use UTF-8\n" +#ifdef HAVE_READLINE_HISTORY + " -H Save readline command history to file\n" +#endif /* HAVE_READLINE_HISTORY */ " -h display this help and exit\n" " -v display version information and exit\n", program_name); @@ -82,6 +87,7 @@ main(int argc, char **argv) { int opt; char *auxfname = NULL; + int use_history_file = 0; int send_kill = 0; char *host = NULL; char *port = NULL; @@ -90,14 +96,20 @@ main(int argc, char **argv) char *country; char *passwd; char *uname; + char *udir; char *colon; int sock; - while ((opt = getopt(argc, argv, "2:krs:uhv")) != EOF) { + while ((opt = getopt(argc, argv, "2:krs:uHhv")) != EOF) { switch (opt) { case '2': auxfname = optarg; break; +#ifdef HAVE_READLINE_HISTORY + case 'H': + use_history_file = 1; + break; +#endif /* HAVE_READLINE_HISTORY */ case 'k': send_kill = 1; break; @@ -146,7 +158,8 @@ main(int argc, char **argv) if (!host) host = empirehost; uname = getenv("LOGNAME"); - if (uname == NULL) { + udir = getenv("HOME"); + if (!uname || !udir) { struct passwd *pwd; pwd = getpwuid(getuid()); @@ -154,7 +167,10 @@ main(int argc, char **argv) fprintf(stderr, "You don't exist. Go away\n"); exit(1); } - uname = pwd->pw_name; + if (!uname) + uname = pwd->pw_name; + if (!udir) + udir = pwd->pw_dir; } if (*ap) { fprintf(stderr, "%s: extra operand %s\n", argv[0], *ap); @@ -172,6 +188,12 @@ main(int argc, char **argv) sock = tcp_connect(host, port); + if (use_history_file) { + history_file = malloc(1024); + strncpy(history_file, udir, 1000); + strcat(history_file, "/.empire.history"); + } + if (!login(sock, uname, country, passwd, send_kill, utf8)) exit(1); @@ -183,12 +205,13 @@ main(int argc, char **argv) #ifdef _WIN32 /* - * Get Windows user name + * Get Windows user name and directory */ static struct passwd * w32_getpw(void) { static char unamebuf[128]; + static char udirbuf[MAX_PATH]; static struct passwd pwd; DWORD unamesize; @@ -199,6 +222,9 @@ w32_getpw(void) pwd.pw_name = "nobody"; } else pwd.pw_name = "nobody"; + if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, udirbuf)) + && strlen(udirbuf) == 0) + pwd.pw_dir = udirbuf; return &pwd; } diff --git a/src/client/misc.h b/src/client/misc.h index 29a548a5..0a09be76 100644 --- a/src/client/misc.h +++ b/src/client/misc.h @@ -44,6 +44,7 @@ extern char empireport[]; extern int eight_bit_clean; extern FILE *auxfp; extern int restricted; +extern char *history_file; #ifdef HAVE_CURSES_TERMINFO void getsose(void); diff --git a/src/client/play.c b/src/client/play.c index 210ff733..ad7028c7 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -51,6 +51,28 @@ #include "ringbuf.h" #include "secure.h" +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) +# include +# else /* !defined(HAVE_READLINE_H) */ +extern char *readline (); +# endif /* !defined(HAVE_READLINE_H) */ +#endif /* HAVE_LIBREADLINE */ + +#ifdef HAVE_READLINE_HISTORY +# if defined(HAVE_READLINE_HISTORY_H) +# include +# elif defined(HAVE_HISTORY_H) +# include +# else /* !defined(HAVE_HISTORY_H) */ +extern void add_history (); +extern int write_history (); +extern int read_history (); +# endif /* !defined(HAVE_HISTORY_H) */ +#endif /* HAVE_READLINE_HISTORY */ + #define EOF_COOKIE "ctld\n" #define INTR_COOKIE "aborted\n" @@ -414,6 +436,24 @@ recv_output(int sock) return n; } +char *history_file = NULL; +#ifdef HAVE_LIBREADLINE +static char *input_from_rl; +static int has_rl_input; + +static void +input_handler(char *line) +{ + input_from_rl = line; + has_rl_input = 1; + rl_already_prompted = 1; +#ifdef HAVE_READLINE_HISTORY + if (line && *line) + add_history(line); +#endif /* HAVE_READLINE_HISTORY */ +} +#endif /* HAVE_LIBREADLINE */ + /* * Receive player input from @fd into @inbuf. * Return 1 on receipt of input, zero on EOF, -1 on error. @@ -423,8 +463,33 @@ recv_input(int fd, struct ring *inbuf) { int n; int res = 1; +#ifdef HAVE_LIBREADLINE + size_t len; - n = ring_from_file(inbuf, fd); + if (fd == 0) { + if (!has_rl_input) + rl_callback_read_char(); + if (!has_rl_input) + return 1; + if (input_from_rl) { + len = strlen(input_from_rl); + n = ring_space(inbuf); + assert(n); + if (len >= (size_t)n) { + ring_putm(inbuf, input_from_rl, n); + memmove(input_from_rl, input_from_rl + n, len - n + 1); + } else { + ring_putm(inbuf, input_from_rl, len); + ring_putc(inbuf, '\n'); + free(input_from_rl); + has_rl_input = 0; + n = len + 1; + } + } else + n = 0; + } else +#endif + n = ring_from_file(inbuf, fd); if (n < 0) return -1; if (n == 0) { @@ -490,6 +555,7 @@ play(int sock) int send_eof; /* need to send EOF_COOKIE */ fd_set rdfd, wrfd; int n; + int ret = -1; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); @@ -497,6 +563,14 @@ play(int sock) sigaction(SIGINT, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); +#ifdef HAVE_LIBREADLINE +#ifdef HAVE_READLINE_HISTORY + if (history_file) + read_history(history_file); +#endif /* HAVE_READLINE_HISTORY */ + rl_bind_key('\t', rl_insert); /* Disable tab completion */ + rl_callback_handler_install("", input_handler); +#endif /* HAVE_LIBREADLINE */ ring_init(&inbuf); eof_fd0 = partial_line_sent = send_eof = send_intr = 0; @@ -525,7 +599,7 @@ play(int sock) if (n < 0) { if (errno != EINTR) { perror("select"); - return -1; + break; } } @@ -544,7 +618,6 @@ play(int sock) input_fd = 0; } } - if (n < 0) continue; @@ -571,7 +644,7 @@ play(int sock) sigaction(SIGINT, &sa, NULL); send_intr = 0; } - } else + } else if (ring_len(&inbuf) > 0) partial_line_sent = ring_peek(&inbuf, -1) != '\n'; } @@ -580,7 +653,7 @@ play(int sock) n = send_input(sock, &inbuf); if (n < 0) { perror("write socket"); - return -1; + break; } } @@ -589,10 +662,22 @@ play(int sock) n = recv_output(sock); if (n < 0) { perror("read socket"); - return -1; + break; + } + if (n == 0) { +#ifdef HAVE_LIBREADLINE +#ifdef HAVE_READLINE_HISTORY + if (history_file) + write_history(history_file); +#endif /* HAVE_READLINE_HISTORY */ +#endif /* HAVE_LIBREADLINE */ + ret = 0; + break; } - if (n == 0) - return 0; } } +#ifdef HAVE_LIBREADLINE + rl_callback_handler_remove(); +#endif + return ret; } diff --git a/src/client/servcmd.c b/src/client/servcmd.c index c02390dc..a8cce6fd 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -46,6 +46,14 @@ #include "proto.h" #include "secure.h" +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) +# include +# endif /* defined(HAVE_READLINE_H) */ +#endif /* HAVE_LIBREADLINE */ + int eight_bit_clean; FILE *auxfp; int restricted; @@ -131,10 +139,18 @@ static void prompt(int code, char *prompt, char *teles) { char *nl; + char pr[1024]; nl = code == C_PROMPT || code == C_INFORM ? "\n" : ""; - printf("%s%s%s", nl, teles, prompt); + snprintf(pr, sizeof(pr), "%s%s", teles, prompt); +#ifdef HAVE_LIBREADLINE + rl_set_prompt(pr); + printf("%s", nl); + rl_forced_update_display(); +#else /* !HAVE_LIBREADLINE */ + printf("%s%s", nl, pr); fflush(stdout); +#endif /* !HAVE_LIBREADLINE */ if (auxfp) { fprintf(auxfp, "%s%s%s", nl, teles, prompt); fflush(auxfp); From 0cb66906009297f0beb9f3b71648635bb190d260 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 13 Dec 2015 09:10:08 +0100 Subject: [PATCH 18/30] client: Tie up a few lose readline ends Document readline in more detail in man/empire.6. Make @history_file local to main(). main() silently truncates the home directory name to 1000 characters when constructing the history file name; mark FIXME. Set @rl_already_prompted just once. Write history file on unsuccessful exit, too. Signed-off-by: Markus Armbruster --- man/empire.6 | 27 ++++++++++++++++++++------- src/client/main.c | 9 ++++++--- src/client/misc.h | 3 +-- src/client/play.c | 18 +++++++++--------- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/man/empire.6 b/man/empire.6 index 66460faa..5db053d7 100644 --- a/man/empire.6 +++ b/man/empire.6 @@ -3,7 +3,7 @@ empire \- Empire client .SH SYNOPSIS .B empire -[\fB\-hkruv\fP] +[\fB\-hHkruv\fP] [\fB\-2\fP \fIoutfile\fP] [\fB\-s\fP \fI[host:]port\fP] [\fIcountry\fP @@ -24,6 +24,11 @@ the thin veneer of civilization that hides the maniac within. .B \-h Help. Print brief usage information and exit. .TP +.B \-H +Save readline command history to file. +.IP +Only available when compiled the GNU \fBreadline\fP library. +.TP .B \-k If someone else is connected to your country, kill their connection. .TP @@ -39,11 +44,6 @@ Use UTF-8 rather than ASCII character set. This requires server version 4.2.21 or later, and a terminal that understands UTF-8. .TP -.B \-H -Save readline command history to file. -.IP -Only available when compiled with readline and history support. -.TP .B \-v Print version information and exit. .TP @@ -79,8 +79,20 @@ representative. .TP .I LOGNAME Your user name. +.TP +.I INPUTRC +The filename for the \fBreadline\fP startup file, overriding the +default of \fI~/.inputrc\fP (see \fBREADLINE\fP below). +.SH READLINE +When compiled with the GNU \fBreadline\fP library, the client supports +fancy line editing and, with option \fB-H\fP, persistent history. See +the readline documentation for details. .SH "SEE ALSO" -\fIemp_server\fR(6). +\fIemp_server\fR(6), \fIreadline\fR(3). +.SH FILES +.TP +.I ~/.inputrc +Individual \fBreadline\fP initialization file .SH AUTHORS .nf Primary Author is Dave Pare @@ -90,6 +102,7 @@ Jeff Anton Markus Armbruster Phill Everson Steven Grimm +Martin Haukeli Lewis R. Jansen Mike St. Johns Ron Koenderink diff --git a/src/client/main.c b/src/client/main.c index bfd5a0e2..cb81f1b0 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -30,8 +30,9 @@ * Dave Pare, 1986 * Steve McClure, 1998 * Ron Koenderink, 2004-2007 - * Markus Armbruster, 2005-2010 + * Markus Armbruster, 2005-2015 * Tom Dickson-Hunt, 2010 + * Martin Haukeli, 2015 */ #include @@ -99,8 +100,9 @@ main(int argc, char **argv) char *udir; char *colon; int sock; + char *history_file; - while ((opt = getopt(argc, argv, "2:krs:uHhv")) != EOF) { + while ((opt = getopt(argc, argv, "2:Hkrs:uhv")) != EOF) { switch (opt) { case '2': auxfname = optarg; @@ -189,6 +191,7 @@ main(int argc, char **argv) sock = tcp_connect(host, port); if (use_history_file) { + /* FIXME don't truncate udir */ history_file = malloc(1024); strncpy(history_file, udir, 1000); strcat(history_file, "/.empire.history"); @@ -197,7 +200,7 @@ main(int argc, char **argv) if (!login(sock, uname, country, passwd, send_kill, utf8)) exit(1); - if (play(sock) < 0) + if (play(sock, history_file) < 0) exit(1); return 0; diff --git a/src/client/misc.h b/src/client/misc.h index 0a09be76..ffdc2dfa 100644 --- a/src/client/misc.h +++ b/src/client/misc.h @@ -44,7 +44,6 @@ extern char empireport[]; extern int eight_bit_clean; extern FILE *auxfp; extern int restricted; -extern char *history_file; #ifdef HAVE_CURSES_TERMINFO void getsose(void); @@ -61,7 +60,7 @@ int parseid(char *); int expect(int s, int match, char *buf); int tcp_connect(char *, char *); int login(int s, char *uname, char *cname, char *cpass, int kill_proc, int); -int play(int); +int play(int, char *); void sendcmd(int s, char *cmd, char *arg); int servercmd(int, char *, int); void outch(char); diff --git a/src/client/play.c b/src/client/play.c index ad7028c7..f02399ba 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -29,6 +29,7 @@ * Known contributors to this file: * Markus Armbruster, 2007-2017 * Ron Koenderink, 2007-2009 + * Martin Haukeli, 2015 */ #include @@ -436,7 +437,6 @@ recv_output(int sock) return n; } -char *history_file = NULL; #ifdef HAVE_LIBREADLINE static char *input_from_rl; static int has_rl_input; @@ -446,7 +446,6 @@ input_handler(char *line) { input_from_rl = line; has_rl_input = 1; - rl_already_prompted = 1; #ifdef HAVE_READLINE_HISTORY if (line && *line) add_history(line); @@ -536,11 +535,12 @@ intr(int sig) /* * Play on @sock. + * @history_file is the name of the history file, or null. * The session must be in the playing phase. * Return 0 when the session ended, -1 on error. */ int -play(int sock) +play(int sock, char *history_file) { /* * Player input flows from INPUT_FD through recv_input() into ring @@ -564,6 +564,7 @@ play(int sock) sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); #ifdef HAVE_LIBREADLINE + rl_already_prompted = 1; #ifdef HAVE_READLINE_HISTORY if (history_file) read_history(history_file); @@ -665,19 +666,18 @@ play(int sock) break; } if (n == 0) { -#ifdef HAVE_LIBREADLINE -#ifdef HAVE_READLINE_HISTORY - if (history_file) - write_history(history_file); -#endif /* HAVE_READLINE_HISTORY */ -#endif /* HAVE_LIBREADLINE */ ret = 0; break; } } } + #ifdef HAVE_LIBREADLINE rl_callback_handler_remove(); +#ifdef HAVE_READLINE_HISTORY + if (history_file) + write_history(history_file); +#endif /* HAVE_READLINE_HISTORY */ #endif return ret; } From 5e82836e3a74d1e0d30510b24ad0582f0a960652 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 6 Nov 2016 17:05:19 +0100 Subject: [PATCH 19/30] client: Fix obscure readline hang If recv_input() can't stuff the whole line into @inbuf, it leaves its tail in @input_from_rl. If send_input() then empties @inbuf, the next iteration will select @input_fd for reading instead of @sock for writing, because @inbuf is empty. Since @has_rl_input is still set, recv_input() will do nothing, and the client hangs. Fix as follows. Factor ring_from_rl() out of recv_input(). Also call it in send_input() to refill @inbuf from @input_from_rl. Signed-off-by: Markus Armbruster --- src/client/play.c | 48 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/client/play.c b/src/client/play.c index f02399ba..2684a2b9 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -451,6 +451,32 @@ input_handler(char *line) add_history(line); #endif /* HAVE_READLINE_HISTORY */ } + +static int +ring_from_rl(struct ring *inbuf) +{ + size_t len; + int n; + + assert(has_rl_input && input_from_rl); + + len = strlen(input_from_rl); + n = ring_space(inbuf); + assert(n); + + if (len >= (size_t)n) { + ring_putm(inbuf, input_from_rl, n); + memmove(input_from_rl, input_from_rl + n, len - n + 1); + } else { + ring_putm(inbuf, input_from_rl, len); + ring_putc(inbuf, '\n'); + free(input_from_rl); + has_rl_input = 0; + n = len + 1; + } + + return n; +} #endif /* HAVE_LIBREADLINE */ /* @@ -462,28 +488,15 @@ recv_input(int fd, struct ring *inbuf) { int n; int res = 1; -#ifdef HAVE_LIBREADLINE - size_t len; +#ifdef HAVE_LIBREADLINE if (fd == 0) { if (!has_rl_input) rl_callback_read_char(); if (!has_rl_input) return 1; if (input_from_rl) { - len = strlen(input_from_rl); - n = ring_space(inbuf); - assert(n); - if (len >= (size_t)n) { - ring_putm(inbuf, input_from_rl, n); - memmove(input_from_rl, input_from_rl + n, len - n + 1); - } else { - ring_putm(inbuf, input_from_rl, len); - ring_putc(inbuf, '\n'); - free(input_from_rl); - has_rl_input = 0; - n = len + 1; - } + n = ring_from_rl(inbuf); } else n = 0; } else @@ -524,6 +537,11 @@ send_input(int fd, struct ring *inbuf) putc(ch, auxfp); } +#ifdef HAVE_LIBREADLINE + if (fd == 0 && has_rl_input && input_from_rl) + ring_from_rl(inbuf); +#endif + return res; } From f83e61cdd2edc62fde0991d284e1b0a7a2ba5d09 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 6 Dec 2015 08:29:17 +0100 Subject: [PATCH 20/30] client: Redistribute work among prompt() and its callers Two out of three callers want an extra newline. Letting the callers do that is simpler, especially now that readline added another case to prompt(). Signed-off-by: Markus Armbruster --- src/client/servcmd.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/client/servcmd.c b/src/client/servcmd.c index a8cce6fd..75ec60b3 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -89,6 +89,7 @@ servercmd(int code, char *arg, int len) (void)fclose(redir_fp); redir_fp = NULL; } + outch('\n'); prompt(code, the_prompt, teles); executing = 0; break; @@ -115,6 +116,7 @@ servercmd(int code, char *arg, int len) if (arg[0] != '\n') { snprintf(teles, sizeof(teles), "(%.*s) ", len - 1, arg); if (!redir_fp) { + outch('\n'); putchar('\07'); prompt(code, the_prompt, teles); } @@ -138,21 +140,18 @@ servercmd(int code, char *arg, int len) static void prompt(int code, char *prompt, char *teles) { - char *nl; char pr[1024]; - nl = code == C_PROMPT || code == C_INFORM ? "\n" : ""; snprintf(pr, sizeof(pr), "%s%s", teles, prompt); #ifdef HAVE_LIBREADLINE rl_set_prompt(pr); - printf("%s", nl); rl_forced_update_display(); #else /* !HAVE_LIBREADLINE */ - printf("%s%s", nl, pr); + printf("%s", pr); fflush(stdout); #endif /* !HAVE_LIBREADLINE */ if (auxfp) { - fprintf(auxfp, "%s%s%s", nl, teles, prompt); + fprintf(auxfp, "%s%s", teles, prompt); fflush(auxfp); } } From 60fee0e6aef5bfc14151194ce9ec94326387af17 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 6 Dec 2015 08:40:14 +0100 Subject: [PATCH 21/30] client: Collect readline-related code in play.c Move prompt() from servcmd.c to play.c and give it external linkage. Signed-off-by: Markus Armbruster --- src/client/misc.h | 1 + src/client/play.c | 19 +++++++++++++++++++ src/client/servcmd.c | 28 ---------------------------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/client/misc.h b/src/client/misc.h index ffdc2dfa..dacaf12b 100644 --- a/src/client/misc.h +++ b/src/client/misc.h @@ -61,6 +61,7 @@ int expect(int s, int match, char *buf); int tcp_connect(char *, char *); int login(int s, char *uname, char *cname, char *cpass, int kill_proc, int); int play(int, char *); +void prompt(int, char *, char *); void sendcmd(int s, char *cmd, char *arg); int servercmd(int, char *, int); void outch(char); diff --git a/src/client/play.c b/src/client/play.c index 2684a2b9..9979ad69 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -699,3 +699,22 @@ play(int sock, char *history_file) #endif return ret; } + +void +prompt(int code, char *prompt, char *teles) +{ + char pr[1024]; + + snprintf(pr, sizeof(pr), "%s%s", teles, prompt); +#ifdef HAVE_LIBREADLINE + rl_set_prompt(pr); + rl_forced_update_display(); +#else /* !HAVE_LIBREADLINE */ + printf("%s", pr); + fflush(stdout); +#endif /* !HAVE_LIBREADLINE */ + if (auxfp) { + fprintf(auxfp, "%s%s", teles, prompt); + fflush(auxfp); + } +} diff --git a/src/client/servcmd.c b/src/client/servcmd.c index 75ec60b3..ba6d974e 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -46,14 +46,6 @@ #include "proto.h" #include "secure.h" -#ifdef HAVE_LIBREADLINE -# if defined(HAVE_READLINE_READLINE_H) -# include -# elif defined(HAVE_READLINE_H) -# include -# endif /* defined(HAVE_READLINE_H) */ -#endif /* HAVE_LIBREADLINE */ - int eight_bit_clean; FILE *auxfp; int restricted; @@ -62,7 +54,6 @@ static FILE *redir_fp; static int redir_is_pipe; static int executing; -static void prompt(int, char *, char *); static void doredir(char *p); static void dopipe(char *p); static int doexecute(char *p); @@ -137,25 +128,6 @@ servercmd(int code, char *arg, int len) return 0; } -static void -prompt(int code, char *prompt, char *teles) -{ - char pr[1024]; - - snprintf(pr, sizeof(pr), "%s%s", teles, prompt); -#ifdef HAVE_LIBREADLINE - rl_set_prompt(pr); - rl_forced_update_display(); -#else /* !HAVE_LIBREADLINE */ - printf("%s", pr); - fflush(stdout); -#endif /* !HAVE_LIBREADLINE */ - if (auxfp) { - fprintf(auxfp, "%s%s", teles, prompt); - fflush(auxfp); - } -} - static char * fname(char *s) { From 1cbda2c7dd6159ad29c21c03804659d2ed044523 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Mon, 7 Dec 2015 21:27:42 +0100 Subject: [PATCH 22/30] client: Rewrite readline configuration AX_LIB_READLINE tries to cope with systems where readline lacks history support, or lacks headers, or needs headers included in unorthodox ways. It puts six HAVE_ macros into config.h, and its usage example takes 24 lines of code just to include two headers. Way too complicated for my taste. Replace with new MY_LIB_READLINE, which succeeds only when you have a sane readline, and then defines *one* macro: HAVE_LIBREADLINE. Signed-off-by: Markus Armbruster --- Make.mk | 2 +- configure.ac | 2 +- m4/ax_lib_readline.m4 | 107 ---------------------------------------- m4/my_lib_readline.m4 | 25 ++++++++++ src/client/configure.ac | 2 +- src/client/main.c | 8 +-- src/client/play.c | 29 ++--------- 7 files changed, 35 insertions(+), 140 deletions(-) delete mode 100644 m4/ax_lib_readline.m4 create mode 100644 m4/my_lib_readline.m4 diff --git a/Make.mk b/Make.mk index c1a6aa92..05547d1c 100644 --- a/Make.mk +++ b/Make.mk @@ -432,5 +432,5 @@ $(srcdir)/src/client/config.h.in: src/client/configure.ac src/client/aclocal.m4 cd $(dir $@) && autoheader touch $@ -$(srcdir)/src/client/aclocal.m4: m4/ax_lib_readline.m4 m4/ax_lib_socket_nsl.m4 m4/my_terminfo.m4 m4/my_windows_api.m4 +$(srcdir)/src/client/aclocal.m4: m4/ax_lib_socket_nsl.m4 m4/my_lib_readline.m4 m4/my_terminfo.m4 m4/my_windows_api.m4 cat $^ >$@ diff --git a/configure.ac b/configure.ac index 589b06ae..c0816116 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,7 @@ LIBS_util="$LIBS" LIBS="$LIBS_SOCKETS $LIBS" AX_LIB_SOCKET_NSL LIBS_server="$LIBS" -AX_LIB_READLINE +MY_LIB_READLINE ### Checks for header files diff --git a/m4/ax_lib_readline.m4 b/m4/ax_lib_readline.m4 deleted file mode 100644 index 056f25c2..00000000 --- a/m4/ax_lib_readline.m4 +++ /dev/null @@ -1,107 +0,0 @@ -# =========================================================================== -# http://www.gnu.org/software/autoconf-archive/ax_lib_readline.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_LIB_READLINE -# -# DESCRIPTION -# -# Searches for a readline compatible library. If found, defines -# `HAVE_LIBREADLINE'. If the found library has the `add_history' function, -# sets also `HAVE_READLINE_HISTORY'. Also checks for the locations of the -# necessary include files and sets `HAVE_READLINE_H' or -# `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or -# 'HAVE_HISTORY_H' if the corresponding include files exists. -# -# The libraries that may be readline compatible are `libedit', -# `libeditline' and `libreadline'. Sometimes we need to link a termcap -# library for readline to work, this macro tests these cases too by trying -# to link with `libtermcap', `libcurses' or `libncurses' before giving up. -# -# Here is an example of how to use the information provided by this macro -# to perform the necessary includes or declarations in a C file: -# -# #ifdef HAVE_LIBREADLINE -# # if defined(HAVE_READLINE_READLINE_H) -# # include -# # elif defined(HAVE_READLINE_H) -# # include -# # else /* !defined(HAVE_READLINE_H) */ -# extern char *readline (); -# # endif /* !defined(HAVE_READLINE_H) */ -# char *cmdline = NULL; -# #else /* !defined(HAVE_READLINE_READLINE_H) */ -# /* no readline */ -# #endif /* HAVE_LIBREADLINE */ -# -# #ifdef HAVE_READLINE_HISTORY -# # if defined(HAVE_READLINE_HISTORY_H) -# # include -# # elif defined(HAVE_HISTORY_H) -# # include -# # else /* !defined(HAVE_HISTORY_H) */ -# extern void add_history (); -# extern int write_history (); -# extern int read_history (); -# # endif /* defined(HAVE_READLINE_HISTORY_H) */ -# /* no history */ -# #endif /* HAVE_READLINE_HISTORY */ -# -# LICENSE -# -# Copyright (c) 2008 Ville Laurikari -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 6 - -AU_ALIAS([VL_LIB_READLINE], [AX_LIB_READLINE]) -AC_DEFUN([AX_LIB_READLINE], [ - AC_CACHE_CHECK([for a readline compatible library], - ax_cv_lib_readline, [ - ORIG_LIBS="$LIBS" - for readline_lib in readline edit editline; do - for termcap_lib in "" termcap curses ncurses; do - if test -z "$termcap_lib"; then - TRY_LIB="-l$readline_lib" - else - TRY_LIB="-l$readline_lib -l$termcap_lib" - fi - LIBS="$ORIG_LIBS $TRY_LIB" - AC_TRY_LINK_FUNC(readline, ax_cv_lib_readline="$TRY_LIB") - if test -n "$ax_cv_lib_readline"; then - break - fi - done - if test -n "$ax_cv_lib_readline"; then - break - fi - done - if test -z "$ax_cv_lib_readline"; then - ax_cv_lib_readline="no" - fi - LIBS="$ORIG_LIBS" - ]) - - if test "$ax_cv_lib_readline" != "no"; then - LIBS="$LIBS $ax_cv_lib_readline" - AC_DEFINE(HAVE_LIBREADLINE, 1, - [Define if you have a readline compatible library]) - AC_CHECK_HEADERS(readline.h readline/readline.h) - AC_CACHE_CHECK([whether readline supports history], - ax_cv_lib_readline_history, [ - ax_cv_lib_readline_history="no" - AC_TRY_LINK_FUNC(add_history, ax_cv_lib_readline_history="yes") - ]) - if test "$ax_cv_lib_readline_history" = "yes"; then - AC_DEFINE(HAVE_READLINE_HISTORY, 1, - [Define if your readline library has \`add_history']) - AC_CHECK_HEADERS(history.h readline/history.h) - fi - fi -])dnl diff --git a/m4/my_lib_readline.m4 b/m4/my_lib_readline.m4 new file mode 100644 index 00000000..5d570e5e --- /dev/null +++ b/m4/my_lib_readline.m4 @@ -0,0 +1,25 @@ +AC_DEFUN([MY_LIB_READLINE], [ + have_readline=no + for readline_lib in readline edit editline; do + for termcap_lib in "" termlib termcap curses ncurses; do + AC_CHECK_LIB([$readline_lib], [add_history], + [have_readline=yes; break 2], [], [$termcap_lib]) + done + done + + if test "$have_readline" = yes; then + AC_CHECK_HEADER([readline/readline.h], [], [have_readline=no], + [AC_INCLUDES_DEFAULT]) + AC_CHECK_HEADER([readline/history.h], [], [have_readline=no], + [AC_INCLUDES_DEFAULT]) + fi + + if test "$have_readline" = yes; then + if test "x$termcap_lib" != x; then + LIBS="-l$termcap_lib $LIBS" + fi + LIBS="-l$readline_lib $LIBS" + AC_DEFINE([HAVE_LIBREADLINE], [1], + [Define if you have libreadline]) + fi +]) diff --git a/src/client/configure.ac b/src/client/configure.ac index ddd49fc4..90cc518e 100644 --- a/src/client/configure.ac +++ b/src/client/configure.ac @@ -58,7 +58,7 @@ if test "$Windows_API" = yes; then AC_LIBOBJ([w32/w32io]) AC_LIBOBJ([w32/w32sockets]) fi -AX_LIB_READLINE +MY_LIB_READLINE ### Checks for header files. diff --git a/src/client/main.c b/src/client/main.c index cb81f1b0..cdffc8ac 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -75,9 +75,9 @@ print_usage(char *program_name) " -r Restricted mode, no redirections\n" " -s [HOST:]PORT Specify server HOST and PORT\n" " -u Use UTF-8\n" -#ifdef HAVE_READLINE_HISTORY +#ifdef HAVE_LIBREADLINE " -H Save readline command history to file\n" -#endif /* HAVE_READLINE_HISTORY */ +#endif /* HAVE_LIBREADLINE */ " -h display this help and exit\n" " -v display version information and exit\n", program_name); @@ -107,11 +107,11 @@ main(int argc, char **argv) case '2': auxfname = optarg; break; -#ifdef HAVE_READLINE_HISTORY +#ifdef HAVE_LIBREADLINE case 'H': use_history_file = 1; break; -#endif /* HAVE_READLINE_HISTORY */ +#endif /* HAVE_LIBREADLINE */ case 'k': send_kill = 1; break; diff --git a/src/client/play.c b/src/client/play.c index 9979ad69..2f75b783 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -53,26 +53,9 @@ #include "secure.h" #ifdef HAVE_LIBREADLINE -# if defined(HAVE_READLINE_READLINE_H) -# include -# elif defined(HAVE_READLINE_H) -# include -# else /* !defined(HAVE_READLINE_H) */ -extern char *readline (); -# endif /* !defined(HAVE_READLINE_H) */ -#endif /* HAVE_LIBREADLINE */ - -#ifdef HAVE_READLINE_HISTORY -# if defined(HAVE_READLINE_HISTORY_H) -# include -# elif defined(HAVE_HISTORY_H) -# include -# else /* !defined(HAVE_HISTORY_H) */ -extern void add_history (); -extern int write_history (); -extern int read_history (); -# endif /* !defined(HAVE_HISTORY_H) */ -#endif /* HAVE_READLINE_HISTORY */ +#include +#include +#endif #define EOF_COOKIE "ctld\n" #define INTR_COOKIE "aborted\n" @@ -446,10 +429,8 @@ input_handler(char *line) { input_from_rl = line; has_rl_input = 1; -#ifdef HAVE_READLINE_HISTORY if (line && *line) add_history(line); -#endif /* HAVE_READLINE_HISTORY */ } static int @@ -583,10 +564,8 @@ play(int sock, char *history_file) sigaction(SIGPIPE, &sa, NULL); #ifdef HAVE_LIBREADLINE rl_already_prompted = 1; -#ifdef HAVE_READLINE_HISTORY if (history_file) read_history(history_file); -#endif /* HAVE_READLINE_HISTORY */ rl_bind_key('\t', rl_insert); /* Disable tab completion */ rl_callback_handler_install("", input_handler); #endif /* HAVE_LIBREADLINE */ @@ -692,10 +671,8 @@ play(int sock, char *history_file) #ifdef HAVE_LIBREADLINE rl_callback_handler_remove(); -#ifdef HAVE_READLINE_HISTORY if (history_file) write_history(history_file); -#endif /* HAVE_READLINE_HISTORY */ #endif return ret; } From 56f426ae9e39a925ea58a63c571612dc736c75dd Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 8 Dec 2015 20:57:49 +0100 Subject: [PATCH 23/30] client: New configure --with-readline Signed-off-by: Markus Armbruster --- configure.ac | 3 ++- m4/my_lib_readline.m4 | 15 +++++++++++++++ src/client/configure.ac | 3 ++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index c0816116..7e56c02a 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,7 @@ LIBS_util="$LIBS" LIBS="$LIBS_SOCKETS $LIBS" AX_LIB_SOCKET_NSL LIBS_server="$LIBS" -MY_LIB_READLINE +MY_WITH_READLINE ### Checks for header files @@ -235,6 +235,7 @@ AC_OUTPUT AC_MSG_NOTICE([]) AC_MSG_NOTICE([-= Configuration summary =-]) AC_MSG_NOTICE([Thread package: $empthread]) +AC_MSG_NOTICE([ readline: $with_readline]) AC_MSG_NOTICE([ terminfo: $with_terminfo]) AC_MSG_NOTICE([ EMPIREHOST: $EMPIREHOST]) AC_MSG_NOTICE([ EMPIREPORT: $EMPIREPORT]) diff --git a/m4/my_lib_readline.m4 b/m4/my_lib_readline.m4 index 5d570e5e..37fedec5 100644 --- a/m4/my_lib_readline.m4 +++ b/m4/my_lib_readline.m4 @@ -23,3 +23,18 @@ AC_DEFUN([MY_LIB_READLINE], [ [Define if you have libreadline]) fi ]) + +AC_DEFUN([MY_WITH_READLINE], +[ + AC_ARG_WITH([readline], + [AS_HELP_STRING([--with-readline], + [support fancy command line editing @<:@default=check@:>@])], + [], + [with_readline=check]) + if test "x$with_readline" != xno; then + MY_LIB_READLINE + if test "x$have_readline$with_readline" = xnoyes; then + AC_MSG_FAILURE([--with-readline was given, but test for readline failed]) + fi + with_readline="$have_readline" + fi]) diff --git a/src/client/configure.ac b/src/client/configure.ac index 90cc518e..2baebcad 100644 --- a/src/client/configure.ac +++ b/src/client/configure.ac @@ -58,7 +58,7 @@ if test "$Windows_API" = yes; then AC_LIBOBJ([w32/w32io]) AC_LIBOBJ([w32/w32sockets]) fi -MY_LIB_READLINE +MY_WITH_READLINE ### Checks for header files. @@ -94,6 +94,7 @@ AC_OUTPUT AC_MSG_NOTICE([]) AC_MSG_NOTICE([-= Configuration summary =-]) +AC_MSG_NOTICE([ readline: $with_readline]) AC_MSG_NOTICE([ terminfo: $with_terminfo]) AC_MSG_NOTICE([ EMPIREHOST: $EMPIREHOST]) AC_MSG_NOTICE([ EMPIREPORT: $EMPIREPORT]) From 48fcff36b2460afc944904ee8a5e3b1b90a38fff Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Tue, 8 Dec 2015 21:36:38 +0100 Subject: [PATCH 24/30] m4: Make MY_WITH_TERMINFO consistent with MY_WITH_READLINE Tweak help text and failure message. Signed-off-by: Markus Armbruster --- m4/my_terminfo.m4 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/m4/my_terminfo.m4 b/m4/my_terminfo.m4 index 844bda3e..f4d33bdf 100644 --- a/m4/my_terminfo.m4 +++ b/m4/my_terminfo.m4 @@ -14,11 +14,11 @@ AC_DEFUN([MY_WITH_TERMINFO], [ AC_ARG_WITH([terminfo], AS_HELP_STRING([--with-terminfo], - [use terminfo for highlighting (default check)])) + [use terminfo for highlighting @<:@default check@:>@])) if test "x$with_terminfo" != xno; then MY_CURSES_TERMINFO if test "$have_terminfo$with_terminfo" = noyes - then AC_MSG_FAILURE([Can't satisfy --with-terminfo]) + then AC_MSG_FAILURE([--with-terminfo was given, but test for terminfo failed]) fi with_terminfo="$have_terminfo" fi From 6b72fefafbec9a647f1ac8df49be59e2e8965a4e Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 13 Dec 2015 08:31:16 +0100 Subject: [PATCH 25/30] include: Factor fnameat.h out of prototypes.h Signed-off-by: Markus Armbruster --- include/fnameat.h | 41 +++++++++++++++++++++++++++++++++++++ include/prototypes.h | 3 +-- src/lib/common/conftab.c | 1 + src/lib/common/emp_config.c | 1 + src/lib/gen/fnameat.c | 4 +++- 5 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 include/fnameat.h diff --git a/include/fnameat.h b/include/fnameat.h new file mode 100644 index 00000000..5bd91c29 --- /dev/null +++ b/include/fnameat.h @@ -0,0 +1,41 @@ +/* + * Empire - A multi-player, client/server Internet based war game. + * Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak, + * Ken Stevens, Steve McClure, Markus Armbruster + * + * Empire 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * --- + * + * See files README, COPYING and CREDITS in the root of the source + * tree for related information and legal notices. It is expected + * that future projects/authors will amend these files as needed. + * + * --- + * + * fnameat.h: Interpret file names relative to a directory + * + * Known contributors to this file: + * Markus Armbruster, 2015 + */ + +#ifndef FNAMEAT_H +#define FNAMEAT_H + +#include + +extern char *fnameat(const char *, const char *); +extern FILE *fopenat(const char *, const char *, const char *); + +#endif diff --git a/include/prototypes.h b/include/prototypes.h index 5760a29c..6fca449d 100644 --- a/include/prototypes.h +++ b/include/prototypes.h @@ -287,8 +287,7 @@ extern int demandupdatecheck(void); /* disassoc.c */ extern int disassoc(void); /* fnameat.c */ -extern char *fnameat(const char *, const char *); -extern FILE *fopenat(const char *, const char *, const char *); +/* in fnameat.h */ /* fsize.c */ extern int fsize(int); extern int blksize(int); diff --git a/src/lib/common/conftab.c b/src/lib/common/conftab.c index 9a743d28..2c5d48e3 100644 --- a/src/lib/common/conftab.c +++ b/src/lib/common/conftab.c @@ -35,6 +35,7 @@ #include #include #include "file.h" +#include "fnameat.h" #include "optlist.h" #include "prototypes.h" #include "xdump.h" diff --git a/src/lib/common/emp_config.c b/src/lib/common/emp_config.c index ff698ff6..d084e3a1 100644 --- a/src/lib/common/emp_config.c +++ b/src/lib/common/emp_config.c @@ -50,6 +50,7 @@ #include #include +#include "fnameat.h" #include "misc.h" #include "optlist.h" #include "prototypes.h" diff --git a/src/lib/gen/fnameat.c b/src/lib/gen/fnameat.c index 181b74ab..d970372c 100644 --- a/src/lib/gen/fnameat.c +++ b/src/lib/gen/fnameat.c @@ -33,7 +33,9 @@ #include #include -#include "prototypes.h" +#include +#include +#include "fnameat.h" static int fname_is_abs(const char *); From a0220e864f6bcf4355896e58d1ac9b499e19016f Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 13 Dec 2015 08:34:26 +0100 Subject: [PATCH 26/30] client: Use fnameat() to construct history file name We truncate the user's home directory name to 1000 characters when constructing the history file name. Use fnameat() to fix that. Signed-off-by: Markus Armbruster --- Make.mk | 5 +++-- src/client/Makefile.in | 9 +++++---- src/client/main.c | 9 +++------ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Make.mk b/Make.mk index 05547d1c..6ea44d09 100644 --- a/Make.mk +++ b/Make.mk @@ -314,7 +314,7 @@ info.html/%.html: info/%.t $(server): $(filter src/server/% src/lib/commands/% src/lib/player/% src/lib/subs/% src/lib/update/%, $(obj)) $(empth_obj) $(empth_lib) $(libs) $(call quiet-command,$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@,LINK $@) -$(client): $(filter src/client/%, $(obj)) src/lib/global/version.o +$(client): $(filter src/client/%, $(obj)) src/lib/global/version.o src/lib/gen/fnameat.o $(call quiet-command,$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@,LINK $@) $(util): $(libs) @@ -373,8 +373,9 @@ dist-client: $(cli_distgen) $(tarball) $(TARNAME)-client-$(VERSION) \ -C $(srcdir)/src/client \ $(notdir $(filter src/client/%, $(src)) $(cli_distgen)) \ - -C $(srcdir)/include proto.h version.h \ + -C $(srcdir)/include fnameat.h proto.h version.h \ -C $(srcdir)/src/lib/global version.c \ + -C $(srcdir)/src/lib/gen fnameat.c \ -C $(srcdir)/src/lib $(addprefix w32/, $(client/w32)) \ -C $(srcdir)/man empire.6 \ -C $(srcdir)/build-aux install-sh \ diff --git a/src/client/Makefile.in b/src/client/Makefile.in index c5a6c3e3..f794c2fd 100644 --- a/src/client/Makefile.in +++ b/src/client/Makefile.in @@ -28,7 +28,7 @@ # Makefile.in: Makefile template for configure # # Known contributors to this file: -# Markus Armbruster, 2005-2013 +# Markus Armbruster, 2005-2015 # CC = @CC@ @@ -53,8 +53,9 @@ srcdir = @srcdir@ VPATH = @srcdir@ prog = empire$E -obj = expect.$O host.$O ipglob.$O linebuf.$O login.$O main.$O play.$O \ -ringbuf.$O secure.$O servcmd.$O termlib.$O version.$O $(LIBOBJS) +obj = expect.$O fnameat.$O host.$O ipglob.$O linebuf.$O login.$O \ +main.$O play.$O ringbuf.$O secure.$O servcmd.$O termlib.$O version.$O \ +$(LIBOBJS) all: $(prog) @@ -83,7 +84,7 @@ expect.$O: misc.h proto.h host.$O: misc.h linebuf.$O: linebuf.h login.$O: misc.h proto.h -main.$O: misc.h version.h +main.$O: fnameat.h misc.h version.h play.$O: linebuf.h misc.h proto.h ringbuf.h secure.h ringbuf.$O: ringbuf.h secure.$O: ringbuf.h secure.h diff --git a/src/client/main.c b/src/client/main.c index cdffc8ac..aaa54e9d 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -47,6 +47,7 @@ #include #endif #include +#include "fnameat.h" #include "misc.h" #include "version.h" @@ -190,12 +191,8 @@ main(int argc, char **argv) sock = tcp_connect(host, port); - if (use_history_file) { - /* FIXME don't truncate udir */ - history_file = malloc(1024); - strncpy(history_file, udir, 1000); - strcat(history_file, "/.empire.history"); - } + if (use_history_file) + history_file = fnameat(".empire_history", udir); if (!login(sock, uname, country, passwd, send_kill, utf8)) exit(1); From de638fd77981daed8882814b08c4444e74733314 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 9 Jul 2017 17:05:51 +0200 Subject: [PATCH 27/30] client: Use readline only when standard input is a TTY Readline is for interactive use. For non-interactive use, it merely complicates things. Case in point: it slows down "make check" by almost 10% for me. Interactive use should always involve a TTY, so use readline only when standard input is a TTY. This supresses readline in "make check". Signed-off-by: Markus Armbruster --- src/client/play.c | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/client/play.c b/src/client/play.c index 2f75b783..bd00768b 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -421,6 +421,7 @@ recv_output(int sock) } #ifdef HAVE_LIBREADLINE +static int use_readline; static char *input_from_rl; static int has_rl_input; @@ -471,7 +472,7 @@ recv_input(int fd, struct ring *inbuf) int res = 1; #ifdef HAVE_LIBREADLINE - if (fd == 0) { + if (fd == 0 && use_readline) { if (!has_rl_input) rl_callback_read_char(); if (!has_rl_input) @@ -519,7 +520,7 @@ send_input(int fd, struct ring *inbuf) } #ifdef HAVE_LIBREADLINE - if (fd == 0 && has_rl_input && input_from_rl) + if (fd == 0 && use_readline && has_rl_input && input_from_rl) ring_from_rl(inbuf); #endif @@ -563,11 +564,14 @@ play(int sock, char *history_file) sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); #ifdef HAVE_LIBREADLINE - rl_already_prompted = 1; - if (history_file) - read_history(history_file); - rl_bind_key('\t', rl_insert); /* Disable tab completion */ - rl_callback_handler_install("", input_handler); + if (isatty(0)) { + use_readline = 1; + rl_already_prompted = 1; + if (history_file) + read_history(history_file); + rl_bind_key('\t', rl_insert); /* Disable tab completion */ + rl_callback_handler_install("", input_handler); + } #endif /* HAVE_LIBREADLINE */ ring_init(&inbuf); @@ -670,9 +674,11 @@ play(int sock, char *history_file) } #ifdef HAVE_LIBREADLINE - rl_callback_handler_remove(); - if (history_file) - write_history(history_file); + if (use_readline) { + rl_callback_handler_remove(); + if (history_file) + write_history(history_file); + } #endif return ret; } @@ -684,12 +690,15 @@ prompt(int code, char *prompt, char *teles) snprintf(pr, sizeof(pr), "%s%s", teles, prompt); #ifdef HAVE_LIBREADLINE - rl_set_prompt(pr); - rl_forced_update_display(); -#else /* !HAVE_LIBREADLINE */ - printf("%s", pr); - fflush(stdout); -#endif /* !HAVE_LIBREADLINE */ + if (use_readline) { + rl_set_prompt(pr); + rl_forced_update_display(); + } else +#endif /* HAVE_LIBREADLINE */ + { + printf("%s", pr); + fflush(stdout); + } if (auxfp) { fprintf(auxfp, "%s%s", teles, prompt); fflush(auxfp); From 2fe38c1acb90bf39934715c9bf581ccbcfb8001e Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 13 Dec 2015 09:30:49 +0100 Subject: [PATCH 28/30] client: Enable history file by default unless -r Make -H take an argument. Default it to ~/.empire_history, except in -r restricted mode, where history is off unless you specify -H. That's because restricted mode restricts the player's access to the local system, and that includes the history file. If you want to grant access to a history file, you have to do so explicitly. Thanks to the previous commit, there is no need to suppress saving to ~/.empire_history in the test suite. Signed-off-by: Markus Armbruster --- man/empire.6 | 13 ++++++++----- src/client/main.c | 16 +++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/man/empire.6 b/man/empire.6 index 5db053d7..ff6a6875 100644 --- a/man/empire.6 +++ b/man/empire.6 @@ -3,8 +3,9 @@ empire \- Empire client .SH SYNOPSIS .B empire -[\fB\-hHkruv\fP] +[\fB\-hkruv\fP] [\fB\-2\fP \fIoutfile\fP] +[\fB\-H\fP \fIhistfile\fP] [\fB\-s\fP \fI[host:]port\fP] [\fIcountry\fP [\fIpassword\fP]] @@ -24,8 +25,10 @@ the thin veneer of civilization that hides the maniac within. .B \-h Help. Print brief usage information and exit. .TP -.B \-H -Save readline command history to file. +.BI \-H " histfile" +Load command history from \fIhistfile\fP, and save it back. Default +is '~/.empire_history' without \fB\-r\fP, and none with \fB-r\fP. You +might want to protect your history file from prying eyes. .IP Only available when compiled the GNU \fBreadline\fP library. .TP @@ -85,8 +88,8 @@ The filename for the \fBreadline\fP startup file, overriding the default of \fI~/.inputrc\fP (see \fBREADLINE\fP below). .SH READLINE When compiled with the GNU \fBreadline\fP library, the client supports -fancy line editing and, with option \fB-H\fP, persistent history. See -the readline documentation for details. +fancy line editing and persistent history. See the readline +documentation for details. .SH "SEE ALSO" \fIemp_server\fR(6), \fIreadline\fR(3). .SH FILES diff --git a/src/client/main.c b/src/client/main.c index aaa54e9d..190380b7 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -77,7 +77,8 @@ print_usage(char *program_name) " -s [HOST:]PORT Specify server HOST and PORT\n" " -u Use UTF-8\n" #ifdef HAVE_LIBREADLINE - " -H Save readline command history to file\n" + " -H FILE Load and save command history from FILE\n" + " (default ~/.empire_history with -r, none without -r)\n" #endif /* HAVE_LIBREADLINE */ " -h display this help and exit\n" " -v display version information and exit\n", @@ -89,7 +90,7 @@ main(int argc, char **argv) { int opt; char *auxfname = NULL; - int use_history_file = 0; + char *history_file = NULL; int send_kill = 0; char *host = NULL; char *port = NULL; @@ -101,16 +102,15 @@ main(int argc, char **argv) char *udir; char *colon; int sock; - char *history_file; - while ((opt = getopt(argc, argv, "2:Hkrs:uhv")) != EOF) { + while ((opt = getopt(argc, argv, "2:H:krs:uhv")) != EOF) { switch (opt) { case '2': auxfname = optarg; break; #ifdef HAVE_LIBREADLINE case 'H': - use_history_file = 1; + history_file = optarg; break; #endif /* HAVE_LIBREADLINE */ case 'k': @@ -191,8 +191,10 @@ main(int argc, char **argv) sock = tcp_connect(host, port); - if (use_history_file) - history_file = fnameat(".empire_history", udir); + if (!restricted && !history_file) + history_file = ".empire_history"; + if (history_file) + history_file = fnameat(history_file, udir); if (!login(sock, uname, country, passwd, send_kill, utf8)) exit(1); From ba484d13896255b74533fe48b7fc11043927c190 Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 13 Dec 2015 10:03:56 +0100 Subject: [PATCH 29/30] man/empire: Explain restricted mode a bit better Signed-off-by: Markus Armbruster --- man/empire.6 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/man/empire.6 b/man/empire.6 index ff6a6875..a1eff5d9 100644 --- a/man/empire.6 +++ b/man/empire.6 @@ -36,7 +36,10 @@ Only available when compiled the GNU \fBreadline\fP library. If someone else is connected to your country, kill their connection. .TP .B \-r -Restricted mode: disable redirections and execute command. +Restricted mode: disable redirections and execute command. This is +useful when you want to grant somebody access to just Empire, but not +to the host system's user account that runs the client. Be careful +with \fB\-H\fP and \fB\-2\fP then. .TP .BI \-s " [host:]port" Specify server \fIhost\fR and \fIport\fR. From 6a0f9d9874276d25ec9d53b77bcbba994ec0c51a Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 13 Dec 2015 17:39:22 +0100 Subject: [PATCH 30/30] client: Support $if Empire in .inputrc Set the application name to "Empire" to support Empire-specific customization of readline. Use in .inputrc looks like this: $if Empire set bell-style audible set history-size 500 else set bell-style visible $endif Signed-off-by: Markus Armbruster --- man/empire.6 | 3 ++- src/client/play.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/man/empire.6 b/man/empire.6 index a1eff5d9..764811e6 100644 --- a/man/empire.6 +++ b/man/empire.6 @@ -91,7 +91,8 @@ The filename for the \fBreadline\fP startup file, overriding the default of \fI~/.inputrc\fP (see \fBREADLINE\fP below). .SH READLINE When compiled with the GNU \fBreadline\fP library, the client supports -fancy line editing and persistent history. See the readline +fancy line editing and persistent history. Its application name for +application-specific settings is \fBEmpire\fP. See the readline documentation for details. .SH "SEE ALSO" \fIemp_server\fR(6), \fIreadline\fR(3). diff --git a/src/client/play.c b/src/client/play.c index bd00768b..5349f323 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -567,6 +567,7 @@ play(int sock, char *history_file) if (isatty(0)) { use_readline = 1; rl_already_prompted = 1; + rl_readline_name = "Empire"; if (history_file) read_history(history_file); rl_bind_key('\t', rl_insert); /* Disable tab completion */