The unified diff between revisions [cc6095ce..] and [e73ee8f7..] is displayed below. It can also be downloaded as a raw diff.

#
#
# patch "channel.h"
#  from [599d696acdeab500ef382b2a738b887e41e02afb]
#    to [aa5deb13fb94e010f897c9e4ac68862a81c26e57]
#
# patch "common-channel.c"
#  from [6f44bbe2729e12dd2854d0bd00a965981774fe05]
#    to [8fa86543a791c4d69addf71cdbbcb27fe90006bd]
#
# patch "common-session.c"
#  from [898ad7d98e569ffec09bff54bc86f2723fef800e]
#    to [4a826507f9cb1e536ceb40a76932c5fdf332da16]
#
# patch "session.h"
#  from [029633beee5cd56701332bc0fa563ae201223469]
#    to [ee0e21426ed403efd6cc2147c137d74c58c38584]
#
# patch "svr-chansession.c"
#  from [2c744cd005e70f8ac60aa3e71887f997b997b3a3]
#    to [7807da20255fbbdfef8c317c51037ac2c0bba2f3]
#
============================================================
--- channel.h	599d696acdeab500ef382b2a738b887e41e02afb
+++ channel.h	aa5deb13fb94e010f897c9e4ac68862a81c26e57
@@ -84,6 +84,8 @@ struct Channel {
 					   for this channel (and are awaiting a confirmation
 					   or failure). */

+	int flushing;
+
 	const struct ChanType* type;

 };
============================================================
--- common-channel.c	6f44bbe2729e12dd2854d0bd00a965981774fe05
+++ common-channel.c	8fa86543a791c4d69addf71cdbbcb27fe90006bd
@@ -148,6 +148,7 @@ struct Channel* newchannel(unsigned int
 	newchan->errfd = FD_CLOSED; /* this isn't always set to start with */
 	newchan->initconn = 0;
 	newchan->await_open = 0;
+	newchan->flushing = 0;

 	newchan->writebuf = cbuf_new(RECV_MAXWINDOW);
 	newchan->extrabuf = NULL; /* The user code can set it up */
@@ -203,12 +204,14 @@ void channelio(fd_set *readfds, fd_set *

 		/* read data and send it over the wire */
 		if (channel->readfd >= 0 && FD_ISSET(channel->readfd, readfds)) {
+			TRACE(("send normal readfd"))
 			send_msg_channel_data(channel, 0);
 		}

 		/* read stderr data and send it over the wire */
-		if (ERRFD_IS_READ(channel) &&
-				channel->errfd >= 0 && FD_ISSET(channel->errfd, readfds)) {
+		if (ERRFD_IS_READ(channel) && channel->errfd >= 0
+			&& FD_ISSET(channel->errfd, readfds)) {
+				TRACE(("send normal errfd"))
 				send_msg_channel_data(channel, 1);
 		}

@@ -265,8 +268,14 @@ static void check_close(struct Channel *
 				cbuf_getused(channel->writebuf),
 				channel->extrabuf ? cbuf_getused(channel->extrabuf) : 0))

+	if (!channel->flushing && channel->type->check_close
+		&& channel->type->check_close(channel))
+	{
+		channel->flushing = 1;
+	}
+
 	if (channel->recv_close && !write_pending(channel)) {
-		if (! channel->sent_close) {
+		if (!channel->sent_close) {
 			TRACE(("Sending MSG_CHANNEL_CLOSE in response to same."))
 			send_msg_channel_close(channel);
 		}
@@ -278,6 +287,22 @@ static void check_close(struct Channel *
 		close_chan_fd(channel, channel->writefd, SHUT_WR);
 	}

+	/* Special handling for flushing read data after an exit. We
+	   read regardless of whether the select FD was set,
+	   and if there isn't data available, the channel will get closed. */
+	if (channel->flushing) {
+		TRACE(("might send data, flushing"))
+		if (channel->readfd >= 0 && channel->transwindow > 0) {
+			TRACE(("send data readfd"))
+			send_msg_channel_data(channel, 0);
+		}
+		if (ERRFD_IS_READ(channel) && channel->readfd >= 0
+			&& channel->transwindow > 0) {
+			TRACE(("send data errfd"))
+			send_msg_channel_data(channel, 1);
+		}
+	}
+
 	/* If we're not going to send any more data, send EOF */
 	if (!channel->sent_eof
 			&& channel->readfd == FD_CLOSED
@@ -287,15 +312,13 @@ static void check_close(struct Channel *

 	/* And if we can't receive any more data from them either, close up */
 	if (!channel->sent_close
-			&& channel->writefd == FD_CLOSED
 			&& channel->readfd == FD_CLOSED
-			&& channel->errfd == FD_CLOSED) {
+			&& !write_pending(channel)) {
+		TRACE(("sending close, readfd is closed"))
 		send_msg_channel_close(channel);
 	}
-
 }

-
 /* Check whether a deferred (EINPROGRESS) connect() was successful, and
  * if so, set up the channel properly. Otherwise, the channel is cleaned up, so
  * it is important that the channel reference isn't used after a call to this
@@ -341,6 +364,9 @@ static void send_msg_channel_close(struc

 	channel->sent_eof = 1;
 	channel->sent_close = 1;
+	close_chan_fd(channel, channel->readfd, SHUT_RD);
+	close_chan_fd(channel, channel->errfd, SHUT_RDWR);
+	close_chan_fd(channel, channel->writefd, SHUT_WR);
 	TRACE(("leave send_msg_channel_close"))
 }

@@ -556,6 +582,7 @@ static void send_msg_channel_data(struct

 	CHECKCLEARTOWRITE();

+	TRACE(("enter send_msg_channel_data"))
 	dropbear_assert(!channel->sent_close);

 	if (isextended) {
@@ -591,6 +618,9 @@ static void send_msg_channel_data(struct
 	len = read(fd, buf_getwriteptr(ses.writepayload, maxlen), maxlen);
 	if (len <= 0) {
 		if (len == 0 || errno != EINTR) {
+			/* This will also get hit in the case of EAGAIN. The only
+			time we expect to receive EAGAIN is when we're flushing a FD,
+			in which case it can be treated the same as EOF */
 			close_chan_fd(channel, fd, SHUT_RD);
 		}
 		ses.writepayload->len = ses.writepayload->pos = 0;
@@ -606,6 +636,14 @@ static void send_msg_channel_data(struct
 	channel->transwindow -= len;

 	encrypt_packet();
+
+	/* If we receive less data than we requested when flushing, we've
+	   reached the equivalent of EOF */
+	if (channel->flushing && len < maxlen)
+	{
+		TRACE(("closing from channel, flushing out."))
+		close_chan_fd(channel, fd, SHUT_RD);
+	}
 	TRACE(("leave send_msg_channel_data"))
 }

============================================================
--- common-session.c	898ad7d98e569ffec09bff54bc86f2723fef800e
+++ common-session.c	4a826507f9cb1e536ceb40a76932c5fdf332da16
@@ -61,6 +61,12 @@ void common_session_init(int sock, char*

 	ses.connecttimeout = 0;

+	if (pipe(ses.signal_pipe) < 0) {
+		dropbear_exit("signal pipe failed");
+	}
+	setnonblocking(ses.signal_pipe[0]);
+	setnonblocking(ses.signal_pipe[1]);
+
 	kexfirstinitialise(); /* initialise the kex state */

 	ses.writepayload = buf_new(MAX_TRANS_PAYLOAD_LEN);
@@ -108,7 +114,6 @@ void common_session_init(int sock, char*

 	ses.allowprivport = 0;

-
 	TRACE(("leave session_init"))
 }

@@ -132,6 +137,10 @@ void session_loop(void(*loophandler)())
 				FD_SET(ses.sock, &writefd);
 			}
 		}
+
+		/* We get woken up when signal handlers write to this pipe.
+		   SIGCHLD in svr-chansession is the only one currently. */
+		FD_SET(ses.signal_pipe[0], &readfd);

 		/* set up for channels which require reading/writing */
 		if (ses.dataallowed) {
@@ -155,6 +164,14 @@ void session_loop(void(*loophandler)())
 			FD_ZERO(&writefd);
 			FD_ZERO(&readfd);
 		}
+
+		/* We'll just empty out the pipe if required. We don't do
+		any thing with the data, since the pipe's purpose is purely to
+		wake up the select() above. */
+		if (FD_ISSET(ses.signal_pipe[0], &readfd)) {
+			char x;
+			while (read(ses.signal_pipe[0], &x, 1) > 0) {}
+		}

 		/* check for auth timeout, rekeying required etc */
 		checktimeouts();
============================================================
--- session.h	029633beee5cd56701332bc0fa563ae201223469
+++ session.h	ee0e21426ed403efd6cc2147c137d74c58c38584
@@ -123,8 +123,9 @@ struct sshsession {

 	unsigned char lastpacket; /* What the last received packet type was */

+    int signal_pipe[2]; /* stores endpoints of a self-pipe used for
+						   race-free signal handling */

-
 	/* KEX/encryption related */
 	struct KEXState kexstate;
 	struct key_context *keys;
============================================================
--- svr-chansession.c	2c744cd005e70f8ac60aa3e71887f997b997b3a3
+++ svr-chansession.c	7807da20255fbbdfef8c317c51037ac2c0bba2f3
@@ -120,9 +120,21 @@ static void sesssigchild_handler(int UNU
 			/* we use this to determine how pid exited */
 			exit->exitsignal = -1;
 		}
+
+		/* Make sure that the main select() loop wakes up */
+		while (1) {
+			/* EAGAIN means the pipe's full, so don't need to write anything */
+			/* isserver is just a random byte to write */
+			if (write(ses.signal_pipe[1], &ses.isserver, 1) == 1 || errno == EAGAIN) {
+				break;
+			}
+			if (errno == EINTR) {
+				continue;
+			}
+			dropbear_exit("error writing signal pipe");
+		}
 	}

-
 	sa_chld.sa_handler = sesssigchild_handler;
 	sa_chld.sa_flags = SA_NOCLDSTOP;
 	sigaction(SIGCHLD, &sa_chld, NULL);
@@ -244,16 +256,17 @@ static void closechansess(struct Channel
 	unsigned int i;
 	struct logininfo *li;

-	chansess = (struct ChanSess*)channel->typedata;
+	TRACE(("enter closechansess"))

-	send_exitsignalstatus(channel);
+	chansess = (struct ChanSess*)channel->typedata;

-	TRACE(("enter closechansess"))
 	if (chansess == NULL) {
 		TRACE(("leave closechansess: chansess == NULL"))
 		return;
 	}

+	send_exitsignalstatus(channel);
+
 	m_free(chansess->cmd);
 	m_free(chansess->term);