// Helper program to generate a new secret for use in two-factor
// authentication.

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "gauth_config.h"
#include "gauth.h"
#include "dmalloc.h"

#include "lib.h"
#include "base32.h"
#include "hmac.h"
#include "sha1.h"

#define SECRET_BITS               128         // Must be divisible by eight
#define VERIFICATION_CODE_MODULUS (1000*1000) // Six digits
#define BITS_PER_BASE32_CHAR      5           // Base32 expands space by 8/5

//#define FALSE 0
//#define TRUE (!FALSE)
static enum { QR_UNSET=0, QR_NONE, QR_ANSI, QR_UTF8 } qr_mode = QR_UNSET;
int compute_code(const uint8_t *secret, int secretLen, unsigned long value) ;

static int generateCode(const char *key, unsigned long tm) {
  uint8_t challenge[8];
  int i;
  int secretLen;
  uint8_t hash[SHA1_DIGEST_LENGTH];
  int offset;
  uint8_t secret[100];
  unsigned int truncatedHash = 0;
  memset(hash,0,SHA1_DIGEST_LENGTH);
  for (i = 8; i--; tm >>= 8) {
    challenge[i] = (uint8_t) tm;
  }

  // Estimated number of bytes needed to represent the decoded secret. Because
  // of white-space and separators, this is an upper bound of the real number,
  // which we later get as a return-value from base32_decode()
  secretLen = ((int)strlen(key) + 7)/8*BITS_PER_BASE32_CHAR;

  // Sanity check, that our secret will fixed into a reasonably-sized static
  // array.
  if (secretLen <= 0 || secretLen > 100) {
    return -1;
  }

  // Decode secret from Base32 to a binary representation, and check that we
  // have at least one byte's worth of secret data.
  if ((secretLen = base32_decode((const uint8_t *)key, secret, secretLen))<1) {
    return -1;
  }

  // Compute the HMAC_SHA1 of the secret and the challenge.
  hmac_sha1(secret, secretLen, challenge, 8, hash, SHA1_DIGEST_LENGTH);
 
  // Pick the offset where to sample our hash value for the actual verification
  // code.
  offset = hash[SHA1_DIGEST_LENGTH - 1] & 0xF;

  // Compute the truncated hash in a byte-order independent loop.
  for (i = 0; i < 4; ++i) {
    truncatedHash <<= 8;
    truncatedHash  |= hash[offset + i];
  }

  // Truncate to a smaller number of digits.
  truncatedHash &= 0x7FFFFFFF;
  truncatedHash %= VERIFICATION_CODE_MODULUS;

  return truncatedHash;
}

static char *ga_urlEncode(const char *s) {
  const size_t size = 3 * strlen(s) + 1;
  char *ret;
  char* newret;
  char *d;
  if (size > 10000) {
    // Anything "too big" is too suspect to let through.
    fprintf(stderr, "Error: Generated URL would be unreasonably large.\n");
    exit(99);
  }
  ret = malloc((int)size);
  d = ret;
  do {
    switch (*s) {
    case '%':
    case '&':
    case '?':
    case '=':
    encode:
      lprintf(d,(int)( size-(d-ret)), "%%%02X", (unsigned char)*s);
      d += 3;
      break;
    default:
      if ((*s && *s <= ' ') || *s >= '\x7F') {
        goto encode;
      }
      *d++ = *s;
      break;
    }
  } while (*s++);
  newret = realloc(ret, (int)strlen(ret) + 1);
  if (newret) {
    ret = newret;
  }
  return ret;
}

static char *ga_getURL(const char *secret, const char *label,
                          char **encoderURL, const int use_totp, const char *issuer) {
  char *encodedLabel = ga_urlEncode(label);
  char *url=malloc(1000);
  const char totp = use_totp ? 't' : 'h';
  if (lprintf(url,999, "otpauth://%cotp/%s?secret=%s", totp, encodedLabel, secret) < 0) {
    fprintf(stderr, "String allocation failed, probably running out of memory.\n");
    _exit(1);
  }

  if (issuer != NULL && strlen(issuer) > 0) {
    // Append to URL &issuer=<issuer>
    char *encodedIssuer = ga_urlEncode(issuer);
    char *newUrl=malloc(1000);
    if (lprintf(newUrl, 999,"%s&issuer=%s", url, encodedIssuer) < 0) {
      fprintf(stderr, "String allocation failed, probably running out of memory.\n");
      _exit(1);
    }
    free(encodedIssuer);
    free(url);
    url = newUrl;
  }

  if (encoderURL) {
    // Show a QR code.
    const char *encoder = "https://www.google.com/chart?chs=200x200&"
                          "chld=M|0&cht=qr&chl=";
    char *encodedURL = ga_urlEncode(url);

    *encoderURL = strcat(strcpy(malloc((int)(strlen(encoder) +
                                       strlen(encodedURL) + 1)),
                                encoder), encodedURL);
    free(encodedURL);
  }
  free(encodedLabel);
  return url;
}




// Display to the user what they need to provision their app.
static void displayEnrollInfo(const char *secret, const char *label,
                              const int use_totp, const char *issuer) {
  char *encoderURL;
  char *url;
  if (qr_mode == QR_NONE) return;
  url = ga_getURL(secret, label, &encoderURL, use_totp, issuer);
  //printf("%s\n", encoderURL);
  free(url);
  free(encoderURL);
}



enum { ASK_MODE, HOTP_MODE, TOTP_MODE } mode = ASK_MODE;
int gauth_check(char *encoded, char *codex)
{
	uint8_t secret[2000];
	int secretLen;
	unsigned int code = atol(codex);
	int window = 8; // chrisp increased from 4 so it gives a reasonable chance of working.  
	int skew = 0;
	int i;
	time_t tm = time(NULL);

	secretLen = base32_decode(encoded,secret,sizeof(secret));
	for (i = -((window-1)/2); i <= window/2; ++i) {
		const unsigned int hash = compute_code(secret, secretLen, (int)(tm/30) + skew + i);
		if (hash == (unsigned int)code) {
			return TRUE;
		}
	}	
	return FALSE;
}
char *gauth_url(char *secret, char *label, char *url)
{	
	char tmp[BFSZ];
	lprintf(url,BFSZ-1, "otpauth://totp/%s?secret=%s", lib_url_encode(tmp,label), secret);
	return url;
}

#define SECRETLEN (SECRET_BITS/8)
int gauth_create(char *encoded)
{
  uint8_t buf[SECRETLEN];
  char secret[2000];
  int force = 0, quiet = 0;
  int r_limit = 0, r_time = 0;
  char *secret_fn = NULL;
  char *label = NULL;
  char *issuer = NULL;
  int window_size = 0;
  int emergency_codes = 0;
  int use_totp;
  int mode	  ;
  int i;
  srand((int)time(NULL));
  for (i=0;i<SECRETLEN;i++) {
	  buf[i] = myrand();
  }


  mode = TOTP_MODE;
  qr_mode = QR_ANSI;
  window_size = 17;
  label = "test@xyz.com";
  issuer = "travel";
  base32_encode(buf, SECRETLEN, (uint8_t *)secret, sizeof(secret));
  use_totp = mode;

  displayEnrollInfo(secret, label, use_totp, issuer);
	ncpy(encoded,secret,200);
  return TRUE;
}
int compute_code(const uint8_t *secret, int secretLen, unsigned long value) 
{
  int i;
  uint8_t val[8];
  uint8_t hash[SHA1_DIGEST_LENGTH];
  int offset=0;
  unsigned int truncatedHash = 0;
  memset(val, 0, sizeof(val));
  for (i = 8; i--; value >>= 8) {
    val[i] = (uint8_t) value;
  }
  memset(hash,0,sizeof(hash));
  hmac_sha1(secret, secretLen, val, 8, hash, SHA1_DIGEST_LENGTH);
  offset = hash[SHA1_DIGEST_LENGTH - 1] & 0xF;
  for (i = 0; i < 4; ++i) {
    truncatedHash <<= 8;
    truncatedHash  |= hash[offset + i];
  }
  memset(hash, 0, sizeof(hash));
  truncatedHash &= 0x7FFFFFFF;
  truncatedHash %= 1000000;
  return truncatedHash;
}
