/*
 * Program:	Baby IMAP4rev1 server
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	5 November 1990
 * Last Edited:	11 December 1999
 *
 * Copyright 1999 by the University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both the
 * above copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the University of Washington not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  This software is made
 * available "as is", and
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
 * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
 * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

/* Primary I/O calls */

#define PBIN getchar		/* primary byte input */
				/* primary string input */
#define PSIN(s,n) fgets (s,n,stdin)
#define PBOUT(c) putchar (c)	/* primary byte output */
				/* primary string output */
#define PSOUT(s) fputs (s,stdout)
#define PFLUSH fflush (stdout)	/* flush primary output */
#define CRLF PSOUT ("\015\012")	/* primary output terpri */


/* Parameter files */

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
extern int errno;		/* just in case */
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <pwd.h>
#include <shadow.h>

/* Constants */

#define NIL 0			/* convenient name */
#define T 1			/* opposite of NIL */


/* Timeouts and timers */

#define MINUTES *60

#define LOGINTIMEOUT 3 MINUTES	/* not logged in autologout timer */
#define IDLETIMER 1 MINUTES	/* IDLE command poll timer */


/* Limits ** AUDIT ALL USERS OF THESES VALUES CAREFULLY!! ** */

#define LITSTKLEN 20		/* length of literal stack */
#define MAXCLIENTLIT 10000	/* maximum non-APPEND client literal size */
#define CMDLEN 8192		/* size of command buffers */
#define RESPBUFLEN 8192		/* size of SASL response buffer */
#define TMPLEN 1024		/* size of temp buffers */


/* Server states */

#define LOGIN 0
#define LOGGEDIN 1
#define LOGOUT 3


/* MD5 context */

#define MD5BLKLEN 64		/* MD5 block length */
#define MD5DIGLEN 16		/* MD5 digest length */

typedef struct {
  unsigned long chigh;		/* high 32bits of byte count */
  unsigned long clow;		/* low 32bits of byte count */
  unsigned long state[4];	/* state (ABCD) */
  unsigned char buf[MD5BLKLEN];	/* input buffer */
  unsigned char *ptr;		/* buffer position */
} MD5CONTEXT;

/* Function prototypes */

int main (int argc,char *argv[]);
void clkint (void);
void kodint (void);
void hupint (void);
void trmint (void);
void slurp (char *s,int n);
char inchar (void);
char *flush (void);
char *parse_astring (char **arg,unsigned long *i,char *del);
char *snarf (char **arg);
char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen);
char *mylocalhost ();
char *tcp_clienthost ();
char *tcp_serverhost ();
char *tcp_name (struct sockaddr_in *sin,long flag);
char *cpystr (const char *string);
char *ucase (char *s);
void *fs_get (size_t size);
void fs_give (void **block);
char *lcase (char *s);
void server_init (char *server,char *service,char *altservice,char *sasl,
		  void *clkint,void *kodint,void *hupint,void *trmint);
long server_login (char *user,char *pass,int argc,char *argv[]);
long authserver_login (char *user,int argc,char *argv[]);
long pw_login (struct passwd *pw,char *user,char *home,int argc,char *argv[]);
void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len);
unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len);
typedef char *(*authresponse_t) (void *challenge,unsigned long clen,
				 unsigned long *rlen);
char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[]);
char *auth_md5_pwd (char *user);
char *hmac_md5 (char *text,unsigned long tl,char *key,unsigned long kl);
void md5_init (MD5CONTEXT *ctx);
void md5_update (MD5CONTEXT *ctx,unsigned char *data,unsigned long len);
void md5_final (unsigned char *digest,MD5CONTEXT *ctx);
static void md5_transform (unsigned long *state,unsigned char *block);
static void md5_encode (unsigned char *dst,unsigned long *src,int len);
static void md5_decode (unsigned long *dst,unsigned char *src,int len);
struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[]);
long loginpw (struct passwd *pw,int argc,char *argv[]);

/* Global storage */

int state = LOGIN;		/* server state */
char cmdbuf[CMDLEN];		/* command buffer */
char *tag;			/* tag portion of command */
char *cmd;			/* command portion of command */
char *arg;			/* pointer to current argument of command */
char *response = NIL;		/* command response */
int logtry = 3;			/* maximum login trials */
int litsp = 0;			/* literal stack pointer */
char *litstk[LITSTKLEN];	/* stack to hold literals */


/* Response texts which appear in multiple places */

char *win = "%.80s OK %.80s completed\015\012";
char *winnocrlf = "%.80s OK %.80s accepted ";
char *lose = "%.80s NO %.80s failed\015\012";
char *misarg = "%.80s BAD Missing required argument to %.80s\015\012";
char *badarg = "%.80s BAD Argument given to %.80s when none expected\015\012";
char *argrdy = "+ Ready for argument\015\012";

/* Main program */

int main (int argc,char *argv[])
{
  char *s,*t,rsp[CMDLEN];
  struct stat sbuf;
  time_t autologouttime = 0;
  if (getuid ()) {		/* do real imapd if logged in */
    execl (REALIMAPD,"imapd",NIL);
    _exit (1);
  }
				/* initialize server */
  server_init (argv[0],"imap","imaps","imap",clkint,kodint,hupint,trmint);
  PSOUT ("* OK ");
  PSOUT (tcp_serverhost ());
  PSOUT (" IMAP4rev1 server ready\015\012");
  PFLUSH;			/* dump output buffer */
  autologouttime = time (0) + LOGINTIMEOUT;
  do switch (state) {		/* command processing loop */
  case LOGGEDIN:
    execl (REALIMAPD,"imapd",NIL);
    _exit (1);
  case LOGIN:			/* waiting to get logged in */
				/* took too long to login */
    if (autologouttime < time (0)) {
      PSOUT ("* BYE Autologout\015\012");
      syslog (LOG_INFO,"Autologout host=%.80s",tcp_clienthost ());
      PFLUSH;			/* make sure output blatted */
      state = LOGOUT;		/* sayonara */
    }
    slurp (cmdbuf,CMDLEN);	/* slurp command */
    while (litsp) fs_give ((void **) &litstk[--litsp]);
				/* find end of line */
    if (!strchr (cmdbuf,'\012')) {
      if (t = strchr (cmdbuf,' ')) *t = '\0';
      if ((t - cmdbuf) > 100) t = NIL;
      flush ();			/* flush excess */
      syslog (LOG_INFO,"Line too long before authentication host=%.80s",
	      tcp_clienthost ());
      sprintf (rsp,response,t ? cmdbuf : "*");
      PSOUT (rsp);
    }
    else if (!(tag = strtok (cmdbuf," \015\012"))) {
      syslog (LOG_INFO,"Null command before authentication host=%.80s",
	      tcp_clienthost ());
      PSOUT ("* BAD Null command\015\012");
    }
    else if (strlen (tag) > 50) PSOUT ("* BAD Excessively long tag\015\012");
    else if (!(cmd = strtok (NIL," \015\012"))) {
      syslog (LOG_INFO,"Missing command before authentication host=%.80s",
	      tcp_clienthost ());
      PSOUT (tag);
      PSOUT (" BAD Missing command\015\012");
    }

    else {			/* parse command */
      response = win;		/* set default response */
      ucase (cmd);		/* canonicalize command case */
				/* snarf argument */
      arg = strtok (NIL,"\015\012");
				/* these commands always valid */
      if (!strcmp (cmd,"NOOP")) {
	if (arg) response = badarg;
      }
      else if (!strcmp (cmd,"LOGOUT")) {
	if (arg) response = badarg;
	else {			/* time to say farewell */
	  server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
	  state = LOGOUT;
	  PSOUT ("* BYE ");
	  PSOUT (mylocalhost ());
	  PSOUT (" IMAP4rev1 server terminating connection\015\012");
	}
      }
      else if (!strcmp (cmd,"CAPABILITY")) {
	if (arg) response = badarg;
	else {
				/* change this when real imapd changes */
	  PSOUT ("* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS THREAD=ORDEREDSUBJECT");
	  if (!stat (MD5FILE,&sbuf)) PSOUT (" AUTH=CRAM-MD5");
          PSOUT (" AUTH=LOGIN");
	  CRLF;
	}
      }

				/* new style authentication */
      else if (!strcmp (cmd,"AUTHENTICATE")) {
	char *user;
				/* single argument */
	if (!(s = snarf (&arg))) response = misarg;
	else if (arg) response = badarg;
	else if (user = mail_auth (s,imap_responder,argc,argv)) {
	  syslog (LOG_INFO,"Authenticated user=%.80s host=%.80s",
		  user,tcp_clienthost ());
	  state = LOGGEDIN;
	  response = winnocrlf;
	}
	else {
	  response = lose;
	  syslog (LOG_INFO,"AUTHENTICATE %.80s failure host=%.80s",s,
		  tcp_clienthost ());
	}
      }
				/* plaintext login with password */
      else if (!strcmp (cmd,"LOGIN")) {
	char *user,*pass;
				/* two arguments */
	if (!((user = snarf (&arg)) && (pass = snarf (&arg))))
	  response = misarg;
	else if (arg) response = badarg;
				/* see if username and password are OK */
	else if (server_login (user,pass,argc,argv)) {
	  syslog (LOG_INFO,"Login user=%.80s host=%.80s",user,
		  tcp_clienthost ());
	  state = LOGGEDIN;
	  response = winnocrlf;
	}
	else response = lose;
      }
      else response = "%.80s BAD Command unrecognized: %.80s\015\012";
				/* build response */
      sprintf (rsp,response,tag,cmd);
      PSOUT (rsp);		/* output response */
    }
    PFLUSH;			/* make sure output blatted */
    break;
  default:
    response = "%.80s BAD Server in unknown state for %.80s command\015\012";
    break;
  } while (state != LOGOUT);	/* until logged out */
  syslog (LOG_INFO,"Logout host=%.80s",tcp_clienthost ());
  return 0;			/* all done */
}

/* Clock interrupt
 */

void clkint (void)
{
  alarm (0);			/* disable all interrupts */
  server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  PSOUT ("* BYE Autologout; idle for too long\015\012");
  syslog (LOG_INFO,"Autologout host=%.80s",tcp_clienthost ());
  PFLUSH;			/* make sure output blatted */
  _exit (1);			/* die die die */
}


/* Kiss Of Death interrupt
 */

void kodint (void)
{
  alarm (0);			/* disable all interrupts */
  server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  PSOUT ("* BYE Lost mailbox lock\015\012");
  PFLUSH;			/* make sure output blatted */
  syslog (LOG_INFO,"Killed (lost mailbox lock) host=%.80s",tcp_clienthost ());
  _exit (1);			/* die die die */
}

/* Hangup interrupt
 */

void hupint (void)
{
  alarm (0);			/* disable all interrupts */
  server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  syslog (LOG_INFO,"Hangup host=%.80s",tcp_clienthost ());
  _exit (1);			/* die die die */
}


/* Termination interrupt
 */

void trmint (void)
{
  alarm (0);			/* disable all interrupts */
  server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
  PSOUT ("* BYE Killed\015\012");
  syslog (LOG_INFO,"Killed host=%.80s",tcp_clienthost ());
  _exit (1);			/* die die die */
}

/* Slurp a command line
 * Accepts: buffer pointer
 *	    buffer size
 */

void slurp (char *s,int n)
{
  s[--n] = '\0';		/* last buffer character is guaranteed NUL */
  alarm (LOGINTIMEOUT);		/* get a command under timeout */
  errno = 0;			/* clear error */
  while (!PSIN (s,n)) {		/* read buffer */
    if (errno==EINTR) errno = 0;/* ignore if some interrupt */
    else {
      char *e = errno ? strerror (errno) : "command stream end of file";
      alarm (0);		/* disable all interrupts */
      server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
      syslog (LOG_INFO,"%.80s, while reading line host=%.80s",
	      e,tcp_clienthost ());
      _exit (1);		/* bye bye */
    }
  }
  alarm (0);			/* make sure timeout disabled */
}


/* Read a character
 * Returns: character
 */

char inchar (void)
{
  int c;
  while ((c = PBIN ()) == EOF) {
    if (errno==EINTR) errno = 0;/* ignore if some interrupt */
    else {
      char *e = errno ? strerror (errno) : "command stream end of file";
      alarm (0);		/* disable all interrupts */
      server_init (NIL,NIL,NIL,NIL,SIG_IGN,SIG_IGN,SIG_IGN,SIG_IGN);
      syslog (LOG_INFO,"%.80s, while reading char host=%.80s",
	      e,tcp_clienthost ());
      _exit (1);		/* bye bye */
    }
  }
  return c;
}


/* Flush until newline seen
 * Returns: NIL, always
 */

char *flush (void)
{
  while (inchar () != '\012');	/* slurp until we find newline */
  response = "%.80s BAD Command line too long\015\012";
  return NIL;
}

/* Parse an IMAP astring
 * Accepts: pointer to argument text pointer
 *	    pointer to returned size
 *	    pointer to returned delimiter
 * Returns: argument
 */

char *parse_astring (char **arg,unsigned long *size,char *del)
{
  unsigned long i;
  char c,*s,*t,*v;
  if (!*arg) return NIL;	/* better be an argument */
  switch (**arg) {		/* see what the argument is */
  default:			/* atom */
    for (s = t = *arg, i = 0;
	 (*t > ' ') && (*t < 0x7f) && (*t != '(') && (*t != ')') &&
	 (*t != '{') && (*t != '%') && (*t != '*') && (*t != '"') &&
	 (*t != '\\'); ++t,++i);
    if (*size = i) break;	/* got atom if non-empty */
  case ')': case '%': case '*': case '\\': case '\0': case ' ':
   return NIL;			/* empty atom is a bogon */
  case '"':			/* hunt for trailing quote */
    for (s = t = v = *arg + 1; (c = *t++) != '"'; *v++ = c) {
      if (c == '\\') c = *t++;	/* quote next character */
				/* else must be a CHAR */
      if (!c || (c & 0x80)) return NIL;
    }
    *v = '\0';			/* tie off string */
    *size = v - s;		/* return size */
    break;

  case '{':			/* literal string */
    s = *arg + 1;		/* get size */
    if (!isdigit (*s)) return NIL;
    if ((*size = i = strtoul (s,&t,10)) > MAXCLIENTLIT) {
      response = "%.80s BAD Absurdly long client literal\015\012";
      syslog (LOG_INFO,"Absurdly long client literal host=%.80s",
	      tcp_clienthost ());
      return NIL;
    }
				/* validate end of literal */
    if (!t || (*t != '}') || t[1]) return NIL;
    if (litsp >= LITSTKLEN) {	/* make sure don't overflow stack */
      response = "%.80s BAD Too many literals in command\015\012";
      return NIL;
    }
    PSOUT (argrdy);		/* tell client ready for argument */
    PFLUSH;			/* dump output buffer */
				/* get a literal buffer */
    s = v = litstk[litsp++] = (char *) fs_get (i+1);
    alarm (LOGINTIMEOUT);	/* get literal under timeout */
    while (i--) *v++ = inchar ();
    alarm (0);			/* stop timeout */
    *v++ = NIL;			/* make sure string tied off */
    				/* get new command tail */
    slurp ((*arg = v = t),CMDLEN - (t - cmdbuf));
    if (!strchr (t,'\012')) return flush ();
				/* reset strtok mechanism, tie off if done */
    if (!strtok (t,"\015\012")) *t = '\0';
    break;
  }
  if (*del = *t) {		/* have a delimiter? */
    *t++ = '\0';		/* yes, stomp on it */
    *arg = t;			/* update argument pointer */
  }
  else *arg = NIL;		/* no more arguments */
  return s;
}

/* Snarf a command argument (simple jacket into parse_astring())
 * Accepts: pointer to argument text pointer
 * Returns: argument
 */

char *snarf (char **arg)
{
  unsigned long i;
  char c;
  char *s = parse_astring (arg,&i,&c);
  return ((c == ' ') || !c) ? s : NIL;
}

/* IMAP4rev1 Authentication responder
 * Accepts: challenge
 *	    length of challenge
 *	    pointer to response length return location if non-NIL
 * Returns: response
 */

char *imap_responder (void *challenge,unsigned long clen,unsigned long *rlen)
{
  unsigned long i,j;
  unsigned char *t,resp[RESPBUFLEN];
  PSOUT ("+ ");
  for (t = rfc822_binary ((void *) challenge,clen,&i),j = 0; j < i; j++)
    if (t[j] > ' ') PBOUT (t[j]);
  fs_give ((void **) &t);
  CRLF;
  PFLUSH;			/* dump output buffer */
				/* slurp response buffer */
  slurp ((char *) resp,RESPBUFLEN);
  if (!(t = (unsigned char *) strchr ((char *) resp,'\012'))) return flush ();
  if (t[-1] == '\015') --t;	/* remove CR */
  *t = '\0';			/* tie off buffer */
  return (resp[0] != '*') ?
    (char *) rfc822_base64 (resp,t-resp,rlen ? rlen : &i) : NIL;
}

/* Return my local host name
 * Returns: my local host name
 */

static char *myLocalHost = NIL;

#define HOSTLEN 100

char *mylocalhost ()
{
  char tmp[HOSTLEN];
  struct hostent *host_name;
  if (!myLocalHost) {
    tmp[HOSTLEN-1] = '\0';
    gethostname (tmp,HOSTLEN-1); /* get local host name */
    myLocalHost = cpystr ((host_name = gethostbyname (tmp)) ?
			  host_name->h_name : tmp);
  }
  return myLocalHost;
}

/* TCP/IP get client host name (server calls only)
 * Returns: client host name
 */

static char *myClientHost = NIL;

char *tcp_clienthost ()
{
  if (!myClientHost) {
    struct sockaddr_in sin;
    int sinlen = sizeof (struct sockaddr_in);
				/* get stdin's peer name */
    myClientHost = getpeername (0,(struct sockaddr *) &sin,(void *) &sinlen) ?
      cpystr ("UNKNOWN") : tcp_name (&sin,T);
  }
  return myClientHost;
}


/* TCP/IP get server host name (server calls only)
 * Returns: server host name
 */

static char *myServerHost = NIL;

char *tcp_serverhost ()
{
  if (!myServerHost) {
    struct sockaddr_in sin;
    int sinlen = sizeof (struct sockaddr_in);
				/* get stdin's name */
    if (getsockname (0,(struct sockaddr *) &sin,(void *) &sinlen))
      myServerHost = cpystr (mylocalhost ());
    else {
      myServerHost = tcp_name (&sin,NIL);
    }
  }
  return myServerHost;
}


/* TCP/IP return name from socket
 * Accepts: socket
 *	    verbose flag
 * Returns: cpystr name
 */

char *tcp_name (struct sockaddr_in *sin,long flag)
{
  char *s,tmp[TMPLEN];
  struct hostent *he;
				/* translate address to name */
  if (!(he = gethostbyaddr ((char *) &sin->sin_addr,
			    sizeof (struct in_addr),sin->sin_family)))
    sprintf (s = tmp,"[%.80s]",inet_ntoa (sin->sin_addr));
  else if (flag) sprintf (s = tmp,"%.80s [%.80s]",he->h_name,
			  inet_ntoa (sin->sin_addr));
  else s = he->h_name;
  return cpystr (s);
}

/* Copy string to free storage
 * Accepts: source string
 * Returns: free storage copy of string
 */

char *cpystr (const char *string)
{
  return string ? strcpy ((char *) fs_get (1 + strlen (string)),string) : NIL;
}


/* Convert string to all uppercase
 * Accepts: string pointer
 * Returns: string pointer
 */

char *ucase (char *s)
{
  char *t;
				/* if lowercase covert to upper */
  for (t = s; *t; t++) if (!(*t & 0x80) && islower (*t)) *t = toupper (*t);
  return s;			/* return string */
}


/* Get a block of free storage
 * Accepts: size of desired block
 * Returns: free storage block
 */

void *fs_get (size_t size)
{
  void *block = malloc (size ? size : (size_t) 1);
  if (!block) {
    syslog (LOG_CRIT,"Out of memory");
    _exit (1);
  }
  return (block);
}


/* Return a block of free storage
 * Accepts: ** pointer to free storage block
 */

void fs_give (void **block)
{
  free (*block);
  *block = NIL;
}

/* Convert string to all lowercase
 * Accepts: string pointer
 * Returns: string pointer
 */

char *lcase (char *s)
{
  char *t;
				/* if uppercase covert to lower */
  for (t = s; *t; t++) if (!(*t & 0x80) && isupper (*t)) *t = tolower (*t);
  return s;			/* return string */
}

void server_init (char *server,char *service,char *altservice,char *sasl,
		  void *clkint,void *kodint,void *hupint,void *trmint)
{
}

/* Server log in
 * Accepts: user name string
 *	    password string
 *	    argument count
 *	    argument vector
 * Returns: T if password validated, NIL otherwise
 */

#define MAXUSER 64

long server_login (char *user,char *pwd,int argc,char *argv[])
{
  char *s,usr[MAXUSER+1];
  struct passwd *pw;
  struct stat sbuf;
  if (strlen (user) > MAXUSER)	/* cretin trying a buffer overflow */
    syslog (LOG_ALERT|LOG_AUTH,"System break-in attempt, host=%.80s",
	    tcp_clienthost ());
				/* validate with case-independence */
  else if ((logtry > 0) && ((pw = getpwnam (strcpy (usr,user))) ||
			    (pw = getpwnam (lcase (usr))))) {
    if (!stat (MD5FILE,&sbuf)) {/* if using CRAM-MD5 authentication */
      char *p = auth_md5_pwd (pw->pw_name);
      if (p) {			/* verify password */
	if (strcmp (p,pwd) && strcmp (p,pwd+1)) pw = NIL;
	memset (p,0,strlen (p));/* erase sensitive information */
	fs_give ((void **) &p);	/* flush erased password */
	if (pw) return pw_login (pw,pw->pw_name,pw->pw_dir,argc,argv);
      }
      else syslog (LOG_ERR|LOG_AUTH,
		   "Login failed: %s has no CRAM-MD5 password, host=%.80s",
		   pw->pw_name,tcp_clienthost ());
    }
				/* ordinary password authentication */
    else if ((pw = checkpw (pw,pwd,argc,argv)) ||
	     ((*pwd == ' ') && (pw = checkpw (getpwnam(usr),pwd+1,argc,argv))))
      return pw_login (pw,pw->pw_name,pw->pw_dir,argc,argv);
  }
  s = (logtry-- > 0) ? "Login failure" : "Excessive login attempts";
				/* note the failure in the syslog */
  syslog (LOG_INFO,"%s user=%.80s host=%.80s",s,user,tcp_clienthost ());
  sleep (3);			/* slow down possible cracker */
  return NIL;
}

/* Authenticated server log in
 * Accepts: user name string
 *	    argument count
 *	    argument vector
 * Returns: T if password validated, NIL otherwise
 */

long authserver_login (char *user,int argc,char *argv[])
{
  struct passwd *pw = getpwnam (user);
  return pw ? pw_login (pw,pw->pw_name,pw->pw_dir,argc,argv) : NIL;
}


/* Finish log in and environment initialization
 * Accepts: passwd struct for loginpw()
 *	    user name
 *	    home directory
 *	    argument count
 *	    argument vector
 * Returns: T if successful, NIL if error
 */

long pw_login (struct passwd *pw,char *user,char *home,int argc,char *argv[])
{
  long ret = NIL;
  char *h = cpystr (home);	/* loginpw may smash it */
  if (ret = (pw->pw_uid && loginpw (pw,argc,argv))) chdir (h);
  fs_give ((void **) &h);	/* flush temporary home directory */
  return ret;			/* return status */
}

/* Convert BASE64 contents to binary
 * Accepts: source
 *	    length of source
 *	    pointer to return destination length
 * Returns: destination as binary or NIL if error
 */

void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
{
  char c;
  void *ret = fs_get ((size_t) (*len = 4 + ((srcl * 3) / 4)));
  char *d = (char *) ret;
  int e = 0;
  memset (ret,0,(size_t) *len);	/* initialize block */
  *len = 0;			/* in case we return an error */
  while (srcl--) {		/* until run out of characters */
    c = *src++;			/* simple-minded decode */
    if (isupper (c)) c -= 'A';
    else if (islower (c)) c -= 'a' - 26;
    else if (isdigit (c)) c -= '0' - 52;
    else if (c == '+') c = 62;
    else if (c == '/') c = 63;
    else if (c == '=') {	/* padding */
      switch (e++) {		/* check quantum position */
      case 3:
	e = 0;			/* restart quantum */
	break;
      case 2:
	if (*src == '=') break;
      default:			/* impossible quantum position */
	fs_give (&ret);
	return NIL;
      }
      continue;
    }
    else continue;		/* junk character */
    switch (e++) {		/* install based on quantum position */
    case 0:
      *d = c << 2;		/* byte 1: high 6 bits */
      break;
    case 1:
      *d++ |= c >> 4;		/* byte 1: low 2 bits */
      *d = c << 4;		/* byte 2: high 4 bits */
      break;
    case 2:
      *d++ |= c >> 2;		/* byte 2: low 4 bits */
      *d = c << 6;		/* byte 3: high 2 bits */
      break;
    case 3:
      *d++ |= c;		/* byte 3: low 6 bits */
      e = 0;			/* reinitialize mechanism */
      break;
    }
  }
  *len = d - (char *) ret;	/* calculate data length */
  return ret;			/* return the string */
}

/* Convert binary contents to BASE64
 * Accepts: source
 *	    length of source
 *	    pointer to return destination length
 * Returns: destination as BASE64
 */

unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
{
  unsigned char *ret,*d;
  unsigned char *s = (unsigned char *) src;
  char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  unsigned long i = ((srcl + 2) / 3) * 4;
  *len = i += 2 * ((i / 60) + 1);
  d = ret = (unsigned char *) fs_get ((size_t) ++i);
  for (i = 0; srcl; s += 3) {	/* process tuplets */
    *d++ = v[s[0] >> 2];	/* byte 1: high 6 bits (1) */
				/* byte 2: low 2 bits (1), high 4 bits (2) */
    *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
				/* byte 3: low 4 bits (2), high 2 bits (3) */
    *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
				/* byte 4: low 6 bits (3) */
    *d++ = srcl ? v[s[2] & 0x3f] : '=';
    if (srcl) srcl--;		/* count third character if processed */
    if ((++i) == 15) {		/* output 60 characters? */
      i = 0;			/* restart line break count, insert CRLF */
      *d++ = '\015'; *d++ = '\012';
    }
  }
  *d++ = '\015'; *d++ = '\012';	/* insert final CRLF */
  *d = '\0';			/* tie off string */
				/* bug trap */
  if (((unsigned long) (d - ret)) != *len) abort ();
  return ret;			/* return the resulting string */
}

/* Authenticate access
 * Accepts: mechanism name
 *	    responder function
 *	    argument count
 *	    argument vector
 * Returns: logged-in username if success, else NIL
 */

#define PWD_USER "User Name"
#define PWD_PWD "Password"

char *mail_auth (char *mechanism,authresponse_t resp,int argc,char *argv[])
{
  char *user,*pass;
  char *ret = NIL;
  struct stat sbuf;
  if (!strcmp (mechanism,"CRAM-MD5") && !stat (MD5FILE,&sbuf)) {
    char *p,*u,chal[TMPLEN];
    unsigned long cl,pl;
				/* generate challenge */
    sprintf (chal,"<%lu.%lu@%.80s>",(unsigned long) getpid (),
	     (unsigned long) time (0),mylocalhost ());
				/* send challenge, get user and hash */
    if (user = (*resp) (chal,cl = strlen (chal),NIL)) {
				/* got user, locate hash */
      if (pass = strrchr (user,' ')) {
	*pass++ = '\0';		/* tie off user */
	if ((p = auth_md5_pwd (user)) || (p = auth_md5_pwd (lcase (user)))) {
				/* quickly verify password */
	  u = strcmp (pass,hmac_md5 (chal,cl,p,pl = strlen (p))) ? NIL : user;
	  memset (p,0,pl);	/* erase sensitive information */
	  fs_give ((void **)&p);/* flush erased password */
				/* now log in for real */
	  if (u && authserver_login (u,argc,argv)) ret = cpystr (user);
	}
      }
      fs_give ((void **) &user);
      if (!ret) sleep (3);	/* slow down poassible cracker */
    }
  }
  else if (!strcmp (mechanism,"LOGIN") &&
	   (user = (*resp) (PWD_USER,sizeof (PWD_USER),NIL))) {
    if (pass = (*resp) (PWD_PWD,sizeof (PWD_PWD),NIL)) {
      if (server_login (user,pass,argc,argv)) ret = cpystr (user);
      fs_give ((void **) &pass);
    }
    fs_give ((void **) &user);
  }
  return ret;			/* return authentication status */
}

/* Return MD5 password for user
 * Accepts: user name
 * Returns: plaintext password if success, else NIL
 *
 * This is much hairier than it needs to be due to the necessary of zapping
 * the password data.
 */

char *auth_md5_pwd (char *user)
{
  struct stat sbuf;
  int fd = open (MD5FILE,O_RDONLY,NIL);
  char *s,*t,*buf;
  char *ret = NIL;
  if (fd >= 0) {		/* found the file? */
    if (!fstat (fd,&sbuf)) {	/* yes, slurp it into memory */
      read (fd,buf = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
      for (s = strtok (buf,"\015\012"); s;
	   s = ret ? NIL : strtok (NIL,"\015\012"))
				/* must be valid entry line */
	if ((*s != '#') && (t = strchr (s,'\t'))) {
	  *t++ = '\0';		/* found tab, tie off user, point to pwd */
	  if (*s && *t && !strcmp (s,user)) ret = cpystr (t);
	}
				/* erase sensitive information from buffer */
      memset (buf,0,sbuf.st_size + 1);
      fs_give ((void **) &buf);	/* flush the buffer */
    }
    close (fd);			/* don't need file any longer */
  }
  return ret;			/* return password */
}

/*
 * RFC 2104 HMAC hashing
 * Accepts: text to hash
 *	    text length
 *	    key
 *	    key length
 * Returns: hash as text, always
 */

char *hmac_md5 (char *text,unsigned long tl,char *key,unsigned long kl)
{
  int i,j;
  static char hshbuf[2*MD5DIGLEN + 1];
  char *s;
  MD5CONTEXT ctx;
  char *hex = "0123456789abcdef";
  unsigned char digest[MD5DIGLEN],k_ipad[MD5BLKLEN+1],k_opad[MD5BLKLEN+1];
  if (kl > MD5BLKLEN) {		/* key longer than pad length? */
    md5_init (&ctx);		/* yes, set key as MD5(key) */
    md5_update (&ctx,(unsigned char *) key,kl);
    md5_final (digest,&ctx);
    key = (char *) digest;
    kl = MD5DIGLEN;
  }
  memcpy (k_ipad,key,kl);	/* store key in pads */
  memset (k_ipad+kl,0,(MD5BLKLEN+1)-kl);
  memcpy (k_opad,k_ipad,MD5BLKLEN+1);
				/* XOR key with ipad and opad values */
  for (i = 0; i < MD5BLKLEN; i++) {/* for each byte of pad */
    k_ipad[i] ^= 0x36;		/* XOR key with ipad */
    k_opad[i] ^= 0x5c;		/*  and opad values */
  }
  md5_init (&ctx);		/* inner MD5: hash ipad and text */
  md5_update (&ctx,k_ipad,MD5BLKLEN);
  md5_update (&ctx,(unsigned char *) text,tl);
  md5_final (digest,&ctx);
  md5_init (&ctx);		/* outer MD5: hash opad and inner results */
  md5_update (&ctx,k_opad,MD5BLKLEN);
  md5_update (&ctx,digest,MD5DIGLEN);
  md5_final (digest,&ctx);
				/* convert to printable hex */
  for (i = 0, s = hshbuf; i < MD5DIGLEN; i++) {
    *s++ = hex[(j = digest[i]) >> 4];
    *s++ = hex[j & 0xf];
  }
  *s = '\0';			/* tie off hash text */
  return hshbuf;
}

/* Everything after this point is derived from the RSA Data Security, Inc.
 * MD5 Message-Digest Algorithm
 */

#define RND1(a,b,c,d,x,s,ac) \
 a += ((b & c) | (d & ~b)) + x + (unsigned long) ac; \
 a &= 0xffffffff; \
 a = b + ((a << s) | (a >> (32 - s)));

#define RND2(a,b,c,d,x,s,ac) \
 a += ((b & d) | (c & ~d)) + x + (unsigned long) ac; \
 a &= 0xffffffff; \
 a = b + ((a << s) | (a >> (32 - s)));

#define RND3(a,b,c,d,x,s,ac) \
 a += (b ^ c ^ d) + x + (unsigned long) ac; \
 a &= 0xffffffff; \
 a = b + ((a << s) | (a >> (32 - s)));

#define RND4(a,b,c,d,x,s,ac) \
 a += (c ^ (b | ~d)) + x + (unsigned long) ac; \
 a &= 0xffffffff; \
 a = b + ((a << s) | (a >> (32 - s)));

/* Initialize MD5 context
 * Accepts: context to initialize
 */

void md5_init (MD5CONTEXT *ctx)
{
  ctx->clow = ctx->chigh = 0;	/* initialize byte count to zero */
				/* initialization constants */
  ctx->state[0] = 0x67452301; ctx->state[1] = 0xefcdab89;
  ctx->state[2] = 0x98badcfe; ctx->state[3] = 0x10325476;
  ctx->ptr = ctx->buf;		/* reset buffer pointer */
}


/* MD5 add data to context
 * Accepts: context
 *	    input data
 *	    length of data
 */

void md5_update (MD5CONTEXT *ctx,unsigned char *data,unsigned long len)
{
  unsigned long i = (ctx->buf + MD5BLKLEN) - ctx->ptr;
				/* update double precision number of bytes */
  if ((ctx->clow += len) < len) ctx->chigh++;
  while (i <= len) {		/* copy/transform data, 64 bytes at a time */
    memcpy (ctx->ptr,data,i);	/* fill up 64 byte chunk */
    md5_transform (ctx->state,ctx->ptr = ctx->buf);
    data += i,len -= i,i = MD5BLKLEN;
  }
  memcpy (ctx->ptr,data,len);	/* copy final bit of data in buffer */
  ctx->ptr += len;		/* update buffer pointer */
}

/* MD5 Finalization
 * Accepts: destination digest
 *	    context
 */

void md5_final (unsigned char *digest,MD5CONTEXT *ctx)
{
  unsigned long i,bits[2];
  bits[0] = ctx->clow << 3;	/* calculate length in bits (before padding) */
  bits[1] = (ctx->chigh << 3) + (ctx->clow >> 29);
  *ctx->ptr++ = 0x80;		/* padding byte */
  if ((i = (ctx->buf + MD5BLKLEN) - ctx->ptr) < 8) {
    memset (ctx->ptr,0,i);	/* pad out buffer with zeros */
    md5_transform (ctx->state,ctx->buf);
				/* pad out with zeros, leaving 8 bytes */
    memset (ctx->buf,0,MD5BLKLEN - 8);
    ctx->ptr = ctx->buf + MD5BLKLEN - 8;
  }
  else if (i -= 8) {		/* need to pad this buffer? */
    memset (ctx->ptr,0,i);	/* yes, pad out with zeros, leaving 8 bytes */
    ctx->ptr += i;
  }
  md5_encode (ctx->ptr,bits,2);	/* make LSB-first length */
  md5_transform (ctx->state,ctx->buf);
				/* store state in digest */
  md5_encode (digest,ctx->state,4);
				/* erase context */
  memset (ctx,0,sizeof (MD5CONTEXT));
}

/* MD5 basic transformation
 * Accepts: state vector
 *	    current 64-byte block
 */

static void md5_transform (unsigned long *state,unsigned char *block)
{
  unsigned long a = state[0],b = state[1],c = state[2],d = state[3],x[16];
  md5_decode (x,block,16);	/* decode into 16 longs */
				/* round 1 */
  RND1 (a,b,c,d,x[ 0], 7,0xd76aa478); RND1 (d,a,b,c,x[ 1],12,0xe8c7b756);
  RND1 (c,d,a,b,x[ 2],17,0x242070db); RND1 (b,c,d,a,x[ 3],22,0xc1bdceee);
  RND1 (a,b,c,d,x[ 4], 7,0xf57c0faf); RND1 (d,a,b,c,x[ 5],12,0x4787c62a);
  RND1 (c,d,a,b,x[ 6],17,0xa8304613); RND1 (b,c,d,a,x[ 7],22,0xfd469501);
  RND1 (a,b,c,d,x[ 8], 7,0x698098d8); RND1 (d,a,b,c,x[ 9],12,0x8b44f7af);
  RND1 (c,d,a,b,x[10],17,0xffff5bb1); RND1 (b,c,d,a,x[11],22,0x895cd7be);
  RND1 (a,b,c,d,x[12], 7,0x6b901122); RND1 (d,a,b,c,x[13],12,0xfd987193);
  RND1 (c,d,a,b,x[14],17,0xa679438e); RND1 (b,c,d,a,x[15],22,0x49b40821);
				/* round 2 */
  RND2 (a,b,c,d,x[ 1], 5,0xf61e2562); RND2 (d,a,b,c,x[ 6], 9,0xc040b340);
  RND2 (c,d,a,b,x[11],14,0x265e5a51); RND2 (b,c,d,a,x[ 0],20,0xe9b6c7aa);
  RND2 (a,b,c,d,x[ 5], 5,0xd62f105d); RND2 (d,a,b,c,x[10], 9, 0x2441453);
  RND2 (c,d,a,b,x[15],14,0xd8a1e681); RND2 (b,c,d,a,x[ 4],20,0xe7d3fbc8);
  RND2 (a,b,c,d,x[ 9], 5,0x21e1cde6); RND2 (d,a,b,c,x[14], 9,0xc33707d6);
  RND2 (c,d,a,b,x[ 3],14,0xf4d50d87); RND2 (b,c,d,a,x[ 8],20,0x455a14ed);
  RND2 (a,b,c,d,x[13], 5,0xa9e3e905); RND2 (d,a,b,c,x[ 2], 9,0xfcefa3f8);
  RND2 (c,d,a,b,x[ 7],14,0x676f02d9); RND2 (b,c,d,a,x[12],20,0x8d2a4c8a);
				/* round 3 */
  RND3 (a,b,c,d,x[ 5], 4,0xfffa3942); RND3 (d,a,b,c,x[ 8],11,0x8771f681);
  RND3 (c,d,a,b,x[11],16,0x6d9d6122); RND3 (b,c,d,a,x[14],23,0xfde5380c);
  RND3 (a,b,c,d,x[ 1], 4,0xa4beea44); RND3 (d,a,b,c,x[ 4],11,0x4bdecfa9);
  RND3 (c,d,a,b,x[ 7],16,0xf6bb4b60); RND3 (b,c,d,a,x[10],23,0xbebfbc70);
  RND3 (a,b,c,d,x[13], 4,0x289b7ec6); RND3 (d,a,b,c,x[ 0],11,0xeaa127fa);
  RND3 (c,d,a,b,x[ 3],16,0xd4ef3085); RND3 (b,c,d,a,x[ 6],23, 0x4881d05);
  RND3 (a,b,c,d,x[ 9], 4,0xd9d4d039); RND3 (d,a,b,c,x[12],11,0xe6db99e5);
  RND3 (c,d,a,b,x[15],16,0x1fa27cf8); RND3 (b,c,d,a,x[ 2],23,0xc4ac5665);
				/* round 4 */
  RND4 (a,b,c,d,x[ 0], 6,0xf4292244); RND4 (d,a,b,c,x[ 7],10,0x432aff97);
  RND4 (c,d,a,b,x[14],15,0xab9423a7); RND4 (b,c,d,a,x[ 5],21,0xfc93a039);
  RND4 (a,b,c,d,x[12], 6,0x655b59c3); RND4 (d,a,b,c,x[ 3],10,0x8f0ccc92);
  RND4 (c,d,a,b,x[10],15,0xffeff47d); RND4 (b,c,d,a,x[ 1],21,0x85845dd1);
  RND4 (a,b,c,d,x[ 8], 6,0x6fa87e4f); RND4 (d,a,b,c,x[15],10,0xfe2ce6e0);
  RND4 (c,d,a,b,x[ 6],15,0xa3014314); RND4 (b,c,d,a,x[13],21,0x4e0811a1);
  RND4 (a,b,c,d,x[ 4], 6,0xf7537e82); RND4 (d,a,b,c,x[11],10,0xbd3af235);
  RND4 (c,d,a,b,x[ 2],15,0x2ad7d2bb); RND4 (b,c,d,a,x[ 9],21,0xeb86d391);
				/* update state */
  state[0] += a; state[1] += b; state[2] += c; state[3] += d;
  memset (x,0,sizeof (x));	/* erase sensitive data */
}

/* MD5 encode unsigned long into LSB-first bytes
 * Accepts: destination pointer
 *	    source
 *	    length of source
 */ 

static void md5_encode (unsigned char *dst,unsigned long *src,int len)
{
  int i;
  for (i = 0; i < len; i++) {
    *dst++ = (unsigned char) (src[i] & 0xff);
    *dst++ = (unsigned char) ((src[i] >> 8) & 0xff);
    *dst++ = (unsigned char) ((src[i] >> 16) & 0xff);
    *dst++ = (unsigned char) ((src[i] >> 24) & 0xff);
  }
}


/* MD5 decode LSB-first bytes into unsigned long
 * Accepts: destination pointer
 *	    source
 *	    length of destination
 */ 

static void md5_decode (unsigned long *dst,unsigned char *src,int len)
{
  int i, j;
  for (i = 0, j = 0; i < len; i++, j += 4)
    dst[i] = ((unsigned long) src[j]) | (((unsigned long) src[j+1]) << 8) |
      (((unsigned long) src[j+2]) << 16) | (((unsigned long) src[j+3]) << 24);
}

/* POSIX-style check password
 * Accepts: login passwd struct
 *	    password string
 *	    argument count
 *	    argument vector
 * Returns: passwd struct if password validated, NIL otherwise
 */

struct passwd *checkpw (struct passwd *pw,char *pass,int argc,char *argv[])
{
  char tmp[TMPLEN];
  struct spwd *sp = NIL;
  time_t left;
  time_t now = time (0);
  struct tm *t = gmtime (&now);
  int zone = t->tm_hour * 60 + t->tm_min;
  int julian = t->tm_yday;
  t = localtime (&now);		/* get local time now */
				/* minus UTC minutes since midnight */
  zone = t->tm_hour * 60 + t->tm_min - zone;
  /* julian can be one of:
   *  36x  local time is December 31, UTC is January 1, offset -24 hours
   *    1  local time is 1 day ahead of UTC, offset +24 hours
   *    0  local time is same day as UTC, no offset
   *   -1  local time is 1 day behind UTC, offset -24 hours
   * -36x  local time is January 1, UTC is December 31, offset +24 hours
   */
  if (julian = t->tm_yday -julian)
    zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
				/* days since 1/1/1970 local time */
  now = ((now /60) + zone) / (60*24);
				/* non-shadow authentication */
  if (!pw->pw_passwd || !pw->pw_passwd[0] ||
      strcmp (pw->pw_passwd,(char *) crypt (pass,pw->pw_passwd))) {
    /* As far as I've been able to determine, here is how the expiration
     * fields in the shadow authentication data work:
     *  lstchg	last password change date if non-negative.  If zero, the
     *		user can not log in without changing password.
     *  max	number of days a password is valid if positive
     *  warn	number of days of password expiration warning
     *  expire	date account expires if positive
     * The expiration day is the *last* day that the password or account
     * is valid.
     */
				/* shadow authentication */
    if ((sp = getspnam (pw->pw_name)) && sp->sp_lstchg &&
	((sp->sp_lstchg < 0) || (sp->sp_max <= 0) ||
	 ((sp->sp_lstchg + sp->sp_max) >= now)) &&
	((sp->sp_expire <= 0) || (sp->sp_expire >= now)) &&
	!strcmp (sp->sp_pwdp,(char *) crypt (pass,sp->sp_pwdp))) {
      if ((sp->sp_lstchg > 0) && (sp->sp_max > 0) &&
	  ((left = (sp->sp_lstchg + sp->sp_max) - now) <= sp->sp_warn)) {
	if (left) {
	  sprintf (tmp,"* OK [ALERT] Password expires in %ld day(s)",
		   (long) left);
	  PSOUT (tmp);
	}
	else PSOUT ("* NO [ALERT] Password expires today!");
      }
      if ((sp->sp_expire > 0) && ((left = sp->sp_expire - now) < 28)) {
	if (left) {
	  sprintf (tmp,"* OK [ALERT] Account expires in %ld day(s)",
		   (long) left);
	  PSOUT (tmp);
	}
	else PSOUT ("* NO [ALERT] Account expires today!");
      }
      endspent ();		/* don't need shadow password data any more */
    }
    else pw = NIL;		/* password failed */
  }
  return pw;
}

/* Standard log in
 * Accepts: login passwd struct
 *	    argument count
 *	    argument vector
 * Returns: T if success, NIL otherwise
 */

long loginpw (struct passwd *pw,int argc,char *argv[])
{
  uid_t uid = pw->pw_uid;
  char *name = cpystr (pw->pw_name);
  long ret = !(setgid (pw->pw_gid) || initgroups (name,pw->pw_gid) ||
	       setuid (uid));
  fs_give ((void **) &name);
  return ret;
}
