From e840c061817a797fae31a14796a790b0c979e43f Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Tue, 22 May 2018 17:31:56 +0200 Subject: Initial import with working simple server based on libssh examples --- src/session.c | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 src/session.c (limited to 'src/session.c') diff --git a/src/session.c b/src/session.c new file mode 100644 index 0000000..6186e6c --- /dev/null +++ b/src/session.c @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pty.h" +#include "session.h" + +#define USER "julien" +#define PASS "graou" +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) +#define SFTP_SERVER_PATH "/usr/lib/sftp-server" + +// callback function for channel data and exceptions +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 == 0 || cdata->pid < 1 || kill(cdata->pid, 0) < 0) { + return 0; + } + + return write(cdata->child_stdin, (char *) data, len); +} + +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; + + + (void) session; + (void) channel; + + if(cdata->pid > 0) { + return SSH_ERROR; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + return exec_pty("-c", command, cdata); + } + return exec_nopty(command, cdata); +} + +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; + + if(cdata->pid > 0) { + return SSH_ERROR; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + return exec_pty("-l", NULL, cdata); + } + /* Client requested a shell without a pty, let's pretend we allow that */ + return SSH_OK; +} + +static int subsystem_request(ssh_session session, ssh_channel channel, + const char *subsystem, void *userdata) { + /* subsystem requests behave simillarly to exec requests. */ + if (strcmp(subsystem, "sftp") == 0) { + return exec_request(session, channel, SFTP_SERVER_PATH, userdata); + } + return SSH_ERROR; +} + +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) == 0 && strcmp(pass, PASS) == 0) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + + sdata->auth_attempts++; + return SSH_AUTH_DENIED; +} + +static ssh_channel channel_open(ssh_session session, void *userdata) { + struct session_data_struct *sdata = (struct session_data_struct *) userdata; + + sdata->channel = ssh_channel_new(session); + return sdata->channel; +} + +static int process_stdout(socket_t fd, int revents, void *userdata) { + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + // TODO implement logging here + } + } + + return n; +} + +static int process_stderr(socket_t fd, int revents, void *userdata) { + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +void handle_session(ssh_event event, ssh_session session) { + int n, rc; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_struct cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + 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 = channel_open, + }; + + 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); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= 3 || n >= 100) { + return; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + n++; + } + + ssh_set_channel_callbacks(sdata.channel, &channel_cb); + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, -1) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while(ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* 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); + } +} -- cgit v1.2.3