#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); } }