[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] examples: Add samplesshd-full example
[Thread Prev] | [Thread Next]
- Subject: [PATCH] examples: Add samplesshd-full example
- From: Audrius Butkevicius <audrius.butkevicius@xxxxxxxxxxxxxxxx>
- Reply-to: libssh@xxxxxxxxxx
- Date: Sun, 26 Jan 2014 02:49:53 +0000
- To: libssh@xxxxxxxxxx
Hi,As promised on IRC, a slightly more full example server implementation using libssh attached. Since I am no C expert, I hope you will have enough patience with this, as I would be very happy to get comments/suggestions of what should be done differently.
Regards, Audrius.
From 9fb95f2840d7515ea2e5c4a105f8d5ecae51d2eb Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius <audrius.butkevicius@xxxxxxxxx> Date: Sun, 26 Jan 2014 02:23:49 +0000 Subject: [PATCH] examples: Add samplesshd-full example A new example which provides a working multi-process SSH server which supports shell, exec and subsystem (sftp) requests. Signed-off-by: Audrius Butkevicius <audrius.butkevicius@xxxxxxxxx> --- examples/CMakeLists.txt | 3 + examples/samplesshd-full.c | 534 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 537 insertions(+), 0 deletions(-) create mode 100644 examples/samplesshd-full.c diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c155e09..1b54127 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,6 +29,9 @@ if (UNIX AND NOT WIN32) if (HAVE_LIBUTIL) add_executable(samplesshd-tty samplesshd-tty.c) target_link_libraries(samplesshd-tty ${LIBSSH_SHARED_LIBRARY} ${ARGP_LIBRARIES} util) + + add_executable(samplesshd-full samplesshd-full.c) + target_link_libraries(samplesshd-full ${LIBSSH_SHARED_LIBRARY} ${ARGP_LIBRARIES} util) endif (HAVE_LIBUTIL) endif (WITH_SERVER) diff --git a/examples/samplesshd-full.c b/examples/samplesshd-full.c new file mode 100644 index 0000000..723a6e1 --- /dev/null +++ b/examples/samplesshd-full.c @@ -0,0 +1,534 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2014 Audrius Butkevicius + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. +*/ + +#include "config.h" + +#include <libssh/libssh.h> +#include <libssh/server.h> +#include <libssh/callbacks.h> + +#ifdef HAVE_ARGP_H +#include <argp.h> +#endif +#include <fcntl.h> +#include <pty.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/wait.h> + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "myuser" +#define PASS "mypassword" +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) +#define SFTP_SERVER "/usr/lib/sftp-server" + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = "<libssh@xxxxxxxxxx>"; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "dsakey", + .key = 'd', + .arg = "FILE", + .flags = 0, + .doc = "Set the dsa key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'd': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +/* Enumerator for different types of connections */ +typedef enum { UNKNOWN, SHELL, EXEC, SUBSYSTEM } channel_type; + +/* A userdata struct for channel */ +struct channel_data_struct { + /* pid of the child process the channel will spawn */ + int pid; + /* Channel type */ + channel_type type; + /* fd for stdin */ + int stdin; + /* fd for stdout */ + int stdout; + /* fd for stderr (not used in case of a SHELL channel type) */ + int stderr; + /* Terminal size */ + struct winsize *winsize; +}; + +/* A userdata struct for session*/ +struct session_data_struct { + /* Channel reference which the session will allocate */ + ssh_channel channel; + int auth_attempts; + int authenticated; +}; + +static int data_function(ssh_session session, ssh_channel channel, void *data, + uint32_t len, int is_stderr, void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void) session; + (void) channel; + (void) is_stderr; + + if (!len || !cdata->pid || !cdata->type || kill(cdata->pid, 0) < 0) + return 0; + + return write(cdata->stdin, (char *)data, len); +} + +static int pty_request(ssh_session session, ssh_channel channel, + const char *term, int cols, int rows, int px, int py, + void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void) session; + (void) channel; + (void) term; + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + return SSH_OK; +} + +static int pty_resize(ssh_session session, ssh_channel channel, int rows, + int cols, int px, int py, void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void) session; + (void) channel; + + if (cdata->type == SHELL) { + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + return ioctl(cdata->stdin, TIOCSWINSZ, cdata->winsize); + } + + return SSH_OK; +} + +static int shell_request(ssh_session session, ssh_channel channel, + void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void) session; + (void) channel; + + switch(cdata->pid = forkpty(&cdata->stdin, NULL, NULL, cdata->winsize)) { + case -1: + close(cdata->stdin); + fprintf(stderr, "Failed to allocate pty\n"); + return SSH_ERROR; + case 0: + execl("/bin/sh", "sh", "-l", NULL); + close(cdata->stdin); + abort(); + } + /* forkpty returns a bi-directional pipe/socket */ + cdata->stdout = cdata->stdin; + + /* Set pipe to allow non-blocking reads */ + fcntl(cdata->stdin, F_SETFL, fcntl(cdata->stdin, F_GETFL, 0) | O_NONBLOCK); + + cdata->type = SHELL; + return SSH_OK; +} + +static int exec_request(ssh_session session, ssh_channel channel, + const char *command, void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + int in[2], out[2], err[2]; + + (void) session; + (void) channel; + + if (pipe(in) != 0) + goto stdin_failed; + if (pipe(out) != 0) + goto stdout_failed; + if (pipe(err) != 0) + goto stderr_failed; + + switch(cdata->pid = fork()) { + case -1: + goto fork_failed; + case 0: + close(in[1]); + close(out[0]); + close(err[0]); + close(0); + close(1); + close(2); + dup(in[0]); + dup(out[1]); + dup(err[1]); + execl("/bin/sh", "sh", "-c", command, NULL); + close(in[0]); + close(out[1]); + close(err[1]); + abort(); + } + + close(in[0]); + close(out[1]); + close(err[1]); + + /* Set pipes to allow non-blocking reads */ + fcntl(out[0], F_SETFL, fcntl(out[0], F_GETFL, 0) | O_NONBLOCK); + fcntl(err[0], F_SETFL, fcntl(err[0], F_GETFL, 0) | O_NONBLOCK); + + cdata->stdin = in[1]; + cdata->stdout = out[0]; + cdata->stderr = err[0]; + + cdata->type = EXEC; + + return SSH_OK; + +fork_failed: + close(err[0]); + close(err[1]); +stderr_failed: + close(out[0]); + close(out[1]); +stdout_failed: + close(in[0]); + close(in[1]); +stdin_failed: + return SSH_ERROR; +} + +static int subsystem_request(ssh_session session, ssh_channel channel, + const char *subsystem, void *userdata) { + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + int rc; + + if (strcmp(subsystem, "sftp")) + return SSH_ERROR; + + rc = exec_request(session, channel, SFTP_SERVER, userdata); + + if (rc == SSH_OK) + cdata->type = SUBSYSTEM; + + return rc; +} + +static int auth_password(ssh_session session, const char *user, + const char *pass, void *userdata) { + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + (void) session; + + if (!strcmp(user, USER) && !strcmp(pass, PASS)) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + + sdata->auth_attempts++; + return SSH_AUTH_DENIED; +} + +static ssh_channel session_open_channel(ssh_session session, void *userdata) { + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + return (sdata->channel = ssh_channel_new(session)); +} + +static void handle_session(ssh_event event, ssh_session session) { + char buf[BUF_SIZE]; + int n, rc; + + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + struct channel_data_struct cdata = { + .pid = 0, + .type = UNKNOWN, + .stdin = -1, + .stdout = -1, + .stderr = -1, + .winsize = &wsize + }; + + struct session_data_struct sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0 + }; + + struct ssh_channel_callbacks_struct channel_cb = { + .userdata = &cdata, + .channel_pty_request_function = pty_request, + .channel_pty_window_change_function = pty_resize, + .channel_shell_request_function = shell_request, + .channel_exec_request_function = exec_request, + .channel_data_function = data_function, + .channel_subsystem_request_function = subsystem_request + }; + + + struct ssh_server_callbacks_struct server_cb = { + .userdata = &sdata, + .auth_password_function = auth_password, + .channel_open_request_session_function = session_open_channel, + }; + + ssh_callbacks_init(&server_cb); + ssh_callbacks_init(&channel_cb); + + ssh_set_server_callbacks(session, &server_cb); + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); + ssh_event_add_session(event, session); + + while (!sdata.authenticated || sdata.channel == NULL) { + if (sdata.auth_attempts >= 3) + return; + + if (ssh_event_dopoll(event, -1) == SSH_ERROR) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + } + + ssh_set_channel_callbacks(sdata.channel, &channel_cb); + + do { + usleep(0.03); + + /* Calls channel_cb.channel_data_function once there is data */ + ssh_channel_poll(sdata.channel, 0); + + /* If the child process is not running yet, skip reading */ + if (!cdata.pid || !cdata.type) + continue; + + /* Read from childs stdout, write it to the channel */ + if ((n = read(cdata.stdout, buf, BUF_SIZE)) > 0) + ssh_channel_write(sdata.channel, buf, n); + + /* Read from childs stderr, write it to the channel */ + if ((n = read(cdata.stderr, buf, BUF_SIZE)) > 0) + ssh_channel_write_stderr(sdata.channel, buf, n); + + } while(ssh_channel_is_open(sdata.channel) && + !ssh_channel_is_eof(sdata.channel) && + (!cdata.pid || !waitpid(cdata.pid, &rc, WNOHANG))); + + close(cdata.stdin); + close(cdata.stdout); + close(cdata.stderr); + + /* If the child process exited */ + if (kill(cdata.pid, 0) < 0) { + if (WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + /* If the client terminated the channel */ + } else { + kill(cdata.pid, SIGKILL); + } + + /* Wait up to 5 seconds for the client to terminate the session */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) + ssh_event_dopoll(event, 100); +} + +static void fork_session_handler(ssh_session session) { + ssh_event event; + + switch(fork()) { + case 0: + /* Remove the SIGCHLD handler inherited from parent */ + signal(SIGCHLD, SIG_DFL); + event = ssh_event_new(); + + handle_session(event, session); + + ssh_event_free(event); + ssh_disconnect(session); + ssh_free(session); + + abort(); + case -1: + fprintf(stderr, "Failed to fork\n"); + } +} + +/* SIGCHLD handler for cleaning up dead children */ +static void sigchld_handler(int signo) { + (void) signo; + while (waitpid(-1, NULL, WNOHANG) > 0); + signal(SIGCHLD, sigchld_handler); +} + +int main(int argc, char **argv) { + ssh_bind sshbind; + ssh_session session; + + ssh_init(); + sshbind = ssh_bind_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, + KEYS_FOLDER "ssh_host_dsa_key"); + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif /* HAVE_ARGP_H */ + + if(ssh_bind_listen(sshbind) < 0) { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + return 1; + } + + signal(SIGCHLD, sigchld_handler); + + while (1) { + session = ssh_new(); + + /* Blocks until there is a new incoming connection */ + if(ssh_bind_accept(sshbind, session) == SSH_ERROR) + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + else + fork_session_handler(session); + + ssh_disconnect(session); + ssh_free(session); + } + + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} -- 1.7.2.5
Re: [PATCH] examples: Add samplesshd-full example | Audrius Butkevicius <audrius.butkevicius@xxxxxxxxxxxxxxxx> |