package com.al_williams.SMTP;

import java.awt.*;
import java.net.*;
import java.util.*;
import java.io.*;



/**
 * SMTP Class. You can use this class to 
 * send e-mail via a SMTP server. From the 
 * Java@Work column in <A HREF=http://www.webtechniques.com>Web Techniques</A>.
 * You can find more info about E-mail and SMTP via the RFC's particularly
 * <A HREF=http://www.faqs.org/rfcs/rfc821.html>RFC821</A> and 
 * <A HREF=http://www.faqs.org/rfcs/rfc822.html>RFC822</A> 
 * @author Al Williams
 * @version 1.1
 */
public class SMTP
{
    // things you might want to change
    // 30 seconds timeout
    final static int WAIT_TIMEOUT = ( 30 * 1000 );
    public static int smtpPort = 25;
    final static String addressSep = ";";  // separates e-mail addresses
// SMTP server for testing
    final static String testServer = "localhost";
    
    String smtpServer;
    // could hardcode this
    MailMessage message;
    String hostname;
    BufferedReader input;
    OutputStream output;
    String errorText;     // copy of last response -- in case of error
    Socket sock;
    
    
    final String crlf = "\r\n";
    /**
     * Get the last response message. Useful for displaying error from SMTP server.
     * @returns String containing last response from server.
     */
    public String getLastResponse()
     {
     if (errorText==null) return "Unable to connect or unknown error";
     return errorText;
     }

    /**
     * This is a test main -- if you run SMTP it will send an e-mail
     * @param args The command line arguments
     */
    public static void main( String args [] )
    {
        int rc;
        Date today = new Date();
        if (args.length!=1)
        {
          System.out.println("Usage: SMTP e-mail address");
          System.exit(1);
        }
        System.out.println("Sending test message to " + args[0]);
        MailMessage msg = new MailMessage( "alw@al-williams.com", args[0], "Test", 
            "Sent at " + today.toString());
        SMTP smtp = new SMTP( testServer);
        rc = smtp.sendMail( msg );
        if( rc != 0 ) 
        {
          System.out.println( "Error " + rc );
          System.out.println(smtp.getLastResponse());
        }
        else 
          System.out.println( "OK" );
        
    }
    /**
     * Constructor. Requires SMTP server name.
     * @param host An SMTP server. Remember, the server must be accessible from this code.
     *             In particular, applets can usually only connect back to the same host
     *             they originated from.
     */
    
    
    public SMTP( String host )
    {
        smtpServer = host;
    }
  
    //read data from input stream to buffer String
    
    private int getResponse( int expect1 ) throws IOException
    {
        return getResponse( expect1, - 1 );
    }
    
    synchronized private int getResponse( int expect1, int expect2 ) throws IOException
    {
        boolean defStatus;
        long startTime;
        int replyCode;
        
        TimeoutRead thread = new TimeoutRead( input );
        thread.setBuffer("");
        thread.start();
        try
        {
            thread.join( WAIT_TIMEOUT );
        }
        catch( InterruptedException e )
        {
        }
        if( thread.isComplete() && thread.getBuffer().length() > 0 )
        {
            try
            {
                errorText = thread.getBuffer();  // if there is an error, this is it.
                replyCode = Integer.valueOf( errorText.substring( 0, 3 ) ).intValue();
                if( replyCode == 0 ) return - 1;
                if( replyCode == expect1 || replyCode == expect2 ) return 0;
                return replyCode;
            }
            catch( NumberFormatException e )
            {
                return - 1;
            }
        }
        return - 1;
        // nothing in buffer
    }
    
    
    
    private void writeString( String s ) throws IOException
    {
	// debugging
	//	System.out.println(s);
        output.write( s.getBytes() );
    }
    private int sendAddresses(String pfx, String addr) throws IOException
    {
       int n0=0;
       int n;
       int rv=0;
       while (rv==0 && (n=addr.indexOf(addressSep,n0))!=-1)
          {
          writeString(pfx+addr.substring(n0,n)+crlf);
	  n0=n+1;
          rv=getResponse( SMTPResults.SMTP_RESULT_COMPLETED, SMTPResults.SMTP_RESULT_FORWARD );
          }
       if (rv==0)
       {
         writeString(pfx+addr.substring(n0)+crlf);
         rv=getResponse( SMTPResults.SMTP_RESULT_COMPLETED, SMTPResults.SMTP_RESULT_FORWARD );
       }
       return rv;
     }


    /**
     * Use sendMail to actually send an e-mail message.
     * @param MailMessage This is a filled-in MailMessage object that specifies the text, subject, and recipients.
     * @return Zero if successful. Otherwise, it returns the SMTP return code.
     * @see SMTPResults
     */
    
    public int sendMail(MailMessage msg) {
	return sendMail(msg,null);
    }

    public int sendMail( MailMessage msg ,Hashtable headers)
    {
        String inBuffer;
        String outBuffer;
        int rv;
        message = msg;
        if( msg.to == null || msg.to.length() == 0 )
        {
            return - 1;
        }
        // Create connection	        
        try
        {
            sock = new Socket( smtpServer, smtpPort );
            hostname = "[" + sock.getLocalAddress().getHostAddress() + "]";
        }
        catch( IOException e )
        {
            return - 1;
        }
        //Create I/O streams
        try
        {
            input = new BufferedReader( new InputStreamReader( sock.getInputStream() ) );
        }
        catch( IOException e )
        {
            return - 1;
        }
        try
        {
            output = sock.getOutputStream();
        }
        catch( IOException e )
        {
            return - 1;
        }
        rv=sendMailEngine(headers);
// end connection
        try
        {
            sock.close();
	    sock=null;
        }
        catch( IOException e )
        {
            return - 1;
        }
        return rv;
    }


// this is a separate routine so the main sendMail can  always close the socket
private int sendMailEngine(Hashtable headers)   
{
         try
        {
            
            int replyCode;
            int n;
            Date today = new Date();
            replyCode = getResponse( SMTPResults.SMTP_RESULT_READY );
            if( replyCode != 0 ) return replyCode;
            //Send HELO
            writeString( "HELO " + hostname + crlf );
            replyCode = getResponse( SMTPResults.SMTP_RESULT_COMPLETED );
            if( replyCode != 0 ) return replyCode;
            // Identify sender
            writeString( "MAIL FROM: " + message.sender + crlf );
            replyCode = getResponse( SMTPResults.SMTP_RESULT_COMPLETED );
            if( replyCode != 0 ) return replyCode;
            // Send to all recipients
            replyCode = sendAddresses("RCPT TO: ",message.to);
            if( replyCode != 0 ) return replyCode;
            // Send to all CC's (if any)
            if( message.cc != null && message.cc.length() != 0 )
            {
                replyCode = sendAddresses("RCPT TO: ",message.cc);
                if( replyCode != 0 ) return replyCode;
             }
                // Send to all BCC's (if any)
             if( message.bcc != null && message.bcc.length() != 0 )
             {
		    replyCode = sendAddresses("RCPT TO: ",message.bcc);
                    if( replyCode != 0 ) return replyCode;
              }
              
                // Send mesage
                writeString( "DATA" + crlf );
                replyCode = getResponse( SMTPResults.SMTP_RESULT_MAIL_START );
                if( replyCode != 0 ) return replyCode;
                
                //Send mail content CRLF.CRLF
                // Start with headers
                writeString( "Subject: " + message.subject + crlf);
                writeString( "From: " + message.sender + crlf);
                writeString( "To: " + message.to + crlf);
                if( message.cc != null && message.cc.length() != 0 )
			writeString( "Cc: " + message.cc + crlf);
                writeString( "X-Mailer: SMTP Java Class by Al Williams" + crlf );
                writeString( "Comment: Unauthenticated sender" + crlf );
		if (headers!=null) {
		    Enumeration key = headers.keys();
		    while (key.hasMoreElements()) {
			String keystring=(String)key.nextElement();
			writeString(keystring+":"+(String)headers.get(keystring)+crlf);
		    }
		}
                writeString( "Date: " + today.toString() +crlf + crlf );
// fix periods for transparency
                StringBuffer body=new StringBuffer(message.body);
                n=0;
                do
                {
                  n=body.toString().indexOf("\n.",n);
                  if (n!=-1) body.insert(++n,'.');
                } while (n!=-1);
                writeString( body.toString() );  // send body
                writeString( crlf + "." + crlf );  // end mail
                replyCode = getResponse( SMTPResults.SMTP_RESULT_COMPLETED );
                if( replyCode != 0 ) return replyCode;
                // Quit
                writeString( "QUIT" + crlf );
                replyCode = getResponse( SMTPResults.SMTP_RESULT_CLOSING );
                if( replyCode != 0 ) return replyCode;
        }
        catch( IOException e )
        {
            return - 1;
        }
    return 0;
    }
 
   protected void finalize() throws Throwable
   {
     if (sock!=null) sock.close();
     super.finalize();
   }

}


// private class to handle the reading in a thread
class TimeoutRead extends Thread
{
    private String buffer = new String( "" );
    private BufferedReader input;
    private boolean complete = false;
    synchronized String getBuffer() { return buffer; }
    synchronized void setBuffer(String s) { buffer=s; }
    synchronized public boolean isComplete()
    {
        return complete;
    }
    public TimeoutRead( BufferedReader i )
    {
        input = i;
    }
    public void run() // do input in thread 
    {
        try
        {
            do
            {
               setBuffer(input.readLine());
            } while (getBuffer().charAt(3)=='-'); // loop on multi-line response
// This line is useful for debugging
	    //               System.out.println(buffer);
        }
        catch( IOException e )
        {
            setBuffer("");
        }
        complete = true;
    }
    
}


