/*
 * NcpQuery - Netware query tool
 * 
 * This tool assumes the Netware target server has Netware's IP "replacement"
 * for IPX is loaded. The NCP part listens on tcp port 524, and is in essence
 * an IPX wrapper. Most of the regular NCP calls useful for IPX will work here
 * with a little bit of trial and error. For example, the SPX sequence numbers
 * are still required (and still work the exact same way), in spite of the fact
 * IP sequence numbers are being used. I guess this is supposed to make NCP 
 * applications backwardly compatible, but in reality as long as you can 
 * hijack the IP session then it should be theoretically possible.
 *
 * Yet I digress. This is a handy tool for making queries against a Netware 
 * server from a Unix box. One of the neat things about this tool is that 
 * pretty much everything that the server sees (static and dynamic) will be 
 * seen by this tool. If the target server has IPX loaded, all non-IP Netware 
 * servers in the tree will be enumerated.
 *
 * Of course, if you take away Browse from [Public] then the tool will not be
 * able to get near as much info. Have fun, and let me know how you use the
 * tool.
 *
 * Compile instructions:
 *   gcc -o ncp ncp.c
 *
 * - Simple Nomad - thegnome@razor.bindview.com, thegnome@nmrc.org
 *
 * 13Oct - Cleaned up the comments.
 * 09Oct - Added version number.
 * 09Oct - Minor bug fix. If connect to port but unable to communicate, was
 *         segfaulting trying to copy non-results from last network write.
 */

/* includes */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

/* defines*/
#define NCP_PORT 524
#define VERSION "1.2"

/* shorthand */
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned long uint32;

/* structs */
typedef struct NCP_Req
{
  uint32 sig;
  uint32 len;
  uint32 ver;
  uint32 reqbuf;
  uint16 reqtype;
  uint8  seq;
  uint8  connlow;
  uint8  task;
  uint8  connhi;
} NCP_req;

typedef struct NCP_Rep
{
  uint32 sig;
  uint32 len;
  uint16 reqtype;  
  uint8  seq;
  uint8  connlow;
  uint8  task;
  uint8  connhi;
  uint8  data[400];   
} NCP_rep;

/* globals */
char buffer_out[256];
char buffer_in[512];

/* subroutines */

/* clean up buffers */
void clean_buf(void)
{
  bzero(buffer_out, 256);
  bzero(buffer_in, 512);  
}

/* This is so unbelievably lame. The old IPX sequence numbering still exists in
   Netware's IP packets. The IPX sequence numbering scheme is still needed, so
   this massive routine handles the numbering. */
uint8 increment_seq (uint8 seq)
{
  if(seq == 0xff) return(0x00); /* if 255, reset to zero */
  else return(seq + 1);         /* otherwise, increment by one */
}

void usage(char *prog)
{
  fprintf(stderr,"USAGE: ");
  fprintf(stderr,"%s [opts] -n netware_server_IP_addr\n\n",prog);
  fprintf(stderr,"  opts are h v i s g u a\n");
  fprintf(stderr,"    -h this help screen\n");
  fprintf(stderr,"    -v verbose\n");
  fprintf(stderr,"    -i get server info\n");
  fprintf(stderr,"    -s enumerate server object types\n");
  fprintf(stderr,"    -g enumerate group object types\n");
  fprintf(stderr,"    -u enumerate user object types (default)\n");
  fprintf(stderr,"    -a enumerate all object types\n");
  fprintf(stderr,"\n");
}

/* main */
int main(int argc, char **argv)
{
  int i=0, len, complete=0;
  struct sockaddr_in address;
  struct NCP_Req ncpreq;
  struct NCP_Rep ncprep;
  int result = 1;
  char ch;
  char tmp1[48];
  uint8 seq, connection;
  int sockfd;
  int verbose = 0, printed = 0;
  int req_hdr_sz = 22;
  char *prog;
  char *target;
  int server = 0, user = 0, group = 0, info = 0, all = 0;

  prog = argv[0];

  fprintf(stdout,"NcpQuery - Netware query tool, v%s\n",VERSION);
  fprintf(stdout,"Comments/bugs - thegnome@razor.bindview.com\n");
  fprintf(stdout,"http://razor.bindview.com\n\n");

  while ((ch = getopt(argc, argv, "iasughvn:")) != EOF) 
    switch(ch)
    {
      case 'i':
        info = 1;
        break;
      case 'a':
        all = 1;
        break;
      case 's':
        server = 1;
        break;
      case 'u':
        user = 1;
        break;
      case 'n':
        result = 0;
        target = optarg;
        break;
      case 'g':
        group = 1;
        break;
      case 'h':
        usage(prog);
        exit(0);
      case 'v':
        verbose = 1;
        break;
      default:
        usage(prog);
        exit(-1);
    }

  if(result)
  {
    fprintf(stderr,"You must specify the IP address of the Netware target\n");
    usage(prog);
    exit(-1);
  }

  if((!info) && (!all) && (!user) && (!group) && (!server)) user = 1;

  if(all)
  {
    user = 1;
    group = 1;
    server = 1;
  }

  clean_buf();

  /* -----------------------------------*/
  /* Step one - connect to the NCP port */
  /* -----------------------------------*/
  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = inet_addr(target);
  address.sin_port = htons(NCP_PORT);
  result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
  if (result == -1)
  {
    fprintf(stderr,"Unable to connect to server %s\n",target);
    exit(1);
  }

  /* ----------------------------------- */
  /* Step two - Build the NCP connection */
  /* ----------------------------------- */
  /* What we do here is build up our NCP connection. The main thing we have to do is
     set up our connection number in the Netware server connection table. */
  ncpreq.sig = 0x54646d44;    /* the sig identifies the packet type */
  ncpreq.len = 0x17000000;
  ncpreq.ver = 0x01000000;
  ncpreq.reqbuf = 0x00000000;
  ncpreq.reqtype = 0x1111;
  ncpreq.seq = 0x00;
  ncpreq.connlow = 0xff;
  ncpreq.task = 0x01;
  ncpreq.connhi = 0xff;
  len = 23;

  memcpy(buffer_out,&ncpreq,len); 
  if (verbose) fprintf(stdout,"Attaching to server.....");
  write(sockfd, &buffer_out,len);
  result = read(sockfd, buffer_in, 16);
  if (result == -1)
  {
    fprintf(stdout,"error\n");
    fprintf(stderr,"Error communicating with server %s during attach\n",target);
    exit(1);
  }
  memcpy(&ncprep,buffer_in,result);

  if(ncprep.reqtype != 0x3333)
  {
    close(sockfd);
    fprintf(stderr,"\nError connecting to server (NCP Reply = %x)\n",ncprep.reqtype);
    exit(-1);
  }
  ncpreq.connlow = ncprep.connlow;
  ncpreq.connhi = ncprep.connhi;
  ncpreq.seq = increment_seq(ncprep.seq);

  if (verbose) 
  {
    connection = ncprep.connlow + ncprep.connhi;
    fprintf(stdout,"Attached\nConnection number     : %d\n", connection);
  }

  clean_buf();

  /* --------------------------------- */
  /* Step three - Get file server info */
  /* --------------------------------- */
  /* now we retrieve basic info about the server, such as the name and a few other
     interesting tidbits */
  if (info)
  {
    ncpreq.sig = 0x54646d44;
    ncpreq.len = 0x1a000000;
    ncpreq.reqbuf = 0x80000000;
    ncpreq.reqtype = 0x2222;
    memcpy(buffer_out,&ncpreq,sizeof(ncpreq)); 
    buffer_out[req_hdr_sz] = 0x17;
    buffer_out[req_hdr_sz+1] = 0x00;
    buffer_out[req_hdr_sz+2] = 0x01;
    buffer_out[req_hdr_sz+3] = 0x11;

    write(sockfd, &buffer_out, 26);

    result = read(sockfd, buffer_in, 256);
    if (result == -1)
    {
      fprintf(stderr,"Error communicating with server %s during info query\n",target);
      exit(1);
    }

    memcpy(&ncprep,buffer_in,result);

    if(ncprep.reqtype != 0x3333)
    {
      close(sockfd);
      fprintf(stderr,"Error getting data from server (NCP Reply = %x)\n",ncprep.reqtype);
      exit(-1);
    }

    ncpreq.seq = increment_seq(ncprep.seq);

    if(ncprep.data[0] == 0x00)
    {
      fprintf(stdout,"Server Name           : ");
      for (i=0; i<48; i++)
        if (ncprep.data[2+i] != 0x00) fprintf(stdout,"%c",ncprep.data[2+i]);
      fprintf(stdout,"\n");
      fprintf(stdout,"Version               : %d\n",ncprep.data[50]);
      fprintf(stdout,"Sub-version           : %d\n",ncprep.data[51]);
      fprintf(stdout,"Max Connections       : %d\n",ncprep.data[52]+ncprep.data[53]);
      fprintf(stdout,"Conn(s) in use        : %d\n",ncprep.data[54]+ncprep.data[55]);
      fprintf(stdout,"Revision              : %d\n",ncprep.data[58]);
      fprintf(stdout,"SFT level             : %d\n",ncprep.data[59]);
      fprintf(stdout,"TTS level             : %d\n",ncprep.data[60]);
      fprintf(stdout,"Max conn ever used    : %d\n",ncprep.data[61]+ncprep.data[62]);
      fprintf(stdout,"Account version       : %d\n",ncprep.data[63]);
      fprintf(stdout,"VAP version           : %d\n",ncprep.data[64]);
      fprintf(stdout,"Queue version         : %d\n",ncprep.data[65]);
      fprintf(stdout,"Print version         : %d\n",ncprep.data[66]);
      fprintf(stdout,"Virtual console vers  : %d\n",ncprep.data[67]);
      fprintf(stdout,"Restriction level     : %d\n",ncprep.data[68]);
      fprintf(stdout,"Internet bridge       : %d\n",ncprep.data[69]);
    }
    else
    {
      close(sockfd);
      fprintf(stdout,"Error getting data from server (cc = %d)\n",ncprep.data[0]);
      exit(-1);
    }

    clean_buf();
  }

  /* -------------------------------------- */
  /* Step four - Ping NDS NCP for tree name */
  /* -------------------------------------- */
  /* this call doesn't require authentication, so we use it to get the NDS
     tree name */

  ncpreq.sig = 0x54646d44;
  ncpreq.len = 0x1b000000;
  ncpreq.reqbuf = 0x28000000;
  ncpreq.reqtype = 0x2222;

  memcpy(buffer_out,&ncpreq,sizeof(ncpreq)); 
  buffer_out[req_hdr_sz] = 0x68;
  buffer_out[req_hdr_sz+1] = 0x01;
  buffer_out[req_hdr_sz+2] = 0x00;
  buffer_out[req_hdr_sz+3] = 0x00;

  write(sockfd, &buffer_out, 27);

  result = read(sockfd, buffer_in, 256);
  if (result == -1)
  {
    fprintf(stderr,"Error communicating with server %s during NDS Ping\n",target);
    exit(1);
  }

  memcpy(&ncprep,buffer_in,result);

  if(ncprep.reqtype != 0x3333)
  {
    close(sockfd);
    fprintf(stderr,"Error getting data from server (NCP Reply = %x)\n",ncprep.reqtype);
    exit(-1);
  }

  ncpreq.seq = increment_seq(ncprep.seq);

  if(ncprep.data[0] == 0x00)
  {
    fprintf(stdout,"NDS Tree Name         : ");
    for (i=0; i<32; i++)
      if (ncprep.data[7+i] != 0x00) fprintf(stdout,"%c",ncprep.data[7+i]);
    printf("\n");
  }
  else
  {
    close(sockfd);
    fprintf(stdout,"Error getting data from server (cc = %d)\n",ncprep.data[0]);
    exit(-1);
  }

  clean_buf();

  /* --------------------------------- */
  /* Step five - Enumerate NDS objects */
  /* --------------------------------- */

  if((all) || (group) || (user) || (server))
  {
    ncpreq.sig = 0x54646d44;
    ncpreq.len = 0x22000000;
    ncpreq.reqbuf = 0x39000000;
    ncpreq.reqtype = 0x2222;
    ncpreq.task = 0x04;

    fprintf(stdout,"\nObject ID Type         Name\n");
    fprintf(stdout,"--------- ------------ ------------------------------------------------\n");

    for (i=0;i<4;i++) ncprep.data[2+i] = 0xff;

    while(1)
    {
      memcpy(buffer_out,&ncpreq,sizeof(ncpreq)); 
      buffer_out[req_hdr_sz] = 0x17;
      buffer_out[req_hdr_sz+1] = 0x00;
      buffer_out[req_hdr_sz+2] = 0x09;
      buffer_out[req_hdr_sz+3] = 0x37;
      for (i=0;i<4;i++)
        buffer_out[req_hdr_sz+4+i] = ncprep.data[2+i];
      buffer_out[req_hdr_sz+8] = 0xff;
      buffer_out[req_hdr_sz+9] = 0xff;
      buffer_out[req_hdr_sz+10] = 0x01;
      buffer_out[req_hdr_sz+11] = 0x2a;

      write(sockfd, &buffer_out, 34);
      result = read(sockfd, buffer_in, 256);
      if (result == -1)
      {
        fprintf(stderr,"Error communicating with server %s during object query\n",target);
        exit(1);
      }
      memcpy(&ncprep,buffer_in,result);

      if(ncprep.reqtype != 0x3333)
      {
        close(sockfd);
        fprintf(stderr,"Error getting data from server (NCP Reply = %x)\n",ncprep.reqtype);
        exit(-1);
      }

      if(ncprep.data[0] != 0x00) break;

      ncpreq.seq = increment_seq(ncprep.seq);

      if((ncprep.data[6] == 0x00) && (ncprep.data[7] == 0x04) && (server))
      {
        for (i=0; i<4; i++) fprintf(stdout,"%02x",ncprep.data[2+i]);
        fprintf(stdout,"  File Server  ");
        for (i=0; i<48; i++)
          if (isprint(ncprep.data[8+i])) fprintf(stdout,"%c",ncprep.data[8+i]);
        fprintf(stdout,"\n");
      }
      else if((ncprep.data[6] == 0x00) && (ncprep.data[7] == 0x02) && (group))
      {
        for (i=0; i<4; i++) fprintf(stdout,"%02x",ncprep.data[2+i]);
        fprintf(stdout,"  Group        ");
        for (i=0; i<48; i++)
          if (isprint(ncprep.data[8+i])) fprintf(stdout,"%c",ncprep.data[8+i]);
        fprintf(stdout,"\n");
      }
      else if((ncprep.data[6] == 0x00) && (ncprep.data[7] == 0x01) && (user))
      {
        for (i=0; i<4; i++) fprintf(stdout,"%02x",ncprep.data[2+i]);
        fprintf(stdout,"  User         ");
        for (i=0; i<48; i++)
          if (isprint(ncprep.data[8+i])) fprintf(stdout,"%c",ncprep.data[8+i]);
        fprintf(stdout,"\n");
      }
      else if(all)
      {
        for (i=0; i<4; i++) fprintf(stdout,"%02x",ncprep.data[2+i]);
        if((ncprep.data[6] == 0x00) && (ncprep.data[7] == 0x03))
          fprintf(stdout,"  Print Queue  ");
        else if((ncprep.data[6] == 0x00) && (ncprep.data[7] == 0x07))
          fprintf(stdout,"  Print Server ");
        else if((ncprep.data[6] == 0x00) && (ncprep.data[7] == 0x47))
          fprintf(stdout,"  Adv Prnt Svr ");
        else if((ncprep.data[6] == 0x00) && (ncprep.data[7] == 0x77))
          fprintf(stdout,"  Unkn. Novell ");
        else if((ncprep.data[6] == 0x00) && (ncprep.data[7] == 0x4b))
          fprintf(stdout,"  Btrieve      ");
        else if((ncprep.data[6] == 0x01) && (ncprep.data[7] == 0x07))
          fprintf(stdout,"  RSPX         ");
        else if((ncprep.data[6] == 0x02) && (ncprep.data[7] == 0x6b))
          fprintf(stdout,"  NDS Tree     ");
        else if((ncprep.data[6] == 0x06) && (ncprep.data[7] == 0x40))
          fprintf(stdout,"  NT RPC Obj   ");
        else if((ncprep.data[6] == 0x06) && (ncprep.data[7] == 0x4e))
          fprintf(stdout,"  NT IIS Svr   ");
        else if((ncprep.data[6] == 0x03) && (ncprep.data[7] == 0x0c))
          fprintf(stdout,"  HP Jet Dir   ");
        else if((ncprep.data[6] == 0x02) && (ncprep.data[7] == 0x78))
          fprintf(stdout,"  Dir Server   ");
        else if((ncprep.data[6] == 0x02) && (ncprep.data[7] == 0x37))
          fprintf(stdout,"  NMS IPX      ");
        else if((ncprep.data[6] == 0x02) && (ncprep.data[7] == 0x38))
          fprintf(stdout,"  NMS IP       ");
        else if((ncprep.data[6] == 0x02) && (ncprep.data[7] == 0x3f))
          fprintf(stdout,"  SMS          ");
        else if((ncprep.data[6] == 0x82) && (ncprep.data[7] == 0x02))
          fprintf(stdout,"  NDPS Broker  ");
        else fprintf(stdout,"  Other 0x%02x%02x ",ncprep.data[6],ncprep.data[7]);
        for (i=0; i<48; i++)
          if (isprint(ncprep.data[8+i])) fprintf(stdout,"%c",ncprep.data[8+i]);
        fprintf(stdout,"\n");
      }
    
      /* cleanup and reset for next object */
      clean_buf();
    }
  }

  close(sockfd);
  exit(0);
}
