libssh库简介
今天依旧是一篇旧文,还是ssh相关。ssh相关的开发大多是基于libssh库。本文关于libssh库的介绍,基本上也是对文档的翻译,时隔较旧,如有纰漏欢迎指正。
Chapter 1:SSH会话示例
创建会话并设置选项
1 |
|
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 |
|
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()组合使用
验证服务器
连接完成后,必须检查刚刚连接的服务器是否一致且安全可用,有两种方式实现:
- (推荐)使用ssh_is_server_known()函数。该函数将查看已知的主机文件(UNIX中的~/.ssh/known_hosts),查找服务器主机名的模式,并确认该主机是否存在列表中
- 使用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 |
|
ssh_get_error_code()
定义:
1 | int ssh_get_error_code(void* error) |
用于接受最后一个错误的错误代码
返回一个错误代码,对应不同错误状态
验证用户
在用户验证服务器是安全可用的远程主机后,下一步是服务器授权用户,使已认证的用户能够访问资源。
libssh支持的认证方法:
- 无认证
- 密码方法
- 键盘交互方式:服务器向用户发出几个挑战,用户必须正确回答问题。志告方式使通过密码本验证成为可能
- 公钥方法
这些认证方式可以结合使用。
一个使用密码进行身份验证的实例:
1 |
|
操作
在双方进行验证后,下一步可以利用SSH协议进行操作,执行远程命令,开启远程终端,交换文件,转发端口;等等。
执行远程命令的示例:
1 | int show_remote_processes(ssh_session session) |
Chapter 2:用户验证相关
公钥,密码,挑战应答模式(键盘交互),无验证
公钥验证方法
libssh与openssh公钥和私钥完全兼容,可以使用libssh提供的自动公钥验证方法,也可以使用公钥函数进行自定义。
验证流程
- 扫描本地包含公钥的文件列表,每个密钥都发送到SSH服务器,直到服务器确认一个密钥(服务器已知的可以认证用户的密钥)。
- 检索此密钥的私钥并发送证明自己知道该私钥的消息。
使用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 | int authenticate_pubkey(ssh_session session) |
密码验证
使用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
认证过程
- 调用ssh_userauth_kbdint()函数并存储答案
- 如果收到的返回是SSH_AUTH_INFO,则说明服务器发送了几个问题,询问用户,使用ssh_userauth_kbdint_getnprompts(),ssh_userauth_kbdint_getname(),ssh_userauth_kbdint_getinstruction()和ssh_userauth_kbdint_getprompt()检索问题
- 使用ssh_userauth_kbdint_setanswer()为挑战中的每个问题设置答案
- 再次调用ssh_userauth_kbdint(),直到这些函数返回的内容不是SSH_AUTH_INFO
示例
1 | //为当前会话进行键盘交互方式的认证 |
使用“无验证”模式
无验证模式主要目的是在没有任何凭证的情况下进行认证(除非确实要授权匿名访问权限,不要使用该方式)。
如果该账号没有密码,且服务器配置为允许通过,则ssh_userauth_none()可能回复SSH_AUTH_SUCCESS
ssh_userauth_none()
定义:
1 | int ssh_userauth_none(ssh_session session,const char* username) |
示例
1 | int authenticate_kbdint(ssh_session session) |
获取支持的身份验证列表
如果不选择指定某种验证方法,可以让服务器展示可以是使用的验证方法
使用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 | int display_banner(ssh_session session) |
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 | int shell_session(ssh_session session) |
交互会话与非交互会话
如果需要一个接一个地键入命令,则认为是交互模式的;如果没有附加终端,类似于后台执行命令,是为为交互式的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 | int interactive_shell_session(ssh_channel channel) |
显示远程计算机发送的数据
在程序中通常要接受来自远程终端的数据,需要进行分析,记录或显示
使用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 | int interactive_shell_session(ssh_channel channel) |
像远程主机发送用户输入
使用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 | int kbhit() |
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 | int interactive_shell_session(ssh_channel channel) |
Chapter4:传递远程命令
该方法只适用于执行一个远程命令,如果要发出多个命令,应使用非交互式远程shell
执行远程命令
1.打开一个SSH信道
1 | int show_remote_files(ssh_session session) |
2.执行远程命令
调用ssh_channel_request_exec()执行远程命令
1 | rc = ssh_channel_request_exec(channel,"ls -l"); |
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 | char buffer[256]; |
4.结束
读取远程命令的结果后,将文件结束符EOF发送到信道,关闭信道并释放信道
1 | ssh_channel_send_eof(channel); |
Chapter 5:SFTP子系统
SFTP是安全文件传输协议的简称,可以用于本地与远程计算机的远程传输.SFTP的功能丰富,现有的版本是版本3,虽然未解决全部功能,但核心功能已经实现
打开和关闭SFTP会话
SFTP子系统不是打开一个SSH信道,而是开启一个SFTP会话
用sftp_new()创建一个新的SFTP会话,用函数sftp_free()初始化,sftp_free()删除.
示例
1 |
|
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 |
|
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 |
|
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 | //以16KB文件块远程打开"/etc/profile"文件 |
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 |
|
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 | int sftp_list_dir(ssh_session,sftp_session sftp) |
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 | int scp_write(ssh_session session) |
示例2:打开连接读取单个文件
1 | int scp_read(ssh_session session) |
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 | int scp_helloworld(ssh_session session,ssh_scp scp) |
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 | int scp_receive(ssh_session session,ssh_scp scp) |
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 | int direct_forwarding(ssh_session session) |
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 | int web_server(ssh_session session) |
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 |
|
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 |
|
必须确保与ssh_threads链接.如果使用gcc,必须使用命令行
gcc -o output input.c -lssh -lssh_threads