/*
 * Q&D type 3binary to type 2 packet converter
 * this isn't as fully featured as it could be, but it's functional,
 * although it could use more error checking.  Note this could be
 * used with PVERT's PKT2QWK to produce (urp) QWK packets from 3binary.
 * Public Domain from M. Kimes
 *
 * command line arguments:
 *    <name-of-type-3binary-pkt> <name-of-type-2-pkt> <my_address>
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "3binary.h"
#include "3addr.h"

struct privdata {
  FILE   *handle;
  char   *fname;
  ADDR   me;
};

/* Packet header structure per FSC-0045 */

struct pkthdr2 {
    unsigned int
        onode,
        dnode,
        opoint,
        dpoint;
    char
        zeros[8];
    unsigned int
        subver,
        version,
        onet,
        dnet;
    char
        product,
        rev_lev,
        password[8];
    unsigned int
        ozone,
        dzone;
    char
        odomain[8],
        ddomain[8];
    long
        specific;
};

/*----------------------------------------------------*/
/* 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          */
/*----------------------------------------------------*/

extern char * type3b_2_fido (BINDATE3 *date);  /* 3date.c */


void process_unknown_chunk3(FILE *handle,USHORT type,long chunklen,int *error) {
  /* dummy */
}

int import_3bmsg (void *priv,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 type 2 packet.
   */

  register char         *p,*pp;
  unsigned int           x;
  struct privdata       *pd;
  char                   pmsg[192];
  CHUNK3                *info;
  long                   pos;
  static long            nummsgs = 0L;
  ADDR                  *taddr = NULL,*faddr = NULL;
  char                  *fname = NULL,*tname = NULL,*area = NULL,
                        *lastquoted = NULL,*lastencrypted = NULL;
  USHORT                 lastquotelen,lastencryptlen;
  long                   attribs,id;

  pd = (struct privdata *)priv;
  *error = NOERR3;

  memset (pmsg, 0, 192);
  *pmsg = 0x02;
  pmsg[1] = 0x00;

  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;
  }
  memcpy (&pmsg[2], &faddr->node, 2);
  memcpy (&pmsg[6], &faddr->net, 2);
  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 = nstrdup3(info->data,info->len);
    if(!area) {
      if(faddr)
        free_address(faddr);
      if(fname)
        free(fname);
      *error = NOMEM3;
      return 0;
    }
  }
  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);
      if(area)
        free(area);
      *error = BADADDR3;
      return 0;
    }
    else {
      memcpy (&pmsg[4], &taddr->node, 2);
      memcpy (&pmsg[8], &taddr->net, 2);
    }
  }
  else {
    memcpy(&pmsg[4],&pd->me.node,2);
    memcpy(&pmsg[8],&pd->me.net,2);
  }
  x = 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)
      x |= MSGPRIVATE;
    if(attribs & ATTR_AUDIT)
      x |= MSGARQ;
    if(attribs & ATTR_RRQ)
      x |= MSGRRQ;
  }
  memcpy (&pmsg[10], &x, 2);
  x = 0;
  memcpy (&pmsg[12], &x, 2);
  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);
    if(area)
      free(area);
    *error = MISSING3;
    return 0;
  }
  pp = type3b_2_fido ((BINDATE3 *)info->data);
  memcpy (&pmsg[14], pp, 20);
  p = &pmsg[34];
  if(!tname)
    strcpy(p,"All");
  else
    strncpy(p,tname,min(strlen(tname),35));
  x = 34;
  while (*p) {
    x++;
    p++;
  }
  p++;
  x++;
  if(fname)
    strncpy (p, fname, min(35,strlen(fname)));
  else
    strcpy(p,"Sysop");
  while (*p) {
    x++;
    p++;
  }
  x++;
  p++;
  info = verify_chunk3_list_item(msg3,SUBJ3);
  if(!info)
    info = verify_chunk3_list_item(globals,SUBJ3);
  if(info)
    strncpy(p,info->data,min(info->len,71));
  while (*p) {
    x++;
    p++;
  }
  x++;
  p++;
  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);
    if(area)
      free(area);
    *error = MISSING3;
    return 0;
  }
  id = *(long *)info->data;
  fwrite (pmsg, x,1,pd->handle);

  if(area) {
    fwrite("AREA:",5,1,pd->handle);
    fwrite(area,strlen(area),1,pd->handle);
    fwrite("\r",1,1,pd->handle);
  }

  fprintf(pd->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,pd->handle);
    fwrite((char *)info->data + 4,info->len - 4,1,pd->handle);
    fprintf(pd->handle,"%lX\r",*(long *)info->data);
  }

  if(!area) {
    fprintf(pd->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,pd->handle);
      fwrite(info->data,info->len,1,pd->handle);
      fwrite("\r",1,1,pd->handle);
    }
  }
  info = verify_chunk3_list_item(msg3,ORIG3);
  if(!info)
    info = verify_chunk3_list_item(globals,ORIG3);
  if(info) {
    fwrite("\01ORIG: ",8,1,pd->handle);
    fwrite(info->data,info->len,1,pd->handle);
    fwrite("\r",1,1,pd->handle);
  }
  info = verify_chunk3_list_item(msg3,ORIGID3);
  if(info) {
    fwrite("\01ORIGID: ",8,1,pd->handle);
    fwrite(info->data,info->len,1,pd->handle);
    fwrite("\r",1,1,pd->handle);
  }

  if(area)
    free(area);
  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,pd->handle);
    fwrite(info->data,info->len,1,pd->handle);
    fwrite("\r",1,1,pd->handle);
  }

  info = msg3;
  while(info) {
    switch(info->type) {
      case TEXT3:
        fwrite(info->data,info->len,1,pd->handle);
        break;
      case CHARSET3:
        fwrite("\r\01CHARSET: ",10,1,pd->handle);
        fwrite(info->data,info->len,1,pd->handle);
        fwrite("\r",1,1,pd->handle);
        break;
      case QUOTE3:
        fwrite("\r  ",3,1,pd->handle);
        if(!*(SHORT *)info->data) {
          if(!lastquoted)   /* error, screw it */
            break;
          fwrite(lastquoted,lastquotelen,1,pd->handle);
        }
        else {
          fwrite((char *)info->data + 2,*(SHORT *)info->data,1,pd->handle);
          lastquoted = (char *)info->data + 2;
          lastquotelen = *(SHORT *)info->data;
        }
        fwrite(":\r",2,1,pd->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,pd->handle);
        break;
/*
      case ENCRYPT3:  // maybe not a good idea; uuencode first?
        fwrite("\r\01ENC: ",5,1,pd->handle);
        if(!*(SHORT *)info->data) {
          if(!lastencrypted)  // error, screw it
            break;
          fwrite(lastencrypted,lastencryptlen,1,pd->handle);
        }
        else {
          fwrite((char *)info->data + 2,*(SHORT *)info->data,1,pd->handle);
          lastencrypted = (char *)info->data + 2;
          lastencryptlen = *(SHORT *)info->data;
        }
        fwrite("\r",1,1,pd->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,pd->handle);
        fwrite("\r\01EENC\r",7,1,pd->handle);
        break;
*/
    }
    info = info->next;
  }

  info = verify_chunk3_list_item(globals,TEXT3);
  if(info)
    fwrite(info->data,info->len,1,pd->handle);

  fwrite("\0\0",3,1,pd->handle);
  pos = ftell(pd->handle);
  if(pos)
    fseek(pd->handle,pos - 2L,SEEK_SET);

  printf("%ld\r",++nummsgs);
  return 0;
}


int check_3bpkthdr (void *priv,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 creates the type 2 packet and
   * writes its header.
   */

  struct privdata *pd;
  struct pkthdr2   pkt2;
  long             pos;
  CHUNK3          *info = NULL;
  char            *area,*name;
  ADDR            *faddr = NULL,*taddr = NULL;

  *error = NOERR3;
  pd = (struct privdata *)priv;
  unlink(pd->fname);

  info = verify_chunk3_list_item(pkt3,PRODUCT3);
  if(!info) {
    *error = BADPKTHDR3;
    return *error;
  }

  info = verify_chunk3_list_item(pkt3,AREA3);
  if(info)
    area = info->data,info->len;

  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;
  }

  pd->handle = fopen(pd->fname,"wb");
  if(pd->handle != NULL) {
    memset(&pkt2,0,sizeof(struct pkthdr2));
    pkt2.version = 2;
    pkt2.subver = 2;
    info = verify_chunk3_list_item(pkt3,PASSWORD3);
    if(info)
      strncpy(pkt2.password,info->data,min(info->len,8));
    pkt2.ozone = faddr->zone;
    pkt2.onet = faddr->net;
    pkt2.onode = faddr->node;
    strncpy(pkt2.odomain,faddr->domain,8);
    if(!area) {
      pkt2.dzone = taddr->zone;
      pkt2.dnet = taddr->net;
      pkt2.dnode = taddr->node;
      pkt2.dpoint = taddr->point;
      strncpy(pkt2.ddomain,taddr->domain,8);
    }
    else {
      pkt2.dzone = pd->me.zone;
      pkt2.dnet =  pd->me.net;
      pkt2.dnode = pd->me.node;
      pkt2.dpoint = pd->me.point;
      strncpy(pkt2.ddomain,pd->me.domain,8);
    }
    fwrite(&pkt2,sizeof(struct pkthdr2),1,pd->handle);
    fwrite("\0\0",3,1,pd->handle);
    pos = ftell(pd->handle);
    if(pos)
      fseek(pd->handle,pos - 3L,SEEK_SET);
  }
  else {
    *error = OPENERR3;
    printf("\n **Open of \"%s\" failed.\n",pd->fname);
  }
  free_address(faddr);
  free_address(taddr);
  return *error;
}



void sort_addresses (ADDR **addr) {

  int   swapped,x;
  ADDR *tempaddr;

  do {
    swapped = 0;
    x = 0;
    while(addr[x + 1]) {
      if(addr[x]->node > addr[x + 1]->node) {
        tempaddr = addr[x];
        addr[x] = addr[x + 1];
        addr[x + 1] = tempaddr;
        swapped++;
      }
      x++;
    }
  } while(swapped);
  do {
    swapped = 0;
    x = 0;
    while(addr[x + 1]) {
      if(addr[x]->net > addr[x + 1]->net) {
        tempaddr = addr[x];
        addr[x] = addr[x + 1];
        addr[x + 1] = tempaddr;
        swapped++;
      }
      x++;
    }
  } while(swapped);
}


void end_3bmsg (void *priv,CHUNK3 *pkt3,CHUNK3 *msg3,CHUNK3 *globals) {

  /*
   * this function is called after import_3bmsg to do "clean up"
   * in this case, it allows for adding those nasty seen-bys to type 2
   */

  struct privdata *pd;
  long             pos;
  char             seenby[81];
  ADDR             *addr[5];
  int              x,y;
  CHUNK3          *info;
  char            *area = NULL,*name;
  ADDR            *faddr = NULL,*taddr = NULL,*paddr = NULL;

  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,info->len;

  if(!area)
    return;

  pd = (struct privdata *)priv;

  info = verify_chunk3_list_item(msg3,FROM3);
  if(!info)
    verify_chunk3_list_item(globals,FROM3);
  if(info) {
    addr_from_3binaddr(info->data,info->len,&faddr,&name);
    if(name)
      free(name);
  }
  info = verify_chunk3_list_item(msg3,TO3);
  if(!info)
    verify_chunk3_list_item(globals,TO3);
  if(info) {
    addr_from_3binaddr(info->data,info->len,&taddr,&name);
    if(name)
      free(name);
  }
  info = verify_chunk3_list_item(pkt3,FROM3);
  if(info) {
    addr_from_3binaddr(info->data,info->len,&paddr,&name);
    if(name)
      free(name);
  }
  x = 0;
  if(faddr)
    addr[x++] = faddr;
  if(taddr)
    addr[x++] = taddr;
  if(paddr)
    addr[x++] = paddr;
  addr[x++] = &pd->me;
  for(;x < 5;x++)
    addr[x] = NULL;
  x = 0;
  while(addr[x]) {  /* remove duplicate addresses (in 2-D) */
    y = 0;
    while(addr[y]) {
      if(x != y && (addr[x]->net == addr[y]->net &&
         addr[x]->node == addr[y]->node) || !addr[x]->net)
        break;
      y++;
    }
    if(addr[y] && addr[x]) { /* remove this one */
      y = x;
      while(addr[y]) {
        addr[y] = addr[y + 1];
        y++;
      }
      continue;
    }
    x++;
  }
  if(addr[0]) {
    pos = ftell(pd->handle);
    if(pos)
      fseek(pd->handle,pos - 1L,SEEK_SET);
    fprintf(pd->handle,"\r---\r * Origin: (%s#%u:%u/%u",faddr->domain,faddr->zone,
            faddr->net,faddr->node);
    if(faddr->point)
      fprintf(pd->handle,"%u",faddr->point);
    fwrite(")",1,1,pd->handle);

    sort_addresses(addr);
    strcpy(seenby,"\rSEEN-BY");
    x = 0;
    while(addr[x]) {
      if(!x || addr[x]->net != addr[x - 1]->net)
        sprintf(&seenby[strlen(seenby)]," %u/%u",addr[x]->net,addr[x]->node);
      else
        sprintf(&seenby[strlen(seenby)]," %u",addr[x]->node);
      x++;
    }
    strcat(seenby,"\r");
    fwrite(seenby,strlen(seenby),1,pd->handle);
  }
  fwrite("\0\0",3,1,pd->handle);
  pos = ftell(pd->handle);
  if(pos)
    fseek(pd->handle,pos - 2L,SEEK_SET);

  if(faddr)
    free_address(faddr);
  if(taddr)
    free_address(taddr);
  if(paddr)
    free_address(paddr);
  return;
}


int main (int argc,char *argv[]) {

  int error;
  struct privdata pd;

  if(argc < 4) {
    printf("\n\n3bto2 packet converter\n"
           "Converts type 3binary packets to type 2\n"
           "\nUsage:  3bto2 <type3bpacket> <type2packet> <to_address>\n"
           "\n<to_address> format:  Domain[.sub[.sub]]#Zone:Net/Node[.Point]\n\n");
    return -1;
  }

  memset(&pd,0,sizeof(struct privdata));
  pd.handle = NULL;
  pd.fname = argv[2];
  if(parse_addr3(argv[3],&pd.me)) {
    printf("\nError in <to_address>\n");
    return BADADDR3;
  }

  printf("\n3bto2 packet converter\n%s -=> %s\n",argv[1],argv[2]);

  printf("\n\n ** Converted %ld msgs\n",
         process_pkt3((void *)&pd,argv[1],&error));

  if(error != NOERR3)
    error3(error);

  if(pd.handle != NULL)
    fclose(pd.handle);

  return error;
}
