用openssl跟Gmail的smtp对话(一)

 
一、目的
 
Gmail的webmail虽然常常报系统遇到错误,用起来不太爽,但是smtp/pop3还是蛮厚道的。
服务稳定,被GFW拦截的概率也小一些,可能是采用SSL加密通讯的缘故吧。所以研究一下gmail的smtp连接还是很有意义的。我们先用手头上的工具进行实际操作,对ssl的连接有个感性的认识,然后用C写一个测试程序,最终的目标是用C++写一个COM可以供ASP调用,当然所谓的C++当然是指基于M$的ATL了,纯C++的COM是学院派的人干的活,作为工程人员应用才是首要目的。为什么要写成COM的,因为纵观网络编程的世界就asp没有实现(或者实现了没有开源)gmail的发信功能,不管jmail还是codsys都没有,可是是没有必要,或者是我孤陋寡闻,其他的php、asp.net、java都很容易实现此功能。在asp遍地的过度,写出来估计还是有一定价值的。
 
二、要求
  1. SSL的大致内容,搞清楚C/S间如何握手的就行了
  2. OpenSSL,开源的成功案例,没有OpenSSL估计我们只能望机兴叹了,搞明白SSL的算法好像不是一两个月能明白的,所有掌握Openssl是必须的,不用太深入,但是起码能知道如何建立ssl的上下文和封装socket的发送和接收。
  3. C/C++基础,上过理工科的人都应该知道。俺打算用C来写HelloGmail程序,被逼无奈啊,谁让OpenSSL是用C写的呢!
  4. COM基础,至少要知道COM是如何实现二进制级代码共享的,如何用ATL做COM的。俺得毕业论文是搞fortran和java的混合编程,所以对混合编程情有独钟。当时还不知道有COM这回事,走了很多的弯路:java调c++写的dll,c++的dll调fotran的dll,相当的丑陋 ^^
 
三、开始
 
1.简单的认识
 
我们先在cmd窗口连连gmail看看,把过程弄明白先。由于采用了ssl,所以用普通的telnet是无法直接连接到gmail的smtp的,我们需要openssl构建一个ssl层,由openssl来负责繁琐的ssl协议
 
我们先要编译OpenSSL,下面这篇博文已经写得很好,俺就不啰嗦了
http://blog.tom.com/blog/read.php?bloggerid=329665&blogid=41867
 
我在E:/openssl-0.9.8e下
 

E:/openssl-0.9.8e/out32dll>openssl s_client -connect smtp.gmail.com:465
Loading 'screen' into random state - done
CONNECTED(00000790)
depth=0 /C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 /C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
verify error:num=27:certificate not trusted
verify return:1
depth=0 /C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
   i:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification S
ervices Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.
com
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDYzCCAsygAwIBAgIQYZrZzKZNh1fKVFuUlZ6rKzANBgkqhkiG9w0BAQUFADCB
zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
cnZlckB0aGF3dGUuY29tMB4XDTA3MDczMDE2NTgwN1oXDTA4MDcyOTE2NTgwN1ow
aDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1v
dW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBJbmMxFzAVBgNVBAMTDnNtdHAu
Z21haWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQF2mUMNM+qw/i
wMVSP2D0pgKb0M3RyWHBTQkno3W4y5TeH8LALnqv9/+Th4wZ5PrZ7YPQjmCxdtz6
Lm5Yx19nDXNw97or6SXvAoZSF+bwh76UFqxpImAGJzvj8Ro7rNkMidJa+KgGaIng
sIcWuqsj0rrK1AXoUHKmO4N5t0c6XwIDAQABo4GmMIGjMB0GA1UdJQQWMBQGCCsG
AQUFBwMBBggrBgEFBQcDAjBABgNVHR8EOTA3MDWgM6Axhi9odHRwOi8vY3JsLnRo
YXd0ZS5jb20vVGhhd3RlUHJlbWl1bVNlcnZlckNBLmNybDAyBggrBgEFBQcBAQQm
MCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0ZS5jb20wDAYDVR0TAQH/
BAIwADANBgkqhkiG9w0BAQUFAAOBgQCUoTmFzdJX+2Pz9FhI+H88lFIeBcFnxpPO
CHO7zs/J3ZI6ZmkuQm4az89tRqvKvRFrQm2CRlzntqWjSdcsIYlKKGZ32iclpNKw
1aW/Q3IIyyZTTUo9DJezyCrFBV7JxFXOQgYd45+YxPVUNnkw1lTd4RqweuB5p7r4
nObS2EE7cA==
-----END CERTIFICATE-----
subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
issuer=/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification
 Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawt
e.com
---
No client certificate CA names sent
---
SSL handshake has read 1025 bytes and written 314 bytes
---
New, TLSv1/SSLv3, Cipher is DES-CBC3-SHA
Server public key is 1024 bit
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol : TLSv1
    Cipher : DES-CBC3-SHA
    Session-ID: AA41A4829FAB5945984CCE49EF1A135703C437F1F169F1BB9D2E8417D6B50B38

    Session-ID-ctx:
    Master-Key: BEE0D063AF73B0645845F359D8E8A488EC33A38497D381C46ECE6F0E7C8DBFB0
2EA5A1EE3FFD583DD111EC9567EE6D8F
    Key-Arg : None
    Start Time: 1188884689
    Timeout : 300 (sec)
    Verify return code: 21 (unable to verify the first certificate)
---
220 mx.google.com ESMTP c5sm1770312qbc
EHLO localhost
250-mx.google.com at your service, [218.80.208.72]
250-SIZE 28311552
250-8BITMIME
250-AUTH LOGIN PLAIN
250 ENHANCEDSTATUSCODES
AUTH LOGIN
334 VXNlcm5hbWU6
eHVxaW55b25nQGdtYWlsLmNvbQ==
334 UGFzc3dvcmQ6
xxxxxxxxx
235 2.7.0 Accepted

上面的连接其实是有问题,因为我们没有装载我们的证书,所以到RCPT命令的时候,gmail就把我们给踢了,我们在C写的HelloGmail中将会装载我们的自己的证书。

 
2.用C写发信程序
 

#include "stdafx.h"

/******************************************************************************************
*SSL/TLS客户端程序WIN32版(以demos/cli.cpp为基础)
*需要用到动态连接库libeay32.dll,ssleay.dll,
*同时在setting中加入ws2_32.lib libeay32.lib ssleay32.lib,
*以上库文件在编译openssl后可在out32dll目录下找到,
*所需证书文件请参照文章自行生成*/

******************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>

#include <winsock2.h>

#include "openssl/rsa.h"
#include "openssl/crypto.h"
#include "openssl/x509.h"
#include "openssl/pem.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
#include "openssl/rand.h"

#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>

/*所有需要的参数信息都在此处以#define的形式提供*/
#define CERTF "client.pem" /*客户端的证书(需经CA签名)*/
#define KEYF "client.key" /*客户端的私钥(建议加密存储)*/
#define CACERT "ca.pem" /*CA 的证书*/
#define PORT 465 /*服务端的端口*/
#define SERVER_ADDR "209.85.147.111" /*服务段的IP地址*/
#define GMAILUSERNAME "[email protected]"/*Gmail 帐号*/
#define GMAILPASSWORD "xxxx"/*Gmail 帐号密码*/


#define CHK_NULL(x) if ((x)==NULL) exit (-1)
#define CHK_ERR(err,s) if ((err)==-1) { perror(s); exit(-2); }
#define CHK_SSL(err) if ((err)==-1) { ERR_print_errors_fp(stderr); exit(-3); }


/*仍数据*/
int put_line(SSL* ssl,char* cmd)
{
    int err;
    printf("C: %s",cmd);
    err = SSL_write (ssl, cmd, strlen(cmd));
    CHK_SSL(err);
    return 1;
}

/*读数据*/
int get_line(SSL* ssl)
{
    char buf [4096];
    int err;
    err = SSL_read (ssl, buf, sizeof(buf) - 1);
    CHK_SSL(err);
    buf[err] = '/0';
    printf ("S: :%s", buf);
    free(buf);
    return 1;
}


char *base64_encode(char *input)
{
    BIO *bmem, *b64;
    BUF_MEM *bptr;

    b64 = BIO_new(BIO_f_base64());
    bmem = BIO_new(BIO_s_mem());
    b64 = BIO_push(b64, bmem);
    BIO_write(b64, input, strlen(input));
    BIO_flush(b64);
    BIO_get_mem_ptr(b64, &bptr);

    char *buff = (char *)malloc(bptr->length);
    memcpy(buff, bptr->data, bptr->length-1);
    buff[bptr->length-1] = 0;

    BIO_free_all(b64);

    return buff;
}


/*捏造信头*/
char* makeHeader()
{
    char buf[128];
    char tmp[1024];
    char *rnt,*next;
    int n,length;

    n = length = 0;

    next = tmp;

    n = sprintf(buf,"Date: %s/r/n","date");
    memcpy(tmp,buf,n);
    next+= n;
    length += n;

    n = sprintf(buf,"Return-Path: %s/r/n",GMAILUSERNAME);
    memcpy(next,buf,n);
    next+= n;
    length += n;

    n = sprintf(buf,"To: %s/r/n",GMAILUSERNAME);
    memcpy(next,buf,n);
    next+= n;
    length += n;
    
    n = sprintf(buf, "From: =?UTF-8?B?%s?= <%s>/r/n",base64_encode(GMAILUSERNAME),GMAILUSERNAME);
    memcpy(next,buf,n);
    next+= n;
    length += n;
    
    n = sprintf(buf, "Subject: =?UTF-8?B?%s?=/r/n",base64_encode("Test mail via C"));
    memcpy(next,buf,n);
    next+= n;
    length += n;

    n = sprintf(buf, "MIME-Version: 1.0/r/n");
    memcpy(next,buf,n);
    next+= n;
    length += n;

    n = sprintf(buf, "Content-Transfer-Encoding: base64/r/n");
    memcpy(next,buf,n);
    next+= n;
    length += n;

    n = sprintf(buf, "Content-Type: text/html; charset=/"UTF-8/"/r/n/r/n");
    memcpy(next,buf,n);
    next+= n;
    length += n;

    tmp[length] = 0;

    rnt = (char *)malloc(length);
    strcpy(rnt, tmp);
    
    return rnt;
}

char* makeBody(char* instr)
{
    char* tmp;
    char* rnt;
    tmp = base64_encode(instr);

    rnt = (char *)malloc(strlen(tmp));
    strcpy(rnt, tmp);
    rnt = strcat(rnt,"/r/n");

    return rnt;
}

int main ()
{
    int err;
    int sd;
    struct sockaddr_in sa;
    SSL_CTX* ctx;
    SSL* ssl;
    X509* server_cert;
    char* str;
    SSL_METHOD *meth;
    int seed_int[100]; /*存放随机序列*/

    WSADATA wsaData;

    if(WSAStartup(MAKEWORD(2,2),&wsaData) != 0){
        printf("WSAStartup()fail:%d/n",GetLastError());
        return -1;
    }

    
//OpenSSL_add_ssl_algorithms(); /*初始化*/

    SSL_library_init();
    SSL_load_error_strings(); /*为打印调试信息作准备*/

    meth=SSLv23_method();
    ctx = SSL_CTX_new (meth);
    CHK_NULL(ctx);

    SSL_CTX_set_default_passwd_cb_userdata(ctx, "password");

    if(!(SSL_CTX_use_certificate_chain_file(ctx, CERTF))){
          ERR_print_errors_fp(stderr);
        exit(-2);
    }
    
    if(!(SSL_CTX_use_PrivateKey_file(ctx, CERTF,SSL_FILETYPE_PEM))){
        ERR_print_errors_fp(stderr);
        exit(-3);
    }

    /* Load the CAs we trust*/
    if(!(SSL_CTX_load_verify_locations(ctx, CACERT,0))){
        printf("Load CA failed!/n");
        exit(-4);
    }


    /*构建随机数生成机制,WIN32平台必需*/
    srand( (unsigned)time( NULL ) );
    for( int i = 0; i < 100;i++ )
    seed_int[i] = rand();
    RAND_seed(seed_int, sizeof(seed_int));

    /*以下是正常的TCP socket建立过程 .............................. */
    printf("Begin tcp socket.../n");

    sd = socket (AF_INET, SOCK_STREAM, 0); CHK_ERR(sd, "socket");

    memset (&sa, '/0', sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr (SERVER_ADDR); /* Server IP */
    sa.sin_port = htons (PORT); /* Server Port number */

    err = connect(sd, (struct sockaddr*) &sa,
    sizeof(sa));
    CHK_ERR(err, "connect");

    /* TCP 链接已建立.开始 SSL 握手过程.......................... */
    printf("Begin SSL negotiation /n");

    ssl = SSL_new (ctx);
    CHK_NULL(ssl);

    SSL_set_fd (ssl, sd);

    err = SSL_connect (ssl);
    CHK_SSL(err);

    /*打印所有加密算法的信息(可选)*/
    printf ("SSL connection using %s/n", SSL_get_cipher (ssl));

    /*得到服务端的证书并打印些信息(可选) */
    server_cert = SSL_get_peer_certificate (ssl);
    CHK_NULL(server_cert);
    printf ("Server certificate:/n");

    str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
    CHK_NULL(str);
    printf ("/t subject: %s/n", str);
    free (str);

    str = X509_NAME_oneline (X509_get_issuer_name (server_cert),0,0);
    CHK_NULL(str);
    printf ("/t issuer: %s/n", str);
    free (str);

    X509_free (server_cert); /*如不再需要,需将证书释放 */

    /* 数据交换开始,用SSL_write,SSL_read代替write,read */
    printf("Begin SSL data exchange/n");
    
    get_line(ssl);
    put_line(ssl,"EHLO localhost/r/n");
    get_line(ssl);
    put_line(ssl,"AUTH LOGIN/r/n");
    get_line(ssl);
    str = strcat(base64_encode(GMAILUSERNAME),"/r/n");
    put_line(ssl,str);
    free(str);
    get_line(ssl);
    str = strcat(base64_encode(GMAILPASSWORD),"/r/n");
    put_line(ssl,str);
    free(str);
    get_line(ssl);
    put_line(ssl,"MAIL FROM:<[email protected]>/r/n");
    get_line(ssl);
    put_line(ssl,"RCPT TO:<[email protected]>/r/n");
    get_line(ssl);
    put_line(ssl,"DATA/r/n");
    get_line(ssl);
    
    str = makeHeader();
    put_line(ssl,str);
    free(str);

    str = makeBody("body");
    put_line(ssl,str);
    free(str);

    put_line(ssl,"/r/n./r/n");
    get_line(ssl);
    put_line(ssl,"QUIT/r/n");
    get_line(ssl);


    /* 收尾工作 */
    SSL_shutdown (ssl); /* send SSL/TLS close_notify */
    shutdown (sd,2);
    SSL_free (ssl);
    SSL_CTX_free (ctx);

    return 0;
}

 
参考资料
 
Beginner's Tutorial: COM/ATL Simple Project
 

你可能感兴趣的:(server,socket,ssl,Google,null,Gmail)