# define INCLUDE_TELNET # include "dgd.h" # include "str.h" # include "array.h" # include "object.h" # include "interpret.h" # include "data.h" # include "comm.h" # ifndef TELOPT_LINEMODE # define TELOPT_LINEMODE 34 /* linemode option */ # define LM_MODE 1 # define MODE_EDIT 0x01 # endif typedef struct _user_ { object *obj; /* associated object */ struct _user_ *prev; /* preceding user */ struct _user_ *next; /* next user */ unsigned int inbufsz; /* bytes in input buffer */ unsigned int outbufsz; /* bytes in output buffer */ char flags; /* connection flags */ char state; /* telnet state */ short newlines; /* # of newlines in input buffer */ connection *conn; /* connection */ char *inbuf; /* input buffer */ char *outbuf; /* output buffer */ unsigned int osleft; /* bytes of output string left */ } user; /* flags */ # define CF_ECHO 0x01 /* client echoes input */ # define CF_TELNET 0x02 /* telnet connection */ # define CF_GA 0x04 /* send GA after prompt */ # define CF_SEENCR 0x08 /* just seen a CR */ # define CF_NOPROMPT 0x10 /* no prompt in telnet output */ # define CF_BLOCKED 0x20 /* binary connection blocked */ # define CF_UDP 0x40 /* receive UDP datagrams on binary connection */ # define CF_UDPDATA 0x80 /* UDP data received */ /* state */ # define TS_DATA 0 # define TS_IAC 1 # define TS_DO 2 # define TS_DONT 3 # define TS_WILL 4 # define TS_WONT 5 # define TS_SB 6 # define TS_SE 7 static user *users; /* array of users */ static user *lastuser; /* last user checked */ static user *freeuser; /* linked list of free users */ static int maxusers; /* max # of users */ static int nusers; /* # of users */ static long newlines; /* # of newlines in all input buffers */ static object *this_user; /* current user */ static bool flush; /* do telnet output buffers need flushing? */ /* * NAME: comm->init() * DESCRIPTION: initialize communications */ bool comm_init(n, telnet_port, binary_port) int n; unsigned int telnet_port, binary_port; { register int i; register user *usr; users = ALLOC(user, maxusers = n); for (i = n, usr = users + i; i > 0; --i) { --usr; usr->obj = (object *) NULL; usr->next = usr + 1; } users[n - 1].next = (user *) NULL; freeuser = usr; lastuser = (user *) NULL; nusers = newlines = 0; flush = FALSE; return conn_init(n, telnet_port, binary_port); } /* * NAME: comm->finish() * DESCRIPTION: terminate connections */ void comm_finish() { comm_flush(FALSE); conn_finish(); } /* * NAME: comm->listen() * DESCRIPTION: start listening on telnet port and binary port */ void comm_listen() { conn_listen(); } /* * NAME: comm->new() * DESCRIPTION: accept a new connection */ static user *comm_new(obj, conn, telnet) object *obj; connection *conn; bool telnet; { static char init[] = { (char) IAC, (char) WONT, (char) TELOPT_ECHO, (char) IAC, (char) DO, (char) TELOPT_LINEMODE }; register user *usr; if (obj->flags & (O_USER | O_EDITOR)) { error("User object is already used for user or editor"); } d_wipe_extravar(o_dataspace(obj)); usr = freeuser; freeuser = usr->next; if (lastuser != (user *) NULL) { usr->prev = lastuser->prev; usr->prev->next = usr; usr->next = lastuser; lastuser->prev = usr; } else { usr->prev = usr; usr->next = usr; lastuser = usr; } usr->obj = obj; obj->flags |= O_USER; obj->etabi = usr - users; usr->inbufsz = 0; usr->conn = conn; if (telnet) { /* initialize connection */ usr->flags = CF_TELNET | CF_ECHO; usr->state = TS_DATA; usr->newlines = 0; m_static(); usr->inbuf = ALLOC(char, INBUF_SIZE); usr->outbuf = ALLOC(char, OUTBUF_SIZE + TELBUF_SIZE); m_dynamic(); memcpy(usr->outbuf, init, usr->outbufsz = sizeof(init)); flush = TRUE; } else { usr->flags = 0; usr->outbufsz = 0; } nusers++; return usr; } /* * NAME: comm->del() * DESCRIPTION: delete a connection */ static void comm_del(f, usr, force) register frame *f; register user *usr; bool force; { object *obj, *olduser; obj = usr->obj; conn_del(usr->conn); if (usr->flags & CF_TELNET) { newlines -= usr->newlines; FREE(usr->outbuf); FREE(usr->inbuf); } if ((obj->flags & O_PENDIO) && usr->osleft != 0) { /* remove old buffer */ d_wipe_extravar(o_dataspace(obj)); } obj->flags &= ~(O_USER | O_PENDIO); usr->obj = (object *) NULL; if (usr->next == usr) { lastuser = (user *) NULL; } else { usr->next->prev = usr->prev; usr->prev->next = usr->next; if (usr == lastuser) { lastuser = usr->next; } } usr->next = freeuser; freeuser = usr; --nusers; olduser = this_user; if (ec_push((ec_ftn) NULL)) { if (obj == olduser) { this_user = (object *) NULL; } else { this_user = olduser; } error((char *) NULL); } else { this_user = obj; (--f->sp)->type = T_INT; f->sp->u.number = force; if (i_call(f, obj, "close", 5, TRUE, 1)) { i_del_value(f->sp++); } if (obj == olduser) { this_user = (object *) NULL; } else { this_user = olduser; } ec_pop(); } } /* * NAME: comm->write() * DESCRIPTION: write more data to a connection; return bytes written (binary) * or bytes left in output buffer (telnet) */ static int comm_write(usr, str, prompt) register user *usr; string *str; int prompt; { register char *p, *q; register unsigned int len; register int size, n; dataspace *data; data = o_dataspace(usr->obj); if (str != (string *) NULL) { /* new string to write */ if (usr->obj->flags & O_PENDIO) { if (usr->flags & CF_TELNET) { /* * discard new data as long as buffer not flushed, to avoid * half-written telnet escape sequences */ usr->flags |= CF_NOPROMPT; return usr->outbufsz; } else { /* * binary connection: discard old data to make room for new */ usr->obj->flags &= ~O_PENDIO; d_wipe_extravar(data); } } len = str->len; p = str->text; } else { /* flush */ len = usr->osleft; if (len != 0) { /* fetch pending output string */ str = d_get_variable(data, data->nvariables - 1)->u.string; p = str->text + str->len - len; } } if (usr->flags & CF_TELNET) { /* * telnet connection */ size = usr->outbufsz; q = usr->outbuf + size; while (len != 0) { if (size >= OUTBUF_SIZE - 1) { n = conn_write(usr->conn, q = usr->outbuf, size); if (n != size) { if (n > 0) { size -= n; for (p = q + n, n = size; n > 0; --n) { *q++ = *p++; } } break; } size = 0; } if (UCHAR(*p) == IAC) { /* * double the telnet IAC character */ *q++ = (char) IAC; size++; } else if (*p == LF) { /* * insert CR before LF */ *q++ = CR; size++; } else if ((*p & 0x7f) < ' ' && *p != HT && *p != BEL && *p != BS && *p != ESC ) { /* * illegal character */ p++; --len; continue; } *q++ = *p++; --len; size++; } if (prompt && (usr->flags & (CF_GA | CF_NOPROMPT)) == CF_GA && len == 0 && (usr->obj == this_user || usr->outbuf[size - 1] != LF)) { /* * If no output has been discarded (which would include the prompt), * add go-ahead, for which there is always space at this point. */ *q++ = (char) IAC; *q++ = (char) GA; size += 2; usr->flags |= CF_NOPROMPT; /* send go-ahead only once */ } if (str == (string *) NULL) { /* * try to flush the buffer */ n = conn_write(usr->conn, q = usr->outbuf, size); if (n > 0) { size -= n; for (p = q + n, n = size; n > 0; --n) { *q++ = *p++; } } } usr->outbufsz = size; } else { /* * binary connection */ size = conn_write(usr->conn, p, len); if (size > 0) { len -= size; } } if (usr->obj->flags & O_PENDIO) { /* old pending output */ if (len == 0) { if (usr->osleft != 0) { /* get rid of output buffer */ d_wipe_extravar(data); } if (usr->outbufsz == 0) { /* no more pending output */ usr->obj->flags &= ~O_PENDIO; usr->flags &= ~CF_NOPROMPT; } } } else if (len != 0 || (str == (string *) NULL && usr->outbufsz != 0)) { /* leftover output */ usr->obj->flags |= O_PENDIO; if (len != 0 && str != (string *) NULL) { value v; /* * buffer the remainder of the string */ v.type = T_STRING; v.u.string = str; d_assign_var(data, d_get_variable(data, data->nvariables - 1), &v); } } else { /* no delayed output at all */ usr->flags &= ~CF_NOPROMPT; } usr->osleft = len; return size; } /* * NAME: comm->send() * DESCRIPTION: send a message to a user */ int comm_send(obj, str) object *obj; string *str; { user *usr; int size; usr = &users[UCHAR(obj->etabi)]; size = comm_write(usr, str, FALSE); if (usr->flags & CF_TELNET) { flush = TRUE; return str->len; } else { return size; } } /* * NAME: comm->udpsend() * DESCRIPTION: send a message on the UDP channel of a binary connection */ int comm_udpsend(obj, str) object *obj; string *str; { register user *usr; usr = &users[UCHAR(obj->etabi)]; if (!(usr->flags & CF_UDP)) { error("Object has no UDP channel"); } if (!(usr->flags & CF_UDPDATA)) { error("No datagrams received yet"); } return conn_udpwrite(usr->conn, str->text, str->len); } /* * NAME: comm_telnet() * DESCRIPTION: add telnet data to output buffer */ static void comm_telnet(usr, buf, len) register user *usr; char *buf; register unsigned int len; { register int size; register char *p, *q; if (usr->outbufsz > OUTBUF_SIZE + TELBUF_SIZE - 2 - len) { /* * attempt to flush the buffer before adding telnet data */ size = conn_write(usr->conn, usr->outbuf, usr->outbufsz); if (size <= 0 || usr->outbufsz - size > OUTBUF_SIZE + TELBUF_SIZE - 2 - len) { return; /* failed to make room; abort */ } /* shift buffer to make room for new data */ usr->outbufsz -= size; for (p = usr->outbuf, q = p + size; size > 0; --size) { *p++ = *q++; } } memcpy(usr->outbuf + usr->outbufsz, buf, len); usr->outbufsz += len; flush = TRUE; } /* * NAME: comm->echo() * DESCRIPTION: turn on/off input echoing for a user */ void comm_echo(obj, echo) object *obj; int echo; { register user *usr; char buf[3]; usr = &users[UCHAR(obj->etabi)]; if ((usr->flags & CF_TELNET) && echo != (usr->flags & CF_ECHO)) { buf[0] = (char) IAC; buf[1] = (echo) ? WONT : WILL; buf[2] = TELOPT_ECHO; comm_telnet(usr, buf, 3); usr->flags ^= CF_ECHO; } } /* * NAME: comm->flush() * DESCRIPTION: flush output to all users */ void comm_flush(prompt) int prompt; { register user *usr; register int i; if (!flush) { return; } flush = FALSE; for (usr = lastuser, i = nusers; i != 0; usr = usr->next, --i) { if (usr->outbufsz != 0 && comm_write(usr, (string *) NULL, prompt) != 0) { /* couldn't flush everything */ flush = TRUE; } } } /* * NAME: comm->block() * DESCRIPTION: suspend or release input from a user */ void comm_block(obj, flag) object *obj; int flag; { register user *usr; usr = &users[UCHAR(obj->etabi)]; if (flag) { usr->flags |= CF_BLOCKED; } else { usr->flags &= ~CF_BLOCKED; } conn_block(usr->conn, flag); } /* * NAME: comm->receive() * DESCRIPTION: receive a message from a user */ void comm_receive(f, poll) register frame *f; int poll; { static char intr[] = { '\177' }; static char brk[] = { '\034' }; static char ayt[] = { CR, LF, '[', 'Y', 'e', 's', ']', CR, LF }; static char tm[] = { (char) IAC, (char) WONT, (char) TELOPT_TM }; static char will_sga[] = { (char) IAC, (char) WILL, (char) TELOPT_SGA }; static char wont_sga[] = { (char) IAC, (char) WONT, (char) TELOPT_SGA }; static char mode_edit[] = { (char) IAC, (char) SB, (char) TELOPT_LINEMODE, (char) LM_MODE, (char) MODE_EDIT, (char) IAC, (char) SE }; char buffer[BINBUF_SIZE]; connection *conn; object *o; register user *usr; register int n, i, state, flags, nls; register char *p, *q; n = conn_select(!poll && newlines == 0); if (n <= 0 && newlines == 0) { /* * call_out to do, or timeout */ return; } if (ec_push((ec_ftn) NULL)) { this_user = (object *) NULL; error((char *) NULL); /* pass on error */ } if (nusers < maxusers) { /* * accept new telnet connection */ conn = conn_tnew(); if (conn != (connection *) NULL) { if (ec_push((ec_ftn) NULL)) { conn_del(conn); /* delete connection */ error((char *) NULL); /* pass on error */ } call_driver_object(f, "telnet_connect", 0); if (f->sp->type != T_OBJECT) { fatal("driver->telnet_connect() did not return an object"); } o = &otable[f->sp->oindex]; f->sp++; endthread(); comm_new(o, conn, TRUE); ec_pop(); this_user = o; if (i_call(f, o, "open", 4, TRUE, 0)) { i_del_value(f->sp++); endthread(); comm_flush(TRUE); } this_user = (object *) NULL; } } while (nusers < maxusers) { /* * accept new binary connection */ conn = conn_bnew(); if (conn == (connection *) NULL) { break; } if (ec_push((ec_ftn) NULL)) { conn_del(conn); /* delete connection */ error((char *) NULL); /* pass on error */ } call_driver_object(f, "binary_connect", 0); if (f->sp->type != T_OBJECT) { fatal("driver->binary_connect() did not return an object"); } o = &otable[f->sp->oindex]; f->sp++; endthread(); usr = comm_new(o, conn, FALSE); ec_pop(); this_user = o; if (i_call(f, o, "open", 4, TRUE, 0)) { if (f->sp->type != T_INT || f->sp->u.number != 0) { i_del_value(f->sp); if (o->flags & O_USER) { /* open UDP channel */ usr->flags |= CF_UDP; conn_udp(conn); } } f->sp++; endthread(); comm_flush(TRUE); } this_user = (object *) NULL; } for (i = nusers; i > 0; --i) { usr = lastuser; lastuser = usr->next; if ((usr->obj->flags & O_PENDIO) && conn_wrdone(usr->conn)) { comm_write(usr, (string *) NULL, FALSE); if (!(usr->obj->flags & O_PENDIO) && !(usr->flags & CF_TELNET)) { /* callback */ this_user = usr->obj; if (i_call(f, this_user, "message_done", 12, TRUE, 0)) { i_del_value(f->sp++); endthread(); comm_flush(TRUE); } this_user = (object *) NULL; break; } } if (usr->flags & CF_BLOCKED) { continue; /* no input on this connection */ } if (usr->flags & CF_TELNET) { /* * telnet connection */ if (usr->inbufsz != INBUF_SIZE) { p = usr->inbuf + usr->inbufsz; n = conn_read(usr->conn, p, INBUF_SIZE - usr->inbufsz); if (n < 0) { if (usr->inbufsz == 0) { /* * empty buffer & no more input */ comm_del(f, usr, FALSE); endthread(); /* this cannot be in comm_del() */ comm_flush(FALSE); break; } else if (p[-1] != LF) { /* * add a newline at the end */ *p = LF; n = 1; } } flags = usr->flags; state = usr->state; nls = usr->newlines; q = p; while (n > 0) { switch (state) { case TS_DATA: switch (UCHAR(*p)) { case '\0': flags &= ~CF_SEENCR; break; case IAC: state = TS_IAC; break; case BS: case 0x7f: if (q != usr->inbuf && q[-1] != LF) { --q; } flags &= ~CF_SEENCR; break; case CR: nls++; newlines++; *q++ = LF; flags |= CF_SEENCR; break; case LF: if ((flags & CF_SEENCR) != 0) { flags &= ~CF_SEENCR; break; } nls++; newlines++; /* fall through */ default: *q++ = *p; flags &= ~CF_SEENCR; break; } break; case TS_IAC: switch (UCHAR(*p)) { case IAC: *q++ = *p; state = TS_DATA; break; case DO: state = TS_DO; break; case DONT: state = TS_DONT; break; case WILL: state = TS_WILL; break; case WONT: state = TS_WONT; break; case SB: state = TS_SB; break; case IP: comm_telnet(usr, intr, sizeof(intr)); state = TS_DATA; break; case BREAK: comm_telnet(usr, brk, sizeof(brk)); state = TS_DATA; break; case AYT: comm_telnet(usr, ayt, sizeof(ayt)); state = TS_DATA; break; default: /* let's hope it wasn't important */ state = TS_DATA; break; } break; case TS_DO: if (UCHAR(*p) == TELOPT_TM) { comm_telnet(usr, tm, sizeof(tm)); } else if (UCHAR(*p) == TELOPT_SGA) { flags &= ~CF_GA; comm_telnet(usr, will_sga, sizeof(will_sga)); } state = TS_DATA; break; case TS_DONT: if (UCHAR(*p) == TELOPT_SGA) { flags |= CF_GA; comm_telnet(usr, wont_sga, sizeof(wont_sga)); } state = TS_DATA; break; case TS_WILL: if (UCHAR(*p) == TELOPT_LINEMODE) { /* linemode confirmed; now request editing */ comm_telnet(usr, mode_edit, sizeof(mode_edit)); } /* fall through */ case TS_WONT: state = TS_DATA; break; case TS_SB: /* skip to the end */ if (UCHAR(*p) == IAC) { state = TS_SE; } break; case TS_SE: if (UCHAR(*p) == SE) { /* end of subnegotiation */ state = TS_DATA; } else { state = TS_SB; } break; } p++; --n; } usr->flags = flags; usr->state = state; usr->newlines = nls; usr->inbufsz = q - usr->inbuf; if (nls == 0) { continue; } /* * input terminated by \n */ p = (char *) memchr(q = usr->inbuf, LF, usr->inbufsz); usr->newlines--; --newlines; n = p - usr->inbuf; p++; /* skip \n */ usr->inbufsz -= n + 1; (--f->sp)->type = T_STRING; str_ref(f->sp->u.string = str_new(usr->inbuf, (long) n)); for (n = usr->inbufsz; n != 0; --n) { *q++ = *p++; } } else { /* * input buffer full */ n = usr->inbufsz; usr->inbufsz = 0; (--f->sp)->type = T_STRING; str_ref(f->sp->u.string = str_new(usr->inbuf, (long) n)); } } else { /* * binary connection */ if (usr->flags & CF_UDP) { n = conn_udpread(usr->conn, buffer, BINBUF_SIZE); if (n >= 0) { /* * received datagram */ usr->flags |= CF_UDPDATA; (--f->sp)->type = T_STRING; str_ref(f->sp->u.string = str_new(buffer, (long) n)); this_user = usr->obj; if (i_call(f, usr->obj, "receive_datagram", 16, TRUE, 1)) { i_del_value(f->sp++); endthread(); comm_flush(TRUE); } this_user = (object *) NULL; } } n = conn_read(usr->conn, p = buffer, BINBUF_SIZE); if (n <= 0) { if (n < 0) { /* * bad connection */ comm_del(f, usr, FALSE); endthread(); /* this cannot be in comm_del() */ comm_flush(FALSE); break; } continue; } (--f->sp)->type = T_STRING; str_ref(f->sp->u.string = str_new(buffer, (long) n)); } this_user = usr->obj; if (i_call(f, usr->obj, "receive_message", 15, TRUE, 1)) { i_del_value(f->sp++); endthread(); comm_flush(TRUE); } this_user = (object *) NULL; break; } ec_pop(); } /* * NAME: comm->ip_number() * DESCRIPTION: return the ip number of a user (as a string) */ string *comm_ip_number(obj) object *obj; { char *ipnum; ipnum = conn_ipnum(users[UCHAR(obj->etabi)].conn); return str_new(ipnum, (long) strlen(ipnum)); } /* * NAME: comm->ip_name() * DESCRIPTION: return the ip name of a user */ string *comm_ip_name(obj) object *obj; { char *ipname; ipname = conn_ipname(users[UCHAR(obj->etabi)].conn); return str_new(ipname, (long) strlen(ipname)); } /* * NAME: comm->close() * DESCRIPTION: remove a user */ void comm_close(f, obj) frame *f; object *obj; { register user *usr; usr = &users[UCHAR(obj->etabi)]; if (usr->outbufsz != 0) { /* * flush last bit of output */ comm_write(usr, (string *) NULL, FALSE); } comm_del(f, usr, TRUE); } /* * NAME: comm->user() * DESCRIPTION: return the current user */ object *comm_user() { return this_user; } /* * NAME: comm->users() * DESCRIPTION: return an array with all user objects */ array *comm_users(data) dataspace *data; { array *a; register int i; register user *usr; register value *v; a = arr_new(data, (long) (i = nusers)); v = a->elts; for (usr = users; i > 0; usr++) { if (usr->obj != (object *) NULL) { v->type = T_OBJECT; v->oindex = usr->obj->index; v->u.objcnt = usr->obj->count; v++; --i; } } return a; } /* * NAME: comm->active() * DESCRIPTION: return TRUE if there is any pending comm activity */ bool comm_active() { return (newlines != 0 || conn_select(0) > 0); }