/* mkpasswd.c - Windows frontend to crypt()
**
** W. Campbell <wcampbel@botbay.net>
**
** Please see the LICENSE file for copyright and licensing information.
**
** $Id: mkpasswd.c,v 1.17 2004/03/17 03:59:20 wcampbel Exp $
*/

/* XXX TODO
** 1.  Magic numbers need to GO
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#include "resource.h"
#include "local_crypt/crypt.h"

#define FLAG_MD5     0x00000001
#define FLAG_DES     0x00000002
#define FLAG_SALT    0x00000004
#define FLAG_PASS    0x00000008
#define FLAG_LENGTH  0x00000010
#define FLAG_BLOWFISH 0x00000020
#define FLAG_ROUNDS  0x00000040

HINSTANCE hMainInstance;
HWND hMainWnd;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpszArgs, int nWinMode);
BOOL CALLBACK DialogFunc(HWND hdwnd, UINT Msg, WPARAM wParam, LPARAM lParam);

/* HACK */
extern void gettimeofday(struct timeval *, void *);

static char *make_des_salt(void);
static char *make_des_salt_para(char *);
static char *make_ext_salt(int);
static char *make_ext_salt_para(int, char *);
static char *make_md5_salt(int);
static char *make_md5_salt_para(char *);
static char *make_bf_salt(int, int);
static char *make_bf_salt_para(int, char *);
static char *int_to_base64(unsigned int);
static char *generate_random_salt(char *, int);

static char saltChars[] =
       "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpszArgs,
                   int nWinMode)
{
  int nRes;
  hMainInstance=hInstance;

  srand(time(NULL));
  nRes=DialogBox(hMainInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), 0,
                 DialogFunc);
  return 0;
}

BOOL CALLBACK DialogFunc(HWND hdwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
  static int crypt_type = IDD_DES;
  static int salt_type = IDD_RANDOM;

  switch(Msg)
  {
    case WM_INITDIALOG:
    {
      hMainWnd=hdwnd;
      SetClassLong(hdwnd, GCL_HICON, (LONG)LoadIcon(hMainInstance, "mkpasswd"));
      /* These two clear all flags for the radio buttons, then set what I
      ** want for the defaults from the variable passed in.
      */
      CheckRadioButton(hdwnd, IDD_DES, IDD_RAW, crypt_type);
      CheckRadioButton(hdwnd, IDD_RANDOM, IDD_SPECSALT, salt_type);
      return TRUE;
    }

    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case ID_EXIT:
          PostQuitMessage(0);
          return FALSE;

        case ID_CRYPT:
        {
          char plaintext[17];
          char salt[101];
          char csalt[101];
          char *crpass = NULL;
          char *cresptr;
          struct timeval start_t;
          struct timeval stop_t;
          UINT uRes;

          uRes = GetDlgItemText(hdwnd, ID_PASS, plaintext, 16);
          if (uRes == 0)
          {
            SetDlgItemText(hdwnd, ID_STATUS, "Please enter a password.");
            return TRUE;
          }

          if (salt_type == IDD_SPECSALT)
          {
            uRes = GetDlgItemText(hdwnd, ID_SALT, salt, 100);
            if (uRes == 0)
            {
              SetDlgItemText(hdwnd, ID_STATUS, "Please enter a salt.");
              return TRUE;
            }
            switch (crypt_type)
            {
              case IDD_DES:
                  cresptr = make_des_salt_para(salt);
                  if (cresptr == NULL)
                    return TRUE;
                  strcpy(csalt, cresptr);
                  break;
              case IDD_EDES:
                  {
                    char rounds_p[5];
                    int rounds;
                    uRes = GetDlgItemText(hdwnd, ID_ROUNDS, rounds_p, 4);
                    if (uRes == 0)
                    {
                      SetDlgItemText(hdwnd, ID_STATUS, "Please enter a rounds value.");
                      return TRUE;
                    }
                    rounds = atoi(rounds_p);
                    cresptr = make_ext_salt_para(rounds, salt);
                    if (cresptr == NULL)
                      return TRUE;
                    strcpy(csalt, cresptr);
                    break;
                  }
              case IDD_MD5:
                  cresptr = make_md5_salt_para(salt);
                  if (cresptr == NULL)
                    return TRUE;
                  strcpy(csalt, cresptr);
                  break;
              case IDD_BF:
                  {
                    char rounds_p[5];
                    int rounds;
                    uRes = GetDlgItemText(hdwnd, ID_ROUNDS, rounds_p, 4);
                    if (uRes == 0)
                    {
                      SetDlgItemText(hdwnd, ID_STATUS, "Please enter a rounds value.");
                      return TRUE;
                    }
                    rounds = atoi(rounds_p);
                    cresptr = make_bf_salt_para(rounds, salt);
                    if (cresptr == NULL)
                      return TRUE;
                    strcpy(csalt, cresptr);
                    break;
                  }
              case IDD_RAW:
                  {
                    int length;
                    length = strlen(salt);
                    if (length > 100)
                    {
                      SetDlgItemText(hdwnd, ID_STATUS, "Raw salt too long.");
                      return TRUE;
                    }
                    strcpy(csalt, salt);
                    break;
                  }
            }
          }
          else
          {
            switch (crypt_type)
            {
              case IDD_DES:
                  /* make_des_salt() can't return NULL */
                  strcpy(csalt, make_des_salt());
                  break;
              case IDD_EDES:
                  {
                    char rounds_p[5];
                    int rounds;
                    uRes = GetDlgItemText(hdwnd, ID_ROUNDS, rounds_p, 4);
                    if (uRes == 0)
                    {
                      SetDlgItemText(hdwnd, ID_STATUS, "Please enter a rounds value.");
                      return TRUE;
                    }
                    rounds = atoi(rounds_p);
                    cresptr = make_ext_salt(rounds);
                    if (cresptr == NULL)
                      return TRUE;
                    strcpy(csalt, cresptr);
                    break;
                  }
              case IDD_MD5:
                  {
                    char length_p[5];
                    int length;
                    uRes = GetDlgItemText(hdwnd, ID_LENGTH, length_p, 4);
                    if (uRes == 0)
                    {
                      SetDlgItemText(hdwnd, ID_STATUS, "Please enter a length value.");
                      return TRUE;
                    }
                    length = atoi(length_p);
                    cresptr = make_md5_salt(length);
                    if (cresptr == NULL)
                      return TRUE;
                    strcpy(csalt, cresptr);
                    break;
                  }
              case IDD_BF:
                  {
                    char length_p[5];
                    char rounds_p[5];
                    int length, rounds;
                    uRes = GetDlgItemText(hdwnd, ID_LENGTH, length_p, 4);
                    if (uRes == 0)
                    {
                      SetDlgItemText(hdwnd, ID_STATUS, "Please enter a length value.");
                      return TRUE;
                    }
                    uRes = GetDlgItemText(hdwnd, ID_ROUNDS, rounds_p, 4);
                    if (uRes == 0)
                    {
                      SetDlgItemText(hdwnd, ID_STATUS, "Please enter a rounds value.");
                      return TRUE;
                    }
                    length = atoi(length_p);
                    rounds = atoi(rounds_p);
                    cresptr = make_bf_salt(rounds, length);
                    if (cresptr == NULL)
                      return TRUE;
                    strcpy(csalt, cresptr);
                    break;
                  }
              case IDD_RAW:
                  SetDlgItemText(hdwnd, ID_STATUS, "Raw crypt usage must include a salt.");
                  return TRUE;
                  /* NOT REACHED */
                  break;
            }
          }

          gettimeofday(&start_t, NULL);
          crpass = local_crypt(plaintext, csalt);
          gettimeofday(&stop_t, NULL);
          SetDlgItemText(hdwnd, ID_HASH, crpass);
          {
            char tmpbuf[101];
            long seconds, useconds;

            seconds = stop_t.tv_sec - start_t.tv_sec;
            /* YUK, manual time handling */
            if (stop_t.tv_usec >= start_t.tv_usec)
            {
              useconds = stop_t.tv_usec - start_t.tv_usec;
            }
            else
            {
              if (seconds > 0)
              {
                seconds--;
                useconds = (1000000L + stop_t.tv_usec) - start_t.tv_usec;
              }
              else /* We've got PROBLEMS */
              {
                seconds = 0;
                useconds = 0;
              }
            }
            useconds += (seconds * 1000000L);

            snprintf(tmpbuf, 101, "Hash Successful: %0.3f seconds",
                     ((double)useconds / 1000000L));
            SetDlgItemText(hdwnd, ID_STATUS, tmpbuf);
          }

          return TRUE;
        }

        case IDD_DES:
            {
              HWND hwndc;
              hwndc = GetDlgItem(hdwnd, ID_ROUNDS);
              EnableWindow(hwndc, FALSE);
              hwndc = GetDlgItem(hdwnd, ID_LENGTH);
              EnableWindow(hwndc, FALSE);
              crypt_type = LOWORD(wParam);
              CheckRadioButton(hdwnd, IDD_DES, IDD_RAW, crypt_type);
              return TRUE;
              break; /* NOT REACHED */
            }
        case IDD_EDES:
            {
              HWND hwndc;
              char toset[] = "25";
              hwndc = GetDlgItem(hdwnd, ID_ROUNDS);
              EnableWindow(hwndc, TRUE);
              SetDlgItemText(hdwnd, ID_ROUNDS, toset);
              hwndc = GetDlgItem(hdwnd, ID_LENGTH);
              EnableWindow(hwndc, FALSE);
              crypt_type = LOWORD(wParam);
              CheckRadioButton(hdwnd, IDD_DES, IDD_RAW, crypt_type);
              return TRUE;
              break; /* NOT REACHED */
            }
        case IDD_MD5:
            {
              HWND hwndc;
              char toset[] = "8";
              hwndc = GetDlgItem(hdwnd, ID_ROUNDS);
              EnableWindow(hwndc, FALSE);
              hwndc = GetDlgItem(hdwnd, ID_LENGTH);
              EnableWindow(hwndc, TRUE);
              SetDlgItemText(hdwnd, ID_LENGTH, toset);
              crypt_type = LOWORD(wParam);
              CheckRadioButton(hdwnd, IDD_DES, IDD_RAW, crypt_type);
              return TRUE;
              break; /* NOT REACHED */
            }
        case IDD_BF:
            {
              HWND hwndc;
              char tosetr[] = "4";
              char tosetl[] = "22";
              hwndc = GetDlgItem(hdwnd, ID_ROUNDS);
              EnableWindow(hwndc, TRUE);
              SetDlgItemText(hdwnd, ID_ROUNDS, tosetr);
              hwndc = GetDlgItem(hdwnd, ID_LENGTH);
              EnableWindow(hwndc, TRUE);
              SetDlgItemText(hdwnd, ID_LENGTH, tosetl);
              crypt_type = LOWORD(wParam);
              CheckRadioButton(hdwnd, IDD_DES, IDD_RAW, crypt_type);
              return TRUE;
              break; /* NOT REACHED */
            }
        case IDD_RAW:
            {
              HWND hwndc;
              hwndc = GetDlgItem(hdwnd, ID_ROUNDS);
              EnableWindow(hwndc, FALSE);
              hwndc = GetDlgItem(hdwnd, ID_LENGTH);
              EnableWindow(hwndc, FALSE);
              crypt_type = LOWORD(wParam);
              CheckRadioButton(hdwnd, IDD_DES, IDD_RAW, crypt_type);
              return TRUE;
              break; /* NOT REACHED */
            }

        case IDD_RANDOM:
        case IDD_SPECSALT: /* All fall through */
          salt_type = LOWORD(wParam);
          CheckRadioButton(hdwnd, IDD_RANDOM, IDD_SPECSALT, salt_type);
          return TRUE;

        default:
          return FALSE;
      }

    case WM_CLOSE: /* X titlebar button or Close */
      PostQuitMessage(0);
      return FALSE;
  }
  return FALSE;
}

static char *make_des_salt(void)
{
  static char salt[3];
  generate_random_salt(salt, 2);
  salt[2] = '\0';
  return salt;
}

static char *make_des_salt_para(char *saltpara)
{
  static char salt[3];
  if (saltpara && strlen(saltpara) == 2)
  {
    strcpy(salt, saltpara);
    return salt;
  }
  SetDlgItemText(hMainWnd, ID_STATUS, "Salt wrong size, must be 2 characters.");
  return NULL;
}

static char *int_to_base64(unsigned int value)
{
  static char buf[5];
  int i;
  unsigned int tmp;

  for (i = 0; i < 4; i++)
  {
    tmp = value & 0x3F;
    buf[i] = saltChars[tmp];
    value >>= 6;
  }

  buf[i] = '\0';
  return buf;
}

static char *make_ext_salt(int rounds)
{
  static char salt[10];

  sprintf(salt, "_%s", int_to_base64((unsigned int)rounds));
  generate_random_salt(&salt[5], 4);
  salt[9] = '\0';
  return salt;
}

static char *make_ext_salt_para(int rounds, char *saltpara)
{
  static char salt[10];

  if (saltpara && strlen(saltpara) == 4)
  {
    sprintf(salt, "_%s%s", int_to_base64((unsigned int)rounds), saltpara);
    return salt;
  }
  SetDlgItemText(hMainWnd, ID_STATUS, "Salt wrong size, must be 4 characters.");
  return NULL;
}

static char *make_md5_salt_para(char *saltpara)
{
  static char salt[21];
  if (saltpara && (strlen(saltpara) <= 16))
  {
    /* sprintf used because of portability requirements, the length
    ** is checked above, so it should not be too much of a concern
    */
    sprintf(salt, "$1$%s$", saltpara);
    return salt;
  }
  SetDlgItemText(hMainWnd, ID_STATUS, "Salt too long, max length 8 characters.");
  return NULL;
}

static char *make_md5_salt(int length)
{
  static char salt[21];
  if (length > 16)
  {
    SetDlgItemText(hMainWnd, ID_STATUS, "Length too large, max 8.");
    return NULL;
  }
  salt[0] = '$';
  salt[1] = '1';
  salt[2] = '$';
  generate_random_salt(&salt[3], length);
  salt[length+3] = '$';
  salt[length+4] = '\0';
  return salt;
}

static char *make_bf_salt_para(int rounds, char *saltpara)
{
  static char salt[31];
  char tbuf[3];
  if (saltpara && (strlen(saltpara) <= 22))
  {
    /* sprintf used because of portability requirements, the length
    ** is checked above, so it should not be too much of a concern
    */
    sprintf(tbuf, "%02d", rounds);
    sprintf(salt, "$2a$%s$%s$", tbuf, saltpara);
    return salt;
  }
  SetDlgItemText(hMainWnd, ID_STATUS, "Salt too long, max length 22 characters.");
  return NULL;
}

static char *make_bf_salt(int rounds, int length)
{
  static char salt[31];
  char tbuf[3];
  if (length > 22)
  {
    SetDlgItemText(hMainWnd, ID_STATUS, "Length too large, max 22.");
    return NULL;
  }
  sprintf(tbuf, "%02d", rounds);
  sprintf(salt, "$2a$%s$", tbuf);
  generate_random_salt(&salt[7], length);
  salt[length+7] = '$';
  salt[length+8] = '\0';
  return salt;
}

static char *generate_random_salt(char *salt, int length)
{
  int i;
  for(i = 0; i < length; i++)
  {
    /* XXX better W32 API call? */
    salt[i] = saltChars[rand() % 64];
  }
  return(salt);
}

