libssh

libssh库简介

今天依旧是一篇旧文,还是ssh相关。ssh相关的开发大多是基于libssh库。本文关于libssh库的介绍,基本上也是对文档的翻译,时隔较旧,如有纰漏欢迎指正。

Chapter 1:SSH会话示例

创建会话并设置选项

1
2
3
4
5
6
7
8
9
10
11
#include <libssh/libssh.h>
#include <stdlib.h>

int main()
{
ssh_session my_ssh_session = ssh_new();
if (my_ssh_session == NULL)
exit(-1);

ssh_free(my_ssh_session);
}

ssh_new()

定义:

1
ssh_session ssh_new(void)

返回一个ssh_session指针,错误时返回NULL

引用ssh_buffer_new()和ssh_set_blocking()

ssh_buffer_new()

定义:

1
struct ssh_buffer_struct * ssh_buffer_new(void)

创建一个新的SSH缓冲区

返回新初始化的SSH缓冲区,错误时为NULL

ssh_set_blocking()

定义:

1
void ssh_set_blocking (ssh_session session,int blocking)

将会话设置为阻塞/非阻塞模式

blocking 参数 0 设置为非阻塞模式

ssh_free()

定义:

1
void ssh_free(ssh_session session)

释放已分配的SSH会话句柄

libssh遵循allocate-it-deallocate-it模式,使用ssh_new()分配,必须使用ssh_free()进行取消分配

ssh_options_set()

定义:

1
int ssh_options_set(ssh_session session, enum ssh_optinons_e type, const void* value)

设置会话的选项

type:要设置的选项类型,常用的选项:

  • SSH_OPTIONS_HOST:连接到的主机名或IP地址(const char*)
  • SSH_OPTIONS_PORT:连接到的端口(unsigned int)
  • SSH_OPTIONS_USER:想要连接的系统用户(const char*)
  • SSH_OPTIONS_LOG_VERBOSITY:打印的消息数量(int)

其中SSH_OPTIONS_HOST是唯一的强制选项;端口号默认22;不使用SSH_OPTIONS_USER,则会使用当前账户的本地账用户名

连接到服务器

完成设置后。可使用ssh_connect()进行连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <libssh/libssh.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
ssh_session my_ssh_session;
int rc;

my_ssh_session = ssh_new();
if (my_ssh_session == NULL)
exit(-1);

ssh_options_set(my_ssh_session,SSH_OPTIONS_HOST,"localhost");

rc = ssh_connect(my_ssh_session);
if (rc!=SSH_OK)
{
fprintf(stderr,"连接到本地主机错误:%s\n",ssh_get_error(my_ssh_session));
exit(-1);
}

ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
}

ssh_connect()

定义:

1
int ssh_connect(ssh_session session)

连接到ssh服务器

成功时返回SSH_OK,错误时返回SSH_ERROR。若会话处于无阻塞模式,且必须要重连,返回SSH_AGAIN

ssh_get_error()

定义:

1
const char* ssh_get_error(void* error)

error ssh_session 或者 ssh_bind

返回描述错误的static字符串

ssh_disconnect()

定义:

1
void ssh_disconnect(ssh_session session)

从一个会话断开连接(服务器或客户端);该会话之后可以在新的会话中重新开始使用。

与ssh_connect()组合使用

验证服务器

连接完成后,必须检查刚刚连接的服务器是否一致且安全可用,有两种方式实现:

  1. (推荐)使用ssh_is_server_known()函数。该函数将查看已知的主机文件(UNIX中的~/.ssh/known_hosts),查找服务器主机名的模式,并确认该主机是否存在列表中
  2. 使用ssh_get_pubkey_hash()函数。使用该函数获取二进制版本的公钥hash值,通过本地数据库检查此公钥是否已知且安全

ssh_is_server_known()

定义:

1
int ssh_is_server_known(ssh_session session)

返回一个状态码

ssh_get_pubkey_hash()

定义:

1
int ssh_get_pubkey_hash(ssh_session session, unsigned char** hash)

推荐使用ssh_get_publickey_hash()

如果时第一次使用远程主机,可以询问用户是否信任主机。如认为主机是有效的并且值得添加到已知主机文件中,可以使用ssh_write_knownhost()将其注册到已知主机文件中,或使用自己的数据库。

ssh_write_knownhost()

定义:

1
int ssh_write_knownhost(ssh_session session)

写入成功返回SSH_OK,失败则返回SSH_ERROR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <error.h>
#include <string.h>

int verify_knownhost(ssh_session session)
{
int state,hlen;
unsigned char *hash = NULL;
char *hexa;
char buf[10];

state = ssh_is_server_known(session);//是否识别远程主机

//获取远程主机的公钥hash
hlen = ssh_get_pubkey_hash(session,&hash);
if (hlen<0)
return -1;

//根据状态返回报错信息或进行下一步
switch(state)
{
case SSH_SERVER_KNOWN_OK:
break;

case SSH_SERVER_KNOWN_CHANGED:
fprintf(stderr,"Host key for server changed:it is now:\n");
ssh_print_hexa("Public key hash",hash,hlen);
fprintf(stderr,"For security reasons, connection will be stopped\n");
free(hash);
return -1;

case SSH_SERVER_FOUND_OTHER:
fprintf(stderr, "The host key for this server was not found but an other type of key exists.\n");
fprintf(stderr, "An attacker might change the default server key to confuse your client into thinking the key does not exist\n");
free(hash);
return -1;

case SSH_SERVER_FILE_NOT_FOUND:
fprintf(stderr, "Could not find known host file.\n");
fprintf(stderr, "If you accept the host key here, the file will be automatically created.\n");

case SSH_SERVER_NOT_KNOWN:
hexa = ssh_get_hexa(hash, hlen);
fprintf(stderr,"The server is unknown. Do you trust the host key?\n");
fprintf(stderr, "Public key hash: %s\n", hexa);
free(hexa);
if (fgets(buf, sizeof(buf), stdin) == NULL)
{
free(hash);
return -1;
}
if (strncasecmp(buf, "yes", 3) != 0)
{
free(hash);
return -1;
}
if (ssh_write_knownhost(session) < 0)
{
fprintf(stderr, "Error %s\n", strerror(errno));
free(hash);
return -1;
}
break;

case SSH_SERVER_ERROR:
fprintf(stderr,"Error %s\n",ssh_get_error?(session));
free(hash);
return -1;
}

free(hash);
return 0;
}

ssh_get_error_code()

定义:

1
int ssh_get_error_code(void* error)

用于接受最后一个错误的错误代码

返回一个错误代码,对应不同错误状态

验证用户

在用户验证服务器是安全可用的远程主机后,下一步是服务器授权用户,使已认证的用户能够访问资源。

libssh支持的认证方法:

  • 无认证
  • 密码方法
  • 键盘交互方式:服务器向用户发出几个挑战,用户必须正确回答问题。志告方式使通过密码本验证成为可能
  • 公钥方法

这些认证方式可以结合使用。

一个使用密码进行身份验证的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <libssh/libssh.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
ssh_session my_ssh_session;
int* rc;
char* password;

my_ssh_session = ssh_new();
if (my_ssh_session == NULL)
exit(-1);
ssh_options_set(my_ssh_session,SSH_OPTIONS_HOST,"localhost");

rc = ssh_connect(my_ssh_session);
if (rc!=SSH_OK)
{
fprintf(stderr,"Error connecting to localhost:%s\n",ssh_get_error(my_ssh_session));
ssh_free(my_ssh_session);
exit(-1);
}

if (verify_knowhost(my_ssh_session)<0)
{
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
exit(-1);
}

password = getpass("Password:");
rc = ssh_userauth_password(my_ssh_session,NULL,password);
if(rc!=SSH_AUTH_SUCCESS)
{
fprintf(stderr,"Error authenticating with password:%s\n",ssh_get_error(my_ssh_session));
ssh_disconnect(my_ssh_session);
exit(-1);
}
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_connect);
}

操作

在双方进行验证后,下一步可以利用SSH协议进行操作,执行远程命令,开启远程终端,交换文件,转发端口;等等。

执行远程命令的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int show_remote_processes(ssh_session session)
{
ssh_channel channel'
int rc;
char buffer[256];
int nbytes;

channel = ssh_channel_new(session);
if (channel == NULL)
return SSH_ERROR;

rc = ssh_channel_open_session(channel);
if (rc != SSH_OK)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
return rc;
}

nbytes = ssh_channel_read(channel,buffer,sizeof(buffer),0);
while(nbytes > 0)
{
if (write(1,buffer,nbytes)!= (unsigned int)nbytes)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
return SSH_ERROR;
}
nbytes = ssh_channel_read(channel,buffer,sizeof(buffer),0);
}

if(nbytes < 0)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
return SSH_ERROR;
}

ssh_channel_send_eof(channel);
ssh_channel_close(channel);
ssh_channel_free(channel);

return SSH_OK;
}

Chapter 2:用户验证相关

公钥,密码,挑战应答模式(键盘交互),无验证

公钥验证方法

libssh与openssh公钥和私钥完全兼容,可以使用libssh提供的自动公钥验证方法,也可以使用公钥函数进行自定义。

验证流程

  1. 扫描本地包含公钥的文件列表,每个密钥都发送到SSH服务器,直到服务器确认一个密钥(服务器已知的可以认证用户的密钥)。
  2. 检索此密钥的私钥并发送证明自己知道该私钥的消息。

使用ssh_userauth_publickey_auto()函数进行验证

ssh_userauth_publickey_auto()

定义:

1
int ssh_userauth_publickey_auto(ssh_session session,const char* username,const char* passphrase)

尝试自动使用公钥或无验证方式进行验证

参数:

  • session SSH会话
  • username 用户名,应该为NULL
  • passphrase 密码,用于解锁私钥,如果不使用密码或需要询问用户时用NULL

返回值:

  • SSH_AUTH_ERROR:发生严重错误
  • SSH_AUTH_DENIED:服务器不接受该公钥验证,尝试另一个公钥或其他方法
  • SSH_AUTH_PARTIAL:已经被部分认证(多验证方式存在),仍需要其他验证方法
  • SSH_AUTH_SUCCESS:公钥通过验证,可以使用ssh_userauth_publickey()
  • SSH_AUTH_AGAIN:非阻塞模式下,须稍后再重新调用

ssh_userauth_publickey()

定义:

1
int ssh_userauth_publickey(ssh_session session,const char* username,const ssh_key privkey)

使用公钥/私钥或证书进行验证,与用户的私钥进行身份验证匹配

大多数服务器实现不允许在认证过程中更改用户名,只有在连接到服务器之前才应该使用ssh_options_set()来设置用户名,username参数应该设置为NULL

使用自己的公钥进行身份验证

步骤:

  • 使用ssh_pki_import_pobkey_file()检索公钥
  • 使用ssh_userauth_try_publickey()将公钥提供给SSH服务器。如果返回值为SSH_AUTH_SUCCESS,则SSH服务器接受使用公钥进行身份验证,则进行下一步
  • 使用ssh_userauth_publickey()与私钥进行身份验证
  • 最后使用ssh_key_free()清理内存

ssh_pki_import_pubkey_file()

定义:

1
int ssh_pki_import_pubkey_file(const char* filename,ssh_key* pkey)

参数:

  • filename 公钥文件的地址
  • pkey 存储公钥分配的指针,需要使用ssh_key_free()释放内存

返回值:

成功时返回SSH_OK;文件不存在或者权限被拒绝返回SSH_EOF;其他返回SSH_ERROR

ssh_userauth_try_publickey()

定义:

1
int ssh_userauth_try_publickey(ssh_session session,const char* username,const ssh_key pubkey)

尝试使用给定的公钥进行身份验证

ssh_pki_import_privkey_file()

定义:

1
int ssh_pki_import_privkey_file(const char* filename,const char* passphrase,ssh_auth_callback auth_fn,void* auth_data,ssh_key* pkey)

从文件中个导入密钥

参数:

  • filename:私钥名称
  • passphrase:私钥的解密密钥。未加密或未知设为NULL
  • auth_fn:希望使用的验证函数或为NULL
  • auth_data:传递给验证函数的私有数据
  • Pkey:分配给存储ssh_key的指针,需要使用ssh_key_free()释放

返回值:

成功时返回SSH_OK;文件不存在或者权限被拒绝返回SSH_EOF;其他返回SSH_ERROR

ssh_key_free()

定义:

1
void ssh_key_free(ssh_key key)

释放一个SSH key

一个示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int authenticate_pubkey(ssh_session session)
{
int rc;

rc = ssh_userauth_publickey_auto(session NULL);

if (rc = SSH_AUTH_ERROR)
{
fprintf(stderr,"Authentication failed: %s\n",ssh_get_error(session));
return SSH_AUTH_ERROR;
}

return rc;
}

密码验证

使用ssh_userauth_password()进行密码身份验证。若密码通过验证,返回SSH_AUTH_SUCCESS。需要询问密码并进行安全分配管理。

如果服务器反馈密码错误,但仍可以使用openssh的客户端进行身份验证,可能是因为openssh只接受键盘交互形式的验证。切换到键盘交互模式,或尝试在SSH服务器上配置纯文本密码。

ssh_userauth_password()

定义:

1
int ssh_userauth_password(ssh_session session,const char* username,const char* password)

尝试使用密码进行验证;该方法通常在SSHv2服务器上被禁止,应当使用键盘交互模式进行验证。

password值必须用UTF-8进行编码,如何与解释密码,并与密码数据库进行验证由服务器决定。

键盘交互认证方法

服务器提出challenge,一个或多个用户必须回答的问题,直到服务器接受认证

ssh_userauth_kbdint()

定义:

1
int ssh_userauth_kbdint(ssh_session session,const char* user,const char* submethods)

尝试通过键盘交互模式进行验证。

参数:

  • session:将要使用的ssh会话
  • user:需要验证的用户名,定义为NULL,有ssh_option_set_username()修改用户名,中途不能修改
  • submethods:设定为NULL

返回:

  • SSH_AUTH_ERROR:发生严重错误
  • SSH_AUTH_DENIED:认证失败:使用另一种方法
  • SSH_AUTH_PARTIAL:部分认证成功,仍需要其他方法的认证
  • SSH_AUTH_SUCCESS:认证成功
  • SSH_AUTH_INFO:服务器询问了一些问题,使用ssh_userauth_kbdint_getnprompts()
  • SSH_AUTH_AGAIN:在无阻塞模式中,需要之后再重新访问

ssh_userauth_kbdint_getnprompts()

定义:

1
int ssh_userauth_kbdint_getprompts(ssh_session session)

获取服务器提供的提示(问题)数量

返回: 提示的数量

ssh_userauth_kbdint_getname()

定义:

const char* ssh_userauth_kbdint_getname(ssh_session session)

获得消息块的名称。调用ssh_userauth_kbdint()并收到了SSH_AUTH_INFO返回码,调用该函数检索远程主机发送的键盘交互认证问题的信息。

返回:消息快的名称,不要释放该指针

ssh_userauth_kbdint_getinstruction()

定义:

1
const char* ssh_userauth_kbdint_getinstruction(ssh_session session)

获取消息块的指令

返回:消息块的指令

ssh_userauth_kbdint_getprompt()

定义:

1
const char* ssh_userauth_kbdint_getprompt(ssh_session session,unsigned int i,char* echo)

从消息块获取提示

参数:

  • session 使用的ssh会话
  • i 当前提示的索引
  • echo 可选项,获取一个布尔值,用于设定用户输入应该被回显或隐藏,密码通常设定为隐藏

返回:指向提示符的指针,不要释放该指针

ssh_user_kbdint_setanswer()

定义:

1
int ssh_userauth_kbdint_setanswer(ssh_session session,unsigned int i,const char* answer)

回复来自消息块的问题的答案

参数:

  • session 当前会话
  • i 当前提示的索引编号
  • answer 给服务器的答案,必须为UTF-8格式的编码。如何解释并使用该值进行验证取决于服务器,但如果使用其他格式编码答案,则必须先转换为UTF-8

返回:成功时返回0,错误时返回值<0

认证过程

  1. 调用ssh_userauth_kbdint()函数并存储答案
  2. 如果收到的返回是SSH_AUTH_INFO,则说明服务器发送了几个问题,询问用户,使用ssh_userauth_kbdint_getnprompts(),ssh_userauth_kbdint_getname(),ssh_userauth_kbdint_getinstruction()和ssh_userauth_kbdint_getprompt()检索问题
  3. 使用ssh_userauth_kbdint_setanswer()为挑战中的每个问题设置答案
  4. 再次调用ssh_userauth_kbdint(),直到这些函数返回的内容不是SSH_AUTH_INFO

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//为当前会话进行键盘交互方式的认证
int authenticate_kbdint(ssh_session session)
{
int rc;//状态码

rc = ssh_userauth_kbdint(session,NULL,NULL)
while(rc == SSH_AUTH_INFO)//调用直到返回值不是SSH_AUTH_INFO
{
const char *name,*instruction;
int nprompts,iprompt;

//检索问题,获取问题的名称,指令,提示
name = ssh_userauth_kbdint_getname(session);
instruction = ssh_userauth_kbdint_getinstruction(session);
nprompts = ssh_userauth_kbdint_getnprompts(session);

if (strlen(name)>0)
printf("%s\n",name);
if (strlen(instruction)>0)
printf("%s\n",instruction);
for (iprompt = 0;iprompt < nmprompts;iprompt++)//循环获取所有的提示,并获取用户的答案
{
const char *prompt;
char echo;

prompt = ssh_userauth_kbdint_getprompt(session,iprompt,&echo);//检索提示,将内容赋给prompt
if (echo)//如果设定为真,显示用户回显
{
char buffer[128], *ptr;

printf("%s",prompt);
if (fgets(buffer,sizeof(buffer),stdin)==NULL)//用户未能回答问题,则验证失败
return SSH_AUTH_ERROR;
buffer[sizeof(buffer)-1] = '\0';//将缓存中的最后一位置为标志'\0'
if ((ptr = strchr(buffer,'\n'))!=NULL)
*ptr = '\0';
if (ssh_userauth_kbdint_setanswer(session,ipromot,buffer)<0)//如果服务器验证未通过,返回失败
return SSH_AUTH_ERROR;
memset(buffer,0,strlen(buffer));//将缓存区置空
}
else//设定为隐藏模式,以密码形式读取
{
char *ptr;

ptr = getpass(prompt);
if (ssh_userauth_kbdint_setanswer(session,iprompt,ptr)<0)
return SSH_AUTH_ERROR;
}
}
rc = ssh_userauth_kbdint(session,NULL,NULL);
}
return rc;
}

使用“无验证”模式

无验证模式主要目的是在没有任何凭证的情况下进行认证(除非确实要授权匿名访问权限,不要使用该方式)。

如果该账号没有密码,且服务器配置为允许通过,则ssh_userauth_none()可能回复SSH_AUTH_SUCCESS

ssh_userauth_none()

定义:

1
int ssh_userauth_none(ssh_session session,const char* username)

示例

1
2
3
4
5
6
7
int authenticate_kbdint(ssh_session session)
{
int rc;

rc = ssh_userauth_none(session,NULL);
return rc;
}

获取支持的身份验证列表

如果不选择指定某种验证方法,可以让服务器展示可以是使用的验证方法

使用ssh_userauth_list()函数获取可用的身份验证方法以及如何使用

ssh_userauth_list()

定义:

1
int ssh_userauth_list(ssh_session session,const char* username)

获取服务器可使用的验证方法,调用该函数前需要调用ssh_userauth_none()

返回:

  • SSH_AUTH_METHOD_PASSWORD
  • SSH_AUTH_METHOD_PUBLICKEY
  • SSH_AUTH_METHOD_HOSTBASED
  • SSH_AUTH_METHOD_INTERACTIVE

获取横幅

SSH服务器可能会发送一个横幅,一般为免责声明等内容用,使用ssh_get_issue_banner()检索横幅,显示给用户

ssh_get_issue_banner()

定义:

1
char* ssh_get_issue_banner(ssh_session session)

返回分配给横幅的字符串指针,出错时返回NULL

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int display_banner(ssh_session session)
{
int rc;
char *banner;

rc = ssh_userauth_none(session,NULL);
if (rc = SSH_AUTH_ERROR)
return rc;

banner = ssh_get_issue_banner(session);
if (banner)
{
printf("%s\n",banner);
free(banner);
}

return rc;
}

Chapter 3:开启远程终端

一个SSH连接可以有多个信道共享。一个信道可以用于多种用途。可以创建信道,用于开启远程终端,在远程计算机上启动命令解释程序。

打开和关闭信道

使用ssh_channel_new()函数创建一个信道,创建好之后,使用ssh_channel_open_session()打开一个SSH会话。不需要该信道时,使用ssh_channel_close()发送文件结束符(eof),此时可以通过ssh_channel_free()销毁信道

ssh_channel_new()

定义:

1
ssh_channel ssh_channel_new(ssh_session session)

分配一个新的信道

返回:指向新分配的信道的指针,错误时返回NULL

ssh_channel_open_session()

定义:

1
int ssh_channel_open_session(ssh_channel channel)

参数:channel:分配好的信道

返回:

  • SSH_OK 成功时返回
  • SSH_ERROR 错误时返回
  • SSH_AGAIN 无阻塞模式下,需要等待重新调用

ssh_channel_close()

关闭一个信道,发送文件结束符(EOF)并关闭信道。关闭后无法恢复无法去要发送或处于缓冲区中的数据

返回:成功返回SSH_OK,失败返回SSH_ERROR

ssh_channel_free()

关闭并释放一个信道

定义:

1
void ssh_channel_free(ssh_channel channel)

调用函数后该信道上的所有数据均会丢失

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int shell_session(ssh_session session)
{
ssh_channel channel;
int rc;

channel = ssh_channel_new(session);
if (channel == NULL)
return SSH_ERROR;

rc = ssh_channel_open_session(channel);
if (rc != SSH_OK)
{
ssh_channel_free(channel);
return rc;
}

ssh_channel_close(channel);
ssh_channel_send_eof(channel);
ssh_channel_free(channel);

return SSH_OK;
}

交互会话与非交互会话

如果需要一个接一个地键入命令,则认为是交互模式的;如果没有附加终端,类似于后台执行命令,是为为交互式的shell.

如果使用交互式shell。需要在远程终端创建一个伪终端,通过ssh_channel_request_pty()请求pty,然后用ssh_channel_change_pty_size()定义其维度(行数和列数)

无论使用交互会话还是非交互会话,都需要使用ssh_channel_request_shell()请求一个shell

ssh_channel_request_pty()

定义:

1
int ssh_channel_request_pty(ssh_channel channel)

请求一个伪终端PTY

返回:成功时返回SSH_OK,失败时返回SSH_ERROR,在非阻塞模式下如果需要重新调用返回SSH_AGAIN

ssh_channel_change_pty_size()

定义:

1
int ssh_channel_change_pty_size(ssh_channel channel,int cols,int rows)

改变远程终端的大小

参数:

  • channel 使用的信道
  • cols 分配的列数
  • rows 分配的行数

返回:成功时返回SSH_OK,失败时返回SSH_ERROR

warning:

如果不确定使用相同信道/会话的其他libssh函数是否在同一时间运行(不是100%线程安全),则不要从信号处理程序调用该函数

ssh_channel_request_shell()

定义:

1
int ssh_channel_request_shell(ssh_channel channel)

请求一个shell

返回:成功时返回SSH_OK,失败时返回SSH_ERROR,在非阻塞模式下如果需要重新调用返回SSH_AGAIN

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int interactive_shell_session(ssh_channel channel)
{
int rc;

rc = ssh_channel_request_pty(channel);
if (rc!=SSH_OK) return rc;

rc = ssh_channel_change_pty_size(channel,80,24);
if (rc!=SSH_OK) return rc;

rc = ssh_channel_request_shell(channel);
if (rc!=SSH_OK) return rc;

return rc;
}

显示远程计算机发送的数据

在程序中通常要接受来自远程终端的数据,需要进行分析,记录或显示

使用ssh_channel_read()和ssh_channel_read_nonblocking()从信道中读取数据

ssh_channel_read()

定义:

1
int ssh_channel_read(ssh_channel channel,void* dest,uint32_t count,int is_stderr)

从信道中读取数据

参数:

  • channel 读取数据的来源信道
  • dest 接受数据的目标缓存区
  • count 读取的数据大小
  • is_stderr 标准错误流stderr中的内容的布尔值标志

返回:

读取的数据的字节数,在错误时返回0或EOF标记或SSH_ERROR。在无阻塞模式在无可用数据或接受到SSH_AGAIN时返回0

warning:

函数可能返回小于count字节的数据,并在count字节被读取之前不会被阻塞。使用缓存区的读取函数重命名为channel_read_buffer()

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int interactive_shell_session(ssh_channel channel)
{
int rc;
string buffer[256];
int nbytes;

rc = ssh_channel_request_pty(channel);
if (rc!=SSH_OK) return rc;

rc = ssh_channel_change_ptu_size(channel,80,24);
if (rc!=SSH_OK) return rc;

rc = ssh_channel_request_shell(channel);
if (rc!=SSH_OK) return rc;

while(ssh_channel_is_open(channel)&&!ssh_channel_is_eof(channel))
{
nbytes = ssh_channel_read(channel,buffer,sizeof(buffer),0);
if (nbytes<0)
return SSH_ERROR;

if (nbytes>0)
write(1,buffer,nbytes);
}

return rc;
}

像远程主机发送用户输入

使用ssh_channel_write()向远程站点发送数据

ssh_channel_write()

定义:

1
int ssh_channel_write(ssh_channel channel,const void* data,uint32_t len)

向信道写入块数据

参数:

  • channel 将要写入的信道
  • data 指向要写入的数据的指针
  • len 写入的缓冲区的长度

返回:写入的字节数;出错时返回SSH_ERROR

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int kbhit()
{
struct timeval tv = {0L,0L}
fd_set fdsl

FD_ZERO(&fds);
FD_SET(0,&fds);

return select(1,&fds,NULL,NULL,&tv);
}//用于Linux系统下检测键盘输入,Windows下是标准函数,不能重复定义 有键盘输入返回1,否则为0

int interactive_shell_session(ssh_channel channel)
{
char buffer[256];
int nbytes,nwritten;

while (ssh_channel_is_open(channel)&&!ssh_channel_is_eof(channel))//信道开启且不是结束符
{
nbytes = ssh_channel_read_nonblocking(channel,buffer,sizeof(buffer),0);
if (nbytes < 0) return SSH_ERROR;
if (nbytes > 0)
{
nwritten = write(1,buffer,nbytes);
if (nwritten != nbytes) return SSH_ERROR;
}

if (!kbhit())//如果键盘没有输入,挂起进程等待0.05秒
{
usleep(50000L);
continue;
}

nbytes = read(0,buffer,sizeof(buffer));
if (nbytes < 0) return SSH_ERROR;
if (nbytes > 0)
{
nwritten = ssh_channel_write(channel,buffer,nbytes);
if (nwritten != nbytes) return SSH_ERROR;
}
}

return rc;
}

ssh_channel_is_open()

定义:

1
int ssh_channel_is_open(ssh_channel channel)

检查信道是否开启

返回:信道关闭返回0,其他情况返回非0值

在远程终端使用图形界面

图形界面的远程终端,可以通过X11协议将图形界面转发到本地

首先声明接受ssh_channel_accept_x11()的X11连接,然后使用ssh_channel_request_x11为X11协议创建转发隧道

ssh_channel_accept_x11()

定义:

1
ssh_channel ssh_channel_accept_x11(ssh_channel channel,int timeout_ms)

接受X11转发信道

参数:

  • channel 允许X11会话的信道
  • timeout_ms 微秒为单位的超时值

返回:新建立的信道,或当没有从服务器来的X11请求时返回NULL

ssh_channel_request_x11()

定义:

1
int ssh_channel_request_x11(ssh_channel channel,int single_connetion,const char* protocol,const char* cookie,int screen_number)

通过现有的会话信道发送X11(x11-req)请求,将远程X11应用的显示重定向到本地X服务器

参数:

  • channel 一个用来执行X11程序的现有会话信道
  • single_connection 标记是否只有单个X11应用被重定向的布尔值
  • protocol X11身份认证协议,传递NULL使用默认值MIT-MAGIC-COOKIE-1
  • cookie X11协议cookie,传递NULL生成你随机cookie
  • screen_number 屏幕号

返回:成功时返回SSH_OK,出错时返回SSH_ERROR,非阻塞模式下需要重新调用返回SSH_AGAIN

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int interactive_shell_session(ssh_channel channel)
{
int rc;
ssh_channel x11channel;

rc = ssh_channel_request_pty(channel);
if (rc != SSH_OK) return rc;

rc = ssh_channel_change_pty_size(channel,80.24);
if (rc != SSH_OK) return rc;

rc = ssh_channel_request_x11(channel,0,NULL,NULL,0);
if(rc != SSH_OK) return rc;

rc = ssh_channel_request_shell(channel);
if (rc = !=SSH_OK) return rc;
}

Chapter4:传递远程命令

该方法只适用于执行一个远程命令,如果要发出多个命令,应使用非交互式远程shell

执行远程命令

1.打开一个SSH信道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int show_remote_files(ssh_session session)
{
ssh_channel channel;
int rc;

channel = ssh_channel_new(session);
if (channel == NULL) return SSH_ERROR;//创建一个信道

rc = ssh_channel_open_session(channel);
if (rc != SSH_OK)
{
ssh_channel_free(channel);//如果不能开启会话,释放掉当前信道
return rc;
}

2.执行远程命令

调用ssh_channel_request_exec()执行远程命令

1
2
3
4
5
6
7
rc = ssh_channel_request_exec(channel,"ls -l");
if (rc != SSH_OK)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
return rc;
}

ssh_channel_request_exec()

定义:

1
int ssh_channel_request_exec(ssh_channel channel,const char* cmd)

运行一条没有交互式shell的shell命令,类似于执行’sh -c command’

参数:

  • channel 执行命令的信道
  • cmd 执行的命令

返回:成功时返回SSH_OK,出错时返回SSH_ERROR,非阻塞模式下需要重新调用返回SSH_AGAIN

3.获取数据

远程命令显示数据,调用ssh_channel_read()获取数据。改善书返回读取的字节数。如果信道上没有更多数据,则函数返回0,转到下一步;如果月到错误,则返回负值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
char buffer[256];
int nbytes;

nbytes = ssh_channel_read(channel,buffer,sizeof(buffer),0);
while (nbytes > 0)
{
if (fwrite(buffer,1,bytes,stdout)!=nbytes)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
return SSH_ERROR;
}
nbytes = ssh_channel_read(channel,buffer,sizeof(buffer),0);
}

if (nbytes < 0)
{
ssh_channel_close(channel);
ssh_channel_free(channel);
return SSH_ERROR;
}

4.结束

读取远程命令的结果后,将文件结束符EOF发送到信道,关闭信道并释放信道

1
2
3
4
5
ssh_channel_send_eof(channel);
ssh_channel_close(channel);
ssh_channel_free(channel);

return SSH_OK

Chapter 5:SFTP子系统

SFTP是安全文件传输协议的简称,可以用于本地与远程计算机的远程传输.SFTP的功能丰富,现有的版本是版本3,虽然未解决全部功能,但核心功能已经实现

打开和关闭SFTP会话

SFTP子系统不是打开一个SSH信道,而是开启一个SFTP会话

用sftp_new()创建一个新的SFTP会话,用函数sftp_free()初始化,sftp_free()删除.

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <libssh/sftp.h>

int sftp_helloworld(ssh_session session)
{
sftp_session sftp;
int rc;

sftp = sftp_new(session);
if (sftp = NULL)
{
fprintf(stderr,"Error allocationg SFTP session:%s\n",ssh_get_error(session));
return SSH_ERROR;
}

rc = sftp_init(sftp);
if (rc != SSH_OK)
{
fprintf(stderr,"Error initializing SFTP session:%s.\n",sftp_get_error(sftp));
sftp_free(sftp);
return rc;
}

sftp_free(sftp);
return SSH_OK;
}

sftp_new()

定义:

sftp_session sftp_new(ssh_session session)

返回:正确分配一个sftp会话或错误时返回NULL

需要使用stfp_free()进行释放

sftp_init()

定义:

1
int sftp_init(sftp_session sftp)

参数:sftp:将要初始化的sftp会话

返回:成功时返回0,失败时返回<0的值并抛出ssh错误

sftp_free()

定义:

1
void sftp_free(sftp_session sftp)

关闭并释放一个sftp会话

SFTP错误

产生错误时,除ssh_get_error_number()抛出常规SSH错误外,使用sftp_get_error()返回SFTP错误号

sftp_get_error()

定义:

1
int sftp_get_error(sftp_session sftp)

获取最后一个错误

返回:保存的错误,如果函数出错返回<0

错误编号

  • SSH_FX_OK: 无错误
  • SSH_FX_\EOF: 遇到文件结束符EOF
  • SSH_FX_NO_SUCH_FILE: 文件不存在
  • SSH_FX_PERMISSION_DENIED: 权限被拒绝
  • SSH_FX_FAILURE: 通配失败
  • SSH_FX_BAD_MESSAGE: 从服务器收到garbage
  • SSH_FX_NO_CONNECTION: 未建立链接
  • SSH_FX_CONNECTION_LOST: 存在链接但已丢失
  • SSH_FX_OP_UNSUPPORTED: 操作不受libssh支持
  • SSH_FX_INVALID_HANDLE: 无效的文件句柄
  • SSH_FX_NO_SUCH_PATH: 不存在此文件或目录
  • SSH_FX_FILE_ALREADY_EXISTS: 尝试创建已经存在的文件或目录
  • SSH_FX_WRITE_PROTECT: 写保护的文件系统
  • SSH_FX_NO_MEDIA: 远程驱动中没有介质

创建一个目录

通过sftp_mkdir()创建目录,目录权限和mkdir函数相同.所需的权限与远程用户的掩码组合确定有效权限

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <libssh/sftp.h>
#include <sys/stat.h>

int sftp_helloworld(ssh_session session,sftp_session sftp)
{
int rc;

rc = sftp_mkdir(sftp,"helloworld",S_IRWXU);
if (rc!=SSH_OK)
{
if (sftp_get_error(sftp)!=SSH_FX_FILE_ALREADY_EXISTS)
{
fprintf(stderr,"无法创建目录:%s\n",ssh_get_error(session));
return rc;
}
}

return SSH_OK;
}

sftp_mkdir()

定义:

1
int sftp_mkdir(sftp_session sftp,const char* directory,mode_t mode)

创建目录

参数:

  • sftp sftp会话的句柄
  • diretory 将要创建的目录
  • mode 指定要使用的权限.由进程的umask掩码进行修饰,通常以创建文件的(mode&~umask)(即mode与用户掩码umask进行位与后的结果)方式决定权限

返回:成功时返回0,错误时返回<0并抛出ssh和sftp错误

与SCP系统中不同,该函数不会将当前目录切换到新创建的目录中

向远程计算机拷贝文件

可以像处理本地文件一样处理远程文件的内容:以特定模式打开文件,移动文件指针,读取或写入数据以及关闭文件

调用sftp_open()函数,该函数类似与本地的open()函数,并额外返回一个sftp_file类型的文件句柄,该文件句柄可以由其他文件操作函数使用,并保持有效直到调用sftp_close()关闭该远程文件

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <libssh/sftp.h>
#include <sys/stat.h>
#include <fcntl.h>

int sftp_helloworld(ssh_session session,sftp_session sftp)
{
int access_type = O_WRONLY | O_CREAT | O_TRUNC;
sftp_file file;//open()函数参数设定,写模式,不存在文件新建,以存在文件,长度被截为0
const char *helloworld = "Hello,World!\n";
int length = strlen(helloworld);
int rc,nwritten;

file = sftp_open(sftp,"helloworld/helloworld.txt",access_type,S_IPWXU);
if (file == NULL)
{
fprintf(stderr,"Can't open file for writing:%s\n",ssh_get_error(session));
return SSH_ERROR;
}

nwritten = sftp_write(file,helloworld,length);
if (nwritten != length)
{
fprintf(stderr,"Can't write data to file:%s\n",ssh_get_error(session));
sftp_close(file);
return SSH_ERROR;
}

rc = sftp_close(file);
if (rc != SSH_OK)
{
fprintf(stderr,"Can't close the written file:%s\n",ssh_get_error(session));
return rc;
}

return SSH_OK;
}

sftp_open()

定义:

1
sftp_file sftp_open(sftp_session session,const char* file,int accessype,mode_t mode)

在远程服务器打开(创建)文件

参数:

  • session sftp会话句柄
  • file 将要打开的文件(指针)
  • accesstype open函数参数,指定文件操作类型等参数
  • mode 如果要创建新文件,指定要使用的权限.由进程的umask掩码进行修饰,通常以创建文件的(mode&~umask)(即mode与用户掩码umask进行位与后的结果)方式决定权限

返回:成功时返回文件句柄(指针),错误时返回NULL并抛出ssh和sftp错误

sftp_close()

定义:

1
int sftp_close(sftp_file file)

释放指针关闭打开的文件句柄

返回:正常关闭时返回SSH_NO_\ERROR,发生错误是返回SSH_ERROR

从远程计算机读取文件

通过sftp读取网络文件,可以通过同步或一部两种方式完成

同步读取使用sftp_read()完成

同步读取文件

调用sftp_read()函数.文件通常以块形式传输,一个好的文件块大小是16KB.

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//以16KB文件块远程打开"/etc/profile"文件
#define MAX_XFER_BUF_SIZE 16384 //16KB

int sftp_read_sync(ssh_session session,sftp_session sftp)
{
int access_type;
sftp_file file;
char buffer[MAX_XFER_BUF_SIZE];
int nbytes,nwritten,rc;
int fd;

access_type = O_RDONLY;
file = sftp_open(sftp,"etc/profile",access_type,0);
if (file == NULL)
{
fprintf(stderr,"Can't open file for reading:%s\n",ssh_get_error(session));
return SSH_ERROR;
}

fd = open("/path/to/profile",O_CREAT);
if (fd < 0)
{
fprintf(stderr,"Can't open file for writing:%s\n",strerror(session));
return SSH_ERROR;
}

for(;;)//读取文件块直到缓存区为空
{
nbytes = sftp_read(file,buffer,sizeof(buffer));
if (nbytes == 0)
{
break;
}
else if (nbytes < 0)
{
fprintf(stderr,"Error while reading file:%s\n",ssh_get_error(session));
sftp_close(file);
return SSH_ERROR;
}

nwritten = write(fd,buffer,nbytes);
if (nwritten != nbytes)
{
fprintf(stderr,"Error writing:%s\n",strerror(errno));
sftp_close(file);
return SSH_ERROR;
}
}

rc = sftp_close(file);
if (rc != SSH_OK)
{
fprintf(stderr,"Can't close the read file:%s\n",ssh_get_error(session));
return rc;
}

return SSH_OK;
}

sftp_read()

定义:

1
ssize_t sftp_read(sftp_file file,void* buf,size_t count)

从打开的文件句柄(文件指针)中读取文件

参数:

  • file 打开的文件指针
  • buf 接收文件内容的缓存区指针
  • count 以字节为单位的缓存区大小

返回:写入的数据字节数,发生错误时返回<0并抛出ssh和sftp错误

异步读取数据

异步读取分两步完成,首先调用sftp_asyns_read_begin(),返回一个请求句柄

然后调用sftp_async_read(),使用该句柄.如果文件以非阻塞模式打开,则该函数可能会返回SSH_AGAIN,此时请求尚未完成,需要稍后重新调用.否则调用sftp_async_read()等待数据到来

以非阻塞模式打开文件,需要在打开文件后立即调用sftp_set_nonblocking()函数(默认时阻塞模式)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#define MAX_XFER_BUF_SIZE 16384

int sftp_read_async(ssh_session,sftp_sesison sftp)
{
int access_type;
sftp_file file;
char buffer [MAX_XFER_BUF_SIZE];
int async_request;
int nbytesl
long counter;
int rc;

access_type = O_RDONLY;
file = sftp_open(sftp,"some_very_big_file",access_type,0);
if (file == NULL)
{
fprintf(stderr,"Can't open file for reading:%s\n",ssh_get_error(session));
return SSH_ERROR;
}
sftp_file_set_nonblocking(file);

async_request = sftp_async_read_begin(file,sizeof(buffer));
counter = 0L;
usleep(10000);
if (async_request >= 0)
{
nbytes = sftp_async_read(file,buffer,sizeof(buffer),async_request);
}
else
{
nbytes = -1;
}

while (nbytes > 0)||nbytes == SSH_AGAIN)
{
if (nbytes > 0)
{
write(1,buffer,nbytes);
async_request = sftp_async_read_begin(file,sizeof(buffer));
}
else
{
counter++;
}
usleep(10000);

if (async_request >= 0)
{
nbytes = sftp_async_read(file,buffer,sizeof(buffer),async_request);
}
else
{
nbytes = -1;
}
}

if (nbytes < 0)
{
fprintf(stderr,"Error while reading file:%s\n",ssh_get_error(session));
sftp_close(file);
return SSH_ERROR;
}

printf("The counter has reached value: %ld\n",counter);

rc = sftp_close(file);
if (rc != SSH_OK)
{
fprintf(stderr,"Can't close the read file:%s\n",ssh_get_error(session));
return rc;
}

return SSH_OK;
}

sftp_async_read_begin()

定义:

1
int sftp_async_read_begin(sftp_file file,uint32_t len)

使用打开的sftp文件句柄进行异步读取.目标是避免与同步读取方式/响应模式相关的慢速读取

参数:

  • file 读取的打开的文件句柄
  • len 将要读取的字节数

返回:与发送的请求对应的响应符,错误时返回<0

warning:

调用该函数时,内部偏移量将会根据len参数进行更新,调用该函数会向服务器发送请求,若服务器应答,libssh非配内存存储响应,直到sftp_async_read()被调用.如果不调用sftp_async_read()会导致内存泄漏

sftp_async_read()

定义:

1
int sftp_async_read(sftp_file file,void* data,uint32_t len,uint32_t id)

等待异步传输读取完成并保存数据

参数:

  • file 将要被读取的文件句柄
  • data 指向接收数据的缓存区的指针
  • len 以字节为单位的缓存区的大小,应该大于等于sftp_async_read_begin()调用的长度参数
  • id sftp_async_read_begin()返回的标识符

返回:读取的字节数;遇到EOF文件结束符返回0;错误时返回SSH_ERROR;阻塞模式返回SSH_AGAIN并需要之后再次访问

warning:

使用无效标识符调用该函数将永远不会返回

sftp_file_set_nonblocking()

定义:

1
void sftp_file_set_nonblocking(sftp_file handle)

将传入的文件句柄设置为无阻塞通信模式

列出目录的内容

使用handle_type:sftp_dir的句柄类型,访问正在读取的目录

sftp_opendir()

定义:

1
sftp_dir sftp_opendir(sftp_session session,const char* path)

打开一个用于获取远程目录条目的目录

参数:

  • session 打开目录的sftp句柄
  • path 将要打开的目录地址

返回:成功时返回目录的sftp句柄 错误时返回NULL并抛出ssh和sftp错误

sftp_readdir()

定义:

1
sftp_attributes sftp_readdir(sftp_session,sftp_dir dir)

获取目录的单个文件属性结构

参数:

  • session 将要读取的目录的sftp会话句柄
  • dir 将要读取的目录的sftp句柄

返回:文件属性结构,出错或目录结尾处返回NULL

文件属性结构 即一个sftp_attributes类型,是一个指向具有目录条目信息结构的指针:

  • name:目录或文件的名称
  • size:以比特为单位的大小
  • 等等其他属性

在出错或目录结尾处可能会返回NULL.通过sftp_dir_eof()判断是否目录结尾

不需要时,必须用sftp_attributes_free()释放属性

sftp_dir_eof()

定义:

1
int sftp_dir_eof(sftp_dir dir)

确定是否达到了文件结束符EOF

参数:dir sftp目录句柄

返回:是EOF时返回1,不是则返回0

sftp_closedir()

定义:

1
int sftp_closedir(sftp_dir dir)

关闭目录句柄

返回:SSH_NO_ERROR或SSH_ERROR

sftp_attributes_free()

定义:

1
void sftp_attributes_free(sftp_attributes file)

释放sftp属性结构的指针

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int sftp_list_dir(ssh_session,sftp_session sftp)
{
sftp_dir dir;
sftp_attributes attributes;
int rc;

dir = sftp_opendir(sftp,"/var/log");
if (!dir)
{
fprintf(stderr,"Directory not opened:%s\n",ssh_get_error(session));
return SSH_ERROR;
}

printf("Name Size Perms Owner\tGroup\n");

while((attributes = sftp_readdir(sftp,dir)) != NULL)
{
printf("%-20s %10llu %.8o %s(%d)\t%s(%d)\n",attributes->name,(long long unsigned int)attributes->size,attributes->permissions,attributes->owner,attributes->uid,attributes->group,attributes->gid);

sftp_attributes_free(attributes);
}

if (!sftp_dir_eof(dir))
{
fprintf(stderr,"Can't list directory: %s\n",ssh_get_error(session));
sftp_closedir(dir);
return SSH_ERROR;
}

rc = sftp_closedir(dir);
if (rc != SSH_OK)
{
fprintf(stderr,"Can't close directory: %s\n",ssh_get_error(session));
return rc;
}
}

Chapter 6:SCP子系统

SCP子系统的功能远远少于SFTP子系统,但如果只需要从远程系统复制文件,SCP可以胜任

开启/关闭SCP会话

SCP子系统中,不直接操作SSH信道,而是开启一个SCP会话

SCP会话中不能同时进行读和写的操作,需要在调用ssh_scp_new()函数时指定读写模式

另一个重要的模式参数是SSH_SCP_RECURSIVE,声明是否使用递归读取目录的方式

ssh_scp_new()创建会话,ssh_scp_init()进行初始化.完成传输后,使用ssh_scp_close()终止SCP链接,并调用ssh_scp_free()释放分配的连接

示例1:递归写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int scp_write(ssh_session session)
{
ssh_scp scp;
int rc;

scp = ssh_scp_new(session,SSH_SCP_WRITE|SSH_SCP_RECURSIVE,".");
if (scp == NULL)
{
fprintf(stderr,"Error allocating scp session:%s\n",ssh_get_error(session));
return SSH_ERROR;
}

rc = ssh_scp_init(scp);
if (rc != SSH_OK)
{
fprintf(stderr,"Error initializing scp session: %s\n");
ssh_scp_free(scp);
return rc;
}

ssh_scp_close(scp);
ssh_scp_free(scp);
return SSH_OK;
}

示例2:打开连接读取单个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int scp_read(ssh_session session)
{
ssh_scp scp;
int rc;

scp = ssh_scp_new(session,SSH_SCP_READ,"hello/helloworld.txt");
if (scp == NULL)
{
fprintf(stderr,"Error allocating scp session: %s\n",ssh_get_error(session));
return SSH_ERROR;
}

rc = ssh_scp_init(scp);
if (rc != SSH_OK)
{
fprintf(stderr,"Error initializing scp session: %s\n",ssh_get_error(session));
ssh_scp_free(scp);
return rc;
}

ssh_scp_close(scp);
ssh_scp_free(scp);
return SSH_OK;
}

ssh_scp_new()

定义:

1
ssh_scp ssh_scp_new(ssh_session session,int mode,const char* location)

创建一个新的scp会话

参数:

  • session 使用的SSH会话
  • mode 标志位 SSH_SCP_WRITE/SSH_SCP_READ,选择读/写模式.SSH_SCP_RECURSIVE可以通过位或运算添加到参数中,表示可以递归操作(访问目录必须)
  • location 写入或读取的目录

返回:ssh_scp句柄,失败时返回NULL

ssh_scp_init()

定义:

1
int ssh_scp_init(ssh_scp scp)

初始化一个scp信道

返回:成功时返回SSH_OK,失败时返回SSH失败码

ssh_scp_close()

关闭scp信道

参数: scp 要关闭的scp连接

返回: 成功时返回SSH_OK,失败时返回SSH错误码

ssh_scp_free()

定义:

1
void ssh_scp_free(ssh_scp scp)

释放scp上下文

创建文件和目录

创建目录:调用ssh_scp_push_diretory()创建目录.在递归模式下,创建目录后会直接进入该目录.如果目录已经存在,且处于递归模式,直接输入该目录即可

创建文件:分为两步,先调用ssh_scp_push_file()准备写入;然后调用ssh_scp_write()写入数据.两个函数间要写入的数据长度必须相同.

不需要打开/关闭文件操作,远端自动完成此操作.如果文件已经存在,将会被覆盖并截断

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int scp_helloworld(ssh_session session,ssh_scp scp)
{
int rc;
const char *helloworld = "Hello\n";
int length = strlen(helloworld);

rc = ssh_scp_push_diretory(scp,"helloworld",S_IRWXU);
if (rc != SSH_OK)
{
fprintf(stderr,"Can't create remote diretory: %s\n",ssh_get_error(session));
return rc;
}

rc = ssh_scp_push_file(scp,"helloworld.txt",length,S_IRUSR|S_IWUSR);
if (rc != SSH_OK)
{
fprintf(stderr,"Can't open remote file: %s\n",ssh_get_error(session));
return rc;
}

rc = ssh_scp_write(scp,helloworld,length);
if (rc != SSH_OK)
{
fprintf(stderr,"Can't write to remote file: %s\n",ssh_get_error(session));
return rc;
}

return SSH_OK;
}

ssh_scp_push_directory()

定义:

1
int ssh_scp_push_diretory(ssh_scp scp,const char* dirname,int mode)

在sink模式下创建一个目录

参数:

  • scp scp句柄
  • dirname 将要创建的目录名
  • mode UNIX权限数值

返回:成功创建目录返回SSH_OK,出错时返回SSH_ERROR

ssh_scp_push_file()

定义:

1
int ssh_scp_push_file(ssh_scp scp,const char* filename,size_t size,int mode)

初始化文件传输

参数:

  • scp scp句柄
  • filename 将要传输的文件名,不应包含任何路径信息
  • size 发送文件的字节大小
  • mode 新文件的UNIX权限

返回:如果文件准备好发送,返回SSH_OK;发生错误返回SSH_ERROR

ssh_scp_write()

定义:

1
int ssh_scp_write(ssh_scp scp,const void* buffer,size_t len)

写入远程文件

参数:

  • scp scp句柄
  • buffer 将要写入的缓存区
  • len 将要吸入的字节数

返回:写入成功返回SSH_OK,发生错误返回SSH_ERROR

读取文件和目录

要接收文件,可以调用ssh_scp_pull_request()向远程端发起请求.

如果函数返回SSH_SCP_REQUEST_NEWFILE,则必须准备好接收文件.

可以调用ssh_scp_request_get_size()获取文件大小并据此分配缓存区.

准备好接收文件后,发送ssh_scp_accept_request(),然后调用ssh_scp_read()读取数据

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
int scp_receive(ssh_session session,ssh_scp scp)
{
int rc;
int size,mode;
char *filename, *buffer;

rc = ssh_scp_pull_request(scp);
if (rc != SSH_SCP_REQUEST_NEWFILE)
{
fprintf(stderr,"Error receiving information about file: %s\n",ssh_get_error(session));
return SSH_ERROR;
}

size = ssh_scp_request_get_size(scp);
filename = strdup(ssh_scp_request_get_filename(scp));
mode = ssh_scp_reqiest_get_permissions(scp);
printf("Receiving file %s, size %d, permissions 0%o\n",filename,size,mode);
free(filename);

buffer = malloc(size);
if (buffer == NULL)
{
fprintf(stderr,"Memory allocation error\n");
return SSH_ERROR;
}

ssh_scp_accept_request(scp);
rc = ssh_scp_read(scp, buffer, size);
if (rc = SSH_ERROR)
{
fprintf(stderr,"Error receiving file data: %s\n",ssh_get_error(session));
free(buffer);
return rc;
}
printf("Done\n");

write(1, buffer, size);
free(buffer);

rc = ssh_scp_pull_request(scp);
if (rc != SSH_SCP_REQUEST_EOF)
{
fprintf(stderr,"Unexpected request: %s\n",ssh_get_error(session));
return SSH_ERROR;
}

return SSH_OK;
}

ssh_scp_pull_request()

定义:

1
int ssh_scp_pull_request(ssh_scp scp)

等待一个scp请求(文件,目录)

返回:

  • SSH_SCP_REQUEST_NEWFILE:另一端在传输一个文件
  • SSH_SCP_REQUEST_NEWDIR:另一端在传输一个目录
  • SSH_SCP_ENDDIR:另一端完成了当前目录的传输
  • SSH_SCP_REQUEST_WARNING:另一端发送了一个警告
  • SSH_SCP_REQUEST_EOF:另一端完成了文件和数据的传输
  • SSH_ERROR:发生了错误

ssh_scp_request_get_size()

定义:

1
size_t ssh_scp_request_get_size(ssh_scp scp)

获取另一端传输的文件的大小

返回:将要读取的文件的大小

Warining:

实际大小可能不适合32位文件域,可能会发生截断

ssh_scp_accept_request()

定义:

1
int ssh_scp_accept_request(ssh_scp scp)

接收远程主机传输的文件或创建目录

返回:SSH_OK;SSH_ERROR

ssh_scp_read()

定义:

1
int ssh_scp_read(ssh_scp scp,void* buffer,size_t size)

读取远程文件

参数:

  • scp scp句柄
  • buffer 目标缓存区
  • size 缓存区的大小

返回:读取的字节数;错误时返回SSH_ERROR

ssh_scp_leave_directory()

定义:

1
int ssh_scp_leave_diretory(ssh_scp scp)

离开一个目录

返回:SSH_OK;SSH_ERROR

从远程服务器接收完整的目录数

以递归模式打开SCP会话,远程端会告知何时更改目录

调用ssh_scp_pull_request()返回SSH_SCP_REQUEST_NEWDIRECTORY时,应使用该本地目录输入;返回SSH_SCP_REQUEST_ENDDIRECTORY时,应离开当前目录.

Chapter7: 转发连接(隧道)

端口转发采用两种不同的SSH协议:直接或反向端口转发.直接端口转发即本地端口转发,反向端口转发即远程端口转发

直接端口转发

直接端口转发时从客户端向服务器的转发.客户端打开一个隧道,并将任何数据转发给服务器;然后服务器连接到一个终点,终点口语主流在另一台机器或SSH服务器本身上

流程

app->本地端口->SSH客户端 ===>SSH服务器->远程端口->目标app

示例

Mail client application   Google Mail
         |                     ^
     5555 (任意端口)            |
         |                143 (IMAP2)
        V                    |
    SSH client   =====>   SSH server 

图例:
 --P-->: 通过端口P连接端口
 =====>: SSH隧道

邮件客户端连接到客户端的端口5555,客户端向服务器建立加密隧道.服务器连接到Google邮件服务器的143端口(终点).本地邮件服务器可以向远程发送邮件

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int direct_forwarding(ssh_session session)
{
ssh_channel forwarding_channel;
int rc;
char *http_get = "GET / HTTP/1.1\nHost: www.google.com\n\n";
int nbytes, nwritten;

forwarding_channel = ssh_channel_new(session);
if (forwarding_channel == NULL)
return rc;

rc = ssh_channel_open_forward(forwarding_channel,"www.google.com",80,"localhost",5555);
if (rc != SSH_OK)
{
ssh_channel_free(forwarding_channel);
return rc;
}

nbytes = strlen(http_get);
nwritten = ssh_channel_write(forwarding_channel,http_get,nbytes);
if (nbytes != nwritten)
{
ssh_channel_free(forwarding_channel);
return SSH_ERROR;
}

ssh_channel_free(forwarding_channel);
return SSH_OK;
}

ssh_channel_open_forward()

定义:

1
int ssh_channel_open_forward(ssh_channel channel,const char* remotehost,int remoteport,const char* sourcehost,int localport)

端口一个TCP/IP转发信道

参数:

  • channel 分配的信道
  • remotehost 将要连接的远程地址(域名或IP)
  • remoteport 远程端口
  • sourcehost 连接请求的来源主机的数字IP.主要用于记录
  • localport 发起连接的来源主机的端口.主要用于记录

返回:SSH_OK;SSH_ERROR;SSH_AGAIN;

Warning:

该函数不绑定本地端口,也不会自动将套接字的内容发送到信道,仍需要调用channel_read()和channel_write()

ssh_select()

定义:

1
int ssh_select(ssh_channel* channels, ssh_channel* outchannels, socket_t maxfd, fd_set* readfds, struct timeval* timeout)

选择系统调用的封装

与select(2)有些类似.不支持重写或异常处理

参数:

  • channels 信道数组的指针由NULL终止,永不支持重写
  • outchannels 和信道相同大小的数组,不需要初始化
  • maxfd 来自readfds的最大文件描述符+1
  • readfds 被选择用于读取的文件描述符fd_set
  • timeout 毫秒单位超市

返回:SSH_OK;SSH_ERROR;被打断时返回SSH_EINTR,重新开始即可

Warning:

libssh在此不可重入(递归调用).意味着在调用此函数时收到了信号,时柄口语调用其他libssh函数的

反向端口转发

远程端口转发是有服务器发起的,从服务器向客户端转发,即使客户端主动建立隧道.一旦隧道建成,服务器将持续监听某个端口,一旦端口产生连接,服务器向客户端转发数据

流程

远程app->监听端口->SSH服务器 ===> SSH客户端->本地端口->本地app

示例

 Local mail server    Mail client application
         ^                     |
         |               5555 (任意端口)
     143 (IMAP2)               |
         |                     V
    SSH client   <=====   SSH server

图例:
 --P-->: 通过端口P连接端口
 =====>: SSH信道

客户端建立隧道,但将用于将服务器上建立的连接转发给客户端

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
int web_server(ssh_session session)
{
int rc;
ssh_channel channel;
char buffer[256];
int nbytes, nwritten;
int port = 0;
char *helloworld = ""
"HTTP/1.1 200 OK\n"
"Content-Type: text/html\n"
"Content-Length: 113\n"
"\n"
"<html>\n"
" <head>\n"
" <title>Hello, World!</title>\n"
" </head>\n"
" <body>\n"
" <h1>Hello, World!</h1>\n"
" </body>\n"
"</html>\n";
rc = ssh_channel_listen_forward(session, NULL, 8080, NULL);
if (rc != SSH_OK)
{
fprintf(stderr, "Error opening remote port: %s\n",
ssh_get_error(session));
return rc;
}
channel = ssh_channel_accept_forward(session, 60000, &port);
if (channel == NULL)
{
fprintf(stderr, "Error waiting for incoming connection: %s\n",
ssh_get_error(session));
return SSH_ERROR;
}
while (1)
{
nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
if (nbytes < 0)
{
fprintf(stderr, "Error reading incoming data: %s\n",
ssh_get_error(session));
ssh_channel_send_eof(channel);
ssh_channel_free(channel);
return SSH_ERROR;
}
if (strncmp(buffer, "GET /", 5)) continue;
nbytes = strlen(helloworld);
nwritten = ssh_channel_write(channel, helloworld, nbytes);
if (nwritten != nbytes)
{
fprintf(stderr, "Error sending answer: %s\n",
ssh_get_error(session));
ssh_channel_send_eof(channel);
ssh_channel_free(channel);
return SSH_ERROR;
}
printf("Sent answer\n");
}
ssh_channel_send_eof(channel);
ssh_channel_free(channel);
return SSH_OK;
}

ssh_channel_listen_forward()

定义:

1
int ssh_channel_listen_forward(ssh_session session, const char* address, int port, int* bound_port)

发送”tcpip-forward”全局请求要求服务器开始监听入站连接

参数:

  • session 发送请求的ssh会话
  • address 将要监听的服务器上的地址.发送NULL监听服务器支持的所有协议族的所有可用地址
  • port 要在服务器上绑定的端口.传递0让服务器分配下一个可以的非特权端口
  • bound_port 获取实际绑定端口的指针.传递NULL忽略该项

返回:SSH_OK;SSH_ERROR;SSH_AGAIN

ssh_channel_accept_forward()

定义:

1
ssh_channel ssh_channel_accept_forward(ssh_session session, int timeout_ms, int* destination_port)

接收传入的TCP/IP转发信道,并获取有关传入连接的信息

参数:

  • session 使用的ssh会话
  • timeout_ms 以毫秒为单位的超时
  • destination_port 指向目的端口或NULL指针

返回:新创建的信道或NULL

X11隧道

流程

图形应用程序(X11客户端)->SSH服务器 ===> SSH客户端 ->本地展示(X11服务器)

由客户端创建SSH信道

Chapter8: 使用libssh的线程

libssh可以用于多线程应用程序,但注意:

  • 线程必须在初始化libssh期间初始化,该初始化必须在任何线程上下文以为完成
  • 如果应用程序使用pthreads,则必须链接libssh_threads动态库并使用ssh_threads_pthreads线程对象初始化线程
  • 如果应用程序正在使用其他线程库,则必须实现ssh_threads_callbacks_struct机构的所有方法,并用它初始化libssh
  • 任何时候都可以在线程内部使用不同的会话,并行连接,在不同会话中读/写等.但不能在多个进程中使用单个会话(或单个会话的信道)

线程初始化

先调用ssh_threads_set_callbacks()选择要使用的线程模型,然后调用ssh_init()

1
2
3
4
#include <libssh/callback.h>

ssh_threads_set_callbacks(ssh_threads_get_noop());
ssh_init();

ssh_threads_noop时不执行任何操作的线程结构,时不适用线程时默认使用的线程回调

ssh_threads_set_callbacks()

定义:

1
int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct* cb)

设置线程回调结构

如果要以多线程方式使用libssh,则该函数时必须的.在调用ssh_init()之前,必须调用该函数,而不是在线程上下文

参数:cb 指向ssh_threads_callbacks_struct结构的指针,包含要设置的不同回调

返回:总返回SSH_OK

在libssh中使用libpthread

1
2
3
4
#include <libssh/callbacks.h>

ssh_threads_set_callbacks(ssh_threads_get_pthread());
ssh_init();

必须确保与ssh_threads链接.如果使用gcc,必须使用命令行

gcc -o output input.c -lssh -lssh_threads