version 1.65, 2017/04/30 23:28:42 |
version 1.66, 2017/09/12 06:32:07 |
|
|
#include <stdarg.h> |
#include <stdarg.h> |
|
|
#include "ssh2.h" |
#include "ssh2.h" |
#include "buffer.h" |
#include "sshbuf.h" |
|
#include "ssherr.h" |
#include "packet.h" |
#include "packet.h" |
#include "channels.h" |
#include "channels.h" |
#include "compat.h" |
#include "compat.h" |
|
|
/* |
/* |
* ACTIONS: should never update the channel states |
* ACTIONS: should never update the channel states |
*/ |
*/ |
static void chan_send_eof2(Channel *); |
static void chan_send_eof2(struct ssh *, Channel *); |
static void chan_send_eow2(Channel *); |
static void chan_send_eow2(struct ssh *, Channel *); |
|
|
/* helper */ |
/* helper */ |
static void chan_shutdown_write(Channel *); |
static void chan_shutdown_write(struct ssh *, Channel *); |
static void chan_shutdown_read(Channel *); |
static void chan_shutdown_read(struct ssh *, Channel *); |
|
|
static char *ostates[] = { "open", "drain", "wait_ieof", "closed" }; |
static const char *ostates[] = { "open", "drain", "wait_ieof", "closed" }; |
static char *istates[] = { "open", "drain", "wait_oclose", "closed" }; |
static const char *istates[] = { "open", "drain", "wait_oclose", "closed" }; |
|
|
static void |
static void |
chan_set_istate(Channel *c, u_int next) |
chan_set_istate(Channel *c, u_int next) |
|
|
} |
} |
|
|
void |
void |
chan_read_failed(Channel *c) |
chan_read_failed(struct ssh *ssh, Channel *c) |
{ |
{ |
debug2("channel %d: read failed", c->self); |
debug2("channel %d: read failed", c->self); |
switch (c->istate) { |
switch (c->istate) { |
case CHAN_INPUT_OPEN: |
case CHAN_INPUT_OPEN: |
chan_shutdown_read(c); |
chan_shutdown_read(ssh, c); |
chan_set_istate(c, CHAN_INPUT_WAIT_DRAIN); |
chan_set_istate(c, CHAN_INPUT_WAIT_DRAIN); |
break; |
break; |
default: |
default: |
|
|
} |
} |
|
|
void |
void |
chan_ibuf_empty(Channel *c) |
chan_ibuf_empty(struct ssh *ssh, Channel *c) |
{ |
{ |
debug2("channel %d: ibuf empty", c->self); |
debug2("channel %d: ibuf empty", c->self); |
if (buffer_len(&c->input)) { |
if (sshbuf_len(c->input)) { |
error("channel %d: chan_ibuf_empty for non empty buffer", |
error("channel %d: chan_ibuf_empty for non empty buffer", |
c->self); |
c->self); |
return; |
return; |
|
|
switch (c->istate) { |
switch (c->istate) { |
case CHAN_INPUT_WAIT_DRAIN: |
case CHAN_INPUT_WAIT_DRAIN: |
if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL))) |
if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL))) |
chan_send_eof2(c); |
chan_send_eof2(ssh, c); |
chan_set_istate(c, CHAN_INPUT_CLOSED); |
chan_set_istate(c, CHAN_INPUT_CLOSED); |
break; |
break; |
default: |
default: |
|
|
} |
} |
|
|
void |
void |
chan_obuf_empty(Channel *c) |
chan_obuf_empty(struct ssh *ssh, Channel *c) |
{ |
{ |
debug2("channel %d: obuf empty", c->self); |
debug2("channel %d: obuf empty", c->self); |
if (buffer_len(&c->output)) { |
if (sshbuf_len(c->output)) { |
error("channel %d: chan_obuf_empty for non empty buffer", |
error("channel %d: chan_obuf_empty for non empty buffer", |
c->self); |
c->self); |
return; |
return; |
} |
} |
switch (c->ostate) { |
switch (c->ostate) { |
case CHAN_OUTPUT_WAIT_DRAIN: |
case CHAN_OUTPUT_WAIT_DRAIN: |
chan_shutdown_write(c); |
chan_shutdown_write(ssh, c); |
chan_set_ostate(c, CHAN_OUTPUT_CLOSED); |
chan_set_ostate(c, CHAN_OUTPUT_CLOSED); |
break; |
break; |
default: |
default: |
|
|
} |
} |
|
|
void |
void |
chan_rcvd_eow(Channel *c) |
chan_rcvd_eow(struct ssh *ssh, Channel *c) |
{ |
{ |
debug2("channel %d: rcvd eow", c->self); |
debug2("channel %d: rcvd eow", c->self); |
switch (c->istate) { |
switch (c->istate) { |
case CHAN_INPUT_OPEN: |
case CHAN_INPUT_OPEN: |
chan_shutdown_read(c); |
chan_shutdown_read(ssh, c); |
chan_set_istate(c, CHAN_INPUT_CLOSED); |
chan_set_istate(c, CHAN_INPUT_CLOSED); |
break; |
break; |
} |
} |
} |
} |
|
|
static void |
static void |
chan_send_eof2(Channel *c) |
chan_send_eof2(struct ssh *ssh, Channel *c) |
{ |
{ |
|
int r; |
|
|
debug2("channel %d: send eof", c->self); |
debug2("channel %d: send eof", c->self); |
switch (c->istate) { |
switch (c->istate) { |
case CHAN_INPUT_WAIT_DRAIN: |
case CHAN_INPUT_WAIT_DRAIN: |
packet_start(SSH2_MSG_CHANNEL_EOF); |
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_EOF)) != 0 || |
packet_put_int(c->remote_id); |
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
packet_send(); |
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: send CHANNEL_EOF: %s", __func__, ssh_err(r)); |
c->flags |= CHAN_EOF_SENT; |
c->flags |= CHAN_EOF_SENT; |
break; |
break; |
default: |
default: |
|
|
} |
} |
|
|
static void |
static void |
chan_send_close2(Channel *c) |
chan_send_close2(struct ssh *ssh, Channel *c) |
{ |
{ |
|
int r; |
|
|
debug2("channel %d: send close", c->self); |
debug2("channel %d: send close", c->self); |
if (c->ostate != CHAN_OUTPUT_CLOSED || |
if (c->ostate != CHAN_OUTPUT_CLOSED || |
c->istate != CHAN_INPUT_CLOSED) { |
c->istate != CHAN_INPUT_CLOSED) { |
|
|
} else if (c->flags & CHAN_CLOSE_SENT) { |
} else if (c->flags & CHAN_CLOSE_SENT) { |
error("channel %d: already sent close", c->self); |
error("channel %d: already sent close", c->self); |
} else { |
} else { |
packet_start(SSH2_MSG_CHANNEL_CLOSE); |
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_CLOSE)) != 0 || |
packet_put_int(c->remote_id); |
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
packet_send(); |
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: send CHANNEL_EOF: %s", __func__, ssh_err(r)); |
c->flags |= CHAN_CLOSE_SENT; |
c->flags |= CHAN_CLOSE_SENT; |
} |
} |
} |
} |
|
|
static void |
static void |
chan_send_eow2(Channel *c) |
chan_send_eow2(struct ssh *ssh, Channel *c) |
{ |
{ |
|
int r; |
|
|
debug2("channel %d: send eow", c->self); |
debug2("channel %d: send eow", c->self); |
if (c->ostate == CHAN_OUTPUT_CLOSED) { |
if (c->ostate == CHAN_OUTPUT_CLOSED) { |
error("channel %d: must not sent eow on closed output", |
error("channel %d: must not sent eow on closed output", |
|
|
} |
} |
if (!(datafellows & SSH_NEW_OPENSSH)) |
if (!(datafellows & SSH_NEW_OPENSSH)) |
return; |
return; |
packet_start(SSH2_MSG_CHANNEL_REQUEST); |
if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_REQUEST)) != 0 || |
packet_put_int(c->remote_id); |
(r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || |
packet_put_cstring("eow@openssh.com"); |
(r = sshpkt_put_cstring(ssh, "eow@openssh.com")) != 0 || |
packet_put_char(0); |
(r = sshpkt_put_u8(ssh, 0)) != 0 || |
packet_send(); |
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: send CHANNEL_EOF: %s", __func__, ssh_err(r)); |
} |
} |
|
|
/* shared */ |
/* shared */ |
|
|
void |
void |
chan_rcvd_ieof(Channel *c) |
chan_rcvd_ieof(struct ssh *ssh, Channel *c) |
{ |
{ |
debug2("channel %d: rcvd eof", c->self); |
debug2("channel %d: rcvd eof", c->self); |
c->flags |= CHAN_EOF_RCVD; |
c->flags |= CHAN_EOF_RCVD; |
if (c->ostate == CHAN_OUTPUT_OPEN) |
if (c->ostate == CHAN_OUTPUT_OPEN) |
chan_set_ostate(c, CHAN_OUTPUT_WAIT_DRAIN); |
chan_set_ostate(c, CHAN_OUTPUT_WAIT_DRAIN); |
if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN && |
if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN && |
buffer_len(&c->output) == 0 && |
sshbuf_len(c->output) == 0 && |
!CHANNEL_EFD_OUTPUT_ACTIVE(c)) |
!CHANNEL_EFD_OUTPUT_ACTIVE(c)) |
chan_obuf_empty(c); |
chan_obuf_empty(ssh, c); |
} |
} |
|
|
void |
void |
chan_rcvd_oclose(Channel *c) |
chan_rcvd_oclose(struct ssh *ssh, Channel *c) |
{ |
{ |
debug2("channel %d: rcvd close", c->self); |
debug2("channel %d: rcvd close", c->self); |
if (!(c->flags & CHAN_LOCAL)) { |
if (!(c->flags & CHAN_LOCAL)) { |
|
|
} |
} |
switch (c->istate) { |
switch (c->istate) { |
case CHAN_INPUT_OPEN: |
case CHAN_INPUT_OPEN: |
chan_shutdown_read(c); |
chan_shutdown_read(ssh, c); |
chan_set_istate(c, CHAN_INPUT_CLOSED); |
chan_set_istate(c, CHAN_INPUT_CLOSED); |
break; |
break; |
case CHAN_INPUT_WAIT_DRAIN: |
case CHAN_INPUT_WAIT_DRAIN: |
if (!(c->flags & CHAN_LOCAL)) |
if (!(c->flags & CHAN_LOCAL)) |
chan_send_eof2(c); |
chan_send_eof2(ssh, c); |
chan_set_istate(c, CHAN_INPUT_CLOSED); |
chan_set_istate(c, CHAN_INPUT_CLOSED); |
break; |
break; |
} |
} |
} |
} |
|
|
void |
void |
chan_write_failed(Channel *c) |
chan_write_failed(struct ssh *ssh, Channel *c) |
{ |
{ |
debug2("channel %d: write failed", c->self); |
debug2("channel %d: write failed", c->self); |
switch (c->ostate) { |
switch (c->ostate) { |
case CHAN_OUTPUT_OPEN: |
case CHAN_OUTPUT_OPEN: |
case CHAN_OUTPUT_WAIT_DRAIN: |
case CHAN_OUTPUT_WAIT_DRAIN: |
chan_shutdown_write(c); |
chan_shutdown_write(ssh, c); |
if (strcmp(c->ctype, "session") == 0) |
if (strcmp(c->ctype, "session") == 0) |
chan_send_eow2(c); |
chan_send_eow2(ssh, c); |
chan_set_ostate(c, CHAN_OUTPUT_CLOSED); |
chan_set_ostate(c, CHAN_OUTPUT_CLOSED); |
break; |
break; |
default: |
default: |
|
|
} |
} |
|
|
void |
void |
chan_mark_dead(Channel *c) |
chan_mark_dead(struct ssh *ssh, Channel *c) |
{ |
{ |
c->type = SSH_CHANNEL_ZOMBIE; |
c->type = SSH_CHANNEL_ZOMBIE; |
} |
} |
|
|
int |
int |
chan_is_dead(Channel *c, int do_send) |
chan_is_dead(struct ssh *ssh, Channel *c, int do_send) |
{ |
{ |
if (c->type == SSH_CHANNEL_ZOMBIE) { |
if (c->type == SSH_CHANNEL_ZOMBIE) { |
debug2("channel %d: zombie", c->self); |
debug2("channel %d: zombie", c->self); |
|
|
if ((datafellows & SSH_BUG_EXTEOF) && |
if ((datafellows & SSH_BUG_EXTEOF) && |
c->extended_usage == CHAN_EXTENDED_WRITE && |
c->extended_usage == CHAN_EXTENDED_WRITE && |
c->efd != -1 && |
c->efd != -1 && |
buffer_len(&c->extended) > 0) { |
sshbuf_len(c->extended) > 0) { |
debug2("channel %d: active efd: %d len %d", |
debug2("channel %d: active efd: %d len %zu", |
c->self, c->efd, buffer_len(&c->extended)); |
c->self, c->efd, sshbuf_len(c->extended)); |
return 0; |
return 0; |
} |
} |
if (c->flags & CHAN_LOCAL) { |
if (c->flags & CHAN_LOCAL) { |
|
|
} |
} |
if (!(c->flags & CHAN_CLOSE_SENT)) { |
if (!(c->flags & CHAN_CLOSE_SENT)) { |
if (do_send) { |
if (do_send) { |
chan_send_close2(c); |
chan_send_close2(ssh, c); |
} else { |
} else { |
/* channel would be dead if we sent a close */ |
/* channel would be dead if we sent a close */ |
if (c->flags & CHAN_CLOSE_RCVD) { |
if (c->flags & CHAN_CLOSE_RCVD) { |
|
|
|
|
/* helper */ |
/* helper */ |
static void |
static void |
chan_shutdown_write(Channel *c) |
chan_shutdown_write(struct ssh *ssh, Channel *c) |
{ |
{ |
buffer_clear(&c->output); |
sshbuf_reset(c->output); |
if (c->type == SSH_CHANNEL_LARVAL) |
if (c->type == SSH_CHANNEL_LARVAL) |
return; |
return; |
/* shutdown failure is allowed if write failed already */ |
/* shutdown failure is allowed if write failed already */ |
|
|
"shutdown() failed for fd %d: %.100s", |
"shutdown() failed for fd %d: %.100s", |
c->self, c->sock, strerror(errno)); |
c->self, c->sock, strerror(errno)); |
} else { |
} else { |
if (channel_close_fd(&c->wfd) < 0) |
if (channel_close_fd(ssh, &c->wfd) < 0) |
logit("channel %d: chan_shutdown_write: " |
logit("channel %d: chan_shutdown_write: " |
"close() failed for fd %d: %.100s", |
"close() failed for fd %d: %.100s", |
c->self, c->wfd, strerror(errno)); |
c->self, c->wfd, strerror(errno)); |
|
|
} |
} |
|
|
static void |
static void |
chan_shutdown_read(Channel *c) |
chan_shutdown_read(struct ssh *ssh, Channel *c) |
{ |
{ |
if (c->type == SSH_CHANNEL_LARVAL) |
if (c->type == SSH_CHANNEL_LARVAL) |
return; |
return; |
|
|
c->self, c->sock, c->istate, c->ostate, |
c->self, c->sock, c->istate, c->ostate, |
strerror(errno)); |
strerror(errno)); |
} else { |
} else { |
if (channel_close_fd(&c->rfd) < 0) |
if (channel_close_fd(ssh, &c->rfd) < 0) |
logit("channel %d: chan_shutdown_read: " |
logit("channel %d: chan_shutdown_read: " |
"close() failed for fd %d: %.100s", |
"close() failed for fd %d: %.100s", |
c->self, c->rfd, strerror(errno)); |
c->self, c->rfd, strerror(errno)); |