#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <unistd.h>
#include <arpa/nameser.h>
#include <pwd.h>
#include "antibadmail.h"
#include "s.h"

#define QMAILDASH	"-"

static char rcsid[] = "$Id: q.c,v 1.13 2008/01/28 06:50:02 yuuji Exp yuuji $";
static char *buf;               /* for malloc */
static char *controldir;
static STR local = {0}, path = {0};

static int localdomain;

/*
 * Return values
 * -1: Home directory unreadable.
 *  0: Dot-qmail file does not exist.
 *  1: Dot-qmail file exists.  Maybe reachable.
 */
int statdotqmail(char *home, char *ext, char *dash)
{
  int baselen, dashlen=strlen(dash);
  struct stat s;
  static STR qfile = {0}; /* shoud be static because it has mallocced area */
  STR extcopy = {0};
  if (!strop(&path, "=", home)) return 0;
  if (stat(path.s, &s) < 0) return 0;
  if (access(path.s, X_OK) != 0) return -1;
  if (!strop(&path, "+", "/.qmail")) return -1;
  baselen = path.len;    /* length of "$HOME/.qmail ($HOME expanded)" */
  if (!strop(&extcopy, "=", ext)) return 0;
  /* Now translate dot-qmail file name;  . -> : and make it lower case */
  while (strchr(extcopy.s, '.'))
    strop(&extcopy, "replace", ".", ":");
  strop(&extcopy, "lower");
  /* copy translated extension */
  strop(&path, "+", extcopy.s);
  strop(&extcopy, "!");        /* free */
  /* try stat */
  if (0 == stat(path.s, &s))
    return 1;
  if (dashlen == 0) return 0;   /* no dash rule, skip -default check */
  for (; path.len>=baselen; strop(&path, "chop")) {
    
    if (!strncmp(path.s+path.len-dashlen, dash, dashlen)) {
      if (!strop(&qfile, "=", path.s)) return -1;
      if (!strop(&qfile, "+", "default")) return -1;
      fprintf(stderr, "dq=%s\n", qfile.s);
      if (0 == stat(qfile.s, &s))
        return 1;
    }
  }
  return 0;
}

/*
 * -1: error
 *  0: NOT found
 *  1: found
 */
int chkuser(char *local)
{
  char *user = NULL;
  int lenmax = strlen(local)+1;
  char *ext = local+strlen(local);
  char *dash = QMAILDASH;
  int dashlen = strlen(dash);
  struct passwd *pw;
  struct stat st;
  int r=-1;
  if (NULL == (user=malloc(lenmax)))
    return -1;

  do {
    strncpy(user, local, ext-local);
    user[ext-local] = '\0';
    if ((pw=getpwnam(user))) {
      if (0 > stat(pw->pw_dir, &st)) {
        goto cont;
      }
      fprintf(stderr, "user=[%s], ext=[%s]\n", user, ext);
      if (*ext)
        r = statdotqmail(pw->pw_dir, ext, dash);
      else {                    /* strict match with user */
        r = 1;
      }
      if (r > 0) goto out;
    }
  cont:
    for (ext--; local<ext && strncmp(ext, dash, dashlen); ext--)
      ;
  } while (local<ext);
  r = 0;                        /* not found */
 out:
  if (user) free(user);
  return r;
}
/*
 * Check if local matches with /var/qmail/users/assign.
 * This function checks the possibility of the existence of local address.
 * So, it returns the first match but most specific one.
 * Return values:
 * -1: Couldn't complete by some error condition
 *  0: Not found
 *  1: Found
 */
int chkassign(char *local)
{
  int r=0, baselen, dashlen;
  FILE *as;
  char *colon, *p, *s, *e, *dash, *ext;
  struct stat st;
  static STR preext = {0};
#ifdef DEBUG_ASSIGN
  int lineno=0;
#endif
  if (!strop(&path, "=", controldir)) return -1;
  if (!strop(&path, "+", "/../users/assign")) return -1;
  //if (!strop(&path, "=", "tdir/assign")) return -1;
  
#ifdef DEBUG_ASSIGN
  fprintf(stderr, "Stat %s\n", path.s);
#endif
  if (0 > stat(path.s, &st))
    return 0;
#ifdef DEBUG_ASSIGN
  fprintf(stderr, "Open %s\n", path.s);
#endif
  if (NULL == (as=fopen(path.s, "r")))
    return 0;

  while (NULL != fgets(buf, MAXDNAME, as)) {
    for (p=buf; *p && (*p == ' ' || *p == '\t'); p++)
      ;
#ifdef DEBUG_ASSIGN
    fprintf(stderr, "Read(%d): %s", ++lineno, buf);
#endif
    if (!strchr("+=", *p))
      continue;
    if (!(colon=strchr(p, ':')))
      continue;
    baselen = colon-p-1;
    if (!strncmp(local, p+1, baselen)) {
      /* Skip to 5th field.  Now p stays at the beginning of 1st field. */
      int c;
      for (c=0, s=p; s && *s && (s=strchr(s, ':')) && c<3; c++) {
        s++;
      }
      if (!s || !*s || !*(s+1))
        continue;
      if (!(e=strchr(s+1, ':')))
        continue;
      *e = '\0';                /* (s+1)..e == homedir */

      /* Now s+1 points to home directory, e+1 points to dash field */
      dash = e+1;
      for (e++; *e && *e != '\n' && *e != ':'; e++);
      if (! *e) continue;     /* insufficient line */
      *e = '\0';
      dashlen=e-dash;
      for (ext=++e; *e && *e != '\n' && *e != ':'; e++);
      if (! *e) continue;     /* insufficient line, too */
      if (!strop(&preext, "=", dash)) return -1;
      if (!strop(&preext, "n+", ext, e-ext)) return -1;
      if (*p == '+') {
        if (!strop(&preext, "+", local+baselen)) return -1;
      }
#ifdef DEBUG_ASSIGN
      fprintf(stderr, "Check dot-qmail: %s/.qmail%s\n", s+1, preext.s);
#endif
      r=(statdotqmail(s+1, preext.s, dash));
      if (r >= 1) {
        fprintf(stderr, "M/w [%s]in assign\n", buf);
        break;
      }
    }
  }
#ifdef DEBUG_ASSIGN
  fprintf(stderr, "chkassign r=%d\n", r);
#endif
  fclose(as);
  return r;
}
int chkalias(char *local)
{
  int r=-1;
  static STR alias = {0};
  if (!strop(&alias, "=", "alias-")) return -1;
  if (!strop(&alias, "+", local)) return -1;
  r = chkuser(alias.s);
  strop(&alias, "!");           /* free() */
  return r;
}
int chklocal(char *local)
{
  int r=-1;
  if ((r=chkassign(local)) > 0)
    return r;
  if ((r=chkuser(local)) > 0)
    return r;
  r=chkalias(local);
  return r;
}
int islocal(char *domain)
{
  FILE *lcl;
  char *q;
  int r=0;
  if (!strop(&path, "=", controldir)) return -1;
  if (!strop(&path, "+", "/locals")) return -1;

  //fprintf(stderr, "path=%s\n", path.s);
  lcl=fopen(path.s, "r");
  strop(&path, "!");
  if (NULL == lcl)
    return -1;
  while (NULL != fgets(buf, MAXDNAME, lcl)) {
    if (buf[0] == '#')
      continue;
    q = buf+strlen(buf)-1;
    while (q>buf && strchr("\r\n ", *q))
      *q-- = '\0';
    if (!strcmp(domain, buf)) {
      r=1;
      break;
    }
  }
  fclose(lcl);
  return r;
}
int dovirtual(char *recipient)
{
  FILE *vdom;
  char *q;
  char *colon;
  int r=0;
  char *at = strchr(recipient, '@');
  char *domain;
  int llen;
  int mlen = MAXPATHLEN;
  
  strop(&local, "=", "");
  if (!at) {
    if (!strop(&local, "=", recipient)) return -1;
    return 0;
  }
  llen = at-recipient;
  domain = at+1;

  if (!strop(&path, "=", controldir)) r=-1;
  else if (!strop(&path, "+", "/virtualdomains")) r=-1;
  //else if (!strop(&path, "+", "/virtualdomains.bak")) r=-1;

  vdom=fopen(path.s, "r");
  strop(&path, "!");            /* free() */
  if (r || NULL == vdom)
    return -1;
  while (NULL != fgets(buf, MAXDNAME, vdom)) {
    if (buf[0] == '#')
      continue;
    if (NULL == (colon=strchr(buf, ':')))
      continue;
    q = buf+strlen(buf);
    while (q>buf && strchr("\r\n ", *q))
      *q-- = '\0';
    if (!strncmp(domain, buf, colon-buf)) {
      /* virtualdomain found! */
      if (strlen(colon+1)+llen+1+1 > mlen)
        return -1;              /* too long */
      if (!strop(&local, "=", colon+1)) return -1;
      if (!strop(&local, "+", "-")) return -1;
      localdomain = 1;
      break;
    }
  }
  fclose(vdom);
  /* for (s=recipient; s < at && t-local < mlen; )
    *t++ = *s++;
    *t++ = '\0';*/
  strop(&local, "n+", recipient, at-recipient);
  fprintf(stderr, "vadd=[%s]\n", local.s);
  return 1;
  
}

/*
 * Return zero if the recipient address RECIP surely does not exist
 * return 1: Found
 *        2: not local (Q_NXNONLOCAL)
 *	 -1: other error
 *	 -2: domain part too long
 */
int q_nonexistent(char *recip, char *cdir)
{
  char *at = strrchr(recip, '@');
  int llen, dlen;
  int r = -1;
  static STR domain;            /* should be static */
  localdomain = 0;

  if (NULL == (buf=malloc(MAXDNAME+1))) goto out;
  
  controldir = cdir;
  if (at) {                     /* user@domain */
    llen = at-recip;
    if (MAXDNAME <= (dlen = strlen(at)))
      return -2;
    if (!strop(&domain, "=", at+1)) return -1;
    if ((r=islocal(domain.s))) {
      localdomain = 1;
      if (r<0) goto out;
      strop(&local, "n=", recip, at-recip);
      //local[at-recip] = '\0';
      fprintf(stderr, "local,%s, %s\n", local.s, domain.s);
    } else {
      r=dovirtual(recip);
      // if (r<0) return r;        /* some error */
    }
  } else {
    strop(&local, "=", recip);
    localdomain = 1;
  }

  fprintf(stderr, "ld=%d\n", localdomain);
  if (localdomain)
    r=chklocal(local.s);
  else
    r = Q_NXNONLOCAL;

 out:
  if (buf) {
    free(buf);
    buf = 0;
  }
  return r;
}

#ifdef Q_MAIN_
# include <ctype.h>
# include "antibadmail.h"
int checkregular(char *name)
{
  char *p;
  for (p=name; *p; p++) {
    if (!isalpha((int)*p) && !isdigit((int)*p) && !strchr("@_-+./", *p))
      return 0;
  }
  return 1;
}

void chomp(char *s)
{
  while (*s && strchr("\r\n", s[strlen(s)-1]))
    s[strlen(s)-1] = '\0';
}
int main(int argc, char *argv[])
{
  static char *buffer;
  char *rep;
  if (NULL == (buffer=malloc(MAXDNAME+1))) {
    fprintf(stderr, "%s: Memory shortage\n", rcsid);
    exit(1);
  }
  while (NULL != fgets(buffer, MAXDNAME, stdin)) {
    int rc;
    chomp(buffer);
    if (checkregular(buffer)) {
      rc = q_nonexistent(buffer, CONTROLDIR);
      if (rc == 0) {
        rep = "NG";
      } else if (rc > 0) {
        rep = "OK Maybe";
      } else {
        rep = "Unknown";
      }
    } else {
      rep = "NG";
    }
    printf("%s\n", rep);
    fflush(stdout);
  }
  return 0;
}
#endif
