1 /*
2  *  beam.c  --  code to beam texts across the TCP connection following a
3  *              special protocol
4  *
5  *  Copyright (C) 1998 by Massimiliano Ghilardi
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  */
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <ctype.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <signal.h>
21 #include <time.h>
22 #include <unistd.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <arpa/telnet.h>
27 
28 #include "defines.h"
29 #include "main.h"
30 #include "utils.h"
31 #include "beam.h"
32 #include "map.h"
33 #include "tcp.h"
34 #include "tty.h"
35 #include "edit.h"
36 #include "eval.h"
37 
38 editsess *edit_sess;	/* head of session list */
39 
40 char edit_start[BUFSIZE]; /* messages to send to host when starting */
41 char edit_end[BUFSIZE];   /* or leaving editing sessions */
42 
__P1(char *,s)43 static void write_message __P1 (char *,s)
44 {
45     clear_input_line(opt_compact);
46     if (!opt_compact) {
47 	tty_putc('\n');
48 	status(1);
49     }
50     tty_puts(s);
51 }
52 
53 /*
54  * Process editing protocol message from buf with len remaining chars.
55  * Return number of characters used in the message.
56  */
__P2(char *,buf,int,len)57 int process_message __P2 (char *,buf, int,len)
58 {
59     int msglen, i, l, used;
60     char *text, *from, *to;
61     char msg[BUFSIZE];
62     int got_iac;
63 
64     status(1);
65 
66     msglen = atoi(buf + 1);
67     for (i = 1; i < len && isdigit(buf[i]); i++)
68       ;
69 
70     if (i < len && buf[i] == '\r') i++;
71     if (i >= len || buf[i] != '\n') {
72 	write_message ("#warning: MPI protocol error\n");
73 	return 0;
74     }
75 
76     l = len - ++i;
77 
78     text = (char *)malloc(msglen);
79     if (!text) {
80 	errmsg("malloc");
81 	return i;
82     }
83 
84     /* only doing trivial (IAC IAC) processing; this should all be
85      * rewritten */
86     got_iac = 0;
87     from = buf + i;
88     to = text;
89     for (i = 0; i < l && (to - text) < msglen; ++i) {
90         if (got_iac) {
91             if (*from != (char)IAC)
92                 errmsg("got IAC in MPI message; treating as IAC IAC");
93             else
94                 ++from;
95             got_iac = 0;
96         } else {
97             got_iac = (*to++ = *from++) == (char)IAC;
98         }
99     }
100     i = to - text;
101     used = from - buf;
102 
103     while (i < msglen) {
104 	/* read() might block here, but it should't be long */
105 	while ((l = read(tcp_fd, text + i, msglen - i)) == -1 && errno == EINTR)
106 	    ;
107 	if (l == -1) {
108 	    errmsg("read message from socket");
109 	    return used > len ? len : used;
110 	}
111         from = to = text + i;
112         for (i = 0; i < l; ++i) {
113             if (got_iac) {
114                 if (*from != (char)IAC)
115                     errmsg("got IAC in MPI message; treating as IAC IAC");
116                 else
117                     ++from;
118                 got_iac = 0;
119             } else {
120                 got_iac = (*to++ = *from++) == (char)IAC;
121             }
122         }
123         i = to - text;
124 	tty_printf("\rgot %d chars out of %d", i, msglen);
125 	tty_flush();
126     }
127     tty_printf("\rread all %d chars.%s\n", msglen, tty_clreoln);
128 
129     switch(*buf) {
130     case 'E':
131         message_edit(text, msglen, 0, 0);
132         break;
133     case 'V':
134         message_edit(text, msglen, 1, 0);
135         break;
136     default:
137         sprintf(msg, "#warning: received a funny message (0x%x)\n", *buf);
138         write_message(msg);
139         free(text);
140         break;
141     }
142 
143     return used;
144 }
145 
146 /*
147  * abort an editing session when
148  * the MUD socket it comes from gets closed
149  */
__P1(int,fd)150 void abort_edit_fd __P1 (int,fd)
151 {
152     editsess **sp, *p;
153 
154     if (fd < 0)
155 	return;
156 
157     for (sp = &edit_sess; *sp; sp = &(*sp)->next) {
158 	p = *sp;
159 	if (p->fd != fd)
160 	    continue;
161 
162 	if (kill(p->pid, SIGKILL) < 0) {       /* Editicide */
163 	    errmsg("kill editor child");
164 	    continue;
165 	}
166 
167 	PRINTF("#aborted \"%s\" (%u)\n", p->descr, p->key);
168 	p->cancel = 1;
169     }
170 }
171 
172 /*
173  * cancel an editing session; does not free anything
174  * (the child death signal handler will remove the session from the list)
175  */
__P1(editsess *,sp)176 void cancel_edit __P1 (editsess *,sp)
177 {
178     char buf[BUFSIZE];
179     char keystr[16];
180 
181     if (kill(sp->pid, SIGKILL) < 0) {       /* Editicide */
182 	errmsg("kill editor child");
183 	return;
184     }
185     PRINTF("#killed \"%s\" (%u)\n", sp->descr, sp->key);
186     sprintf(keystr, "C%u\n", sp->key);
187     sprintf(buf, "%sE%d\n%s", MPI, (int)strlen(keystr), keystr);
188     tcp_write(sp->fd, buf);
189     sp->cancel = 1;
190 }
191 
read_file(int fd,void * buf,size_t count)192 static ssize_t read_file(int fd, void *buf, size_t count)
193 {
194     size_t result = 0;
195     while (count > 0) {
196         int r;
197         do {
198             r = read(fd, buf, count);
199         } while (r < 0 && errno == EINTR);
200         if (r < 0)
201             return -1;
202         if (r == 0)
203             break;
204         result += r;
205         buf += r;
206         count -= r;
207     }
208     return result;
209 }
210 
211 /*
212  * send back edited text to server, or cancel the editing session if the
213  * file was not changed.
214  */
__P1(editsess *,sp)215 static void finish_edit __P1 (editsess *,sp)
216 {
217     char *realtext = NULL, *text;
218     int fd, txtlen, hdrlen;
219     struct stat sbuf;
220     char keystr[16], buf[256], hdr[65];
221 
222     if (sp->fd == -1)
223 	goto cleanup_file;
224 
225     fd = open(sp->file, O_RDONLY);
226     if (fd == -1) {
227 	errmsg("open edit file");
228 	goto cleanup_file;
229     }
230     if (fstat(fd, &sbuf) == -1) {
231 	errmsg("fstat edit file");
232 	goto cleanup_fd;
233     }
234 
235     txtlen = sbuf.st_size;
236 
237     if (!sp->cancel && (sbuf.st_mtime > sp->ctime || txtlen != sp->oldsize)) {
238 	/* file was changed by editor: send back result to server */
239 
240 	realtext = (char *)malloc(txtlen + 65);
241 	/* +1 is for possible LF, +64 for header */
242 	if (!realtext) {
243 	    errmsg("malloc");
244 	    goto cleanup_fd;
245 	}
246 
247 	text = realtext + 64;
248         txtlen = read_file(fd, text, txtlen);
249         if (txtlen < 0)
250 	    goto cleanup_text;
251 
252 	if (txtlen && text[txtlen - 1] != '\n') {
253 	    /* If the last line isn't LF-terminated, add an LF;
254 	     * however, empty files must remain empty */
255 	    text[txtlen] = '\n';
256 	    txtlen++;
257 	}
258 
259 	sprintf(keystr, "E%u\n", sp->key);
260 
261 	sprintf(hdr, "%sE%d\n%s", MPI, (int)(txtlen + strlen(keystr)), keystr);
262 
263 	text -= (hdrlen = strlen(hdr));
264 	memcpy(text, hdr, hdrlen);
265 
266 	/* text[hdrlen + txtlen] = '\0'; */
267 	tcp_write_escape_iac(sp->fd, text, hdrlen + txtlen);
268 
269 	sprintf(buf, "#completed session %s (%u)\n", sp->descr, sp->key);
270 	write_message(buf);
271     } else {
272 	/* file wasn't changed, cancel editing session */
273 	sprintf(keystr, "C%u\n", sp->key);
274 	sprintf(hdr, "%sE%d\n%s", MPI, (int) strlen(keystr), keystr);
275 
276 	tcp_raw_write(sp->fd, hdr, strlen(hdr));
277 
278 	sprintf(buf, "#cancelled session %s (%u)\n", sp->descr, sp->key);
279 	write_message(buf);
280     }
281 
282 cleanup_text: if (realtext) free(realtext);
283 cleanup_fd:   close(fd);
284 cleanup_file: if (unlink(sp->file) < 0)
285 		errmsg("unlink edit file");
286 }
287 
288 /*
289  * start an editing session: process the EDIT/VIEW message
290  * if view == 1, text will be viewed, else edited
291  */
__P4(char *,text,int,msglen,char,view,char,builtin)292 void message_edit __P4 (char *,text, int,msglen, char,view, char,builtin)
293 {
294     char tmpname[BUFSIZE], command_str[BUFSIZE], buf[BUFSIZE];
295     char *errdesc = "#warning: protocol error (message_edit, no %s)\n";
296     int tmpfd, i, childpid;
297     unsigned int key;
298     editsess *s;
299     char *editor, *descr;
300     char *args[4];
301     int waitforeditor;
302 
303     status(1);
304 
305     args[0] = "/bin/sh"; args[1] = "-c";
306     args[2] = command_str; args[3] = 0;
307 
308     if (view) {
309 	key = (unsigned int)-1;
310 	i = 0;
311     } else {
312 	if (text[0] != 'M') {
313 	    tty_printf(errdesc, "M");
314 	    free(text);
315 	    return;
316 	}
317 	for (i = 1; i < msglen && isdigit(text[i]); i++)
318 	    ;
319 	if (text[i++] != '\n' || i >= msglen) {
320 	    tty_printf(errdesc, "\\n");
321 	    free(text);
322 	    return;
323 	}
324 	key = strtoul(text + 1, NULL, 10);
325     }
326     descr = text + i;
327     while (i < msglen && text[i] != '\n') i++;
328     if (i >= msglen) {
329 	tty_printf(errdesc, "desc");
330 	free(text);
331 	return;
332     }
333     text[i++] = '\0';
334 
335     sprintf(tmpname, "/tmp/powwow.%u.%d%d", key, getpid(), abs(rand()) >> 8);
336     if ((tmpfd = open(tmpname, O_WRONLY | O_CREAT, 0600)) < 0) {
337 	errmsg("create temp edit file");
338 	free(text);
339 	return;
340     }
341     if (write(tmpfd, text + i, msglen - i) < msglen - i) {
342 	errmsg("write to temp edit file");
343 	free(text);
344 	close(tmpfd);
345 	return;
346     }
347     close(tmpfd);
348 
349     s = (editsess*)malloc(sizeof(editsess));
350     if (!s) {
351 	errmsg("malloc");
352 	return;
353     }
354     s->ctime = time((time_t*)NULL);
355     s->oldsize = msglen - i;
356     s->key = key;
357     s->fd = (view || builtin) ? -1 : tcp_fd; /* MUME doesn't expect a reply. */
358     s->cancel = 0;
359     s->descr = my_strdup(descr);
360     s->file = my_strdup(tmpname);
361     free(text);
362 
363     /* send a edit_start message (if wanted) */
364     if ((!edit_sess) && (*edit_start)) {
365 	error = 0;
366 	parse_instruction(edit_start, 0, 0, 1);
367 	history_done = 0;
368     }
369 
370     if (view) {
371 	if (!(editor = getenv("POWWOWPAGER")) && !(editor = getenv("PAGER")))
372 	    editor = "more";
373     } else {
374 	if (!(editor = getenv("POWWOWEDITOR")) && !(editor = getenv("EDITOR")))
375 	    editor = "emacs";
376     }
377 
378     if (editor[0] == '&') {
379 	waitforeditor = 0;
380 	editor++;
381     } else
382 	waitforeditor = 1;
383 
384     if (waitforeditor) {
385 	tty_quit();
386 	/* ignore SIGINT since interrupting the child would interrupt us too,
387 	 if we are in the same tty group */
388 	sig_permanent(SIGINT, SIG_IGN);
389 	sig_permanent(SIGCHLD, SIG_DFL);
390     }
391 
392     switch(childpid = fork()) {		/* let's get schizophrenic */
393       case 0:
394 	sprintf(command_str, "%s %s", editor, s->file);
395 	sprintf(buf, "TITLE=%s", s->descr);
396 	putenv(buf);
397 	/*	   setenv("TITLE", s->descr, 1);*/
398 	execvp((char *)args[0], (char **)args);
399 	syserr("execve");
400 	break;
401       case -1:
402 	errmsg("fork");
403 	free(s->descr);
404 	free(s->file);
405 	free(s);
406 	return;
407     }
408     s->pid = childpid;
409     if (waitforeditor) {
410 	while ((i = waitpid(childpid, (int*)NULL, 0)) == -1 && errno == EINTR)
411 	    ;
412 
413 	signal_start();		/* reset SIGINT and SIGCHLD handlers */
414 	tty_start();
415 
416 	if (s->fd != -1) {
417 	    tty_gotoxy(0, lines - 1);
418 	    tty_putc('\n');
419 	}
420 
421 	if (i == -1)
422 	    errmsg("waitpid");
423 	else
424 	    finish_edit(s);
425 
426 	free(s->descr);
427 	free(s->file);
428 	if (i != -1 && !edit_sess && *edit_end) {
429 	    error = 0;
430 	    parse_instruction(edit_end, 0, 0, 1);
431 	    history_done = 0;
432 	}
433 
434 	free(s);
435 
436     } else {
437 	s->next = edit_sess;
438 	edit_sess = s;
439     }
440 }
441 
442 /*
443  * Our child has snuffed it. check if it was an editor, and update the
444  * session list if that is the case.
445  */
__P0(void)446 void sig_chld_bottomhalf __P0 (void)
447 {
448     int fd, pid, ret;
449     editsess **sp, *p;
450 
451     /* GH: while() instead of just one check */
452     while ((pid = waitpid(-1, &ret, WNOHANG)) > 0) {
453 
454 	/* GH: check for WIFSTOPPED unnecessary since no */
455 	/* WUNTRACED to waitpid()			 */
456 	for (sp = &edit_sess; *sp && (*sp)->pid != pid; sp = &(*sp)->next)
457 	    ;
458 	if (*sp) {
459 	    finish_edit(*sp);
460 	    p = *sp; *sp = p->next;
461 	    fd = p->fd;
462 	    free(p->descr);
463 	    free(p->file);
464 	    free(p);
465 
466 	    /* GH: only send message if found matching session */
467 
468 	    /* send the edit_end message if this is the last editor... */
469 	    if ((!edit_sess) && (*edit_end)) {
470 		int otcp_fd = tcp_fd;  /* backup current socket fd */
471 		tcp_fd = fd;
472 		error = 0;
473 		parse_instruction(edit_end, 0, 0, 1);
474 		history_done = 0;
475 		tcp_fd = otcp_fd;
476 	    }
477 	}
478     }
479 }
480 
481