$(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)
$(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 \
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 $^ >$@
LIBS="$LIBS_SOCKETS $LIBS"
AX_LIB_SOCKET_NSL
LIBS_server="$LIBS"
+MY_WITH_READLINE
### Checks for header files
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])
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * ---
+ *
+ * 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 <stdio.h>
+
+extern char *fnameat(const char *, const char *);
+extern FILE *fopenat(const char *, const char *, const char *);
+
+#endif
/* 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);
--- /dev/null
+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])
[
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
.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]]
.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.
.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 <mr-frog@scam.berkeley.edu>
Markus Armbruster <armbru@pond.sub.org>
Phill Everson <everson@compsci.bristol.ac.uk>
Steven Grimm <koreth@ucscb.UCSC.EDU>
+Martin Haukeli <martin.haukeli@gmail.com>
Lewis R. Jansen <lrj@helios.tn.cornell.edu>
Mike St. Johns <stjohns@edn-vax.arpa>
Ron Koenderink <rkoenderink@yahoo.ca>
# Makefile.in: Makefile template for configure
#
# Known contributors to this file:
-# Markus Armbruster, 2005-2013
+# Markus Armbruster, 2005-2015
#
CC = @CC@
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)
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
AC_LIBOBJ([w32/w32io])
AC_LIBOBJ([w32/w32sockets])
fi
+MY_WITH_READLINE
### Checks for header files.
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])
* 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 <config.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
+#include <shlobj.h>
#include "sys/socket.h"
#else
#include <pwd.h>
#endif
#include <unistd.h>
+#include "fnameat.h"
#include "misc.h"
#include "version.h"
struct passwd {
char *pw_name;
+ char *pw_dir;
};
static struct passwd *w32_getpw(void);
" -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);
{
int opt;
char *auxfname = NULL;
+ char *history_file = NULL;
int send_kill = 0;
char *host = NULL;
char *port = NULL;
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;
if (!host)
host = empirehost;
uname = getenv("LOGNAME");
- if (uname == NULL) {
+ udir = getenv("HOME");
+ if (!uname || !udir) {
struct passwd *pwd;
pwd = getpwuid(getuid());
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);
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;
#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;
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;
}
*
* Known contributors to this file:
* Steve McClure, 1998
+ * Markus Armbruster, 2004-2017
*/
#ifndef MISC_H
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;
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
* 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 <config.h>
#include "ringbuf.h"
#include "secure.h"
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#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;
#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.
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));
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;
}
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.
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;
}
/*
* 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
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);
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;
/*
* 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))
if (n < 0) {
if (errno != EINTR) {
perror("select");
- return -1;
+ break;
}
}
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;
}
}
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);
+ }
}
* ringbuf.c: Simple ring buffer
*
* Known contributors to this file:
- * Markus Armbruster, 2007-2009
+ * Markus Armbruster, 2007-2017
*/
#include <config.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
-#include <sys/uio.h>
#include "ringbuf.h"
/*
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];
}
/*
* 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])
}
/*
- * 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;
}
* ringbuf.h: Simple ring buffer
*
* Known contributors to this file:
- * Markus Armbruster, 2007
+ * Markus Armbruster, 2007-2017
*/
#ifndef RINGBUF_H
#define RINGBUF_H
#include <stddef.h>
+#include <sys/uio.h>
#define RING_SIZE 4096
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
* secure.c: Check redir etc. to protect against tampering deity
*
* Known contributors to this file:
- * Markus Armbruster, 2007
+ * Markus Armbruster, 2007-2017
*/
#include <config.h>
#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
#include <string.h>
#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;
}
}
* 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
#include <stddef.h>
-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
* Dave Pare, 1989
* Steve McClure, 1998
* Ron Koenderink, 2005
- * Markus Armbruster, 2005-2010
+ * Markus Armbruster, 2005-2017
*/
#include <config.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
+#include <unistd.h>
#include "misc.h"
#include "proto.h"
#include "secure.h"
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;
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);
(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;
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)
if (arg[0] != '\n') {
snprintf(teles, sizeof(teles), "(%.*s) ", len - 1, arg);
if (!redir_fp) {
+ outch('\n');
putchar('\07');
prompt(code, the_prompt, teles);
}
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 *
}
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;
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
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;
}
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;
}
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) : "");
}
}
{
int fd;
- if (!redir_authorized(p, "execute batch file", 1))
+ if (!exec_authorized(p))
return -1;
p = fname(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;
}
#include <errno.h>
#include <stdio.h>
#include "file.h"
+#include "fnameat.h"
#include "optlist.h"
#include "prototypes.h"
#include "xdump.h"
#include <string.h>
#include <unistd.h>
+#include "fnameat.h"
#include "misc.h"
#include "optlist.h"
#include "prototypes.h"
#include <config.h>
#include <errno.h>
-#include "prototypes.h"
+#include <stdlib.h>
+#include <string.h>
+#include "fnameat.h"
static int fname_is_abs(const char *);