# 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; }