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.h
和zlib-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.lib
和libssl.lib
,里面并没有dll文件,很奇怪,因为dll生成在openssl-3.0.5
目录下了,分别叫libcrypto-3-x64.dll
和libssl-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;
}