aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormilo <milo@r0ot.me>2011-04-20 04:24:11 +0200
committermilo <milo@r0ot.me>2011-04-20 14:10:59 +0200
commitb81f4546c25039b284688d0f54859f101890e7dc (patch)
tree3bd2a0856b7ae407e777559fbd537e0a9964a1d8
parent56394917b15e41603c641c22a4e29c33b096d673 (diff)
downloadlibssh-sshd.tar.gz
libssh-sshd.tar.xz
libssh-sshd.zip
Started to make a full sshd serversshd
-rw-r--r--examples/CMakeLists.txt3
-rw-r--r--examples/samplesshd-full.c682
2 files changed, 685 insertions, 0 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 5513b75..36ac047 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -39,6 +39,9 @@ if (LINUX)
if (HAVE_LIBUTIL)
add_executable(samplesshd-tty samplesshd-tty.c)
target_link_libraries(samplesshd-tty ${LIBSSH_SHARED_LIBRARY} util)
+
+ add_executable(samplesshd-full samplesshd-full.c)
+ target_link_libraries(samplesshd-full ${LIBSSH_SHARED_LIBRARY} 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..ee8fa39
--- /dev/null
+++ b/examples/samplesshd-full.c
@@ -0,0 +1,682 @@
+/* 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 <signal.h>
+#include "config.h"
+
+#include <libssh/libssh.h>
+#include <libssh/server.h>
+#include <libssh/callbacks.h>
+
+#ifdef HAVE_ARGP_H
+#include <argp.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <poll.h>
+#include <pty.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define SSHD_USER "libssh"
+#define SSHD_PASSWORD "libssh"
+
+#ifndef KEYS_FOLDER
+#ifdef _WIN32
+#define KEYS_FOLDER
+#else
+#define KEYS_FOLDER "/etc/ssh/"
+#endif
+#endif
+
+struct sshd_struct {
+ char **argv;
+ int bind_fd;
+ int session_fd;
+ ssh_session session;
+ ssh_event event;
+ ssh_channel channel;
+ enum {
+ ct_unknown=0,
+ ct_shell,
+ ct_exec
+ } channel_type;
+ int verbose;
+ int childpid;
+} sshd = {
+ NULL,
+ 0,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ ct_unknown,
+ 0,
+ 0
+};
+
+static void reexec(void) {
+ /* this block to avoid piling up fds at re-exec */
+ printf("reexec %s\n", sshd.argv[0]);
+ {
+ int i, j[2];
+ if(pipe(j) < 0) {
+ exit(-1);
+ }
+ for(i = 3; i <= 100; i++) {
+ close(i);
+ }
+ }
+ execv(sshd.argv[0], sshd.argv);
+}
+
+#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(const char *user, const char *password){
+ return 1;
+ 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 = "<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";
+
+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 */
+
+#define BUFSIZE (8*1024)
+#define MAX_TRANSMIT (BUFSIZE * 4)
+static int fd_data(socket_t fd, int revents, void *userdata) {
+ ssh_channel chan = (ssh_channel)userdata;
+ char buf[BUFSIZE];
+ int sz = 0;
+ int bytes = 0;
+ int status;
+ static pid_t p = 0;
+
+ printf("fd received event 0x%x\n", revents);
+ if(p) {
+ return -1;
+ }
+ if(!chan) {
+ printf("chan is NULL\n");
+ close(fd);
+ return -1;
+ }
+ if(revents & POLLIN) {
+ int ws;
+
+ do {
+ ws = ssh_channel_window_size(chan);
+ ws = ws < BUFSIZE ? ws : BUFSIZE;
+ if(ws && (bytes = read(fd, buf, ws)) > 0) {
+ sz += bytes;
+ ssh_channel_write(chan, buf, bytes);
+ }
+ printf("received %d bytes on fd\n", bytes);
+ } while(ws > 0 && bytes == BUFSIZE); // && sz < MAX_TRANSMIT)
+ printf("received total %d bytes on fd\n", sz);
+ }
+ if(revents & (POLLHUP | POLLERR | POLLNVAL)) {
+ if((p = waitpid(sshd.childpid, &status, WNOHANG)) > 0) {
+ ssh_channel_request_send_exit_status(chan, WEXITSTATUS(status));
+ } else {
+ printf("wait: error\n");
+ }
+ printf("fd has been closed %d %x\n", status, status);
+ ssh_channel_send_eof(chan);
+ printf("Sent EOF on chan\n");
+ ssh_channel_close(chan);
+ printf("Closed chan\n");
+ sz = -1;
+ ssh_event_remove_fd(sshd.event, fd);
+ }
+ return sz;
+}
+
+static int chan_data(ssh_session session,
+ ssh_channel channel,
+ void *data,
+ uint32_t len,
+ int is_stderr,
+ void *userdata) {
+ int fd = (long)userdata;
+ int sz;
+ (void)session;
+ (void)channel;
+ (void)is_stderr;
+
+ printf("received %d bytes on chan\n", len);
+ if(!len) {
+ return 0;
+ }
+ sz = write(fd, data, len);
+ printf("received %d from write(fd=%d)\n", sz, fd);
+ return sz;
+}
+
+static void chan_eof(ssh_session session, ssh_channel channel, void *userdata) {
+ int fd = (long)userdata;
+ (void)session;
+ (void)channel;
+
+ printf("chan received eof\n");
+ shutdown(fd, SHUT_WR);
+}
+
+static void chan_close(ssh_session session, ssh_channel channel,
+ void *userdata) {
+ int fd = (long)userdata;
+ (void)session;
+ (void)channel;
+
+ printf("chan has been closed\n");
+ close(fd);
+}
+
+static void chan_exit(ssh_session session, ssh_channel channel, int exitcode,
+ void *userdata) {
+ int fd = (long)userdata;
+ (void)session;
+ (void)channel;
+ (void)fd;
+
+ printf("chan exit %d\n", exitcode);
+}
+
+static void chan_signal(ssh_session session, ssh_channel channel,
+ const char*sig, void *userdata) {
+ int fd = (long)userdata;
+ (void)session;
+ (void)channel;
+ (void)fd;
+
+ printf("chan signal %s\n", sig);
+}
+
+static void chan_exit_signal(ssh_session session, ssh_channel channel,
+ const char *sig,
+ int core,
+ const char *errmsg,
+ const char *lang, void *userdata) {
+ int fd = (long)userdata;
+ (void)session;
+ (void)channel;
+ (void)fd;
+ (void)core;
+ (void)lang;
+
+ printf("chan exit_signal %s %s%s\n", sig, core?"(core dumped)":"", errmsg);
+}
+
+struct ssh_channel_callbacks_struct cb = {
+ .channel_data_function = chan_data,
+ .channel_eof_function = chan_eof,
+ .channel_close_function = chan_close,
+ .channel_exit_status_function = chan_exit,
+ .channel_signal_function = chan_signal,
+ .channel_exit_signal_function = chan_exit_signal,
+ .userdata = NULL
+};
+
+static int do_shell(ssh_event event, ssh_channel chan) {
+ socket_t fd;
+ struct termios *term = NULL;
+ struct winsize *win = NULL;
+ short events;
+ int fd_status;
+
+
+ sshd.childpid = forkpty(&fd, NULL, term, win);
+ if(sshd.childpid == 0) {
+ close(sshd.bind_fd);
+ close(sshd.session_fd);
+
+ execl("/bin/bash", "/bin/bash", (char *)NULL);
+ abort();
+ } else if(sshd.childpid < 0) {
+ printf("Couldn't start shell subprocess\n");
+ return -1;
+ }
+
+ fd_status = fcntl(fd, F_GETFL, 0);
+ if(fcntl(fd, F_SETFL, fd_status | O_NONBLOCK) < 0) {
+ printf("Couldn't set non-blocking mode on fd\n");
+ return -1;
+ }
+
+ cb.userdata = (void *)(long)fd;
+ ssh_callbacks_init(&cb);
+ ssh_set_channel_callbacks(chan, &cb);
+
+ events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL;
+
+ if(ssh_event_add_fd(event, fd, events, fd_data, chan) != SSH_OK) {
+ printf("Couldn't add an fd to the event\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int fork_exec(const char *cmd) {
+ int spair[2];
+ int fd_status;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, spair) < 0) {
+ return -1;
+ }
+
+ sshd.childpid = fork();
+
+ if(sshd.childpid == 0) {
+ close(sshd.bind_fd);
+ close(sshd.session_fd);
+
+ close(0);
+ close(1);
+ close(2);
+ close(spair[1]);
+ dup2(spair[0], 0);
+ dup2(spair[0], 1);
+ dup2(spair[0], 2);
+ close(spair[0]);
+ execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
+ exit(0);
+ } else if(sshd.childpid < 0) {
+ return -1;
+ }
+
+ close(spair[0]);
+
+ fd_status = fcntl(spair[1], F_GETFL, 0);
+ if(fcntl(spair[1], F_SETFL, fd_status | O_NONBLOCK) < 0) {
+ printf("Couldn't set non-blocking mode on fd\n");
+ return -1;
+ }
+ return spair[1];
+}
+
+static int do_exec(ssh_event event, ssh_channel chan, const char *cmd) {
+ socket_t fd;
+ short events;
+
+
+ printf("execing cmd %s\n", cmd);
+ fd = fork_exec(cmd);
+ if(fd < 0) {
+ printf("Couldn't start cmd subprocess\n");
+ return -1;
+ }
+
+ printf("execed cmd %s\n", cmd);
+ cb.userdata = (void *)(long)fd;
+ ssh_callbacks_init(&cb);
+ ssh_set_channel_callbacks(chan, &cb);
+
+ events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL;
+
+ if(ssh_event_add_fd(event, fd, events, fd_data, chan) != SSH_OK) {
+ printf("Couldn't add an fd to the event\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int channel_request_callback(ssh_session session, ssh_message message, void *data) {
+ const char *cmd;
+ (void)data;
+ (void)session;
+
+ /* wait for a shell */
+ switch(ssh_message_type(message)) {
+ case SSH_REQUEST_CHANNEL:
+ switch(ssh_message_subtype(message)) {
+ case SSH_CHANNEL_REQUEST_SHELL:
+ printf("Message subtype shell\n");
+ sshd.channel_type = ct_shell;
+ if(do_shell(sshd.event, sshd.channel) < 0) {
+ goto deny;
+ }
+ goto accept_end;
+
+ case SSH_CHANNEL_REQUEST_EXEC:
+ printf("Message subtype exec\n");
+ sshd.channel_type = ct_exec;
+ cmd = ssh_message_channel_request_command(message);
+ if(do_exec(sshd.event, sshd.channel, cmd) < 0) {
+ goto deny;
+ }
+ goto accept_end;
+
+ case SSH_CHANNEL_REQUEST_PTY:
+ printf("Message subtype pty\n");
+ goto accept;
+
+ default:
+ printf("Message subtype unknown\n");
+ goto deny;
+ }
+ default:
+ printf("Message type unknown\n");
+ goto deny;
+ }
+
+deny:
+ printf("channel_request denied\n");
+ return 1;
+
+accept_end:
+ printf("Ending channel_request callback: ");
+ //ssh_set_message_callback(sshd.session, NULL, NULL);
+accept:
+ printf("channel_request accepted\n");
+ ssh_message_channel_request_reply_success(message);
+ return 0;
+}
+
+static int channel_open_callback(ssh_session session, ssh_message message, void *data) {
+ ssh_channel *channel = data;
+ (void)session;
+
+ /* wait for a channel session */
+ switch(ssh_message_type(message)) {
+ case SSH_REQUEST_CHANNEL_OPEN:
+ switch(ssh_message_subtype(message)) {
+ case SSH_CHANNEL_SESSION:
+ goto accept;
+ default:
+ goto deny;
+ }
+ default:
+ goto deny;
+ }
+deny:
+ return 1;
+
+accept:
+ ssh_set_message_callback(sshd.session, channel_request_callback, NULL);
+ *channel = ssh_message_channel_request_open_reply_accept(message);
+ return 0;
+}
+
+static int authenticate_callback(ssh_session session, ssh_message message, void *data) {
+ (void)session;
+ (void)data;
+
+ printf("Got a message %d:%d\n", ssh_message_type(message), ssh_message_subtype(message));
+ switch(ssh_message_type(message)){
+ case SSH_REQUEST_AUTH:
+ switch(ssh_message_subtype(message)){
+ case SSH_AUTH_METHOD_PUBLICKEY:
+ /* allow anyone for the purpose of the test */
+ goto accept;
+
+ 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))){
+ goto accept;
+ }
+ ssh_message_auth_set_methods(message,
+ SSH_AUTH_METHOD_PUBLICKEY |
+ SSH_AUTH_METHOD_PASSWORD |
+ SSH_AUTH_METHOD_INTERACTIVE);
+ // not authenticated, send default message
+ goto deny;
+
+ 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_PUBLICKEY |
+ SSH_AUTH_METHOD_PASSWORD |
+ SSH_AUTH_METHOD_INTERACTIVE);
+ goto deny;
+ }
+ break;
+ default:
+ printf("Unknown auth request %d\n", ssh_message_type(message));
+ ssh_message_auth_set_methods(message,
+ SSH_AUTH_METHOD_PUBLICKEY |
+ SSH_AUTH_METHOD_PASSWORD |
+ SSH_AUTH_METHOD_INTERACTIVE);
+ }
+
+deny:
+ return 1;
+accept:
+ ssh_set_message_callback(sshd.session, channel_open_callback, &sshd.channel);
+ ssh_message_auth_reply_success(message,0);
+ return 0;
+}
+
+int main(int argc, char **argv){
+ ssh_bind sshbind;
+ int r;
+
+ sshd.argv = argv;
+ sshd.event = ssh_event_new();
+ if(sshd.event == NULL) {
+ printf("Couldn't get a event\n");
+ return -1;
+ }
+
+ sshbind = ssh_bind_new();
+ sshd.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(sshd.session);
+#endif
+
+ printf("\n\n#############################################################\n");
+ printf("\n\n#############################################################\n");
+
+ if(ssh_bind_listen(sshbind)<0){
+ printf("Error listening to socket: %s\n", ssh_get_error(sshbind));
+ return 1;
+ }
+ sshd.bind_fd = ssh_bind_get_fd(sshbind);
+ 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);
+
+ ssh_set_message_callback(sshd.session, authenticate_callback, NULL);
+
+ r = ssh_bind_accept(sshbind, sshd.session);
+ if(r==SSH_ERROR){
+ printf("Error accepting a connection: %s\n", ssh_get_error(sshbind));
+ return 1;
+ }
+
+ sshd.session_fd = ssh_get_fd(sshd.session);
+
+ if (ssh_handle_key_exchange(sshd.session)) {
+ printf("ssh_handle_key_exchange: %s\n", ssh_get_error(sshd.session));
+ return 1;
+ }
+
+ if(ssh_event_add_session(sshd.event, sshd.session) != SSH_OK) {
+ printf("Couldn't add the session to the event\n");
+ return -1;
+ }
+
+ printf("it works !\n");
+
+ do {
+ printf("dopoll\n");
+ ssh_event_dopoll(sshd.event, 10000);
+ } while(ssh_is_connected(sshd.session));
+
+ ssh_event_remove_session(sshd.event, sshd.session);
+
+ ssh_event_free(sshd.event);
+
+ ssh_disconnect(sshd.session);
+ ssh_bind_free(sshbind);
+#ifdef WITH_PCAP
+ cleanup_pcap();
+#endif
+ ssh_finalize();
+
+ reexec();
+ return 0;
+}
+