text: //
  // Wunderland Mudlib
  //
  // global/daemon/socketd.c  --  internet-sockets (needs xerq)
  //
  // This file comes in two guises - the blueprint is the master
  // actual managing all data transfers and the clones representin
  // the connections for the using wizards. They get the socket object
  // from the master and handle all subsequent issues with object.
  //
  // Far based on socketd from Brian Gerst (Garion@Timewarp)
  // Inspired by the Msg modul from Sriram Srinivasan
  // Mudmode conversion based on code from Skylight (pli@shell.portal.com)
  //
  // Find the newest version of this file at
  //   http://wl.mud.de/mud/doc/wwwstd/standardobjekte.html
  //
  // $Log: socketd.c,v $
  // Revision 1.15  2004/11/10 07:25:06  Fiona
  // Fixed warning problem with 3.3 in State() (Joseph Graham, Lars Duening)
  //
  
  /*****
   *
   * How does the socketd work internally?
   *
   * Each connection has an associated socket object which handles all
   * communication with the wizards object. These objects stay in the
   * inventory of the socketd.
   *
   * To destringuish the different connections when a messages comes
   * in from the erq the clone number of the socket object is used.
   * The number is closured with the callback function. The number
   * is used instead of the object itself because the object may be
   * destructed meanwhile: the connection is to be killed then but the
   * ticket is needed for that. So we need some unique identifier that
   * is still usable after the object's destruction.
   * 
   * All opened connections are stored in sockets with the unique identifier
   * (the clone number of the socket object) as key. Furthermore there is
   * used_fd which translates unique identifieres to file descriptor numbers
   * which are used outside the socketd to identify a connection. The fds
   * are numbered from 0 to n using old numbers if that fd is not active
   * anymore.
   *
   * If Write() or Send() is issued on a connection that is not open yet
   * the data will be buffered and sent when the socket becomes writabe.
   * It is even possible to Close() the socket wich will be delayed until
   * all buffered data is sent. Bear in mind that a Close() will not destruct
   * the socket object in that case and you will get the callbacks if the
   * issuing object still exists. If it does not exist anymore you cannot
   * tell if the data is really sent or some timeout occured. So in general
   * you should wait with Send()s until the connection is established.
   *
   */
  
  #pragma strict_types
  #pragma no_inherit
  #pragma no_shadow
  
  #include <wizlevels.h>
  #include <erq.h>
  
  #define NEED_PROTOTYPES
  #include <daemon/socket.h>
  
  #ifdef ME
  #undef ME
  #endif
  #define ME this_object()
  #ifdef PO
  #undef PO
  #endif
  #define PO previous_object()
  
  // Access for internal output cache
  #define OUT_UFD  0  // destination FD
  #define OUT_DATA 1  // the data itself
  #define OUT_SIZE 2  // size of all previous chunks of this Write()
  
  mapping sockets;    // all the open sockets
  mixed*  output;     // things to be sent
  int     sending;    // answer from erq pending
  mapping used_fd;    // the 'file descriptor' translation
  int     so_fd;      // fd of socket object
  int     so_ufd;     // socket object's clone number
  string  so_buf;     // mudmode buffer
  mixed   so_bufs;    // expected packet size
  int     sent, rcvt; // number of sent and received bytes
  object  debugger;
  private string* valids;
  
  void create() {
    // _debug(1);
    if (clonep()) {
      if (!PO || object_name(PO) != SOCKETD) {
        destruct(ME);
        return;
      }
      move_object(ME, PO);
      so_fd = -1;
      return;
    }
    seteuid(getuid());
    if (!sockets) sockets = m_allocate(0, 17);
    if (!used_fd) used_fd = ([]);
    if (!output)  output  = ({});
    debug("*** created socket daemon");
  }
  
  //////
  // functions used in the socket objects only
  
  // socket object is initialized from the daemon here
  object so_init(mapping socks, mapping usedfd, int fd) {
    if (!clonep()) return 0;
    if (object_name(PO) != SOCKETD) return 0;
    if (sockets) return ME;
    sockets  = socks;  // local reference to global mapping
    used_fd  = usedfd; // dito
    so_fd    = fd;
    so_ufd   = UNIQUE_ID(ME);
    return ME;
  }
  
  // some checks for the user functions in the socket objects
  static int so_check(string fun) {
    if (!clonep()) return 0;
    if (so_fd < 0 || !member(sockets, so_fd)) {
      remove();
      RAISE_ERROR("uninitialized socket object\n");
    }
    if (!member(used_fd, so_ufd) || used_fd[so_ufd] != so_fd) {
      remove();
      RAISE_ERROR("filehandle mismatch\n");
    }
    if (!sockets[so_fd, S_OBJECT]) {
      remove();
      return 0;
    }
    if (PO != sockets[so_fd, S_OBJECT])
      RAISE_ERROR("privilege violation: "+fun+"()\n");
    return 1;
  }
  
  // write data over the socket (tcp only) (user function)
  public int Write(mixed data) {
    int size;
    if (!so_check("Write")) return 0;
    if (sockets[so_fd, S_OPTS] & SOCKET_MUDMODE) {
      data = to_mudmode(data);
      data = to_array(data)[0..strlen(data)-1];
      size = sizeof(data);
      data = ({ (size & 0x7f000000) >> 24, (size & 0xff0000) >> 16,
                (size & 0xff00) >> 8, size & 0xff }) + data;
    } else {
      if (!stringp(data) && !pointerp(data))
        RAISE_ERROR("Bad argument 1 to Write()\n");
    }
  
    return (int) SOCKETD->socket_write(so_ufd, data, 0);
  }
  
  // send data over the socket (udp only) (user function)
  public int Send(string host, int port, mixed msg) {
    if (!so_check("Send")) return 0;
    if (!stringp(host)) RAISE_ERROR("Bad argument 1 to Send()\n");
    if (!intp(port)) RAISE_ERROR("Bad argument 2 to Send()\n");
    if (!stringp(msg) && !pointerp(msg))
      RAISE_ERROR("Bad argument 3 to Send()\n");
    if (sockets[so_fd, S_STATE] != S_UDP)
      RAISE_ERROR("Send() on a non-udp socket\n");
    
    return (int) SOCKETD->udp_write(so_ufd, host, port, msg);
  }
  
  // accepts a new connection (tcp_listen only) (user function)
  public object Accept(closure cb) {
    object ob;
    if (!so_check("Accept")) return 0;
    if (sockets[so_fd, S_TYPE] != TCP_LISTEN)
      RAISE_ERROR("Accept() called on non-listening socket\n");
    if (!closurep(cb) || !(ob=to_object(cb)))
      RAISE_ERROR("Bad argument 1 to Accept()\n");
  
    return (object) SOCKETD->socket_accept(so_ufd, cb);
  }
  
  // close the opened socket (user function)
  public int Close() {
    int i;
    if (!so_check("Close")) return 0;
    return (int) SOCKETD->socket_close(so_ufd);
  }
  
  // get all connection information on this socket (user function)
  public mixed* State() {
    mixed* data;
    if (!so_check("State")) return 0;
    map(sockets, (: if ($1 == $3) $4 = copy($2); return 0; :), so_fd, &data);
    data[S_TICKET] = copy(data[S_TICKET]);
    data[S_WAIT_DATA] = 0; // not visible for user
    return data;
  }
  
  //////
  // functions used in the daemon only
  
  // create a new socket (user function)
  public object New(int type, mixed host, int port, closure cb, int opts) {
    int fd, *fds, ufd;
    object cb_o, so;
  
    debug("sockD::New(%O, %O, %O, %O, %O)", type, host, port, cb, opts);
  
    // TCP_ACCEPT could be generated internally only
    if (!intp(type) || type < 0 || type > (extern_call() ? 3 : 5))
      RAISE_ERROR("Bad argument 1 to New()\n");
    if (!(type & 4)) {
      if ((type & 1 && host) || (!(type & 1) && !stringp(host)))
        RAISE_ERROR("Bad argument 2 to New()\n");
    } else if (!objectp(host))
      RAISE_ERROR("Bad argument 2 to New()\n");
    if (!intp(port))
      RAISE_ERROR("Bad argument 3 to New()\n");
    if (!(type & 4) && (!closurep(cb) || !(cb_o=to_object(cb))))
      RAISE_ERROR("Bad argument 4 to New()\n");
    if (!intp(opts))
      RAISE_ERROR("Bad argument 5 to New()\n");
  
    if (extern_call() && !valid_socket(cb_o))
      RAISE_ERROR("privilege violation: New()\n");
  
    if (opts & SOCKET_MUDMODE) {
      opts |= SOCKET_BINARY;
      if (type == UDP) RAISE_ERROR("mudmode is tcp only\n");
    }
  
    // find empty fd
    fds = m_values(used_fd);
    for (fd = 0; fd < MAX_SOCKETS; fd++) {
      if (member(fds, fd) < 0) break;
    }
    if (fd >= MAX_SOCKETS) return 0; // no slot available
  
    if (type & 4) cb_o = sockets[port, S_OBJECT];
  
    while (1) {
      so  = (object) clone_object(program_name())->so_init(sockets, used_fd, fd);
      ufd = UNIQUE_ID(so);
      if (!member(used_fd, ufd)) break;
      // could happen but is very unlikely, just try again
      destruct(so);
    }
  
    used_fd[ufd] = fd;
    sockets[fd, S_SOCKOBJ]     = so;
    sockets[fd, S_TYPE]        = type;
    sockets[fd, S_TICKET]      = 0;
    sockets[fd, S_STATE]       = S_UNCONNECTED;
    sockets[fd, S_OPTS]        = opts;
    sockets[fd, S_HOST]        = host;
    sockets[fd, S_PORT]        = port;
    sockets[fd, S_CALLBACK]    = cb;
    sockets[fd, S_OBJECT]      = cb_o;
    sockets[fd, S_PENDING]     = 0;
    sockets[fd, S_OWNER]       = getuid(cb_o);
  
    if (type & 1)
      sockets[fd, S_LPORT] = port;
    if (type & 4)
      sockets[fd, S_LPORT] = sockets[sockets[fd, S_LISTEN_FD], S_PORT];
      
    switch (type) {
      case TCP_OPEN:
        lookup(host, lambda(({ 'msg }), ({ #'looked_up, 'msg, ufd })));
        break;
  
      case TCP_LISTEN:
        send_erq(ERQ_LISTEN, ({ port/256, port&255 }),
          lambda(({ 'msg }), ({ #'socket_cb, 'msg, ufd })));
        break;
        
      case UDP:
        send_erq(ERQ_OPEN_UDP, ({ port/256, port&255 }),
          lambda(({ 'msg }), ({ #'socket_cb, 'msg, ufd })));
    }
  
    return so;
  }
  
  // write data over a socket
  int socket_write(int ufd, mixed msg, int* udp_addr) {
    int size, chunk, *ticket, *out, pos, is_udp, fd;
    string snipsel;
    mixed* data;
  
    debug("sockD::socket_write(%O, %O)", ufd, udp_addr);
    //debug("sockD::socket_write(%O, %O, %O)", ufd, msg, udp_addr);
    
    if (!intp(ufd)) RAISE_ERROR("Bad argument 1 to socket_write()\n");
    if (PO && PO != ME) {
      if (!m_contains(&fd, used_fd, ufd) || PO != sockets[fd, S_SOCKOBJ])
        RAISE_ERROR("privilege violation: socket_write()\n");
    } else {
      if (!m_contains(&fd, used_fd, ufd)) return 0; //vanished
    }
    if (!stringp(msg) && !pointerp(msg))
      RAISE_ERROR("Bad argument 2 to socket_write()\n");
    is_udp = sockets[fd, S_STATE] == S_UDP;
    if (is_udp && !pointerp(udp_addr) || !is_udp && udp_addr)
      RAISE_ERROR("Bad argument 3 to socket_write()\n");
    if ((!udp_addr && sockets[fd, S_STATE] != S_CONNECTED)
      || !sockets[fd, S_TICKET]) {
      if (sockets[fd, S_STATE] == S_UNCONNECTED) {
        data = sockets[fd, S_PENDING];
        if (!data) data = ({});
        data += ({ ({ msg, udp_addr }) });
        return 2;
      }
      else
        RAISE_ERROR("socket_write() on an unconnected socket\n");
    }
  
    if (!udp_addr) udp_addr = ({});
    ticket = sockets[fd, S_TICKET];
    chunk = WRITE_MAX - sizeof(ticket) - sizeof(udp_addr);
  
    if (pointerp(msg)) {
      size = sizeof(msg);
      for (; pos < size; pos += chunk) {
        sendbuf(ufd, udp_addr + msg[pos .. pos+chunk-1], pos);
        ++sockets[fd, S_TO_WRITE];
      }
  
    } else {
      size = strlen(msg);
      for (; pos < size; pos += chunk) {
        snipsel = msg[pos .. pos+chunk-1];
        sendbuf(ufd, udp_addr + to_array(snipsel)[0..strlen(snipsel)-1], pos);
        ++sockets[fd, S_TO_WRITE];
      }
    }
    if (!sockets[fd, S_WAITS]) flush(ufd);
  
    return 1;
  }
  
  // accepts a pending connection from a listened to port
  object socket_accept(int ufd, closure cb) {
    object so, ob;
    int fd;
  
    debug("sockD::socket_accept(%O, %O)", ufd, cb);
    if (!intp(ufd)) RAISE_ERROR("Bad argument 1 to socket_accept()\n");
    if (!m_contains(&fd, used_fd, ufd) || PO != sockets[fd, S_SOCKOBJ])
        RAISE_ERROR("privilege violation: socket_accept()\n");
    if (sockets[fd, S_TYPE] != TCP_LISTEN)
      RAISE_ERROR("socket_accept() called on non-listening socket\n");
    if (!closurep(cb) || !(ob=to_object(cb)))
      RAISE_ERROR("Bad argument 2 to socket_accept()\n");
  
    so = New(TCP_ACCEPT, sockets[fd, S_SOCKOBJ], fd, cb, sockets[fd, S_OPTS]);
    if (!so) { // no more sockets left
      debug("too many connections, closing accepted connection");
      return 0;
    }
  
    send_erq(ERQ_ACCEPT, sockets[fd, S_TICKET],
      lambda(({ 'msg }), ({ #'accept_cb, 'msg, UNIQUE_ID(so) })));
  
    return so;
  }
  
  // write data via udp
  int udp_write(int ufd, string host, int port, mixed umsg) {
    int fd;
    debug("sockD::udp_write(%O, %O)", ufd, host, port, umsg);
    if (!intp(ufd)) RAISE_ERROR("Bad argument 1 to udp_write()\n");
    if (!m_contains(&fd, used_fd, ufd) || PO != sockets[fd, S_SOCKOBJ])
        RAISE_ERROR("privilege violation: udp_write()\n");
    if (!stringp(host)) RAISE_ERROR("Bad argument 2 to udp_write()\n");
    if (!intp(port)) RAISE_ERROR("Bad argument 3 to udp_write()\n");
    if (!stringp(umsg) && !pointerp(umsg))
      RAISE_ERROR("Bad argument 4 to udp_write()\n");
    if (sockets[fd, S_TYPE] != UDP)
      RAISE_ERROR("udp_write() on a non-udp socket\n");
  
    lookup(host, lambda(({ 'msg }),
      ({ #'socket_write, ufd, umsg,
        ({ #'+, ({ #'[..], 'msg, 1, 4 }),
                ({ #'({, port / 256, port & 255 })
        })
      }) ));
    return 1;
  }
  
  // a socket is closed here. it can only be done from the socket object
  int socket_close(int ufd) {
    int i, fd, fd2, ufd2;
    object so;
  
    debug("sockD::socket_close(%O)", ufd);
    if (!intp(ufd)) RAISE_ERROR("Bad argument 1 to socket_close()\n");
    if (PO && PO != ME) {
      if (!m_contains(&fd, used_fd, ufd) || PO != sockets[fd, S_SOCKOBJ])
        RAISE_ERROR("privilege violation: socket_close()\n");
    } else {
      if (!m_contains(&fd, used_fd, ufd)) return 0; //vanished
    }
    if (!member(sockets, fd)) return 0;
  
    // delete associated data
    i = sizeof(output);
    for (; i--;)
      if (output[i][OUT_UFD] == ufd)
        output[i..i] = ({});
  
    if (sockets[fd, S_TICKET] && sockets[fd, S_STATE] != S_CLOSING) {
      // never kill twice
      sockets[fd, S_STATE] = S_CLOSING;
      send_erq(ERQ_KILL, sockets[fd, S_TICKET] + ({ 0, 0, 0, 0 }),
        lambda(({ 'msg }), ({ #'socket_cb, 'msg, ufd })));
    }
    else {
      so = sockets[fd, S_SOCKOBJ];
      m_delete(sockets, fd);
      m_delete(used_fd, ufd);
      if (so) destruct(so);
    }
    return 1;
  }
  
  // looks up a DNS name
  static void lookup(string host, closure cb) {
    int *msg;
  
    debug("sockD::lookup(%O, %O)", host, cb);
    msg = allocate(5);
    if (sscanf(host, "%d.%d.%d.%d", msg[1], msg[2], msg[3], msg[4]) == 4) {
      funcall(cb, msg);
      return;
    }
  
    send_erq(ERQ_LOOKUP, host, cb);
  }
  
  // the DNS answer for TCP is processed here
  static void looked_up(int* msg, int ufd) {
    int fd;
  
    debug("sockD::looked_up(%O, %O)", msg, ufd);
    if (!m_contains(&fd, used_fd, ufd)) return; // vanished
  
    if (msg[0] != ERQ_OK) {
      callback(ufd, SOCKET_ERROR, note_err("unknown response", msg, fd));
      return;
    }
  
    sockets[fd, S_HOST] = sprintf("%d.%d.%d.%d",
      msg[1] & 255, msg[2] & 255, msg[3] & 255, msg[4] & 255);
  
    send_erq(ERQ_OPEN_TCP, msg[1..4] +
      ({ sockets[fd, S_PORT] / 256, sockets[fd, S_PORT] & 255 }),
      lambda(({ 'mess }), ({ #'socket_cb, 'mess, ufd })));
  }
  
  // most of data gets here from the erq (see write_cb)
  static void socket_cb(int* msg, int ufd) {
    int state, opts, port, type, fd;
    string host;
    object so;
  
    debug("sockD::socket_cb(%O, %O)", msg, ufd);
    if (!m_contains(&fd, used_fd, ufd)) return; // should not happen
    msg   = map(msg, #'&, 255);
    type  = sockets[fd, S_TYPE];
    state = sockets[fd, S_STATE];
    opts  = sockets[fd, S_OPTS];
  
    switch (msg[0]) {
  
      case ERQ_OK: {
        debug("ERQ_OK");
        if (!sockets[fd, S_TICKET]) {
          switch (type) {
            case TCP_OPEN:   state = S_CONNECTED; break;
            case TCP_LISTEN: state = S_LISTEN;    break;
            case UDP:        state = S_UDP;       break;
            default:         debug("unexpected type", type); break;
  
            case TCP_ACCEPT:
              debug("accepting");
              host = sprintf("%d.%d.%d.%d", msg[1], msg[2], msg[3], msg[4]);
              port = msg[5] * 256 + msg[6];
              if (member(sockets, sockets[fd, S_LISTEN_FD]))
                sockets[sockets[fd, S_LISTEN_FD], S_PENDING] = 0;
              sockets[fd, S_TICKET] = msg[7..];
              sockets[fd, S_STATE]  = S_CONNECTED;
              sockets[fd, S_HOST]   = host;
              sockets[fd, S_PORT]   = port;
              callback(ufd, SOCKET_READY,
                ({ sockets[fd, S_SOCKOBJ], host, port }));
              return;
          }
          sockets[fd, S_TICKET] = msg[1..];
          sockets[fd, S_STATE]  = state;
          callback(ufd, SOCKET_READY, 0);
        }
        else {
          debug("socket has a ticket");
        }
        return;
      }
  
      case ERQ_STDOUT: {
        debug("ERQ_STDOUT");
        switch (state) {
  
          case S_LISTEN:
            debug("incoming connection (tcp)");
            if (sockets[fd, S_SOCKOBJ] && sockets[fd, S_OBJECT]) {
              sockets[fd, S_PENDING] = 1;
              callback(ufd, SOCKET_ACCEPT, 0);
            }
            else socket_close(ufd); // weird
            break;
  
          case S_CONNECTED:
            msg = msg[1..];
            rcvt += sizeof(msg);
            ++sockets[fd, S_RECEIVED];
            if (opts & SOCKET_BINARY)
              callback(ufd, SOCKET_READ, quote(msg));
            else
              callback(ufd, SOCKET_READ, to_ascii(msg));
            break;
  
          case S_UDP:
            debug("incoming connection (udp)");
            host = sprintf("%d.%d.%d.%d", msg[1], msg[2], msg[3], msg[4]);
            port = msg[5] * 256 + msg[6];
            msg = msg[7..];
            rcvt += sizeof(msg);
            if (opts & SOCKET_BINARY)
              callback(ufd, SOCKET_READ, ({ msg, host, port }));
            else
              callback(ufd, SOCKET_READ, ({ to_ascii(msg), host, port }));
            break;
  
          default:
            debug("unexpected state", state);
            break;
        }
        return;
      }
  
      case ERQ_EXITED:
        debug("ERQ_EXITED");
        so = sockets[fd, S_SOCKOBJ];
        sockets[fd, S_STATE] = S_CLOSING;
        callback(ufd, SOCKET_CLOSE, 0);
        m_delete(sockets, fd);
        m_delete(used_fd, ufd);
        if (so) destruct(so);
        return;
  
      case ERQ_E_ARGLENGTH:
        // last message ignored by xerq
        callback(ufd, SOCKET_ERROR, note_err("ERQ_E_ARGLENGTH", msg, fd));
        return;
  
      case ERQ_E_NSLOTS:
        callback(ufd, SOCKET_ERROR, note_err("ERQ_E_NSLOTS", msg, fd));
        break;
  
      case ERQ_E_WOULDBLOCK:
        callback(ufd, SOCKET_ERROR, note_err("ERQ_WOULDBLOCK", msg, fd));
        debug("ERQ_E_WOULDBLOCK socket cb"); 
        break;
  
      case ERQ_E_TICKET:
        callback(ufd, SOCKET_ERROR, note_err("ERQ_E_TICKET", msg, fd));
        break;
  
      case ERQ_E_ILLEGAL:
        callback(ufd, SOCKET_ERROR, note_err("ERQ_E_ILLEGAL", msg, fd));
        break;
  
      case ERQ_E_UNKNOWN:
        callback(ufd, SOCKET_ERROR, note_err("ERQ_E_UNKNOWN", msg, fd));
        break;
  
      default:
        callback(ufd, SOCKET_ERROR, note_err("unknown response", msg, fd));
        break;
    }
  
    // if we get here an error occured. the erq closed the socket already
    if (member(sockets, fd)) sockets[fd, S_STATE] = S_CLOSING;
    callback(ufd, SOCKET_CLOSE, 0);
    socket_close(ufd);
  }
  
  // answers from the erq upon writes get here to handle the
  // data buffer (output) according to its answers
  static void write_cb(int* msg, int ufd, int offset) {
    int fd, x;
    sending = 0;
    debug("sockD::write_cb(%O, %O)", msg, ufd);
    if (!m_contains(&fd, used_fd, ufd)) return; // vanished
  
    switch (msg[0]) {
  
      case ERQ_E_INCOMPLETE:
        debug("ERQ_E_INCOMPLETE");
        sockets[fd, S_WAITS] = S_WAITS_INCOM;
        msg = map(msg, #'&, 255);
        x = (((msg[1] << 8) + msg[2] << 8) + msg[3] << 8) + msg[4];
        callback(ufd, SOCKET_INCOMPLETE, ({ x, offset + x }) );
        return;
  
      case ERQ_OK:
        debug("ERQ_OK");
        sockets[fd, S_WAITS] = S_WAITS_NOT;
        sockets[fd, S_WAIT_DATA] = 0;
        ++sockets[fd, S_WRITTEN];
        if (sizeof(output)) flush(ufd);
        callback(ufd, SOCKET_WRITTEN,
          sockets[fd, S_TO_WRITE] == sockets[fd, S_WRITTEN]);
        return;
  
      case ERQ_E_WOULDBLOCK:
        // The xerq could not handle the sent data - this condition
        // could never happen. However, if it happens we have to resend
        // the last packet after some time. We just put it back in the
        // output queue and wait for another ERQ_OK.
        // If you have problems with sockets that seem to freeze after a
        // wouldblock (which indicates a bad erq implementation), you could
        // try and start a resend callout here.
        if (!sockets[fd, S_WAIT_DATA]) {
          // something went extremly wrong, there were several packets
          // sent without waiting for erq response. Original data is
          // lost already. Mind, this is a 'could never happen' case
          // within a 'could never happen' case ;o))
          //
          // Just do nothing.
        } else
          output = ({ sockets[fd, S_WAIT_DATA] }) + output; // back on stack
        sockets[fd, S_WAITS] = S_WAITS_BLOCK;
        sockets[fd, S_WAIT_DATA] = 0;
        callback(ufd, SOCKET_ERROR, note_err("ERQ_E_WOULDBLOCK", msg, fd));
        return;
  
      case ERQ_E_TICKET:
        callback(ufd, SOCKET_ERROR, note_err("ERQ_E_TICKET", msg, fd));
        break;
  
      case ERQ_E_PIPE:
        // should only happen with childs
        callback(ufd, SOCKET_ERROR, note_err("ERQ_E_PIPE", msg, fd));
        break;
  
      case ERQ_E_UNKNOWN:
        callback(ufd, SOCKET_ERROR, note_err("ERQ_E_UNKNOWN", msg, fd));
        break;
  
      default:
        callback(ufd, SOCKET_ERROR, note_err("unknown response", msg, fd));
        break;
    }
  
    // if we get here an error occured. the erq closed the socket already
    if (member(sockets, fd)) sockets[fd, S_STATE] = S_CLOSING;
    callback(ufd, SOCKET_CLOSE, 0);
    socket_close(ufd);
  }
  
  // special cb for accept
  // the xerq sends ERQ_E_TICKET also if the socket has no pending
  // connection :-/ because we cannot distinguish that to a wrong
  // ticket we just assume here it must be the first.
  static void accept_cb(int* msg, int ufd) {
    int fd;
    if (msg[0] != ERQ_E_TICKET) return socket_cb(msg, ufd);
    if (!m_contains(&fd, used_fd, ufd)) return; // vanished
    if (member(sockets, sockets[fd, S_LISTEN_FD]))
      sockets[sockets[fd, S_LISTEN_FD], S_PENDING] = 0;
    sockets[fd, S_STATE] = S_CLOSING;
    callback(ufd, SOCKET_NOPEND, 0);
    note_err("SOCKET_NOPEND", msg, fd);
    callback(ufd, SOCKET_CLOSE, 0);
    socket_close(ufd);
  }
  
  // the function calls the user object on new data or errors
  static void callback(int ufd, int action, mixed args) {
    int fd;
    mixed data, item;
  
    debug("sockD::callback(%O, %O, %O)", fd, action, args);
    if (!m_contains(&fd, used_fd, ufd) || !member(sockets, fd))
      return; // vanished
  
    // automatic delayed write
    data = sockets[fd, S_PENDING];
    if (pointerp(data) && action == SOCKET_READY) {
      foreach (item : data) socket_write(ufd, item[0], item[1]);
      sockets[fd, S_PENDING] = sizeof(item);
    }
    else if (intp(data) && data > 0 && action == SOCKET_WRITTEN) {
      sockets[fd, S_PENDING] = data - 1;
    }
    if (!sockets[fd, S_SOCKOBJ] || !sockets[fd, S_OBJECT]) {
      if (!data && sockets[fd, S_STATE] != S_CLOSING) socket_close(ufd);
      return;
    }
  
    if (action == SOCKET_READ && sockets[fd, S_OPTS] & SOCKET_MUDMODE) {
      args = to_array(args); // unquote args
      sockets[fd, S_SOCKOBJ]->so_get_mudmode(args);
      return;
    }
    apply(sockets[fd, S_CALLBACK], sockets[fd, S_SOCKOBJ], action, args);
  }
  
  
  // send a message trough the network (buffered)
  static void sendbuf(int ufd, int* data, int offset) {
    int fd;
    // debug("sockD::sendbuf(%O, %O)", ufd, data);
    debug("sockD::sendbuf(%O)", ufd);
    if (!m_contains(&fd, used_fd, ufd)) return; // vanished
    output += ({ ({ ufd, data, offset }) });
  }
  
  // Flush the message buffer for socket ufd.
  //
  // Xerq works as follows:
  //   1 Erq gets new data
  //   2 Erq tries to send data packet
  //   3 Erq returns OK if successfull -=> continue at 1
  //   4 Erq returns INCOMPLETE. Rest of packet is buffered and sent
  //     when possible.
  //   5 Erq returns OK if all buffered data could be written (*new*)
  //   6 Continue at 1
  //
  //   There is only one packet buffered inside the erq for every socket.
  //   If you try to send a packet while one is in the queue already, the erq
  //   returns WOULDBLOCK and the new packet is discarded!
  static void flush(int ufd) {
    int ret, i, n, fd;
    int* data;
      
    debug("sockD::flush(%O)", ufd);
    // debug(output);
  
    n = sizeof(output);
    if (!n) {
      debug("nothing to send");
      return;
    }
  
    // remove buffered data if socket is gone
    if (!m_contains(&fd, used_fd, ufd) || !member(sockets, fd)) {
      for (;n--;) {
        if (output[n][OUT_UFD] != ufd) continue;
        debug("discarding %O -> %O", ufd, output[0][OUT_DATA]);
        output[n..n] = ({});
      }
      return;
    }
  
    if (sockets[fd, S_WAITS]) {
      debug("socket busy");
      return;
    }
  
    // find socket's data
    for (; i < n; i++) {
      if (output[i][OUT_UFD] == ufd) break;
    }
    if (i == n) {
      debug("no data pending");
      return;
    }
  
    sent += sizeof(output[i][OUT_DATA]);
    data = sockets[fd, S_TICKET] + output[i][OUT_DATA];
    debug("sending");
    ret = send_erq(ERQ_SEND, data,
      lambda(({ 'msg }), ({ #'write_cb, 'msg, ufd, output[i][OUT_SIZE] })));
    if (!ret) {
      debug("got send_erg() error, retry in 1 second");
      call_out("flush", 1, ufd);
      return;
    }
    sockets[fd, S_WAITS] = S_WAITS_ERQ;
    sockets[fd, S_WAIT_DATA] = output[i..i];
    output[i..i] = ({});
  }
  
  // mudmode is an invention of MudOS. We have to simulate it here in
  // some way or another. Its quite similar to ldmuds save_value() but
  // does not change "\n" to "\\n" etc.
  static string to_mudmode(mixed data) {
    int num, i, len;
  
    // use save_value(data)[5..<2] with pre 3.2.10 drivers
    data = save_value(data, 0)[5..<2];
    len = strlen(data);
  
    for (i=0; i<len; i++) {
      if (data[i] == '\\') {
        num++;
        continue;
      }
      if (!num) continue;
      if (num & 1) {
        switch (data[i]) {
          case 'r': data[i-1..i] = "\r"; len--; i--; break;
          case 'f': data[i-1..i] = "\f"; len--; i--; break;
          case 'v': data[i-1..i] = "\v"; len--; i--; break;
          case 'n': data[i-1..i] = "\n"; len--; i--; break;
          case 't': data[i-1..i] = "\t"; len--; i--; break;
          case 'b': data[i-1..i] = "\b"; len--; i--; break;
          case 'a': data[i-1..i] = "\a"; len--; i--; break;
        }
      }
      num = 0;
    }
  
    return data;
  }
  
  mixed from_mudmode(string data) {
    return restore_value("#0:0\n"+data+"\n");
  }
  
  // this is inside the socket object to collect full mudmode packets
  void so_get_mudmode(int* data) {
    mixed mix;
    string err;
    int i;
    if (object_name(PO) != SOCKETD)
      RAISE_ERROR("privilege violation: so_get_mudmode()\n");
    while (sizeof(data)) {
      if (!so_bufs || !intp(so_bufs)) {
        if (!so_bufs) so_bufs = ({});
        i = min(sizeof(data), 4);
        i -= sizeof(so_bufs);
        so_bufs += data[0..i-1];
        data = data[i..];
        if (sizeof(so_bufs) == 4) {
          so_bufs = (((((so_bufs[0] & 0xff)) << 8
                      | (so_bufs[1] & 0xff)) << 8
                      | (so_bufs[2] & 0xff)) << 8
                      | (so_bufs[3] & 0xff));
          debug("mudmode packet size ", so_bufs);
          // String length is critical here, one string has to hold the
          // complete packet. It seems that modmode is working with rather
          // small packets (< 1kb) which fit nicely in a chararray, but
          // we dont want do depend on that (and use strings).
        }
        else break;
        if (so_bufs == 0)
          debug("strange size, isn't it?");
      }
      if (!so_buf) so_buf = "";
      i = sizeof(data);
      if (i >= so_bufs) {
        if (data[so_bufs-1] == 0)
          so_buf += to_string(data[0..so_bufs-2]);
        else
          so_buf += to_string(data[0..so_bufs-1]);
        data = data[so_bufs..];
        err = catch(mix = from_mudmode(so_buf));
        so_buf = "";
        so_bufs = 0;
        if (err) { // invalid packet
          funcall(sockets[so_fd, S_CALLBACK], ME, SOCKET_ERROR,
            "mudmode protocoll violation");
          SOCKETD->socket_close(so_ufd);
          return;
        }
        funcall(sockets[so_fd, S_CALLBACK], ME, SOCKET_READ, mix);
      } else {
        so_buf += to_string(data);
        so_bufs -= i;
        debug("... still data pending");
        break; // no data left
      }
    }
  }
  
  // different handling of removal for master and clones
  int remove() {
    if (clonep()) {
      SOCKETD->socket_close(so_ufd);
      destruct(ME);
      return 1;
    }
  
    if (object_name(PO) != __MASTER_OBJECT__ && !SPECIAL_PREVILEGE) {
      printf("DONT DO THAT!\n");
      return 0;
    }
  
    if (first_inventory()) {
      debug("*** going to remove socketd... (3 sec)");
      map(m_indices(used_fd), #'socket_close);
      call_out("do_remove", 3);
    } else destruct(ME);
    return 1;
  }
  
  static void do_remove() {
    debug("*** done");
    destruct(ME);
  }
  
  //////
  // internal helper functions
  
  // which objects may create sockets
  static int valid_socket(object ob) {
    string on, v;
  
    on = object_name(ob);
    if (!valids) valids = VALID_SOCKETS;
    foreach (v : valids)
      if (!strstr(on, v)) return 1;
    return 0;
  }
  
  // convert message to ascii
  // values are signed -128 .. 127
  static string to_ascii(int* msg) {
    msg -= ({ 0 });
    return to_string(msg);
  }
  
  // some erq-error occured, log it
  static mixed* note_err(string str, int* msg, int fd) {
    string who, extra, e2;
  
    if (sockets[fd, S_OBJECT]) who = to_string(sockets[fd, S_OBJECT]);
    else who = "destructed object";
  
    if (msg[0] == ERQ_E_UNKNOWN) {
      extra = ({
            // all errno-codes
            // use list according to your system (this is linux)
            "??? not used",
            "EPERM Operation not permitted",
            "ENOENT No such file or directory",
            "ESRCH No such process",
            "EINTR Interrupted system call",
            "EIO I/O error",
            "ENXIO No such device or address",
            "E2BIG Arg list too long",
            "ENOEXEC Exec format error",
            "EBADF Bad file number",
            "ECHILD No child processes", // 10
            "EAGAIN Try again",
            "ENOMEM Out of memory",
            "EACCES Permission denied",
            "EFAULT Bad address",
            "ENOTBLK Block device required",
            "EBUSY Device or resource busy",
            "EEXIST File exists",
            "EXDEV Cross-device link",
            "ENODEV No such device",
            "ENOTDIR Not a directory", // 20
            "EISDIR Is a directory",
            "EINVAL Invalid argument",
            "ENFILE File table overflow",
            "EMFILE Too many open files",
            "ENOTTY Not a typewriter",
            "ETXTBSY Text file busy",
            "EFBIG File too large",
            "ENOSPC No space left on device",
            "ESPIPE Illegal seek",
            "EROFS Read-only file system", // 30
            "EMLINK Too many links",
            "EPIPE Broken pipe",
            "EDOM Math argument out of domain of func",
            "ERANGE Math result not representable",
            "EDEADLK Resource deadlock would occur",
            "ENAMETOOLONG File name too long",
            "ENOLCK No record locks available",
            "ENOSYS Function not implemented",
            "ENOTEMPTY Directory not empty",
            "ELOOP Too many symbolic links encountered", // 40
            "??? not used",
            "ENOMSG No message of desired type",
            "EIDRM Identifier removed",
            "ECHRNG Channel number out of range",
            "EL2NSYNC Level 2 not synchronized",
            "EL3HLT Level 3 halted",
            "EL3RST Level 3 reset",
            "ELNRNG Link number out of range",
            "EUNATCH Protocol driver not attached",
            "ENOCSI No CSI structure available", // 50
            "EL2HLT Level 2 halted",
            "EBADE Invalid exchange",
            "EBADR Invalid request descriptor",
            "EXFULL Exchange full",
            "ENOANO No anode",
            "EBADRQC Invalid request code",
            "EBADSLT Invalid slot",
            "??? not used",
            "EBFONT Bad font file format",
            "ENOSTR Device not a stream", // 60
            "ENODATA No data available",
            "ETIME Timer expired",
            "ENOSR Out of streams resources",
            "ENONET Machine is not on the network",
            "ENOPKG Package not installed",
            "EREMOTE Object is remote",
            "ENOLINK Link has been severed",
            "EADV Advertise error",
            "ESRMNT Srmount error",
            "ECOMM Communication error on send", // 70
            "EPROTO Protocol error",
            "EMULTIHOP Multihop attempted",
            "EDOTDOT RFS specific error",
            "EBADMSG Not a data message",
            "EOVERFLOW Value too large for defined data type",
            "ENOTUNIQ Name not unique on network",
            "EBADFD File descriptor in bad state",
            "EREMCHG Remote address changed",
            "ELIBACC Can not access a needed shared library",
            "ELIBBAD Accessing a corrupted shared library", // 80
            "ELIBSCN .lib section in a.out corrupted",
            "ELIBMAX Attempting to link in too many shared libraries",
            "ELIBEXEC Cannot exec a shared library directly",
            "EILSEQ Illegal byte sequence",
            "ERESTART Interrupted system call should be restarted",
            "ESTRPIPE Streams pipe error",
            "EUSERS Too many users",
            "ENOTSOCK Socket operation on non-socket",
            "EDESTADDRREQ Destination address required",
            "EMSGSIZE Message too long", // 90
            "EPROTOTYPE Protocol wrong type for socket",
            "ENOPROTOOPT Protocol not available",
            "EPROTONOSUPPORT Protocol not supported",
            "ESOCKTNOSUPPORT Socket type not supported",
            "EOPNOTSUPP Operation not supported on transport endpoint",
            "EPFNOSUPPORT Protocol family not supported",
            "EAFNOSUPPORT Address family not supported by protocol",
            "EADDRINUSE Address already in use",
            "EADDRNOTAVAIL Cannot assign requested address",
            "ENETDOWN Network is down", // 100
            "ENETUNREACH Network is unreachable",
            "ENETRESET Network dropped connection because of reset",
            "ECONNABORTED Software caused connection abort",
            "ECONNRESET Connection reset by peer",
            "ENOBUFS No buffer space available",
            "EISCONN Transport endpoint is already connected",
            "ENOTCONN Transport endpoint is not connected",
            "ESHUTDOWN Cannot send after transport endpoint shutdown",
            "ETOOMANYREFS Too many references: cannot splice",
            "ETIMEDOUT Connection timed out", // 110
            "ECONNREFUSED Connection refused",
            "EHOSTDOWN Host is down",
            "EHOSTUNREACH No route to host",
            "EALREADY Operation already in progress",
            "EINPROGRESS Operation now in progress",
            "ESTALE Stale NFS file handle",
            "EUCLEAN Structure needs cleaning",
            "ENOTNAM Not a XENIX named type file",
            "ENAVAIL No XENIX semaphores available",
            "EISNAM Is a named type file", // 120
            "EREMOTEIO Remote I/O error",
            "EDQUOT Quota exceeded",
            "ENOMEDIUM No medium found",
            "EMEDIUMTYPE Wrong medium type", })[msg[1]];
  
      debug(str + " (%O) %O (%O) for %O", msg[0], e2, msg[1], who);
    }
    else debug(str + " (%O) for %O", msg[0], who);
  
  
    log_file(SOCKETLOG, ctime()[4..19] + str + " (" + msg[0] + ") " + extra||"" +
      "for " + who + "\n");
  
    return ({ str, msg, extra });
  }
  
  object _debug(int on) {
    if (!SPECIAL_PREVILEGE) return 0;
    if (!on) return debugger = 0;
    return debugger = this_interactive();
  }
  
  static void debug(varargs mixed* xx) {
    string txt;
    mixed y, x;
    int i;
  
    if (!debugger) return;
  
    x = xx + ({}); // strange behavior in 3.3's foreach
    if (stringp(x[0])) {
      txt = x[0];
      x = x[1..];
    }
    else txt = "";
  
    foreach (y : x) {
      i = strstr(txt, "%O");
      if (i >= 0) txt[i..i+1] = MIXED2STR(y);
      else txt += " " + MIXED2STR(y);
    }
  
    tell_object(debugger, txt + "\n");
  }
  
  // you could send a wizcommand here
  public int netstat(string arg) {
    string proto, mode, state, h_p;
    object so;
    int fd;
  
    if (stringp(arg)) {
      arg = trim(arg);
      fd = to_int(arg);
      if (!member(sockets, fd)) {
        printf("No socket with file descriptor '" + arg + "'.\n");
        return 1;
      }
      printf("Full information on socket fd " + fd + "\n");
      printf("  object:   " + to_string(sockets[fd, S_SOCKOBJ]) + "\n");
      printf("  type:     " + ({ "TCP_OPEN", "TCP_LISTEN", "?", "UDP", "?",
        "TCP_ACCEPT"})[sockets[fd, S_TYPE]] + "\n");
      printf("  ticket:   " +
        implode(map(sockets[fd, S_TICKET], #'to_string), ",") + "\n");
      printf("  state:    "+ ({ "UNCONNECTED", "CONNECTED", "LISTEN", "LISTEN",
        "CLOSING", "ACCEPT" })[sockets[fd, S_STATE]] + "\n");
      printf("  option:   " + ({ "ASCII", "BINARY", "?",
        "MUDMODE" })[sockets[fd, S_OPTS]] + "\n");
      printf("  callback: %O\n", sockets[fd, S_CALLBACK]);
      printf("  creater:  " + to_string(sockets[fd, S_OBJECT]) + "\n");
      printf("  created:  " + ctime(object_time(sockets[fd, S_SOCKOBJ])) + "\n");
      printf("  owner id: " + sockets[fd, S_OWNER] + "\n");
      printf("  packets to write: " + sockets[fd, S_TO_WRITE] + "\n");
      printf("  packets written:  " + sockets[fd, S_WRITTEN] +
        " (" + (sockets[fd, S_TO_WRITE] - sockets[fd, S_WRITTEN]) +
        " remaining)\n");
      printf("  packets received: " + sockets[fd, S_RECEIVED] + "\n");
      printf("  if applicable:\n");
      printf("    host:        " + sockets[fd, S_HOST] + "\n");
      printf("    port:        " + sockets[fd, S_PORT] + "\n");
      printf("    listen fd:   " + sockets[fd, S_LISTEN_FD] + "\n");
      printf("    listen port: " + sockets[fd, S_LPORT] + "\n");
      return 1;
    }
  
    printf("fd  proto      mode    address               local   state     "
      "pend  owner\n");
    foreach (fd : sort_array(m_indices(sockets),#'>)) {
      if (!sockets[fd, S_OBJECT]) {
        m_delete(sockets, fd);
        continue;
      }
      proto = ({ "TCP_OPEN", "TCP_LISTEN", 0, "UDP", 0,
        "TCP_ACCEPT"})[sockets[fd, S_TYPE]];
      if (sockets[fd, S_OPTS] == 0) mode = "ASCII";
      else if (sockets[fd, S_OPTS] & 2) mode = "MUDMODE";
      else mode = "BINARY";
  
      state = ({ "UNCONNECTED", "CONNECTED", "LISTEN", "LISTEN", "CLOSING",
        "ACCEPT" })[sockets[fd, S_STATE]];
      if (sockets[fd, S_TYPE] == TCP_ACCEPT
        && sockets[fd, S_STATE] != S_CONNECTED) h_p = "(unknown)";
      else {
        h_p = sockets[fd, S_HOST];
        if (h_p) h_p += " " + sockets[fd, S_PORT];
        else h_p = "---";
      }
  
      printf("%2d  %-10s %-7s %-21s %-7s %-11s %c %-10s\n",
        fd, proto, mode, h_p,
        sockets[fd, S_LPORT] ? "("+sockets[fd, S_LPORT]+")" : "",
        state, sockets[fd, S_PENDING] ? '*' : ' ',
        sockets[fd, S_OWNER] + regreplace(object_name(sockets[fd, S_OBJECT]),
        ".*/([^/#]+).*", ":\\1", 0));
    }
  
    if (!sizeof(sockets)) printf(" <--  empty list  -->\n");
    printf("%d socket objects for %d sockets\n", sizeof(all_inventory()),
      sizeof(sockets));
    printf("%d bytes send, %d bytes received\n", sent, rcvt);
    return 1;
  }
  