function Server(options) {
  if (options) {
    if (typeof (options) !== 'object')
      throw new TypeError('options (object) required');
    if (typeof (options.log) !== 'object')
      throw new TypeError('options.log must be an object');

    if (options.certificate || options.key) {
      if (!(options.certificate && options.key) ||
          (typeof (options.certificate) !== 'string' &&
          !Buffer.isBuffer(options.certificate)) ||
          (typeof (options.key) !== 'string' &&
          !Buffer.isBuffer(options.key))) {
        throw new TypeError('options.certificate and options.key ' +
                            '(string or buffer) are both required for TLS');
      }
    }
  } else {
    options = {};
  }
  var self = this;

  EventEmitter.call(this, options);

  this._chain = [];
  this.log = options.log;
  this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true;

  var log = this.log;

  function setupConnection(c) {
    assert.ok(c);

    if (c.type === 'unix') {
      c.remoteAddress = self.server.path;
      c.remotePort = c.fd;
    } else if (c.socket) {
      // TLS
      c.remoteAddress = c.socket.remoteAddress;
      c.remotePort = c.socket.remotePort;
    }


    var rdn = new dn.RDN({cn: 'anonymous'});

    c.ldap = {
      id: c.remoteAddress + ':' + c.remotePort,
      config: options,
      _bindDN: new DN([rdn])
    };
    c.addListener('timeout', function () {
      log.trace('%s timed out', c.ldap.id);
      c.destroy();
    });
    c.addListener('end', function () {
      log.trace('%s shutdown', c.ldap.id);
    });
    c.addListener('error', function (err) {
      log.warn('%s unexpected connection error', c.ldap.id, err);
      self.emit('clientError', err);
      c.destroy();
    });
    c.addListener('close', function (had_err) {
      log.trace('%s close; had_err=%j', c.ldap.id, had_err);
      c.end();
    });

    c.ldap.__defineGetter__('bindDN', function () {
      return c.ldap._bindDN;
    });
    c.ldap.__defineSetter__('bindDN', function (val) {
      if (!(val instanceof DN))
        throw new TypeError('DN required');

      c.ldap._bindDN = val;
      return val;
    });
    return c;
  }

  function newConnection(c) {
    setupConnection(c);
    log.trace('new connection from %s', c.ldap.id);

    dtrace.fire('server-connection', function () {
      return [c.remoteAddress];
    });

    c.parser = new Parser({
      log: options.log
    });
    c.parser.on('message', function (req) {
      req.connection = c;
      req.logId = c.ldap.id + '::' + req.messageID;
      req.startTime = new Date().getTime();

      if (log.debug())
        log.debug('%s: message received: req=%j', c.ldap.id, req.json);

      var res = getResponse(req);
      if (!res) {
        log.warn('Unimplemented server method: %s', req.type);
        c.destroy();
        return false;
      }

      // parse string DNs for routing/etc
      try {
        switch (req.protocolOp) {
        case Protocol.LDAP_REQ_BIND:
          req.name = dn.parse(req.name);
          break;
        case Protocol.LDAP_REQ_ADD:
        case Protocol.LDAP_REQ_COMPARE:
        case Protocol.LDAP_REQ_DELETE:
          req.entry = dn.parse(req.entry);
          break;
        case Protocol.LDAP_REQ_MODIFY:
          req.object = dn.parse(req.object);
          break;
        case Protocol.LDAP_REQ_MODRDN:
          req.entry = dn.parse(req.entry);
          // TODO: handle newRdn/Superior
          break;
        case Protocol.LDAP_REQ_SEARCH:
          req.baseObject = dn.parse(req.baseObject);
          break;
        default:
          break;
        }
      } catch (e) {
        if (self.strictDN) {
          return res.end(errors.LDAP_INVALID_DN_SYNTAX);
        }
      }

      res.connection = c;
      res.logId = req.logId;
      res.requestDN = req.dn;

      var chain = self._getHandlerChain(req, res);

      var i = 0;
      return function (err) {
        function sendError(err) {
          res.status = err.code || errors.LDAP_OPERATIONS_ERROR;
          res.matchedDN = req.suffix ? req.suffix.toString() : '';
          res.errorMessage = err.message || '';
          return res.end();
        }

        function after() {
          if (!self._postChain || !self._postChain.length)
            return;

          function next() {} // stub out next for the post chain

          self._postChain.forEach(function (c) {
            c.call(self, req, res, next);
          });
        }

        if (err) {
          log.trace('%s sending error: %s', req.logId, err.stack || err);
          self.emit('clientError', err);
          sendError(err);
          return after();
        }

        try {
          var next = arguments.callee;
          if (chain.handlers[i])
            return chain.handlers[i++].call(chain.backend, req, res, next);

          if (req.protocolOp === Protocol.LDAP_REQ_BIND && res.status === 0)
            c.ldap.bindDN = req.dn;

          return after();
        } catch (e) {
          if (!e.stack)
            e.stack = e.toString();
          log.error('%s uncaught exception: %s', req.logId, e.stack);
          return sendError(new errors.OperationsError(e.message));
        }

      }();
    });

    c.parser.on('error', function (err, message) {
      self.emit('error', new VError(err, 'Parser error for %s', c.ldap.id));

      if (!message)
        return c.destroy();

      var res = getResponse(message);
      if (!res)
        return c.destroy();

      res.status = 0x02; // protocol error
      res.errorMessage = err.toString();
      return c.end(res.toBer());
    });

    c.on('data', function (data) {
      if (log.trace())
        log.trace('data on %s: %s', c.ldap.id, util.inspect(data));

      c.parser.write(data);
    });

  } // end newConnection

  this.routes = {};
  if ((options.cert || options.certificate) && options.key) {
    options.cert = options.cert || options.certificate;
    this.server = tls.createServer(options, newConnection);
  } else {
    this.server = net.createServer(newConnection);
  }
  this.server.log = options.log;
  this.server.ldap = {
    config: options
  };
  this.server.on('close', function () {
    self.emit('close');
  });
  this.server.on('error', function (err) {
    self.emit('error', err);
  });
}