#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#include "h.h"
#ifdef HEADER_REGEX
#include <regex.h>
#ifndef REG_EXTENDED
#undef HEADER_REGEX
#endif
#define	REG_OPT	(REG_EXTENDED|REG_ICASE)
#endif

static
char rcsid[] = "$Id: h.c,v 1.17 2013/04/07 15:39:07 yuuji Exp yuuji $";

/*
 * This module should be sharpened.
 */

#ifdef H_MAIN_
#define DEBUG
#endif

static
int isdir(char *path)
{
  struct stat s;
  return (0==stat(path, &s) && (s.st_mode&S_IFDIR));
}

static
int init(HM *hm)
{
  char buf[160];
  DIR *topdir, *dbdir, *entdir;
  FILE *fp;
  struct dirent *d, *dd, *ddd;
  static STR path = {0}, path2 = {0}, vbuf={0};
  struct patterns *ptns;
  struct a_pattern *aptn;
  if (NULL == hm->dir) return -1;
  if (NULL == (topdir=opendir(hm->dir))) return hm->n = -1;
  hm->n = 0; hm->ptn = 0;
  // fprintf(stderr, "opendir[%s]\n", hm->dir);
  while (NULL != (d=readdir(topdir))) {
    if (!strcmp(".", d->d_name) || !strcmp("..", d->d_name)) continue;
    if (!strop(&path, "=", hm->dir)) goto outtop;
    if (!strop(&path, "join", "/", d->d_name, NULL)) goto outtop;
    if (!isdir(path.s)) continue;
    //fprintf(stderr, "OPENDIR[%s]...\n", path.s);
    if (NULL == (dbdir=opendir(path.s))) continue;
    /* Create new */
    hm->ptn=realloc(hm->ptn, (++hm->n)*(sizeof (struct patterns*)));
    //fprintf(stderr, "hm->n[%d]...", hm->n);
    if (NULL == hm->ptn)
      goto outtop;
    if (NULL == ((hm->ptn)[hm->n-1]
                 = (struct patterns*)malloc(sizeof (struct patterns))))
      goto outtop;
    ptns = (hm->ptn)[hm->n-1];
    memset(ptns, 0, sizeof *ptns);
    if (!strop(&ptns->name, "=", d->d_name)) goto outtop;
    strop(&ptns->name, "lower");
    while (NULL != (dd=readdir(dbdir))) {
      char *fn = dd->d_name, *colon, *env;
      static STR file = {0};
      if (!strcmp(".", fn) || !strcmp("..", fn)) continue;
      if (!strop(&path2, "=", path.s)) goto outdb;
      if (!strop(&path2, "join", "/", fn, NULL)) goto outdb;
      if (!isdir(path2.s)) continue;

      /* Read ptn */
      //fprintf(stderr, "peeking file[%s], ra=%p\n", file.s, ptns->a_ptn);
      // // // if (NULL == (fp=fopen(file.s, "r"))) continue;
      if (NULL == (entdir=opendir(path2.s))) continue;
      while (NULL != (ddd=readdir(entdir))) {
        if ('v' == ddd->d_name[0] && (colon=strchr(ddd->d_name, ':'))) {
          /* If filename matches with `v*:VARNAME', include its pattern
             iff environment variable VARNAME is set and not equal to "0". */
          memset(buf, 0, sizeof buf);
          strncpy(buf, colon+1, sizeof buf-1);
          fprintf(stderr, "try this %s\n", buf);
          if (!buf[0] || NULL == (env=getenv(buf)) || 0 == strcmp(env, "0"))
            continue;
        } else if ('p' != ddd->d_name[0]) continue;
        /* Expected file name, start to parse  */
        if (!strop(&file, "=", path2.s)) goto outdb;
        if (!strop(&file, "join", "/", ddd->d_name, NULL)) goto outdb;

        if (NULL == (fp=fopen(file.s, "r"))) continue;
        if (NULL == (ptns->a_ptn
                     = realloc(ptns->a_ptn,
                               (++ptns->n)*sizeof (struct a_pattern*))))
          goto outdb;
        if (NULL == ((ptns->a_ptn)[ptns->n-1]
                     = (struct a_pattern*)malloc(sizeof (struct a_pattern))))
          goto outdb;
        /* fprintf(stderr, "reading file [%s]\n", file.s); */
        aptn = (ptns->a_ptn)[ptns->n-1];
        memset(aptn, 0, sizeof *aptn); /* Do not forget this */
        while (NULL != fgets(buf, sizeof buf, fp)) {
          STR *s;
          /* Check if buf has newline  */
          if (!strop(&vbuf, "+", buf)) goto outfp;
          if ('\n' != buf[strlen(buf)-1]) {
            continue;           /* No newline */
          }
          /* Alloc pattern */
          if (NULL == (aptn->pattern
                       =realloc(aptn->pattern,
                                (++aptn->n)*sizeof (STR*))))
            goto outfp;
          if (NULL == ((aptn->pattern)[aptn->n-1]
                       =(STR*)malloc(sizeof (STR))))
            goto outfp;
          s = (aptn->pattern)[aptn->n-1];
          memset(s, 0, sizeof *s);
          if (!strop(s, "=", vbuf.s)) goto outfp;
          strop(s, "chomp");
          strop(s, "lower");
          strop(&vbuf, "=", ""); /* Reset vbuffer */
#ifdef DEBUG
          fprintf(stderr, "Entry[%s][%s][%s][%d]=[%s]\n", ptns->name.s, fn, ddd->d_name, aptn->n, (aptn->pattern[aptn->n-1])->s);
#endif
        }
        fclose(fp);
        /* Set pattern name from file name */
        strop(&aptn->pname, "=", "");
        if (!strop(&aptn->pname, "join", dd->d_name, "/", ddd->d_name, NULL))
           goto outdb;
        /* Read corresponding error message */
        strop(&aptn->errmsg, "=", "");
        if (!strop(&file, "=", path2.s)) goto outdb;
        if (!strop(&file, "join", "/errmsg", NULL)) goto outdb;
        strop(&aptn->errmsg, "=", ""); /* Clear default error message */
        if (NULL == (fp=fopen(file.s, "r"))) {
          continue;
        }
        while (NULL != fgets(buf, sizeof buf, fp)) {
          char *p = buf;
          while (*p && isspace((int)*p)) p++;
          if ('#' == *p) continue;
        if (!strop(&aptn->errmsg, "+", buf)) goto outdb;
        }
      //fprintf(stderr, "msg=%s\n", aptn->errmsg.s);
        fclose(fp);
        //  fprintf(stderr, "Total patterns for %s = %d\n", fn, aptn->n);
      }
    }
    //fprintf(stderr, "dbdir close\n");
    closedir(dbdir);
    //fprintf(stderr, "Total entries for %s = %d\n", ptns->name.s, ptns->n);
  }
  closedir(topdir);
  return 0;
  outtop:
  closedir(topdir);
  outdb:
  closedir(dbdir);
  outfp:
  fclose(fp);
  strop(&vbuf, "!");
  return -1;
}

/*
 * Return: 0: no match
 *         1: match
 *        -1: error
 * Using linear search, which should be replaced with more intelligent one.
 *
 * Matching pattern string is one of as follows.
 *  =STRING	Match with entire line
 *  ^STRING	Match with the beginning of line
 *  $STRING	Match with the end of line
 *  /REGEXP	Match with REGEXP in a form of regular expression(regex(3))
 *  :STRING	Match with any part of line
 *  STRING	Same as :STRING if STRING doesn't start widh any of =^$/:
 *
 * Multiple-lines pattern will be compared respectively from left to right.
 * Comparing with the second pattern is performed against the position after
 * the end of first-line pattern matched.  Third pattern is compared with
 * sub-string after second pattern matching, and so on.
 */

int
match_internal(HM *hm, char *header, char *line, HMR *hmr)
{
  int nhm, nptns, naptn;
  struct patterns *ptns;
  struct a_pattern *aptn;
  STR *s;
  char *t;
#ifdef HEADER_REGEX
  regex_t re;
  regmatch_t rm[2];
  int nmatch = (sizeof rm/sizeof rm[0])-1;
#endif
  for (t=line+strlen(line)-1; line<t && *t && strchr("\r\n", *t); t--);
  hmr->rcode = 0;
  hmr->msg = NULL;
  for (nhm=0; nhm < hm->n; nhm++) {
    if (strcasecmp(header, hm->ptn[nhm]->name.s)) continue;
    ptns = hm->ptn[nhm];
    for (nptns=0; nptns < ptns->n; nptns++) {
      int match = 1, r, len;
      char *p = line;
      aptn = ptns->a_ptn[nptns];
      for (naptn=0; naptn < aptn->n; naptn++) {
        while (*p && isspace((int)*p)) p++;
        if (!*p) {
          match = 0;
          break;
        }
        s = aptn->pattern[naptn];
        len = s->len-1;
        switch (s->s[0]) {
        case '=':
          r = (!strncmp(s->s+1, p, len) && strchr("\r\n", *(p+len)));
          break;
        case '^':
          r = !strncmp(s->s+1, p, len);
          break;
        case '$':
          r = !strncmp(s->s+1, t-len+1, len);
          break;
#ifdef HEADER_REGEX
        case '/':
          /* Pattern `s' is chomped in init() */
          if (regcomp(&re, s->s+1, REG_OPT) != 0) {
            fprintf(stderr, "regex error in %s: %s\n", aptn->pname.s, s->s);
            return 0;
          }
          r= regexec(&re, p, nmatch, rm, 0); /* r becomes 0 if match */
          regfree(&re);                 /* free regex area */
          r = r ? 0 : 1;                /* negate r */
          if (r) len = rm[0].rm_eo;     /* real length to skip */
          break;
#endif
        case ':':
          r = (strstr(p, s->s+1) ? 1 : 0);
          break;
        default:
          len = s->len;
          r = (strstr(p, s->s) ? 1 : 0);
        }
        match *= (r ? 1 : 0);
        if (match == 0) break;
        p += len;
      }
      if ((hmr->rcode=match)) {
        hmr->msg = &aptn->errmsg;
        hmr->pname = &aptn->pname;
        return 1;
      }
    }
  }
  return 0;
}

void header_match_delete(HM *hm)
{
  int nhm, nptns, naptn;
  struct patterns *ptns;
  struct a_pattern *aptn;
  STR *s;
  for (nhm=0; nhm < hm->n; nhm++) {
    ptns = hm->ptn[nhm];
    for (nptns=0; nptns < ptns->n; nptns++) {
      aptn = ptns->a_ptn[nptns];
      for (naptn=0; naptn < aptn->n; naptn++) {
        s = aptn->pattern[naptn];
        if (s) {
          strop(s, "!");          /* free STR */
          free(s);
        }
      }
      free(aptn->pattern);
      free(aptn);
    }
    free(ptns->a_ptn);
    free(ptns);
  }
  free(hm->ptn);
  hm->n = 0;
}

/*
 * Return: 0: no match
 *         1: match
 *        -1: error
 */
int
header_match(HM *hm, char *header_line, HMR *hmr)
{
  int r = 0;
  static STR header = {0};
  char *colon;
  if (0 == hm->n) {
    r=init(hm);
    if (r<0) {
      fprintf(stderr, "header_match: init failure(%d)\n", r);
      return r;
    }
  }
  if (NULL == (colon=strchr(header_line, ':'))) return 0;
  if (!strop(&header, "n=", header_line, colon-header_line)) return -1;
  if (strchr(header.s, ' ') || strchr(header.s, '\t')) return 0;
  
  if (0 < (r=match_internal(hm, header.s, colon+1, hmr)))
    hmr->where = header.s;
  return r;
}

#ifdef H_MAIN_
char b[500];

int memabort()
{
  fprintf(stderr, "Can't malloc\n");
  exit(2);
}

int reg_debug(char *argv[], int iarg, int stdinmatch)
{
  regex_t *rp;
  regex_t **re = NULL;
  regmatch_t rm[2];
  int i, rc, r=0, rn=0, ln;
  FILE *fp;
  char *buf = NULL, *begin;
  struct stat s;
  for (i=iarg, ln=0; argv[i]; i++) {
    if (-1 == stat(argv[i], &s))
      continue;
    if (NULL == (fp=fopen(argv[i], "r")))
      continue;
    if (NULL == (buf=realloc(buf, s.st_size))) memabort();
    while ((begin=fgets(buf, s.st_size, fp))) {
      ln++;
      *begin=='/' && begin++;
      if (strchr("\n\r", begin[strlen(begin)-1]))
        begin[strlen(begin)-1] = '\0';
      if (!begin[0]) continue;
      (rp = malloc(sizeof *rp)) || memabort();
      if ((rc=regcomp(rp, begin, REG_OPT))) {
        regfree(rp);            /* regcomp error */
        fprintf(stderr, "%s:%d regcomp error %d: %s\n", argv[i], ln, rc, buf);
      } else {
        re = realloc(re, ++rn*(sizeof *re));
        if (NULL == re) memabort();
        re[rn-1] = rp;
        printf("#%d rc=%d: %s\n", rn-1, r+=rc, buf);
      }
    }
    fclose(fp);
  }
  if (!stdinmatch)
    return r!=0;
  r=1;
  while (fgets(b, sizeof b, stdin)) {
    STR line = {0};
    strop(&line, "=", b);
    strop(&line, "chomp");
    for (i=0; i<rn; i++)
      if (0 == regexec(re[i], line.s, 1, rm, 0)) {
        printf("%s ->Rule=%d, offset=%d, length=%d\n",
               line.s, i, (int)rm[0].rm_so, (int)rm[0].rm_eo-(int)rm[0].rm_so);
        r=0;
      }
  }
  return r;
}

int reg_help(char *me)
{
  printf("Usage %s %s\n", me, "[-option]\n\
Options are:\n\
  -r PtnFile(s)		Check regex syntax of PtnFile(s).\n\
  -R PtnFile(s)		Do regexp matching with lines from stdin.\n\
\n\
Without option:\n\
  Read header lines from stdin and try matching it with header patterns\n\
  in $BADHDRDIR dir.  $BADHDRDIR defaults to /var/qmail/control/badhdrdir.\n\
  If matches with header patterns, return code set to 1, otherwise 0.");
  return 0;
}

int main(int argc, char *argv[])
{
  static HM hmatcher = {0};
  static HMR hresult;
  STR s = {0};
  char *dp = "/var/qmail/control/badhdrdir";
  int iarg;
  for (iarg=1; argv[iarg] && argv[iarg][0] == '-'; iarg++) {
    switch (argv[iarg][1]) {
    case 'r': return reg_debug(argv, 1+iarg, 0);
    case 'R': return reg_debug(argv, 1+iarg, 1);
    case 'h': return reg_help(argv[0]);
    }
  }
  if (getenv("BADHDRDIR")) dp = getenv("BADHDRDIR");
  fprintf(stderr, "Reading bad header dir from %s\n", dp);
  hmatcher.dir = dp;
  if (argc>0 && argv[iarg])
    hmatcher.dir = argv[iarg];
  while (NULL != fgets(b, sizeof b, stdin)) {
    strop(&s, "=", b);
    strop(&s, "chomp");
    strop(&s, "lower");
    printf("[%s]\n->%d\n", s.s, header_match(&hmatcher, s.s, &hresult));
  }
  return 0;
}
#endif
