libcli provides a telnet command-line environment which can be embedded in other programs. This environment includes useful features such as automatic authentication, history, and command-line editing.
This guide should show you everything you need to embed libcli into your program. If you have any corrections, suggestions or modifications, please E-mail David Parrish <david@dparrish.com>.
Two methods of authentcation are supported by libcli - internal and callback.
Internal authentication is based on a list of username / password combinations that are set up before cli_loop() is called. Passwords may be clear text, MD5 encrypted, or DES encrypted (requires a {crypt} prefix to distinguish from clear text).
Callback based authentication calls a callback with the username and password that the user enters, and must return a CLI_OK or CLI_ERROR. This can be used for checking passwords against some other database such as LDAP.
If neither cli_set_auth_callback() or cli_allow_user() have been called before cli_loop(), then authentication will be disabled and the user will not be prompted for a username / password combination.
Authentication for the privileged state can also be defined by a static password or by a callback. Use the cli_set_enable_callback() or cli_allow_enable functions to set the enable password.
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <libcli.h> int main(int argc, char *argv[]) { struct sockaddr_in servaddr; struct cli_command *c; struct cli_def *cli; int on = 1, x, s; // Must be called first to setup data structures cli = cli_init(); // Set the hostname (shown in the the prompt) cli_set_hostname(cli, "test"); // Set the greeting cli_set_banner(cli, "Welcome to the CLI test program."); // Enable 2 username / password combinations cli_allow_user(cli, "fred", "nerk"); cli_allow_user(cli, "foo", "bar"); // Set up a few simple one-level commands cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simple", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); // This command takes arguments, and requires privileged mode (enable) cli_register_command(cli, NULL, "set", cmd_set, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL); // Set up 2 commands "show counters" and "show junk" c = cli_register_command(cli, NULL, "show", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); // Note how we store the previous command and use it as the parent for this one. cli_register_command(cli, c, "junk", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); // This one has some help text cli_register_command(cli, c, "counters", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the counters that the system uses"); // Create a socket s = socket(AF_INET, SOCK_STREAM, 0); setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); // Listen on port 12345 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(12345); bind(s, (struct sockaddr *)&servaddr, sizeof(servaddr)); // Wait for a connection listen(s, 50); while ((x = accept(s, NULL, 0))) { // Pass the connection off to libcli cli_loop(cli, x); close(x); } // Free data structures cli_done(cli); return 0; }
This code snippet is all that's required to enable a libcli program. However it's not yet compilable because we haven't created the callback functions.
A few commands have been created:
Note that simon isn't on this list because callback was NULL when it was registered, so the command will not be available.
Also, the standard libcli commands help, exit, logout, quit and history are also available automatically.
int cmd_test(struct cli_def *cli, char *command, char *argv[], int argc) { cli_print(cli, "called %s with %s\r\n", __FUNCTION__, command); return CLI_OK; } int cmd_set(struct cli_def *cli, char *command, char *argv[], int argc) { if (argc < 2) { cli_print(cli, "Specify a variable to set\r\n"); return CLI_OK; } cli_print(cli, "Setting %s to %s\r\n", argv[0], argv[1]); return CLI_OK; }
2 callback functions are defined here, cmd_test() and cmd_set(). cmd_test() is called by many of the commands defined in the tutorial, although in reality you would usually use a callback for a single command.
cmd_test() simply echos the command entered back to the
client. Note that it shows the full expanded command, so you can enter
"te
" at the prompt and it will print back "called
with test
".
cmd_set handles the arguments given on the command line. This allows you to use a single callback to handle lots of arguments like:
gcc libclitest.c -o libclitest -lcliYou can now run the program with ./libclitest and telnet to port 12345 to see your work in action.
This must be called before any other cli_xxx function. It sets up the internal data structures used for command-line processing.
Returns a struct cli_def *
which must be passed to all
other cli_xxx functions.
This is optional, but it's a good idea to call this when you are finished with libcli. This frees memory used by libcli.
Add a command to the internal command tree. Returns a struct cli_command *, which you can pass as parent to another call to cli_register_command().
When the command has been entered by the user, callback is checked. If it is not NULL, then the callback is called with:
The callback must return CLI_OK if the command was successful, CLI_ERROR if processing wasn't successful and the next matching command should be tried (if any), or CLI_QUIT to drop the connection (e.g. on a fatal error).
If parent is NULL, the command is added to the top level of commands, otherwise it is a subcommand of parent
privilege should be set to either PRIVILEGE_PRIVILEGED or PRIVILEGE_UNPRIVILEGED. If set to PRIVILEGE_PRIVILEGED then the user must have entered enable before running this command.
mode should be set to MODE_EXEC for no configuration mode, MODE_CONFIG for generic configuration commands, or your own config level. The user can enter the generic configuration level by entering configure terminal, and can return to MODE_EXEC by entering exit or CTRL-Z. You can define commands to enter your own configuration levels, which should call the cli_set_configmode() function.
If help is provided, it is given to the user when the use the help command or press ?.
Remove a command command and all children. There is not provision yet for removing commands at lower than the top level.
The main loop of the command-line environment. This must be called with the FD of a socket open for bi-directional communication (sockfd).
cli_loop() handles the telnet negotiation and authentication. It returns only when the connection is finished, either by a server or client disconnect.
Returns CLI_OK.
Enables or disables callback based authentication.
If auth_callback is not NULL, then authentication will be required on connection. auth_callback will be called with the username and password that the user enters.
auth_callback must return a non-zero value if authentication is successful.
If auth_callback is NULL, then callback based authentication will be disabled.
Enables internal authentication, and adds username/password to the list of allowed users.
The internal list of users will be checked before callback based authentication is tried.
Removes username/password from the list of allowed users.
If this is the last combination in the list, then internal authentication will be disabled.
Sets the greeting that clients will be presented with when they connect. This may be a security warning for example.
If this function is not called or called with a NULL argument, no banner will be presented.
Sets the hostname to be displayed as the first part of the prompt.
Adds a callback function which will be called every second that a user is connected to the cli. This can be used for regular processing such as debugging, time counting or implementing idle timeouts.
Pass NULL as the callback function to disable this at runtime.
If the callback function does not return CLI_OK, then the user will be disconnected.
This reads and processes every line read from f as if it were entered at the console. The privilege level will be set to privilege and mode set to mode during the processing of the file.
This function should be called for any output generated by a command callback.
It takes a printf() style format string and a variable number of arguments.
Be aware that any output generated by cli_print will be passed through any filter currently being applied, and the output will be redirected to the cli_print_callback() if one has been specified.
A variant of cli_print() which does not have filters applied.
Whenever cli_print() or cli_error() is called, the output generally goes to the user. If you specify a callback using this function, then the output will be sent to that callback. The function will be called once for each line, and it will be passed a single null-terminated string, without any newline characters.
Specifying NULL as the callback parameter will make libcli use the default cli_print() function.
Just like cli_set_auth_callback this takes a pointer to a callback function to authorize privileged access. However this callback only takes a single string - the password.
This will allow a static password to be used for the enable command. This static password will be checked before running any enable callbacks.
Set this to NULL to not have a static enable password.
This will set the configuration mode. Once set, commands will be restricted to only ones in the selected configuration mode, plus any set to MODE_ANY. The previous mode value is returned.
The string passed will be used to build the prompt in the set configuration mode. e.g. if you set the string test, the prompt will become:
hostname(config-test)#David Parrish <david@dparrish.com> 2004-06-25