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