分享
 
 
 

MX记录获取组件的编写

王朝java/jsp·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

一、MX记录的应用

Email是迄今为止互联网上最成功的应用了,试想一个触网者如果没有属于自己的Email邮箱,那将难以称作“网民”。互联网网上同Email相关的应用也增多。我们编写网络应用时,经常需要涉及将Email应用嵌入到自己的应用程序中。这种应用往往是将第三方的Email相关组件拿来使用,完成Email的撰写、发送、收取、解码。在发送Email的过程中,NT平台一般采用NT系统自带的CDO组件来完成,而这类组件往往依靠在本机的服务或一个特定的SMTP服务器来实现,不能够确保信件是否到达,而且由于存在中继延迟,还有丢失信件的可能。所以很多高手采用直接向目的邮箱的服务器投递Email的办法来实现Email发送,而实现此功能关键在于三个要点:

1、邮件的MIME编码

2、SMTP会话控制

3、MX记录的获取。

通过我的了解,互联网上关于1、2两点的文章很多,相应的参考代码很多,而对于如何定位目标邮箱接收服务器-获得邮箱域名的MX记录却很少详细讨论,搜索相关的组件得到一个ASP环境的服务器组件(收费),没有详细的源码可以参考。基于此,我通过学习Java语言的机会编写了一个类似的组件,以助大家了解这方面的细节。

二、DNS记录分类以及原理

我们知道域名系统是互联网的基石,我们能够通过亲切的类似yahoo.com这样的名字来访问自己感兴趣的网站完全是域名系统的功劳。当您在浏览器敲下www.yahoo.com时,您的浏览器是不知道这个域名指向的网页该往哪儿去取,便会询问设定好的域名解析服务器(DNS),域名解析服务器会寻找自己的记录库是否存在请求的域名记录,如果存在会直接返回,不存在就向上一级域名解析服务器请求(转发请求),直到最后找到此域名的解析记录返回给您的浏览器,告诉您的浏览器该到哪一个IP地址上去获得相关的内容。这样一个将互联网域名转换为对应的IP的过程叫做解析。

域名记录有多种,常见的有 A(地址)记录、别名(CNAME)记录、MX(邮件交换)记录。A记录就是一个域名对应一个IP的记录,刚才提到的www.yahoo.com对应某个IP地址即是A记录的查询过程。别名记录主要用于用一个已经存在A记录的域名替代需要解析的域名,譬如www.yahoo.com查询时会得到www.yahoo.akadns.net 的别名值,而进一步解析www.yahoo.akadns.net会得到IP地址64.58.76.177。邮件交换记录是用于邮件投递时采用的。譬如您需要发送一封到 abc@yahoo.com的Email,那么一般情况下,您的ISP的SMTP服务器(专业术语成为邮件代理,即代替用户直接投递邮件)会接受您发出的信件,然后SMTP服务器查询yahoo.com的MX记录,看应当将邮件发送到哪一个服务器上去,如果查询到yahoo.com的MX记录,那么您的ISP的SMTP服务器会建立到MX记录指定的服务器的TCP连接,并传出您的Email到目的服务器上去,完成Email的投递过程。

如果能够获得某个域名的MX记录,即获得了向目的邮箱直接投递的途径,而要获得这个MX记录,您必须能够正确的向域名服务器DNS提出您的请求,同时也要能够理解DNS返回的信息,读出自己需要的内容。这里面的细节相对复杂,我这里简单介绍一下:

客户向DNS的53端口发出的UDP报文,然后服务器查询(中间可能经过对此转发或者称作递归查询)后发回客户机需要的记录,也是UDP报文。此类报文的一般格式为:

| 2字节的标识 | 2字节的标志 | 2字节的问题个数 | 2字节的资源记录数 | 2字节的授权资源记录个数 | 2字节额外的资源记录数 | 查询的域名(不固定长度) | 针对请求的应答资源记录(长度不固定)| 授权资源记录(长度不固定) | 额外记录信息(长度不固定)

标识字段用于指出报文的编号,一般由客户指定,DNS服务器返回信息时带上此标识,告诉客户端回答的是哪一个请求。

标志字段的16比特划分为8个子字段,从左至右(高位到低位)分别为:

QR 1 bit :0 查询报文 1 响应报文

Opcode 4 bit :通常为0,表示标准查询 ,1 反向查询,2 服务器状态查询

AA 1 bit :用于服务器返回报文,表示是否是授权回答

TC 1 bit :由于UDP自身长度限制,往往会截断512字节后的内容,该位表示是否可截断

RD 1 bit :该为用于在查询报文中设置,并由服务器响应报文中返回。该位告诉服务器必须处理此查询,如果该位为0,且服务器返回的授权回答个数为0,那么服务器必须返回一个能够解答该查询的其他服务器的列表

RA 1 bit :如果服务器支持递归,那么服务器在响应报文中设定该位。

随后的3bit必须为0

rcode 4 bit :最后为返回码,0 无差错,3 名字差错,即在服务器上不存在要查询的域名的记录,一般用于从最终的授权名字服务器返回。

查询问题部分由查询名字 查询类型 查询类组成。查询名字由多个标识符的序列组成,每一个标识首字节说明该标识符的长度,最终由字节0表示名字结束。譬如cn..yahoo.com由2 c n 5 y a h o o 3 c o m 0组成。如果此域名后面还用到,一般在后面采用压缩格式,那么首字节不是长度了,而是一个最高位为1的字节,一般是0xc0,因为不会出现长度超过64的标识符(由于域名的规定)。压缩格式的标志字节后是该域名的原标识的偏移值。查询类型为2字节,1 表示A记录查询 5 表示CNAME记录查询 15 表示MX记录查询。类表示是否是Internet数据。

应答报文中的应答记录由 域名(长度不固定) 类型(2字节) 类(2字节) 生存时间(4字节,秒数) 资源数据长度(2字节) 资源数据(不固定)。域名的格式同查询域名格式相同。类型、类的解释同查询问题部分。资源数据根据记录类型不同而不同。

如果我们按照上面的格式同时结合自己的需求,向有域名解析功能的服务器发出UDP报问候就可以收到DNS服务器发回的应答,这样我们就可以获得想的记录。所以,主要难点在报文的构造和应答报文的分析。

三、编写MX记录组件

启动VJ。要求建立一个DLL工程,然后修改主类名为自己想要的名字,该工程建立完毕。如果一切无误的话,就会得到一个DLL,具有域名MX记录查询功能。具体代码见后面的源代码分析。关键就是结合自己的查询问题构造查询报文和解释得到的应答报文,同服务器的网络连接方式是UDP方式。

为了调试方便,我添加了一个Main函数入口,这样可以在DOS窗口调用jvew来察看域名MX记录的结果,同时也方便了调试。

四、应用举例及扩充

有了这样一个查询组件,可以直接获得域名的MX记录,可以帮助我们获得投递Email的方法。同时,我们也知道了该域名邮件接收服务器的位置,通过试探不同得用户名,我们还可以获得该域名的邮局存在那些邮箱用户名。你可能奇怪自己的邮箱如何被他人获得,很可能被一些Email搜索器通过查询MX记录,得到邮件服务器的地址,然后通过特别的用户名产生算法得到不同的用户名,一一试探从而得到邮箱。在你的应用程序中加入此功能,可以直接将邮件传递到目标务器,不依赖于服务器上的组件。甚至您可以编写邮件转发服务器,服务于您的用户(一般的发件服务器不对外开放的)。

五、源码及分析

import java.net.*;

import java.io.*;

import java.util.StringTokenizer;

//import java.util.*;

/**

* This class is designed to be packaged with a COM DLL output format.

* The class has no standard entry points, other than the constructor.

* Public methods will be exposed as methods on the default COM interface.

* @com.register ( clsid=20AE856F-E2F8-488E-B41E-753E6BEBD375, typelib=36611559-AFAB-479E-8572-9A0B1F06CCDA )

*/

public class MXDNS

{

private String theDnsServer; //当前DNS服务器的地址

private DatagramPacket outPk; //发送数据包

private DatagramPacket inPk; //接收数据包

private DatagramSocket UDPSocket ; //UDP陶接字

private InetAddress DNSServerIP; //域名解析服务器的IP

private int position,id,length;//分析的DNS记录时需要的变量

private String DMname; //查询的域名

private MXRec mxrecs; //DNS应答的记录

private static int DNS_PORT=53; //DNS服务的端口

private byte[] pkdata=new byte[512] ; //得到512字节的数据包

public MXDNS() //构造函数

{

id=(new java.util.Date()).getSeconds()* 60 * (new java.util.Random()).nextInt();

//获得唯一ID

}

//以下为暴露的属性和方法

public void setDnsServer(String dnsserver)

{

theDnsServer=dnsserver; //设定当前的DNS服务器的名字或者IP

}

public String getMXRecords(String dm) //获得所有DNSMX记录数组

{

return(getMXRecords(dm,theDnsServer));

}

public String getMXRecords(String dm,String DNSServer)

{

try

{

DNSServerIP=InetAddress.getByName(DNSServer) ;

//获得DNS服务器的InetAddress

outPk=new DatagramPacket(pkdata,pkdata.length,DNSServerIP,DNS_PORT );

//外发的数据报

UDPSocket=new DatagramSocket(DNS_PORT); //数据包端口

makeDNSQuery(id,dm); //产生查询报文

UDPSocket.send(outPk); //发送数据报文

inPk=new DatagramPacket(pkdata,pkdata.length );

UDPSocket.receive(inPk); //接收返回的应答报文

pkdata=inPk.getData();//获得字接

length=pkdata.length;

}

catch(UnknownHostException ue)

{

}

catch(SocketException se)

{

}

catch(IOException ioe)

{

}

return(getResponse()); //分析返回的数据报文,得到记录结果

}

public void makeDNSQuery(int id,String dm)

{//在PKdate byte数组中产生查询数据

for(int i=0;i<512;i++)

{

pkdata[i]=0;

}

pkdata[0]=(byte)(id>>8);

pkdata[1]=(byte)(id & 0xff); //查询的标识2字节

pkdata[2]=(byte)1; //Qrbit位为1,表示是查询报文

pkdata[3]=(byte)0;

pkdata[4]=(byte)0;

pkdata[5]=(byte)1; //1个问题

pkdata[6]=(byte)0; //资源记录数、授权资源记录、额外资源记录均为0个,因为是查询报文

pkdata[7]=(byte)0;

pkdata[8]=(byte)0;

pkdata[9]=(byte)0;

pkdata[10]=(byte)0;

pkdata[11]=(byte)0;

StringTokenizer st=new StringTokenizer(dm,".");//.分隔域名

String label;

position=12; //从第12字节开始生成查询问题

while(st.hasMoreTokens ())

{

label=st.nextToken();

pkdata[position++]=(byte)(label.length() & 0xFF);//转换为字节

byte[] b=label.getBytes();

for(int j=0;j

{

pkdata[position++]=b[j];

}

}

pkdata[position++]=(byte)0;//以0结束域名

pkdata[position++]=(byte)0;

pkdata[position++]=(byte)15; //查询类型15表示MX记录查询

pkdata[position++]=(byte)0;

pkdata[position++]=(byte)1; //Internet数据记录查询

}//构造查询完成

private String getResponse() //获得反馈信息

{

String temp="";

int qCount=((pkdata[4] & 0xff)<<8) |(pkdata[5] & 0xFF); //获得问题数

if( qCount<0)

{return("");} //问题个数小于0,返回空字符串

int aCount=((pkdata[6] & 0xff)<<8)|(pkdata[7] & 0xff);//获得应答问题数

if (aCount<0)

{return(""); } //问题个数小于0,返回空字符串

position=12;//查询问题部分起始位置

for(int i=0;i

{

DMname="";

position=Proc(position);

position+=4; //增加长度字节部分查询类型、查询类

}

for(int i=0;i { DMname=""; position=Proc(position); position+=10; //类型2字节、类2字节、生存时间4字节、资源长度2字节共10字节 int pref=(pkdata[position++]<<8) | (pkdata[position++] & 0xff); //得到MX记录的交换器基数 DMname=""; position=Proc(position); //得到交换器的值,一般为一个标准域名 if( temp.equalsIgnoreCase("") ) {temp="" +pref +" "+DMname; } //返回数据 else {temp= temp + "," +pref +" "+DMname;} } return(temp); } private int Proc(int position) {//该过程从pkdata字节数组中寻找域名 int len=(pkdata[position++]&0xff); //取得将要处理标识符的长度 if(len==0)//没有其他标识符,结束返回 {return position;} int offset;//偏移 do{ if((len & 0xc0)==0xc0) //压缩格式么 { if(position>=length) //超过包的大小,那么偏移显然错误 {return(-1);} offset=((len&0x3f)<<8)|(pkdata[position++] & 0xff); //获得压缩源标识符的偏移 Proc(offset);//递归调用获得压缩前的名称 return(position); } else //非压缩格式 { if((position + len)>length) //超过长度 { return(-1);} DMname+=new String(pkdata,position,len); //获得域名标识符各个部分 position+=len; } if (position>length) {return(-1);} len=pkdata[position++] & 0xff;//最后是0结尾么 if(len!=0) { DMname+=".";//加上.构成完整域名 } }while(len!=0); return(position); } public static void main(String args[]) throws Exception { MXDNS mx=new MXDNS(); String s=mx.getMXRecords("sina.com","202.96.128.68"); System.out.println(s); int k=System.in.read() ; } }

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有