/***************************************************************************
                          posask.cpp  -  posask query tool
                             -------------------
    begin                : vr mei 23 2003
    copyright            : (C) 2003 by Meilof
    email                : meilof@users.sourceforge.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/*
 * This file has been taken, with some modifications, from the Poslib sources.
 * The Poslib version, in turn, was just an ugly hack of the Posadis 0.50.x
 * Posask. As such, you'll find some old Posadis artifacts. The code does
 * "just work"(r) though.
 */
#include <stdio.h>
#include <posadis-config.h>
#ifdef HAVE_RESOLV_H
#ifdef HAVE_NAMESER_H
#include <nameser.h>
#endif
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/nameser.h>
#include <resolv.h>
#endif

#include <poslib/poslib.h>
#include <answertostring.cpp>
#include <getdnsservers.cpp>

/*_addr server;*/
stl_slist(_addr) servers;
domainname dname = ".";
int dname_given = 0;
int qtype = 0;
int serial = 0;
int rd = 1;
int use_tcp = -1; /* default */
int timeout = 5;
int tries = 3;

int cmd_server(char *line);
int cmd_dname(char *line);
int cmd_qtype(char *line);
int cmd_serial(char *line);
int cmd_rd(char *line);
int cmd_tcp(char *line);
int cmd_timeout(char *line);
int cmd_tries(char *line);
int cmd_query(char *line);
int cmd_set(char *line);
int cmd_help(char *line);
int cmd_exit(char *line);

typedef int(*commandfn)(char *);
typedef char*(*completefn)(const char *, int);

typedef struct {
  char *command;
  char *help;
  commandfn fn;
} _command;

_command commands[] = {
  { "server", "Sets the server at run-time", cmd_server },
  { "dname", "Sets domain name to query", cmd_dname },
  { "qtype", "Sets the default QTYPE", cmd_qtype },
  { "serial", "Sets the previous serial for IXFR requests", cmd_serial },
  { "rd", "Sets whether we demand recursion", cmd_rd },
  { "use_tcp", "Sets whether we use TCP/IP (default only xfr)", cmd_tcp },
  { "timeout", "Sets the time in seconds to wait for an answer", cmd_timeout },
  { "tries", "Sets the number of tries for the query", cmd_tries },
  { "query", "Sends a query to the nameserver", cmd_query },
  { "q", "Synonym for 'query'", cmd_query },
  { "set", "Show current settings", cmd_set },
  { "help", "Print this message", cmd_help },
  { "exit", "Quits the program", cmd_exit },
};
int n_commands = sizeof(commands) / sizeof(_command);

char* settings[] = { "server", "dname", "qtype", "rd", "use_tcp", "timeout" };
int n_settings = sizeof(settings) / sizeof(char *);

int issue_command(char *name, char *arg) {
  int t;
  for (t = 0; t < n_commands; t++) {
    if (strcasecmp(name, commands[t].command) == 0) {
      return commands[t].fn(arg);
    }
  }

  printf("*** Command unknown: %s\n", name);
  return -1;
}

int cmd_server(char *line) {
  _addr server;
  try {
    txt_to_addr(&server, line);
    servers.push_front(server);
    return 1;
  } catch (PException p) {
    if (!address_lookup(&server, line, DNS_PORT)) {
      printf("*** Server %s: incorrect identifier: %s\n", line, p.message);
      return -1;
    }
    servers.push_front(server);
  }
  return 1;
}

int cmd_dname(char *line) {
  dname_given = 1;
  try {
    dname = line;
    return 1;
  } catch (PException p) {
    printf("*** Incorrect dname %s: %s!\n", line, p.message);
    return -1;
  }
}

int cmd_qtype(char *line) {
  int ret;
  try {
    if (strncasecmp(line, "ixfr", 4) == 0) {
      if (line[4] == '/') {
        ret = cmd_serial(&line[5]);
        if (ret > 0) qtype = QTYPE_IXFR;
        return ret;
      } else {
        qtype = QTYPE_IXFR;
        return 1;
      }
    } else {
      qtype = qtype_getcode(line);
    }
    return 1;
  } catch (PException p) {
    printf("*** Error in qtype %s: %s\n", line, p.message);
    return -1;
  }
}

int cmd_serial(char *line) {
  try {
    serial = txt_to_int(line);
    return 1;
  } catch (PException p) {
    printf("*** Error in serial %s: %s\n", line, p.message);
    return -1;
  }
}

int cmd_rd(char *line) {
  try {
    rd = (txt_to_bool(line)) ? 1 : 0;
    return 1;
  } catch (PException p) {
    printf("*** Error in RD value %s: %s\n", line, p.message);
    return -1;
  }
}

int cmd_tcp(char *line) {
  try {
    use_tcp = (txt_to_bool(line)) ? 1 : 0;
    return 1;
  } catch (PException p) {
    printf("*** Error in use_tcp value %s: %s\n", line, p.message);
    return -1;
  }
}

int cmd_timeout(char *line) {
  try {
    timeout = txt_to_int(line);
    if (timeout < 0) {
      timeout = 5;
      throw PException(true, "%s: cannot be negative!", line);
    }
  } catch (PException p) {
    printf("**** Error in timeout: %s\n", p.message);
    return -1;
  }
  return 1;
}

int cmd_tries(char *line) {
  try {
    tries = txt_to_int(line);
    if (tries < 0) {
      tries = 3;
      throw PException(true, "#tries %s: cannot be negative!", line);
    }
  } catch (PException p) {
    printf("**** Error in #tries: %s\n", p.message);
    return -1;
  }
  return 1;
}

int cmd_query(char *line) {
  DnsMessage *q = NULL, *a = NULL;
  int prevtype = qtype;
  char *tmp;
  char *ptr;
  postime_t endtime, begintime;
  bool do_tcp;
  pos_cliresolver resolver;

  try {
    if (line[0]) {
      ptr = &line[strlen(line) - 1];
      while (ptr > line && *ptr != ' ') ptr--;
      if (*ptr == ' ') {
        *ptr = '\0';
        if (cmd_qtype(ptr + 1) < 0) return -1;
      }
      if (cmd_dname(line) < 0) { qtype = prevtype; return -1; }
    }

    begintime = getcurtime();
    q = new DnsMessage();
    q->questions.push_front(DnsQuestion(dname, qtype, CLASS_IN));
    qtype = prevtype;
    q->ID = 3000;
    if (qtype == QTYPE_IXFR) {
      /* add a SOA record */
      tmp = (char *)malloc(22);
      memset(tmp, 0, 22);
      memcpy(tmp + 2, uint32_buff(serial), 4);
      q->authority.push_front(DnsRR(dname, DNS_TYPE_SOA, CLASS_IN, 0));
      q->authority.begin()->RDLENGTH = 22;
      q->authority.begin()->RDATA = tmp;
    }
    if (rd) q->RD = true;
    if (qtype == QTYPE_AXFR) q->RD = false;
    if (use_tcp == 1 || (use_tcp == -1 && (qtype == QTYPE_AXFR || qtype == QTYPE_IXFR))) {
      /* do a tcp query */
      do_tcp = true;
      int sockid = resolver.tcpconnect(&*servers.begin());
      resolver.tcp_timeout = timeout * 1000;
      resolver.tcpquery(q, a, sockid);
      resolver.tcpdisconnect(sockid);
    } else {
      /* do an udp query */
      free(resolver.udp_tries);
      if (tries < 1) tries = 1;
      if (tries > 10) tries = 10;
      if (timeout < 1) timeout = 1;
      resolver.udp_tries = (int *)malloc(tries * sizeof(int));
      resolver.n_udp_tries = tries;
      for (int tr = 0; tr < tries; tr++) {
        resolver.udp_tries[tr] = timeout * 1000 * (tr + 1) * 2 / tries / (tries + 1);
      }
      do_tcp = false;
      servers.reverse();
      resolver.query(q, a, servers, Q_NOTCP);
    }
    
    endtime = getcurtime();

    stl_list(stl_string) answer = answer_to_advanced_string(a,
      addrs_to_string(servers).c_str(), do_tcp, endtime.after(begintime), true);
    stl_list(stl_string)::iterator it = answer.begin();
    while (it != answer.end()) {
      printf("%s\n", it->c_str());
      it++;
    }
    
    if (q) delete q;
    if (a) delete a;
  } catch (PException p) {
    printf("*** Query failed: %s!\n", p.message);
    if (q) delete q;
    if (a) delete a;
  }

  return 1;
}

int cmd_set(char *line) {
  if (!line[0] || strcasecmp(line, "server") == 0) {
    printf("Server:      %s\n", addrs_to_string(servers).c_str());
  }
  if (!line[0] || strcasecmp(line, "dname") == 0) {
    printf("Domain name: %s\n", dname.tostring().c_str());
  }
  if (!line[0] || strcasecmp(line, "qtype") == 0)
    printf("QTYPE:       %s\n", str_qtype(qtype).c_str());
  if (!line[0] || strcasecmp(line, "serial") == 0)
    printf("IXFR serial: %d\n", serial);
  if (!line[0] || strcasecmp(line, "rd") == 0)
    printf("Recursion:   %s\n", (rd == 1) ? "true" : "false");
  if (!line[0] || strcasecmp(line, "use_tcp") == 0)
    printf("Use TCP:     %s\n", (use_tcp == 1) ? "true" : "false");
  if (!line[0] || strcasecmp(line, "timeout") == 0)
    printf("Timeout:     %d\n", timeout);
  if (!line[0] || strcasecmp(line, "tries") == 0)
    printf("Tries:       %d\n", tries);
  return 1;
}

int cmd_help(char *line) {
  int x, p = 0;

  for (x = 0; x < n_commands; x++) {
    if (!line[0] ||
      strcasecmp(line, commands[x].command) == 0) {
      printf("%-16s %s\n", commands[x].command, commands[x].help);
      p = 1;
    }
  }
  if (p == 0) {
    printf("*** %s is not a known command!\n", line);
    return -1;
  }
  return 1;
}

int cmd_exit(char *line) {
  return 0;
}

int main(int argc, char **argv) {
  int x, ret = 0;
  char *ptr;

  /* at first, read the command line */
  for (x = 1; x < argc; x++) {
    if (strcasecmp(argv[x], "--help") == 0 || strcasecmp(argv[x], "-h") == 0) {
      printf(
        "Posask - Command-line DNS querier\n"
        "Usage:\n"
        "  posask [--help|-h]\n"
        "  posask [--version|-v]\n"
        "  posask [@[server][:port]] [option=value] [qname] [qtype]\n"
        "If no server is given, the system resolver is assumed. If no qtype is given, A\n"
        "is assumed. If no qname is given, a query for {. ns} is assumed.\n"
        "\n"
        "Possible options:\n"
        "  rd=[yes|no]        Specifies whether to demand recursion. Default: yes\n"
        "  serial=n           Default serial number for IXFR requests\n"
        "  use_tcp=[yes|no]   Specifies whether or not to use TCP. Default: only xfr\n"
        "  timeout=n          Number of seconds to wait for an answer. Default: 5\n"
        "  tries=n            Number of times to retry query (in timeout). Default: 3\n"
        "\n"
        "Posask is part of the Posadis DNS Server package. It is distributed under the\n"
        "terms and conditions of the GNU General Public License. More info:\n"
        "   http://posadis.sourceforge.net/\n");
      return 1;
    }
    if (strcasecmp(argv[x], "--version") == 0 || strcasecmp(argv[x], "-v") == 0) {
      printf(
        "Posask - Command-line DNS querier\n"
        "Version ID:      $Revision: 1.14 $\n"
#ifdef __DATE
        "Compiled on:    " __DATE__
#endif
        /*"Posadis version: " VERSION "\n"*/);
      return 1;
    }
//    if (stricmp(argv[x], "--resolve") == 0 || stricmp(argv[x], "-r") == 0) {
//#ifdef HAVE_RESOLV_H
//      res_init();
//      memcpy(&server, &_res.nsaddr, sizeof(sockaddr_in));
//      continue;
//#else
//      printf("*** Posadis not built with system resolver support!\n");
//      return 1;
//#endif
//    }
    if (argv[x][0] == '-') {
      printf("*** Command-line option not supported: %s\n", argv[x]);
      return 1;
    }
    if (argv[x][0] == '@') {
      /* server */
      if (cmd_server(&argv[x][1]) < 0) return 2;
      continue;
    }
    ptr = strchr(argv[x], '=');
    if (ptr) {
      *ptr = '\0';
      ret = issue_command(argv[x], ptr + 1);
      *ptr = '=';
      if (ret < 0) return 2;
    } else {
      if (dname_given != 0) {
        /* qtype */
        if (qtype) {
          printf("*** Didn't expect another command-line argument: %s\n", argv[x]);
          return 2;
        } else {
          if (cmd_qtype(argv[x]) < 0) return 2;
        }
      } else {
        /* dname */
        if (cmd_dname(argv[x]) < 0) return 2;
      }
    }
  }
  
  /* set default */
  if (servers.size() == 0) {
    try {
      servers = get_os_dns_servers();
    } catch(PException p) {
      _addr tmp;
      txt_to_addr(&tmp, "127.0.0.1", DNS_PORT);
      servers.clear();
      servers.push_front(tmp);

      printf("*** Error: %s. Falling back to 127.0.0.1.\n", p.message);
    }
  } else servers.reverse();

  if (!dname_given) {
    cmd_dname(".");
    qtype = DNS_TYPE_NS;
  }

  if (qtype == 0) {
    if (dname >= "in-addr.arpa" || dname >= "ip6.int" || dname >= "ip6.arpa") {
      qtype = DNS_TYPE_PTR;
    } else {
      qtype = DNS_TYPE_A;
    }
  }
  
  cmd_query("");
  if (ret < 0) return 3; else return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1