c - 尝试使用 C 程序通过 SMTP 与电子邮件服务器通信

标签 c sockets email smtp

我正在尝试创建一个允许我通过 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 代码而不继续,而当我错过键入命令时,它只会崩溃。

有谁能帮帮我吗?

附注我不相信代码,我只是修改了一些微软的套接字客户端文档来与 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]='\0';
            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 协议(protocol)。您只是在服务器上抛出任意数据并读取它返回的任意数据。 SMTP 有您需要考虑的规则。例如,SMTP 服务器在接受命令之前发送问候语。你不是在读那个问候语。此外,SMTP 响应可能有 1 行或多行,因此您必须单独解析每一行(格式在 RFC 5321 中概述)以发现需要读取多少行。继续阅读,直到遇到响应的最后一行。在完全读取上一个响应之前不要发送下一个命令(除非你想实现 SMTP pipelining ,我不会在这里讨论)。

至于“必须首先发出 STARTTLS 命令”错误,这是不言自明的。您通过不安全的连接发送了 SMTP 命令,但服务器希望通过安全连接发送该命令(例如发送包含纯文本身份验证凭据的 AUTH 命令时)。 STARTTLS命令用于激活新的 SSL/TLS session 以保护当前不安全的连接。

您可以使用像 OpenSSL 这样的库,或类似 Microsoft 的 API 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] = '\0';

        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] = '\0';
    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] = '\0';
    
            // 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;
}

关于c - 尝试使用 C 程序通过 SMTP 与电子邮件服务器通信,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37973097/

相关文章:

c - 在 C 中通过引用自动传递参数

ruby-on-rails - 使用 Mikel 的 Mail gem 访问未解析的电子邮件地址

javascript - 如何检查来自用户收件箱的网页请求

html - CSS 按钮不会在电子邮件模板中呈现

c - 在旧编译器下,求和扫描的数字有时会失败

c - 在c中添加浮点值

c++ - 在visual studio c中调试时跳出for循环中的函数

java - SocketInitiator 的队列容量使用情况 (QuickFIX/J)

Java - 无法写入第二个文件

c# - 我如何知道服务器已使用套接字通过 UDP 关闭