From: Markus Armbruster Date: Sat, 8 Jul 2017 18:43:29 +0000 (+0200) Subject: Merge branch 'readline' X-Git-Tag: v4.4.0~65 X-Git-Url: http://git.pond.sub.org/?p=empserver;a=commitdiff_plain;h=d3a64a4f6e8aa1dc1ee027086aa9b280864e80e4;hp=92693cba65c241b52f9431c03a3a320bd2e0e61d Merge branch 'readline' --- diff --git a/Make.mk b/Make.mk index 5746cc380..8c921d309 100644 --- a/Make.mk +++ b/Make.mk @@ -323,7 +323,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) @@ -398,8 +398,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 \ @@ -457,5 +458,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_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 8c1c6cd28..a30010816 100644 --- a/configure.ac +++ b/configure.ac @@ -79,6 +79,7 @@ LIBS_util="$LIBS" LIBS="$LIBS_SOCKETS $LIBS" AX_LIB_SOCKET_NSL LIBS_server="$LIBS" +MY_WITH_READLINE ### Checks for header files @@ -240,6 +241,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/include/fnameat.h b/include/fnameat.h new file mode 100644 index 000000000..5bd91c29f --- /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 0471335bc..ec89b40df 100644 --- a/include/prototypes.h +++ b/include/prototypes.h @@ -285,8 +285,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/m4/my_lib_readline.m4 b/m4/my_lib_readline.m4 new file mode 100644 index 000000000..37fedec56 --- /dev/null +++ b/m4/my_lib_readline.m4 @@ -0,0 +1,40 @@ +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 +]) + +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/m4/my_terminfo.m4 b/m4/my_terminfo.m4 index 844bda3e5..f4d33bdfe 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 diff --git a/man/empire.6 b/man/empire.6 index 4be599412..764811e67 100644 --- a/man/empire.6 +++ b/man/empire.6 @@ -5,6 +5,7 @@ empire \- Empire client .B empire [\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,11 +25,21 @@ the thin veneer of civilization that hides the maniac within. .B \-h Help. Print brief usage information and exit. .TP +.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 .B \-k 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. @@ -74,8 +85,21 @@ 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 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). +\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 @@ -85,6 +109,7 @@ Jeff Anton Markus Armbruster Phill Everson Steven Grimm +Martin Haukeli Lewis R. Jansen Mike St. Johns Ron Koenderink diff --git a/src/client/Makefile.in b/src/client/Makefile.in index 998e737a8..e7154c311 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/configure.ac b/src/client/configure.ac index 9352989f0..f529d721d 100644 --- a/src/client/configure.ac +++ b/src/client/configure.ac @@ -60,6 +60,7 @@ if test "$Windows_API" = yes; then AC_LIBOBJ([w32/w32io]) AC_LIBOBJ([w32/w32sockets]) fi +MY_WITH_READLINE ### Checks for header files. @@ -97,6 +98,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]) diff --git a/src/client/main.c b/src/client/main.c index f7c1c5344..9cfba7112 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 @@ -40,11 +41,13 @@ #include #ifdef _WIN32 #include +#include #include "sys/socket.h" #else #include #endif #include +#include "fnameat.h" #include "misc.h" #include "version.h" @@ -55,6 +58,7 @@ struct passwd { char *pw_name; + char *pw_dir; }; static struct passwd *w32_getpw(void); @@ -72,6 +76,10 @@ 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_LIBREADLINE + " -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", program_name); @@ -82,6 +90,7 @@ main(int argc, char **argv) { int opt; char *auxfname = NULL; + char *history_file = NULL; int send_kill = 0; char *host = NULL; char *port = NULL; @@ -90,14 +99,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:H:krs:uhv")) != EOF) { switch (opt) { case '2': auxfname = optarg; break; +#ifdef HAVE_LIBREADLINE + case 'H': + history_file = optarg; + break; +#endif /* HAVE_LIBREADLINE */ case 'k': send_kill = 1; break; @@ -146,7 +161,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 +170,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,10 +191,15 @@ main(int argc, char **argv) sock = tcp_connect(host, port); + 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); - if (play(sock) < 0) + if (play(sock, history_file) < 0) exit(1); return 0; @@ -183,12 +207,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 +224,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 c7a476d08..e92454a13 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,8 +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; @@ -61,9 +60,10 @@ 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 prompt(int, char *, char *); 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 3f6400177..87a698662 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -27,8 +27,9 @@ * play.c: Playing the game * * Known contributors to this file: - * Markus Armbruster, 2007-2010 + * Markus Armbruster, 2007-2017 * Ron Koenderink, 2007-2009 + * Martin Haukeli, 2015 */ #include @@ -51,6 +52,24 @@ #include "ringbuf.h" #include "secure.h" +#ifdef HAVE_LIBREADLINE +#include +#include +#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 + */ +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,13 +320,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" - -int input_fd; -int send_eof; /* need to send EOF_COOKIE */ -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. @@ -335,7 +347,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)); @@ -381,7 +393,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; } @@ -397,28 +420,73 @@ recv_output(int sock) return n; } +#ifdef HAVE_LIBREADLINE +static int use_readline; +static char *input_from_rl; +static int has_rl_input; + +static void +input_handler(char *line) +{ + input_from_rl = line; + has_rl_input = 1; + if (line && *line) + add_history(line); +} + +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 */ + /* - * 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 recv_input(int fd, struct ring *inbuf) { - static struct lbuf cmdbuf; - int n, i, ch; - char *line; + int n; int res = 1; - n = ring_from_file(inbuf, fd); +#ifdef HAVE_LIBREADLINE + if (fd == 0 && use_readline) { + if (!has_rl_input) + rl_callback_read_char(); + if (!has_rl_input) + return 1; + if (input_from_rl) { + n = ring_from_rl(inbuf); + } else + n = 0; + } else +#endif + 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. @@ -426,19 +494,36 @@ 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); + return res; +} + +static int +send_input(int fd, struct ring *inbuf) +{ + struct iovec iov[2]; + int cnt, i, ch; + ssize_t res; + + cnt = ring_to_iovec(inbuf, iov); + res = writev(fd, iov, cnt); + if (res < 0) + return res; + + /* Copy input to @auxfp etc. */ + for (i = 0; i < res; i++) { + ch = ring_getc(inbuf); 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); } +#ifdef HAVE_LIBREADLINE + if (fd == 0 && use_readline && has_rl_input && input_from_rl) + ring_from_rl(inbuf); +#endif + return res; } @@ -450,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 @@ -466,8 +552,10 @@ 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; + int ret = -1; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); @@ -475,6 +563,17 @@ play(int sock) sigaction(SIGINT, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); +#ifdef HAVE_LIBREADLINE + 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 */ + rl_callback_handler_install("", input_handler); + } +#endif /* HAVE_LIBREADLINE */ ring_init(&inbuf); eof_fd0 = partial_line_sent = send_eof = send_intr = 0; @@ -487,10 +586,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)) @@ -502,7 +602,7 @@ play(int sock) if (n < 0) { if (errno != EINTR) { perror("select"); - return -1; + break; } } @@ -511,51 +611,52 @@ 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; if (input_fd) { /* execute aborted, switch back to fd 0 */ close(input_fd); - input_fd = eof_fd0 ? -1 : 0; + input_fd = 0; } } - if (n < 0) continue; /* read player input */ - if (input_fd >= 0 && FD_ISSET(input_fd, &rdfd)) { + if (FD_ISSET(input_fd, &rdfd) && ring_space(&inbuf)) { 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 = 1; 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 = 1; eof_fd0 = 1; - input_fd = -1; sa.sa_handler = SIG_DFL; sigaction(SIGINT, &sa, NULL); + send_intr = 0; } - } else + } else if (ring_len(&inbuf) > 0) partial_line_sent = ring_peek(&inbuf, -1) != '\n'; } /* 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; + break; } } @@ -564,10 +665,43 @@ play(int sock) n = recv_output(sock); if (n < 0) { perror("read socket"); - return -1; + break; + } + if (n == 0) { + ret = 0; + break; } - if (n == 0) - return 0; } } + +#ifdef HAVE_LIBREADLINE + if (use_readline) { + rl_callback_handler_remove(); + if (history_file) + write_history(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 + 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); + } } diff --git a/src/client/ringbuf.c b/src/client/ringbuf.c index 9e04bc81a..9782d9143 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-2017 */ #include @@ -35,7 +35,6 @@ #include #include #include -#include #include "ringbuf.h" /* @@ -82,9 +81,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]; } @@ -149,16 +156,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]) @@ -206,39 +214,29 @@ 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) +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; 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; - cnt = 2; - } else { + if (prod > cons) { /* r->buf[cons..prod-1] */ iov[0].iov_len = prod - cons; - cnt = 1; + return 1; } - res = writev(fd, iov, cnt); - if (res < 0) - return res; - r->cons += res; - return res; + /* 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; } diff --git a/src/client/ringbuf.h b/src/client/ringbuf.h index a3794e8bd..9d1a58bf8 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 + * Markus Armbruster, 2007-2017 */ #ifndef RINGBUF_H #define RINGBUF_H #include +#include #define RING_SIZE 4096 @@ -59,8 +60,8 @@ 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); +extern int ring_to_iovec(struct ring *, struct iovec[]); #endif diff --git a/src/client/secure.c b/src/client/secure.c index 808301882..14bb6d1fe 100644 --- a/src/client/secure.c +++ b/src/client/secure.c @@ -27,79 +27,81 @@ * secure.c: Check redir etc. to protect against tampering deity * * Known contributors to this file: - * Markus Armbruster, 2007 + * Markus Armbruster, 2007-2017 */ #include #include +#include +#include #include #include "ringbuf.h" #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. + * Remember input @inp for a while. */ -size_t -save_input(char *inp) +void +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) { - eol = ring_search(&recent_input, "\n"); + while (ring_putc(&recent_input, inp) < 0) { + eol = ring_search(&recent_input, "\n", 0); 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; + return ring_search(&recent_input, tail, 0) >= 0; } /* - * Forget remembered input up to @seen. - * @seen should be obtained from save_input() or seen_input(). + * Can you still remember input that looks like an execute @arg? + * @arg must end with a newline. */ -void -forget_input(size_t seen) +int +seen_exec_input(char *arg) { - size_t forgotten = saved_bytes - ring_len(&recent_input); + 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; - assert(seen); + /* 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; - if (seen > forgotten) { - assert(ring_peek(&recent_input, seen - forgotten - 1) == '\n'); - ring_discard(&recent_input, seen - forgotten); + /* 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 425cd7976..60986ac50 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,8 +35,8 @@ #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 *); +extern int seen_exec_input(char *); #endif diff --git a/src/client/servcmd.c b/src/client/servcmd.c index f2d0e3fc6..fd83f9036 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-2017 */ #include @@ -41,6 +41,7 @@ #include #include #include +#include #include "misc.h" #include "proto.h" #include "secure.h" @@ -52,14 +53,12 @@ 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); static void dopipe(char *p); static int doexecute(char *p); -void +int servercmd(int code, char *arg, int len) { static int nmin, nbtu, fd; @@ -69,7 +68,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\n", arg); + fprintf(stderr, "Warning: server sent malformed prompt %s", + arg); } snprintf(the_prompt, sizeof(the_prompt), "[%d:%d] Command : ", nmin, nbtu); @@ -80,10 +80,7 @@ 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; - } + outch('\n'); prompt(code, the_prompt, teles); executing = 0; break; @@ -93,13 +90,9 @@ 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) executing = 1; - } - break; + return fd; case C_EXIT: printf("Exit: %s", arg); if (auxfp) @@ -114,6 +107,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); } @@ -130,20 +124,8 @@ servercmd(int code, char *arg, int len) assert(0); break; } -} -static void -prompt(int code, char *prompt, char *teles) -{ - char *nl; - - nl = code == C_PROMPT || code == C_INFORM ? "\n" : ""; - printf("%s%s%s", nl, teles, prompt); - fflush(stdout); - if (auxfp) { - fprintf(auxfp, "%s%s%s", nl, teles, prompt); - fflush(auxfp); - } + return 0; } static char * @@ -158,10 +140,8 @@ fname(char *s) } static int -redir_authorized(char *arg, char *attempt, int expected) +common_authorized(char *arg, char *attempt) { - size_t seen = seen_input(arg); - if (restricted) { fprintf(stderr, "Can't %s in restricted mode\n", attempt); return 0; @@ -171,20 +151,37 @@ redir_authorized(char *arg, char *attempt, int expected) fprintf(stderr, "Can't %s in a batch file\n", attempt); return 0; } + return 1; +} - if (!expected) { - fprintf(stderr, "WARNING! Server attempted to %s unexpectedly\n", - attempt); +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_to_forget && input_to_forget != seen)) { - fprintf(stderr, "WARNING! Server attempted to %s %s\n", + if (!seen_input(arg)) { + fprintf(stderr, "Warning: server attempted to %s %s", attempt, arg); return 0; } - input_to_forget = seen; - return 1; + + 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 @@ -193,10 +190,10 @@ 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! Weird redirection %s", p); + fprintf(stderr, "Warning: dropped weird redirection %s", p); return; } @@ -228,10 +225,10 @@ 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! Weird pipe %s", p); + fprintf(stderr, "Warning: dropped weird pipe %s", p); return; } @@ -241,10 +238,14 @@ dopipe(char *p) return; } + /* strip newline */ + 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) : ""); } } @@ -253,7 +254,7 @@ doexecute(char *p) { int fd; - if (!redir_authorized(p, "execute batch file", 1)) + if (!exec_authorized(p)) return -1; p = fname(p); @@ -263,7 +264,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; } diff --git a/src/lib/common/conftab.c b/src/lib/common/conftab.c index 72f1d62d2..ae79d13cb 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 fa9fb4f59..90299ddb2 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 098b03224..5a44bb25f 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 *);