diff options
| author | 2018-04-25 18:07:30 -0400 | |
|---|---|---|
| committer | 2018-04-25 18:07:30 -0400 | |
| commit | 9b1b081cfdb1c0fb6457278775e0823f8bc10f62 (patch) | |
| tree | ce8840148d8445055ba9e4f12263b2208f234c16 /src/lib/net | |
Import Upstream version 2.0.0+dfsgupstream/2.0.0+dfsg
Diffstat (limited to 'src/lib/net')
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); |
