aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Dessaux2018-05-22 17:31:56 +0200
committerJulien Dessaux2018-05-22 17:31:56 +0200
commite840c061817a797fae31a14796a790b0c979e43f (patch)
treea64752420ae1b7a91e43e4d4314b5df9f8609313
downloadbastion-e840c061817a797fae31a14796a790b0c979e43f.tar.gz
bastion-e840c061817a797fae31a14796a790b0c979e43f.tar.bz2
bastion-e840c061817a797fae31a14796a790b0c979e43f.zip
Initial import with working simple server based on libssh examples
-rw-r--r--.gitignore6
-rw-r--r--GNUmakefile26
-rw-r--r--src/main.c103
-rw-r--r--src/pty.c128
-rw-r--r--src/pty.h15
-rw-r--r--src/session.c271
-rw-r--r--src/session.h10
-rw-r--r--src/util.h32
8 files changed, 591 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fa0ede0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+bastion
+libssh/
+ssh_host_*_key
+test_client.c
+*.[do]
+*.sw[a-p]
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..f6e0f7c
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,26 @@
+CC=clang
+DEBUG=-g
+CFLAGS= ${DEBUG} -Wall -Werror -Wextra
+
+sources=$(wildcard src/*.c)
+OBJ=$(sources:.c=.o)
+
+all: bastion
+
+bastion: $(OBJ)
+ $(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lpthread -lssh_threads -lutil
+
+clean:
+ $(RM) bastion *.[do] src/*.[do]
+
+%.o: %.c
+ $(CC) -c $(CFLAGS) $*.c -o $*.o
+ $(CC) -MM $(CFLAGS) $*.c > $*.d
+ @mv -f $*.d $*.d.tmp
+ @sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d
+ @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $*.d
+ @rm -f $*.d.tmp
+
+valgrind:
+ valgrind --leak-check=full --show-leak-kinds=all --suppressions=$HOME/.valgrind_suppressions ./bastion
+ #valgrind -v --leak-check=full --show-leak-kinds=all --suppressions=$HOME/.valgrind_suppressions --gen-suppressions=yes ./bastion
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..cd8e74f
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,103 @@
+#include <libssh/callbacks.h>
+#include <libssh/server.h>
+//#include <fcntl.h>
+//#include <libutil.h>
+//#include <poll.h>
+//#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+//#include <sys/ioctl.h>
+#include <sys/wait.h>
+//#include <util.h>
+
+#include "session.h"
+
+/* SIGCHLD handler for cleaning up dead children. */
+static void sigchld_handler(int signo) {
+ (void) signo;
+ while (waitpid(-1, NULL, WNOHANG) > 0);
+}
+
+int main()
+{
+ // Set up SIGCHLD handler
+ struct sigaction sa;
+ sa.sa_handler = sigchld_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
+ if (sigaction(SIGCHLD, &sa, NULL) != 0) {
+ fprintf(stderr, "Failed to register SIGCHLD handler\n");
+ return 1;
+ }
+
+ // Initializing ssh context
+ ssh_threads_set_callbacks(ssh_threads_get_pthread());
+ ssh_init();
+
+ // Initializing ssh_bind
+ ssh_bind sshbind = ssh_bind_new();
+ if (sshbind == NULL) {
+ fprintf(stderr, "Error initializing ssh_bind\n");
+ exit(-1);
+ }
+ int port = 2222;
+ ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT, &port);
+ ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, "ssh_host_dsa_key");
+ ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, "ssh_host_rsa_key");
+ ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, "ssh_host_ecdsa_key");
+
+ if (ssh_bind_listen(sshbind) < 0) {
+ printf("Error listening to socket: %s\n", ssh_get_error(sshbind));
+ return 1;
+ }
+
+ while (1) {
+ ssh_session session = ssh_new();
+ if (session == NULL) {
+ fprintf(stderr, "Error initializing ssh_session\n");
+ break;
+ }
+ int verbosity = SSH_LOG_PROTOCOL;
+ ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
+
+ // Blocks until there is a new incoming connection
+ if (ssh_bind_accept(sshbind,session) == SSH_OK){
+ switch(fork()) {
+ case 0:
+ /* Remove the SIGCHLD handler inherited from parent. */
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGCHLD, &sa, NULL);
+ /* Remove socket binding, which allows us to restart the
+ * parent process, without terminating existing sessions. */
+ ssh_bind_free(sshbind);
+
+ ssh_event event = ssh_event_new();
+ if (event != NULL) {
+ /* Blocks until the SSH session ends by either
+ * child process exiting, or client disconnecting. */
+ handle_session(event, session);
+ ssh_event_free(event);
+ } else {
+ fprintf(stderr, "Could not create polling context\n");
+ }
+ ssh_disconnect(session);
+ ssh_free(session);
+
+ exit(0);
+ case -1:
+ fprintf(stderr, "Failed to fork\n");
+ }
+ } else {
+ fprintf(stderr, "Error accepting a connection : %s\n", ssh_get_error(sshbind));
+ exit(1);
+ }
+ /* Since the session has been passed to a child fork, do some cleaning
+ * up at the parent process. */
+ ssh_disconnect(session);
+ ssh_free(session);
+ }
+
+ ssh_bind_free(sshbind);
+ ssh_finalize();
+ return 0;
+}
diff --git a/src/pty.c b/src/pty.c
new file mode 100644
index 0000000..c888df3
--- /dev/null
+++ b/src/pty.c
@@ -0,0 +1,128 @@
+#include <pty.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <utmp.h>
+
+#include "pty.h"
+
+// callback function for SSH channel PTY request from a client
+int pty_request(ssh_session session, ssh_channel channel,
+ const char *term, int cols, int rows, int py, int px,
+ 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;
+
+ if (openpty(&cdata->pty_master, &cdata->pty_slave, NULL, NULL,
+ cdata->winsize) != 0) {
+ fprintf(stderr, "Failed to open pty\n");
+ return SSH_ERROR;
+ }
+ return SSH_OK;
+}
+
+// callback function for SSH channel PTY resize from a client
+int pty_resize(ssh_session session, ssh_channel channel, int cols,
+ int rows, int py, int px, void *userdata) {
+ struct channel_data_struct *cdata = (struct channel_data_struct *)userdata;
+
+ (void) session;
+ (void) channel;
+
+ cdata->winsize->ws_row = rows;
+ cdata->winsize->ws_col = cols;
+ cdata->winsize->ws_xpixel = px;
+ cdata->winsize->ws_ypixel = py;
+
+ if (cdata->pty_master != -1) {
+ return ioctl(cdata->pty_master, TIOCSWINSZ, cdata->winsize);
+ }
+
+ return SSH_ERROR;
+}
+
+int exec_pty(const char *mode, const char *command,
+ struct channel_data_struct *cdata) {
+ switch(cdata->pid = fork()) {
+ case -1:
+ close(cdata->pty_master);
+ close(cdata->pty_slave);
+ fprintf(stderr, "Failed to fork\n");
+ return SSH_ERROR;
+ case 0:
+ close(cdata->pty_master);
+ if (login_tty(cdata->pty_slave) != 0) {
+ exit(1);
+ }
+ execl("/bin/sh", "sh", mode, command, NULL);
+ exit(0);
+ default:
+ close(cdata->pty_slave);
+ /* pty fd is bi-directional */
+ cdata->child_stdout = cdata->child_stdin = cdata->pty_master;
+ }
+ return SSH_OK;
+}
+
+int exec_nopty(const char *command, struct channel_data_struct *cdata) {
+ int in[2], out[2], err[2];
+
+ /* Do the plumbing to be able to talk with the child process. */
+ 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:
+ /* Finish the plumbing in the child process. */
+ close(in[1]);
+ close(out[0]);
+ close(err[0]);
+ dup2(in[0], STDIN_FILENO);
+ dup2(out[1], STDOUT_FILENO);
+ dup2(err[1], STDERR_FILENO);
+ close(in[0]);
+ close(out[1]);
+ close(err[1]);
+ /* exec the requested command. */
+ execl("/bin/sh", "sh", "-c", command, NULL);
+ exit(0);
+ }
+
+ close(in[0]);
+ close(out[1]);
+ close(err[1]);
+
+ cdata->child_stdin = in[1];
+ cdata->child_stdout = out[0];
+ cdata->child_stderr = err[0];
+
+ 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;
+}
diff --git a/src/pty.h b/src/pty.h
new file mode 100644
index 0000000..c130f4a
--- /dev/null
+++ b/src/pty.h
@@ -0,0 +1,15 @@
+#ifndef _PTY_H_
+#define _PTY_H_
+
+#include "util.h"
+
+int pty_request(ssh_session session, ssh_channel channel,
+ const char *term, int cols, int rows, int py, int px,
+ void *userdata);
+int pty_resize(ssh_session session, ssh_channel channel, int cols,
+ int rows, int py, int px, void *userdata);
+int exec_pty(const char *mode, const char *command,
+ struct channel_data_struct *cdata);
+int exec_nopty(const char *command, struct channel_data_struct *cdata);
+
+#endif
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);
+ }
+}
diff --git a/src/session.h b/src/session.h
new file mode 100644
index 0000000..2a4ea8d
--- /dev/null
+++ b/src/session.h
@@ -0,0 +1,10 @@
+#ifndef _SESSION_H_
+#define _SESSION_H_
+
+#include <libssh/libssh.h>
+
+#include "util.h"
+
+void handle_session(ssh_event event, ssh_session session);
+
+#endif
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..48a83ff
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,32 @@
+#ifndef _UTIL_H_
+#define _UTIL_H_
+
+#include <libssh/libssh.h>
+
+/* A userdata struct for channel. */
+struct channel_data_struct {
+ /* pid of the child process the channel will spawn. */
+ pid_t pid;
+ /* For PTY allocation */
+ socket_t pty_master;
+ socket_t pty_slave;
+ /* For communication with the child process. */
+ socket_t child_stdin;
+ socket_t child_stdout;
+ /* Only used for subsystem and exec requests. */
+ socket_t child_stderr;
+ /* Event which is used to poll the above descriptors. */
+ ssh_event event;
+ /* Terminal size struct. */
+ struct winsize *winsize;
+};
+
+/* A userdata struct for session. */
+struct session_data_struct {
+ /* Pointer to the channel the session will allocate. */
+ ssh_channel channel;
+ int auth_attempts;
+ int authenticated;
+};
+
+#endif