/********************************************************
 * File: mb_msg.c
 * Created at Sun Jan 28 22:10:30 MSK 2001 by raorn // raorn@binec.ru
 * *.MSG support
 * $Id: mb_msg.c,v 1.21 2002/03/03 21:05:57 raorn Exp $
 *******************************************************/
#include "crashecho.h"

struct msg_Area {
  struct msg_Area *Next;
  struct Area *area;
  ulong LowMsg;
  ulong HighMsg;
  ulong OldHighWater;
  ulong HighWater;
};

bool msg_GetHighLowMsg(struct msg_Area *area);
bool msg_WriteHighWater(struct msg_Area *area);
bool msg_WriteMSG(struct MemMessage *mm, uchar * file, bool impseenby);
ulong msg_ReadCR(uchar * buf, ulong maxlen, FILE *fh);
bool msg_ExportMSGNum(struct Area *area, ulong num, bool (*acceptfunc) (struct MemMessage *mm), bool (*handlefunc) (struct MemMessage *mm));

struct jbList msg_AreaList;

bool msg_messageend;
bool msg_shortread;

struct msg_Area *msg_getarea(struct Area *area)
{
  struct msg_Area *ma;

  /* Check if area already exists */
  for (ma = (struct msg_Area *) msg_AreaList.First; ma; ma = ma->Next)
    if (ma->area == area)
      return (ma);

  /* This is the first time we use this area */
  if (!(ma = calloc(1, sizeof(struct msg_Area)))) {
    nomem = TRUE;
    return (FALSE);
  }

  jbAddNode(&msg_AreaList, (struct jbNode *) ma);
  ma->area = area;

  if (!msg_GetHighLowMsg(ma))
    return (FALSE);

  return (ma);
}

bool msg_beforefunc(void)
{
  jbNewList(&msg_AreaList);

  return (TRUE);
}

bool msg_afterfunc(bool success)
{
  struct msg_Area *ma;

  if (success && (config.cfg_msg_Flags & CFG_MSG_HIGHWATER))
    for (ma = (struct msg_Area *) msg_AreaList.First; ma; ma = ma->Next)
      if (ma->HighWater != ma->OldHighWater)
        msg_WriteHighWater(ma);

  return (TRUE);
}

bool msg_importfunc(struct MemMessage * mm, struct Area * area)
{
  uchar buf[200], buf2[20];
  struct msg_Area *ma;

  if (!(ma = msg_getarea(area)))
    return (FALSE);

  do {
    ma->HighMsg++;
    snprintf(buf2, 200, "%lu.msg", ma->HighMsg);
    MakeFullPath(ma->area->Path, buf2, buf, 200);
  } while (Exists(buf));

  return msg_WriteMSG(mm, buf, area->Flags & AREA_IMPORTSEENBY);
}

bool msg_scanfunc(struct Area * area, bool usehw, bool ignorehw, ulong max, struct jbList *msglist, bool(*acceptfunc) (struct MemMessage * mm), bool(*handlefunc) (struct MemMessage * mm))
{
  ulong start, stop;
  uchar buf[200];
  struct StoredMsg Msg;
  FILE *fh;
  struct msg_Area *ma;

  if (!(ma = msg_getarea(area)))
    return (FALSE);

  if (usehw && config.cfg_msg_Flags & CFG_MSG_HIGHWATER) {
    if (ma->HighWater == 0) {
      MakeFullPath(area->Path, "1.msg", buf, 200);

      if ((fh = fopen(buf, "rb"))) {
        if ((fread(&Msg, 1, sizeof(struct StoredMsg), fh) == sizeof(struct StoredMsg))) {
          ma->HighWater = Msg.ReplyTo;
          ma->OldHighWater = Msg.ReplyTo;
        }
        fclose(fh);
      }
    }
  }

  if (ignorehw)
    start = 1;
  else {
    if (ma->HighWater)
      start = ma->HighWater + 1;
    else
      start = ma->LowMsg;

    if (start < ma->LowMsg)
      start = ma->LowMsg;
  }

  stop = ma->HighMsg;

  if (max != 0 && stop - start + 1 > max)
    start = stop - max + 1;

  while (start <= stop) {
    if (!msg_ExportMSGNum(area, start, acceptfunc, handlefunc))
      return (FALSE);

    if (ctrlc)
      return (FALSE);

    start++;
  }

  if (usehw)
    ma->HighWater = stop- 1;

  return (TRUE);
}

bool msg_ExportMSGNum(struct Area * area, ulong num, bool(*acceptfunc) (struct MemMessage *mm), bool(*handlefunc) (struct MemMessage *mm))
{
  ulong rlen;
  uchar buf[200], buf2[50];
  bool kludgeadd;
  FILE *fh;
  struct StoredMsg Msg;
  struct MemMessage *mm;
  bool gotarea = FALSE, gotfrom = FALSE;

  LogWrite(6, SYSTEMINFO, "In msg_ExportMSGNum(\"%s\", %d, ...)", area->Tagname, num);

  if (!(mm = mmAlloc()))
    return (FALSE);

  snprintf(buf2, 50, "%lu.msg", num);
  MakeFullPath(area->Path, buf2, buf, 200);

  if (!(fh = fopen(buf, "rb"))) {
    /* Message doesn't exist */
    mmFree(mm);
    return (TRUE);
  }

  if (fread(&Msg, 1, sizeof(struct StoredMsg), fh) != sizeof(struct StoredMsg)) {
    LogWrite(1, TOSSINGERR, "Unexpected EOF while reading %s, message ignored", buf);
    fclose(fh);
    mmFree(mm);
    return (TRUE);
  }

  mm->OrigNode.Net = Msg.OrigNet;
  mm->OrigNode.Node = Msg.OrigNode;
  mm->DestNode.Net = Msg.DestNet;
  mm->DestNode.Node = Msg.DestNode;

  if (area->AreaType == AREATYPE_NETMAIL)
    mm->Area[0]=0;
  else
    strcpy(mm->Area, area->Tagname);

  mystrncpy(mm->To, Msg.To, 36);
  mystrncpy(mm->From, Msg.From, 36);
  mystrncpy(mm->Subject, Msg.Subject, 72);

  mystrncpy(mm->DateTime, Msg.DateTime, 20);

  mm->Attr = Msg.Attr;
  mm->Cost = Msg.Cost;

  kludgeadd = FALSE;
  msg_messageend = FALSE;
  msg_shortread = FALSE;

  do {
    rlen = msg_ReadCR(buf, 200, fh);

    if (buf[0] == 1 && area->AreaType == AREATYPE_BAD) {
      if (!gotfrom && rlen > 6 && !strncmp(buf, "\001FROM:", 6)) {
        struct Node4D node;
        uchar buf2[100];

        mystrncpy(buf2, &buf[6], (rlen - 6 > 99 ? 99 : rlen - 6) + 1);
        stripleadtrail(buf2);

        if (Parse4D(buf2, &node))
          Copy4D(&mm->PktOrig, &node);

        /* Just skip this kludge */
        gotfrom = TRUE;
        continue;
      } else if (!gotarea && rlen > 6 && !strncmp(buf, "\001AREA:", 6)) {
        mystrncpy(mm->Area, &buf[6], (rlen - 6 > 79 ? 79 : rlen - 6) + 1);
        stripleadtrail(mm->Area); /* Strip spaces from area name */
        gotarea = TRUE;

        /* Just skip this kludge */
        continue;
      } else if (rlen >= 8 && !strncmp(buf, "\001REASON:", 8))
        /* Just skip this kludge */
        continue;
    }

    if (buf[0] != 1 && buf[0] != 10 && !kludgeadd) {
      kludgeadd = TRUE;

      if (mm->Area[0] == 0) {
        if (mm->DestNode.Zone == 0 || mm->OrigNode.Zone == 0) {
          /* No INTL line and no zone in header */
          mm->DestNode.Zone = area->Aka->Node.Zone;
          mm->OrigNode.Zone = area->Aka->Node.Zone;
          Msg.DestZone = area->Aka->Node.Zone;
          Msg.OrigZone = area->Aka->Node.Zone;
          if(config.cfg_Flags & CFG_FORCEINTL) {
            snprintf(buf2, 50, "\001INTL %u:%u/%u %u:%u/%u\015",
                     Msg.DestZone, Msg.DestNet, Msg.DestNode,
                     Msg.OrigZone, Msg.OrigNet, Msg.OrigNode);
            mmAddLine(mm,buf2, MM_HEAD);
            /* FIXME What with FMPT/TOPT? */
          }
        }
      }
    }

    if (buf[0]) {
      if (!mmAddLine(mm, buf, MM_ADD)) {
        fclose(fh);
        mmFree(mm);
        return (FALSE);
      }
    }

  } while (!msg_messageend && !msg_shortread);

  fclose(fh);

  mm->msgnum = num;

  /* FIXME Check if /^\001FLAGS.*LOK.*$/ */
  if ((*acceptfunc) (mm)) {
    if (!(*handlefunc) (mm)) {
      mmFree(mm);
      return (FALSE);
    }

    snprintf(buf2, 50, "%lu.msg", num);
    MakeFullPath(area->Path, buf2, buf, 200);

    if (mm->Changed) {
        Msg.Attr = mm->Attr;
      if (mm->Deleted) {
        unlink(buf);
      } else {
        if ((fh = fopen(buf, "r+b"))) {
          fwrite(&Msg, sizeof(struct StoredMsg), 1, fh);
          fclose(fh);
        }
      }
    }
  }

  mmFree(mm);
  return (TRUE);
}

ulong msg_templowmsg;
ulong msg_temphighmsg;

void msg_scandirfunc(uchar * file)
{
  if (strlen(file) > 4) {
    if (stricmp(&file[strlen(file) - 4], ".msg") == 0) {
      if (atol(file) > msg_temphighmsg)
        msg_temphighmsg = atol(file);

      if (atol(file) < msg_templowmsg || msg_templowmsg == 0 ||
          msg_templowmsg == 1)
        if (atol(file) >= 2)
          msg_templowmsg = atol(file);
    }
  }
}

bool msg_GetHighLowMsg(struct msg_Area *area)
{
  mode_t oldumask;

  if (!Exists(area->area->Path)) {
    LogWrite(2, SYSTEMINFO, "Creating directory \"%s\"", area->area->Path);

    oldumask = umask(config.cfg_MsgbaseUmask);
    if (mkdir(area->area->Path, 0777)) {
      umask(oldumask);
      LogWrite(1, SYSTEMERR, "Unable to create directory");
      LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
      return (FALSE);
    }
    umask(oldumask);
  }

  msg_templowmsg = 0;
  msg_temphighmsg = 0;

  if (!ScanDir(area->area->Path, msg_scandirfunc)) {
    LogWrite(1, SYSTEMERR, "Failed to scan directory %s", area->area->Path);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    return (FALSE);
  }

  area->HighMsg = msg_temphighmsg;
  area->LowMsg = msg_templowmsg;

  if (area->HighMsg == 0)
    area->HighMsg = 1;

  if (area->LowMsg == 0 || area->LowMsg == 1)
    area->LowMsg = 2;

  return (TRUE);
}

bool msg_WriteHighWater(struct msg_Area * area)
{
  FILE *fh;
  uchar buf[200];
  struct StoredMsg Msg;
  mode_t oldumask;

  if (area->HighWater > 65535) {
    LogWrite(1, TOSSINGERR, "Warning: Highwater mark in %s exceeds 65535, cannot store in 1.msg",
                            area->area->Tagname);
    return (TRUE);
  }

  strcpy(Msg.From, "CrashEcho");
  strcpy(Msg.To, "All");
  strcpy(Msg.Subject, "HighWater mark");

  MakeFidoDate(time(NULL), Msg.DateTime);

  Msg.TimesRead = 0;
  Msg.DestNode = 0;
  Msg.OrigNode = 0;
  Msg.Cost = 0;
  Msg.OrigNet = 0;
  Msg.DestNet = 0;
  Msg.DestZone = 0;
  Msg.OrigZone = 0;
  Msg.OrigPoint = 0;
  Msg.DestPoint = 0;
  Msg.ReplyTo = area->HighWater;
  Msg.Attr = FLAG_SENT | FLAG_PVT;
  Msg.NextReply = 0;

  MakeFullPath(area->area->Path, "1.msg", buf, 200);

  oldumask = umask(config.cfg_MsgbaseUmask);
  if (!(fh = fopen(buf, "wb"))) {
    umask(oldumask);
    LogWrite(1, SYSTEMERR, "Failed to write Highwater mark to %s", buf);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    return (FALSE);
  }
  umask(oldumask);

  if (fwrite(&Msg, 1, sizeof(struct StoredMsg), fh) != sizeof(struct StoredMsg)) {
    ioerror = TRUE;
    ioerrornum = errno;
  }

  if (fwrite("", 1, 1, fh) != 1) {
    ioerror = TRUE;
    ioerrornum = errno;
  }

  fclose(fh);

  if (ioerror)
    return (FALSE);

  return (TRUE);
}

bool msg_WriteMSG(struct MemMessage * mm, uchar * file, bool impseenby)
{
  struct StoredMsg Msg;
  struct TextChunk *chunk;
  struct Path *path;
  FILE *fh;
  int c;
  mode_t oldumask;

  strcpy(Msg.From, mm->From);
  strcpy(Msg.To, mm->To);
  strcpy(Msg.Subject, mm->Subject);
  strcpy(Msg.DateTime, mm->DateTime);

  Msg.TimesRead = 0;
  Msg.ReplyTo = 0;
  Msg.NextReply = 0;
  Msg.Cost = mm->Cost;
  Msg.Attr = mm->Attr;

  if (mm->Area[0] == 0) {
    Msg.DestZone = mm->DestNode.Zone;
    Msg.DestNet = mm->DestNode.Net;
    Msg.DestNode = mm->DestNode.Node;
    Msg.DestPoint = mm->DestNode.Point;

    Msg.OrigZone = mm->OrigNode.Zone;
    Msg.OrigNet = mm->OrigNode.Net;
    Msg.OrigNode = mm->OrigNode.Node;
    Msg.OrigPoint = mm->OrigNode.Point;
  } else {
    Msg.DestZone = 0;
    Msg.DestNet = 0;
    Msg.DestNode = 0;
    Msg.DestPoint = 0;

    Msg.OrigZone = 0;
    Msg.OrigNet = 0;
    Msg.OrigNode = 0;
    Msg.OrigPoint = 0;
  }

  oldumask = umask(config.cfg_MsgbaseUmask);
  if (!(fh = fopen(file, "wb"))) {
    umask(oldumask);
    LogWrite(1, SYSTEMERR, "Failed to write to %s\n", file);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    return (FALSE);
  }
  umask(oldumask);

  /* Write header */
  if (fwrite(&Msg, 1, sizeof(struct StoredMsg), fh) != sizeof(struct StoredMsg)) {
    ioerror = TRUE;
    ioerrornum = errno;
  }

  /* Write text */
  for (chunk = (struct TextChunk *) mm->TextChunks.First; chunk; chunk = chunk->Next) {
    if (chunk->Data)
      if (fwrite(chunk->Data, 1, strlen(chunk->Data), fh) != strlen(chunk->Data)) {
        ioerror = TRUE;
        ioerrornum = errno;
      }
  }

  /* Write seen-by */
  if (impseenby && mm->Area[0] != 0) {
    uchar *sbbuf;

    if (!(sbbuf = mmMakeSeenByBuf(&mm->SeenBy))) {
      fclose(fh);
      return (FALSE);
    }

    if (sbbuf[0]) {
      if (fwrite(sbbuf, 1, strlen(sbbuf), fh) != strlen(sbbuf)) {
        ioerror = TRUE;
        ioerrornum = errno;
      }
    }

    free(sbbuf);
  }

  /* Write path */
  for (path = (struct Path *) mm->Path.First; path; path = path->Next)
    for (c = 0; c < path->Paths; c++)
      if (path->Path[c][0] != 0) {
        if (fwrite("\001PATH: ", 1, 7, fh) != 7) {
          ioerror = TRUE;
          ioerrornum = errno;
        }

        if (fwrite(path->Path[c], 1, strlen(path->Path[c]), fh) != strlen(path->Path[c])) {
          ioerror = TRUE;
          ioerrornum = errno;
        }

        if (fwrite("\015", 1, 1, fh) != 1) {
          ioerror = TRUE;
          ioerrornum = errno;
        }
      }

  if (fputc(0, fh) == EOF) {
    ioerror = TRUE;
    ioerrornum = errno;
  }

  fclose(fh);

  if (ioerror)
    return (FALSE);

  return (TRUE);
}

ulong msg_ReadCR(uchar * buf, ulong maxlen, FILE *fh)
{
  /* Reads from fh until buffer full or CR */
  short ch, c = 0;

  ch = fgetc(fh);

  while (ch != EOF && ch != 0 && ch != 13 && c != maxlen - 2) {
    if(ch != 10)
      buf[c++] = ch;
    if (c != maxlen - 2)
      ch = fgetc(fh);
  }

  if (ch == 13 || ch == 10)
    buf[c++] = ch;

  buf[c] = 0;

  if (ch == 0)
    msg_messageend = TRUE;
  if (ch == EOF)
    msg_shortread = TRUE;

  return (c);
}


syntax highlighted by Code2HTML, v. 0.9.1