/*****************************************************************************
 * HPT --- FTN NetMail/EchoMail Tosser
 *****************************************************************************
 * Copyright (C) 1997-1999
 *
 * Matthias Tichy
 *
 * Fido:     2:2433/1245 2:2433/1247 2:2432/605.14
 * Internet: mtt@tichy.de
 *
 * Grimmestr. 12         Buchholzer Weg 4
 * 33098 Paderborn       40472 Duesseldorf
 * Germany               Germany
 *
 * This file is part of HPT.
 *
 * HPT 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, or (at your option) any
 * later version.
 *
 * HPT 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 HPT; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************
 * $Id: pktread.c,v 1.96.2.1 2004/01/06 20:29:01 d_sergienko Exp $
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <smapi/compiler.h>

#include <fidoconf/fidoconf.h>
#include <fidoconf/common.h>
#include <fidoconf/xstr.h>
#include <fidoconf/afixcmd.h>
#include <fidoconf/log.h>
#include <fidoconf/recode.h>

#include <global.h>
#include <fcommon.h>
#include <pkt.h>
#include <global.h>

typedef unsigned long flag_t;  /* for at least 32 bit flags */
#define FTSC_FLAWY  1           /* FTSC field has correctable errors */
#define FTSC_BROKEN 2           /* FTSC field can't even be parsed   */
#define FTSC_SEADOG 16          /* Seadog style string in the FTSC   */
#define FTSC_TS_BROKEN 128      /* Only timestamp broken, date is OK */

time_t readPktTime(FILE *pkt)
{
  struct tm time;

  time.tm_year  = getUINT16(pkt) - 1900; /* years since 1900 */
  time.tm_mon   = getUINT16(pkt);
  time.tm_mday  = getUINT16(pkt);
  time.tm_hour  = getUINT16(pkt);
  time.tm_min   = getUINT16(pkt);
  time.tm_sec   = getUINT16(pkt);
  time.tm_isdst = 0;                    /* disable daylight saving */

  return mktime(&time);
}

void readPktPassword(FILE *pkt, UCHAR *password)
{
   int i;

   for (i=0 ;i<8 ;i++ ) {
     password[i] = (UCHAR) getc(pkt); /* no EOF check :-( */
   } /* endfor */
   password[8] = 0;
}

s_pktHeader *openPkt(FILE *pkt)
{
  s_pktHeader *header;
  UINT16      pktVersion, capWord;

  header = (s_pktHeader *) safe_malloc(sizeof(s_pktHeader));
  memset(header, '\0', sizeof(s_pktHeader));
  header->origAddr.node = getUINT16(pkt);
  header->destAddr.node = getUINT16(pkt);
  header->pktCreated = readPktTime(pkt); /*  12 bytes */

  getUINT16(pkt); /* read 2 bytes for the unused baud field */

  pktVersion = getUINT16(pkt);
  if (pktVersion != 2) {
	  nfree(header);
	  w_log(LL_ERR,"Invalid pkt version %u!",pktVersion);
	  return NULL;
  } /* endif */

  header->origAddr.net = getUINT16(pkt);
  header->destAddr.net = getUINT16(pkt);
  
  header->loProductCode = (UCHAR) getc(pkt);
  header->majorProductRev = (UCHAR) getc(pkt);

  readPktPassword(pkt, (UCHAR *)header->pktPassword); /*  8 bytes */

  header->origAddr.zone = getUINT16(pkt);
  header->destAddr.zone = getUINT16(pkt);

  header->auxNet = getUINT16(pkt);

  header->capabilityWord = (UINT16)((fgetc(pkt) << 8) + fgetc(pkt));
  header->hiProductCode = (UCHAR) getc(pkt);
  header->minorProductRev = (UCHAR) getc(pkt);

  capWord = getUINT16(pkt);

  if (!config->ignoreCapWord) {
	  /* if both capabilitywords aren't the same, abort */
	  /* but read stone-age pkt */
	  if (capWord!=header->capabilityWord && header->capabilityWord!=0) {
		  nfree(header);
		  w_log(LL_ERR,"CapabilityWord error in following pkt! rtfm: IgnoreCapWord.");
		  return NULL;
	  } /* endif */
  }
  
  getUINT16(pkt); getUINT16(pkt); /* read the additional zone info */

  header->origAddr.point = getUINT16(pkt);
  header->destAddr.point = getUINT16(pkt);

  getUINT16(pkt); getUINT16(pkt); /* read ProdData */

  if (header->origAddr.net == 65535) {
	  if (header->origAddr.point) header->origAddr.net = header->auxNet;
	  else header->origAddr.net = header->destAddr.net; /*  not in FSC ! */
  }

  if (header->origAddr.zone == 0) {
	  for (capWord=0; capWord<config->addrCount; capWord++) {
		  if (header->origAddr.net==config->addr[capWord].net) {
			  header->origAddr.zone = config->addr[capWord].zone;
			  break;
		  }
	  }
	  if (header->origAddr.zone==0) header->origAddr.zone=config->addr[0].zone;
  }
  if (header->destAddr.zone == 0) {
	  for (capWord=0; capWord<config->addrCount; capWord++) {
		  if (header->destAddr.net==config->addr[capWord].net) {
			  header->destAddr.zone = config->addr[capWord].zone;
			  break;
		  }
	  }
	  if (header->destAddr.zone==0) header->destAddr.zone=config->addr[0].zone;
  }

  return header;
}

void correctEMAddr(s_message *msg)
{
   char *start = NULL, buffer[48];
   int i, brokenOrigin = 1;

   start = strrstr(msg->text, " * Origin:");

   if (start) {
	   while (*start && (*start != '\r') && (*start != '\n')) start++;  /*  get to end of line */

	   if (*(start-1) == ')') {         /*  if there is no ')', there is no origin */
		   while (start>msg->text && *(--start)!='('); /*  find beginning '(' */
		   start++;                     /*  and skip it */
		   i=0;
   
		   while (*start && (*start!=')') && (*start!='\r') && (*start!='\n') && (i<47)) {
			   if (isdigit(*start) || *start==':' || *start=='/' || *start=='.') {
				   buffer[i] = *start;
				   i++;
			   }
			   start++;
		   }
		   buffer[i]   = '\0';
		   string2addr(buffer, &(msg->origAddr));
		   brokenOrigin = 0;
	   }
   }

   /*  this is really needed? */
   if (brokenOrigin) {
	   start = strstr(msg->text, "\001PATH: ");
	   if (start && strlen(start) > 7) {
		   start += 7;
		   buffer[0] = '0';
		   buffer[1] = ':';
		   i = 2;

		   while (*start && (!isspace(*start)) && (*start!='\r') && (*start!='\n') && (i<47)) {
			   if (isdigit(*start) || *start=='/') {
				   buffer[i] = *start;
				   i++;
			   }
			   start++;
		   }
		   buffer[i]   = '\0';
		   string2addr(buffer, &(msg->origAddr));
	   }
   }
}

void correctNMAddr(s_message *msg, s_pktHeader *header)
{
   char *start, *copy, *text=NULL, buffer[35]; /* FIXME: static buffer */
   int valid_intl_kludge = 0;
   int zonegated = 0;
   hs_addr intl_from, intl_to;
   UINT i;

   copy = buffer;
   start = strstr(msg->text, "FMPT");
   if (start) {
      start += 6;                  /* skip "FMPT " */
      while (isdigit(*start)) {     /* copy all digit data */
         *copy = *start;
         copy++;
         start++;
      } /* endwhile */
      *copy = '\0';                /* don't forget to close the string with 0 */

      msg->origAddr.point = atoi(buffer);
   } else {
      msg->origAddr.point = 0;
   } /* endif */

   /* and the same for TOPT */
   copy = buffer;
   start = strstr(msg->text, "TOPT");
   if (start) {
      start += 6;                  /* skip "TOPT " */
      while (isdigit(*start)) {     /* copy all digit data */
         *copy = *start;
         copy++;
         start++;
      } /* endwhile */
      *copy = '\0';                /* don't forget to close the string with 0 */

      msg->destAddr.point = atoi(buffer);
   } else {
      msg->destAddr.point = 0;
   } /* endif */

   /* Parse the INTL Kludge */

   start = strstr(msg->text, "INTL ");
   if (start) {
      
      start += 6;                 /*  skip "INTL " */

      while(1)
      {
          while (*start && isspace(*start)) start++;
          if (!*start) break;

          copy = buffer;
          while (*start && !isspace(*start)) *copy++ = *start++;
          *copy='\0';
          if (strchr(buffer,':')==NULL || strchr(buffer,'/')==NULL) break;
          string2addr(buffer, &intl_to);
          
          while (*start && isspace(*start)) start++;
          if (!*start) break;

          copy = buffer;
          while (*start && !isspace(*start)) *copy++ = *start++;
          *copy='\0';
          if (strchr(buffer,':')==NULL || strchr(buffer,'/')==NULL) break;
          string2addr(buffer, &intl_from);

          intl_from.point = msg->origAddr.point;
          intl_to.point = msg->destAddr.point;

          valid_intl_kludge = 1;
	  break;
      }
   }

   /* now interpret the INTL kludge */

   if (valid_intl_kludge)
   {
      /* the from part is easy - we can always use it */

      msg->origAddr.zone = intl_from.zone;
      msg->origAddr.net  = intl_from.net;
      msg->origAddr.node = intl_from.node;

      /* the to part is more complicated */

      zonegated = 0;

      if (msg->destAddr.net == intl_from.zone &&
          msg->destAddr.node == intl_to.zone)
      {
         zonegated = 1;

         /* we want to ignore the zone gating if we are the zone gate */

         for (i = 0; i < config->addrCount; i++)
         {
            if (config->addr[i].zone == msg->destAddr.net &&
                config->addr[i].net == msg->destAddr.net &&
                config->addr[i].node == msg->destAddr.node &&
                config->addr[i].point == 0)
            {
               zonegated = 0;
            }
         }
      }

      if (zonegated)
      {
         msg->destAddr.zone = intl_from.zone;
         msg->destAddr.net  = intl_from.zone;
         msg->destAddr.node = intl_to.zone;
      }
      else
      {
         msg->destAddr.zone = intl_to.zone;
         msg->destAddr.net  = intl_to.net;
         msg->destAddr.node = intl_to.node;
      }

   } else {
      
      /* no INTL kludge */

      msg->destAddr.zone = header->destAddr.zone;
      msg->origAddr.zone = header->origAddr.zone;

      msg->textLength += xscatprintf(&text,"\1INTL %u:%u/%u %u:%u/%u\r",msg->destAddr.zone,msg->destAddr.net,msg->destAddr.node,msg->origAddr.zone,msg->origAddr.net,msg->origAddr.node);
      xstrcat(&text,msg->text);
      nfree(msg->text);
      msg->text = text;
      
      w_log( LL_PKT, "Mail without INTL-Kludge. Assuming %i:%i/%i.%i -> %i:%i/%i.%i",
		    msg->origAddr.zone, msg->origAddr.net, msg->origAddr.node, msg->origAddr.point,
		    msg->destAddr.zone, msg->destAddr.net, msg->destAddr.node, msg->destAddr.point);
   } /* endif */
}

void correctAddr(s_message *msg,s_pktHeader *header)
{
	if (strncmp(msg->text, "AREA:",5) == 0) {

		if (strncmp(msg->text+5, "NETMAIL\r",8) == 0) {
			switch (config->kludgeAreaNetmail) {
			case kanKill: /*  kill "AREA:NETMAIL\r" */
				memmove(msg->text, msg->text+13, msg->textLength-12);
			case kanIgnore: /*  process as netmail. don't touch kludge. */
				msg->netMail = 1;
			default: /*  process as echomail */
				break;
			}
		}
		
	} else msg->netMail = 1;
	
	if (msg->netMail) correctNMAddr(msg,header);
	else correctEMAddr(msg);
}

/* Some toupper routines crash when they get invalid input. As this program
   is intended to be portable and deal with any sort of malformed input,
   we have to provide our own toupper routine. */
char safe_toupper(char c)
{
    const char *from_table = "abcdefghijklmnopqrstuvwxyz";
    const char *to_table   = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const char *p;

    if ((p = strchr(from_table, c)) != NULL)
    {
        return (to_table[p - from_table]);
    }

    return c;
}

static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

int get_month(const char *pmon, flag_t *flag)
{
    int i;

    if (strlen(pmon) != 3 && flag != NULL)
    {
        (*flag) |= FTSC_FLAWY;
    }

    for (i=0; i < 12; i++)
    {
        if (pmon[0] == months[i][0] &&
            pmon[1] == months[i][1] &&
            pmon[2] == months[i][2])
        {
            return i;
        }
    }

    for (i=0; i < 12; i++)
    {
        if (safe_toupper(pmon[0]) == safe_toupper(months[i][0]) &&
            safe_toupper(pmon[1]) == safe_toupper(months[i][1]) &&
            safe_toupper(pmon[2]) == safe_toupper(months[i][2]))
        {
            (*flag) |= FTSC_FLAWY;
            return i;
        }
    }

    (*flag) |= FTSC_BROKEN;
    return 0;
}

static flag_t parse_ftsc_date(struct tm * ptm, char *pdatestr)
{
    const char *pday, *pmon, *pyear, *phour, *pminute, *psecond;
    flag_t rval;
    char buf[22];
	int fixseadog=0;
	struct tm *pnow;
    time_t t_now;

    time(&t_now);
    pnow = localtime(&t_now);   /* get the current time */

    pday = pmon = pyear = phour = pminute = psecond = NULL;

    rval = FTSC_BROKEN;

    memcpy(buf, pdatestr, 21); buf[21] = 0;

    if ((pday = strtok(pdatestr, " ")) != NULL)
        if ((pmon = strtok(NULL, " ")) != NULL)
            if ((pyear = strtok(NULL, " ")) != NULL)
                if ((phour = strtok(NULL, ":")) != NULL)
                    if ((pminute = strtok(NULL, ":")) != NULL)
                        if ((psecond = strtok(NULL, " ")) != NULL)
                            rval = 0;

    if (rval == FTSC_BROKEN)
    {
                 /* let's try and see if it might be the old SeaDog format */

        rval = FTSC_BROKEN;

        if ((strtok(buf, " ")) != NULL)
            if ((pday = strtok(NULL, " ")) != NULL)
                if ((pmon = strtok(NULL, " ")) != NULL)
                    if ((pyear = strtok(NULL, " ")) != NULL)
                        if ((phour = strtok(NULL, ": ")) != NULL)
                            if ((pminute = strtok(NULL, ": ")) != NULL)
                            {
                                psecond = NULL;
                                if (fixseadog)
                                    rval = FTSC_SEADOG;
                                else
                                    rval = 0;
                            }
    }


    ptm->tm_sec = ptm->tm_min = ptm->tm_hour = ptm->tm_mday = ptm->tm_mon =
        ptm->tm_year = 0;

    while (rval != FTSC_BROKEN)    /* at least we could tokenize it! */
    {
        if (psecond != NULL)
        {
            ptm->tm_sec = atoi(psecond);   /* Is the number of seconds valid? */
            if (strlen(psecond) == 1)
            {
                rval |= FTSC_FLAWY;
                if (ptm->tm_sec < 6) (ptm->tm_sec *= 10);
            }
            if (ptm->tm_sec < 0 || ptm->tm_sec > 59)
            {
                rval |= FTSC_TS_BROKEN; ptm->tm_sec = 0;
            }
        }
        else
        {
            ptm->tm_sec = 0;
        }

        ptm->tm_min = atoi(pminute);   /* Is the number of minutes valid? */
        if (ptm->tm_min < 0 || ptm->tm_min > 59)
        {
            rval |= FTSC_TS_BROKEN; ptm->tm_min = 0;
        }

        ptm->tm_hour = atoi(phour);    /* Is the number of hours valid? */
        if (ptm->tm_hour < 0 || ptm->tm_hour>23)
        {
            rval |= FTSC_TS_BROKEN; ptm->tm_hour = 0;
        }

        ptm->tm_mday = atoi(pday);     /* Is the day in the month valid? */
        if (ptm->tm_mday < 1 || ptm->tm_mday>31) { rval |= FTSC_BROKEN; break; }

        ptm->tm_mon = get_month(pmon, &rval); /* Is the month valid? */

        if (strlen(pyear) != 2)        /* year field format check */
        {
            rval |= FTSC_FLAWY;
        }

        if (*pyear)
        {
            ptm->tm_year = (*pyear - '0'); /* allows for the ":0" bug */
            for (pyear++; isdigit((int)(*pyear)); pyear++)
            {
                ptm->tm_year *= 10;
                ptm->tm_year += (*pyear - '0');
            }
            if (*pyear)
            {
                rval |= FTSC_BROKEN;
                break;
            }
        }
        else
        {
            rval |= FTSC_BROKEN;
            break;
        }

        if (ptm->tm_year < 100)  /* correct date field! */
        {
            while (pnow->tm_year - ptm->tm_year > 50)
            {                         /* sliding window adaption */
                ptm->tm_year += 100;
            }
        }
        else if (ptm->tm_year < 1900) /* probably the field directly */
                                      /* contains tm_year, like produced */
                                      /* by the Timed/Netmgr bug and others */
        {
            rval |= FTSC_FLAWY;
        }
        else                          /* 4 digit year field, not correct! */
        {
            ptm->tm_year -= 1900;
            rval |= FTSC_FLAWY;
        }
        break;
    }


    return rval;

}

static void make_ftsc_date(char *pdate, const struct tm *ptm)
{
    sprintf(pdate, "%02d %-3.3s %02d  %02d:%02d:%02d",
            ptm->tm_mday % 100, months[ptm->tm_mon], ptm->tm_year % 100,
            ptm->tm_hour % 100, ptm->tm_min % 100, ptm->tm_sec % 100);
}

int readMsgFromPkt(FILE *pkt, s_pktHeader *header, s_message **message)
{
    s_message *msg;
    int len, badmsg=0;
    struct tm tm;
    char *p, *q;
    long unread;
    
    if (2 != getUINT16(pkt)) {
        *message = NULL;
        
        unread = ftell(pkt);
        fseek(pkt, 0L, SEEK_END);
        unread = ftell(pkt) - unread; /*  unread bytes */
        
        if (unread) {
            w_log(LL_ERR,"There is %d bytes of unknown data at the end of pkt file!",
                unread);
            return 2; /*  rename to bad */
        }
        else return 0; /*  end of pkt file */
    }
    
    msg = (s_message*) safe_malloc(sizeof(s_message));
    memset(msg, '\0', sizeof(s_message));
    
    msg->origAddr.node   = getUINT16(pkt);
    msg->destAddr.node   = getUINT16(pkt);
    msg->origAddr.net    = getUINT16(pkt);
    msg->destAddr.net    = getUINT16(pkt);
    msg->attributes      = getUINT16(pkt);
    
    getc(pkt); getc(pkt);                /*  read unused cost fields (2bytes) */
    
    fgetsUntil0 (msg->datetime, 22, pkt, NULL);
    parse_ftsc_date(&tm, (char*)msg->datetime);
    make_ftsc_date((char*)msg->datetime, &tm);
    
    if (globalBuffer==NULL) {
        globalBuffer = (UCHAR *) safe_malloc(BUFFERSIZE+1); /*  128K (32K in MS-DOS) */
    }
    
    len = fgetsUntil0 ((UCHAR *) globalBuffer, BUFFERSIZE+1, pkt, NULL);
    if (len > XMSG_TO_SIZE) {
        if (config->intab) recodeToInternalCharset((CHAR*) globalBuffer);
        w_log(LL_ERR, "wrong msg header: toUserName (%s) longer than %d bytes.",
            globalBuffer, XMSG_TO_SIZE-1);
        if (config->outtab) recodeToTransportCharset((CHAR*) globalBuffer);
        globalBuffer[XMSG_TO_SIZE-1]='\0';
        badmsg++;
    }
    xstrcat(&msg->toUserName, (char *) globalBuffer);
    
    fgetsUntil0((UCHAR *) globalBuffer, BUFFERSIZE+1, pkt, NULL);
    if (len > XMSG_FROM_SIZE) {
        if (config->intab) recodeToInternalCharset((CHAR*) globalBuffer);
        w_log(LL_ERR, "wrong msg header: fromUserName (%s) longer than %d bytes.",
            globalBuffer, XMSG_FROM_SIZE-1);
        if (config->outtab) recodeToTransportCharset((CHAR*) globalBuffer);
        globalBuffer[XMSG_FROM_SIZE-1]='\0';
        badmsg++;
    }
    xstrcat(&msg->fromUserName, (char *) globalBuffer);
    
    len = fgetsUntil0((UCHAR *) globalBuffer, BUFFERSIZE+1, pkt, NULL);
    if (len > XMSG_SUBJ_SIZE) {
        if (config->intab) recodeToInternalCharset((CHAR*) globalBuffer);
        w_log(LL_ERR, "wrong msg header: subectLine (%s) longer than %d bytes.",
            globalBuffer, XMSG_SUBJ_SIZE-1);
        if (config->outtab) recodeToTransportCharset((CHAR*) globalBuffer);
        globalBuffer[XMSG_SUBJ_SIZE-1]='\0';
        badmsg++;
    }
    xstrcat(&msg->subjectLine, (char *) globalBuffer);
    
    if (badmsg) {
        freeMsgBuffers(msg);
        *message = NULL;
        w_log(LL_ERR, "wrong msg header: renaming pkt to bad.");
        return 2; /*  exit with error */
    }
    
#if !defined(__DOS__) || defined(__FLAT__)
#ifdef DEBUG_HPT
    w_log(LL_DEBUG, "readMsgFromPkt()  32bit");
#endif
    do {
        len = fgetsUntil0((UCHAR *) globalBuffer, BUFFERSIZE+1, pkt, "\n");
        xstrcat(&msg->text, (char*)globalBuffer);
        msg->textLength+=len-1; /*  trailing \0 is not the text */
    } while (len == BUFFERSIZE+1);
#else
#ifdef DEBUG_HPT
    w_log(LL_DEBUG, "readMsgFromPkt() DOS-16");
#endif
    /* DOS: read only one segment of message */
    len = fgetsUntil0((UCHAR *) globalBuffer, BUFFERSIZE+1, pkt, "\n");
    xstrcat(&msg->text, globalBuffer);
    msg->textLength+=len-1; /*  trailing \0 is not the text */
    
    if( (len == BUFFERSIZE+1) ) {
        badmsg++;
        xstrscat(&msg->text, "\r* Mesage too big, truncated by ", versionStr, "\r",NULL);
        do {
            char *origin;
            
            len = fgetsUntil0((UCHAR *) globalBuffer, BUFFERSIZE+1, pkt, "\n");
            /* add kludges to end of striped text */
            if((origin=strstr(globalBuffer, " * Origin"))) {
                xstrcat(&msg->text, origin);
            }
        } while (len == BUFFERSIZE+1);
        strncpy( globalBuffer, aka2str(msg->destAddr), BUFFERSIZE );
        w_log(LL_ERR, "Message from %s to %s too big!", aka2str(msg->origAddr),
            globalBuffer);
    }
#endif
    
    correctAddr(msg, header);
    
    /*  del "\001FLAGS" from message text */
    if (NULL != (p=strstr(msg->text,"\001FLAGS"))) {
        for (q=p; *q && *q!='\r'; q++);
        memmove(p,q+1,msg->textLength-(q-msg->text));
        msg->textLength -= (q-p+1);
    }
    
    *message = msg;
    return 1;
}
