# 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 */ char flags; /* connection flags */ char state; /* telnet state */ connection *conn; /* connection */ char inbuf[INBUF_SIZE]; /* input buffer */ char outbuf[OUTBUF_SIZE]; /* output buffer */ } user; # define CF_ECHO 0x01 # define CF_TELNET 0x02 # define CF_GA 0x04 # define CF_SEENCR 0x08 # 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 long binchars; /* # characters in binary buffers */ static object *this_user; /* current user */ /* * NAME: comm->init() * DESCRIPTION: initialize communications */ void comm_init(nusers, telnet_port, binary_port) int nusers, telnet_port, binary_port; { register int i; register user **usr; conn_init(nusers, telnet_port, binary_port); 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, telnet) object *obj; connection *conn; bool telnet; { static char echo_on[] = { IAC, WONT, TELOPT_ECHO }; register user **usr; if (obj->flags & (O_USER | O_EDITOR)) { error("user object is already used for user or editor"); } for (usr = users; *usr != (user *) NULL; usr++) ; mstatic(); *usr = ALLOC(user, 1); mdynamic(); (*usr)->u.obj = obj; obj->flags |= O_USER; obj->eduser = usr - users; (*usr)->inbufsz = 0; (*usr)->outbufsz = 0; (*usr)->conn = conn; if (telnet) { /* start with echo on */ conn_write(conn, echo_on, 3); (*usr)->flags = CF_TELNET | CF_ECHO; (*usr)->state = TS_DATA; } else { (*usr)->flags = 0; } nusers++; this_user = obj; } /* * 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) { p = (*usr)->inbuf; while (n > 0 && (q=(char *) memchr(p, LF, n)) != (char *) NULL) { --newlines; q++; n -= q - p; p = q; } } else { binchars -= n; } 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->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)]; p = str->text; len = str->len; size = usr->outbufsz; q = usr->outbuf + size; if (usr->flags & CF_TELNET) { /* * telnet connection */ 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->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 */ object *comm_receive(buf, size) char *buf; int *size; { static int lastuser; connection *conn; object *o; register int n, i, state, flags; register char *p, *q; if (nusers < maxusers) { /* * accept new telnet connection */ conn = conn_tnew(); if (conn != (connection *) NULL) { if (ec_push()) { conn_del(conn); /* delete connection */ error((char *) NULL); /* pass on error */ } call_driver_object("telnet_connect", 0); ec_pop(); if (sp->type != T_OBJECT) { fatal("driver->telnet_connect() did not return an object"); } comm_new(o = o_object(sp->oindex, sp->u.objcnt), conn, TRUE); sp++; if (i_call(o, "open", TRUE, 0)) { i_del_value(sp++); } comm_flush(TRUE); } } if (nusers < maxusers) { /* * accept new binary connection */ conn = conn_bnew(); if (conn != (connection *) NULL) { if (ec_push()) { conn_del(conn); /* delete connection */ error((char *) NULL); /* pass on error */ } call_driver_object("binary_connect", 0); ec_pop(); if (sp->type != T_OBJECT) { fatal("driver->binary_connect() did not return an object"); } comm_new(o = o_object(sp->oindex, sp->u.objcnt), conn, FALSE); sp++; if (i_call(o, "open", TRUE, 0)) { i_del_value(sp++); } comm_flush(TRUE); } } /* * read input from users */ this_user = (object *) NULL; n = conn_select(newlines == 0 && binchars == 0); if (n <= 0) { /* * call_out to do, or timeout */ if (newlines == 0 && binchars == 0) { return (object *) NULL; } } else { 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; for (i = maxusers, usr = users; i > 0; --i, usr++) { if (*usr != (user *) NULL && (*usr)->inbufsz != INBUF_SIZE) { p = (*usr)->inbuf + (*usr)->inbufsz; n = conn_read((*usr)->conn, p, INBUF_SIZE - (*usr)->inbufsz); if (n < 0) { /* * bad connection */ comm_del(usr); } else if ((*usr)->flags & CF_TELNET) { /* * telnet mode */ flags = (*usr)->flags; state = (*usr)->state; q = p; while (n > 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++; *q++ = LF; flags |= CF_SEENCR; break; case LF: if ((flags & CF_SEENCR) != 0) { flags &= ~CF_SEENCR; break; } 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++; --n; } (*usr)->flags = flags; (*usr)->state = state; (*usr)->inbufsz = q - (*usr)->inbuf; } else { /* * binary mode */ (*usr)->inbufsz += n; binchars += n; } } } } if (newlines != 0 || binchars != 0) { register user *usr; n = lastuser; for (;;) { n = (n + 1) % maxusers; usr = users[n]; if (usr != (user *) NULL && usr->inbufsz != 0) { if (usr->flags & CF_TELNET) { /* * telnet connection */ p = (char *) memchr(usr->inbuf, LF, usr->inbufsz); if (p != (char *) NULL) { /* * input terminated by \n */ --newlines; lastuser = n; *size = n = p - usr->inbuf; if (n != 0) { memcpy(buf, usr->inbuf, n); } p++; /* skip \n */ n++; usr->inbufsz -= n; if (usr->inbufsz != 0) { /* can't rely on memcpy */ for (q = usr->inbuf, n = usr->inbufsz; n > 0; --n) { *q++ = *p++; } } return this_user = usr->u.obj; } else if (usr->inbufsz == INBUF_SIZE) { /* * input buffer full */ lastuser = n; memcpy(buf, usr->inbuf, *size = INBUF_SIZE); usr->inbufsz = 0; return this_user = usr->u.obj; } } else { /* * binary connection */ lastuser = n; binchars -= usr->inbufsz; memcpy(buf, usr->inbuf, *size = usr->inbufsz); usr->inbufsz = 0; return this_user = usr->u.obj; } } } } return (object *) NULL; } /* * 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->eduser)]->conn); return str_new(ipnum, (long) strlen(ipnum)); } /* * 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; }