pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file info_slave.c
 *
 * \brief The information slave process.
 *
 * When running PennMUSH under Unix, a second process (info_slave) is
 * started and the server farms out DNS and ident lookups to the
 * info_slave, and reads responses from the info_slave asynchronously. 
 * Communication between server and slave is by means of a local socket.
 *
 */
#include "copyrite.h"
#include "config.h"

#ifdef WIN32
#error "info_slave is not currently supported on Windows"
#endif

#include <stdio.h>
#include <stdlib.h>
#ifdef I_SYS_TYPES
#include <sys/types.h>
#endif
#ifdef I_SYS_SOCKET
#include <sys/socket.h>
#endif
#ifdef I_NETINET_IN
#include <netinet/in.h>
#endif
#include <netdb.h>
#include <ctype.h>
#include <string.h>
#ifdef I_UNISTD
#include <unistd.h>
#endif
#include <sys/uio.h>

#include "conf.h"
#include "externs.h"
#include "ident.h"
#include "mysocket.h"
#include "confmagic.h"

/* Duplicate these, rather than trying to include strutil.o... */
/** Arguments for functions that call APPEND_TO_BUF */
#define APPEND_ARGS int len, blen, clen
/** Add string c to buffer buff of max length mlen */
#define APPEND_TO_BUF(mlen) \
  /* Trivial cases */  \
  if (c[0] == '\0') \
    return 0; \
  /* The array is at least two characters long here */ \
  if (c[1] == '\0') \
    return safe_chr(c[0], buff, bp); \
  len = strlen(c); \
  blen = *bp - buff; \
  if (blen > (mlen)) \
    return len; \
  if ((len + blen) <= (mlen)) \
    clen = len; \
  else \
    clen = (mlen) - blen; \
  memcpy(*bp, c, clen); \
  *bp += clen; \
  return len - clen

#ifdef SAFE_CHR_FUNCTION
int
safe_chr(char c, char *buf, char **bufp)
{
  /* adds a character to a string, being careful not to overflow buffer */

  if ((*bufp - buf >= BUFFER_LEN - 1))
    return 1;

  *(*bufp)++ = c;
  return 0;
}
#endif

int
safe_str(const char *c, char *buff, char **bp)
{
  /* copies a string into a buffer, making sure there's no overflow. */
  APPEND_ARGS;

  if (!c || !*c)
    return 0;

  APPEND_TO_BUF(BUFFER_LEN);
}

#undef APPEND_ARGS
#undef APPEND_TO_BUF


int
main(int argc, char *argv[])
{
  int mush;
  int port;
  int fd;
  union sockaddr_u local, remote;
  static char buf[BUFFER_LEN];	/* overkill */
  char *bp;
  int len, size;
  IDENT *ident_result;
  char host[NI_MAXHOST];
  char lport[NI_MAXSERV];
  int use_ident, use_dns, timeout;
  socklen_t llen, rlen;
  struct iovec dat[3];

  if (argc < 2) {
    fprintf(stderr, "info_slave needs a port number!\n");
    return EXIT_FAILURE;
  }
  port = atoi(argv[1]);
  use_ident = 1;
  if (argc >= 3) {
    /* The second argument is -1 if we don't want ident used.
     * Anything else is the timeout. Default is 5 seconds.
     */
    use_ident = atoi(argv[2]);
  } else
    use_ident = 5;

  if (argc >= 4) {
    /* The third argument is 1 to do DNS lookups, 0 to not. */
    use_dns = atoi(argv[3]);
  } else
    use_dns = 1;

#ifdef HAS_SOCKETPAIR
  mush = port;			/* We inherit open file descriptions and sockets from parent */
#else
  mush = make_socket_conn("127.0.0.1", NULL, 0, port, NULL);
  if (mush == -1) {		/* Couldn't connect */
    fprintf(stderr, "Couldn't connect to mush!\n");
    return EXIT_FAILURE;
  }
#endif
  /* yes, we are _blocking_ */

  for (;;) {
    /* grab a request */
    /* First, the address size. */

    len = read(mush, &rlen, sizeof rlen);
    if (len < (int) sizeof rlen) {
      perror("info_slave reading remote size (Did the mush crash?)");
      return EXIT_FAILURE;
    }
    /* Now the first address and len of the second. */
    dat[0].iov_base = (char *) &remote.data;
    dat[0].iov_len = rlen;
    dat[1].iov_base = (char *) &llen;
    dat[1].iov_len = sizeof llen;
    size = rlen + sizeof llen;
    len = readv(mush, dat, 2);
    if (len < size) {
      perror("info_slave reading remote sockaddr and local size");
      return EXIT_FAILURE;
    }

    /* Now the second address and fd. */
    dat[0].iov_base = (char *) &local.data;
    dat[0].iov_len = llen;
    dat[1].iov_base = (char *) &fd;
    dat[1].iov_len = sizeof fd;
    size = llen + sizeof fd;
    len = readv(mush, dat, 2);
    if (len < size) {
      perror("info_slave reading local sockaddr and fd");
      return EXIT_FAILURE;
    }

    if (!fd)
      /* MUSH aborted query part way through or only wrote a partial
       * packet */
      continue;

    bp = buf;
    if (getnameinfo(&remote.addr, rlen, host, sizeof host, NULL, 0,
		    NI_NUMERICHOST | NI_NUMERICSERV) != 0)
      safe_str("An error occured", buf, &bp);
    else
      safe_str(host, buf, &bp);
    safe_chr('^', buf, &bp);
    if (getnameinfo(&local.addr, llen, NULL, 0, lport, sizeof lport,
		    NI_NUMERICHOST | NI_NUMERICSERV) != 0)
      safe_str("An error occured", buf, &bp);
    else
      safe_str(lport, buf, &bp);
    safe_chr('^', buf, &bp);

    if (use_ident > 0) {
      timeout = use_ident;
      ident_result =
	ident_query(&local.addr, llen, &remote.addr, rlen, &timeout);
      if (ident_result && ident_result->identifier) {
	safe_str(ident_result->identifier, buf, &bp);
	safe_chr('@', buf, &bp);
      }
      if (ident_result)
	ident_free(ident_result);
    }
    if (use_dns) {
      if (getnameinfo(&remote.addr, rlen, host, sizeof host, NULL, 0,
		      NI_NUMERICSERV) != 0) {
	safe_str("An error occured", buf, &bp);
      } else {
	safe_str(host, buf, &bp);
      }
    } else
      safe_str(host, buf, &bp);
    *bp = '\0';
    size = strlen(buf);
    dat[0].iov_base = (char *) &fd;
    dat[0].iov_len = sizeof fd;
    dat[1].iov_base = (char *) &size;
    dat[1].iov_len = sizeof size;
    dat[2].iov_base = buf;
    dat[2].iov_len = size;
    len = writev(mush, dat, 3);
    size = dat[0].iov_len + dat[1].iov_len + dat[2].iov_len;
    if (len < size) {
      perror("info_slave write packet");
      return EXIT_FAILURE;
    }
  }
  return EXIT_SUCCESS;
}