Archived
1
0
Fork 0

Made a working ssh proxy server

This commit is contained in:
Julien Dessaux 2018-06-20 13:23:48 +02:00
parent e840c06181
commit 8c04f0d56d
12 changed files with 573 additions and 410 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
bastion bastion
libssh/ libssh/
id_rsa
ssh_host_*_key ssh_host_*_key
test_client.c test_client.c
*.[do] *.[do]

View file

@ -1,6 +1,6 @@
CC=clang CC=clang
DEBUG=-g DEBUG=-g
CFLAGS= ${DEBUG} -Wall -Werror -Wextra CFLAGS= ${DEBUG} -Wall -Werror -Wextra -Weverything -Wno-missing-prototypes -Wno-disabled-macro-expansion
sources=$(wildcard src/*.c) sources=$(wildcard src/*.c)
OBJ=$(sources:.c=.o) OBJ=$(sources:.c=.o)
@ -8,7 +8,8 @@ OBJ=$(sources:.c=.o)
all: bastion all: bastion
bastion: $(OBJ) bastion: $(OBJ)
$(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lpthread -lssh_threads -lutil #$(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil -lpthread -lssh_threads
$(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil
clean: clean:
$(RM) bastion *.[do] src/*.[do] $(RM) bastion *.[do] src/*.[do]
@ -22,5 +23,5 @@ clean:
@rm -f $*.d.tmp @rm -f $*.d.tmp
valgrind: valgrind:
valgrind --leak-check=full --show-leak-kinds=all --suppressions=$HOME/.valgrind_suppressions ./bastion valgrind --leak-check=full --show-leak-kinds=all --trace-children=yes --suppressions=${HOME}/.valgrind_suppressions ./bastion
#valgrind -v --leak-check=full --show-leak-kinds=all --suppressions=$HOME/.valgrind_suppressions --gen-suppressions=yes ./bastion #valgrind -v --leak-check=full --show-leak-kinds=all --trace-children=yes --suppressions=${HOME}/.valgrind_suppressions --gen-suppressions=yes ./bastion

187
src/client.c Normal file
View file

@ -0,0 +1,187 @@
#include <libssh/callbacks.h>
#include <stdio.h>
#include <stdlib.h>
#include "../config.h"
#include "client.h"
// callback function for channel data and exceptions
static int client_data_function(ssh_session session, ssh_channel channel, void *data,
uint32_t len, int is_stderr, void *userdata) {
struct client_channel_data_struct *cdata = (struct client_channel_data_struct *) userdata;
(void) session;
(void) channel;
(void) is_stderr;
if (ssh_channel_is_open(cdata->proxy_channel))
return ssh_channel_write(cdata->proxy_channel, (char*) data, len);
else
return SSH_ERROR;
}
static void client_channel_eof_callback (ssh_session session, ssh_channel channel, void *userdata)
{
struct client_channel_data_struct *cdata = (struct client_channel_data_struct *) userdata;
(void) session;
(void) channel;
if (ssh_channel_is_open(cdata->proxy_channel))
ssh_channel_send_eof(cdata->proxy_channel);
}
static void client_channel_close_callback (ssh_session session, ssh_channel channel, void *userdata)
{
struct client_channel_data_struct *cdata = (struct client_channel_data_struct *) userdata;
(void) session;
(void) channel;
if (ssh_channel_is_open(cdata->proxy_channel))
ssh_channel_close(cdata->proxy_channel);
}
static void client_channel_exit_status_callback (ssh_session session, ssh_channel channel, int exit_status, void *userdata)
{
(void) session;
(void) channel;
(void) userdata;
printf("client exit status callback %d\n", exit_status);
}
static void client_channel_signal_callback (ssh_session session, ssh_channel channel,
const char *signal, void *userdata) {
(void) session;
(void) channel;
(void) signal;
(void) userdata;
printf("client signal callback\n");
}
static void client_channel_exit_signal_callback (ssh_session session, ssh_channel channel,
const char *signal, int core, const char *errmsg,
const char *lang, void *userdata) {
(void) session;
(void) channel;
(void) signal;
(void) core;
(void) errmsg;
(void) lang;
(void) userdata;
printf("client exit signal callback\n");
}
struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata, const char * hostname)
{
struct client_channel_data_struct *cdata = malloc(sizeof(*cdata));
cdata->event = event;
cdata->my_session = NULL;
cdata->my_channel = NULL;
cdata->proxy_channel = pdata->my_channel;
cdata->client_channel_cb = NULL;
/* First we try to add the private key that the server will accept */
ssh_key privkey;
if (ssh_pki_import_privkey_file(PRIVKEY_PATH, NULL, NULL, NULL, &privkey) != SSH_OK) {
printf("Error importing private key");
goto privkey_clean;
}
/* We try to connect to the remote server */
printf("Connecting to %s\n", hostname);
cdata->my_session = ssh_new();
ssh_options_set(cdata->my_session, SSH_OPTIONS_HOST, hostname);
ssh_options_set(cdata->my_session, SSH_OPTIONS_USER, USER_TO_LOGIN_AS);
int verbosity = SSH_LOG_PROTOCOL;
ssh_options_set(cdata->my_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
if (ssh_connect(cdata->my_session) != SSH_OK) {
printf("Error connecting to %s: %s\n", hostname, ssh_get_error(cdata->my_session));
goto session_clean;
}
/* We now validate the remote server's public key */
ssh_key server_pub_key;
unsigned char * hash = NULL;
size_t hlen;
char * hexa = NULL;
if (ssh_get_server_publickey(cdata->my_session, &server_pub_key) != SSH_OK) {
fprintf(stderr, "Error getting server publickey: %s\n", ssh_get_error(cdata->my_session));
goto pubkey_clean;
}
if (ssh_get_publickey_hash(server_pub_key, SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen) != SSH_OK) {
fprintf(stderr, "Error getting publickey hash: %s\n", ssh_get_error(cdata->my_session));
goto pubkey_hash_clean;
}
hexa = ssh_get_hexa(hash, hlen);
printf("Server public key hash : %s\n", hexa); // TODO validate the key
// if error goto pubkey_nomatch_clean
/* With the server checked, we can authenticate */
if(ssh_userauth_publickey(cdata->my_session, NULL, privkey) == SSH_AUTH_SUCCESS){
printf("Authentication success\n");
} else {
printf("Error private key was rejected\n");
goto auth_clean;
}
/* we open the client channel */
cdata->my_channel = ssh_channel_new(cdata->my_session);
if (cdata->my_channel == NULL) {
printf("Couldn't open client channel to %s\n", hostname);
goto channel_clean;
}
/* we open a session channel for the future shell, not suitable for tcp
* forwarding */
if (ssh_channel_open_session(cdata->my_channel) != SSH_OK) {
printf("Couldn't open the session channel\n");
goto channel_clean;
}
cdata->client_channel_cb = malloc(sizeof(*cdata->client_channel_cb));
cdata->client_channel_cb->userdata = cdata;
cdata->client_channel_cb->channel_data_function = client_data_function;
cdata->client_channel_cb->channel_eof_function = client_channel_eof_callback;
cdata->client_channel_cb->channel_close_function = client_channel_close_callback;
cdata->client_channel_cb->channel_exit_status_function = client_channel_exit_status_callback;
cdata->client_channel_cb->channel_signal_function = client_channel_signal_callback;
cdata->client_channel_cb->channel_exit_signal_function = client_channel_exit_signal_callback;
ssh_callbacks_init(cdata->client_channel_cb);
ssh_set_channel_callbacks(cdata->my_channel, cdata->client_channel_cb);
ssh_event_add_session(event, cdata->my_session);
ssh_string_free_char(hexa);
ssh_clean_pubkey_hash(&hash);
ssh_key_free(server_pub_key);
ssh_key_free(privkey);
return cdata;
channel_clean:
ssh_channel_free(cdata->my_channel);
auth_clean:
// TODO
//pubkey_nomatch_clean:
ssh_string_free_char(hexa);
pubkey_hash_clean:
ssh_clean_pubkey_hash(&hash);
pubkey_clean:
ssh_key_free(server_pub_key);
session_clean:
ssh_disconnect(cdata->my_session);
ssh_free(cdata->my_session);
privkey_clean:
ssh_key_free(privkey);
free(cdata);
return NULL;
}
void client_cleanup(struct client_channel_data_struct *cdata)
{
ssh_event_remove_session(cdata->event, cdata->my_session);
ssh_channel_free(cdata->my_channel);
ssh_disconnect(cdata->my_session);
ssh_free(cdata->my_session);
free(cdata->client_channel_cb);
free(cdata);
}

22
src/client.h Normal file
View file

@ -0,0 +1,22 @@
#ifndef CLIENT_H_
#define CLIENT_H_
#include <libssh/libssh.h>
#include "proxy.h"
#include "session.h"
/* A userdata struct for channel. */
struct client_channel_data_struct {
/* Event which is used to poll */
ssh_event event;
ssh_session my_session;
ssh_channel my_channel;
ssh_channel proxy_channel;
struct ssh_channel_callbacks_struct * client_channel_cb;
};
struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata, const char * hostname);
void client_cleanup(struct client_channel_data_struct *cdata);
#endif

View file

@ -1,15 +1,10 @@
#include <libssh/callbacks.h> #include <libssh/callbacks.h>
#include <libssh/server.h> #include <libssh/server.h>
//#include <fcntl.h>
//#include <libutil.h>
//#include <poll.h>
//#include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
//#include <sys/ioctl.h>
#include <sys/wait.h> #include <sys/wait.h>
//#include <util.h>
#include "../config.h"
#include "session.h" #include "session.h"
/* SIGCHLD handler for cleaning up dead children. */ /* SIGCHLD handler for cleaning up dead children. */
@ -18,6 +13,19 @@ static void sigchld_handler(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0); while (waitpid(-1, NULL, WNOHANG) > 0);
} }
/* SIGINT handler for cleaning up on forced exit. */
static ssh_bind sshbind;
static ssh_session session;
__attribute__((noreturn)) void sigint_handler(int signo)
{
(void) signo;
ssh_free(session);
ssh_bind_free(sshbind);
ssh_finalize();
exit(0);
}
int main() int main()
{ {
// Set up SIGCHLD handler // Set up SIGCHLD handler
@ -29,30 +37,40 @@ int main()
fprintf(stderr, "Failed to register SIGCHLD handler\n"); fprintf(stderr, "Failed to register SIGCHLD handler\n");
return 1; return 1;
} }
// Set up SIGINT handler
struct sigaction sa2;
sa2.sa_handler = sigint_handler;
sigemptyset(&sa2.sa_mask);
sa2.sa_flags = 0;
if (sigaction(SIGINT, &sa2, NULL) != 0) {
fprintf(stderr, "Failed to register SIGINT handler\n");
return 1;
}
// Initializing ssh context // Initializing ssh context
ssh_threads_set_callbacks(ssh_threads_get_pthread());
ssh_init(); ssh_init();
// Initializing ssh_bind // Initializing ssh_bind
ssh_bind sshbind = ssh_bind_new(); sshbind = ssh_bind_new();
if (sshbind == NULL) { if (sshbind == NULL) {
fprintf(stderr, "Error initializing ssh_bind\n"); fprintf(stderr, "Error initializing ssh_bind\n");
exit(-1); exit(-1);
} }
int port = 2222; int port = 2222;
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT, &port); 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_DSAKEY, DSAKEY_PATH);
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, "ssh_host_rsa_key"); ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, RSAKEY_PATH);
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, "ssh_host_ecdsa_key"); ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, ECDSAKEY_PATH);
if (ssh_bind_listen(sshbind) < 0) { if (ssh_bind_listen(sshbind) < 0) {
printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); printf("Error listening to socket: %s\n", ssh_get_error(sshbind));
ssh_bind_free(sshbind);
ssh_finalize();
return 1; return 1;
} }
while (1) { while (1) {
ssh_session session = ssh_new(); session = ssh_new();
if (session == NULL) { if (session == NULL) {
fprintf(stderr, "Error initializing ssh_session\n"); fprintf(stderr, "Error initializing ssh_session\n");
break; break;
@ -67,14 +85,12 @@ int main()
/* Remove the SIGCHLD handler inherited from parent. */ /* Remove the SIGCHLD handler inherited from parent. */
sa.sa_handler = SIG_DFL; sa.sa_handler = SIG_DFL;
sigaction(SIGCHLD, &sa, NULL); sigaction(SIGCHLD, &sa, NULL);
/* Remove socket binding, which allows us to restart the /* Remove socket binding, which allows us to restart the parent process, without terminating existing sessions. */
* parent process, without terminating existing sessions. */
ssh_bind_free(sshbind); ssh_bind_free(sshbind);
ssh_event event = ssh_event_new(); ssh_event event = ssh_event_new();
if (event != NULL) { if (event != NULL) {
/* Blocks until the SSH session ends by either /* Blocks until the SSH session ends */
* child process exiting, or client disconnecting. */
handle_session(event, session); handle_session(event, session);
ssh_event_free(event); ssh_event_free(event);
} else { } else {
@ -82,21 +98,24 @@ int main()
} }
ssh_disconnect(session); ssh_disconnect(session);
ssh_free(session); ssh_free(session);
ssh_finalize();
exit(0); return 0;
case -1: case -1:
fprintf(stderr, "Failed to fork\n"); fprintf(stderr, "Failed to fork\n");
} }
} else { } else {
fprintf(stderr, "Error accepting a connection : %s\n", ssh_get_error(sshbind)); fprintf(stderr, "Error accepting a connection : %s\n", ssh_get_error(sshbind));
exit(1); ssh_disconnect(session);
ssh_free(session);
ssh_bind_free(sshbind);
ssh_finalize();
return 1;
} }
/* Since the session has been passed to a child fork, do some cleaning /* Since the session has been passed to a child fork, do some cleaning up at the parent process. */
* up at the parent process. */
ssh_disconnect(session); ssh_disconnect(session);
ssh_free(session); ssh_free(session);
} }
ssh_bind_free(sshbind); ssh_bind_free(sshbind);
ssh_finalize(); ssh_finalize();
return 0; return 0;

230
src/proxy.c Normal file
View file

@ -0,0 +1,230 @@
#include <libssh/callbacks.h>
#include <stdio.h>
#include <stdlib.h>
#include "client.h"
#include "proxy.h"
// callback function for channel data and exceptions
static int proxy_data_function(ssh_session session, ssh_channel channel, void *data,
uint32_t len, int is_stderr, void *userdata) {
struct proxy_channel_data_struct *pdata = (struct proxy_channel_data_struct *) userdata;
(void) session;
(void) channel;
(void) is_stderr;
if (ssh_channel_is_open(pdata->client_channel))
return ssh_channel_write(pdata->client_channel, (char*) data, len);
else
return SSH_ERROR;
}
// callback function for SSH channel PTY request from a client
static int proxy_pty_request(ssh_session session, ssh_channel channel,
const char *term, int cols, int rows, int py, int px,
void *userdata) {
struct proxy_channel_data_struct *pdata = (struct proxy_channel_data_struct *)userdata;
(void) session;
(void) channel;
(void) py;
(void) px;
printf("pty request\n");
int rc = ssh_channel_request_pty_size(pdata->client_channel, term, cols, rows);
if (rc == SSH_OK) {
printf("pty request successfull\n");
} else {
printf("pty request failed\n");
}
return rc;
}
// callback function for SSH channel PTY resize from a client
static int proxy_pty_resize(ssh_session session, ssh_channel channel, int cols,
int rows, int py, int px, void *userdata) {
struct proxy_channel_data_struct *pdata = (struct proxy_channel_data_struct *)userdata;
(void) session;
(void) channel;
(void) py;
(void) px;
if (pdata->client_channel == NULL || ssh_channel_is_open(pdata->client_channel) == 0) {
fprintf(stderr, "proxy pty oups!!!!!\n");
return SSH_ERROR;
}
printf("pty resize\n");
int rc = ssh_channel_change_pty_size(pdata->client_channel, cols, rows);
if (rc == SSH_OK) {
printf("pty resize successfull\n");
} else {
printf("pty resize failed\n");
}
return rc;
}
static int proxy_exec_request(ssh_session session, ssh_channel channel,
const char *command, void *userdata) {
struct proxy_channel_data_struct *pdata = (struct proxy_channel_data_struct *) userdata;
(void) session;
(void) channel;
printf("exec request : %s\n", command); // TODO
int rc = ssh_channel_request_exec(pdata->client_channel, command);
if (rc == SSH_OK) {
printf("exec request successfull\n");
} else {
printf("exec request failed\n");
}
return rc;
}
static int proxy_shell_request(ssh_session session, ssh_channel channel,
void *userdata) {
struct proxy_channel_data_struct *pdata = (struct proxy_channel_data_struct *) userdata;
(void) session;
(void) channel;
printf("shell request\n");
int rc = ssh_channel_request_shell(pdata->client_channel);
if (rc == SSH_OK) {
printf("shell request successfull\n");
} else {
printf("shell request failed\n");
}
return rc;
}
static int proxy_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) {
// printf("sftp request\n"); // TODO
// return exec_request(session, channel, SFTP_SERVER_PATH, userdata);
//}
(void) session;
(void) channel;
(void) subsystem;
(void) userdata;
return SSH_ERROR; // TODO
}
static void proxy_channel_eof_callback (ssh_session session, ssh_channel channel, void *userdata)
{
(void) session;
(void) channel;
(void) userdata;
printf("proxy eof callback\n");
}
static void proxy_channel_close_callback (ssh_session session, ssh_channel channel, void *userdata)
{
(void) session;
(void) channel;
(void) userdata;
printf("proxy close callback\n");
}
static void proxy_channel_exit_status_callback (ssh_session session, ssh_channel channel, int exit_status, void *userdata)
{
(void) session;
(void) channel;
(void) exit_status;
(void) userdata;
printf("proxy exit status callback\n");
}
static void proxy_channel_signal_callback (ssh_session session, ssh_channel channel,
const char *signal, void *userdata) {
(void) session;
(void) channel;
(void) signal;
(void) userdata;
printf("proxy signal callback\n");
}
static void proxy_channel_exit_signal_callback (ssh_session session, ssh_channel channel,
const char *signal, int core, const char *errmsg,
const char *lang, void *userdata) {
(void) session;
(void) channel;
(void) signal;
(void) core;
(void) errmsg;
(void) lang;
(void) userdata;
printf("proxy exit signal callback\n");
}
void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel, const char * hostname)
{
struct client_channel_data_struct * cdata;
struct proxy_channel_data_struct pdata = {
.event = event,
.my_session = session,
.my_channel = my_channel,
.client_channel = NULL,
};
//ssh_event_remove_session(event, session);
cdata = client_dial(event, &pdata, hostname);
//for (int n = 0; n < 10; n++) {
// ssh_event_dopoll(event, 100);
//}
//ssh_event_add_session(event, session);
if (cdata == NULL) {
return;
}
pdata.client_channel = cdata->my_channel;
/* We tie everything together */
struct ssh_channel_callbacks_struct channel_cb = {
.userdata = &pdata,
.channel_data_function = proxy_data_function,
.channel_eof_function = proxy_channel_eof_callback,
.channel_close_function = proxy_channel_close_callback,
.channel_signal_function = proxy_channel_signal_callback,
.channel_exit_status_function = proxy_channel_exit_status_callback,
.channel_exit_signal_function = proxy_channel_exit_signal_callback,
.channel_pty_request_function = proxy_pty_request,
.channel_shell_request_function = proxy_shell_request,
.channel_pty_window_change_function = proxy_pty_resize,
.channel_exec_request_function = proxy_exec_request,
.channel_subsystem_request_function = proxy_subsystem_request,
/** This function will be called when a client requests agent
* authentication forwarding.
*/
//ssh_channel_auth_agent_req_callback channel_auth_agent_req_function;
/** This function will be called when a client requests X11
* forwarding.
*/
//ssh_channel_x11_req_callback channel_x11_req_function;
/** This function will be called when a client requests an environment
* variable to be set.
*/
/** This function will be called when the channel write is guaranteed
* not to block.
*/
// .channel_write_wontblock_function = proxy_channel_write_wontblock,
};
ssh_callbacks_init(&channel_cb);
ssh_set_channel_callbacks(my_channel, &channel_cb);
do {
/* Poll the main event which takes care of the sessions and channels */
if (ssh_event_dopoll(event, -1) == SSH_ERROR) {
break;
}
} while(ssh_channel_is_open(my_channel) && ssh_channel_is_open(pdata.client_channel));
if (ssh_channel_is_open(my_channel))
ssh_channel_close(my_channel);
if (ssh_channel_is_open(cdata->my_channel))
ssh_channel_close(cdata->my_channel);
client_cleanup(cdata);
}

18
src/proxy.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef PROXY_H_
#define PROXY_H_
#include <libssh/libssh.h>
#include "session.h"
/* A userdata struct for channel. */
struct proxy_channel_data_struct {
/* Event which is used to poll */
ssh_event event;
ssh_session my_session;
ssh_channel my_channel;
ssh_channel client_channel;
};
void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel, const char * hostname);
#endif

128
src/pty.c
View file

@ -1,128 +0,0 @@
#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;
}

View file

@ -1,15 +0,0 @@
#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

View file

@ -8,178 +8,76 @@
#include <string.h> #include <string.h>
#include <sys/wait.h> #include <sys/wait.h>
#include "pty.h" #include "../config.h"
#include "proxy.h"
#include "session.h" #include "session.h"
#define USER "julien" int auth_pubkey(ssh_session session, const char *user,
#define PASS "graou" struct ssh_key_struct *pubkey,
#define BUF_SIZE 1048576 char signature_state, void *userdata) {
#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; struct session_data_struct *sdata = (struct session_data_struct *) userdata;
(void) session; (void) session;
if (strcmp(user, USER) == 0 && strcmp(pass, PASS) == 0) { // For some reason, libssh can call this twice for the same key
sdata->authenticated = 1; if (sdata->authenticated == 1)
return SSH_AUTH_SUCCESS; return SSH_ERROR;
}
if (signature_state != SSH_PUBLICKEY_STATE_NONE && signature_state != SSH_PUBLICKEY_STATE_VALID) {
fprintf(stderr, "Invalid signature state\n");
sdata->auth_attempts++; sdata->auth_attempts++;
return SSH_AUTH_DENIED; return SSH_AUTH_DENIED;
}
// TODO check for an invite
ssh_key reference_key = ssh_key_new();
ssh_pki_import_pubkey_base64(USER_RSA_PUBKEY, SSH_KEYTYPE_RSA, &reference_key); // TODO fetch all pubkeys from db
if (!ssh_key_cmp(pubkey, reference_key, SSH_KEY_CMP_PUBLIC)) {
sdata->authenticated = 1;
ssh_key_free(reference_key);
size_t len = strnlen(user, MAX_HOSTNAME_LENGTH + 1);
if (len == MAX_HOSTNAME_LENGTH + 1) {
fprintf(stderr, "Hostname too long, max length is %d.\n", MAX_HOSTNAME_LENGTH);
return SSH_ERROR;
}
sdata->login_username = malloc(len+1);
memset(sdata->login_username, 0, len+1);
strncpy(sdata->login_username, user, len);
return SSH_AUTH_SUCCESS;
} else {
ssh_key_free(reference_key);
sdata->auth_attempts++;
return SSH_AUTH_DENIED;
}
} }
static ssh_channel channel_open(ssh_session session, void *userdata) { ssh_channel channel_open(ssh_session session, void *userdata) {
struct session_data_struct *sdata = (struct session_data_struct *) userdata; struct session_data_struct *sdata = (struct session_data_struct *) userdata;
if (sdata->channel == NULL) {
sdata->channel = ssh_channel_new(session); sdata->channel = ssh_channel_new(session);
return sdata->channel; return sdata->channel;
} } else {
// Only one channel allowed
static int process_stdout(socket_t fd, int revents, void *userdata) { return NULL;
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) { 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. */ /* Our struct holding information about the session. */
struct session_data_struct sdata = { struct session_data_struct sdata = {
.channel = NULL, .channel = NULL,
.auth_attempts = 0, .auth_attempts = 0,
.authenticated = 0 .authenticated = 0,
}; .login_username = NULL
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 = { struct ssh_server_callbacks_struct server_cb = {
.userdata = &sdata, .userdata = &sdata,
.auth_password_function = auth_password, .auth_pubkey_function = auth_pubkey,
.channel_open_request_session_function = channel_open, .channel_open_request_session_function = channel_open,
}; };
ssh_callbacks_init(&server_cb); ssh_callbacks_init(&server_cb);
ssh_callbacks_init(&channel_cb);
ssh_set_server_callbacks(session, &server_cb); ssh_set_server_callbacks(session, &server_cb);
if (ssh_handle_key_exchange(session) != SSH_OK) { if (ssh_handle_key_exchange(session) != SSH_OK) {
@ -187,14 +85,18 @@ void handle_session(ssh_event event, ssh_session session) {
return; return;
} }
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); ssh_set_auth_methods(session, SSH_AUTH_METHOD_PUBLICKEY);
ssh_event_add_session(event, session); ssh_event_add_session(event, session);
n = 0; for (int n=0; sdata.authenticated == 0 || sdata.channel == NULL; n++) {
while (sdata.authenticated == 0 || sdata.channel == NULL) {
/* If the user has used up all attempts, or if he hasn't been able to /* If the user has used up all attempts, or if he hasn't been able to
* authenticate in 10 seconds (n * 100ms), disconnect. */ * authenticate in 10 seconds (n * 100ms), disconnect. */
if (sdata.auth_attempts >= 3 || n >= 100) { if (sdata.auth_attempts >= 3) {
fprintf(stderr, "Closing connection after 3 failed auth attempts\n");
return;
}
if (n >= 100) {
fprintf(stderr, "Closing connection after 10 seconds without successfull authentication\n");
return; return;
} }
@ -202,70 +104,18 @@ void handle_session(ssh_event event, ssh_session session) {
fprintf(stderr, "%s\n", ssh_get_error(session)); fprintf(stderr, "%s\n", ssh_get_error(session));
return; return;
} }
n++;
} }
ssh_set_channel_callbacks(sdata.channel, &channel_cb); handle_proxy_session(event, session, sdata.channel, sdata.login_username);
do { if (ssh_channel_is_open(sdata.channel)) {
/* 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); 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. */ /* 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++) { for (int n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) {
ssh_event_dopoll(event, 100); ssh_event_dopoll(event, 100);
} }
free(sdata.login_username);
ssh_event_remove_session(event, session);
} }

View file

@ -1,9 +1,19 @@
#ifndef _SESSION_H_ #ifndef SESSION_H_
#define _SESSION_H_ #define SESSION_H_
#include <libssh/libssh.h> #include <libssh/libssh.h>
#include "util.h" #define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR)
/* 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;
// ssh user name when login
char * login_username;
};
void handle_session(ssh_event event, ssh_session session); void handle_session(ssh_event event, ssh_session session);

View file

@ -1,32 +0,0 @@
#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