#include "ruby.h"
#include "mcrypt.h"
#include "string.h"

//#define MTRACE
#ifdef MTRACE
  #include "mcheck.h"
#endif


static char ruby_mcrypt_c[] = "$Id: ruby-mcrypt.c,v 1.3 2001/06/20 12:39:55 hds Exp $";


//
// New MCrypt object.
///

static VALUE _mcrypt_new(
  VALUE class,
  VALUE algorithm, VALUE algorithm_dir,
  VALUE mode, VALUE mode_dir,
  VALUE password,
  VALUE ivec
)
{
  VALUE obj;
  MCRYPT td;
  int keysize;
  char *key;
  int ivsize;
  char *iv;
  int i;
  char *a_dir;
  char *m_dir;

  Check_Type( algorithm, T_STRING );

  if ( NIL_P( algorithm_dir ) ) {
    a_dir = NULL;
  }
  else {
    Check_Type( algorithm_dir, T_STRING );
    a_dir = STR2CSTR( algorithm_dir );
  }

  Check_Type( mode, T_STRING );

  if ( NIL_P( mode_dir ) ) {
    m_dir = NULL;
  }
  else {
    Check_Type( mode_dir, T_STRING );
    m_dir = STR2CSTR( mode_dir );
  }

  Check_Type( password, T_STRING );

  if ( !NIL_P( ivec ) ) {
    Check_Type( ivec, T_STRING );
  }

  td = mcrypt_module_open(
    STR2CSTR( algorithm ), a_dir,
    STR2CSTR( mode ), m_dir
  );

  if ( td == MCRYPT_FAILED ) {
    rb_raise( rb_eRuntimeError, "mcrypt_module_open() failed!" );
  }

  keysize = mcrypt_enc_get_key_size( td );
  key = malloc( keysize );
  memset( key, 0, keysize );
  memcpy( key, RSTRING( password )->ptr, RSTRING( password )->len );

  if ( NIL_P( ivec ) ) {
    iv = NULL;
  }
  else {
    ivsize = mcrypt_enc_get_iv_size( td );
    if ( ivsize == 0 ) {
      iv = NULL;
    }
    else {
      iv = malloc( ivsize );
      memset( iv, 0, ivsize );
      memcpy( iv, RSTRING( ivec )->ptr, RSTRING( ivec )->len );
    }
  }

  i = mcrypt_generic_init( td, key, keysize, iv );
  if ( i < 0 ) {
    rb_raise( rb_eRuntimeError, mcrypt_strerror( i ) );
  }

  obj = Data_Wrap_Struct( class, 0, free, td );

  if ( iv != NULL ) {
    free( iv );
  }
  free( key );

  return obj;
}


//
// mcrypt_generic
//

VALUE _mcrypt_generic( VALUE self, VALUE data )
{
  MCRYPT td;
  VALUE str;
  int bs;
  int blocksize;
  char *blockbuffer;
  int rc;


  Data_Get_Struct( self, MCRYPT, (void *)td ) ;

  if ( mcrypt_enc_is_block_mode( td ) == 0 ) {
    str = rb_str_dup( data );
    rc = mcrypt_generic( td, RSTRING( str )->ptr, RSTRING( str )->len );
  }
  else {
    bs = mcrypt_enc_get_block_size( td );
    blocksize = (RSTRING( data )->len / bs)* bs;
    if ( (RSTRING( data)->len % bs) != 0 ) {
      blocksize += bs;
    }
    blockbuffer = malloc( blocksize );
    memset( blockbuffer, 0, blocksize );
    memcpy( blockbuffer, RSTRING( data )->ptr, RSTRING( data )->len );
    rc = mcrypt_generic( td, blockbuffer, blocksize );
    str = rb_str_new( blockbuffer, blocksize );
    free( blockbuffer );
  }

  if ( rc != 0 ) {
    rb_raise( rb_eRuntimeError, "mcrypt_generic() failed!" );
  }

  return str;
}


//
// mdecrypt_generic
//

VALUE _mdecrypt_generic( VALUE self, VALUE data )
{
  MCRYPT td;
  VALUE str;
  int bs;
  int blocksize;
  char *blockbuffer;
  int rc;

  Data_Get_Struct( self, MCRYPT, (void *)td ) ;

  if ( mcrypt_enc_is_block_mode( td ) == 0 ) {
    str = rb_str_dup( data );
    rc = mdecrypt_generic( td, RSTRING( str )->ptr, RSTRING( str )->len );
  }
  else {
    bs = mcrypt_enc_get_block_size( td );
    blocksize = (RSTRING( data )->len / bs)* bs;
    if ( (RSTRING( data)->len % bs) != 0 ) {
      blocksize += bs;
    }
    blockbuffer = malloc( blocksize );
    memset( blockbuffer, 0, blocksize );
    memcpy( blockbuffer, RSTRING( data )->ptr, RSTRING( data )->len );
    rc = mdecrypt_generic( td, blockbuffer, blocksize );
    str = rb_str_new( blockbuffer, blocksize );
    free( blockbuffer );
  }

  if ( rc != 0 ) {
    rb_raise( rb_eRuntimeError, "mdecrypt_generic() failed!" );
  }

  return str;
}


//
// mcrypt_enc_get_algorithms_name
//

VALUE _mcrypt_enc_get_algorithms_name( VALUE self )
{
  MCRYPT td;
  char *str;
  VALUE algo_name;

  Data_Get_Struct( self, MCRYPT, (void *)td ) ;

  str = mcrypt_enc_get_algorithms_name( td );

  algo_name = rb_str_new2( str );

  mcrypt_free( str );

  return algo_name;
}


//
// mcrypt_enc_get_modes_name
//

VALUE _mcrypt_enc_get_modes_name( VALUE self )
{
  MCRYPT td;
  char *str;
  VALUE mode_name;

  Data_Get_Struct( self, MCRYPT, (void *)td ) ;

  str = mcrypt_enc_get_modes_name( td );

  mode_name = rb_str_new2( str );

  mcrypt_free( str );

  return mode_name;
}


//
// mcrypt_generic_end
//

VALUE _mcrypt_generic_end( VALUE self )
{
  MCRYPT td;
  int rc;

  Data_Get_Struct( self, MCRYPT, (void *)td ) ;
  rc = mcrypt_generic_end( td );

  if ( rc < 0 ) {
    rb_raise( rb_eRuntimeError, mcrypt_strerror( rc ) );
  }

  return Qnil;
}


//
// mcrypt_list_algorithms
//

VALUE _mcrypt_list_algorithms ( VALUE class, VALUE libdir )
{
  char *l_dir;
  int size;
  char **algo_list;
  VALUE ary;
  int i;

  if ( NIL_P( libdir ) ) {
    l_dir = NULL;
  }
  else {
    Check_Type( libdir, T_STRING );
    l_dir = STR2CSTR( libdir );
  }

  algo_list = mcrypt_list_algorithms( l_dir, &size );

  ary = rb_ary_new();

  for ( i = 0; i < size; i++ ) {
    rb_ary_push( ary, rb_str_new2( algo_list[i] ) );
  }

  mcrypt_free_p( algo_list, size );

  return ary;
}


//
// mcrypt_list_modes
//

VALUE _mcrypt_list_modes ( VALUE class, VALUE libdir )
{
  char *l_dir;
  int size;
  char **mode_list;
  VALUE ary;
  int i;

  if ( NIL_P( libdir ) ) {
    l_dir = NULL;
  }
  else {
    Check_Type( libdir, T_STRING );
    l_dir = STR2CSTR( libdir );
  }

  mode_list = mcrypt_list_modes( l_dir, &size );

  ary = rb_ary_new();

  for ( i = 0; i < size; i++ ) {
    rb_ary_push( ary, rb_str_new2( mode_list[i] ) );
  }

  mcrypt_free_p( mode_list, size );

  return ary;
}


//
// mcrypt_enc_get_supported_key_sizes
//

VALUE _mcrypt_enc_get_supported_key_sizes( VALUE self )
{
  MCRYPT td;
  int sizes;
  int *sizes_list;
  VALUE ary;
  int i;

  Data_Get_Struct( self, MCRYPT, (void *)td ) ;
  sizes_list = mcrypt_enc_get_supported_key_sizes( td, &sizes );

  ary = rb_ary_new();

  for ( i = 0; i < sizes; i++ ) {
    rb_ary_push( ary, INT2FIX( sizes_list[i] * 8 ) );
  }

  mcrypt_free( sizes_list );

  return ary;
}


//
// mcrypt_enc_is_block_algorithm
//

VALUE _mcrypt_enc_is_block_algorithm( VALUE self )
{
  MCRYPT td;

  Data_Get_Struct( self, MCRYPT, (void *)td ) ;

  if ( mcrypt_enc_is_block_algorithm( td ) ) {
    return Qtrue;
  }
  else {
    return Qfalse;
  }
}


//
// mcrypt_enc_is_block_mode
//

VALUE _mcrypt_enc_is_block_mode( VALUE self )
{
  MCRYPT td;

  Data_Get_Struct( self, MCRYPT, (void *)td ) ;

  if ( mcrypt_enc_is_block_mode( td ) ) {
    return Qtrue;
  }
  else {
    return Qfalse;
  }
}


//
// mcrypt_enc_is_block_algorithm_mode
//

VALUE _mcrypt_enc_is_block_algorithm_mode( VALUE self )
{
  MCRYPT td;

  Data_Get_Struct( self, MCRYPT, (void *)td ) ;

  if ( mcrypt_enc_is_block_algorithm_mode( td ) ) {
    return Qtrue;
  }
  else {
    return Qfalse;
  }
}


//
// mcrypt_enc_get_block_size
//

VALUE _mcrypt_enc_get_block_size( VALUE self )
{
  MCRYPT td;

  Data_Get_Struct( self, MCRYPT, (void *)td ) ;

  return INT2FIX( mcrypt_enc_get_block_size( td ) * 8 );
}


//
// Initialisation
//

void Init_MCrypt()
{
  VALUE cMCrypt = rb_define_class( "MCrypt", rb_cObject );

#ifdef MTRACE
  mtrace();
#endif

  rb_define_singleton_method( cMCrypt, "new", _mcrypt_new, 6 );
  rb_define_singleton_method( cMCrypt, "mcrypt_list_algorithms", _mcrypt_list_algorithms, 1 );
  rb_define_singleton_method( cMCrypt, "mcrypt_list_modes", _mcrypt_list_modes, 1 );

  rb_define_method( cMCrypt, "mcrypt_generic", _mcrypt_generic, 1 );
  rb_define_method( cMCrypt, "mdecrypt_generic", _mdecrypt_generic, 1 );
  rb_define_method( cMCrypt, "mcrypt_enc_get_algorithms_name", _mcrypt_enc_get_algorithms_name, 0 );
  rb_define_method( cMCrypt, "mcrypt_enc_get_modes_name", _mcrypt_enc_get_modes_name, 0 );
  rb_define_method( cMCrypt, "mcrypt_generic_end", _mcrypt_generic_end, 0 );
  rb_define_method( cMCrypt, "mcrypt_enc_get_supported_key_sizes", _mcrypt_enc_get_supported_key_sizes, 0 );
  rb_define_method( cMCrypt, "mcrypt_enc_is_block_algorithm", _mcrypt_enc_is_block_algorithm, 0 );
  rb_define_method( cMCrypt, "mcrypt_enc_is_block_mode", _mcrypt_enc_is_block_mode, 0 );
  rb_define_method( cMCrypt, "mcrypt_enc_is_block_algorithm_mode", _mcrypt_enc_is_block_algorithm_mode, 0 );
  rb_define_method( cMCrypt, "mcrypt_enc_get_block_size", _mcrypt_enc_get_block_size, 0 );
}

//
// EOF
//
// Copyright 2001, Hans-Dieter Stich <hd@rubylinks.de>
//
