/
dgd-net/
dgd-net/doc/
dgd-net/doc/kfun/
dgd-net/src/host/unix/
dgd-net/src/kfun/
# 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;
}