From 000d659ea2eb709bf99e27c4c76233032869294f Mon Sep 17 00:00:00 2001 From: milo Date: Sun, 20 Feb 2011 13:07:34 +0100 Subject: [PATCH] examples: Added a event context based sshd example. --- ConfigureChecks.cmake | 1 + examples/CMakeLists.txt | 6 + examples/samplesshd-tty.c | 453 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 460 insertions(+) create mode 100644 examples/samplesshd-tty.c diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index fecfe340..28862b22 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -111,6 +111,7 @@ if (UNIX) endif (HAVE_LIBRT) endif (NOT LINUX) + check_library_exists(util forkpty "" HAVE_LIBUTIL) check_function_exists(getaddrinfo HAVE_GETADDRINFO) check_function_exists(poll HAVE_POLL) check_function_exists(select HAVE_SELECT) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1ccd8f27..5513b758 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -35,6 +35,12 @@ if (LINUX) add_executable(samplesshd-kbdint samplesshd-kbdint.c) target_link_libraries(samplesshd-kbdint ${LIBSSH_SHARED_LIBRARY}) + + if (HAVE_LIBUTIL) + add_executable(samplesshd-tty samplesshd-tty.c) + target_link_libraries(samplesshd-tty ${LIBSSH_SHARED_LIBRARY} util) + endif (HAVE_LIBUTIL) + endif (WITH_SERVER) endif (LINUX) diff --git a/examples/samplesshd-tty.c b/examples/samplesshd-tty.c new file mode 100644 index 00000000..c70f6bdc --- /dev/null +++ b/examples/samplesshd-tty.c @@ -0,0 +1,453 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2011 Aris Adamantiadis + +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. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include +#include +#include + +#define SSHD_USER "libssh" +#define SSHD_PASSWORD "libssh" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#ifdef WITH_PCAP +const char *pcap_file="debug.server.pcap"; +ssh_pcap_file pcap; + +static void set_pcap(ssh_session session){ + if(!pcap_file) + return; + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap=NULL; + return; + } + ssh_set_pcap_file(session,pcap); +} + +static void cleanup_pcap(){ + ssh_pcap_file_free(pcap); + pcap=NULL; +} +#endif + + +static int auth_password(char *user, char *password){ + if(strcmp(user, SSHD_USER)) + return 0; + if(strcmp(password, SSHD_PASSWORD)) + return 0; + return 1; // authenticated +} +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " + SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +static int port = 22; + +/* 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, 0, 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); + port = atoi(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 */ + +static int authenticate(ssh_session session) { + ssh_message message; + + do { + message=ssh_message_get(session); + if(!message) + break; + switch(ssh_message_type(message)){ + case SSH_REQUEST_AUTH: + switch(ssh_message_subtype(message)){ + case SSH_AUTH_METHOD_PASSWORD: + printf("User %s wants to auth with pass %s\n", + ssh_message_auth_user(message), + ssh_message_auth_password(message)); + if(auth_password(ssh_message_auth_user(message), + ssh_message_auth_password(message))){ + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + // not authenticated, send default message + ssh_message_reply_default(message); + break; + + case SSH_AUTH_METHOD_NONE: + default: + printf("User %s wants to auth with unknown auth %d\n", + ssh_message_auth_user(message), + ssh_message_subtype(message)); + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + break; + } + break; + default: + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + ssh_message_free(message); + } while (1); + return 0; +} + +static int copy_fd_to_chan(socket_t fd, int revents, void *userdata) { + ssh_channel chan = (ssh_channel)userdata; + char buf[2048]; + int sz = 0; + + if(!chan) { + close(fd); + return -1; + } + if(revents & POLLIN) { + sz = read(fd, buf, 2048); + if(sz > 0) { + ssh_channel_write(chan, buf, sz); + } + } + if(revents & POLLHUP) { + ssh_channel_close(chan); + sz = -1; + } + return sz; +} + +static int copy_chan_to_fd(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) { + int fd = *(int*)userdata; + int sz; + (void)session; + (void)channel; + (void)is_stderr; + + sz = write(fd, data, len); + return sz; +} + +static void chan_close(ssh_session session, ssh_channel channel, void *userdata) { + int fd = *(int*)userdata; + (void)session; + (void)channel; + + close(fd); +} + +struct ssh_channel_callbacks_struct cb = { + .channel_data_function = copy_chan_to_fd, + .channel_eof_function = chan_close, + .channel_close_function = chan_close, + .userdata = NULL +}; + +static int main_loop(ssh_channel chan) { + ssh_session session = ssh_channel_get_session(chan); + socket_t fd; + struct termios *term = NULL; + struct winsize *win = NULL; + pid_t childpid; + ssh_event event; + short events; + + + childpid = forkpty(&fd, NULL, term, win); + if(childpid == 0) { + execl("/bin/bash", "/bin/bash", (char *)NULL); + abort(); + } + + cb.userdata = &fd; + ssh_callbacks_init(&cb); + ssh_set_channel_callbacks(chan, &cb); + + events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; + + event = ssh_event_new(); + if(event == NULL) { + printf("Couldn't get a event\n"); + return -1; + } + if(ssh_event_add_fd(event, fd, events, copy_fd_to_chan, chan) != SSH_OK) { + printf("Couldn't add an fd to the event\n"); + return -1; + } + if(ssh_event_add_session(event, session) != SSH_OK) { + printf("Couldn't add the session to the event\n"); + return -1; + } + + do { + ssh_event_dopoll(event, 1000); + } while(!ssh_channel_is_closed(chan)); + + ssh_event_remove_fd(event, fd); + + ssh_event_remove_session(event, session); + + ssh_event_free(event); + return 0; +} + + +int main(int argc, char **argv){ + ssh_session session; + ssh_bind sshbind; + ssh_message message; + ssh_channel chan=0; + int auth=0; + int shell=0; + int r; + + sshbind=ssh_bind_new(); + session=ssh_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 + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif +#ifdef WITH_PCAP + set_pcap(session); +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); + return 1; + } + printf("Started sample libssh sshd on port %d\n", port); + printf("You can login as the user %s with the password %s\n", SSHD_USER, + SSHD_PASSWORD); + r = ssh_bind_accept(sshbind, session); + if(r==SSH_ERROR){ + printf("Error accepting a connection: %s\n", ssh_get_error(sshbind)); + return 1; + } + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + + /* proceed to authentication */ + auth = authenticate(session); + if(!auth){ + printf("Authentication error: %s\n", ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + + + /* wait for a channel session */ + do { + message = ssh_message_get(session); + if(message){ + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(message) == SSH_CHANNEL_SESSION) { + chan = ssh_message_channel_request_open_reply_accept(message); + ssh_message_free(message); + break; + } else { + ssh_message_reply_default(message); + ssh_message_free(message); + } + } else { + break; + } + } while(!chan); + + if(!chan) { + printf("Error: cleint did not ask for a channel session (%s)\n", + ssh_get_error(session)); + ssh_finalize(); + return 1; + } + + + /* wait for a shell */ + do { + message = ssh_message_get(session); + if(message != NULL) { + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL) { + if(ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) { + shell = 1; + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + break; + } else if(ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_PTY) { + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + continue; + } + } + ssh_message_reply_default(message); + ssh_message_free(message); + } else { + break; + } + } while(!shell); + + if(!shell) { + printf("Error: No shell requested (%s)\n", ssh_get_error(session)); + return 1; + } + + printf("it works !\n"); + + main_loop(chan); + + ssh_disconnect(session); + ssh_bind_free(sshbind); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + ssh_finalize(); + return 0; +} +