/* * This file is part of DGD, http://dgd-osr.sourceforge.net/ * Copyright (C) 1993-2010 Dworkin B.V. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ # define INCLUDE_TELNET # include "dgd.h" # include "str.h" # include "array.h" # include "object.h" # include "interpret.h" # include "data.h" # include "comm.h" # include "version.h" # include <errno.h> # ifndef TELOPT_LINEMODE # define TELOPT_LINEMODE 34 /* linemode option */ # define LM_MODE 1 # define MODE_EDIT 0x01 # endif # define MAXIACSEQLEN 7 /* longest IAC sequence sent */ typedef struct _user_ { uindex oindex; /* associated object index */ struct _user_ *prev; /* preceding user */ struct _user_ *next; /* next user */ struct _user_ *flush; /* next in flush list */ #ifdef NETWORK_EXTENSIONS short flags; /* connection flags */ #else char flags; /* connection flags */ #endif char state; /* telnet state */ short newlines; /* # of newlines in input buffer */ connection *conn; /* connection */ char *inbuf; /* input buffer */ array *extra; /* object's extra value */ string *outbuf; /* output buffer string */ ssizet inbufsz; /* bytes in input buffer */ ssizet osdone; /* bytes of output string done */ } user; /* flags */ # define CF_BINARY 0x00 /* binary connection */ # define CF_UDP 0x02 /* receive UDP datagrams */ # define CF_UDPDATA 0x04 /* UDP data received */ # define CF_TELNET 0x01 /* telnet connection */ # define CF_ECHO 0x02 /* client echoes input */ # define CF_GA 0x04 /* send GA after prompt */ # define CF_PROMPT 0x08 /* prompt in telnet output */ # define CF_BLOCKED 0x10 /* input blocked */ # define CF_FLUSH 0x20 /* in flush list */ # define CF_OUTPUT 0x40 /* pending output */ # define CF_ODONE 0x80 /* output done */ #ifdef NETWORK_EXTENSIONS # define CF_PORT 0x0100 /* port (listening) connection */ # define CF_DATAGRAM 0x0200 /* independent UDP socket */ # define CF_OPENDING 0x0400 /* waiting for connect() to complete */ #endif /* state */ # define TS_DATA 0 # define TS_CRDATA 1 # define TS_IAC 2 # define TS_DO 3 # define TS_DONT 4 # define TS_WILL 5 # define TS_WONT 6 # define TS_SB 7 # define TS_SE 8 static user *users; /* array of users */ static user *lastuser; /* last user checked */ static user *freeuser; /* linked list of free users */ static user *flush; /* flush list */ static int maxusers; /* max # of users */ #ifdef NETWORK_EXTENSIONS static int maxports; /* max # of ports */ static int nports; /* # of ports */ #endif static int nusers; /* # of users */ static int odone; /* # of users with output done */ static long newlines; /* # of newlines in all input buffers */ #ifdef NETWORK_EXTENSIONS static short opending; /* # of users with connect() pending */ #endif static uindex this_user; /* current user */ static int ntport, nbport; /* # telnet/binary ports */ static int nexttport; /* next telnet port to check */ static int nextbport; /* next binary port to check */ static char ayt[22]; /* are you there? */ /* * NAME: comm->init() * DESCRIPTION: initialize communications */ #ifdef NETWORK_EXTENSIONS bool comm_init(n, p, thosts, bhosts, tports, bports, ntelnet, nbinary) int n, p, ntelnet, nbinary; #else bool comm_init(n, thosts, bhosts, tports, bports, ntelnet, nbinary) int n, ntelnet, nbinary; #endif char **thosts, **bhosts; unsigned short *tports, *bports; { register int i; register user *usr; #ifdef NETWORK_EXTENSIONS maxusers = n; maxports = p; users = ALLOC(user, (maxusers + maxports)); #else users = ALLOC(user, maxusers = n); #endif #ifdef NETWORK_EXTENSIONS for (i = (n+p), usr = users + i; i > 0; --i) { #else for (i = n, usr = users + i; i > 0; --i) { #endif --usr; usr->oindex = OBJ_NONE; usr->next = usr + 1; } #ifdef NETWORK_EXTENSIONS users[n+p - 1].next = (user *) NULL; #else users[n - 1].next = (user *) NULL; #endif freeuser = usr; lastuser = (user *) NULL; flush = (user *) NULL; nusers = odone = newlines = 0; #ifdef NETWORK_EXTENSIONS nports = 0; opending = 0; #endif this_user = OBJ_NONE; sprintf(ayt, "\15\12[%s]\15\12", VERSION); nexttport = nextbport = 0; #ifdef NETWORK_EXTENSIONS return conn_init(n+p, thosts, bhosts, tports, bports, ntport = ntelnet, nbport = nbinary); #else return conn_init(n, thosts, bhosts, tports, bports, ntport = ntelnet, nbport = nbinary); #endif } #ifdef NETWORK_EXTENSIONS void comm_openport(f, obj, protocol, portnr) frame *f; object *obj; unsigned char protocol; unsigned short portnr; { register connection *conn; uindex olduser; short flags; register user *usr; dataspace *data; array *arr; value val; int i; if (nports >= maxports) error("Max number of port objects exceeded"); switch (protocol) { case P_TELNET: flags=CF_TELNET|CF_PORT; protocol=P_TCP; break; case P_UDP: flags=CF_DATAGRAM|CF_PORT; break; case P_TCP: flags=CF_PORT; break; default: error("Unknown protocol"); } conn=(connection *)conn_openlisten(protocol, portnr); if (conn==(connection *) NULL) error("Can't open port"); 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; } d_wipe_extravar(data=o_dataspace(obj)); switch (protocol) { case P_TCP: arr = arr_new(data, 3L); arr->elts[0] = zero_int; arr->elts[1] = arr->elts[2] = nil_value; PUT_ARRVAL_NOREF(&val, arr); d_set_extravar(data, &val); break; case P_UDP: arr=arr_new(data, 4L); arr->elts[0]=zero_int; arr->elts[1]=nil_value; arr->elts[2]=nil_value; arr->elts[3]=nil_value; PUT_ARRVAL_NOREF(&val, arr); d_set_extravar(data, &val); break; } usr->oindex=obj->index; obj->flags |= O_USER; obj->etabi = usr-users; usr->conn = conn; usr->outbuf = (string *) NULL; usr->osdone = 0; usr->flags=flags; olduser=this_user; this_user=obj->index; (--f->sp)->type=T_INT; f->sp->u.number=conn_at(conn); if (i_call(f, obj, (array *) NULL, "open", 4, TRUE, 1)) { i_del_value(f->sp++); } nports++; this_user=olduser; } #endif /* * NAME: comm->finish() * DESCRIPTION: terminate connections */ void comm_finish() { conn_finish(); } #ifndef NETWORK_EXTENSIONS /* * NAME: comm->listen() * DESCRIPTION: start listening on telnet port and binary port */ void comm_listen() { conn_listen(); } #endif /* * NAME: addtoflush() * DESCRIPTION: add a user to the flush list */ static void addtoflush(usr, arr) register user *usr; register array *arr; { usr->flags |= CF_FLUSH; usr->flush = flush; flush = usr; arr_ref(usr->extra = arr); /* remember initial buffer */ if (d_get_elts(arr)[1].type == T_STRING) { str_ref(usr->outbuf = arr->elts[1].u.string); } } /* * NAME: comm->new() * DESCRIPTION: accept a new connection */ static user *comm_new(f, obj, conn, telnet) frame *f; 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; dataspace *data; array *arr; value val; if (obj->flags & O_SPECIAL) { error("User object is already special purpose"); } /* initialize dataspace before the object receives the user role */ if (!O_HASDATA(obj) && i_call(f, obj, (array *) NULL, (char *) NULL, 0, TRUE, 0)) { i_del_value(f->sp++); } 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; } d_wipe_extravar(data = o_dataspace(obj)); arr = arr_new(data, 3L); arr->elts[0] = zero_int; arr->elts[1] = arr->elts[2] = nil_value; PUT_ARRVAL_NOREF(&val, arr); d_set_extravar(data, &val); usr->oindex = obj->index; obj->flags |= O_USER; obj->etabi = usr - users; usr->conn = conn; usr->outbuf = (string *) NULL; usr->osdone = 0; if (telnet) { /* initialize connection */ usr->flags = CF_TELNET | CF_ECHO | CF_OUTPUT; usr->state = TS_DATA; usr->newlines = 0; usr->inbufsz = 0; m_static(); usr->inbuf = ALLOC(char, INBUF_SIZE + 1); *usr->inbuf++ = LF; /* sentinel */ m_dynamic(); addtoflush(usr, arr); arr->elts[0].u.number = CF_ECHO; PUT_STRVAL_NOREF(&val, str_new(init, (long) sizeof(init))); d_assign_elt(data, arr, &arr->elts[1], &val); } else { usr->flags = 0; } nusers++; return usr; } #ifdef NETWORK_EXTENSIONS void comm_connect(f, obj, addr, protocol, port) frame *f; object *obj; char *addr; unsigned char protocol; unsigned short port; { register connection *conn; register user *usr; uindex olduser; if (nusers >= maxusers) error("Max number of connection objects exceeded"); conn=(connection *)conn_connect(addr, port); if (conn==(connection *) NULL) error("Can't connect to server"); usr=comm_new(f,obj, conn, (protocol==P_TELNET)); addtoflush(usr, d_get_extravar(o_dataspace(obj))->u.array); opending++; usr->flags |= CF_OPENDING; } int comm_senddatagram(obj, str, ip, port) object * obj; string * str; string * ip; int port; { register user *usr; dataspace *data; array *arr; register value *v1, *v2, *v3, *v4; value val; usr = &users[EINDEX(obj->etabi)]; if ((usr->flags & (CF_PORT|CF_DATAGRAM)) != (CF_PORT|CF_DATAGRAM)) { error("Object is not a udp port object"); } if (!conn_checkaddr(ip->text)) { error("Not a IP address"); } arr=d_get_extravar(data=obj->data)->u.array; if (!(usr->flags & CF_FLUSH)) { addtoflush(usr, arr); } v1=arr->elts +1;/*used for string*/ v2=arr->elts +2;/*used for ip */ v3=arr->elts +3;/*used for port*/ usr->flags |= CF_OUTPUT; PUT_STRVAL_NOREF(&val, str); d_assign_elt(data, arr, v1, &val); PUT_STRVAL_NOREF(&val, ip); d_assign_elt(data, arr, v2, &val); PUT_INTVAL(&val, port); d_assign_elt(data, arr, v3, &val); return str->len; } #endif /* * NAME: comm->del() * DESCRIPTION: delete a connection */ static void comm_del(f, usr, obj, destruct) register frame *f; register user *usr; object *obj; bool destruct; { dataspace *data; uindex olduser; data = o_dataspace(obj); if (!destruct) { /* if not destructing, make sure the connection terminates */ if (!(usr->flags & CF_FLUSH)) { addtoflush(usr, d_get_extravar(data)->u.array); } obj->flags &= ~O_USER; } /* make sure opending gets decreased if we were still waiting for * connection esteblishment when receiving a close event. */ #ifdef NETWORK_EXTENSIONS if(usr->flags & CF_OPENDING) { opending--; usr->flags &= ~CF_OPENDING; } #endif olduser = this_user; if (ec_push((ec_ftn) NULL)) { this_user = olduser; error((char *) NULL); } else { this_user = obj->index; PUSH_INTVAL(f, destruct); if (i_call(f, obj, (array *) NULL, "close", 5, TRUE, 1)) { i_del_value(f->sp++); } this_user = olduser; ec_pop(); } if (destruct) { /* if destructing, don't disconnect if there's an error in close() */ if (!(usr->flags & CF_FLUSH)) { addtoflush(usr, d_get_extravar(data)->u.array); } obj->flags &= ~O_USER; } } /* * NAME: comm->challenge() * DESCRIPTION: set the UDP challenge for a binary connection */ void comm_challenge(obj, str) object *obj; string *str; { register user *usr; dataspace *data; array *arr; register value *v; value val; usr = &users[obj->etabi]; if (usr->flags & CF_TELNET) { error("Datagram channel cannot be attached to telnet connection"); } arr = d_get_extravar(data = obj->data)->u.array; if (!(usr->flags & CF_FLUSH)) { addtoflush(usr, arr); } v = arr->elts + 2; if ((usr->flags & CF_UDP) || v->type == T_STRING) { error("Datagram challenge already set"); } usr->flags |= CF_OUTPUT; PUT_STRVAL_NOREF(&val, str); d_assign_elt(data, arr, v, &val); } /* * NAME: comm->write() * DESCRIPTION: add bytes to output buffer */ static int comm_write(usr, obj, str, text, len) register user *usr; object *obj; register string *str; char *text; unsigned int len; { dataspace *data; array *arr; register value *v; register ssizet osdone, olen; value val; arr = d_get_extravar(data = o_dataspace(obj))->u.array; if (!(usr->flags & CF_FLUSH)) { addtoflush(usr, arr); } v = arr->elts + 1; if (v->type == T_STRING) { /* append to existing buffer */ osdone = (usr->outbuf == v->u.string) ? usr->osdone : 0; olen = v->u.string->len - osdone; if (olen + len > MAX_STRLEN) { len = MAX_STRLEN - olen; if (len == 0 || ((usr->flags & CF_TELNET) && text[0] == (char) IAC && len < MAXIACSEQLEN)) { return 0; } } str = str_new((char *) NULL, (long) olen + len); memcpy(str->text, v->u.string->text + osdone, olen); memcpy(str->text + olen, text, len); } else { /* create new buffer */ if (usr->flags & CF_ODONE) { usr->flags &= ~CF_ODONE; --odone; } usr->flags |= CF_OUTPUT; if (str == (string *) NULL) { str = str_new(text, (long) len); } } PUT_STRVAL_NOREF(&val, str); d_assign_elt(data, arr, v, &val); return len; } /* * NAME: comm->send() * DESCRIPTION: send a message to a user */ int comm_send(obj, str) object *obj; string *str; { register user *usr; usr = &users[EINDEX(obj->etabi)]; if (usr->flags & CF_TELNET) { char outbuf[OUTBUF_SIZE]; register char *p, *q; register unsigned int len, size, n, length; /* * telnet connection */ p = str->text; len = str->len; q = outbuf; size = 0; length = 0; for (;;) { if (len == 0 || size >= OUTBUF_SIZE - 1 || UCHAR(*p) == IAC) { n = comm_write(usr, obj, (string *) NULL, outbuf, size); if (n != size) { /* * count how many bytes of original string were written */ for (n = size - n; n != 0; --n) { if (*--p == *--q) { len++; if (UCHAR(*p) == IAC) { break; } } else if (*q == CR) { /* inserted CR */ p++; } else { /* skipped char */ q++; len++; } } return str->len - len; } if (len == 0) { return str->len; } size = 0; q = outbuf; } if (UCHAR(*p) == IAC) { /* * double the telnet IAC character */ *q++ = (char) IAC; size++; } else if (*p == LF) { /* * insert CR before LF */ *q++ = CR; size++; } *q++ = *p++; --len; size++; } } else { /* * binary connection */ return comm_write(usr, obj, str, str->text, str->len); } } /* * 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; dataspace *data; array *arr; register value *v; value val; usr = &users[EINDEX(obj->etabi)]; if ((usr->flags & (CF_TELNET | CF_UDP)) != CF_UDP) { error("Object has no datagram channel"); } if (!(usr->flags & CF_UDPDATA)) { error("No response to datagram challenge received yet"); } arr = d_get_extravar(data = obj->data)->u.array; if (!(usr->flags & CF_FLUSH)) { addtoflush(usr, arr); } v = arr->elts + 2; if (v->type == T_STRING) { return 0; /* datagram queued already */ } usr->flags |= CF_OUTPUT; PUT_STRVAL_NOREF(&val, str); d_assign_elt(data, arr, v, &val); return str->len; } /* * NAME: comm->echo() * DESCRIPTION: turn on/off input echoing for a user */ bool comm_echo(obj, echo) object *obj; int echo; { register user *usr; register dataspace *data; array *arr; register value *v; usr = &users[EINDEX(obj->etabi)]; if (usr->flags & CF_TELNET) { arr = d_get_extravar(data = obj->data)->u.array; v = d_get_elts(arr); if (echo != (v->u.number & CF_ECHO) >> 1) { value val; if (!(usr->flags & CF_FLUSH)) { addtoflush(usr, arr); } val = *v; val.u.number ^= CF_ECHO; d_assign_elt(data, arr, v, &val); } return TRUE; } return FALSE; } /* * NAME: comm->block() * DESCRIPTION: suspend or release input from a user */ void comm_block(obj, block) object *obj; int block; { register user *usr; register dataspace *data; array *arr; register value *v; usr = &users[EINDEX(obj->etabi)]; arr = d_get_extravar(data = obj->data)->u.array; v = d_get_elts(arr); if (block != (v->u.number & CF_BLOCKED) >> 4) { value val; if (!(usr->flags & CF_FLUSH)) { addtoflush(usr, arr); } val = *v; val.u.number ^= CF_BLOCKED; d_assign_elt(data, arr, v, &val); } } #ifdef NETWORK_EXTENSIONS static void comm_udpflush(usr, obj, data, arr) register user *usr; object *obj; dataspace *data; array *arr; { register value *v; register int i,j; register char *buf; int res; v=d_get_elts(arr); if (!conn_wrdone(usr->conn)) { return; } buf=v[1].u.string->text; res=conn_udpsend(usr->conn, buf, strlen(buf), v[2].u.string->text, (unsigned short) v[3].u.number ); if (res==-1) { /* EAGAIN occured, datagram could not be sent */ } usr->flags &= ~CF_OUTPUT; usr->flags |= CF_ODONE; odone++; } #endif /* * NAME: comm->uflush() * DESCRIPTION: flush output buffers for a single user only */ static void comm_uflush(usr, obj, data, arr) register user *usr; object *obj; register dataspace *data; array *arr; { register value *v; register int n; v = d_get_elts(arr); if (v[1].type == T_STRING) { if (conn_wrdone(usr->conn)) { n = conn_write(usr->conn, v[1].u.string->text + usr->osdone, v[1].u.string->len - usr->osdone); if (n >= 0) { n += usr->osdone; if (n == v[1].u.string->len) { /* buffer fully drained */ n = 0; usr->flags &= ~CF_OUTPUT; usr->flags |= CF_ODONE; odone++; d_assign_elt(data, arr, &v[1], &nil_value); } usr->osdone = n; } else { /* wait for conn_read() to discover the problem */ usr->flags &= ~CF_OUTPUT; } } } else { /* just a datagram */ usr->flags &= ~CF_OUTPUT; } if (v[2].type == T_STRING) { if (usr->flags & CF_UDP) { conn_udpwrite(usr->conn, v[2].u.string->text, v[2].u.string->len); } else if (conn_udp(usr->conn, v[2].u.string->text, v[2].u.string->len)) { usr->flags |= CF_UDP; } d_assign_elt(data, arr, &v[2], &nil_value); } } /* * NAME: comm->flush() * DESCRIPTION: flush state, output and connections */ void comm_flush() { register user *usr; object *obj; array *arr; register value *v; while (flush != (user *) NULL) { usr = flush; flush = usr->flush; /* * status change */ obj = OBJ(usr->oindex); arr = usr->extra; v = arr->elts; if (usr->flags & CF_TELNET) { if ((v->u.number ^ usr->flags) & CF_ECHO) { char buf[3]; /* change echo */ buf[0] = (char) IAC; buf[1] = (v->u.number & CF_ECHO) ? WONT : WILL; buf[2] = TELOPT_ECHO; if (comm_write(usr, obj, (string *) NULL, buf, 3) != 0) { usr->flags ^= CF_ECHO; } } if (usr->flags & CF_PROMPT) { usr->flags &= ~CF_PROMPT; if ((usr->flags & CF_GA) && v[1].type == T_STRING && usr->outbuf != v[1].u.string) { static char ga[] = { (char) IAC, (char) GA }; /* append go-ahead */ comm_write(usr, obj, (string *) NULL, ga, 2); } } } if ((v->u.number ^ usr->flags) & CF_BLOCKED) { usr->flags ^= CF_BLOCKED; conn_block(usr->conn, ((usr->flags & CF_BLOCKED) != 0)); } /* * write */ if (usr->outbuf != (string *) NULL) { if (usr->outbuf != v[1].u.string) { usr->osdone = 0; /* new mesg before buffer drained */ } str_del(usr->outbuf); usr->outbuf = (string *) NULL; } if (usr->flags & CF_OUTPUT) { #ifdef NETWORK_EXTENSIONS if ( (usr->flags & CF_DATAGRAM) ) { comm_udpflush(usr, obj, obj->data, arr); } else #endif comm_uflush(usr, obj, obj->data, arr); } /* * disconnect */ if ((obj->flags & O_SPECIAL) != O_USER) { d_wipe_extravar(obj->data); conn_del(usr->conn); #ifdef NETWORK_EXTENSIONS if (usr->flags & (CF_TELNET|CF_PORT) == CF_TELNET) { #else if (usr->flags & CF_TELNET) { #endif newlines -= usr->newlines; FREE(usr->inbuf - 1); } if (usr->flags & CF_ODONE) { --odone; } usr->oindex = OBJ_NONE; 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; #ifdef NETWORK_EXTENSIONS if(usr->flags & CF_PORT) { --nports; } else { --nusers; } #else --nusers; #endif } arr_del(arr); usr->flags &= ~CF_FLUSH; } } /* * NAME: comm->taccept() * DESCRIPTION: accept a telnet connection */ static void comm_taccept(f, conn, port) register frame *f; struct _connection_ *conn; int port; { user *usr; object *obj; if (ec_push((ec_ftn) NULL)) { conn_del(conn); /* delete connection */ error((char *) NULL); /* pass on error */ } PUSH_INTVAL(f, port); call_driver_object(f, "telnet_connect", 1); if (f->sp->type != T_OBJECT) { fatal("driver->telnet_connect() did not return persistent object"); } obj = OBJ(f->sp->oindex); f->sp++; usr = comm_new(f, obj, conn, TRUE); ec_pop(); endthread(); usr->flags |= CF_PROMPT; addtoflush(usr, d_get_extravar(o_dataspace(obj))->u.array); this_user = obj->index; if (i_call(f, obj, (array *) NULL, "open", 4, TRUE, 0)) { i_del_value(f->sp++); endthread(); } this_user = OBJ_NONE; } /* * NAME: comm->baccept() * DESCRIPTION: accept a binary connection */ static void comm_baccept(f, conn, port) register frame *f; struct _connection_ *conn; int port; { user *usr; object *obj; if (ec_push((ec_ftn) NULL)) { conn_del(conn); /* delete connection */ error((char *) NULL); /* pass on error */ } PUSH_INTVAL(f, port); call_driver_object(f, "binary_connect", 1); if (f->sp->type != T_OBJECT) { fatal("driver->binary_connect() did not return persistent object"); } obj = OBJ(f->sp->oindex); f->sp++; usr = comm_new(f, obj, conn, FALSE); ec_pop(); endthread(); this_user = obj->index; if (i_call(f, obj, (array *) NULL, "open", 4, TRUE, 0)) { i_del_value(f->sp++); endthread(); } this_user = OBJ_NONE; } /* * NAME: comm->receive() * DESCRIPTION: receive a message from a user */ void comm_receive(f, timeout, mtime) register frame *f; Uint timeout; unsigned int mtime; { static char intr[] = { '\177' }; static char brk[] = { '\034' }; 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 *obj; register user *usr; register int n, i, state, nls; register char *p, *q; if (newlines != 0 || odone != 0) { timeout = mtime = 0; #ifdef NETWORK_EXTENSIONS } else if(opending != 0) { timeout = 0; if(mtime > 250) { mtime = 250; } #endif } n = conn_select(timeout, mtime); #ifdef NETWORK_EXTENSIONS if ((n <= 0) && (newlines == 0) && (odone == 0) && (opending == 0)) { #else if (n <= 0 && newlines == 0 && odone == 0) { #endif /* * call_out to do, or timeout */ return; } if (ec_push(errhandler)) { endthread(); this_user = OBJ_NONE; return; } #ifndef NETWORK_EXTENSIONS if (ntport != 0 &&nusers < maxusers) { n = nexttport; do { /* * accept new telnet connection */ conn = conn_tnew6(n); if (conn != (connection *) NULL) { comm_taccept(f, conn, n); nexttport = (n + 1) % ntport; } if (nusers < maxusers) { conn = conn_tnew(n); if (conn != (connection *) NULL) { comm_taccept(f, conn, n); nexttport = (n + 1) % ntport; } } n = (n + 1) % ntport; } while (n != nexttport); } if (nbport != 0 && nusers < maxusers) { n = nextbport; do { /* * accept new binary connection */ conn = conn_bnew6(n); if (conn != (connection *) NULL) { comm_baccept(f, conn, n); } if (nusers < maxusers) { conn = conn_bnew(n); if (conn != (connection *) NULL) { comm_baccept(f, conn, n); } } n = (n + 1) % nbport; if (nusers == maxusers) { nextbport = n; break; } } while (n != nextbport); } for (i = nusers; i > 0; --i) { #else for (i = nusers + nports; i > 0; --i) { #endif usr = lastuser; lastuser = usr->next; obj = OBJ(usr->oindex); #ifdef NETWORK_EXTENSIONS /* * Check if we have an event pending from connect() and if so, handle it. */ if(usr->flags & CF_OPENDING) { int retval; uindex old_user; retval = conn_check_connected(usr->conn); /* * Something happened to the connection.. * its either connected or in error state now. */ if(retval != 0) { opending--; usr->flags &= ~CF_OPENDING; old_user = this_user; this_user = obj->index; /* * Error, report it to the user object. */ if(retval < 0) { if(retval == -1) { PUSH_STRVAL(f, str_new(strerror(errno), strlen(strerror(errno)))); } else { PUSH_STRVAL( f, str_new( "socket unexpectedly closed", 26) ); } if( i_call( f, obj, ( array * ) NULL, "receive_error", 13, TRUE, 1 ) ) { i_del_value(f->sp++); } endthread(); /* * Connection completed, call open in the user object. */ } else if(retval > 0) { if (i_call(f, obj, (array *) NULL, "open", 4, TRUE, 0)) { i_del_value(f->sp++); } endthread(); } this_user = old_user; } /* * Don't do anything else for user objects with pending * connects. */ continue; } #endif if (usr->flags & CF_OUTPUT) { dataspace *data; data = o_dataspace(obj); comm_uflush(usr, obj, data, d_get_extravar(data)->u.array); } if (usr->flags & CF_ODONE) { /* callback */ usr->flags &= ~CF_ODONE; --odone; this_user = obj->index; #ifdef NETWORK_EXTENSIONS /* * message_done for tcp, datagram_done for udp */ if(usr->flags & CF_DATAGRAM) { if (i_call(f, obj, (array *) NULL, "datagram_done", 13, TRUE, 0)) { i_del_value(f->sp++); endthread(); } } else { #endif if (i_call(f, obj, (array *) NULL, "message_done", 12, TRUE, 0)) { i_del_value(f->sp++); endthread(); } #ifdef NETWORK_EXTENSIONS } #endif this_user = OBJ_NONE; if (obj->count == 0) { break; /* continue, unless the connection was closed */ } } if (usr->flags & CF_BLOCKED) { continue; /* no input on this connection */ } #ifdef NETWORK_EXTENSIONS if ( (usr->flags & (CF_DATAGRAM | CF_PORT))==(CF_PORT|CF_DATAGRAM)) { char *addr; int port; n= conn_udpreceive(usr->conn, buffer, BINBUF_SIZE, &addr, &port); if (n >=0) { PUSH_STRVAL(f, str_new(buffer, (long) n)); PUSH_STRVAL(f, str_new(addr, strlen(addr))); PUSH_INTVAL(f, port); if (i_call(f, obj, (array *) NULL, "receive_datagram", 16, TRUE, 3)) { i_del_value(f->sp++); endthread(); } } continue; } if ( (usr->flags & (CF_PORT | CF_DATAGRAM))==CF_PORT) { if (nusers <= maxusers) { connection * conn; char ip[40]; user *newusr; uindex olduser; conn=(connection *)conn_accept(usr->conn); if (conn==(connection *) NULL) { break; } if(nusers == maxusers) { conn_del(conn); error("Maximum number of users exceeded"); } if (ec_push((ec_ftn) NULL)) { conn_del(conn); error((char *) NULL); } (--f->sp)->type=T_STRING; conn_ipnum(conn,ip); PUT_STRVAL(f->sp, str_new(ip, strlen(ip))); (--f->sp)->type=T_INT; f->sp->u.number=conn_at(conn); if (!i_call(f, OBJ(usr->oindex), (array *) NULL, "connection", 10, TRUE, 2)) { conn_del(conn); error("Missing connection()-function"); } if (f->sp->type != T_OBJECT) { conn_del(conn); error("object->connection() did not return a persistent object"); } obj=OBJ(f->sp->oindex); f->sp++; newusr=comm_new(f,obj, conn, usr->flags & CF_TELNET); ec_pop(); endthread(); newusr->flags |=CF_PROMPT; addtoflush(newusr, d_get_extravar(o_dataspace(obj))->u.array); olduser=this_user; this_user=obj->index; if (i_call(f, obj, (array *) NULL, "open", 4, TRUE, 0)) { i_del_value(f->sp++); } this_user=olduser; } continue; } if ( (obj->flags & O_USER ) != O_USER ) { continue; } #endif 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) { if (p[-1] != LF) { /* * add a newline at the end */ *p = LF; n = 1; } } else if (!(usr->flags & CF_OUTPUT)) { /* * empty buffer, no more input, no pending output */ comm_del(f, usr, obj, FALSE); endthread(); /* this cannot be in comm_del() */ break; } } state = usr->state; nls = usr->newlines; 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[-1] != LF) { --q; } break; case CR: nls++; newlines++; *q++ = LF; state = TS_CRDATA; break; case LF: nls++; newlines++; /* fall through */ default: *q++ = *p; /* fall through */ case '\0': break; } break; case TS_CRDATA: switch (UCHAR(*p)) { case IAC: state = TS_IAC; break; case CR: nls++; newlines++; *q++ = LF; break; default: *q++ = *p; /* fall through */ case '\0': case LF: case BS: case 0x7f: state = TS_DATA; 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_write(usr, obj, (string *) NULL, intr, sizeof(intr)); state = TS_DATA; break; case BREAK: comm_write(usr, obj, (string *) NULL, brk, sizeof(brk)); state = TS_DATA; break; case AYT: comm_write(usr, obj, (string *) NULL, ayt, strlen(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_write(usr, obj, (string *) NULL, tm, sizeof(tm)); } else if (UCHAR(*p) == TELOPT_SGA) { usr->flags &= ~CF_GA; comm_write(usr, obj, (string *) NULL, will_sga, sizeof(will_sga)); } state = TS_DATA; break; case TS_DONT: if (UCHAR(*p) == TELOPT_SGA) { usr->flags |= CF_GA; comm_write(usr, obj, (string *) NULL, wont_sga, sizeof(wont_sga)); } state = TS_DATA; break; case TS_WILL: if (UCHAR(*p) == TELOPT_LINEMODE) { /* linemode confirmed; now request editing */ comm_write(usr, obj, (string *) NULL, 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->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; PUSH_STRVAL(f, 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; PUSH_STRVAL(f, str_new(usr->inbuf, (long) n)); } usr->flags |= CF_PROMPT; if (!(usr->flags & CF_FLUSH)) { addtoflush(usr, d_get_extravar(o_dataspace(obj))->u.array); } } else { /* * binary connection */ if (usr->flags & CF_UDP) { if (usr->flags & CF_UDPDATA) { n = conn_udpread(usr->conn, buffer, BINBUF_SIZE); if (n >= 0) { /* * received datagram */ PUSH_STRVAL(f, str_new(buffer, (long) n)); this_user = obj->index; if (i_call(f, obj, (array *) NULL, "receive_datagram", 16, TRUE, 1)) { i_del_value(f->sp++); endthread(); } this_user = OBJ_NONE; } } else if (conn_udpcheck(usr->conn)) { usr->flags |= CF_UDPDATA; this_user = obj->index; if (i_call(f, obj, (array *) NULL, "open_datagram", 13, TRUE, 0)) { i_del_value(f->sp++); endthread(); } this_user = OBJ_NONE; } } n = conn_read(usr->conn, p = buffer, BINBUF_SIZE); if (n <= 0) { if (n < 0 && !(usr->flags & CF_OUTPUT)) { /* * no more input and no pending output */ comm_del(f, usr, obj, FALSE); endthread(); /* this cannot be in comm_del() */ break; } continue; } PUSH_STRVAL(f, str_new(buffer, (long) n)); } this_user = obj->index; if (i_call(f, obj, (array *) NULL, "receive_message", 15, TRUE, 1)) { i_del_value(f->sp++); endthread(); } this_user = OBJ_NONE; break; } ec_pop(); comm_flush(); } /* * NAME: comm->ip_number() * DESCRIPTION: return the ip number of a user (as a string) */ string *comm_ip_number(obj) object *obj; { char ipnum[40]; conn_ipnum(users[EINDEX(obj->etabi)].conn, ipnum); 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[1024]; conn_ipname(users[EINDEX(obj->etabi)].conn, ipname); return str_new(ipname, (long) strlen(ipname)); } /* * NAME: comm->close() * DESCRIPTION: remove a user */ void comm_close(f, obj) frame *f; object *obj; { comm_del(f, &users[EINDEX(obj->etabi)], obj, TRUE); } /* * NAME: comm->user() * DESCRIPTION: return the current user */ object *comm_user() { object *obj; return (this_user != OBJ_NONE && (obj=OBJR(this_user))->count != 0) ? obj : (object *) NULL; } /* * NAME: comm->users() * DESCRIPTION: return an array with all user objects */ #ifdef NETWORK_EXTENSIONS array *comm_users(data, ports) dataspace *data; bool ports; #else array *comm_users(data) dataspace *data; #endif { array *a; #ifdef NETWORK_EXTENSIONS register int i, n, f; #else register int i, n; #endif register user *usr; register value *v; register object *obj; n = 0; #ifdef NETWORK_EXTENSIONS for (i = (nusers + nports), usr = users; i > 0; usr++) { #else for (i = nusers, usr = users; i > 0; usr++) { #endif if (usr->oindex != OBJ_NONE) { --i; #ifdef NETWORK_EXTENSIONS if (OBJR(usr->oindex)->count != 0 && ( !((f=usr->flags & CF_PORT) || ports) || f &&ports)) { #else if (OBJR(usr->oindex)->count != 0) { #endif n++; } } } a = arr_new(data, (long) n); v = a->elts; for (usr = users; n > 0; usr++) { #ifdef NETWORK_EXTENSIONS if (usr->oindex != OBJ_NONE && (obj=OBJR(usr->oindex))->count != 0 && ( !((f=usr->flags & CF_PORT) || ports) || f &&ports)) { #else if (usr->oindex != OBJ_NONE && (obj=OBJR(usr->oindex))->count != 0) { #endif PUT_OBJVAL(v, obj); v++; --n; } } return a; } #ifdef NETWORK_EXTENSIONS /* * NAME: comm->is_connection() * DESCRIPTION: is this REALLY a user object? */ bool comm_is_connection(obj) object *obj; { register user *usr; if((obj->flags & O_SPECIAL) == O_USER) { usr = &users[EINDEX(obj->etabi)]; if((usr->flags & CF_PORT) != CF_PORT) { return 1; } } return 0; } #endif