C++学习:使用libssh2实现交互式shell的ssh2类,linux和windows通用。

使用ssh2实现shell自动化测试,实际工作中遇到非常多。各种语言都有相应的库可以使用。比如,c/c++语言可以使用libssh2;python可以使用paramkio库等。但这些库网上的帮助,都不是很全,都比较浅显。shell自动化,最基本的三个需求,一个是适合多重类型的操作系统;二是要能够支持交互式shell,比如使用sudo执行时,需要输入密码;三是读数据时要非阻塞的。

paramkio好像不支持交互式(shell命令不需要再根据输出输入不同的参数,实际上,这种情况遇到非常多),而且paramkio在windows上也很不好用,要实现非阻塞,找了几天都没能找到好的办法。

libssh2表面上不支持交互式,官方的例子也没有针对交互式shell进行举例,给库的使用者带来很大的困扰,比较难入门。实际上libssh2库是支持交互式shell的,而且支持多种OS,实现起来也相当的简单,只是过程摸索过于痛苦。关于libssh2的编译,也比较简单,这里就不列举了。libssh2是c语言的,样例写起来很繁琐,因此我用c++进行封装,学习起来比较直观。

实际libssh2两个官方的样例scp.c和scp_echo.c已经提供了交互式shell的实现方式,只是scp_echo.c有关ssh2登陆认证方式,没有像scp.c使用了ssh2的键盘交互式方式,一般ssh2服务器默认提供这个认证方式。scp_echo.c不容易调试通过。

首先,先看一下实现Ssh2类的使用代码:

#include <iostream>
#include "ssh2.h"

int main(int argc, const char * argv[])
{
    using namespace std;
    using namespace fish;

    Ssh2 ssh("127.0.0.1");
    ssh.Connect("test","xxxxxx");
    Channel* channel = ssh.CreateChannel();
    channel->Write("cd /;pwd");
    cout<<channel->Read()<<endl;
    channel->Write("ssh 127.0.0.1");
    cout<<channel->Read(":")<<endl;
    channel->Write("xxxxxx");
    cout<<channel->Read()<<endl;
    channel->Write("pwd");
    cout<<channel->Read()<<endl;
    delete channel;
    return 0;
}

读写都是非阻塞的,这个实际使用非常方便。代码就不详细讲解了。

Ssh2类的实现代码如下:

    class Ssh2
    {
    public:
        Ssh2(const string &srvIp, int srvPort=22 );
        ~Ssh2(void);

        bool Connect( const string &userName, const string &userPwd);
        bool Disconnect(void);

        Channel* CreateChannel(const string &ptyType = "vanilla");

    public:
        static void S_KbdCallback(const char*, int, const char*, int, int, const LIBSSH2_USERAUTH_KBDINT_PROMPT*, LIBSSH2_USERAUTH_KBDINT_RESPONSE*, void **a );
        static string s_password;

    private:
        string m_srvIp;
        int    m_srvPort;
        string m_userName;
        string m_password;
        int    m_sock;
        LIBSSH2_SESSION *m_session;
    };

由于ssh2服务器要通过键盘交互式是通过回调函数实现认证的,所以使用了两个静态成员,一个是成员函数,一个是成员变量(当然,这种实现方式比较丑,暂时没有找到好的方法)。这里代码,没有考虑到多线程安全,因为实际需求不是很严格。当然,要支持多线程下ssh2登陆认证,也很容易实现。

string Ssh2::s_password;

    void Ssh2::S_KbdCallback(const char *name, int name_len,
                             const char *instruction, int instruction_len,
                             int num_prompts,
                             const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
                             LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
                             void **abstract)
    {
        (void)name;
        (void)name_len;
        (void)instruction;
        (void)instruction_len;
        if (num_prompts == 1)
        {
            responses[0].text   = strdup(s_password.c_str());
            responses[0].length = (int)s_password.size();
        }
        (void)prompts;
        (void)abstract;
    }

    Ssh2::Ssh2(const string &srvIp, int srvPort)
        :m_srvIp(srvIp),m_srvPort(srvPort)
    {
        m_sock = -1;
        m_session = NULL;
        libssh2_init(0);
    }

    Ssh2::~Ssh2(void)
    {
        Disconnect();
        libssh2_exit();
    }

    bool Ssh2::Connect(const string &userName, const string &userPwd)
    {
        m_sock = socket(AF_INET, SOCK_STREAM, 0);

        sockaddr_in sin;
        sin.sin_family = AF_INET;
        sin.sin_port = htons(22);
        sin.sin_addr.s_addr = inet_addr(m_srvIp.c_str());
        if ( connect( m_sock, (sockaddr*)(&sin), sizeof(sockaddr_in) ) != 0 )
        {
            Disconnect();
            return false;
        }

        m_session = libssh2_session_init();
        if ( libssh2_session_handshake(m_session, m_sock) )
        {
            Disconnect();
            return false;
        }

        int auth_pw = 0;
        string fingerprint = libssh2_hostkey_hash(m_session, LIBSSH2_HOSTKEY_HASH_SHA1);
        string userauthlist = libssh2_userauth_list( m_session, userName.c_str(), (int)userName.size() );
        if ( strstr( userauthlist.c_str(), "password") != NULL )
        {
            auth_pw |= 1;
        }
        if ( strstr( userauthlist.c_str(), "keyboard-interactive") != NULL )
        {
            auth_pw |= 2;
        }
        if ( strstr(userauthlist.c_str(), "publickey") != NULL)
        {
            auth_pw |= 4;
        }

        if (auth_pw & 1)
        {
            /* We could authenticate via password */
            if ( libssh2_userauth_password(m_session, userName.c_str(), userPwd.c_str() ) )
            {
                Disconnect();
                return false;
            }
        }
        else if (auth_pw & 2)
        {
            /* Or via keyboard-interactive */
            s_password = userPwd;
            if (libssh2_userauth_keyboard_interactive(m_session, userName.c_str(), &S_KbdCallback) )
            {
                Disconnect();
                return false;
            }
        }
        else
        {
            Disconnect();
            return false;
        }

        return true;
    }

    bool Ssh2::Disconnect(void)
    {
        if( m_session )
        {
            libssh2_session_disconnect(m_session, "Bye bye, Thank you");
            libssh2_session_free(m_session);
            m_session = NULL;
        }
        if( m_sock != -1 )
        {
#ifdef WIN32
            closesocket(m_sock);
#else
            close(m_sock);
#endif
            m_sock = -1;
        }
        return true;
    }

    Channel* Ssh2::CreateChannel(const string &ptyType)
    {
        if( NULL == m_session )
        {
            return NULL;
        }

        LIBSSH2_CHANNEL *channel = NULL;
        /* Request a shell */
        if ( !(channel = libssh2_channel_open_session(m_session)) )
        {
            return NULL;
        }

        /* Request a terminal with 'vanilla' terminal emulation
         * See /etc/termcap for more options
         */
        if ( libssh2_channel_request_pty(channel, ptyType.c_str() ) )
        {
            libssh2_channel_free(channel);
            return NULL;
        }

        /* Open a SHELL on that pty */
        if ( libssh2_channel_shell(channel) )
        {

            libssh2_channel_free(channel);
            return NULL;
        }

        Channel *ret = new Channel(channel);
        cout<<ret->Read()<<endl;
        return ret;
    }

这里要关注两个函数调用,一个是libssh2_channel_request_pty指定channel类型,如果使用xterm,就可以使用彩色显示。libssh2_channel_shell用来指定是交互式shell。

下面是Channel类的定义:

const int CHANNEL_READ_TIMEOUT = 3000;

    class Channel
    {
    public:
        Channel(LIBSSH2_CHANNEL *channel);
        ~Channel(void);

        string Read( const string &strend = "$", int timeout = CHANNEL_READ_TIMEOUT );
        bool   Write(const string &data);
    private:
        Channel(const Channel&);
        Channel operator=(const Channel&);
    private:
        LIBSSH2_CHANNEL *m_channel;
    };

Channel类的实现:

Channel::Channel( LIBSSH2_CHANNEL*channel)
        :m_channel(channel)
    {

    }

    Channel::~Channel(void)
    {
        if (m_channel)
        {
            libssh2_channel_free(m_channel);
            m_channel = NULL;
        }
    }

    string Channel::Read( const string &strend, int timeout )
    {
        string data;
        if( NULL == m_channel )
        {
            return data;
        }

        LIBSSH2_POLLFD *fds = new LIBSSH2_POLLFD;
        fds->type = LIBSSH2_POLLFD_CHANNEL;
        fds->fd.channel = m_channel;
        fds->events = LIBSSH2_POLLFD_POLLIN | LIBSSH2_POLLFD_POLLOUT;

        if( timeout % 50 )
        {
            timeout += timeout %50;
        }
        while(timeout>0)
        {
            int rc = (libssh2_poll(fds, 1, 10));
            if (rc < 1)
            {
                timeout -= 50;
                usleep(50*1000);
                continue;
            }

            if ( fds->revents & LIBSSH2_POLLFD_POLLIN )
            {
                char buffer[64*1024] = "";
                size_t n = libssh2_channel_read( m_channel, buffer, sizeof(buffer) );
                if ( n == LIBSSH2_ERROR_EAGAIN )
                {
                    //fprintf(stderr, "will read again\n");
                }
                else if (n <= 0)
                {
                    return data;
                }
                else
                {
                    data += string(buffer);
                    if( "" == strend )
                    {
                        return data;
                    }
                    size_t pos = data.rfind(strend);
                    if( pos != string::npos && data.substr(pos, strend.size()) == strend  )
                    {
                        return data;
                    }
                }
            }
            timeout -= 50;
            usleep(50*1000);
        }

        cout<<"read data timeout"<<endl;
        return data;
    }

    bool Channel::Write(const string &data)
    {
        if( NULL == m_channel )
        {
            return false;
        }

        string send_data = data + "\n";
        return libssh2_channel_write_ex( m_channel, 0, send_data.c_str(), send_data.size() ) == data.size();
        //return true;
    }

这里主要关注libssh2_poll函数,主要是检测通道是否有数据可以读。read函数简单做了封装,增加期望结果,有超时。Ssh2是支持多个Channel读写的,可以同时打个多个Channel,分别执行不同的操作,对于ssh2有登陆数限制时,还是很相当有用的。目前Ssh2客户端基本可用了。大家不妨试试。有关libssh2库再简单的例子,估计网上还没有吧,当然,有关libssh2安装,网上是有一大把。

用到的头文件包含如下:

#ifdef WIN32
#include <windows.h>
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>

#endif

#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>

#include <libssh2.h>
#include <libssh2_sftp.h>

c++比较复杂,也想在python中使用libssh2的便利性,扩展python语言。实现起来,当然要复杂一些。但复杂度也很低,只需要使用boost::python类,扩展python就行了。windows和linux下,都不复杂。后续有时间再整理。

时间: 2024-10-15 11:09:01

C++学习:使用libssh2实现交互式shell的ssh2类,linux和windows通用。的相关文章

详解Linux交互式shell脚本中创建对话框实例教程_linux服务器

本教程我们通过实现来讲讲Linux交互式shell脚本中创建各种各样对话框,对话框在Linux中可以友好的提示操作者,感兴趣的朋友可以参考学习一下. 当你在终端环境下安装新的软件时,你可以经常看到信息对话框弹出,需要你的输入.对话框的类型有密码箱,检查表,菜单,等等.他们可以引导你以一种直观的方式输入必要的信息,使用这样的用户友好的对话框的好处是显而易见的.如下图所示: 当你写一个交互式shell脚本,你可以使用这样的对话框来接受用户的输入.whiptail可以在shell脚本中创建基于终端的对

登录式与非登录式&amp;交互式与非交互式shell及其环境初始化过程

交互式shell和非交互式shell(interactive shell and non-interactive shell) 交互式模式就是在终端上执行,shell等待你的输入,并且立即执行你提交的命令.这种模式被称作交互式是因为shell与用户进行交互.这种模式也是大多数用户非常熟悉的:登录.执行一些命令.退出.当你退出后,shell也终止了. shell也可以运行在另外一种模式:非交互式模式,以shell script(非交互)方式执行.在这种模式 下,shell不与你进行交互,而是读取存

交互式shell与非交互式shell

首先一点是明确的:/etc/profile是设置所有用户的环境变量的配置文件,/home/omm/.profile是针对特定的用户设置环境变量的配置文件(omm用户). 但是重要的区别是:/etc/profile并不是每次都会加载的.它要区分login,non-login,interactive和non-interactive 模式的情况. login 代表用户登入, 比如使用 "su -" 命令, 或者用 ssh 连接到某一个服务器上, 都会使用该用户默认 shell 启动 logi

Linux 学习作业:认识bash shell

本次记录bash shell基础知识及bash变量功能,bash操作环境的配置内容,涉及解释什么是shell?.bash变量的定义和引用.怎样读入与设定bash的环境配置文件等. ? Shell的基本概念 1. Shell是什么? Linux系统由3个重要部分组成: w 内核(kernel) w Shell w 应用程序 内核真正在控制着计算机系统上的各种硬件与软件.功能包括进程管理.内存管理.设备管理.文件系统管理等等. 内核相当抽象,使用者不易和它直接通信,因此需要一个良好的接口,使得操作时

交互式shell和非交互式shell、登录shell和非登录shell的区别

交互式shell和非交互式shell.登录shell和非登录shell的区别.首先,这是两个不同的维度来划分的,一个是是否交互式,另一个是是否登录. 交互式shell和非交互式shell(interactive shell and non-interactive shell)交互式模式就是在终端上执行,shell等待你的输入,并且立即执行你提交的命令.这种模式被称作交互式是因为shell与用户进行交互.这种模式也是大多数用户非常熟悉的:登录.执行一些命令.退出.当你退出后,shell也终止了.s

《学习bash》笔记--调试shell程序

在shell中,最简单的调试助手时输出语句echo,可以通过把许多echo语句放到代码中进行调试,但必须花费足够的时间以定位 要查看的信息.可能必须通过许多的输出才能发现要查找的信息. 1.set选项 最基本的时set -o命令选项,当运行脚本时,这些选项可以用在命令行上,如下表所示: set -o选项      命令行选项      行为 noexec            -n                     不运行命令,值检查语法错误 verbose           -v  

《学习bash》笔记--基础shell编程

1.shell脚本和函数 脚本是包含shell命令的文件,它是一个shell程序,有三种方式运行它们: 一是键入source scriptname,使得脚本的命令被读取并运行,就好像键入它们一样. 二是运行bash scriptname,打开一个子shell来读取并执行脚本文件中命令.该脚本文件可以无"执行权限". 三是使用./scriptname,打开一个子shell读取并执行脚本文件中的命令,该脚本需要"执行权限". 1.1.函数 函数是一种脚本内脚本,你使用它

JMeter学习-015-JMeter 断言之-Bean Shell Assertion

前面的博文中有对 JMeter 中的 响应断言 进行了讲解并实例演示,详情敬请参阅博文:JMeter学习-007-JMeter 断言实例之一 - 响应断言. 在 JMeter 中总计提供了如下几种 BeanShell 组件: Beanshell Sampler:单独的采样器. Beanshell PreProcessor:针对其他采样器的前置处理器.可以在采样器执行之前进行预处理操作,生成相关测试数据供采样器使用,相当于 TestNG 中的 before annotation. Beanshel

ipython是python的交互式shell工具

ipython: 是python的交互式shell工具,比默认的python shell工具要好用.支持变了自动补全,自动缩进,内置了很多的功能和函数 启动:可以通过cmd来启动该工具 自动补全: In [12]: import os In [13]: os.w  #直接回车,会自动显示出来该模块下的所有方法,如果我们忘记了os模块的方法具有哪些,方法记不全就可以采用这个办法os.waitpid os.walk os.write %env显示环境变量 %hist 或 %history显示历史记录