/* 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 */