ActivePerl + OpenSSL + Zlib + Libssh2 + Visual Studio 2008
SSH2是一套安全通讯协议框架(早期的SSH1由于存在安全漏洞,现在已经不用了),基于SSH2协议的产品目前主要有openssh( http://www.openssh.org/ ),putty( http://www.putty.org/ ),SSH Secure Shell Client(从 http://www.moodisk.com/zh_CN/index.html?src=download.php 可以下载)等,这些都是开源的,但是这些代码非常难懂而且复杂,一个个函数深层次的调用很快就让人在C语言代码的海洋中迷失了方向,妄图通过从这些开源软件中抽取程序代码段来“组装”自己的应用程序是非一般人所能实现的。不过还好网路上出现了一些开源的SSH2开发库,利用这些开发库开发自己的SSH2程序却要简单得多,由于这些开发库都是开源的,往往是针对linux平台的,而且一般只提供了源代码。在windows上利用这些库还必须要完成:编译有关依赖库-->编译ssh2库-->集成到开发环境(如Visual Studio)中-->熟悉SSH2库函数用法-->开始编写自己的程序。由于开发基于ssh2协议的例子网上很少,中文资料就更少。本人在完成这么一个开发环境就断断续续耗费了我一周的时间,现在终于可以开始编写的基于SSH2协议的程序了。我不敢独享,整理出一篇博文,和各位IT同仁分享。
本文的内容安排是:首先介绍如何编译各种依赖库,然后介绍如何把这些依赖库继集成到Visual Studio 2008中,接下来介绍基于SSH2协议的程序的一般框架,最后举一个实际的开发例子。附录A是我自己开发的一个实际例子,附录B列出了全部的 libssh2库函数。
一·准备一些工具
1、安装Visual Studio 2008开发环境(最好是英文版的,我的是VS2008版本是9.0.30729.1 SP, Windows SDK 6.1)。什么?你不会装!你去google上搜一下“Visual Studio 2008安装过程详解”或者点击参考文章: http://dev.yesky.com/msdn/329/7823829.shtml
2、安装最新的MSDN文档库(可选,不装也行)。蛮大的,从网上下载要一些时间,或者参考在线文档 http://msdn.microsoft.com/en-us/library/default.aspx
3、安装解压缩工具winRAR。从 http://cncspace.newhua.com/down/files/wrar380sc.exe 下载并安装。
4、安装汇编工具nasm。从 http://www.nasm.us/pub/nasm/releasebuilds/2.06/win32/nasm-2.06-installer.exe 下载并安装。
5、安装脚本语言ActivePerl。从 http://downloads.activestate.com/ActivePerl/Windows/5.10/ActivePerl-5.10.0.1005-MSWin32-x86-290470.msi 下载,然后安装(安装过程中选择默认选项即可)。
二、编译各种依赖库
LibSSH2库依赖openssl和zlib两个库,所以我们必须先编译zlib和openssl两个库。
1、zlib库。网上提供了源码和目标DLL安装包,我们直接下载DLL安装比较快捷,从 http://www.zlib.net/zlib123-dll.zip 下载,并解压到C:/zlib下(最终存在目录C:/zlib/include即表示正确),把C:/zlib/zlib1.dll拷贝到c:/windows/system32下。
2、OpenSSL库。OpenSSL库网上只有源代码,我们首先必须编译。从 http://www.openssl.org/source/openssl-0.9.8k.tar.gz 下载源代码包,然后解压到目录C:/openssl-0.9.8k下(最终存在目录C:/openssl-0.9.8k/apps即表示正确)。进入 Visual Studio 2008的命令提示符(开始-->所有程序-->Microsoft Visual Studio 2008-->Visual Studio Tools-->Visual Studio 2008 Command Prompt),依次输入如下命令:
mkdir c:/openssl_lib
cd C:/openssl-0.9.8k
perl Configure VC-WIN32 --prefix=c:/openssl_lib
---输出如下的信息:
……
RC4_CHUNK is undefined
Configured for VC-WIN32.
ms/do_masm
nmake -f ms/nt.mak
---好了,去喝杯咖啡吧,半个小时后应该编译完了。
nmake -f ms/nt.mak test
---如何库编译正确,你应该看到“passwd all tests”字样。
nmake -f ms/nt.mak install
---现在应该在c:/openssl_lib下安装了openssl库文件和头文件了。
---如果编译出错,那么也可以查看文件C:/openssl-0.9.8k/INSTALL.W32,里面列举了一些错误处理方法。
3、LibSSH2库。LibSSH2库网上只有源代码,我们首先必须编译。从 http://nchc.dl.sourceforge.net/sourceforge/libssh2/libssh2-1.1.tar.gz 下载源代码包,然后解压到目录C:/libssh2-1.1下(存在目录C:/libssh2-1.1/include表示正确),创建目录“C: /libssh2/lib”和“C:/libssh2/include”,在Visual Studio 2008 IDE中打开C:/libssh2-1.1/win32/libssh2.dsw,编辑文件libssh2.h,把如下的第54行
# define LIBSSH2_API __declspec(dllexport)
替换成:
# define LIBSSH2_API// __declspec(dllexport)
在左边的Solution Explorer中右击libssh2_lib-->Properties-->Configuration Properties:
-->C/C++ --> General -->选择Additional Include Dirextories-->附加
;C:/openssl_lib/include;C:/zlib/include
-->Code Generation --> Runtime Library -->选择Multi-thread(/MT)
-->Librarian --> General:
Output File --> C:/libssh2/lib/libssh2.lib
Additional Dependencies --> libeay32.lib ssleay32.lib zdll.lib
Additional Library Directories --> 附加路径C:/openssl_lib/lib;C:/zlib/lib
最后点击“OK"确定。
现在可以开始编译了,右击libssh2_lib选择Build。如果编译成功那么在C:/libssh2/lib下存在文件 libssh2.lib了。接下来再把C:/libssh2-1.1/include下的全部文件拷贝到C:/libssh2/include下,把文件 C:/libssh2-1.1/win32/libssh2_config.h也拷贝到C:/libssh2/include下。好了,那么我们最终编译的库和头文件布局如下:
C:/libssh2/include下有文件:libssh2.h,libssh2_config.h,libssh2_publickey.h,libssh2_sftp.h;
C:/libssh2/lib下有文件libssh2.lib
--- 如果编译失败,你可以从 这里 直接下载我编译好的LibSSH2库。
三·一个实例
windwos平台上基于SSH2协议的程序框架是:
初始化Winsock动态库(WSAStartup())
|
V
创建标准的socket套接字并连接(socket(),connect())
|
V
创建和启用一个SSH2会话(libssh2_session_init(),libssh2_session_startup())
|
V
获取指纹数据(libssh2_hostkey_hash())
|
V
认证(libssh2_userauth_password()或libssh2_userauth_publickey_fromfile())
|
V
初始化sftp子系统(libssh2_sftp_init())
|
V
进行各种sftp操作命令(如创建目录、下载等,函数参见本文附录B)
|
V
关闭sftp子系统(libssh2_sftp_shutdown())
|
V
拆除ssh2会话(libssh2_session_disconnect())
|
V
释放ssh2会话结构(libssh2_session_free())
|
V
关闭标准socket套接字(closesocket())
注意:上面列出的函数的具体用法请参阅网站在线文档: http://www.libssh2.org/wiki/index.php/Documentation
下面用一个具体的实例进一步说明如何开发一个ssh2世纪功能,它实现一个简单的功能:在服务器上创建一个目录,列出另外一个目录下的文件。本实例主要展示SSH2协议的程序框架。
1、在Visual Studio 2008开发环境中新建一个工程: File-->New-->Project...-->展开Visual C++ --> 选择Win32 Console Application,Name处输入testsftp,Location处出入C:/projects,Solution Name处输入testftp,勾选Create directory for solution,最后点击OK进入下一步再点击Next。这一页只点选Console application,其他项都不要勾选或点选。最后点击Finish按钮完成新工程的创建。
2、在左边的工程浏览器窗口中展开Source Files,双击“testsftp.cpp”文件打开,里面只有寥寥数行:
// newsftp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
3、在屏幕左边的Solution Explorer中右击工程testsftp-->Properties-->Configuration Properties:
-->C/C++ --> General -->选择Additional Include Dirextories-->加C:/libssh2/include
--> Code Generation -->Runtime Library -->选择Multi-thread(/MT)
-->Linker
--> General -->选择Additional Library Directories --> 加C:/libssh2/lib
--> Input -->选择Additional Dependencies --> 加libssh2.lib
--> Command Line -->在Additional options中加 Ws2_32.lib
最后点击“OK"确定。
4、开始编译。按F7或点击菜单Build-->Build Solution。
附录A:我的testsftp.cpp文件内容
// testsftp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <libssh2_config.h>
#include <io.h>
#include <libssh2.h>
#include <libssh2_sftp.h>
#ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
# ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
int _tmain(int argc, char* argv[])
{
int sock, i, auth_pw = 1;
struct sockaddr_in sin;
const char *fingerprint;
LIBSSH2_SESSION *session;
int rc;
LIBSSH2_SFTP *sftp_session;
LIBSSH2_SFTP_HANDLE *sftp_handle;
#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(2,0), &wsadata);
#endif
/*
* The application code is responsible for creating the socket
* and establishing the connection
*/
if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))==INVALID_SOCKET){
fprintf(stderr,"failed to create a socket!/n");
return -1;
}
sin.sin_family = AF_INET;
sin.sin_port = htons(22);
if((sin.sin_addr.s_addr = inet_addr("192.168.104.105"))==INADDR_NONE){
fprintf(stderr,"The address is invalid!/n");
return -1;
}
if(connect(sock, (struct sockaddr*)(&sin),sizeof(struct sockaddr_in))!= 0){
fprintf(stderr, "failed to connect!/n");
return -1;
}
/* Create a session instance*/
if(!(session = libssh2_session_init())){
fprintf(stderr,"Init SSH session failed!/n");
goto CLOSESOCKET;
}
/* ... start it up. This will trade welcome banners, exchange keys,
* and setup crypto, compression, and MAC layers
*/
if((rc = libssh2_session_startup(session, sock))){
fprintf(stderr, "Failure establishing SSH session: %d/n", rc);
libssh2_session_free(session);
goto CLOSESOCKET;
}
/* At this point we havn't yet authenticated. The first thing to do
* is check the hostkey's fingerprint against our known hosts Your app
* may have it hard coded, may go to a file, may present it to the
* user, that's your call
*/
if((fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5))){
printf("Fingerprint: ");
for(i = 0; i < 16; i++) {
printf("%02X ", (unsigned char)fingerprint[i]);
}
printf("/n");
}
if (auth_pw) {
/* We could authenticate via password */
if ((libssh2_userauth_password(session, "sftpuser", "abc123"))) {
printf("Authentication by password failed./n");
goto SHUTDOWN;
}
} else {
/* Or by public key */
if (libssh2_userauth_publickey_fromfile(session, "sftpuser","/home/username/.ssh/id_rsa.pub","/home/username/.ssh/id_rsa","abc123")) {
printf("/tAuthentication by public key failed/n");
goto SHUTDOWN;
}
}
fprintf(stderr, "libssh2_sftp_init()!/n");
if(!(sftp_session = libssh2_sftp_init(session))){
fprintf(stderr, "Unable to init SFTP session/n");
goto SHUTDOWN;
}
/* Since we have not set non-blocking, tell libssh2 we are blocking */
libssh2_session_set_blocking(session, 1);
fprintf(stderr, "libssh2_sftp_opendir()!/n");
//建目录
if(libssh2_sftp_mkdir(sftp_session, "sftpdir/cba",
LIBSSH2_SFTP_S_IRWXU|
LIBSSH2_SFTP_S_IRGRP|LIBSSH2_SFTP_S_IXGRP|
LIBSSH2_SFTP_S_IROTH|LIBSSH2_SFTP_S_IXOTH)==-1)
fprintf(stderr,"Create dir failed!/n");
//浏览一个目录中的文件
if(!(sftp_handle = libssh2_sftp_opendir(sftp_session, "sftpdir"))){
fprintf(stderr, "Unable to open dir with SFTP/n");
goto SHUTDOWN;
}
fprintf(stderr, "libssh2_sftp_opendir() is done, now receive listing!/n");
do {
char mem[512];
char longentry[512];
LIBSSH2_SFTP_ATTRIBUTES attrs;
/* loop until we fail */
rc = libssh2_sftp_readdir_ex(sftp_handle, mem, sizeof(mem), longentry, sizeof(longentry), &attrs);
if(rc > 0) {
/* rc is the length of the file name in the mem
buffer */
if (longentry[0] != '/0') {
printf("%s/n", longentry);
} else {
if(attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) {
/* this should check what permissions it
is and print the output accordingly */
printf("--fix----- ");
}
else {
printf("---------- ");
}
if(attrs.flags & LIBSSH2_SFTP_ATTR_UIDGID) {
printf("%4ld %4ld ", attrs.uid, attrs.gid);
}
else {
printf(" - - ");
}
if(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) {
/* attrs.filesize is an uint64_t according to
the docs but there is no really good and
portable 64bit type for C before C99, and
correspondingly there was no good printf()
option for it... */
printf("%8lld ", attrs.filesize);
}
printf("%s/n", mem);
}
}
else
break;
} while (1);
libssh2_sftp_closedir(sftp_handle);
libssh2_sftp_shutdown(sftp_session);
SHUTDOWN:
libssh2_session_disconnect(session, "Normal Shutdown, Thank you for playing");
libssh2_session_free(session);
CLOSESOCKET:
#ifdef WIN32
Sleep(1000);
closesocket(sock);
#else
sleep(1);
close(sock);
#endif
printf("all done/n");
return 0;
}
附录B:libssh2.lib中函数列表
1. Session API:
libssh2_session_init()
libssh2_session_abstract()
libssh2_session_callback_set()
libssh2_banner_set()
libssh2_session_startup()
libssh2_session_disconnect()
libssh2_session_free()
libssh2_hostkey_hash()
libssh2_session_method_pref()
libssh2_session_methods()
libssh2_session_last_error()
libssh2_session_flag()
2. Userauth API:
libssh2_userauth_list()
libssh2_userauth_authenticated()
libssh2_userauth_password()
libssh2_userauth_publickey_fromfile()
libssh2_userauth_hostbased_fromfile()
libssh2_userauth_keyboard_interactive()
3. Channel API:
Channel Creation and Setup
libssh2_channel_open_session()
libssh2_channel_direct_tcpip()
libssh2_channel_forward_listen()
libssh2_channel_forward_cancel()
libssh2_channel_forward_accept()
libssh2_channel_setenv()
libssh2_channel_request_pty()
libssh2_channel_process_startup()
libssh2_channel_x11_req()
libssh2_scp_recv()
libssh2_scp_send()
Channel Shutdown and Destruction
libssh2_channel_send_eof()
libssh2_channel_eof()
libssh2_channel_close()
libssh2_channel_wait_closed()
libssh2_channel_get_exit_status()
libssh2_channel_free()
Input/Output
libssh2_channel_set_blocking()
libssh2_channel_read()
libssh2_channel_write()
libssh2_channel_handle_extended_data()
libssh2_channel_flush()
libssh2_poll_channel_read()
libssh2_poll()
Windowing
libssh2_channel_window_read()
libssh2_channel_receive_window_adjust()
libssh2_channel_window_write()
4. SFTP Subsystem:
Protocol startup and shutdown
libssh2_sftp_init()
libssh2_sftp_shutdown()
libssh2_sftp_last_error()
File/Directory Access
libssh2_sftp_open()
libssh2_sftp_opendir()
libssh2_sftp_read()
libssh2_sftp_readdir()
libssh2_sftp_write()
libssh2_sftp_close()
libssh2_sftp_closedir()
libssh2_sftp_seek()
libssh2_sftp_rewind()
libssh2_sftp_tell()
libssh2_sftp_fstat()
libssh2_sftp_fsetstat()
File/Directory Manipulation
libssh2_sftp_rename()
libssh2_sftp_unlink()
libssh2_sftp_mkdir()
libssh2_sftp_rmdir()
libssh2_sftp_stat()
libssh2_sftp_lstat()
libssh2_sftp_setstat()
libssh2_sftp_symlink()
libssh2_sftp_readlink()
libssh2_sftp_realpath()
5. Publickey Subsystem:
libssh2_publickey_init()
libssh2_publickey_shutdown()
libssh2_publickey_add()
libssh2_publickey_remove()
libssh2_publickey_list_fetch()
libssh2_publickey_list_free()
参考文献:
1. Using libcurl with SSH support in Visual Studio 2008.pdf(点击 这里 下载)
2. http://www.libssh2.org/wiki/index.php/Documentation