将CURL使用的普通套接字转换为C

时间:2018-03-13 06:09:03

标签: c sockets ssl libcurl

我正在处理由第三方编写的代码(我不想根据我的解释进行根本改变),这会打开一个tcp连接来与服务器通话json-rpc。我想将ssl添加到此,因为服务器具有用于相同通信的ssl端口。编辑:我想避免使用外部代理(如stunnel,它工作)。

目前,套接字由socket()打开,并通过CURLOPT_OPENSOCKETFUNCTION传递给curl,并告知libcurl在通过CURLOPT_CONNECT_ONLY连接(不执行数据传输)后停止。

我尝试从curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);开始启用CURL中的SSL选项,但它们没有效果,即连接本身成功,函数connect()返回true(CURL很高兴)但数据仍然存在未加密地发送。

我的理解是:套接字是在外部创建的(并传递给CURL,被告知不处理数据传输)然后直接写入,而不是通过CURL或OpenSSL,所以没有实际加密传输...尽管启用了SSL在CURL。

我还尝试通过执行sprintf(sctx->curl_url, "https%s", strstr(url, "://"));将CURL指向https://然后我得到:

SSL: couldn't create a context: error:140A90A1:lib(20):func(169):reason(161)

我还尝试直接在打开的套接字上使用OpenSSL,就像这里https://stackoverflow.com/a/41321247/1676217一样,通过初始化OpenSSL并将其文件描述符设置为sctx->sock,这是传递给CURL的相同套接字(即调用SSL_set_fd(ssl, sctx->sock);)但SSL_Connect()失败。

我无法完全更改代码以删除CURL或删除外部套接字创建,因为两者都在代码的其他部分中使用,但我可以尝试对其进行修改。

基本上,我希望保留thread_recv_line()thread_send_line()的函数声明,并让它们使用SSL。

如果有人精通OpenSSL,CURL和/或套接字可以告诉我如何在这段代码中做到这一点,我将非常感激。

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#define socket_blocks() (errno == EAGAIN || errno == EWOULDBLOCK)

#define RBUFSIZE 2048
#define RECVSIZE (RBUFSIZE - 4)

struct ctx {
    char *url;
    CURL *curl;
    char *curl_url;
    char curl_err_str[CURL_ERROR_SIZE];
    curl_socket_t sock;
    size_t sockbuf_size;
    char *sockbuf;
    pthread_mutex_t sock_lock;
};

static curl_socket_t opensocket_grab_cb(void *clientp, curlsocktype purpose,
    struct curl_sockaddr *addr)
{
    curl_socket_t *sock = (curl_socket_t*) clientp;
    *sock = socket(addr->family, addr->socktype, addr->protocol);
    return *sock;
}

bool connect(struct ctx *sctx, const char *url)
{
    CURL *curl;
    int rc;

    pthread_mutex_lock(&sctx->sock_lock);
    if (sctx->curl)
        curl_easy_cleanup(sctx->curl);
    sctx->curl = curl_easy_init();
    if (!sctx->curl) {
        pthread_mutex_unlock(&sctx->sock_lock);
        return false;
    }
    curl = sctx->curl;
    if (!sctx->sockbuf) {
        sctx->sockbuf = (char*) calloc(RBUFSIZE, 1);
        sctx->sockbuf_size = RBUFSIZE;
    }
    sctx->sockbuf[0] = '\0';
    pthread_mutex_unlock(&sctx->sock_lock);

    if (url != sctx->url) {
        free(sctx->url);
        sctx->url = strdup(url);
    }
    free(sctx->curl_url);
    sctx->curl_url = (char*) malloc(strlen(url));
    sprintf(sctx->curl_url, "http%s", strstr(url, "://"));

    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
    curl_easy_setopt(curl, CURLOPT_URL, sctx->curl_url);
    curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);
    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sctx->curl_err_str);
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
    curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1);
    curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1);
    curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_keepalive_cb);
    curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_grab_cb);
    curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &sctx->sock);
    curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1);

    rc = curl_easy_perform(curl);
    if (rc) {
        curl_easy_cleanup(curl);
        sctx->curl = NULL;
        return false;
    }

    return true;
}

static bool send_line(curl_socket_t sock, char *s)
{
    size_t sent = 0;
    int len = (int) strlen(s);
    s[len++] = '\n';
    while (len > 0) {
        struct timeval timeout = {0, 0};
        int n;
        fd_set wd;
        FD_ZERO(&wd);
        FD_SET(sock, &wd);
        if (select((int) (sock + 1), NULL, &wd, NULL, &timeout) < 1)
            return false;
        n = send(sock, s + sent, len, 0);
        if (n < 0) {
            if (!socket_blocks())
                return false;
            n = 0;
        }
        sent += n;
        len -= n;
    }
    return true;
}

bool thread_send_line(struct ctx *sctx, char *s)
{
    bool ret = false;
    pthread_mutex_lock(&sctx->sock_lock);
    ret = send_line(sctx->sock, s);
    pthread_mutex_unlock(&sctx->sock_lock);
    return ret;
}

static bool socket_full(curl_socket_t sock, int timeout)
{
    struct timeval tv;
    fd_set rd;
    FD_ZERO(&rd);
    FD_SET(sock, &rd);
    tv.tv_sec = timeout;
    tv.tv_usec = 0;
    if (select((int)(sock + 1), &rd, NULL, NULL, &tv) > 0)
        return true;
    return false;
}

static void buffer_append(struct ctx *sctx, const char *s)
{
    size_t old, n;
    old = strlen(sctx->sockbuf);
    n = old + strlen(s) + 1;
    if (n >= sctx->sockbuf_size) {
        sctx->sockbuf_size = n + (RBUFSIZE - (n % RBUFSIZE));
        sctx->sockbuf = (char*) realloc(sctx->sockbuf, sctx->sockbuf_size);
    }
    strcpy(sctx->sockbuf + old, s);
}

char *thread_recv_line(struct ctx *sctx)
{
    ssize_t len, buflen;
    char *tok, *sret = NULL;

    if (!strstr(sctx->sockbuf, "\n")) {
        bool ret = true;
        time_t rstart;
        time(&rstart);

        if (!socket_full(sctx->sock, 60))
            return sret;
        do {
            char s[RBUFSIZE];
            ssize_t n;

            memset(s, 0, RBUFSIZE);
            n = recv(sctx->sock, s, RECVSIZE, 0);
            if (!n) {
                ret = false;
                break;
            }
            if (n < 0) {
                if (!socket_blocks() || !socket_full(sctx->sock, 1)) {
                    ret = false;
                    break;
                }
            } else
                buffer_append(sctx, s);
        } while (time(NULL) - rstart < 60 && !strstr(sctx->sockbuf, "\n"));

        if (!ret)
            return sret;
    }

    buflen = (ssize_t) strlen(sctx->sockbuf);
    tok = strtok(sctx->sockbuf, "\n");
    if (!tok)
        return sret;
    sret = strdup(tok);
    len = (ssize_t) strlen(sret);

    if (buflen > len + 1)
        memmove(sctx->sockbuf, sctx->sockbuf + len + 1, buflen - len + 1);
    else
        sctx->sockbuf[0] = '\0';
}

2 个答案:

答案 0 :(得分:0)

这不是您要求的,但您应该考虑在代码和远程服务器之间放置一个单独的TCP-to-TLS代理(例如HAProxy)。

您不必弄乱TLS协议细节,也不必修改现有代码。这样的设置可能比你自己写的任何东西都更安全,更快。

答案 1 :(得分:0)

Libcurl与服务器建立SSL连接,即使CURLOPT_CONNECT_ONLY也是如此。无法在同一TCP连接之上建立另一个SSL连接。继续使用的唯一方法是使用libcurl已经建立的相同SSL连接。

幸运的是,可以使用curl_easy_getinfoCURLINFO_TLS_SSL_PTR从卷曲句柄中检索SSL连接数据。它应该返回SSL*类型的指针。

thread_send_linethread_recv_line函数不应直接使用套接字,而是在检索到的SSL指针上调用SSL_readSSL_write

有关详细信息,请参阅CURLINFO_TLS_SSL_PTR。确保您获得SSL指针,而不是SSL_CTX指针。

这样的事情应该有效:

bool thread_send_line(struct ctx *sctx, char *s)
{    
    bool ret = false;
    struct curl_tlssessioninfo* session;
    curl_easy_getinfo (sctx->curl, CURLINFO_TLS_SSL_PTR, &session);
    SSL* ssl = session->internals;

    pthread_mutex_lock(&sctx->sock_lock);

    int len = (int) strlen(s);
    s[len++] = '\n';
    ret = SSL_write(ssl, s, len);

    pthread_mutex_unlock(&sctx->sock_lock);
    return ret == len;    
}
相关问题