📄 clientloop.c
字号:
/* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * All rights reserved * The main loop for the interactive session (client side). * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * * Copyright (c) 1999 Theo de Raadt. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * SSH2 support added by Markus Friedl. * Copyright (c) 1999, 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */#include "includes.h"RCSID("$OpenBSD: clientloop.c,v 1.101 2002/06/09 13:32:01 markus Exp $");#include "ssh.h"#include "ssh1.h"#include "ssh2.h"#include "xmalloc.h"#include "packet.h"#include "buffer.h"#include "compat.h"#include "channels.h"#include "dispatch.h"#include "buffer.h"#include "bufaux.h"#include "key.h"#include "kex.h"#include "log.h"#include "readconf.h"#include "clientloop.h"#include "authfd.h"#include "atomicio.h"#include "sshtty.h"#include "misc.h"#include "readpass.h"/* import options */extern Options options;/* Flag indicating that stdin should be redirected from /dev/null. */extern int stdin_null_flag;/* * Name of the host we are connecting to. This is the name given on the * command line, or the HostName specified for the user-supplied name in a * configuration file. */extern char *host;/* * Flag to indicate that we have received a window change signal which has * not yet been processed. This will cause a message indicating the new * window size to be sent to the server a little later. This is volatile * because this is updated in a signal handler. */static volatile sig_atomic_t received_window_change_signal = 0;static volatile sig_atomic_t received_signal = 0;/* Flag indicating whether the user\'s terminal is in non-blocking mode. */static int in_non_blocking_mode = 0;/* Common data for the client loop code. */static int quit_pending; /* Set to non-zero to quit the client loop. */static int escape_char; /* Escape character. */static int escape_pending; /* Last character was the escape character */static int last_was_cr; /* Last character was a newline. */static int exit_status; /* Used to store the exit status of the command. */static int stdin_eof; /* EOF has been encountered on standard error. */static Buffer stdin_buffer; /* Buffer for stdin data. */static Buffer stdout_buffer; /* Buffer for stdout data. */static Buffer stderr_buffer; /* Buffer for stderr data. */static u_long stdin_bytes, stdout_bytes, stderr_bytes;static u_int buffer_high;/* Soft max buffer size. */static int connection_in; /* Connection to server (input). */static int connection_out; /* Connection to server (output). */static int need_rekeying; /* Set to non-zero if rekeying is requested. */static int session_closed = 0; /* In SSH2: login session closed. */static void client_init_dispatch(void);int session_ident = -1;/*XXX*/extern Kex *xxx_kex;/* Restores stdin to blocking mode. */static voidleave_non_blocking(void){ if (in_non_blocking_mode) { (void) fcntl(fileno(stdin), F_SETFL, 0); in_non_blocking_mode = 0; fatal_remove_cleanup((void (*) (void *)) leave_non_blocking, NULL); }}/* Puts stdin terminal in non-blocking mode. */static voidenter_non_blocking(void){ in_non_blocking_mode = 1; (void) fcntl(fileno(stdin), F_SETFL, O_NONBLOCK); fatal_add_cleanup((void (*) (void *)) leave_non_blocking, NULL);}/* * Signal handler for the window change signal (SIGWINCH). This just sets a * flag indicating that the window has changed. */static voidwindow_change_handler(int sig){ received_window_change_signal = 1; signal(SIGWINCH, window_change_handler);}/* * Signal handler for signals that cause the program to terminate. These * signals must be trapped to restore terminal modes. */static voidsignal_handler(int sig){ received_signal = sig; quit_pending = 1;}/* * Returns current time in seconds from Jan 1, 1970 with the maximum * available resolution. */static doubleget_current_time(void){ struct timeval tv; gettimeofday(&tv, NULL); return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0;}/* * This is called when the interactive is entered. This checks if there is * an EOF coming on stdin. We must check this explicitly, as select() does * not appear to wake up when redirecting from /dev/null. */static voidclient_check_initial_eof_on_stdin(void){ int len; char buf[1]; /* * If standard input is to be "redirected from /dev/null", we simply * mark that we have seen an EOF and send an EOF message to the * server. Otherwise, we try to read a single character; it appears * that for some files, such /dev/null, select() never wakes up for * read for this descriptor, which means that we never get EOF. This * way we will get the EOF if stdin comes from /dev/null or similar. */ if (stdin_null_flag) { /* Fake EOF on stdin. */ debug("Sending eof."); stdin_eof = 1; packet_start(SSH_CMSG_EOF); packet_send(); } else { enter_non_blocking(); /* Check for immediate EOF on stdin. */ len = read(fileno(stdin), buf, 1); if (len == 0) { /* EOF. Record that we have seen it and send EOF to server. */ debug("Sending eof."); stdin_eof = 1; packet_start(SSH_CMSG_EOF); packet_send(); } else if (len > 0) { /* * Got data. We must store the data in the buffer, * and also process it as an escape character if * appropriate. */ if ((u_char) buf[0] == escape_char) escape_pending = 1; else buffer_append(&stdin_buffer, buf, 1); } leave_non_blocking(); }}/* * Make packets from buffered stdin data, and buffer them for sending to the * connection. */static voidclient_make_packets_from_stdin_data(void){ u_int len; /* Send buffered stdin data to the server. */ while (buffer_len(&stdin_buffer) > 0 && packet_not_very_much_data_to_write()) { len = buffer_len(&stdin_buffer); /* Keep the packets at reasonable size. */ if (len > packet_get_maxsize()) len = packet_get_maxsize(); packet_start(SSH_CMSG_STDIN_DATA); packet_put_string(buffer_ptr(&stdin_buffer), len); packet_send(); buffer_consume(&stdin_buffer, len); stdin_bytes += len; /* If we have a pending EOF, send it now. */ if (stdin_eof && buffer_len(&stdin_buffer) == 0) { packet_start(SSH_CMSG_EOF); packet_send(); } }}/* * Checks if the client window has changed, and sends a packet about it to * the server if so. The actual change is detected elsewhere (by a software * interrupt on Unix); this just checks the flag and sends a message if * appropriate. */static voidclient_check_window_change(void){ struct winsize ws; if (! received_window_change_signal) return; /** XXX race */ received_window_change_signal = 0; if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) < 0) return; debug2("client_check_window_change: changed"); if (compat20) { channel_request_start(session_ident, "window-change", 0); packet_put_int(ws.ws_col); packet_put_int(ws.ws_row); packet_put_int(ws.ws_xpixel); packet_put_int(ws.ws_ypixel); packet_send(); } else { packet_start(SSH_CMSG_WINDOW_SIZE); packet_put_int(ws.ws_row); packet_put_int(ws.ws_col); packet_put_int(ws.ws_xpixel); packet_put_int(ws.ws_ypixel); packet_send(); }}/* * Waits until the client can do something (some data becomes available on * one of the file descriptors). */static voidclient_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp, int *nallocp, int rekeying){ /* Add any selections by the channel mechanism. */ channel_prepare_select(readsetp, writesetp, maxfdp, nallocp, rekeying); if (!compat20) { /* Read from the connection, unless our buffers are full. */ if (buffer_len(&stdout_buffer) < buffer_high && buffer_len(&stderr_buffer) < buffer_high && channel_not_very_much_buffered_data()) FD_SET(connection_in, *readsetp); /* * Read from stdin, unless we have seen EOF or have very much * buffered data to send to the server. */ if (!stdin_eof && packet_not_very_much_data_to_write()) FD_SET(fileno(stdin), *readsetp); /* Select stdout/stderr if have data in buffer. */ if (buffer_len(&stdout_buffer) > 0) FD_SET(fileno(stdout), *writesetp); if (buffer_len(&stderr_buffer) > 0) FD_SET(fileno(stderr), *writesetp); } else { /* channel_prepare_select could have closed the last channel */ if (session_closed && !channel_still_open() && !packet_have_data_to_write()) { /* clear mask since we did not call select() */ memset(*readsetp, 0, *nallocp); memset(*writesetp, 0, *nallocp); return; } else { FD_SET(connection_in, *readsetp); } } /* Select server connection if have data to write to the server. */ if (packet_have_data_to_write()) FD_SET(connection_out, *writesetp); /* * Wait for something to happen. This will suspend the process until * some selected descriptor can be read, written, or has some other * event pending. Note: if you want to implement SSH_MSG_IGNORE * messages to fool traffic analysis, this might be the place to do * it: just have a random timeout for the select, and send a random * SSH_MSG_IGNORE packet when the timeout expires. */ if (select((*maxfdp)+1, *readsetp, *writesetp, NULL, NULL) < 0) { char buf[100]; /* * We have to clear the select masks, because we return. * We have to return, because the mainloop checks for the flags * set by the signal handlers. */ memset(*readsetp, 0, *nallocp); memset(*writesetp, 0, *nallocp); if (errno == EINTR) return; /* Note: we might still have data in the buffers. */ snprintf(buf, sizeof buf, "select: %s\r\n", strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; }}static voidclient_suspend_self(Buffer *bin, Buffer *bout, Buffer *berr){ struct winsize oldws, newws; /* Flush stdout and stderr buffers. */ if (buffer_len(bout) > 0) atomicio(write, fileno(stdout), buffer_ptr(bout), buffer_len(bout)); if (buffer_len(berr) > 0) atomicio(write, fileno(stderr), buffer_ptr(berr), buffer_len(berr)); leave_raw_mode(); /* * Free (and clear) the buffer to reduce the amount of data that gets * written to swap. */ buffer_free(bin); buffer_free(bout); buffer_free(berr); /* Save old window size. */ ioctl(fileno(stdin), TIOCGWINSZ, &oldws); /* Send the suspend signal to the program itself. */ kill(getpid(), SIGTSTP); /* Check if the window size has changed. */ if (ioctl(fileno(stdin), TIOCGWINSZ, &newws) >= 0 && (oldws.ws_row != newws.ws_row || oldws.ws_col != newws.ws_col || oldws.ws_xpixel != newws.ws_xpixel || oldws.ws_ypixel != newws.ws_ypixel)) received_window_change_signal = 1; /* OK, we have been continued by the user. Reinitialize buffers. */ buffer_init(bin); buffer_init(bout); buffer_init(berr); enter_raw_mode();}static voidclient_process_net_input(fd_set * readset){ int len; char buf[8192]; /* * Read input from the server, and add any such data to the buffer of * the packet subsystem. */ if (FD_ISSET(connection_in, readset)) { /* Read as much as possible. */ len = read(connection_in, buf, sizeof(buf)); if (len == 0) { /* Received EOF. The remote host has closed the connection. */ snprintf(buf, sizeof buf, "Connection to %.300s closed by remote host.\r\n", host); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; return; } /* * There is a kernel bug on Solaris that causes select to * sometimes wake up even though there is no data available. */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -