/*
 *	ZMailer 2.99.53+ Scheduler "mailq2" routines
 *
 *	Copyright Matti Aarnio <mea@nic.funet.fi> 1999-2000,2004
 *
 */

#include "scheduler.h"
#include "prototypes.h"
#include <ctype.h>
#include <unistd.h>
#include "zsyslog.h"
#include <stdlib.h>
#include <errno.h>

#include "ta.h"
#include "libz.h"
#include "md5.h"

#include <arpa/inet.h>

/*
 *  MAILQv2 autentication database info content:
 *
 *  - username (search key)
 *  - cleartext password (for AUTH hash to work)
 *  - controlling attributes
 *  - IP ACL per tcp-wrapper (how?)
 *
 *
 *  Field separator classical double-colon (':'), meaning that
 *  the cleartext password shall *not* contain that character.
 */

struct mq2pw {
	char *user;
	char *plain;
	char *attrs;
	int auth;
};

struct mq2keys {
  long value;
  char *name;
};
static struct mq2keys keys[] =
{
  { 0,			"NONE"	},
  { MQ2MODE_SNMP,	"SNMP"	},
  { MQ2MODE_QQ,		"QQ"	},
  { MQ2MODE_FULL,	"TT"	},
  { MQ2MODE_ETRN,	"ETRN"	},
  { MQ2MODE_KILL,	"KILL"	},
  /* other modes ? */
  { 0x7fffffff,		"ALL"	},
  { 0, NULL },
};


static long mq2authtokens(keys,s)
     struct mq2keys *keys;
     char *s;
{
  char *p = s;
  long rc = 0;
  struct mq2keys *m;

  while (p && *p) {
    s = p;
    while (*p && *p != ' ') ++p;
    if (*p) *p = 0; else p = NULL;
    for (m = keys;m->name;++m) {
      if (strcmp(m->name,s)==0) {
	rc |= m->value;
	break;
      }
    }
    if (p) *p++ = ' ';
  }
  return rc;
}

static int parseaddrlit __((const char **, Usockaddr *));
static int
parseaddrlit(hostp, au)
	const char **hostp;
	Usockaddr *au;
{
	int rc = 0, err;
	const char *host = *hostp;
	char *hh = (void *) host;

	memset(au, 0, sizeof(*au));

	hh = strchr(hh, ']');
	if (hh) *hh = 0;

#if defined(AF_INET6) && defined(INET6)
	if (CISTREQN(host,"[IPv6:",6) ||
	    CISTREQN(host,"[IPv6.",6)) {
	  au->v6.sin6_family = AF_INET6;
	  err = inet_pton(AF_INET6, host+6, &au->v6.sin6_addr);
	  if (err > 0) rc = 128;
	} else
#endif
	  if (*host == '[') {
	    au->v4.sin_family = AF_INET;
	    err = inet_pton(AF_INET, host+1, &au->v4.sin_addr);
	    if (err > 0) rc = 32;
	  } else
	    err = -1;

	if (hh) *hh = ']';

	while (*host && *host != ']') ++host;
	if (*host == ']') ++host;

	if (*host == '/') {
	  ++host;
	  rc = -1;
	  while ('0' <= *host && *host <= '9') {
	    if (rc < 0) rc = 0;
	    rc = rc * 10 + (*host) - '0';
	    ++host;
	  }
	}

	*hostp = host;

	if (err < 0) rc = -1;
	return rc;
}

static void mask_ip_bits __((void *, int, int));
static void mask_ip_bits(ipnump, width, maxwidth)
     void *ipnump;
     int width, maxwidth;
{
    unsigned char *ipnum = ipnump;
    int i, bytewidth, bytemaxwidth;

    bytemaxwidth = maxwidth >> 3;	/* multiple of 8 */
    bytewidth = (width + 7) >> 3;

    /* All full zero bytes... */
    for (i = bytewidth; i < bytemaxwidth; ++i)
	ipnum[i] = 0;

    /* Now the remaining byte */
    i = 8 - (width & 7);	/* Modulo 8 */

    bytewidth = width >> 3;
    if (i != 8) {
	/* Not exactly multiple-of-byte-width operand to be masked    */
	/* For 'width=31' we get now 'bytewidth=3', and 'i=1'         */
	/* For 'width=25' we get now 'bytewidth=3', and 'i=7'         */
	ipnum[bytewidth] &= (0xFF << i);
    }
}


static int mq2amaskcompare(qa, width, ua)
     int width;
     Usockaddr *qa, *ua;
{
  unsigned char ipbuf1[16], ipbuf2[16];

#ifdef INET6
  if (qa->v6.sin6_family == AF_INET6) {
    const u_char zv4mapprefix[16] = 
      { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0};

    if (memcmp((void *)&qa->v6.sin6_addr, zv4mapprefix, 12) == 0) {
      /* Is a IPv4 address mapped via IPv6 prefix. */
      qa->v4.sin_family = AF_INET;
      memcpy((void*)&qa->v4.sin_addr, 12+((char*)&qa->v6.sin6_addr), 4);
    }
  }
  if (qa->v6.sin6_family == AF_INET6) {
    if (ua->v6.sin6_family != AF_INET6) return 0; /* No match! */
    memcpy(ipbuf1, &qa->v6.sin6_addr, 16);
    memcpy(ipbuf2, &ua->v6.sin6_addr, 16);
    mask_ip_bits(ipbuf1, width, 128);
    mask_ip_bits(ipbuf2, width, 128);
    if (memcmp(ipbuf1, ipbuf2, 16) == 0)
      return 1; /* Match! */
    return 0; /* No match */

  } else
#endif
    if (qa->v4.sin_family == AF_INET) {
      if (ua->v4.sin_family != AF_INET) return 0; /* No match! */
      memcpy(ipbuf1, &qa->v4.sin_addr,  4);
      memcpy(ipbuf2, &ua->v4.sin_addr, 4);
      mask_ip_bits(ipbuf1, width, 32);
      mask_ip_bits(ipbuf2, width, 32);
      if (memcmp(ipbuf1, ipbuf2, 4) == 0)
	return 1; /* Match! */
      return 0; /* No match */

    } else {
      return 0; /* NO MATCH! */

    }
}

static int mq2amaskverify(qa, s)
     Usockaddr *qa;
     const char *s;
{
  /* Verify that  mq->qaddr  stored address is ok
     for this user/authenticator to use us.        */
  int rc;
  Usockaddr ua;
  int not = 0;

  while (*s == ' ') ++s;
  if (*s == 0) return 0; /* Empty -> Any OK */

  for ( ; *s; ++s) {
    if (*s == ',') { not = 0; continue; }
    if (*s == ' ') { not = 0; continue; }
    if (*s == '!') { not = 1; continue; }
    if (*s == '[') {
      rc = parseaddrlit( &s, &ua );
      if (rc >= 0 && mq2amaskcompare(qa, rc, &ua))
	return not;
    }
  }

  return -1;  /* Non-empty -> no match -> not ok */
}


struct mq2pw * mq2_authuser(mq, authfile, user)
     struct mailq *mq;
     const char *authfile, *user;
{
  static char linebuf[2000];
  static struct mq2pw mpw;
  char *s;
  Sfio_t *fp;
  int ulen = user ? strlen(user)+1 : 0;

  if (!authfile) return NULL; /* D'uh! */

  fp = sfopen(NULL, authfile, "r");
  if (!fp) return NULL; /* D'uh! */

  mpw.user = linebuf;
  while (csfgets(linebuf, sizeof(linebuf)-1, fp) >= 0) {
    if (*linebuf == '#' || *linebuf == '*' || *linebuf == '\n')
      continue;
    s = strchr(linebuf,'\n');
    if (s) *s = 0;
    s = strchr(linebuf,':');
    if (!s) continue; /* Bad syntax! */
    *s++ = '\000';
    if (!user || memcmp(linebuf,user,ulen) == 0) {
      /* FOUND! */
      mpw.plain = s;	/* authenticator verification happens at caller.. */
      s = strchr(s, ':');
      if (!s) continue; /* Bad syntax! */
      *s++ = '\000';

      mpw.attrs = s;
      s = strchr(s, ':');
      if (!s) continue; /* Bad syntax! */
      *s++ = '\000';

      if (mq2amaskverify(& mq->qaddr, s)) continue; /* BAD user address! */
      mpw.auth = mq2authtokens(keys, mpw.attrs);
      if (! mpw.auth) continue; /* No known ACL tokens! try next! */

      sfclose(fp);
      return & mpw;
    }
  }

  sfclose(fp);
  return NULL; /* nothing found */
}



void mq2auth(mq, authfile, str)
     struct mailq *mq;
     const char *authfile;
     char *str;
{
  char *p = str;
  struct mq2pw *pw;
  MD5_CTX CTX;
  unsigned char digest[16];
  char authbuf[32+1];
  int i;

  mq->auth = 0;

  while (*p && (*p != ' ') && (*p != '\t')) ++p;
  if (*p) *p++ = '\000';
  while (*p == ' ' || *p == '\t') ++p;

  /* Now 'str' points to username, and from 'p' onwards
     there is the HEX-encoded MD5 authenticator.. */

  pw = mq2_authuser(mq, authfile, str);

  if (!pw) {
    mq2_puts(mq,"-BAD USER OR AUTHENTICATOR OR CONTACT ADDRESS\n");
    return;
  }

  MD5Init(&CTX);
  MD5Update(&CTX, (const void *)(mq->challenge), strlen(mq->challenge));
  MD5Update(&CTX, (const void *)(pw->plain),     strlen(pw->plain));
  MD5Final(digest, &CTX);

  for (i = 0; i < 16; ++i)
    sprintf(authbuf+i+i, "%02x", digest[i]);

  if (strcmp(authbuf,p) != 0) {
    mq2_puts(mq,"-BAD USER OR PASSWORD");
#if 0 /* used to debug MD5 code... */
    mq2_puts(mq,"; real auth:");
    mq2_puts(mq,authbuf);
#endif
    mq2_puts(mq,"\n");
    return;
  }

  /* Right, authenticator is ok */
  mq->auth = pw->auth;

  mq2_puts(mq,"+OK\n");
}
