使用ssh客户端,登录到ssh服务器,或者使用scp命令,从ssh服务器上传或者下载文件,非常方便。
但是,如何使用ssh协议,开发文件的处理,集成到我们的应用中呢?其实非常简单,libssh2都给我们包装好了。
环境部署
如何使用Redhat族系统,比如fedora,可以使用
dnf install libssh2-devel
来安装开发库。如果是Debian族的系统,则可以使用
apt install libssh2-1-dev
来安装。
安装好以后,就可以使用
pkg-config --cflags
来获取C/C++语言的头文件包含参数。
使用
pkg-config --libs
来获取C/C++的动态连接库参数。
API以及通用流程
ssh协议中,使用文件相关的api以及通用的api,都在libssh2.h中。而涉及到目录相关的api,一般都在libssh2_sftp.h中。
在使用ssh2协议开始之前,需要进行全局的初始化,即调用:
int libssh2_init(int flags);
flags如果不设置为0,其实只有一个可用的值:LIBSSH2_INIT_NO_CRYPTO。
不再使用ssh2协议之后,可以调用:
void libssh2_exit(void);
来释放相应资源。
- 注:为简化理解,以下函数说明中,均使用了库文件中的没有_ex的宏。其实带_ex的函数,只是把一些字符串长度传递到了函数中,与直接使用宏定义的版本区别不大。
一般来说,进行一个ssh协议的文件或者目录操作,需要这样几步:
- 连接
- 认证
- 打开操作句柄
- 操作
- 关闭句柄
连接
使用ssh2建立连接的过程,与使用SSL建立连接的过程很类似,都需要我们手动建立一个TCP连接,之后再把一个ssh2的Session关联到TCP连接上去。
即,先创建一个socket,然后调用connect,然后调用:
LIBSSH2_SESSION * libssh2_session_init ();
创建一个session,再使用
int libssh2_session_handshake(LIBSSH2_SESSION *session, libssh2_socket_t sock);
建立ssh2协议的握手。
认证
握手成功以后,先获取ssh服务器支持的认证方式,然后选择一个,进行认证。
获取认证方式的函数为:
char *libssh2_userauth_list(LIBSSH2_SESSION *session,
const char *username,
unsigned int username_len);
认证的函数有几个,分别对应几种认证方式,分别为:
// 密码
int
libssh2_userauth_password(LIBSSH2_SESSION *session,
const char *username,
const char *password,
LIBSSH2_PASSWD_CHANGEREQ_FUNC
((*passwd_change_cb)));
// 交互式密码
int
libssh2_userauth_keyboard_interactive(LIBSSH2_SESSION* session,
const char *username,
LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC
((*response_callback)));
// 证书认证
int
libssh2_userauth_publickey(LIBSSH2_SESSION *session,
const char *username,
const unsigned char *pubkeydata,
size_t pubkeydata_len,
LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC
((*sign_callback)),
void **abstract);
int
libssh2_userauth_publickey_fromfile(LIBSSH2_SESSION *session,
const char *username,
const char *publickey,
const char *privatekey,
const char *passphrase);
操作
连接成功以后,就可以调用文件或者目录相关的API了。下面在具体的例子中说明。
连接示例代码:
int
ssh2_connect (const char *username, const char *pass, const char *sshkey_pub, const char *sshkey_pri)
{
int sock, rc;
int auth_pw = 0;
char *userauthlist;
LIBSSH2_SESSION *session;
// sock创建过程,省略
// sock = socket, connect
session = libssh2_session_init ();
if (session == NULL)
{
return -1;
}
libssh2_session_set_blocking (session, 1);
rc = libssh2_session_handshake (session, sock);
if (rc)
{
fprintf (stderr, "Failure establishing SSH session: %d\n", rc);
libss2_session_free (session);
close (sock);
return -1;
}
userauthlist = libssh2_userauth_list (session, username,
(unsigned int)strlen (username));
if (userauthlist)
{
if (strstr (userauthlist, "password"))
{
auth_pw |= 1;
}
if (strstr (userauthlist, "publickey"))
{
auth_pw |= 4;
}
if ((auth_pw & 1) && username && pass)
{
auth_pw = 1;
}
if ((auth_pw & 4) && sshkey_pub && sshkey_pri)
{
auth_pw = 4;
}
if (auth_pw == 1)
{
if (libssh2_userauth_password (session, username, pass))
{
libss2_session_free (session);
close (sock);
return -1;
}
}
else if (auth_pw == 4)
{
if (libssh2_userauth_publickey_fromfile (
session, username, sshkey_pub,
sshkey_pri, pass))
{
libss2_session_free (session);
close (sock);
return -1;
}
}
else
{
fprintf (stderr, "No supported authentication methods found!\n");
libss2_session_free (session);
close (sock);
return -1;
}
}
断开连接
static void
ssh2_disconnect (LIBSSH2_SESSION *session, int sock)
{
libssh2_session_disconnect (session, "Normal Shutdown");
libssh2_session_free (session);
close (sock);
}
获取目录列表
使用libssh2获取目录列表,需要先创建一个LIBSSH2_SFTP的Session,在这个Session上打开目录句柄LIBSSH2_SFTP_HANDLE,之后像使用本地目录句柄一样,依次读取目录中的节点。
创建、销毁LIBSS2_SFTP以及获取错误的函数分别为:
LIBSSH2_API LIBSSH2_SFTP *libssh2_sftp_init(LIBSSH2_SESSION *session);
LIBSSH2_API int libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp);
LIBSSH2_API unsigned long libssh2_sftp_last_error(LIBSSH2_SFTP *sftp);
打开、关闭目录句柄的函数分别为:
LIBSSH2_SFTP_HANDLE * libssh2_sftp_opendir(LIBSSH2_SFTP *sftp,
const char *filename);
int libssh2_sftp_closedir(LIBSSH2_SFTP_HANDLE *handle);
读取目录节点的函数为:
int libssh2_sftp_readdir(LIBSSH2_SFTP_HANDLE *handle, \
char *buffer, size_t buffer_maxlen,
LIBSSH2_SFTP_ATTRIBUTES *attrs);
对于每一个读取出来的节点,使用LIBSSH2_SFTP_ATTRIBUTES结构可以获取节点的信息。
这个结构的定义为:
struct _LIBSSH2_SFTP_ATTRIBUTES {
/* If flags & ATTR_* bit is set, then the value in this struct will be
* meaningful Otherwise it should be ignored
*/
unsigned long flags;
libssh2_uint64_t filesize;
unsigned long uid, gid;
unsigned long permissions;
unsigned long atime, mtime;
};
如:在falgs得到了permissions的情况下,可以通过
#define LIBSSH2_SFTP_S_ISLNK(m) \
(((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFLNK)
#define LIBSSH2_SFTP_S_ISREG(m) \
(((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFREG)
#define LIBSSH2_SFTP_S_ISDIR(m) \
(((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFDIR)
#define LIBSSH2_SFTP_S_ISCHR(m) \
(((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFCHR)
#define LIBSSH2_SFTP_S_ISBLK(m) \
(((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFBLK)
#define LIBSSH2_SFTP_S_ISFIFO(m) \
(((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFIFO)
#define LIBSSH2_SFTP_S_ISSOCK(m) \
(((m) & LIBSSH2_SFTP_S_IFMT) == LIBSSH2_SFTP_S_IFSOCK)
这些宏来判断节点的文件类型。
以下是一个获取目录节点的例子:
int
ssh2_list_dir (LIBSSH2_SESSION *session, const char *dir)
{
LIBSSH2_SFTP *sftp;
LIBSSH2_SFTP_HANDLE *handle;
LIBSSH2_SFTP_ATTRIBUTES attrs[1];
struct sftp_node *node;
char mem[512];
int rc;
sftp = libssh2_sftp_init (session);
handle = libssh2_sftp_opendir (sftp, dir);
if (handle == NULL)
{
return -1;
}
do
{
rc = libssh2_sftp_readdir (handle, mem, sizeof (mem), attrs);
if (rc > 0)
{
if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS)
{
if (LIBSSH2_SFTP_S_ISDIR (attrs->permissions))
{
if (strcmp (mem, ".") == 0 || strcmp (mem, "..") == 0)
continue;
printf ("got dir: %s\n", mem);
}
else if (LIBSSH2_SFTP_S_ISREG (attrs->permissions))
{
if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE)
{
printf ("got file: %s, size: %ld\n", mem, attr->filesize);
}
else
{
printf ("got file: %s has no size\n", mem);
}
}
else
{
printf ("file: %s type not supported\n", mem);
}
}
else
{
printf ("file: %s has not permissions\n", mem);
}
}
}
while (rc > 0);
libssh2_sftp_closedir (handle);
libssh2_sftp_shutdown (sftp);
return rc;
}
创建目录
创建目录使用的函数为:
int libssh2_sftp_mkdir(LIBSSH2_SFTP *sftp,
const char *path,
long mode);
如:
int
ssh2_mkdir (LIBSSH2_SFTP *sftp, const char *dir)
{
int rc;
rc = libssh2_sftp_mkdir (sftp, dir, 0777);
if (rc != 0)
{
fprintf (stderr, "mkdir error: %lu\n",
libssh2_sftp_last_error (sftp->sftp_session));
return -1;
}
return 0;
}
删除目录
删除目录使用的函数为:
int libssh2_sftp_rmdir(LIBSSH2_SFTP *sftp,
const char *path);
重命名文件
重命名文件(目录)使用的函数为:
int libssh2_sftp_rename(LIBSSH2_SFTP *sftp,
const char *name,
const char *new_name);
删除文件
删除文件使用的函数为:
int libssh2_sftp_unlink(LIBSSH2_SFTP *sftp,
const char *path);
下载文件
下载或者上传文件的时候,不用使用LIBSSH2_SFTP结构,而是直接使用LIBSSH2_SESSION来创建LIBSSH2_CHANNEL。
首先打开一个接收文件句柄:
LIBSSH2_CHANNEL *libssh2_scp_recv(LIBSSH2_SESSION *session,
const char *path,
struct stat *sb);
然后依次读取文件的内容:
ssize_t libssh2_channel_read(LIBSSH2_CHANNEL *channel,
char *buf,
size_t buflen);
最后关闭文件句柄:
int libssh2_channel_close(LIBSSH2_CHANNEL *channel);
以下是下载一个文件的示例:
int
ssh2_download_file (LIBSSH2_SESSION *session, const char *path)
{
int rc;
FILE *fp;
size_t got;
libssh2_struct_stat fileinfo[1];
LIBSSH2_CHANNEL *channel;
fp = fopen (path, "wb");
if (fp == NULL)
{
return -1;
}
// 打开远程文件句柄
channel = libssh2_scp_recv2 (session, path, fileinfo);
if (channel == NULL)
{
fclose (fp);
return -1;
}
got = 0;
while (got < fileinfo->st_size)
{
char mem[64 * 1024];
int amount = sizeof (mem);
ssize_t nread;
if ((fileinfo->st_size - got) < amount)
{
amount = (int)(fileinfo->st_size - got);
}
nread = libssh2_channel_read (channel, mem, amount);
if (nread > 0)
{
fwrite (mem, 1, nread, fp);
// 为方便理解,这里忽略文件写入的错误处理。
}
else if (nread <= 0)
{
break;
}
got += nread;
}
if (got == fileinfo->st_size)
rc = 0;
else
rc = -1;
libssh2_channel_free (channel);
libssh2_channel_close (channel);
fclose (fp);
return rc;
}
上传文件
上传文件与下载文件大同小异,区别是打开文件句柄的时候,使用的函数是libssh2_scp_send,另外最后关闭channel之前,执行以下这些函数:
libssh2_channel_send_eof (channel);
libssh2_channel_wait_eof (channel);
libssh2_channel_wait_closed (channel);
确保远端正常接收完毕。