C语言编写smtp用户代理之socket操作

本文介绍了Socket编程的基础,包括函数的定义与实现,并详细讲解了如何通过SSL进行数据加密传输。提供了myclient_sock等核心函数的实现细节,以及在启用SSL加密前后数据收发的具体方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、函数的准备和功能介绍
首先是 renyj_sock.h文件

#ifndef RENYJ_SOCK_H
#define RENYJ_SOCK_H    
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/file.h>
#include<sys/socket.h>
#include<ctype.h>
#include<netinet/in.h>
#include<netdb.h>
#include<sys/ioctl.h>
#include<arpa/inet.h>
#include<time.h>
#include<signal.h>
#include<errno.h>
#include<sys/wait.h>
#include<pwd.h>
#include<grp.h>
#include<fcntl.h>
#include<limits.h>


#ifdef HAS_SSL  
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#endif


/*宏定义用来打印调试信息*/
#define debug(format,args...) do{fprintf(stderr,"Debug>>>%s->%s()->line.%d:"format"\n",__FILE__,__FUNCTION__,__LINE__,##args);}while(0);

/*这个函数用来初始化socket并且返回一个可以使用的sokect描述符,第一个参数没有用,这里是为了扩展*/
int myclient_sock(int protel,char *addr,int port,int timeout);

/*这个函数用来从socket读取数据,供外界调用的接口函数*/
int mysock_gets(char* str,int size,int timeout);

/*这个函数用来从socket发送数据,提供给外界的接口函数*/
int mysock_puts(char* str);

/*从socket中读取数据,不提供给外界*/
int mysock_read(int sock,char *str,int size,int timeout);

/*向socket 发送数据,不提供给外界*/
int mysock_write(int sock,char *str,int size);

/*关闭socket*/
int mysock_close();

/*获取socket 描述符的值,提供给外界*/
int get_socket();

/*设置socket 描述符的值,提供给外界*/
int set_socket(int socket);

/*将IP或者域名的字符串转变成IP地址的函数,只支持IPv4*/
struct in_addr *str_to_addr(char *addr);

//由于使用qq邮箱需要加密传输,所以在这里需要开启ssl。



#ifdef HAS_SSL
//封装一个读取ssl加密后数据的函数 
int mysock_read_ssl(SSL *ssl,char *str,int size,int timeout);
//封装一个发送ssl数据的函数
int mysock_write_ssl(SSL *ssl,char *str,int size); 
//获取SSL标志
SSL* get_ssl();
//设置ssl标志
int set_ssl(SSL *ssl);
//初始化 ssl
int openssl_init(char *cipher);
//将某个socket描述符对应的socket传输的数据设置为ssl加密
int turn_on_ssl(int fd);

int turn_ssl_status_on();

int turn_ssl_status_off();
#endif
#endif

二、函数的实现
renyj_sock.c文件

int renyj_fd;
SSL *renyj_ssl;
int renyj_ssl_status;
/*初始化sokcet,准备连接*/
int myclient_sock(int protel,char *seraddr,int port,int timeout)
{
    struct sockaddr_in sa;
    struct in_addr* addr;
    int sock_fd;


    struct timeval tv;
    fd_set fdset;
    //获取IP地址
    addr = str_to_addr(seraddr);
    if (NULL == addr)
    {
        debug("invalid domain");
        return -1;
    }
    memset(&sa,0x00,sizeof(sa));    
    //准备通信地址
    sa.sin_family = AF_INET;
    sa.sin_port = htons(port);
    sa.sin_addr.s_addr = addr->s_addr;

    //创建socket
    sock_fd = socket(AF_INET,SOCK_STREAM,PF_UNSPEC);
    if (-1 == sock_fd)
    {
        return -1;
    }

    //监控socket,如果连接超时,则退出。
    int rc;
    rc = connect(sock_fd,(struct sockaddr*)(&sa),sizeof(sa));
    if (-1 == rc)
    {
        debug("connect error");
        return -1;
    }

    FD_ZERO(&fdset);
    FD_SET(sock_fd,&fdset);

    tv.tv_sec = timeout;
    tv.tv_usec = 0;

    rc = select(sock_fd+1,NULL,&fdset,NULL,&tv);
    if (-1 == rc)
    {
        debug("select error");
        return -1;
    }
    if (0 == rc)
    {
        debug("connect to %s:%d timeout after %d seconds ",
                seraddr,port,timeout);
        return -1;
    }
    //返回一个socket描述符
    return sock_fd;

}
int mysock_gets(char *str, int size,int timeout)
{
#ifdef HAS_SSL
    mysock_read_ssl(get_ssl(),str,size,timeout);
#else
    mysock_read(get_socket(),str,size,timeout); 
#endif
}
int mysock_puts(char *str)
{
#ifdef HAS_SSL
    mysock_write_ssl(get_ssl(),str,strlen(str));
#else
    mysock_write(get_socket(),str,strlen(str));
#endif
}
int mysock_read(int sock,char *str,int size,int timeout)
{
    if (NULL == str)
    {
        return -1;
    }
    char *cur = str;
    char buff[1];
    int count=0;
    char lastread = 0;
    int tmpcount = 0;
    while (10 != lastread)
    {
        tmpcount = recv(sock,buff,1,0);
        if (-1 == tmpcount)
        {
            debug("recv error");
            return -1;
        }
        lastread = buff[0];
        if ((count < size)&&(10 != lastread)&&(13!=lastread) )
        {
            count++;
            *cur = lastread;
            cur++;
        }

    }
    if (count > 0)
    {
        *cur = 0;
    }

    return count;
}
int mysock_write(int sock,char *str,int count)
{
    int curcount = 0;
    int bytessent = 0;

    while (bytessent < count)
    {
        curcount = send(sock,str,count-bytessent,0);
        if (curcount < 0)
        {
            debug("send error");
            return curcount;
        }
        bytessent = bytessent + curcount;
        str += curcount; 
    }
    return count;
}
int mysock_close()
{
    close(renyj_fd);
}

int get_socket()
{
    return renyj_fd;
}

int set_socket(int socket)
{
    renyj_fd = socket;
}
struct in_addr *str_to_addr(char *addr)
{
    static struct in_addr myaddr;
    struct hostent *host;

    myaddr.s_addr=inet_addr(addr);
    if(-1!=myaddr.s_addr)
    {
        return (&myaddr); 
    }

    host = gethostbyname(addr);
    if (NULL == host)
    {
        return NULL;
    }
    return (struct in_addr*)(*host->h_addr_list);
}
#ifdef HAS_SSL 
SSL *get_ssl()
{
    return renyj_ssl;
}

int set_ssl(SSL *ssl)
{
    renyj_ssl = ssl;
}

int openssl_init(char *cipher)
{
    static const char rnd_seed[]="my huge entropy for rng.. blah";
    SSL_CTX *ssl_ctx=(SSL_CTX *) NULL;
    SSL *ssl=NULL;
    SSL_library_init();
    SSL_load_error_strings();
    RAND_seed(rnd_seed,sizeof(rnd_seed));
    OpenSSL_add_all_algorithms();
    ssl_ctx=SSL_CTX_new(SSLv23_client_method());
    if (ssl_ctx == NULL)
    {
        debug("Could not create SSL context\n");
        return -1;
    }
    if (cipher)
    {
        if (!SSL_CTX_set_cipher_list(ssl_ctx,cipher))
        {
            debug("Could not set cipher list %s\n",cipher);
            return -1;
        }
    }
    ssl=SSL_new(ssl_ctx);
    if (ssl == NULL)
    {
        debug("SSL_new() failed\n");
        return -1;
    }
    /* set ssl to msock's static */
    set_ssl(ssl);

}

int turn_on_ssl(int fd)
{
    int
        rc=(-1);

    SSL
        *ssl;

    ssl = get_ssl();
    if (ssl)
    {
        if (!SSL_set_fd(ssl,fd))
        {
            debug("turn_on_raw_ssl: failed to set socket %d to SSL\n",fd);
            return(-1);
        }
        /* must set back to msock's static */
        set_ssl(ssl);
        debug("connect ...");
        rc=SSL_connect(ssl);
        if (rc < 1)
        {
            debug("turn_on_raw_ssl: SSL connection failed\n");
            return(-1);
        }
        debug("connect end ")
        rc=0;
        return(0);
    }
    return(-1);
}

int turn_ssl_status_on()
{
    renyj_ssl_status = 1;
}

int turn_ssl_status_off()
{
    renyj_ssl_status = 0;
}

int mysock_write_ssl(SSL *ssl,char *str,int count)
{
    int curcount = 0;
    int bytessent = 0;

    while (bytessent < count)
    {
        curcount = SSL_write(ssl,str,count-bytessent);
        if (curcount < 0)
        {
            debug("send error");
            return curcount;
        }
        bytessent = bytessent + curcount;
        str += curcount; 
    }
    return count;
}

int mysock_read_ssl(SSL *ssl,char *str,int size,int timeout)
{
    if (NULL == str)
    {
        return -1;
    }
    char *cur = str;
    char buff[1];
    int count=0;
    char lastread = 0;
    int tmpcount = 0;
    while (10 != lastread)
    {
        tmpcount = SSL_read(ssl,buff,1);
        if (-1 == tmpcount)
        {
            debug("recv error");
            return -1;
        }
        lastread = buff[0];
        if ((count < size)&&(10 != lastread)&&(13!=lastread) )
        {
            count++;
            *cur = lastread;
            cur++;
        }

    }
    if (count > 0)
    {
        *cur = 0;
    }

    return count;
}
#endif
主要功能: 1、可以发送带附件的邮件,附件可以是多个,附件大小限制由发送方服务器而定,暂未测试具体为多少MB 2、邮件内容和主题可以是空,但当有附件时,主题取第一个附件的文件名(不含扩展名) 3、密码验证均为base64加密 4、邮件正文和附件的数据传送方式,均为base64 5、自动解析发件箱的SMTP服务器 压缩包文件简介: base.c:包含一些基本的函数,其中有一些在此程序中并未用到,只要使用了其中的base64加密算法 mail.c:包含邮件发送、数据读取、编码转换、smtp服务器连接、ip解析等函数 mailsend.c:包含main的c源文件,mail.exe则是根据mailsend.c、mail.c、base.c编译成的,具体编译方 法可参考makefile libbase.a:make之后生成的静态库 moontalk.cfg:base.c用到的配置文件,可能没用,放在这里进攻阅读参考 mail.cfg:自定义用户的配置文件,可用可不用,用作读代码的参考 mail.exe:邮件发送的执行文件,仅有命令行模式完善了,逐步输入(直接双击)的方式还不完善 b64.exe:base64加密解密的小工具,仅供参考,mail.cfg中用到密码的地方,可以使这个工具得到。 makefile:工程编译链接文件 注意:在本地使用mingw环境开发,遵循ANSI C标准,本地有系统的工程库,但是上传的时候,把这些文件 都放在一起了,可以先参考makefile进行工程调整,如果有任何问题,请发送到邮箱moontalk@yeah.net, 技术交流,不胜感激。
/* ========================================================================================= // 利用OpenSSL库对Socket传输进行安全加密(RSA+AES) // 1. 利用RSA传输AES生成密钥所需的Seed(32字节BUF) // 2. 利用AES_encrypt/AES_decrypt对Socket上面的数据报文进行AES对称性加密 // --- // * 理论上只需要AES就能保证全部流程,但由于AES加密所需要的AES-KEY是一个结构 // * 这个一个结构,如果通过网络进行传输,就需要对它进行网络编码,OpenSSL里面没有现成的API // * 所以就引入RSA来完成首次安全的传输,保证Seed不会被窃听 // * 同样,只使用RSA也能完成全部流程,但由于RSA的处理效率比AES低, // * 所以在业务数据传输加密上还是使用AES // --- // 下面的代码包含了上述传输加密流程所需的所有步骤(OpenSSL部分) // 在实际的Socket应用开发时,需要将这些步骤插入到Client/Server网络通信的特定阶段 // --- // 为能完成代码的编译和执行,需要先安装OpenSSL执行库及开发库 // 以Debian为例,需要安装openssl 和 libssl-dev // 编译命令: g++ -o rsa-encrypt rsa-encrypt.cpp -lcrypto // --- // 所需的OpenSSL主要的API及功能描述 // 1. RSA_generate_key() 随机生成一个RSA密钥对,供RSA加密/解密使用 // 2. i2d_RSAPublicKey() 将RSA密钥对里面的公钥提出到一个BUF,用于网络传输给对方 // 3. d2i_RSAPublicKey() 将从网络传过来的公钥信息生成一个加密使用的RSA(它里面只有公钥) // 4. RSA_public_encrypt() 使用RSA的公钥对数据进行加密 // 5. RSA_private_decrypt() 使用RSA的私钥对数据进行加密 // 6. AES_set_encrypt_key() 根据Seed生成AES密钥对中的加密密钥 // 7. AES_set_decrypt_key() 根据Seed生成AES密钥对中的解密密钥 // 8. AES_encrypt() 使用AES加密密钥对数据进行加密 // 9. AES_decrypt() 使用AES解密密钥对数据进行解密 // --- // 一个典型的安全Socket的建立流程, 其实就是如何将Server随机Seed安全发给Client // -- C: Client S:Server // C: RSA_generate_key() --> RSAKey --> i2d_RSAPublicKey(RSAKey) --> RSAPublicKey // C: Send(RSAPublicKey) TO Server // S: Recv() --> RSAPublicKey --> d2i_RSAPublicKey(RSAPublicKey) --> RSAKey // S: Rand() --> Seed --> RSA_public_encrypt(RSAKey, Seed) --> EncryptedSeed // S: Send(EncryptedSeed) TO Client // C: Recv() --> EncryptedSeed --> RSA_private_decrypt(RSAKey, EncryptedSeed) --> Seed // --- 到此, Client和Server已经完成完成传输Seed的处理 // --- 后面的流程是它们怎样使用这个Seed来进行业务数据的安全传输 // C: AES_set_encrypt_key(Seed) --> AESEncryptKey // C: AES_set_decrypt_key(Seed) --> AESDecryptKey // S: AES_set_encrypt_key(Seed) --> AESEncryptKey // S: AES_set_decrypt_key(Seed) --> AESDecryptKey // --- Client传输数据给Server // C: AES_encrypt(AESEncryptKey, Data) --> EncryptedData --> Send() --> Server // S: Recv() --> EncryptedData --> AES_decrypt(AESDecryptKey, EncryptedData) --> Data // --- Server传输数据给Client // S: AES_encrypt(AESEncryptKey, Data) --> EncryptedData --> Send() --> Client // C: Recv() --> EncryptedData --> AES_decrypt(AESDecryptKey, EncryptedData) --> Data / ========================================================================================= */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值