尝试使用 C 程序通过 SMTP 与电子邮件服务器通信
trying to talk to email server via SMTP with c program
我正在尝试创建一个允许我通过 smtp 与邮件服务器通信的小 C 程序,我创建了这个小程序:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "25"
#define DEFAULT_ADDRESS "smtp.live.com"
int __cdecl main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
char messaggio[100];
char recvbuf[DEFAULT_BUFLEN];
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
while(1){// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo(DEFAULT_ADDRESS, DEFAULT_PORT, &hints, &result);
if ( iResult != 0 ) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
// Attempt to connect to an address until one succeeds
for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
printf("messaggio: ");
gets(messaggio);
fflush(stdin);
printf("%s\n", messaggio);
// Send an initial buffer
iResult = send( ConnectSocket, messaggio, (int)strlen(messaggio), 0 );
if (iResult == SOCKET_ERROR) {
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Bytes Sent: %ld\n", iResult);
// shutdown the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
// Receive until the peer closes the connection
// do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if ( iResult > 0 ){
printf("Bytes received: %d\n", iResult);
printf("messaggio: %s\n", recvbuf);}
else if ( iResult == 0 )
printf("Connection closed\n");
else
printf("recv failed with error: %d\n", WSAGetLastError());
// } while( iResult > 0 );
// cleanu
closesocket(ConnectSocket);
WSACleanup();
}
system("PAUSE");
return 0;
}
当我启动它时,它会连接到 smtp 服务器,然后发送消息(例如 Hello 消息)并收到 220 回复代码,这意味着服务已准备就绪。当我继续输入命令时,它会继续向我发送 220 代码而不继续,而当我错过输入命令时,它就会崩溃。
有谁能帮帮我吗?
p.s。我不相信代码,我只是修改了一些微软的套接字客户端文档来与 smtp.live.com 交谈,显然它确实有效......
[编辑] x2:
好的,感谢 Remy Lebeau,我显然设法解决了这些问题,希望这是最后一个。
编辑代码:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "25"
#define DEFAULT_ADDRESS "smtp.live.com"
int __cdecl main(int argc, char **argv){
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
char messaggio[100];
char recvbuf[DEFAULT_BUFLEN];
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
int success = 1;
iResult = WSAStartup(MAKEWORD(2,2), &wsaData); // Initialize Winsock
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
iResult = getaddrinfo(DEFAULT_ADDRESS, DEFAULT_PORT, &hints, &result); // Resolve the server address and port
if ( iResult != 0 ) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) { // Attempt to connect to an address until one succeeds
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); //Connect to server.
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
while(success){
printf("messaggio: ");
scanf(" %[^\n]", messaggio);
printf("%s\n", messaggio);
iResult = send( ConnectSocket, strcat(messaggio,"\r\n"), (int)strlen(messaggio)+2, 0 ); //Send an initial buffer
if (iResult < 0 || iResult == SOCKET_ERROR) {
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
system("PAUSE");
return 1;
}
printf("Bytes Sent: %ld\n", iResult);
do{ //riceve tutto il possibile dal server
iResult = recv(ConnectSocket, recvbuf, recvbuflen-1, 0);
recvbuf[iResult]='[=11=]';
if ( iResult > 0 ){
printf("Bytes received: %d\n", iResult);
printf("messaggio: %s\n", recvbuf);
}
else{
printf("recv failed with error: %d\n", WSAGetLastError());
success=0;
}
}while(recvbuf[iResult-3]=='\r'&& recvbuf[iResult-2]=='\n');
printf("stop receive\n");
}
iResult = shutdown(ConnectSocket, SD_SEND);
closesocket(ConnectSocket);
WSACleanup();
system("PAUSE");
return 0;
}
现在它确实接受了我的命令并且它确实响应了,我做了 helo
、mail from: <mail>
和 to: <mail>
,此时它说:“530(验证问题代码)必须先发出 STARTTLS 命令。
有办法解决这个问题吗?我必须对我发送的命令进行编码,有没有办法用 winsock 做到这一点?
我是不是快明白问题的重点了,还是这不是问题所在?
您的 SMTP 客户端代码逻辑不完整。
TCP是字节流。无法保证您能够在单个 send()
调用中发送完整的命令,或者您能够在单个 recv()
调用中接收到完整的响应。你必须继续发送,直到你没有什么可发送的,你必须继续阅读,直到没有什么可读的。对于 SMTP,这意味着当您遇到终止每一行的 \r\n
时。
一旦您的底层套接字I/O 开始工作,您仍然没有实现实际的 SMTP 协议。您只是在服务器上抛出任意数据并读取它 returns 的任意数据。 SMTP 有您需要考虑的规则。例如,SMTP 服务器在接受命令之前发送问候语。你不是在读那个问候语。此外,SMTP 响应可能有 1 行或多行,因此您必须单独解析每一行(格式在 RFC 5321) to discover how many lines need to be read. Keep reading until you encounter the last line of a response. Don't send the next command until the previous response has been fully read (unless you want to implement SMTP pipelining 中概述,我不会在这里介绍)。
至于“必须首先发出 STARTTLS 命令”错误,这是不言自明的。您通过不安全的连接发送了 SMTP 命令,但服务器希望通过安全连接发送该命令(例如发送 AUTH
command containing authentication credentials in plain text). The STARTTLS
命令用于激活新的 SSL/TLS 会话以确保安全当前不安全的连接。
您可以使用 OpenSSL, or APIs like Microsoft's SChannel 等库在您的套接字上实现实际的 SSL/TLS。这超出了这个问题的范围。如果您在实现 SSL/TLS 方面需要帮助,有很多关于该主题的书籍和教程。
综上所述,试试这样的东西:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#pragma command(lib, "mswsock.lib")
//#pragma command(lib, "advapi32.lib")
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "25"
#define DEFAULT_ADDRESS "smtp.live.com"
char *recvbuf = NULL;
int recvbuflen = 0;
int recvbufused = 0;
int sendData(SOCKET s, char *buffer, int buflen)
{
int iResult;
while (buflen > 0)
{
// TODO: if the connection is secure, encrypt the data being sent...
iResult = send(s, buffer, buflen, 0);
if (iResult == SOCKET_ERROR)
{
printf("send failed with error: %d\n", WSAGetLastError());
return -1;
}
buffer += iResult;
buflen -= iResult;
}
return 0;
}
int sendLine(SOCKET s, char *line)
{
int iResult = sendData(s, line, strlen(line));
if (iResult == 0)
iResult = sendData(s, "\r\n", 2);
return iResult;
}
int readLine(SOCKET s, char **line)
{
char buf[DEFAULT_BUFLEN];
int iResult;
int offset = 0, len, total;
char *p;
*line = NULL;
do
{
// check if the line terminator is already in the buffer
p = memchr(recvbuf+offset, '\n', recvbufused-offset);
if (p != NULL)
{
++p;
total = (p - recvbuf);
// check if the line terminator is actually "\r\n"
len = total - 1;
if ((len > 0) && (recvbuf[len-1] == '\r'))
--len;
// extract the line and remove it from the buffer
*line = malloc(len+1);
if (*line == NULL)
{
printf("Unable to allocate memory! Bytes: %d\n", len+1);
return -1;
}
memcpy(*line, recvbuf, len);
(*line)[len] = 0;
memmove(recvbuf, recvbuf+total, recvbufused-total);
recvbufused -= total;
printf("%s\n", *line);
return len;
}
// line terminator is not in the buffer, keep reading
// do not search the buffer that was already searched
offset = recvbufused;
// read more data from the socket
iResult = recv(s, buf, sizeof(buf), 0);
if (iResult <= 0)
{
if (iResult == 0)
printf("server closed the connection\n");
else
printf("recv failed with error: %d\n", WSAGetLastError());
break;
}
// TODO: if the connection is secure, decrypt the data that was received...
// if the buffer is too small for the new data, grow it
if ((recvbufused+iResult) > recvbuflen)
{
len = (recvbufused+iResult) + DEFAULT_BUFLEN;
p = realloc(recvbuf, len);
if (p == NULL)
{
printf("Unable to allocate memory! Bytes: %d\n", len);
break;
}
recvbuf = p;
recvbuflen = len;
}
// now add the data to the buffer
memcpy(recvbuf+recvbufused, buf, iResult);
recvbufused += iResult;
}
while (1);
return -1;
}
int readResponse(SOCKET s)
{
char *line, term[5];
int len, code = 0;
// read the first line
len = readLine(s, &line);
if (len == -1)
return -1;
// check if the response has multiple lines
if ((len > 3) && (line[3] == '-'))
{
// keep reading until the last line is received
memcpy(term, line, 3);
term[3] = ' ';
term[4] = '[=10=]';
do
{
free(line);
len = readLine(s, &line);
if (len == -1)
return -1;
}
while ((len > 3) && (strncmp(line, term, 4) != 0));
}
// now extract the response code and return it
if (len > 3) line[3] = '[=10=]';
code = strtol(line, NULL, 10);
free(line);
if (code == 0)
return -1;
return code;
}
int __cdecl main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo hints, *result, *ptr;
char messaggio[100];
int iResult;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0)
{
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo(DEFAULT_ADDRESS, DEFAULT_PORT, &hints, &result);
if (iResult != 0)
{
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
// Attempt to connect to an address until one succeeds
for(ptr=result; ptr != NULL; ptr=ptr->ai_next)
{
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET)
{
printf("socket failed with error: %ld\n", WSAGetLastError());
break;
}
//Connect to server.
iResult = connect(ConnectSocket, ptr->ai_addr, ptr->ai_addrlen);
if (iResult == 0)
break;
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET)
{
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
printf("Connected to server\n");
// read the greeting
iResult = readResponse(ConnectSocket);
if (iResult == 220)
{
do
{
// ask the user for a command
printf("messaggio: ");
if (!fgets(messaggio, sizeof(messaggio), stdin))
break;
// remove the line terminator if present
iResult = strlen(messaggio);
if ((iResult > 0) && (messaggio[iResult-1] == '\n'))
messaggio[iResult-1] = '[=10=]';
// send the command
iResult = sendLine(ConnectSocket, messaggio);
if (iResult == -1)
break;
// read the response
iResult = readResponse(ConnectSocket);
if (iResult == -1)
break;
// need to stop now?
if (strcmp(messaggio, "QUIT") == 0)
break;
// need to turn on SSL/TLS now?
if ((strcmp(messaggio, "STARTTLS") == 0) && (iResult == 220))
{
// TODO: activate SSL/TLS, stop if failed...
}
}
while(1);
}
shutdown(ConnectSocket, SD_SEND);
do
{
iResult = recv(ConnectSocket, messaggio, sizeof(messaggio), 0);
}
while (iResult > 0);
closesocket(ConnectSocket);
WSACleanup();
free(recvbuf);
system("PAUSE");
return 0;
}
我正在尝试创建一个允许我通过 smtp 与邮件服务器通信的小 C 程序,我创建了这个小程序:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "25"
#define DEFAULT_ADDRESS "smtp.live.com"
int __cdecl main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
char messaggio[100];
char recvbuf[DEFAULT_BUFLEN];
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
while(1){// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo(DEFAULT_ADDRESS, DEFAULT_PORT, &hints, &result);
if ( iResult != 0 ) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
// Attempt to connect to an address until one succeeds
for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
printf("messaggio: ");
gets(messaggio);
fflush(stdin);
printf("%s\n", messaggio);
// Send an initial buffer
iResult = send( ConnectSocket, messaggio, (int)strlen(messaggio), 0 );
if (iResult == SOCKET_ERROR) {
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Bytes Sent: %ld\n", iResult);
// shutdown the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
// Receive until the peer closes the connection
// do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if ( iResult > 0 ){
printf("Bytes received: %d\n", iResult);
printf("messaggio: %s\n", recvbuf);}
else if ( iResult == 0 )
printf("Connection closed\n");
else
printf("recv failed with error: %d\n", WSAGetLastError());
// } while( iResult > 0 );
// cleanu
closesocket(ConnectSocket);
WSACleanup();
}
system("PAUSE");
return 0;
}
当我启动它时,它会连接到 smtp 服务器,然后发送消息(例如 Hello 消息)并收到 220 回复代码,这意味着服务已准备就绪。当我继续输入命令时,它会继续向我发送 220 代码而不继续,而当我错过输入命令时,它就会崩溃。
有谁能帮帮我吗?
p.s。我不相信代码,我只是修改了一些微软的套接字客户端文档来与 smtp.live.com 交谈,显然它确实有效......
[编辑] x2:
好的,感谢 Remy Lebeau,我显然设法解决了这些问题,希望这是最后一个。
编辑代码:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "25"
#define DEFAULT_ADDRESS "smtp.live.com"
int __cdecl main(int argc, char **argv){
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
char messaggio[100];
char recvbuf[DEFAULT_BUFLEN];
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
int success = 1;
iResult = WSAStartup(MAKEWORD(2,2), &wsaData); // Initialize Winsock
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
iResult = getaddrinfo(DEFAULT_ADDRESS, DEFAULT_PORT, &hints, &result); // Resolve the server address and port
if ( iResult != 0 ) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) { // Attempt to connect to an address until one succeeds
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); //Connect to server.
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
while(success){
printf("messaggio: ");
scanf(" %[^\n]", messaggio);
printf("%s\n", messaggio);
iResult = send( ConnectSocket, strcat(messaggio,"\r\n"), (int)strlen(messaggio)+2, 0 ); //Send an initial buffer
if (iResult < 0 || iResult == SOCKET_ERROR) {
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
system("PAUSE");
return 1;
}
printf("Bytes Sent: %ld\n", iResult);
do{ //riceve tutto il possibile dal server
iResult = recv(ConnectSocket, recvbuf, recvbuflen-1, 0);
recvbuf[iResult]='[=11=]';
if ( iResult > 0 ){
printf("Bytes received: %d\n", iResult);
printf("messaggio: %s\n", recvbuf);
}
else{
printf("recv failed with error: %d\n", WSAGetLastError());
success=0;
}
}while(recvbuf[iResult-3]=='\r'&& recvbuf[iResult-2]=='\n');
printf("stop receive\n");
}
iResult = shutdown(ConnectSocket, SD_SEND);
closesocket(ConnectSocket);
WSACleanup();
system("PAUSE");
return 0;
}
现在它确实接受了我的命令并且它确实响应了,我做了 helo
、mail from: <mail>
和 to: <mail>
,此时它说:“530(验证问题代码)必须先发出 STARTTLS 命令。
有办法解决这个问题吗?我必须对我发送的命令进行编码,有没有办法用 winsock 做到这一点?
我是不是快明白问题的重点了,还是这不是问题所在?
您的 SMTP 客户端代码逻辑不完整。
TCP是字节流。无法保证您能够在单个 send()
调用中发送完整的命令,或者您能够在单个 recv()
调用中接收到完整的响应。你必须继续发送,直到你没有什么可发送的,你必须继续阅读,直到没有什么可读的。对于 SMTP,这意味着当您遇到终止每一行的 \r\n
时。
一旦您的底层套接字I/O 开始工作,您仍然没有实现实际的 SMTP 协议。您只是在服务器上抛出任意数据并读取它 returns 的任意数据。 SMTP 有您需要考虑的规则。例如,SMTP 服务器在接受命令之前发送问候语。你不是在读那个问候语。此外,SMTP 响应可能有 1 行或多行,因此您必须单独解析每一行(格式在 RFC 5321) to discover how many lines need to be read. Keep reading until you encounter the last line of a response. Don't send the next command until the previous response has been fully read (unless you want to implement SMTP pipelining 中概述,我不会在这里介绍)。
至于“必须首先发出 STARTTLS 命令”错误,这是不言自明的。您通过不安全的连接发送了 SMTP 命令,但服务器希望通过安全连接发送该命令(例如发送 AUTH
command containing authentication credentials in plain text). The STARTTLS
命令用于激活新的 SSL/TLS 会话以确保安全当前不安全的连接。
您可以使用 OpenSSL, or APIs like Microsoft's SChannel 等库在您的套接字上实现实际的 SSL/TLS。这超出了这个问题的范围。如果您在实现 SSL/TLS 方面需要帮助,有很多关于该主题的书籍和教程。
综上所述,试试这样的东西:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#pragma command(lib, "mswsock.lib")
//#pragma command(lib, "advapi32.lib")
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "25"
#define DEFAULT_ADDRESS "smtp.live.com"
char *recvbuf = NULL;
int recvbuflen = 0;
int recvbufused = 0;
int sendData(SOCKET s, char *buffer, int buflen)
{
int iResult;
while (buflen > 0)
{
// TODO: if the connection is secure, encrypt the data being sent...
iResult = send(s, buffer, buflen, 0);
if (iResult == SOCKET_ERROR)
{
printf("send failed with error: %d\n", WSAGetLastError());
return -1;
}
buffer += iResult;
buflen -= iResult;
}
return 0;
}
int sendLine(SOCKET s, char *line)
{
int iResult = sendData(s, line, strlen(line));
if (iResult == 0)
iResult = sendData(s, "\r\n", 2);
return iResult;
}
int readLine(SOCKET s, char **line)
{
char buf[DEFAULT_BUFLEN];
int iResult;
int offset = 0, len, total;
char *p;
*line = NULL;
do
{
// check if the line terminator is already in the buffer
p = memchr(recvbuf+offset, '\n', recvbufused-offset);
if (p != NULL)
{
++p;
total = (p - recvbuf);
// check if the line terminator is actually "\r\n"
len = total - 1;
if ((len > 0) && (recvbuf[len-1] == '\r'))
--len;
// extract the line and remove it from the buffer
*line = malloc(len+1);
if (*line == NULL)
{
printf("Unable to allocate memory! Bytes: %d\n", len+1);
return -1;
}
memcpy(*line, recvbuf, len);
(*line)[len] = 0;
memmove(recvbuf, recvbuf+total, recvbufused-total);
recvbufused -= total;
printf("%s\n", *line);
return len;
}
// line terminator is not in the buffer, keep reading
// do not search the buffer that was already searched
offset = recvbufused;
// read more data from the socket
iResult = recv(s, buf, sizeof(buf), 0);
if (iResult <= 0)
{
if (iResult == 0)
printf("server closed the connection\n");
else
printf("recv failed with error: %d\n", WSAGetLastError());
break;
}
// TODO: if the connection is secure, decrypt the data that was received...
// if the buffer is too small for the new data, grow it
if ((recvbufused+iResult) > recvbuflen)
{
len = (recvbufused+iResult) + DEFAULT_BUFLEN;
p = realloc(recvbuf, len);
if (p == NULL)
{
printf("Unable to allocate memory! Bytes: %d\n", len);
break;
}
recvbuf = p;
recvbuflen = len;
}
// now add the data to the buffer
memcpy(recvbuf+recvbufused, buf, iResult);
recvbufused += iResult;
}
while (1);
return -1;
}
int readResponse(SOCKET s)
{
char *line, term[5];
int len, code = 0;
// read the first line
len = readLine(s, &line);
if (len == -1)
return -1;
// check if the response has multiple lines
if ((len > 3) && (line[3] == '-'))
{
// keep reading until the last line is received
memcpy(term, line, 3);
term[3] = ' ';
term[4] = '[=10=]';
do
{
free(line);
len = readLine(s, &line);
if (len == -1)
return -1;
}
while ((len > 3) && (strncmp(line, term, 4) != 0));
}
// now extract the response code and return it
if (len > 3) line[3] = '[=10=]';
code = strtol(line, NULL, 10);
free(line);
if (code == 0)
return -1;
return code;
}
int __cdecl main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo hints, *result, *ptr;
char messaggio[100];
int iResult;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0)
{
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo(DEFAULT_ADDRESS, DEFAULT_PORT, &hints, &result);
if (iResult != 0)
{
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
// Attempt to connect to an address until one succeeds
for(ptr=result; ptr != NULL; ptr=ptr->ai_next)
{
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET)
{
printf("socket failed with error: %ld\n", WSAGetLastError());
break;
}
//Connect to server.
iResult = connect(ConnectSocket, ptr->ai_addr, ptr->ai_addrlen);
if (iResult == 0)
break;
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET)
{
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
printf("Connected to server\n");
// read the greeting
iResult = readResponse(ConnectSocket);
if (iResult == 220)
{
do
{
// ask the user for a command
printf("messaggio: ");
if (!fgets(messaggio, sizeof(messaggio), stdin))
break;
// remove the line terminator if present
iResult = strlen(messaggio);
if ((iResult > 0) && (messaggio[iResult-1] == '\n'))
messaggio[iResult-1] = '[=10=]';
// send the command
iResult = sendLine(ConnectSocket, messaggio);
if (iResult == -1)
break;
// read the response
iResult = readResponse(ConnectSocket);
if (iResult == -1)
break;
// need to stop now?
if (strcmp(messaggio, "QUIT") == 0)
break;
// need to turn on SSL/TLS now?
if ((strcmp(messaggio, "STARTTLS") == 0) && (iResult == 220))
{
// TODO: activate SSL/TLS, stop if failed...
}
}
while(1);
}
shutdown(ConnectSocket, SD_SEND);
do
{
iResult = recv(ConnectSocket, messaggio, sizeof(messaggio), 0);
}
while (iResult > 0);
closesocket(ConnectSocket);
WSACleanup();
free(recvbuf);
system("PAUSE");
return 0;
}