static int message_callback(ssh_session session, ssh_message message, void *userdata) { ssh_channel channel; int socket_fd, *pFd; struct ssh_channel_callbacks_struct *cb_chan; struct event_fd_data_struct *event_fd_data; (void)session; (void)message; (void)userdata; _ssh_log(SSH_LOG_PACKET, "=== message_callback", "Message type: %d", ssh_message_type(message)); _ssh_log(SSH_LOG_PACKET, "=== message_callback", "Message Subtype: %d", ssh_message_subtype(message)); if (ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN) { _ssh_log(SSH_LOG_PROTOCOL, "=== message_callback", "channel_request_open"); if (ssh_message_subtype(message) == SSH_CHANNEL_DIRECT_TCPIP) { channel = ssh_message_channel_request_open_reply_accept(message); if (channel == NULL) { _ssh_log(SSH_LOG_WARNING, "=== message_callback", "Accepting direct-tcpip channel failed!"); return 1; } else { _ssh_log(SSH_LOG_PROTOCOL, "=== message_callback", "Connected to channel!"); socket_fd = open_tcp_socket(message); if (-1 == socket_fd) { return 1; } pFd = malloc(sizeof *pFd); cb_chan = malloc(sizeof *cb_chan); event_fd_data = malloc(sizeof *event_fd_data); (*pFd) = socket_fd; event_fd_data->channel = channel; event_fd_data->p_fd = pFd; event_fd_data->stacked = 0; event_fd_data->cb_chan = cb_chan; cb_chan->userdata = event_fd_data; cb_chan->channel_eof_function = my_channel_eof_function; cb_chan->channel_close_function = my_channel_close_function; cb_chan->channel_data_function = my_channel_data_function; cb_chan->channel_exit_status_function = my_channel_exit_status_function; ssh_callbacks_init(cb_chan); ssh_set_channel_callbacks(channel, cb_chan); ssh_event_add_fd(mainloop, (socket_t)*pFd, POLLIN, my_fd_data_function, event_fd_data); return 0; } } } return 1; }
static int main_loop(ssh_channel chan) { ssh_session session = ssh_channel_get_session(chan); socket_t fd; struct termios *term = NULL; struct winsize *win = NULL; pid_t childpid; ssh_event event; short events; childpid = forkpty(&fd, NULL, term, win); if(childpid == 0) { execl("/bin/bash", "/bin/bash", (char *)NULL); abort(); } cb.userdata = &fd; ssh_callbacks_init(&cb); ssh_set_channel_callbacks(chan, &cb); events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; event = ssh_event_new(); if(event == NULL) { printf("Couldn't get a event\n"); return -1; } if(ssh_event_add_fd(event, fd, events, copy_fd_to_chan, chan) != SSH_OK) { printf("Couldn't add an fd to the event\n"); return -1; } if(ssh_event_add_session(event, session) != SSH_OK) { printf("Couldn't add the session to the event\n"); return -1; } do { ssh_event_dopoll(event, 1000); } while(!ssh_channel_is_closed(chan)); ssh_event_remove_fd(event, fd); ssh_event_remove_session(event, session); ssh_event_free(event); return 0; }
static int do_shell (ssh_event event, ssh_channel chan) { socket_t fd; struct termios *term = NULL; struct winsize *win = NULL; short events; int fd_status; state.childpid = forkpty (&fd, NULL, term, win); if (state.childpid == 0) { close (state.bind_fd); close (state.session_fd); execl ("/bin/bash", "/bin/bash", NULL); _exit (127); } else if (state.childpid < 0) { g_critical ("forkpty failed: %s", g_strerror (errno)); return -1; } fd_status = fcntl (fd, F_GETFL, 0); if (fcntl (fd, F_SETFL, fd_status | O_NONBLOCK) < 0) { g_critical ("couldn't set non-blocking mode"); return -1; } cb.userdata = (gpointer)(long)fd; ssh_callbacks_init(&cb); ssh_set_channel_callbacks (chan, &cb); events = POLLIN | POLLOUT | POLLPRI | POLLERR | POLLHUP | POLLNVAL; if (ssh_event_add_fd (event, fd, events, fd_data, chan) != SSH_OK) g_return_val_if_reached(-1); return 0; }
static int do_exec (ssh_event event, ssh_channel chan, const gchar *cmd) { socket_t fd; short events; fd = fork_exec (cmd); if (fd < 0) return -1; cb.userdata = GINT_TO_POINTER (fd); ssh_callbacks_init(&cb); ssh_set_channel_callbacks (chan, &cb); events = POLLIN | POLLOUT | POLLPRI | POLLERR | POLLHUP | POLLNVAL; if (ssh_event_add_fd (event, fd, events, fd_data, chan) != SSH_OK) g_return_val_if_reached(-1); return 0; }
/** * @brief A wrapper for the select syscall * * This functions acts more or less like the select(2) syscall.\n * There is no support for writing or exceptions.\n * * @param[in] channels Arrays of channels pointers terminated by a NULL. * It is never rewritten. * * @param[out] outchannels Arrays of same size that "channels", there is no need * to initialize it. * * @param[in] maxfd Maximum +1 file descriptor from readfds. * * @param[in] readfds A fd_set of file descriptors to be select'ed for * reading. * * @param[in] timeout A timeout for the select. * * @return SSH_OK on success, * SSH_ERROR on error, * SSH_EINTR if it was interrupted. In that case, * just restart it. * * @warning libssh is not reentrant here. That means that if a signal is caught * during the processing of this function, you cannot call libssh * functions on sessions that are busy with ssh_select(). * * @see select(2) */ int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, fd_set *readfds, struct timeval *timeout) { fd_set origfds; socket_t fd; int i,j; int rc; int base_tm, tm; struct ssh_timestamp ts; ssh_event event = ssh_event_new(); int firstround=1; base_tm = tm=timeout->tv_sec * 1000 + timeout->tv_usec/1000; for (i=0 ; channels[i] != NULL; ++i){ ssh_event_add_session(event, channels[i]->session); } FD_ZERO(&origfds); for (fd = 0; fd < maxfd ; fd++) { if (FD_ISSET(fd, readfds)) { ssh_event_add_fd(event, fd, POLLIN, ssh_select_cb, readfds); FD_SET(fd, &origfds); } } outchannels[0] = NULL; FD_ZERO(readfds); ssh_timestamp_init(&ts); do { /* Poll every channel */ j = 0; for (i = 0; channels[i]; i++) { if(ssh_channel_poll(channels[i], 0) != 0) { outchannels[j] = channels[i]; j++; } else if(ssh_channel_poll(channels[i], 1) != 0) { outchannels[j] = channels[i]; j++; } } outchannels[j] = NULL; if(j != 0) break; /* watch if a user socket was triggered */ for (fd = 0; fd < maxfd; fd++) { if (FD_ISSET(fd, readfds)) { goto out; } } /* If the timeout is elapsed, we should go out */ if(!firstround && ssh_timeout_elapsed(&ts, base_tm)) goto out; /* since there's nothing, let's fire the polling */ rc = ssh_event_dopoll(event,tm); if (rc == SSH_ERROR){ goto out; } tm = ssh_timeout_update(&ts, base_tm); firstround=0; } while (1); out: for (fd = 0; fd < maxfd; fd++) { if (FD_ISSET(fd, &origfds)) { ssh_event_remove_fd(event, fd); } } ssh_event_free(event); return SSH_OK; }
static void handle_session(ssh_event event, ssh_session session) { int n; int rc = 0; /* 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, }; if (authorizedkeys[0]) { server_cb.auth_pubkey_function = auth_publickey; ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY); } else ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); 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_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); } } /* SIGCHLD handler for cleaning up dead children. */ static void sigchld_handler(int signo) { (void) signo; while (waitpid(-1, NULL, WNOHANG) > 0); } int main(int argc, char **argv) { ssh_bind sshbind; ssh_session session; ssh_event event; struct sigaction sa; int rc; /* Set up SIGCHLD handler. */ 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; } rc = ssh_init(); if (rc < 0) { fprintf(stderr, "ssh_init failed\n"); return 1; } sshbind = ssh_bind_new(); if (sshbind == NULL) { fprintf(stderr, "ssh_bind_new failed\n"); return 1; } #ifdef HAVE_ARGP_H argp_parse(&argp, argc, argv, 0, 0, sshbind); #else (void) argc; (void) argv; set_default_keys(sshbind, 0, 0, 0); #endif /* HAVE_ARGP_H */ if(ssh_bind_listen(sshbind) < 0) { fprintf(stderr, "%s\n", ssh_get_error(sshbind)); return 1; } while (1) { session = ssh_new(); if (session == NULL) { fprintf(stderr, "Failed to allocate session\n"); continue; } /* Blocks until there is a new incoming connection. */ if(ssh_bind_accept(sshbind, session) != SSH_ERROR) { 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); 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, "%s\n", ssh_get_error(sshbind)); } /* 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; }
int event_add_fd(ssh_event event, socket_t fd, short events, void *userdata) { return ssh_event_add_fd(event, fd, events, event_callback, userdata); }