/* $Id: smtp_auth.cpp,v 1.11 2003/04/21 01:08:15 duncan_haldane Exp $ */
/*******************************************************************************
 *   This program is part of a library used by the Archimedes email client     * 
 *                                                                             *
 *   Copyright : (C) 2001 Duncan Haldane,<duncan_haldane@users.sourceforge.net>*
 *               (C) 2001 by the Archimedes Project                            *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   This program is free software; you can redistribute it and/or modify      *
 *   it under the terms of the GNU Library General Public License as published *
 *   by the Free Software Foundation; either version 2 of the License, or      *
 *   (at your option) any later version.                                       *
 *                                                                             *
 *   This program is distributed in the hope that it will be useful,           *
 *   but WITHOUT ANY WARRANTY, without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 *   GNU Library General Public License for more details.                      *
 *                                                                             *
 *   You should have received a copy of the GNU Library General Public License *
 *   along with this program; if not, write to the Free Software               *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307, USA.  *
 *                                                                             *
 ******************************************************************************/

#include <fmail.h>
#define _SMTP_AUTH_C_
#include <smtp_auth.h>
#include <md5.h>

char smtp_password[MAX_AUTH_WORD_LEN+1];
char smtp_username[MAX_AUTH_WORD_LEN+1];


 void erase_password( char *, int );

 void erase_password( char * password , int len )
{
  int i;
  for ( i = 0 ; i < len ; i++ )
	*(password + i) = '\0';
  return;
}

int smtp_authenticate( char * smtp_hostname, char * server_auth_list )
{  
  /*
	RFC2554 "SMTP Service Extension for Authentication" dialog.
	Used if the initial EHLO response from server includes the
	AUTH EHLO keyword providing a list of server-supported
	AUTH mechanisms.   Used at the beginning of the SMTP
	negotiation; could also be invoked if the Server isses a
	"530 Authentication required" response later during the
	session.
  */
  
  int init_result, dialog_result,reset_result,bufsize;
  int error = 0, count = 0, prev_count = 0;
  int max_tries = MAX_AUTH_ATTEMPTS;
  char response_buf[ AUTH_RESPONSE_BUFSIZE ] ;
  char * client_to_server = NULL; 
  char client_auth_list[CLIENT_AUTH_LIST_LEN] ;
  char plain[6] = "PLAIN";
  char auth_mech[21];
  char * auth_list = NULL;
  char * server_response = NULL;
  char * * pointer_to_response = &server_response; 
  char reset[2] = "*";

  smtp_username[0] = '\0';
  smtp_password[0] = '\0';

  get_smtp_username (smtp_username , sizeof(smtp_username) - 1 );
  if ( strlen( smtp_username) ) 
	   get_smtp_password( smtp_password, sizeof(smtp_password) - 1);

  if ( !strlen( smtp_password ) )
	   ask_smtp_password ( smtp_hostname,  smtp_username, 
			   smtp_password, sizeof(smtp_password) - 1 ); 


  
  /* 
	 client_auth_list should be an ordered list of all the
	 mechanisms supported by the  client.  First are those
	 advertised by the server, in the order that the
	 server specified them, then all those not advertised by
	 the server, in the order that the client specified them.
  */
  get_client_auth_list(client_auth_list,server_auth_list);
  
  auth_list = client_auth_list;
  
  /* loop over possible auth mechanisms */
  while ( auth_list  && count < max_tries ) 
	{
	  if ( count == prev_count)
	auth_list = get_next_item (auth_list, auth_mech, 20) ;
	  prev_count = count;
	  /* 
	 initiate auth_mech dialog: call smtp_auth_response
	 with a NULL server challenge: "-1" is returned
	 if  auth_mech is not supported by this client 
	  */
	  
	  response_buf[0] = '\0';
	  bufsize = sizeof(response_buf);
	  client_to_server = response_buf ;
	  switch( error = smtp_auth_response(NULL,auth_mech,
					 client_to_server,bufsize)) {
	case 0:
	  
	  init_result=smtp_auth_dialog(client_to_server, pointer_to_response);
	  switch (init_result)
		{
		case  235:   /* success */
		  erase_password(smtp_password,sizeof(smtp_password));
		  return 0;
		  break;
		  
		case 334:   /* challenge-response authentication dialog */
		  dialog_result = 334;
		  /* keep talking till server response != 334 */
		  while (dialog_result == 334)
		{
		  response_buf[0] = '\0';
		  bufsize = sizeof(response_buf);
		  client_to_server = response_buf ;
		  switch( smtp_auth_response(server_response,auth_mech,
						 client_to_server,bufsize))
			{
			case 0:
			  dialog_result = smtp_auth_dialog(client_to_server,
							   pointer_to_response);
			  break;
			case -1:  /*   mechanism not supported by client */
			  erase_password(smtp_password,sizeof(smtp_password));
			  return -1;   /* this cannot happen here !*/
			  break;
			case -2:  /*  error in generating responsem */
			  reset_result = smtp_auth_dialog( (char * ) reset,
							   pointer_to_response);
			  if (reset_result != 501 ) {
			erase_password(smtp_password,sizeof(smtp_password));
			return -1;
			  }  
			  dialog_result = -2;
			  break;
			case -3:  /* response buffer  too small   */
			  reset_result = smtp_auth_dialog( (char * ) reset,
							   pointer_to_response);
			  if (reset_result != 501 ) {
			erase_password(smtp_password,sizeof(smtp_password));
			return -1;
			  }
			  dialog_result = -3;
			  break;
			default :     
			  erase_password(smtp_password,sizeof(smtp_password));
			  return -1 ;
			  break;
			}
		}
		  /* examine response !=334 from server */
		  switch (dialog_result)  {
		  case 235:  /* success */
		erase_password(smtp_password,sizeof(smtp_password));
		return 0;
		break;
		
		  case 535:   /* client authentication data rejected by server */
		erase_password(smtp_password,sizeof(smtp_password));
		return 535;
		break;
		
		  case 501:  /* server cannot decode "client_to_server" string */
		/*( Hmmm. perhaps we should retry a few times  
		  in case this is  because the communication was disrupted
		  temporarily?)  for now, just pass to next auth mechanism */
		break;
		
		  case 454:  /* temporary server failure, try again */
		count ++;
		break;
		
		  default:    /* pass to next mechanism in list */
		break;
		  }
		  break;
		case 432:   /* special use of PLAIN this time only */
		  auth_list = plain;
		  break;
		  
		case 454:  /* server failed: try again, up to max_tries times */
		  count ++;
		  break;
		  
		case 501:    /* Authentication process cancelled  */
		  erase_password(smtp_password,sizeof(smtp_password));
		  return -1;
		  break ;
		  
		case 503:     /* AUTH previously succeeded */
		  erase_password(smtp_password,sizeof(smtp_password));
		  return 0;
		  break;
		  
		case 504:     /* unrecognized authentication type */
		  /* try next mechanism */
		  break; 
		  
		case 534:   /* selected AUTH mechanism is too weak */
		  /* try next mechanism */
		  break; 
		  
		case 535:   /* authentication failed */
		  erase_password(smtp_password,sizeof(smtp_password));
		  return 535;
		  break;
		  
		case 538:   /* encrypted conection required */
		  erase_password(smtp_password,sizeof(smtp_password));
		  return -1;
		  break; 
		  
		default :
		  break;
		}
	  break;
	  /* unsuccessful return values from smtp_auth_response() */
	  case -1:  /*  authentication mechanism unsupported by client */
	/* try next mechanism */
	  case -2:  /*  error in generating response for this mechanism */
	/* try next mechanism */
	  case -3:  /* response  buffer too small for this mechanism */
	/* try next mechanism */
	  default :
	break;
	  }
	}
  erase_password(smtp_password,sizeof(smtp_password));
  return -1 ;
}

char * get_next_item (char * list, char * item, int maxlen )
{
  /* process a space-separated list to remove  the next
	 entry.   If it has strlen <= maxlen, it is copied
	 as *item, otherwise it is discarded.
	 A pointer to the truncated list is returned,
	 until the list is exhausted, when a NULL pointer
	 is returned.
  */
  char  *save_item = item;
  int len = 0;
  
  while ( *list == ' ')
		list ++;
  
  while ( *list != ' ' &&  *list )                  
	{
	  len++;
	  if (len <= maxlen)
	*item++ = *list++;
	  else
	list++;
	}
  
  if (len <=  maxlen)
	*item = '\0';
  else
	*save_item = '\0';

  while ( *list == ' ')
		list ++;
  
	if (*list )
	return list;
	  else

	return NULL;
}


void  get_client_auth_list(char * auth_list, char * server_auth_list)
{
  /* provides an ordered space-separated list of client-supported 
	 authentication mechanisms pointed to by auth_list.   
	 RFC2222 specifies mechanism names be 1-20 chars long. */

  char client_auth_list[ CLIENT_AUTH_LIST_LEN ] = CLIENT_AUTH_LIST ;
  char server_auth[21], client_auth[21] , * auth_mech = NULL;
  char * listA = NULL, * listB = NULL, * listC = NULL;
  int len , match;

  listA = server_auth_list;
  listB = client_auth_list;
  listC = auth_list;
  auth_mech = server_auth;
  
  
  /* 
	 will first try all authentication mechanisms advertised by the
	 server, that match the list supported by the client, then
	 (in desperation) try all remaining mechanisms supported by the client.
  */
  
  /* first find server-supported mechanisms that the client supports */
  while( listA )
	{
	  listA= get_next_item (listA, auth_mech, 20) ;
	  len = strlen(auth_mech);
	  if ( len )
	{
	  char * item = client_auth ;
	  match = 0;
	  listB = client_auth_list;
	  while (listB)
		{
		  listB = get_next_item(listB, item ,20);
		  if (! strncmp( auth_mech , item, len) )
		match = 1;
		}
	  if (match )     /* add  to list if they match*/
		{                           
		  strncpy(listC,auth_mech,len); 
		  listC += len;
		  *(listC) = ' ';
		  listC++;
		}
	}
	}
  /*
	now find client-supported mechanisms not yet  listed
	( in case the server supports then without advertising them )
  */
  
  listB = client_auth_list;
  auth_mech = client_auth;
  while( listB )
	{
	  
	  listB = get_next_item (listB, auth_mech, 20) ;
	  len = strlen(auth_mech);
	  if ( len )
	{
	  char * item = server_auth ;
	  match  = 0;
	  listA = server_auth_list;
	  while (listA)
		{
		  listA = get_next_item(listA, item ,20);
		  if ( !strncmp( auth_mech , item, len) )
		match = 1;
		}
	  if (! match )   /* add  to list if they DON'T match*/
		{ 
		  strncpy(listC,auth_mech,len);
		  listC += len;
		  *(listC) = ' ';
		  listC++;
		}
	}
	}
  listC--;
  *listC = '\0';
  return ;
}

 
int  smtp_auth_response (char * challenge , char * auth_mech,
			 char * response, int bufsize )
{
  /*
	 determines whether the mail client supports a given
	 AUTH mechanism advertised by the SMTP server, and selects
	 the appropriate response function module.
	 Add to this as new mechanisms get supported.

	 According to RFC2554,"SMTP Service Extension for Authentication"),
	 supported mechanisms should be  mechanisms registered with IANA, 
	 http://www.iana.org., (see RFC2222).
	 
	 "LOGIN" ("AUTH=LOGIN") (base64 -encoded  Username:/Password:)
	 is also supported, but is not RFC2554 compliant.  (It is 
	 apparently a variant from the preliminary draft that eventually
	 became RFC2554.) 
	 
	 smtp_auth_response places the response string in the
	 size=bufsize buffer pointed to by char * response.
	 If called with a NULL challenge, the response is the  
	 dialog initiation response for a supported mechanism auth_mech.

	 return values:
	 response generated:          0
	 auth_mech is unsupported:   -1
	 some failure occurred:      -2
	 bufsize is too small:       -3

  */
  int retval ;

  /* PROTOCOL "AUTH=LOGIN":  (base64-encoded standard login)  */
  
  if (!strncmp( auth_mech,"LOGIN",5))      
	{
	  retval  = smtp_auth_LOGIN(challenge,response,bufsize);
	  return retval;            
	}
  
  /* PROTOCOL "PLAIN": RFC2595 */
  
  if (!strcmp( auth_mech,"PLAIN"))     
	{
	  retval = smtp_auth_PLAIN(challenge, response, bufsize);
	  return retval;            
	}
  
  /* PROTOCOL "CRAM-MD5": RFC2195 */

  if (! strcmp( auth_mech,"CRAM-MD5"))      
	{
	  retval = smtp_auth_CRAM_MD5(challenge, response, bufsize);
	  return retval;            
	}

	/* UNSUPPORTED SASL PROTOCOLS */

  if (!strcmp(auth_mech,"DIGEST-MD5"))    /* RFC2831 */
	return -1;                            /* not yet supported */
  
  if (!strcmp(auth_mech,"KERBEROS_V4"))   /* RFC2222 */
	return -1;                            /* not yet supported */
  
  if (!strcmp(auth_mech,"GSSAPI"))        /* RFC2222 */
	return -1;                            /* not yet supported */
  
  if (!strcmp(auth_mech,"SKEY"))          /* RFC2444 , (OBSOLETE)*/
	return -1;                            /* not yet supported */
  
  if (!strcmp(auth_mech,"EXTERNAL"))      /* RFC2222 */
	return -1;                            /* not yet supported */
  
  if (!strcmp(auth_mech,"ANONYMOUS"))     /* RFC2245 */
	return -1;                            /* not yet supported */
  
  if (!strcmp(auth_mech,"OTP"))           /* RFC2444 */
	return -1;                   /* not yet supported */
  
  if (!strcmp(auth_mech,"SECURID"))       /* RFC2808 */
	return -1;                            /* not yet supported */
  
  if (!strcmp(auth_mech,"GSS -SPNEGO"))   /* Paul Leach, Microsoft */
	return -1;                            /* not yet supported */
  
  if (!strcmp(auth_mech,"NTLM"))          /* Paul Leach, Microsoft */
	return -1;                            /* not yet supported */
  
  if (!strcmp(auth_mech,"NMAS_LOGIN"))    /* Mark Gayman, Novell */
	return -1;                            /* not yet supported */
 
  if (!strcmp(auth_mech,"NMAS_AUTHEN"))   /* Mark Gayman, Novell */
	return -1;                            /* not yet supported */
  
  return -1;
}

int smtp_auth_LOGIN ( char *  challenge , char * response , int bufsize )
{
  /* 
	 "AUTH=LOGIN" is not a RFC2554-compliant IANA-registered
	 protocol, but is  quite common. It is just the standard  
	 "Username:" +"Password:" sequence,  encoded in base64.
	 It must only be used over encrypted connections.
  */
  /* this implemention uses base64 encoding/decoding
	 routines from xfmail's mime.c */

  char * challenge_text = NULL;
  char * response_text = NULL;
  char * keyword = "AUTH LOGIN";
  int len, len1, len2, len64,  ecode = CE_BASE64;
  char * init = NULL, * body = NULL, * end = NULL ; 
 
  *response = '\0';
  /*  return initiation response if * challenge = NULL */
  if ( ! challenge )
	{
	  len = strlen(keyword);
	  if ( len >= bufsize )
	return -3;
	  strncpy(response,keyword,len);
	  *(response+len) = '\0';
	  return 0;
	}
	
  /* decode the challenge */
  base64_decode(NULL, &ecode);   /* initialize decoder */
  challenge_text = base64_decode(challenge,&ecode) ;
  if( ! challenge_text )
	return -2;
  
  if (strstr( ( const char *) challenge_text,"name:"))
	{
	  /* send username as response */
	  response_text = smtp_username;
	}
  else if (strstr( ( const char *) challenge_text,"assword:"))
	{
	  /* send password as reponse */
	  response_text = smtp_password;
	}
  else
	return  -2 ;
  
  len = strlen(response_text);
  if (len > MAX_AUTH_WORD_LEN) 
	return -2;

  len64 = 4*((len + 2)/3);
  if (len64 >= bufsize)
	return -3;

  init = base64_encode(NULL,len64 + 12);    /* initialize encoding */ 
  if (! init)
	return -2;
  body = base64_encode(response_text,len);
  if (!  body)
	return -2;
  end = base64_encode(NULL,len);       /* terminate encoding */
  if (!  end)
	return -2;
  
  len1 = strlen(body);
  len2 = strlen(end);
  if ( len1 + len2 >= bufsize )
	return -3;
  
  strncpy(response,body,len1);
  strncpy(response+len1,end,len2);
  *(response+len1+len2) = '\0';
  
  return 0;
}

int smtp_auth_PLAIN ( char *  challenge, char * response, int bufsize )
{
  /* See RFC 2595. Only for use over  encrypted connections.*/

  /* this implemention uses base64 encoding/decoding
	 routines from  mime.c */
  
  char response_text[3*MAX_AUTH_WORD_LEN+3];
  char * keyword = "AUTH PLAIN ";
  char * authentication_id = NULL;
  char  *authorization_id = NULL; 
  char *password = NULL;
  int pos, len, len0, len1, len2, len64 ;
  char * init = NULL, * body = NULL, * end = NULL ;

  *response = '\0';

  /* this protocol does not have a dialog  */
  if (challenge)
	return -2;

  /* use authentication_id == authorization_id  (this could change) */
  authorization_id   = smtp_username;
  authentication_id  = smtp_username;
  password           = smtp_password;
  
  pos = 0;
  if((strcmp(authentication_id,authorization_id)))
	{
	  len = strlen(authorization_id);
	  if (len > MAX_AUTH_WORD_LEN)
	return -2;
	  strncpy(response_text + pos,authorization_id,len);
	  pos += len;
	}
  response_text[pos] = '\0';
  pos++;
  len = strlen(authentication_id);
  if (len > MAX_AUTH_WORD_LEN)
	return -2;
  strncpy(response_text + pos,authentication_id,len);
  pos += len;
  response_text[pos] = '\0';
  pos++;
  len = strlen(password);
  if (len > MAX_AUTH_WORD_LEN)
	return -2;
  strncpy(response_text + pos,password,len);
  pos += len;
  
  len64 = 4*( ( pos + 2 )/3);
 
  init = base64_encode(NULL,len64 + 12);       /* initialize encoding */ 
  if (! init)
	return -2;
  body = base64_encode(response_text,pos);
  if (!  body)
	return -2;
  end = base64_encode(NULL,pos);                /* terminate encoding */
  if (!  end)
	return -2;

  len0 = strlen(keyword);
  len1 = strlen(body);
  len2 = strlen(end);
  if ( len0 + len1 + len2 >= bufsize )
	return -3;
  strncpy(response,keyword,len0);
  strncpy(response+len0,body,len1);
  strncpy(response+len0+len1,end,len2);
  *(response+len0+len1+len2) = '\0';

  return 0;
}


int smtp_auth_CRAM_MD5 ( char *  challenge , char * response , int bufsize )
{
  /* 
	 CRAM-MD5 ( Challenge-Reponse Authentication Mechanism)
	 see RFC2195 (www.ietf.org)
  */

  /* this implemention uses base64 encoding/decoding
	 routines from  mime.c */

  /* this implemention uses MD5 message digest
	 routines from md5.c */

  char * username = NULL;
  char * shared_secret = NULL;
  char * challenge_text = NULL;
  char response_text[ MAX_AUTH_WORD_LEN + 34] ;
  char * keyword = "AUTH CRAM-MD5";
  int i,len, len1, len2, len64,  ecode = CE_BASE64;
  unsigned int inputLen;
  char * init = NULL, * body = NULL, * end = NULL ; 
  MD5_CTX ctx;
  unsigned char digest[16], i_pad[65], o_pad[65];
  char  hex_digest[40];
  char * p = NULL;

  memset(i_pad, 0, 65);
  memset(o_pad, 0, 65);

  *response = '\0';

  /*  return initiation response if * challenge = NULL */
  if ( ! challenge ) {
	  len = strlen(keyword);
	  if ( len >= bufsize )
	return -3;
	  strncpy(response,keyword,len);
	  *(response+len) = '\0';
	  return 0;
  }
	
  username = smtp_username;
  /* use  password as shared_secret */
  shared_secret = smtp_password;

  /* decode the challenge */
  base64_decode(NULL, &ecode);   /* initialize decoder */
  challenge_text = base64_decode(challenge,&ecode) ;
  if( ! challenge_text )
	return -2;
  
  inputLen = strlen(shared_secret);
  if ( inputLen > 64 ) {
	MD5Init(&ctx);
	MD5Update(&ctx, (unsigned char *) shared_secret, inputLen );
	MD5Final(digest,&ctx);
	memcpy(i_pad, digest, 16);
	memcpy(o_pad, digest, 16);
  }
  else {
	memcpy(i_pad,shared_secret,inputLen);
	memcpy(o_pad,shared_secret,inputLen);
  }
  
  for (i = 0; i < 64; i++ ) {
	i_pad[i] ^= 0x36;
	o_pad[i] ^= 0x5c;
  }
  
  
  inputLen = strlen(challenge_text);
  MD5Init(&ctx);
  MD5Update(&ctx, i_pad, 64 );
  MD5Update(&ctx, ( unsigned char *) challenge_text, inputLen );
  MD5Final(digest,&ctx);
  
  MD5Init(&ctx);
  MD5Update(&ctx, o_pad , 64 );
  MD5Update(&ctx, digest , 16 );
  MD5Final(digest,&ctx);
  
  for ( p = hex_digest, i = 0 ; i < 16 ; i++, p+= 2)
	sprintf(p, "%02x", digest[i] &0xff );

  hex_digest[32] = '\0';
  len2  =  32;

  len1 = strlen(username);
  
  p = response_text;
  strncpy(p,username,len1);
  p += len1;
  *p = ' ';
  p++;
  strncpy(p,hex_digest,len2);
  p += len2;
  *p = '\0';
  len = strlen(response_text);
  len64 =  4*( (len+2) / 3 );
  if(len64 >= bufsize )
	return -3;

  init = base64_encode(NULL,len64 + 12);    /* initialize encoding */ 
  if (! init)
	return -2;
  body = base64_encode(response_text,len);
  if (!  body)
	return -2;
  end = base64_encode(NULL,len);       /* terminate encoding */
  if (!  end)
	return -2;
  
  len1 = strlen(body);
  len2 = strlen(end);
  if ( len1 + len2 >= bufsize )
	return -3;
  
  strncpy(response,body,len1);
  strncpy(response+len1,end,len2);
  *(response+len1+len2) = '\0';

  return 0;
}

/* add new protocol modules  here */








