diff options
Diffstat (limited to 'tcp.c')
-rw-r--r-- | tcp.c | 1022 |
1 files changed, 1022 insertions, 0 deletions
@@ -0,0 +1,1022 @@ +/* + * tcp.c -- telnet protocol communication module for powwow + * + * Copyright (C) 1998,2002 by Massimiliano Ghilardi + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <netdb.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/telnet.h> +#ifndef TELOPT_NAWS +# define TELOPT_NAWS 31 +#endif +#include <arpa/inet.h> +#ifndef NEXT +# include <unistd.h> +#endif + +#ifdef TERM +# include "client.h" +#endif + +#include "defines.h" +#include "main.h" +#include "utils.h" +#include "tcp.h" +#include "tty.h" +#include "edit.h" +#include "beam.h" +#include "log.h" + +#ifdef TELOPTS +# define TELOPTSTR(n) ((n) > NTELOPTS ? "unknown" : telopts[n]) +#endif + +int tcp_fd = -1; /* current socket file descriptor + * -1 means no socket */ +int tcp_main_fd = -1; /* socket file descriptor of main connect. + * -1 means no socket */ +int tcp_max_fd = 0; /* highest used fd */ + +int tcp_count = 0; /* number of open connections */ +int tcp_attachcount = 0; /* number of spawned commands */ + +int conn_max_index; /* 1 + highest used conn_list[] index */ + +connsess conn_list[MAX_CONNECTS]; /* connection list */ + +byte conn_table[MAX_FDSCAN]; /* fd -> index translation table */ + +fd_set fdset; /* set of descriptors to select() on */ + +/* + * process suboptions. + * so far, only terminal type is processed but future extensions are + * window size, X display location, etc. + */ +static void dosubopt __P1 (byte *,str) +{ + char buf[256], *term; + int len, err; + + if (str[0] == TELOPT_TTYPE) { + if (str[1] == 1) { + /* 1 == SEND */ +#ifdef TELOPTS + tty_printf("[got SB TERMINAL TYPE SEND]\n"); +#endif + if (!(term = getenv("TERM"))) term = "unknown"; + sprintf(buf, "%c%c%c%c%.*s%c%c", IAC, SB, TELOPT_TTYPE, 0, + 256-7, term, IAC, SE); /* 0 == IS */ + + len = strlen(term) + 6; + while ((err = write(tcp_fd, buf, len)) < 0 && errno == EINTR) + ; + if (err != len) { + errmsg("write subopt to socket"); + return; + } +#ifdef TELOPTS + tty_printf("[sent SB TERMINAL TYPE IS %s]\n", term); +#endif + } + } +} + +/* + * send an option negotiation + * 'what' is one of WILL, WONT, DO, DONT + */ +static void sendopt __P2 (byte,what, byte,opt) +{ + static byte buf[3] = { IAC, 0, 0 }; + int i; + buf[1] = what; buf[2] = opt; + + while ((i = write(tcp_fd, buf, 3)) < 0 && errno == EINTR) + ; + if (i != 3) { + errmsg("write option to socket"); + return; + } + +#ifdef TELOPTS + tty_printf("[sent %s %s]\n", (what == WILL) ? "WILL" : + (what == WONT) ? "WONT" : + (what == DO) ? "DO" : (what == DONT) ? "DONT" : "error", + TELOPTSTR(opt)); +#endif +} + +/* + * connect to remote host + * Warning: some voodoo code here + */ +int tcp_connect __P2 (char *,addr, int,port) +{ + struct sockaddr_in address; + struct hostent *host_info; + int err, newtcp_fd; + + status(1); + + memzero((char *)&address, sizeof(address)); + /* + * inet_addr has a strange design: It is documented to return -1 for + * malformed requests, but it is declared to return unsigned long! + * Anyway, this works. + */ + +#ifndef TERM + address.sin_addr.s_addr = inet_addr(addr); + if (address.sin_addr.s_addr != (unsigned int)-1) + address.sin_family = AF_INET; + else + { + if (echo_int) + tty_printf("#looking up %s... ", addr); + tty_flush(); + host_info = gethostbyname(addr); + if (host_info == 0) { + if (!echo_int) { + tty_printf("#looking up %s... ", addr); + } + tty_printf("unknown host!\n"); + return -1; + } + memmove((char *)&address.sin_addr, host_info->h_addr, + host_info->h_length); + address.sin_family = host_info->h_addrtype; + if (echo_int) + tty_puts("found.\n"); + } + address.sin_port = htons(port); + + newtcp_fd = socket(address.sin_family, SOCK_STREAM, 0); + if (newtcp_fd == -1) { + errmsg("create socket"); + return -1; + } else if (newtcp_fd >= MAX_FDSCAN) { + tty_printf("#connect: #error: too many open connections\n"); + close(newtcp_fd); + return -1; + } + + tty_printf("#trying %s... ", addr); + tty_flush(); + + err = connect(newtcp_fd, (struct sockaddr *)&address, sizeof(address)); + + if (err == -1) { /* CTRL-C pressed, or other errors */ + errmsg("connect to host"); + close(newtcp_fd); + return -1; + } + +#else /* term */ + + if ((newtcp_fd = connect_server(0)) < 0) { + tty_puts("\n#powwow: unable to connect to term server\n"); + return -1; + } else { + if (newtcp_fd >= MAX_FDSCAN) { + tty_printf("#connect: #error: too many open connections\n"); + close(newtcp_fd); + return -1; + } + send_command(newtcp_fd, C_PORT, 0, "%s:%d", addr, port); + tty_puts("Connected to term server...\n"); +#ifdef TERM_COMPRESS + send_command(newtcp_fd, C_COMPRESS, 1, "y"); +#endif + send_command(newtcp_fd, C_DUMB, 1, 0); + } + +#endif /* term */ + + tty_puts("connected!\n"); + + + { + /* + * Now set some options on newtcp_fd : + * First, no-nagle + */ + int opt = 1; +# ifndef SOL_TCP +# define SOL_TCP IPPROTO_TCP +# endif + if (setsockopt(newtcp_fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt))) + errmsg("setsockopt(TCP_NODELAY) failed"); + + /* + * Then, close-on-exec: + * we don't want children to inherit the socket! + */ + + fcntl(newtcp_fd, F_SETFD, FD_CLOEXEC); + } + + return newtcp_fd; +} + +/* + * we don't expect IAC commands here, except IAC IAC (a protected ASCII 255) + * which we replace with a single IAC (a plain ASCII 255) + */ +int tcp_unIAC __P2 (char *,buffer, int,len) +{ + char *s, *start, warnIACs = 1; + if (!memchr(buffer, IAC, len)) + return len; + + for (s = start = buffer; len > 0; buffer++, len--) { + if (buffer[0] == (char)(byte)IAC) { + if (len > 1 && buffer[1] == (char)(byte)IAC) + buffer++, len--; + else if (warnIACs) { + PRINTF("#warning: received IAC inside MPI message, treating as IAC IAC.\n"); + warnIACs = 0; + } + } + *s++ = *buffer; + } + return s - start; +} + +/* + * the reverse step: protect ASCII 255 as IAC IAC + * the dest buffer is assumed to be big enough to hold the whole data + */ +static int tcp_addIAC __P3 (char *,dest, char *,txt, int,len) +{ + char *s = dest; + while (len-- > 0) { + if ((*s++ = *txt++) == (char)(byte)IAC) + *s++ = (char)(byte)IAC; + } + return s - dest; +} + +/* + * read from an fd, protecting ASCII 255 as IAC IAC while we read. + * the buffer is assumed to be big enough to hold the whole file + */ +int tcp_read_addIAC __P3 (int,fd, char *,data, int,len) +{ + char *s = data; + char buf[BUFSIZE]; + int i; + + while (len > 0) { + while ((i = read(fd, buf, MIN2(len, BUFSIZE))) < 0 && errno == EINTR) + ; + if (i < 0) { + errmsg("read from file"); + return -1; + } else if (i == 0) + break; + s += tcp_addIAC(s, buf, i); + len -= i; + } + return s - data; +} + +/* + * read a maximum of size chars from remote host + * using the telnet protocol. return chars read. + */ +int tcp_read __P3 (int,fd, char *,buffer, int,maxsize) +{ + char state = CONN_LIST(fd).state; + int i; + static byte subopt[MAX_SUBOPT]; + static int subchars; + byte *p, *s, *linestart; + + while ((i = read(fd, buffer, maxsize)) < 0 && errno == EINTR) + ; + + if (i == 0) { + CONN_LIST(fd).state = NORMAL; + tcp_close(NULL); + return 0; + } + if (i < 0) { + errmsg("read from socket"); + return 0; + } + + /* + * scan through the buffer, + * interpret telnet protocol escapes and MUME MPI messages + */ + for (s = p = linestart = (byte *)buffer; i; s++, i--) { + switch (state) { + case NORMAL: + case ALTNORMAL: + case GOT_R: + case GOT_N: + /* + * Some servers like to send NULs and other funny chars. + * Clean up as much as possible. + */ + switch (*s) { + case IAC: + state = GOTIAC; + break; + case '\r': + /* start counting \r, unless just got \n */ + if (state == NORMAL || state == ALTNORMAL) { + /* + * ALTNORMAL is safe here: \r cannot be in MPI header, + * and any previous MPI header has already been rejected + */ + state = GOT_R; + } else if (state == GOT_N) + /* after \n\r, we forbid MPI messages */ + state = ALTNORMAL; + break; + case '\n': + state = GOT_N; + *p++ = *s; + linestart = p; + break; + case '\0': + /* skip NULs */ + break; + default: + /* first, flush any missing \r */ + if (state == GOT_R) + *p++ = '\r'; + + *p++ = *s; + + /* check for MUME MPI messages: */ + if (p - linestart == MPILEN && !memcmp(linestart, MPI, MPILEN)) { + if (!(CONN_LIST(fd).flags & IDEDITOR)) { + PRINTF("#warning: MPI message received without #request editor!\n"); + } else if (state == ALTNORMAL) { + /* no MPI messages after \n\r */ + PRINTF("#warning: MPI attack?\n"); + } else { + subchars = process_message(s+1, i-1); + /* no +MPILEN here, as it was already processed. */ + s += subchars; i-= subchars; + p = linestart; + } + } + + if (state != ALTNORMAL) + state = NORMAL; + break; + } + break; + + case GOTIAC: + switch (*s) { + case WILL: + state = GOTWILL; break; + case WONT: + state = GOTWONT; break; + case DO: + state = GOTDO; break; + case DONT: + state = GOTDONT; break; + case SB: /* BUG (multiple connections): */ + state = GOTSB; /* there is only one subopt buffer */ + subchars = 0; + break; + case IAC: + *p++ = IAC; + state = NORMAL; + break; + case GA: + /* I should handle GA as end-of-prompt marker one day */ + /* one day has come ;) - Max */ + prompt_set_iac(p); + state = NORMAL; + break; + default: + /* ignore the rest of the telnet commands */ +#ifdef TELOPTS + tty_printf("[skipped IAC <%d>]\n", *s); +#endif + state = NORMAL; + break; + } + break; + + case GOTWILL: +#ifdef TELOPTS + tty_printf("[got WILL %s]\n", TELOPTSTR(*s)); +#endif + switch(*s) { + case TELOPT_ECHO: + /* host echoes, turn off echo here + * but only for main connection, since we do not want + * subsidiary connection password entries to block anything + * in the main connection + */ + if (fd == tcp_main_fd) + linemode |= LM_NOECHO; + sendopt(DO, *s); + break; + case TELOPT_SGA: + /* this can't hurt */ + linemode |= LM_CHAR; + tty_special_keys(); + sendopt(DO, *s); + break; + default: + /* don't accept other options */ + sendopt(DONT, *s); + break; + } + state = NORMAL; + break; + + case GOTWONT: +#ifdef TELOPTS + tty_printf("[got WONT %s]\n", TELOPTSTR(*s)); +#endif + if (*s == TELOPT_ECHO) { + /* host no longer echoes, we do it instead */ + linemode &= ~LM_NOECHO; + } + /* accept any WONT */ + sendopt(DONT, *s); + state = NORMAL; + break; + + case GOTDO: +#ifdef TELOPTS + tty_printf("[got DO %s]\n", TELOPTSTR(*s)); +#endif + switch(*s) { + case TELOPT_SGA: + linemode |= LM_CHAR; + tty_special_keys(); + /* FALLTHROUGH */ + case TELOPT_TTYPE: + sendopt(WILL, *s); + break; + case TELOPT_NAWS: + sendopt(WILL, *s); + tcp_write_tty_size(); + break; + default: + /* accept nothing else */ + sendopt(WONT, *s); + break; + } + state = NORMAL; + break; + + case GOTDONT: +#ifdef TELOPTS + tty_printf("[got DONT %s]\n", TELOPTSTR(*s)); +#endif + if (*s == TELOPT_SGA) { + linemode &= ~LM_CHAR; + tty_special_keys(); + } + sendopt(WONT, *s); + state = NORMAL; + break; + + case GOTSB: + if (*s == IAC) { + state = GOTSBIAC; + } else { + if (subchars < MAX_SUBOPT) + subopt[subchars++] = *s; + } + break; + + case GOTSBIAC: + if (*s == IAC) { + if (subchars < MAX_SUBOPT) + subopt[subchars++] = IAC; + state = GOTSB; + } else if (*s == SE) { + subopt[subchars] = '\0'; + dosubopt(subopt); + state = NORMAL; + } else { + /* problem! I haven't the foggiest idea of what to do here. + * I'll just ignore it and hope it goes away. */ + PRINTF("#telnet: got IAC <%d> instead of IAC SE!\n", (int)*s); + state = NORMAL; + } + break; + } + } + CONN_LIST(fd).state = state; + + if (!(CONN_LIST(tcp_fd).flags & SPAWN)) { + log_write(buffer, (char *)p - buffer, 0); + } + + return (char *)p - buffer; +} + +void tcp_raw_write __P3 (int,fd, char *,data, int,len) +{ + int i; + tcp_flush(); + while (len > 0) { + while ((i = write(fd, data, len)) < 0 && errno == EINTR) + ; + if (i < 0) { + errmsg("write to socket"); + break; + } + data += i; + len -= i; + } +} + +/* + * Send current terminal size (RFC 1073) + */ +void tcp_write_tty_size __P0 (void) +{ + static byte buf[] = { IAC, SB, TELOPT_NAWS, 0, 0, 0, 0, IAC, SE }; + + buf[3] = cols >> 8; + buf[4] = cols & 0xff; + buf[5] = lines >> 8; + buf[6] = lines & 0xff; + + tcp_raw_write(tcp_main_fd, (char *)buf, 9); +#ifdef TELOPTS + tty_printf("[sent term size %d %d]\n", cols, lines); +#endif +} + +/* + * send a string to the main connection on the remote host + */ +void tcp_main_write __P1 (char *,data) +{ + tcp_write(tcp_main_fd, data); +} + + +static char output_buffer[BUFSIZE]; +static int output_len = 0; /* number of characters in output_buffer */ +static int output_socket = -1; /* to which socket buffer should be sent*/ + +/* + * put data in the output buffer for transmission to the remote host + */ +void tcp_write __P2 (int,fd, char *,data) +{ + char *iacs, *out; + int len, space, iacp; + len = strlen(data); + + if (tcp_main_fd != -1 && tcp_main_fd == fd) { + if (linemode & LM_NOECHO) + log_write("", 0, 1); /* log a newline only */ + else + log_write(data, len, 1); + reprint_writeline(data); + } + + /* must be AFTER reprint_writeline() */ + if (CONN_LIST(tcp_fd).flags & SPAWN) + status(1); + else + status(-1); + + if (fd != output_socket) { /* is there data to another socket? */ + tcp_flush(); /* then flush it */ + output_socket = fd; + } + + out = output_buffer + output_len; + space = BUFSIZE - output_len; + + while (len) { + iacs = memchr(data, IAC, len); + iacp = iacs ? iacs - data : len; + + if (iacp == 0) { + /* we're at the IAC, send it */ + if (space < 2) { + tcp_flush(); + out = output_buffer; + space = BUFSIZE; + } + *out++ = (char)IAC; *out++ = (char)IAC; output_len += 2; space -= 2; + data++; len--; + continue; + } + + while (space < iacp) { + memcpy(out, data, space); + data += space; len -= space; + iacp -= space; + output_len = BUFSIZE; + + tcp_flush(); + out = output_buffer; + space = BUFSIZE; + } + + if (iacp /* && space >= iacp */ ) { + memcpy(out, data, iacp); + out += iacp; output_len += iacp; space -= iacp; + data += iacp; len -= iacp; + } + } + if (!space) { + tcp_flush(); + out = output_buffer; + } + *out++ = '\n'; + output_len++; +} + +/* + * send all buffered data to the remote host + */ +void tcp_flush __P0 (void) +{ + int n; + char *p = output_buffer; + + if (output_len && output_socket == -1) { + clear_input_line(1); + PRINTF("#no open connections. Use '#connect main <address> <port>' to open a connection.\n"); + output_len = 0; + return; + } + + if (!output_len) + return; + + while (output_len) { + while ((n = write(output_socket, p, output_len)) < 0 && errno == EINTR) + ; + if (n < 0) { + output_len = 0; + errmsg("write to socket"); + return; + } + sent += n; + p += n; + output_len -= n; + } + + if (CONN_LIST(output_socket).flags & SPAWN) + status(1); + else + /* sent stuff, so we expect a prompt */ + status(-1); +} + +/* + * Below are multiple-connection support functions: + */ + +/* + * return connection's fd given id, + * or -1 if null or invalid id is given + */ +int tcp_find __P1 (char *,id) +{ + int i; + + for (i=0; i<conn_max_index; i++) { + if (CONN_INDEX(i).id && !strcmp(CONN_INDEX(i).id, id)) + return CONN_INDEX(i).fd; + } + return -1; +} + +/* + * show list of open connections + */ +void tcp_show __P0 (void) +{ + int i = tcp_count+tcp_attachcount; + + PRINTF("#%s connection%s opened%c\n", i ? "The following" : "No", + i==1 ? " is" : "s are", i ? ':' : '.'); + + + for (i=0; i<conn_max_index; i++) + if (CONN_INDEX(i).id && !(CONN_INDEX(i).flags & SPAWN)) { + tty_printf("MUD %sactive %s ##%s\t (%s %d)\n", + CONN_INDEX(i).flags & ACTIVE ? " " : "non", + i == tcp_main_fd ? "(default)" : " ", + CONN_INDEX(i).id, + CONN_INDEX(i).host, CONN_INDEX(i).port); + } + for (i=0; i<conn_max_index; i++) + if (CONN_INDEX(i).id && (CONN_INDEX(i).flags & SPAWN)) { + tty_printf("CMD %sactive %s ##%s\t (%s)\n", + CONN_INDEX(i).flags & ACTIVE ? " " : "non", + i == tcp_main_fd ? "(default)" : " ", + CONN_INDEX(i).id, CONN_INDEX(i).host); + } +} + +/* + * permanently change main connection + */ +void tcp_set_main __P1 (int,fd) +{ + /* GH: reset linemode and prompt */ + tcp_main_fd = fd; + if (linemode & LM_CHAR) + linemode = 0, tty_special_keys(); + else + linemode = 0; + status(-1); + reprint_clear(); +} + +/* + * open another connection + */ +void tcp_open __P4 (char *,id, char *,initstring, char *,host, int,port) +{ + int newtcp_fd, i; + + if (tcp_count+tcp_attachcount >= MAX_CONNECTS) { + PRINTF("#too many open connections.\n"); + return; + } + if (tcp_find(id)>=0) { + PRINTF("#connection `%s' already open.\n", id); + return; + } + + /* find a free slot */ + for (i=0; i<MAX_CONNECTS; i++) { + if (!CONN_INDEX(i).id) + break; + } + if (i == MAX_CONNECTS) { + PRINTF("#internal error, connection table full :(\n"); + return; + } + + if (!(CONN_INDEX(i).host = my_strdup(host))) { + errmsg("malloc"); + return; + } + if (!(CONN_INDEX(i).id = my_strdup(id))) { + errmsg("malloc"); + free(CONN_INDEX(i).host); + return; + } + + /* dial the number by moving the right index in small circles */ + if ((newtcp_fd = tcp_connect(host, port)) < 0) { + free(CONN_INDEX(i).host); + free(CONN_INDEX(i).id); + CONN_INDEX(i).id = 0; + return; + } + + conn_table[newtcp_fd] = i; + CONN_INDEX(i).flags = ACTIVE; + CONN_INDEX(i).state = NORMAL; + CONN_INDEX(i).port = port; + CONN_INDEX(i).fd = newtcp_fd; + + if (tcp_max_fd < newtcp_fd) + tcp_max_fd = newtcp_fd; + if (conn_max_index <= i) + conn_max_index = i+1; + + FD_SET(newtcp_fd, &fdset); /* add socket to select() set */ + tcp_count++; + + if (echo_int && tcp_count) { + PRINTF("#default connection is now `%s'\n", id); + } + tcp_set_main(tcp_fd = newtcp_fd); + if (opt_sendsize) + tcp_write_tty_size(); + + if (initstring) { + parse_instruction(initstring, 0, 0, 1); + history_done = 0; + } +} + +/* + * close a connection + */ +void tcp_close __P1 (char *,id) +{ + int i, sfd; + + status(1); + tty_puts(edattrend); + /* + * because we may be called from get_remote_input() + * if tcp_read gets an EOF, before edattrend is + * printed by get_remote_input() itself. + */ + + if (id) { /* #zap cmd */ + if ((sfd = tcp_find(id)) < 0) { + tty_printf("#no such connection: `%s'\n", id); + return; + } + } else + sfd = tcp_fd; /* connection closed by remote host */ + + shutdown(sfd, 2); + close(sfd); + + abort_edit_fd(sfd); + + tty_printf("#connection on `%s' closed.\n", CONN_LIST(sfd).id); + + if (sfd == tcp_main_fd) { /* main connection closed */ + if (tcp_count == 1) { /* was last connection */ + if (opt_exit) + exit_powwow(); + tty_puts("#no connections left. Type #quit to quit.\n"); + tcp_fd = tcp_main_fd = -1; + } else { + /* must find another connection and promote it to main */ + for (i=0; i<conn_max_index; i++) { + if (!CONN_INDEX(i).id || CONN_INDEX(i).fd == sfd + || (CONN_INDEX(i).flags & SPAWN)) + continue; + tty_printf("#default connection is now `%s'\n", CONN_INDEX(i).id); + tcp_main_fd = CONN_INDEX(i).fd; + break; + } + if (sfd == tcp_main_fd) { + tty_printf("#PANIC! internal error in tcp_close()\nQuitting.\n"); + syserr(NULL); + } + } + tcp_set_main(tcp_main_fd); + } + + if (tcp_fd == sfd) + tcp_fd = -1; /* no further I/O allowed on sfd, as we just closed it */ + + FD_CLR(sfd, &fdset); + if (CONN_LIST(sfd).flags & SPAWN) + tcp_attachcount--; + else + tcp_count--; + CONN_LIST(sfd).flags = 0; + CONN_LIST(sfd).state = NORMAL; + CONN_LIST(sfd).port = 0; + free(CONN_LIST(sfd).host); CONN_LIST(sfd).host = 0; + free(CONN_LIST(sfd).id); CONN_LIST(sfd).id = 0; + if (CONN_LIST(sfd).fragment) { + free(CONN_LIST(sfd).fragment); + CONN_LIST(sfd).fragment = 0; + } + + /* recalculate conn_max_index */ + i = conn_table[sfd]; + if (i+1 == conn_max_index) { + do { + i--; + } while (i>=0 && !CONN_INDEX(i).id); + conn_max_index = i+1; + } + + /* recalculate tcp_max_fd */ + for (i = tcp_max_fd = 0; i<conn_max_index; i++) { + if (CONN_INDEX(i).id && tcp_max_fd < CONN_INDEX(i).fd) + tcp_max_fd = CONN_INDEX(i).fd; + } +} + +/* + * toggle output display from another connection + */ +void tcp_togglesnoop __P1 (char *,id) +{ + int sfd; + + sfd = tcp_find(id); + if (sfd>=0) { + CONN_LIST(sfd).flags ^= ACTIVE; + if (echo_int) { + PRINTF("#connection %s is now %sactive.\n", + CONN_LIST(sfd).id, CONN_LIST(sfd).flags & ACTIVE ? "" : "non"); + } + } else { + PRINTF("#no such connection: %s\n", id); + } +} + +void tcp_spawn __P2 (char *,id, char *,cmd) +{ + int i, childpid, sockets[2]; + + if (tcp_find(id)>=0) { + PRINTF("#connection `%s' already open.\n", id); + return; + } + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) { + errmsg("create socketpair"); + return; + } + unescape(cmd); + + switch (childpid = fork()) { + case 0: + /* child */ + close(0); close(1); close(2); + setsid(); + dup2(sockets[1], 0); + dup2(sockets[1], 1); + dup2(sockets[1], 2); + close(sockets[0]); + close(sockets[1]); + execl("/bin/sh", "sh", "-c", cmd, NULL); + syserr("execl"); + break; + case -1: + close(sockets[0]); + close(sockets[1]); + errmsg("fork"); + return; + } + close(sockets[1]); + + /* Again, we don't want children to inherit sockets */ + fcntl(sockets[0], F_SETFD, FD_CLOEXEC); + + /* now find a free slot */ + for (i=0; i<MAX_CONNECTS; i++) { + if (!CONN_INDEX(i).id) { + conn_table[sockets[0]] = i; + break; + } + } + if (i == MAX_CONNECTS) { + PRINTF("#internal error, connection table full :(\n"); + close(sockets[0]); + return; + } + + if (!(CONN_INDEX(i).host = my_strdup(cmd))) { + errmsg("malloc"); + close(sockets[0]); + return; + } + if (!(CONN_INDEX(i).id = my_strdup(id))) { + errmsg("malloc"); + free(CONN_INDEX(i).host); + close(sockets[0]); + return; + } + CONN_INDEX(i).flags = ACTIVE | SPAWN; + CONN_INDEX(i).state = NORMAL; + CONN_INDEX(i).port = 0; + CONN_INDEX(i).fd = sockets[0]; + + FD_SET(sockets[0], &fdset); /* add socket to select() set */ + tcp_attachcount++; + + if (tcp_max_fd < sockets[0]) + tcp_max_fd = sockets[0]; + if (conn_max_index <= i) + conn_max_index = i+1; + + if (echo_int) { + PRINTF("#successfully spawned `%s' with pid %d\n", id, childpid); + } + + /* + * when the child exits we also get an EOF on the socket, + * so no special care is needed by the SIGCHLD handler. + */ +} + |