为了一个简单的邮件还要弄个邮件服务器太麻烦了,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() ) );
}
}