aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/net
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@ubuntu.com>2018-04-25 18:07:30 -0400
committerLibravatarUnit 193 <unit193@ubuntu.com>2018-04-25 18:07:30 -0400
commit9b1b081cfdb1c0fb6457278775e0823f8bc10f62 (patch)
treece8840148d8445055ba9e4f12263b2208f234c16 /src/lib/net
Import Upstream version 2.0.0+dfsgupstream/2.0.0+dfsg
Diffstat (limited to 'src/lib/net')
-rw-r--r--src/lib/net/CMakeLists.txt28
-rw-r--r--src/lib/net/IDataSocket.cpp39
-rw-r--r--src/lib/net/IDataSocket.h73
-rw-r--r--src/lib/net/IListenSocket.h51
-rw-r--r--src/lib/net/ISocket.h60
-rw-r--r--src/lib/net/ISocketFactory.h48
-rw-r--r--src/lib/net/ISocketMultiplexerJob.h76
-rw-r--r--src/lib/net/NetworkAddress.cpp214
-rw-r--r--src/lib/net/NetworkAddress.h123
-rw-r--r--src/lib/net/SecureListenSocket.cpp85
-rw-r--r--src/lib/net/SecureListenSocket.h36
-rw-r--r--src/lib/net/SecureSocket.cpp867
-rw-r--r--src/lib/net/SecureSocket.h95
-rw-r--r--src/lib/net/SocketMultiplexer.cpp352
-rw-r--r--src/lib/net/SocketMultiplexer.h111
-rw-r--r--src/lib/net/TCPListenSocket.cpp158
-rw-r--r--src/lib/net/TCPListenSocket.h60
-rw-r--r--src/lib/net/TCPSocket.cpp596
-rw-r--r--src/lib/net/TCPSocket.h116
-rw-r--r--src/lib/net/TCPSocketFactory.cpp68
-rw-r--r--src/lib/net/TCPSocketFactory.h44
-rw-r--r--src/lib/net/TSocketMultiplexerMethodJob.h109
-rw-r--r--src/lib/net/XSocket.cpp117
-rw-r--r--src/lib/net/XSocket.h98
24 files changed, 3624 insertions, 0 deletions
diff --git a/src/lib/net/CMakeLists.txt b/src/lib/net/CMakeLists.txt
new file mode 100644
index 0000000..5439450
--- /dev/null
+++ b/src/lib/net/CMakeLists.txt
@@ -0,0 +1,28 @@
+# barrier -- mouse and keyboard sharing utility
+# Copyright (C) 2012-2016 Symless Ltd.
+# Copyright (C) 2009 Nick Bolton
+#
+# This package is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# found in the file LICENSE that should have accompanied this file.
+#
+# This package 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/>.
+
+file(GLOB headers "*.h")
+file(GLOB sources "*.cpp")
+
+if (BARRIER_ADD_HEADERS)
+ list(APPEND sources ${headers})
+endif()
+
+add_library(net STATIC ${sources})
+
+if (UNIX)
+ target_link_libraries(net mt io ${OPENSSL_LIBS})
+endif()
diff --git a/src/lib/net/IDataSocket.cpp b/src/lib/net/IDataSocket.cpp
new file mode 100644
index 0000000..cc679c3
--- /dev/null
+++ b/src/lib/net/IDataSocket.cpp
@@ -0,0 +1,39 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#include "net/IDataSocket.h"
+#include "base/EventQueue.h"
+
+//
+// IDataSocket
+//
+
+void
+IDataSocket::close()
+{
+ // this is here to work around a VC++6 bug. see the header file.
+ assert(0 && "bad call");
+}
+
+void*
+IDataSocket::getEventTarget() const
+{
+ // this is here to work around a VC++6 bug. see the header file.
+ assert(0 && "bad call");
+ return NULL;
+}
diff --git a/src/lib/net/IDataSocket.h b/src/lib/net/IDataSocket.h
new file mode 100644
index 0000000..dc07df5
--- /dev/null
+++ b/src/lib/net/IDataSocket.h
@@ -0,0 +1,73 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "net/ISocket.h"
+#include "io/IStream.h"
+#include "base/String.h"
+#include "base/EventTypes.h"
+
+//! Data stream socket interface
+/*!
+This interface defines the methods common to all network sockets that
+represent a full-duplex data stream.
+*/
+class IDataSocket : public ISocket, public barrier::IStream {
+public:
+ class ConnectionFailedInfo {
+ public:
+ ConnectionFailedInfo(const char* what) : m_what(what) { }
+ String m_what;
+ };
+
+ IDataSocket(IEventQueue* events) { }
+
+ //! @name manipulators
+ //@{
+
+ //! Connect socket
+ /*!
+ Attempt to connect to a remote endpoint. This returns immediately
+ and sends a connected event when successful or a connection failed
+ event when it fails. The stream acts as if shutdown for input and
+ output until the stream connects.
+ */
+ virtual void connect(const NetworkAddress&) = 0;
+
+ //@}
+
+ // ISocket overrides
+ // close() and getEventTarget() aren't pure to work around a bug
+ // in VC++6. it claims the methods are unused locals and warns
+ // that it's removing them. it's presumably tickled by inheriting
+ // methods with identical signatures from both superclasses.
+ virtual void bind(const NetworkAddress&) = 0;
+ virtual void close();
+ virtual void* getEventTarget() const;
+
+ // IStream overrides
+ virtual UInt32 read(void* buffer, UInt32 n) = 0;
+ virtual void write(const void* buffer, UInt32 n) = 0;
+ virtual void flush() = 0;
+ virtual void shutdownInput() = 0;
+ virtual void shutdownOutput() = 0;
+ virtual bool isReady() const = 0;
+ virtual bool isFatal() const = 0;
+ virtual UInt32 getSize() const = 0;
+};
diff --git a/src/lib/net/IListenSocket.h b/src/lib/net/IListenSocket.h
new file mode 100644
index 0000000..73dcc6e
--- /dev/null
+++ b/src/lib/net/IListenSocket.h
@@ -0,0 +1,51 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "net/ISocket.h"
+#include "base/EventTypes.h"
+
+class IDataSocket;
+
+//! Listen socket interface
+/*!
+This interface defines the methods common to all network sockets that
+listen for incoming connections.
+*/
+class IListenSocket : public ISocket {
+public:
+ //! @name manipulators
+ //@{
+
+ //! Accept connection
+ /*!
+ Accept a connection, returning a socket representing the full-duplex
+ data stream. Returns NULL if no socket is waiting to be accepted.
+ This is only valid after a call to \c bind().
+ */
+ virtual IDataSocket*
+ accept() = 0;
+
+ //@}
+
+ // ISocket overrides
+ virtual void bind(const NetworkAddress&) = 0;
+ virtual void close() = 0;
+ virtual void* getEventTarget() const = 0;
+};
diff --git a/src/lib/net/ISocket.h b/src/lib/net/ISocket.h
new file mode 100644
index 0000000..0e9688b
--- /dev/null
+++ b/src/lib/net/ISocket.h
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "base/Event.h"
+#include "base/EventTypes.h"
+
+class NetworkAddress;
+
+//! Generic socket interface
+/*!
+This interface defines the methods common to all network sockets.
+Generated events use \c this as the target.
+*/
+class ISocket : public IInterface {
+public:
+ //! @name manipulators
+ //@{
+
+ //! Bind socket to address
+ /*!
+ Binds the socket to a particular address.
+ */
+ virtual void bind(const NetworkAddress&) = 0;
+
+ //! Close socket
+ /*!
+ Closes the socket. This should flush the output stream.
+ */
+ virtual void close() = 0;
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Get event target
+ /*!
+ Returns the event target for events generated by this socket.
+ */
+ virtual void* getEventTarget() const = 0;
+
+ //@}
+};
diff --git a/src/lib/net/ISocketFactory.h b/src/lib/net/ISocketFactory.h
new file mode 100644
index 0000000..e440953
--- /dev/null
+++ b/src/lib/net/ISocketFactory.h
@@ -0,0 +1,48 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "common/IInterface.h"
+#include "arch/IArchNetwork.h"
+
+class IDataSocket;
+class IListenSocket;
+
+//! Socket factory
+/*!
+This interface defines the methods common to all factories used to
+create sockets.
+*/
+class ISocketFactory : public IInterface {
+public:
+ //! @name accessors
+ //@{
+
+ //! Create data socket
+ virtual IDataSocket* create(
+ IArchNetwork::EAddressFamily family,
+ bool secure) const = 0;
+
+ //! Create listen socket
+ virtual IListenSocket* createListen(
+ IArchNetwork::EAddressFamily family,
+ bool secure) const = 0;
+
+ //@}
+};
diff --git a/src/lib/net/ISocketMultiplexerJob.h b/src/lib/net/ISocketMultiplexerJob.h
new file mode 100644
index 0000000..ddd3ba5
--- /dev/null
+++ b/src/lib/net/ISocketMultiplexerJob.h
@@ -0,0 +1,76 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "arch/IArchNetwork.h"
+#include "common/IInterface.h"
+
+//! Socket multiplexer job
+/*!
+A socket multiplexer job handles events on a socket.
+*/
+class ISocketMultiplexerJob : public IInterface {
+public:
+ //! @name manipulators
+ //@{
+
+ //! Handle socket event
+ /*!
+ Called by a socket multiplexer when the socket becomes readable,
+ writable, or has an error. It should return itself if the same
+ job can continue to service events, a new job if the socket must
+ be serviced differently, or NULL if the socket should no longer
+ be serviced. The socket is readable if \p readable is true,
+ writable if \p writable is true, and in error if \p error is
+ true.
+
+ This call must not attempt to directly change the job for this
+ socket by calling \c addSocket() or \c removeSocket() on the
+ multiplexer. It must instead return the new job. It can,
+ however, add or remove jobs for other sockets.
+ */
+ virtual ISocketMultiplexerJob*
+ run(bool readable, bool writable, bool error) = 0;
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Get the socket
+ /*!
+ Return the socket to multiplex
+ */
+ virtual ArchSocket getSocket() const = 0;
+
+ //! Check for interest in readability
+ /*!
+ Return true if the job is interested in being run if the socket
+ becomes readable.
+ */
+ virtual bool isReadable() const = 0;
+
+ //! Check for interest in writability
+ /*!
+ Return true if the job is interested in being run if the socket
+ becomes writable.
+ */
+ virtual bool isWritable() const = 0;
+
+ //@}
+};
diff --git a/src/lib/net/NetworkAddress.cpp b/src/lib/net/NetworkAddress.cpp
new file mode 100644
index 0000000..c395ab0
--- /dev/null
+++ b/src/lib/net/NetworkAddress.cpp
@@ -0,0 +1,214 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#include "net/NetworkAddress.h"
+
+#include "net/XSocket.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+
+#include <cstdlib>
+
+//
+// NetworkAddress
+//
+
+// name re-resolution adapted from a patch by Brent Priddy.
+
+NetworkAddress::NetworkAddress() :
+ m_address(NULL),
+ m_hostname(),
+ m_port(0)
+{
+ // note -- make no calls to Network socket interface here;
+ // we're often called prior to Network::init().
+}
+
+NetworkAddress::NetworkAddress(int port) :
+ m_address(NULL),
+ m_hostname(),
+ m_port(port)
+{
+ checkPort();
+ m_address = ARCH->newAnyAddr(IArchNetwork::kINET);
+ ARCH->setAddrPort(m_address, m_port);
+}
+
+NetworkAddress::NetworkAddress(const NetworkAddress& addr) :
+ m_address(addr.m_address != NULL ? ARCH->copyAddr(addr.m_address) : NULL),
+ m_hostname(addr.m_hostname),
+ m_port(addr.m_port)
+{
+ // do nothing
+}
+
+NetworkAddress::NetworkAddress(const String& hostname, int port) :
+ m_address(NULL),
+ m_hostname(hostname),
+ m_port(port)
+{
+ // check for port suffix
+ String::size_type i = m_hostname.rfind(':');
+ if (i != String::npos && i + 1 < m_hostname.size()) {
+ // found a colon. see if it looks like an IPv6 address.
+ bool colonNotation = false;
+ bool dotNotation = false;
+ bool doubleColon = false;
+ for (String::size_type j = 0; j < i; ++j) {
+ if (m_hostname[j] == ':') {
+ colonNotation = true;
+ dotNotation = false;
+ if (m_hostname[j + 1] == ':') {
+ doubleColon = true;
+ }
+ }
+ else if (m_hostname[j] == '.' && colonNotation) {
+ dotNotation = true;
+ }
+ }
+
+ // port suffix is ambiguous with IPv6 notation if there's
+ // a double colon and the end of the address is not in dot
+ // notation. in that case we assume it's not a port suffix.
+ // the user can replace the double colon with zeros to
+ // disambiguate.
+ if ((!doubleColon || dotNotation) && !colonNotation) {
+ // parse port from hostname
+ char* end;
+ const char* chostname = m_hostname.c_str();
+ long suffixPort = strtol(chostname + i + 1, &end, 10);
+ if (end == chostname + i + 1 || *end != '\0') {
+ throw XSocketAddress(XSocketAddress::kBadPort,
+ m_hostname, m_port);
+ }
+
+ // trim port from hostname
+ m_hostname.erase(i);
+
+ // save port
+ m_port = static_cast<int>(suffixPort);
+ }
+ }
+
+ // check port number
+ checkPort();
+}
+
+NetworkAddress::~NetworkAddress()
+{
+ if (m_address != NULL) {
+ ARCH->closeAddr(m_address);
+ }
+}
+
+NetworkAddress&
+NetworkAddress::operator=(const NetworkAddress& addr)
+{
+ ArchNetAddress newAddr = NULL;
+ if (addr.m_address != NULL) {
+ newAddr = ARCH->copyAddr(addr.m_address);
+ }
+ if (m_address != NULL) {
+ ARCH->closeAddr(m_address);
+ }
+ m_address = newAddr;
+ m_hostname = addr.m_hostname;
+ m_port = addr.m_port;
+ return *this;
+}
+
+void
+NetworkAddress::resolve()
+{
+ // discard previous address
+ if (m_address != NULL) {
+ ARCH->closeAddr(m_address);
+ m_address = NULL;
+ }
+
+ try {
+ // if hostname is empty then use wildcard address otherwise look
+ // up the name.
+ if (m_hostname.empty()) {
+ m_address = ARCH->newAnyAddr(IArchNetwork::kINET);
+ }
+ else {
+ m_address = ARCH->nameToAddr(m_hostname);
+ }
+ }
+ catch (XArchNetworkNameUnknown&) {
+ throw XSocketAddress(XSocketAddress::kNotFound, m_hostname, m_port);
+ }
+ catch (XArchNetworkNameNoAddress&) {
+ throw XSocketAddress(XSocketAddress::kNoAddress, m_hostname, m_port);
+ }
+ catch (XArchNetworkNameUnsupported&) {
+ throw XSocketAddress(XSocketAddress::kUnsupported, m_hostname, m_port);
+ }
+ catch (XArchNetworkName&) {
+ throw XSocketAddress(XSocketAddress::kUnknown, m_hostname, m_port);
+ }
+
+ // set port in address
+ ARCH->setAddrPort(m_address, m_port);
+}
+
+bool
+NetworkAddress::operator==(const NetworkAddress& addr) const
+{
+ return ARCH->isEqualAddr(m_address, addr.m_address);
+}
+
+bool
+NetworkAddress::operator!=(const NetworkAddress& addr) const
+{
+ return !operator==(addr);
+}
+
+bool
+NetworkAddress::isValid() const
+{
+ return (m_address != NULL);
+}
+
+const ArchNetAddress&
+NetworkAddress::getAddress() const
+{
+ return m_address;
+}
+
+int
+NetworkAddress::getPort() const
+{
+ return m_port;
+}
+
+String
+NetworkAddress::getHostname() const
+{
+ return m_hostname;
+}
+
+void
+NetworkAddress::checkPort()
+{
+ // check port number
+ if (m_port <= 0 || m_port > 65535) {
+ throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port);
+ }
+}
diff --git a/src/lib/net/NetworkAddress.h b/src/lib/net/NetworkAddress.h
new file mode 100644
index 0000000..cbd15f5
--- /dev/null
+++ b/src/lib/net/NetworkAddress.h
@@ -0,0 +1,123 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "base/String.h"
+#include "base/EventTypes.h"
+#include "arch/IArchNetwork.h"
+
+//! Network address type
+/*!
+This class represents a network address.
+*/
+class NetworkAddress {
+public:
+ /*!
+ Constructs the invalid address
+ */
+ NetworkAddress();
+
+ /*!
+ Construct the wildcard address with the given port. \c port must
+ not be zero.
+ */
+ NetworkAddress(int port);
+
+ /*!
+ Construct the network address for the given \c hostname and \c port.
+ If \c hostname can be parsed as a numerical address then that's how
+ it's used, otherwise it's used as a host name. If \c hostname ends
+ in ":[0-9]+" then that suffix is extracted and used as the port,
+ overridding the port parameter. The resulting port must be a valid
+ port number (zero is not a valid port number) otherwise \c XSocketAddress
+ is thrown with an error of \c XSocketAddress::kBadPort. The hostname
+ is not resolved by the c'tor; use \c resolve to do that.
+ */
+ NetworkAddress(const String& hostname, int port);
+
+ NetworkAddress(const NetworkAddress&);
+
+ ~NetworkAddress();
+
+ NetworkAddress& operator=(const NetworkAddress&);
+
+ //! @name manipulators
+ //@{
+
+ //! Resolve address
+ /*!
+ Resolves the hostname to an address. This can be done any number of
+ times and is done automatically by the c'tor taking a hostname.
+ Throws XSocketAddress if resolution is unsuccessful, after which
+ \c isValid returns false until the next call to this method.
+ */
+ void resolve();
+
+ //@}
+ //! @name accessors
+ //@{
+
+ //! Check address equality
+ /*!
+ Returns true if this address is equal to \p address.
+ */
+ bool operator==(const NetworkAddress& address) const;
+
+ //! Check address inequality
+ /*!
+ Returns true if this address is not equal to \p address.
+ */
+ bool operator!=(const NetworkAddress& address) const;
+
+ //! Check address validity
+ /*!
+ Returns true if this is not the invalid address.
+ */
+ bool isValid() const;
+
+ //! Get address
+ /*!
+ Returns the address in the platform's native network address
+ structure.
+ */
+ const ArchNetAddress& getAddress() const;
+
+ //! Get port
+ /*!
+ Returns the port passed to the c'tor as a suffix to the hostname,
+ if that existed, otherwise as passed directly to the c'tor.
+ */
+ int getPort() const;
+
+ //! Get hostname
+ /*!
+ Returns the hostname passed to the c'tor sans any port suffix.
+ */
+ String getHostname() const;
+
+ //@}
+
+private:
+ void checkPort();
+
+private:
+ ArchNetAddress m_address;
+ String m_hostname;
+ int m_port;
+};
diff --git a/src/lib/net/SecureListenSocket.cpp b/src/lib/net/SecureListenSocket.cpp
new file mode 100644
index 0000000..58ffe09
--- /dev/null
+++ b/src/lib/net/SecureListenSocket.cpp
@@ -0,0 +1,85 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#include "SecureListenSocket.h"
+
+#include "SecureSocket.h"
+#include "net/NetworkAddress.h"
+#include "net/SocketMultiplexer.h"
+#include "net/TSocketMultiplexerMethodJob.h"
+#include "arch/XArch.h"
+
+static const char s_certificateDir[] = { "SSL" };
+static const char s_certificateFilename[] = { "Barrier.pem" };
+
+//
+// SecureListenSocket
+//
+
+SecureListenSocket::SecureListenSocket(
+ IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ IArchNetwork::EAddressFamily family) :
+ TCPListenSocket(events, socketMultiplexer, family)
+{
+}
+
+IDataSocket*
+SecureListenSocket::accept()
+{
+ SecureSocket* socket = NULL;
+ try {
+ socket = new SecureSocket(
+ m_events,
+ m_socketMultiplexer,
+ ARCH->acceptSocket(m_socket, NULL));
+ socket->initSsl(true);
+
+ if (socket != NULL) {
+ setListeningJob();
+ }
+
+ String certificateFilename = barrier::string::sprintf("%s/%s/%s",
+ ARCH->getProfileDirectory().c_str(),
+ s_certificateDir,
+ s_certificateFilename);
+
+ bool loaded = socket->loadCertificates(certificateFilename);
+ if (!loaded) {
+ delete socket;
+ return NULL;
+ }
+
+ socket->secureAccept();
+
+ return dynamic_cast<IDataSocket*>(socket);
+ }
+ catch (XArchNetwork&) {
+ if (socket != NULL) {
+ delete socket;
+ setListeningJob();
+ }
+ return NULL;
+ }
+ catch (std::exception &ex) {
+ if (socket != NULL) {
+ delete socket;
+ setListeningJob();
+ }
+ throw ex;
+ }
+}
diff --git a/src/lib/net/SecureListenSocket.h b/src/lib/net/SecureListenSocket.h
new file mode 100644
index 0000000..d0c6e23
--- /dev/null
+++ b/src/lib/net/SecureListenSocket.h
@@ -0,0 +1,36 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "net/TCPListenSocket.h"
+#include "common/stdset.h"
+
+class IEventQueue;
+class SocketMultiplexer;
+class IDataSocket;
+
+class SecureListenSocket : public TCPListenSocket{
+public:
+ SecureListenSocket(IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ IArchNetwork::EAddressFamily family);
+
+ // IListenSocket overrides
+ virtual IDataSocket*
+ accept();
+};
diff --git a/src/lib/net/SecureSocket.cpp b/src/lib/net/SecureSocket.cpp
new file mode 100644
index 0000000..1fefae0
--- /dev/null
+++ b/src/lib/net/SecureSocket.cpp
@@ -0,0 +1,867 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#include "SecureSocket.h"
+
+#include "net/TSocketMultiplexerMethodJob.h"
+#include "base/TMethodEventJob.h"
+#include "net/TCPSocket.h"
+#include "mt/Lock.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <cstring>
+#include <cstdlib>
+#include <memory>
+#include <fstream>
+#include <memory>
+
+//
+// SecureSocket
+//
+
+#define MAX_ERROR_SIZE 65535
+
+static const float s_retryDelay = 0.01f;
+
+enum {
+ kMsgSize = 128
+};
+
+static const char kFingerprintDirName[] = "SSL/Fingerprints";
+//static const char kFingerprintLocalFilename[] = "Local.txt";
+static const char kFingerprintTrustedServersFilename[] = "TrustedServers.txt";
+//static const char kFingerprintTrustedClientsFilename[] = "TrustedClients.txt";
+
+struct Ssl {
+ SSL_CTX* m_context;
+ SSL* m_ssl;
+};
+
+SecureSocket::SecureSocket(
+ IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ IArchNetwork::EAddressFamily family) :
+ TCPSocket(events, socketMultiplexer, family),
+ m_ssl(nullptr),
+ m_secureReady(false),
+ m_fatal(false)
+{
+}
+
+SecureSocket::SecureSocket(
+ IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ ArchSocket socket) :
+ TCPSocket(events, socketMultiplexer, socket),
+ m_ssl(nullptr),
+ m_secureReady(false),
+ m_fatal(false)
+{
+}
+
+SecureSocket::~SecureSocket()
+{
+ isFatal(true);
+ // take socket from multiplexer ASAP otherwise the race condition
+ // could cause events to get called on a dead object. TCPSocket
+ // will do this, too, but the double-call is harmless
+ setJob(NULL);
+ if (m_ssl->m_ssl != NULL) {
+ SSL_shutdown(m_ssl->m_ssl);
+
+ SSL_free(m_ssl->m_ssl);
+ m_ssl->m_ssl = NULL;
+ }
+ if (m_ssl->m_context != NULL) {
+ SSL_CTX_free(m_ssl->m_context);
+ m_ssl->m_context = NULL;
+ }
+ // removing sleep() because I have no idea why you would want to do it
+ // ... smells of trying to cover up a bug you don't understand
+ //ARCH->sleep(1);
+ delete m_ssl;
+}
+
+void
+SecureSocket::close()
+{
+ isFatal(true);
+
+ SSL_shutdown(m_ssl->m_ssl);
+
+ TCPSocket::close();
+}
+
+void
+SecureSocket::connect(const NetworkAddress& addr)
+{
+ m_events->adoptHandler(m_events->forIDataSocket().connected(),
+ getEventTarget(),
+ new TMethodEventJob<SecureSocket>(this,
+ &SecureSocket::handleTCPConnected));
+
+ TCPSocket::connect(addr);
+}
+
+ISocketMultiplexerJob*
+SecureSocket::newJob()
+{
+ // after TCP connection is established, SecureSocket will pick up
+ // connected event and do secureConnect
+ if (m_connected && !m_secureReady) {
+ return NULL;
+ }
+
+ return TCPSocket::newJob();
+}
+
+void
+SecureSocket::secureConnect()
+{
+ setJob(new TSocketMultiplexerMethodJob<SecureSocket>(
+ this, &SecureSocket::serviceConnect,
+ getSocket(), isReadable(), isWritable()));
+}
+
+void
+SecureSocket::secureAccept()
+{
+ setJob(new TSocketMultiplexerMethodJob<SecureSocket>(
+ this, &SecureSocket::serviceAccept,
+ getSocket(), isReadable(), isWritable()));
+}
+
+TCPSocket::EJobResult
+SecureSocket::doRead()
+{
+ static UInt8 buffer[4096];
+ memset(buffer, 0, sizeof(buffer));
+ int bytesRead = 0;
+ int status = 0;
+
+ if (isSecureReady()) {
+ status = secureRead(buffer, sizeof(buffer), bytesRead);
+ if (status < 0) {
+ return kBreak;
+ }
+ else if (status == 0) {
+ return kNew;
+ }
+ }
+ else {
+ return kRetry;
+ }
+
+ if (bytesRead > 0) {
+ bool wasEmpty = (m_inputBuffer.getSize() == 0);
+
+ // slurp up as much as possible
+ do {
+ m_inputBuffer.write(buffer, bytesRead);
+
+ status = secureRead(buffer, sizeof(buffer), bytesRead);
+ if (status < 0) {
+ return kBreak;
+ }
+ } while (bytesRead > 0 || status > 0);
+
+ // send input ready if input buffer was empty
+ if (wasEmpty) {
+ sendEvent(m_events->forIStream().inputReady());
+ }
+ }
+ else {
+ // remote write end of stream hungup. our input side
+ // has therefore shutdown but don't flush our buffer
+ // since there's still data to be read.
+ sendEvent(m_events->forIStream().inputShutdown());
+ if (!m_writable && m_inputBuffer.getSize() == 0) {
+ sendEvent(m_events->forISocket().disconnected());
+ m_connected = false;
+ }
+ m_readable = false;
+ return kNew;
+ }
+
+ return kRetry;
+}
+
+TCPSocket::EJobResult
+SecureSocket::doWrite()
+{
+ static bool s_retry = false;
+ static int s_retrySize = 0;
+ static std::unique_ptr<char[]> s_staticBuffer;
+ static std::size_t s_staticBufferSize = 0;
+
+ // write data
+ int bufferSize = 0;
+ int bytesWrote = 0;
+ int status = 0;
+
+ if (!isSecureReady())
+ return kRetry;
+
+ if (s_retry) {
+ bufferSize = s_retrySize;
+ } else {
+ bufferSize = m_outputBuffer.getSize();
+ if (bufferSize > s_staticBufferSize) {
+ s_staticBuffer.reset(new char[bufferSize]);
+ s_staticBufferSize = bufferSize;
+ }
+ if (bufferSize > 0) {
+ memcpy(s_staticBuffer.get(), m_outputBuffer.peek(bufferSize), bufferSize);
+ }
+ }
+
+ if (bufferSize == 0) {
+ return kRetry;
+ }
+
+ status = secureWrite(s_staticBuffer.get(), bufferSize, bytesWrote);
+ if (status > 0) {
+ s_retry = false;
+ } else if (status < 0) {
+ return kBreak;
+ } else if (status == 0) {
+ s_retry = true;
+ s_retrySize = bufferSize;
+ return kNew;
+ }
+
+ if (bytesWrote > 0) {
+ discardWrittenData(bytesWrote);
+ return kNew;
+ }
+
+ return kRetry;
+}
+
+int
+SecureSocket::secureRead(void* buffer, int size, int& read)
+{
+ if (m_ssl->m_ssl != NULL) {
+ LOG((CLOG_DEBUG2 "reading secure socket"));
+ read = SSL_read(m_ssl->m_ssl, buffer, size);
+
+ static int retry;
+
+ // Check result will cleanup the connection in the case of a fatal
+ checkResult(read, retry);
+
+ if (retry) {
+ return 0;
+ }
+
+ if (isFatal()) {
+ return -1;
+ }
+ }
+ // According to SSL spec, the number of bytes read must not be negative and
+ // not have an error code from SSL_get_error(). If this happens, it is
+ // itself an error. Let the parent handle the case
+ return read;
+}
+
+int
+SecureSocket::secureWrite(const void* buffer, int size, int& wrote)
+{
+ if (m_ssl->m_ssl != NULL) {
+ LOG((CLOG_DEBUG2 "writing secure socket:%p", this));
+
+ wrote = SSL_write(m_ssl->m_ssl, buffer, size);
+
+ static int retry;
+
+ // Check result will cleanup the connection in the case of a fatal
+ checkResult(wrote, retry);
+
+ if (retry) {
+ return 0;
+ }
+
+ if (isFatal()) {
+ return -1;
+ }
+ }
+ // According to SSL spec, r must not be negative and not have an error code
+ // from SSL_get_error(). If this happens, it is itself an error. Let the
+ // parent handle the case
+ return wrote;
+}
+
+bool
+SecureSocket::isSecureReady()
+{
+ return m_secureReady;
+}
+
+void
+SecureSocket::initSsl(bool server)
+{
+ m_ssl = new Ssl();
+ m_ssl->m_context = NULL;
+ m_ssl->m_ssl = NULL;
+
+ initContext(server);
+}
+
+bool
+SecureSocket::loadCertificates(String& filename)
+{
+ if (filename.empty()) {
+ showError("ssl certificate is not specified");
+ return false;
+ }
+ else {
+ std::ifstream file(filename.c_str());
+ bool exist = file.good();
+ file.close();
+
+ if (!exist) {
+ String errorMsg("ssl certificate doesn't exist: ");
+ errorMsg.append(filename);
+ showError(errorMsg.c_str());
+ return false;
+ }
+ }
+
+ int r = 0;
+ r = SSL_CTX_use_certificate_file(m_ssl->m_context, filename.c_str(), SSL_FILETYPE_PEM);
+ if (r <= 0) {
+ showError("could not use ssl certificate");
+ return false;
+ }
+
+ r = SSL_CTX_use_PrivateKey_file(m_ssl->m_context, filename.c_str(), SSL_FILETYPE_PEM);
+ if (r <= 0) {
+ showError("could not use ssl private key");
+ return false;
+ }
+
+ r = SSL_CTX_check_private_key(m_ssl->m_context);
+ if (!r) {
+ showError("could not verify ssl private key");
+ return false;
+ }
+
+ return true;
+}
+
+void
+SecureSocket::initContext(bool server)
+{
+ SSL_library_init();
+
+ const SSL_METHOD* method;
+
+ // load & register all cryptos, etc.
+ OpenSSL_add_all_algorithms();
+
+ // load all error messages
+ SSL_load_error_strings();
+
+ if (CLOG->getFilter() >= kINFO) {
+ showSecureLibInfo();
+ }
+
+ // SSLv23_method uses TLSv1, with the ability to fall back to SSLv3
+ if (server) {
+ method = SSLv23_server_method();
+ }
+ else {
+ method = SSLv23_client_method();
+ }
+
+ // create new context from method
+ SSL_METHOD* m = const_cast<SSL_METHOD*>(method);
+ m_ssl->m_context = SSL_CTX_new(m);
+
+ // drop SSLv3 support
+ SSL_CTX_set_options(m_ssl->m_context, SSL_OP_NO_SSLv3);
+
+ if (m_ssl->m_context == NULL) {
+ showError();
+ }
+}
+
+void
+SecureSocket::createSSL()
+{
+ // I assume just one instance is needed
+ // get new SSL state with context
+ if (m_ssl->m_ssl == NULL) {
+ assert(m_ssl->m_context != NULL);
+ m_ssl->m_ssl = SSL_new(m_ssl->m_context);
+ }
+}
+
+int
+SecureSocket::secureAccept(int socket)
+{
+ createSSL();
+
+ // set connection socket to SSL state
+ SSL_set_fd(m_ssl->m_ssl, socket);
+
+ LOG((CLOG_DEBUG2 "accepting secure socket"));
+ int r = SSL_accept(m_ssl->m_ssl);
+
+ static int retry;
+
+ checkResult(r, retry);
+
+ if (isFatal()) {
+ // tell user and sleep so the socket isn't hammered.
+ LOG((CLOG_ERR "failed to accept secure socket"));
+ LOG((CLOG_INFO "client connection may not be secure"));
+ m_secureReady = false;
+ ARCH->sleep(1);
+ retry = 0;
+ return -1; // Failed, error out
+ }
+
+ // If not fatal and no retry, state is good
+ if (retry == 0) {
+ m_secureReady = true;
+ LOG((CLOG_INFO "accepted secure socket"));
+ if (CLOG->getFilter() >= kDEBUG1) {
+ showSecureCipherInfo();
+ }
+ showSecureConnectInfo();
+ return 1;
+ }
+
+ // If not fatal and retry is set, not ready, and return retry
+ if (retry > 0) {
+ LOG((CLOG_DEBUG2 "retry accepting secure socket"));
+ m_secureReady = false;
+ ARCH->sleep(s_retryDelay);
+ return 0;
+ }
+
+ // no good state exists here
+ LOG((CLOG_ERR "unexpected state attempting to accept connection"));
+ return -1;
+}
+
+int
+SecureSocket::secureConnect(int socket)
+{
+ createSSL();
+
+ // attach the socket descriptor
+ SSL_set_fd(m_ssl->m_ssl, socket);
+
+ LOG((CLOG_DEBUG2 "connecting secure socket"));
+ int r = SSL_connect(m_ssl->m_ssl);
+
+ static int retry;
+
+ checkResult(r, retry);
+
+ if (isFatal()) {
+ LOG((CLOG_ERR "failed to connect secure socket"));
+ retry = 0;
+ return -1;
+ }
+
+ // If we should retry, not ready and return 0
+ if (retry > 0) {
+ LOG((CLOG_DEBUG2 "retry connect secure socket"));
+ m_secureReady = false;
+ ARCH->sleep(s_retryDelay);
+ return 0;
+ }
+
+ retry = 0;
+ // No error, set ready, process and return ok
+ m_secureReady = true;
+ if (verifyCertFingerprint()) {
+ LOG((CLOG_INFO "connected to secure socket"));
+ if (!showCertificate()) {
+ disconnect();
+ return -1;// Cert fail, error
+ }
+ }
+ else {
+ LOG((CLOG_ERR "failed to verify server certificate fingerprint"));
+ disconnect();
+ return -1; // Fingerprint failed, error
+ }
+ LOG((CLOG_DEBUG2 "connected secure socket"));
+ if (CLOG->getFilter() >= kDEBUG1) {
+ showSecureCipherInfo();
+ }
+ showSecureConnectInfo();
+ return 1;
+}
+
+bool
+SecureSocket::showCertificate()
+{
+ X509* cert;
+ char* line;
+
+ // get the server's certificate
+ cert = SSL_get_peer_certificate(m_ssl->m_ssl);
+ if (cert != NULL) {
+ line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
+ LOG((CLOG_INFO "server ssl certificate info: %s", line));
+ OPENSSL_free(line);
+ X509_free(cert);
+ }
+ else {
+ showError("server has no ssl certificate");
+ return false;
+ }
+
+ return true;
+}
+
+void
+SecureSocket::checkResult(int status, int& retry)
+{
+ // ssl errors are a little quirky. the "want" errors are normal and
+ // should result in a retry.
+
+ int errorCode = SSL_get_error(m_ssl->m_ssl, status);
+
+ switch (errorCode) {
+ case SSL_ERROR_NONE:
+ retry = 0;
+ // operation completed
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ // connection closed
+ isFatal(true);
+ LOG((CLOG_DEBUG "ssl connection closed"));
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ retry++;
+ LOG((CLOG_DEBUG2 "want to read, error=%d, attempt=%d", errorCode, retry));
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ // Need to make sure the socket is known to be writable so the impending
+ // select action actually triggers on a write. This isn't necessary for
+ // m_readable because the socket logic is always readable
+ m_writable = true;
+ retry++;
+ LOG((CLOG_DEBUG2 "want to write, error=%d, attempt=%d", errorCode, retry));
+ break;
+
+ case SSL_ERROR_WANT_CONNECT:
+ retry++;
+ LOG((CLOG_DEBUG2 "want to connect, error=%d, attempt=%d", errorCode, retry));
+ break;
+
+ case SSL_ERROR_WANT_ACCEPT:
+ retry++;
+ LOG((CLOG_DEBUG2 "want to accept, error=%d, attempt=%d", errorCode, retry));
+ break;
+
+ case SSL_ERROR_SYSCALL:
+ LOG((CLOG_ERR "ssl error occurred (system call failure)"));
+ if (ERR_peek_error() == 0) {
+ if (status == 0) {
+ LOG((CLOG_ERR "eof violates ssl protocol"));
+ }
+ else if (status == -1) {
+ // underlying socket I/O reproted an error
+ try {
+ ARCH->throwErrorOnSocket(getSocket());
+ }
+ catch (XArchNetwork& e) {
+ LOG((CLOG_ERR "%s", e.what()));
+ }
+ }
+ }
+
+ isFatal(true);
+ break;
+
+ case SSL_ERROR_SSL:
+ LOG((CLOG_ERR "ssl error occurred (generic failure)"));
+ isFatal(true);
+ break;
+
+ default:
+ LOG((CLOG_ERR "ssl error occurred (unknown failure)"));
+ isFatal(true);
+ break;
+ }
+
+ if (isFatal()) {
+ retry = 0;
+ showError();
+ disconnect();
+ }
+}
+
+void
+SecureSocket::showError(const char* reason)
+{
+ if (reason != NULL) {
+ LOG((CLOG_ERR "%s", reason));
+ }
+
+ String error = getError();
+ if (!error.empty()) {
+ LOG((CLOG_ERR "%s", error.c_str()));
+ }
+}
+
+String
+SecureSocket::getError()
+{
+ unsigned long e = ERR_get_error();
+
+ if (e != 0) {
+ char error[MAX_ERROR_SIZE];
+ ERR_error_string_n(e, error, MAX_ERROR_SIZE);
+ return error;
+ }
+ else {
+ return "";
+ }
+}
+
+void
+SecureSocket::disconnect()
+{
+ sendEvent(getEvents()->forISocket().stopRetry());
+ sendEvent(getEvents()->forISocket().disconnected());
+ sendEvent(getEvents()->forIStream().inputShutdown());
+}
+
+void
+SecureSocket::formatFingerprint(String& fingerprint, bool hex, bool separator)
+{
+ if (hex) {
+ // to hexidecimal
+ barrier::string::toHex(fingerprint, 2);
+ }
+
+ // all uppercase
+ barrier::string::uppercase(fingerprint);
+
+ if (separator) {
+ // add colon to separate each 2 charactors
+ size_t separators = fingerprint.size() / 2;
+ for (size_t i = 1; i < separators; i++) {
+ fingerprint.insert(i * 3 - 1, ":");
+ }
+ }
+}
+
+bool
+SecureSocket::verifyCertFingerprint()
+{
+ // calculate received certificate fingerprint
+ X509 *cert = cert = SSL_get_peer_certificate(m_ssl->m_ssl);
+ EVP_MD* tempDigest;
+ unsigned char tempFingerprint[EVP_MAX_MD_SIZE];
+ unsigned int tempFingerprintLen;
+ tempDigest = (EVP_MD*)EVP_sha1();
+ int digestResult = X509_digest(cert, tempDigest, tempFingerprint, &tempFingerprintLen);
+
+ if (digestResult <= 0) {
+ LOG((CLOG_ERR "failed to calculate fingerprint, digest result: %d", digestResult));
+ return false;
+ }
+
+ // format fingerprint into hexdecimal format with colon separator
+ String fingerprint(reinterpret_cast<char*>(tempFingerprint), tempFingerprintLen);
+ formatFingerprint(fingerprint);
+ LOG((CLOG_NOTE "server fingerprint: %s", fingerprint.c_str()));
+
+ String trustedServersFilename;
+ trustedServersFilename = barrier::string::sprintf(
+ "%s/%s/%s",
+ ARCH->getProfileDirectory().c_str(),
+ kFingerprintDirName,
+ kFingerprintTrustedServersFilename);
+
+ // check if this fingerprint exist
+ String fileLine;
+ std::ifstream file;
+ file.open(trustedServersFilename.c_str());
+
+ bool isValid = false;
+ while (!file.eof() && file.is_open()) {
+ getline(file,fileLine);
+ if (!fileLine.empty()) {
+ if (fileLine.compare(fingerprint) == 0) {
+ isValid = true;
+ break;
+ }
+ }
+ }
+
+ file.close();
+ return isValid;
+}
+
+ISocketMultiplexerJob*
+SecureSocket::serviceConnect(ISocketMultiplexerJob* job,
+ bool, bool write, bool error)
+{
+ Lock lock(&getMutex());
+
+ int status = 0;
+#ifdef SYSAPI_WIN32
+ status = secureConnect(static_cast<int>(getSocket()->m_socket));
+#elif SYSAPI_UNIX
+ status = secureConnect(getSocket()->m_fd);
+#endif
+
+ // If status < 0, error happened
+ if (status < 0) {
+ return NULL;
+ }
+
+ // If status > 0, success
+ if (status > 0) {
+ sendEvent(m_events->forIDataSocket().secureConnected());
+ return newJob();
+ }
+
+ // Retry case
+ return new TSocketMultiplexerMethodJob<SecureSocket>(
+ this, &SecureSocket::serviceConnect,
+ getSocket(), isReadable(), isWritable());
+}
+
+ISocketMultiplexerJob*
+SecureSocket::serviceAccept(ISocketMultiplexerJob* job,
+ bool, bool write, bool error)
+{
+ Lock lock(&getMutex());
+
+ int status = 0;
+#ifdef SYSAPI_WIN32
+ status = secureAccept(static_cast<int>(getSocket()->m_socket));
+#elif SYSAPI_UNIX
+ status = secureAccept(getSocket()->m_fd);
+#endif
+ // If status < 0, error happened
+ if (status < 0) {
+ return NULL;
+ }
+
+ // If status > 0, success
+ if (status > 0) {
+ sendEvent(m_events->forClientListener().accepted());
+ return newJob();
+ }
+
+ // Retry case
+ return new TSocketMultiplexerMethodJob<SecureSocket>(
+ this, &SecureSocket::serviceAccept,
+ getSocket(), isReadable(), isWritable());
+}
+
+void
+showCipherStackDesc(STACK_OF(SSL_CIPHER) * stack) {
+ char msg[kMsgSize];
+ int i = 0;
+ for ( ; i < sk_SSL_CIPHER_num(stack) ; i++) {
+ const SSL_CIPHER * cipher = sk_SSL_CIPHER_value(stack,i);
+
+ SSL_CIPHER_description(cipher, msg, kMsgSize);
+
+ // Why does SSL put a newline in the description?
+ int pos = (int)strlen(msg) - 1;
+ if (msg[pos] == '\n') {
+ msg[pos] = '\0';
+ }
+
+ LOG((CLOG_DEBUG1 "%s",msg));
+ }
+}
+
+void
+SecureSocket::showSecureCipherInfo()
+{
+ STACK_OF(SSL_CIPHER) * sStack = SSL_get_ciphers(m_ssl->m_ssl);
+
+ if (sStack == NULL) {
+ LOG((CLOG_DEBUG1 "local cipher list not available"));
+ }
+ else {
+ LOG((CLOG_DEBUG1 "available local ciphers:"));
+ showCipherStackDesc(sStack);
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+ // m_ssl->m_ssl->session->ciphers is not forward compatable,
+ // In future release of OpenSSL, it's not visible,
+ STACK_OF(SSL_CIPHER) * cStack = m_ssl->m_ssl->session->ciphers;
+#else
+ // Use SSL_get_client_ciphers() for newer versions
+ STACK_OF(SSL_CIPHER) * cStack = SSL_get_client_ciphers(m_ssl->m_ssl);
+#endif
+ if (cStack == NULL) {
+ LOG((CLOG_DEBUG1 "remote cipher list not available"));
+ }
+ else {
+ LOG((CLOG_DEBUG1 "available remote ciphers:"));
+ showCipherStackDesc(cStack);
+ }
+ return;
+}
+
+void
+SecureSocket::showSecureLibInfo()
+{
+ LOG((CLOG_INFO "%s",SSLeay_version(SSLEAY_VERSION)));
+ LOG((CLOG_DEBUG1 "openSSL : %s",SSLeay_version(SSLEAY_CFLAGS)));
+ LOG((CLOG_DEBUG1 "openSSL : %s",SSLeay_version(SSLEAY_BUILT_ON)));
+ LOG((CLOG_DEBUG1 "openSSL : %s",SSLeay_version(SSLEAY_PLATFORM)));
+ LOG((CLOG_DEBUG1 "%s",SSLeay_version(SSLEAY_DIR)));
+ return;
+}
+
+void
+SecureSocket::showSecureConnectInfo()
+{
+ const SSL_CIPHER* cipher = SSL_get_current_cipher(m_ssl->m_ssl);
+
+ if (cipher != NULL) {
+ char msg[kMsgSize];
+ SSL_CIPHER_description(cipher, msg, kMsgSize);
+ LOG((CLOG_INFO "%s", msg));
+ }
+ return;
+}
+
+void
+SecureSocket::handleTCPConnected(const Event& event, void*)
+{
+ if (getSocket() == nullptr) {
+ LOG((CLOG_DEBUG "disregarding stale connect event"));
+ return;
+ }
+ secureConnect();
+}
diff --git a/src/lib/net/SecureSocket.h b/src/lib/net/SecureSocket.h
new file mode 100644
index 0000000..01d3c3f
--- /dev/null
+++ b/src/lib/net/SecureSocket.h
@@ -0,0 +1,95 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2015-2016 Symless Ltd.
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "net/TCPSocket.h"
+#include "net/XSocket.h"
+
+class IEventQueue;
+class SocketMultiplexer;
+class ISocketMultiplexerJob;
+
+struct Ssl;
+
+//! Secure socket
+/*!
+A secure socket using SSL.
+*/
+class SecureSocket : public TCPSocket {
+public:
+ SecureSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family);
+ SecureSocket(IEventQueue* events,
+ SocketMultiplexer* socketMultiplexer,
+ ArchSocket socket);
+ ~SecureSocket();
+
+ // ISocket overrides
+ void close();
+
+ // IDataSocket overrides
+ virtual void connect(const NetworkAddress&);
+
+ ISocketMultiplexerJob*
+ newJob();
+ bool isFatal() const { return m_fatal; }
+ void isFatal(bool b) { m_fatal = b; }
+ bool isSecureReady();
+ void secureConnect();
+ void secureAccept();
+ int secureRead(void* buffer, int size, int& read);
+ int secureWrite(const void* buffer, int size, int& wrote);
+ EJobResult doRead();
+ EJobResult doWrite();
+ void initSsl(bool server);
+ bool loadCertificates(String& CertFile);
+
+private:
+ // SSL
+ void initContext(bool server);
+ void createSSL();
+ int secureAccept(int s);
+ int secureConnect(int s);
+ bool showCertificate();
+ void checkResult(int n, int& retry);
+ void showError(const char* reason = NULL);
+ String getError();
+ void disconnect();
+ void formatFingerprint(String& fingerprint,
+ bool hex = true,
+ bool separator = true);
+ bool verifyCertFingerprint();
+
+ ISocketMultiplexerJob*
+ serviceConnect(ISocketMultiplexerJob*,
+ bool, bool, bool);
+
+ ISocketMultiplexerJob*
+ serviceAccept(ISocketMultiplexerJob*,
+ bool, bool, bool);
+
+ void showSecureConnectInfo();
+ void showSecureLibInfo();
+ void showSecureCipherInfo();
+
+ void handleTCPConnected(const Event& event, void*);
+
+private:
+ Ssl* m_ssl;
+ bool m_secureReady;
+ bool m_fatal;
+};
diff --git a/src/lib/net/SocketMultiplexer.cpp b/src/lib/net/SocketMultiplexer.cpp
new file mode 100644
index 0000000..c4bc64a
--- /dev/null
+++ b/src/lib/net/SocketMultiplexer.cpp
@@ -0,0 +1,352 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#include "net/SocketMultiplexer.h"
+
+#include "net/ISocketMultiplexerJob.h"
+#include "mt/CondVar.h"
+#include "mt/Lock.h"
+#include "mt/Mutex.h"
+#include "mt/Thread.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+#include "base/TMethodJob.h"
+#include "common/stdvector.h"
+
+//
+// SocketMultiplexer
+//
+
+SocketMultiplexer::SocketMultiplexer() :
+ m_mutex(new Mutex),
+ m_thread(NULL),
+ m_update(false),
+ m_jobsReady(new CondVar<bool>(m_mutex, false)),
+ m_jobListLock(new CondVar<bool>(m_mutex, false)),
+ m_jobListLockLocked(new CondVar<bool>(m_mutex, false)),
+ m_jobListLocker(NULL),
+ m_jobListLockLocker(NULL)
+{
+ // this pointer just has to be unique and not NULL. it will
+ // never be dereferenced. it's used to identify cursor nodes
+ // in the jobs list.
+ // TODO: Remove this evilness
+ m_cursorMark = reinterpret_cast<ISocketMultiplexerJob*>(this);
+
+ // start thread
+ m_thread = new Thread(new TMethodJob<SocketMultiplexer>(
+ this, &SocketMultiplexer::serviceThread));
+}
+
+SocketMultiplexer::~SocketMultiplexer()
+{
+ m_thread->cancel();
+ m_thread->unblockPollSocket();
+ m_thread->wait();
+ delete m_thread;
+ delete m_jobsReady;
+ delete m_jobListLock;
+ delete m_jobListLockLocked;
+ delete m_jobListLocker;
+ delete m_jobListLockLocker;
+ delete m_mutex;
+
+ // clean up jobs
+ for (SocketJobMap::iterator i = m_socketJobMap.begin();
+ i != m_socketJobMap.end(); ++i) {
+ delete *(i->second);
+ }
+}
+
+void
+SocketMultiplexer::addSocket(ISocket* socket, ISocketMultiplexerJob* job)
+{
+ assert(socket != NULL);
+ assert(job != NULL);
+
+ // prevent other threads from locking the job list
+ lockJobListLock();
+
+ // break thread out of poll
+ m_thread->unblockPollSocket();
+
+ // lock the job list
+ lockJobList();
+
+ // insert/replace job
+ SocketJobMap::iterator i = m_socketJobMap.find(socket);
+ if (i == m_socketJobMap.end()) {
+ // we *must* put the job at the end so the order of jobs in
+ // the list continue to match the order of jobs in pfds in
+ // serviceThread().
+ JobCursor j = m_socketJobs.insert(m_socketJobs.end(), job);
+ m_update = true;
+ m_socketJobMap.insert(std::make_pair(socket, j));
+ }
+ else {
+ JobCursor j = i->second;
+ if (*j != job) {
+ delete *j;
+ *j = job;
+ }
+ m_update = true;
+ }
+
+ // unlock the job list
+ unlockJobList();
+}
+
+void
+SocketMultiplexer::removeSocket(ISocket* socket)
+{
+ assert(socket != NULL);
+
+ // prevent other threads from locking the job list
+ lockJobListLock();
+
+ // break thread out of poll
+ m_thread->unblockPollSocket();
+
+ // lock the job list
+ lockJobList();
+
+ // remove job. rather than removing it from the map we put NULL
+ // in the list instead so the order of jobs in the list continues
+ // to match the order of jobs in pfds in serviceThread().
+ SocketJobMap::iterator i = m_socketJobMap.find(socket);
+ if (i != m_socketJobMap.end()) {
+ if (*(i->second) != NULL) {
+ delete *(i->second);
+ *(i->second) = NULL;
+ m_update = true;
+ }
+ }
+
+ // unlock the job list
+ unlockJobList();
+}
+
+void
+SocketMultiplexer::serviceThread(void*)
+{
+ std::vector<IArchNetwork::PollEntry> pfds;
+ IArchNetwork::PollEntry pfd;
+
+ // service the connections
+ for (;;) {
+ Thread::testCancel();
+
+ // wait until there are jobs to handle
+ {
+ Lock lock(m_mutex);
+ while (!(bool)*m_jobsReady) {
+ m_jobsReady->wait();
+ }
+ }
+
+ // lock the job list
+ lockJobListLock();
+ lockJobList();
+
+ // collect poll entries
+ if (m_update) {
+ m_update = false;
+ pfds.clear();
+ pfds.reserve(m_socketJobMap.size());
+
+ JobCursor cursor = newCursor();
+ JobCursor jobCursor = nextCursor(cursor);
+ while (jobCursor != m_socketJobs.end()) {
+ ISocketMultiplexerJob* job = *jobCursor;
+ if (job != NULL) {
+ pfd.m_socket = job->getSocket();
+ pfd.m_events = 0;
+ if (job->isReadable()) {
+ pfd.m_events |= IArchNetwork::kPOLLIN;
+ }
+ if (job->isWritable()) {
+ pfd.m_events |= IArchNetwork::kPOLLOUT;
+ }
+ pfds.push_back(pfd);
+ }
+ jobCursor = nextCursor(cursor);
+ }
+ deleteCursor(cursor);
+ }
+
+ int status;
+ try {
+ // check for status
+ if (!pfds.empty()) {
+ status = ARCH->pollSocket(&pfds[0], (int)pfds.size(), -1);
+ }
+ else {
+ status = 0;
+ }
+ }
+ catch (XArchNetwork& e) {
+ LOG((CLOG_WARN "error in socket multiplexer: %s", e.what()));
+ status = 0;
+ }
+
+ if (status != 0) {
+ // iterate over socket jobs, invoking each and saving the
+ // new job.
+ UInt32 i = 0;
+ JobCursor cursor = newCursor();
+ JobCursor jobCursor = nextCursor(cursor);
+ while (i < pfds.size() && jobCursor != m_socketJobs.end()) {
+ if (*jobCursor != NULL) {
+ // get poll state
+ unsigned short revents = pfds[i].m_revents;
+ bool read = ((revents & IArchNetwork::kPOLLIN) != 0);
+ bool write = ((revents & IArchNetwork::kPOLLOUT) != 0);
+ bool error = ((revents & (IArchNetwork::kPOLLERR |
+ IArchNetwork::kPOLLNVAL)) != 0);
+
+ // run job
+ ISocketMultiplexerJob* job = *jobCursor;
+ ISocketMultiplexerJob* newJob = job->run(read, write, error);
+
+ // save job, if different
+ if (newJob != job) {
+ Lock lock(m_mutex);
+ delete job;
+ *jobCursor = newJob;
+ m_update = true;
+ }
+ ++i;
+ }
+
+ // next job
+ jobCursor = nextCursor(cursor);
+ }
+ deleteCursor(cursor);
+ }
+
+ // delete any removed socket jobs
+ for (SocketJobMap::iterator i = m_socketJobMap.begin();
+ i != m_socketJobMap.end();) {
+ if (*(i->second) == NULL) {
+ m_socketJobs.erase(i->second);
+ m_socketJobMap.erase(i++);
+ m_update = true;
+ }
+ else {
+ ++i;
+ }
+ }
+
+ // unlock the job list
+ unlockJobList();
+ }
+}
+
+SocketMultiplexer::JobCursor
+SocketMultiplexer::newCursor()
+{
+ Lock lock(m_mutex);
+ return m_socketJobs.insert(m_socketJobs.begin(), m_cursorMark);
+}
+
+SocketMultiplexer::JobCursor
+SocketMultiplexer::nextCursor(JobCursor cursor)
+{
+ Lock lock(m_mutex);
+ JobCursor j = m_socketJobs.end();
+ JobCursor i = cursor;
+ while (++i != m_socketJobs.end()) {
+ if (*i != m_cursorMark) {
+ // found a real job (as opposed to a cursor)
+ j = i;
+
+ // move our cursor just past the job
+ m_socketJobs.splice(++i, m_socketJobs, cursor);
+ break;
+ }
+ }
+ return j;
+}
+
+void
+SocketMultiplexer::deleteCursor(JobCursor cursor)
+{
+ Lock lock(m_mutex);
+ m_socketJobs.erase(cursor);
+}
+
+void
+SocketMultiplexer::lockJobListLock()
+{
+ Lock lock(m_mutex);
+
+ // wait for the lock on the lock
+ while (*m_jobListLockLocked) {
+ m_jobListLockLocked->wait();
+ }
+
+ // take ownership of the lock on the lock
+ *m_jobListLockLocked = true;
+ m_jobListLockLocker = new Thread(Thread::getCurrentThread());
+}
+
+void
+SocketMultiplexer::lockJobList()
+{
+ Lock lock(m_mutex);
+
+ // make sure we're the one that called lockJobListLock()
+ assert(*m_jobListLockLocker == Thread::getCurrentThread());
+
+ // wait for the job list lock
+ while (*m_jobListLock) {
+ m_jobListLock->wait();
+ }
+
+ // take ownership of the lock
+ *m_jobListLock = true;
+ m_jobListLocker = m_jobListLockLocker;
+ m_jobListLockLocker = NULL;
+
+ // release the lock on the lock
+ *m_jobListLockLocked = false;
+ m_jobListLockLocked->broadcast();
+}
+
+void
+SocketMultiplexer::unlockJobList()
+{
+ Lock lock(m_mutex);
+
+ // make sure we're the one that called lockJobList()
+ assert(*m_jobListLocker == Thread::getCurrentThread());
+
+ // release the lock
+ delete m_jobListLocker;
+ m_jobListLocker = NULL;
+ *m_jobListLock = false;
+ m_jobListLock->signal();
+
+ // set new jobs ready state
+ bool isReady = !m_socketJobMap.empty();
+ if (*m_jobsReady != isReady) {
+ *m_jobsReady = isReady;
+ m_jobsReady->signal();
+ }
+}
diff --git a/src/lib/net/SocketMultiplexer.h b/src/lib/net/SocketMultiplexer.h
new file mode 100644
index 0000000..4aa39fc
--- /dev/null
+++ b/src/lib/net/SocketMultiplexer.h
@@ -0,0 +1,111 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "arch/IArchNetwork.h"
+#include "common/stdlist.h"
+#include "common/stdmap.h"
+
+template <class T>
+class CondVar;
+class Mutex;
+class Thread;
+class ISocket;
+class ISocketMultiplexerJob;
+
+//! Socket multiplexer
+/*!
+A socket multiplexer services multiple sockets simultaneously.
+*/
+class SocketMultiplexer {
+public:
+ SocketMultiplexer();
+ ~SocketMultiplexer();
+
+ //! @name manipulators
+ //@{
+
+ void addSocket(ISocket*, ISocketMultiplexerJob*);
+
+ void removeSocket(ISocket*);
+
+ //@}
+ //! @name accessors
+ //@{
+
+ // maybe belongs on ISocketMultiplexer
+ static SocketMultiplexer*
+ getInstance();
+
+ //@}
+
+private:
+ // list of jobs. we use a list so we can safely iterate over it
+ // while other threads modify it.
+ typedef std::list<ISocketMultiplexerJob*> SocketJobs;
+ typedef SocketJobs::iterator JobCursor;
+ typedef std::map<ISocket*, JobCursor> SocketJobMap;
+
+ // service sockets. the service thread will only access m_sockets
+ // and m_update while m_pollable and m_polling are true. all other
+ // threads must only modify these when m_pollable and m_polling are
+ // false. only the service thread sets m_polling.
+ void serviceThread(void*);
+
+ // create, iterate, and destroy a cursor. a cursor is used to
+ // safely iterate through the job list while other threads modify
+ // the list. it works by inserting a dummy item in the list and
+ // moving that item through the list. the dummy item will never
+ // be removed by other edits so an iterator pointing at the item
+ // remains valid until we remove the dummy item in deleteCursor().
+ // nextCursor() finds the next non-dummy item, moves our dummy
+ // item just past it, and returns an iterator for the non-dummy
+ // item. all cursor calls lock the mutex for their duration.
+ JobCursor newCursor();
+ JobCursor nextCursor(JobCursor);
+ void deleteCursor(JobCursor);
+
+ // lock out locking the job list. this blocks if another thread
+ // has already locked out locking. once it returns, only the
+ // calling thread will be able to lock the job list after any
+ // current lock is released.
+ void lockJobListLock();
+
+ // lock the job list. this blocks if the job list is already
+ // locked. the calling thread must have called requestJobLock.
+ void lockJobList();
+
+ // unlock the job list and the lock out on locking.
+ void unlockJobList();
+
+private:
+ Mutex* m_mutex;
+ Thread* m_thread;
+ bool m_update;
+ CondVar<bool>* m_jobsReady;
+ CondVar<bool>* m_jobListLock;
+ CondVar<bool>* m_jobListLockLocked;
+ Thread* m_jobListLocker;
+ Thread* m_jobListLockLocker;
+
+ SocketJobs m_socketJobs;
+ SocketJobMap m_socketJobMap;
+ ISocketMultiplexerJob*
+ m_cursorMark;
+};
diff --git a/src/lib/net/TCPListenSocket.cpp b/src/lib/net/TCPListenSocket.cpp
new file mode 100644
index 0000000..8e1540e
--- /dev/null
+++ b/src/lib/net/TCPListenSocket.cpp
@@ -0,0 +1,158 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#include "net/TCPListenSocket.h"
+
+#include "net/NetworkAddress.h"
+#include "net/SocketMultiplexer.h"
+#include "net/TCPSocket.h"
+#include "net/TSocketMultiplexerMethodJob.h"
+#include "net/XSocket.h"
+#include "io/XIO.h"
+#include "mt/Lock.h"
+#include "mt/Mutex.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+#include "base/IEventQueue.h"
+
+//
+// TCPListenSocket
+//
+
+TCPListenSocket::TCPListenSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) :
+ m_events(events),
+ m_socketMultiplexer(socketMultiplexer)
+{
+ m_mutex = new Mutex;
+ try {
+ m_socket = ARCH->newSocket(family, IArchNetwork::kSTREAM);
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketCreate(e.what());
+ }
+}
+
+TCPListenSocket::~TCPListenSocket()
+{
+ try {
+ if (m_socket != NULL) {
+ m_socketMultiplexer->removeSocket(this);
+ ARCH->closeSocket(m_socket);
+ }
+ }
+ catch (...) {
+ // ignore
+ }
+ delete m_mutex;
+}
+
+void
+TCPListenSocket::bind(const NetworkAddress& addr)
+{
+ try {
+ Lock lock(m_mutex);
+ ARCH->setReuseAddrOnSocket(m_socket, true);
+ ARCH->bindSocket(m_socket, addr.getAddress());
+ ARCH->listenOnSocket(m_socket);
+ m_socketMultiplexer->addSocket(this,
+ new TSocketMultiplexerMethodJob<TCPListenSocket>(
+ this, &TCPListenSocket::serviceListening,
+ m_socket, true, false));
+ }
+ catch (XArchNetworkAddressInUse& e) {
+ throw XSocketAddressInUse(e.what());
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketBind(e.what());
+ }
+}
+
+void
+TCPListenSocket::close()
+{
+ Lock lock(m_mutex);
+ if (m_socket == NULL) {
+ throw XIOClosed();
+ }
+ try {
+ m_socketMultiplexer->removeSocket(this);
+ ARCH->closeSocket(m_socket);
+ m_socket = NULL;
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketIOClose(e.what());
+ }
+}
+
+void*
+TCPListenSocket::getEventTarget() const
+{
+ return const_cast<void*>(static_cast<const void*>(this));
+}
+
+IDataSocket*
+TCPListenSocket::accept()
+{
+ IDataSocket* socket = NULL;
+ try {
+ socket = new TCPSocket(m_events, m_socketMultiplexer, ARCH->acceptSocket(m_socket, NULL));
+ if (socket != NULL) {
+ setListeningJob();
+ }
+ return socket;
+ }
+ catch (XArchNetwork&) {
+ if (socket != NULL) {
+ delete socket;
+ setListeningJob();
+ }
+ return NULL;
+ }
+ catch (std::exception &ex) {
+ if (socket != NULL) {
+ delete socket;
+ setListeningJob();
+ }
+ throw ex;
+ }
+}
+
+void
+TCPListenSocket::setListeningJob()
+{
+ m_socketMultiplexer->addSocket(this,
+ new TSocketMultiplexerMethodJob<TCPListenSocket>(
+ this, &TCPListenSocket::serviceListening,
+ m_socket, true, false));
+}
+
+ISocketMultiplexerJob*
+TCPListenSocket::serviceListening(ISocketMultiplexerJob* job,
+ bool read, bool, bool error)
+{
+ if (error) {
+ close();
+ return NULL;
+ }
+ if (read) {
+ m_events->addEvent(Event(m_events->forIListenSocket().connecting(), this, NULL));
+ // stop polling on this socket until the client accepts
+ return NULL;
+ }
+ return job;
+}
diff --git a/src/lib/net/TCPListenSocket.h b/src/lib/net/TCPListenSocket.h
new file mode 100644
index 0000000..1060356
--- /dev/null
+++ b/src/lib/net/TCPListenSocket.h
@@ -0,0 +1,60 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "net/IListenSocket.h"
+#include "arch/IArchNetwork.h"
+
+class Mutex;
+class ISocketMultiplexerJob;
+class IEventQueue;
+class SocketMultiplexer;
+
+//! TCP listen socket
+/*!
+A listen socket using TCP.
+*/
+class TCPListenSocket : public IListenSocket {
+public:
+ TCPListenSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family);
+ virtual ~TCPListenSocket();
+
+ // ISocket overrides
+ virtual void bind(const NetworkAddress&);
+ virtual void close();
+ virtual void* getEventTarget() const;
+
+ // IListenSocket overrides
+ virtual IDataSocket*
+ accept();
+
+protected:
+ void setListeningJob();
+
+public:
+ ISocketMultiplexerJob*
+ serviceListening(ISocketMultiplexerJob*,
+ bool, bool, bool);
+
+protected:
+ ArchSocket m_socket;
+ Mutex* m_mutex;
+ IEventQueue* m_events;
+ SocketMultiplexer* m_socketMultiplexer;
+};
diff --git a/src/lib/net/TCPSocket.cpp b/src/lib/net/TCPSocket.cpp
new file mode 100644
index 0000000..dce81ee
--- /dev/null
+++ b/src/lib/net/TCPSocket.cpp
@@ -0,0 +1,596 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#include "net/TCPSocket.h"
+
+#include "net/NetworkAddress.h"
+#include "net/SocketMultiplexer.h"
+#include "net/TSocketMultiplexerMethodJob.h"
+#include "net/XSocket.h"
+#include "mt/Lock.h"
+#include "arch/Arch.h"
+#include "arch/XArch.h"
+#include "base/Log.h"
+#include "base/IEventQueue.h"
+#include "base/IEventJob.h"
+
+#include <cstring>
+#include <cstdlib>
+#include <memory>
+
+//
+// TCPSocket
+//
+
+TCPSocket::TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) :
+ IDataSocket(events),
+ m_events(events),
+ m_mutex(),
+ m_flushed(&m_mutex, true),
+ m_socketMultiplexer(socketMultiplexer)
+{
+ try {
+ m_socket = ARCH->newSocket(family, IArchNetwork::kSTREAM);
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketCreate(e.what());
+ }
+
+ LOG((CLOG_DEBUG "Opening new socket: %08X", m_socket));
+
+ init();
+}
+
+TCPSocket::TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, ArchSocket socket) :
+ IDataSocket(events),
+ m_events(events),
+ m_mutex(),
+ m_socket(socket),
+ m_flushed(&m_mutex, true),
+ m_socketMultiplexer(socketMultiplexer)
+{
+ assert(m_socket != NULL);
+
+ LOG((CLOG_DEBUG "Opening new socket: %08X", m_socket));
+
+ // socket starts in connected state
+ init();
+ onConnected();
+ setJob(newJob());
+}
+
+TCPSocket::~TCPSocket()
+{
+ try {
+ close();
+ }
+ catch (...) {
+ // ignore
+ }
+}
+
+void
+TCPSocket::bind(const NetworkAddress& addr)
+{
+ try {
+ ARCH->bindSocket(m_socket, addr.getAddress());
+ }
+ catch (XArchNetworkAddressInUse& e) {
+ throw XSocketAddressInUse(e.what());
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketBind(e.what());
+ }
+}
+
+void
+TCPSocket::close()
+{
+ LOG((CLOG_DEBUG "Closing socket: %08X", m_socket));
+
+ // remove ourself from the multiplexer
+ setJob(NULL);
+
+ Lock lock(&m_mutex);
+
+ // clear buffers and enter disconnected state
+ if (m_connected) {
+ sendEvent(m_events->forISocket().disconnected());
+ }
+ onDisconnected();
+
+ // close the socket
+ if (m_socket != NULL) {
+ ArchSocket socket = m_socket;
+ m_socket = NULL;
+ try {
+ ARCH->closeSocket(socket);
+ }
+ catch (XArchNetwork& e) {
+ // ignore, there's not much we can do
+ LOG((CLOG_WARN "error closing socket: %s", e.what()));
+ }
+ }
+}
+
+void*
+TCPSocket::getEventTarget() const
+{
+ return const_cast<void*>(static_cast<const void*>(this));
+}
+
+UInt32
+TCPSocket::read(void* buffer, UInt32 n)
+{
+ // copy data directly from our input buffer
+ Lock lock(&m_mutex);
+ UInt32 size = m_inputBuffer.getSize();
+ if (n > size) {
+ n = size;
+ }
+ if (buffer != NULL && n != 0) {
+ memcpy(buffer, m_inputBuffer.peek(n), n);
+ }
+ m_inputBuffer.pop(n);
+
+ // if no more data and we cannot read or write then send disconnected
+ if (n > 0 && m_inputBuffer.getSize() == 0 && !m_readable && !m_writable) {
+ sendEvent(m_events->forISocket().disconnected());
+ m_connected = false;
+ }
+
+ return n;
+}
+
+void
+TCPSocket::write(const void* buffer, UInt32 n)
+{
+ bool wasEmpty;
+ {
+ Lock lock(&m_mutex);
+
+ // must not have shutdown output
+ if (!m_writable) {
+ sendEvent(m_events->forIStream().outputError());
+ return;
+ }
+
+ // ignore empty writes
+ if (n == 0) {
+ return;
+ }
+
+ // copy data to the output buffer
+ wasEmpty = (m_outputBuffer.getSize() == 0);
+ m_outputBuffer.write(buffer, n);
+
+ // there's data to write
+ m_flushed = false;
+ }
+
+ // make sure we're waiting to write
+ if (wasEmpty) {
+ setJob(newJob());
+ }
+}
+
+void
+TCPSocket::flush()
+{
+ Lock lock(&m_mutex);
+ while (m_flushed == false) {
+ m_flushed.wait();
+ }
+}
+
+void
+TCPSocket::shutdownInput()
+{
+ bool useNewJob = false;
+ {
+ Lock lock(&m_mutex);
+
+ // shutdown socket for reading
+ try {
+ ARCH->closeSocketForRead(m_socket);
+ }
+ catch (XArchNetwork&) {
+ // ignore
+ }
+
+ // shutdown buffer for reading
+ if (m_readable) {
+ sendEvent(m_events->forIStream().inputShutdown());
+ onInputShutdown();
+ useNewJob = true;
+ }
+ }
+ if (useNewJob) {
+ setJob(newJob());
+ }
+}
+
+void
+TCPSocket::shutdownOutput()
+{
+ bool useNewJob = false;
+ {
+ Lock lock(&m_mutex);
+
+ // shutdown socket for writing
+ try {
+ ARCH->closeSocketForWrite(m_socket);
+ }
+ catch (XArchNetwork&) {
+ // ignore
+ }
+
+ // shutdown buffer for writing
+ if (m_writable) {
+ sendEvent(m_events->forIStream().outputShutdown());
+ onOutputShutdown();
+ useNewJob = true;
+ }
+ }
+ if (useNewJob) {
+ setJob(newJob());
+ }
+}
+
+bool
+TCPSocket::isReady() const
+{
+ Lock lock(&m_mutex);
+ return (m_inputBuffer.getSize() > 0);
+}
+
+bool
+TCPSocket::isFatal() const
+{
+ // TCP sockets aren't ever left in a fatal state.
+ LOG((CLOG_ERR "isFatal() not valid for non-secure connections"));
+ return false;
+}
+
+UInt32
+TCPSocket::getSize() const
+{
+ Lock lock(&m_mutex);
+ return m_inputBuffer.getSize();
+}
+
+void
+TCPSocket::connect(const NetworkAddress& addr)
+{
+ {
+ Lock lock(&m_mutex);
+
+ // fail on attempts to reconnect
+ if (m_socket == NULL || m_connected) {
+ sendConnectionFailedEvent("busy");
+ return;
+ }
+
+ try {
+ if (ARCH->connectSocket(m_socket, addr.getAddress())) {
+ sendEvent(m_events->forIDataSocket().connected());
+ onConnected();
+ }
+ else {
+ // connection is in progress
+ m_writable = true;
+ }
+ }
+ catch (XArchNetwork& e) {
+ throw XSocketConnect(e.what());
+ }
+ }
+ setJob(newJob());
+}
+
+void
+TCPSocket::init()
+{
+ // default state
+ m_connected = false;
+ m_readable = false;
+ m_writable = false;
+
+ try {
+ // turn off Nagle algorithm. we send lots of very short messages
+ // that should be sent without (much) delay. for example, the
+ // mouse motion messages are much less useful if they're delayed.
+ ARCH->setNoDelayOnSocket(m_socket, true);
+ }
+ catch (XArchNetwork& e) {
+ try {
+ ARCH->closeSocket(m_socket);
+ m_socket = NULL;
+ }
+ catch (XArchNetwork&) {
+ // ignore
+ }
+ throw XSocketCreate(e.what());
+ }
+}
+
+TCPSocket::EJobResult
+TCPSocket::doRead()
+{
+ UInt8 buffer[4096];
+ memset(buffer, 0, sizeof(buffer));
+ size_t bytesRead = 0;
+
+ bytesRead = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
+
+ if (bytesRead > 0) {
+ bool wasEmpty = (m_inputBuffer.getSize() == 0);
+
+ // slurp up as much as possible
+ do {
+ m_inputBuffer.write(buffer, (UInt32)bytesRead);
+
+ bytesRead = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
+ } while (bytesRead > 0);
+
+ // send input ready if input buffer was empty
+ if (wasEmpty) {
+ sendEvent(m_events->forIStream().inputReady());
+ }
+ }
+ else {
+ // remote write end of stream hungup. our input side
+ // has therefore shutdown but don't flush our buffer
+ // since there's still data to be read.
+ sendEvent(m_events->forIStream().inputShutdown());
+ if (!m_writable && m_inputBuffer.getSize() == 0) {
+ sendEvent(m_events->forISocket().disconnected());
+ m_connected = false;
+ }
+ m_readable = false;
+ return kNew;
+ }
+
+ return kRetry;
+}
+
+TCPSocket::EJobResult
+TCPSocket::doWrite()
+{
+ // write data
+ UInt32 bufferSize = 0;
+ int bytesWrote = 0;
+
+ bufferSize = m_outputBuffer.getSize();
+ const void* buffer = m_outputBuffer.peek(bufferSize);
+ bytesWrote = (UInt32)ARCH->writeSocket(m_socket, buffer, bufferSize);
+
+ if (bytesWrote > 0) {
+ discardWrittenData(bytesWrote);
+ return kNew;
+ }
+
+ return kRetry;
+}
+
+void
+TCPSocket::setJob(ISocketMultiplexerJob* job)
+{
+ // multiplexer will delete the old job
+ if (job == NULL) {
+ m_socketMultiplexer->removeSocket(this);
+ }
+ else {
+ m_socketMultiplexer->addSocket(this, job);
+ }
+}
+
+ISocketMultiplexerJob*
+TCPSocket::newJob()
+{
+ // note -- must have m_mutex locked on entry
+
+ if (m_socket == NULL) {
+ return NULL;
+ }
+ else if (!m_connected) {
+ assert(!m_readable);
+ if (!(m_readable || m_writable)) {
+ return NULL;
+ }
+ return new TSocketMultiplexerMethodJob<TCPSocket>(
+ this, &TCPSocket::serviceConnecting,
+ m_socket, m_readable, m_writable);
+ }
+ else {
+ if (!(m_readable || (m_writable && (m_outputBuffer.getSize() > 0)))) {
+ return NULL;
+ }
+ return new TSocketMultiplexerMethodJob<TCPSocket>(
+ this, &TCPSocket::serviceConnected,
+ m_socket, m_readable,
+ m_writable && (m_outputBuffer.getSize() > 0));
+ }
+}
+
+void
+TCPSocket::sendConnectionFailedEvent(const char* msg)
+{
+ ConnectionFailedInfo* info = new ConnectionFailedInfo(msg);
+ m_events->addEvent(Event(m_events->forIDataSocket().connectionFailed(),
+ getEventTarget(), info, Event::kDontFreeData));
+}
+
+void
+TCPSocket::sendEvent(Event::Type type)
+{
+ m_events->addEvent(Event(type, getEventTarget(), NULL));
+}
+
+void
+TCPSocket::discardWrittenData(int bytesWrote)
+{
+ m_outputBuffer.pop(bytesWrote);
+ if (m_outputBuffer.getSize() == 0) {
+ sendEvent(m_events->forIStream().outputFlushed());
+ m_flushed = true;
+ m_flushed.broadcast();
+ }
+}
+
+void
+TCPSocket::onConnected()
+{
+ m_connected = true;
+ m_readable = true;
+ m_writable = true;
+}
+
+void
+TCPSocket::onInputShutdown()
+{
+ m_inputBuffer.pop(m_inputBuffer.getSize());
+ m_readable = false;
+}
+
+void
+TCPSocket::onOutputShutdown()
+{
+ m_outputBuffer.pop(m_outputBuffer.getSize());
+ m_writable = false;
+
+ // we're now flushed
+ m_flushed = true;
+ m_flushed.broadcast();
+}
+
+void
+TCPSocket::onDisconnected()
+{
+ // disconnected
+ onInputShutdown();
+ onOutputShutdown();
+ m_connected = false;
+}
+
+ISocketMultiplexerJob*
+TCPSocket::serviceConnecting(ISocketMultiplexerJob* job,
+ bool, bool write, bool error)
+{
+ Lock lock(&m_mutex);
+
+ // should only check for errors if error is true but checking a new
+ // socket (and a socket that's connecting should be new) for errors
+ // should be safe and Mac OS X appears to have a bug where a
+ // non-blocking stream socket that fails to connect immediately is
+ // reported by select as being writable (i.e. connected) even when
+ // the connection has failed. this is easily demonstrated on OS X
+ // 10.3.4 by starting a barrier client and telling to connect to
+ // another system that's not running a barrier server. it will
+ // claim to have connected then quickly disconnect (i guess because
+ // read returns 0 bytes). unfortunately, barrier attempts to
+ // reconnect immediately, the process repeats and we end up
+ // spinning the CPU. luckily, OS X does set SO_ERROR on the
+ // socket correctly when the connection has failed so checking for
+ // errors works. (curiously, sometimes OS X doesn't report
+ // connection refused. when that happens it at least doesn't
+ // report the socket as being writable so barrier is able to time
+ // out the attempt.)
+ if (error || true) {
+ try {
+ // connection may have failed or succeeded
+ ARCH->throwErrorOnSocket(m_socket);
+ }
+ catch (XArchNetwork& e) {
+ sendConnectionFailedEvent(e.what());
+ onDisconnected();
+ return newJob();
+ }
+ }
+
+ if (write) {
+ sendEvent(m_events->forIDataSocket().connected());
+ onConnected();
+ return newJob();
+ }
+
+ return job;
+}
+
+ISocketMultiplexerJob*
+TCPSocket::serviceConnected(ISocketMultiplexerJob* job,
+ bool read, bool write, bool error)
+{
+ Lock lock(&m_mutex);
+
+ if (error) {
+ sendEvent(m_events->forISocket().disconnected());
+ onDisconnected();
+ return newJob();
+ }
+
+ EJobResult result = kRetry;
+ if (write) {
+ try {
+ result = doWrite();
+ }
+ catch (XArchNetworkShutdown&) {
+ // remote read end of stream hungup. our output side
+ // has therefore shutdown.
+ onOutputShutdown();
+ sendEvent(m_events->forIStream().outputShutdown());
+ if (!m_readable && m_inputBuffer.getSize() == 0) {
+ sendEvent(m_events->forISocket().disconnected());
+ m_connected = false;
+ }
+ result = kNew;
+ }
+ catch (XArchNetworkDisconnected&) {
+ // stream hungup
+ onDisconnected();
+ sendEvent(m_events->forISocket().disconnected());
+ result = kNew;
+ }
+ catch (XArchNetwork& e) {
+ // other write error
+ LOG((CLOG_WARN "error writing socket: %s", e.what()));
+ onDisconnected();
+ sendEvent(m_events->forIStream().outputError());
+ sendEvent(m_events->forISocket().disconnected());
+ result = kNew;
+ }
+ }
+
+ if (read && m_readable) {
+ try {
+ result = doRead();
+ }
+ catch (XArchNetworkDisconnected&) {
+ // stream hungup
+ sendEvent(m_events->forISocket().disconnected());
+ onDisconnected();
+ result = kNew;
+ }
+ catch (XArchNetwork& e) {
+ // ignore other read error
+ LOG((CLOG_WARN "error reading socket: %s", e.what()));
+ }
+ }
+
+ return result == kBreak ? NULL : result == kNew ? newJob() : job;
+}
diff --git a/src/lib/net/TCPSocket.h b/src/lib/net/TCPSocket.h
new file mode 100644
index 0000000..1006f88
--- /dev/null
+++ b/src/lib/net/TCPSocket.h
@@ -0,0 +1,116 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "net/IDataSocket.h"
+#include "io/StreamBuffer.h"
+#include "mt/CondVar.h"
+#include "mt/Mutex.h"
+#include "arch/IArchNetwork.h"
+
+class Mutex;
+class Thread;
+class ISocketMultiplexerJob;
+class IEventQueue;
+class SocketMultiplexer;
+
+//! TCP data socket
+/*!
+A data socket using TCP.
+*/
+class TCPSocket : public IDataSocket {
+public:
+ TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family);
+ TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, ArchSocket socket);
+ virtual ~TCPSocket();
+
+ // ISocket overrides
+ virtual void bind(const NetworkAddress&);
+ virtual void close();
+ virtual void* getEventTarget() const;
+
+ // IStream overrides
+ virtual UInt32 read(void* buffer, UInt32 n);
+ virtual void write(const void* buffer, UInt32 n);
+ virtual void flush();
+ virtual void shutdownInput();
+ virtual void shutdownOutput();
+ virtual bool isReady() const;
+ virtual bool isFatal() const;
+ virtual UInt32 getSize() const;
+
+ // IDataSocket overrides
+ virtual void connect(const NetworkAddress&);
+
+
+ virtual ISocketMultiplexerJob*
+ newJob();
+
+protected:
+ enum EJobResult {
+ kBreak = -1, //!< Break the Job chain
+ kRetry, //!< Retry the same job
+ kNew //!< Require a new job
+ };
+
+ ArchSocket getSocket() { return m_socket; }
+ IEventQueue* getEvents() { return m_events; }
+ virtual EJobResult doRead();
+ virtual EJobResult doWrite();
+
+ void setJob(ISocketMultiplexerJob*);
+
+ bool isReadable() { return m_readable; }
+ bool isWritable() { return m_writable; }
+
+ Mutex& getMutex() { return m_mutex; }
+
+ void sendEvent(Event::Type);
+ void discardWrittenData(int bytesWrote);
+
+private:
+ void init();
+
+ void sendConnectionFailedEvent(const char*);
+ void onConnected();
+ void onInputShutdown();
+ void onOutputShutdown();
+ void onDisconnected();
+
+ ISocketMultiplexerJob*
+ serviceConnecting(ISocketMultiplexerJob*,
+ bool, bool, bool);
+ ISocketMultiplexerJob*
+ serviceConnected(ISocketMultiplexerJob*,
+ bool, bool, bool);
+
+protected:
+ bool m_readable;
+ bool m_writable;
+ bool m_connected;
+ IEventQueue* m_events;
+ StreamBuffer m_inputBuffer;
+ StreamBuffer m_outputBuffer;
+
+private:
+ Mutex m_mutex;
+ ArchSocket m_socket;
+ CondVar<bool> m_flushed;
+ SocketMultiplexer* m_socketMultiplexer;
+};
diff --git a/src/lib/net/TCPSocketFactory.cpp b/src/lib/net/TCPSocketFactory.cpp
new file mode 100644
index 0000000..6ff4ef8
--- /dev/null
+++ b/src/lib/net/TCPSocketFactory.cpp
@@ -0,0 +1,68 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#include "net/TCPSocketFactory.h"
+#include "net/TCPSocket.h"
+#include "net/TCPListenSocket.h"
+#include "net/SecureSocket.h"
+#include "net/SecureListenSocket.h"
+#include "arch/Arch.h"
+#include "base/Log.h"
+
+//
+// TCPSocketFactory
+//
+
+TCPSocketFactory::TCPSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer) :
+ m_events(events),
+ m_socketMultiplexer(socketMultiplexer)
+{
+ // do nothing
+}
+
+TCPSocketFactory::~TCPSocketFactory()
+{
+ // do nothing
+}
+
+IDataSocket*
+TCPSocketFactory::create(IArchNetwork::EAddressFamily family, bool secure) const
+{
+ if (secure) {
+ SecureSocket* secureSocket = new SecureSocket(m_events, m_socketMultiplexer, family);
+ secureSocket->initSsl (false);
+ return secureSocket;
+ }
+ else {
+ return new TCPSocket(m_events, m_socketMultiplexer, family);
+ }
+}
+
+IListenSocket*
+TCPSocketFactory::createListen(IArchNetwork::EAddressFamily family, bool secure) const
+{
+ IListenSocket* socket = NULL;
+ if (secure) {
+ socket = new SecureListenSocket(m_events, m_socketMultiplexer, family);
+ }
+ else {
+ socket = new TCPListenSocket(m_events, m_socketMultiplexer, family);
+ }
+
+ return socket;
+}
diff --git a/src/lib/net/TCPSocketFactory.h b/src/lib/net/TCPSocketFactory.h
new file mode 100644
index 0000000..0195ec4
--- /dev/null
+++ b/src/lib/net/TCPSocketFactory.h
@@ -0,0 +1,44 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "net/ISocketFactory.h"
+#include "arch/IArchNetwork.h"
+
+class IEventQueue;
+class SocketMultiplexer;
+
+//! Socket factory for TCP sockets
+class TCPSocketFactory : public ISocketFactory {
+public:
+ TCPSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer);
+ virtual ~TCPSocketFactory();
+
+ // ISocketFactory overrides
+ virtual IDataSocket* create(
+ IArchNetwork::EAddressFamily family,
+ bool secure) const;
+ virtual IListenSocket* createListen(
+ IArchNetwork::EAddressFamily family,
+ bool secure) const;
+
+private:
+ IEventQueue* m_events;
+ SocketMultiplexer* m_socketMultiplexer;
+};
diff --git a/src/lib/net/TSocketMultiplexerMethodJob.h b/src/lib/net/TSocketMultiplexerMethodJob.h
new file mode 100644
index 0000000..90efbe7
--- /dev/null
+++ b/src/lib/net/TSocketMultiplexerMethodJob.h
@@ -0,0 +1,109 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2004 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "net/ISocketMultiplexerJob.h"
+#include "arch/Arch.h"
+
+//! Use a method as a socket multiplexer job
+/*!
+A socket multiplexer job class that invokes a member function.
+*/
+template <class T>
+class TSocketMultiplexerMethodJob : public ISocketMultiplexerJob {
+public:
+ typedef ISocketMultiplexerJob*
+ (T::*Method)(ISocketMultiplexerJob*, bool, bool, bool);
+
+ //! run() invokes \c object->method(arg)
+ TSocketMultiplexerMethodJob(T* object, Method method,
+ ArchSocket socket, bool readable, bool writeable);
+ virtual ~TSocketMultiplexerMethodJob();
+
+ // IJob overrides
+ virtual ISocketMultiplexerJob*
+ run(bool readable, bool writable, bool error);
+ virtual ArchSocket getSocket() const;
+ virtual bool isReadable() const;
+ virtual bool isWritable() const;
+
+private:
+ T* m_object;
+ Method m_method;
+ ArchSocket m_socket;
+ bool m_readable;
+ bool m_writable;
+ void* m_arg;
+};
+
+template <class T>
+inline
+TSocketMultiplexerMethodJob<T>::TSocketMultiplexerMethodJob(T* object,
+ Method method, ArchSocket socket,
+ bool readable, bool writable) :
+ m_object(object),
+ m_method(method),
+ m_socket(ARCH->copySocket(socket)),
+ m_readable(readable),
+ m_writable(writable)
+{
+ // do nothing
+}
+
+template <class T>
+inline
+TSocketMultiplexerMethodJob<T>::~TSocketMultiplexerMethodJob()
+{
+ ARCH->closeSocket(m_socket);
+}
+
+template <class T>
+inline
+ISocketMultiplexerJob*
+TSocketMultiplexerMethodJob<T>::run(bool read, bool write, bool error)
+{
+ if (m_object != NULL) {
+ return (m_object->*m_method)(this, read, write, error);
+ }
+ return NULL;
+}
+
+template <class T>
+inline
+ArchSocket
+TSocketMultiplexerMethodJob<T>::getSocket() const
+{
+ return m_socket;
+}
+
+template <class T>
+inline
+bool
+TSocketMultiplexerMethodJob<T>::isReadable() const
+{
+ return m_readable;
+}
+
+template <class T>
+inline
+bool
+TSocketMultiplexerMethodJob<T>::isWritable() const
+{
+ return m_writable;
+}
diff --git a/src/lib/net/XSocket.cpp b/src/lib/net/XSocket.cpp
new file mode 100644
index 0000000..13e0fc3
--- /dev/null
+++ b/src/lib/net/XSocket.cpp
@@ -0,0 +1,117 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#include "net/XSocket.h"
+#include "base/String.h"
+
+//
+// XSocketAddress
+//
+
+XSocketAddress::XSocketAddress(EError error,
+ const String& hostname, int port) _NOEXCEPT :
+ m_error(error),
+ m_hostname(hostname),
+ m_port(port)
+{
+ // do nothing
+}
+
+XSocketAddress::EError
+XSocketAddress::getError() const throw()
+{
+ return m_error;
+}
+
+String
+XSocketAddress::getHostname() const throw()
+{
+ return m_hostname;
+}
+
+int
+XSocketAddress::getPort() const throw()
+{
+ return m_port;
+}
+
+String
+XSocketAddress::getWhat() const throw()
+{
+ static const char* s_errorID[] = {
+ "XSocketAddressUnknown",
+ "XSocketAddressNotFound",
+ "XSocketAddressNoAddress",
+ "XSocketAddressUnsupported",
+ "XSocketAddressBadPort"
+ };
+ static const char* s_errorMsg[] = {
+ "unknown error for: %{1}:%{2}",
+ "address not found for: %{1}",
+ "no address for: %{1}",
+ "unsupported address for: %{1}",
+ "invalid port" // m_port may not be set to the bad port
+ };
+ return format(s_errorID[m_error], s_errorMsg[m_error],
+ m_hostname.c_str(),
+ barrier::string::sprintf("%d", m_port).c_str());
+}
+
+
+//
+// XSocketIOClose
+//
+
+String
+XSocketIOClose::getWhat() const throw()
+{
+ return format("XSocketIOClose", "close: %{1}", what());
+}
+
+
+//
+// XSocketBind
+//
+
+String
+XSocketBind::getWhat() const throw()
+{
+ return format("XSocketBind", "cannot bind address: %{1}", what());
+}
+
+
+//
+// XSocketConnect
+//
+
+String
+XSocketConnect::getWhat() const throw()
+{
+ return format("XSocketConnect", "cannot connect socket: %{1}", what());
+}
+
+
+//
+// XSocketCreate
+//
+
+String
+XSocketCreate::getWhat() const throw()
+{
+ return format("XSocketCreate", "cannot create socket: %{1}", what());
+}
diff --git a/src/lib/net/XSocket.h b/src/lib/net/XSocket.h
new file mode 100644
index 0000000..be02b5b
--- /dev/null
+++ b/src/lib/net/XSocket.h
@@ -0,0 +1,98 @@
+/*
+ * barrier -- mouse and keyboard sharing utility
+ * Copyright (C) 2012-2016 Symless Ltd.
+ * Copyright (C) 2002 Chris Schoeneman
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * found in the file LICENSE that should have accompanied this file.
+ *
+ * This package 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/>.
+ */
+
+#pragma once
+
+#include "io/XIO.h"
+#include "base/XBase.h"
+#include "base/String.h"
+#include "common/basic_types.h"
+
+//! Generic socket exception
+XBASE_SUBCLASS(XSocket, XBase);
+
+//! Socket bad address exception
+/*!
+Thrown when attempting to create an invalid network address.
+*/
+class XSocketAddress : public XSocket {
+public:
+ //! Failure codes
+ enum EError {
+ kUnknown, //!< Unknown error
+ kNotFound, //!< The hostname is unknown
+ kNoAddress, //!< The hostname is valid but has no IP address
+ kUnsupported, //!< The hostname is valid but has no supported address
+ kBadPort //!< The port is invalid
+ };
+
+ XSocketAddress(EError, const String& hostname, int port) _NOEXCEPT;
+ virtual ~XSocketAddress() _NOEXCEPT { }
+
+ //! @name accessors
+ //@{
+
+ //! Get the error code
+ EError getError() const throw();
+ //! Get the hostname
+ String getHostname() const throw();
+ //! Get the port
+ int getPort() const throw();
+
+ //@}
+
+protected:
+ // XBase overrides
+ virtual String getWhat() const throw();
+
+private:
+ EError m_error;
+ String m_hostname;
+ int m_port;
+};
+
+//! I/O closing exception
+/*!
+Thrown if a stream cannot be closed.
+*/
+XBASE_SUBCLASS_FORMAT(XSocketIOClose, XIOClose);
+
+//! Socket cannot bind address exception
+/*!
+Thrown when a socket cannot be bound to an address.
+*/
+XBASE_SUBCLASS_FORMAT(XSocketBind, XSocket);
+
+//! Socket address in use exception
+/*!
+Thrown when a socket cannot be bound to an address because the address
+is already in use.
+*/
+XBASE_SUBCLASS(XSocketAddressInUse, XSocketBind);
+
+//! Cannot connect socket exception
+/*!
+Thrown when a socket cannot connect to a remote endpoint.
+*/
+XBASE_SUBCLASS_FORMAT(XSocketConnect, XSocket);
+
+//! Cannot create socket exception
+/*!
+Thrown when a socket cannot be created (by the operating system).
+*/
+XBASE_SUBCLASS_FORMAT(XSocketCreate, XSocket);