#ifdef REFERENCES_MSC96
/* Projet References -- Generation de References: correctes pour ifmail
 * Auteur: Marc SCHAEFER <schaefer@alphanet.ch>
 * Date creation: 19/02/96
 * Date modification: 14/06/97
 * V1.0 PV003 MSC97
 * DESCRIPTION
 *    Lorsqu'on recoit des articles de news a destination de FTN on
 *    stocke dans la base DBM ref_db
 *       o une cle: le MSG-ID FTN
 *       o une valeur: le Message-ID: RFC-1036
 *    Il y a une semantique de remplacement.
 *    Si un message Usenet correspond a plusieurs messages FTN, toutes
 *    les entrees necessaires sont stockees: les parties supplementaires
 *    d'un grand message sont donc vues comme duplicates (j'appelle cela
 *    une feature).
 *    Lorsqu'on recoit un article EchoMail FTN, on recherche dans la base
 *    pour le champ Message-ID: et le champ References:
 *
 *    Avantages: meilleure detection des doublons (en particulier multi-parts)
 *               References: correctes.
 *
 *          FTN  1 ------- 1  RFC
 *    La base doit etre scannee periodiquement pour y supprimer les entrees
 *    plus vieille qu'une certaine duree (au lieu de scanner history).
 *
 *    Si une association n'est pas trouvee, on garde l'ancien MSGID/REF
 *    FTN (qui est mieux que rien). Cela ne devrait se produire que
 *    si l'association n'est plus dans la base, donc si ref_cleanup
 *    l'a supprime, et donc l'article n'est plus dans l'history.
 * NOTES
 *    - utilise dbm BSD43 donc -L/usr/ucblib -ldbm, cf Makefile
 *    - ifmail 2.8patche ne genere pas pour les References: ce qui
 *      n'est pas important (ils ont aussi passe), ni pour les Message-ID
 *      en reverse (ce qui empeche des controles de loop etendus).
 *    - CHECK_IN_HISTORY n'est pas implemente correctement, et serait d'une
 *      complexite tres grande. L'expiration se base sur une date maintenue
 *      dans la base. 
 * ASSUMPTIONS
 *    - rfcmsgid and ftnmsgid are null-terminated strings.
 * BUGS
 *    - locking
 *    - dbm errmsg not printed
 *    - pas de parcoureur
 *    - dbm_delete ne compacte pas.
 *    - MAXDAYS devrait etre configurable
 * TODO
 *    - implement locking in ref_init/ref_deinit with
 *      waiting for delocking.
 * MODIFICATION-HISTORY
 *    PV002           schaefer implemented cleanup correctly; especially
 *                             added some debugging information and bugfix
 *                             for end detection. See also cleanup_ref.c
 *    PV003  14/06/97 schaefer interface change see ref_interface.h
 */

#include <sys/types.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#ifdef HAS_NDBM_H
#include <fcntl.h>
#include <ndbm.h>
#else
#include <dbm.h>
#endif
#include "config.h"
#include "ref_interface.h"
#ifdef TESTING
#include "lutil.h"
#endif

#define MAXBUFLEN 1024
#define DAYSEC 86400
#define MAXDAYS 30

#define CHECK_DBASE(dbase, txt, x) if (!dbase) {\
                                      fprintf(stderr,\
                                      "FATAL: ref.c: %s: dbase not opened.\n",\
                                      txt);\
                                      x\
                                   }

#ifdef CHECK_IN_HISTORY
/* OBSOLETE (and will not work anymore because of changes in some
 * structures)
 */
static int in_history(char *history, char *name, int len) {
   FILE *f;
   char buf[MAXBUFLEN];
   int result = 0;

   f = fopen(history, "r");
   if (f) {
      char *s;
      while (!result && fgets(buf, MAXBUFLEN, f)) {
        s = strchr(buf, '\t');
        if (s && ((s - buf) == len)) {
           *s = '\0';
           if (!strncmp(name, buf, len)) {
              result = -1;
#ifdef DEBUG
              printf("found %s.\n", buf);
#endif
           }
        }
      }
      fclose(f);
   }
   else {
      result = -1;
   }
   return result;
}
#endif

ref_private_t *ref_init(char *dbm_file_name) {
   DBM *dbase;

   dbase = dbm_open(dbm_file_name, O_RDWR|O_CREAT, 0600);

   return (ref_private_t *) dbase;
}

int ref_store(ref_private_t *dbase, char *ftn_msgid, char *rfc_msgid) {
   time_t maintenant = time(NULL);

   return ref_store_extended(dbase, ftn_msgid, rfc_msgid, maintenant);
}

int ref_store_extended(ref_private_t *dbase,
		       char *ftn_msgid,
		       char *rfc_msgid,
		       time_t save_time) {
   datum value;
   datum key;
   char buf[MAXBUFLEN];
   int rfc_msgid_len = strlen(rfc_msgid);

   CHECK_DBASE(dbase, "ref_store", return 0;);

   if (MAXBUFLEN < rfc_msgid_len + sizeof(time_t)) {
      fprintf(stderr, "FATAL: ref.c: ref_store: internal limit.\n");
      return 0;
   }

   /* Nous stockons sans le zero final. Pour DBM, il s'agit
    * de structures (pas forcement des chaines terminees par
    * zero). Nous mettons d'abord le temps, puis la chaine.
    */

   key.dptr = ftn_msgid;
   key.dsize = strlen(ftn_msgid);

   value.dptr = buf;
   memcpy(buf, &save_time, sizeof(time_t));
   strcpy(buf + sizeof(time_t), rfc_msgid);
   value.dsize = rfc_msgid_len + sizeof(time_t);

   if (dbm_store(dbase, key, value, DBM_REPLACE)) { /* politique de
                                                     * remplacement
                                                     */
      return 0;
   }
   else {
      return -1;
   }
}

int ref_get(ref_private_t *dbase,
            char *ftn_msgid,
            char *rfc_msgid,
            int rfc_msgid_len) {
   time_t ignored;

   return ref_get_extended(dbase, ftn_msgid, rfc_msgid, rfc_msgid_len,
                                  &ignored);
}

int ref_get_extended(ref_private_t *dbase,
		     char *ftn_msgid,
		     char *rfc_msgid,
		     int rfc_msgid_len,
                     time_t *saved_time) {
   datum value;
   datum key;

   CHECK_DBASE(dbase, "ref_get", return 0;);

   key.dptr = ftn_msgid;
   key.dsize = strlen(ftn_msgid);
   value = dbm_fetch(dbase, key);

   if (value.dptr) {
      /* Nous devons compter avec le zero final */
      if (rfc_msgid_len > (value.dsize - sizeof(time_t))) {
         memcpy(saved_time, value.dptr, sizeof(time_t));
         strncpy(rfc_msgid, value.dptr + sizeof(time_t),
                 value.dsize - sizeof(time_t));
         rfc_msgid[value.dsize - sizeof(time_t)] = '\0';
         return -1;
      }
      else {
         fprintf(stderr,
            "ERROR: ref: ref_get: size too small for rfc_msgid.\n");
         return 0; /* taille trop petite */
      }
   }
   else {
      return 0; /* entree non trouvee */
   }
}

void ref_deinit(ref_private_t *dbase) {
   CHECK_DBASE(dbase, "ref_deinit", return;);
   dbm_close(dbase);
}

int ref_first_key(ref_private_t *dbase, char *ftnmsgid, int len) {
   datum key;

   CHECK_DBASE(dbase, "ref_first_key", return 0;);

   key = dbm_firstkey(dbase);

   /* I don't know what this is for START */
   if (key.dsize < 0) {
      return 0;
   }

   if ((key.dsize + 1) > len) { /* y.c. zero */
      return 0;
   }

   memcpy(ftnmsgid, key.dptr, key.dsize);
   ftnmsgid[key.dsize] = '\0';
   return -1;
}

int ref_next_key(ref_private_t *dbase, char *ftnmsgid, int len) {
   datum key;
   CHECK_DBASE(dbase, "ref_next_key", return 0;);

   key = dbm_nextkey(dbase);

   /* I don't know what this is for START */
   if ((key.dsize < 0) || (key.dptr == NULL)) {
      return 0;
   }

   if ((key.dsize + 1) > len) { /* y.c. zero */
      return 0;
   }

   memcpy(ftnmsgid, key.dptr, key.dsize);
   ftnmsgid[key.dsize] = '\0';
   return -1;
}


/* BUGS
 *    - Should use the key functions
 */
int ref_cleanup(ref_private_t *dbase, char *history) {
   datum value, key, new_key;
   int result = -1;
   char buf[MAXBUFLEN];
   char buf2[MAXBUFLEN];
   char buf3[MAXBUFLEN];
   time_t nouveau = time(NULL);
   time_t ancien;

   CHECK_DBASE(dbase, "ref_cleanup", return 0;);

   if (1) { /* was dbm open from history -- not working */
      key = dbm_firstkey(dbase);

      while (key.dptr != NULL) {
         int todel = 0;

#ifdef DEBUG
         fwrite("? ", 2, 1, stdout);
         fwrite(key.dptr, key.dsize, 1, stdout);
         fwrite(".\n", 2, 1, stdout);
#endif
         value = dbm_fetch(dbase, key);
   
         /* Attention: les chaines ne sont pas terminees par zero.
          * Cette magouille est necessaire a cause des allocations
          * statiques par DBM.
          */

         if ((value.dsize - sizeof(time_t)) <= MAXBUFLEN) {
            strncpy(buf, value.dptr + sizeof(time_t),
                    value.dsize - sizeof(time_t));
            memcpy(&ancien, value.dptr, sizeof(time_t));
            if (key.dsize <= MAXBUFLEN) {
               strncpy(buf2, key.dptr, key.dsize);
               key.dptr = buf2;
#ifdef DEBUG
               fwrite("chk ", 4, 1, stdout);
               fwrite(buf, value.dsize - sizeof(time_t), 1,
                      stdout);
               fwrite(".\n", 2, 1, stdout);
#endif
#ifdef CHECK_IN_HISTORY
               /* Une erreur dans in_history doit retourner -1 !! */
               if (!in_history(history, buf, value.dsize)) {
                  todel = -1;
               }
#else
               /* Verifie la date */
               if (ancien <= nouveau) {
                  int days;

                  if ( (days = ((nouveau - ancien) / DAYSEC)) > MAXDAYS) {
                     todel = -1;
#ifdef DEBUG
                     printf("will delete because %d days.\n", days);
#endif
                  }
#ifdef DEBUG
                  else {
                     printf("will NOT delete, days=%d.\n", days);
                  }
#endif
               }
               else {
                  fprintf(stderr,
                          "ERROR: ref.c: ref_cleanup: time collapsus now %ld before %ld\nTO FIX: call Einstein for more information.\n", nouveau, ancien);
                  result = 0;
               }
#endif
	     }
            else {
               fprintf(stderr,
                       "FATAL: ref.c: ref_cleanup: built-in limits 1.\nTO FIX: increase MAXBUFLEN to %d.\n", key.dsize);
               result = 0;
            }
	 }
         else {
            fprintf(stderr,
                    "FATAL: ref.c: ref_cleanup: built-in limits 2.\nTO FIX: increase MAXBUFLEN to %d.\n", (value.dsize - sizeof(time_t)));
            result = 0;
         }

         new_key = dbm_nextkey(dbase); /* overwrites dptr !! */
         /* I don't know what this is for START */
         if (new_key.dsize < 0) {
            break;
	 }
         /* I don't know what this is for END.
          * NOTES
          *    - Without it, it does not work correctly.
          */

         if (new_key.dptr && (new_key.dsize <= MAXBUFLEN)) {
            strncpy(buf3, new_key.dptr, new_key.dsize);
            new_key.dptr = buf3; /* parce que dbm_delete utilise probablement
                                  * la zone interne.
                                  */
         }
         else {
            if (new_key.dptr) {
	       fprintf(stderr,
		       "FATAL: ref.c: ref_cleanup: built-in limits 3.\nTO FIX: increase MAXBUFLEN to %d.\n", new_key.dsize);
	       result = 0;
            }
         }

         if (todel) {
#ifdef DEBUG
            fwrite("del ", 4, 1, stdout);
            fwrite(key.dptr, key.dsize, 1, stdout);
            fwrite(".\n", 2, 1, stdout);
#endif
            if (dbm_delete(dbase, key)) {
               fprintf(stderr, "ERROR: ref.c: ref_cleanup: delete error.\n");
               result = 0;
            }
	 }

         key.dptr = new_key.dptr;
         key.dsize = new_key.dsize;

         if (key.dsize < 0) { /* Is that necessary ??? */
            break;
         }
      }
   }
   else {
      fprintf(stderr, "ERROR: ref.c: ref_cleanup: can't open %s.\n", history);
      result = 0;
   }
   
   return result;
}

int ref_store_msgid(ref_private_t *ref_dbase, ftnmsg *fmsg, char *p)
{
	int value=0;
	char buf[1024];
	char buf2[1024];
	char *start = strchr(p, '<');
	char *end   = strchr(p, '>');
	time_t maintenant = time(NULL);

	if (start && end && ((end-start) + 2) <= 1024)
	{
		strncpy(buf, start, (end-start) + 1);
		buf[(end-start) +1] = '\0';
		start = strchr(p, '@');
		sprintf(buf2, "<%lu%s", fmsg->msgid_n, start);
		buf2[strlen(buf2)-1] = '\0';
#ifdef TESTING
		loginf("RFC-1036-Message-ID: %s.\n", buf);
		loginf("FTN-MSGID: %s %lu.\n", S(fmsg->msgid_a), fmsg->msgid_n);
		loginf("ftn Message-ID: %s.\n", buf2);
#endif
		value=ref_store_extended(ref_dbase, buf2, buf, maintenant);
	}
	return value;
}
#endif
