version 1.194, 2008/05/19 20:53:52 |
version 1.195, 2008/06/12 03:40:52 |
|
|
|
|
/* Common data for the client loop code. */ |
/* Common data for the client loop code. */ |
static volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */ |
static volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */ |
static int escape_char; /* Escape character. */ |
static int escape_char1; /* Escape character. (proto1 only) */ |
static int escape_pending; /* Last character was the escape character */ |
static int escape_pending1; /* Last character was an escape (proto1 only) */ |
static int last_was_cr; /* Last character was a newline. */ |
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 exit_status; /* Used to store the exit status of the command. */ |
static int stdin_eof; /* EOF has been encountered on standard error. */ |
static int stdin_eof; /* EOF has been encountered on standard error. */ |
|
|
static void client_init_dispatch(void); |
static void client_init_dispatch(void); |
int session_ident = -1; |
int session_ident = -1; |
|
|
|
/* Track escape per proto2 channel */ |
|
struct escape_filter_ctx { |
|
int escape_pending; |
|
int escape_char; |
|
}; |
|
|
|
/* Context for channel confirmation replies */ |
struct channel_reply_ctx { |
struct channel_reply_ctx { |
const char *request_type; |
const char *request_type; |
int id, do_close; |
int id, do_close; |
|
|
* and also process it as an escape character if |
* and also process it as an escape character if |
* appropriate. |
* appropriate. |
*/ |
*/ |
if ((u_char) buf[0] == escape_char) |
if ((u_char) buf[0] == escape_char1) |
escape_pending = 1; |
escape_pending1 = 1; |
else |
else |
buffer_append(&stdin_buffer, buf, 1); |
buffer_append(&stdin_buffer, buf, 1); |
} |
} |
|
|
xfree(fwd.connect_host); |
xfree(fwd.connect_host); |
} |
} |
|
|
/* process the characters one by one */ |
/* |
|
* Process the characters one by one, call with c==NULL for proto1 case. |
|
*/ |
static int |
static int |
process_escapes(Buffer *bin, Buffer *bout, Buffer *berr, char *buf, int len) |
process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr, |
|
char *buf, int len) |
{ |
{ |
char string[1024]; |
char string[1024]; |
pid_t pid; |
pid_t pid; |
|
|
u_int i; |
u_int i; |
u_char ch; |
u_char ch; |
char *s; |
char *s; |
|
int *escape_pendingp, escape_char; |
|
struct escape_filter_ctx *efc; |
|
|
|
if (c == NULL) { |
|
escape_pendingp = &escape_pending1; |
|
escape_char = escape_char1; |
|
} else { |
|
if (c->filter_ctx == NULL) |
|
return 0; |
|
efc = (struct escape_filter_ctx *)c->filter_ctx; |
|
escape_pendingp = &efc->escape_pending; |
|
escape_char = efc->escape_char; |
|
} |
|
|
if (len <= 0) |
if (len <= 0) |
return (0); |
return (0); |
|
|
|
|
/* Get one character at a time. */ |
/* Get one character at a time. */ |
ch = buf[i]; |
ch = buf[i]; |
|
|
if (escape_pending) { |
if (*escape_pendingp) { |
/* We have previously seen an escape character. */ |
/* We have previously seen an escape character. */ |
/* Clear the flag now. */ |
/* Clear the flag now. */ |
escape_pending = 0; |
*escape_pendingp = 0; |
|
|
/* Process the escaped character. */ |
/* Process the escaped character. */ |
switch (ch) { |
switch (ch) { |
case '.': |
case '.': |
/* Terminate the connection. */ |
/* Terminate the connection. */ |
snprintf(string, sizeof string, "%c.\r\n", escape_char); |
snprintf(string, sizeof string, "%c.\r\n", |
|
escape_char); |
buffer_append(berr, string, strlen(string)); |
buffer_append(berr, string, strlen(string)); |
|
|
quit_pending = 1; |
if (c && c->ctl_fd != -1) { |
|
chan_read_failed(c); |
|
chan_write_failed(c); |
|
return 0; |
|
} else |
|
quit_pending = 1; |
return -1; |
return -1; |
|
|
case 'Z' - 64: |
case 'Z' - 64: |
|
/* XXX support this for mux clients */ |
|
if (c && c->ctl_fd != -1) { |
|
noescape: |
|
snprintf(string, sizeof string, |
|
"%c%c escape not available to " |
|
"multiplexed sessions\r\n", |
|
escape_char, ch); |
|
buffer_append(berr, string, |
|
strlen(string)); |
|
continue; |
|
} |
/* Suspend the program. */ |
/* Suspend the program. */ |
/* Print a message to that effect to the user. */ |
/* Print a message to that effect to the user. */ |
snprintf(string, sizeof string, "%c^Z [suspend ssh]\r\n", escape_char); |
snprintf(string, sizeof string, |
|
"%c^Z [suspend ssh]\r\n", escape_char); |
buffer_append(berr, string, strlen(string)); |
buffer_append(berr, string, strlen(string)); |
|
|
/* Restore terminal modes and suspend. */ |
/* Restore terminal modes and suspend. */ |
|
|
continue; |
continue; |
|
|
case '&': |
case '&': |
|
if (c && c->ctl_fd != -1) |
|
goto noescape; |
/* |
/* |
* Detach the program (continue to serve connections, |
* Detach the program (continue to serve connections, |
* but put in background and no more new connections). |
* but put in background and no more new connections). |
|
|
continue; |
continue; |
|
|
case '?': |
case '?': |
snprintf(string, sizeof string, |
if (c && c->ctl_fd != -1) { |
|
snprintf(string, sizeof string, |
"%c?\r\n\ |
"%c?\r\n\ |
Supported escape sequences:\r\n\ |
Supported escape sequences:\r\n\ |
%c. - terminate connection\r\n\ |
%c. - terminate session\r\n\ |
%cB - send a BREAK to the remote system\r\n\ |
%cB - send a BREAK to the remote system\r\n\ |
%cC - open a command line\r\n\ |
%cC - open a command line\r\n\ |
%cR - Request rekey (SSH protocol 2 only)\r\n\ |
%cR - Request rekey (SSH protocol 2 only)\r\n\ |
%c^Z - suspend ssh\r\n\ |
%c# - list forwarded connections\r\n\ |
%c# - list forwarded connections\r\n\ |
%c? - this message\r\n\ |
%c& - background ssh (when waiting for connections to terminate)\r\n\ |
%c%c - send the escape character by typing it twice\r\n\ |
%c? - this message\r\n\ |
|
%c%c - send the escape character by typing it twice\r\n\ |
|
(Note that escapes are only recognized immediately after newline.)\r\n", |
(Note that escapes are only recognized immediately after newline.)\r\n", |
escape_char, escape_char, escape_char, escape_char, |
escape_char, escape_char, |
escape_char, escape_char, escape_char, escape_char, |
escape_char, escape_char, |
escape_char, escape_char, escape_char); |
escape_char, escape_char, |
|
escape_char, escape_char, |
|
escape_char); |
|
} else { |
|
snprintf(string, sizeof string, |
|
"%c?\r\n\ |
|
Supported escape sequences:\r\n\ |
|
%c. - terminate connection (and any multiplexed sessions)\r\n\ |
|
%cB - send a BREAK to the remote system\r\n\ |
|
%cC - open a command line\r\n\ |
|
%cR - Request rekey (SSH protocol 2 only)\r\n\ |
|
%c^Z - suspend ssh\r\n\ |
|
%c# - list forwarded connections\r\n\ |
|
%c& - background ssh (when waiting for connections to terminate)\r\n\ |
|
%c? - this message\r\n\ |
|
%c%c - send the escape character by typing it twice\r\n\ |
|
(Note that escapes are only recognized immediately after newline.)\r\n", |
|
escape_char, escape_char, |
|
escape_char, escape_char, |
|
escape_char, escape_char, |
|
escape_char, escape_char, |
|
escape_char, escape_char, |
|
escape_char); |
|
} |
buffer_append(berr, string, strlen(string)); |
buffer_append(berr, string, strlen(string)); |
continue; |
continue; |
|
|
case '#': |
case '#': |
snprintf(string, sizeof string, "%c#\r\n", escape_char); |
snprintf(string, sizeof string, "%c#\r\n", |
|
escape_char); |
buffer_append(berr, string, strlen(string)); |
buffer_append(berr, string, strlen(string)); |
s = channel_open_message(); |
s = channel_open_message(); |
buffer_append(berr, s, strlen(s)); |
buffer_append(berr, s, strlen(s)); |
|
|
*/ |
*/ |
if (last_was_cr && ch == escape_char) { |
if (last_was_cr && ch == escape_char) { |
/* It is. Set the flag and continue to next character. */ |
/* It is. Set the flag and continue to next character. */ |
escape_pending = 1; |
*escape_pendingp = 1; |
continue; |
continue; |
} |
} |
} |
} |
|
|
packet_start(SSH_CMSG_EOF); |
packet_start(SSH_CMSG_EOF); |
packet_send(); |
packet_send(); |
} |
} |
} else if (escape_char == SSH_ESCAPECHAR_NONE) { |
} else if (escape_char1 == SSH_ESCAPECHAR_NONE) { |
/* |
/* |
* Normal successful read, and no escape character. |
* Normal successful read, and no escape character. |
* Just append the data to buffer. |
* Just append the data to buffer. |
|
|
* Normal, successful read. But we have an escape character |
* Normal, successful read. But we have an escape character |
* and have to process the characters one by one. |
* and have to process the characters one by one. |
*/ |
*/ |
if (process_escapes(&stdin_buffer, &stdout_buffer, |
if (process_escapes(NULL, &stdin_buffer, |
&stderr_buffer, buf, len) == -1) |
&stdout_buffer, &stderr_buffer, buf, len) == -1) |
return; |
return; |
} |
} |
} |
} |
|
|
|
|
/* scan buf[] for '~' before sending data to the peer */ |
/* scan buf[] for '~' before sending data to the peer */ |
|
|
static int |
/* Helper: allocate a new escape_filter_ctx and fill in its escape char */ |
simple_escape_filter(Channel *c, char *buf, int len) |
void * |
|
client_new_escape_filter_ctx(int escape_char) |
{ |
{ |
|
struct escape_filter_ctx *ret; |
|
|
|
ret = xmalloc(sizeof(*ret)); |
|
ret->escape_pending = 0; |
|
ret->escape_char = escape_char; |
|
return (void *)ret; |
|
} |
|
|
|
int |
|
client_simple_escape_filter(Channel *c, char *buf, int len) |
|
{ |
if (c->extended_usage != CHAN_EXTENDED_WRITE) |
if (c->extended_usage != CHAN_EXTENDED_WRITE) |
return 0; |
return 0; |
|
|
return process_escapes(&c->input, &c->output, &c->extended, buf, len); |
return process_escapes(c, &c->input, &c->output, &c->extended, |
|
buf, len); |
} |
} |
|
|
static void |
static void |
|
|
start_time = get_current_time(); |
start_time = get_current_time(); |
|
|
/* Initialize variables. */ |
/* Initialize variables. */ |
escape_pending = 0; |
escape_pending1 = 0; |
last_was_cr = 1; |
last_was_cr = 1; |
exit_status = -1; |
exit_status = -1; |
stdin_eof = 0; |
stdin_eof = 0; |
|
|
stdout_bytes = 0; |
stdout_bytes = 0; |
stderr_bytes = 0; |
stderr_bytes = 0; |
quit_pending = 0; |
quit_pending = 0; |
escape_char = escape_char_arg; |
escape_char1 = escape_char_arg; |
|
|
/* Initialize buffers. */ |
/* Initialize buffers. */ |
buffer_init(&stdin_buffer); |
buffer_init(&stdin_buffer); |
|
|
|
|
if (compat20) { |
if (compat20) { |
session_ident = ssh2_chan_id; |
session_ident = ssh2_chan_id; |
if (escape_char != SSH_ESCAPECHAR_NONE) |
if (escape_char_arg != SSH_ESCAPECHAR_NONE) |
channel_register_filter(session_ident, |
channel_register_filter(session_ident, |
simple_escape_filter, NULL); |
client_simple_escape_filter, NULL, |
|
client_new_escape_filter_ctx(escape_char_arg)); |
if (session_ident != -1) |
if (session_ident != -1) |
channel_register_cleanup(session_ident, |
channel_register_cleanup(session_ident, |
client_channel_closed, 0); |
client_channel_closed, 0); |