/********************************************************
 * File: outbound.c
 * Created at Sun Jan 28 22:10:31 MSK 2001 by raorn // raorn@binec.ru
 * Outbound handling
 * $Id: outbound.c,v 1.13 2002/10/27 22:47:52 raorn Exp $
 *******************************************************/
#include "crashecho.h"

struct jbList ArcList;

bool doAddFlow(uchar * filename, uchar * basename, uchar type, long mode);

void MakeBaseName(struct Node4D *n4d, uchar * basename)
{
  struct Aka *firstaka;
  uchar *ospathchars;
  ulong num, c;
  uchar buf[50];

  ospathchars = PATH_CHARS;

  firstaka = (struct Aka *) config.AkaList.First;

  /* Main domain */
  strcpy(basename, config.cfg_Outbound);

  if (basename[0]) {
    if (strchr(ospathchars, basename[strlen(basename) - 1]))
      basename[strlen(basename) - 1] = 0; /* Strip */
  }

  if (config.cfg_Flags & CFG_USEASO) {
    /* Add slash */
    c = strlen(basename);
    basename[c++] = ospathchars[0];
    basename[c++] = 0;

    /* Add ASO name */
    snprintf(buf, 50, "%u.%u.%u.%u", n4d->Zone, n4d->Net, n4d->Node, n4d->Point);
    strcat(basename, buf);
  } else {
    if (n4d->Zone != firstaka->Node.Zone) {
      /* Not in main zone */
      num = n4d->Zone;

      if (!(config.cfg_Flags & CFG_NOMAXOUTBOUNDZONE)) {
        if (num > 0xfff)
          num = 0xfff;
      }

      snprintf(buf, 50, ".%03lx", num);
      strcat(basename, buf);
    }

    if (!Exists(basename))
      mkdir(basename, 0777);

    /* Add slash */
    c = strlen(basename);
    basename[c++] = ospathchars[0];
    basename[c++] = 0;

    /* Add net/node */
    snprintf(buf, 50, "%04x%04x", n4d->Net, n4d->Node);
    strcat(basename, buf);

    if (n4d->Point) {
      strcat(basename, ".pnt");

      if (!Exists(basename))
        mkdir(basename, 0777);

      /* Add slash */
      c = strlen(basename);
      basename[c++] = ospathchars[0];
      basename[c++] = 0;

      /* Add point */
      snprintf(buf, 50, "%08x", n4d->Point);
      strcat(basename, buf);
    }
  }
}


void WriteIndex(void)
{
  FILE *fh;
  uchar buf[200];
  struct ConfigNode *cnode;

  MakeFullPath(config.cfg_PacketDir, "ceindex", buf, 200);

  /* Get basenum */
  if (!(fh = fopen(buf, "wt")))
    return;

  for (cnode = (struct ConfigNode *) config.CNodeList.First; cnode; cnode = cnode->Next)
    if (cnode->LastArcName[0]) {
      Print4D(&cnode->Node, buf, 200);
      fprintf(fh, "%s %s\n", buf, cnode->LastArcName);
    }

  fclose(fh);
}

void ReadIndex(void)
{
  FILE *fh;
  uchar buf[200], buf2[200];
  ulong jbcpos;
  struct ConfigNode *cnode, *c1, *c2;
  struct Node4D n4d;

  MakeFullPath(config.cfg_PacketDir, "ceindex", buf, 200);

  /* Get basenum */
  if (!(fh = fopen(buf, "rt")))
    return;

  while (fgets(buf, 200, fh)) {
    striptrail(buf);

    jbcpos = 0;

    jbstrcpy(buf2, buf, 200, &jbcpos);

    if (Parse4D(buf2, &n4d)) {
      jbstrcpy(buf2, buf, 200, &jbcpos);

      for (cnode = (struct ConfigNode *) config.CNodeList.First; cnode; cnode = cnode->Next)
        if (Compare4D(&cnode->Node, &n4d) == 0)
          mystrncpy(cnode->LastArcName, buf2, 13);
    }
  }

  fclose(fh);

  /* Check for duplicates */
  for (c1 = (struct ConfigNode *) config.CNodeList.First; c1; c1 = c1->Next)
    for (c2 = c1->Next; c2; c2 = c2->Next)
      if (c1->LastArcName[0] && hextodec(c1->LastArcName) == hextodec(c2->LastArcName)) {
        LogWrite(1, TOSSINGINFO,
                 "Warning: The same bundle name is used for %u:%u/%u.%u and %u:%u/%u.%u",
                 c1->Node.Zone, c1->Node.Net, c1->Node.Node, c1->Node.Point,
                 c2->Node.Zone, c2->Node.Net, c2->Node.Node, c2->Node.Point);

        LogWrite(1, TOSSINGINFO, "Cleared bundle name for %u:%u/%u.%u",
                 c2->Node.Zone, c2->Node.Net, c2->Node.Node, c2->Node.Point);

        c2->LastArcName[0] = 0;
        WriteIndex();
      }
}

bool ExistsBasenum(ulong num)
{
  uchar name[20];
  struct FileEntry *fe;
  struct ConfigNode *cnode;

  snprintf(name, 20, "%08lx.", num);

  for (fe = (struct FileEntry *) ArcList.First; fe; fe = fe->Next)
    if (IsArc(fe->Name) && hextodec(fe->Name) == num)
      return (TRUE);

  for (cnode = (struct ConfigNode *) config.CNodeList.First; cnode; cnode = cnode->Next)
    if (cnode->LastArcName[0] && hextodec(cnode->LastArcName) == num)
      return (TRUE);

  return (FALSE);
}

bool ExistsBundle(ulong basenum, ulong num)
{
  uchar name[20];
  struct FileEntry *fe;
  uchar *daynames[] = { "su", "mo", "tu", "we", "th", "fr", "sa" };

  snprintf(name, 20, "%08lx.%s%ld", basenum, daynames[num / 10], num % 10);

  for (fe = (struct FileEntry *) ArcList.First; fe; fe = fe->Next)
    if (stricmp(fe->Name, name) == 0)
      return (TRUE);

  return (FALSE);
}

void MakeArcName(struct ConfigNode *cnode, uchar * dest, size_t destlen)
{
  struct FileEntry *fe, *fe2, *foundfe;
  uchar arc[20], buf[200], ext[10];
  ulong basenum;
  long suffix, newsuffix, day, i;
  uchar *daynames[] = { "su", "mo", "tu", "we", "th", "fr", "sa" };
  time_t t;
  struct tm *tp;

  time(&t);
  tp = localtime(&t);

  day = tp->tm_wday;

  /* Get basenum */
  if (!cnode->LastArcName[0]) {
    basenum = time(NULL);

    while (ExistsBasenum(basenum))
      basenum++;
  } else {
    basenum = hextodec(cnode->LastArcName);
  }

  /* Get latest in list */
  foundfe = NULL;

  if (cnode->LastArcName[0]) {
    for (fe = (struct FileEntry *) ArcList.First; fe; fe = fe->Next)
      if (stricmp(cnode->LastArcName, fe->Name) == 0)
        foundfe = fe;
  }

  /* Get extension of latest */
  suffix = -1;

  if (foundfe) {
    strncpy(ext, &foundfe->Name[strlen(foundfe->Name) - 3], 3);
    ext[2] = 0;

    for (i = 0; i < 7; i++) {
      if (stricmp(ext, daynames[i]) == 0) {
        suffix = i * 10;
        suffix += foundfe->Name[strlen(foundfe->Name) - 1] - '0';
      }
    }
  }

  if (suffix == -1) {
    if ((config.cfg_Flags & CFG_WEEKDAYNAMING))
      newsuffix = day * 10;

    else
      newsuffix = 0;
  } else {
    /* There was an old bundle */
    newsuffix = suffix;

    if (foundfe->Size == 0)
      newsuffix = -1;

    if (foundfe->Size > config.cfg_MaxBundleSize)
      newsuffix = -1;

    if ((config.cfg_Flags & CFG_WEEKDAYNAMING) && suffix / 10 != day)
      newsuffix = -1;

    if (newsuffix == -1) {
      newsuffix = suffix + 1;
      if (newsuffix == 70)
        newsuffix = 0;

      if ((config.cfg_Flags & CFG_WEEKDAYNAMING) && newsuffix / 10 != day)
        newsuffix = day * 10;

      if (ExistsBundle(basenum, newsuffix))
        newsuffix = suffix;
    }
  }

  /* Delete zero length bundles for this node */
  fe = (struct FileEntry *) ArcList.First;
  snprintf(arc, 20, "%08lx.", basenum);

  while (fe) {
    fe2 = fe->Next;

    if (strnicmp(arc, fe->Name, 9) == 0 && fe->Size == 0) {
      MakeFullPath(config.cfg_PacketDir, fe->Name, buf, 200);
      unlink(buf);
      jbFreeNode(&ArcList, (struct jbNode *) fe);
    }

    fe = fe2;
  }

  snprintf(dest, destlen, "%08lx.%s%ld", basenum, daynames[newsuffix / 10], newsuffix % 10);

  if (stricmp(cnode->LastArcName, dest) != 0) {
    mystrncpy(cnode->LastArcName, dest, 13);
    WriteIndex();
  }
}

void HandleOrphan(uchar * name)
{
  FILE *fh;
  uchar buf[200], buf2[200];
  char type;
  bool mode;
  ulong jbcpos;
  struct Node4D n4d;

  if (!(fh = fopen(name, "rt"))) {
    LogWrite(1, SYSTEMERR, "Failed to open orphan file \"%s\"", name);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    return;
  }

  if (!fgets(buf, 100, fh)) {
    LogWrite(1, SYSTEMERR, "Orphan file \"%s\" contains no information", name);
    fclose(fh);
    return;
  }

  fclose(fh);

  jbcpos = 0;

  jbstrcpy(buf2, buf, 100, &jbcpos);

  if (stricmp(buf2, "Normal") == 0)
    type = PKTS_NORMAL;

  else if (stricmp(buf2, "Hold") == 0)
    type = PKTS_HOLD;

  else if (stricmp(buf2, "Direct") == 0)
    type = PKTS_DIRECT;

  else if (stricmp(buf2, "Crash") == 0)
    type = PKTS_CRASH;

  else {
    LogWrite(1, SYSTEMERR, "Unknown flavour \"%s\" in \"%s\"", buf2, name);
    return;
  }

  jbstrcpy(buf2, buf, 100, &jbcpos);

  if (!Parse4D(buf2, &n4d)) {
    LogWrite(1, SYSTEMERR, "Invalid node \"%s\" in \"%s\"", buf2, name);
    return;
  }

  mode = FLOW_NONE;

  jbstrcpy(buf2, buf, 100, &jbcpos);

  if (stricmp(buf2, "Truncate") == 0)
    mode = FLOW_TRUNC;

  if (stricmp(buf2, "Delete") == 0)
    mode = FLOW_DELETE;

  mystrncpy(buf, name, 200);
  buf[strlen(buf) - 7] = 0;     /* Remove .orphan */

  if (AddFlow(buf, &n4d, type, mode))
    unlink(name);             /* Orphan file no longer needed */
}

void MakeOrphan(uchar * file, struct Node4D *n4d, char type, long mode)
{
  uchar buf[200];
  FILE *fh;

  snprintf(buf, 200, "%s.orphan", file);

  if (!(fh = fopen(buf, "wt"))) {
    LogWrite(1, SYSTEMERR, "Failed to open \"%s\", cannot make .orphan file", buf);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    return;
  }

  snprintf(buf, 200, "%s %d:%d/%d.%d", prinames[(int) type], n4d->Zone, n4d->Net, n4d->Node, n4d->Point);
  if (mode == FLOW_TRUNC)
    strcat(buf, " Truncate");
  if (mode == FLOW_DELETE)
    strcat(buf, " Delete");

  fputs(buf, fh);
  fclose(fh);
}

/* Only call if file is already locked */
/* MakeOrphan() should be called if necessary */
bool doAddFlow(uchar * filename, uchar * basename, uchar type, long mode)
{
  uchar buf[200], letter, *prefix;
  FILE *fh;

  switch (type) {
  case PKTS_NORMAL:
  case PKTS_ECHOMAIL:
    letter = 'f';
    break;
  case PKTS_HOLD:
    letter = 'h';
    break;
  case PKTS_DIRECT:
    letter = 'd';
    break;
  case PKTS_CRASH:
    letter = 'c';
    break;
  default:
    letter = 'f';
  }

  snprintf(buf, 200, "%s.%clo", basename, letter);

  if (!(fh = fopen(buf, "r+t")) && !(fh = fopen(buf, "w+t"))) {
    LogWrite(1, SYSTEMERR, "Failed to open \"%s\"", buf);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    return (FALSE);
  }

  while (fgets(buf, 200, fh)) {
    striptrail(buf);

    if (buf[0] == '#')
      strcpy(buf, &buf[1]);
    if (buf[0] == '~')
      continue;
    if (buf[0] == '^')
      strcpy(buf, &buf[1]);

    if (stricmp(buf, filename) == 0) {
      fclose(fh);
      return (TRUE);            /* Was already in flow file */
    }
  }

  fseek(fh, 0, SEEK_END);

  prefix = "";

  if (mode == FLOW_TRUNC)
    prefix = "#";

  if (mode == FLOW_DELETE)
    prefix = "^";

  fprintf(fh, "%s%s\n", prefix, filename);
  fclose(fh);

  return (TRUE);
}

/* Handles locking and MakeOrphan() */
bool AddFlow(uchar * filename, struct Node4D * n4d, uchar type, long mode)
{
  uchar basename[200];

  MakeBaseName(n4d, basename);

  if (!Lock(basename, config.cfg_Flags & CFG_LOCKBYLINK, TRUE)) {
    LogWrite(1, SYSTEMERR, "Cannot add to %s, node is busy...\n", GetFilePart(basename));
    MakeOrphan(filename, n4d, type, mode);
    return (FALSE);
  }

  if (!doAddFlow(filename, basename, type, mode))
    MakeOrphan(filename, n4d, type, mode);

  Unlock(basename);

  return (TRUE);
}

bool MakePktTmp(uchar * name)
{
  uchar buf[200];

  MakeFullPath(config.cfg_PacketDir, GetFilePart(name), buf, 200);
  strcpy(&buf[strlen(buf) - 6], "pkttmp"); /* Change suffix */

  if (!movefile(name, buf)) {
    LogWrite(1, SYSTEMERR, "Failed to move file \"%s\" to \"%s\"", name, buf);
    return (FALSE);
  }

  return (TRUE);
}

void UpdateFile(uchar * name)
{
  struct FileEntry *newfe, *fe;

  if (!(newfe = GetFileEntry(name)))
    return;

  for (fe = (struct FileEntry *) ArcList.First; fe; fe = fe->Next)
    if (stricmp(fe->Name, name) == 0)
      break;

  if (fe) {
    fe->Date = newfe->Date;
    fe->Size = newfe->Size;
    free(newfe);
  } else {
    jbAddNode(&ArcList, (struct jbNode *) newfe);
  }
}

#define COPYBUFSIZE 5000

bool PackFile(char *file)
{
  uchar basename[200], arcname[200], pktname[200], buf[200], buf2[200];
  ulong jbcpos;
  int c, res, rc;
  struct Node4D n4d;
  struct ConfigNode *cnode;

  /* Parse filename */
  mystrncpy(buf, GetFilePart(file), 200);

  for (c = 0; buf[c]; c++)
    if (buf[c] == '_')
      buf[c] = ' ';

  jbcpos = 0;

  /* Skip hexnum */
  jbstrcpy(buf2, buf, 100, &jbcpos);

  /* Skip pktorig */
  jbstrcpy(buf2, buf, 100, &jbcpos);
  jbstrcpy(buf2, buf, 100, &jbcpos);
  jbstrcpy(buf2, buf, 100, &jbcpos);
  jbstrcpy(buf2, buf, 100, &jbcpos);

  /* Get pktdest */
  jbstrcpy(buf2, buf, 100, &jbcpos);
  n4d.Zone = atol(buf2);

  jbstrcpy(buf2, buf, 100, &jbcpos);
  n4d.Net = atol(buf2);

  jbstrcpy(buf2, buf, 100, &jbcpos);
  n4d.Node = atol(buf2);

  jbstrcpy(buf2, buf, 100, &jbcpos);
  n4d.Point = atol(buf2);

  /* Make basename for this node */
  MakeBaseName(&n4d, basename);

  if (!Lock(basename, config.cfg_Flags & CFG_LOCKBYLINK, TRUE)) {
    LogWrite(1, TOSSINGINFO, "Cannot add \"%s\" to outbound, node is busy...", GetFilePart(file));
    return (FALSE);
  }

  /* Handle echomail packet */
  for (cnode = (struct ConfigNode *) config.CNodeList.First; cnode; cnode = cnode->Next)
    if (Compare4D(&cnode->Node, &n4d) == 0)
      break;

  if (cnode && cnode->Packer) {
    /* Pack echomail */
    MakeArcName(cnode, buf, 200);
    MakeFullPath(config.cfg_PacketDir, buf, arcname, 200);

    mystrncpy(pktname, file, 200);
    GetFilePart(pktname)[8] = 0;
    strcat(pktname, ".pkt");

    LogWrite(4, TOSSINGINFO, "Packing %s for %d:%d/%d.%d with %s",
             GetFilePart(pktname), cnode->Node.Zone, cnode->Node.Net,
             cnode->Node.Node, cnode->Node.Point, cnode->Packer->Name);

    ExpandPacker(cnode->Packer->Packer, buf, 200, arcname, pktname);

    rename(file, pktname);
    res = Execute(buf, &rc);

    if (!res && rc == 0) {
      UpdateFile(arcname);

      unlink(pktname);

      if (!doAddFlow(arcname, basename, cnode->EchomailPri, FLOW_TRUNC))
        MakeOrphan(arcname, &n4d, cnode->EchomailPri, FLOW_TRUNC);
    } else {
      rename(pktname, file);
      if (rc)
        LogWrite(1, SYSTEMERR, "Packer failed: %lu", rc);
      else
        LogWrite(1, SYSTEMERR, "Failed to run packer");
      Unlock(basename);
      return (FALSE);
    }
  } else {
    /* Send unpacked echomail */
    MakeFullPath(config.cfg_PacketDir, GetFilePart(file), pktname, 200);
    GetFilePart(pktname)[8] = 0;
    strcat(pktname, ".pkt");

    LogWrite(4, TOSSINGINFO, "Sending %s unpacked to %d:%d/%d.%d",
             GetFilePart(pktname), cnode->Node.Zone, cnode->Node.Net,
             cnode->Node.Node, cnode->Node.Point);

    if (!movefile(file, pktname)) {
      LogWrite(1, SYSTEMERR, "Failed to move file \"%s\" to \"%s\"", file, pktname);
      Unlock(basename);
      return (FALSE);
    } else {
      if (!doAddFlow(pktname, basename, cnode->EchomailPri, FLOW_DELETE))
        MakeOrphan(pktname, &n4d, cnode->EchomailPri, FLOW_DELETE);
    }
  }

  Unlock(basename);

  if (ioerror)
    return (FALSE);

  return (TRUE);
}

bool ArchiveOutbound(void)
{
  struct jbList PktList;
  struct FileEntry *fe;
  uchar buf[200];

  /* Orphan files */
  LogWrite(3, ACTIONINFO, "Scanning for orphan files");

  if (!(ReadDir(config.cfg_PacketDir, &ArcList, IsOrphan))) {
    LogWrite(1, SYSTEMERR, "Failed to read directory \"%s\"", config.cfg_PacketDir);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    return (FALSE);
  }

  SortFEList(&ArcList);

  for (fe = (struct FileEntry *) ArcList.First; fe && !ctrlc; fe = fe->Next) {
    LogWrite(1, SYSTEMINFO, "Found orphan file \"%s\", retrying...", fe->Name);

    MakeFullPath(config.cfg_PacketDir, fe->Name, buf, 200);
    HandleOrphan(buf);
  }

  jbFreeList(&ArcList);

  /* Read ArcList */
  if (!(ReadDir(config.cfg_PacketDir, &ArcList, IsArc))) {
    LogWrite(1, SYSTEMERR, "Failed to read directory \"%s\"", config.cfg_PacketDir);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    return (FALSE);
  }

  /* Read index */
  ReadIndex();

  /* Old packets */
  LogWrite(3, ACTIONINFO, "Scanning for old packets");

  if (!(ReadDir(config.cfg_PacketDir, &PktList, IsPktTmp))) {
    LogWrite(1, SYSTEMERR, "Failed to read directory \"%s\"", config.cfg_PacketDir);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    jbFreeList(&ArcList);
    return (FALSE);
  }

  SortFEList(&PktList);

  for (fe = (struct FileEntry *) PktList.First; fe; fe = fe->Next) {
    LogWrite(1, SYSTEMINFO, "Found old packet file \"%s\", retrying...", fe->Name);

    MakeFullPath(config.cfg_PacketDir, fe->Name, buf, 200);
    PackFile(buf);
  }

  jbFreeList(&PktList);

  /* New packets */
  LogWrite(3, ACTIONINFO, "Scanning for new files to pack");

  if (!(ReadDir(config.cfg_PacketCreate, &PktList, IsNewPkt))) {
    LogWrite(1, SYSTEMERR, "Failed to read directory \"%s\"", config.cfg_PacketCreate);
    LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno));
    jbFreeList(&ArcList);
    return (FALSE);
  }

  SortFEList(&PktList);

  for (fe = (struct FileEntry *) PktList.First; fe; fe = fe->Next) {
    MakeFullPath(config.cfg_PacketCreate, fe->Name, buf, 200);

    if (!PackFile(buf))
      if (!MakePktTmp(buf)) {
        jbFreeList(&PktList);
        jbFreeList(&ArcList);
        return (FALSE);
      }
  }

  jbFreeList(&PktList);
  jbFreeList(&ArcList);

  return (TRUE);
}


syntax highlighted by Code2HTML, v. 0.9.1