aboutsummaryrefslogtreecommitdiff
path: root/src/session.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/session.c')
-rw-r--r--src/session.c271
1 files changed, 271 insertions, 0 deletions
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 <libssh/callbacks.h>
+#include <libssh/server.h>
+#include <poll.h>
+#include <pty.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#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);
+ }
+}