通过 C 套接字编程的邮件格式
Posted
技术标签:
【中文标题】通过 C 套接字编程的邮件格式【英文标题】:Mail format via C socket programming 【发布时间】:2018-07-29 10:50:09 【问题描述】:我正在学习 C 中的套接字编程,而学习某些东西的最佳方法是实现它。所以我编写了一个简单的程序,通过 C 套接字编程发送带有附件和一些文本正文的邮件。该代码到目前为止是成功的,只有一个例外 - 格式化。 MIME 标头显示在邮件正文中(附截图),邮件主题全部混乱。
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#define SMTP_PORT "25"
#define DEFAULT_BUFLEN 1024
int sendData(SOCKET* socket, const char* data)
int iResult;
printf("%s", data);
iResult = send(*socket, data, (int)strlen(data), 0);
if (iResult == SOCKET_ERROR)
printf("send failed (msg: %s): %d\n", data, WSAGetLastError());
return iResult;
void recvData(SOCKET* socket)
int recvbuflen = DEFAULT_BUFLEN;
char recvbuf[DEFAULT_BUFLEN];
int iResult;
iResult = recv(*socket, recvbuf, recvbuflen - 1, 0);
printf("In recvData function: %d\n", iResult);
if (iResult > 0)
recvbuf[iResult] = '\0';
printf("%s", recvbuf);
else if (iResult == 0)
printf("Connection closed\n");
else
printf("recv failed: %d\n", WSAGetLastError());
int main(int argc, char** argv)
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0)
printf("WSAStartup failed: %d\n", iResult);
return 1;
struct addrinfo *result = NULL;
struct addrinfo *ptr = NULL;
struct addrinfo hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
iResult = getaddrinfo("mail.sharklasers.com", SMTP_PORT, &hints, &result);
if (iResult != 0)
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
SOCKET ConnectSocket = INVALID_SOCKET;
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: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
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;
sendData(&ConnectSocket, "HELO mail.sharklasers.com\r\n");
recvData(&ConnectSocket);
sendData(&ConnectSocket, "MAIL FROM: psgibrxp@sharklasers.com\r\n");
recvData(&ConnectSocket);
sendData(&ConnectSocket, "RCPT TO: psgibrxp@sharklasers.com\r\n");
recvData(&ConnectSocket);
sendData(&ConnectSocket, "DATA\r\n");
recvData(&ConnectSocket);
sendData(&ConnectSocket, "Subject:This is subject of my mail\r\n");
sendData(&ConnectSocket, "And this is text\r\n");
sendData(&ConnectSocket, "MIME-Version: 1.0\r\n");
sendData(&ConnectSocket, "Content-Type:multipart/mixed;boundary=\"KkK170891tpbkKk__FV_KKKkkkjjwq\"\r\n");
sendData(&ConnectSocket, "--KkK170891tpbkKk__FV_KKKkkkjjwq\r\n");
sendData(&ConnectSocket, "Content-Transfer-Encoding: quoted-printable\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain;name=\"details.txt\"\r\n");
sendData(&ConnectSocket, "Content-Disposition: attachment; filename=\"details.txt\"\r\n");
sendData(&ConnectSocket, "\r\n\r\n");
FILE* MailFilePtr = fopen("details.txt", "r");
if (MailFilePtr == NULL)
printf("Error opening attachment\n");
char FileBuffer[1024];
char buf[1024];
memset(FileBuffer, 0, sizeof(FileBuffer));
while (fgets(FileBuffer, sizeof(FileBuffer), MailFilePtr))
sprintf(buf, "%s", FileBuffer);
buf[strlen(buf) - 1] = 0;
sendData(&ConnectSocket, buf);
memset(FileBuffer, 0, sizeof(FileBuffer));
memset(buf, 0, sizeof(buf));
fclose(MailFilePtr);
sendData(&ConnectSocket, "\r\n\r\n--KkK170891tpbkKk__FV_KKKkkkjjwq--\r\n\r\n");
sendData(&ConnectSocket, ".\r\n");
recvData(&ConnectSocket);
sendData(&ConnectSocket, "QUIT\r\n");
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR)
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
closesocket(ConnectSocket);
WSACleanup();
return 0;
问题: 如何获得正确格式的邮件正文?我想我缺少一些 SMTP 命令或命令序列,但我不确定。
注意
-
我现在使用 Guerrilla Mail,因为 Gmail 需要 SSL\TLS 进行通信(如果我错了,请纠正我)。我将为此使用 openssl 库。
这不是最终代码。我必须做一些代码清理。不过,也欢迎提出代码审查建议。
我是来学习的,欢迎任何与 C 语言和网络编程相关的链接、参考书目。
编辑
到目前为止,已编辑的代码基于 cmets 和答案。不幸的是,问题仍然存在。 我没有读取文件,而是直接传递 base64 编码的附件(这是有效的)。此编辑后代码的输出如下屏幕截图所示
sendData(&ConnectSocket, "MIME-Version: 1.0\r\n");
sendData(&ConnectSocket, "Content-Type:multipart/mixed;boundary=\"977d81ff9d852ab2a0cad646f8058349\"\r\n");
sendData(&ConnectSocket, "Subject:This is subject of my mail\r\n");
sendData(&ConnectSocket, "--977d81ff9d852ab2a0cad646f8058349\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain; charset=\"utf-8\"\r\n");
sendData(&ConnectSocket, "Content-Transfer-Encoding: quoted-printable\r\n\r\n");
sendData(&ConnectSocket, "Hi Me,=0A=0AThis is an empty file.=0A=0ARegards,=0A<ME>=0A=0A---- =0ASent using Guerrillamail.com =0ABlock or report abuse : https://www.guerrillamail.com//abuse/?a=3DUVJzDA8SW6Q1mwa14nUTcwfCX9ne0dhd=0A \r\n\r\n");
sendData(&ConnectSocket, "--977d81ff9d852ab2a0cad646f8058349\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain\r\n");
sendData(&ConnectSocket, "Content-Transfer-Encoding: base64\r\n");
sendData(&ConnectSocket, "Content-Disposition: attachment; filename=\"details.txt\"\r\n\r\n");
sendData(&ConnectSocket, "U2FtcGxlIFRleHQu");
sendData(&ConnectSocket, "\r\n\r\n--977d81ff9d852ab2a0cad646f8058349--\r\n\r\n");
sendData(&ConnectSocket, ".\r\n");
recvData(&ConnectSocket);
sendData(&ConnectSocket, "QUIT\r\n");
编辑后的输出邮件截图:
【问题讨论】:
您确定您的文件是正确编码的quoted-printable 吗?您是否尝试先发送一封非常简单的邮件(没有附件)?您是否尝试检查并与普通邮寄者发送的邮件进行比较? 您的sendata
函数应该发送字节而不是字符串(至少在发送文件内容时)。
请参考问题中的编辑部分
【参考方案1】:
正文需要用空行与标题分开。
sendData(&ConnectSocket, "MIME-Version: 1.0\r\n");
sendData(&ConnectSocket, "Content-Type:multipart/mixed;boundary=\"977d81ff9d852ab2a0cad646f8058349\"\r\n");
sendData(&ConnectSocket, "Subject:This is subject of my mail\r\n");
sendData(%ConnectSocket, "\r\n"); /* added */
sendData(&ConnectSocket, "--977d81ff9d852ab2a0cad646f8058349\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain; charset=\"utf-8\"\r\n");
sendData(&ConnectSocket, "Content-Transfer-Encoding: quoted-printable\r\n\r\n");
sendData(&ConnectSocket, "Hi Me,=0A=0AThis is an empty file.=0A=0ARegards,=0A<ME>=0A=0A---- =0ASent using Guerrillamail.com =0ABlock or report abuse : https://www.guerrillamail.com//abuse/?a=3DUVJzDA8SW6Q1mwa14nUTcwfCX9ne0dhd=0A \r\n\r\n");
sendData(&ConnectSocket, "--977d81ff9d852ab2a0cad646f8058349\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain\r\n");
sendData(&ConnectSocket, "Content-Transfer-Encoding: base64\r\n");
sendData(&ConnectSocket, "Content-Disposition: attachment; filename=\"details.txt\"\r\n\r\n");
sendData(&ConnectSocket, "U2FtcGxlIFRleHQu");
sendData(&ConnectSocket, "\r\n\r\n--977d81ff9d852ab2a0cad646f8058349--\r\n\r\n");
sendData(&ConnectSocket, ".\r\n");
当您向 SMTP 发送内容时,您应该正确检查结果代码是否符合预期(大多数命令为 2xx,DATA
为 345)。
随着时间的推移,使用静态边界字符串将无法正常工作(尽管我想如果您发现边界字符串嵌入在内容中,您可以通过对边界字符串进行编码来替换或混淆)。
当消息的长度超过大约 1,000 个字符时,将整个消息作为单个长行发送将开始中断。 quoted-printable 有用的原因之一是它允许您在内容中透明地嵌入换行符(尽管在这种情况下,仅在消息包含换行符的地方发送文字换行符已经让您走得很远了)。
格式良好的消息还应具有From:
和To:
标头。
【讨论】:
成功了!不敢相信 '\r\n' 几乎占据了我一整天。非常感谢。【参考方案2】:name 在 Content-Type 中看起来不需要,将其更改为
sendData(&ConnectSocket, "Content-Type: text/plain");
编辑:
请改成这个(将邮件信息移到 text/plain 下面,然后进行其他更改)
sendData(&ConnectSocket, "MIME-Version: 1.0\r\n");
sendData(&ConnectSocket, "Content-Type:multipart/mixed;boundary=\"KkK170891tpbkKk__FV_KKKkkkjjwq\"\r\n");
sendData(&ConnectSocket, "--KkK170891tpbkKk__FV_KKKkkkjjwq\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain\r\n");
sendData(&ConnectSocket, "And this is text\r\n");
sendData(&ConnectSocket, "--KkK170891tpbkKk__FV_KKKkkkjjwq\r\n");
sendData(&ConnectSocket, "Content-Type: text/plain;name=\"details.txt\"\r\n");
sendData(&ConnectSocket, "Content-Transfer-Encoding: base64\r\n");
sendData(&ConnectSocket, "Content-Disposition: attachment; filename=\"details.txt\"\r\n");
【讨论】:
不工作。删除“名称”并尝试使用纯文本和 html。 单独发送邮件,不带附件是否正常工作?我怀疑需要两个边界而不是一个。 在编辑后的代码中添加了两个边界。请查看修改后的代码。 在Content-Type:
和Content-Disposition:
中传递文件名的约定是无害的,并且通常实现向后兼容性(尽管现在可能完全没用)。以上是关于通过 C 套接字编程的邮件格式的主要内容,如果未能解决你的问题,请参考以下文章