/*
 * Q&D type 3 binary to *.MSG
 * example import code
 * Public Domain from M. Kimes
 *
 * command line arguments:  <name-of-type-3binary-pkt> <netmail directory> [areas.bbs]
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "3binary.h"
#include "3addr.h"

typedef struct {        /* per FTS-0001 */
      char   from[36];
      char   to[36];
      char   subj[72];
      char   date[20];
      USHORT timesread;
      USHORT d_node;
      USHORT o_node;
      USHORT cost;
      USHORT o_net;
      USHORT d_net;
      USHORT d_zone;
      USHORT o_zone;
      USHORT d_point;
      USHORT o_point;
      USHORT replyto;
      USHORT attr;
      USHORT nextreply;
} FIDOMSG;

/*----------------------------------------------------*/
/* FIDO Message attributes                            */
/*----------------------------------------------------*/
#define MSGPRIVATE 0x0001  /* private message,        */
#define MSGCRASH   0x0002  /* accept for forwarding   */
#define MSGREAD    0x0004  /* read by addressee       */
#define MSGSENT    0x0008  /* sent OK (remote)        */
#define MSGFILE    0x0010  /* file attached to msg    */
#define MSGFWD     0x0020  /* being forwarded         */
#define MSGORPHAN  0x0040  /* unknown dest node       */
#define MSGKILL    0x0080  /* kill after mailing      */
#define MSGLOCAL   0x0100  /* FidoNet vs. local       */
#define MSGXX1     0x0200  /*                         */
#define MSGXX2     0x0400  /* STRIPPED by FidoNet<tm> */
#define MSGFRQ     0x0800  /* file request            */
#define MSGRRQ     0x1000  /* receipt requested       */
#define MSGCPT     0x2000  /* is a return receipt     */
#define MSGARQ     0x4000  /* audit trail requested   */
#define MSGURQ     0x8000  /* update request          */
/*----------------------------------------------------*/

typedef struct AREAS {
  char *area;
  char *directory;
  /* nodes we feed could be kept here to flesh this out... */
  struct AREAS *next;
} AREAS;

typedef struct {
  char  *directory;
  AREAS *areas;
} PUBDATA;

extern char * type3b_2_fido (BINDATE3 *date);  /* 3date.c */


void process_unknown_chunk3(FILE *handle,USHORT type,long chunklen,int *error) {
  /* dummy; ignore unknown master types */
}


int import_3bmsg (void *pdata,CHUNK3 *pkt3,CHUNK3 *msg3,CHUNK3 *globals,
                  long msglen,int *error) {

  /*
   * this function is called to import/forward the message
   * in this case, it writes a header and message text to
   * a *.MSG file.
   */

  register char         *pp;
  PUBDATA               *pd = (PUBDATA *)pdata;
  FIDOMSG                fmsg;
  CHUNK3                *info;
  static long            nummsgs = 0L,msgnum = 3L;
  ADDR                  *taddr = NULL,*faddr = NULL;
  char                  *fname = NULL,*tname = NULL,*area = NULL,build[257],
                        *directory = NULL,*lastquoted = NULL,
                        *lastencrypted = NULL;
  USHORT                 lastquotelen,lastencryptlen;
  long                   attribs,id;
  FILE                  *handle;

  *error = NOERR3;
  memset (&fmsg, 0, sizeof(FIDOMSG));
  info = verify_chunk3_list_item(msg3,AREA3);
  if(!info)
    info = verify_chunk3_list_item(globals,AREA3);
  if(!info)
    info = verify_chunk3_list_item(pkt3,AREA3);
  if(info)
    area = info->data;
  if(area) {  /* find area in our list */

    AREAS *step = pd->areas;

    while(step) {
      if(!strnicmp(step->area,area,info->len) &&
         info->len == (SHORT)strlen(step->area)) {
        directory = step->directory;
        break;
      }
      step = step->next;
    }
  }
  else  /* netmail */
    directory = pd->directory;

  if(!directory) {  /* wasn't a good area */

    /*
     * what we do here is build a packet named UNKNOWN.$$$ containing any
     * messages for areas we don't know about.  We can try to process it
     * later in case the sysop adds the areas to AREAS.BBS
     */

    FILE *badhandle;
    int   ierror;

    ierror = NOERR3;
    badhandle = wopen_pkt3("UNKNOWN.$$$",pkt3,&ierror);
    if(badhandle) {
      if(globals)  /* not terribly efficient, but quick */
        write_chunk3_list(badhandle,GLOBAL3,globals,&ierror);
      if(ierror == NOERR3)
        write_chunk3_list(badhandle,MSG3,msg3,&ierror);
      badhandle = wclose_pkt3(badhandle);
    }
    *error = BADAREA3;
    return 0;
  }

  /* get FROM chunk */
  info = verify_chunk3_list_item(msg3,FROM3);
  if(!info)
    info = verify_chunk3_list_item(globals,FROM3);
  if(!info) {
    *error = MISSING3;
    return 0;
  }
  if(addr_from_3binaddr(info->data,info->len,&faddr,&fname) != NOERR3 ||
     !faddr) {
    if(faddr)
      free_address(faddr);
    if(fname)
      free(fname);
    *error = BADADDR3;
    return 0;
  }
  fmsg.o_node = faddr->node;
  fmsg.o_zone = faddr->zone;
  fmsg.o_point = faddr->point;
  fmsg.o_net = faddr->net;

  /* get TO chunk */
  info = verify_chunk3_list_item(msg3,TO3);
  if(!info)
    info = verify_chunk3_list_item(globals,TO3);
  if(info)
    addr_from_3binaddr(info->data,info->len,&taddr,&tname);
  if(!area) {
    if(!taddr) {
      if(faddr)
        free_address(faddr);
      if(fname)
        free(fname);
      if(tname)
        free(tname);
      *error = BADADDR3;
      return 0;
    }
    else {
      fmsg.d_node = taddr->node;
      fmsg.d_net = taddr->net;
      fmsg.d_zone = taddr->zone;
      fmsg.d_point = taddr->point;
    }
  }
  else {
    if(taddr) {
      fmsg.d_node = taddr->node;
      fmsg.d_net = taddr->net;
      fmsg.d_point = taddr->point;
      fmsg.d_zone = taddr->zone;
    }
  }

  /* get ATTR chunk */
  fmsg.attr = 0;
  info = verify_chunk3_list_item(msg3,ATTRIB3);
  if(!info)
    info = verify_chunk3_list_item(globals,ATTRIB3);
  if(info) {
    attribs = *(long *)info->data;
    if(attribs & ATTR_PRIVATE)
      fmsg.attr |= MSGPRIVATE;
    if(attribs & ATTR_AUDIT)
      fmsg.attr |= MSGARQ;
    if(attribs & ATTR_RRQ)
      fmsg.attr |= MSGRRQ;
  }
  fmsg.cost = 0;

  /* get DATE chunk */
  info = verify_chunk3_list_item(msg3,DATE3);
  if(!info) {
    if(faddr)
      free_address(faddr);
    if(fname)
      free(fname);
    if(taddr)
      free_address(taddr);
    if(tname)
      free(tname);
    *error = MISSING3;
    return 0;
  }
  pp = type3b_2_fido ((BINDATE3 *)info->data);
  strcpy (fmsg.date, pp);

  if(!tname)
    strcpy(fmsg.to,"All");
  else
    strncpy(fmsg.to,tname,min(strlen(tname),36));
  fmsg.to[35] = 0;
  if(fname)
    strncpy (fmsg.from, fname, min(35,strlen(fname)));
  else
    strcpy(fmsg.from,"Sysop");
  fmsg.from[35] = 0;

  /* get SUBJECT chunk */
  info = verify_chunk3_list_item(msg3,SUBJ3);
  if(!info)
    info = verify_chunk3_list_item(globals,SUBJ3);
  if(info)
    strncpy(fmsg.subj,info->data,min(info->len,72));
  fmsg.subj[71] = 0;

  /* get ID chunk */
  info = verify_chunk3_list_item(msg3,ID3);
  if(!info) {
    if(faddr)
      free_address(faddr);
    if(fname)
      free(fname);
    if(taddr)
      free_address(taddr);
    if(tname)
      free(tname);
    *error = MISSING3;
    return 0;
  }
  id = *(long *)info->data;

  { /* don't know any portable way to do this one... */
    /* build a *.MSG filename */
    struct stat st;

    do {
      sprintf(build,"%s/%u.MSG",directory,msgnum++);
    } while(!stat(build,&st) && msgnum > 0L);
    if(msgnum < 0L) {
      if(faddr)
        free_address(faddr);
      if(fname)
        free(fname);
      if(taddr)
        free_address(taddr);
      if(tname)
        free(tname);
      *error = MISSING3;
      return -1;
    }
    handle = fopen(build,"wb");
    if(!handle) {
      if(faddr)
        free_address(faddr);
      if(fname)
        free(fname);
      if(taddr)
        free_address(taddr);
      if(tname)
        free(tname);
      *error = OPENERR3;
      return 0;
    }
  }

  fwrite(&fmsg,sizeof(FIDOMSG),1,handle);

  fprintf(handle,"\01MSGID: %s#%u:%u/%u.%u %lX\r",
          faddr->domain,faddr->zone,faddr->net,faddr->node,
          faddr->point,id);

  info = verify_chunk3_list_item(msg3,REF3);
  if(info) {
    fwrite("\01REPLY: ",8,1,handle);
    fwrite((char *)info->data + 4,info->len - 4,1,handle);
    fprintf(handle,"%lX\r",*(long *)info->data);
  }

  if(!area) {
    fprintf(handle,"\01MSGTO: %s#%u:%u/%u.%u\r",
            taddr->domain,taddr->zone,taddr->net,taddr->node,
            taddr->point);
    info = verify_chunk3_list_item(msg3,DEST3);
    if(!info)
      info = verify_chunk3_list_item(globals,DEST3);
    if(info) {
      fwrite("\01DEST: ",8,1,handle);
      fwrite(info->data,info->len,1,handle);
      fwrite("\r",1,1,handle);
    }
  }
  info = verify_chunk3_list_item(msg3,ORIG3);
  if(!info)
    info = verify_chunk3_list_item(globals,ORIG3);
  if(info) {
    fwrite("\01ORIG: ",8,1,handle);
    fwrite(info->data,info->len,1,handle);
    fwrite("\r",1,1,handle);
  }
  info = verify_chunk3_list_item(msg3,ORIGID3);
  if(info) {
    fwrite("\01ORIGID: ",8,1,handle);
    fwrite(info->data,info->len,1,handle);
    fwrite("\r",1,1,handle);
  }

  if(taddr)
    free_address(taddr);
  if(faddr)
    free_address(faddr);
  if(tname)
    free(tname);
  if(fname)
    free(fname);

  info = verify_chunk3_list_item(globals,CHARSET3);
  if(info) {
    fwrite("\01CHARSET: ",9,1,handle);
    fwrite(info->data,info->len,1,handle);
    fwrite("\r",1,1,handle);
  }

  info = msg3;
  while(info) {
    switch(info->type) {
      case TEXT3:
        fwrite(info->data,info->len,1,handle);
        break;
      case CHARSET3:
        fwrite("\r\01CHARSET: ",10,1,handle);
        fwrite(info->data,info->len,1,handle);
        fwrite("\r",1,1,handle);
        break;
      case QUOTE3:
        fwrite("\r  ",3,1,handle);
        if(!*(SHORT *)info->data) {
          if(!lastquoted)   /* error, screw it */
            break;
          fwrite(lastquoted,lastquotelen,1,handle);
        }
        else {
          fwrite((char *)info->data + 2,*(SHORT *)info->data,1,handle);
          lastquoted = (char *)info->data + 2;
          lastquotelen = *(SHORT *)info->data;
        }
        fwrite(":\r",2,1,handle);
        fwrite((char *)info->data + (*(SHORT *)info->data) + sizeof(SHORT) +
               (*(SHORT *)info->data % 2) + 2,
               *(SHORT *)info->data + (*(SHORT *)info->data) + sizeof(SHORT) +
               (*(SHORT *)info->data % 2),1,handle);
        break;
/*
      case ENCRYPT3:  // maybe not a good idea; uuencode first?
        fwrite("\r\01ENC: ",5,1,handle);
        if(!*(SHORT *)info->data) {
          if(!lastencrypted)  // error, screw it
            break;
          fwrite(lastencrypted,lastencryptlen,1,handle);
        }
        else {
          fwrite((char *)info->data + 2,*(SHORT *)info->data,1,handle);
          lastencrypted = (char *)info->data + 2;
          lastencryptlen = *(SHORT *)info->data;
        }
        fwrite("\r",1,1,handle);
        fwrite((char *)info->data + (*(SHORT *)info->data) + sizeof(SHORT) +
               (*(SHORT *)info->data % 2) + 2,
               *(SHORT *)info->data + (*(SHORT *)info->data) + sizeof(SHORT) +
               (*(SHORT *)info->data % 2),1,handle);
        fwrite("\r\01EENC\r",7,1,handle);
        break;
*/
    }
    info = info->next;
  }

  info = verify_chunk3_list_item(globals,TEXT3);
  if(info)
    fwrite(info->data,info->len,1,handle);

  fwrite("",1,1,handle);  /* nul-terminate */

  printf("%ld\r",++nummsgs);
  fclose(handle);
  return 0;
}


int check_3bpkthdr (void *pdata,char *fname,CHUNK3 *pkt3,CHUNK3 *globals,
                    long hdrlen,int *error) {

  /*
   * this "callback" function should check packet "header" and decide
   * if you want to process the packet.  return 0 if so, anything
   * else if not.  in this case it just checks for valid from/to
   * addresses
   */

  CHUNK3          *info = NULL;
  char            *area,*name;
  PUBDATA         *pd = (PUBDATA *)pdata;
  ADDR            *faddr = NULL,*taddr = NULL;

  *error = NOERR3;

  info = verify_chunk3_list_item(pkt3,PRODUCT3);
  if(!info) {
    *error = BADPKTHDR3;
    return *error;
  }

  info = verify_chunk3_list_item(pkt3,AREA3);
  if(info) {

    AREAS *step = pd->areas;

    area = info->data,info->len;
    while(step) {
      if(!strnicmp(step->area,area,info->len) &&
         info->len == (SHORT)strlen(step->area))
        break;
      step = step->next;
    }
    if(!step) {  /* wasn't a good area */
      *error = BADAREA3;
      return *error;
    }
  }

  info = verify_chunk3_list_item(pkt3,FROM3);
  if(info) {
    addr_from_3binaddr(info->data,info->len,&faddr,&name);
    if(name)
      free(name);
  }
  info = verify_chunk3_list_item(pkt3,TO3);
  if(info) {
    addr_from_3binaddr(info->data,info->len,&taddr,&name);
    if(name)
      free(name);
  }

  if(!faddr || (!taddr && !area) || !faddr->zone || !faddr->net ||
     !faddr->domain || (taddr && (!taddr->zone || !taddr->net ||
     !taddr->domain))) {
    printf("\n **Grunged type 3b packet header address(es)\n");
    *error = BADPKTHDR3;
    free_address(faddr);
    free_address(taddr);
    return *error;
  }
  free_address(faddr);
  free_address(taddr);
  return *error;
}


void end_3bmsg (void *priv,CHUNK3 *pkt3,CHUNK3 *msg3,CHUNK3 *globals) {

  /*
   * this function is called after import_3bmsg to do "clean up"
   * don't need it here; this is a dummy.
   */
}


AREAS * load_areasbbs (char *filename) {

  FILE  *handle;
  char   s[257],*p,*area,*dir;
  AREAS *head = NULL,*info,*last = NULL;

  handle = fopen(filename,"rt");
  if(handle) {
    fgets(s,256,handle);
    while(!feof(handle)) {
      if(!fgets(s,256,handle))
        break;
      if(s[strlen(s) - 1] == '\n')
        s[strlen(s) - 1] = 0;
      p = strchr(s,';');
      if(p)
        *p = 0;
      p = &s[strlen(s) - 1];
      while(p > s && (*p == ' ' || *p == '\t')) {
        *p = 0;
        p--;
      }
      p = s;
      while(*p && (*p == ' ' || *p == '\t'))
        p++;
      if(p != s)
        memmove(s,p,strlen(p) + 1);
      if(!*s)
        continue;
      area = strtok(s," ");
      dir = strtok(NULL," ");
      if(area && dir) {
        info = malloc(sizeof(AREAS));
        if(info) {
          info->area = malloc(strlen(area) + 1);
          if(info->area) {
            strcpy(info->area,area);
            info->directory = malloc(strlen(dir) + 1);
            if(info->directory) {
              strcpy(info->directory,dir);
              if(!head)
                head = info;
              if(last)
                last->next = info;
              last = info;
              info->next = NULL;
            }
            else {
              free(info->area);
              free(info);
              break;
            }
          }
          else {
            free(info);
            break;
          }
        }
        else
          break;
      }
    }
  }
  return head;
}


int main (int argc,char *argv[]) {

  int      error;
  PUBDATA pd;

  if(argc < 3) {
    printf("\n\n3btomsg importer\n"
           "Imports type 3binary packets to *.MSG files\n"
           "\nUsage:  3btomsg <type3bpacket> <netmail directory> [areas.bbs]\n");
    return -1;
  }

  printf("\n3btomsg importer\n%s -=> %s%s%s%s\n",argv[1],argv[2],
         (argv[3]) ? " (" : "",(argv[3]) ? argv[3] : "",(argv[3]) ? ")" : "");

  pd.areas = load_areasbbs((argv[3]) ? argv[3] : "AREAS.BBS");
  if(!pd.areas)
    printf("\n *Processing netmail only\n");
  pd.directory = argv[2];

  printf("\n\n ** Imported %ld msgs\n",
         process_pkt3((void *)&pd,argv[1],&error));

  if(error != NOERR3)
    error3(error);

  rename("UNKNOWN.$$$","UNKNOWN.3PN");

  return error;
}
