无须SMTP服务器中转直接将电子邮件发送到对方邮箱

2023-11-16

前言

大家一定熟悉Foxmail中的“特快专递”,它能直接将电子邮件发送到对方的邮件服务器中,而不需要经过SMTP服务器中转,这样做有什么好处?第一:发送速度比较快,不需要等SMTP服务器对邮件进行查毒、派发、验证;第二:你可以及时掌握邮件是否发送成功的信息。有时我们用Outlook发送一封邮件,到第二天对方都没收到,可我这边确实已经发送成功了,只好让对方多收几次,到了第三天SMTP服务器回信说“不好意思,你发往XXX的邮件因为XXX原因未能送达……”,原来邮件被打回来了,尤其最近163邮箱非常离谱,我发出去的10封邮件,至少有3封会被无故打回来,说什么“网络连接失败”所以被打回,莫名其妙,可能我是免费邮箱的缘故吧,没办法只好再申请多几个邮箱,我现在已经有“chrys@21cn.comchrys.xie@gmail.comhwxie@ust.hk ……”好多邮箱了,就是为了防止给别人发邮件时被无故退回……撤远了,不好意思。第三:我们有时需要在程序里将某些敏感信息发送至公司邮箱,例如:计算注册码时我们需要用户操作我们的软件将申请注册的信息发送回我们的售后服务邮箱,由我们的工作人员处理来这些邮件。

大家一定会想用SMTPSimple Mail Transfer Protocol)借助SMTP服务器也能通过程序实现邮件发送,但是有一个很大问题就是安全问题,很多著名的邮件服务器运营商对于用软件方式通过SMTP协议频繁提交邮件转发的申请是不欢迎的,我的163邮箱就曾经深受其害,我那次是在写SMTP客户端发送邮件的程序,顺手就用了163SMTP服务器,我刚发到第5封邮件时就发送失败了,我再登录163网站一查,原来我的账号被封了,原因就是我用软件发送邮件太多了(天啦,才5封而已啊),后来我花了近两个月时间跟新浪公司又赔礼又道歉,还把身份证传真过去了我的账号才被恢复。

剖析邮件传送过程

废话说太多请别介意,现在言归正传,要直接将邮件送到对方(POPIMAP)服务器上,而不经过SMTP邮件服务器转交,其实也不难,你只要改用Unix/Linux操作系统,直接SendMail命令就能完成,但在Windows下想要实现这个功能恐怕得花一点心思了。我们首先要从协议RFC821 - Simple Mail Transfer Protocol入手来分析。

首先我们看一下Email的递送过程:

邮件原文 编码 SMTP客户端 SMTP转交服务器 远程SMTP服务器(对方邮局)。

“特快专递”的实现思路

邮件编码后被递送到一个SMTP转交服务器上,该服务器对信件分检(到同一邮局的被放在一起)后,根据优先级以及信件的先后次序被发送到远程邮局的SMTP服务器上。换句话说,只要我们知道了SMTP转交服务器是如何确定远程邮局SMTP服务器的地址的,就可以直接递送到远程邮局服务器。SMTP转交服务器又是知道远程邮局的地址呢?这就是域名解析所完成的工作了,就好比我们在IE浏览器输入“www.viction.net”这个域名,IE浏览器又如何知道目标服务器的IP地址呢?也是域名解析服务器的功劳。

电子邮件地址由两部分组成,例如:chrys@163.com,这里的chrys是邮箱名(即用户名,一个用户对应一个邮箱),163.com是邮箱服务器地址,邮箱名和邮箱服务器地址之间以“@”作为分隔。

我们只要向域名服务器发送查询“163.com”的远程邮局服务器地址便可找到远程邮局SMTP服务器的IP 地址,该查询指令被称作MX(Mail Exchange)邮件交换服务器的地址查询。远程邮局SMTP服务器的地址可能不止一个,这时,你可根据信件优先级的不同来选择对应的远程邮局,我为了安全起见会对每一个远程邮局服务器按照等级高低逐一尝试,只要将邮件成功地发送到其中一个邮局我们的任务就完成了。

我们要完成几项编程工作:本机DNS的获取、与DNS服务器通信实现MX指令查询、SMTP邮件提交,下面我们一一阐述。

获取本机DNS

代码中我封装了一个类CnetAdapterInfo,该类可以获取本机网卡的系列信息,包括本机IP地址、子网掩码、DNSWins、网卡MAC地址等相关信息。

首先我们需要调用IPHelpAPI 库中的GetAdaptersInfo()函数来获取系统中所有网卡信息。

DWORD GetAdaptersInfo (

  __out    PIP_ADAPTER_INFO pAdapterInfo,

  __inout  PULONG pOutBufLen

);

该函数有两个参数,pAdapterInfo是一个指针,指向一个用户定义的结构体,一般是用HeapAlloc()申请的内存空间,pOutBufLen传入pAdapterInfo所指空间的大小,传出实际需要的缓冲大小,第一次调用该函数时pOutBufLen传入0,函数将返回 ERROR_BUFFER_OVERFLOW 表示需要更多的缓冲,并将实际需要的缓冲长度返回,我们根据实际长度用HeapAlloc()函数申请空间再次调用该函数,以下代码是枚举所有网卡并将信息保存到数组 m_Ary_NetAdapterInfo 中:

#define MALLOC( bytes ) ::HeapAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, (bytes) )

#define FREE( ptr )       if( ptr ) ::HeapFree( ::GetProcessHeap(), 0, ptr )

#define REMALLOC( ptr, bytes ) ::HeapReAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, bytes )

//

// 枚举网络适配器

// return : ------------------------------------------------------------

//     -1    -      失败

//     >=0 -      网络适配器数量

//

int CNetAdapterInfo::EnumNetworkAdapters ()

{

       DeleteAllNetAdapterInfo ();

 

       IP_ADAPTER_INFO* pAdptInfo = NULL;

       IP_ADAPTER_INFO* pNextAd   = NULL;

       ULONG ulLen                             = 0;

       int nCnt                               = 0;

      

       DWORD dwError = ::GetAdaptersInfo ( pAdptInfo, &ulLen );

       if( dwError != ERROR_BUFFER_OVERFLOW ) return -1;

       pAdptInfo = ( IP_ADAPTER_INFO* )MALLOC ( ulLen );

       dwError = ::GetAdaptersInfo( pAdptInfo, &ulLen );

       if ( dwError != ERROR_SUCCESS ) return -1;

      

       pNextAd = pAdptInfo;

       while( pNextAd )

       {

              COneNetAdapterInfo *pOneNetAdapterInfo = new COneNetAdapterInfo ( pNextAd );

              if ( pOneNetAdapterInfo )

              {

                     m_Ary_NetAdapterInfo.Add ( pOneNetAdapterInfo );

              }

              nCnt ++;

              pNextAd = pNextAd->Next;

       }

      

       // free any memory we allocated from the heap before

       // exit.  we wouldn't wanna leave memory leaks now would we? ;p

       FREE( pAdptInfo );             

      

       return nCnt;

}

针对每个网卡信息,我们需要调用 GetPerAdapterInfo()函数来获取指定网卡的DNS信息,使用方法和GetAdaptersInfo()类似。以下代码获取网卡基本信息:

//

// 根据传入的 pAdptInfo 信息来获取指定网卡的基本信息

//

BOOL COneNetAdapterInfo::Init ()

{

       IP_ADDR_STRING* pNext                = NULL;

       IP_PER_ADAPTER_INFO* pPerAdapt       = NULL;

       ULONG ulLen                                    = 0;

       DWORD dwErr = ERROR_SUCCESS;

       ASSERT ( m_AdptInfo.AddressLength > 0 );

       t_IPINFO iphold;

 

       // 将变量清空

       m_bInitOk = FALSE;

       m_csName.Empty ();

       m_csDesc.Empty ();

       m_CurIPInfo.csIP.Empty ();

       m_CurIPInfo.csSubnet.Empty ();

       m_Ary_IP.RemoveAll ();

       m_Ary_DNS.RemoveAll ();

       m_Ary_Gateway.RemoveAll ();

      

#ifndef _UNICODE

       m_csName                   = m_AdptInfo.AdapterName;

       m_csDesc                    = m_AdptInfo.Description;

#else

       USES_CONVERSION;

       m_csName                   = A2W ( m_AdptInfo.AdapterName );

       m_csDesc                    = A2W ( m_AdptInfo.Description );

#endif

      

       // 获取当前正在使用的IP地址

       if ( m_AdptInfo.CurrentIpAddress )

       {

              m_CurIPInfo.csIP         = m_AdptInfo.CurrentIpAddress->IpAddress.String;

              m_CurIPInfo.csSubnet  = m_AdptInfo.CurrentIpAddress->IpMask.String;

       }

       else

       {

              m_CurIPInfo.csIP         = _T("0.0.0.0");

              m_CurIPInfo.csSubnet  = _T("0.0.0.0");

       }

      

       // 获取本网卡中所有的IP地址

       pNext = &( m_AdptInfo.IpAddressList );

       while ( pNext )

       {

              iphold.csIP            = pNext->IpAddress.String;

              iphold.csSubnet      = pNext->IpMask.String;

              m_Ary_IP.Add ( iphold );

              pNext = pNext->Next;

       }

      

       // 获取本网卡中所有的网关信息

       pNext = &( m_AdptInfo.GatewayList );

       while ( pNext )

       {

              m_Ary_Gateway.Add ( pNext->IpAddress.String );

              pNext = pNext->Next;

       }

      

       // 获取本网卡中所有的 DNS

       dwErr = ::GetPerAdapterInfo ( m_AdptInfo.Index, pPerAdapt, &ulLen );

       if( dwErr == ERROR_BUFFER_OVERFLOW )

       {

              pPerAdapt = ( IP_PER_ADAPTER_INFO* ) MALLOC( ulLen );

              dwErr = ::GetPerAdapterInfo( m_AdptInfo.Index, pPerAdapt, &ulLen );

             

              // if we succeed than we need to drop into our loop

              // and fill the dns array will all available IP

              // addresses.

              if( dwErr == ERROR_SUCCESS )

              {

                     pNext = &( pPerAdapt->DnsServerList );

                     while( pNext )

                     {

                            m_Ary_DNS.Add( pNext->IpAddress.String );

                            pNext = pNext->Next;

                     }                         

                     m_bInitOk = TRUE;

              }

 

              // this is done outside the dwErr == ERROR_SUCCES just in case. the macro

              // uses NULL pointer checking so it is ok if pPerAdapt was never allocated.

              FREE( pPerAdapt );

       }

 

       return m_bInitOk;

}

至此我们已经获取到系统中所有DNS服务器地址了。

MX指令查询获取远程邮局地址

DNS服务器通信其实就是一个简单的UDP网络通信,端口号为53,通信的数据格式如下:

 

所有的DNS消息基本上都是相同的数据结构,但DNS RR是采用了其他的数据结构。

QNAME是一个表示域长度的变量,表示每一节有多少字节,例如:www.sockets.com将表示为:

 

最后的“Additional”通常包含了查询服务器期望被发送的纪录以减少通信量,例如,回应MX查询时通常在“Additional”中包含‘A’纪录。

具体的MX查询过程请参加源代码,以下代码实现了获取本机所有DNS,然后逐一尝试MX查询的方法:

//

// 尝试所有的DNS来查询邮局服务器地址

//

BOOL GetMX (

       char *pszQuery,                                               // 要查询的域名

       OUT t_Ary_MXHostInfos &Ary_MXHostInfos   // 输出 Mail Exchange 主机名

                     )

{

       CNetAdapterInfo m_NetAdapterInfo;

       m_NetAdapterInfo.Refresh ();

       int nNetAdapterCount = m_NetAdapterInfo.GetNetCardCount();

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

       {

              COneNetAdapterInfo *pOneNetAdapterInfo = m_NetAdapterInfo.Get_OneNetAdapterInfo ( i );

              if ( pOneNetAdapterInfo )

              {

                     int nDNSCount = pOneNetAdapterInfo->Get_DNSCount ();

                     for ( int j=0; j<nDNSCount; j++ )

                     {

                            CString csDNS = pOneNetAdapterInfo->Get_DNSAddr ( j );

                            if ( GetMX ( pszQuery, csDNS.GetBuffer(0), Ary_MXHostInfos ) )

                                   return TRUE;

                     }

              }

       }

       return FALSE;

}

如果查询“gmail.com”的邮局服务器地址,将得到如下的结果:

gsmtp147.google.com 50

gsmtp183.google.com 50

gmail-smtp-in.l.google.com       5

alt1.gmail-smtp-in.l.google.com 10

alt2.gmail-smtp-in.l.google.com 10

SMTP协议给远程邮局直接发送邮件

SMTP是一个简单邮件传输协议,通过TCP连接服务器的25端口号即可进行数据通信,以下是我用telnet手工发送邮件的过程:

 

其中红色矩形框起来的是服务器回应的数据,绿色矩形框起来的是我手工输入的数据,这里发送的邮件内容为“我是手工发送的电子邮件”,邮件被直接发送到chrys.xie@gmail.com邮箱中,不需要讨厌的SMTP服务器中转,当然,因为这是手工发送的邮件,其内容未经过任何MIME编码,这封邮件可以被FoxmailOutlook收到,但可能被判为垃圾邮件,因为这封邮件连标题都没有,是无头苍蝇,肯定是垃圾,呵呵……关于邮件内容的编码请参考其他相关资料,我有一本书,名叫《Visual C++ 网络通信协议分析与应用实》,这本书有详细的电子邮件编码介绍,可以下载电子文档看看。

当我们知道了SMTP通信的全过程,再编写一个TCP网络通信程序处理与SMTP服务器请求就不是难事了。本代码中的CHwSMTP类已经封装了整个通信过程,可以发送普通的电子邮件,还可以发送带附件的电子邮件,配合DNS查找,远程邮局地址MX查询便可实现任意邮件直接发送到对方邮箱的功能。

软件操作界面介绍

程序执行后界面如下:

 

From中可以输入一个虚假的邮箱地址,也可以输入真实的邮箱地址(如:21cn的邮箱),但不能输入163的邮箱,否则发送会失败,163邮箱不太欢迎大家用软件方式进行邮件收发,人家要靠这个吃饭嘛。

注意事项

看到这里是否已经很兴奋了,想着要自己写一个SMTP服务器,甚至想要写一个邮局服务器程序,但我觉得恐怕还没那么容易,我用这个软件给我的google邮箱(gmail)发送邮件,很快就能成功收到了,可我尝试过用这种方式给163邮箱和21cn邮箱发送邮件时却失败了,发给163时服务器说你的IP不被允许,提示信息如下:

550-5.7.1 [116.25.186.155] The IP you're using to send mail is not authorized

550-5.7.1 to send email directly to our servers. Please use the SMTP

看来163邮箱只接收大牌邮件服务器发过来的邮件,难怪我们这些免费的163用户发送邮件时常会被退回,原来163服务器还认牌子的,faint

到底要怎么样做才可以直接给163等著名的邮局发邮件呢?不知道用IP欺骗方式能否成功,请有高手知道解决这个问题的一定要告诉我啊,我的邮箱是:chrys@163.com,先谢过了!

电子邮件在目前的Internet上被广泛地使用,为了安全很多邮局服务器做了安全认证等诸多限制,我们要想让自己的SMTP服务器能向所有的邮局发邮件,恐怕还得做更多的努力。

结束语

知识就是力量,知识共享将具有推动时代进步的力量。希望我能为中国的软件行业尽一份薄力。

你可以任意修改复制本代码,但请保留版权信息文字不要修改。

由于水平有限,错误再所难免,请知情者原谅并告知,多谢!

 

源代码下载

CSDN 下载:http://download.csdn.net/source/802519

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

无须SMTP服务器中转直接将电子邮件发送到对方邮箱 的相关文章

  • 数论算法:唯一因子分解定理

    这里讲一下算法中常用到的唯一因子分解定理 合数a仅能以一种方式写成如下乘积形式 a p1 e1 p2 e2 pr er 其中pi为素数 p1
  • Vue基本知识1,安装创建以及常用指令

    Vue基本知识1 安装创建以及常用指令 1 Vue的概念 概念 vue是一个渐进式的JavaScript开发框架 基于MVVM实现数据驱动的框架 官网 还可以用来做SPA single page web application 一个网页就是
  • Qt udp数据发送太快,数据丢失

    Qt 在单线程中 如果数据发送太快 应用程序还在处理别的程序 就会触发不了readyRead信号 导致造成数据的丢失 解决方法可以将udp处理类继承于QThread 在多线程入口run函数里通过死循环接收数据 通过信号槽机制通知处理函数进行
  • 微信小程序&PHP 改变小程序码中间logo的方法

    需求 获取小程序码 小程序码中间logo换成用户头像 仔细看了小程序本身的程序 没有发现好的方法 如果有 不吝赐教呀 所以改变方法 把头像传回后台 使用 php gd库在后台操作 然后传回小程序端 初始数据准备 define PATH op
  • Hive的联级(cascade)- 修改分区表的字段类型或者增加新字段

    一 问题描述 踩坑 数仓的分区表 由于需求需要 要把int类型的字段改为bigint 我直接执行的以下语句 alter table table name change column 字段 字段 bigint 出现的问题 之后的分区数据可以正
  • 经济复苏!!! ReportLinker调整2020-2027 IMU市场预测金额至335亿美元

    2022年10月 市场研究机构ReportLinker发布了2020 2027全球IMU市场预测报告 报告预测到2027年全球IMU市场金额将达到236亿美元 2020 2027年期间复合增长率为5 4 新冠过后 经济复苏 ReportLi
  • Python同时显示多张图片在一个画面中(两种方法)

    很多时候需要把很多图片同时显示到一个画面中 现在分享两个方法 这里我恰好拿之前写的爬取网上图片保存到本地的爬虫模型爬一些图片作为素材Python 爬虫批量爬取网页图片保存到本地 得到素材如下所示 现在让这些图片同时显示 方法一 subplo

随机推荐

  • [论文笔记]知识图谱+推荐系统

    仅作个人笔记 2021 3 22 2021 3 29 1 RippleNet Propagating User Preferences on the Knowledge Graph for Recommender Systems 看到一篇翻
  • 深入理解程序设计使用Linux汇编语言

    关于函数 4 1 由系统提供的函数称为原函数 或源语 因为其他一切都是建立在它们之上 在汇编语言中 原语通常就是系统调用 4 3 由于内存的结构 栈是从内存顶部开始向下增长的 当我们提到 栈顶 请记住这是栈内存的底部 movl esp ea
  • C语言 - 制作一个可以容纳一千人的本地通讯录

    本章目录 前言 一 菜单制作 二 创建通讯录 1 创建人员信息结构体 2 创建完整通讯录结构体 3 初始化通讯录 4 存放数据 5 判断空间容量 三 各功能的实现 1 增加人员信息的功能 2 按名字查找的功能 内部使用 3 删除人员信息的功
  • 手把手教你CIFAR数据集可视化

    CIFAR数据集介绍与获取 如同从小到的父母教我们识别每个物体是什么一样 除了看到的画面 父母会在旁边告诉看到的画面是什么 这种学习方式叫做监督学习 与此对应还有无监督学习 计算机也一样 数据集通常应该至少包含两部分内容 一个是图像 一个是
  • C语言:三目运算符 “?”号

    三目运算符的表示一般为 该运算符连接3个对象 是C语言中唯一一个三目运算符 又称条件运算符 它的一般形式如下 表达式a 表达式b 表达式c 其执行步骤如下 1 计算表达式a的值 2 如果表达式a的值为1 则执行表达式b 3 如果表达式b的值
  • 支付宝API支付使用文档--java《扫一扫支付》--demo但是封装高可用--改一改配置就拿走用!超详细!!有手就行!!!--spring +vue-调用支付宝的当面付的预创建接口

    支付宝API支付使用文档 java 扫一扫支付 demo但是封装高可用 改一改配置就拿走用 超详细 有手就行 上 修改官网配置类 一单成的博客 CSDN博客 阿丹 上一篇文章具体的描述和讲解了官方提供的配置类 以及如何使用注册开发沙箱 本篇
  • Linux系统图形界面,字符界面切换快捷键。启动图形界面服务。

    Ctrl Alt F3 启动字符界面 Ctrl Alt F7 启动图形界面 启动图形界面服务 cd etc init d service lightdm restart
  • 重装win8.1搜索不到 wifi

    这几天一直忙着研究装系统 毕竟自己是个小白 经常搞到深夜4点钟 今天终于算是有点眉目了 重新装完win8 1 电脑竟然搜索不到wifi 点开右下角那个图标 只有宽带连接这一个选项 于是到网上搜索 怎么解决 网上给了很多答案 基本都差不多 我
  • TCP/IP的三次握手、四次挥手

    本文通过图来梳理TCP IP协议相关知识 TCP通信过程包括三个步骤 建立TCP连接通道 传输数据 断开TCP连接通道 如图1所示 给出了TCP通信过程的示意图 上图主要包括三部分 建立连接 传输数据 断开连接 建立TCP连接很简单 通过三
  • Temporary failure in name resolution解决方法

    终端运行sudo su 输密码 vi etc reslov conf 输入i进入编辑模式 在文档末尾加入 nameserver 8 8 8 8 nameserver 114 114 114 114 按esc 输入 wq保存退出 执行 etc
  • 第八章 Oracle恢复内部原理(重置日志RESETLOGS)

    重置日志选项用于下列情形后的第一次打开数据库的时候 不完全恢复 基于备份控制文件的恢复 CREATE CONTROLFILE RESETLOGS 重置日志的最主要的作用就是丢弃不完全恢复中没有使用的重做日志并保证后续的恢复不再需要 为此 重
  • dll修复工具哪个比较好?修复工具介绍

    DLL 动态链接库 是Windows操作系统中非常重要的一部分 它们存储了各种软件应用程序所需的公共代码和数据 然而 随着时间的推移 电脑上的DLL文件可能会因为各种原因而损坏或丢失 导致系统出现错误 因此 修复DLL错误是一项非常重要的任
  • linux 下 C++ 与三菱PLC 通过MC Qna3E 二进制 协议进行交互

    西门子plc 有snap7库 进行交互 并且支持c 而且跨平台 但是三菱系列PLC并没有现成的开源项目 没办法只能自己拼接 我这里实现了MC 协议 Qna3E 帧 并使用二进制进行交互 pragma once include
  • HDU--1236:排名 (水题)

    1 题目源地址 http acm hdu edu cn showproblem php pid 1236 2 程序源代码 HOJ 1236 排名 include
  • 软件测试框架理论知识

    一 软件测试的定义 1 软件测试的目标应该服从于软件项目的目标 软件测试通过更高效的方法和工具 提高软件开发的效率和质量 2 在规定的条件下 对软件进行审核 运行 评估 验证软件系统是否满足需求规格说明书 3 预防 发现 跟踪软件缺陷 提高
  • Unity篇:加快unity导入工程速度-Cache server链接

    1 已有unity打开 编辑窗口选择点击edit edit下拉展示的列表里打开Preferences 2 选择Cache server界面 Cache server Mode 选择remote 举例 输入192 168 15 131或者19
  • 目标检测中的mAP(mean Average Precision)快速理解

    前言 最近开始接触目标检测 object detection 但是对于衡量算法好坏的mAP Mean Average Precision 并不太理解 经过了一番整理 下面我们就来看看什么是mAP 在目标检测算法中 像是Faster R CN
  • vue上下滑动(仿高德)

    效果图 需求 一个页面底部对应一块区域 底部的区域可以上下滑动到顶部 可以来回上下滑动延伸 如上所述 代码
  • C++实现行列式的相关操作

    目录 一 前言 二 行列式运算操作集 1 概览 2 行列式的定义 3 行列式的输出与输入 4 行列式行与行 列与列的相加 5 行列式的行交换与列交换 6 行列式的行提取公因数与列提取公因数 7 行列式系数清零恢复 8 判断一个行列式是否是三
  • 无须SMTP服务器中转直接将电子邮件发送到对方邮箱

    前言 大家一定熟悉Foxmail中的 特快专递 它能直接将电子邮件发送到对方的邮件服务器中 而不需要经过SMTP服务器中转 这样做有什么好处 第一 发送速度比较快 不需要等SMTP服务器对邮件进行查毒 派发 验证 第二 你可以及时掌握邮件是