C 语言使用 SMTP 协议发送邮件

操作系统:Windows 7 (32-bit);编译器:Tiny C Compiler 0.9.27。

0. SMTP 协议通信流程

  1. 与服务器端建立 TCP 连接
  2. 发送 HELO <name> 命令标识发件人
  3. 发送 AUTH LOGIN 命令开始登录
  4. 发送用户名(经过 Base64 编码)
  5. 发送密码(经过 Base64 编码)
  6. 发送发件人邮箱地址 MAIL FROM: <addr>
  7. 发送收件人邮箱地址 RCPT TO: <addr>
  8. 发送 DATA 命令开始发送邮件正文
  9. 发送邮件正文(以 \r\n.\r\n 结束)
  10. 发送 QUIT 命令结束

注:每行命令都要以 \r\n 结尾。

1. 实现 Base64 编码

使用共用体。

#include <string.h>

// 查表
#define BASE_TAB \
  "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
  "abcdefghijklmnopqrstuvwxyz" "0123456789+/"

union base64_t {
    struct {
        unsigned c: 8;
        unsigned b: 8;
        unsigned a: 8;
    } src;
    struct {
        unsigned d: 6;
        unsigned c: 6;
        unsigned b: 6;
        unsigned a: 6;
    } res;
};

void _base64(char *res, char *src) {
    union base64_t data;
    data.src.a = (unsigned)src[0];
    data.src.b = (unsigned)src[1];
    data.src.c = (unsigned)src[2];
    res[0] = BASE_TAB[data.res.a];
    res[1] = BASE_TAB[data.res.b];
    res[2] = BASE_TAB[data.res.c];
    res[3] = BASE_TAB[data.res.d];
}

void base64(char *res, char *src) {
    int len = strlen(src);
    // src 中的每 3 个字符做一次操作
    while (*src != 0) {
        _base64(res, src);
        // 若 len 不是 3 的倍数, 则 res 末尾会有若干个 0
        src += 3, res += 4;
    }
    // 填充 '='
    if (len % 3 != 0) {
        // 先回退
        res -= (3 - len % 3);
        // 再填充
        memset(res, '=', 3 - len % 3);
    }
}

对于共用体中 d b c a 的顺序,可参见这篇文章

2. 关于建立连接和发送、接收数据

将其封装为函数或宏,便于使用。

#include <string.h>
#include <winsock2.h>

#define ADDR_SIZE  sizeof(struct sockaddr)
#define ADDR_OF(p) (struct sockaddr *)(p)

// 发送数据
#define snsend(sock, str, len, ...) \
    do { memset(str, 0, len); \
         snprintf(str, len, __VA_ARGS__); \
         send(sock, str, strlen(str), 0); \
    } while (0);

// 接收数据
#define snrecv(sock, str, len) \
    do { memset(str, 0, len); \
         recv(sock, str, len, 0); \
    } while (0);

// 客户端建立服务器端连接
SOCKET client(char *ip, int port) {
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip);
    addr.sin_port = htons(port);
    connect(sock, ADDR_OF(&addr), ADDR_SIZE);
    return sock;
}

3. 发送邮件函数

#include <stdio.h>
#include <string.h>

// 字符串和缓冲区大小
#define STR_SIZE  256
// 邮箱服务器 IP 和端口
#define SMTP_ADDR "8.8.8.8"
#define SMTP_PORT 25

typedef char string[STR_SIZE];

// 此处 pass 需要事先在函数外 Base64 编码,而 user 不需要
void send_email(char *user, char *pass, char *addr, char *body) {
    SOCKET sock =  0;
    string buf  = {0}; // 发送缓冲区
    string rbuf = {0}; // 接收缓冲区
    string tmp  = {0};

    sock = client(SMTP_ADDR, SMTP_PORT);
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息

    // HELO & AUTH LOGIN
    snsend(sock, buf, STR_SIZE, "HELO smtp\r\n");
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息
    snsend(sock, buf, STR_SIZE, "AUTH LOGIN\r\n");
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息

    // 用户名 & 密码
    base64(tmp, user);
    snsend(sock, buf, STR_SIZE, "%s\r\n", tmp);
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息
    snsend(sock, buf, STR_SIZE, "%s\r\n", pass);
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息

    // MAIL FROM & RCPT TO
    snsend(sock, buf, STR_SIZE, "MAIL FROM: <%s>\r\n", user)
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息
    snsend(sock, buf, STR_SIZE, "RCPT TO: <%s>\r\n", addr);
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息

    // DATA & 邮件正文
    snsend(sock, buf, STR_SIZE, "DATA\r\n");
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息
    snsend(sock, buf, STR_SIZE, "%s\r\n.\r\n", body);
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息

    // QUIT
    snsend(sock, buf, STR_SIZE, "QUIT\r\n");
    snrecv(sock, rbuf, STR_SIZE);
    // printf("%s", rbuf); // 显示服务器返回信息
    closesocket(sock);
}

4. 测试用例

int main(void) {
    char tmp[STR_SIZE] = {0};
    char *user = "alice@a.com";
    char *pass = "password";
    char *addr = "bob@b.com";
    char *body = "From: \"alice\"<a@a.com>\r\n" \
                 "To: \"bob\"<b@b.com>\r\n" \
                 "Subject: Hello\r\n\r\n" \
                 "Hello, world!";
    base64(tmp, pass);
    send_email(user, tmp, addr, body);
    return 0;
}

posted on 2021-07-23 23:56  UXOD  阅读(478)  评论(0编辑  收藏  举报