Loading

libcurl windows下编译并实现sftp传输文件

最近几年信息安全事件频发,尤其是去年的log4j2漏洞,可以说是互联网史上破坏力最大、最危险的漏洞之一了,信息安全成了软件开发中非常重要的考虑因素。很多项目里功能项现在强制要求安全检测,只有符合安全标准才能发布。

作为信创产业的一枚螺丝钉,接到一个任务,要把原先使用的文件传输协议由FTP升级到SFTP,走读源代码后发现FTP协议是由Windows自带wininet库实现的,但是wininet不支持SFTP,想要实现SFTP传输文件只能用其他方法。这里我选择了libcurl。不过要注意的是libcurl默认下并不支持SFTP,需要在编译的时候添加libssh2依赖项,而libssh2又依赖于openssl和zlib,有了明晰的编译链,就开始我们的编译吧。

这次编译全部选择的是当前(2022年8月12日)最新版本编译,zlib-1.2.12、libssh2-1.10.0、openssl-3.0.5、libcurl-7.84.0,相比于网上其他编译教程应该更具参考性。
编译平台:Windows10。
IDE:VS2019。
Windows SDK version:10.0.20348.0。

zlib编译

zlib源码下 zlib-1.2.12\contrib\vstudio 目录自带VC9到VC14的项目文件,从VS2008到VS2015,而且zlib-1.2.12\contrib\vstudio\readme.txt 很贴心的告诉你,你甚至不需要自己编译项目,直接去 zLibDll 下载就好了。不过这个网站上的库文件都是旧版本(1.2.3),想要用新版zlib库还是要自己编译。
作为初次接触Windows编译的新手,没有用自带的项目文件编译,还是选择了最熟悉的cmake,Windows编译对我来说,过程挺麻烦的……
打开cmd进入zlib-1.2.12

mkdir build
cd build
cmake ..

一套三连后,build文件夹下生成了个巨多的项目文件。打开 zlib.sln ,默认是Debug编译,我们切换到Release。

编译ALL_BUILD,成功生成静态库和动态库,以及一些测试程序。生成路径在 zlib-1.2.12\build\Release

要注意的是,编译完成后并没有自动生成include和lib文件夹,需要我们自行把zlib-1.2.12下的zlib.hzlib-1.2.12\build下的zconf.h放到zlib-1.2.12\build\Release中,供后面使用。

openssl编译

openssl编译需要安装perl,这里默认已经安装好了perl。同时不再使用cmd,而是使用vs命令行工具,工具可以在开始菜单里找到,我们选择x64版本,而且一定要用管理员打开。

使用vs命令行工具进入openssl源码路径openssl-3.0.5 下,执行命令

mkdir build
perl Configure VC-WIN64A no-asm --prefix=D:\download\sftp_project\openssl-3.0.5\build
nmake
nmake install

perl生成makefile文件成功
经过长时间的nmake和nmake install后,在build下生成库文件和头文件,lib文件夹下生成2个库文件:libcrypto.liblibssl.lib,里面并没有dll文件,很奇怪,因为dll生成在openssl-3.0.5目录下了,分别叫libcrypto-3-x64.dlllibssl-3-x64.dll


[nmake install后生成的4个文件夹]

libssh2编译

libssh2源码目录下自带windows项目文件,位于win32文件夹下。用VS2019打开libssh2.dsp,进入的时候会提示单向升级,点击确定即可。
进入项目后,修改配置管理器,改为openssl dll release,并且把x86改为x64。


接着右键解决方案,点击属性,增加 LIBSSH2_HAVE_ZLIB 宏定义。

然后将zlib和openssl的头文件路径、库路径加入到配置中,再将库加入到配置中。要注意的是原先的库配置里有 libeay32.lib ,这个库是openssl已经过时的生成库,在openssl 1.1.0以后libeay32.lib 改名为 libcrypto.lib。所以我们删除原先的libeay32.lib,添加libcrypto.lib


[将openssl和zlib头文件路径加入到path里]


[将openssl和zlib库文件路径加入到path里]


[删除libeay32.lib并加入libcrypto.lib]

准备完成后,编译项目,成功编译出libss2.dll。生成路径在 libssh2-1.10.0\win32\Release_dll

libcurl编译

libcurl也提供了windows project文件,不过我们选择nmake编译。
curl-7.84.0\winbuild下,README.md 文件写了很详细的编译流程。
首先需要在curl-7.84.0同父目录里创建一个deps目录。

然后在deps下创建三个目录,分别为:bin、include和lib

在include目录下放入zlib、libssh2、openssl的头文件文件:

在lib下放入上面生成的库文件文件:

依赖文件准备完毕后,打开VS命令行工具进入curl-7.84.0\winbuild文件夹,执行命令

nmake /f Makefile.vc mode=dll VC=16 WITH_ZLIB=dll WITH_SSH2=dll MACHINE=x64 WITH_SSL=dll WITH_PREFIX=D:\download\sftp_project\curl-7.84.0\winbuild\build

执行完毕后会在curl-7.84.0\winbuild\build目录下生成bin、include和lib文件夹,里面存放了我们需要的库文件和头文件以及curl.exe。其中动态库文件放在了bin文件夹下,静态库文件放在了lib下。
至此,libcurl编译完成。

接下来进行测试,libcurl官方很贴心的提供了很多example代码,在libcurl - source code examples 里。我们选择sftpget进行sftp下载测试。
需要稍微修改一下SFTP服务器IP和登录账户密码。测试了一下,可以正常下载文件。

/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at https://curl.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 * SPDX-License-Identifier: curl
 *
 ***************************************************************************/
 /* <DESC>
  * Gets a file using an SFTP URL.
  * </DESC>
  */

#include <stdio.h>

#include <curl/curl.h>

  /* define this to switch off the use of ssh-agent in this program */
#undef DISABLE_SSH_AGENT

/*
 * This is an example showing how to get a single file from an SFTP server.
 * It delays the actual destination file creation until the first write
 * callback so that it will not create an empty file in case the remote file
 * does not exist or something else fails.
 */

struct FtpFile {
    const char* filename;
    FILE* stream;
};

static size_t my_fwrite(void* buffer, size_t size, size_t nmemb,
    void* stream)
{
    struct FtpFile* out = (struct FtpFile*)stream;
    if (!out->stream) {
        /* open file for writing */
        out->stream = fopen(out->filename, "wb");
        if (!out->stream)
            return -1; /* failure, cannot open file to write */
    }
    return fwrite(buffer, size, nmemb, out->stream);
}


int main(void)
{
    CURL* curl;
    CURLcode res;
    struct FtpFile ftpfile = {
      "D:\\download\\1.txt", /* name to store the file as if successful */
      NULL
    };

    curl_global_init(CURL_GLOBAL_DEFAULT);

    curl = curl_easy_init();
    if (curl) {
        /*
         * You better replace the URL with one that works!
         */
        curl_easy_setopt(curl, CURLOPT_URL,
            "sftp://127.0.0.1:22/usr/1.txt");
        curl_easy_setopt(curl, CURLOPT_USERPWD, "username:password");
        /* Define our callback to get called when there's data to be written */
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_fwrite);
        /* Set a pointer to our struct to pass to the callback */
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ftpfile);

#ifndef DISABLE_SSH_AGENT
        /* We activate ssh agent. For this to work you need
           to have ssh-agent running (type set | grep SSH_AGENT to check) or
           pageant on Windows (there is an icon in systray if so) */
        curl_easy_setopt(curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PASSWORD);
#endif

        /* Switch on full protocol/debug output */
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

        res = curl_easy_perform(curl);

        /* always cleanup */
        curl_easy_cleanup(curl);

        if (CURLE_OK != res) {
            /* we failed */
            fprintf(stderr, "curl told us %d\n", res);
        }
    }

    if (ftpfile.stream)
        fclose(ftpfile.stream); /* close the local file */

    curl_global_cleanup();

    return 0;
}
posted @ 2022-08-15 21:02  柴承训  阅读(1410)  评论(0编辑  收藏  举报