#include "recipients.h"

#include <unistd.h>

#include "buffer.h"
#include "byte.h"
#include "case.h"
#include "cdbread.h"
#include "constmap.h"
#include "fd.h"
#include "open.h"
#include "sig.h"
#include "str.h"
#include "stralloc.h"
#include "wait.h"

#include "auto_break.h"
#include "control.h"
#include "qmail.h"

#define FDAUTH 3

static stralloc key = {0};
static stralloc domain = {0};
static stralloc wildhost = {0};
static stralloc address = {0};
static stralloc rcptline = {0};
static stralloc vkey = {0};
static stralloc verp = {0};
static stralloc user = {0};
static stralloc ukey = {0};
static int flagrcpts = 0;
static int fdrcps;
static struct cdb cdb;

/**
  @file  recipients.c
  @brief functions recipients_init, recipients, recipients_parse, callapam
  @param pointer to address, length of address
  @return         -3: problem with PAM
                  -2: out of memory
                  -1: error reading control file
                   0: address not found; unsuccessful
                   1: CDB lookup; successful
                   2: PAM lookup; successful
                   3: USERS lookup; successful
                   4: Wildcarded domain; successful
                   5: Pass-thru; neutral
                  10: none existing control file; pass-thru
*/

int recipients_init()
{
  flagrcpts = control_readfile(&rcptline, "control/recipients", 0);
  if (flagrcpts != 1) return flagrcpts;
  return 0;
}

char rcptbuf[512];
buffer br = BUFFER_INIT(safewrite, FDAUTH, rcptbuf, sizeof(rcptbuf));

int callapam(char *pam, char *addr)
{
  int i;
  int j = 0;
  int wstat;
  int pi[2];
  int child;
  char ch;
  static stralloc mailaddress = {0};

  char *childargs[7] = {0, 0, 0, 0, 0, 0, 0};
  stralloc pamarg = {0};
  stralloc pamname = {0};
  stralloc pamarg1 = {0};
  stralloc pamarg2 = {0};
  stralloc pamarg3 = {0};
  stralloc pamarg4 = {0};
  stralloc pamarg5 = {0};

  for (i = 0; (ch = pam[i]); i++) {
    if (j < 6) {
      if (ch != ' ')
        if (!stralloc_append(&pamarg, &ch)) return -2;
      if (ch == ' ' || ch == '\n' || i == str_len(pam) - 1) {
        if (!stralloc_0(&pamarg)) return -2;
        switch (j) {
          case 0:
            if (!stralloc_copy(&pamname, &pamarg)) return -2;
            childargs[0] = pamname.s;
          case 1:
            if (!stralloc_copy(&pamarg1, &pamarg)) return -2;
            childargs[1] = pamarg1.s;
          case 2:
            if (!stralloc_copy(&pamarg2, &pamarg)) return -2;
            childargs[2] = pamarg2.s;
          case 3:
            if (!stralloc_copy(&pamarg3, &pamarg)) return -2;
            childargs[3] = pamarg3.s;
          case 4:
            if (!stralloc_copy(&pamarg4, &pamarg)) return -2;
            childargs[4] = pamarg4.s;
          case 5:
            if (!stralloc_copy(&pamarg5, &pamarg)) return -2;
            childargs[5] = pamarg5.s;
        }
        j++;
        if (!stralloc_copys(&pamarg, "")) return -2;
      }
    }
  }
  childargs[j] = 0;

  close(FDAUTH);
  if (pipe(pi) == -1) return -3;
  if (pi[0] != FDAUTH) return -3;

  switch (child = fork()) {
    case -1: return -3;
    case 0:
      close(pi[1]);
      if (fd_copy(FDAUTH, pi[0]) == -1) return -3;
      sig_pipedefault();
      execvp(childargs[0], childargs);
      return 111;
  }
  close(pi[0]);

  /* checkpassword compliant form: address\0\0\0 */

  if (!stralloc_copys(&mailaddress, addr)) return -2;
  if (!stralloc_0(&mailaddress)) return -2;
  if (!stralloc_0(&mailaddress)) return -2;
  if (!stralloc_0(&mailaddress)) return -2;

  buffer_init(&br, write, pi[1], rcptbuf, sizeof(rcptbuf));
  if (buffer_put(&br, mailaddress.s, mailaddress.len) == -1) return -3;
  if (buffer_flush(&br) == -1) return -3;
  close(pi[1]);

  if (wait_pid(&wstat, child) == -1) return -3;
  if (wait_crashed(wstat)) return -3;
  return wait_exitcode(wstat);
}

int recipients_parse(
    char *rhost,
    int rlen,
    char *addr,
    char *rkey,
    int klen,
    char *vaddr,
    char *vkey,
    int vlen,
    char *ukey,
    int ulen)
{
  int i;
  int r;
  int j = 0;
  int k = 0;
  int u = 0;
  static stralloc line = {0};
  int seenhost = 0;

  if (!stralloc_copys(&line, "")) return -2;
  if (!stralloc_copys(&wildhost, "!")) return -2;
  if (!stralloc_cats(&wildhost, rhost)) return -2;
  if (!stralloc_0(&wildhost)) return -2;

  for (i = 0; i < rcptline.len; ++i) {
    if (!stralloc_append(&line, &rcptline.s[i])) return -2;

    if (rcptline.s[i] == '\0') {
      if (!stralloc_0(&line)) return -2;

      j = byte_chr(line.s, line.len, ':'); /* cdb */
      k = byte_chr(line.s, line.len, '|'); /* pam */
      u = byte_chr(line.s, line.len, '='); /* assign users */

      if (!str_diffn(line.s, wildhost.s, wildhost.len - 1)) return 4; /* wilddomain */
      if ((j && j < line.len) || (k && k < line.len) || (u && u < line.len))
        if (!str_diffn(line.s, "@", 1)) /* exact */
          if (!str_diffn(line.s + 1, rhost, rlen - 1)) seenhost = 1;

      if (!seenhost) { /* domain */
        if (j && rlen >= j)
          if (!str_diffn(line.s, rhost + rlen - j - 1, j - 1)) seenhost = 2;
        if (k && rlen >= k)
          if (!str_diffn(line.s, rhost + rlen - k - 1, k - 1)) seenhost = 3;
        if (u && rlen >= u)
          if (!str_diffn(line.s, rhost + rlen - u - 1, u - 1)) seenhost = 4;
      }
      if (!seenhost) /* pass-thru */
        if (!str_diffn(line.s, "!*", 2)) return 5;

      if (k && k < line.len) /* pam */
        if (seenhost || !str_diffn(line.s, "*", 1)) {
          r = callapam(line.s + k + 1, addr);
          if (vlen > 0 && r != 0) r = callapam(line.s + k + 1, vaddr);
          if (r == 0) return 2;
          if (r == 111) return r;
        }

      if (u && u < line.len) /* qmail-users */
        if (seenhost || !str_diffn(line.s, "*", 1)) {
          fdrcps = open_read("users/assign.cdb");
          if (fdrcps != -1) {
            cdb_init(&cdb, fdrcps);
            r = cdb_find(&cdb, ukey, ulen - 1);
            cdb_free(&cdb);
            close(fdrcps);
            if (r) return 3;
          }
        }

      if (j && j < line.len) /* cdb */
        if (seenhost || !str_diffn(line.s, "*", 1)) {
          fdrcps = open_read(line.s + j + 1);
          if (fdrcps != -1) {
            cdb_init(&cdb, fdrcps);
            r = cdb_find(&cdb, rkey, klen - 2);
            if (vlen > 0 && r == 0) r = cdb_find(&cdb, vkey, vlen - 2);
            cdb_free(&cdb);
            close(fdrcps);
            if (r) return 1;
          }
        }

      if (!seenhost) {
        fdrcps = open_read(line.s); /* legacy cdb */
        if (fdrcps != -1) {
          cdb_init(&cdb, fdrcps);
          r = cdb_find(&cdb, rkey, klen - 2);
          if (vlen > 0 && r == 0) r = cdb_find(&cdb, vkey, vlen - 2);
          cdb_free(&cdb);
          close(fdrcps);
          if (r) return 1;
        }
      }

      if (!stralloc_copys(&line, "")) return -2;
    }
  }
  return 0;
}

int recipients(char *buf, int len)
{
  int at;
  int i;
  int r;

  if (flagrcpts != 1) return 10;

  at = byte_rchr(buf, len, '@');
  if (at && at < len) {
    if (!stralloc_copyb(&domain, buf + at + 1, len - at - 1)) return -2;
    if (!stralloc_copyb(&address, buf, len)) return -2;
  } else {
    if (!stralloc_copyb(&address, buf, len)) return -2;
    if (!stralloc_append(&address, "@")) return -2;
    if (!stralloc_copys(&domain, "localhost")) return -2;
    if (!stralloc_cat(&address, &domain)) return -2;
  }
  if (!stralloc_copyb(&user, buf, at - 1)) return -2;

  if (!stralloc_0(&user)) return -2;
  if (!stralloc_0(&address)) return -2;
  if (!stralloc_0(&domain)) return -2;

  if (!stralloc_copys(&key, ":")) return -2;
  if (!stralloc_cat(&key, &address)) return -2;
  if (!stralloc_0(&key)) return -2; /* \0\0 terminated */
  case_lowerb(key.s, key.len);
  case_lowerb(domain.s, domain.len);

  if (!stralloc_copys(&ukey, "!")) return -2;
  if (!stralloc_cat(&ukey, &user)) return -2;
  if (!stralloc_0(&ukey)) return -2; /* \0 terminated */
  case_lowerb(ukey.s, ukey.len);

  for (i = 0; i < at; i++) {                                       /* VERP addresses */
    if (buf[i] == *auto_break || buf[i] == '=' || buf[i] == '+') { /* SRS delimiter */
      if (!stralloc_copyb(&verp, buf, i + 1)) return -2;
      if (!stralloc_append(&verp, "@")) return -2;
      if (!stralloc_cat(&verp, &domain)) return -2;
      if (!stralloc_copys(&vkey, ":")) return -2;
      if (!stralloc_cat(&vkey, &verp)) return -2;
      if (!stralloc_0(&vkey)) return -2; /* \0\0 terminated */
      case_lowerb(vkey.s, vkey.len);
      break;
    }
  }

  r = recipients_parse(
      domain.s, domain.len, address.s, key.s, key.len, verp.s, vkey.s, vkey.len, ukey.s, ukey.len);
  if (r) return r;
  return 0;
}