The Kahimyang Project Logo
Primo's Code Blog
Howtos tips and tricks
The Kahimyang Project

Using expect script C/C++ library to manage Linux hosts

The expect C library is an altenative to using plain expect scripts, specially useful if you are a C or C++ programmer who also mange Linux hosts.  The program that follows was built in Debian, it is a simple demonstration on how to use the library.

Let us start by installing the library and other tools we need.

apt-get update
apt-get install build-essential libgoogle-glog-dev expect-dev tcl8.5 dev

The function below executes a single command and exit after successful ssh login.  Please refer to the inline comments and the description that follows for guidance.


#include <sys/wait.h>
#include <tcl8.5/expect.h>
#include <glog/logging.h>

#include <iostream>
#include <fstream>
#include <string>
#include <iterator>
#include <sstream>
#include <vector>

using namespace std;

int task(const string& host, const string& password,
        const string& command) {

    exp_is_debugging = 0;
    exp_timeout = 20;

    // exp_open spawn our ssh/program and returns a stream.	
    FILE *expect = exp_popen((char *) host.c_str());
    if (expect == 0) {
        return 1;
    }

    enum { denied, invalid, command_not_found, 
            command_failed, prompt };

    // exp_fexpectl takes variable parameters terminated by
    // exp_end.  A parameter takes the form 
    // {type, pattern, and return value}  corresponding to
    // exp_glob, "password", prompt,  in exp_fexpectl call below.
    // If a pattern matches, exp_fexpectl returns with the value
    // corresponding to the pattern.  

    switch (exp_fexpectl(expect,
                exp_glob, "password: ", prompt,
                exp_end)) {
        case prompt:
                // continue 
                break;
        case EXP_TIMEOUT:
                LOG(ERROR) << "Timeout,  may be invalid host" << endl;
                return 1;
    }

    bool success = true

    // In sending command the return character \r is required.
    fprintf(expect, "%s\r", password.c_str());

    switch (exp_fexpectl(expect,
                exp_glob, "denied", denied, // 1 case		
                exp_glob, "invalid password", invalid, // another case
                exp_glob, "$ ", prompt, // third case			
                exp_end)) {
        case denied:
                LOG(ERROR) << "Access denied" << endl;
                success = false;
                break;
        case invalid:
                success = false;
                LOG(ERROR) << "Invalid password" << endl;
                break;
        case EXP_TIMEOUT:
                LOG(ERROR) << "Login timeout" << endl;
                break;
        case prompt:
                // continue;		
                break;
        default:
                break;
    }

    if (!success) {
        fclose(expect);
        waitpid(exp_pid, 0, 0);
        return 1;
    }

    // Note that the pattern used below are dependent
    // on the command.  Adding more cases to cover
    // various command will help.

    fprintf(expect, "%s\r", command.c_str());

    switch (exp_fexpectl(expect,
        exp_glob, "cannot create", command_failed,
        exp_glob, "command not found", command_not_found,
        exp_glob, "$ ", prompt,
        exp_end)) {
    case command_failed:
        LOG(ERROR) << "Could not create directory" << endl;
        success = false;;
        break;
    case command_not_found:
        LOG(ERROR) << "Command not found" << endl;
        success = false;
        break;
    case prompt:
        fprintf(expect, "%s\r", "exit");
        LOG(INFO) << "Task completed." << endl;
        break;
    }

    // You should always do this.
    // exp_popen spawns a process with pid exp_pid and 
    // return a FILE object.  

    fclose(expect);
    waitpid(exp_pid, 0, 0);

    if (success)
        return 0;
    else
        return 1;
}

Note that exp_fexpectl will not return and will wait for the pattern to arrive until exp_timeout.   Make sure you have all the expected string patterns covered.

The pattern type exp_glob is an enum which determines how the pattern is interpreted.  Use exp_regex if you want to use reqular expressions in the case string pattern.  Please see expect.h in your include directory for more options.

exp_fexpectv can be used instead of exp_fexpectl. exp_fexpectv takes struct exp_case as parameter.  The code fragment below corresponds to the section of code above beginning at line 52.

struct exp_case cases [] = { 
        { "denied", NULL, exp_glob, denied},
        { "invalid password", NULL,exp_glob,invalid},
        { "$ ", NULL, exp_glob, prompt},
        0
};

switch (exp_fexpectv (expect, cases)) {	
case denied:
        LOG(ERROR) << "Access denied" << endl;
        retval=error;
        break; 
case invalid:
        retval=failure;
        LOG(ERROR) << "Invalid password" << endl;
        break;
case prompt:
        // continue
        break;
case EXP_TIMEOUT:
        LOG(ERROR) << "Login timeout" << endl;
        break;
}

The terminating null character in exp_case cases above is required.

We have 3 switch blocks above which makes the code a little bit longer.  If preferred, all the 3 switch blocks can be placed in a loop and then expand the case patters to cover all possible prompts.

The main function.


int main(int argc, char *argv[]) {
    // Initialize logging
    google::InitGoogleLogging("exp");
    ifstream tasks("tasks.txt", ifstream::in);

    // This is how we read our tasks.txt file.  You may
    // want to employ a way you are comfortable with.

    if (tasks.is_open()) {
        for (string line; getline(tasks, line);) {
            stringstream str(line);
            istream_iterator<string> it(str);
            istream_iterator<string> end;
            vector<string> result(it, end);

            string password;
            ostringstream command, host;
            host << "ssh ";

            for (vector<int>::size_type i = 0;
                    i != result.size(); i++) {
                    switch (i) {
                    case 0:
                            host << result [i];
                            break;
                    case 1:
                            password = result [i];
                            break;
                    default:
                            command << result [i] << " ";
                            break;
                    }
            }

            if (task(host.str(), password, command.str()) == 1) {
                // Error occurred.  You may want to stop
                // at this point if the next task is dependent 
                // on the previous task.
            }
        }
    }

    return 0;
}

Our programs takes a text file,  tasks.txt as input.   Each line is separated by spaces with the first to being use@host and password.  The third and beyond is the command and its parameters.

To build the program above use the following from your command line.

g++ exp.cpp -Wall -std=c++0x -lexpect -ltcl8.5 -lglog -o exp

That's it Good Luck.

RSS Logo



Related articles

Comment icon   Comments (Newest first)

Recent posts in C/C++ category
Blue dot icon Using jQuery UI in CGI C++ Web application
Blue dot icon Using expect script C/C++ library to manage Linux hosts
Blue dot icon A simple C/C++ database daemon
Blue dot icon How to setup FLV streaming with crtmpserver C++ RTMP server
Blue dot icon Using MySQL data to populate sections of HTML template and render page with CGI C++
Blue dot icon How to build a C/C++ web application using CGI C++ library for Debian Linux deployment





Most popular articles
Blue dot icon Using Expect script to automate SSH logins and do routine tasks accross multiple hosts   (15289)
Blue dot icon How to setup Tomcat 7 as your primary webserver on Debian Squeeze    (14053)
Blue dot icon How to setup FLV streaming with crtmpserver C++ RTMP server   (9768)
Blue dot icon A Java class for sending multipart Email messages through your Gmail account    (6968)
Blue dot icon How to use Google Translate's Text to Speech (TTS) services in your web page using Servlet   (5642)
Blue dot icon Speed up Primefaces page load with p:remoteCommand partial update   (5411)
Blue dot icon Building a mobile website with JSF 2 core and JQuery Mobile 1.0   (5309)
Blue dot icon Google Map-Adding markers, info window, circle, small control, and events in Javascript    (5209)