int get_snd_buf_size(struct ssh *ssh) { int fd = ssh_packet_get_connection_out(ssh); int optval; socklen_t optvallen = sizeof(optval); if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &optval, &optvallen) != 0) optval = DEFAULT_ROAMBUF; return optval; }
void server_loop2(struct ssh *ssh) { fd_set *readset = NULL, *writeset = NULL; int r, rekeying = 0, max_fd; u_int nalloc = 0; u_int64_t rekey_timeout_ms = 0; debug("Entering interactive session for SSH2."); signal(SIGCHLD, sigchld_handler); child_terminated = 0; connection_in = ssh_packet_get_connection_in(ssh); connection_out = ssh_packet_get_connection_out(ssh); if (!use_privsep) { signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); signal(SIGQUIT, sigterm_handler); } notify_setup(); max_fd = MAX(connection_in, connection_out); max_fd = MAX(max_fd, notify_pipe[0]); server_init_dispatch(ssh); for (;;) { process_buffered_input_packets(ssh); rekeying = (ssh->kex != NULL && !ssh->kex->done); if (!rekeying && ssh_packet_not_very_much_data_to_write(ssh)) channel_output_poll(); if (options.rekey_interval > 0 && compat20 && !rekeying) rekey_timeout_ms = ssh_packet_get_rekey_timeout(ssh) * 1000; else rekey_timeout_ms = 0; wait_until_can_do_something(ssh, &readset, &writeset, &max_fd, &nalloc, rekey_timeout_ms); if (received_sigterm) { logit("Exiting on signal %d", (int)received_sigterm); /* Clean up sessions, utmp, etc. */ cleanup_exit(255); } collect_children(); if (!rekeying) { channel_after_select(readset, writeset); if (ssh_packet_need_rekeying(ssh)) { debug("need rekeying"); ssh->kex->done = 0; if ((r = kex_send_kexinit(ssh)) != 0) { fatal("%s: kex_send_kexinit: %s", __func__, ssh_err(r)); } } } process_input(ssh, readset); if (connection_closed) break; process_output(ssh, writeset); } collect_children(); free(readset); free(writeset); /* free all channels, no more reads and writes */ channel_free_all(); /* free remaining sessions, e.g. remove wtmp entries */ session_destroy_all(NULL); }
/* * Waits for the server identification string, and sends our own * identification string. */ void ssh_exchange_identification(struct ssh *ssh, int timeout_ms) { char buf[256], remote_version[256]; /* must be same size! */ int remote_major, remote_minor, mismatch; int connection_in = ssh_packet_get_connection_in(ssh); int connection_out = ssh_packet_get_connection_out(ssh); int minor1 = PROTOCOL_MINOR_1; u_int i, n; size_t len; int fdsetsz, remaining, rc; struct timeval t_start, t_remaining; fd_set *fdset; fdsetsz = howmany(connection_in + 1, NFDBITS) * sizeof(fd_mask); fdset = xcalloc(1, fdsetsz); /* Read other side's version identification. */ remaining = timeout_ms; for (n = 0;;) { for (i = 0; i < sizeof(buf) - 1; i++) { if (timeout_ms > 0) { gettimeofday(&t_start, NULL); ms_to_timeval(&t_remaining, remaining); FD_SET(connection_in, fdset); rc = select(connection_in + 1, fdset, NULL, fdset, &t_remaining); ms_subtract_diff(&t_start, &remaining); if (rc == 0 || remaining <= 0) fatal("Connection timed out during " "banner exchange"); if (rc == -1) { if (errno == EINTR) continue; fatal("ssh_exchange_identification: " "select: %s", strerror(errno)); } } len = roaming_atomicio(read, connection_in, &buf[i], 1); if (len != 1 && errno == EPIPE) fatal("ssh_exchange_identification: " "Connection closed by remote host"); else if (len != 1) fatal("ssh_exchange_identification: " "read: %.100s", strerror(errno)); if (buf[i] == '\r') { buf[i] = '\n'; buf[i + 1] = 0; continue; /**XXX wait for \n */ } if (buf[i] == '\n') { buf[i + 1] = 0; break; } if (++n > 65536) fatal("ssh_exchange_identification: " "No banner received"); } buf[sizeof(buf) - 1] = 0; if (strncmp(buf, "SSH-", 4) == 0) break; debug("ssh_exchange_identification: %s", buf); } server_version_string = xstrdup(buf); xfree(fdset); /* * Check that the versions match. In future this might accept * several versions and set appropriate flags to handle them. */ if (sscanf(server_version_string, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3) fatal("Bad remote protocol version identification: '%.100s'", buf); debug("Remote protocol version %d.%d, remote software version %.100s", remote_major, remote_minor, remote_version); ssh->compat = compat_datafellows(remote_version); mismatch = 0; switch (remote_major) { case 1: if (remote_minor == 99 && (options.protocol & SSH_PROTO_2) && !(options.protocol & SSH_PROTO_1_PREFERRED)) { enable_compat20(); break; } if (!(options.protocol & SSH_PROTO_1)) { mismatch = 1; break; } if (remote_minor < 3) { fatal("Remote machine has too old SSH software version."); } else if (remote_minor == 3 || remote_minor == 4) { /* We speak 1.3, too. */ enable_compat13(); minor1 = 3; if (options.forward_agent) { logit("Agent forwarding disabled for protocol 1.3"); options.forward_agent = 0; } } break; case 2: if (options.protocol & SSH_PROTO_2) { enable_compat20(); break; } /* FALLTHROUGH */ default: mismatch = 1; break; } if (mismatch) fatal("Protocol major versions differ: %d vs. %d", (options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, remote_major); /* Send our own protocol version identification. */ snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s%s", compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, compat20 ? PROTOCOL_MINOR_2 : minor1, SSH_VERSION, compat20 ? "\r\n" : "\n"); if (roaming_atomicio(vwrite, connection_out, buf, strlen(buf)) != strlen(buf)) fatal("write: %.100s", strerror(errno)); client_version_string = xstrdup(buf); chop(client_version_string); chop(server_version_string); debug("Local version string %.100s", client_version_string); }
/* * Performs the interactive session. This handles data transmission between * the client and the program. Note that the notion of stdin, stdout, and * stderr in this function is sort of reversed: this function writes to * stdin (of the child program), and reads from stdout and stderr (of the * child program). */ void server_loop(pid_t pid, int fdin_arg, int fdout_arg, int fderr_arg) { struct ssh *ssh = active_state; /* XXX */ fd_set *readset = NULL, *writeset = NULL; int max_fd = 0; u_int nalloc = 0; int wait_status; /* Status returned by wait(). */ pid_t wait_pid; /* pid returned by wait(). */ int waiting_termination = 0; /* Have displayed waiting close message. */ u_int64_t max_time_milliseconds; u_int previous_stdout_buffer_bytes; u_int stdout_buffer_bytes; int r, type; debug("Entering interactive session."); /* Initialize the SIGCHLD kludge. */ child_terminated = 0; signal(SIGCHLD, sigchld_handler); if (!use_privsep) { signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); signal(SIGQUIT, sigterm_handler); } /* Initialize our global variables. */ fdin = fdin_arg; fdout = fdout_arg; fderr = fderr_arg; /* nonblocking IO */ set_nonblock(fdin); set_nonblock(fdout); /* we don't have stderr for interactive terminal sessions, see below */ if (fderr != -1) set_nonblock(fderr); if (!(ssh->compat & SSH_BUG_IGNOREMSG) && isatty(fdin)) fdin_is_tty = 1; connection_in = ssh_packet_get_connection_in(ssh); connection_out = ssh_packet_get_connection_out(ssh); notify_setup(); previous_stdout_buffer_bytes = 0; /* Set approximate I/O buffer size. */ if (ssh_packet_is_interactive(ssh)) buffer_high = 4096; else buffer_high = 64 * 1024; #if 0 /* Initialize max_fd to the maximum of the known file descriptors. */ max_fd = MAX(connection_in, connection_out); max_fd = MAX(max_fd, fdin); max_fd = MAX(max_fd, fdout); if (fderr != -1) max_fd = MAX(max_fd, fderr); #endif /* Initialize Initialize buffers. */ if ((stdin_buffer = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((stdout_buffer = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((stderr_buffer = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); /* * If we have no separate fderr (which is the case when we have a pty * - there we cannot make difference between data sent to stdout and * stderr), indicate that we have seen an EOF from stderr. This way * we don't need to check the descriptor everywhere. */ if (fderr == -1) fderr_eof = 1; server_init_dispatch(ssh); /* Main loop of the server for the interactive session mode. */ for (;;) { /* Process buffered packets from the client. */ process_buffered_input_packets(ssh); /* * If we have received eof, and there is no more pending * input data, cause a real eof by closing fdin. */ if (stdin_eof && fdin != -1 && sshbuf_len(stdin_buffer) == 0) { if (fdin != fdout) close(fdin); else shutdown(fdin, SHUT_WR); /* We will no longer send. */ fdin = -1; } /* Make packets from buffered stderr data to send to the client. */ make_packets_from_stderr_data(ssh); /* * Make packets from buffered stdout data to send to the * client. If there is very little to send, this arranges to * not send them now, but to wait a short while to see if we * are getting more data. This is necessary, as some systems * wake up readers from a pty after each separate character. */ max_time_milliseconds = 0; stdout_buffer_bytes = sshbuf_len(stdout_buffer); if (stdout_buffer_bytes != 0 && stdout_buffer_bytes < 256 && stdout_buffer_bytes != previous_stdout_buffer_bytes) { /* try again after a while */ max_time_milliseconds = 10; } else { /* Send it now. */ make_packets_from_stdout_data(ssh); } previous_stdout_buffer_bytes = sshbuf_len(stdout_buffer); /* Send channel data to the client. */ if (ssh_packet_not_very_much_data_to_write(ssh)) channel_output_poll(); /* * Bail out of the loop if the program has closed its output * descriptors, and we have no more data to send to the * client, and there is no pending buffered data. */ if (fdout_eof && fderr_eof && !ssh_packet_have_data_to_write(ssh) && sshbuf_len(stdout_buffer) == 0 && sshbuf_len(stderr_buffer) == 0) { if (!channel_still_open()) break; if (!waiting_termination) { const char *s = "Waiting for forwarded connections to terminate...\r\n"; char *cp = channel_open_message(); waiting_termination = 1; /* Display list of open channels. */ if ((r = sshbuf_put(stderr_buffer, s, strlen(s))) != 0 || (r = sshbuf_put(stderr_buffer, s, strlen(s))) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); free(cp); } } max_fd = MAX(connection_in, connection_out); max_fd = MAX(max_fd, fdin); max_fd = MAX(max_fd, fdout); max_fd = MAX(max_fd, fderr); max_fd = MAX(max_fd, notify_pipe[0]); /* Sleep in select() until we can do something. */ wait_until_can_do_something(ssh, &readset, &writeset, &max_fd, &nalloc, max_time_milliseconds); if (received_sigterm) { logit("Exiting on signal %d", (int)received_sigterm); /* Clean up sessions, utmp, etc. */ cleanup_exit(255); } /* Process any channel events. */ channel_after_select(readset, writeset); /* Process input from the client and from program stdout/stderr. */ process_input(ssh, readset); /* Process output to the client and to program stdin. */ process_output(ssh, writeset); } free(readset); free(writeset); /* Cleanup and termination code. */ /* Wait until all output has been sent to the client. */ drain_output(ssh); debug("End of interactive session; stdin %ld, stdout (read %ld, sent %ld), stderr %ld bytes.", stdin_bytes, fdout_bytes, stdout_bytes, stderr_bytes); /* Free and clear the buffers. */ sshbuf_free(stdin_buffer); sshbuf_free(stdout_buffer); sshbuf_free(stderr_buffer); /* Close the file descriptors. */ if (fdout != -1) close(fdout); fdout = -1; fdout_eof = 1; if (fderr != -1) close(fderr); fderr = -1; fderr_eof = 1; if (fdin != -1) close(fdin); fdin = -1; channel_free_all(); /* We no longer want our SIGCHLD handler to be called. */ signal(SIGCHLD, SIG_DFL); while ((wait_pid = waitpid(-1, &wait_status, 0)) < 0) if (errno != EINTR) ssh_packet_disconnect(ssh, "wait: %.100s", strerror(errno)); if (wait_pid != pid) error("Strange, wait returned pid %ld, expected %ld", (long)wait_pid, (long)pid); /* Check if it exited normally. */ if (WIFEXITED(wait_status)) { /* Yes, normal exit. Get exit status and send it to the client. */ debug("Command exited with status %d.", WEXITSTATUS(wait_status)); if ((r = sshpkt_start(ssh, SSH_SMSG_EXITSTATUS)) != 0 || (r = sshpkt_put_u32(ssh, WEXITSTATUS(wait_status))) != 0 || (r = sshpkt_send(ssh)) != 0) fatal("%s: %s", __func__, ssh_err(r)); ssh_packet_write_wait(ssh); /* * Wait for exit confirmation. Note that there might be * other packets coming before it; however, the program has * already died so we just ignore them. The client is * supposed to respond with the confirmation when it receives * the exit status. */ do { type = ssh_packet_read(ssh); } while (type != SSH_CMSG_EXIT_CONFIRMATION); debug("Received exit confirmation."); return; } /* Check if the program terminated due to a signal. */ if (WIFSIGNALED(wait_status)) ssh_packet_disconnect(ssh, "Command terminated on signal %d.", WTERMSIG(wait_status)); /* Some weird exit cause. Just exit. */ ssh_packet_disconnect(ssh, "wait returned status %04x.", wait_status); /* NOTREACHED */ }