2010年10月7日星期四

基于INDY控件实现SMTP身份验证(转)

摘自:http://txdz.blog.163.com/blog/static/6252249200611321327459/

2003年4月发表于《现代计算机》杂志(专业版,总第159期)

 

基于INDY控件实现SMTP身份验证

 

唐**

 

[摘要] 本文介绍了在C++ Builder 6.0中,如何利用IndyTIdSMTP控件实现SMTP服务器的身份验证。

    [关键字] IndyTIdSMTP控件;身份验证

 

Authenticating Base on Indy Components

Tang Xiaodong 1, Kang Li 1, Wang Zhenghua 2

(1.China Aerodynamics Research and Development Center, Mianyang, Sichuan 621000, China)

(2. Dept. Of Computer, National University of Defense Technology,ChangSha,410073)

 

Abstract: This paper introduces how to accomplish verifying user’s identification via SMTP server by Indy’s TIdSMTP component in C ++ Builder .

Keywords: Indy; TIdSMTP Components; Authentication

 

1  Indy概述

    现在许多邮件服务器都具有SMTP身份验证的功能,但在C++ Builder 6.0以前的版本中,收发电子邮件是通过TNMSMTPTNMPOP3两个控件实现的。其中TNMSMTP控件没有提供SMTP服务器身份验证功能,这给制作邮件客户端程序带来不便。

Borland C++Builder 6.0DELPHI 6.0开始,Insprise公司开始将Indy控件加入到控件页上。Indy是一套开放源码的Internet组件,可以在Indy的网站http://www.nevrona.com/indy/上下载源代码。Indy以前的名字叫Winshoes,直到8.0后才正式改名为Indy,而且从8.0开始正式支持Linux,目前最新的版本是Indy 10。在C++BuilderIndy 8.0占去了三个控件页:Indy ClientsIndy ServersIndy Misc。其中的TIdSMTP控件除了遵从Simple Mail Transfer ProtocolRFC 821)和SMTP Service ExtensionsRFC 1869),还遵从SMTP Service Extension for AuthenticationRFC 2554),故它能实现身份验证与回执请求。它与Borland产品以前的TNMSMTP控件相比差别明显,下面进行详细介绍。

2  SMTP服务器的身份验证

怎样通过需要身份验证的SMTP Server发送电子邮件呢?回答这一问题前,我们来了解一下身份验证的由来。最初的RFC 821制定于1982年,第三方SMTP服务器和网关必须在主机之间转发信息且在源SMTP服务器和目的SMTP服务器间充当中介。由于Internet的开放,必然要冒着地址被滥用来发垃圾邮件的风险。通常,拒绝垃圾邮件有三种办法:第一种是IP绑定,SMTP服务器只接收指定IP地址发来的邮件;第二种是用户先得登录到POP3服务器,然后尽快发邮件。第三种方法是扩展SMTP协议,以便能使用各种不同的SASL(Simple Authentication and Security)机制,确保用户在能发出邮件前已经过身份验证。Indy即采用第三种方式。在Indy 8.09.0中,其TIdSMTP控件实现了一种称为LOGINSASL机制。TIdSMTP控件与身份验证有关的属性和方法见表1

属性或方法

          

Host

SMTP服务器名,如smtp.sohu.com

Port

SMTP所使用端口,默认为25

UserId

邮件账号

Password

邮件密码

AuthenticationType

身份验证类型(atLoginatNone

AuthSchemesSupported

指示是否支持身份验证及验证方式

Authenticate()方法

根据账号密码进行身份验证

Connect()方法

连接SMTP服务器

Disconnect()方法

断开SMTP服务器

Connected()方法

判断是否与SMTP服务器连接

Disconnected()方法

判断是否与SMTP服务器断开

Verify(AnsiString uid)方法

判断SMTP服务器是否存在某个UserId

Send(TidMessage msg)方法

发送电子邮件

1 TIdSMTP控件的属性与方法

21 身份验证的实现

一种方法是,直接通过设置TIdSMTPUserName password属性为用户邮件账号、密码,将AuthenticationType设置为atLogin,然后调用Authenticate方法即可。

IdSMTP1->UserId = “abcd”;// 假定邮件账号是abcd

IdSMTP1->Password = “123456”;// 假定口令是123456

IdSMTP1->AuthenticationType = atLogin;

bool auth;

auth = IdSMTP1->Authenticate(); //验证,anthtrue表成功

if(auth) ShowMessage(IdSMTP1->UserId+"身份验证通过!");

else ShowMessage(IdSMTP1->UserId+"身份验证失败!");

另一种情况,如果在没有验证之前就使用Send方法发送邮件时,Send方法将自动调用Authenticate方法,将编码过的UserIdPassword发给SMTP服务器。当SMTP服务器支持身份验证时,以下语句可以在发信时自动验证:

IdSMTP1->UserId = SmtpServerUser;

IdSMTP1->Password = SmtpServerPassword;

IdSMTP1->AuthenticationType = atLogin;

IdSMTP1->Send(IdMessage1);

22 检测SMTP服务器是否支持身份验证

目前国内许多SMTP服务器均支持身份验证功能,但还有一些服务器没有此项功能。Indy 提供了对SMTP服务器是否提供LOGIN身份验证支持的检测, 当通过Connect()方法与SMTP服务器连接后,由AuthSchemesSupported指示这一特性。如下所示:

IdSMTP1->AuthenticationType = atNone; // 先设置为无身份验证能力

IdSMTP1->Connect(); //连接SMTP Server

if(IdSMTP1->AuthSchemesSupported->IndexOf(“LOGIN”)>-1)

// 若支持身份验证

ShowMessage(IdSMTP1->Host+"支持身份验证!");

else

ShowMessage(IdSMTP1->Host+"不支持身份验证!");

2.3  服务器的账号核实

Verify方法验证一个账号或别名是SMTP服务器的用户邮箱,不需要知道邮箱密码。这与登录SMTP服务器时的验证是不同的。需要说明的是,这一方法在Indy 9.0中才开始提供。其声明为:

function Verify(AUserName: String): String; virtual;

如果成功,Verify方法将返回完整的用户标识名和E-Mail地址。TNMSMTP控件的Verify方法也是这个功能,只是返回值是布尔型。

3 身份验证的应用实例

C++Builder6.0中,创建一个新的工程:加入TIdSMTP控件IdSMTP1TIdMessage控件实例IdMessage14TBitBtn控件的Caption分别是检查服务器验证能力、身份验证、发信、退出,分别对应检查SMTP服务器是否支持身份验证、根据账号及口令验证身份、发送电子邮件、退出四个模块。窗体设计如图1所示:

 

1  身份验证范例程序的设计界面

 

下面是范例程序的部分源代码:

#include <vcl.h>

#pragma hdrstop

#include "Unit1.h"

#pragma package(smart_init)

#pragma resource "*.dfm"

TForm1 *Form1;

__fastcall TForm1::TForm1(TComponent* Owner)

        : TForm(Owner)

{

}

// 检查SMTP服务器是否支持身份验证功能

void __fastcall TForm1::BitBtn1Click(TObject *Sender)

{

  IdSMTP1->Host = Edit1->Text ;

  IdSMTP1->Port = StrToInt ( Edit2->Text );

  IdSMTP1->AuthenticationType = atNone;         // 先设置为无身份验证能力

  if(!IdSMTP1->Connected()) IdSMTP1->Connect(); //连接SMTP Server

  try{

    if(IdSMTP1->AuthSchemesSupported->IndexOf("LOGIN")>-1)

    // 若支持身份验证

    {

      IdSMTP1->AuthenticationType = atLogin;    // 设置身份验证类型

      AuthType->ItemIndex = 1;

      Edit12->Text = "SMTP Support Authentication";

    }

    else {

      AuthType->ItemIndex = 0;

      Edit12->Text = "SMTP No Authentication";

    }

  }

  __finally {

    IdSMTP1->Disconnect();

  }

}

 

//  根据账号及口令验证身份

void __fastcall TForm1::BitBtn2Click(TObject *Sender)

{

  if(AuthType->ItemIndex==-1) //若没有检查是否支持身份验证

    BitBtn1Click(NULL);

  IdSMTP1->UserId = Edit5->Text ;   //账号

  IdSMTP1->Password = Edit6->Text ; //口令

  if(AuthType->ItemIndex!=1)

  {

    IdSMTP1->AuthenticationType = atNone;

    ShowMessage("Not Surport Authentication !");

    return;

  }

  IdSMTP1->AuthenticationType = atLogin;

  bool auth;

  if(IdSMTP1->Connected()) IdSMTP1->Disconnect();

  IdSMTP1->Connect();   // 连接SMTP服务器

  auth = IdSMTP1->Authenticate(); //验证

  if(auth)  // authtrue, 表成功

     ShowMessage("Authentication Success!");

  else

     ShowMessage("Authentication Fail!");

  IdSMTP1->Disconnect() ;

}

 

//  发送电子邮件

void __fastcall TForm1::BitBtn3Click(TObject *Sender)

{

  if(AuthType->ItemIndex==-1) //若没有检查是否支持身份验证

    BitBtn1Click(NULL);

  IdSMTP1->UserId = Edit5->Text ;   //账号

  IdSMTP1->Password = Edit6->Text ; //口令

  IdSMTP1->AuthenticationType = atNone;

  if(AuthType->ItemIndex==1)

    IdSMTP1->AuthenticationType = atLogin;

  IdMessage1->From->Text = Edit7->Text;                 //发件人地址

  IdMessage1->Recipients->EMailAddresses = Edit8->Text; //收件人地址

  IdMessage1->CCList->EMailAddresses = Edit9->Text;     //抄送收件人地址

  IdMessage1->BccList->EMailAddresses = Edit10->Text;   //暗送收件人地址

  IdMessage1->Subject = Edit11->Text;                   //邮件主题

  IdMessage1->Body->Assign(Memo1->Lines);               //邮件内容

  if(ReturnReciept->Checked) // 设定回执接收者为发件人

     IdMessage1->ReceiptRecipient->Text = IdMessage1->From->Text;

  else // 表示不需回执

     IdMessage1->ReceiptRecipient->Text = "";

  if(IdSMTP1->Connected()) IdSMTP1->Disconnect();

  IdSMTP1->Connect();   // 连接SMTP服务器

  try {

    IdSMTP1->Send(IdMessage1);  // 发送

  } catch(...) { }

  ShowMessage("Mail has been sent. ");

  IdSMTP1->Disconnect() ;

}

 

void __fastcall TForm1::BitBtn4Click(TObject *Sender)

{

  Close();// 退出范例程序

}

4 结语

       需要说明的是,C++ Builder 6.0DELPHI 6.0才开始提供Indy控件。对于C++ Builder 5.x / DELPHI5.x及以下版本,可以到Indy的网站下载其源代码,自行安装Package。这里就不再赘述。

虽然Indy 8.0所提供的身份验证能力,能满足一般邮件服务器身份验证的需要,但它仅实现了简单的LOGIN验证机制,即以用户名和密码进行验证,不支持CRAM-MD5, DIGEST-MD5, KERBEROS_V4, GSSAPI, SKEYTLS等。由于SASL LOGIN没有加密机制,现在已有CRAM-MD5DIGEST-MD5这两类提供较好加密性能的SASL验证机制。Indy 10中也准备提供一个新的Indy SASL控件页,并由TIdSASLList实现这些验证机制。

 

 

参考文献:

1  http://www.nevrona.com/indy/

2  http://www.armware.dk/rfc/ 

3  Borland C++ Builder 5 高级开发技术  李冬、王宏  中国水利出版社 2000.7

4  清宏计算机工作室  C++Builder 编程技巧  机械工业出版社 2001.1

没有评论: