/*
 * IRC - Internet Relay Chat, ircd/umkpasswd.c
 * Copyright (C) 2002 hikari
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 1, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: umkpasswd.c 1842 2007-11-17 13:48:15Z entrope $
*/
#include "config.h"
#include <unistd.h>
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
/* #include <assert.h> -- Now using assert in ircd_log.h */

/* ircu headers */
#include "ircd_alloc.h"
#include "ircd_log.h" /* for ircd's assert.h */
#include "ircd_string.h"
#include "umkpasswd.h"
#include "s_debug.h"
#include "ircd_md5.h"

/* crypto mech headers */
#include "ircd_crypt.h"
#include "ircd_crypt_smd5.h"
#include "ircd_crypt_native.h"
#include "ircd_crypt_plain.h"
#include "ircd_crypt_bcrypt.h"

/* bleah, evil globals */
umkpasswd_conf_t* umkpasswd_conf;
crypt_mechs_t* crypt_mechs_root;
int log_inassert = 0;
time_t CurrentTime;

void sendto_opmask_butone(struct Client *one, unsigned int mask,
			  const char *pattern, ...)
{
  /* only needed with memdebug, which also calls Debug() */
}

void copyright(void)
{
  fprintf(stderr, "umkpasswd - Copyright (c) 2002 hikari\n");
return;
}

void show_help(void)
{
#ifdef DEBUGMODE
 char *debughelp = "[-d <level>] ";
#else
 char *debughelp = "";
#endif

 copyright();
 /*fprintf(stderr, "umkpasswd [-l] [[[-a]||[-u]] <username>] [-y <class>] %s[-c <file>] -m <mech> [password]\n\n", debughelp);*/
 fprintf(stderr, "umkpasswd [-l] %s-m <mech> [password]\n\n", debughelp);
 fprintf(stderr, "  -l            List mechanisms available.\n");
#if 0
 fprintf(stderr, "  -a <user>     Add user to conf file.\n");
 fprintf(stderr, "  -u <user>     Update user's password field.\n");
 fprintf(stderr, "  -y <class>    Class to place oper in.\n");
#endif
 fprintf(stderr, "  -m <mech>     Mechanism to use [MANDATORY].\n");
#ifdef DEBUGMODE
 fprintf(stderr, "  -d <level>    Debug level to run at.\n");
#endif
/*
 fprintf(stderr, "  -c <file>     Conf file to use, default is DPATH/CPATH.\n\n");
*/
return;
}

/* our implementation of debug() */
void debug(int level, const char *form, ...)
{
va_list vl;
int err = errno;

  if (level <= (umkpasswd_conf->debuglevel))
  {
    va_start(vl, form);
    vfprintf(stderr, form, vl);
    fprintf(stderr, "\n");
    va_end(vl);
  }
  errno = err;
}

/* quick implementation of log_write() for assert() call */
void log_write(enum LogSys subsys, enum LogLevel severity,
	       unsigned int flags, const char *fmt, ...)
{
  va_list vl;
  va_start(vl, fmt);
  vfprintf(stderr, fmt, vl);
  fprintf(stderr, "\n");
  va_end(vl);
}

/** Generate random bytes from /dev/urandom
 * @param buf Buffer to fill
 * @param len Number of bytes to generate
 * @return 0 on success, -1 on failure
 */
static int get_random_bytes(unsigned char* buf, size_t len)
{
  int fd;
  ssize_t n;

  fd = open("/dev/urandom", O_RDONLY);
  if (fd < 0)
    return -1;

  n = read(fd, buf, len);
  close(fd);

  return (n == (ssize_t)len) ? 0 : -1;
}

/* secure salt generator using /dev/urandom */
char *make_salt(const char *salts)
{
char *tmp = NULL;
unsigned char rand_bytes[2];
size_t salts_len;

 salts_len = strlen(salts);

 if((tmp = calloc(3, sizeof(char))) != NULL)
 {
  /* Use /dev/urandom for cryptographically secure random salt */
  if (get_random_bytes(rand_bytes, 2) < 0) {
    /* Fallback to less secure method if /dev/urandom unavailable */
    rand_bytes[0] = (unsigned char)(time(NULL) & 0xFF);
    rand_bytes[1] = (unsigned char)((time(NULL) >> 8) & 0xFF);
  }

  tmp[0] = salts[rand_bytes[0] % salts_len];
  tmp[1] = salts[rand_bytes[1] % salts_len];

  Debug((DEBUG_DEBUG, "salts = %s", salts));
  Debug((DEBUG_DEBUG, "strlen(salts) = %d", salts_len));
 }

return tmp;
}

/* our implementation of ircd_crypt_register_mech() */
int ircd_crypt_register_mech(crypt_mech_t* mechanism)
{
crypt_mechs_t* crypt_mech;

 Debug((DEBUG_INFO, "ircd_crypt_register_mech: registering mechanism: %s", mechanism->shortname));

 /* try to allocate some memory for the new mechanism */
 if ((crypt_mech = (crypt_mechs_t*)MyMalloc(sizeof(crypt_mechs_t))) == NULL)
 {
  /* aww poot, we couldn't get any memory, scream a little then back out */
  Debug((DEBUG_MALLOC, "ircd_crypt_register_mech: could not allocate memory for %s", mechanism->shortname));
  return -1;
 }

 /* ok, we have memory, initialise it */
 memset(crypt_mech, 0, sizeof(crypt_mechs_t));

 /* assign the data */
 crypt_mech->mech = mechanism;
 crypt_mech->next = crypt_mech->prev = NULL;

 /* first of all, is there anything there already? */
 if(crypt_mechs_root->next == NULL)
 {
  /* nope, just add ourself */
  crypt_mechs_root->next = crypt_mechs_root->prev = crypt_mech;
 } else {
  /* nice and simple, put ourself at the end */
  crypt_mech->prev = crypt_mechs_root->prev;
  crypt_mech->next = NULL;
  crypt_mechs_root->prev = crypt_mech->prev->next = crypt_mech;
 }

 /* we're done */
 Debug((DEBUG_INFO, "ircd_crypt_register_mech: registered mechanism: %s, crypt_function is at 0x%X.", crypt_mech->mech->shortname, &crypt_mech->mech->crypt_function));
 Debug((DEBUG_INFO, "ircd_crypt_register_mech: %s: %s", crypt_mech->mech->shortname, crypt_mech->mech->description));

return 0;
}

void sum(char* tmp)
{
char* str;
FILE* file;
MD5_CTX context;
int len;
unsigned char buffer[1024], digest[16], vstr[32];

vstr[0] = '\0';
 str = tmp + strlen(tmp);
 while (str[-1] == '\r' || str[-1] == '\n') *--str = '\0';
 if (NULL == (file = fopen(tmp, "r")))
 {
  fprintf(stderr, "unable to open %s: %s", tmp, strerror(errno));
  exit(0);
 }
 MD5Init(&context);
 while ((fgets((char*)buffer, sizeof(buffer), file)) != NULL)
 {
  MD5Update(&context, buffer, strlen((char*)buffer));
  str = strstr((char*)buffer, "$Id: ");
  if (str != NULL)
  {
   for (str += 5; !isspace(*str); ++str) {}
   while (isspace(*++str)) {}
   for (len = 0; !isspace(str[len]); ++len) vstr[len] = str[len];
   vstr[len] = '\0';
   break;
  }
 }
 while ((len = fread (buffer, 1, sizeof(buffer), file)))
  MD5Update(&context, buffer, len);
 MD5Final(digest, &context);
 fclose(file);

 str = strrchr(tmp, '/');
 printf("    \"[ %s: ", str ? (str + 1) : tmp);
 for (len = 0; len < 16; len++)
  printf ("%02x", digest[len]);
 printf(" %s ]\",\n", vstr);
}

/* dump the loaded mechs list */
void show_mechs(void)
{
crypt_mechs_t* mechs;

 copyright();
 printf("\nAvailable mechanisms:\n");

 if(crypt_mechs_root == NULL)
  return;

 mechs = crypt_mechs_root->next;

 for(;;)
 {
  if(mechs == NULL)
   return;

  printf(" %s\t\t%s\n", mechs->mech->mechname, mechs->mech->description);

  mechs = mechs->next;
 }
}

/* load in the mech "modules" */
void load_mechs(void)
{
 /* we need these loaded by hand for now */

 ircd_register_crypt_native();
 ircd_register_crypt_smd5();
 ircd_register_crypt_plain(); /* yes I know it's slightly pointless */
 ircd_register_crypt_bcrypt();

return;
}

crypt_mechs_t* hunt_mech(const char* mechname)
{
crypt_mechs_t* mech;

 assert(NULL != mechname);

 if(crypt_mechs_root == NULL)
  return NULL;

 mech = crypt_mechs_root->next;

 for(;;)
 {
  if(mech == NULL)
   return NULL;

  if(0 == (ircd_strcmp(mech->mech->mechname, mechname)))
   return mech;

  mech = mech->next;
 }
}

char* crypt_pass(const char* pw, const char* mech)
{
crypt_mechs_t* crypt_mech;
char* salt, *untagged, *tagged;
int len;

 assert(NULL != pw);
 assert(NULL != mech);

 Debug((DEBUG_DEBUG, "pw = %s\n", pw));
 Debug((DEBUG_DEBUG, "mech = %s\n", mech));

 if (NULL == (crypt_mech = hunt_mech(mech)))
 {
  printf("Unable to find mechanism %s\n", mech);
  return NULL;
 }

 salt = make_salt(default_salts);

 untagged = (char *)CryptFunc(crypt_mech->mech)(pw, salt);
 tagged = (char *)MyMalloc(strlen(untagged)+CryptTokSize(crypt_mech->mech)+1);
 len = strlen(untagged)+1;
 memset(tagged, 0, strlen(untagged)+CryptTokSize(crypt_mech->mech)+1);
 strncpy(tagged, CryptTok(crypt_mech->mech), CryptTokSize(crypt_mech->mech));
 strncpy(tagged+CryptTokSize(crypt_mech->mech), untagged, len);

return tagged;
}

char* parse_arguments(int argc, char **argv)
{
int len = 0, c = 0;
const char* options = "a:d:lm:u:y:5";

 umkpasswd_conf = (umkpasswd_conf_t*)MyMalloc(sizeof(umkpasswd_conf_t));

 umkpasswd_conf->flags = 0;
 umkpasswd_conf->debuglevel = 0;
 umkpasswd_conf->operclass = 0;
 umkpasswd_conf->user = NULL;
 umkpasswd_conf->mech = NULL;


 len = strlen(DPATH) + strlen(CPATH) + 2;
 umkpasswd_conf->conf = (char *)MyMalloc(len*sizeof(char));
 memset(umkpasswd_conf->conf, 0, len*sizeof(char));
 ircd_strncpy(umkpasswd_conf->conf, DPATH, strlen(DPATH) + 1);
 *((umkpasswd_conf->conf) + strlen(DPATH)) = '/';
 ircd_strncpy((umkpasswd_conf->conf) + strlen(DPATH) + 1, CPATH, strlen(CPATH) + 1);

 len = 0;

 while ((EOF != (c = getopt(argc, argv, options))) && !len)
 {
  switch (c)
  {
   case '5':
   {
    char t1[1024];
    while (fgets(t1, sizeof(t1), stdin)) sum(t1);
   }
   exit(0);

   case 'y':
    umkpasswd_conf->operclass = atoi(optarg);
    if (umkpasswd_conf->operclass < 0)
     umkpasswd_conf->operclass = 0;
   break;

   case 'u':
    if(umkpasswd_conf->flags & ACT_ADDOPER)
    {
     fprintf(stderr, "-a and -u are mutually exclusive.  Use either or neither.\n");
     abort(); /* b0rk b0rk b0rk */
    }

    umkpasswd_conf->flags |= ACT_UPDOPER;
    umkpasswd_conf->user = optarg;
   break;

   case 'm':
    umkpasswd_conf->mech = optarg;
   break;

   case 'l':
    load_mechs();
    show_mechs();
    exit(0);
   break;

   case 'd':
    umkpasswd_conf->debuglevel = atoi(optarg);
    if (umkpasswd_conf->debuglevel < 0)
     umkpasswd_conf->debuglevel = 0;
   break;

   case 'c':
    umkpasswd_conf->conf = optarg;
   break;

   case 'a':
    if(umkpasswd_conf->flags & ACT_UPDOPER) 
    {
     fprintf(stderr, "-a and -u are mutually exclusive.  Use either or neither.\n");
     abort(); /* b0rk b0rk b0rk */
    }

    umkpasswd_conf->flags |= ACT_ADDOPER;
    umkpasswd_conf->user = optarg;
   break;

   default:
    /* unknown option - spit out syntax and b0rk */
    show_help();
    exit(1);
   break;
  }
 }

 Debug((DEBUG_DEBUG, "flags = %d", umkpasswd_conf->flags));
 Debug((DEBUG_DEBUG, "operclass = %d", umkpasswd_conf->operclass));
 Debug((DEBUG_DEBUG, "debug = %d", umkpasswd_conf->debuglevel));

 if (NULL != umkpasswd_conf->mech)
  Debug((DEBUG_DEBUG, "mech = %s", umkpasswd_conf->mech));
 else
  Debug((DEBUG_DEBUG, "mech is unset"));

 if (NULL != umkpasswd_conf->conf)
  Debug((DEBUG_DEBUG, "conf = %s", umkpasswd_conf->conf));
 else
  Debug((DEBUG_DEBUG, "conf is unset"));

 if (NULL != umkpasswd_conf->user)
  Debug((DEBUG_DEBUG, "user = %s", umkpasswd_conf->user));
 else
  Debug((DEBUG_DEBUG, "user is unset"));

/* anything left over should be password */
return argv[optind];
}

int main(int argc, char **argv)
{
char* pw, *crypted_pw;

 crypt_mechs_root = (crypt_mechs_t*)MyMalloc(sizeof(crypt_mechs_t));
 crypt_mechs_root->mech = NULL;
 crypt_mechs_root->next = crypt_mechs_root->prev = NULL;

 if (argc < 2)
 {
  show_help();
  exit(0);
 }

 pw = parse_arguments(argc, argv);
 load_mechs();

 if (NULL == umkpasswd_conf->mech)
 {
  fprintf(stderr, "No mechanism specified.\n");
  abort();
 }

 if (NULL == pw)
 {
  pw = getpass("Password: ");
 }
 crypted_pw = crypt_pass(pw, umkpasswd_conf->mech);

 printf("Crypted Pass: %s\n", crypted_pw);
 memset(pw, 0, strlen(pw));

return 0;
}
