/*
* This code was written by:
* Joshua James Drake (jdrake@pulsar.net)
*
* Published 6/9/98 @ 12:02 AM
*
* The following lines of code are, in a nutshell, written to pry
* some information from a nameserver.  The information it gives
* you may or may not be useful.  That is for you to decide.
*
* However, it will tell you if the server supports IQUERY and, if
* possible, what version of bind (by Paul Vixie) it is running.
*
*/

/* --- code was modified to allow root users to send queries using source
   --- port 53 in order to query behind (some) packet filters 
   ---
   --- version v1.00 torh@bogus.net 29/9/1999
       |
       +-does not seem to work with Linux (won't run)
       | fixes appreciated!
       |
       +-does not seem to work with OpenBSD (source port still random)
       | fixes appreciated!
       |
       +- COMPILE WITH gcc -DSOLARIS -lsocket -lnsl (tested on Solaris 2.6)

   --- version v1.01 torh@bogus.net 29/9/1999
       |
       +-now works with OpenBSD (tested on 2.4)

*/

/* local type includes */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <sys/errno.h>
/* network type includes */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netdb.h>

/* another addition for 2.6 / torh */

#ifdef SOLARIS
#include <errno.h>
#endif

/* bulky shit for printing dnspkts need to link dnspkt.o from dnspkt.c too */
#ifdef DEBUG
#include "dnspkt.h"
#endif

/* prototypes */
int lookup_host(struct sockaddr_in *ra, char *hn, unsigned short rp);
void probe_bind(struct sockaddr_in ra);
int talk(int sd, char *pkt, int pktl, char opc);
int make_keypkt(char *pktbuf, char opc);
void print_ver(char *host, int vul, char *buf);
void handle_alarm(int signum);

/*
* here we simply check arguments, resolve the hostname given and
* if all is well, initialize the radom seed and probe away.
*/
void
main(argc, argv)
  int argc;
  char *argv[];
{
  struct sockaddr_in ra;

  if (argc != 2)
    {
       printf("usage: %s <host>\n", argv[0]);
       return;
    }
  if (!lookup_host(&ra, argv[1], NAMESERVER_PORT))
     return;
  srand(time(NULL));
  probe_bind(ra);
}

/*
* resolve a hostname to a sockaddr_in struct.
* we first try treating it like an ip address in a.b.c.d notation
* then, if that fails, we try to resolve using DNS ways
* if all fails, we return 0. (failed)
* if we get the sockaddr_in struct all filled out, we return 1.
*/
int
lookup_host(ra, hn, rp)
  struct sockaddr_in *ra;
  char *hn;
  unsigned short rp;
{
  struct hostent *he;

  ra->sin_family = AF_INET;
  ra->sin_port = htons(rp);
  if ((ra->sin_addr.s_addr = inet_addr(hn)) != -1)
     return 1;
  if ((he = gethostbyname(hn)) != (struct hostent *)NULL)
    {
       memcpy(&ra->sin_addr.s_addr, he->h_addr, 4);
       return 1;
    }
#ifdef SOLARIS
  perror("Unable to resolve hostname");
#else
  herror("Unable to resolve hostname");
#endif
  return 0;
}

/*
* here we allocate some space for our packets and make sure it's
* "fullanull".  then we attempt to allocate and setup our socket.
* if failure occurs, we shall report error and return.
* the we attempt to reverse our address in the sockaddr_in structure
* passed as the only argument into a dns name, if that fails, we go
* with the ascii ip address representation.  then we attempt to
* communicate the remote server, if failure, we return.  after we
* have successfully got our packets back, we close the socket and
* print out a summary of what we got.
*/
void
probe_bind(ra)
  struct sockaddr_in ra;
{
  int sd;
  char iquery[512], vquery[512], rname[256];
  struct hostent *he;

  /* --- in order to manipulate source details, use another sockaddr
     --- struct (sa), which contains source port and source ip / torh */

  struct sockaddr_in sa;       
  char localhost[128+1];
  struct hostent *hp;

  HEADER *dh = (HEADER *)iquery;

  memset(vquery, 0, sizeof(vquery));
  memset(iquery, 0, sizeof(iquery));

  /* --- this portion has been modified so that root issues source_port=53
     --- in attempt to bypass packet filters / torh */

  if(getuid()) {

    /* --- running as luser, no special privs, use original code / torh */

    printf("Unable to set source port < 1024, using luser privileges (random port).\n");

    if(((sd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) || 
       (connect(sd, (struct sockaddr *)&ra, sizeof(ra)) == -1)) {
      perror("Unable to connect");
      if (sd != -1)
	close(sd);
      return;
    }

  } else {

    /* --- running as root, so we are allowed to force the source port 
       --- value / torh */

    printf("This code is executing as root, assuming source port 53.\n");

    if((sd = socket(AF_INET,SOCK_DGRAM,0)) == -1) {
      perror("Unable to create socket()");
      return;
    }

    /* --- set localhost information / torh */

    gethostname(localhost, 128);
    printf("localhost: %s\n", localhost);
    if ((hp = gethostbyname(localhost)) == NULL) {
      perror("Couldn't resolve localhost");
      return;
    }

    /* --- change source port, and bind the socket file descriptor
       --- with the new host/port pair / torh */

    sa.sin_family=AF_INET;
    bcopy(hp->h_addr,(char *)&sa.sin_addr,hp->h_length);
    sa.sin_port=htons(53);
    if(bind(sd,(struct sockaddr *)&sa,sizeof(sa)) == -1) {
      perror("Unable to bind()");
      if(sd != -1) {close(sd);}
      return;
    }
    
    if(connect(sd,(struct sockaddr *)&ra,sizeof(ra)) == -1) {
      perror("Unable to connect()");
      if(sd != -1) {close(sd);}
      return;
    }    
  } 
      
  if((he = gethostbyaddr((char *)&ra.sin_addr, sizeof(ra.sin_addr), AF_INET)) == (struct hostent *)NULL)
    sprintf(rname, "%s", inet_ntoa(ra.sin_addr));
  else
    strncpy(rname, he->h_name, sizeof(rname));

  if (!talk(sd, iquery, sizeof(iquery), IQUERY))
    return;
  if (!talk(sd, vquery, sizeof(vquery), QUERY))
    return;
  close(sd);

  /* if dh->rcode == 0, then our iquery request was answered and the remote server
     supports iquery */
  print_ver(rname, dh->rcode == 0, vquery);
}

/*
* write our packet from pkt, wait for a response and put it in pkt.
* if the alarm goes off or the read fails, we print error
* and return 0.  otherwise, our response packet is in pkt and we return 1.
*/
int
talk(sd, pkt, pktl, opc)
  int sd, pktl;
  char *pkt, opc;
{
  int pktlen;

  pktlen = make_keypkt(pkt, opc);
  if (!write(sd, pkt, pktlen))
    {
       perror("write failed");
       close(sd);
       return 0;
    }
#ifdef DEBUG
  printf("write() success\n");
#endif
  siginterrupt(SIGALRM, 1);
  signal(SIGALRM, handle_alarm);
  alarm(3);
  pktlen = read(sd, pkt, pktl);
  if (pktlen <= 0)
    {
       if (errno == EINTR)
          errno = ETIMEDOUT;
       perror("read failed");
       close(sd);
       return 0;
    }
#ifdef DEBUG
  printf("read success\n");
#endif
  alarm(0);
  return 1;
}

/*
* this forms a valid dns packet based on the op code given by opc.
* only two opcodes are supported because that's all we need to support.
* the packet ends up in pktbuf and the length of the packet is returned.
*/
int
make_keypkt(pktbuf, opc)
  char *pktbuf;
  char opc;
{
  HEADER *dnsh;
  char *ptr = pktbuf;
  int pktlen = 0;

  dnsh = (HEADER *) ptr;
  /* fill out the parts of the DNS header that aren't 0 */
  dnsh->id = htons(rand() % 65535);
  dnsh->opcode = opc;
  dnsh->rd = 1;
  dnsh->ra = 1;
  /* one answer for IQUERY, one question for QUERY */
  if (opc == IQUERY)
     dnsh->ancount = htons(1);
  else if (opc == QUERY)
     dnsh->qdcount = htons(1);
  pktlen += sizeof(HEADER);
  ptr += sizeof(HEADER);

  /* we have to make a QUERY, fill out the question section */
  if (opc == QUERY)
    {
       /* version.bind. == elite */
       char qstr[] = "\007version\004bind\000";
       int qlen = strlen(qstr) + 1;

       memcpy(ptr, qstr, qlen);
       ptr += qlen;
       pktlen += qlen;
       PUTSHORT(T_TXT, ptr);
       PUTSHORT(C_CHAOS, ptr);
       pktlen += sizeof(short) * 2;
    }
  /* add a resource record for the inverse query */
  else if (opc == IQUERY)
    {
       unsigned long addr = inet_addr("1.2.3.4");
       unsigned long ttl = 31337;
       unsigned short addrlen = 4;

       *(ptr++) = '\0';
       pktlen++;
       PUTSHORT(T_A, ptr);
       PUTSHORT(C_IN, ptr);
       PUTLONG(ttl, ptr);
       PUTSHORT(addrlen, ptr);
       PUTLONG(addr, ptr);
       pktlen += (sizeof(short) * 3) + (sizeof(long) * 2);
    }
  /* if we're debugging, show what we just made */
#ifdef DEBUG
  print_dnspkt(pktbuf, pktbuf + pktlen);
#endif
  return pktlen;
}

/*
* This function takes a DNS packet in buf, and whether or not it reponds to IQUERY in vul.
* We cast the packet and extract the response as long as there is one.
* If there isn't one, then we assume that the remote server is an old version of bind.
* this is the end of the line.
*/
void
print_ver(host, vul, buf)
  char *host, *buf;
  int vul;
{
  HEADER *dnsh = (HEADER *)buf;
  char *ptr, *verstr;
  int len;

  if (dnsh->rcode != 0)
    {
       printf("%s's named that %s iquery does not respond to version.bind.\n", host, vul ? "supports" : "errors on");
       return;
    }
  /* So we actually have a response.  Lets skip the crap, starting with the header */
  ptr = (buf + sizeof(HEADER));
  /* then the question section domain name. */
  while (*ptr != '\0')
    ptr++;
  /* then the trailing null and the type/class of the question */
  ptr += 1 + (sizeof(short) * 2);
  /* now we skip the answer section domain name. (should be the same as the question) */
  while (*ptr != '\0')
    ptr++;
  /* don't forget the trailing null, type, class, and time to live. */
  ptr += 1 + (sizeof(long) + (sizeof(short) * 2));
  /* Here we are at the resource record data length, extract it */
  GETSHORT(len, ptr);
  /* avoid the need to decompress the string (treat it as one) */
  ptr++;
  /* allocate space for and copy the version response txt */
  verstr = (char *)malloc(len);
  memset(verstr, 0, len);
  memcpy(verstr, ptr, len-1);
  /* run through the vesion string and replace non-printable and non-whitespace characters
     with a '.' */
  for (ptr = verstr; ptr - verstr != len - 1; ptr++)
     if (!isprint(*ptr) && !isspace(*ptr))
        *ptr = '.';
  /* print the version and iquery support status, woo hoo */
  printf("%s's named that %s iquery is version: %s\n", host, vul ? "supports" : "errors on", verstr);
}

/*
* handle the alarm signal by resetting the alarm timer and
* the signal handler for SIGALRM.  This stuff probably isn't needed,
* but I did it anyway.  It's good for debugging, ran into some problems with
* alarm() not doing its job.
*/
void
handle_alarm(signum)
  int signum;
{
  alarm(0);
  signal(SIGALRM, SIG_DFL);
#ifdef DEBUG
  printf("recieved alarm\n");
#endif
}


