设为首页 友情链接
在线留言 发表文章
加入收藏 广告联系

刺猬首页

| 专案技术 | 网络技术 | 图形图象 | 网络编程 | 网页设计 | 操作系统 | 服务器 | 技术白皮书 | 在线实验室 | 刺猬论坛 |
小说专版  | 数据库 | 设计赏析 | 存储频道 | 网络安全 | 私服架设 |  Solaris | 网站评估 | PC维护技巧 | 下载中心 | 博 客 |
专   题: | Linux | java | cisco | 防病毒 | 刀片 | SOA | iscsi | ASP.NET | SQL | Oracle |
您现在的位置: IT公社 IT community >> Linux专题 >> Linux 软件开发 >> 教程正文 用户登录 新用户注册
专 题 栏 目
最 新 热 门
最 新 推 荐
相 关 文 章
Linux上实现Socket的多进…
CpuMemSets在Linux操作系…
Perl实现eth0挂起后自动…
用Perl语言进行Socket编…
如何利用Perl开发Intern…
BSD Socket 简易入门手册
两种特殊的Java容器类Li…
提高Linux上socket性能-…
Fedora Core 5.0 用Xen虚…
netbeans的ide.cfg文件配…
  fetchmail代码阅读笔记---ESMTP的认证方式         
fetchmail代码阅读笔记---ESMTP的认证方式
 

0 引言
fetchmail是Eric S. Raymond组织编写的一款全功能的 IMAP 和 POP 客户程序. 它允许用户自动地从远程的 IMAP 和 POP 服务器上下载邮件, 并转发到指定邮箱中.
本文主要通过对其中的smtp.c文件中描述的ESMTP认证函数( SMTP_auth )进行分析, 并由此对ESMTP中的认证方式有一个大致的介绍.

1 概述
本文主要内容是分析fetchmail的代码, 而非详细叙述ESMTP的认证过程. 相关内容可以参看[1] [2] [3] 以及相关RFC.
fetchmail实现的ESMTP认证方式包括: CRAM-MD5 PLAIN和LOGIN三种. 本文所给出的代码不能单独编译. 本文所用的fetchmail版本为6.3.4

2 精简代码及其注释

这里首先把源代码中用到的一些重要函数的功能介绍一下, 它们的具体实现可以不用关心:

o SockPrintf
函数原型: int SockPrintf(int sock, const char *format, ...) ;
函数定义所在文件: $FETCHMAIL/socket.c( $FETCHMAIL表示fechtmail所在目录.下同 )
函数功能: 根据format指定的格式, 向文件描述符sock写入字符串. 该函数的参数列表是可变长的. 具体使用方法与printf相同.
函数返回值: 返回写入到sock的字节数.发生错误返回-1

o SockRead
函数原型: int SockWrite(int sock, char *buf, int size);
函数定义所在文件: $FETCHMAIL/socket.c
函数功能: 从文件描述符sock读入最多size个字节的信息到buf起始的内存区域.
函数返回值: 返回真正读入到buf的字节数. 发生错误返回-1

o from64tobits
函数原型: int from64tobits(void *out_, const char *in, int maxlen);
函数定义所在文件: $FETCHMAIL/base64.c
函数功能: 利用base64解码算法, 将字符串in解码. 将解码后的信息存入out_指定的内存区域中. 最多写入maxlen个字节到out_指定的内存区域.存入到out_内的信息不以NULL结尾
函数返回值: 返回实际写入到out_内的字节数. 发生错误返回-1

o to64frombits
函数原型: void to64frombits(char *out, const void *in_, int inlen);
函数定义所在文件: $FETCHMAIL/base64.c
函数功能: 将指定信息进行base64编码. in_表示指定信息存储的内存区域的起始地址, inlen表示指定信息的字节数. out表示将指定信息编码后存储到的位置. 存入到out内的信息是以NULL结尾字符串
函数返回值: 无

o hmac_md5
函数原型: void hmac_md5 (char *password, size_t pass_len,
char *challenge, size_t chal_len, unsigned char *response, size_t resp_len);
函数定义所在文件: $FETCHMAIL/cram.c
函数功能: 将password和challenge按照hmac-md5[2]指定的方法计算出摘要, 并将摘要存储在response中. pass_len chal_len和resp_len分别表示password challenge和response的长度
函数返回值: 无

o SMTP_ok
函数原型: int SMTP_ok(int sock, char smtp_mode);
函数定义所在文件: $FETCHMAIL/smtp.c
函数功能: 读取SMTP服务器传来的状态信息. smtp_mode表示使用的协议是SMTP还是ESMTP.
函数返回值: 返回当前服务器状态值

o report
函数原型: void report (FILE *errfp, const char *message, ...);
函数定义所在文件: $FETCHMAIL/report.c
函数功能: 向指定文件写入信息. 主要用于程序报错
函数返回值: 无

这里给出的代码是精简之后的代码. 删除了一些调试信息和原有的注释.
 
static void SMTP_auth(int sock, char smtp_mode, char *username, char *password, char *buf)
/* 函数参数说明:
 * sock: 用于通信的套接字
 * smtp_mode: 表示使用的协议是SMTP还是ESMTP. 可以忽略此参数
 * username: 用户名
 * passowrd: 密码
 * buf: 向服务器发送EHLO后服务器的返回信息. 用于判断采用的认证方式
 */

 int c;
 char *p = 0;
 char b64buf[512];
 char tmp[512];
/* 变量声明 */
 if (!username || !password) return;
/* 非法参数 */
 memset(b64buf, 0, sizeof(b64buf));
 memset(tmp, 0, sizeof(tmp));
/* 临时变量初始化 */
 if (strstr(buf, "CRAM-MD5")) {
/* 采用CRAM-MD5的认证方式[3] */
  unsigned char digest[16];
 /* 临时变量. 用于存储hmac_md5计算出的摘要 */
  memset(digest, 0, sizeof(digest));
  SockPrintf(sock, "AUTH CRAM-MD5\r\n");
  SockRead(sock, smtp_response, sizeof(smtp_response) - 1);
  strncpy(tmp, smtp_response, sizeof(tmp));
 
  tmp[sizeof(tmp)-1] = '\0';
 /* 向服务器提出使用CRAM-MD5认证方式. 读取服务器的回复信息,
  * 并把回复信息存储到tmp中
  */
  if (strncmp(tmp, "334 ", 4)) {
   SMTP_auth_error(sock, GT_("Server rejected the AUTH command.\n"));
   return;
  }
 /* 服务器返回状态码不等于334, 表示服务器端拒绝认证方式. 通过SMTP_auth_error函数报错并返回 */
  p = strchr(tmp, ' ');
  p++;
 /* 跳过服务器返回的状态码 */
  if (from64tobits(b64buf, p, sizeof(b64buf) - 1) <= 0) {
   SMTP_auth_error(sock, GT_("Bad base64 reply from server.\n"));
   return;
  }
 /* 服务器发送来消息格式是:
  * 状态码 字符串CRLF
  * 状态码在前面p=strchr(tmp,' ');p++两句执行后已经被跳过
  * CRLF代表"\r\n"
  * 此时p指向了一个字符串.
  * 该字符串是由服务器随机产生的, 并且已经经过base64编码
  * from64tobits的作用就是对该字符串进行解码. 如果发生错误则报错
  */
  hmac_md5(password, strlen(password),
    b64buf, strlen(b64buf), digest, sizeof(digest));
 /* 将服务器返回的随机字符串和用户的密码通过hmac-md5[3]算法算出摘要. 并将摘要存储在digest中 */
  snprintf(tmp, sizeof(tmp),
  "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
  username,  digest[0], digest[1], digest[2], digest[3],
  digest[4], digest[5], digest[6], digest[7], digest[8],
  digest[9], digest[10], digest[11], digest[12], digest[13],
  digest[14], digest[15]);
 /* 此函数执行完后, tmp中则存储着用户名, 之后一个空格,
  * 然后是用16进制表示的摘要( 通过hmac-md5算法计算出的 ). 摘要用的16进制表示需使用小写字母.
  */
  to64frombits(b64buf, tmp, strlen(tmp));
  SockPrintf(sock, "%s\r\n", b64buf);
 /* 将tmp中存储的内容进行base64编码, 并传递给服务器端 */
  SMTP_ok(sock, smtp_mode);
 /* 通过SMTP_ok函数查看是否正常通过认证 */
 }
 else if (strstr(buf, "PLAIN")) {
/* 采用PLAIN认证方式 */
  int len;
  snprintf(tmp, sizeof(tmp), "^%s^%s", username, password);
 /* 将用户名和密码中间用'^'字符分割, 并将'^'字符至于用户名前 */
  len = strlen(tmp);
  for (c = len - 1; c >= 0; c--)
  {
   if (tmp[c] == '^')
    tmp[c] = '\0';
  }
 /* 将tmp中的'^'内容转换成'\0'. 换句话说, tmp将是以'\0'开头,
  * 而用户名和密码之间有一个'\0'
  */
  to64frombits(b64buf, tmp, len);
  SockPrintf(sock, "AUTH PLAIN %s\r\n", b64buf);
 /* 将tmp中存储的内容前加上"AUTH PLAIN "后进行base64编码, 并传递给服务器端 */
  SMTP_ok(sock, smtp_mode);
 /* 通过SMTP_ok函数查看是否正常通过认证 */
 }
 else if (strstr(buf, "LOGIN")) {
/* 采用LOGIN认证方式 */
  SockPrintf(sock, "AUTH LOGIN\r\n");
  SockRead(sock, smtp_response, sizeof(smtp_response) - 1);
  strncpy(tmp, smtp_response, sizeof(tmp));
  tmp[sizeof(tmp)-1] = '\0';
 /* 向服务器提出使用LOGIN认证方式. 读取服务器的回复信息,
  * 并把回复信息存储到tmp中
  */
  if (strncmp(tmp, "334 ", 4)) {
   SMTP_auth_error(sock, GT_("Server rejected the AUTH command.\n"));
   return;
  }
 /* 服务器返回状态码不等于334, 表示服务器端拒绝认证方式. 通过SMTP_auth_error函数报错并返回 */
  p = strchr(tmp, ' ');
  p++;
 /* 跳过服务器返回的状态码 */
  if (from64tobits(b64buf, p, sizeof(b64buf) - 1) <= 0) {
   SMTP_auth_error(sock, GT_("Bad base64 reply from server.\n"));
   return;
  }
 /* 服务器返回一个由base64编码的字符串.
  * 该字符串在之后的认证过程中不会用到.
  *  因此此处仅仅是查看该字符串的合法性, 而不必对其内容进行分析
  */
  to64frombits(b64buf, username, strlen(username));
  SockPrintf(sock, "%s\r\n", b64buf);
 /* 将用户名进行base64编码后发送给服务器.*/
  SockRead(sock, smtp_response, sizeof(smtp_response) - 1);
  strncpy(tmp, smtp_response, sizeof(tmp));
  tmp[sizeof(tmp)-1] = '\0';
 /* 读取服务器的回复信息, 并并把回复信息转存到tmp中 */
  p = strchr(tmp, ' ');
 /* 跳过服务器的状态码 */
  if (!p) {
   SMTP_auth_error(sock, GT_("Bad base64 reply from server.\n"));
   return;
  }
 /* 服务器返回的格式非法, 程序报错 */
  p++;
  memset(b64buf, 0, sizeof(b64buf));
  if (from64tobits(b64buf, p, sizeof(b64buf) - 1) <= 0) {
   SMTP_auth_error(sock, GT_("Bad base64 reply from server.\n"));
   return;
  }
 /* 服务器返回的信息格式是一个状态码加一个空格, 之后是一个由base64编码后的字符串
  * 同样, 这个字符串在下面的认证过程中不会被用到, 因此此处也仅仅是检查其合法性
  */
  to64frombits(b64buf, password, strlen(password));
  SockPrintf(sock, "%s\r\n", b64buf);
 /* 将密码进行base64编码后发送给服务器 */
  SMTP_ok(sock, smtp_mode);
 /* 通过SMTP_ok函数查看是否正常通过认证 */
 }
 return;
}
3 认证过程分析
fetchmail中实现了ESMTP的三个认证方式: CRAM-MD5 PLAIN和LOGIN. 下面对这三种认证方式的流程进行一个总结.

CRAM-MD5:
客户端首先向服务器端发送一个字符串: "AUTH[SPACE]CRAM-MD5[CRLF]".其中[SPACE]表示一个空格;[CRLF]表示回车换行符, 即"\r\n". 下同.
如果服务器拒绝认证方式, 则返回一个字符串: "[NUM][SPACE]str". 其中[NUM]为三位数字的服务器状态码( 下同 ).当状态码不等于334表示拒绝认证方式. str是一个服务器端定义的字符串, 用于描述错误.
如果服务器接受认证方式, 则返回一个字符串: "[NUM][SPACE]str_base64".其中str_base64是一个随机字符串经过base64编码后的字符串.
客户端收到服务器的信息后, 执行如下操作:
首先利用base64解码算法将str_base64解码. 解码后的字符串存入str
* call base64_decode
* input str_base64
* output str

之后利用hmac-md5算法计算出一个摘要digest
* call hmac_md5
* input password, str
* output digest

将摘要用小写字母的16进制表示, 并把字符串"username "与它合并, 成为字符串tmp
* string tmp = 'username digest'

将tmp进行base64编码
* call base64_encode
* input tmp
* output tmp_base64
最后, 客户端向服务器端发送字符串: "tmp_base64[CRLF]"
根据服务器的返回判断是否认证成功

PLAIN:
客户端首先做如下操作:
* string tmp = '^username^password'
* for each character in tmp
* tmp[i] = '\0' where tmp[i] == '^'
* call base64_encode
* input tmp
* output tmp_base64
最后, 客户端向服务器端发送字符串: "tmp_base64[CRLF]"
根据服务器的返回判断是否认证成功

LOGIN:
客户端首先

[1] [2] 下一页

频道声明:本频道的文章除部分特别声明禁止转载的专稿外,可以自由转载.但请务必注明出出处和原始作者 文章版权归本频道与文章作者所有.对于被频道转载文章的个人和网站,我们表示深深的谢意。

原始作者:佚名 录入时间:2007-1-2 3:23:13
信息来源:不详 投稿信箱:itqoo@126.com
教程录入:itqoo    责任编辑:itqoo 
  • 上一个教程:

  • 下一个教程:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    - 关于我们 - 合作伙伴 - 友情链接 - 广告刊登 - 投稿热线 - 在线留言版权声明联系方式 -
    IT公社版权所有 粤ICP备05127012号
    Copyrigh@2005-2006 itqoo.com.Inc All Rights Reserved  推荐分辨率 1024*768
    联系站长:E-Mail:itqoo@126.com     MSN:urchincc@hotmail.com    QQ:点击这里给我发消息
    特别感谢:亿太网络提供空间支持