#include <nasd/nasd_options.h>
#include <nasd/nasd_getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <nasd/nasd_pdrive.h>
#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_pdrive_client_kpdev.h>
#include <nasd/nasd_security.h>
#include <nasd/nasd_varargs.h>
#include <nasd/nasd_remote_types.h>
#include <nasd/nasd_remote_types_marshall.h>


extern char nasd_premote_plaintext[];
extern char nasd_premote_ciphertext[];

static nasd_error_string_t thread_unsafe_error_buffer;
#define NASD_ASSERT_NASD_OK(res) if(res) {exit_bad("Failure in %s (%s:%d): nasd_error = %s (0x%x)\n",__FUNCTION__,__FILE__,__LINE__,nasd_error_string(res),(res));}
#define NASD_ASSERT_RPC_OK(DR_P,RES) if(RES) {exit_bad("RPC Failure in %s (%s:%d): rpc_error = %s (0x%x)\n",__FUNCTION__,__FILE__,__LINE__,nasd_cl_error_string((DR_P)->h,RES,thread_unsafe_error_buffer));}

void exit_bad(const char *fmt, ...);
void verbose(const char *fmt, ...);

char *progname;

int                        Verbose=1;


int                        binding_type;
int                        binding_args_len;
void                      *binding_args;
char                      *binding_port = NASD_PDRIVE_PORT;
nasd_drive_param_kpdev_t   kpdev_args;
int                        nondefault_binding = 0;

nasd_error_string_t error_text;

typedef struct super_handle_s {
  nasd_drive_handle_t           h;
  char                          *name;
  nasd_partnum_t                partnum;
  nasd_identifier_t             nasdid;
  char                          *master_password;
  nasd_cookie_t                 cookie;
  nasd_sec_keyring_t            keyring;
  nasd_security_param_t         sec_param;
  nasd_uint16                   protection;
} super_handle_t;


void
usage()
{
  int i;
  fprintf(stderr, "USAGE: %s [options] servername partnum nasdid master_password\n", progname);
  fprintf(stderr, "     : use nasdid 0 to have %s create the nasd object on its own\n",progname);
  fprintf(stderr, "Options:\n");
  fprintf(stderr, "  -c create object\n");
  fprintf(stderr, "  -p print output\n");
  fprintf(stderr, "  -k use kernel device\n");
  fprintf(stderr, "  -l use colocated drive\n");
  fprintf(stderr, "  -M use message queues\n");
  fprintf(stderr, "  -s sec_level\n");
  fprintf(stderr, "  where sec_level is one of\n");
  for(i = 0; i <= NASD_MAX_SECURITY_LEVEL; i++) {
    fprintf(stderr, "     %d %s\n", i, nasd_sec_level_string(i));
  }
#if NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE
  fprintf(stderr, "  -T use DCE-TCP\n");
#endif /* NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE */
  fprintf(stderr, "  -v verbose\n");
  fflush(stderr);
  exit(1);
}

nasd_len_t
do_write(super_handle_t *dr_p,nasd_byte_t *buf, nasd_offset_t offset, nasd_len_t len)
{
  nasd_p_smpl_op_dr_args_t      args;
  nasd_p_fastwrite_dr_res_t     res;
  nasd_rpc_status_t             rpc_status;

  memset(&args,0,sizeof(args));
  
  args.in_identifier = dr_p->nasdid;
  args.in_offset = offset;
  args.in_len= len;
  args.in_partnum = dr_p->partnum;

  nasd_cl_p_write_simple_dr(dr_p->h,dr_p->cookie.key,&dr_p->sec_param, &dr_p->cookie.capability,
                            &args,buf,&res,&rpc_status);
  NASD_ASSERT_RPC_OK(dr_p,rpc_status);
  NASD_ASSERT_NASD_OK(res.nasd_status);
  NASD_ASSERT(res.out_datalen == len); /* XXX maybe don't want the assert here if we return length for caller to check */
  return res.out_datalen;
}

nasd_len_t
do_read(super_handle_t *dr_p,nasd_byte_t *buf, nasd_offset_t offset, nasd_len_t len)
{
  nasd_p_smpl_op_dr_args_t      args;
  nasd_p_fastread_dr_res_t      res;
  nasd_rpc_status_t             rpc_status;

  memset(&args,0,sizeof(args));
  
  args.in_identifier = dr_p->nasdid;
  args.in_offset = offset;
  args.in_len= len;
  args.in_partnum = dr_p->partnum;

  nasd_cl_p_read_simple_dr(dr_p->h,dr_p->cookie.key,&dr_p->sec_param, &dr_p->cookie.capability,
                            &args,buf,&res,&rpc_status);
  NASD_ASSERT_RPC_OK(dr_p,rpc_status);
  NASD_ASSERT_NASD_OK(res.nasd_status);
  return res.out_datalen;
}



nasd_identifier_t
do_create(super_handle_t *dr_p)
{
  nasd_p_create_dr_args_t       args;
  nasd_p_create_dr_res_t         res;
  nasd_attribute_t              *ap;
  nasd_rpc_status_t             rpc_status;

  dr_p->sec_param.type = NASD_BLACK_KEY;
  dr_p->sec_param.partnum = dr_p->partnum;
  dr_p->sec_param.actual_protection = dr_p->protection;

  bzero(&args,sizeof(nasd_p_create_dr_args_t));
  args.in_partnum = dr_p->partnum;
  
  ap = &args.in_attribute;
  ap->object_len = 3000;
  /* Testing standard AV */ 
  
  args.in_attribute.av=7;
  args.in_fieldmask = (NASD_ATTR_OBJECT_LEN | NASD_ATTR_AV);
  nasd_cl_p_create_dr(dr_p->h, dr_p->cookie.key, &dr_p->sec_param, NULL, &args, &res, &rpc_status); 
  NASD_ASSERT_RPC_OK(dr_p,rpc_status);
  NASD_ASSERT_NASD_OK(res.nasd_status);
  
  dr_p->nasdid = res.out_identifier;
  verbose("New object identifier 0x%" NASD_ID_FMT "\n", dr_p->nasdid);
  return dr_p->nasdid; 
}

nasd_status_t
do_attach(super_handle_t *dr_p,char *name, void *buf, nasd_len_t len)
{
  nasd_security_param_t            sec_param;
  nasd_p_remote_attach_dr_args_t   args;
  nasd_p_remote_attach_dr_res_t    res;
  nasd_rpc_status_t                rpc_status;
  
  

  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = dr_p->partnum;
  sec_param.actual_protection = dr_p->protection;

  bzero(&args,sizeof(args));

  args.in_partnum = dr_p->partnum;
  args.in_identifier = dr_p->nasdid;
  strncpy(args.in_name,name,NASD_REMOTE_FUNCTION_NAME_SIZE);
  args.in_args_len = len;
  
  nasd_cl_p_remote_attach_dr(dr_p->h,
                             dr_p->cookie.key,
                             &dr_p->sec_param,
                             &dr_p->cookie.capability,
                             &args,
                             buf, 
                             &res,
                             &rpc_status);
  NASD_ASSERT_RPC_OK(dr_p,rpc_status);
  NASD_ASSERT_NASD_OK(res.nasd_status);
  return (res.nasd_status);
}


nasd_len_t
do_invoke(super_handle_t *dr_p,nasd_byte_t *buf, nasd_offset_t offset, nasd_len_t len)
{
  nasd_security_param_t            sec_param;
  nasd_p_smpl_op_dr_args_t         args;
  nasd_p_fastread_dr_res_t         res;
  nasd_rpc_status_t                rpc_status;

  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = dr_p->partnum;
  sec_param.actual_protection = dr_p->protection;
  
  bzero(&args,sizeof(args));
  
  args.in_partnum = dr_p->partnum;
  args.in_identifier = dr_p->nasdid;
  args.in_offset = offset;
  args.in_len = len;
  
  nasd_cl_p_remote_invoke_dr(dr_p->h,
                             dr_p->cookie.key,
                             &dr_p->sec_param,
                             &dr_p->cookie.capability,
                             &args,
                             buf, 
                             &res,
                             &rpc_status);
  NASD_ASSERT_RPC_OK(dr_p,rpc_status);
  NASD_ASSERT_NASD_OK(res.nasd_status);
  
  return res.out_datalen;
}


nasd_status_t
do_detach(super_handle_t *dr_p)
{
  nasd_security_param_t            sec_param;
  nasd_p_remote_detach_dr_args_t   args;
  nasd_p_remote_detach_dr_res_t    res;
  nasd_rpc_status_t                rpc_status;
  
  bzero(&args,sizeof(args));
  
  args.in_partnum = dr_p->partnum;
  args.in_identifier = dr_p->nasdid;
  
  nasd_cl_p_remote_detach_dr(dr_p->h,
                             dr_p->cookie.key,
                             &dr_p->sec_param,
                             &dr_p->cookie.capability,
                             &args,
                             &res,
                             &rpc_status);
  NASD_ASSERT_RPC_OK(dr_p,rpc_status);
  NASD_ASSERT_NASD_OK(res.nasd_status);
  return (res.nasd_status);
}


#define BIG_BUF (1<<17)

int
main(
  int     argc,
  char  **argv)
{
  super_handle_t        dr;
  nasd_status_t         rc;
  int                   p;
  int                   i;
  char                  *master_password;
  nasd_timespec_t       tm;
  char                  c;
  int                   sec_level = 0;
  nasd_uint16           protection;
  nasd_byte_t           buf[BIG_BUF];
  nasd_len_t            len;
  int                   nasdid_supplied=0;

  
  nasd_remote_caesar_args_t     remote_args;
  nasd_remote_caesar_args_otw_t remote_args_otw;

  progname = argv[0];

  binding_type = NASD_BIND_DEFAULT;
  binding_args = NULL;
  binding_args_len = 0;
  
  while (nasd_getopt(argc, argv, "cklpMs:Tv", &c)) {
    switch(c) {
      case 'k':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_KPDEV_DEFAULT;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'l':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_COLOCATE;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'M':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_MSGQ;
        break;
      case 's':
        if (sscanf(nasd_optarg, "%d", &sec_level) != 1) {
          usage();
        }
        break;
#if NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE
      case 'T':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_DCE_DIRECT_TCP;
        break;
#endif /* NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE */
      case 'v':
        Verbose++;
        break;
      default:
        fprintf(stderr, "Unknown option '%c'\n", nasd_optopt);
        usage();
    }
  }

  rc = nasd_cl_p_init();
  NASD_ASSERT_NASD_OK(rc);

  bzero((char *)&dr, sizeof(dr));


  if (nasd_optind >= argc)
    usage();
  dr.name = argv[nasd_optind];
  nasd_optind++;
  
  if (nasd_optind >= argc)
    usage();
  if (sscanf(argv[nasd_optind], "%d", &p) != 1)
    usage();
  nasd_optind++;
  dr.partnum = p;

  if (nasd_optind >= argc)
    usage();
  rc = nasd_str_to_nasd_id(argv[nasd_optind], &dr.nasdid);
  nasd_optind++;
  if (rc) {
    printf ("Could not make a nasdid out of %s\n",argv[nasd_optind]); /*DB*/
    usage();
  }

  if (nasd_optind >= argc)
    usage();
  dr.master_password=argv[nasd_optind];
  nasd_optind++;

  if (nasd_optind < argc)
    usage();

  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if (rc) {
    fprintf(stderr, "ERROR: invalid security level %d\n", sec_level);
    usage();
  }

  if (sec_level>0) {
    fprintf(stderr,"Remote execution is not supported with a non-zero security level.  \n");
    exit(0);
  }

  rc = nasd_bind_to_drive(dr.name, binding_port,
    binding_type, binding_args, binding_args_len, &dr.h);
  NASD_ASSERT_NASD_OK(rc);

  /*  Non-boilerplate code starts here.  Note: I've stuck in relevant
      error-checking asserts in the do_* code which is why there are few
      error checks here */

  NASD_ASSERT(BIG_BUF > strlen(nasd_premote_plaintext));
  NASD_ASSERT(BIG_BUF > strlen(nasd_premote_ciphertext));

  if (dr.nasdid) {
    nasdid_supplied = 1;
    dr.sec_param.type = NASD_BLACK_KEY;
    dr.sec_param.partnum = dr.partnum;
    dr.sec_param.actual_protection = dr.protection;
  } else {
    nasdid_supplied = 0;
    do_create(&dr);
    do_write(&dr,nasd_premote_ciphertext,0,strlen(nasd_premote_ciphertext));
  }
    
  len = do_read(&dr,buf,0,sizeof(buf));
  buf[128]=0;
  verbose("First 128 characters of data on drive are:\n%s\n",buf);
  memset(buf,0,sizeof(buf));

  remote_args.key=13;
  remote_args.marshall1000=1000;

  nasd_remote_caesar_args_t_marshall(&remote_args,remote_args_otw);
  do_attach(&dr,"caesar_test",remote_args_otw,
            sizeof(remote_args_otw));


  memset(buf,0,sizeof(buf));
  len = do_invoke(&dr,buf,0,sizeof(buf));
  if (len != strlen(nasd_premote_plaintext)) {
    exit_bad("Got back %d characters, plaintext has %d \n",len,strlen(nasd_premote_plaintext));
  }

  if (!nasdid_supplied) {
    if (rc = strncmp(buf,nasd_premote_plaintext,len)) {
      for (i=0;i<len;i++) {
        if (buf[i] != nasd_premote_plaintext[i]) {
          nasd_printf ("differ [%d] vs [%d] [%c] vs [%c] at %d (len %d)\n",
                       buf[i],nasd_premote_plaintext[i],
                       buf[i],nasd_premote_plaintext[i],
                       i,len);
        }
      }
      buf[128] = 0;
      nasd_premote_plaintext[128]=0;
      exit_bad("Drive response and plaintext differ at %d\nFirst 128 characters of response:\n%s\nFirst 128 characters of plaintext:\n%s\n",rc,buf,nasd_premote_plaintext);
    }
    buf[128]=0;
    verbose("First 128 characters of drive response are %s\n",buf);
  } else {
    nasd_printf("Drive returns the following from remote execution\n%s\n",buf); /*DB*/
  }
  
  
  
  do_detach(&dr);
  
  rc = nasd_unbind_drive(&dr.h);
  if (rc) {
    fprintf(stderr, "ERROR: got 0x%x (%s) from nasd_unbind_drive()\n",
      rc, nasd_error_string(rc));
    fflush(stderr);
    exit(2);
  }

  nasd_cl_p_shutdown();

  exit(0);
}


void
exit_bad(const char *fmt, ...)
{
    int errno_save;
    va_list ap;
    static int here_already = 0;

    /* eliminate recursive calls.  XXX not signal/thread safe.  */
    if(here_already) {
	return;
    }
    here_already = 1;

    errno_save = errno;

    NASD_ASSERT(fmt);

    NASD_VARARG_START(ap, fmt);
    vfprintf(stderr, fmt, ap);
    NASD_VARARG_END(ap);

    fprintf(stderr, "\n");

    if(errno_save) {
        fprintf(stderr, "system error: %s\n", strerror(errno_save));
    }

    abort();
    exit(EXIT_FAILURE);
}


void
verbose(const char *fmt, ...)
{
  va_list ap;
  
  
  NASD_ASSERT(fmt);

  NASD_VARARG_START(ap, fmt);
  if (Verbose) {
    vfprintf(stderr, fmt, ap);
  }
  NASD_VARARG_END(ap);

}






/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */



#if 0

  /* generate capability */
  nasd_sec_password_to_keys(dr.master_password, dr.partnum, &dr.keyring);
  nasd_drive_handle_get_time(dr.h, &tm);
  tm.ts_sec += (60*60);
  nasd_sec_build_capability(dr.partnum, args.dh.nasdid,
                            NASD_ACCESS_RIGHTS_GETATTR, 0, tm.ts_sec,
                            protection, NASD_BLACK_CAPABILITY, 0, 0, 7,
                            dh.keys.black_key, &cookie);
#endif /* 0 */
