/*
 * 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,v 1.8 2005/05/11 04:15:04 entrope Exp $
*/
#include "config.h"
#include <unistd.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.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"

/* 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);
}

/* quick and dirty salt generator */
char *make_salt(const char *salts)
{
char *tmp = NULL;
long int n = 0;

 /* try and get around them running this time after time in quick succession */
 sleep(1);
 srandom((unsigned int)time(NULL));

 if((tmp = calloc(3, sizeof(char))) != NULL)
 {
  /* can't optimize this much more than just doing it twice */
  n = ((float)(strlen(salts))*random()/(RAND_MAX+1.0));
  memcpy(tmp, (salts+n), 1);
  sleep(2);
  n = ((float)(strlen(salts))*random()/(RAND_MAX+1.0));
  memcpy((tmp+1), (salts+n), 1);

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

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;
}

char *basename_into(char *tmp, char *target)
{
  unsigned int len, ii;

  len = strlen(tmp);
  for (ii = len; ii > 0; )
    if (tmp[--ii] == '/')
      break;
  if (ii < len - 1)
    return tmp + ii + (tmp[ii] == '/');
  else if (tmp[ii] != '/')
    return tmp;
  else if (ii == 0)
    return tmp;
  else
  {
    while (ii > 0)
      if (tmp[--ii] == '/')
         break;
    if (tmp[ii] == '/')
        ii++;
    for (len = 0; tmp[ii] != '/'; )
      target[len++] = tmp[ii++];
    target[len] = '\0';
    return target;
  }
}

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

 if (NULL == (file = fopen(tmp, "r")))
  exit(0);
 MD5Init(&context);
 while ((len = fread (buffer, 1, 1024, file)))
  MD5Update(&context, buffer, len);
 MD5Final(digest, &context);
 fclose(file);

 printf("%s: ", basename_into(tmp, (char*)buffer));
 for (len = 0; len < 16; len++)
  printf ("%02x", digest[len]);
 printf("\n");
 exit(0);
}

/* 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 */

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;

 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);
 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, strlen(untagged));

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));
 *((umkpasswd_conf->conf) + strlen(DPATH)) = '/';
 ircd_strncpy((umkpasswd_conf->conf) + strlen(DPATH) + 1, CPATH, strlen(CPATH));

 len = 0;

 while ((EOF != (c = getopt(argc, argv, options))) && !len)
 {
  switch (c)
  {
   case '5':
    sum(optarg);
   break;

   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();
    abort();
   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;
}
