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.butkevic...@gmail.com>
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.butkevic...@gmail.com>
---
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@libssh.org>";
+
+/* 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