/*
 * Copyright (c) 2011-2014 Yubico AB
 * All rights reserved.
 *
 * Author : Fredrik Thulin <fredrik@yubico.com>
 *
 * Based on ykchalresp.c from yubikey-personalization.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <limits.h>

#include <ykpers.h>

#include "util.h"

#define ACTION_ADD_HMAC_CHALRESP	"add_hmac_chalresp"
#define ACTION_MAX_LEN			1024

const char *usage =
  "Usage: ykpamcfg [options]\n"
  "\n"
  "Options :\n"
  "\n"
  "\t-1           Send challenge to slot 1. This is the default.\n"
  "\t-2           Send challenge to slot 2.\n"
  "\t-A action    What to do.\n"
  "\t-p path      Specify an output path for the challenge file.\n"
  "\t-i iters     Number of iterations to use for PBKDF2 (defaults to 10000)\n"
  "\n"
  "\t-v           Increase verbosity\n"
  "\t-V           Show version and exit\n"
  "\t-h           Show help (this text) and exit\n"
  "\n"
  "Actions :\n"
  "\n"
  "\t" ACTION_ADD_HMAC_CHALRESP "\tAdds a challenge-response state file for a connected YubiKey (default)\n"
  "\n"
  "\n"
  ;
const char *optstring = "12A:p:i:vVh";

static void
report_yk_error(void)
{
  if (ykp_errno)
    fprintf(stderr, "YubiKey personalization error: %s\n",
	    ykp_strerror(ykp_errno));
  if (yk_errno) {
    if (yk_errno == YK_EUSBERR) {
      fprintf(stderr, "USB error: %s\n",
	      yk_usb_strerror());
    } else {
      fprintf(stderr, "YubiKey core error: %s\n",
	      yk_strerror(yk_errno));
    }
  }
}

static int
parse_args(int argc, char **argv,
	   int *slot, bool *verbose,
	   char **action, char **output_dir,
     unsigned int *iterations)
{
  int c;
  int i;

  while((c = getopt(argc, argv, optstring)) != -1) {
    switch (c) {
    case '1':
      *slot = 1;
      break;
    case '2':
      *slot = 2;
      break;
    case 'A':
      i = snprintf(*action, ACTION_MAX_LEN, "%s", optarg);
      if (i < 0 || i >= ACTION_MAX_LEN) {
        fprintf(stderr, "action too long: %s\n", optarg);
        exit(1);
      }
      break;
    case 'p':
      *output_dir = optarg;
      break;
    case 'i':
      {
	char *endptr;
	*iterations = strtoul(optarg, &endptr, 10);
	if(*endptr != '\0') {
	  fprintf(stderr, "iterations must be numeric, %s isn't.\n", optarg);
	  exit(1);
	}
      }
      break;
    case 'v':
      *verbose = true;
      break;
    case 'V':
      printf("%s\n", VERSION);
      exit(0);
    case 'h':
    default:
      fputs(usage, stderr);
      exit(0);
    }
  }

  return 1;
}

static int
do_add_hmac_chalresp(YK_KEY *yk, uint8_t slot, bool verbose, char *output_dir, unsigned int iterations, int *exit_code)
{
  char buf[CR_RESPONSE_SIZE + 16];
  CR_STATE state;
  int ret = 0;
  unsigned int response_len;
  char *fn;
  struct passwd *p;
  int fd;
  FILE *f = NULL;
  struct stat st;

  state.iterations = iterations;
  state.slot = slot;
  *exit_code = 1;

  p = getpwuid (getuid ());
  
  if (! p) {
    fprintf (stderr, "Who am I???");
    goto out;
  }

 /*
  * Create default output directory for the user
  */
  
  if (!output_dir){
      char fullpath[PATH_MAX];
      int i = snprintf(fullpath, PATH_MAX, "%s/.yubico", p->pw_dir);

      if (i < 0 || i >= PATH_MAX) {
        fprintf(stderr, "Failed to construct fullpath: %s\n", p->pw_dir);
	goto out;
      }

      //check if directory exists     
      if (stat(fullpath,&st)!=0 ){     
	if(mkdir(fullpath, S_IRWXU)==-1){
	  fprintf(stderr, "Failed to create directory '%s': %s\n",
		  fullpath, strerror(errno));
	}
	if(verbose){
	  printf("Directory %s created successfully.\n", fullpath);
	}
      }
      else{
	if(!S_ISDIR(st.st_mode)){
	  fprintf(stderr, "Destination %s already exists and is not a directory.\n",
		  fullpath);
	  goto out;
	  }
      }
  }

  if (! get_user_challenge_file(yk, output_dir, p, &fn, stdout)) {
    fprintf (stderr, "Failed to get chalresp state filename\n");
    goto out;
  }

  if (stat(fn, &st) == 0) {
    fprintf(stderr, "File %s already exists, refusing to overwrite.\n", fn);
    goto out;
  }

  if (generate_random(state.challenge, CR_CHALLENGE_SIZE)) {
    fprintf (stderr, "Failed to get %i bytes of random data\n", CR_CHALLENGE_SIZE);
    goto out;
  }
  state.challenge_len = CR_CHALLENGE_SIZE;

  if (! challenge_response(yk, state.slot, state.challenge, CR_CHALLENGE_SIZE,
			   true, true, verbose,
			   buf, sizeof(buf), &response_len))
    goto out;

  /* Make sure we get different responses for different challenges
     There is a firmware bug in YubiKey 2.2 that makes it issue same
     response for all challenges unless HMAC_LT64 is set. */
  {
    char buf2[CR_RESPONSE_SIZE + 16];
    char challenge[CR_CHALLENGE_SIZE];

    if (generate_random(challenge, CR_CHALLENGE_SIZE)) {
      fprintf (stderr, "Failed to get %i bytes of random data\n", CR_CHALLENGE_SIZE);
      goto out;
    }
    if (! challenge_response(yk, state.slot, challenge, CR_CHALLENGE_SIZE,
          true, true, verbose,
          buf2, sizeof(buf2), &response_len))
      goto out;

    if (memcmp(buf, buf2, response_len) == 0) {
      fprintf (stderr, "Failed: YubiKey is outputting the same response for different challenges."
          "Make sure you configure the key with the option HMAC_LT64.\n");
      goto out;
    }
  }

  if (response_len > sizeof (state.response)) {
    fprintf (stderr, "Got too long response ??? (%u/%lu)", response_len, (unsigned long) sizeof(state.response));
    goto out;
  }
  memcpy (state.response, buf, response_len);
  state.response_len = response_len;

  umask(077);

  fd = open (fn, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY, S_IRUSR | S_IWUSR);
  if (fd < 0) {
    fprintf (stderr, "Failed to open '%s' for writing: %s\n", fn, strerror (errno));
    goto out;
  }
  f = fdopen (fd, "w");
  if (! f) {
    fprintf (stderr, "fdopen: %s\n", strerror (errno));
    close(fd);
    goto out;
  }

  if (! write_chalresp_state (f, &state))
    goto out;

  printf ("Stored initial challenge and expected response in '%s'.\n", fn);

  *exit_code = 0;
  ret = 1;

 out:
  if (f)
    fclose (f);

  return ret;
}

int
main(int argc, char **argv)
{
  YK_KEY *yk = NULL;
  bool error = true;
  int exit_code = 0;

  /* Options */
  bool verbose = false;
  char action[ACTION_MAX_LEN];
  char *ptr = action;
  char *output_dir = NULL;
  int slot = 1;
  unsigned int iterations = CR_DEFAULT_ITERATIONS;

  ykp_errno = 0;
  yk_errno = 0;

  strncpy(action, ACTION_ADD_HMAC_CHALRESP, ACTION_MAX_LEN);

  if (! parse_args(argc, argv,
		   &slot, &verbose,
		   &ptr, &output_dir,
       &iterations))
    goto err;

  exit_code = 1;

  if (! strncmp(action, ACTION_ADD_HMAC_CHALRESP, ACTION_MAX_LEN)) {
    /*
     * Set up challenge-response login authentication
     */
    if (! init_yubikey (&yk))
      goto err;

    if (! check_firmware_version(yk, verbose, false, stdout))
      goto err;    

    if (! do_add_hmac_chalresp (yk, slot, verbose, output_dir, iterations, &exit_code))
      goto err;
  } else {
    fprintf (stderr, "Unknown action '%s'\n", action);
    goto err;
  }

  exit_code = 0;
  error = false;

 err:
  if (error || exit_code != 0) {
    report_yk_error ();
  }

  if (yk && !yk_close_key (yk)) {
    report_yk_error ();
    exit_code = 2;
  }

  if (!yk_release ()) {
    report_yk_error ();
    exit_code = 2;
  }

  exit (exit_code);
}
