/* aide, Advanced Intrusion Detection Environment
 *  
 * Copyright (C) 1999 Rami Lehti,Pablo Virolainen
 * $Header: /cvs-root-aide/aide/src/crypt/enum.c,v 1.5 1999/07/22 13:08:33 rammer Exp $
 *
 * 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 2 of the
 * License, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "cipher.h"
#include "list.h"

typedef struct ext_list {
    struct ext_list *next;
    int internal;
  #ifdef HAVE_DL_DLOPEN
    void *handle; /* handle from dlopen() */
  #else
    int handle;   /* if the function has been loaded, this is true */
  #endif
    int  failed;  /* already tried but failed */
    void * (*enumfunc)(int, int*, int*, int*);
    char *hintstr; /* pointer into name */
    char name[1];
} *EXTLIST;

static EXTLIST extensions;

typedef struct {
    EXTLIST r;
    int seq1;
    int seq2;
    void *sym;
    int reqalgo;
} ENUMCONTEXT;

/* This function was taken from gnupg's fileutil.c */
int
compare_filenames( const char *a, const char *b )
{
    /* ? check whether this is an absolute filename and
     * resolve symlinks?
     */
  #ifdef HAVE_DRIVE_LETTERS
    return stricmp(a,b);
  #else
    return strcmp(a,b);
  #endif
}


void
register_internal_cipher_extension(
			const char *module_id,
			void * (*enumfunc)(int, int*, int*, int*)
				  )
{
    EXTLIST r, el;

    el = malloc( sizeof *el + strlen(module_id) );/*m_alloc_clear*/
    memset((void*)el,0,sizeof *el + strlen(module_id));
    strcpy(el->name, module_id );
    el->internal = 1;

    /* check that it is not already registered */
    for(r = extensions; r; r = r->next ) {
	if( !compare_filenames(r->name, el->name) ) {
	    error(10,"extension `%s' already registered\n", el->name );
	    free(el);/* m_free */
	    return;
	}
    }
    /* and register */
    el->enumfunc = enumfunc;
  #ifdef HAVE_DL_DLOPEN
    el->handle = (void*)1;
  #else
    el->handle = 1;
  #endif
    el->next = extensions;
    extensions = el;
}


static int
load_extension( EXTLIST el )
{
  #ifdef USE_DYNAMIC_LINKING
    char **name;
  #ifdef HAVE_DL_DLOPEN
    const char *err;
    int seq = 0;
    int class, vers;
    void *sym;
  #else
    unsigned long addr;
    int rc;
  #endif

    /* make sure we are not setuid */
    if( getuid() != geteuid() )
	log_bug("trying to load an extension while still setuid\n");

    /* now that we are not setuid anymore, we can safely load modules */
  #ifdef HAVE_DL_DLOPEN
    el->handle = dlopen(el->name, RTLD_NOW);
    if( !el->handle ) {
	log_error("%s: error loading extension: %s\n", el->name, dlerror() );
	goto failure;
    }
    name = (char**)dlsym(el->handle, SYMBOL_VERSION);
    if( (err=dlerror()) ) {
	log_error("%s: not a gnupg extension: %s\n", el->name, err );
	goto failure;
    }
  #else /* have dld */
    if( !did_dld_init ) {
	did_dld_init = 1;
	if( !mainpgm_path )
	    log_error("DLD is not correctly initialized\n");
	else {
	    rc = dld_init( dld_find_executable(mainpgm_path) );
	    if( rc )
		log_error("DLD init failed: %s\n", dld_strerror(rc) );
	    else
		dld_available = 1;
	}
    }
    if( !dld_available ) {
	log_error("%s: DLD not available\n", el->name );
	goto failure;
    }

    rc = dld_link( el->name );
    if( rc ) {
	log_error("%s: error loading extension: %s\n",
				    el->name, dld_strerror(rc) );
	goto failure;
    }
    addr = dld_get_symbol(SYMBOL_VERSION);
    if( !addr ) {
	log_error("%s: not a gnupg extension: %s\n",
				el->name, dld_strerror(dld_errno) );
	goto failure;
    }
    name = (char**)addr;
  #endif

    if( g10_opt_verbose > 1 )
	log_info("%s: %s%s%s%s\n", el->name, *name,
		  el->hintstr? " (":"",
		  el->hintstr? el->hintstr:"",
		  el->hintstr? ")":"");

  #ifdef HAVE_DL_DLOPEN
    sym = dlsym(el->handle, SYMBOL_ENUM);
    if( (err=dlerror()) ) {
	log_error("%s: invalid gnupg extension: %s\n", el->name, err );
	goto failure;
    }
    el->enumfunc = (void *(*)(int,int*,int*,int*))sym;
  #else /* dld */
    addr = dld_get_func(SYMBOL_ENUM);
    if( !addr ) {
	log_error("%s: invalid gnupg extension: %s\n",
				el->name, dld_strerror(dld_errno) );
	goto failure;
    }
    rc = dld_function_executable_p(SYMBOL_ENUM);
    if( rc ) {
	log_error("%s: extension function is not executable: %s\n",
					el->name, dld_strerror(rc) );
	goto failure;
    }
    el->enumfunc = (void *(*)(int,int*,int*,int*))addr;
    el->handle = 1; /* mark as usable */
  #endif

  #ifdef HAVE_DL_DLOPEN
    if( g10_opt_verbose > 2 ) {
	/* list the contents of the module */
	while( (sym = (*el->enumfunc)(0, &seq, &class, &vers)) ) {
	    if( vers != 1 ) {
		log_info("%s: ignoring func with version %d\n",el->name,vers);
		continue;
	    }
	    switch( class ) {
	      case 11:
	      case 21:
	      case 31:
		log_info("%s: provides %s algorithm %d\n", el->name,
				class == 11? "md"     :
				class == 21? "cipher" : "pubkey",
						       *(int*)sym);
		break;
	      default:
		/*log_debug("%s: skipping class %d\n", el->name, class);*/
		break;
	    }
	}
    }
  #endif
    return 0;

  failure:
  #ifdef HAVE_DL_DLOPEN
    if( el->handle ) {
	dlclose(el->handle);
	el->handle = NULL;
    }
  #endif
    el->failed = 1;
  #endif /*USE_DYNAMIC_LINKING*/
    return -1;
}



int
enum_digests( void **enum_context,
	    int *algo,
	    const char *(**r_get_info)( int, size_t*,byte**, int*, int*,
				       void (**)(void*),
				       void (**)(void*,byte*,size_t),
				       void (**)(void*),byte *(**)(void*)) )
{
    EXTLIST r;
    ENUMCONTEXT *ctx;

    if( !*enum_context ) { /* init context */
      ctx = malloc( sizeof( *ctx ) ); /* m_alloc_clear */
      memset((void*)ctx,0,sizeof(*ctx));
      ctx->r = extensions;
      ctx->reqalgo = *algo;
      *enum_context = ctx;
    }
    else if( !algo ) { /* release the context */
      free(*enum_context); /* m_free */
      *enum_context = NULL;
      return 0;
    }
    else
	ctx = *enum_context;

    for( r = ctx->r; r; r = r->next )  {
      int class, vers;

      if( r->failed )
	continue;
      if( !r->handle && load_extension(r) )
	continue;
      /* get a digest info function */
      if( ctx->sym )
	goto inner_loop;
      while( (ctx->sym = (*r->enumfunc)(10, &ctx->seq1, &class, &vers)) ) {
	void *sym;
	/* must check class because enumfunc may be wrong coded */
	if( vers != 1 || class != 10 )
	  continue;
      inner_loop:
	*r_get_info = ctx->sym;
	while( (sym = (*r->enumfunc)(11, &ctx->seq2, &class, &vers)) ) {
	  if( vers != 1 || class != 11 )
	    continue;
	  *algo = *(int*)sym;
	  ctx->r = r;
	  return 1;
	}
	ctx->seq2 = 0;
      }
      ctx->seq1 = 0;
    }
    ctx->r = r;
    return 0;
}

void cipher_modules_constructor(void)
{
  static int done = 0 ;

  if(done)
    return;

  done=1;

  md5_constructor();
  sha1_constructor();
  rmd160_constructor();
  tiger_constructor();
}
