1 /*
2  *  tcp.c  --  telnet protocol communication module for powwow
3  *
4  *  Copyright (C) 1998,2002 by Massimiliano Ghilardi
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  */
12 
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <signal.h>
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <signal.h>
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <sys/time.h>
23 #include <netdb.h>
24 #include <netinet/in.h>
25 #include <netinet/tcp.h>
26 #include <arpa/telnet.h>
27 #ifndef TELOPT_NAWS
28 #  define TELOPT_NAWS 31
29 #endif
30 #include <arpa/inet.h>
31 #ifndef NEXT
32 #  include <unistd.h>
33 #endif
34 
35 #ifdef TERM
36 #  include "client.h"
37 #endif
38 
39 #include "defines.h"
40 #include "main.h"
41 #include "utils.h"
42 #include "tcp.h"
43 #include "tty.h"
44 #include "edit.h"
45 #include "beam.h"
46 #include "log.h"
47 
48 #ifdef TELOPTS
49 # define TELOPTSTR(n) ((n) > NTELOPTS ? "unknown" : telopts[n])
50 #endif
51 
52 int tcp_fd = -1;                   /* current socket file descriptor
53 				    * -1 means no socket */
54 int tcp_main_fd = -1;		   /* socket file descriptor of main connect.
55 				    * -1 means no socket */
56 int tcp_max_fd = 0;		   /* highest used fd */
57 
58 int tcp_count = 0;		   /* number of open connections */
59 int tcp_attachcount = 0;	   /* number of spawned commands */
60 
61 int conn_max_index;		   /* 1 + highest used conn_list[] index */
62 
63 connsess conn_list[MAX_CONNECTS];    /* connection list */
64 
65 byte conn_table[MAX_FDSCAN];	     /* fd -> index translation table */
66 
67 fd_set fdset;			/* set of descriptors to select() on */
68 
69 /*
70  * process suboptions.
71  * so far, only terminal type is processed but future extensions are
72  * window size, X display location, etc.
73  */
__P1(byte *,str)74 static void dosubopt __P1 (byte *,str)
75 {
76     char buf[256], *term;
77     int len, err;
78 
79     if (str[0] == TELOPT_TTYPE) {
80 	if (str[1] == 1) {
81 	    /* 1 == SEND */
82 #ifdef TELOPTS
83 	    tty_printf("[got SB TERMINAL TYPE SEND]\n");
84 #endif
85 	    if (!(term = getenv("TERM"))) term = "unknown";
86 	    sprintf(buf, "%c%c%c%c%.*s%c%c", IAC, SB, TELOPT_TTYPE, 0,
87 		    256-7, term, IAC, SE);	/* 0 == IS */
88 
89 	    len = strlen(term) + 6;
90 	    while ((err = write(tcp_fd, buf, len)) < 0 && errno == EINTR)
91 	        ;
92 	    if (err != len) {
93 	        errmsg("write subopt to socket");
94 		return;
95 	    }
96 #ifdef TELOPTS
97 	    tty_printf("[sent SB TERMINAL TYPE IS %s]\n", term);
98 #endif
99 	}
100     }
101 }
102 
103 /*
104  * send an option negotiation
105  * 'what' is one of WILL, WONT, DO, DONT
106  */
__P2(byte,what,byte,opt)107 static void sendopt __P2 (byte,what, byte,opt)
108 {
109     static byte buf[3] = { IAC, 0, 0 };
110     int i;
111     buf[1] = what; buf[2] = opt;
112 
113     while ((i = write(tcp_fd, buf, 3)) < 0 && errno == EINTR)
114 	;
115     if (i != 3) {
116 	errmsg("write option to socket");
117 	return;
118     }
119 
120 #ifdef TELOPTS
121     tty_printf("[sent %s %s]\n", (what == WILL) ? "WILL" :
122 	       (what == WONT) ? "WONT" :
123 	       (what == DO) ? "DO" : (what == DONT) ? "DONT" : "error",
124 	       TELOPTSTR(opt));
125 #endif
126 }
127 
128 /*
129  * connect to remote host
130  * Warning: some voodoo code here
131  */
__P2(char *,addr,int,port)132 int tcp_connect __P2 (char *,addr, int,port)
133 {
134     struct sockaddr_in address;
135     struct hostent *host_info;
136     int err, newtcp_fd;
137 
138     status(1);
139 
140     memzero((char *)&address, sizeof(address));
141     /*
142      * inet_addr has a strange design: It is documented to return -1 for
143      * malformed requests, but it is declared to return unsigned long!
144      * Anyway, this works.
145      */
146 
147 #ifndef TERM
148     address.sin_addr.s_addr = inet_addr(addr);
149     if (address.sin_addr.s_addr != (unsigned int)-1)
150 	address.sin_family = AF_INET;
151     else
152     {
153 	if (opt_info)
154 	    tty_printf("#looking up %s... ", addr);
155 	tty_flush();
156 	host_info = gethostbyname(addr);
157 	if (host_info == 0) {
158 	    if (!opt_info) {
159 		tty_printf("#looking up %s... ", addr);
160 	    }
161 	    tty_printf("unknown host!\n");
162 	    return -1;
163 	}
164 	memmove((char *)&address.sin_addr, host_info->h_addr,
165 	      host_info->h_length);
166 	address.sin_family = host_info->h_addrtype;
167 	if (opt_info)
168 	    tty_puts("found.\n");
169     }
170     address.sin_port = htons(port);
171 
172     newtcp_fd = socket(address.sin_family, SOCK_STREAM, 0);
173     if (newtcp_fd == -1) {
174 	errmsg("create socket");
175 	return -1;
176     } else if (newtcp_fd >= MAX_FDSCAN) {
177 	tty_printf("#connect: #error: too many open connections\n");
178 	close(newtcp_fd);
179 	return -1;
180     }
181 
182     tty_printf("#trying %s... ", addr);
183     tty_flush();
184 
185     err = connect(newtcp_fd, (struct sockaddr *)&address, sizeof(address));
186 
187     if (err == -1) { /* CTRL-C pressed, or other errors */
188 	errmsg("connect to host");
189 	close(newtcp_fd);
190 	return -1;
191     }
192 
193 #else /* term */
194 
195     if ((newtcp_fd = connect_server(0)) < 0) {
196 	tty_puts("\n#powwow: unable to connect to term server\n");
197 	return -1;
198     } else {
199 	if (newtcp_fd >= MAX_FDSCAN) {
200 	    tty_printf("#connect: #error: too many open connections\n");
201 	    close(newtcp_fd);
202 	    return -1;
203 	}
204 	send_command(newtcp_fd, C_PORT, 0, "%s:%d", addr, port);
205 	tty_puts("Connected to term server...\n");
206 #ifdef TERM_COMPRESS
207 	send_command(newtcp_fd, C_COMPRESS, 1, "y");
208 #endif
209 	send_command(newtcp_fd, C_DUMB, 1, 0);
210     }
211 
212 #endif /* term */
213 
214     tty_puts("connected!\n");
215 
216 
217     {
218 	/*
219 	 * Now set some options on newtcp_fd :
220 	 * First, no-nagle
221 	 */
222 	int opt = 1;
223 #	ifndef SOL_TCP
224 #	 define SOL_TCP IPPROTO_TCP
225 #	endif
226 	if (setsockopt(newtcp_fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt)))
227 	    errmsg("setsockopt(TCP_NODELAY) failed");
228 
229 	/* TCP keep-alive */
230 	if (setsockopt(newtcp_fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)))
231 	    errmsg("setsockopt(SO_KEEPALIVE) failed");
232 
233 	/*
234 	 * Then, close-on-exec:
235 	 * we don't want children to inherit the socket!
236 	 */
237 
238 	fcntl(newtcp_fd, F_SETFD, FD_CLOEXEC);
239     }
240 
241     return newtcp_fd;
242 }
243 
244 /*
245  * we don't expect IAC commands here, except IAC IAC (a protected ASCII 255)
246  * which we replace with a single IAC (a plain ASCII 255)
247  */
__P2(char *,buffer,int,len)248 int tcp_unIAC __P2 (char *,buffer, int,len)
249 {
250     char *s, *start, warnIACs = 1;
251     if (!memchr(buffer, IAC, len))
252 	return len;
253 
254     for (s = start = buffer; len > 0; buffer++, len--) {
255 	if (buffer[0] == (char)(byte)IAC) {
256 	    if (len > 1 && buffer[1] == (char)(byte)IAC)
257 		buffer++, len--;
258 	    else if (warnIACs) {
259 	        PRINTF("#warning: received IAC inside MPI message, treating as IAC IAC.\n");
260 		warnIACs = 0;
261 	    }
262 	}
263 	*s++ = *buffer;
264     }
265     return s - start;
266 }
267 
268 /*
269  * the reverse step: protect ASCII 255 as IAC IAC
270  * the dest buffer is assumed to be big enough to hold the whole data
271  */
__P3(char *,dest,char *,txt,int,len)272 static int tcp_addIAC __P3 (char *,dest, char *,txt, int,len)
273 {
274     char *s = dest;
275     while (len-- > 0) {
276 	if ((*s++ = *txt++) == (char)(byte)IAC)
277 	    *s++ = (char)(byte)IAC;
278     }
279     return s - dest;
280 }
281 
282 /*
283  * read from an fd, protecting ASCII 255 as IAC IAC while we read.
284  * the buffer is assumed to be big enough to hold the whole file
285  */
__P3(int,fd,char *,data,int,len)286 int tcp_read_addIAC __P3 (int,fd, char *,data, int,len)
287 {
288     char *s = data;
289     char buf[BUFSIZE];
290     int i;
291 
292     while (len > 0) {
293 	while ((i = read(fd, buf, MIN2(len, BUFSIZE))) < 0 && errno == EINTR)
294 	    ;
295 	if (i < 0) {
296 	    errmsg("read from file");
297 	    return -1;
298 	} else if (i == 0)
299 	    break;
300 	s += tcp_addIAC(s, buf, i);
301 	len -= i;
302     }
303     return s - data;
304 }
305 
306 /*
307  * read a maximum of size chars from remote host
308  * using the telnet protocol. return chars read.
309  */
__P3(int,fd,char *,buffer,int,maxsize)310 int tcp_read __P3 (int,fd, char *,buffer, int,maxsize)
311 {
312     char state = CONN_LIST(fd).state;
313     int i;
314     static byte subopt[MAX_SUBOPT];
315     static int subchars;
316     byte *p, *s, *linestart;
317 
318     while ((i = read(fd, buffer, maxsize)) < 0 && errno == EINTR)
319 	;
320 
321     if (i == 0) {
322 	CONN_LIST(fd).state = NORMAL;
323 	tcp_close(NULL);
324 	return 0;
325     }
326     if (i < 0) {
327 	errmsg("read from socket");
328 	return 0;
329     }
330 
331     /*
332      * scan through the buffer,
333      * interpret telnet protocol escapes and MUME MPI messages
334      */
335     for (s = p = linestart = (byte *)buffer; i; s++, i--) {
336 	switch (state) {
337 	 case NORMAL:
338 	 case ALTNORMAL:
339 	 case GOT_R:
340 	 case GOT_N:
341 	    /*
342 	     * Some servers like to send NULs and other funny chars.
343 	     * Clean up as much as possible.
344 	     */
345 	    switch (*s) {
346 	     case IAC:
347 		state = GOTIAC;
348 		break;
349 	     case '\r':
350 		/* start counting \r, unless just got \n */
351 		if (state == NORMAL || state == ALTNORMAL) {
352 		    /*
353 		     * ALTNORMAL is safe here: \r cannot be in MPI header,
354 		     * and any previous MPI header has already been rejected
355 		     */
356 		    state = GOT_R;
357 		} else if (state == GOT_N)
358 		  /* after \n\r, we forbid MPI messages */
359 		    state = ALTNORMAL;
360 		break;
361 	     case '\n':
362 		state = GOT_N;
363 		*p++ = *s;
364 		linestart = p;
365 		break;
366 	     case '\0':
367 		/* skip NULs */
368 		break;
369 	     default:
370 		/* first, flush any missing \r */
371 		if (state == GOT_R)
372 		  *p++ = '\r';
373 
374 		*p++ = *s;
375 
376 		/* check for MUME MPI messages: */
377 		if (p - linestart == MPILEN && !memcmp(linestart, MPI, MPILEN)) {
378 		    if (!(CONN_LIST(fd).flags & IDEDITOR)) {
379 			PRINTF("#warning: MPI message received without #request editor!\n");
380 		    } else if (state == ALTNORMAL) {
381 			/* no MPI messages after \n\r */
382 			PRINTF("#warning: MPI attack?\n");
383 		    } else {
384 			subchars = process_message((char*)s+1, i-1);
385 			/* no +MPILEN here, as it was already processed. */
386 			s += subchars; i-= subchars;
387 			p = linestart;
388 		    }
389 		}
390 
391 		if (state != ALTNORMAL)
392 		    state = NORMAL;
393 		break;
394 	    }
395 	    break;
396 
397 	 case GOTIAC:
398 	    switch (*s) {
399 	     case WILL:
400 		state = GOTWILL; break;
401 	     case WONT:
402 		state = GOTWONT; break;
403 	     case DO:
404 		state = GOTDO; break;
405 	     case DONT:
406 		state = GOTDONT; break;
407 	     case SB:		/* BUG (multiple connections):	*/
408 		state = GOTSB;	/* there is only one subopt buffer */
409 		subchars = 0;
410 		break;
411 	     case IAC:
412 		*p++ = IAC;
413 		state = NORMAL;
414 		break;
415 	     case GA:
416 		/* I should handle GA as end-of-prompt marker one day */
417 		/* one day has come ;) - Max */
418 		prompt_set_iac((char*)p);
419 		state = NORMAL;
420 		break;
421 	     default:
422 		/* ignore the rest of the telnet commands */
423 #ifdef TELOPTS
424 		tty_printf("[skipped IAC <%d>]\n", *s);
425 #endif
426 		state = NORMAL;
427 		break;
428 	    }
429 	    break;
430 
431 	 case GOTWILL:
432 #ifdef TELOPTS
433 	    tty_printf("[got WILL %s]\n", TELOPTSTR(*s));
434 #endif
435 	    switch(*s) {
436 	     case TELOPT_ECHO:
437 		/* host echoes, turn off echo here
438 		 * but only for main connection, since we do not want
439 		 * subsidiary connection password entries to block anything
440 		 * in the main connection
441 		 */
442 		if (fd == tcp_main_fd)
443 		    linemode |= LM_NOECHO;
444 		sendopt(DO, *s);
445 		break;
446 	     case TELOPT_SGA:
447 		/* this can't hurt */
448 		linemode |= LM_CHAR;
449 		tty_special_keys();
450 		sendopt(DO, *s);
451 		break;
452 	     default:
453 		/* don't accept other options */
454 		sendopt(DONT, *s);
455 		break;
456 	    }
457 	    state = NORMAL;
458 	    break;
459 
460 	 case GOTWONT:
461 #ifdef TELOPTS
462 	    tty_printf("[got WONT %s]\n", TELOPTSTR(*s));
463 #endif
464 	    if (*s == TELOPT_ECHO) {
465 		/* host no longer echoes, we do it instead */
466 		linemode &= ~LM_NOECHO;
467 	    }
468 	    /* accept any WONT */
469 	    sendopt(DONT, *s);
470 	    state = NORMAL;
471 	    break;
472 
473 	 case GOTDO:
474 #ifdef TELOPTS
475 	    tty_printf("[got DO %s]\n", TELOPTSTR(*s));
476 #endif
477 	    switch(*s) {
478 	     case TELOPT_SGA:
479 		linemode |= LM_CHAR;
480 		tty_special_keys();
481 		/* FALLTHROUGH */
482 	     case TELOPT_TTYPE:
483 		sendopt(WILL, *s);
484 		break;
485 	     case TELOPT_NAWS:
486 		sendopt(WILL, *s);
487 		tcp_write_tty_size();
488 		break;
489 	     default:
490 		/* accept nothing else */
491 		sendopt(WONT, *s);
492 		break;
493 	    }
494 	    state = NORMAL;
495 	    break;
496 
497 	 case GOTDONT:
498 #ifdef TELOPTS
499 	    tty_printf("[got DONT %s]\n", TELOPTSTR(*s));
500 #endif
501 	    if (*s == TELOPT_SGA) {
502 		linemode &= ~LM_CHAR;
503 		tty_special_keys();
504 	    }
505 	    sendopt(WONT, *s);
506 	    state = NORMAL;
507 	    break;
508 
509 	 case GOTSB:
510 	    if (*s == IAC) {
511 		state = GOTSBIAC;
512 	    } else {
513 		if (subchars < MAX_SUBOPT)
514 		  subopt[subchars++] = *s;
515 	    }
516 	    break;
517 
518 	 case GOTSBIAC:
519 	    if (*s == IAC) {
520 		if (subchars < MAX_SUBOPT)
521 		  subopt[subchars++] = IAC;
522 		state = GOTSB;
523 	    } else if (*s == SE) {
524 		subopt[subchars] = '\0';
525 		dosubopt(subopt);
526 		state = NORMAL;
527 	    } else {
528 		/* problem! I haven't the foggiest idea of what to do here.
529 		 * I'll just ignore it and hope it goes away. */
530 		PRINTF("#telnet: got IAC <%d> instead of IAC SE!\n", (int)*s);
531 		state = NORMAL;
532 	    }
533 	    break;
534 	}
535     }
536     CONN_LIST(fd).state = state;
537 
538     if (!(CONN_LIST(tcp_fd).flags & SPAWN)) {
539         log_write(buffer, (char *)p - buffer, 0);
540     }
541 
542     return (char *)p - buffer;
543 }
544 
__P3(int,fd,const char *,data,int,len)545 static void internal_tcp_raw_write __P3 (int,fd, const char *,data, int,len)
546 {
547     while (len > 0) {
548         int i;
549 	while ((i = write(fd, data, len)) < 0 && errno == EINTR)
550 	    ;
551 	if (i < 0) {
552 	    errmsg("write to socket");
553 	    break;
554 	}
555 	data += i;
556 	len -= i;
557     }
558 }
559 
__P3(int,fd,const char *,data,int,len)560 void tcp_raw_write __P3 (int,fd, const char *,data, int,len)
561 {
562     tcp_flush();
563     internal_tcp_raw_write(fd, data, len);
564 }
565 
566 /* write data, escape any IACs */
__P3(int,fd,const char *,data,int,len)567 void tcp_write_escape_iac __P3 (int,fd, const char *,data, int,len)
568 {
569     tcp_flush();
570 
571     for (;;) {
572         const char *iac = memchr(data, IAC, len);
573         size_t l = iac ? (iac - data) + 1 : len;
574         internal_tcp_raw_write(fd, data, l);
575         if (iac == NULL)
576             return;
577         internal_tcp_raw_write(fd, iac, 1);
578         len -= l;
579         data = iac + 1;
580     }
581 }
582 
583 /*
584  * Send current terminal size (RFC 1073)
585  */
__P0(void)586 void tcp_write_tty_size __P0 (void)
587 {
588     static byte buf[] = { IAC, SB, TELOPT_NAWS, 0, 0, 0, 0, IAC, SE };
589 
590     buf[3] = cols >> 8;
591     buf[4] = cols & 0xff;
592     buf[5] = lines >> 8;
593     buf[6] = lines & 0xff;
594 
595     tcp_raw_write(tcp_main_fd, (char *)buf, 9);
596 #ifdef TELOPTS
597     tty_printf("[sent term size %d %d]\n", cols, lines);
598 #endif
599 }
600 
601 /*
602  * send a string to the main connection on the remote host
603  */
__P1(char *,data)604 void tcp_main_write __P1 (char *,data)
605 {
606     tcp_write(tcp_main_fd, data);
607 }
608 
609 
610 static char output_buffer[BUFSIZE];
611 static int output_len = 0;	/* number of characters in output_buffer */
612 static int output_socket = -1;	/* to which socket buffer should be sent*/
613 
614 /*
615  * put data in the output buffer for transmission to the remote host
616  */
__P2(int,fd,char *,data)617 void tcp_write __P2 (int,fd, char *,data)
618 {
619     char *iacs, *out;
620     int len, space, iacp;
621     len = strlen(data);
622 
623     if (tcp_main_fd != -1 && tcp_main_fd == fd) {
624 	if (linemode & LM_NOECHO)
625 	    log_write("", 0, 1); /* log a newline only */
626 	else
627 	    log_write(data, len, 1);
628 	reprint_writeline(data);
629     }
630 
631     /* must be AFTER reprint_writeline() */
632     if (CONN_LIST(tcp_fd).flags & SPAWN)
633 	status(1);
634     else
635 	status(-1);
636 
637     if (fd != output_socket) { /* is there data to another socket? */
638 	tcp_flush(); /* then flush it */
639 	output_socket = fd;
640     }
641 
642     out = output_buffer + output_len;
643     space = BUFSIZE - output_len;
644 
645     while (len) {
646 	iacs = memchr(data, IAC, len);
647 	iacp = iacs ? iacs - data : len;
648 
649 	if (iacp == 0) {
650 	    /* we're at the IAC, send it */
651 	    if (space < 2) {
652 		tcp_flush();
653 		out = output_buffer;
654 		space = BUFSIZE;
655 	    }
656 	    *out++ = (char)IAC; *out++ = (char)IAC; output_len += 2; space -= 2;
657 	    data++; len--;
658 	    continue;
659 	}
660 
661 	while (space < iacp) {
662 	    memcpy(out, data, space);
663 	    data += space; len -= space;
664 	    iacp -= space;
665 	    output_len = BUFSIZE;
666 
667 	    tcp_flush();
668 	    out = output_buffer;
669 	    space = BUFSIZE;
670 	}
671 
672 	if (iacp /* && space >= iacp */ ) {
673 	    memcpy(out, data, iacp);
674 	    out += iacp; output_len += iacp; space -= iacp;
675 	    data += iacp; len -= iacp;
676 	}
677     }
678     if (!space) {
679 	tcp_flush();
680 	out = output_buffer;
681     }
682     *out++ = '\n';
683     output_len++;
684 }
685 
686 /*
687  * send all buffered data to the remote host
688  */
__P0(void)689 void tcp_flush __P0 (void)
690 {
691     int n;
692     char *p = output_buffer;
693 
694     if (output_len && output_socket == -1) {
695 	clear_input_line(1);
696 	PRINTF("#no open connections. Use '#connect main <address> <port>' to open a connection.\n");
697 	output_len = 0;
698 	return;
699     }
700 
701     if (!output_len)
702 	return;
703 
704     while (output_len) {
705 	while ((n = write(output_socket, p, output_len)) < 0 && errno == EINTR)
706 	    ;
707 	if (n < 0) {
708 	    output_len = 0;
709 	    errmsg("write to socket");
710 	    return;
711 	}
712 	sent += n;
713 	p += n;
714 	output_len -= n;
715     }
716 
717     if (CONN_LIST(output_socket).flags & SPAWN)
718 	status(1);
719     else
720 	/* sent stuff, so we expect a prompt */
721 	status(-1);
722 }
723 
724 /*
725  * Below are multiple-connection support functions:
726  */
727 
728 /*
729  * return connection's fd given id,
730  * or -1 if null or invalid id is given
731  */
__P1(char *,id)732 int tcp_find __P1 (char *,id)
733 {
734     int i;
735 
736     for (i=0; i<conn_max_index; i++) {
737 	if (CONN_INDEX(i).id && !strcmp(CONN_INDEX(i).id, id))
738 	    return CONN_INDEX(i).fd;
739     }
740     return -1;
741 }
742 
743 /*
744  * show list of open connections
745  */
__P0(void)746 void tcp_show __P0 (void)
747 {
748     int i = tcp_count+tcp_attachcount;
749 
750     PRINTF("#%s connection%s opened%c\n", i ? "The following" : "No",
751 	       i==1 ? " is" : "s are", i ? ':' : '.');
752 
753 
754     for (i=0; i<conn_max_index; i++)
755 	if (CONN_INDEX(i).id && !(CONN_INDEX(i).flags & SPAWN)) {
756 	    tty_printf("MUD %sactive %s ##%s\t (%s %d)\n",
757 		       CONN_INDEX(i).flags & ACTIVE ? "   " : "non",
758 		       i == tcp_main_fd ? "(default)" : "         ",
759 		       CONN_INDEX(i).id,
760 		       CONN_INDEX(i).host, CONN_INDEX(i).port);
761 	}
762     for (i=0; i<conn_max_index; i++)
763 	if (CONN_INDEX(i).id && (CONN_INDEX(i).flags & SPAWN)) {
764 	    tty_printf("CMD %sactive %s ##%s\t (%s)\n",
765 		       CONN_INDEX(i).flags & ACTIVE ? "   " : "non",
766 		       i == tcp_main_fd ? "(default)" : "         ",
767 		       CONN_INDEX(i).id, CONN_INDEX(i).host);
768 	}
769 }
770 
771 /*
772  * permanently change main connection
773  */
__P1(int,fd)774 void tcp_set_main __P1 (int,fd)
775 {
776     /* GH: reset linemode and prompt */
777     tcp_main_fd = fd;
778     if (linemode & LM_CHAR)
779 	linemode = 0, tty_special_keys();
780     else
781 	linemode = 0;
782     status(-1);
783     reprint_clear();
784 }
785 
786 /*
787  * open another connection
788  */
__P4(char *,id,char *,initstring,char *,host,int,port)789 void tcp_open __P4 (char *,id, char *,initstring, char *,host, int,port)
790 {
791     int newtcp_fd, i;
792 
793     if (tcp_count+tcp_attachcount >= MAX_CONNECTS) {
794 	PRINTF("#too many open connections.\n");
795 	return;
796     }
797     if (tcp_find(id)>=0) {
798 	PRINTF("#connection \"%s\" already open.\n", id);
799 	return;
800     }
801 
802     /* find a free slot */
803     for (i=0; i<MAX_CONNECTS; i++) {
804 	if (!CONN_INDEX(i).id)
805 	    break;
806     }
807     if (i == MAX_CONNECTS) {
808 	PRINTF("#internal error, connection table full :(\n");
809 	return;
810     }
811 
812     if (!(CONN_INDEX(i).host = my_strdup(host))) {
813 	errmsg("malloc");
814 	return;
815     }
816     if (!(CONN_INDEX(i).id = my_strdup(id))) {
817 	errmsg("malloc");
818 	free(CONN_INDEX(i).host);
819 	return;
820     }
821 
822     /* dial the number by moving the right index in small circles */
823     if ((newtcp_fd = tcp_connect(host, port)) < 0) {
824 	free(CONN_INDEX(i).host);
825 	free(CONN_INDEX(i).id);
826 	CONN_INDEX(i).id = 0;
827 	return;
828     }
829 
830     conn_table[newtcp_fd] = i;
831     CONN_INDEX(i).flags = ACTIVE;
832     CONN_INDEX(i).state = NORMAL;
833     CONN_INDEX(i).port = port;
834     CONN_INDEX(i).fd = newtcp_fd;
835 
836     if (tcp_max_fd < newtcp_fd)
837 	tcp_max_fd = newtcp_fd;
838     if (conn_max_index <= i)
839 	conn_max_index = i+1;
840 
841     FD_SET(newtcp_fd, &fdset); 		/* add socket to select() set */
842     tcp_count++;
843 
844     if (opt_info && tcp_count) {
845 	PRINTF("#default connection is now \"%s\"\n", id);
846     }
847     tcp_set_main(tcp_fd = newtcp_fd);
848     if (opt_sendsize)
849 	tcp_write_tty_size();
850 
851     if (initstring) {
852 	parse_instruction(initstring, 0, 0, 1);
853 	history_done = 0;
854     }
855 }
856 
857 /*
858  * close a connection
859  */
__P1(char *,id)860 void tcp_close __P1 (char *,id)
861 {
862     int i, sfd;
863 
864     status(1);
865     tty_puts(edattrend);
866     /*
867      * because we may be called from get_remote_input()
868      * if tcp_read gets an EOF, before edattrend is
869      * printed by get_remote_input() itself.
870      */
871 
872     if (id) {  /* #zap cmd */
873 	if ((sfd = tcp_find(id)) < 0) {
874 	    tty_printf("#no such connection: \"%s\"\n", id);
875 	    return;
876 	}
877     } else
878 	sfd = tcp_fd;  /* connection closed by remote host */
879 
880     shutdown(sfd, 2);
881     close(sfd);
882 
883     abort_edit_fd(sfd);
884 
885     tty_printf("#connection on \"%s\" closed.\n", CONN_LIST(sfd).id);
886 
887     if (sfd == tcp_main_fd) { /* main connection closed */
888 	if (tcp_count == 1) { /* was last connection */
889 	    if (opt_exit)
890 		exit_powwow();
891 	    tty_puts("#no connections left. Type #quit to quit.\n");
892 	    tcp_fd = tcp_main_fd = -1;
893 	} else {
894 	    /* must find another connection and promote it to main */
895 	    for (i=0; i<conn_max_index; i++) {
896 		if (!CONN_INDEX(i).id || CONN_INDEX(i).fd == sfd
897 		    || (CONN_INDEX(i).flags & SPAWN))
898 		    continue;
899 		tty_printf("#default connection is now \"%s\"\n", CONN_INDEX(i).id);
900 		tcp_main_fd = CONN_INDEX(i).fd;
901 		break;
902 	    }
903 	    if (sfd == tcp_main_fd) {
904 	        tty_printf("#PANIC! internal error in tcp_close()\nQuitting.\n");
905 		syserr(NULL);
906 	    }
907 	}
908 	tcp_set_main(tcp_main_fd);
909     }
910 
911     if (tcp_fd == sfd)
912 	tcp_fd = -1; /* no further I/O allowed on sfd, as we just closed it */
913 
914     FD_CLR(sfd, &fdset);
915     if (CONN_LIST(sfd).flags & SPAWN)
916 	tcp_attachcount--;
917     else
918 	tcp_count--;
919     CONN_LIST(sfd).flags = 0;
920     CONN_LIST(sfd).state = NORMAL;
921     CONN_LIST(sfd).port = 0;
922     free(CONN_LIST(sfd).host); CONN_LIST(sfd).host = 0;
923     free(CONN_LIST(sfd).id);   CONN_LIST(sfd).id = 0;
924     if (CONN_LIST(sfd).fragment) {
925 	free(CONN_LIST(sfd).fragment);
926 	CONN_LIST(sfd).fragment = 0;
927     }
928 
929     /* recalculate conn_max_index */
930     i = conn_table[sfd];
931     if (i+1 == conn_max_index) {
932 	do {
933 	    i--;
934 	} while (i>=0 && !CONN_INDEX(i).id);
935 	conn_max_index = i+1;
936     }
937 
938     /* recalculate tcp_max_fd */
939     for (i = tcp_max_fd = 0; i<conn_max_index; i++) {
940 	if (CONN_INDEX(i).id && tcp_max_fd < CONN_INDEX(i).fd)
941 	    tcp_max_fd = CONN_INDEX(i).fd;
942     }
943 }
944 
945 /*
946  * toggle output display from another connection
947  */
__P1(char *,id)948 void tcp_togglesnoop __P1 (char *,id)
949 {
950     int sfd;
951 
952     sfd = tcp_find(id);
953     if (sfd>=0) {
954         CONN_LIST(sfd).flags ^= ACTIVE;
955 	if (opt_info) {
956 	    PRINTF("#connection %s is now %sactive.\n",
957 		      CONN_LIST(sfd).id, CONN_LIST(sfd).flags & ACTIVE ? "" : "non");
958 	}
959     } else {
960         PRINTF("#no such connection: %s\n", id);
961     }
962 }
963 
__P2(char *,id,char *,cmd)964 void tcp_spawn __P2 (char *,id, char *,cmd)
965 {
966     int i, childpid, sockets[2];
967 
968     if (tcp_find(id)>=0) {
969 	PRINTF("#connection \"%s\" already open.\n", id);
970 	return;
971     }
972     if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
973 	errmsg("create socketpair");
974 	return;
975     }
976     unescape(cmd);
977 
978     switch (childpid = fork()) {
979       case 0:
980 	/* child */
981 	close(0); close(1); close(2);
982 	setsid();
983 	dup2(sockets[1], 0);
984 	dup2(sockets[1], 1);
985 	dup2(sockets[1], 2);
986 	close(sockets[0]);
987 	close(sockets[1]);
988 	execl("/bin/sh", "sh", "-c", cmd, NULL);
989 	syserr("execl");
990 	break;
991       case -1:
992 	close(sockets[0]);
993 	close(sockets[1]);
994 	errmsg("fork");
995 	return;
996     }
997     close(sockets[1]);
998 
999     /* Again, we don't want children to inherit sockets */
1000     fcntl(sockets[0], F_SETFD, FD_CLOEXEC);
1001 
1002     /* now find a free slot */
1003     for (i=0; i<MAX_CONNECTS; i++) {
1004 	if (!CONN_INDEX(i).id) {
1005 	    conn_table[sockets[0]] = i;
1006 	    break;
1007 	}
1008     }
1009     if (i == MAX_CONNECTS) {
1010 	PRINTF("#internal error, connection table full :(\n");
1011 	close(sockets[0]);
1012 	return;
1013     }
1014 
1015     if (!(CONN_INDEX(i).host = my_strdup(cmd))) {
1016 	errmsg("malloc");
1017 	close(sockets[0]);
1018 	return;
1019     }
1020     if (!(CONN_INDEX(i).id = my_strdup(id))) {
1021 	errmsg("malloc");
1022 	free(CONN_INDEX(i).host);
1023 	close(sockets[0]);
1024 	return;
1025     }
1026     CONN_INDEX(i).flags = ACTIVE | SPAWN;
1027     CONN_INDEX(i).state = NORMAL;
1028     CONN_INDEX(i).port = 0;
1029     CONN_INDEX(i).fd = sockets[0];
1030 
1031     FD_SET(sockets[0], &fdset);	       /* add socket to select() set */
1032     tcp_attachcount++;
1033 
1034     if (tcp_max_fd < sockets[0])
1035 	tcp_max_fd = sockets[0];
1036     if (conn_max_index <= i)
1037 	conn_max_index = i+1;
1038 
1039     if (opt_info) {
1040 	PRINTF("#successfully spawned \"%s\" with pid %d\n", id, childpid);
1041     }
1042 
1043     /*
1044      * when the child exits we also get an EOF on the socket,
1045      * so no special care is needed by the SIGCHLD handler.
1046      */
1047 }
1048 
1049