aboutsummaryrefslogtreecommitdiffstats
path: root/main/mosh
diff options
context:
space:
mode:
authorCarlo Landmeter <clandmeter@gmail.com>2014-04-29 21:25:14 +0000
committerCarlo Landmeter <clandmeter@gmail.com>2014-04-29 21:28:05 +0000
commit29998a2cb982d3f563efef720de541a3a5025e34 (patch)
tree2ada7877afd2d0c45021dd6eee0418f8c898f659 /main/mosh
parentcfed6fb2c495ecba7fb70cdcdcd5ee7f54f60f53 (diff)
downloadaports-29998a2cb982d3f563efef720de541a3a5025e34.tar.bz2
aports-29998a2cb982d3f563efef720de541a3a5025e34.tar.xz
main/mosh: add support for ssh agent forwarding and out of band data
Diffstat (limited to 'main/mosh')
-rw-r--r--main/mosh/APKBUILD18
-rw-r--r--main/mosh/out-of-band-data-and-ssh-agent-forwarding.patch2301
2 files changed, 2313 insertions, 6 deletions
diff --git a/main/mosh/APKBUILD b/main/mosh/APKBUILD
index 22f4ca1c66..633a04d723 100644
--- a/main/mosh/APKBUILD
+++ b/main/mosh/APKBUILD
@@ -2,16 +2,18 @@
# Maintainer: Francesco Colista <francesco.colista@gmail.com>
pkgname=mosh
pkgver=1.2.4
-pkgrel=4
+pkgrel=5
pkgdesc="Mobile shell (mosh) surviving disconnects with local echo and line editing"
url="http://mosh.mit.edu"
arch="all"
license="GPL3+"
depends="$pkgname-client $pkgname-server"
-makedepends="ncurses-dev zlib-dev openssl-dev perl-dev perl-io-tty protobuf-dev"
+makedepends="ncurses-dev zlib-dev openssl-dev perl-dev perl-io-tty protobuf-dev
+ automake autoconf libtool"
subpackages="$pkgname-doc $pkgname-client $pkgname-server"
source="http://$pkgname.mit.edu/$pkgname-$pkgver.tar.gz
- disable-utf8-check.patch"
+ disable-utf8-check.patch
+ out-of-band-data-and-ssh-agent-forwarding.patch"
_builddir="$srcdir"/$pkgname-$pkgver
@@ -28,6 +30,7 @@ prepare() {
build() {
cd "$_builddir"
+ ./autogen.sh || return 1
./configure --prefix=/usr \
--sysconfdir=/etc
make || return 1
@@ -59,8 +62,11 @@ client() {
}
md5sums="c2d918f4d91fdc32546e2e089f9281b2 mosh-1.2.4.tar.gz
-f9e6a14dc7a300d95625265ab5e847d7 disable-utf8-check.patch"
+f9e6a14dc7a300d95625265ab5e847d7 disable-utf8-check.patch
+8f05f2418ca7311ceb1bc6732db17ca3 out-of-band-data-and-ssh-agent-forwarding.patch"
sha256sums="e74d0d323226046e402dd469a176075fc2013b69b0e67cea49762c957175df46 mosh-1.2.4.tar.gz
-60416de55be97a3c80d3b89e44b8602a8b4dcca6de8e70cb15d2c96e30a7de42 disable-utf8-check.patch"
+60416de55be97a3c80d3b89e44b8602a8b4dcca6de8e70cb15d2c96e30a7de42 disable-utf8-check.patch
+5f35f7e84c08e38f112d8b8f06df09063f54f35feccaf62e972b4b52302aa2d6 out-of-band-data-and-ssh-agent-forwarding.patch"
sha512sums="f7505faffdc8da734179b37339b554f83cbf5450b251cd2aa50d63cd6e4cbefa0da17a1c1b2a61858735ac9e5cee5841ed20e81e244380f5f9a02af1b87199cc mosh-1.2.4.tar.gz
-3c3b60b9aa837d76e53855907c59c3b1648e3a2e166b3ec902aec117e4e56d850553a089401a3bb9901412c125d30d4dac76d204721a17286a0ddc922508f6fc disable-utf8-check.patch"
+3c3b60b9aa837d76e53855907c59c3b1648e3a2e166b3ec902aec117e4e56d850553a089401a3bb9901412c125d30d4dac76d204721a17286a0ddc922508f6fc disable-utf8-check.patch
+54d8ce032a3d1cb5adaf7272b685a263e9aebe4daefae7dfb6d7f52be275d7a05c22e06b9d7424b3aa05642e60e1edc88d093a4feeca5b97a313ae8cd883a28f out-of-band-data-and-ssh-agent-forwarding.patch"
diff --git a/main/mosh/out-of-band-data-and-ssh-agent-forwarding.patch b/main/mosh/out-of-band-data-and-ssh-agent-forwarding.patch
new file mode 100644
index 0000000000..7132f0b6ae
--- /dev/null
+++ b/main/mosh/out-of-band-data-and-ssh-agent-forwarding.patch
@@ -0,0 +1,2301 @@
+From 0c1b247b616bd4810041f48f4d903828af0ba837 Mon Sep 17 00:00:00 2001
+From: "Timo J. Rinne" <tri@iki.fi>
+Date: Sat, 11 May 2013 21:52:40 +0000
+Subject: [PATCH] Pluggable out of band communication mechanism over Mosh
+ transport layer and agent forwarding support in top of out of band mechanism.
+
+I contrubute this code to Mosh project under the same license that
+Mosh itself is distributed. All licensing options and clauses
+included.
+
+Signed-off-by: Timo J. Rinne <tri@iki.fi>
+---
+ configure.ac | 17 +-
+ man/mosh.1 | 9 +
+ scripts/mosh | 20 +-
+ src/Makefile.am | 2 +-
+ src/agent/Makefile.am | 7 +
+ src/agent/agent.cc | 486 +++++++++++++++++++++++++++++++
+ src/agent/agent.h | 110 +++++++
+ src/frontend/Makefile.am | 4 +-
+ src/frontend/mosh-client.cc | 9 +-
+ src/frontend/mosh-server.cc | 62 +++-
+ src/frontend/stmclient.cc | 31 +-
+ src/frontend/stmclient.h | 5 +-
+ src/network/Makefile.am | 2 +-
+ src/network/networktransport.cc | 5 +
+ src/network/networktransport.h | 3 +
+ src/network/outofband.cc | 265 +++++++++++++++++
+ src/network/outofband.h | 107 +++++++
+ src/network/transportsender.cc | 21 +-
+ src/network/transportsender.h | 8 +-
+ src/protobufs/Makefile.am | 2 +-
+ src/protobufs/agent.proto | 8 +
+ src/protobufs/oob.proto | 11 +
+ src/protobufs/transportinstruction.proto | 2 +
+ src/util/Makefile.am | 2 +-
+ src/util/swrite.cc | 45 +++
+ src/util/swrite.h | 3 +
+ 26 files changed, 1217 insertions(+), 29 deletions(-)
+ create mode 100644 src/agent/Makefile.am
+ create mode 100644 src/agent/agent.cc
+ create mode 100644 src/agent/agent.h
+ create mode 100644 src/network/outofband.cc
+ create mode 100644 src/network/outofband.h
+ create mode 100644 src/protobufs/agent.proto
+ create mode 100644 src/protobufs/oob.proto
+
+diff --git a/configure.ac b/configure.ac
+index b07291a..bf1a841 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -164,19 +164,28 @@ AS_IF([test x"$with_utempter" != xno],
+ [AC_MSG_WARN([Unable to find libutempter; utmp entries will not be made.])],
+ [AC_MSG_ERROR([--with-utempter was given but libutempter was not found.])])])])
+
++# Handle --disable-agent-forwarding
++AC_ARG_ENABLE(agent-forwarding,
++ AS_HELP_STRING([--disable-agent-forwarding],
++ [disable ssh agent forwarding in compile time]),
++ , enable_agent_forwarding=yes)
++
++
+ AC_SEARCH_LIBS([compress], [z], , [AC_MSG_ERROR([Unable to find zlib.])])
+
+ AC_SEARCH_LIBS([socket], [socket])
+ AC_SEARCH_LIBS([inet_addr], [nsl])
+
+ # Checks for header files.
+-AC_CHECK_HEADERS([arpa/inet.h fcntl.h langinfo.h limits.h locale.h netinet/in.h stddef.h stdint.h inttypes.h stdlib.h string.h sys/ioctl.h sys/resource.h sys/socket.h sys/stat.h sys/time.h termios.h unistd.h wchar.h wctype.h], [], [AC_MSG_ERROR([Missing required header file.])])
++AC_CHECK_HEADERS([arpa/inet.h fcntl.h langinfo.h limits.h locale.h netinet/in.h stddef.h stdint.h inttypes.h stdlib.h string.h sys/ioctl.h sys/resource.h sys/socket.h sys/stat.h sys/time.h termios.h unistd.h wchar.h wctype.h errno.h], [], [AC_MSG_ERROR([Missing required header file.])])
+
+ AC_CHECK_HEADERS([pty.h util.h libutil.h paths.h])
+ AC_CHECK_HEADERS([endian.h sys/endian.h])
+ AC_CHECK_HEADERS([utmpx.h])
+ AC_CHECK_HEADERS([termio.h])
+ AC_CHECK_HEADERS([sys/uio.h])
++AC_CHECK_HEADERS([sys/un.h])
++AC_CHECK_HEADERS([sys/types.h])
+
+ # Checks for typedefs, structures, and compiler characteristics.
+ AC_HEADER_STDBOOL
+@@ -322,6 +331,11 @@ AC_CHECK_DECL([IUTF8],
+ [AC_MSG_WARN([No IUTF8 termios mode; character-erase of multibyte character sequence probably does not work properly in canonical mode on this platform.])],
+ [[#include <termios.h>]])
+
++if test "$enable_agent_forwarding" = "yes"; then
++ AC_DEFINE([SUPPORT_AGENT_FORWARDING], [], [
++ Define to enable support for SSH agent forwarding])
++fi
++
+ # Checks for protobuf
+ PKG_CHECK_MODULES([protobuf], [protobuf])
+
+@@ -334,6 +348,7 @@ AC_CONFIG_FILES([
+ src/protobufs/Makefile
+ src/statesync/Makefile
+ src/terminal/Makefile
++ src/agent/Makefile
+ src/util/Makefile
+ scripts/Makefile
+ src/examples/Makefile
+diff --git a/man/mosh.1 b/man/mosh.1
+index 14405bf..5d98053 100644
+--- a/man/mosh.1
++++ b/man/mosh.1
+@@ -99,6 +99,11 @@ OpenSSH command to remotely execute mosh-server on remote machine (default: "ssh
+ An alternate ssh port can be specified with, \fIe.g.\fP, \-\-ssh="ssh \-p 2222".
+
+ .TP
++.B \-\-forward-agent
++Enable ssh authentication agent forwarding. If you use this, please be
++aware of the security implications.
++
++.TP
+ .B \-\-predict=\fIWHEN\fP
+ Controls use of speculative local echo. WHEN defaults to `adaptive'
+ (show predictions on slower links and to smooth out network glitches)
+@@ -113,6 +118,10 @@ of the terminal has been confirmed by the server, without any
+ intervening control character keystrokes.
+
+ .TP
++.B \-A
++Synonym for \-\-forward-agent
++
++.TP
+ .B \-a
+ Synonym for \-\-predict=always
+
+diff --git a/scripts/mosh b/scripts/mosh
+index 4e8b796..3c7f197 100755
+--- a/scripts/mosh
++++ b/scripts/mosh
+@@ -52,6 +52,8 @@ my $ssh = 'ssh';
+
+ my $term_init = 1;
+
++my $forward_agent = 0;
++
+ my $help = undef;
+ my $version = undef;
+
+@@ -78,6 +80,8 @@ qq{Usage: $0 [options] [--] [user@]host [command...]
+ (example: "ssh -p 2222")
+ (default: "ssh")
+
++-A --forward-agent enable ssh agent forwarding
++
+ --no-init do not send terminal initialization string
+
+ --help this message
+@@ -112,6 +116,8 @@ GetOptions( 'client=s' => \$client,
+ 'n' => sub { $predict = 'never' },
+ 'p=s' => \$port_request,
+ 'ssh=s' => \$ssh,
++ 'A' => \$forward_agent,
++ 'forward-agent!' => \$forward_agent,
+ 'init!' => \$term_init,
+ 'help' => \$help,
+ 'version' => \$version,
+@@ -242,6 +248,10 @@ if ( $pid == 0 ) { # child
+
+ my @server = ( 'new' );
+
++ if ( $forward_agent ) {
++ push @server, ( '-A' );
++ }
++
+ push @server, ( '-c', $colors );
+
+ push @server, @bind_arguments;
+@@ -259,6 +269,7 @@ if ( $pid == 0 ) { # child
+ }
+
+ my $quoted_self = shell_quote( $0 );
++
+ exec "$ssh " . shell_quote( '-S', 'none', '-o', "ProxyCommand=$quoted_self --fake-proxy -- %h %p", '-n', '-tt', $userhost, '--', "$server " . shell_quote( @server ) );
+ die "Cannot exec ssh: $!\n";
+ } else { # parent
+@@ -302,7 +313,14 @@ if ( $pid == 0 ) { # child
+ $ENV{ 'MOSH_KEY' } = $key;
+ $ENV{ 'MOSH_PREDICTION_DISPLAY' } = $predict;
+ $ENV{ 'MOSH_NO_TERM_INIT' } = '1' if !$term_init;
+- exec {$client} ("$client @cmdline |", $ip, $port);
++
++ my @client_av = ();
++ if ( $forward_agent ) {
++ push @client_av, ( '-A' );
++ }
++ push @client_av, ( $ip, $port );
++
++ exec {$client} ("$client @cmdline |", @client_av);
+ }
+
+ sub shell_quote { join ' ', map {(my $a = $_) =~ s/'/'\\''/g; "'$a'"} @_ }
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 2390f7c..332bb2c 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -1 +1 @@
+-SUBDIRS = protobufs util crypto terminal network statesync frontend examples tests
++SUBDIRS = protobufs util crypto terminal network statesync agent frontend examples tests
+diff --git a/src/agent/Makefile.am b/src/agent/Makefile.am
+new file mode 100644
+index 0000000..7ec93ea
+--- /dev/null
++++ b/src/agent/Makefile.am
+@@ -0,0 +1,7 @@
++AM_CPPFLAGS = -I$(srcdir)/../util -I$(srcdir)/../network -I$(srcdir)/../protobufs -I$(srcdir)/../crypto $(TINFO_CFLAGS)
++AM_CXXFLAGS = $(WARNING_CXXFLAGS) $(PICKY_CXXFLAGS) $(HARDEN_CFLAGS) $(MISC_CXXFLAGS)
++
++noinst_LIBRARIES = libmoshagent.a
++
++libmoshagent_a_SOURCES = agent.h agent.cc
++
+diff --git a/src/agent/agent.cc b/src/agent/agent.cc
+new file mode 100644
+index 0000000..12cd31b
+--- /dev/null
++++ b/src/agent/agent.cc
+@@ -0,0 +1,486 @@
++/*
++ Mosh: the mobile shell
++ Copyright 2012 Keith Winstein
++
++ SSH Agent forwarding for Mosh
++ Copyright 2013 Timo J. Rinne
++
++ This program is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program. If not, see <http://www.gnu.org/licenses/>.
++
++ In addition, as a special exception, the copyright holders give
++ permission to link the code of portions of this program with the
++ OpenSSL library under certain conditions as described in each
++ individual source file, and distribute linked combinations including
++ the two.
++
++ You must obey the GNU General Public License in all respects for all
++ of the code used other than OpenSSL. If you modify file(s) with this
++ exception, you may extend this exception to your version of the
++ file(s), but you are not obligated to do so. If you do not wish to do
++ so, delete this exception statement from your version. If you delete
++ this exception statement from all source files in the program, then
++ also delete it here.
++*/
++
++#include "config.h"
++
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++#include <fcntl.h>
++#include <errno.h>
++#include <sys/stat.h>
++#ifdef HAVE_SYS_TYPES_H
++#include <sys/types.h>
++#endif
++#include <sys/socket.h>
++
++#ifdef SUPPORT_AGENT_FORWARDING
++#ifdef HAVE_SYS_UN_H
++#include <sys/un.h>
++#else
++#undef SUPPORT_AGENT_FORWARDING
++#endif
++#endif
++
++#include "prng.h"
++#include "network.h"
++#include "swrite.h"
++#include "select.h"
++#include "outofband.h"
++#include "agent.h"
++#include "agent.pb.h"
++#include "fatal_assert.h"
++
++using namespace Agent;
++using std::string;
++using std::map;
++using Network::OutOfBand;
++using Network::OutOfBandCommunicator;
++
++ProxyAgent::ProxyAgent( bool is_server, bool dummy ) {
++ server = is_server;
++ ok = false;
++ l_sock = -1;
++ l_dir = "";
++ l_path = "";
++ cnt = 0;
++ oob_ctl_ptr = NULL;
++ comm = NULL;
++#ifdef SUPPORT_AGENT_FORWARDING
++ if ( dummy ) {
++ return;
++ }
++ if (server) {
++ PRNG prng;
++ string dir("/tmp/ma-");
++ string voc = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
++ int i;
++ for ( i = 0; i < 10; i++ ) {
++ dir += voc.substr( prng.uint32() % voc.length(), 1 );
++ }
++ if ( mkdir( dir.c_str(), 0700 ) != 0 ) {
++ return;
++ }
++ string path(dir + "/");
++ for ( i = 0; i < 12; i++ ) {
++ path += voc.substr( prng.uint32() % voc.length(), 1 );
++ }
++ int sock = socket( AF_UNIX, SOCK_STREAM, 0 );
++ if ( sock < 0 ) {
++ (void) rmdir( dir.c_str() );
++ return;
++ }
++ if ( fcntl( sock, F_SETFD, FD_CLOEXEC ) != 0 ) {
++ (void) rmdir( dir.c_str() );
++ return;
++ }
++ struct sockaddr_un sunaddr;
++ memset( &sunaddr, 0, sizeof (sunaddr) );
++ sunaddr.sun_family = AF_UNIX;
++ if ( path.length() >= sizeof (sunaddr.sun_path) ) {
++ (void) close( sock );
++ (void) rmdir( dir.c_str() );
++ return;
++ }
++ strncpy( sunaddr.sun_path, path.c_str(), sizeof (sunaddr.sun_path) );
++ if ( bind( sock, (struct sockaddr *) &sunaddr, sizeof (sunaddr) ) < 0 ) {
++ (void) close( sock );
++ (void) rmdir( dir.c_str() );
++ return;
++ }
++ if ( listen( sock, AGENT_PROXY_LISTEN_QUEUE_LENGTH ) < 0) {
++ (void) close( sock );
++ (void) unlink( path.c_str() );
++ (void) rmdir( dir.c_str() );
++ return;
++ }
++ l_sock = sock;
++ l_path = path;
++ l_dir = dir;
++ }
++ ok = true;
++#endif
++}
++
++ProxyAgent::~ProxyAgent( void ) {
++#ifdef SUPPORT_AGENT_FORWARDING
++ shutdown_server();
++#endif
++}
++
++void ProxyAgent::close_sessions( void ) {
++#ifdef SUPPORT_AGENT_FORWARDING
++ map< uint64_t, AgentConnection * >::iterator i = agent_sessions.begin();
++ while ( i != agent_sessions.end() ) {
++ AgentConnection *ac = i->second;
++ agent_sessions.erase( i );
++ delete ac;
++ i = agent_sessions.begin();
++ }
++#endif
++}
++
++void ProxyAgent::shutdown_server( void ) {
++#ifdef SUPPORT_AGENT_FORWARDING
++ detach_oob();
++ if (ok) {
++ if ( server && l_sock >= 0 ) {
++ (void) close( l_sock );
++ (void) unlink( l_path.c_str() );
++ (void) rmdir( l_dir.c_str() );
++ l_sock = -1;
++ l_path = "";
++ l_dir = "";
++ }
++ close_sessions();
++ ok = false;
++ }
++#endif
++}
++
++void ProxyAgent::attach_oob(OutOfBand *oob_ctl) {
++ detach_oob();
++ fatal_assert(oob_ctl != NULL);
++ oob_ctl_ptr = oob_ctl;
++ comm = oob_ctl_ptr->init(AGENT_FORWARD_OOB_NAME, Network::OOB_MODE_RELIABLE_DATAGRAM);
++ fatal_assert(comm != NULL);
++}
++
++void ProxyAgent::detach_oob(void) {
++ if (oob_ctl_ptr != NULL) {
++ oob_ctl_ptr->uninit(AGENT_FORWARD_OOB_NAME);
++ }
++ oob_ctl_ptr = NULL;
++}
++
++void ProxyAgent::pre_poll( void ) {
++#ifdef SUPPORT_AGENT_FORWARDING
++ if ( ! ok ) {
++ return;
++ }
++ Select &sel = Select::get_instance();
++ if ( server && l_sock >= 0 ) {
++ sel.add_fd( l_sock );
++ }
++ for ( map< uint64_t, AgentConnection * >::iterator i = agent_sessions.begin(); i != agent_sessions.end(); i++ ) {
++ AgentConnection *ac = i->second;
++ if ( ac->sock() >= 0 ) {
++ sel.add_fd( ac->sock() );
++ ac->mark_in_read_set(true);
++ } else {
++ ac->mark_in_read_set(false);
++ }
++ }
++#endif
++}
++
++void ProxyAgent::post_poll( void ) {
++#ifdef SUPPORT_AGENT_FORWARDING
++ if ( ! ok ) {
++ return;
++ }
++ Select &sel = Select::get_instance();
++ // First handle possible incoming data from local sockets
++ map< uint64_t, AgentConnection * >::iterator i = agent_sessions.begin();
++ while ( ((! server) || (l_sock >= 0)) && i != agent_sessions.end() ) {
++ AgentConnection *ac = i->second;
++ if ( (comm == NULL) || (oob_ctl_ptr == NULL) || ac->eof() || (ac->idle_time() > AGENT_IDLE_TIMEOUT) ) {
++ agent_sessions.erase( i++ );
++ delete ac;
++ continue;
++ }
++
++ if ( ac->in_read_set() && sel.read( ac->sock() ) ) {
++ while ( true ) {
++ string packet = ac->recv_packet();
++ if ( ! packet.empty() ) {
++ AgentBuffers::Instruction inst;
++ inst.set_agent_id(ac->s_id);
++ inst.set_agent_data(packet);
++ string pb_packet;
++ fatal_assert(inst.SerializeToString(&pb_packet));
++ comm->send(pb_packet);
++ continue;
++ }
++ if ( ac->eof() ) {
++ notify_eof(ac->s_id);
++ agent_sessions.erase( i++ );
++ delete ac;
++ break;
++ }
++ i++;
++ break;
++ }
++ } else {
++ i++;
++ }
++ }
++ if ( ! server ) {
++ return;
++ }
++ // Then see if we have mysteriously died in between.
++ if ( l_sock < 0 ) {
++ return;
++ }
++ // Then check for new incoming connections.
++ if ( sel.read( l_sock ) ) {
++ AgentConnection *new_as = get_session();
++ if ( new_as != NULL ) {
++ agent_sessions[new_as->s_id] = new_as;
++ }
++ }
++#endif
++}
++
++void ProxyAgent::post_tick( void ) {
++#ifdef SUPPORT_AGENT_FORWARDING
++ if ( (! ok) || (comm == NULL) ) {
++ return;
++ }
++ while (comm->readable()) {
++ string pb_packet = comm->recv();
++ AgentBuffers::Instruction inst;
++ fatal_assert( inst.ParseFromString(pb_packet) );
++ uint64_t agent_id = inst.agent_id();
++ string agent_data = inst.has_agent_data() ? inst.agent_data() : "";
++ if (agent_data.empty()) {
++ map < uint64_t, AgentConnection* >::iterator i = agent_sessions.find(agent_id);
++ if (i != agent_sessions.end()) {
++ AgentConnection *ac = i->second;
++ agent_sessions.erase( i );
++ delete ac;
++ }
++ } else {
++ map < uint64_t, AgentConnection* >::iterator i = agent_sessions.find(agent_id);
++ if (i == agent_sessions.end()) {
++ AgentConnection *new_as = NULL;
++ if (! server) {
++ const char *ap = getenv( "SSH_AUTH_SOCK" );
++ if ( ap != NULL ) {
++ string agent_path(ap);
++ if ( ! agent_path.empty() ) {
++ new_as = new AgentConnection ( agent_path, agent_id, this );
++ }
++ }
++ }
++ if (new_as == NULL) {
++ notify_eof(agent_id);
++ } else {
++ agent_sessions[agent_id] = new_as;
++ }
++ i = agent_sessions.find(agent_id);
++ }
++ if (i != agent_sessions.end()) {
++ AgentConnection *ac = i->second;
++ uint64_t idle = ac->idle_time();
++ uint64_t timeout = idle < AGENT_IDLE_TIMEOUT ? (AGENT_IDLE_TIMEOUT - idle) * 1000 : 1;
++ if ( swrite_timeout( ac->sock(), timeout, agent_data.c_str(), agent_data.length() ) != 0 ) {
++ agent_sessions.erase( i );
++ delete ac;
++ notify_eof(agent_id);
++ }
++ }
++ }
++ }
++#endif
++}
++
++void ProxyAgent::notify_eof(uint64_t agent_id) {
++#ifdef SUPPORT_AGENT_FORWARDING
++ if (comm == NULL) {
++ return;
++ }
++ AgentBuffers::Instruction inst;
++ inst.set_agent_id(agent_id);
++ string pb_packet;
++ fatal_assert(inst.SerializeToString(&pb_packet));
++ comm->send(pb_packet);
++#endif
++}
++
++
++AgentConnection *ProxyAgent::get_session() {
++#ifdef SUPPORT_AGENT_FORWARDING
++ if ( (! server) || l_sock < 0) {
++ return NULL;
++ }
++ struct sockaddr_un sunaddr;
++ socklen_t slen = sizeof ( sunaddr );
++ memset( &sunaddr, 0, slen );
++ int sock = accept ( l_sock, (struct sockaddr *)&sunaddr, &slen );
++ if ( sock < 0 ) {
++ return NULL;
++ }
++
++ if ( (comm == NULL) || (oob_ctl_ptr == NULL) ) {
++ (void) close( sock );
++ return NULL;
++ }
++
++ /* Here we should check that peer effective uid matches with the
++ euid of this process. Skipping however and trusting the file
++ system to protect the socket. This would basically catch root
++ accessing the socket, but root can change its effective uid to
++ match the socket anyways, so it doesn't really help at all. */
++
++ /* If can't set the socket mode, discard it. */
++ if ( fcntl( sock, F_SETFD, FD_CLOEXEC ) != 0 || fcntl( sock, F_SETFL, O_NONBLOCK ) != 0 ) {
++ (void) close( sock );
++ return NULL;
++ }
++ return new AgentConnection ( sock, ++cnt, this );
++#else
++ return NULL;
++#endif
++}
++
++AgentConnection::AgentConnection(int sock, uint64_t id, ProxyAgent *s_agent_ptr) {
++ agent_ptr = s_agent_ptr;
++ s_sock = sock;
++ s_id = id;
++ s_in_read_set = false;
++#ifndef SUPPORT_AGENT_FORWARDING
++ if (sock >= 0) {
++ (void) close( sock );
++ }
++ s_sock = -1;
++#endif
++ idle_start = Network::timestamp();
++ packet_buf = "";
++ packet_len = 0;
++}
++
++AgentConnection::AgentConnection(std::string agent_path, uint64_t id, ProxyAgent *s_agent_ptr) {
++ agent_ptr = s_agent_ptr;
++ s_sock = -1;
++ s_id = id;
++ s_in_read_set = false;
++#ifdef SUPPORT_AGENT_FORWARDING
++ int sock = socket( AF_UNIX, SOCK_STREAM, 0 );
++ struct sockaddr_un sunaddr;
++ memset( &sunaddr, 0, sizeof (sunaddr) );
++ sunaddr.sun_family = AF_UNIX;
++ if ( agent_path.length() >= sizeof (sunaddr.sun_path) ) {
++ (void) close( sock );
++ return;
++ }
++ if ( fcntl( sock, F_SETFD, FD_CLOEXEC ) != 0 ) {
++ (void) close( sock );
++ return;
++ }
++ strncpy( sunaddr.sun_path, agent_path.c_str(), sizeof (sunaddr.sun_path) );
++ if ( connect(sock, (struct sockaddr *)&sunaddr, sizeof (sunaddr)) < 0 ) {
++ (void) close( sock );
++ return;
++ }
++ if ( fcntl( sock, F_SETFL, O_NONBLOCK ) != 0 ) {
++ (void) close( sock );
++ return;
++ }
++ s_sock = sock;
++#endif
++ idle_start = Network::timestamp();
++ packet_buf = "";
++ packet_len = 0;
++}
++
++AgentConnection::~AgentConnection() {
++ if ( s_sock >= 0 ) {
++ (void) close ( s_sock );
++ }
++}
++
++uint64_t AgentConnection::idle_time() {
++ return (Network::timestamp() - idle_start) / 1000;
++}
++
++string AgentConnection::recv_packet() {
++#ifdef SUPPORT_AGENT_FORWARDING
++ if (eof()) {
++ return "";
++ }
++ ssize_t rv;
++ if (packet_len < 1) {
++ unsigned char buf[4];
++ rv = read( s_sock, buf, 4 );
++ if ( (rv < 0) && ( errno == EAGAIN || errno == EWOULDBLOCK ) ) {
++ return "";
++ }
++ if ( rv != 4 ) {
++ (void) close(s_sock);
++ s_sock = -1;
++ return "";
++ }
++ if ( buf[0] != 0 ) {
++ (void) close(s_sock);
++ s_sock = -1;
++ return "";
++ }
++
++ packet_len = (((size_t)buf[1]) << 16) | (((size_t)buf[2]) << 8) | ((size_t)buf[3]);
++ if ( packet_len < 1 || packet_len > AGENT_MAXIMUM_PACKET_LENGTH ) {
++ (void) close(s_sock);
++ s_sock = -1;
++ return "";
++ }
++ packet_buf.append((char *)buf, 4);
++ idle_start = Network::timestamp();
++ }
++ /* read in loop until the entire packet is read or EAGAIN happens */
++ do {
++ unsigned char buf[1024];
++ size_t len = packet_len + 4 - packet_buf.length();
++ if (len > sizeof (buf)) {
++ len = sizeof (buf);
++ }
++ rv = read(s_sock, buf, len);
++ if ( (rv < 0) && ( errno == EAGAIN || errno == EWOULDBLOCK ) ) {
++ return "";
++ }
++ if ( rv < 1 ) {
++ (void) close(s_sock);
++ s_sock = -1;
++ return "";
++ }
++ packet_buf.append((char *)buf, rv);
++ idle_start = Network::timestamp();
++ } while (packet_buf.length() < (packet_len + 4));
++ string packet(packet_buf);
++ packet_buf = "";
++ packet_len = 0;
++ return packet;
++#endif
++ return "";
++}
+diff --git a/src/agent/agent.h b/src/agent/agent.h
+new file mode 100644
+index 0000000..d8f98d4
+--- /dev/null
++++ b/src/agent/agent.h
+@@ -0,0 +1,110 @@
++/*
++ Mosh: the mobile shell
++ Copyright 2012 Keith Winstein
++
++ SSH Agent forwarding for Mosh
++ Copyright 2013 Timo J. Rinne
++
++ This program is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program. If not, see <http://www.gnu.org/licenses/>.
++
++ In addition, as a special exception, the copyright holders give
++ permission to link the code of portions of this program with the
++ OpenSSL library under certain conditions as described in each
++ individual source file, and distribute linked combinations including
++ the two.
++
++ You must obey the GNU General Public License in all respects for all
++ of the code used other than OpenSSL. If you modify file(s) with this
++ exception, you may extend this exception to your version of the
++ file(s), but you are not obligated to do so. If you do not wish to do
++ so, delete this exception statement from your version. If you delete
++ this exception statement from all source files in the program, then
++ also delete it here.
++*/
++
++#ifndef AGENT_HPP
++#define AGENT_HPP
++
++#include <string>
++#include <map>
++
++#include "outofband.h"
++
++#define AGENT_MAXIMUM_PACKET_LENGTH 32768 // Not counting the length field.
++#define AGENT_MAXIMUM_OUTPUT_BUFFER_LENGTH (AGENT_MAXIMUM_PACKET_LENGTH * 4) // Counting all data
++#define AGENT_IDLE_TIMEOUT 30 // In seconds. Must be enforced by the caller.
++#define AGENT_PROXY_LISTEN_QUEUE_LENGTH 4
++#define AGENT_FORWARD_OOB_NAME "ssh-agent-forward"
++
++namespace Agent {
++
++ class ProxyAgent;
++
++ class AgentConnection
++ {
++ private:
++ bool s_in_read_set;
++ int s_sock;
++ uint64_t s_id;
++ uint64_t idle_start;
++ string packet_buf;
++ size_t packet_len;
++ AgentConnection(int sock, uint64_t id, ProxyAgent *s_agent_ptr);
++ AgentConnection(std::string agent_path, uint64_t id, ProxyAgent *s_agent_ptr);
++ ~AgentConnection();
++ int sock() { return s_sock; }
++ bool eof() { return (s_sock < 0); }
++ std::string recv_packet();
++ uint64_t idle_time();
++ void mark_in_read_set(bool val) { s_in_read_set = val; }
++ bool in_read_set( void ) { return s_in_read_set; }
++ ProxyAgent *agent_ptr;
++
++ public:
++ friend ProxyAgent;
++ };
++
++ class ProxyAgent {
++ private:
++ Network::OutOfBand *oob_ctl_ptr;
++ Network::OutOfBand *oob( void ) { return oob_ctl_ptr; }
++ void detach_oob(void);
++ void notify_eof(uint64_t agent_id);
++ AgentConnection *get_session();
++ bool server;
++ bool ok;
++ int l_sock;
++ string l_dir;
++ string l_path;
++ uint64_t cnt;
++ std::map< uint64_t, AgentConnection * > agent_sessions;
++ Network::OutOfBandCommunicator *comm;
++ public:
++ ProxyAgent( bool is_server, bool dummy = false );
++ ~ProxyAgent( void );
++ void attach_oob(Network::OutOfBand *oob_ctl);
++ bool active() { return ok && ((! server) || (l_sock >= 0)); }
++ std::string listener_path( void ) { if ( ok && server && l_sock >= 0 ) return l_path; return ""; }
++ void pre_poll( void );
++ void post_poll( void );
++ void post_tick( void );
++ void close_sessions( void );
++ void shutdown_server( void );
++
++ friend AgentConnection;
++ };
++
++}
++
++#endif
+diff --git a/src/frontend/Makefile.am b/src/frontend/Makefile.am
+index a0345ae..91b5a7b 100644
+--- a/src/frontend/Makefile.am
++++ b/src/frontend/Makefile.am
+@@ -1,7 +1,7 @@
+-AM_CPPFLAGS = -I$(srcdir)/../statesync -I$(srcdir)/../terminal -I$(srcdir)/../network -I$(srcdir)/../crypto -I../protobufs -I$(srcdir)/../util $(TINFO_CFLAGS) $(protobuf_CFLAGS) $(OPENSSL_CFLAGS)
++AM_CPPFLAGS = -I$(srcdir)/../statesync -I$(srcdir)/../terminal -I$(srcdir)/../network -I$(srcdir)/../crypto -I$(srcdir)/../protobufs -I$(srcdir)/../agent -I$(srcdir)/../util $(TINFO_CFLAGS) $(protobuf_CFLAGS) $(OPENSSL_CFLAGS)
+ AM_CXXFLAGS = $(WARNING_CXXFLAGS) $(PICKY_CXXFLAGS) $(HARDEN_CFLAGS) $(MISC_CXXFLAGS)
+ AM_LDFLAGS = $(HARDEN_LDFLAGS)
+-LDADD = ../crypto/libmoshcrypto.a ../network/libmoshnetwork.a ../statesync/libmoshstatesync.a ../terminal/libmoshterminal.a ../util/libmoshutil.a ../protobufs/libmoshprotos.a -lm $(TINFO_LIBS) $(protobuf_LIBS) $(OPENSSL_LIBS)
++LDADD = ../crypto/libmoshcrypto.a ../network/libmoshnetwork.a ../statesync/libmoshstatesync.a ../terminal/libmoshterminal.a ../agent/libmoshagent.a ../util/libmoshutil.a ../protobufs/libmoshprotos.a -lm $(TINFO_LIBS) $(protobuf_LIBS) $(OPENSSL_LIBS)
+
+ mosh_server_LDADD = $(LDADD) $(LIBUTIL)
+
+diff --git a/src/frontend/mosh-client.cc b/src/frontend/mosh-client.cc
+index e338682..970cd22 100644
+--- a/src/frontend/mosh-client.cc
++++ b/src/frontend/mosh-client.cc
+@@ -101,10 +101,15 @@ int main( int argc, char *argv[] )
+ /* Detect edge case */
+ fatal_assert( argc > 0 );
+
++ bool forward_agent = false;
++
+ /* Get arguments */
+ int opt;
+- while ( (opt = getopt( argc, argv, "c" )) != -1 ) {
++ while ( (opt = getopt( argc, argv, "cA" )) != -1 ) {
+ switch ( opt ) {
++ case 'A':
++ forward_agent = true;
++ break;
+ case 'c':
+ print_colorcount();
+ exit( 0 );
+@@ -170,7 +175,7 @@ int main( int argc, char *argv[] )
+ set_native_locale();
+
+ try {
+- STMClient client( ip, port, key, predict_mode );
++ STMClient client( ip, port, key, predict_mode, forward_agent );
+ client.init();
+
+ try {
+diff --git a/src/frontend/mosh-server.cc b/src/frontend/mosh-server.cc
+index ae2505f..0c54547 100644
+--- a/src/frontend/mosh-server.cc
++++ b/src/frontend/mosh-server.cc
+@@ -80,6 +80,7 @@
+ #include "locale_utils.h"
+ #include "pty_compat.h"
+ #include "select.h"
++#include "agent.h"
+ #include "timestamp.h"
+ #include "fatal_assert.h"
+
+@@ -93,11 +94,13 @@ typedef Network::Transport< Terminal::Complete, Network::UserStream > ServerConn
+
+ void serve( int host_fd,
+ Terminal::Complete &terminal,
+- ServerConnection &network );
++ ServerConnection &network,
++ Agent::ProxyAgent &agent );
+
+ int run_server( const char *desired_ip, const char *desired_port,
+ const string &command_path, char *command_argv[],
+- const int colors, bool verbose, bool with_motd );
++ const int colors, bool verbose, bool with_motd,
++ bool with_agent_fwd );
+
+ using namespace std;
+
+@@ -166,6 +169,7 @@ int main( int argc, char *argv[] )
+ string command_path;
+ char **command_argv = NULL;
+ int colors = 0;
++ bool with_agent_fwd = false;
+ bool verbose = false; /* don't close stdin/stdout/stderr */
+ /* Will cause mosh-server not to correctly detach on old versions of sshd. */
+ list<string> locale_vars;
+@@ -186,8 +190,11 @@ int main( int argc, char *argv[] )
+ && (strcmp( argv[ 1 ], "new" ) == 0) ) {
+ /* new option syntax */
+ int opt;
+- while ( (opt = getopt( argc - 1, argv + 1, "i:p:c:svl:" )) != -1 ) {
++ while ( (opt = getopt( argc - 1, argv + 1, "i:p:c:svl:A" )) != -1 ) {
+ switch ( opt ) {
++ case 'A':
++ with_agent_fwd = true;
++ break;
+ case 'i':
+ desired_ip = optarg;
+ break;
+@@ -316,7 +323,7 @@ int main( int argc, char *argv[] )
+ }
+
+ try {
+- return run_server( desired_ip, desired_port, command_path, command_argv, colors, verbose, with_motd );
++ return run_server( desired_ip, desired_port, command_path, command_argv, colors, verbose, with_motd, with_agent_fwd );
+ } catch ( const Network::NetworkException& e ) {
+ fprintf( stderr, "Network exception: %s: %s\n",
+ e.function.c_str(), strerror( e.the_errno ) );
+@@ -330,7 +337,8 @@ int main( int argc, char *argv[] )
+
+ int run_server( const char *desired_ip, const char *desired_port,
+ const string &command_path, char *command_argv[],
+- const int colors, bool verbose, bool with_motd ) {
++ const int colors, bool verbose, bool with_motd,
++ bool with_agent_fwd ) {
+ /* get initial window size */
+ struct winsize window_size;
+ if ( ioctl( STDIN_FILENO, TIOCGWINSZ, &window_size ) < 0 ) {
+@@ -383,6 +391,13 @@ int run_server( const char *desired_ip, const char *desired_port,
+
+ fprintf( stderr, "[mosh-server detached, pid = %d]\n", (int)getpid() );
+
++ /* initialize agent listener if requested */
++ Agent::ProxyAgent agent( true, ! with_agent_fwd );
++ if ( with_agent_fwd && (! agent.active()) ) {
++ fprintf( stderr, "Warning: Agent listener initialization failed. Disabling agent forwarding.\n" );
++ with_agent_fwd = false;
++ }
++
+ int master;
+
+ #ifdef HAVE_IUTF8
+@@ -453,6 +468,14 @@ int run_server( const char *desired_ip, const char *desired_port,
+ exit( 1 );
+ }
+
++ /* set SSH_AUTH_SOCK */
++ if ( agent.active() ) {
++ if ( setenv( "SSH_AUTH_SOCK", agent.listener_path().c_str(), true ) < 0 ) {
++ perror( "setenv" );
++ exit( 1 );
++ }
++ }
++
+ /* ask ncurses to send UTF-8 instead of ISO 2022 for line-drawing chars */
+ if ( setenv( "NCURSES_NO_UTF8_ACS", "1", true ) < 0 ) {
+ perror( "setenv" );
+@@ -487,7 +510,7 @@ int run_server( const char *desired_ip, const char *desired_port,
+ #endif
+
+ try {
+- serve( master, terminal, *network );
++ serve( master, terminal, *network, agent );
+ } catch ( const Network::NetworkException& e ) {
+ fprintf( stderr, "Network exception: %s: %s\n",
+ e.function.c_str(), strerror( e.the_errno ) );
+@@ -513,7 +536,7 @@ int run_server( const char *desired_ip, const char *desired_port,
+ return 0;
+ }
+
+-void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network )
++void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network, Agent::ProxyAgent &agent )
+ {
+ /* prepare to poll for events */
+ Select &sel = Select::get_instance();
+@@ -529,6 +552,10 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ saved_addr.s_addr = 0;
+ #endif
+
++ if ( agent.active() ) {
++ agent.attach_oob( network.oob() );
++ }
++
+ while ( 1 ) {
+ try {
+ uint64_t now = Network::timestamp();
+@@ -550,6 +577,10 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ sel.add_fd( host_fd );
+ }
+
++ if ( agent.active() ) {
++ agent.pre_poll();
++ }
++
+ int active_fds = sel.select( timeout );
+ if ( active_fds < 0 ) {
+ perror( "select" );
+@@ -635,6 +666,7 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ /* If the pty slave is closed, reading from the master can fail with
+ EIO (see #264). So we treat errors on read() like EOF. */
+ if ( bytes_read <= 0 ) {
++ agent.shutdown_server();
+ network.start_shutdown();
+ } else {
+ string terminal_to_host = terminal.act( string( buf, bytes_read ) );
+@@ -652,6 +684,7 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ if ( sel.any_signal() ) {
+ /* shutdown signal */
+ if ( network.has_remote_addr() && (!network.shutdown_in_progress()) ) {
++ agent.shutdown_server();
+ network.start_shutdown();
+ } else {
+ break;
+@@ -665,6 +698,7 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+
+ if ( (!network.shutdown_in_progress()) && sel.error( host_fd ) ) {
+ /* host problem */
++ agent.shutdown_server();
+ network.start_shutdown();
+ }
+
+@@ -709,15 +743,29 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ && time_since_remote_state >= uint64_t( timeout_if_no_client ) ) {
+ fprintf( stderr, "No connection within %d seconds.\n",
+ timeout_if_no_client / 1000 );
++ agent.shutdown_server();
+ break;
+ }
+
++ if ( agent.active() ) {
++ if ( time_since_remote_state > (AGENT_IDLE_TIMEOUT * 1000) || time_since_remote_state > 30000 ) {
++ agent.close_sessions();
++ }
++ agent.post_poll();
++ }
++
+ network.tick();
++
++ if ( agent.active() ) {
++ agent.post_tick();
++ }
++
+ } catch ( const Network::NetworkException& e ) {
+ fprintf( stderr, "%s: %s\n", e.function.c_str(), strerror( e.the_errno ) );
+ spin();
+ } catch ( const Crypto::CryptoException& e ) {
+ if ( e.fatal ) {
++ agent.shutdown_server();
+ throw;
+ } else {
+ fprintf( stderr, "Crypto exception: %s\n", e.text.c_str() );
+diff --git a/src/frontend/stmclient.cc b/src/frontend/stmclient.cc
+index 7d68ab1..791300c 100644
+--- a/src/frontend/stmclient.cc
++++ b/src/frontend/stmclient.cc
+@@ -59,6 +59,7 @@
+ #include "pty_compat.h"
+ #include "select.h"
+ #include "timestamp.h"
++#include "agent.h"
+
+ #include "networktransport.cc"
+
+@@ -346,6 +347,11 @@ void STMClient::main( void )
+ /* initialize signal handling and structures */
+ main_init();
+
++ Agent::ProxyAgent agent( false, ! forward_agent );
++ if ( agent.active() ) {
++ agent.attach_oob( network->oob() );
++ }
++
+ /* prepare to poll for events */
+ Select &sel = Select::get_instance();
+
+@@ -371,6 +377,10 @@ void STMClient::main( void )
+ }
+ sel.add_fd( STDIN_FILENO );
+
++ if ( agent.active() ) {
++ agent.pre_poll();
++ }
++
+ int active_fds = sel.select( wait_time );
+ if ( active_fds < 0 ) {
+ perror( "select" );
+@@ -390,6 +400,7 @@ void STMClient::main( void )
+
+ if ( sel.error( *it ) ) {
+ /* network problem */
++ agent.shutdown_server();
+ break;
+ }
+ }
+@@ -403,9 +414,12 @@ void STMClient::main( void )
+ if ( !process_user_input( STDIN_FILENO ) ) {
+ if ( !network->has_remote_addr() ) {
+ break;
+- } else if ( !network->shutdown_in_progress() ) {
+- overlays.get_notification_engine().set_notification_string( wstring( L"Exiting..." ), true );
+- network->start_shutdown();
++ } else {
++ agent.shutdown_server();
++ if ( !network->shutdown_in_progress() ) {
++ overlays.get_notification_engine().set_notification_string( wstring( L"Exiting..." ), true );
++ network->start_shutdown();
++ }
+ }
+ }
+ }
+@@ -428,6 +442,7 @@ void STMClient::main( void )
+ break;
+ } else if ( !network->shutdown_in_progress() ) {
+ overlays.get_notification_engine().set_notification_string( wstring( L"Signal received, shutting down..." ), true );
++ agent.shutdown_server();
+ network->start_shutdown();
+ }
+ }
+@@ -438,6 +453,7 @@ void STMClient::main( void )
+ break;
+ } else if ( !network->shutdown_in_progress() ) {
+ overlays.get_notification_engine().set_notification_string( wstring( L"Exiting..." ), true );
++ agent.shutdown_server();
+ network->start_shutdown();
+ }
+ }
+@@ -466,6 +482,7 @@ void STMClient::main( void )
+ if ( timestamp() - network->get_latest_remote_state().timestamp > 15000 ) {
+ if ( !network->shutdown_in_progress() ) {
+ overlays.get_notification_engine().set_notification_string( wstring( L"Timed out waiting for server..." ), true );
++ agent.shutdown_server();
+ network->start_shutdown();
+ }
+ } else {
+@@ -477,8 +494,16 @@ void STMClient::main( void )
+ overlays.get_notification_engine().set_notification_string( L"" );
+ }
+
++ if ( agent.active() ) {
++ agent.post_poll();
++ }
++
+ network->tick();
+
++ if ( agent.active() ) {
++ agent.post_tick();
++ }
++
+ const Network::NetworkException *exn = network->get_send_exception();
+ if ( exn ) {
+ overlays.get_notification_engine().set_network_exception( *exn );
+diff --git a/src/frontend/stmclient.h b/src/frontend/stmclient.h
+index 51150c6..6946243 100644
+--- a/src/frontend/stmclient.h
++++ b/src/frontend/stmclient.h
+@@ -47,6 +47,7 @@ class STMClient {
+ std::string ip;
+ int port;
+ std::string key;
++ bool forward_agent;
+
+ struct termios saved_termios, raw_termios;
+
+@@ -77,8 +78,8 @@ class STMClient {
+ void resume( void ); /* restore state after SIGCONT */
+
+ public:
+- STMClient( const char *s_ip, int s_port, const char *s_key, const char *predict_mode )
+- : ip( s_ip ), port( s_port ), key( s_key ),
++ STMClient( const char *s_ip, int s_port, const char *s_key, const char *predict_mode, bool s_forward_agent )
++ : ip( s_ip ), port( s_port ), key( s_key ), forward_agent( s_forward_agent ),
+ saved_termios(), raw_termios(),
+ window_size(),
+ local_framebuffer( NULL ),
+diff --git a/src/network/Makefile.am b/src/network/Makefile.am
+index 3143cc4..fe8e3ed 100644
+--- a/src/network/Makefile.am
++++ b/src/network/Makefile.am
+@@ -3,4 +3,4 @@ AM_CXXFLAGS = $(WARNING_CXXFLAGS) $(PICKY_CXXFLAGS) $(HARDEN_CFLAGS) $(MISC_CXXF
+
+ noinst_LIBRARIES = libmoshnetwork.a
+
+-libmoshnetwork_a_SOURCES = network.cc network.h networktransport.cc networktransport.h transportfragment.cc transportfragment.h transportsender.cc transportsender.h transportstate.h compressor.cc compressor.h
++libmoshnetwork_a_SOURCES = network.cc network.h networktransport.cc networktransport.h transportfragment.cc transportfragment.h transportsender.cc transportsender.h transportstate.h compressor.cc compressor.h outofband.h outofband.cc
+diff --git a/src/network/networktransport.cc b/src/network/networktransport.cc
+index 127e80c..6b62e2d 100644
+--- a/src/network/networktransport.cc
++++ b/src/network/networktransport.cc
+@@ -130,6 +130,11 @@ void Transport<MyState, RemoteState>::recv( void )
+ }
+ }
+
++ /* Deliver out of band data */
++ if (inst.has_oob()) {
++ oob()->input(inst.oob());
++ }
++
+ /* apply diff to reference state */
+ TimestampedState<RemoteState> new_state = *reference_state;
+ new_state.timestamp = timestamp();
+diff --git a/src/network/networktransport.h b/src/network/networktransport.h
+index c23d0bd..aca5d38 100644
+--- a/src/network/networktransport.h
++++ b/src/network/networktransport.h
+@@ -83,6 +83,9 @@ namespace Network {
+ /* Find diff between last receiver state and current remote state, then rationalize states. */
+ string get_remote_diff( void );
+
++ /* Get refenrece to out of band control object */
++ OutOfBand *oob( void ) { return sender.oob(); }
++
+ /* Shut down other side of connection. */
+ /* Illegal to change current_state after this. */
+ void start_shutdown( void ) { sender.start_shutdown(); }
+diff --git a/src/network/outofband.cc b/src/network/outofband.cc
+new file mode 100644
+index 0000000..41511ef
+--- /dev/null
++++ b/src/network/outofband.cc
+@@ -0,0 +1,265 @@
++/*
++ Mosh: the mobile shell
++ Copyright 2012 Keith Winstein
++
++ Out of band protocol extension for Mosh
++ Copyright 2013 Timo J. Rinne
++
++ This program is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program. If not, see <http://www.gnu.org/licenses/>.
++
++ In addition, as a special exception, the copyright holders give
++ permission to link the code of portions of this program with the
++ OpenSSL library under certain conditions as described in each
++ individual source file, and distribute linked combinations including
++ the two.
++
++ You must obey the GNU General Public License in all respects for all
++ of the code used other than OpenSSL. If you modify file(s) with this
++ exception, you may extend this exception to your version of the
++ file(s), but you are not obligated to do so. If you do not wish to do
++ so, delete this exception statement from your version. If you delete
++ this exception statement from all source files in the program, then
++ also delete it here.
++*/
++
++#include <list>
++#include <stdio.h>
++#include <stdlib.h>
++#include <time.h>
++
++#include "fatal_assert.h"
++
++#include "outofband.h"
++#include "oob.pb.h"
++
++#include <limits.h>
++
++using namespace Network;
++using namespace OutOfBandBuffers;
++using namespace std;
++
++
++OutOfBand::OutOfBand() {
++ seq_num_out = 0;
++ ack_num_out = 0;
++}
++
++OutOfBandCommunicator *OutOfBand::init(string name, OutOfBandMode mode) {
++ map < string, OutOfBandCommunicator * >::iterator i = comms.find(name);
++ if (i != comms.end()) {
++ return NULL;
++ }
++ OutOfBandCommunicator *comm = new OutOfBandCommunicator(mode, name, this);
++ comms[name] = comm;
++ return comm;
++}
++
++void OutOfBand::uninit(string name) {
++ map < string, OutOfBandCommunicator * >::iterator i = comms.find(name);
++ if (i == comms.end()) {
++ return;
++ }
++ OutOfBandCommunicator *comm = i->second;
++ comms.erase(i);
++ delete comm;
++}
++
++void OutOfBand::uninit(OutOfBandCommunicator *comm) {
++ uninit(comm->name);
++}
++
++void OutOfBand::uninit(void) {
++ map < string, OutOfBandCommunicator * >::iterator i;
++ while ((i = comms.begin()) != comms.end()) {
++ OutOfBandCommunicator *comm = i->second;
++ comms.erase(i);
++ delete comm;
++ }
++}
++
++void OutOfBand::input(string data) {
++ Instruction inst;
++ fatal_assert( inst.ParseFromString(data) );
++ if (inst.has_ack_num()) {
++ uint64_t ack_num = inst.ack_num();
++ if (ack_num != 0) {
++ list < OutOfBandBuffers::Instruction >::iterator i = reliable_instruction_out_sent.begin();
++ while (i != reliable_instruction_out_sent.end()) {
++ fatal_assert((*i).has_seq_num());
++ if ((*i).seq_num() <= ack_num) {
++ i = reliable_instruction_out_sent.erase(i);
++ continue;
++ }
++ break;
++ }
++ }
++ }
++
++ bool ack = false;
++
++ if (inst.has_payload_type() && inst.has_payload_data()) {
++ string payload_type = inst.payload_type();
++ string payload_data = inst.payload_data();
++ uint64_t seq_num = inst.has_seq_num() ? inst.seq_num() : 0;
++ uint64_t oob_mode = inst.has_oob_mode() ? inst.oob_mode() : 0;
++ OutOfBandCommunicator *comm = NULL;
++ map < string, OutOfBandCommunicator * >::iterator i = comms.find(payload_type);
++ if (i != comms.end()) {
++ comm = i->second;
++ fatal_assert(oob_mode == (uint64_t)comm->mode);
++ }
++ if (seq_num == 0) {
++ fatal_assert(oob_mode == (uint64_t)OOB_MODE_DATAGRAM);
++ if (comm != NULL) {
++ comm->datagram_queue.push(payload_data);
++ }
++ } else {
++ fatal_assert(oob_mode == (uint64_t)OOB_MODE_STREAM || oob_mode == (uint64_t)OOB_MODE_RELIABLE_DATAGRAM);
++ if (seq_num == next_seq_num(ack_num_out)) {
++ if (comm != NULL) {
++ switch (comm->mode) {
++ case OOB_MODE_STREAM:
++ comm->stream_buf += payload_data;
++ break;
++ case OOB_MODE_RELIABLE_DATAGRAM:
++ comm->datagram_queue.push(payload_data);
++ break;
++ default:
++ //NOTREACHED
++ fatal_assert(comm->mode == OOB_MODE_STREAM || comm->mode == OOB_MODE_RELIABLE_DATAGRAM);
++ }
++ }
++ ack_num_out = seq_num;
++ }
++ ack = true;
++ }
++ }
++
++ if (ack && (! has_unsent_output())) {
++ Instruction inst;
++ datagram_instruction_out.push(inst);
++ }
++}
++
++bool OutOfBand::has_output(void) {
++ return (! (datagram_instruction_out.empty() && reliable_instruction_out_sent.empty() && reliable_instruction_out_unsent.empty()));
++}
++
++bool OutOfBand::has_unsent_output(void) {
++ return (! (datagram_instruction_out.empty() && reliable_instruction_out_unsent.empty()));
++}
++
++string OutOfBand::output(void) {
++ string rv("");
++ if (! datagram_instruction_out.empty()) {
++ Instruction inst = datagram_instruction_out.front();
++ if (ack_num_out != 0) {
++ inst.set_ack_num(ack_num_out);
++ }
++ fatal_assert(inst.SerializeToString(&rv));
++ datagram_instruction_out.pop();
++ return rv;
++ }
++ if (! reliable_instruction_out_sent.empty()) {
++ Instruction inst = reliable_instruction_out_sent.front();
++ if (ack_num_out != 0) {
++ inst.set_ack_num(ack_num_out);
++ }
++ fatal_assert(inst.SerializeToString(&rv));
++ return rv;
++ }
++ if (! reliable_instruction_out_unsent.empty()) {
++ Instruction inst = reliable_instruction_out_unsent.front();
++ reliable_instruction_out_sent.push_back(inst);
++ reliable_instruction_out_unsent.pop_front();
++ if (ack_num_out != 0) {
++ inst.set_ack_num(ack_num_out);
++ }
++ fatal_assert(inst.SerializeToString(&rv));
++ return rv;
++ }
++ return "";
++}
++
++OutOfBandCommunicator::OutOfBandCommunicator(OutOfBandMode oob_mode, string oob_name, OutOfBand *oob_ctl) {
++ mode = oob_mode;
++ name = oob_name;
++ oob_ctl_ptr = oob_ctl;
++ stream_buf = "";
++}
++
++void OutOfBandCommunicator::send(string data) {
++ Instruction inst;
++ if (oob()->ack_num_out != 0) {
++ inst.set_ack_num(oob()->ack_num_out);
++ }
++ inst.set_payload_type(name);
++ inst.set_payload_data(data);
++ inst.set_oob_mode((uint64_t)mode);
++ switch (mode) {
++ case OOB_MODE_STREAM:
++ case OOB_MODE_RELIABLE_DATAGRAM:
++ inst.set_seq_num(oob()->increment_seq_num_out());
++ oob()->reliable_instruction_out_unsent.push_back(inst);
++ break;
++ //FALLTHROUGH
++ case OOB_MODE_DATAGRAM:
++ oob()->datagram_instruction_out.push(inst);
++ }
++}
++
++bool OutOfBandCommunicator::readable(void) {
++ switch (mode) {
++ case OOB_MODE_STREAM:
++ return (! stream_buf.empty());
++ case OOB_MODE_DATAGRAM:
++ case OOB_MODE_RELIABLE_DATAGRAM:
++ return (! datagram_queue.empty());
++ }
++ //NOTREACHED
++ return false;
++}
++
++string OutOfBandCommunicator::recv(void) {
++ string rv("");
++ switch (mode) {
++ case OOB_MODE_STREAM:
++ if (stream_buf.empty()) {
++ return rv;
++ }
++ rv = stream_buf;
++ stream_buf = "";
++ return rv;
++ case OOB_MODE_RELIABLE_DATAGRAM:
++ case OOB_MODE_DATAGRAM:
++ if (datagram_queue.empty()) {
++ return rv;
++ }
++ rv = datagram_queue.front();
++ datagram_queue.pop();
++ return rv;
++ }
++ //NOTREACHED
++ return "";
++}
++
++string OutOfBandCommunicator::read(size_t len) {
++ fatal_assert(mode == OOB_MODE_STREAM);
++ if (stream_buf.length() < len) {
++ return "";
++ }
++ string rv = stream_buf.substr(0, len);
++ stream_buf = stream_buf.substr(len);
++ return rv;
++}
+diff --git a/src/network/outofband.h b/src/network/outofband.h
+new file mode 100644
+index 0000000..1c95ed5
+--- /dev/null
++++ b/src/network/outofband.h
+@@ -0,0 +1,107 @@
++/*
++ Mosh: the mobile shell
++ Copyright 2012 Keith Winstein
++
++ Out of band protocol extension for Mosh
++ Copyright 2013 Timo J. Rinne
++
++ This program is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program. If not, see <http://www.gnu.org/licenses/>.
++
++ In addition, as a special exception, the copyright holders give
++ permission to link the code of portions of this program with the
++ OpenSSL library under certain conditions as described in each
++ individual source file, and distribute linked combinations including
++ the two.
++
++ You must obey the GNU General Public License in all respects for all
++ of the code used other than OpenSSL. If you modify file(s) with this
++ exception, you may extend this exception to your version of the
++ file(s), but you are not obligated to do so. If you do not wish to do
++ so, delete this exception statement from your version. If you delete
++ this exception statement from all source files in the program, then
++ also delete it here.
++*/
++
++
++#ifndef OUT_OF_BAND_HPP
++#define OUT_OF_BAND_HPP
++
++#include <string>
++#include <queue>
++#include <list>
++#include <map>
++
++#include "oob.pb.h"
++
++using std::string;
++using std::queue;
++using std::list;
++using std::map;
++
++namespace Network {
++
++ enum OutOfBandMode { OOB_MODE_STREAM = 1, OOB_MODE_DATAGRAM = 2, OOB_MODE_RELIABLE_DATAGRAM = 3 };
++
++ class OutOfBand;
++
++ class OutOfBandCommunicator
++ {
++ private:
++ OutOfBandMode mode;
++ string name;
++ string stream_buf;
++ queue < string > datagram_queue;
++ OutOfBand *oob_ctl_ptr;
++ OutOfBand *oob(void) { return oob_ctl_ptr; }
++ OutOfBandCommunicator(OutOfBandMode oob_mode, string oob_name, OutOfBand *oob_ctl);
++
++ public:
++ void send(string data);
++ bool readable(void);
++ string recv(void);
++ string read(size_t len);
++
++ friend class OutOfBand;
++ };
++
++ class OutOfBand
++ {
++ private:
++ map < string, OutOfBandCommunicator * > comms;
++ queue < OutOfBandBuffers::Instruction > datagram_instruction_out;
++ list < OutOfBandBuffers::Instruction > reliable_instruction_out_sent;
++ list < OutOfBandBuffers::Instruction > reliable_instruction_out_unsent;
++ uint64_t seq_num_out;
++ uint64_t ack_num_out;
++ uint64_t next_seq_num(uint64_t sn) { sn++; if (sn == 0) sn++; return sn; }
++ uint64_t increment_seq_num_out(void) { seq_num_out = next_seq_num(seq_num_out); return seq_num_out; }
++
++ public:
++ OutOfBand();
++ ~OutOfBand() { uninit(); }
++ OutOfBandCommunicator *init(string name, OutOfBandMode mode);
++ void uninit(string name);
++ void uninit(OutOfBandCommunicator *comm);
++ void uninit(void);
++ bool has_output(void);
++ bool has_unsent_output(void);
++ // input and output are to be called from transport code only
++ void input(string data);
++ string output(void);
++
++ friend class OutOfBandCommunicator;
++ };
++}
++
++#endif
+diff --git a/src/network/transportsender.cc b/src/network/transportsender.cc
+index e641655..816b76d 100644
+--- a/src/network/transportsender.cc
++++ b/src/network/transportsender.cc
+@@ -96,14 +96,18 @@ void TransportSender<MyState>::calculate_timers( void )
+ next_ack_time = now + ACK_DELAY;
+ }
+
+- if ( !(current_state == sent_states.back().state) ) {
++ if ( oob()->has_unsent_output() ) {
++ next_send_time = sent_states.back().timestamp + send_interval();
++ if ( mindelay_clock != uint64_t( -1 ) ) {
++ next_send_time = max( next_send_time, mindelay_clock + SEND_MINDELAY );
++ }
++ } else if ( !(current_state == sent_states.back().state) ) {
+ if ( mindelay_clock == uint64_t( -1 ) ) {
+ mindelay_clock = now;
+ }
+-
+ next_send_time = max( mindelay_clock + SEND_MINDELAY,
+ sent_states.back().timestamp + send_interval() );
+- } else if ( !(current_state == assumed_receiver_state->state)
++ } else if ( ((!(current_state == assumed_receiver_state->state)) || (oob()->has_output()))
+ && (last_heard + ACTIVE_RETRY_TIMEOUT > now) ) {
+ next_send_time = sent_states.back().timestamp + send_interval();
+ if ( mindelay_clock != uint64_t( -1 ) ) {
+@@ -181,11 +185,12 @@ void TransportSender<MyState>::tick( void )
+ if ( diff.empty() && (now >= next_ack_time) ) {
+ send_empty_ack();
+ mindelay_clock = uint64_t( -1 );
+- } else if ( !diff.empty() && ( (now >= next_send_time)
+- || (now >= next_ack_time) ) ) {
++ } else if ( !diff.empty() && ((now >= next_send_time) || (now >= next_ack_time)) ) {
+ /* Send diffs or ack */
+ send_to_receiver( diff );
+ mindelay_clock = uint64_t( -1 );
++ } else if ( oob()->has_output() && ((now >= next_send_time) || (now >= next_ack_time)) ) {
++ send_empty_ack();
+ }
+ }
+
+@@ -194,7 +199,7 @@ void TransportSender<MyState>::send_empty_ack( void )
+ {
+ uint64_t now = timestamp();
+
+- assert( now >= next_ack_time );
++ assert( now >= next_ack_time || oob()->has_output() );
+
+ uint64_t new_num = sent_states.back().num + 1;
+
+@@ -316,6 +321,10 @@ void TransportSender<MyState>::send_in_fragments( string diff, uint64_t new_num
+ inst.set_diff( diff );
+ inst.set_chaff( make_chaff() );
+
++ if (oob()->has_output()) {
++ inst.set_oob(oob()->output());
++ }
++
+ if ( new_num == uint64_t(-1) ) {
+ shutdown_tries++;
+ }
+diff --git a/src/network/transportsender.h b/src/network/transportsender.h
+index 572c47f..3d4285b 100644
+--- a/src/network/transportsender.h
++++ b/src/network/transportsender.h
+@@ -42,6 +42,7 @@
+ #include "transportstate.h"
+ #include "transportfragment.h"
+ #include "prng.h"
++#include "outofband.h"
+
+ using std::list;
+ using std::pair;
+@@ -104,6 +105,8 @@ namespace Network {
+
+ uint64_t last_heard; /* last time received new state */
+
++ OutOfBand oob_ctl; /* out of band protocol object */
++
+ /* chaff to disguise instruction length */
+ PRNG prng;
+ const string make_chaff( void );
+@@ -133,7 +136,10 @@ namespace Network {
+ void remote_heard( uint64_t ts ) { last_heard = ts; }
+
+ /* Starts shutdown sequence */
+- void start_shutdown( void ) { if ( !shutdown_in_progress ) { shutdown_start = timestamp(); shutdown_in_progress = true; } }
++ void start_shutdown( void ) { if ( !shutdown_in_progress ) { oob_ctl.uninit(); shutdown_start = timestamp(); shutdown_in_progress = true; } }
++
++ /* Get refenrece to out of band control object */
++ OutOfBand *oob( void ) { return &oob_ctl; }
+
+ /* Misc. getters and setters */
+ /* Cannot modify current_state while shutdown in progress */
+diff --git a/src/protobufs/Makefile.am b/src/protobufs/Makefile.am
+index 131ec4e..419525a 100644
+--- a/src/protobufs/Makefile.am
++++ b/src/protobufs/Makefile.am
+@@ -1,4 +1,4 @@
+-source = userinput.proto hostinput.proto transportinstruction.proto
++source = userinput.proto hostinput.proto transportinstruction.proto oob.proto agent.proto
+
+ AM_CPPFLAGS = $(protobuf_CFLAGS)
+ AM_CXXFLAGS = $(WARNING_CXXFLAGS) $(HARDEN_CFLAGS) $(MISC_CXXFLAGS)
+diff --git a/src/protobufs/agent.proto b/src/protobufs/agent.proto
+new file mode 100644
+index 0000000..b350331
+--- /dev/null
++++ b/src/protobufs/agent.proto
+@@ -0,0 +1,8 @@
++option optimize_for = LITE_RUNTIME;
++
++package AgentBuffers;
++
++message Instruction {
++ required uint64 agent_id = 1;
++ optional bytes agent_data = 2;
++}
+diff --git a/src/protobufs/oob.proto b/src/protobufs/oob.proto
+new file mode 100644
+index 0000000..561ca31
+--- /dev/null
++++ b/src/protobufs/oob.proto
+@@ -0,0 +1,11 @@
++option optimize_for = LITE_RUNTIME;
++
++package OutOfBandBuffers;
++
++message Instruction {
++ optional uint64 seq_num = 1;
++ optional uint64 ack_num = 2;
++ optional uint64 oob_mode = 3;
++ optional bytes payload_type = 4;
++ optional bytes payload_data = 5;
++}
+diff --git a/src/protobufs/transportinstruction.proto b/src/protobufs/transportinstruction.proto
+index d4a76f7..bb22a8c 100644
+--- a/src/protobufs/transportinstruction.proto
++++ b/src/protobufs/transportinstruction.proto
+@@ -13,4 +13,6 @@ message Instruction {
+ optional bytes diff = 6;
+
+ optional bytes chaff = 7;
++
++ optional bytes oob = 8;
+ }
+diff --git a/src/util/Makefile.am b/src/util/Makefile.am
+index 25dc3dd..ea41058 100644
+--- a/src/util/Makefile.am
++++ b/src/util/Makefile.am
+@@ -2,4 +2,4 @@ AM_CXXFLAGS = $(WARNING_CXXFLAGS) $(PICKY_CXXFLAGS) $(HARDEN_CFLAGS) $(MISC_CXXF
+
+ noinst_LIBRARIES = libmoshutil.a
+
+-libmoshutil_a_SOURCES = locale_utils.cc locale_utils.h swrite.cc swrite.h dos_assert.h fatal_assert.h select.h select.cc timestamp.h timestamp.cc pty_compat.cc pty_compat.h
++libmoshutil_a_SOURCES = locale_utils.cc locale_utils.h swrite.cc swrite.h dos_assert.h fatal_assert.h select.h select.cc timestamp.h timestamp.cc pty_compat.cc pty_compat.h
+diff --git a/src/util/swrite.cc b/src/util/swrite.cc
+index 64a772c..458477c 100644
+--- a/src/util/swrite.cc
++++ b/src/util/swrite.cc
+@@ -30,11 +30,17 @@
+ also delete it here.
+ */
+
++#include "config.h"
++
+ #include <unistd.h>
+ #include <string.h>
+ #include <stdio.h>
++#include <errno.h>
++#include <time.h>
+
++#include "timestamp.h"
+ #include "swrite.h"
++#include "fatal_assert.h"
+
+ int swrite( int fd, const char *str, ssize_t len )
+ {
+@@ -53,3 +59,42 @@ int swrite( int fd, const char *str, ssize_t len )
+
+ return 0;
+ }
++
++
++int swrite_timeout( int fd, uint64_t timeout_ms, const char *str, ssize_t len )
++{
++ size_t total_bytes_written = 0;
++ size_t bytes_to_write = ( len >= 0 ) ? len : (ssize_t) strlen( str );
++ uint64_t t0 = frozen_timestamp();
++ uint64_t iteration = 0;
++
++ while ( total_bytes_written < bytes_to_write ) {
++ iteration++;
++ ssize_t rv = write( fd, str + total_bytes_written, bytes_to_write - total_bytes_written);
++ if ( rv > 0 ) {
++ total_bytes_written += rv;
++ continue;
++ } else if ( rv < 0 && ( errno == EAGAIN || errno == EWOULDBLOCK ) ) {
++ uint64_t t1 = frozen_timestamp();
++ fatal_assert( t1 >= t0 );
++ uint64_t total_time_spent = t1 - t0;
++ if ( total_time_spent > timeout_ms ) {
++ perror( "write" );
++ return -1;
++ }
++ uint64_t time_left = timeout_ms - total_time_spent;
++ uint64_t sleep_time = 999;
++ if ( time_left < sleep_time ) sleep_time = time_left;
++ if ( iteration * 50 < sleep_time ) sleep_time = iteration * 50;
++ fatal_assert( sleep_time > 0 );
++ struct timespec req;
++ req.tv_sec = 0;
++ req.tv_nsec = sleep_time * 1000000;
++ nanosleep( &req, NULL );
++ continue;
++ } else {
++ perror( "write" );
++ }
++ }
++ return 0;
++}
+diff --git a/src/util/swrite.h b/src/util/swrite.h
+index e75bf7e..2f72782 100644
+--- a/src/util/swrite.h
++++ b/src/util/swrite.h
+@@ -33,6 +33,9 @@
+ #ifndef SWRITE_HPP
+ #define SWRITE_HPP
+
++#include <stdint.h>
++
+ int swrite( int fd, const char *str, ssize_t len = -1 );
++int swrite_timeout( int fd, uint64_t timeout_ms, const char *str, ssize_t len = -1 );
+
+ #endif
+--
+1.9.1
+
+From 42da0db244f79be4218fb7aa0edf801bf4d2e1f3 Mon Sep 17 00:00:00 2001
+From: "Timo J. Rinne" <tri@iki.fi>
+Date: Tue, 14 May 2013 07:57:44 +0000
+Subject: [PATCH] Fixed broken friend declaration.
+
+Signed-off-by: Timo J. Rinne <tri@iki.fi>
+---
+ src/agent/agent.h | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/agent/agent.h b/src/agent/agent.h
+index d8f98d4..835e29c 100644
+--- a/src/agent/agent.h
++++ b/src/agent/agent.h
+@@ -72,7 +72,7 @@ namespace Agent {
+ ProxyAgent *agent_ptr;
+
+ public:
+- friend ProxyAgent;
++ friend class ProxyAgent;
+ };
+
+ class ProxyAgent {
+@@ -102,7 +102,7 @@ namespace Agent {
+ void close_sessions( void );
+ void shutdown_server( void );
+
+- friend AgentConnection;
++ friend class AgentConnection;
+ };
+
+ }
+--
+1.9.1
+
+From 3052df6e3cb41db932e7dd940ab39b83a14b4f29 Mon Sep 17 00:00:00 2001
+From: "Timo J. Rinne" <tri@iki.fi>
+Date: Tue, 14 May 2013 12:18:51 +0000
+Subject: [PATCH] Added some generalization to handling oob objects in event
+ loop. Does not add any new functionality as such, but makes it easier to add
+ new stuff in future.
+
+Signed-off-by: Timo J. Rinne <tri@iki.fi>
+---
+ src/agent/agent.cc | 6 ++---
+ src/agent/agent.h | 23 ++++++++++++--------
+ src/frontend/mosh-server.cc | 30 ++++++++++---------------
+ src/frontend/stmclient.cc | 27 +++++++++--------------
+ src/network/outofband.cc | 53 ++++++++++++++++++++++++++++++++++++++++++---
+ src/network/outofband.h | 29 +++++++++++++++++++++++--
+ 6 files changed, 115 insertions(+), 53 deletions(-)
+
+diff --git a/src/agent/agent.cc b/src/agent/agent.cc
+index 12cd31b..5316f05 100644
+--- a/src/agent/agent.cc
++++ b/src/agent/agent.cc
+@@ -136,7 +136,7 @@ ProxyAgent::ProxyAgent( bool is_server, bool dummy ) {
+
+ ProxyAgent::~ProxyAgent( void ) {
+ #ifdef SUPPORT_AGENT_FORWARDING
+- shutdown_server();
++ shutdown();
+ #endif
+ }
+
+@@ -152,7 +152,7 @@ void ProxyAgent::close_sessions( void ) {
+ #endif
+ }
+
+-void ProxyAgent::shutdown_server( void ) {
++void ProxyAgent::shutdown( void ) {
+ #ifdef SUPPORT_AGENT_FORWARDING
+ detach_oob();
+ if (ok) {
+@@ -174,7 +174,7 @@ void ProxyAgent::attach_oob(OutOfBand *oob_ctl) {
+ detach_oob();
+ fatal_assert(oob_ctl != NULL);
+ oob_ctl_ptr = oob_ctl;
+- comm = oob_ctl_ptr->init(AGENT_FORWARD_OOB_NAME, Network::OOB_MODE_RELIABLE_DATAGRAM);
++ comm = oob_ctl_ptr->init(AGENT_FORWARD_OOB_NAME, Network::OOB_MODE_RELIABLE_DATAGRAM, this);
+ fatal_assert(comm != NULL);
+ }
+
+diff --git a/src/agent/agent.h b/src/agent/agent.h
+index 835e29c..64bbf8a 100644
+--- a/src/agent/agent.h
++++ b/src/agent/agent.h
+@@ -75,11 +75,13 @@ namespace Agent {
+ friend class ProxyAgent;
+ };
+
+- class ProxyAgent {
++ class ProxyAgent : public Network::OutOfBandPlugin
++ {
+ private:
++ void detach_oob(void);
++ Network::OutOfBandCommunicator *comm;
+ Network::OutOfBand *oob_ctl_ptr;
+ Network::OutOfBand *oob( void ) { return oob_ctl_ptr; }
+- void detach_oob(void);
+ void notify_eof(uint64_t agent_id);
+ AgentConnection *get_session();
+ bool server;
+@@ -89,18 +91,21 @@ namespace Agent {
+ string l_path;
+ uint64_t cnt;
+ std::map< uint64_t, AgentConnection * > agent_sessions;
+- Network::OutOfBandCommunicator *comm;
++
+ public:
+- ProxyAgent( bool is_server, bool dummy = false );
+- ~ProxyAgent( void );
+- void attach_oob(Network::OutOfBand *oob_ctl);
+- bool active() { return ok && ((! server) || (l_sock >= 0)); }
+- std::string listener_path( void ) { if ( ok && server && l_sock >= 0 ) return l_path; return ""; }
++ // Required by parent class
++ bool active( void ) { return ok && ((! server) || (l_sock >= 0)); }
+ void pre_poll( void );
+ void post_poll( void );
+ void post_tick( void );
+ void close_sessions( void );
+- void shutdown_server( void );
++ void shutdown( void );
++ void attach_oob(Network::OutOfBand *oob_ctl);
++
++ // Class specific stuff
++ ProxyAgent( bool is_server, bool dummy = false );
++ ~ProxyAgent( void );
++ std::string listener_path( void ) { if ( ok && server && l_sock >= 0 ) return l_path; return ""; }
+
+ friend class AgentConnection;
+ };
+diff --git a/src/frontend/mosh-server.cc b/src/frontend/mosh-server.cc
+index 0c54547..65c18f7 100644
+--- a/src/frontend/mosh-server.cc
++++ b/src/frontend/mosh-server.cc
+@@ -552,9 +552,7 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ saved_addr.s_addr = 0;
+ #endif
+
+- if ( agent.active() ) {
+- agent.attach_oob( network.oob() );
+- }
++ agent.attach_oob(network.oob());
+
+ while ( 1 ) {
+ try {
+@@ -577,9 +575,7 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ sel.add_fd( host_fd );
+ }
+
+- if ( agent.active() ) {
+- agent.pre_poll();
+- }
++ network.oob()->pre_poll();
+
+ int active_fds = sel.select( timeout );
+ if ( active_fds < 0 ) {
+@@ -666,7 +662,7 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ /* If the pty slave is closed, reading from the master can fail with
+ EIO (see #264). So we treat errors on read() like EOF. */
+ if ( bytes_read <= 0 ) {
+- agent.shutdown_server();
++ network.oob()->shutdown();
+ network.start_shutdown();
+ } else {
+ string terminal_to_host = terminal.act( string( buf, bytes_read ) );
+@@ -684,7 +680,7 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ if ( sel.any_signal() ) {
+ /* shutdown signal */
+ if ( network.has_remote_addr() && (!network.shutdown_in_progress()) ) {
+- agent.shutdown_server();
++ network.oob()->shutdown();
+ network.start_shutdown();
+ } else {
+ break;
+@@ -698,7 +694,7 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+
+ if ( (!network.shutdown_in_progress()) && sel.error( host_fd ) ) {
+ /* host problem */
+- agent.shutdown_server();
++ network.oob()->shutdown();
+ network.start_shutdown();
+ }
+
+@@ -743,29 +739,25 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network
+ && time_since_remote_state >= uint64_t( timeout_if_no_client ) ) {
+ fprintf( stderr, "No connection within %d seconds.\n",
+ timeout_if_no_client / 1000 );
+- agent.shutdown_server();
++ network.oob()->shutdown();
+ break;
+ }
+
+- if ( agent.active() ) {
+- if ( time_since_remote_state > (AGENT_IDLE_TIMEOUT * 1000) || time_since_remote_state > 30000 ) {
+- agent.close_sessions();
+- }
+- agent.post_poll();
++ if ( time_since_remote_state > (AGENT_IDLE_TIMEOUT * 1000) || time_since_remote_state > 30000 ) {
++ network.oob()->close_sessions();
+ }
++ network.oob()->post_poll();
+
+ network.tick();
+
+- if ( agent.active() ) {
+- agent.post_tick();
+- }
++ network.oob()->post_tick();
+
+ } catch ( const Network::NetworkException& e ) {
+ fprintf( stderr, "%s: %s\n", e.function.c_str(), strerror( e.the_errno ) );
+ spin();
+ } catch ( const Crypto::CryptoException& e ) {
+ if ( e.fatal ) {
+- agent.shutdown_server();
++ network.oob()->shutdown();
+ throw;
+ } else {
+ fprintf( stderr, "Crypto exception: %s\n", e.text.c_str() );
+diff --git a/src/frontend/stmclient.cc b/src/frontend/stmclient.cc
+index 791300c..ceba763 100644
+--- a/src/frontend/stmclient.cc
++++ b/src/frontend/stmclient.cc
+@@ -348,9 +348,8 @@ void STMClient::main( void )
+ main_init();
+
+ Agent::ProxyAgent agent( false, ! forward_agent );
+- if ( agent.active() ) {
+- agent.attach_oob( network->oob() );
+- }
++
++ agent.attach_oob(network->oob());
+
+ /* prepare to poll for events */
+ Select &sel = Select::get_instance();
+@@ -377,9 +376,7 @@ void STMClient::main( void )
+ }
+ sel.add_fd( STDIN_FILENO );
+
+- if ( agent.active() ) {
+- agent.pre_poll();
+- }
++ network->oob()->pre_poll();
+
+ int active_fds = sel.select( wait_time );
+ if ( active_fds < 0 ) {
+@@ -400,7 +397,7 @@ void STMClient::main( void )
+
+ if ( sel.error( *it ) ) {
+ /* network problem */
+- agent.shutdown_server();
++ network->oob()->shutdown();
+ break;
+ }
+ }
+@@ -415,7 +412,7 @@ void STMClient::main( void )
+ if ( !network->has_remote_addr() ) {
+ break;
+ } else {
+- agent.shutdown_server();
++ network->oob()->shutdown();
+ if ( !network->shutdown_in_progress() ) {
+ overlays.get_notification_engine().set_notification_string( wstring( L"Exiting..." ), true );
+ network->start_shutdown();
+@@ -442,7 +439,7 @@ void STMClient::main( void )
+ break;
+ } else if ( !network->shutdown_in_progress() ) {
+ overlays.get_notification_engine().set_notification_string( wstring( L"Signal received, shutting down..." ), true );
+- agent.shutdown_server();
++ network->oob()->shutdown();
+ network->start_shutdown();
+ }
+ }
+@@ -453,7 +450,7 @@ void STMClient::main( void )
+ break;
+ } else if ( !network->shutdown_in_progress() ) {
+ overlays.get_notification_engine().set_notification_string( wstring( L"Exiting..." ), true );
+- agent.shutdown_server();
++ network->oob()->shutdown();
+ network->start_shutdown();
+ }
+ }
+@@ -482,7 +479,7 @@ void STMClient::main( void )
+ if ( timestamp() - network->get_latest_remote_state().timestamp > 15000 ) {
+ if ( !network->shutdown_in_progress() ) {
+ overlays.get_notification_engine().set_notification_string( wstring( L"Timed out waiting for server..." ), true );
+- agent.shutdown_server();
++ network->oob()->shutdown();
+ network->start_shutdown();
+ }
+ } else {
+@@ -494,15 +491,11 @@ void STMClient::main( void )
+ overlays.get_notification_engine().set_notification_string( L"" );
+ }
+
+- if ( agent.active() ) {
+- agent.post_poll();
+- }
++ network->oob()->post_poll();
+
+ network->tick();
+
+- if ( agent.active() ) {
+- agent.post_tick();
+- }
++ network->oob()->post_tick();
+
+ const Network::NetworkException *exn = network->get_send_exception();
+ if ( exn ) {
+diff --git a/src/network/outofband.cc b/src/network/outofband.cc
+index 41511ef..d03a689 100644
+--- a/src/network/outofband.cc
++++ b/src/network/outofband.cc
+@@ -55,16 +55,62 @@ OutOfBand::OutOfBand() {
+ ack_num_out = 0;
+ }
+
+-OutOfBandCommunicator *OutOfBand::init(string name, OutOfBandMode mode) {
++OutOfBandCommunicator *OutOfBand::init(string name, OutOfBandMode mode, OutOfBandPlugin *plugin) {
+ map < string, OutOfBandCommunicator * >::iterator i = comms.find(name);
+ if (i != comms.end()) {
+ return NULL;
+ }
+- OutOfBandCommunicator *comm = new OutOfBandCommunicator(mode, name, this);
++ OutOfBandCommunicator *comm = new OutOfBandCommunicator(mode, name, this, plugin);
+ comms[name] = comm;
+ return comm;
+ }
+
++void OutOfBand::pre_poll( void ) {
++ map < string, OutOfBandCommunicator * >::iterator i = comms.begin();
++ while (i != comms.end()) {
++ OutOfBandCommunicator *comm = (i++)->second;
++ if (comm->plugin_ptr->active()) {
++ comm->plugin_ptr->pre_poll();
++ }
++ }
++}
++
++void OutOfBand::post_poll( void ) {
++ map < string, OutOfBandCommunicator * >::iterator i = comms.begin();
++ while (i != comms.end()) {
++ OutOfBandCommunicator *comm = (i++)->second;
++ if (comm->plugin_ptr->active()) {
++ comm->plugin_ptr->post_poll();
++ }
++ }
++}
++
++void OutOfBand::post_tick( void ) {
++ map < string, OutOfBandCommunicator * >::iterator i = comms.begin();
++ while (i != comms.end()) {
++ OutOfBandCommunicator *comm = (i++)->second;
++ if (comm->plugin_ptr->active()) {
++ comm->plugin_ptr->post_tick();
++ }
++ }
++}
++
++void OutOfBand::close_sessions( void ) {
++ map < string, OutOfBandCommunicator * >::iterator i = comms.begin();
++ while (i != comms.end()) {
++ OutOfBandCommunicator *comm = (i++)->second;
++ comm->plugin_ptr->close_sessions();
++ }
++}
++
++void OutOfBand::shutdown( void ) {
++ map < string, OutOfBandCommunicator * >::iterator i = comms.begin();
++ while (i != comms.end()) {
++ OutOfBandCommunicator *comm = (i++)->second;
++ comm->plugin_ptr->shutdown();
++ }
++}
++
+ void OutOfBand::uninit(string name) {
+ map < string, OutOfBandCommunicator * >::iterator i = comms.find(name);
+ if (i == comms.end()) {
+@@ -192,10 +238,11 @@ string OutOfBand::output(void) {
+ return "";
+ }
+
+-OutOfBandCommunicator::OutOfBandCommunicator(OutOfBandMode oob_mode, string oob_name, OutOfBand *oob_ctl) {
++OutOfBandCommunicator::OutOfBandCommunicator(OutOfBandMode oob_mode, string oob_name, OutOfBand *oob_ctl, OutOfBandPlugin *plugin) {
+ mode = oob_mode;
+ name = oob_name;
+ oob_ctl_ptr = oob_ctl;
++ plugin_ptr = plugin;
+ stream_buf = "";
+ }
+
+diff --git a/src/network/outofband.h b/src/network/outofband.h
+index 1c95ed5..e43b271 100644
+--- a/src/network/outofband.h
++++ b/src/network/outofband.h
+@@ -54,6 +54,8 @@ namespace Network {
+ enum OutOfBandMode { OOB_MODE_STREAM = 1, OOB_MODE_DATAGRAM = 2, OOB_MODE_RELIABLE_DATAGRAM = 3 };
+
+ class OutOfBand;
++ class OutOfBandPlugin;
++ class OutOfBandCommunicator;
+
+ class OutOfBandCommunicator
+ {
+@@ -62,9 +64,10 @@ namespace Network {
+ string name;
+ string stream_buf;
+ queue < string > datagram_queue;
++ OutOfBandPlugin *plugin_ptr;
+ OutOfBand *oob_ctl_ptr;
+ OutOfBand *oob(void) { return oob_ctl_ptr; }
+- OutOfBandCommunicator(OutOfBandMode oob_mode, string oob_name, OutOfBand *oob_ctl);
++ OutOfBandCommunicator(OutOfBandMode oob_mode, string oob_name, OutOfBand *oob_ctl, OutOfBandPlugin *plugin);
+
+ public:
+ void send(string data);
+@@ -90,7 +93,14 @@ namespace Network {
+ public:
+ OutOfBand();
+ ~OutOfBand() { uninit(); }
+- OutOfBandCommunicator *init(string name, OutOfBandMode mode);
++
++ void pre_poll( void );
++ void post_poll( void );
++ void post_tick( void );
++ void close_sessions( void );
++ void shutdown( void );
++
++ OutOfBandCommunicator *init(string name, OutOfBandMode mode, OutOfBandPlugin *plugin);
+ void uninit(string name);
+ void uninit(OutOfBandCommunicator *comm);
+ void uninit(void);
+@@ -102,6 +112,21 @@ namespace Network {
+
+ friend class OutOfBandCommunicator;
+ };
++
++ class OutOfBandPlugin
++ {
++ public:
++ virtual bool active( void ) = 0;
++ virtual void pre_poll( void ) = 0;
++ virtual void post_poll( void ) = 0;
++ virtual void post_tick( void ) = 0;
++ virtual void close_sessions( void ) = 0;
++ virtual void shutdown( void ) = 0;
++ virtual void attach_oob(Network::OutOfBand *oob_ctl) = 0;
++
++ friend class OutOfBand;
++ };
++
+ }
+
+ #endif
+--
+1.9.1
+