]> git.pond.sub.org Git - empserver/blob - src/client/play.c
Flaws in the Empire protocol make redirections within execute next to
[empserver] / src / client / play.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2007, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                           Ken Stevens, Steve McClure
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  *  ---
21  *
22  *  See files README, COPYING and CREDITS in the root of the source
23  *  tree for related information and legal notices.  It is expected
24  *  that future projects/authors will amend these files as needed.
25  *
26  *  ---
27  *
28  *  play.c: Playing the game
29  * 
30  *  Known contributors to this file:
31  *     Markus Armbruster, 2007
32  */
33
34 #include <config.h>
35
36 #include <assert.h>
37 #include <errno.h>
38 #include <signal.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <sys/time.h>
42 #include <sys/types.h>
43 #include <unistd.h>
44 #include "linebuf.h"
45 #include "misc.h"
46 #include "proto.h"
47 #include "ringbuf.h"
48 #include "secure.h"
49
50 #define EOF_COOKIE "ctld\n"
51 #define INTR_COOKIE "\naborted\n"
52
53 int input_fd;
54 int send_eof;                           /* need to send EOF_COOKIE */
55 static volatile sig_atomic_t send_intr; /* need to send INTR_COOKIE */
56
57 /*
58  * Receive and process server output from SOCK.
59  * Return number of characters received on success, -1 on error.
60  */
61 static int
62 recv_output(int sock)
63 {
64     /*
65      * Read a chunk of server output and feed its characters into a
66      * simple state machine.
67      * Initial state is SCANNING_ID.
68      * In state SCANNING_ID, buffer the character.  If it's a space,
69      * decode the id that has been buffered, and enter state BUFFERING
70      * or COPYING depending on its value.
71      * In state BUFFERING, buffer the character.  If it's newline,
72      * pass id and buffered text to servercmd(), then enter state
73      * SCANNING_ID.
74      * In state COPYING, pass the character to outch().  If it's
75      * newline, enter state SCANNING_ID.
76      */
77     static enum {
78         SCANNING_ID, BUFFERING, COPYING
79     } state = SCANNING_ID;
80     static int id;
81     static struct lbuf lbuf;
82     char buf[4096];
83     ssize_t n;
84     int i, ch, len;
85     char *line, *end;
86
87     n = read(sock, buf, sizeof(buf));
88     if (n < 0)
89         return -1;
90
91     for (i = 0; i < n; i++) {
92         ch = buf[i];
93         switch (state) {
94         case SCANNING_ID:
95             if (ch == '\n') {
96                 /* FIXME gripe unexpected! */
97                 lbuf_init(&lbuf);
98                 break;
99             }
100             if (ch != ' ') {
101                 lbuf_putc(&lbuf, ch);
102                 break;
103             }
104             line = lbuf_line(&lbuf);
105             id = strtol(line, &end, 16);
106             if (end == line || *end) {
107                 /* FIXME gripe bad id */
108                 id = -1;
109             }
110             lbuf_init(&lbuf);
111
112             switch (id) {
113             case C_PROMPT:
114             case C_FLUSH:
115             case C_EXECUTE:
116             case C_EXIT:
117             case C_FLASH:
118             case C_INFORM:
119             case C_PIPE:
120             case C_REDIR:
121                 state = BUFFERING;
122                 break;
123             default:
124                 /* unknown or unexpected id, treat like C_DATA */
125             case C_DATA:
126                 state = COPYING;
127                 break;
128             }
129             break;
130
131         case BUFFERING:
132             len = lbuf_putc(&lbuf, ch);
133             if (len) {
134                 line = lbuf_line(&lbuf);
135                 servercmd(id, line, len);
136                 lbuf_init(&lbuf);
137                 state = SCANNING_ID;
138             }
139             break;
140
141         case COPYING:
142             outch(ch);
143             if (ch == '\n')
144                 state = SCANNING_ID;
145         }
146     }
147
148     return n;
149 }
150
151 /*
152  * Receive command input from FD into INBUF.
153  * Return 1 on receipt of input, zero on EOF, -1 on error.
154  */
155 static int
156 recv_input(int fd, struct ring *inbuf)
157 {
158     static struct lbuf cmdbuf;
159     int n, i, ch;
160     char *line;
161     int res = 1;
162
163     n = ring_from_file(inbuf, fd);
164     if (n < 0)
165         return -1;
166     if (n == 0) {
167         /* EOF on input */
168         if (lbuf_len(&cmdbuf)) {
169             /* incomplete line */
170             ring_putc(inbuf, '\n');
171             n++;
172         }
173         /*
174          * Can't put EOF cookie into INBUF here, it may not fit.
175          * Leave it to caller.
176          */
177         res = 0;
178     }
179
180     /* copy input to AUXFP etc. */
181     for (i = -n; i < 0; i++) {
182         ch = ring_peek(inbuf, i);
183         assert(ch != EOF);
184         if (lbuf_putc(&cmdbuf, ch)) {
185             line = lbuf_line(&cmdbuf);
186             if (auxfp)
187                 fputs(line, auxfp);
188             save_input(line);
189             lbuf_init(&cmdbuf);
190         }
191     }
192
193     return res;
194 }
195
196 static void
197 intr(int sig)
198 {
199     send_intr = 1;
200 #ifdef _WIN32
201     signal(SIGINT, intr);
202 #endif
203 }
204
205 /*
206  * Play on SOCK.
207  * The session must be in the playing phase.
208  * Return 0 when the session ended, -1 on error.
209  */
210 int
211 play(int sock)
212 {
213     /*
214      * Player input flows from INPUT_FD through recv_input() into ring
215      * buffer INBUF, which drains into SOCK.  This must not block.
216      * Server output flows from SOCK into recv_output().  Reading SOCK
217      * must not block.
218      */
219     struct sigaction sa;
220     struct ring inbuf;          /* input buffer, draining to SOCK */
221     int eof_fd0;                /* read fd 0 hit EOF? */
222     fd_set rdfd, wrfd;
223     int n;
224
225     sa.sa_flags = 0;
226     sa.sa_handler = intr;
227     sigaction(SIGINT, &sa, NULL);
228     sa.sa_handler = SIG_IGN;
229     sigaction(SIGPIPE, &sa, NULL);
230
231     ring_init(&inbuf);
232     eof_fd0 = send_eof = send_intr = 0;
233     input_fd = 0;
234
235     for (;;) {
236         FD_ZERO(&rdfd);
237         FD_ZERO(&wrfd);
238
239         /*
240          * Want to read player input only when we don't need to send
241          * cookies, and we haven't hit EOF on fd 0, and INBUF can
242          * accept some.
243          */
244         if (!send_intr && !send_eof && !eof_fd0 && ring_space(&inbuf))
245             FD_SET(input_fd, &rdfd);
246         /* Want to send player input only when we have something */
247         if (send_intr || send_eof || ring_len(&inbuf))
248             FD_SET(sock, &wrfd);
249         /* Always want to read server output */
250         FD_SET(sock, &rdfd);
251
252         n = select(MAX(input_fd, sock) + 1, &rdfd, &wrfd, NULL, NULL);
253         if (n < 0) {
254             if (errno != EINTR) {
255                 perror("select");
256                 return -1;
257             }
258         }
259
260         if (send_eof
261             && ring_putm(&inbuf, EOF_COOKIE, sizeof(EOF_COOKIE) - 1) >= 0)
262             send_eof--;
263         if (send_intr
264             && ring_putm(&inbuf, INTR_COOKIE, sizeof(INTR_COOKIE) - 1) >= 0)
265             send_intr = 0;
266
267         if (n < 0)
268             continue;
269
270         /* read player input */
271         if (FD_ISSET(input_fd, &rdfd)) {
272             n = recv_input(input_fd, &inbuf);
273             if (n < 0) {
274                 perror("read stdin"); /* FIXME stdin misleading, could be execing */
275                 n = 0;
276             }
277             if (n == 0) {
278                 /* EOF on input */
279                 send_eof++;
280                 if (input_fd) {
281                     /* execute done, switch back to fd 0 */
282                     close(input_fd);
283                     input_fd = 0;
284                 } else {
285                     /* stop reading input, drain socket ring buffers */
286                     eof_fd0 = 1;
287                     sa.sa_handler = SIG_DFL;
288                     sigaction(SIGINT, &sa, NULL);
289                 }
290             }
291         }
292
293         /* send it to the server */
294         if (FD_ISSET(sock, &wrfd)) {
295             n = ring_to_file(&inbuf, sock);
296             if (n < 0) {
297                 perror("write socket");
298                 return -1;
299             }
300         }
301
302         /* read server output and print it */
303         if (FD_ISSET(sock, &rdfd)) {
304             n = recv_output(sock);
305             if (n < 0) {
306                 perror("read socket");
307                 return -1;
308             }
309             if (n == 0)
310                 return 0;
311         }
312     }
313 }