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

    If you have any questions, please contact Jeremy Beker <servicio.informatica@uv.es>
  */


// Usamos la version thread-safe de las rutinas hash
#define _GNU_SOURCE  
#include <search.h>

// Y necesitamos las funciones de threads 
#include <pthread.h>

// Y usamos el time()
#include <time.h>

// Para el open 
#include <fcntl.h>

#include <libmilter/mfapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <syslog.h>

#ifndef bool
#define bool char
#define TRUE 1
#define FALSE 0
#endif
#define not !
#define and &&
#define or ||
#define EndString '\0'
#define SYSLOG if (use_syslog) syslog

/* Global Structures */

/* ... priv milter struc ... */
struct mlfiPRIV {
  char *hostname;
  char *envfrom;
  };
  
/* ... tabla de castigados ... */
#define MAXCASTIGADOS 5000
int ncastigados=0;
struct tipocastigado {
  char *hostname;
  int  culpa;
  time_t tculpa;
  } castigados[MAXCASTIGADOS];

// y su mutex:
pthread_mutex_t mutexcasti = PTHREAD_MUTEX_INITIALIZER;  

// momento del ultimo perdon
time_t tultperdon;

// Se rechaza tras este número de "user unknown"
#define VENIAL 5
// Cada estos segundos se repasa y limpia la tabla
#define TCHECK 30*60
// Los castigos más antiguos de estos segundos, se olvidan
#define TPERDON 15*60

/* ......................*/

/* Global variables */
bool debug;
bool verbose;
     
bool pruebas=FALSE;  // Si TRUE, siempre devuelve SMFIS_CONTINUE
// ... PRUEBAS
// bool pruebas=TRUE;  
// #define TCHECK 1
// #define TPERDON 2*60

#define fierr "/var/tmp/ctoloc-milter.err"
#define fitabdom  "/root/CORREO/TABLAS/OPER/tolocDOMGRU"
#define fitab  "/root/CORREO/TABLAS/OPER/toloc"

/*
 * #define fitabP "/root/CORREO/TABLAS/OPER/tolocP"
 * #define fitabA "/root/CORREO/TABLAS/OPER/tolocA"
 * #define fitabE "/root/CORREO/TABLAS/OPER/tolocE"
 * #define fitabF "/root/CORREO/TABLAS/OPER/tolocF"
 * #define fitabL "/root/CORREO/TABLAS/OPER/tolocL"
 * #define fitabU "/root/CORREO/TABLAS/OPER/tolocU"
 * #define fitabW "/root/CORREO/TABLAS/OPER/tolocW"
*/

#define MAXDOMTAB 255
struct domain_data {
  char *ldominio[MAXDOMTAB],*lgrupou[MAXDOMTAB];
  struct hsearch_data *usertab[MAXDOMTAB];
  int londomtab;
  } domdata;

/*
*struct hsearch_data domtab,usertabP,usertabA,usertabE,
*                    usertabF,usertabL,usertabU,usertabW;
*/

static void usage();

/* libmilter callback functions */
sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr);
sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv);
sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv);
sfsistat mlfi_abort(SMFICTX *ctx);
sfsistat mlfi_close(SMFICTX *ctx);


struct smfiDesc smfilter =
{
    "ctoloc",       /* filter name */
    SMFI_VERSION,   /* version code -- do not change */
    0,              /* flags */
    mlfi_connect,   /* connection info filter */
    NULL,           /* SMTP HELO command filter */
    mlfi_envfrom,   /* envelope sender filter */
    mlfi_envrcpt,   /* envelope recipient filter */
    NULL,           /* header filter */
    NULL,           /* end of header */
    NULL,           /* body block filter */
    NULL,           /* end of message */
    NULL,           /* message aborted */
    mlfi_close,     /* connection cleanup */
};

bool use_syslog = FALSE;
static char AppIdent[] = "ctoloc-milter";


/* ===================================================================
 *                      UTILIDADES GENERALES
 * ===================================================================*/

/* -------------------------------------------------------------------*/

// .............
char *NEWSTR( char *cadena )
{
  int lonbuf=sizeof(char)*( strlen(cadena)+1 );
  char *p=malloc(lonbuf);
  if (p!=NULL) { memcpy(p,cadena,lonbuf); }
  return p;
}

// .............
char *NEWCATSTR( char *cadena1, char *cadena2 )
{
  int loncad1=sizeof(char) * (strlen(cadena1));
  int loncad2=sizeof(char) * (strlen(cadena2)+1);
  int lonbuf=loncad1+loncad2;
  char *p=malloc(lonbuf);
  if (p!=NULL) { 
    memcpy(p,cadena1,loncad1); 
    memcpy(p+loncad1,cadena2,loncad2); 
    }
  return p;
}

// .............
#define  FREESTR(cadena) { if (cadena!=NULL) { free(cadena); }; cadena=NULL; }
#define  EQSTR(cadena1,cadena2) (strcmp(cadena1,cadena2)==0) 

// ............. La segunda cadena es la cola de referencia
int TailCompare( char *cadena1, char *cadena2) 
{
  char *p1,*p2;
  int lon1=strlen(cadena1), lon2=strlen(cadena2);
  p1=cadena1+lon1; p2=cadena2+lon2;  // apuntan al nulo final
  while (*p1 == *p2 and p1>cadena1 and p2>cadena2) { p1--; p2--; };
  if (*p1 == *p2 and p2==cadena2 ) return 1;
  else return 0;
}

// ............. Quira la cola de la PRIMERA cadena. La segunda cadena 
//               es la cola de referencia.
int TailErase( char *cadena1, char *cadena2) 
{
  char *p1,*p2;
  int lon1=strlen(cadena1), lon2=strlen(cadena2);
  p1=cadena1+lon1; p2=cadena2+lon2;  // apuntan al nulo final
  while (*p1 == *p2 and p1>cadena1 and p2>cadena2) { p1--; p2--; };
  if (*p1 == *p2 and p2==cadena2) { *p1='\0'; return TRUE; }
  return FALSE;
}

/* -------------------------------------------------------------------
*  Extrae la direccion entre <> y la pasa a minusculas 
*/
int ExtractAddress(char *origen)
{
  char *ptr1,*destino;
  long len = 0;

  destino=origen;
  ptr1 = strchr(origen, '<');
  if (ptr1 == NULL) ptr1=origen;
  else ptr1++;

  while ( (*ptr1!='\0') and (*ptr1!='>') )  {
    *destino++=tolower(*ptr1++);
    len++;
    }
  *destino='\0';

  // ... quita puntos al final de la direccion. No parecen significativos ¿? 
  while ( (*(--destino)=='.') and (len>0) ) {
    *destino='\0';
    len--;
    }

  return len;
}
                 
/* -------------------------------------------------------------------
* Comprueba que la direccion, quitándole el dominio indicado, es un
* usuario en la tabla indicada.
* Devuelve TRUE si lo encuentra.
*/
int GoodRcptUserDom(char *envrcpt, char *domain, struct hsearch_data *pusertab)
{
  ENTRY d,*preturnd;
  char *rcpt=NEWSTR(envrcpt);

  // ... Le quita el "@dominio" indicado
  if (not TailErase(rcpt,domain) ) return FALSE; 
  
  // ... Le quita el "+inbox" si lo tiene (para lavadora)
  TailErase(rcpt,"+inbox");

  // ... Lo busca en la tabla especificada
  d.key=rcpt;
  hsearch_r(d,FIND,&preturnd,pusertab);

  FREESTR(rcpt);
  if (preturnd != NULL) { 
    return TRUE;
    } 
  else { return FALSE; }
}
        
/* -------------------------------------------------------------------
* Busca el dominio de la direccion en la tabla de dominios y, si lo
* encuentra, comprueba que el usuario esta en la tabla correspondiente.
* Devuelve TRUE si lo encuentra.
*/
int GoodRcptUser(char *envrcpt, struct domain_data *domdata)
{

  int good=0;
  int d; 
  for ( d=0; d<domdata->londomtab; ++d ){
    // ... TailCompare(rcpt,"@alumni.uv.es")
    if( TailCompare(envrcpt,domdata->ldominio[d]) ){
      // ... GoodRcptUserDom(envrcpt,"@alumni.uv.es",&usertabA) 
      if( GoodRcptUserDom(envrcpt, domdata->ldominio[d], domdata->usertab[d]) ) good=1;
      }
    }
  return good;  
}

/* -------------------------------------------------------------------
* Comprueba que la direccion, 
*/

int EsParaUV(char *envrcpt, struct domain_data *domdata)
{
  int EsUV=0;
  int d; 
  for ( d=0; d<domdata->londomtab; ++d ){
    // ... TailCompare(rcpt,"@alumni.uv.es")
    if( TailCompare(envrcpt,domdata->ldominio[d]) ) EsUV=1;
    }
  return EsUV;
}
        
/* -------------------------------------------------------------------
* Devuelve TRUE si la direccion es SRS 
*/
int RcptSRS(char *envrcpt)
{
  if (strncmp(envrcpt,"srs0+",5)==0 ||
      strncmp(envrcpt,"srs0-",5)==0 ||
      strncmp(envrcpt,"srs0=",5)==0 ||
      strncmp(envrcpt,"srs1+",5)==0 ||
      strncmp(envrcpt,"srs1-",5)==0 ||
      strncmp(envrcpt,"srs1=",5)==0  ) { 
    return TRUE;
    } 
  else { return FALSE; }
 
}
/* ===================================================================
 *                          TABLA DE CASTIGOS
 * ===================================================================*/

/* -------------------------------------------------------------------
* Busca el host en la tabla de castigados y le incrementa la culpa.
* Si no está, lo inserta.
*/
void Castigar(char *hostname)
{
  int i;
  pthread_mutex_lock( &mutexcasti );                              
  for (i=0;i<ncastigados ;i++) {
    if EQSTR( castigados[i].hostname, hostname) {
      castigados[i].culpa++; break;
      }
    }
  if (i==ncastigados) { // No lo ha encontrado, añadir.
    if (ncastigados < MAXCASTIGADOS ) { 
      castigados[i].hostname=NEWSTR(hostname);
      castigados[i].culpa=1;
      castigados[i].tculpa=time(NULL);
      ncastigados++;
      }
    }
  pthread_mutex_unlock( &mutexcasti );
}

/* .....................................................................
* Busca el host en la tabla de castigados y le decrementa la culpa.
* Si no está, nada.
*/
void Perdonar(char *hostname)
{
  int i;
  pthread_mutex_lock( &mutexcasti );
  for (i=0;i<ncastigados ;i++) {
    if EQSTR( castigados[i].hostname, hostname) {
      castigados[i].culpa--; break;
      }
    }
  pthread_mutex_unlock( &mutexcasti );
}

/* .....................................................................
* Busca el host en la tabla de castigados y devuelve su culpa.
* Sino, devuelve 0
*/
int Culpa(char *hostname)
{
  int i,culpa=0;;
  pthread_mutex_lock( &mutexcasti );
  for (i=0;i<ncastigados ;i++) {
    if EQSTR( castigados[i].hostname, hostname) {
      culpa=castigados[i].culpa;  break;
      }
    }
  pthread_mutex_unlock( &mutexcasti );
  return culpa;
}

/* .....................................................................
* Borra de la tabla de castigados aquellos que estan hace mas de 
* X tiempo. Reorganiza la tabla moviendo hacia el principio los
* que quedan.
*/
void Indulgencia()
{
  int i,k;
  time_t ahora=time(NULL);
  
  // ... Vuelve si hace menos de TCHECK segundos que lo ha hecho
  if ( (ahora-tultperdon) < TCHECK ) return;
  
  pthread_mutex_lock( &mutexcasti );
  tultperdon=ahora;
  
  SYSLOG (LOG_INFO,"INDULGENCIA %d",ahora);
  
  k=0; 
  for (i=0;i<ncastigados ;i++) {
    if ( (ahora - castigados[i].tculpa) <= TPERDON ){
      castigados[k].hostname=castigados[i].hostname;
      castigados[k].culpa=castigados[i].culpa;
      castigados[k].tculpa=castigados[i].tculpa;
      k++;
      }
    else { FREESTR(castigados[i].hostname); }
    }
  ncastigados=k;
  
  pthread_mutex_unlock( &mutexcasti );
}

/* ===================================================================
 *                           CALLBACKS
 * ===================================================================*/

sfsistat mlfi_connect(SMFICTX * ctx, char *hostname, _SOCK_ADDR * hostaddr)
{

        struct mlfiPRIV *priv;
        enum tiporesfin { mcontinue, mtempfail } resfin=mcontinue;
        
        if (debug) SYSLOG (LOG_INFO,"CONN %s",hostname);

        // ... Creando priv
        priv = (struct mlfiPRIV *) malloc(sizeof *priv);
        if (priv == NULL) { 
          SYSLOG (LOG_ERR,"***Can't get memory for priv data");
          perror("Malloc failed for priv data");
          return SMFIS_ACCEPT;  
          }
        memset(priv, 0, sizeof(*priv));

        //... Si ha superado un máximo de "User Unknown", devolverle tempfail
        {
        Indulgencia();
        
        int culpa=Culpa(hostname);
        if ( culpa > VENIAL ) {
          SYSLOG (LOG_INFO,"CONN [%d] %s PUNISHED",ncastigados,hostname);
          resfin=mtempfail;
          }
        else SYSLOG (LOG_INFO,"CONN [%d] %s",ncastigados, hostname);
        }
        
        priv->hostname=NEWSTR ( hostname );
        priv->envfrom=NULL;
        smfi_setpriv(ctx, priv);

        if (pruebas) return SMFIS_CONTINUE;
        if (resfin==mcontinue) {
          return SMFIS_CONTINUE;
          }
        else {
          sleep (5); // Bloquear al maldito! (no poner mas o dara timeout el milter)
          // Lo siguiente es INUTIL en el caso de xxxx_connect: 
          //   SIEMPRE devuelve "421 4.7.0 postin.uv.es closing connection"
          smfi_setreply(ctx,"421","4.7.1","... No resources to check user");
          return SMFIS_TEMPFAIL;
          }  
}

/* -------------------------------------------------------------------*/
sfsistat mlfi_envfrom(SMFICTX *ctx, char **args) 
{
       struct mlfiPRIV *priv;
       char *envfrom=args[0];

       priv=(struct mlfiPRIV *)smfi_getpriv(ctx);
       if (priv == NULL) return SMFIS_TEMPFAIL;
       
       FREESTR(priv->envfrom); // puede haber varios "Msg from" en una conexion
       priv->envfrom=NEWSTR ( envfrom );
       
       smfi_setpriv(ctx, priv); 
       return SMFIS_CONTINUE;
}

/* -------------------------------------------------------------------*/
sfsistat mlfi_envrcpt(SMFICTX *ctx, char **args) 
{
       struct mlfiPRIV *priv;
       char *envrcpt=args[0];
       enum tiporesfin { mcontinue, munknown, mnores } resfin=mcontinue;

       priv=(struct mlfiPRIV *)smfi_getpriv(ctx);
       if (priv == NULL) return SMFIS_TEMPFAIL;
       
       {
       char *hostname,*message,*rcpt,lin[100];
       int culpa;

       hostname=priv->hostname;
       message="";

       rcpt=NEWSTR(envrcpt);
       ExtractAddress(rcpt);
       
       if (debug) SYSLOG (LOG_INFO,"RCPT %s %s",rcpt, hostname);
       
       //... Si ha superado un máximo de "User Unknown", devolverle tempfail
       culpa=Culpa(hostname);
       if ( culpa > VENIAL ) {
         message="PUNISHED";
         resfin=mnores;
         }
       else {
         // ¿Es para algun dominio de la UV?
         if( EsParaUV(rcpt, &domdata) ){
           // .. Es direccion SRS?
           if ( RcptSRS(rcpt) ){
             // ... se deja pasar, pero como no esta confirmada, no se perdona.
             message="LOCAL SRS addr OK";
             }
           // ... ¿Es usuario correcto para ese dominio?
           else if ( GoodRcptUser(rcpt, &domdata) ){
             message="LOCAL user OK"; 
             Perdonar(hostname);
             }
           else {
             // ... Unknown user, rechazar.
             // ... Si es host uv NO castigar
             if ( TailCompare(hostname,".uv.es") ){ 
               message="LOCAL user UNKNOWN host UVHOST";
               if (pruebas) Castigar(hostname);
               }
             // ... Si es host RedIris NO castigar
             else if ( TailCompare(hostname,".rediris.es") ){ 
               message="LOCAL user UNKNOWN host REDIRISHOST";
               if (pruebas) Castigar(hostname);
               }
             else {
               sprintf(lin,"LOCAL user UNKNOWN guilt %d",culpa+1);
               message=lin;
               Castigar(hostname);
               }
             resfin=munknown;
             }
           }
         else { 
           message="EXT";
           }
         }

       SYSLOG(LOG_INFO,"RCPT %s %s TO %s %s",hostname,priv->envfrom,rcpt,message);
       FREESTR(rcpt); 
       }
       
       smfi_setpriv(ctx, priv);  // ¿es necesario redifinirlo cada vez?

       if (pruebas) resfin=mcontinue;
       switch (resfin) {
         case munknown:
           smfi_setreply(ctx,"550","5.1.1","... User unknown at UV");
           return SMFIS_REJECT; 
           break;
         case mnores:
           sleep (5); // Bloquear al maldito! (no poner mas o dara timeout el milter)
           smfi_setreply(ctx,"421","4.7.1","... No resources to check user");
           return SMFIS_TEMPFAIL; 
           break;
         case mcontinue:
         default:
           return SMFIS_CONTINUE;
           break;
         }

}

/* -------------------------------------------------------------------*/
sfsistat mlfi_close(SMFICTX * ctx)
{
        struct mlfiPRIV *priv;

        // puede ser llamado ANTES del connect!
        // El valor de vuelta se IGNORA
        
        priv=(struct mlfiPRIV *)smfi_getpriv(ctx);
        if (priv == NULL) {
          smfi_setpriv(ctx, NULL);  // ¿es necesario redifinirlo cada vez?
          }
        else {
          if(debug) { SYSLOG(LOG_INFO,"CLOSE %s",priv->hostname); } 
          
          // .. liberar priv CONEXION siempre en el close!!
          FREESTR( priv->hostname );
          FREESTR( priv->envfrom ); // deberia ser en fin "mensaje"
          free(priv); 
          smfi_setpriv(ctx, NULL);
          }
                        
        return SMFIS_CONTINUE;
}

/* -------------------------------------------------------------------
NO SE USA!
*/
sfsistat mlfi_abort(SMFICTX * ctx)
{
        struct mlfiPRIV *priv;
        priv=(struct mlfiPRIV *)smfi_getpriv(ctx);
        if (priv == NULL) return SMFIS_CONTINUE;
        
        if(debug) { SYSLOG(LOG_INFO,"ABORT %s",priv->hostname); } 

        // .. NO liberar priv CONEXION aquí: siempre se llama al close despues
        // Liberar memoria asignada POR MENSAJE, puede ser llamada
        // entre cualquier message callback

        return SMFIS_CONTINUE;
}



/* ==================================================================
 *                PROGRAMA PRINCIPAL y sus rutinas 
 * ==================================================================*/

static void usage()
{
        fprintf(stderr,
                "Usage: ctoloc-milter [-f] [-t timeout] [-l] -p socket-addr\n");
        // fprintf(stderr, "\t-f\tRun in foreground\n");
        fprintf(stderr, "\t-d\tDebug\n");
        fprintf(stderr, "\t-p\tSocket Address for Sendmail connection\n");
        fprintf(stderr, "\t-t\tTimeout\n");
        fprintf(stderr, "\t-v\tShow domains and tables loaded and exits\n");
        fprintf(stderr, "\t-l\tLog to syslog (else nothing)\n");
}

/* -------------------------------------------------------------------*/
int chomp(char *s)
{
  int lon;
  lon=strlen(s);
  if (s[lon-1] != '\n') { perror("Reading: line too long"); exit (55); }
  s[lon-1] = '\0'; 
  return lon-1;
}

/* -------------------------------------------------------------------
* Carga el hash que se le pasa con el fichero texto indicado con 
* las líneas del texto como "keys". 
*/
void LoadDomTab(char *nomfitexto, struct domain_data *domdata)
{
# define MAXBUFF 256
  char buff[MAXBUFF];
  int numele=0;
  
  FILE *FITAB;
  
  // ... Primer barrido para saber el tamaño
  FITAB=fopen (nomfitexto,"r");
  if (FITAB == NULL) { perror("LoadDomTab first fopen"); exit (31); }
  
  while ( fgets(buff,MAXBUFF,FITAB) ) {
    int *valp,lon;
    lon=chomp(buff); 

    char *pwhite=strchr(buff,' ');
    if( pwhite == NULL ){
      printf ("BAD DomTab ENTRY %s",buff);
      }
    else {  // Hay valor, separar por el espacio "key val"
      *pwhite=EndString;     // parto el string en dos
      domdata->ldominio[numele]=NEWCATSTR("@",buff);  // DEBE tener la "@"!
      domdata->lgrupou[numele]=NEWSTR(pwhite+1);
      domdata->usertab[numele]=NULL;
      }
    if( numele<MAXDOMTAB ) numele++;
    else printf ("TOO MANY DomTabs entries!!!: %d",numele);
    }
  fclose (FITAB);
  
  if( verbose ) printf ("READED TAB %s DomTab %d elements\n",nomfitexto,numele);
  domdata->londomtab=numele;
}


/* -------------------------------------------------------------------
* Carga el hash que se le pasa con el fichero texto indicado con 
* las líneas del texto como "keys". Hace dos pasadas pues hay
* que averiguar el tamaño del hash antes de crearlo.
*/
void LoadUserTab(char *nomfitexto, struct hsearch_data *pusertab)
{
# define MAXBUFF 256
  char buff[MAXBUFF];
  int numele=0,isok=0;
  
  FILE *FITAB;
  
  // ... Primer barrido para saber el tamaño
  FITAB=fopen (nomfitexto,"r");
  if (FITAB == NULL) { perror("LoadUserTab first fopen"); exit (31); }
  
  while ( fgets(buff,MAXBUFF,FITAB) ) {
    int *valp,lon;
    lon=chomp(buff); 
    numele++;
    }
  fclose (FITAB);
  
  if( verbose ) printf ("READED TAB %s UserTab %d elements\n",nomfitexto,numele);

  // ... Segundo barrido, creacion y carga de la tabla
  bzero( pusertab,sizeof (*pusertab)); // hay que ponerla a zero
  isok=hcreate_r(numele,pusertab);
  if (not isok) { perror("LoadUserTab creating hash"); exit (32); }
  
  FITAB=fopen (nomfitexto,"r");
  if (FITAB == NULL) { perror("LoadUserTab fopen"); exit (33); }
  
  while ( fgets(buff,MAXBUFF,FITAB) ) {
    ENTRY d,*pretd;
    int *valp;
    chomp(buff); 
    char *pwhite=strchr(buff,' ');
    if( pwhite == NULL ){
      d.key=NEWSTR(buff);
      d.data=NULL;   // No necesito valor, solo si existe
      }
    else {  // Hay valor, separar por el espacio "key val"
      *pwhite=EndString;     // parto el string en dos
      d.key=NEWSTR(buff);
      d.data=NEWSTR(pwhite+1);
      }
    isok=hsearch_r(d,ENTER,&pretd,pusertab);
    if (not isok) { perror("LoadUserTab writing key"); exit (34); }
    
    // printf ("%s\n",d.key);
    }
  fclose (FITAB);
  
  // Prueba de busqueda
  // {
  // ENTRY d,*preturnd;
  // // d.key="aadrian"; 
  // d.key="abajus"; 
  // isok=hsearch_r(d,FIND,&preturnd,pusertab);
  // if (preturnd == NULL) { printf("NO ENCONTRADO\n"); } else { printf("GUAY!!\n"); }
  // }
  
}

/* -------------------------------------------------------------------*/
LoadAllUserTabs(struct domain_data *domdata)
{
  // ... Cargamos las tablas de direcciones para cada dominio
  //     Como puede haber varios dominios con la misma tabla, lo comprobamos
  int d;
  if( verbose ){ 
    for ( d=0; d<domdata->londomtab; ++d ){
      printf("DOMAIN %s [%s]\n",domdata->ldominio[d],domdata->lgrupou[d]);
      }
    }
  for ( d=0; d<domdata->londomtab; ++d ){
#   define LONPATH 1024
    char *pathtxt=NEWCATSTR(fitab, domdata->lgrupou[d]);
    // printf("%s = %s %s\n",domdata->ldominio[d],domdata->lgrupou[d],pathtxt);
    
    //... buscar si ya esta leida y, si lo esta, poner el mismo puntero 
    struct hsearch_data *read_usertab;
    int dd; int yaesta=0;
    for ( dd=0; dd<domdata->londomtab; ++dd ){ 
      if( domdata->usertab[dd] != NULL and 
          strcmp(domdata->lgrupou[dd],domdata->lgrupou[d])==0 ){ 
        yaesta=1; 
        domdata->usertab[d]=domdata->usertab[dd];
        }
      }
    if ( not yaesta ){
      read_usertab=malloc(sizeof(struct hsearch_data));
      if( read_usertab == NULL ){ perror("malloc read_usertab failed"); exit(66); }
      LoadUserTab(pathtxt,read_usertab);
      domdata->usertab[d]=read_usertab;
      }
    FREESTR(pathtxt);  
    }
}

/* -------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
  int retval = 0;
  char c;
  const char *optstring = "p:t:hdrlv";
  extern char *optarg;
  char *socket = NULL, *regerrbuf;
  pid_t child;
  bool bg = TRUE;

  debug = FALSE;
  tultperdon=time(NULL);

  /* Process command line options */
  while ((c = getopt(argc, argv, optstring)) != (char) EOF) {
    switch (c) {

      case 'p':
              if (optarg == NULL || *optarg == '\0') {
                (void) fprintf(stderr,
                                     "Illegal conn: %s\n",
                                     optarg);
                exit(EX_USAGE);
                }

              socket = malloc(strlen(optarg) + 1);
              memcpy(socket, optarg, strlen(optarg) + 1);

              /* 
                 ** If we're using a local socket, make sure it doesn't
                 ** already exist.
               */
              if (strncmp(optarg, "unix:", 5) == 0) {
                unlink(optarg + 5);
                } 
              else if (strncmp(optarg, "local:", 6) == 0) {
                unlink(optarg + 6);
                }
              break;

      case 't':
              if (optarg == NULL || *optarg == '\0') {
                (void) fprintf(stderr,
                                     "Illegal timeout: %s\n",
                                     optarg);
                exit(EX_USAGE);
                }
              if (smfi_settimeout(atoi(optarg)) == MI_FAILURE) {
                (void) fputs("smfi_settimeout failed", stderr);
                exit(EX_SOFTWARE);
                }
              break;

      case 'd':
              debug = TRUE;
              break;

      case 'v':
              verbose = TRUE;
              break;

      case 'l':
              use_syslog = TRUE;
              break;

      case 'h':
      default:
              usage();
              exit(0);
      }     
    }

  retval = 0;
  
  // Cargamos la tabla de dominios
  LoadDomTab(fitabdom,&domdata);

  LoadAllUserTabs(&domdata);

  if( verbose) exit (88);
  // ...  
    
  if (bg) {
    int fderr;
    child = fork();
    // REOPEN stdin, stdout, stderr
    // Else errors are lost and any open will use 0,1,2 and... interfere
    // with "perrors" et al.
    close(STDIN_FILENO);
    open("/dev/null", O_RDONLY);
    close(STDOUT_FILENO);
    fderr=open(fierr, O_CREAT|O_APPEND|O_WRONLY,0750);
    if (fderr<0) { perror("CTOLOC can't open 1 error LOG!"); exit (55); }
    close(STDERR_FILENO);
    fderr=open(fierr, O_CREAT|O_APPEND|O_WRONLY,0750);
    if (fderr<0) { perror("CTOLOC can't open 2 error LOG!"); exit (55); }
    } 
  else { child = 0; }

  if (child == -1) {
    fprintf(stderr, "Error forking\n");
    } 
  else if (child == 0) {
    if (use_syslog) {
      openlog(AppIdent,LOG_NDELAY,LOG_LOCAL2);
      SYSLOG (LOG_INFO, "START");  
      if(debug) SYSLOG (LOG_INFO, "DEBUG enabled");  
      }
    perror("START");
 
    if (smfi_register(smfilter) == MI_FAILURE) {
      SYSLOG (LOG_ERR,"smfi_register failed");
      exit(EX_UNAVAILABLE);
      }

    if (smfi_setconn(socket) == MI_FAILURE) {
      SYSLOG (LOG_ERR,"smfi_setconn failed");
      exit(EX_SOFTWARE);
      }
    
    retval = smfi_main();

    
    // NEVER RETURNS, except crash
    perror("STOP");
    if (use_syslog) { 
      SYSLOG (LOG_INFO, "STOPING");
      closelog();  
      }
    
    // clean up mem ??
    free(socket);
   
    } 
  else { 
    // parent. All channels closed: can't write
    // finished.
    }

  return retval;
}

