/*****************************************************************************
 * 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.86.2.3 2003/04/01 09:28:26 stas_degteff Exp $
 */
#include <stdlib.h>
#include <stdio.h>

// changed by tobi: malloc.h is completely nonstandard and should not be
// necessary if stdlib.h is there. if your compiler needs malloc.h, then
// please include it by doing a #ifdef YOURCOMPILER, and don't do it like
// below. E.g., we want to list the compilers that NEED malloc.h, not to 
// list those that do NOT need it!
//
// #if !defined(__FreeBSD__) 
// #include <malloc.h>
// #endif

#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>

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 != '\r') && (*start != '\n') && (*start != '\0'))
		   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) {
		   start += 7;
		   buffer[0] = '0';
		   buffer[1] = ':';
		   i = 2;

		   while ((!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;
   s_addr intl_from, intl_to;
   int 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(start,':')==NULL || strchr(start,'/')==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(start,':')==NULL || strchr(start,'/')==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);
      free(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 defined(MSDOS) && !defined(__FLAT__) || !defined(__DJGPP__)
   char *origin;
#endif

   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(MSDOS) || defined(__FLAT__) || defined(__DJGPP__)
   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
   /* 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(strrstr(msg->text, " * Origin"))
   {  /* Read rest of kludges SEEN-BY & PATH */
     len = fgetsUntil0((UCHAR *) globalBuffer, BUFFERSIZE+1, pkt, "\n");
     xstrcat(&msg->text, (char*)globalBuffer);
     msg->textLength+=len-1; /*  trailing \0 is not the text */
   }
   else
   { badmsg++;
     strncpy( globalBuffer, aka2str(msg->destAddr), BUFFERSIZE );
     w_log(LL_ERR, "Message from %s to %s too big!", aka2str(msg->origAddr),
                                                     globalBuffer);
     /*  Message too big, skip msg text */
     for (len=-1; len == BUFFERSIZE+1; ) {
       len = fgetsUntil0((UCHAR *) globalBuffer, BUFFERSIZE+1, pkt, "\n");
     }
     if(len>=0)
     { /*  add origin, seen-by's & path */
       origin = strrstr(globalBuffer, " * Origin");
       if (origin) {
         xstrscat(&msg->text, "\r", origin, NULL);
         msg->textLength+=strlen(origin);
       }
     }
   }
#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;
}
