272 lines
8.3 KiB
C
272 lines
8.3 KiB
C
|
#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);
|
||
|
}
|
||
|
}
|