+ /*
+ * Start a shell or a remote command.
+ */
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+ ssh2_pkt_adduint32(mainchan->remoteid); /* recipient channel */
+ if (cfg.ssh_subsys) {
+ ssh2_pkt_addstring("subsystem");
+ ssh2_pkt_addbool(1); /* want reply */
+ ssh2_pkt_addstring(cfg.remote_cmd);
+ } else if (*cfg.remote_cmd) {
+ ssh2_pkt_addstring("exec");
+ ssh2_pkt_addbool(1); /* want reply */
+ ssh2_pkt_addstring(cfg.remote_cmd);
+ } else {
+ ssh2_pkt_addstring("shell");
+ ssh2_pkt_addbool(1); /* want reply */
+ }
+ ssh2_pkt_send();
+ do {
+ crWaitUntilV(ispkt);
+ if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+ unsigned i = ssh2_pkt_getuint32();
+ struct ssh_channel *c;
+ c = find234(ssh_channels, &i, ssh_channelfind);
+ if (!c)
+ continue; /* nonexistent channel */
+ c->v2.remwindow += ssh2_pkt_getuint32();
+ }
+ } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
+ if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
+ bombout(("Server got confused by shell/command request"));
+ crReturnV;
+ }
+ bombout(("Server refused to start a shell/command"));
+ crReturnV;
+ } else {
+ logevent("Started a shell/command");
+ }
+
+ ssh_state = SSH_STATE_SESSION;
+ if (size_needed)
+ ssh_size();
+ if (eof_needed)
+ ssh_special(TS_EOF);
+
+ /*
+ * Transfer data!
+ */
+ ldisc_send(NULL, 0); /* cause ldisc to notice changes */
+ ssh_send_ok = 1;
+ while (1) {
+ static int try_send;
+ crReturnV;
+ try_send = FALSE;
+ if (ispkt) {
+ if (pktin.type == SSH2_MSG_CHANNEL_DATA ||
+ pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
+ char *data;
+ int length;
+ unsigned i = ssh2_pkt_getuint32();
+ struct ssh_channel *c;
+ c = find234(ssh_channels, &i, ssh_channelfind);
+ if (!c)
+ continue; /* nonexistent channel */
+ if (pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
+ ssh2_pkt_getuint32() != SSH2_EXTENDED_DATA_STDERR)
+ continue; /* extended but not stderr */
+ ssh2_pkt_getstring(&data, &length);
+ if (data) {
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ from_backend(pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA,
+ data, length);
+ break;
+ case CHAN_X11:
+ x11_send(c->u.x11.s, data, length);
+ break;
+ }
+ /*
+ * Enlarge the window again at the remote
+ * side, just in case it ever runs down and
+ * they fail to send us any more data.
+ */
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_adduint32(length);
+ ssh2_pkt_send();
+ }
+ } else if (pktin.type == SSH2_MSG_DISCONNECT) {
+ ssh_state = SSH_STATE_CLOSED;
+ logevent("Received disconnect message");
+ crReturnV;
+ } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
+ continue; /* exit status et al; ignore (FIXME?) */
+ } else if (pktin.type == SSH2_MSG_CHANNEL_EOF) {
+ unsigned i = ssh2_pkt_getuint32();
+ struct ssh_channel *c;
+
+ c = find234(ssh_channels, &i, ssh_channelfind);
+ if (!c)
+ continue; /* nonexistent channel */
+
+ if (c->type == CHAN_X11) {
+ /*
+ * Remote EOF on an X11 channel means we should
+ * wrap up and close the channel ourselves.
+ */
+ x11_close(c->u.x11.s);
+ sshfwd_close(c);
+ }
+ } else if (pktin.type == SSH2_MSG_CHANNEL_CLOSE) {
+ unsigned i = ssh2_pkt_getuint32();
+ struct ssh_channel *c;
+ enum234 e;
+
+ c = find234(ssh_channels, &i, ssh_channelfind);
+ if (!c)
+ continue; /* nonexistent channel */
+ if (c->closes == 0) {
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_send();
+ }
+ /* Do pre-close processing on the channel. */
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ break; /* nothing to see here, move along */
+ case CHAN_X11:
+ break;
+ }
+ del234(ssh_channels, c);
+ sfree(c->v2.outbuffer);
+ sfree(c);
+
+ /*
+ * See if that was the last channel left open.
+ */
+ c = first234(ssh_channels, &e);
+ if (!c) {
+ logevent("All channels closed. Disconnecting");
+ ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+ ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
+ ssh2_pkt_addstring("All open channels closed");
+ ssh2_pkt_addstring("en"); /* language tag */
+ ssh2_pkt_send();
+ ssh_state = SSH_STATE_CLOSED;
+ crReturnV;
+ }
+ continue; /* remote sends close; ignore (FIXME) */
+ } else if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+ unsigned i = ssh2_pkt_getuint32();
+ struct ssh_channel *c;
+ c = find234(ssh_channels, &i, ssh_channelfind);
+ if (!c)
+ continue; /* nonexistent channel */
+ mainchan->v2.remwindow += ssh2_pkt_getuint32();
+ try_send = TRUE;
+ } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) {
+ char *type;
+ int typelen;
+ char *error = NULL;
+ struct ssh_channel *c;
+ ssh2_pkt_getstring(&type, &typelen);
+ c = smalloc(sizeof(struct ssh_channel));
+
+ if (typelen == 3 && !memcmp(type, "x11", 3)) {
+ if (!ssh_X11_fwd_enabled)
+ error = "X11 forwarding is not enabled";
+ else if ( x11_init(&c->u.x11.s, cfg.x11_display, c) != NULL ) {
+ error = "Unable to open an X11 connection";
+ } else {
+ c->type = CHAN_X11;
+ }
+ } else {
+ error = "Unsupported channel type requested";
+ }
+
+ c->remoteid = ssh2_pkt_getuint32();
+ if (error) {
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_adduint32(SSH2_OPEN_CONNECT_FAILED);
+ ssh2_pkt_addstring(error);
+ ssh2_pkt_addstring("en"); /* language tag */
+ ssh2_pkt_send();
+ sfree(c);
+ } else {
+ struct ssh_channel *d;
+ unsigned i;
+ enum234 e;
+
+ for (i=1, d = first234(ssh_channels, &e); d;
+ d = next234(&e)) {
+ if (d->localid > i)
+ break; /* found a free number */
+ i = d->localid + 1;
+ }
+ c->localid = i;
+ c->closes = 0;
+ c->v2.remwindow = ssh2_pkt_getuint32();
+ c->v2.remmaxpkt = ssh2_pkt_getuint32();
+ c->v2.outbuffer = NULL;
+ c->v2.outbuflen = c->v2.outbufsize = 0;
+ add234(ssh_channels, c);
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_adduint32(c->localid);
+ ssh2_pkt_adduint32(0x8000UL); /* our window size */
+ ssh2_pkt_adduint32(0x4000UL); /* our max pkt size */
+ ssh2_pkt_send();
+ }