/* cryptfuncs.c: -*- C -*-  Produce encrypted/decrypted versions of data. */

/* Author: Brian J. Fox (bfox@ai.mit.edu) Mon Aug  5 11:30:41 1996.

   This file is part of <Meta-HTML>(tm), a system for the rapid deployment
   of Internet and Intranet applications via the use of the Meta-HTML
   language.

   Copyright (c) 1995, 1996, Brian J. Fox (bfox@ai.mit.edu).
   Copyright (c) 1996, Universal Access Inc. (http://www.ua.com).

   Meta-HTML is free software; you can redistribute it and/or modify
   it under the terms of the UAI Free Software License as published
   by Universal Access Inc.; 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
   UAI Free Software License for more details.

   You should have received a copy of the UAI Free Software License
   along with this program; if you have not, you may obtain one by
   writing to:

   Universal Access Inc.
   129 El Paseo Court
   Santa Barbara, CA
   93101  */

#include "language.h"
#include <des.h>

static void pf_encrypt (PFunArgs);
static void pf_decrypt (PFunArgs);
static void pf_base64decode (PFunArgs);

PFunDesc cryptfunc_table[] = {
  { "ENCRYPT",		0, 0, pf_encrypt },
  { "DECRYPT",		0, 0, pf_decrypt },
  { "BASE64DECODE",	0, 0, pf_base64decode },
  { (char *)NULL,	0, 0, (PFunHandler *)NULL }
};

PACKAGE_INITIALIZER (initialize_crypt_functions)

void
initialize_crypt_functions (Package *package)
{
  register int i;
  Symbol *sym;

  for (i = 0; cryptfunc_table[i].tag != (char *)NULL; i++)
    {
      sym = symbol_intern_in_package (package, cryptfunc_table[i].tag);
      sym->type = symtype_FUNCTION;
      sym->values = (char **)(&cryptfunc_table[i]);
    }
}

static unsigned char *
triple_des (unsigned char *srcbuff, char *key, int *len, int encrypt_p)
{
  register int i;
  int output_len = (((*len) + 7) / 8) * 8;
  unsigned char *result = (unsigned char *)xmalloc (1 + output_len);
  unsigned char *buffer = (unsigned char *)xmalloc (1 + output_len);
  int interim_result = 0;
  des_cblock deskey1, deskey2;
  des_key_schedule sched1, sched2;

  memset (result, 0, output_len);
  memcpy (buffer, srcbuff, *len);
  memset (buffer + *len, 0, output_len - *len);
  interim_result = des_string_to_2keys (key, &deskey1, &deskey2);
  interim_result = des_key_sched (&deskey1, sched1);
  interim_result = des_key_sched (&deskey2, sched2);

  for (i = 0; i < *len; i += 8)
    interim_result =
      des_3ecb_encrypt ((des_cblock *)(buffer + i),
			(des_cblock *)(result + i),
			sched1, sched2, encrypt_p);

  /* Handle last left-over byte. */
  if (*len < output_len)
    {
      register int j;
      unsigned char input[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

      for (j = 0; j < output_len - *len; j++)
	input[j] = buffer[i + j];

      interim_result =
	des_3ecb_encrypt ((des_cblock *)input, (des_cblock *)(result + i),
			  sched1, sched2, encrypt_p);
    }

  *len = output_len;
  free (buffer);
  return (result);
}

static char *
encode_data (unsigned char *data, int length)
{
  register int i;
  char *result;

  result = (char *)xmalloc (1 + (2 * length));
  for (i = 0; i < length; i++)
    sprintf (result + (2 * i), "%02x", data[i]);

  result[2 * i] = '\0';

  return (result);
}

static unsigned char *
decode_data (char *data)
{
  register int i;
  unsigned char *result;

  result = (unsigned char *)xmalloc (1 + (strlen (data) / 2));

  for (i = 0; data[i] != '\0'; i+= 2)
    {
      unsigned char value = 0;

      if (isdigit (data[i]))
	value = data[i] - '0';
      else
	value = data[i] - 'a' + 10;

      value *= 16;

      if (isdigit (data[i + 1]))
	value += data[i + 1] - '0';
      else
	value += data[i + 1] - 'a' + 10;

      result[i / 2] = value;
    }

  result[i / 2] = '\0';
  return (result);
}

/* <encrypt varname key... [algorithm=[3des]>

   Encrypts the contents of VARNAME using ALGORITHM (defaults to 3des).
   The contents of VARNAME are replaced with an encrypted version.
   KEY is the cleartext key to use for encrypting the data. */
static void
pf_encrypt (PFunArgs)
{
  char *name = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *key = mhtml_evaluate_string (get_positional_arg (vars, 1));
  Symbol *sym = symbol_lookup (name);

  if ((sym != (Symbol *)NULL) && (!empty_string_p (key)) &&
      (sym->values != (char **)NULL) &&
      (((sym->type == symtype_BINARY) &&
	(((Datablock *)(sym->values))->length != 0)) ||
       ((sym->type == symtype_STRING) &&
	(sym->values_index != 0))))
    {
      unsigned char *data = (char *)NULL;
      char *final = (char *)NULL;
      int length = 0;

      if (sym->type == symtype_BINARY)
	{
	  Datablock *block = (Datablock *)sym->values;
	  data = block->data;
	  length = block->length;
	}
      else
	{
	  data = (unsigned char *)sym->values[0];
	  length = 1 + strlen (data);
	}

      /* Do the encryption. */
      {
	int result_length = length;
	unsigned char *result = triple_des (data, key, &result_length, 1);

	/* Now make the data be all ASCII. */
	final = encode_data (result, result_length);
	free (result);
      }

      /* Put the result back in the variable. */
      if (sym->type == symtype_BINARY)
	{
	  Datablock *block = (Datablock *)xmalloc (sizeof (Datablock));
	  block->length = 1 + strlen (final);
	  block->data = final;
	  datablock_free ((Datablock *)sym->values);
	  sym->values = (char **)block;
	}
      else
	{
	  free (sym->values[0]);
	  sym->values[0] = final;
	}
    }
}

/* <decrypt varname key... [algorithm=[3des]]>

   Decrypts the contents of VARNAME using ALGORITHM (defaults to 3des).
   The contents of VARNAME are replaced with a decrypted version.
   KEY is the cleartext key to use for decrypting the data. */
static void
pf_decrypt (PFunArgs)
{
  char *name = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *key = mhtml_evaluate_string (get_positional_arg (vars, 1));
  Symbol *sym = symbol_lookup (name);

  if ((sym != (Symbol *)NULL) && (!empty_string_p (key)) &&
      (sym->values != (char **)NULL) &&
      (((sym->type == symtype_BINARY) &&
	(((Datablock *)(sym->values))->length != 0)) ||
       ((sym->type == symtype_STRING) &&
	(sym->values_index != 0))))
    {
      unsigned char *data = (char *)NULL;
      unsigned char *final = (char *)NULL;
      unsigned char *result = (char *)NULL;
      int length = 0;

      if (sym->type == symtype_BINARY)
	{
	  Datablock *block = (Datablock *)sym->values;
	  data = block->data;
	  length = block->length / 2;
	}
      else
	{
	  data = (unsigned char *)sym->values[0];
	  length = strlen (data) / 2;
	}

      /* Decode the hex bits. */
      result = decode_data ((char *)data);

      /* Do the decryption. */
      final = triple_des (result, key, &length, 0);
      free (result);

      /* Put the result back in the variable. */
      if (sym->type == symtype_BINARY)
	{
	  Datablock *block = (Datablock *)xmalloc (sizeof (Datablock));
	  block->length = length;
	  block->data = final;
	  datablock_free ((Datablock *)sym->values);
	  sym->values = (char **)block;
	}
      else
	{
	  free (sym->values[0]);
	  sym->values[0] = (char *)final;
	}
    }
}

static void
pf_base64decode (PFunArgs)
{
  char *string = mhtml_evaluate_string (get_positional_arg (vars, 0));

  if (!empty_string_p (string))
    {
      int length = 0;
      char *result = mhtml_base64decode (string, &length);

#if defined (FUCK_THAT_NOISE)
      /* Manually insert the data instead of letting bprintf
	 do it for us.  This is because the data could contain
	 null characters, and then the buffer wouldn't necessarily
	 reflect the length of what was inserted. */
      if ((length + page->bindex) >= page->bsize)
	page->buffer = (char *)xrealloc
	  (page->buffer, (page->bsize += (length + 100)));

      memmove (page->buffer + start + length, page->buffer + start,
	       (page->bindex + 1) - start);

      memcpy (page->buffer + start, result, length);
      page->bindex += length;
      *newstart += length;
#else
      bprintf_insert (page, start, "%s", result);
      *newstart += strlen (result);
#endif
      free (result);
    }

  if (string) free (string);
}
