#include <unistd.h>
#include "cdbread.h"
#include "byte.h"
#include "open.h"
#include "control.h"
#include "constmap.h"
#include "stralloc.h"
#include "recipients.h"
#include "wait.h"
#include "str.h"
#include "fd.h"
#include "sig.h"
#include "case.h"
#include "buffer.h"
#include "auto_break.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;
}