不需要smtp服务器的邮件发送

时间:2021-01-19 05:04:48
工作中总用到需要发送邮件的情形,但是每次都需要一个smtp服务器来转发,他们仅仅是转发而已
为了一个简单的邮件还要弄个邮件服务器太麻烦了,Postfix不会装,sendmail也不熟悉。
而且如果公司的邮件服务器不稳定(别说你没碰到),总是能遇到邮件发送延迟,甚至丢失的情况
干嘛不自己伪造个协议,跳过通过smtp服务器这一层呢?而且可以模拟任何人给任何人发送邮件(虽然可以,但是鄙视发垃圾邮件的人)。

如果你的邮件地址是a@host.com,而你要用这个邮箱发送一封邮件到to@tohost.com,你需要连接到服务器host.com上,当然这个连接可能需要认证,现在基本上都要验证,然后是发送邮件到服务器host.com上,关闭连接。在host.com上,你所发送的邮件进入发送队列中,轮到你要发送的邮件时,host.com主机再联系tohost.com,将邮件传输到服务器tohost.com上。


//这段是我转载的
首先我们看一下Email的递送过程:
Email(Encode) -> a SMTP Relay Server  -> Remote SMTP Server(远程邮局)。
非常简单,邮件编码后被递送到一个SMTP转交服务器上,该服务器对信件分检
(到同一邮局的被放在一起)后,根据优先级以及信件的先后次序被发送到远程
邮局的SMTP服务器上。换句话说,只要我们知道了SMTP转交服务器是如何确定远
程邮局SMTP服务器的地址的,就可以轻松地将饶开SMTP Relay Server直接递送
到远程邮局服务器。
  SMTP Relay Server是如何确定远程邮局服务器的地址的呢?如果你熟悉域名解
析,就知道是怎么回事了,我们知道电子邮件的地址由两部分构成postbox@address.com,
邮箱(postbox)和地址(address.com),给域名服务器发送指令查询“address.com”
的远程邮局服务器的地址即可找到远程邮局SMTP服务器的IP 地址,该指令查询是
被称作MX(Mail Exchange)邮件交换服务器的地址查询。远程邮局SMTP服务器的地
址可能不止一个,这时,你可根据信件优先级的不同,将对应优先级的信件发到
对应地址的远程邮局SMTP服务器,当然,你也可以不管三七二十一,随便选一个
SMTP服务器发送,见后附“域名解析结果样例”。简单吧。这下,自己编写一个
SMTP Server不难了吧!

废话少说,这个寻找smtp relay server 的过程不需要我们来弄,JAVA里线程的
 
    private String[] getSMTPServerByJNDI(String to) throws Exception { 
        String host=getDomainFromAddress(to);
        Properties jndiEnvironmentProperties = new Properties(); 
        jndiEnvironmentProperties.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); 
        InitialDirContext initialDirContext = new InitialDirContext(jndiEnvironmentProperties); 
        Attributes attributes = initialDirContext.getAttributes(host, new String[] {"MX"}); 
       
        Attribute attribute = attributes.get("MX"); 
        String[] servers = new String[attribute.size()]; 
        for (int i = 0; i < attribute.size(); i++) { 
            servers[i] = attribute.get(i).toString(); 
            servers[i]=servers[i].substring(servers[i].indexOf(" ") + 1, servers[i].length() -1); 
           
        } 
        return servers; 
    }


########当然还算要贴完整的代码的=================
package libtools.util;
import java.io.PrintStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.InitialDirContext;

import libtools.tar.TarFsPublic;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



public class SendMail
{
    private static final Logger log = LoggerFactory.getLogger(SendMail.class);
   
    private final static String DATE_TEMPLET = "d MMM yyyy HH:mm:ss Z";//"EEE, d MMM yyyy HH:mm:ss Z";  //"Sat, 12 Sep 2009 20:52:10 +0800"

    private final static String BOUNDARY_PREFIX = "--";

    private final static String CRLF = "\r\n";

    private Socket socket;

    private BufferedReader input;

    private PrintStream output=null;

    private SimpleDateFormat format;

    public SendMail()
    {}
   
   
   
    public static void main(String[] args) throws Exception {
        SendMail send=new SendMail();
        send.send("myn@163.com,165162897@qq.com,yannian.mu@alipay.com", "我的测试", "正文内容", "", "utf8", "", "");
    }
   
   
    public void send(String to,String subject,String content,String attachment,String charset,String user,String pass)
    {
        String[] toList=to.split("[,|;]+");
        for(String tostr:toList)
        {
            try {
                 String[] addressList= getSMTPServerByJNDI(tostr);
                  for(int i=0;i<addressList.length;i++)
                  {
                      Boolean result=false;
                      String[] fromlist={"yannian.mu@alipay.com","myn@163.com","165162897@qq.com"};
                      //这样做是有些服务器,堆特定网站的邮箱进行了校验,有可能会失败
                     for(String from:fromlist)
                     {
                         result=send(from,"yannian",tostr, to,subject,content,attachment, charset, addressList[i],addressList[i],user,pass, 25);
                         if(result)
                         {
                             break;
                         }
                     }
                     if(result)
                     {
                         break;
                     }
                  }
            } catch (Exception e) {}
        }
    }
   
    public void send(String from,String formName,String to,String subject,String content,String attachment,String charset,String user,String pass)
    {
        String[] toList=to.split("[,|;]+");
         for(String tostr:toList)
         {
             try {
                 String[] addressList= getSMTPServerByJNDI(tostr);
                  for(int i=0;i<addressList.length;i++)
                  {
                     Boolean result=send(from,formName,tostr, to,subject,content,attachment, charset, addressList[i],addressList[i],user,pass, 25);
                     if(result)
                     {
                         break;
                     }
                  }
            } catch (Exception e) {}
         }
    }

 

    private String getDomainFromAddress( String EmailAddress )
    {
       StringTokenizer   tokenizer = new StringTokenizer( EmailAddress, "@>;" );
       String            DomainOnly = tokenizer.nextToken();

       DomainOnly = tokenizer.nextToken();

       return DomainOnly;
    }
   
    private String[] getSMTPServerByJNDI(String to) throws Exception { 
        String host=getDomainFromAddress(to);
        Properties jndiEnvironmentProperties = new Properties(); 
        jndiEnvironmentProperties.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); 
        InitialDirContext initialDirContext = new InitialDirContext(jndiEnvironmentProperties); 
        Attributes attributes = initialDirContext.getAttributes(host, new String[] {"MX"}); 
       
        Attribute attribute = attributes.get("MX"); 
        String[] servers = new String[attribute.size()]; 
        for (int i = 0; i < attribute.size(); i++) { 
            servers[i] = attribute.get(i).toString(); 
            servers[i]=servers[i].substring(servers[i].indexOf(" ") + 1, servers[i].length() -1); 
           
        } 
        return servers; 
    }
       
    public boolean send(String from,String formName,String to,String replyTO,String subject,String content,String attachment,String charset,String smtpAddress,String smtpHost,String user,String pass,int port)
    {       
        try
        {
            String boundary = "=======ThisIsBoundary=======";
            socket = new Socket( smtpAddress, port/*COMMON*/ );
            socket.setSoTimeout(10000);
            input = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
            output = new PrintStream( socket.getOutputStream() );
            getResponse( "220", "Failed to connect to: " + smtpHost, true );
            sendCommand( "HELO " + smtpHost/*NO SPACE IN IT*/ + CRLF);
            getResponse( "250", "Failed to get HELO response from server.", true );
            if(user!=null && !user.isEmpty() && pass!=null && !pass.isEmpty())
            {
                sendCommand( "AUTH LOGIN" + CRLF );
                getResponse( "334", "Failed to get USERNAME request from server.", true );
                sendCommand( getBase64String( user ) + CRLF );  //Username
                getResponse( "334", "Failed to get PASSWORD request from server.", true );
                sendCommand( getBase64String( pass ) + CRLF );  //Password
                getResponse( "235", "Failed to send AUTH LOGIN username and password to server.", true );
            }

            sendCommand( "MAIL FROM: <" + from + ">" + CRLF );
            getResponse( "250", "Failed to get MAIL FROM response from server.", true );
           
            String[] toList=to.split("[,|;]+");
            for(String tostr:toList)
            {
                sendCommand( "RCPT TO: <" + tostr + ">" + CRLF );
                getResponse( "250", "Failed to get RCPT TO response from server.", false/*NOTE*/ );
            }
           
            if(replyTO==null||replyTO.isEmpty())
            {
                replyTO=to;
            }

            sendCommand( "DATA" + CRLF );
            getResponse( "354", "Failed to get DATA response from server.", true );
            sendCommand( "Subject: " + getBase64Subject( subject, charset ) + CRLF );
            sendCommand( "Date: " + getDateString() + CRLF );
            sendCommand( "From: " + ""+formName+"<" + from + ">" + CRLF );
            sendCommand( "To: "  +replyTO + CRLF );
            sendCommand( "MIME-Version: 1.0" + CRLF );

            sendCommand( "Content-Type: multipart/mixed; boundary=\"" + boundary + "\"" + CRLF );
            sendCommand( "Content-Transfer-Encoding: 7bit" + CRLF + CRLF/*NOTE*/ );
            sendCommand( "This is a multi-part message in MIME format." + CRLF + CRLF/*NOTE*/ );
           
            sendCommand( BOUNDARY_PREFIX + boundary + CRLF );
            sendCommand( "Content-Type: text/plain;" + CRLF );
            sendCommand( "Content-Transfer-Encoding: base64" + CRLF + CRLF/*NOTE*/ );
            sendCommand( getBase64String( content ) + CRLF + CRLF/*NOTE*/ );
            String[] fileList=attachment.split("[ |\t|,]+");

            for(String eachFile:fileList){
                if(eachFile.trim().isEmpty())
                {
                    continue;
                }
                sendCommand( BOUNDARY_PREFIX + boundary + CRLF );
                String [] filenamesplit=eachFile.split("[\\\\|/]+");
                String filename=filenamesplit[filenamesplit.length-1];
               
                if(eachFile.startsWith("hdfs@")&&filename.indexOf(".")<0)
                {
                    filename+=".txt";
                }
               
                sendCommand( "Content-Type: application/octet-stream; name=\"" + filename + "\"" + CRLF );
                sendCommand( "Content-Transfer-Encoding: base64" + CRLF );
                sendCommand( "Content-Disposition: attachment; filename=\"" + filename + "\"" + CRLF + CRLF/*NOTE*/ );
                sendAttachment( eachFile );
            }
           
 

            sendCommand( CRLF + "." + CRLF/*NOTE*/ );  //Indicate the end of date using "/r/n./r/n"
            getResponse( "250", "Failed to send DATA content to server.", true );
            sendCommand( "QUIT" + CRLF );
            getResponse( "221", "Failed to get QUIT response from server.", true );
            System.out.println("succ "+from+" to "+to+"    "+smtpAddress);

            return true;
        }
        catch( Exception e ){
            System.out.println("fail "+from+" to "+to+"    "+smtpAddress);
            return false;
        }

        finally{
            try{ output.close(); input.close(); socket.close(); }catch( Exception e ){}
        }

    }

    private void sendAttachment( String fileName ) throws Exception
    {
       if(fileName.startsWith("hdfs@"))
       {
           sendHDFSAttachment(fileName.replaceAll("hdfs@", ""));
           return ;
       }
       
        FileInputStream inputStream = new FileInputStream( fileName );
        int base64PerLine = 76;  //76 base64 charactors per line
        int charsPerLine = 57;  //57 = 76 / 4 * 3
        byte[] src = new byte[4096];
        byte[] dest = new byte[src.length * 2];
        int length = 0, remain = 0, sOffset = 0, dOffset = 0;
        while( ( length = inputStream.read( src, remain, src.length - remain ) ) != -1 )
        {
            length = length + remain;
            remain = length % charsPerLine;
            length = length / charsPerLine * charsPerLine;
            for( sOffset = 0, dOffset = 0; sOffset < length; )
            {
                Base64Encode.encode( src, sOffset, charsPerLine, dest, dOffset );
                sOffset += charsPerLine; dOffset += base64PerLine;
                dest[dOffset ++] = '\r'; dest[dOffset ++] = '\n';
            }
            output.print( new String( dest, 0, dOffset ) );
            if( remain > 0 ){ System.arraycopy( src, sOffset, src, 0, remain ); }
        }
        if( remain > 0 )
        {
            Base64Encode.encode( src, 0, remain, dest, 0 );
            dOffset = ( remain + 2 ) / 3 * 4;
            dest[dOffset ++] = '\r'; dest[dOffset ++] = '\n';
            output.print( new String( dest, 0, dOffset ) );
        }
        inputStream.close();
    }
   
    private void sendHDFSAttachment( String fileName ) throws Exception
    {
        FileSystem fs=TarFsPublic.getFileSystem();
        FSDataInputStream inputStream = fs.open(new Path(fileName));
        int base64PerLine = 76;  //76 base64 charactors per line
        int charsPerLine = 57;  //57 = 76 / 4 * 3
        byte[] src = new byte[4096];
        byte[] dest = new byte[src.length * 2];
        int length = 0, remain = 0, sOffset = 0, dOffset = 0;
        while( ( length = inputStream.read( src, remain, src.length - remain ) ) != -1 )
        {
            length = length + remain;
            remain = length % charsPerLine;
            length = length / charsPerLine * charsPerLine;
            for( sOffset = 0, dOffset = 0; sOffset < length; )
            {
                Base64Encode.encode( src, sOffset, charsPerLine, dest, dOffset );
                sOffset += charsPerLine; dOffset += base64PerLine;
                dest[dOffset ++] = '\r'; dest[dOffset ++] = '\n';
            }
            output.print( new String( dest, 0, dOffset ) );
            if( remain > 0 ){ System.arraycopy( src, sOffset, src, 0, remain ); }
        }
        if( remain > 0 )
        {
            Base64Encode.encode( src, 0, remain, dest, 0 );
            dOffset = ( remain + 2 ) / 3 * 4;
            dest[dOffset ++] = '\r'; dest[dOffset ++] = '\n';
            output.print( new String( dest, 0, dOffset ) );
        }
        inputStream.close();
    }

    private void sendCommand( String command ) throws Exception
    {
        if( output == null || command == null || command.length() < 1 ){ return ; }
        output.print( command );
    }

    private void getResponse( String code, String message, boolean shouldQuit ) throws Exception
    {
        if( input == null || code == null || code.length() < 1 ){ return; }

        String line = input.readLine();
        if( line.startsWith( code ) ){ /**/ }else if( shouldQuit ){ throw new Exception( message ); }
    }

    private String getBase64String( String message )
    {
        if( message == null ){ return null; }

        byte[] bytes = message.getBytes();

        return Base64Encode.encode( bytes, 0, bytes.length );
    }

    private String getBase64Subject( String subject, String charset )
    {
        if( subject == null ){ return null; }

        byte[] bytes = null;

        try{ bytes = ( charset == null ? subject.getBytes() : subject.getBytes( charset ) ); }catch( Exception e ){
            log.error("libtools",e);
        }

        return ( bytes == null ? null : "=?" + charset + "?B?" + Base64Encode.encode( bytes, 0, bytes.length ) + "?=" );
    }

    private String getDateString()
    {
        if( format == null ){ try{ format = new SimpleDateFormat( DATE_TEMPLET, Locale.ENGLISH ); }catch( Exception e ){log.error("libtools",e);} }

        return ( format == null ? new Date().toString() : format.format( new Date() ) );
    }

  
}