具有 SSL 连接的 SMTP 客户端

SMTP client with SSL connection

我有下一个使用 SMTP 客户端的示例代码,当我尝试通过使用 SSL 的邮件发送邮件时,我得到以下信息:

550 SMTP is available only with SSL or TLS connection enabled.

我如何扩展此示例以实现 SSL,例如通过 gmail 发送电子邮件等...我更喜欢套接字编码并且没有外部库:

int main()
{
  if (FAILED (WSAStartup (MAKEWORD( 1,1 ), &ws))) 
  { 
    printf("Error in WSAStartup(...)\n");
    return 1; }

  // creating socket
  s = socket (AF_INET, SOCK_STREAM, 0);
  if (s == INVALID_SOCKET) 
  { 
    printf("Error in socket(...)\n");
    return 1;  }

  //get server address
  d_addr = gethostbyname ("smtp.mail.ru");
  if (d_addr==NULL)
  {
    printf("Error in gethostbyname(...)\n");
    return 1;  };

  // fill address parameters
  addr.sin_family = AF_INET; 
  addr.sin_addr.s_addr = *((unsigned long *) d_addr->h_addr);
  addr.sin_port = htons (25);

  // connecting...
  if (SOCKET_ERROR == (connect (s, (sockaddr *) &addr,
                       sizeof (addr)))) 
  { 
    printf("Error in connect(...)\n");
    return 1; }

  // waiting from answer from server
  recv(s,text,sizeof(text),0);
  printf("recv - %s", text);

  // sy hello to server
  strcpy(text,"HELO smtp.mail.ru\r\n");
  send(s,text,strlen(text),0);
  printf("send - %s", text);

  // waiting approve from server
  recv(s,text,sizeof(text),0);
  printf("recv - %s", text);

  // set sender
  strcpy(text,"MAIL FROM: sender@mail.ru\r\n");
  send(s,text,strlen(text),0);
  printf("send - %s", text);

  // waiting for approve
  recv(s,text,sizeof(text),0);
  printf("recv - %s", text);

  // set receiver
  strcpy(text,"RCPT TO: receiver@mtu-net.ru\r\n");
  send(s,text,strlen(text),0);
  printf("send - %s", text);

  // waiting for approve
  recv(s,text,sizeof(text),0);
  printf("recv - %s", text);

  // ready to start sending letter
  strcpy(text,"DATA\r\n");
  send(s,text,strlen(text),0);
  printf("send - %s", text);

  // waiting for approve
  recv(s,text,sizeof(text),0);
  printf("recv - %s", text);

  // from whom letter
  strcpy(text,"FROM: sender@mail.ru\r\n");
  send(s,text,strlen(text),0);
  printf("send - %s", text);

  // receiver
  strcpy(text,"TO: receiver@mtu-net.ru\r\n");
  send(s,text,strlen(text),0);
  printf("send - %s", text);

  // letter subject
  strcpy(text,"SUBJECT: test\r\n");
  send(s,text,strlen(text),0);
  printf("send - %s", text);

  // letter text
  strcpy(text,"Hi!\nIt is a message for you\n");
  send(s,text,strlen(text),0);
  printf("send - %s", text);

  // telling that we finish
  strcpy(text,"\r\n.\r\n");
  send(s,text,strlen(text),0);
  printf("send - %s", text);
  recv(s,text,sizeof(text),0);
  printf("recv - %s", text);

  // quit
  strcpy(text,"QUIT");
  send(s,text,strlen(text),0);
  printf("send - %s", text);

  // close socket
  closesocket(s);

  return 0;
}

服务器告诉您发送的 SMTP 命令要求套接字连接首先处于安全状态(尽管服务器应该为此目的使用回复代码 530)。

您正在连接到端口 25,它通常是一个 未加密 SMTP 端口。 加密 SMTP 端口通常是 465(隐式 SSL)和 587(显式 TLS)。

您还在使用 HELO 命令,该命令已过时。您应该改用 EHLO 命令(参见 RFC 2821 Section 4.1.1.1)。这将允许服务器向您发送其功能列表(特别是其当前的安全和身份验证设置)。

在端口 25 和 587 上,SMTP 通信最初是未加密。您可以连接并立即收到服务器的 SMTP 问候语,并发送您的初始 HELO/EHLO 命令。如果 EHLO 回复包含 STARTTLS 能力(参见 RFC 3207),您可以发送 STARTTLS 命令以启动 SSL/TLS 握手到 从那一刻起加密 通信。 STARTTLS 成功后,发送新的 EHLO 命令以获取更新的功能,然后再发送任何后续命令。

在端口 465 上,SMTP 通信始终加密。您必须在连接到服务器后立即开始 SSL/TLS 握手,然后它才能发送其 SMTP 问候语并且您发送初始 HELO/EHLO 命令。

现在,关于 SSL/TLS 会话本身,有许多不同的方法可以实现它,但不要尝试从头开始实现 SSL/TLS!这是非常复杂的,你会弄错的。使用现有的外部 API/library 为您完成所有艰苦的工作。要在现有套接字代码之上添加 SSL/TLS,您可以使用 OpenSSL, or Microsoft's Crypto API (in particular, its SChannel provider), or even WinSock's own Secure Socket extensions. Or, you could replace your manual socket code with a 3rd party library that handles all of the SMTP and SSL/TLS logic for you, such as libcurl.

解决此问题的另一种方法可能是 运行 您的 C 程序在 Scott Gifford 的 sslclient 下(参见 http://www.superscript.com/ucspi-ssl/sslclient.html)。 sslclient 将生成您的程序并在您指定的端口上打开到服务器的 SSL 连接,并将程序的标准输出通过管道传输到服务器,并将服务器的输出通过管道传输到程序的标准输入。这样做的好处是,您可以让 sslclient 完成所有繁重的工作,例如设置套接字和 ssl 等,您可以专注于程序的核心功能。