/*
 * $Id: jam2mbox.c,v 1.12 2001/01/22 09:32:34 lord Exp $
 *
 * Utility to convert JAM message base to USENET messges
 * stored in MBOX file.
 *
 * Author: Vadim Zaliva <lord@crocodile.org>
 * Based on Mats Wallin & Mats Birch JAM Utility.
 * Uses JAM C API by Joaquim Homrighausen, Andrew Milner, Mats Birch, and
 * Mats Wallin.
 *
 * Written for OBEC.PACTET WWW gatway project:
 * http://www.crocodile.org/software.html
 */

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/param.h>
#include <errno.h>

#include "jammb.h"

#include "jam2mbox.h"

extern char *strdup(const char *s);

#define XATOU_ERR (-1)
static int xatou(const char *);

static UINT32 filelength( int fh );
static int ProcessMessage(int n, FILE *o);
static int ConvertText(char *buf, size_t len);
static CHAR8 * GetSubFldName( JAMSUBFIELD * pSubFld );
static CHAR8 * GetSubFldStr( JAMSUBFIELD * pSubFld );
static char * domain_from_FTN(char *msgid_f);

int main( int argc, CHAR8 * argv [] )
{
    CHAR8 JamFileName  [PATH_MAX+NAME_MAX];
    CHAR8 MboxFileName [PATH_MAX+NAME_MAX];
    INT32 MsgNo, TotalMsgs;
    FILE *o;
    
    fprintf(stderr,  "JAM2MBOX\n");

    if( argc < 3 || argc > 4 )
    {
        fprintf( stderr, "Format: JAM2MBOX <JAMfile> <MBOXfile> [<MsgNo>]\n" 
                 "where: <JAMfile> is the path and base filename to the JAM messagebase\n"
                 "       <MBOXfile> is the path and filename to the resulting file\n"
                 "       <MsgNo>   is the first message number to convert\n" );
        return( 1 );
    }


    strcpy( JamFileName , argv [1] );
    strcpy( MboxFileName, argv [2] );
    if( argc == 4 )
        MsgNo = atol( argv [3] );
    else
        MsgNo = 0;


    /*
     *  Initiate the JAMAPIREC variable, and open the JAM messagebase
     */

    if( !JAMsysInitApiRec( &JamRec, JamFileName, WORKBUFSIZE ))
    {
        fprintf(stderr, "Out of memory" );
        return( 1 );
    }

    if((o=fopen(MboxFileName,"wb"))==NULL)
    {
        fprintf(stderr, "Unable to open mbox file: %s\n", MboxFileName);
        JAMmbClose(&JamRec);
        return 1;
    }
    
    if( !JAMmbOpen( &JamRec ))
    {
        fprintf(stderr, "Unable to open messagebase: %s, code: %d, errno: %d\n", JamFileName, JamRec.APImsg, JamRec.Errno );
        fclose(o);
        return( 1 );
    }

    if( MsgNo == 0L )
        MsgNo = JamRec.HdrInfo.BaseMsgNum;

    TotalMsgs = filelength( JamRec.IdxHandle ) / sizeof(JAMIDXREC);
    
    while(MsgNo < TotalMsgs)
    {
        ProcessMessage(++MsgNo, o);
    }

    JAMmbClose(&JamRec);
    fclose(o);
    
    return 0;
}

static int ProcessMessage(int MsgNo, FILE *o)
{
    char date[1024];
    time_t unix_date;
    UCHAR8  *p;
    int first, pos;
    char c;
    UINT32  SubFldLen,Len;
    int msgid_generated = 0;
    int from_generated  = 0;

    char *msgid_f       = NULL ;
    char *sender_name_f = NULL ;
    char *subject_f     = NULL ;
    char *reply_id_f    = NULL ;

    char *domain        = NULL;
    
    /* fprintf(stderr,"Processing message %d\n", MsgNo); */

    if( !JAMmbFetchMsgHdr( &JamRec, MsgNo, 1))
    {
        fprintf(stderr, "Error reading message header of message #%d, code: %d, errno: %d. Skipping.\n", MsgNo, JamRec.APImsg, JamRec.Errno );
        return( 1 );
    }

    if(!JamRec.Hdr.DateWritten)
    {
        fprintf(stderr,"Warning: Message %d does not have date field. Skipping.\n");
        return 1;
    }

    if(JamRec.Hdr.Attribute & MSG_ESCAPED)
    {
        fprintf(stderr,"Warning: Message %d is escaped.  Not implemented. Skipping.\n");
        return 1;
    }
    
    unix_date = JamRec.Hdr.DateWritten;
    strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&unix_date));
    
    fprintf(o,"\nFrom somebody@nowhere %s\n", date);
    fprintf(o,"Date: %s\n", date);
    
    fprintf(o,"MIME-Version: 1.0\n");
    fprintf(o,"Content-Type: text/plain; charset=\"koi8-r\"\n");

    fprintf(o, "X-OBEC-Import-DBN: %d\n", MsgNo);
    SubFldLen = JamRec.Hdr.SubfieldLen;
    if(SubFldLen > JamRec.WorkLen)
    {
        fprintf(stderr,"Not enough memory to fetch subfields");
        return 1;
    }

    /* print all fields as X-FTN */
    for(JamRec.SubFieldPtr = (JAMSUBFIELD *)JamRec.WorkBuf;
        JamRec.SubFieldPtr->DatLen + sizeof(JAMBINSUBFIELD) <= SubFldLen;
         Len = JAMsysAlign(JamRec.SubFieldPtr->DatLen + sizeof(JAMBINSUBFIELD)),
            SubFldLen -= Len,
            JamRec.SubFieldPtr = JAMsysAddPtr( JamRec.SubFieldPtr, Len ))
    {
        char *sfname = GetSubFldName( JamRec.SubFieldPtr );
        char *sfval  = GetSubFldStr ( JamRec.SubFieldPtr );
        if(sfname && sfval)
        {
            if(ConvertText(sfval, strlen(sfval)))
            {
                fprintf(stderr,"Charset conversion error in subfield of message %d\n", MsgNo);
                return 1;
            } else
            {
                int klu=0;
                if(JamRec.SubFieldPtr->HiID==0)
                {
                    switch(JamRec.SubFieldPtr->LoID)
                    {
                    case JAMSFLD_SUBJECT    : subject_f     = strdup(sfval); break;
                    case JAMSFLD_MSGID      : msgid_f       = strdup(sfval); break;
                    case JAMSFLD_SENDERNAME : sender_name_f = strdup(sfval); break;
                    case JAMSFLD_REPLYID    : reply_id_f    = strdup(sfval); break;
                    case JAMSFLD_FTSKLUDGE  : klu           = 1; break;
                    }
                }
                if(klu && strncasecmp(sfval,"RFC-",4)==0)
                {
                    if(strncasecmp(sfval,"RFC-Message-Id", 14)==0)
                        msgid_generated=1;
                    else if(strncasecmp(sfval,"RFC-From", 8)==0)
                        from_generated=1;
                    
                    fprintf(o, "%s\n", sfval+4);
                } else
                {
                    fprintf(o, "%s: %s\n", sfname, sfval);
                }
            }
        } else
        {
            fprintf(stderr,"Null subfield name of value");
            return 1;
        }
    }

    /* Add some special fields */

    if(subject_f)
    {
        fprintf(o, "Subject: %s\n", subject_f);
        free(subject_f);
    }

    if(!msgid_generated)
    {
        if(msgid_f)
        {
            char *s=strchr(msgid_f, ' ');
            if(s)
            {
                long mi;
                
                *s='\0';
                mi     = strtol(s+1, (char **)NULL, 16);
                domain = domain_from_FTN(msgid_f);
                if(!domain)
                {
                    fprintf(stderr,"Could not extract domain from messageid in message %d\n", MsgNo);
                    return 1;
                } else
                {
                    fprintf(o, "Message-Id: <%ld@%s>\n", mi, domain);
                }
            } else
            {
                fprintf(stderr,"Could not parse msgid from message %d (%s)\n",MsgNo, msgid_f);
                return 1;
            }
        } else
        {
            fprintf(stderr,"No msgid in message %d\n",MsgNo);
            /* TODO: generate msgid ? */
            return 1;
        }
    }

    if(reply_id_f)
    {
        /* TODO: produce usenet REPLY headers */
    }

    first = 1 ;
    
    /* Fetch first block of the text */
    if( !JAMmbFetchMsgTxt( &JamRec, first ))
    {
        fprintf(stderr, "Error reading message text for message %d, code: %d, errno: %d\n", MsgNo, JamRec.APImsg, JamRec.Errno );
        return 1;
    }
    
    if( JamRec.APImsg == JAMAPIMSG_NOMORETEXT )
    {
        fprintf(stderr,"Empty message %d\n",MsgNo);
        return 1;
    }

    pos   = 0 ;

    /*
     * We generate FROM after first block of text is fetched,
     * since in some cases FROM will be extracted from there.
     */
    if(!from_generated)
    {
        if(strncasecmp(JamRec.WorkBuf,"From: ", 6)==0)
        {
            char *p=JamRec.WorkBuf;
            /* fprintf(stderr,"Extracting FROM from body %d\n",MsgNo); */
            
            while(p<(JamRec.WorkBuf+JamRec.WorkPos))
            {
                if(*p=='\n' || *p=='\r')
                {
                    fprintf(o,"\n");
                    break;
                }
                fprintf(o,"%c",*p);
                p++;
            }
        } else if(strncasecmp(JamRec.WorkBuf,"X-RealName: ", 12)==0)
        {
            char *p=JamRec.WorkBuf+12;
            /* fprintf(stderr,"Extracting REALNAME from body in %d\n", MsgNo); */
            
            fprintf(o,"From: ");
            while(p<(JamRec.WorkBuf+JamRec.WorkPos) &&
                  *p!='\n' && *p!='\r')
            {
                fprintf(o,"%c",*p);
                p++;
            }
            fprintf(o," <%s>\n", sender_name_f);
        } else
        {
            char *x=sender_name_f;
            
            while(*x)
            {
                if(*x==' ')
                    *x=' ';
                x++;
            }
            assert(sender_name_f!=NULL);
            assert(domain!=NULL);
            fprintf(o, "From: %s <%s@%s>\n", sender_name_f, sender_name_f, domain);
        }
    }
    
    /* Output text */

    c='\0';
    while(1)
    {
        int state=1, from_len=6;
        static char *FROM="\nFrom ";
        
        if(first)
        {
            first = 0;
        } else
        {
            if( !JAMmbFetchMsgTxt( &JamRec, first ))
            {
                fprintf(stderr, "Error reading message text for message %d, code: %d, errno: %d\n", MsgNo, JamRec.APImsg, JamRec.Errno );
                return 1;
            }
            if( JamRec.APImsg == JAMAPIMSG_NOMORETEXT )
                break;
        }

        if(ConvertText(JamRec.WorkBuf, (JamRec.WorkPos - pos)))
        {
            fprintf(stderr,"Charset conversion error in body of message %d\n",MsgNo);
            return 1;
        }

        for(p = (UCHAR8 *)JamRec.WorkBuf; pos < JamRec.WorkPos; pos++, p++)
        {
            c=*p;
            if(c=='\r')
                c='\n';
            
            if(FROM[state]==c)
            {
                if(state==(from_len-1))
                {
                    /* from found */
                    fprintf(o,"\n>%s",FROM+1);
                    state=0;
                } else
                {
                    state++;
                }
            } else
            {
                /* not right seq */
                if(state)
                {
                    char *f=FROM;
                    while(state--)
                        fprintf(o,"%c", *f++);
                    if(c==FROM[0])
                    {
                        state=1;
                    } else
                    {
                        state=0;
                        fprintf(o,"%c",c);
                    }
                } else
                {
                    fprintf(o,"%c",c);
                }
            }
        }
    }
    if(c!='\n')
        fprintf(o,"\n");
    fprintf(o,"\n");
    
    return 0; /* ok */
}

static int ConvertText(char *buf, size_t len)
{
    static unsigned char table[256]={
        0x00,
        0x01,
        0x02,
        0x03,
        0x04,
        0x05,
        0x06,
        0x07,
        0x08,
        0x09,
        0x0a,
        0x0b,
        0x0c,
        0x0d,
        0x0e,
        0x0f,
        0x10,
        0x11,
        0x12,
        0x13,
        0x14,
        0x15,
        0x16,
        0x17,
        0x18,
        0x19,
        0x1a,
        0x1b,
        0x1c,
        0x1d,
        0x1e,
        0x1f,
        0x20,
        0x21,
        0x22,
        0x23,
        0x24,
        0x25,
        0x26,
        0x27,
        0x28,
        0x29,
        0x2a,
        0x2b,
        0x2c,
        0x2d,
        0x2e,
        0x2f,
        0x30,
        0x31,
        0x32,
        0x33,
        0x34,
        0x35,
        0x36,
        0x37,
        0x38,
        0x39,
        0x3a,
        0x3b,
        0x3c,
        0x3d,
        0x3e,
        0x3f,
        0x40,
        0x41,
        0x42,
        0x43,
        0x44,
        0x45,
        0x46,
        0x47,
        0x48,
        0x49,
        0x4a,
        0x4b,
        0x4c,
        0x4d,
        0x4e,
        0x4f,
        0x50,
        0x51,
        0x52,
        0x53,
        0x54,
        0x55,
        0x56,
        0x57,
        0x58,
        0x59,
        0x5a,
        0x5b,
        0x5c,
        0x5d,
        0x5e,
        0x5f,
        0x60,
        0x61,
        0x62,
        0x63,
        0x64,
        0x65,
        0x66,
        0x67,
        0x68,
        0x69,
        0x6a,
        0x6b,
        0x6c,
        0x6d,
        0x6e,
        0x6f,
        0x70,
        0x71,
        0x72,
        0x73,
        0x74,
        0x75,
        0x76,
        0x77,
        0x78,
        0x79,
        0x7a,
        0x7b,
        0x7c,
        0x7d,
        0x7e,
        0x7f,
        0xe1,
        0xe2,
        0xf7,
        0xe7,
        0xe4,
        0xe5,
        0xf6,
        0xfa,
        0xe9,
        0xea,
        0xeb,
        0xec,
        0xed,
        0xee,
        0xef,
        0xf0,
        0xf2,
        0xf3,
        0xf4,
        0xf5,
        0xe6,
        0xe8,
        0xe3,
        0xfe,
        0xfb,
        0xfd,
        0xff,
        0xf9,
        0xf8,
        0xfc,
        0xe0,
        0xf1,
        0xc1,
        0xc2,
        0xd7,
        0xc7,
        0xc4,
        0xc5,
        0xd6,
        0xda,
        0xc9,
        0xca,
        0xcb,
        0xcc,
        0xcd,
        0xce,
        0xcf,
        0xd0,
        0x90,
        0x91,
        0x92,
        0x81,
        0x87,
        0xb2,
        0xb4,
        0xa7,
        0xa6,
        0xb5,
        0xa1,
        0xa8,
        0xae,
        0xad,
        0xac,
        0x83,
        0x84,
        0x89,
        0x88,
        0x86,
        0x80,
        0x8a,
        0xaf,
        0xb0,
        0xab,
        0xa5,
        0xbb,
        0xb8,
        0xb1,
        0xa0,
        0xbe,
        0xb9,
        0xba,
        0xb6,
        0xb7,
        0xaa,
        0xa9,
        0xa2,
        0xa4,
        0xbd,
        0xbc,
        0x85,
        0x82,
        0x8d,
        0x8c,
        0x8e,
        0x8f,
        0x8b,
        0xd2,
        0xd3,
        0xd4,
        0xd5,
        0xc6,
        0xc8,
        0xc3,
        0xde,
        0xdb,
        0xdd,
        0xdf,
        0xd9,
        0xd8,
        0xdc,
        0xc0,
        0xd1,
        0xb3,
        0xa3,
        0x99,
        0x98,
        0x93,
        0x9b,
        0x9f,
        0x97,
        0x9c,
        0x95,
        0x9e,
        0x96,
        0xbf,
        0x9d,
        0x94,
        0x9a
    };
    
    if(!buf || len<0)
        return 1;

    while(len--)
    {
        *buf=table[(unsigned char)*buf];
        buf++;
    }

    return 0;
}

static CHAR8 * GetSubFldName( JAMSUBFIELD * pSubFld )
{
  int     i;

  for( i = 0; i < sizeof( SubFieldInfo ) / sizeof( SubFieldInfo [0] ); i++ )
    {
    if( pSubFld->LoID == SubFieldInfo [i].LoID &&
        pSubFld->HiID == SubFieldInfo [i].HiID )
        return( SubFieldInfo [i].pName );
    }

  return NULL;
}


static CHAR8 * GetSubFldStr( JAMSUBFIELD * pSubFld )
{
  static CHAR8    Buffer [4096],
                * pBuf;
  UINT32          i;
  int             BufPos;

  for(  pBuf = Buffer, i = BufPos = 0;
        i < pSubFld->DatLen && BufPos + 5 < sizeof( Buffer );
        pBuf++, i++, BufPos++ )
    {
      *pBuf = pSubFld->Buffer [( int ) i];
    }

  *pBuf = '\0';

  return( Buffer );
}

/*
 * Like atio, but returns XATOU_ERR if conversion
 * failed. Only digits allowed, no -, spaces.
 */
static int xatou(const char *nptr)
{
    if(!nptr)
    {
        return XATOU_ERR;
    } else
    {
        long res;
        const char *p=nptr;
        while(*p)
        {
            if(!isdigit(*p))
                return XATOU_ERR;
            else
                p++;
        }
        res = strtol(nptr, (char **)NULL, 10);
        if(res==LONG_MIN || res==LONG_MAX || res > INT_MAX)
            return XATOU_ERR;
        else
            return (int)res;
    }
}

/**
 * Converts FTN address in form 2:463/80.1@fidonet.org
 * to internet domain in form p1.f80.n463.z2.fidonet.org
 */
static char * domain_from_FTN(char *msgid_f)
{
    int  z,n,f,p;
    char *x;
    static char buf[1024];
    
    x=strtok(msgid_f, ":");
    if((z=xatou(x))==XATOU_ERR)
        return NULL;

    x=strtok(NULL, "/");
    if((n=xatou(x))==XATOU_ERR)
        return NULL;

    x=strtok(NULL, ".@");
    if((f=xatou(x))==XATOU_ERR)
        return NULL;

    x=strtok(NULL, "@");
    p=xatou(x);

    /* TODO: domain */
    
    if(p!=-1)
        sprintf(buf,"p%d.f%d.n%d.z%d.fidonet.org",p,f,n,z);
    else
        sprintf(buf,"f%d.n%d.z%d.fidonet.org",f,n,z);
        
    return buf;
}

#if !defined(__MSDOS__)

static UINT32 filelength( int fh )
{
    UINT32    Pos,  Length;
    
    Pos    = JAMsysSeek( NULL, fh, SEEK_CUR, 0L );
    Length = JAMsysSeek( NULL, fh, SEEK_END, 0L );
    JAMsysSeek( NULL, fh, SEEK_SET, Pos );
    
    return( Length );
}

#endif

