#include <sys/stat.h>
#include <unistd.h>

#include <stdio.h>  // rename

#include "buffer.h"
#include "byte.h"
#include "case.h"
#include "cdbmake.h"
#include "exit.h"
#include "fmt.h"
#include "getln.h"
#include "logmsg.h"
#include "open.h"
#include "scan.h"
#include "stralloc.h"

#include "auto_qmail.h"

#define WHO "qmail-mfrules"

int rename(const char *, const char *);  // stdio.h

stralloc address = {0};
stralloc data = {0};
stralloc key = {0};
stralloc line = {0};

char inbuf[1024];
buffer bi;

int fd;
int fdtemp;
int match = 1;

struct cdb_make cdb;

void die_nomem()
{
  logmsg(WHO, 112, FATAL, "out of memory");
}
void die_parse()
{
  if (!stralloc_0(&line)) die_nomem();
  logmsg(WHO, 100, ERROR, B("unable to parse this line: ", line.s));
}
void die_read()
{
  logmsg(WHO, 111, ERROR, "unable to read control/mailfromrules");
}
void die_write()
{
  logmsg(WHO, 111, ERROR, "unable to write to control/mailfromrules.tmp");
}

char strnum[FMT_ULONG];
stralloc sanum = {0};

void getnum(char *buf, int len, unsigned long *u)
{
  if (!stralloc_copyb(&sanum, buf, len)) die_nomem();
  if (!stralloc_0(&sanum)) die_nomem();
  if (sanum.s[scan_ulong(sanum.s, u)]) die_parse();
}

void doaddressdata()
{
  int i;
  int left;
  int right;
  unsigned long bot;
  unsigned long top;

  if (byte_chr(address.s, address.len, '=') == address.len)
    if (byte_chr(address.s, address.len, '@') == address.len) {
      i = byte_chr(address.s, address.len, '-');
      if (i < address.len) {
        left = byte_rchr(address.s, i, '.');
        if (left == i)
          left = 0;
        else
          ++left;

        ++i;
        right = i + byte_chr(address.s + i, address.len - i, '.');

        getnum(address.s + left, i - 1 - left, &bot);
        getnum(address.s + i, right - i, &top);
        if (top > 255) top = 255;

        while (bot <= top) {
          if (!stralloc_copyb(&key, address.s, left)) die_nomem();
          if (!stralloc_catb(&key, strnum, fmt_ulong(strnum, bot))) die_nomem();
          if (!stralloc_catb(&key, address.s + right, address.len - right)) die_nomem();
          case_lowerb(key.s, key.len);
          if (cdb_make_add(&cdb, key.s, key.len, data.s, data.len) == -1) die_write();
          ++bot;
        }

        return;
      }
    }

  case_lowerb(address.s, address.len);
  case_lowerb(data.s, data.len);
  if (cdb_make_add(&cdb, address.s, address.len, data.s, data.len) == -1) die_write();
}

int main()
{
  int amper;
  int i;
  int len;
  char *x;
  char ch;

  umask(033);
  if (chdir(auto_qmail) == -1) logmsg(WHO, 111, ERROR, B("unable to chdir to: ", auto_qmail));

  fd = open_read("control/mailfromrules");
  if (fd == -1) die_read();

  buffer_init(&bi, read, fd, inbuf, sizeof(inbuf));

  fdtemp = open_trunc("control/mailfromrules.tmp");
  if (fdtemp == -1) die_write();

  if (cdb_make_start(&cdb, fdtemp) == -1) die_write();

  while (match) {
    if (getln(&bi, &line, &match, '\n') != 0) die_read();

    x = line.s;
    len = line.len;

    if (!len) break;
    if (x[0] == '#') continue;
    if (x[0] == '\n') continue;

    while (len) {
      ch = x[len - 1];
      if (ch != '\n')
        if (ch != ' ')
          if (ch != '\t') break;
      --len;
    }
    line.len = len; /* for die_parse() */

    amper = byte_chr(x, len, '&');
    if (!amper) die_parse();
    if (amper)
      if (amper == len || amper < 2) die_parse();

    if (!stralloc_copyb(&address, x, amper)) die_nomem();
    if (!stralloc_copys(&data, "")) die_nomem();

    x = line.s + amper + 1;
    len = line.len - amper - 1;

    while (len) {
      if (len < 3) die_parse(); /* input checks */
      if (*x == ',' || *x == ' ' || *x == '\t') die_parse();
      i = byte_chr(x, len, ','); /* &addr1,addr2,.. */
      if (i > 0 && i < len) {
        if (!stralloc_catb(&data, "+", 1)) die_nomem();
        if (!stralloc_catb(&data, x, i)) die_nomem();
        x += i + 1;
        len -= i + 1;
      } else {
        if (!stralloc_catb(&data, "+", 1)) die_nomem();
        if (!stralloc_catb(&data, x, len)) die_nomem();
        len = 0;
      }
    }
    doaddressdata();
  }

  if (cdb_make_finish(&cdb) == -1) die_write();
  if (fsync(fdtemp) == -1) die_write();
  if (close(fdtemp) == -1) die_write(); /* NFS stupidity */
  if (rename("control/mailfromrules.tmp", "control/mailfromrules.cdb") == -1)
    logmsg(WHO, 111, ERROR, "unable to move control/mailfromrules.tmp to control/mailfromrules.cdb");

  _exit(0);
}