+ if (ssh->pktin.type == SSH2_MSG_CHANNEL_DATA ||
+ ssh->pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
+ char *data;
+ int length;
+ unsigned i = ssh2_pkt_getuint32(ssh);
+ struct ssh_channel *c;
+ c = find234(ssh->channels, &i, ssh_channelfind);
+ if (!c)
+ continue; /* nonexistent channel */
+ if (ssh->pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
+ ssh2_pkt_getuint32(ssh) != SSH2_EXTENDED_DATA_STDERR)
+ continue; /* extended but not stderr */
+ ssh2_pkt_getstring(ssh, &data, &length);
+ if (data) {
+ int bufsize = 0;
+ c->v.v2.locwindow -= length;
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ bufsize =
+ from_backend(ssh->frontend, ssh->pktin.type ==
+ SSH2_MSG_CHANNEL_EXTENDED_DATA,
+ data, length);
+ break;
+ case CHAN_X11:
+ bufsize = x11_send(c->u.x11.s, data, length);
+ break;
+ case CHAN_SOCKDATA:
+ bufsize = pfd_send(c->u.pfd.s, data, length);
+ break;
+ case CHAN_AGENT:
+ while (length > 0) {
+ if (c->u.a.lensofar < 4) {
+ int l = min(4 - c->u.a.lensofar, length);
+ memcpy(c->u.a.msglen + c->u.a.lensofar,
+ data, l);
+ data += l;
+ length -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == 4) {
+ c->u.a.totallen =
+ 4 + GET_32BIT(c->u.a.msglen);
+ c->u.a.message = snewn(c->u.a.totallen,
+ unsigned char);
+ memcpy(c->u.a.message, c->u.a.msglen, 4);
+ }
+ if (c->u.a.lensofar >= 4 && length > 0) {
+ int l =
+ min(c->u.a.totallen - c->u.a.lensofar,
+ length);
+ memcpy(c->u.a.message + c->u.a.lensofar,
+ data, l);
+ data += l;
+ length -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == c->u.a.totallen) {
+ void *reply;
+ int replylen;
+ if (agent_query(c->u.a.message,
+ c->u.a.totallen,
+ &reply, &replylen,
+ ssh_agentf_callback, c))
+ ssh_agentf_callback(c, reply, replylen);
+ sfree(c->u.a.message);
+ c->u.a.lensofar = 0;
+ }
+ }
+ bufsize = 0;
+ break;
+ }
+ /*
+ * If we are not buffering too much data,
+ * enlarge the window again at the remote side.
+ */
+ if (bufsize < OUR_V2_WINSIZE)
+ ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
+ }
+ } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_EOF) {
+ unsigned i = ssh2_pkt_getuint32(ssh);
+ 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 (c->type == CHAN_AGENT) {
+ sshfwd_close(c);
+ } else if (c->type == CHAN_SOCKDATA) {
+ pfd_close(c->u.pfd.s);
+ sshfwd_close(c);
+ }
+ } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_CLOSE) {
+ unsigned i = ssh2_pkt_getuint32(ssh);
+ struct ssh_channel *c;
+
+ c = find234(ssh->channels, &i, ssh_channelfind);
+ if (!c || ((int)c->remoteid) == -1) {
+ bombout(("Received CHANNEL_CLOSE for %s channel %d\n",
+ c ? "half-open" : "nonexistent", i));
+ crStopV;
+ }
+ /* Do pre-close processing on the channel. */
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ break; /* nothing to see here, move along */
+ case CHAN_X11:
+ if (c->u.x11.s != NULL)
+ x11_close(c->u.x11.s);
+ sshfwd_close(c);
+ break;
+ case CHAN_AGENT:
+ sshfwd_close(c);
+ break;
+ case CHAN_SOCKDATA:
+ if (c->u.pfd.s != NULL)
+ pfd_close(c->u.pfd.s);
+ sshfwd_close(c);
+ break;
+ }
+ if (c->closes == 0) {
+ ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_CLOSE);
+ ssh2_pkt_adduint32(ssh, c->remoteid);
+ ssh2_pkt_send(ssh);
+ }
+ del234(ssh->channels, c);
+ bufchain_clear(&c->v.v2.outbuffer);
+ sfree(c);
+
+ /*
+ * See if that was the last channel left open.
+ */
+ if (count234(ssh->channels) == 0) {
+ logevent("All channels closed. Disconnecting");
+#if 0
+ /*
+ * We used to send SSH_MSG_DISCONNECT here,
+ * because I'd believed that _every_ conforming
+ * SSH2 connection had to end with a disconnect
+ * being sent by at least one side; apparently
+ * I was wrong and it's perfectly OK to
+ * unceremoniously slam the connection shut
+ * when you're done, and indeed OpenSSH feels
+ * this is more polite than sending a
+ * DISCONNECT. So now we don't.
+ */
+ ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
+ ssh2_pkt_adduint32(ssh, SSH2_DISCONNECT_BY_APPLICATION);
+ ssh2_pkt_addstring(ssh, "All open channels closed");
+ ssh2_pkt_addstring(ssh, "en"); /* language tag */
+ ssh2_pkt_send(ssh);
+#endif
+ ssh_closing((Plug)ssh, NULL, 0, 0);
+ crStopV;
+ }
+ continue; /* remote sends close; ignore (FIXME) */
+ } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+ unsigned i = ssh2_pkt_getuint32(ssh);
+ struct ssh_channel *c;
+ c = find234(ssh->channels, &i, ssh_channelfind);
+ if (!c || c->closes)
+ continue; /* nonexistent or closing channel */
+ c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
+ s->try_send = TRUE;
+ } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+ unsigned i = ssh2_pkt_getuint32(ssh);
+ struct ssh_channel *c;
+ c = find234(ssh->channels, &i, ssh_channelfind);
+ if (!c)
+ continue; /* nonexistent channel */
+ if (c->type != CHAN_SOCKDATA_DORMANT)
+ continue; /* dunno why they're confirming this */
+ c->remoteid = ssh2_pkt_getuint32(ssh);
+ c->type = CHAN_SOCKDATA;
+ c->v.v2.remwindow = ssh2_pkt_getuint32(ssh);
+ c->v.v2.remmaxpkt = ssh2_pkt_getuint32(ssh);
+ if (c->u.pfd.s)
+ pfd_confirm(c->u.pfd.s);
+ if (c->closes) {
+ /*
+ * We have a pending close on this channel,
+ * which we decided on before the server acked
+ * the channel open. So now we know the
+ * remoteid, we can close it again.
+ */
+ ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_CLOSE);
+ ssh2_pkt_adduint32(ssh, c->remoteid);
+ ssh2_pkt_send(ssh);
+ }
+ } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
+ unsigned i = ssh2_pkt_getuint32(ssh);
+ struct ssh_channel *c;
+ c = find234(ssh->channels, &i, ssh_channelfind);
+ if (!c)
+ continue; /* nonexistent channel */
+ if (c->type != CHAN_SOCKDATA_DORMANT)
+ continue; /* dunno why they're failing this */
+
+ logevent("Forwarded connection refused by server");
+
+ pfd_close(c->u.pfd.s);
+
+ del234(ssh->channels, c);
+ sfree(c);
+ } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
+ unsigned localid;
+ char *type;
+ int typelen, want_reply;
+ struct ssh_channel *c;
+
+ localid = ssh2_pkt_getuint32(ssh);
+ ssh2_pkt_getstring(ssh, &type, &typelen);
+ want_reply = ssh2_pkt_getbool(ssh);
+
+ /*
+ * First, check that the channel exists. Otherwise,
+ * we can instantly disconnect with a rude message.
+ */
+ c = find234(ssh->channels, &localid, ssh_channelfind);
+ if (!c) {
+ char buf[80];
+ sprintf(buf, "Received channel request for nonexistent"
+ " channel %d", localid);
+ logevent(buf);
+ ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
+ ssh2_pkt_adduint32(ssh, SSH2_DISCONNECT_BY_APPLICATION);
+ ssh2_pkt_addstring(ssh, buf);
+ ssh2_pkt_addstring(ssh, "en"); /* language tag */
+ ssh2_pkt_send(ssh);
+ connection_fatal(ssh->frontend, "%s", buf);
+ ssh_closing((Plug)ssh, NULL, 0, 0);
+ crStopV;
+ }
+
+ /*
+ * Having got the channel number, we now look at
+ * the request type string to see if it's something
+ * we recognise.
+ */
+ if (typelen == 11 && !memcmp(type, "exit-status", 11) &&
+ c == ssh->mainchan) {
+ /* We recognise "exit-status" on the primary channel. */
+ char buf[100];
+ ssh->exitcode = ssh2_pkt_getuint32(ssh);
+ sprintf(buf, "Server sent command exit status %d",
+ ssh->exitcode);
+ logevent(buf);
+ if (want_reply) {
+ ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_SUCCESS);
+ ssh2_pkt_adduint32(ssh, c->remoteid);
+ ssh2_pkt_send(ssh);
+ }
+ } else {
+ /*
+ * This is a channel request we don't know
+ * about, so we now either ignore the request
+ * or respond with CHANNEL_FAILURE, depending
+ * on want_reply.
+ */
+ if (want_reply) {
+ ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_FAILURE);
+ ssh2_pkt_adduint32(ssh, c->remoteid);
+ ssh2_pkt_send(ssh);
+ }
+ }
+ } else if (ssh->pktin.type == SSH2_MSG_GLOBAL_REQUEST) {
+ char *type;
+ int typelen, want_reply;
+
+ ssh2_pkt_getstring(ssh, &type, &typelen);
+ want_reply = ssh2_pkt_getbool(ssh);
+
+ /*
+ * We currently don't support any global requests
+ * at all, so we either ignore the request or
+ * respond with REQUEST_FAILURE, depending on
+ * want_reply.
+ */
+ if (want_reply) {
+ ssh2_pkt_init(ssh, SSH2_MSG_REQUEST_FAILURE);
+ ssh2_pkt_send(ssh);
+ }
+ } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN) {
+ char *type;
+ int typelen;
+ char *peeraddr;
+ int peeraddrlen;
+ int peerport;
+ char *error = NULL;
+ struct ssh_channel *c;
+ unsigned remid, winsize, pktsize;
+ ssh2_pkt_getstring(ssh, &type, &typelen);
+ c = snew(struct ssh_channel);
+ c->ssh = ssh;
+
+ remid = ssh2_pkt_getuint32(ssh);
+ winsize = ssh2_pkt_getuint32(ssh);
+ pktsize = ssh2_pkt_getuint32(ssh);
+
+ if (typelen == 3 && !memcmp(type, "x11", 3)) {
+ char *addrstr;
+
+ ssh2_pkt_getstring(ssh, &peeraddr, &peeraddrlen);
+ addrstr = snewn(peeraddrlen+1, char);
+ memcpy(addrstr, peeraddr, peeraddrlen);
+ peeraddr[peeraddrlen] = '\0';
+ peerport = ssh2_pkt_getuint32(ssh);
+
+ if (!ssh->X11_fwd_enabled)
+ error = "X11 forwarding is not enabled";
+ else if (x11_init(&c->u.x11.s, ssh->cfg.x11_display, c,
+ ssh->x11auth, addrstr, peerport,
+ &ssh->cfg) != NULL) {
+ error = "Unable to open an X11 connection";
+ } else {
+ c->type = CHAN_X11;
+ }
+
+ sfree(addrstr);
+ } else if (typelen == 15 &&
+ !memcmp(type, "forwarded-tcpip", 15)) {
+ struct ssh_rportfwd pf, *realpf;
+ char *dummy;
+ int dummylen;
+ ssh2_pkt_getstring(ssh, &dummy, &dummylen);/* skip address */
+ pf.sport = ssh2_pkt_getuint32(ssh);
+ ssh2_pkt_getstring(ssh, &peeraddr, &peeraddrlen);
+ peerport = ssh2_pkt_getuint32(ssh);
+ realpf = find234(ssh->rportfwds, &pf, NULL);
+ if (realpf == NULL) {
+ error = "Remote port is not recognised";
+ } else {
+ const char *e = pfd_newconnect(&c->u.pfd.s,
+ realpf->dhost,
+ realpf->dport, c,
+ &ssh->cfg);
+ logeventf(ssh, "Received remote port open request"
+ " for %s:%d", realpf->dhost, realpf->dport);
+ if (e != NULL) {
+ logeventf(ssh, "Port open failed: %s", e);
+ error = "Port open failed";
+ } else {
+ logevent("Forwarded port opened successfully");
+ c->type = CHAN_SOCKDATA;
+ }
+ }
+ } else if (typelen == 22 &&
+ !memcmp(type, "auth-agent@openssh.com", 3)) {
+ if (!ssh->agentfwd_enabled)
+ error = "Agent forwarding is not enabled";
+ else {
+ c->type = CHAN_AGENT; /* identify channel type */
+ c->u.a.lensofar = 0;
+ }
+ } else {
+ error = "Unsupported channel type requested";
+ }
+
+ c->remoteid = remid;
+ if (error) {
+ ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE);
+ ssh2_pkt_adduint32(ssh, c->remoteid);
+ ssh2_pkt_adduint32(ssh, SSH2_OPEN_CONNECT_FAILED);
+ ssh2_pkt_addstring(ssh, error);
+ ssh2_pkt_addstring(ssh, "en"); /* language tag */
+ ssh2_pkt_send(ssh);
+ sfree(c);
+ } else {
+ c->localid = alloc_channel_id(ssh);
+ c->closes = 0;
+ c->v.v2.locwindow = OUR_V2_WINSIZE;
+ c->v.v2.remwindow = winsize;
+ c->v.v2.remmaxpkt = pktsize;
+ bufchain_init(&c->v.v2.outbuffer);
+ add234(ssh->channels, c);
+ ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+ ssh2_pkt_adduint32(ssh, c->remoteid);
+ ssh2_pkt_adduint32(ssh, c->localid);
+ ssh2_pkt_adduint32(ssh, c->v.v2.locwindow);
+ ssh2_pkt_adduint32(ssh, 0x4000UL); /* our max pkt size */
+ ssh2_pkt_send(ssh);
+ }