# define INCLUDE_TELNET
# include "dgd.h"
# include "str.h"
# include "array.h"
# include "object.h"
# include "interpret.h"
# include "comm.h"
typedef struct _user_ {
union {
object *obj; /* associated object */
struct _user_ *next; /* next in free list */
} u;
int inbufsz; /* bytes in input buffer */
int outbufsz; /* bytes in output buffer */
int newlines; /* complete lines in input buffer */
char flags; /* connection flags */
char state; /* telnet state */
connection *conn; /* connection */
char *inbuf; /* input buffer */
char *outbuf; /* output buffer */
} user;
# define CF_ECHO 0x01
# define CF_TELNET 0x02
# define CF_GA 0x04
# define CF_SEENCR 0x08
# define CF_PORT 0x10
# define CF_DATAGRAM 0x20
# define CF_CONNECTING 0x40
# define TS_DATA 0
# define TS_IAC 1
# define TS_DO 2
# define TS_DONT 3
# define TS_IGNORE 4
static user **users; /* array of users */
static int maxusers; /* max # of users */
static int nusers; /* # of users */
static int newlines; /* # of newlines in all input buffers */
static object *this_user; /* current user */
/*
* NAME: comm->init()
* DESCRIPTION: initialize communications
*/
void comm_init(nusers)
int nusers;
{
register int i;
register user **usr;
conn_init(nusers);
users = ALLOC(user*, maxusers = nusers);
for (i = nusers, usr = users; i > 0; --i, usr++) {
*usr = (user *) NULL;
}
}
/*
* NAME: comm->finish()
* DESCRIPTION: terminate connections
*/
void comm_finish()
{
comm_flush(FALSE);
conn_finish();
}
/*
* NAME: comm->new()
* DESCRIPTION: accept a new connection
*/
static void comm_new(obj, conn, flags)
object *obj;
connection *conn;
int flags;
{
static char echo_on[] = { IAC, WONT, TELOPT_ECHO };
register user **usr;
register int size;
if (obj->flags & (O_USER | O_EDITOR)) {
error("user object is already used for user or editor");
}
if (flags & CF_PORT) {
size = sizeof(user);
} else if (flags & CF_TELNET) {
size = sizeof(user) + INBUF_SIZE + OUTBUF_SIZE;
} else {
size = sizeof(user) + OUTBUF_SIZE;
}
for (usr = users; *usr != (user *) NULL; usr++) ;
mstatic();
*usr = (user *) ALLOC(char, size);
mdynamic();
(*usr)->u.obj = obj;
obj->flags |= O_USER;
obj->eduser = usr - users;
(*usr)->inbufsz = 0;
(*usr)->outbufsz = 0;
(*usr)->conn = conn;
(*usr)->flags = flags;
if (flags & CF_PORT) {
(*usr)->inbuf = (char *) NULL;
(*usr)->outbuf = (char *) NULL;
} else if (flags & CF_TELNET) {
(*usr)->inbuf = (char *) *usr + sizeof(user);
(*usr)->outbuf = (*usr)->inbuf + INBUF_SIZE;
if (flags & CF_ECHO) {
conn_write(conn, echo_on, 3);
}
(*usr)->state = TS_DATA;
} else {
(*usr)->inbuf = (char *) NULL;
(*usr)->outbuf = (char *) *usr + sizeof(user);
}
nusers++;
}
/*
* NAME: comm->del()
* DESCRIPTION: delete a connection
*/
static void comm_del(usr)
register user **usr;
{
register char *p, *q;
register int n;
object *obj, *olduser;
conn_del((*usr)->conn);
n = (*usr)->inbufsz;
if ((*usr)->flags & CF_TELNET) {
newlines -= (*usr)->newlines;
}
obj = (*usr)->u.obj;
obj->flags &= ~O_USER;
FREE(*usr);
*usr = (user *) NULL;
--nusers;
olduser = this_user;
this_user = obj;
if (i_call(obj, "close", TRUE, 0)) {
i_del_value(sp++);
}
if (obj == olduser) {
this_user = (object *) NULL;
} else {
this_user = olduser;
}
}
/*
* NAME: comm->listen()
* DESCRIPTION: have an object listen to a port
*/
void comm_listen(obj, port, protocol)
object *obj;
int port, protocol;
{
register connection *conn;
int flags;
if (protocol == PRC_TELNET) {
flags = CF_TELNET | CF_ECHO | CF_PORT;
protocol = PRC_TCP;
} else if (protocol == PRC_UDP) {
flags = CF_DATAGRAM | CF_PORT;
} else {
flags = CF_PORT;
}
conn = conn_listen(port, protocol);
if (conn != (connection *) NULL) {
comm_new(obj, conn, flags);
} else {
error("could not open port");
}
if (i_call(obj, "open", TRUE, 0)) {
i_del_value(sp++);
}
}
/*
* NAME: comm->connect()
* DESCRIPTION: initiate a telnet or tcp connection
*/
void comm_connect(obj, host, port, protocol)
object *obj;
char *host;
int port, protocol;
{
register connection *conn;
int flags;
if (protocol == PRC_TELNET) {
flags = CF_TELNET | CF_CONNECTING;
protocol = PRC_TCP;
} else if (protocol == PRC_TCP) {
flags = CF_CONNECTING;
}
conn = conn_connect(host, port, protocol);
if (conn != (connection *) NULL) {
comm_new(obj, conn, flags);
}
}
/*
* NAME: comm->send()
* DESCRIPTION: send a message to a user
*/
void comm_send(obj, str)
object *obj;
string *str;
{
register user *usr;
register char *p, *q;
register unsigned short len, size;
usr = users[UCHAR(obj->eduser)];
if (usr->flags & (CF_DATAGRAM | CF_PORT)) {
error("object is not a stream connection");
}
if (usr->flags & CF_CONNECTING) {
error("connection is not yet ready");
}
p = str->text;
len = str->len;
size = usr->outbufsz;
q = usr->outbuf + size;
if (usr->flags & CF_TELNET) {
while (len != 0) {
if (UCHAR(*p) == IAC) {
/*
* double the telnet IAC character
*/
if (size == OUTBUF_SIZE) {
conn_write(usr->conn, q = usr->outbuf, size);
size = 0;
}
*q++ = IAC;
size++;
} else if (*p == LF) {
/*
* insert CR before LF
*/
if (size == OUTBUF_SIZE) {
conn_write(usr->conn, q = usr->outbuf, size);
size = 0;
}
*q++ = CR;
size++;
} else if ((*p & 0x7f) < ' ' && *p != HT && *p != BEL && *p != BS) {
/*
* illegal character
*/
p++;
--len;
continue;
}
if (size == OUTBUF_SIZE) {
conn_write(usr->conn, q = usr->outbuf, size);
size = 0;
}
*q++ = *p++;
--len;
size++;
}
} else {
/*
* binary connection
*/
while (size + len > OUTBUF_SIZE) {
memcpy(q, p, OUTBUF_SIZE - size);
p += OUTBUF_SIZE - size;
len -= OUTBUF_SIZE - size;
conn_write(usr->conn, q = usr->outbuf, OUTBUF_SIZE);
size = 0;
}
memcpy(q, p, len);
size += len;
}
usr->outbufsz = size;
}
/*
* NAME: comm->sendto()
* DESCRIPTION: send a datagram
*/
void comm_sendto(obj, data, host, port)
object *obj;
string *data;
char *host;
int port;
{
register user *usr;
usr = users[UCHAR(obj->eduser)];
if (usr->flags & CF_DATAGRAM == 0) {
error("object is not a datagram object");
}
conn_sendto(usr->conn, data->text, data->len, host, port);
}
/*
* NAME: comm->echo()
* DESCRIPTION: turn on/off input echoing for a user
*/
void comm_echo(obj, echo)
object *obj;
bool echo;
{
register user *usr;
char buf[3];
usr = users[UCHAR(obj->eduser)];
if ((usr->flags & CF_TELNET) && echo != (usr->flags & CF_ECHO)) {
buf[0] = IAC;
buf[1] = (echo) ? WONT : WILL;
buf[2] = TELOPT_ECHO;
conn_write(usr->conn, buf, 3);
usr->flags ^= CF_ECHO;
}
}
/*
* NAME: comm->flush()
* DESCRIPTION: flush output to all users
*/
void comm_flush(prompt)
bool prompt;
{
register user **usr;
register int i, size;
register char *p;
for (usr = users, i = maxusers; i > 0; usr++, --i) {
if (*usr != (user *) NULL && (size=(*usr)->outbufsz) > 0) {
if (prompt && (*usr)->u.obj == this_user &&
((*usr)->flags & (CF_TELNET | CF_GA)) == (CF_TELNET | CF_GA)) {
/*
* append "go ahead" to indicate that the prompt has been sent
*/
if (size >= OUTBUF_SIZE - 2) {
conn_write((*usr)->conn, (*usr)->outbuf, size);
size = 0;
}
p = (*usr)->outbuf + size;
*p++ = IAC;
*p++ = GA;
size += 2;
}
conn_write((*usr)->conn, (*usr)->outbuf, size);
(*usr)->outbufsz = 0;
}
}
this_user = (object *) NULL;
}
/*
* NAME: comm->receive()
* DESCRIPTION: receive a message from a user
*/
void comm_receive()
{
static int lastuser;
connection *conn;
object *o;
register int n, i;
register char *p, *q;
this_user = (object *) NULL;
n = conn_select(newlines == 0);
if (n > 0 || newlines > 0) {
static char intr[] = { '\177' };
static char brk[] = { '\034' };
static char tm[] = { IAC, WILL, TELOPT_TM };
static char will_sga[] = { IAC, WILL, TELOPT_SGA };
static char wont_sga[] = { IAC, WONT, TELOPT_SGA };
register user *usr;
char buf[INBUF_SIZE];
Int size;
n = lastuser;
for (;;) {
n = (n + 1) % maxusers;
usr = users[n];
if (usr != (user *) NULL) {
if (usr->flags & CF_CONNECTING) {
int r;
r = conn_connected(usr->conn);
if (r == 1) {
usr->flags &= ~CF_CONNECTING;
this_user = usr->u.obj;
if (i_call(this_user, "open", TRUE, 0)) {
i_del_value(sp++);
}
comm_flush(TRUE);
} else if (r == -1) {
comm_del(&users[n]);
}
} else if (usr->flags & CF_DATAGRAM) {
size = conn_recvfrom(usr->conn, buf, INBUF_SIZE);
if (size > 0) {
lastuser = n;
(--sp)->type = T_STRING;
str_ref(sp->u.string = str_new(buf, (long) size));
(--sp)->type = T_STRING;
str_ref(sp->u.string = conn_ipnum(usr->conn));
(--sp)->type = T_INT;
sp->u.number = conn_port(usr->conn);
this_user = usr->u.obj;
if (i_call(this_user, "receive_datagram", TRUE, 3)) {
i_del_value(sp++);
}
return;
}
} else if (usr->flags & CF_PORT) {
if (nusers < maxusers) {
conn = conn_accept(usr->conn);
if (conn != (connection *) NULL) {
(--sp)->type = T_STRING;
str_ref(sp->u.string = conn_ipnum(conn));
(--sp)->type = T_INT;
sp->u.number = conn_port(conn);
if (ec_push()) {
conn_del(conn);
error((char *) NULL); /* pass on error */
}
this_user = usr->u.obj;
if (!i_call(this_user, "connection", TRUE, 2)
|| sp->type != T_OBJECT) {
error("connection() did not return an object");
}
ec_pop();
comm_new(o = o_object(sp->oindex, sp->u.objcnt),
conn, usr->flags & ~CF_PORT);
sp++;
this_user = o;
if (i_call(this_user, "open", TRUE, 0)) {
i_del_value(sp++);
}
comm_flush(TRUE);
}
}
} else if (usr->flags & CF_TELNET) {
p = usr->inbuf + usr->inbufsz;
size = conn_read(usr->conn, p, INBUF_SIZE - usr->inbufsz);
if (size < 0) {
/*
* bad connection
*/
comm_del(&users[n]);
} else if (size > 0) {
register int state, flags;
flags = usr->flags;
state = usr->state;
q = p;
while (size > 0) {
switch (state) {
case TS_DATA:
switch (UCHAR(*p)) {
case IAC:
state = TS_IAC;
break;
case BS:
case 0x7f:
if (q != usr->inbuf && q[-1] != LF) {
--q;
}
flags &= ~CF_SEENCR;
break;
case CR:
newlines++;
usr->newlines++;
*q++ = LF;
flags |= CF_SEENCR;
break;
case LF:
if ((flags & CF_SEENCR) != 0) {
flags &= ~CF_SEENCR;
break;
}
newlines++;
usr->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:
case WONT:
state = TS_IGNORE;
break;
case IP:
conn_write(usr->conn, intr, 1);
state = TS_DATA;
break;
case BREAK:
conn_write(usr->conn, brk, 1);
state = TS_DATA;
break;
default:
state = TS_DATA;
break;
}
break;
case TS_DO:
if (UCHAR(*p) == TELOPT_TM) {
conn_write(usr->conn, tm, 3);
} else if (UCHAR(*p) == TELOPT_SGA) {
flags &= ~CF_GA;
conn_write(usr->conn, will_sga, 3);
}
state = TS_DATA;
break;
case TS_DONT:
if (UCHAR(*p) == TELOPT_SGA) {
flags |= CF_GA;
conn_write(usr->conn, wont_sga, 3);
}
/* fall through */
case TS_IGNORE:
state = TS_DATA;
break;
}
p++;
--size;
}
usr->flags = flags;
usr->state = state;
usr->inbufsz = q - usr->inbuf;
if (usr->newlines > 0) {
p = (char *) memchr(usr->inbuf, LF, usr->inbufsz);
--newlines;
--(usr->newlines);
lastuser = n;
size = n = p - usr->inbuf;
if (size != 0) {
memcpy(buf, usr->inbuf, size);
}
p++; /* skip \n */
n++;
usr->inbufsz -= size + 1;
if (usr->inbufsz != 0) {
/* can't rely on memcpy */
for (q = usr->inbuf, n = usr->inbufsz; n > 0;
--n) {
*q++ = *p++;
}
}
this_user = usr->u.obj;
(--sp)->type = T_STRING;
str_ref(sp->u.string = str_new(buf, (long) size));
if (i_call(this_user, "receive_message", TRUE, 1)) {
i_del_value(sp++);
}
return;
} else if (usr->inbufsz == INBUF_SIZE) {
/*
* input buffer full
*/
lastuser = n;
(--sp)->type = T_STRING;
str_ref(sp->u.string =
str_new(usr->inbuf, (long) INBUF_SIZE));
usr->inbufsz = 0;
this_user = usr->u.obj;
if (i_call(this_user, "receive_message", TRUE, 1)) {
i_del_value(sp++);
}
return;
}
}
} else {
/*
* binary mode
*/
size = conn_read(usr->conn, buf, INBUF_SIZE);
if (size < 0) {
comm_del(&users[n]);
} else if (size > 0) {
lastuser = n;
(--sp)->type = T_STRING;
str_ref(sp->u.string = str_new(buf, (long) size));
this_user = usr->u.obj;
if (i_call(this_user, "receive_message", TRUE, 1)) {
i_del_value(sp++);
}
return;
}
}
}
if (n == lastuser) {
return;
}
}
}
}
/*
* NAME: comm->ip_number()
* DESCRIPTION: return the ip number of a user (as a string)
*/
string *comm_ip_number(obj)
object *obj;
{
return conn_ipnum(users[UCHAR(obj->eduser)]->conn);
}
/*
* NAME: comm->close()
* DESCRIPTION: remove a user
*/
void comm_close(obj)
object *obj;
{
register user **usr;
usr = &users[UCHAR(obj->eduser)];
if ((*usr)->outbufsz != 0) {
/*
* flush last bit of output
*/
conn_write((*usr)->conn, (*usr)->outbuf, (*usr)->outbufsz);
}
comm_del(usr);
}
/*
* 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()
{
array *a;
register int i;
register user **usr;
register value *v;
a = arr_new((long) nusers);
v = a->elts;
for (i = nusers, usr = users; i > 0; usr++) {
if (*usr != (user *) NULL) {
v->type = T_OBJECT;
v->oindex = (*usr)->u.obj->index;
v->u.objcnt = (*usr)->u.obj->count;
v++;
--i;
}
}
return a;
}