/* Copyright 1991, 1993 - 1997 J"orn Rennecke */ #include <signal.h> #include <unistd.h> #include <sys/ioctl.h> #include <fcntl.h> #include <stdio.h> #include "machine.h" #ifdef HAVE_NETDB_H #include <netdb.h> #endif #include "common.h" #include "comm.h" #include "exec.h" #include "object.h" #include "telnet.h" #include "interpret.h" #include "schedule.h" int comm_nfds; struct fd_entry fd_table[MAX_USER+MAX_MISC_DESCRIPTORS]; int port_number = PORTNO; static int listen_socket; static struct sockaddr_in host_ip_addr; static struct in_addr host_ip_number; static int num_users; static struct interactive *first_user_for_flush; static fd_set nullfds, *readfdsp; static void new_user(struct fd_entry *, int accept_socket); static void user_input(struct fd_entry *, int accept_socket); static void set_socket_nonblocking(int); static void set_close_on_exec(int); static int telnet_neg(struct interactive *ip); static void remove_interactive(struct interactive *ip); static void remove_flush_entry(struct interactive *ip); static void urgent_data_handler(); void init_comm() { int new_socket; int tmp; char host_name[128]; struct hostent *he; /* Need to use gethostbyname to find out if it's PF_INET or PF_INET6 ??? */ if (gethostname(host_name, sizeof host_name) < 0) { perror("gethostname"); fatal("\n"); } he = gethostbyname(host_name); if (!he) { perror("gethostbyname"); fatal("\n"); } host_ip_addr.sin_family = he->h_addrtype; host_ip_addr.sin_port = htons((u_short)port_number); host_ip_addr.sin_addr.s_addr = INADDR_ANY; new_socket = socket(host_ip_addr.sin_family, SOCK_STREAM, 0); if (new_socket < 0) { perror("socket"); exit(1); } tmp = 1; if (setsockopt(new_socket, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof tmp) < 0) { perror("setsockopt"); exit(1); } if (bind( new_socket, (struct sockaddr *)&host_ip_addr, sizeof host_ip_addr)) { if (errno == EADDRINUSE) { fatal("Socket already bound.\n"); } perror("bind"); exit(1); } if (listen(new_socket, 7) == -1) { perror("listen"); exit(1); } set_socket_nonblocking(new_socket); set_close_on_exec(new_socket); if (new_socket >= comm_nfds) comm_nfds = new_socket + 1; signal(SIGPIPE, SIG_IGN); signal(SIGURG, urgent_data_handler); fd_table[new_socket].f = new_user; listen_socket = new_socket; } void start_comm(fd_set *fdsp) { readfdsp = fdsp; FD_SET(listen_socket, fdsp); } void set_socket_nonblocking(int new_socket) { int tmp; tmp = 1; #ifdef USE_IOCTL_FIONBIO if (ioctl(new_socket, FIONBIO, &tmp)) { perror("ioctl socket FIONBIO"); exit(1); } #else /* !USE_IOCTL_FIONBIO */ #ifdef USE_FCNTL_O_NDELAY if (fcntl(new_socket, F_SETFL, O_NDELAY) < 0) { #else if (fcntl(new_socket, F_SETFL, FNDELAY) < 0) { #endif perror("fcntl socket FNDELAY"); exit(1); } #endif /* !USE_IOCTL_FIONBIO */ } static void set_close_on_exec(int new_socket) { fcntl(new_socket, F_SETFD, 1L); } void initialize_host_ip_number() { char host_name[145]; struct hostent *hp; if (gethostname(host_name, sizeof host_name-1) < 0) { perror("gethostname"); exit(1); } hp = gethostbyname(host_name); if (!hp) { perror("getting ipaddr of this machine:"); exit(1); } host_ip_number = ((struct sockaddr_in *)hp->h_addr)->sin_addr; } const char *query_host_ip_number() { return inet_ntoa(host_ip_number); } #define TS_DEACTIVATED 0 #define TS_COMMAND 1 #define TS_CHARMODE 2 #define TS_STRINGMODE 3 #define TS_IAC 4 #define TS_WILL 5 #define TS_WONT 6 #define TS_DO 7 #define TS_DONT 8 #define TS_SB 9 #define TS_SB_IAC 10 #define TS_GOBBLE_NL 11 #define TS_GOBBLE_CR 12 #define TS_SYNCH 13 static volatile long urgent_data_time; static void urgent_data_handler() { ISR_SET_JOBS(JOB(urgent_data)); urgent_data_time = current_time; signal(SIGURG, urgent_data_handler); } void urgent_data() { struct timeval timeout; fd_set exceptfds; int i; struct interactive *ip; CLEAR_JOB(urgent_data); timeout.tv_sec = 0; timeout.tv_usec = 0; memset((char *)&exceptfds, 255, comm_nfds + 7 >> 3); if (select(comm_nfds, 0, 0, &exceptfds, &timeout) > 0) { for (i = comm_nfds; --i >= 0;) { ip = fd_table[i].ip; if (!ip) continue; if (FD_ISSET(i, &exceptfds)) { ip->tn_data_state2 = ip->tn_data_state; ip->tn_data_state = TS_SYNCH; switch (ip->tn_state) { case TS_COMMAND: case TS_CHARMODE: case TS_STRINGMODE: case TS_GOBBLE_NL: case TS_GOBBLE_CR: ip->tn_state = TS_SYNCH; } call_hook(ip->hook[IH_SYNCH], ip->object, 0); } } /* Maybe the data didn't arrive yet, so try again later. But don't waste time doing it for too long. */ } else if (current_time - urgent_data_time < 600) { SET_JOB(urgent_data); } REMAINING_JOBS(urgent_data); } static void new_user(struct fd_entry *fde, int accept_socket) { struct sockaddr_in addr; int length; int new_socket; struct interactive *ip; union svalue addr_string, ob; length = sizeof addr; new_socket = accept(accept_socket, (struct sockaddr *)&addr, &length); if (new_socket < 0) { if (errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) return; fatal("accept failed\n"); } set_socket_nonblocking(new_socket); set_close_on_exec(new_socket); if (new_socket >= NELEM(fd_table)) { union svalue sv; sv = driver_hook[H_NO_IPC_SLOT]; if (!SV_IS_NUMBER(sv) && SV_IS_STRING(sv)) { struct counted_string cstr = sv_string2(sv); write(new_socket, cstr.start, cstr.len); } goto failure1; } ip = alloc_gen(sizeof *ip); if (!ip) { goto failure1; } #ifdef ACCESS_CONTROL { char *message; message = allow_host_access(&addr.sin_addr, &ip->access_class); #ifdef ACCESS_LOG { FILE *log_file = fopen (ACCESS_LOG, "a"); if (log_file) { fprintf(log_file, "%s: %s\n", inet_ntoa(addr->sin_addr), message ? "denied" : "granted"); fclose(log_file); } } #endif if (message) { write(new_socket, message, strlen(message)); write(new_socket, "\r\n", 2); goto failure2; } } #endif /* ACCESS_CONTROL */ addr_string = make_string((uint8 *)&addr.sin_addr, 4); push_svalue(addr_string); ob = call_hook(driver_hook[H_NEW_USER], master_ob, 1); if (SV_IS_NUMBER(ob)) goto failure3; if (SV_TYPE(ob) != T_OBJECT) { goto failure4; } else { struct object_x *x = alloc_object_x(ob); if (!x || OX_FLAGS(x) & O_X_INTERACTIVE) { failure4: FREE_ALLOCED_SVALUE(ob); failure3: FREE_SVALUE(addr_string); failure2: free_gen(ip); failure1: close(new_socket); return; } OX_FLAGS(x) |= O_X_INTERACTIVE; if (SV_REFINC(ob)) O_REF(&SV_OBJECT(ob))++; FD_SET(new_socket, readfdsp); if (new_socket >= comm_nfds) comm_nfds = new_socket + 1; x->user = ip; ip->socket = new_socket; ip->addr = addr.sin_addr; ip->object = ob; ip->message_length = 0; memset(ip->charset, 255, sizeof ip->charset); ip->charset['\n'/8] &= ~(1 << '\n' % 8); ip->charset['\0'/8] &= ~(1 << '\0' % 8); ip->last_command_time = current_time; ip->total_commands = 0; ip->quote_iac = 1; ip->tn_state = TS_COMMAND; ip->tn_data_state = TS_COMMAND; ip->tn_start = 0; ip->tn_end = 0; fd_table[new_socket].f = user_input; fd_table[new_socket].ip = ip; num_users++; push_svalue(ob); PUSH_REFERENCED_SVALUE(addr_string); PUSH_NUMBER(addr.sin_port); call_hook(driver_hook[H_LOGON], ob, 3); return; } } static void user_input(struct fd_entry *fde, int in_socket) { struct interactive *ip; int len; ip = fde->ip; len = USER_INPUT_BUFSIZE - ip->input_end; len = read(in_socket, ip->input + ip->input_end, len); if (len <= 0) { if (len == 0) { remove_interactive(ip); return; } switch(errno) { default: perror("read() from user socket"); remove_interactive(ip); return; } } ip->input_end += len; ip->in_total += len; len = telnet_neg(ip); if (len >= 0) { union svalue sv; ip->total_commands++; push_svalue(make_astring(ip->input, len)); sv = call_hook(ip->hook[IH_INPUT], ip->object, 1); FREE_SVALUE(sv); ip->tn_state = ip->tn_data_state; len = telnet_neg(ip); if (len >= 0) { extern struct interactive **pending_link; ip->chars_ready = len; *pending_link = ip; pending_link = &ip->next_pending; FD_CLR(ip->socket, readfdsp); } } } static void user_output(struct fd_entry *fde, int in_socket) { struct interactive *ip; struct timeval timeout; ip = fde->ip; FD_SET(ip->socket, &nullfds); timeout.tv_sec = 0; timeout.tv_usec = 0; if (select(ip->socket + 1, 0, &nullfds, 0, &timeout) > 0) { /* Socket is writable again. */ fde->f = user_input; FD_CLR(ip->socket, &nullfds); call_hook(ip->hook[IH_OUTPUT], ip->object, 0); fde->f(fde, in_socket); } } void pending_commands(struct interactive *ip) { for (; ip; ip = ip->next_pending) { union svalue sv; int len; if (ip->chars_ready < 0) continue; ip->total_commands++; push_svalue(make_astring(ip->input, ip->chars_ready)); sv = call_hook(ip->hook[IH_INPUT], ip->object, 1); FREE_SVALUE(sv); len = telnet_neg(ip); if (len >= 0) { ip->chars_ready = len; *pending_link = ip; pending_link = &ip->next_pending; } else { FD_SET(ip->socket, readfdsp); } } } static void h_telnet(struct interactive *ip, int c1, int c2) { union svalue sv; PUSH_NUMBER(c1); PUSH_NUMBER(c2); sv = call_hook(ip->hook[IH_TELNET_NEG], ip->object, 2); FREE_SVALUE(sv); } /* telnet_neg returns number of chars (maybe 0) if command found, else -1. */ static int telnet_neg(struct interactive *ip) { char *to, *from; int ch; char *first, *end; first = ip->input; from = &first[ip->tn_end]; end = &first[ip->input_end]; if (from >= end) { ip->input_end = ip->tn_end = ip->command_end; return -1; } to = &first[ip->command_end]; do { ch = (*from++ & 0xff); switch(ip->tn_state) { int state; ts_data: if (from >= end) { ip->input_end = ip->tn_end = ip->command_end = to - first; if (ip->input_end >= USER_INPUT_BUFSIZE) { ip->input_end = ip->tn_end = ip->command_end = 0; /* this looks like a super-long command. * Return the input so far as partial command. */ return to - first; } return -1; } ch = (*from++ & 0xff); case TS_COMMAND: switch(ch) { case IAC: new_iac: state = TS_IAC; change_state: ip->tn_state = state; continue; case '\b': /* Backspace */ case 0x7f: /* Delete */ if (to > first) to--; goto ts_data; default: *to++ = ch; case '\0': goto ts_data; case '\r': /* rfc854: '\r' '\n' is the correct NVT sequence */ if (from >= end) { ip->tn_state = TS_GOBBLE_NL; } else { ch = (*from & 0xff); if (ch == '\n') from++; } input_complete: ip->command_end = 0; ip->tn_end = from - first; return to - first; case '\n': /* alas, some broken clients send '\n' '\r'. */ ip->tn_state = TS_GOBBLE_CR; goto input_complete; } case TS_GOBBLE_NL: if (ch != '\n') from--; state = ip->tn_data_state; goto change_state; case TS_GOBBLE_CR: if (ch != '\r') from--; state = ip->tn_data_state; goto change_state; case TS_DEACTIVATED: return -1; case TS_CHARMODE: if (ch == IAC) { state = TS_IAC; goto change_state; } *to++ = ch; goto input_complete; continue_stringmode: ch = (*from++ & 0xff); case TS_STRINGMODE: if (ch == IAC) { ip->tn_state = TS_IAC; if (to > first) goto input_complete; continue; } *to++ = ch; if (from < end) goto continue_stringmode; goto input_complete; case TS_IAC: switch(ch) { case WILL: state = TS_WILL; goto change_state; case WONT: state = TS_WONT; goto change_state; case DO: state = TS_DO; goto change_state; case DONT: state = TS_DONT; goto change_state; case SB: ip->tn_start = to - first; state = TS_SB; goto change_state; case DM: data_mark: if (ip->tn_data_state == TS_SYNCH) { struct timeval timeout; FD_SET(ip->socket, &nullfds); timeout.tv_sec = 0; timeout.tv_usec = 0; if (! select(ip->socket + 1, 0, 0, &nullfds, &timeout)) { /* Synch operation finished */ ip->tn_data_state = ip->tn_data_state2; } FD_CLR(ip->socket, &nullfds); } break; case NOP: case GA: default: break; } state = ip->tn_data_state; goto change_state; case TS_WILL: h_telnet(ip, WILL, ch); state = ip->tn_data_state; goto change_state; case TS_WONT: h_telnet(ip, WONT, ch); state = ip->tn_data_state; goto change_state; case TS_DO: h_telnet(ip, DO, ch); state = ip->tn_data_state; goto change_state; case TS_DONT: h_telnet(ip, DONT, ch); state = ip->tn_data_state; goto change_state; case TS_SB: if (ch == IAC) { state = TS_SB_IAC; goto change_state; } *to++ = ch; continue; case TS_SB_IAC: { mp_int size; uint8 *str; union svalue sv; if (ch == IAC) { *to++ = ch; state = TS_SB; goto change_state; } else if (ch == SE && (size = (to - first) - ip->tn_start - 1) >= 0 && (str = &ip->input[ip->tn_start], (sv = make_string(str + 1, size)).i) ) { PUSH_NUMBER(SB); PUSH_NUMBER(*str); PUSH_REFERENCED_SVALUE(sv); sv = call_hook(ip->hook[IH_TELNET_NEG], ip->object, 3); FREE_SVALUE(sv); } to = &first[ip->tn_start]; state = ip->tn_data_state; goto change_state; } case TS_SYNCH: if (ch == IAC) goto new_iac; if (ch == DM) goto data_mark; continue; default: debug_message("Bad state: 0x%x\n", ip->tn_state); state = ip->tn_data_state; goto change_state; } } while(from < end); ip->input_end = ip->tn_end = ip->command_end = to - first; if (ip->input_end == USER_INPUT_BUFSIZE) { /* telnet negotiation shouldn't have such large data chunks. * Ignore all data altogether and return to text mode. */ ip->input_end = ip->tn_end = ip->command_end = 0; ip->tn_state = ip->tn_data_state; } return -1; } static void remove_interactive(struct interactive *ip) { union svalue ob; struct object_x * x; adtstat[COMM_COMMANDS] += ip->total_commands; adtstat[COMM_IN_TOTAL] += ip->in_total; adtstat[COMM_OUTPACKETS] += ip->out_packets; adtstat[COMM_OUTTOTAL] += ip->out_total; FD_CLR(ip->socket, readfdsp); close(ip->socket); ob = ip->object; x = SV_OBJECT(ob).x.x; x->user = 0; if (!x->shadowing.i && !x->shadowed_by.i && O_REF(&SV_OBJECT(ob)) == 1) { SV_OBJECT(ob).x.uid = x->uid; free_block((uint8 *)x - sizeof(char *) + 1, sizeof *x); } FREE_ALLOCED_SVALUE(ob); free_gen(ip); } void remove_deactivated_interactives() { int i = comm_nfds; do { struct interactive *ip = fd_table[i-1].ip; if (ip && ip->tn_state == TS_DEACTIVATED) { remove_interactive(ip); } } while(--i); CLEAR_JOB(remove_deactivated_interactives); EXTRA_JOBS(); } /* * deactivated_interactive() may be called from user_input(), * pending_commands() or EXTRA_JOBS(). Note that the interactive being * deactivated is not necessarily the one being processed. */ static void deactivate_interactive(struct interactive *ip) { if (ip && !ip->tn_state != TS_DEACTIVATED) { /* set tn_data_state too in case we are called from inside telnet_neg */ ip->tn_state = ip->tn_data_state = TS_DEACTIVATED; ip->chars_ready = -1; SET_JOB(remove_deactivated_interactives); } } void flush_message(struct interactive *ip, p_int len) { int n, length; int retries; if ( !(length = len) ) { if ( (length = ip->message_length) ) remove_flush_entry(ip); else return; } ip->message_length = 0; for (retries = 6;;) { if ((n = write(ip->socket, ip->message_buf, length)) >= 0) break; switch (errno) { case EINTR: if (--retries) continue; case EWOULDBLOCK: fd_table[ip->socket].f = user_output; push_svalue(make_string(ip->message_buf + n, length - n)); PUSH_NUMBER(errno); call_hook(ip->hook[IH_BLOCKED], ip->object, 2); return; default: perror("flush_message(): write"); break; } deactivate_interactive(ip); return; } ip->out_packets++; ip->out_total += n; if (n != length) { fd_table[ip->socket].f = user_output; push_svalue(make_string(ip->message_buf + n, length - n)); call_hook(ip->hook[IH_BLOCKED], ip->object, 1); } return; } void add_message(struct interactive *ip, char *str, p_int len) { int chunk, length; int min_length; char clobbered_char; int old_message_length; char *source, *end, *dest; min_length = MAX_OUTPUT_PACKET_SIZE; old_message_length = ip->message_length; adtstat[ADDMESS_CALLS]++; source = str; clobbered_char = source[len]; source[len] = '\0'; dest = &ip->message_buf[old_message_length]; end = &ip->message_buf[sizeof ip->message_buf]; do { char c; if (dest == end) { c = '\0'; } else for (;;) { c = *source++; if ( ip->charset[(c&0xff)>>3] & 1<<(c&7) ) { *dest++ = c; } else if (c == '\0') { source--; break; } else if (c == '\n') { /* Insert CR before NL */ *dest++ = '\r'; if (dest == end) break; *dest++ = c; } else if ( (unsigned char)c == IAC && ip->quote_iac) { *dest++ = c; if (dest == end) break; *dest++ = c; } if (dest == end) { c = '\0'; break; } } chunk = dest - ip->message_buf; if (chunk < min_length) break; /* send */ flush_message(ip, chunk); dest = &ip->message_buf[0]; if (c) *dest++ = c; } while (*source); ip->message_length = length = dest - ip->message_buf; if (length) { if (!old_message_length ) { /* buffer became 'dirty' */ if ( (ip->next_user_for_flush = first_user_for_flush) ) { first_user_for_flush->previous_user_for_flush = ip; } ip->previous_user_for_flush = 0; first_user_for_flush = ip; SET_JOB(flush_all_output); } } else { if (old_message_length) { /* buffer has become empty */ remove_flush_entry(ip); } } str[len] = clobbered_char; } static void remove_flush_entry(struct interactive *ip) { if (ip->previous_user_for_flush) { ip->previous_user_for_flush->next_user_for_flush = ip->next_user_for_flush; } else { first_user_for_flush = ip->next_user_for_flush; } if (ip->next_user_for_flush) { ip->next_user_for_flush->previous_user_for_flush = ip->previous_user_for_flush; } } void flush_all_output() { struct interactive *u; CLEAR_JOB(flush_all_output); if ( (u = first_user_for_flush) ) { do { flush_message(u, u->message_length); } while ( (u = u->next_user_for_flush) ); first_user_for_flush = 0; } REMAINING_JOBS(flush_all_output); } const char *query_host_name() { static char name[21]; char *p; gethostname(name, sizeof name-1); /* some platforms return the FQHN, but we don't want it. */ p = strchr(name, '.'); if (p) *p = '\0'; return name; } svalue *f_text_message(svalue *sp, struct frame *fp) { svalue sv = *sp; if (SV_IS_NUMBER(sv) || !SV_IS_STRING(sv)) bad_efun_arg(1); else { struct object_x *ox= SV_OBJECT(fp->object).x.x; if (OX_FLAGS(ox) & O_X_INTERACTIVE) { struct counted_string cs = sv_string2(sv); add_message(ox->user, cs.start, cs.len); } FREE_ALLOCED_SVALUE(sv); sp--; } return sp; } svalue *f_set_interactive_hook(svalue *sp, struct frame *fp) { svalue hn, hv, sv, *svp; struct object_x *ox; hn = sp[-1]; if (!SV_IS_NUMBER(hn)) { bad_efun_arg(1); return sp; } sv = hv = sp[0]; ox = SV_OBJECT(fp->object).x.x; if (OX_FLAGS(ox) & O_X_INTERACTIVE) { svp = &ox->user->hook[hn.i >> 1]; sv = *svp; *svp = hv; } FREE_SVALUE(sv); return sp - 2; } #ifdef ACCESS_CONTROL void refresh_access_data( void (*add_entry)(struct in_addr*, long*) ) { int i = comm_nfds; do { struct interactive *ip = fd_table[i-1].ip; if (ip) (*add_entry)(&ip->addr, &ip->access_class); } while(--i); } #endif /* ACCESS_CONTROL */