From b224a3ceb63ff8ebc57648bf304e079d0bf55023 Mon Sep 17 00:00:00 2001
From: Erwin Hoffmann <erwin@fehcom.net>
Date: Fri, 12 Jul 2019 23:56:05 -0600
Subject: [PATCH] SMTP authentication for qmail-smtpd, qmail-remote, v0.8.3

From https://www.fehcom.de/qmail/smtpauth.html#PATCHES

Generic SMTP authentication for qmail-smtpd and qmail-remote.
Complies to RFC 3848 and RFC 4409.

 * Provides now CRAM-MD5 authentication for qmail-remote as well.
 * Fine tuning of SMTPAUTH annoncements for qmail-smtpd and
   SUBMISSION support.
 * Sender based authentication according to 'Mail From:' and
   authenticated smarthost relaying for qmail-remote.
 * Fixed bug (0.8.2): qmail-smtpd secretly allows auth even when
   disabled! (tx Chris P.)
 * Fixed AMD64 bug for MD5 (0.8.3) and small others (tx Alan P.)
---
 FILES.auth                |  27 +++
 LICENSE.authentication    |  43 +++++
 Makefile                  |  25 ++-
 README.auth               | 154 +++++++++++++++++
 TARGETS                   |   3 +
 base64.c                  | 124 ++++++++++++++
 base64.h                  |   7 +
 case_startb.c             |  21 +++
 global.h                  |  51 ++++++
 hmac_md5.c                |  76 +++++++++
 hmac_md5.h                |  11 ++
 install_authentication.sh | 101 ++++++++++++
 md5.h                     |  49 ++++++
 md5c.c                    | 334 +++++++++++++++++++++++++++++++++++++
 qmail-control.9           |   1 +
 qmail-remote.8            | 124 +++++++++++++-
 qmail-remote.c            | 293 ++++++++++++++++++++++++++++++---
 qmail-showctl.c           |   4 +-
 qmail-smtpd.8             |  86 +++++++++-
 qmail-smtpd.c             | 336 +++++++++++++++++++++++++++++++++++++-
 20 files changed, 1825 insertions(+), 45 deletions(-)
 create mode 100644 FILES.auth
 create mode 100644 LICENSE.authentication
 create mode 100644 README.auth
 create mode 100644 base64.c
 create mode 100644 base64.h
 create mode 100644 case_startb.c
 create mode 100644 global.h
 create mode 100644 hmac_md5.c
 create mode 100644 hmac_md5.h
 create mode 100755 install_authentication.sh
 create mode 100644 md5.h
 create mode 100644 md5c.c

diff --git a/FILES.auth b/FILES.auth
new file mode 100644
index 0000000..e9c4b44
--- /dev/null
+++ b/FILES.auth
@@ -0,0 +1,27 @@
+The qmail-smtpd Auth patch modifies the following QMAIL 1.03 files:
+
+= TARGETS
+= Makefile
+= qmail-smtpd.c
+= qmail-smtpd.8
+= qmail-remote.c
+= qmail-remote.8
+= qmail-showctl.c
+= qmail-control.9
+
+Added files:
+
++ base64.c
++ base64.h
++ case_startb.c
++ hmac_md5.h
++ hmac_md5.c
++ global.h
++ md5.h
++ md5c.c
+
+Informational files:
+
+% install_auth.sh  (Installation shell script)
+% LICENSE.authentication (some sort of ...)
+% README.auth
diff --git a/LICENSE.authentication b/LICENSE.authentication
new file mode 100644
index 0000000..707a870
--- /dev/null
+++ b/LICENSE.authentication
@@ -0,0 +1,43 @@
+AUTHOR
+======
+
+Author:
+	Dr. Erwin Hoffmann - FEHCom Germany
+Web-Site: 	
+	http://www.fehcom.de/qmail.html
+E-Mail: 	
+	feh@fehcom.de
+
+
+LICENSE
+=======
+
+qmail AUTHENTICATION is free software.
+This includes:
+	You can download and use qmail AUTHENTICATION (and parts of it) as you like.
+	You can modify the source code without notification to or permission by the author.
+Please check:
+	http://www.cr.yp.to/softwarelaw.html
+
+
+DEPENDENCIES
+============
+
+qmail AUTHENTICATION patches (modifies) parts of the qmail-1.03 source files.
+It should only be applied against the source as supplied by D.J. Bernstein.
+
+
+FITNESS
+=======
+
+The Author does not guarantee a specific fitness of qmail AUTHENTICATION.
+If you use qmail AUTHENTICATION, it's on your own risk.
+
+
+DISTRIBUTION
+============
+
+qmail AUTHENTICATION may be included in ports and packages under the following conditions:
+	The port/package has to show the current version number of qmail AUTHENTICATION.
+	All files (namely this) have to be included.
+
diff --git a/Makefile b/Makefile
index 5cc18ea..c3ba2a1 100644
--- a/Makefile
+++ b/Makefile
@@ -197,6 +197,18 @@ auto_groupq.o: \
 compile auto_groupq.c
 	./compile auto_groupq.c
 
+base64.o: \
+compile base64.c base64.h stralloc.h substdio.h str.h
+	./compile base64.c
+
+md5c.o : \
+compile md5c.c md5.h
+	./compile md5c.c
+
+hmac_md5.o : \
+compile hmac_md5.c hmac_md5.h global.h
+	./compile hmac_md5.c
+
 binm1: \
 binm1.sh conf-qmail
 	cat binm1.sh \
@@ -1404,12 +1416,15 @@ qmail-remote: \
 load qmail-remote.o control.o constmap.o timeoutread.o timeoutwrite.o \
 timeoutconn.o tcpto.o now.o dns.o ip.o ipalloc.o ipme.o quote.o \
 ndelay.a case.a sig.a open.a lock.a seek.a getln.a stralloc.a alloc.a \
-substdio.a error.a str.a fs.a auto_qmail.o dns.lib socket.lib
+substdio.a error.a str.a fs.a auto_qmail.o \
+base64.o md5c.o hmac_md5.o \
+dns.lib socket.lib
 	./load qmail-remote control.o constmap.o timeoutread.o \
 	timeoutwrite.o timeoutconn.o tcpto.o now.o dns.o ip.o \
 	ipalloc.o ipme.o quote.o ndelay.a case.a sig.a open.a \
 	lock.a seek.a getln.a stralloc.a alloc.a substdio.a error.a \
-	str.a fs.a auto_qmail.o  `cat dns.lib` `cat socket.lib`
+	base64.o md5c.o hmac_md5.o \
+	str.a fs.a auto_qmail.o `cat dns.lib` `cat socket.lib`
 
 qmail-remote.0: \
 qmail-remote.8
@@ -1504,12 +1519,12 @@ load qmail-smtpd.o rcpthosts.o commands.o timeoutread.o \
 timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \
 date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \
 open.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a str.a \
-fs.a auto_qmail.o socket.lib
+fs.a auto_qmail.o base64.o socket.lib
 	./load qmail-smtpd rcpthosts.o commands.o timeoutread.o \
 	timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \
 	received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \
 	datetime.a getln.a open.a sig.a case.a env.a stralloc.a \
-	alloc.a substdio.a error.a str.a fs.a auto_qmail.o  `cat \
+	alloc.a substdio.a error.a str.a fs.a auto_qmail.o base64.o  `cat \
 	socket.lib`
 
 qmail-smtpd.0: \
@@ -1520,7 +1535,7 @@ compile qmail-smtpd.c sig.h readwrite.h stralloc.h gen_alloc.h \
 substdio.h alloc.h auto_qmail.h control.h received.h constmap.h \
 error.h ipme.h ip.h ipalloc.h ip.h gen_alloc.h ip.h qmail.h \
 substdio.h str.h fmt.h scan.h byte.h case.h env.h now.h datetime.h \
-exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h
+exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h base64.h
 	./compile qmail-smtpd.c
 
 qmail-start: \
diff --git a/README.auth b/README.auth
new file mode 100644
index 0000000..0a014b5
--- /dev/null
+++ b/README.auth
@@ -0,0 +1,154 @@
+README qmail SMTP Authentication
+================================
+
+Scope:
+------
+
+This patch supports RFC 2554 "SMTP Service Extension for Authentication" and 
+RFC 4409 "Message Submission for Mail" for 
+
+* qmail-smtpd and
+* qmail-remote
+
+and supports commonly the AUTH methods
+
+- CRAM-MD5
+- LOGIN (unsecure)
+- PLAIN (unsecure)
+
+Additionally, RFC 1870 is honoured ("SMTP Service Extension for Message Size Declaration").
+For more technical details see: http://www.fehcom.de/qmail/docu/smtpauth.html.
+
+
+Installation:
+-------------
+
+* Untar the source in the qmail-1.03 home direcotry.
+* Run ./install_auth.
+* Re-make qmail.
+
+
+Setup for qmail-smtpd:
+----------------------
+
+1. Prereqs:
+
+In order to use SMTP Authentication you have to use a 'Pluggable Authentication Module'
+PAM to be called by qmail-smtpd; typically
+
+	/var/qmail/bin/qmail-smtpd /bin/checkpassword true 2>&1
+
+Since qmail-smtpd does not run as root, checkpassword has to be made sticky.
+There is no need to include additionally the hostname in the call.
+In order to compute the CRAM-MD5 challenge, qmail-smtpd uses the 'tcplocalhost' information.
+
+2. Invocation:
+
+In order activate SMTP authentication, you need to provide the environment
+variable 'SMTPAUTH' to qmail-smtpd.
+
+Possible choices:
+	
+	a) SMTPAUTH=""; 	qmail-smtpd supports auth of type PLAIN and/or LOGIN.
+	b) SMTPAUTH="+cram";	qmail-smtpd will additionally annonce CRAM-MD5,
+				this requires a CRAM-MD5 supporting PAM.
+	c) SMTPAUTH="cram";	qmail-smtpd will only annonce CRAM-MD5.
+	d) SMTPAUTH="!";	this instructs qmail-smtpd to require (any type) authentication for this connection.
+				This behavior is equivalent to the Submission feaure.
+	e) SMTPAUTH="!cram";	same as d) but now CRAM-MD5 is the only method instead.
+
+
+Setup for qmail-remote:
+-----------------------
+
+SMTP Authentication with qmail-remote is faclitated by two means:
+
+a) SMTP Authentication by sender/sending domain as provided in the 'Mail From:'
+
+The control file 'authsenders' which works similar to 'control/smtproutes'
+but with additional authentication information (username + password):
+
+    @example.com:relay.example.com|user|passwd
+    info@example.com:relay.example.com:26|infouser|infopasswd
+    :mailrelay.example.com:587|e=mc2|testpass
+
+Note: The choice of the AUTH method depends on the capabilities of the server.
+
+b) SMTP Authentication by recipient domain: 
+
+The control file 'smtproutes' is enhanced with the authentication information:
+
+    authdomain.com:mx.authdomain.com:125|myuserid|mypassword
+    :mailrelay.example.com:587|e=mc2|testpass
+
+
+Historical Notes:
+-----------------
+
+SMTP authentication for qmail-smtpd was initially provided by Krysztof Dabrowski (version 0.31):
+
+
+Changes wrt. Krysztof Dabrowski's patch:
+
+* Avoid the 'hostname' in the call of the PAM.
+* Confirm to Dan Bernstein's checkpassword interface even for CRAM-MD5.
+* Doesn't close FD 2; thus not inhibiting logging to STDERR.
+* Fixed bugs in base64.c.
+* Modified unconditional close of FD 3 in order to sustain reading of 'control/morecpthosts.cdb'.
+* Evaluation of the (informational) Mail From: < > Auth=username.
+* Additional support for the advertised "Size" via 'Mail From: <return-path> SIZE=123456780' (RFC 1870).
+* RFC 3848 conformance for Received header in case of SMTP Auth (keyword ESMTPA).
+* Added SMTPAUTH environment variable.
+* The particular Submission feature has been removed; obsolete.
+
+
+SMTP authentication for qmail-remote is taken from Bjoern Kalkbrenner.
+
+Changes wrt. Bjoern Kalkbrenner's patch (version 0.0.1 / 20020715):
+
+* Uniform, modular support for LOGIN and PLAIN.
+* Added 'Mail From: < > Auth=username' information in provisionally XTEXT format.
+* Added CRAM-MD5 support.
+* Added authentication by recipient domain.
+
+
+Release Notes:
+--------------
+
+Version:	Notes:					Date:
+-------------------------------------------------------------------
+0.5.9		qmail-smtpd AUTH (only)			25.3.2008
+0.6.9		qmail-authentication			1.2.2010
+0.7.0		Based on qmail-authentication 0.69
+		including now CRAM-MD5 support
+		for qmail-remote			31.7.2010
+0.7.1		cosmetics for qmail-remote		5.8.2010
+0.7.2		authorization-id = authentication-id
+		for qmail-remote;
+		added SMTPAUTH environment variable 
+		for qmail-smtpd; backport from SC 2.7	29.4.2012
+0.7.3		Fixed missing AUTH for qmai-smtpd announcement.
+		Improved SUBMISSION port handling.	2.5.2012
+0.7.4		Fixed missing 250 statements for AUTH.
+		Changed SMTPAUTH settings for cram-md5 	18.5.2012
+0.7.5		Fixed bug in qmail-remote not respecting
+		announced AUTH types. Tx. Callum Gibson.
+		Added '432' server code evaluation for
+	 	AUTH password expired in qmail-remote. 	23.10.2012
+0.7.6		Fixed order of SMTP commands (tx roberto).
+							02.02.2013
+0.8.0		Added authentication by recipient domain
+		for qmail-remote.
+		Removed SUBMISSION port feature for
+		qmail-smtpd.				22.03.2013
+0.8.1		Added SMTPAUTH="!+crom" feature.	24.03.2013
+0.8.2		Bug fix: qmail-smtpd ACCEPTS auth commands
+		even if SMTPAUTH="-". (tx chris).	21.01.2015
+0.8.3		Fixed bug in MD5 calculation for AMD64.
+		Fixed 'die_nomem' bug in qmail-smtpd.	23.08.2015
+
+
+
+Erwin Hoffmann - Hoehn 2015-08-23 (www.fehcom.de)
+
+
diff --git a/TARGETS b/TARGETS
index 707d386..ba7176b 100644
--- a/TARGETS
+++ b/TARGETS
@@ -7,6 +7,7 @@ qmail-local.o
 qmail.o
 quote.o
 now.o
+base64.o
 gfrom.o
 myctime.o
 slurpclose.o
@@ -200,6 +201,8 @@ dns.lib
 qmail-remote
 qmail-rspawn.o
 tcpto_clean.o
+md5c.o
+hmac_md5.o
 qmail-rspawn
 direntry.h
 qmail-clean.o
diff --git a/base64.c b/base64.c
new file mode 100644
index 0000000..9784755
--- /dev/null
+++ b/base64.c
@@ -0,0 +1,124 @@
+#include "base64.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "str.h"
+
+static char *b64alpha =
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+#define B64PAD '='
+
+/* returns 0 ok, 1 illegal, -1 problem */
+
+int b64decode(in,l,out)
+const unsigned char *in;
+int l;
+stralloc *out; /* not null terminated */
+{
+  int p = 0;
+  int n;
+  unsigned int x;
+  int i, j;
+  char *s;
+  unsigned char b[3];
+
+  if (l == 0)
+  {
+    if (!stralloc_copys(out,"")) return -1;
+    return 0;
+  }
+
+  while(in[l-1] == B64PAD) {
+    p ++;
+    l--;
+  }
+
+  n = (l + p) / 4;
+  i = (n * 3) - p;
+  if (!stralloc_ready(out,i)) return -1;
+  out->len = i;
+  s = out->s;
+
+  for(i = 0; i < n - 1 ; i++) {
+    x = 0;
+    for(j = 0; j < 4; j++) {
+      if(in[j] >= 'A' && in[j] <= 'Z')
+        x = (x << 6) + (unsigned int)(in[j] - 'A' + 0);
+      else if(in[j] >= 'a' && in[j] <= 'z')
+        x = (x << 6) + (unsigned int)(in[j] - 'a' + 26);
+      else if(in[j] >= '0' && in[j] <= '9')
+        x = (x << 6) + (unsigned int)(in[j] - '0' + 52);
+      else if(in[j] == '+')
+        x = (x << 6) + 62;
+      else if(in[j] == '/')
+        x = (x << 6) + 63;
+      else if(in[j] == '=')
+        x = (x << 6);
+    }
+
+    s[2] = (unsigned char)(x & 255); x >>= 8;
+    s[1] = (unsigned char)(x & 255); x >>= 8;
+    s[0] = (unsigned char)(x & 255); x >>= 8;
+    s += 3; in += 4;
+  }
+
+  x = 0;
+  for(j = 0; j < 4; j++) {
+    if(in[j] >= 'A' && in[j] <= 'Z')
+      x = (x << 6) + (unsigned int)(in[j] - 'A' + 0);
+    else if(in[j] >= 'a' && in[j] <= 'z')
+      x = (x << 6) + (unsigned int)(in[j] - 'a' + 26);
+    else if(in[j] >= '0' && in[j] <= '9')
+      x = (x << 6) + (unsigned int)(in[j] - '0' + 52);
+    else if(in[j] == '+')
+      x = (x << 6) + 62;
+    else if(in[j] == '/')
+      x = (x << 6) + 63;
+    else if(in[j] == '=')
+      x = (x << 6);
+  }
+
+  b[2] = (unsigned char)(x & 255); x >>= 8;
+  b[1] = (unsigned char)(x & 255); x >>= 8;
+  b[0] = (unsigned char)(x & 255); x >>= 8;
+
+  for(i = 0; i < 3 - p; i++)
+    s[i] = b[i];
+
+  return 0;
+}
+
+int b64encode(in,out)
+stralloc *in;
+stralloc *out; /* not null terminated */
+{
+  unsigned char a, b, c;
+  int i;
+  char *s;
+
+  if (in->len == 0)
+  {
+    if (!stralloc_copys(out,"")) return -1;
+    return 0;
+  }
+
+  i = in->len / 3 * 4 + 4;   
+  if (!stralloc_ready(out,i)) return -1;
+  s = out->s;
+
+  for (i = 0;i < in->len;i += 3) {
+    a = in->s[i];
+    b = i + 1 < in->len ? in->s[i + 1] : 0;
+    c = i + 2 < in->len ? in->s[i + 2] : 0;
+
+    *s++ = b64alpha[a >> 2];
+    *s++ = b64alpha[((a & 3 ) << 4) | (b >> 4)];
+
+    if (i + 1 >= in->len) *s++ = B64PAD;
+    else *s++ = b64alpha[((b & 15) << 2) | (c >> 6)];
+
+    if (i + 2 >= in->len) *s++ = B64PAD;
+    else *s++ = b64alpha[c & 63];
+  }
+  out->len = s - out->s;
+  return 0;
+}
diff --git a/base64.h b/base64.h
new file mode 100644
index 0000000..a9164c0
--- /dev/null
+++ b/base64.h
@@ -0,0 +1,7 @@
+#ifndef BASE64_H
+#define BASE64_H
+
+extern int b64decode();
+extern int b64encode();
+
+#endif
diff --git a/case_startb.c b/case_startb.c
new file mode 100644
index 0000000..ee88efe
--- /dev/null
+++ b/case_startb.c
@@ -0,0 +1,21 @@
+#include "case.h"
+
+int case_startb(s,len,t)
+register char *s;
+unsigned int len;
+register char *t;
+{
+  register unsigned char x;
+  register unsigned char y;
+
+  for (;;) {
+    y = *t++ - 'A';
+    if (y <= 'Z' - 'A') y += 'a'; else y += 'A';
+    if (!y) return 1;
+    if (!len) return 0;
+    --len;
+    x = *s++ - 'A';
+    if (x <= 'Z' - 'A') x += 'a'; else x += 'A';
+    if (x != y) return 0;
+  }
+}
diff --git a/global.h b/global.h
new file mode 100644
index 0000000..e0cf2dc
--- /dev/null
+++ b/global.h
@@ -0,0 +1,51 @@
+/* GLOBAL.H - RSAREF types and constants */
+
+#include <string.h>
+#include "uint32.h"
+
+/* Copyright (C) RSA Laboratories, a division of RSA Data Security,
+     Inc., created 1991. All rights reserved.
+ */
+
+#ifndef _GLOBAL_H_
+#define _GLOBAL_H_ 1
+
+/* PROTOTYPES should be set to one if and only if the compiler supports
+     function argument prototyping.
+   The following makes PROTOTYPES default to 1 if it has not already been
+     defined as 0 with C compiler flags.
+ */
+#ifndef PROTOTYPES
+#define PROTOTYPES 1
+#endif
+
+/* POINTER defines a generic pointer type */
+typedef unsigned char *POINTER;
+
+/* UINT2 defines a two byte word */
+typedef unsigned short int UINT2;
+
+/* UINT4 defines a four byte word */
+#ifdef UINT32_H
+#define UINT4 uint32
+#endif
+
+#ifndef NULL_PTR
+#define NULL_PTR ((POINTER)0)
+#endif
+
+#ifndef UNUSED_ARG
+#define UNUSED_ARG(x) x = *(&x);
+#endif
+
+/* PROTO_LIST is defined depending on how PROTOTYPES is defined above.
+   If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it
+     returns an empty list.  
+ */
+#if PROTOTYPES
+#define PROTO_LIST(list) list
+#else
+#define PROTO_LIST(list) ()
+#endif
+
+#endif /* end _GLOBAL_H_ */
diff --git a/hmac_md5.c b/hmac_md5.c
new file mode 100644
index 0000000..606979d
--- /dev/null
+++ b/hmac_md5.c
@@ -0,0 +1,76 @@
+#include "global.h"
+#include "md5.h"
+
+/*
+** Function: hmac_md5
+*/
+
+void hmac_md5(text, text_len, key, key_len, digest)
+unsigned char*  text;                /* pointer to data stream */
+int             text_len;            /* length of data stream */
+unsigned char*  key;                 /* pointer to authentication key */
+int             key_len;             /* length of authentication key */
+unsigned char   *digest;              /* caller digest to be filled in */
+
+{
+        MD5_CTX context;
+        unsigned char k_ipad[65];    /* inner padding -
+                                      * key XORd with ipad
+                                      */
+        unsigned char k_opad[65];    /* outer padding -
+                                      * key XORd with opad
+                                      */
+        unsigned char tk[16];
+        int i;
+        /* if key is longer than 64 bytes reset it to key=MD5(key) */
+        if (key_len > 64) {
+
+                MD5_CTX      tctx;
+
+                MD5Init(&tctx);
+                MD5Update(&tctx, key, key_len);
+                MD5Final(tk, &tctx);
+
+                key = tk;
+                key_len = 16;
+        }
+
+        /*
+         * the HMAC_MD5 transform looks like:
+         *
+         * MD5(K XOR opad, MD5(K XOR ipad, text))
+         *
+         * where K is an n byte key
+         * ipad is the byte 0x36 repeated 64 times
+         * opad is the byte 0x5c repeated 64 times
+         * and text is the data being protected
+         */
+
+        /* start out by storing key in pads */
+        bzero( k_ipad, sizeof k_ipad);
+        bzero( k_opad, sizeof k_opad);
+        bcopy( key, k_ipad, key_len);
+        bcopy( key, k_opad, key_len);
+
+        /* XOR key with ipad and opad values */
+        for (i=0; i<64; i++) {
+                k_ipad[i] ^= 0x36;
+                k_opad[i] ^= 0x5c;
+        }
+        /*
+         * perform inner MD5
+         */
+        MD5Init(&context);                   /* init context for 1st pass */
+        MD5Update(&context, k_ipad, 64);      /* start with inner pad */
+        MD5Update(&context, text, text_len); /* then text of datagram */
+        MD5Final(digest, &context);          /* finish up 1st pass */
+        /*
+         * perform outer MD5
+         */
+        MD5Init(&context);                   /* init context for 2nd
+                                              * pass */
+        MD5Update(&context, k_opad, 64);     /* start with outer pad */
+        MD5Update(&context, digest, 16);     /* then results of 1st
+                                              * hash */
+        MD5Final(digest, &context);          /* finish up 2nd pass */
+}
diff --git a/hmac_md5.h b/hmac_md5.h
new file mode 100644
index 0000000..8c1d835
--- /dev/null
+++ b/hmac_md5.h
@@ -0,0 +1,11 @@
+
+/* prototypes */
+
+void hmac_md5( unsigned char* text, int text_len, unsigned char* key, int key_len, unsigned char* digest);
+
+/* pointer to data stream */
+/* length of data stream */
+/* pointer to authentication key */
+/* length of authentication key */
+/* caller digest to be filled in */
+
diff --git a/install_authentication.sh b/install_authentication.sh
new file mode 100755
index 0000000..49f3786
--- /dev/null
+++ b/install_authentication.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# qmail-smtpd AUTH (UN)INSTALL Script (install_authentication.sh)
+# -------------------------------------------------------------------------------------
+#
+# Purpose:      To install and uninstall the qmail-smtpd Authentication Patch
+#
+# Parameters:   -u (uninstall)
+#	        VRF (Version to be uninstalled)
+#
+# Usage:        ./install_authentication.sh [-u] [Version]
+#
+#		Installation: 	./install_authentication.sh
+# 		Uninstallation: ./install_authentication.sh -u 105
+#
+# Return Codes: 0 - Patches applied successfully
+#		1 - Original QMAIL files not found (Patch not extracted in QMAIL source directory)
+#		2 - Patch files not found 
+#
+# Output:	install_auth.log
+#
+# History:      1.0.0 - Erwin Hoffmann - Initial release
+#		1.0.1 - 	       - grep fix; Gentoo fix
+#		1.0.2 -			 removed '-v' optio for cp
+#		1.0.3 -			 mods for 'qmail authentication'
+#		1.0.4 -                  some renameing
+#
+#---------------------------------------------------------------------------------------
+#
+DATE=$(date)
+LOCDIR=${PWD}
+QMAILHOME=$(head -n 1 conf-qmail)
+SOLARIS=$(sh ./find-systype.sh | grep -ci "SunOS")
+LOGFILE=auth.log
+TARGETS=FILES.auth
+IFSKEEP=${IFS}
+REL=083 # Should be identical to qmail AUTH level
+BUILD=20150823180830
+
+
+if [ $# -eq 0 ] ; then
+
+	echo "Installing qmail AUTH $REL (Build $BUILD) at $DATE <<<" | tee -a $LOGFILE 2>&1 
+
+	for FILE in $(grep "^= " ${TARGETS} | awk '{print $2}'); do
+		echo "Targeting file $FILE ..." | tee -a $LOGFILE 2>&1
+		if [ -s ${FILE} ] ; then
+			cp ${FILE} ${FILE}.$REL | tee -a $LOGFILE 2>&1
+			echo "--> ${FILE} copied to ${FILE}.$REL" | tee -a $LOGFILE 2>&1
+		else
+			echo "${FILE} not found !"
+			exit 1
+		fi
+		if [ -s ${FILE}.patch ] ; then
+			if [ ${SOLARIS} -gt 0 ]; then
+				echo "--> Patching qmail source file ${FILE} for Solaris ...." | tee -a $LOGFILE 2>&1
+				patch -i ${FILE}.patch ${FILE} 2>&1 | tee -a $LOGFILE
+			else
+				echo "--> Patching qmail source file ${FILE}  ...." | tee -a $LOGFILE 2>&1
+				patch ${FILE} ${FILE}.patch 2>&1 | tee -a $LOGFILE
+			fi
+		else
+			echo "!! ${FILE}.patch not found !"
+			exit 2
+		fi
+	done 
+
+
+	echo "Copying documentation and samples to ${QMAILHOME}/doc/ ..." | tee -a $LOGFILE 2>&1 
+
+	cp README.auth* ${QMAILHOME}/doc/ | tee -a $LOGFILE 2>&1
+	echo ""
+	echo "If you dont wont CRAM-MD5 suport disable '#define CRAM_MD5' in qmail-smtpd !"
+	echo "Installation of qmail authentication $REL (Build $BUILD) finished at $DATE <<<" | tee -a $LOGFILE 2>&1 
+
+# Now go for the uninstallation....
+
+elif [ "$1" = "-u" ] ; then
+
+# Get the Version Number from INPUT 
+
+	if [ $# -eq 2 ] ; then
+		REL=$2
+	fi
+
+	echo "De-installing qmail authentication $REL (Build $BUILD) at $DATE <<<" | tee -a $LOGFILE 2>&1 
+
+	for FILE in $(grep "^= " ${TARGETS} | awk '{print $2}'); do
+		echo "Targeting file $FILE ..." | tee -a $LOGFILE 2>&1
+		if [ -s ${FILE}.$REL ] ; then
+			mv ${FILE}.$REL ${FILE} | tee -a $LOGFILE 2>&1
+			touch ${FILE}
+			echo "--> ${FILE}.$REL moved to ${FILE}" | tee -a $LOGFILE 2>&1
+		else
+			echo "!! ${FILE}.$REL not found !"
+		fi
+	done
+	echo "De-installation of qmail authentication $REL (Build $BUILD) finished at $DATE <<<" | tee -a $LOGFILE 2>&1 
+fi
+
+exit 0
diff --git a/md5.h b/md5.h
new file mode 100644
index 0000000..94774ba
--- /dev/null
+++ b/md5.h
@@ -0,0 +1,49 @@
+/* MD5.H - header file for MD5C.C
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+   rights reserved.
+
+   License to copy and use this software is granted provided that it
+   is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+   Algorithm" in all material mentioning or referencing this software
+   or this function.
+
+   License is also granted to make and use derivative works provided
+   that such works are identified as "derived from the RSA Data
+   Security, Inc. MD5 Message-Digest Algorithm" in all material
+   mentioning or referencing the derived work.  
+                                                                    
+   RSA Data Security, Inc. makes no representations concerning either
+   the merchantability of this software or the suitability of this
+   software for any particular purpose. It is provided "as is"
+   without express or implied warranty of any kind.  
+                                                                    
+   These notices must be retained in any copies of any part of this
+   documentation and/or software.  
+ */
+
+#ifndef _MD5_H_
+#define _MD5_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* MD5 context. */
+typedef struct {
+  UINT4 state[4];                                   /* state (ABCD) */
+  UINT4 count[2];        /* number of bits, modulo 2^64 (lsb first) */
+  unsigned char buffer[64];                         /* input buffer */
+} MD5_CTX;
+
+void MD5Init PROTO_LIST ((MD5_CTX *));
+void MD5Update PROTO_LIST
+  ((MD5_CTX *, unsigned char *, unsigned int));
+void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/md5c.c b/md5c.c
new file mode 100644
index 0000000..8b0c856
--- /dev/null
+++ b/md5c.c
@@ -0,0 +1,334 @@
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+   rights reserved.
+
+   License to copy and use this software is granted provided that it
+   is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+   Algorithm" in all material mentioning or referencing this software
+   or this function.
+
+   License is also granted to make and use derivative works provided
+   that such works are identified as "derived from the RSA Data
+   Security, Inc. MD5 Message-Digest Algorithm" in all material
+   mentioning or referencing the derived work.  
+                                                                    
+   RSA Data Security, Inc. makes no representations concerning either
+   the merchantability of this software or the suitability of this
+   software for any particular purpose. It is provided "as is"
+   without express or implied warranty of any kind.  
+                                                                    
+   These notices must be retained in any copies of any part of this
+   documentation and/or software.  
+ */
+
+#include "global.h"
+#include "md5.h"
+
+/* Constants for MD5Transform routine.
+ */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64]));
+static void Encode PROTO_LIST
+  ((unsigned char *, UINT4 *, unsigned int));
+static void Decode PROTO_LIST
+  ((UINT4 *, unsigned char *, unsigned int));
+static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int));
+static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int));
+
+static unsigned char PADDING[64] = {
+  0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+   Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+    (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+    (a) = ROTATE_LEFT ((a), (s)); \
+    (a) += (b); \
+  }
+#define GG(a, b, c, d, x, s, ac) { \
+    (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+    (a) = ROTATE_LEFT ((a), (s)); \
+    (a) += (b); \
+  }
+#define HH(a, b, c, d, x, s, ac) { \
+    (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+    (a) = ROTATE_LEFT ((a), (s)); \
+    (a) += (b); \
+  }
+#define II(a, b, c, d, x, s, ac) { \
+    (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+    (a) = ROTATE_LEFT ((a), (s)); \
+    (a) += (b); \
+  }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context.
+ */
+void MD5Init (context)
+MD5_CTX *context;                                        /* context */
+{
+  context->count[0] = context->count[1] = 0;
+
+  /* Load magic initialization constants.
+   */
+  context->state[0] = 0x67452301;
+  context->state[1] = 0xefcdab89;
+  context->state[2] = 0x98badcfe;
+  context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+     operation, processing another message block, and updating the
+     context.
+ */
+void MD5Update (context, input, inputLen)
+MD5_CTX *context;                                        /* context */
+unsigned char *input;                                /* input block */
+unsigned int inputLen;                     /* length of input block */
+{
+  unsigned int i, index, partLen;
+
+  /* Compute number of bytes mod 64 */
+  index = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+  /* Update number of bits */
+  if ((context->count[0] += ((UINT4)inputLen << 3))
+      < ((UINT4)inputLen << 3))
+    context->count[1]++;
+  context->count[1] += ((UINT4)inputLen >> 29);
+  
+  partLen = 64 - index;
+  
+  /* Transform as many times as possible.
+   */
+  if (inputLen >= partLen) {
+    MD5_memcpy
+      ((POINTER)&context->buffer[index], (POINTER)input, partLen);
+    MD5Transform (context->state, context->buffer);
+  
+    for (i = partLen; i + 63 < inputLen; i += 64)
+      MD5Transform (context->state, &input[i]);
+    
+    index = 0;
+  }
+  else
+    i = 0;
+  
+  /* Buffer remaining input */
+  MD5_memcpy 
+    ((POINTER)&context->buffer[index], (POINTER)&input[i],
+     inputLen-i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+     the message digest and zeroizing the context.
+ */
+void MD5Final (digest, context)
+unsigned char digest[16];                         /* message digest */
+MD5_CTX *context;                                       /* context */
+{
+  unsigned char bits[8];
+  unsigned int index, padLen;
+
+  /* Save number of bits */
+  Encode (bits, context->count, 8);
+
+  /* Pad out to 56 mod 64.
+   */
+  index = (unsigned int)((context->count[0] >> 3) & 0x3f);
+  padLen = (index < 56) ? (56 - index) : (120 - index);
+  MD5Update (context, PADDING, padLen);
+  
+  /* Append length (before padding) */
+  MD5Update (context, bits, 8);
+
+  /* Store state in digest */
+  Encode (digest, context->state, 16);
+  
+  /* Zeroize sensitive information.
+   */
+  MD5_memset ((POINTER)context, 0, sizeof (*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block.
+ */
+static void MD5Transform (state, block)
+UINT4 state[4];
+unsigned char block[64];
+{
+  UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+  
+  Decode (x, block, 64);
+
+  /* Round 1 */
+  FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+  FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+  FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+  FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+  FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+  FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+  FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+  FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+  FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+  FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+  FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+  FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+  FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+  FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+  FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+  FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+  /* Round 2 */
+  GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+  GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+  GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+  GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+  GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+  GG (d, a, b, c, x[10], S22,  0x2441453); /* 22 */
+  GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+  GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+  GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+  GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+  GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+  GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+  GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+  GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+  GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+  GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+  /* Round 3 */
+  HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+  HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+  HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+  HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+  HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+  HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+  HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+  HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+  HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+  HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+  HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+  HH (b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */
+  HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+  HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+  HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+  HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+  /* Round 4 */
+  II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+  II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+  II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+  II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+  II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+  II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+  II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+  II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+  II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+  II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+  II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+  II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+  II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+  II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+  II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+  II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+  state[0] += a;
+  state[1] += b;
+  state[2] += c;
+  state[3] += d;
+  
+  /* Zeroize sensitive information.
+   */
+  MD5_memset ((POINTER)x, 0, sizeof (x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char). Assumes len is
+     a multiple of 4.
+ */
+static void Encode (output, input, len)
+unsigned char *output;
+UINT4 *input;
+unsigned int len;
+{
+  unsigned int i, j;
+
+  for (i = 0, j = 0; j < len; i++, j += 4) {
+    output[j] = (unsigned char)(input[i] & 0xff);
+    output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+    output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+    output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+  }
+}
+
+/* Decodes input (unsigned char) into output (UINT4). Assumes len is
+     a multiple of 4.
+ */
+static void Decode (output, input, len)
+UINT4 *output;
+unsigned char *input;
+unsigned int len;
+{
+  unsigned int i, j;
+
+  for (i = 0, j = 0; j < len; i++, j += 4)
+    output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
+      (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
+}
+
+/* Note: Replace "for loop" with standard memcpy if possible.
+ */
+static void MD5_memcpy (output, input, len)
+POINTER output;
+POINTER input;
+unsigned int len;
+{
+  unsigned int i;
+  
+  for (i = 0; i < len; i++)
+    output[i] = input[i];
+}
+
+/* Note: Replace "for loop" with standard memset if possible.
+ */
+static void MD5_memset (output, value, len)
+POINTER output;
+int value;
+unsigned int len;
+{
+  unsigned int i;
+  
+  for (i = 0; i < len; i++)
+    ((char *)output)[i] = (char)value;
+}
diff --git a/qmail-control.9 b/qmail-control.9
index 503ce93..2857813 100644
--- a/qmail-control.9
+++ b/qmail-control.9
@@ -40,6 +40,7 @@ See the corresponding man pages for further details.
 .ta 5c 10c
 control	default	used by
 
+.I authsender	\fR(none)	\fRqmail-remote
 .I badmailfrom	\fR(none)	\fRqmail-smtpd
 .I bouncefrom	\fRMAILER-DAEMON	\fRqmail-send
 .I bouncehost	\fIme	\fRqmail-send
diff --git a/qmail-remote.8 b/qmail-remote.8
index e38d169..fe8a9c3 100644
--- a/qmail-remote.8
+++ b/qmail-remote.8
@@ -60,6 +60,7 @@ will invoke the contents of
 instead of
 .BR qmail-remote,
 if that environment variable is set.
+
 .SH TRANSPARENCY
 End-of-file in SMTP is encoded as dot CR LF.
 A dot at the beginning of a line is encoded as dot dot.
@@ -107,6 +108,73 @@ Message report: permanent failure.
 After this letter comes a human-readable description of
 what happened.
 
+.B qmail-remote
+may use SMTP Authenticaton of type CRAM-MD4, PLAIN, or LOGIN
+(in this order) to connect to remote hosts.
+The following reports are provided:
+.TP 5
+K
+no supported AUTH method found, continuing without authentication.
+.TP 5
+Z
+Connected to 
+.I host
+but authentication was rejected (AUTH PLAIN).
+.TP 5
+Z
+Connected to 
+.I host 
+but unable to base64encode (plain).
+.TP 5
+Z
+Connected to 
+.I host
+but authentication was rejected (plain)."
+.TP 5
+Z
+Connected to
+.I host
+but authentication was rejected (AUTH LOGIN).
+.TP 5
+Z
+Connected to 
+.I host
+but unable to base64encode user.
+.TP 5
+Z
+Connected to 
+.I host 
+but authentication was rejected (username).
+.TP 5
+Z
+Connected to 
+.I host 
+but unable to base64encode pass.
+.TP 5
+Z
+Connected to
+.I host
+but authentication was rejected (AUTH CRAM-MD5).
+Z
+Connected to 
+.I host
+but unable to base64decode challenge.
+.TP 5
+Z
+Connected to 
+.I host
+but unable to base64encode username+digest.
+.TP 5
+Z
+Connected to 
+.I host 
+but password expired.
+.TP 5
+Z
+Connected to 
+.I host 
+but authentication was rejected (username+digest).
+.PP
 The recipient reports will always be printed in the same order as
 .BR qmail-remote 's
 .I recip
@@ -121,6 +189,51 @@ arguments.
 always exits zero.
 .SH "CONTROL FILES"
 .TP 5
+.I authsenders
+Authenticated sender.
+For each
+.I sender 
+included in 
+.IR authsenders :
+.I sender\fB:\fIrelay\fB:\fIport\fB|\fIuser\fB|\fIpassword 
+.B qmail-remote
+will try SMTP Authentication 
+of type CRAM-MD5, LOGIN, or PLAIN 
+with the provided user name
+.I user 
+and password
+.I password 
+(the authentication information) 
+and eventually relay the 
+mail through
+.I relay
+on port
+.IR port .
+The use of 
+.I relay
+and 
+.I port 
+follows the same rules as for
+.IR smtproutes 
+Note: In case
+.I sender
+is empty, 
+.B qmail-remote
+will try to deliver each outgoing mail 
+SMTP authenticated. If the authentication
+information is missing, the mail is 
+delivered none-authenticated.
+.I authsenders
+can be constructed as follows:
+
+.EX
+   @example.com|generic|passwd
+   .subdomain.example.com|other|otherpw
+   mail@example.com|test|testpass
+   info@example.com:smtp.example.com:26|other|otherpw
+   :mailrelay.example.com:587|e=mc2|testpass
+.EE
+.TP 5
 .I helohost
 Current host name,
 for use solely in saying hello to the remote SMTP server.
@@ -135,7 +248,9 @@ refuses to run.
 Artificial SMTP routes.
 Each route has the form
 .IR domain\fB:\fIrelay ,
-without any extra spaces.
+or 
+.IR domain\fB:\fIrelay\fB|\fIuser\fB|\fIpassword
+in case of authenticated routes without any extra spaces.
 If
 .I domain
 matches
@@ -156,6 +271,7 @@ normal SMTP port, 25:
 
 .EX
    inside.af.mil:firewall.af.mil:26
+  :submission.myrelay.com:587|myuserid|mypasswd
 .EE
 
 .I relay
@@ -184,11 +300,15 @@ any other address is artificially routed to
 The
 .B qmail
 system does not protect you if you create an artificial
-mail loop between machines.
+mail loop between machines. 
 However,
 you are always safe using
 .I smtproutes
 if you do not accept mail from the network.
+Note:   
+.I authsender 
+routes have precedence over
+.IR smtproutes .
 .TP 5
 .I timeoutconnect
 Number of seconds
diff --git a/qmail-remote.c b/qmail-remote.c
index 0cef50a..c7b1222 100644
--- a/qmail-remote.c
+++ b/qmail-remote.c
@@ -27,6 +27,7 @@
 #include "timeoutconn.h"
 #include "timeoutread.h"
 #include "timeoutwrite.h"
+#include "base64.h"
 
 #define HUGESMTPTEXT 5000
 
@@ -43,6 +44,16 @@ struct constmap maproutes;
 stralloc host = {0};
 stralloc sender = {0};
 
+stralloc authsenders = {0};
+struct constmap mapauthsenders;
+stralloc user = {0};
+stralloc pass = {0};
+stralloc auth = {0};
+stralloc plain = {0};
+stralloc chal  = {0};
+stralloc slop  = {0};
+char *authsender;
+
 saa reciplist = {0};
 
 struct ip_address partner;
@@ -85,6 +96,12 @@ Sorry. Although I'm listed as a best-preference MX or A for that host,\n\
 it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n");
 zerodie(); }
 
+void err_authprot() {
+  out("Kno supported AUTH method found, continuing without authentication.\n");
+  zero();
+  substdio_flush(subfdoutsmall);
+}
+
 void outhost()
 {
   char x[IPFMT];
@@ -215,24 +232,192 @@ void blast()
 
 stralloc recip = {0};
 
+void mailfrom()
+{
+  substdio_puts(&smtpto,"MAIL FROM:<");
+  substdio_put(&smtpto,sender.s,sender.len);
+  substdio_puts(&smtpto,">\r\n");
+  substdio_flush(&smtpto);
+}
+
+stralloc xuser = {0};
+
+int xtext(sa,s,len)
+stralloc *sa;
+char *s;
+int len;
+{
+  int i;
+
+  if(!stralloc_copys(sa,"")) temp_nomem();
+  
+  for (i = 0; i < len; i++) {
+    if (s[i] == '=') {
+      if (!stralloc_cats(sa,"+3D")) temp_nomem();
+    } else if (s[i] == '+') {  
+        if (!stralloc_cats(sa,"+2B")) temp_nomem(); 
+    } else if ((int) s[i] < 33 || (int) s[i] > 126) {
+        if (!stralloc_cats(sa,"+3F")) temp_nomem(); /* ok. not correct */
+    } else if (!stralloc_catb(sa,s+i,1)) {
+        temp_nomem();
+    }
+  }
+
+  return sa->len;
+}
+
+void mailfrom_xtext()
+{
+  if (!xtext(&xuser,user.s,user.len)) temp_nomem();
+  substdio_puts(&smtpto,"MAIL FROM:<");
+  substdio_put(&smtpto,sender.s,sender.len);
+  substdio_puts(&smtpto,"> AUTH=");
+  substdio_put(&smtpto,xuser.s,xuser.len);
+  substdio_puts(&smtpto,"\r\n");
+  substdio_flush(&smtpto);
+}
+
+int mailfrom_plain()
+{
+  substdio_puts(&smtpto,"AUTH PLAIN\r\n");
+  substdio_flush(&smtpto);
+  if (smtpcode() != 334) { quit("ZConnected to "," but authentication was rejected (AUTH PLAIN)."); return -1; }
+
+  if (!stralloc_cat(&plain,&user)) temp_nomem(); /* <authorization-id> */
+  if (!stralloc_0(&plain)) temp_nomem();
+  if (!stralloc_cat(&plain,&user)) temp_nomem(); /* <authentication-id> */
+  if (!stralloc_0(&plain)) temp_nomem();
+  if (!stralloc_cat(&plain,&pass)) temp_nomem(); /* password */
+  if (b64encode(&plain,&auth)) quit("ZConnected to "," but unable to base64encode (plain).");
+  substdio_put(&smtpto,auth.s,auth.len);
+  substdio_puts(&smtpto,"\r\n");
+  substdio_flush(&smtpto);
+  if (smtpcode() == 235) { mailfrom_xtext(); return 0; }
+  else if (smtpcode() == 432) { quit("ZConnected to "," but password expired."); return 1; }
+  else { quit("ZConnected to "," but authentication was rejected (plain)."); return 1; }
+  
+  return 0;
+}
+
+int mailfrom_login()
+{
+  substdio_puts(&smtpto,"AUTH LOGIN\r\n");
+  substdio_flush(&smtpto);
+  if (smtpcode() != 334) { quit("ZConnected to "," but authentication was rejected (AUTH LOGIN)."); return -1; }
+
+  if (!stralloc_copys(&auth,"")) temp_nomem();
+  if (b64encode(&user,&auth)) quit("ZConnected to "," but unable to base64encode user.");
+  substdio_put(&smtpto,auth.s,auth.len);
+  substdio_puts(&smtpto,"\r\n");
+  substdio_flush(&smtpto);
+  if (smtpcode() != 334) quit("ZConnected to "," but authentication was rejected (username).");
+
+  if (!stralloc_copys(&auth,"")) temp_nomem();
+  if (b64encode(&pass,&auth)) quit("ZConnected to "," but unable to base64encode pass.");
+  substdio_put(&smtpto,auth.s,auth.len);
+  substdio_puts(&smtpto,"\r\n");
+  substdio_flush(&smtpto);
+  if (smtpcode() == 235) { mailfrom_xtext(); return 0; }
+  else if (smtpcode() == 432) { quit("ZConnected to "," but password expired."); return 1; }
+  else { quit("ZConnected to "," but authentication was rejected (password)."); return 1; }
+}
+
+int mailfrom_cram()
+{
+  int j;
+  unsigned char h;
+  unsigned char digest[16];
+  unsigned char digascii[33];
+  static char hextab[]="0123456789abcdef";
+
+  substdio_puts(&smtpto,"AUTH CRAM-MD5\r\n");
+  substdio_flush(&smtpto);
+  if (smtpcode() != 334) { quit("ZConnected to "," but authentication was rejected (AUTH CRAM-MD5)."); return -1; }
+
+  if (str_chr(smtptext.s+4,' ')) { 			/* Challenge */
+    if(!stralloc_copys(&slop,"")) temp_nomem();
+    if (!stralloc_copyb(&slop,smtptext.s+4,smtptext.len-5)) temp_nomem();
+    if (b64decode(slop.s,slop.len,&chal)) quit("ZConnected to "," but unable to base64decode challenge.");
+  }
+   
+  hmac_md5(chal.s,chal.len,pass.s,pass.len,digest);
+
+  for (j = 0;j < 16;j++)				/* HEX => ASCII */
+  {
+    digascii[2*j] = hextab[digest[j] >> 4];  
+    digascii[2*j+1] = hextab[digest[j] & 0xf]; 
+  }
+  digascii[32]=0;
+
+  slop.len = 0;
+  if (!stralloc_copys(&slop,"")) temp_nomem();
+  if (!stralloc_cat(&slop,&user)) temp_nomem();		 /* user-id */
+  if (!stralloc_cats(&slop," ")) temp_nomem();
+  if (!stralloc_catb(&slop,digascii,32)) temp_nomem();   /* digest */ 
+
+  if (!stralloc_copys(&auth,"")) temp_nomem();
+  if (b64encode(&slop,&auth)) quit("ZConnected to "," but unable to base64encode username+digest.");
+  substdio_put(&smtpto,auth.s,auth.len);
+  substdio_puts(&smtpto,"\r\n");
+  substdio_flush(&smtpto);
+  if (smtpcode() == 235) { mailfrom_xtext(); return 0; }
+  else if (smtpcode() == 432) { quit("ZConnected to "," but password expired."); return 1; }
+  else { quit("ZConnected to "," but authentication was rejected (username+digest)."); return 1; } 
+}
+
+void smtp_auth()
+{
+  int i, j; 
+
+  for (i = 0; i + 8 < smtptext.len; i += str_chr(smtptext.s+i,'\n')+1)
+    if (!str_diffn(smtptext.s+i+4,"AUTH",4)) {  
+      if (j = str_chr(smtptext.s+i+8,'C') > 0)
+        if (case_starts(smtptext.s+i+8+j,"CRAM"))
+          if (mailfrom_cram() >= 0) return;
+
+      if (j = str_chr(smtptext.s+i+8,'P') > 0)
+        if (case_starts(smtptext.s+i+8+j,"PLAIN")) 
+          if (mailfrom_plain() >= 0) return;
+
+      if (j = str_chr(smtptext.s+i+8,'L') > 0)
+        if (case_starts(smtptext.s+i+8+j,"LOGIN")) 
+          if (mailfrom_login() >= 0) return;
+
+      err_authprot();
+      mailfrom();
+    }
+}
+
 void smtp()
 {
   unsigned long code;
   int flagbother;
   int i;
  
-  if (smtpcode() != 220) quit("ZConnected to "," but greeting failed");
+  code = smtpcode();
+  if (code >= 500) quit("DConnected to "," but greeting failed");
+  if (code != 220) quit("ZConnected to "," but greeting failed");
  
-  substdio_puts(&smtpto,"HELO ");
+  substdio_puts(&smtpto,"EHLO ");
   substdio_put(&smtpto,helohost.s,helohost.len);
   substdio_puts(&smtpto,"\r\n");
   substdio_flush(&smtpto);
-  if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected");
- 
-  substdio_puts(&smtpto,"MAIL FROM:<");
-  substdio_put(&smtpto,sender.s,sender.len);
-  substdio_puts(&smtpto,">\r\n");
-  substdio_flush(&smtpto);
+
+  if (smtpcode() != 250) {
+    substdio_puts(&smtpto,"HELO ");
+    substdio_put(&smtpto,helohost.s,helohost.len);
+    substdio_puts(&smtpto,"\r\n");
+    substdio_flush(&smtpto);
+    code = smtpcode();
+    if (code >= 500) quit("DConnected to "," but my name was rejected");
+    if (code != 250) quit("ZConnected to "," but my name was rejected");
+  }
+
+  if (user.len && pass.len)
+    smtp_auth();
+  else 
+    mailfrom();
+
   code = smtpcode();
   if (code >= 500) quit("DConnected to "," but sender was rejected");
   if (code >= 400) quit("ZConnected to "," but sender was rejected");
@@ -323,48 +508,102 @@ void getcontrols()
     case 1:
       if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break;
   }
+  
+  switch(control_readfile(&authsenders,"control/authsenders",0)) {
+    case -1:
+       temp_control();
+    case 0:
+      if (!constmap_init(&mapauthsenders,"",0,1)) temp_nomem(); break;
+    case 1:
+      if (!constmap_init(&mapauthsenders,authsenders.s,authsenders.len,1)) temp_nomem(); break;
+  }
 }
 
-void main(argc,argv)
+int main(argc,argv)
 int argc;
 char **argv;
 {
   static ipalloc ip = {0};
-  int i;
+  int i, j;
   unsigned long random;
   char **recips;
   unsigned long prefme;
   int flagallaliases;
   int flagalias;
   char *relayhost;
- 
+   
   sig_pipeignore();
   if (argc < 4) perm_usage();
   if (chdir(auto_qmail) == -1) temp_chdir();
   getcontrols();
  
- 
   if (!stralloc_copys(&host,argv[1])) temp_nomem();
- 
+
+  authsender = 0;
   relayhost = 0;
-  for (i = 0;i <= host.len;++i)
-    if ((i == 0) || (i == host.len) || (host.s[i] == '.'))
-      if (relayhost = constmap(&maproutes,host.s + i,host.len - i))
+
+  addrmangle(&sender,argv[2],&flagalias,0);
+
+  for (i = 0;i <= sender.len;++i)
+    if ((i == 0) || (i == sender.len) || (sender.s[i] == '.') || (sender.s[i] == '@'))
+      if (authsender = constmap(&mapauthsenders,sender.s + i,sender.len - i))
         break;
-  if (relayhost && !*relayhost) relayhost = 0;
- 
-  if (relayhost) {
-    i = str_chr(relayhost,':');
-    if (relayhost[i]) {
-      scan_ulong(relayhost + i + 1,&port);
-      relayhost[i] = 0;
+
+  if (authsender && !*authsender) authsender = 0;
+
+  if (authsender) {
+    i = str_chr(authsender,'|');
+    if (authsender[i]) {
+      j = str_chr(authsender + i + 1,'|');
+      if (authsender[j]) {
+        authsender[i] = 0;
+        authsender[i + j + 1] = 0;
+        if (!stralloc_copys(&user,"")) temp_nomem();
+        if (!stralloc_copys(&user,authsender + i + 1)) temp_nomem();
+        if (!stralloc_copys(&pass,"")) temp_nomem();
+        if (!stralloc_copys(&pass,authsender + i + j + 2)) temp_nomem();
+      }
+    }
+    i = str_chr(authsender,':');
+    if (authsender[i]) {
+      scan_ulong(authsender + i + 1,&port);
+      authsender[i] = 0;
     }
-    if (!stralloc_copys(&host,relayhost)) temp_nomem();
-  }
 
+    if (!stralloc_copys(&relayhost,authsender)) temp_nomem();
+    if (!stralloc_copys(&host,authsender)) temp_nomem();
+
+  }
+  else {					/* default smtproutes -- authenticated */
+    for (i = 0;i <= host.len;++i)
+      if ((i == 0) || (i == host.len) || (host.s[i] == '.'))
+        if (relayhost = constmap(&maproutes,host.s + i,host.len - i))
+          break;
+
+    if (relayhost && !*relayhost) relayhost = 0;
+
+    if (relayhost) {
+      i = str_chr(relayhost,'|');
+      if (relayhost[i]) {
+        j = str_chr(relayhost + i + 1,'|');
+        if (relayhost[j]) {
+          relayhost[i] = 0;
+          relayhost[i + j + 1] = 0;
+          if (!stralloc_copys(&user,"")) temp_nomem();
+          if (!stralloc_copys(&user,relayhost + i + 1)) temp_nomem();
+          if (!stralloc_copys(&pass,"")) temp_nomem();
+          if (!stralloc_copys(&pass,relayhost + i + j + 2)) temp_nomem();
+        }
+      }
+      i = str_chr(relayhost,':');
+      if (relayhost[i]) {
+        scan_ulong(relayhost + i + 1,&port);
+        relayhost[i] = 0;
+      }
+      if (!stralloc_copys(&host,relayhost)) temp_nomem();
+    }
+  }
 
-  addrmangle(&sender,argv[2],&flagalias,0);
- 
   if (!saa_readyplus(&reciplist,0)) temp_nomem();
   if (ipme_init() != 1) temp_oserr();
  
diff --git a/qmail-showctl.c b/qmail-showctl.c
index e039a1b..588c7b9 100644
--- a/qmail-showctl.c
+++ b/qmail-showctl.c
@@ -240,6 +240,7 @@ void main()
     _exit(111);
   }
 
+  do_lst("authsenders","No authenticated SMTP sender.","Authenicated SMTP sender: ","");
   do_lst("badmailfrom","Any MAIL FROM is allowed.",""," not accepted in MAIL FROM.");
   do_str("bouncefrom",0,"MAILER-DAEMON","Bounce user name is ");
   do_str("bouncehost",1,"bouncehost","Bounce host name is ");
@@ -291,8 +292,7 @@ void main()
   while (d = readdir(dir)) {
     if (str_equal(d->d_name,".")) continue;
     if (str_equal(d->d_name,"..")) continue;
-    if (str_equal(d->d_name,"bouncefrom")) continue;
-    if (str_equal(d->d_name,"bouncehost")) continue;
+    if (str_equal(d->d_name,"authsenders")) continue;
     if (str_equal(d->d_name,"badmailfrom")) continue;
     if (str_equal(d->d_name,"bouncefrom")) continue;
     if (str_equal(d->d_name,"bouncehost")) continue;
diff --git a/qmail-smtpd.8 b/qmail-smtpd.8
index 4eeae49..a57027b 100644
--- a/qmail-smtpd.8
+++ b/qmail-smtpd.8
@@ -23,7 +23,30 @@ or
 header fields.
 
 .B qmail-smtpd
-supports ESMTP, including the 8BITMIME and PIPELINING options.
+supports ESMTP, including the 8BITMIME, DATA, PIPELINING, SIZE, and AUTH options.
+.B qmail-smtpd
+includes a \'MAIL FROM:\' parameter parser and obeys \'Auth\' and \'Size\' advertisements.
+.B qmail-smtpd
+can accept LOGIN, PLAIN, and CRAM-MD5 AUTH types. It invokes
+.IR checkprogram ,
+which reads on file descriptor 3 the username, a 0 byte, the password
+or CRAM-MD5 digest/response derived from the SMTP client,
+another 0 byte, a CRAM-MD5 challenge (if applicable to the AUTH type),
+and a final 0 byte.
+.I checkprogram
+invokes
+.I subprogram
+upon successful authentication, which should in turn return 0 to
+.BR qmail-smtpd ,
+effectively setting the environment variables $RELAYCLIENT and $TCPREMOTEINFO
+(any supplied value replaced with the authenticated username).
+.B qmail-smtpd
+will reject the authentication attempt if it receives a nonzero return
+value from
+.I checkprogram
+or
+.IR subprogram .
+
 .SH TRANSPARENCY
 .B qmail-smtpd
 converts the SMTP newline convention into the UNIX newline convention
@@ -169,6 +192,67 @@ Number of seconds
 .B qmail-smtpd
 will wait for each new buffer of data from the remote SMTP client.
 Default: 1200.
+
+.SH "ENVIRONMENT VARIABLES READ"
+Environment variables may be defined globally in the
+.B qmail-smtpd
+startup script and/or individually as part of the
+.B tcpserver's
+cdb database.
+The environment variables may be quoted ("variable", or 'variable') and
+in case of global use, have to be exported.
+.B qmail-smtpd
+supports the following legacy environment variables, typically
+provided by
+.B tcpserver
+or
+.B sslserver
+or
+.BR tcp-env :
+.IR TCPREMOTEIP ,
+.IR TCPREMOTEHOST
+.IR TCPREMOTEINFO
+and
+.IR TCPLOCALPORT
+as well as
+.IR RELAYCLIENT .
+
+.B qmail-smtpd
+may use the following environment variables for SMTP authentication:
+.TP 5
+.IR SMTPAUTH
+is used to enable SMTP Authentication for the AUTH types
+LOGIN and PLAIN.
+In case
+.TP 5
+.IR SMTPAUTH='+cram'
+is defined,
+.B qmail-smtpd
+honors LOGIN, PLAIN, and additionally CRAM-MD5 authentication.
+Simply 
+.TP 5
+.IR SMTPAUTH='cram'
+restricts authentication just to CRAM-MD5.
+If however
+.TP 5
+.IR SMTPAUTH='!'
+starts with an exclamation mark, AUTH is required. 
+You can enforce 'Submission' using this option 
+and binding
+.B qmail-smtpd
+to the SUBMISSION port \'587'\.
+In particular,
+.TP 5
+.IR SMTPAUTH='!cram'
+may be useful.
+In opposite, if
+.TP 5
+.IR SMTPAUTH='-'
+starts with a dash, AUTH is disabled for particular
+connections.
+
+Note: The use of 'cram' requires a CRAM-MD5 enabled PAM.
+
 .SH "SEE ALSO"
 tcp-env(1),
 tcp-environ(5),
diff --git a/qmail-smtpd.c b/qmail-smtpd.c
index c12bf16..a87a1d8 100644
--- a/qmail-smtpd.c
+++ b/qmail-smtpd.c
@@ -23,6 +23,9 @@
 #include "timeoutread.h"
 #include "timeoutwrite.h"
 #include "commands.h"
+#include "wait.h"
+
+#define AUTHSLEEP 5
 
 #define MAXHOPS 100
 unsigned int databytes = 0;
@@ -49,6 +52,7 @@ void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _
 void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); }
 void straynewline() { out("451 See https://cr.yp.to/docs/smtplf.html.\r\n"); flush(); _exit(1); }
 
+void err_size() { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); }
 void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); }
 void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }
 void err_unimpl(arg) char *arg; { out("502 unimplemented (#5.5.1)\r\n"); }
@@ -59,6 +63,17 @@ void err_noop(arg) char *arg; { out("250 ok\r\n"); }
 void err_vrfy(arg) char *arg; { out("252 send some mail, i'll try my best\r\n"); }
 void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); }
 
+int err_child() { out("454 oops, problem with child and I can't auth (#4.3.0)\r\n"); return -1; }
+int err_fork() { out("454 oops, child won't start and I can't auth (#4.3.0)\r\n"); return -1; }
+int err_pipe() { out("454 oops, unable to open pipe and I can't auth (#4.3.0)\r\n"); return -1; }
+int err_write() { out("454 oops, unable to write pipe and I can't auth (#4.3.0)\r\n"); return -1; }
+void err_authd() { out("503 you're already authenticated (#5.5.0)\r\n"); }
+void err_authmail() { out("503 no auth during mail transaction (#5.5.0)\r\n"); }
+int err_noauth() { out("504 auth type unimplemented (#5.5.1)\r\n"); return -1; }
+int err_authabrt() { out("501 auth exchange canceled (#5.0.0)\r\n"); return -1; }
+int err_input() { out("501 malformed auth input (#5.5.4)\r\n"); return -1; }
+void err_authfail() { out("535 authentication failed (#5.7.1)\r\n"); }
+void err_submission() { out("530 Authorization required (#5.7.1) \r\n"); }
 
 stralloc greeting = {0};
 
@@ -76,11 +91,14 @@ void smtp_quit(arg) char *arg;
   smtp_greet("221 "); out("\r\n"); flush(); _exit(0);
 }
 
+char *protocol;
 char *remoteip;
 char *remotehost;
 char *remoteinfo;
 char *local;
+char *localport;
 char *relayclient;
+char *auth;
 
 stralloc helohost = {0};
 char *fakehelo; /* pointer into helohost, or 0 */
@@ -91,6 +109,7 @@ void dohelo(arg) char *arg; {
   fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0;
 }
 
+int smtpauth = 0;
 int liphostok = 0;
 stralloc liphost = {0};
 int bmfok = 0;
@@ -109,7 +128,6 @@ void setup()
   if (liphostok == -1) die_control();
   if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control();
   if (timeout <= 0) timeout = 1;
-
   if (rcpthosts_init() == -1) die_control();
 
   bmfok = control_readfile(&bmf,"control/badmailfrom",0);
@@ -122,19 +140,32 @@ void setup()
   if (x) { scan_ulong(x,&u); databytes = u; }
   if (!(databytes + 1)) --databytes;
  
+  protocol = "SMTP";
   remoteip = env_get("TCPREMOTEIP");
   if (!remoteip) remoteip = "unknown";
   local = env_get("TCPLOCALHOST");
   if (!local) local = env_get("TCPLOCALIP");
   if (!local) local = "unknown";
+  localport = env_get("TCPLOCALPORT");
+  if (!localport) localport = "0";
   remotehost = env_get("TCPREMOTEHOST");
   if (!remotehost) remotehost = "unknown";
   remoteinfo = env_get("TCPREMOTEINFO");
   relayclient = env_get("RELAYCLIENT");
+  auth = env_get("SMTPAUTH");
+  if (auth) {
+    smtpauth = 1;
+    case_lowers(auth);
+    if (!case_diffs(auth,"-")) smtpauth = 0;
+    if (!case_diffs(auth,"!")) smtpauth = 11;
+    if (case_starts(auth,"cram")) smtpauth = 2;
+    if (case_starts(auth,"+cram")) smtpauth = 3;
+    if (case_starts(auth,"!cram")) smtpauth = 12;
+    if (case_starts(auth,"!+cram")) smtpauth = 13;
+  }
   dohelo(remotehost);
 }
 
-
 stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */
 
 int addrparse(arg)
@@ -216,11 +247,72 @@ int addrallowed()
   return r;
 }
 
-
+char *auth;
+int seenauth = 0;
 int seenmail = 0;
 int flagbarf; /* defined if seenmail */
+int flagsize;
 stralloc mailfrom = {0};
 stralloc rcptto = {0};
+stralloc fuser = {0};
+stralloc mfparms = {0};
+
+int mailfrom_size(arg) char *arg;
+{
+  long r;
+  unsigned long sizebytes = 0;
+
+  scan_ulong(arg,&r);
+  sizebytes = r;
+  if (databytes) if (sizebytes > databytes) return 1;
+  return 0;
+}
+
+void mailfrom_auth(arg,len) 
+char *arg; 
+int len;
+{
+  if (!stralloc_copys(&fuser,"")) die_nomem();
+  if (case_starts(arg,"<>")) { if (!stralloc_cats(&fuser,"unknown")) die_nomem(); }
+  else 
+    while (len) {
+      if (*arg == '+') {
+        if (case_starts(arg,"+3D")) { arg=arg+2; len=len-2; if (!stralloc_cats(&fuser,"=")) die_nomem(); }
+        if (case_starts(arg,"+2B")) { arg=arg+2; len=len-2; if (!stralloc_cats(&fuser,"+")) die_nomem(); }
+      }
+      else
+        if (!stralloc_catb(&fuser,arg,1)) die_nomem();
+      arg++; len--;
+    }
+  if(!stralloc_0(&fuser)) die_nomem();
+  if (!remoteinfo) {
+    remoteinfo = fuser.s;
+    if (!env_unset("TCPREMOTEINFO")) die_read();
+    if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem();
+  }
+}
+
+void mailfrom_parms(arg) char *arg;
+{
+  int i;
+  int len;
+
+    len = str_len(arg);
+    if (!stralloc_copys(&mfparms,"")) die_nomem();
+    i = byte_chr(arg,len,'>');
+    if (i > 4 && i < len) {
+      while (len) {
+        arg++; len--; 
+        if (*arg == ' ' || *arg == '\0' ) {
+           if (case_starts(mfparms.s,"SIZE=")) if (mailfrom_size(mfparms.s+5)) { flagsize = 1; return; }
+           if (case_starts(mfparms.s,"AUTH=")) mailfrom_auth(mfparms.s+5,mfparms.len-5);  
+           if (!stralloc_copys(&mfparms,"")) die_nomem();
+        }
+        else
+          if (!stralloc_catb(&mfparms,arg,1)) die_nomem(); 
+      }
+    }
+}
 
 void smtp_helo(arg) char *arg;
 {
@@ -229,17 +321,30 @@ void smtp_helo(arg) char *arg;
 }
 void smtp_ehlo(arg) char *arg;
 {
-  smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
+  char size[FMT_ULONG];
+  size[fmt_ulong(size,(unsigned int) databytes)] = 0;
+  smtp_greet("250-"); 
+  out("\r\n250-PIPELINING\r\n250-8BITMIME\r\n");
+  if (smtpauth == 1 || smtpauth == 11) out("250-AUTH LOGIN PLAIN\r\n");
+  if (smtpauth == 2 || smtpauth == 12) out("250-AUTH CRAM-MD5\r\n");
+  if (smtpauth == 3 || smtpauth == 13) out("250-AUTH LOGIN PLAIN CRAM-MD5\r\n");
+  out("250 SIZE "); out(size); out("\r\n");
   seenmail = 0; dohelo(arg);
 }
 void smtp_rset(arg) char *arg;
 {
-  seenmail = 0;
+  seenmail = 0; seenauth = 0; 
+  mailfrom.len = 0; rcptto.len = 0;
   out("250 flushed\r\n");
 }
 void smtp_mail(arg) char *arg;
 {
+  if (smtpauth)
+    if (smtpauth > 10 && !seenauth) { err_submission(); return; }
   if (!addrparse(arg)) { err_syntax(); return; }
+  flagsize = 0;
+  mailfrom_parms(arg);
+  if (flagsize) { err_size(); return; }
   flagbarf = bmfcheck();
   seenmail = 1;
   if (!stralloc_copys(&rcptto,"")) die_nomem();
@@ -378,7 +483,7 @@ void smtp_data(arg) char *arg; {
   qp = qmail_qp(&qqt);
   out("354 go ahead\r\n");
  
-  received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo);
+  received(&qqt,protocol,local,remoteip,remotehost,remoteinfo,fakehelo);
   blast(&hops);
   hops = (hops >= MAXHOPS);
   if (hops) qmail_fail(&qqt);
@@ -388,16 +493,228 @@ void smtp_data(arg) char *arg; {
   qqx = qmail_close(&qqt);
   if (!*qqx) { acceptmessage(qp); return; }
   if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; }
-  if (databytes) if (!bytestooverflow) { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); return; }
+  if (databytes) if (!bytestooverflow) { err_size(); return; }
   if (*qqx == 'D') out("554 "); else out("451 ");
   out(qqx + 1);
   out("\r\n");
 }
 
+/* this file is too long ----------------------------------------- SMTP AUTH */
+
+char unique[FMT_ULONG + FMT_ULONG + 3];
+static stralloc authin = {0};   /* input from SMTP client */
+static stralloc user = {0};     /* authorization user-id */
+static stralloc pass = {0};     /* plain passwd or digest */
+static stralloc resp = {0};     /* b64 response */
+static stralloc chal = {0};     /* plain challenge */
+static stralloc slop = {0};     /* b64 challenge */
+
+char **childargs;
+char ssauthbuf[512];
+substdio ssauth = SUBSTDIO_FDBUF(safewrite,3,ssauthbuf,sizeof(ssauthbuf));
+
+int authgetl(void) {
+  int i;
+
+  if (!stralloc_copys(&authin,"")) die_nomem();
+  for (;;) {
+    if (!stralloc_readyplus(&authin,1)) die_nomem(); /* XXX */
+    i = substdio_get(&ssin,authin.s + authin.len,1);
+    if (i != 1) die_read();
+    if (authin.s[authin.len] == '\n') break;
+    ++authin.len;
+  }
+
+  if (authin.len > 0) if (authin.s[authin.len - 1] == '\r') --authin.len;
+  authin.s[authin.len] = 0;
+  if (*authin.s == '*' && *(authin.s + 1) == 0) { return err_authabrt(); }
+  if (authin.len == 0) { return err_input(); }
+  return authin.len;
+}
+
+int authenticate(void)
+{
+  int child;
+  int wstat;
+  int pi[2];
+
+  if (!stralloc_0(&user)) die_nomem();
+  if (!stralloc_0(&pass)) die_nomem();
+  if (!stralloc_0(&chal)) die_nomem();
+
+  if (pipe(pi) == -1) return err_pipe();
+  switch(child = fork()) {
+    case -1:
+      return err_fork();
+    case 0:
+      close(pi[1]);
+      if(fd_copy(3,pi[0]) == -1) return err_pipe();
+      sig_pipedefault();
+        execvp(*childargs, childargs);
+      _exit(1);
+  }
+  close(pi[0]);
+
+  substdio_fdbuf(&ssauth,write,pi[1],ssauthbuf,sizeof ssauthbuf);
+  if (substdio_put(&ssauth,user.s,user.len) == -1) return err_write();
+  if (substdio_put(&ssauth,pass.s,pass.len) == -1) return err_write();
+  if (smtpauth == 2 || smtpauth == 3 || smtpauth == 12 || smtpauth == 13)  
+    if (substdio_put(&ssauth,chal.s,chal.len) == -1) return err_write();
+  if (substdio_flush(&ssauth) == -1) return err_write();
+
+  close(pi[1]);
+  if (!stralloc_copys(&chal,"")) die_nomem();
+  if (!stralloc_copys(&slop,"")) die_nomem();
+  byte_zero(ssauthbuf,sizeof ssauthbuf);
+  if (wait_pid(&wstat,child) == -1) return err_child();
+  if (wait_crashed(wstat)) return err_child();
+  if (wait_exitcode(wstat)) { sleep(AUTHSLEEP); return 1; } /* no */
+  return 0; /* yes */
+}
+
+int auth_login(arg) char *arg;
+{
+  int r;
+
+  if (*arg) {
+    if (r = b64decode(arg,str_len(arg),&user) == 1) return err_input();
+  }
+  else {
+    out("334 VXNlcm5hbWU6\r\n"); flush();       /* Username: */
+    if (authgetl() < 0) return -1;
+    if (r = b64decode(authin.s,authin.len,&user) == 1) return err_input();
+  }
+  if (r == -1) die_nomem();
+
+  out("334 UGFzc3dvcmQ6\r\n"); flush();         /* Password: */
+
+  if (authgetl() < 0) return -1;
+  if (r = b64decode(authin.s,authin.len,&pass) == 1) return err_input();
+  if (r == -1) die_nomem();
+
+  if (!user.len || !pass.len) return err_input();
+  return authenticate();
+}
+
+int auth_plain(arg) char *arg;
+{
+  int r, id = 0;
+
+  if (*arg) {
+    if (r = b64decode(arg,str_len(arg),&resp) == 1) return err_input();
+  }
+  else {
+    out("334 \r\n"); flush();
+    if (authgetl() < 0) return -1;
+    if (r = b64decode(authin.s,authin.len,&resp) == 1) return err_input();
+  }
+  if (r == -1 || !stralloc_0(&resp)) die_nomem();
+  while (resp.s[id]) id++;                       /* "authorize-id\0userid\0passwd\0" */
+
+  if (resp.len > id + 1)
+    if (!stralloc_copys(&user,resp.s + id + 1)) die_nomem();
+  if (resp.len > id + user.len + 2)
+    if (!stralloc_copys(&pass,resp.s + id + user.len + 2)) die_nomem();
+
+  if (!user.len || !pass.len) return err_input();
+  return authenticate();
+}
+
+int auth_cram()
+{
+  int i, r;
+  char *s;
+
+  s = unique;                                           /* generate challenge */
+  s += fmt_uint(s,getpid());
+  *s++ = '.';
+  s += fmt_ulong(s,(unsigned long) now());
+  *s++ = '@';
+  *s++ = 0;
+  if (!stralloc_copys(&chal,"<")) die_nomem();
+  if (!stralloc_cats(&chal,unique)) die_nomem();
+  if (!stralloc_cats(&chal,local)) die_nomem();
+  if (!stralloc_cats(&chal,">")) die_nomem();
+  if (b64encode(&chal,&slop) < 0) die_nomem();
+  if (!stralloc_0(&slop)) die_nomem();
+
+  out("334 ");                                          /* "334 base64_challenge \r\n" */
+  out(slop.s);
+  out("\r\n");
+  flush();
+
+  if (authgetl() < 0) return -1;                        /* got response */
+  if (r = b64decode(authin.s,authin.len,&resp) == 1) return err_input();
+  if (r == -1 || !stralloc_0(&resp)) die_nomem();
+
+  i = str_rchr(resp.s,' ');
+  s = resp.s + i;
+  while (*s == ' ') ++s;
+  resp.s[i] = 0;
+  if (!stralloc_copys(&user,resp.s)) die_nomem();       /* userid */
+  if (!stralloc_copys(&pass,s)) die_nomem();            /* digest */
+
+  if (!user.len || !pass.len) return err_input();
+  return authenticate();
+}
+
+struct authcmd {
+  char *text;
+  int (*fun)();
+} authcmds[] = {
+  { "login",auth_login }
+, { "plain",auth_plain }
+, { "cram-md5",auth_cram }
+, { 0,err_noauth }
+};
+
+void smtp_auth(arg)
+char *arg;
+{
+  int i;
+  char *cmd = arg;
+
+  if (!smtpauth || !*childargs) { out("503 auth not available (#5.3.3)\r\n"); return; }
+  if (seenauth) { err_authd(); return; }
+  if (seenmail) { err_authmail(); return; }
+
+  if (!stralloc_copys(&user,"")) die_nomem();
+  if (!stralloc_copys(&pass,"")) die_nomem();
+  if (!stralloc_copys(&resp,"")) die_nomem();
+  if (!stralloc_copys(&chal,"")) die_nomem();
+
+  i = str_chr(cmd,' ');
+  arg = cmd + i;
+  while (*arg == ' ') ++arg;
+  cmd[i] = 0;
+
+  for (i = 0;authcmds[i].text;++i)
+    if (case_equals(authcmds[i].text,cmd)) break;
+
+  switch (authcmds[i].fun(arg)) {
+    case 0:
+      seenauth = 1;
+      protocol = "ESMTPA";
+      relayclient = "";
+      remoteinfo = user.s;
+      if (!env_unset("TCPREMOTEINFO")) die_read();
+      if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem();
+      if (!env_put2("RELAYCLIENT",relayclient)) die_nomem();
+      out("235 ok, go ahead (#2.0.0)\r\n");
+      break;
+    case 1:
+      err_authfail(user.s,authcmds[i].text);
+  }
+}
+
+
+/* this file is too long --------------------------------------------- GO ON */
+
 struct commands smtpcommands[] = {
   { "rcpt", smtp_rcpt, 0 }
 , { "mail", smtp_mail, 0 }
 , { "data", smtp_data, flush }
+, { "auth", smtp_auth, flush }
 , { "quit", smtp_quit, flush }
 , { "helo", smtp_helo, flush }
 , { "ehlo", smtp_ehlo, flush }
@@ -408,8 +725,11 @@ struct commands smtpcommands[] = {
 , { 0, err_unimpl, flush }
 } ;
 
-void main()
+void main(argc,argv)
+int argc;
+char **argv;
 {
+  childargs = argv + 1;
   sig_pipeignore();
   if (chdir(auto_qmail) == -1) die_control();
   setup();