mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2024-11-28 11:04:12 +01:00
1246 lines
33 KiB
C++
1246 lines
33 KiB
C++
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
|
|
*
|
|
* This is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This software 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 software; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
* USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
//#include <io.h>
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#define errorNumber WSAGetLastError()
|
|
#else
|
|
#define errorNumber errno
|
|
#define closesocket close
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netdb.h>
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#include <sys/un.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <wordexp.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/ssl.h>
|
|
#include "websocket.h"
|
|
|
|
#include <network/GetAPI.h>
|
|
#include <network/TcpSocket.h>
|
|
#include <network/Udp.h>
|
|
#include <rfb/LogWriter.h>
|
|
#include <rfb/Configuration.h>
|
|
#include <rfb/ServerCore.h>
|
|
|
|
#ifdef WIN32
|
|
#include <os/winerrno.h>
|
|
#endif
|
|
|
|
#ifndef INADDR_NONE
|
|
#define INADDR_NONE ((unsigned long)-1)
|
|
#endif
|
|
#ifndef INADDR_LOOPBACK
|
|
#define INADDR_LOOPBACK ((unsigned long)0x7F000001)
|
|
#endif
|
|
|
|
#ifndef IN6_ARE_ADDR_EQUAL
|
|
#define IN6_ARE_ADDR_EQUAL(a,b) \
|
|
(memcmp ((const void*)(a), (const void*)(b), sizeof (struct in6_addr)) == 0)
|
|
#endif
|
|
|
|
// Missing on older Windows and OS X
|
|
#ifndef AI_NUMERICSERV
|
|
#define AI_NUMERICSERV 0
|
|
#endif
|
|
|
|
using namespace network;
|
|
using namespace rdr;
|
|
|
|
static rfb::LogWriter vlog("TcpSocket");
|
|
|
|
static rfb::BoolParameter UseIPv4("UseIPv4", "Use IPv4 for incoming and outgoing connections.", true);
|
|
static rfb::BoolParameter UseIPv6("UseIPv6", "Use IPv6 for incoming and outgoing connections.", true);
|
|
|
|
rfb::StringParameter httpDir("httpd",
|
|
"Directory containing files to serve via HTTP",
|
|
WWWDIR);
|
|
|
|
/* Tunnelling support. */
|
|
int network::findFreeTcpPort (void)
|
|
{
|
|
int sock;
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if ((sock = socket (AF_INET, SOCK_STREAM, 0)) < 0)
|
|
throw SocketException ("unable to create socket", errorNumber);
|
|
|
|
addr.sin_port = 0;
|
|
if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) < 0)
|
|
throw SocketException ("unable to find free port", errorNumber);
|
|
|
|
socklen_t n = sizeof(addr);
|
|
if (getsockname (sock, (struct sockaddr *)&addr, &n) < 0)
|
|
throw SocketException ("unable to get port number", errorNumber);
|
|
|
|
closesocket (sock);
|
|
return ntohs(addr.sin_port);
|
|
}
|
|
|
|
int network::getSockPort(int sock)
|
|
{
|
|
vnc_sockaddr_t sa;
|
|
socklen_t sa_size = sizeof(sa);
|
|
if (getsockname(sock, &sa.u.sa, &sa_size) < 0)
|
|
return 0;
|
|
|
|
switch (sa.u.sa.sa_family) {
|
|
case AF_INET6:
|
|
return ntohs(sa.u.sin6.sin6_port);
|
|
default:
|
|
return ntohs(sa.u.sin.sin_port);
|
|
}
|
|
}
|
|
|
|
WebSocket::WebSocket(int sock) : Socket(sock)
|
|
{
|
|
}
|
|
|
|
char* WebSocket::getPeerAddress() {
|
|
struct sockaddr_un addr;
|
|
socklen_t len = sizeof(struct sockaddr_un);
|
|
if (getpeername(getFd(), (struct sockaddr *) &addr, &len) != 0) {
|
|
vlog.error("unable to get peer name for socket");
|
|
return rfb::strDup("websocket");
|
|
}
|
|
return rfb::strDup(addr.sun_path + 1);
|
|
}
|
|
|
|
char* WebSocket::getPeerEndpoint() {
|
|
char buf[1024];
|
|
sprintf(buf, "%s::websocket", getPeerAddress());
|
|
|
|
return rfb::strDup(buf);
|
|
}
|
|
|
|
// -=- TcpSocket
|
|
|
|
TcpSocket::TcpSocket(int sock) : Socket(sock)
|
|
{
|
|
// Disable Nagle's algorithm, to reduce latency
|
|
enableNagles(false);
|
|
}
|
|
|
|
TcpSocket::TcpSocket(const char *host, int port)
|
|
{
|
|
int sock, err, result;
|
|
struct addrinfo *ai, *current, hints;
|
|
|
|
// - Create a socket
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_canonname = NULL;
|
|
hints.ai_addr = NULL;
|
|
hints.ai_next = NULL;
|
|
|
|
if ((result = getaddrinfo(host, NULL, &hints, &ai)) != 0) {
|
|
throw GAIException("unable to resolve host by name", result);
|
|
}
|
|
|
|
sock = -1;
|
|
err = 0;
|
|
for (current = ai; current != NULL; current = current->ai_next) {
|
|
int family;
|
|
vnc_sockaddr_t sa;
|
|
socklen_t salen;
|
|
char ntop[NI_MAXHOST];
|
|
|
|
family = current->ai_family;
|
|
|
|
switch (family) {
|
|
case AF_INET:
|
|
if (!UseIPv4)
|
|
continue;
|
|
break;
|
|
case AF_INET6:
|
|
if (!UseIPv6)
|
|
continue;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
salen = current->ai_addrlen;
|
|
memcpy(&sa, current->ai_addr, salen);
|
|
|
|
if (family == AF_INET)
|
|
sa.u.sin.sin_port = htons(port);
|
|
else
|
|
sa.u.sin6.sin6_port = htons(port);
|
|
|
|
getnameinfo(&sa.u.sa, salen, ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST);
|
|
vlog.debug("Connecting to %s [%s] port %d", host, ntop, port);
|
|
|
|
sock = socket (family, SOCK_STREAM, 0);
|
|
if (sock == -1) {
|
|
err = errorNumber;
|
|
freeaddrinfo(ai);
|
|
throw SocketException("unable to create socket", err);
|
|
}
|
|
|
|
/* Attempt to connect to the remote host */
|
|
while ((result = connect(sock, &sa.u.sa, salen)) == -1) {
|
|
err = errorNumber;
|
|
#ifndef WIN32
|
|
if (err == EINTR)
|
|
continue;
|
|
#endif
|
|
vlog.debug("Failed to connect to address %s port %d: %d",
|
|
ntop, port, err);
|
|
closesocket(sock);
|
|
sock = -1;
|
|
break;
|
|
}
|
|
|
|
if (result == 0)
|
|
break;
|
|
}
|
|
|
|
freeaddrinfo(ai);
|
|
|
|
if (sock == -1) {
|
|
if (err == 0)
|
|
throw Exception("No useful address for host");
|
|
else
|
|
throw SocketException("unable to connect to socket", err);
|
|
}
|
|
|
|
// Take proper ownership of the socket
|
|
setFd(sock);
|
|
|
|
// Disable Nagle's algorithm, to reduce latency
|
|
enableNagles(false);
|
|
}
|
|
|
|
char* TcpSocket::getPeerAddress() {
|
|
vnc_sockaddr_t sa;
|
|
socklen_t sa_size = sizeof(sa);
|
|
|
|
if (getpeername(getFd(), &sa.u.sa, &sa_size) != 0) {
|
|
vlog.error("unable to get peer name for socket");
|
|
return rfb::strDup("");
|
|
}
|
|
|
|
if (sa.u.sa.sa_family == AF_INET6) {
|
|
char buffer[INET6_ADDRSTRLEN + 2];
|
|
int ret;
|
|
|
|
buffer[0] = '[';
|
|
|
|
ret = getnameinfo(&sa.u.sa, sizeof(sa.u.sin6),
|
|
buffer + 1, sizeof(buffer) - 2, NULL, 0,
|
|
NI_NUMERICHOST);
|
|
if (ret != 0) {
|
|
vlog.error("unable to convert peer name to a string");
|
|
return rfb::strDup("");
|
|
}
|
|
|
|
strcat(buffer, "]");
|
|
|
|
return rfb::strDup(buffer);
|
|
}
|
|
|
|
if (sa.u.sa.sa_family == AF_INET) {
|
|
char *name;
|
|
|
|
name = inet_ntoa(sa.u.sin.sin_addr);
|
|
if (name == NULL) {
|
|
vlog.error("unable to convert peer name to a string");
|
|
return rfb::strDup("");
|
|
}
|
|
|
|
return rfb::strDup(name);
|
|
}
|
|
|
|
vlog.error("unknown address family for socket");
|
|
return rfb::strDup("");
|
|
}
|
|
|
|
char* TcpSocket::getPeerEndpoint() {
|
|
rfb::CharArray address; address.buf = getPeerAddress();
|
|
vnc_sockaddr_t sa;
|
|
socklen_t sa_size = sizeof(sa);
|
|
int port;
|
|
|
|
getpeername(getFd(), &sa.u.sa, &sa_size);
|
|
|
|
if (sa.u.sa.sa_family == AF_INET6)
|
|
port = ntohs(sa.u.sin6.sin6_port);
|
|
else if (sa.u.sa.sa_family == AF_INET)
|
|
port = ntohs(sa.u.sin.sin_port);
|
|
else
|
|
port = 0;
|
|
|
|
int buflen = strlen(address.buf) + 32;
|
|
char* buffer = new char[buflen];
|
|
sprintf(buffer, "%s::%d", address.buf, port);
|
|
return buffer;
|
|
}
|
|
|
|
bool TcpSocket::enableNagles(bool enable) {
|
|
int one = enable ? 0 : 1;
|
|
if (setsockopt(getFd(), IPPROTO_TCP, TCP_NODELAY,
|
|
(char *)&one, sizeof(one)) < 0) {
|
|
int e = errorNumber;
|
|
vlog.error("unable to setsockopt TCP_NODELAY: %d", e);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TcpSocket::cork(bool enable) {
|
|
#ifndef TCP_CORK
|
|
return false;
|
|
#else
|
|
int one = enable ? 1 : 0;
|
|
if (setsockopt(getFd(), IPPROTO_TCP, TCP_CORK, (char *)&one, sizeof(one)) < 0)
|
|
return false;
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
TcpListener::TcpListener(int sock) : SocketListener(sock)
|
|
{
|
|
}
|
|
|
|
TcpListener::TcpListener(const struct sockaddr *listenaddr,
|
|
socklen_t listenaddrlen)
|
|
{
|
|
int one = 1;
|
|
vnc_sockaddr_t sa;
|
|
int sock;
|
|
|
|
if ((sock = socket (listenaddr->sa_family, SOCK_STREAM, 0)) < 0)
|
|
throw SocketException("unable to create listening socket", errorNumber);
|
|
|
|
memcpy (&sa, listenaddr, listenaddrlen);
|
|
#ifdef IPV6_V6ONLY
|
|
if (listenaddr->sa_family == AF_INET6) {
|
|
if (setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one))) {
|
|
int e = errorNumber;
|
|
closesocket(sock);
|
|
throw SocketException("unable to set IPV6_V6ONLY", e);
|
|
}
|
|
}
|
|
#endif /* defined(IPV6_V6ONLY) */
|
|
|
|
#ifdef FD_CLOEXEC
|
|
// - By default, close the socket on exec()
|
|
fcntl(sock, F_SETFD, FD_CLOEXEC);
|
|
#endif
|
|
|
|
// SO_REUSEADDR is broken on Windows. It allows binding to a port
|
|
// that already has a listening socket on it. SO_EXCLUSIVEADDRUSE
|
|
// might do what we want, but requires investigation.
|
|
#ifndef WIN32
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
|
(char *)&one, sizeof(one)) < 0) {
|
|
int e = errorNumber;
|
|
closesocket(sock);
|
|
throw SocketException("unable to create listening socket", e);
|
|
}
|
|
#endif
|
|
|
|
if (bind(sock, &sa.u.sa, listenaddrlen) == -1) {
|
|
int e = errorNumber;
|
|
closesocket(sock);
|
|
throw SocketException("failed to bind socket", e);
|
|
}
|
|
|
|
listen(sock);
|
|
}
|
|
|
|
Socket* TcpListener::createSocket(int fd) {
|
|
return new TcpSocket(fd);
|
|
}
|
|
|
|
void TcpListener::getMyAddresses(std::list<char*>* result) {
|
|
struct addrinfo *ai, *current, hints;
|
|
|
|
initSockets();
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_canonname = NULL;
|
|
hints.ai_addr = NULL;
|
|
hints.ai_next = NULL;
|
|
|
|
// Windows doesn't like NULL for service, so specify something
|
|
if ((getaddrinfo(NULL, "1", &hints, &ai)) != 0)
|
|
return;
|
|
|
|
for (current= ai; current != NULL; current = current->ai_next) {
|
|
switch (current->ai_family) {
|
|
case AF_INET:
|
|
if (!UseIPv4)
|
|
continue;
|
|
break;
|
|
case AF_INET6:
|
|
if (!UseIPv6)
|
|
continue;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
char *addr = new char[INET6_ADDRSTRLEN];
|
|
|
|
getnameinfo(current->ai_addr, current->ai_addrlen, addr, INET6_ADDRSTRLEN,
|
|
NULL, 0, NI_NUMERICHOST);
|
|
|
|
result->push_back(addr);
|
|
}
|
|
|
|
freeaddrinfo(ai);
|
|
}
|
|
|
|
int TcpListener::getMyPort() {
|
|
return getSockPort(getFd());
|
|
}
|
|
|
|
extern settings_t settings;
|
|
|
|
static uint8_t *screenshotCb(void *messager, uint16_t w, uint16_t h, const uint8_t q,
|
|
const uint8_t dedup,
|
|
uint32_t *len, uint8_t *staging)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netGetScreenshot(w, h, q, dedup, *len, staging);
|
|
}
|
|
|
|
static uint8_t adduserCb(void *messager, const char name[], const char pw[],
|
|
const uint8_t read, const uint8_t write, const uint8_t owner)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netAddUser(name, pw, read, write, owner);
|
|
}
|
|
|
|
static uint8_t removeCb(void *messager, const char name[])
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netRemoveUser(name);
|
|
}
|
|
|
|
static uint8_t updateUserCb(void *messager, const char name[], const uint64_t mask,
|
|
const char password[],
|
|
const uint8_t read, const uint8_t write, const uint8_t owner)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netUpdateUser(name, mask, password, read, write, owner);
|
|
}
|
|
|
|
static uint8_t addOrUpdateUserCb(void *messager, const struct kasmpasswd_entry_t *entry)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netAddOrUpdateUser(entry);
|
|
}
|
|
|
|
static void getUsersCb(void *messager, const char **ptr)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
msgr->netGetUsers(ptr);
|
|
}
|
|
|
|
static void bottleneckStatsCb(void *messager, char *buf, uint32_t len)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
msgr->netGetBottleneckStats(buf, len);
|
|
}
|
|
|
|
static void frameStatsCb(void *messager, char *buf, uint32_t len)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
msgr->netGetFrameStats(buf, len);
|
|
}
|
|
|
|
static void resetFrameStatsCb(void *messager)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
msgr->netResetFrameStatsCall();
|
|
}
|
|
|
|
static uint8_t requestFrameStatsNoneCb(void *messager)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netRequestFrameStats(GetAPIMessager::WANT_FRAME_STATS_SERVERONLY, NULL);
|
|
}
|
|
|
|
static uint8_t requestFrameStatsOwnerCb(void *messager)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netRequestFrameStats(GetAPIMessager::WANT_FRAME_STATS_OWNER, NULL);
|
|
}
|
|
|
|
static uint8_t requestFrameStatsAllCb(void *messager)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netRequestFrameStats(GetAPIMessager::WANT_FRAME_STATS_ALL, NULL);
|
|
}
|
|
|
|
static uint8_t requestFrameStatsOneCb(void *messager, const char *client)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netRequestFrameStats(GetAPIMessager::WANT_FRAME_STATS_SPECIFIC, client);
|
|
}
|
|
|
|
static uint8_t ownerConnectedCb(void *messager)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netOwnerConnected();
|
|
}
|
|
|
|
static uint8_t numActiveUsersCb(void *messager)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netNumActiveUsers();
|
|
}
|
|
|
|
static uint8_t getClientFrameStatsNumCb(void *messager)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netGetClientFrameStatsNum();
|
|
}
|
|
|
|
static uint8_t serverFrameStatsReadyCb(void *messager)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
return msgr->netServerFrameStatsReady();
|
|
}
|
|
|
|
static void clearClipboardCb(void *messager)
|
|
{
|
|
GetAPIMessager *msgr = (GetAPIMessager *) messager;
|
|
msgr->netClearClipboard();
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x1010000f
|
|
|
|
static pthread_mutex_t *sslmutex;
|
|
|
|
static void openssl_lock(int mode, int n, const char *, int)
|
|
{
|
|
if (mode & CRYPTO_LOCK)
|
|
pthread_mutex_lock(&sslmutex[n]);
|
|
else
|
|
pthread_mutex_unlock(&sslmutex[n]);
|
|
}
|
|
|
|
static unsigned long openssl_id()
|
|
{
|
|
return pthread_self();
|
|
}
|
|
|
|
#endif
|
|
|
|
static void openssl_threads() {
|
|
|
|
SSL_library_init();
|
|
OpenSSL_add_all_algorithms();
|
|
SSL_load_error_strings();
|
|
ERR_load_BIO_strings();
|
|
ERR_load_crypto_strings();
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x1010000f
|
|
|
|
sslmutex = (pthread_mutex_t *) calloc(CRYPTO_num_locks(), sizeof(pthread_mutex_t));
|
|
unsigned i;
|
|
for (i = 0; i < (unsigned) CRYPTO_num_locks(); i++)
|
|
pthread_mutex_init(&sslmutex[i], NULL);
|
|
|
|
CRYPTO_set_locking_callback(openssl_lock);
|
|
CRYPTO_set_id_callback(openssl_id);
|
|
|
|
#endif
|
|
}
|
|
|
|
WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr,
|
|
socklen_t listenaddrlen,
|
|
bool sslonly, const char *cert, const char *certkey,
|
|
bool disablebasicauth,
|
|
const char *httpdir)
|
|
{
|
|
int one = 1;
|
|
vnc_sockaddr_t sa;
|
|
int sock;
|
|
|
|
if ((sock = socket (listenaddr->sa_family, SOCK_STREAM, 0)) < 0)
|
|
throw SocketException("unable to create listening socket", errorNumber);
|
|
|
|
memcpy (&sa, listenaddr, listenaddrlen);
|
|
#ifdef IPV6_V6ONLY
|
|
if (listenaddr->sa_family == AF_INET6) {
|
|
if (setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one))) {
|
|
int e = errorNumber;
|
|
closesocket(sock);
|
|
throw SocketException("unable to set IPV6_V6ONLY", e);
|
|
}
|
|
}
|
|
#endif /* defined(IPV6_V6ONLY) */
|
|
|
|
#ifdef FD_CLOEXEC
|
|
// - By default, close the socket on exec()
|
|
fcntl(sock, F_SETFD, FD_CLOEXEC);
|
|
#endif
|
|
|
|
// SO_REUSEADDR is broken on Windows. It allows binding to a port
|
|
// that already has a listening socket on it. SO_EXCLUSIVEADDRUSE
|
|
// might do what we want, but requires investigation.
|
|
#ifndef WIN32
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
|
(char *)&one, sizeof(one)) < 0) {
|
|
int e = errorNumber;
|
|
closesocket(sock);
|
|
throw SocketException("unable to create listening socket", e);
|
|
}
|
|
#endif
|
|
|
|
if (bind(sock, &sa.u.sa, listenaddrlen) == -1) {
|
|
int e = errorNumber;
|
|
closesocket(sock);
|
|
throw SocketException("failed to bind socket, is someone else on our -websocketPort?", e);
|
|
}
|
|
|
|
listen(sock); // sets the internal fd
|
|
|
|
//
|
|
// External TCP socket now created. Create the internal ones
|
|
//
|
|
internalSocket = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
|
|
char sockname[32];
|
|
sprintf(sockname, ".KasmVNCSock%u", getpid());
|
|
|
|
struct sockaddr_un addr;
|
|
addr.sun_family = AF_UNIX;
|
|
strcpy(addr.sun_path, sockname);
|
|
addr.sun_path[0] = '\0';
|
|
|
|
if (bind(internalSocket, (struct sockaddr *) &addr,
|
|
sizeof(sa_family_t) + strlen(sockname))) {
|
|
throw SocketException("failed to bind socket", errorNumber);
|
|
}
|
|
|
|
listen(internalSocket);
|
|
|
|
settings.passwdfile = NULL;
|
|
|
|
wordexp_t wexp;
|
|
if (!wordexp(rfb::Server::kasmPasswordFile, &wexp, WRDE_NOCMD))
|
|
settings.passwdfile = strdup(wexp.we_wordv[0]);
|
|
wordfree(&wexp);
|
|
|
|
settings.disablebasicauth = disablebasicauth;
|
|
settings.cert = cert;
|
|
settings.key = certkey;
|
|
settings.ssl_only = sslonly;
|
|
settings.verbose = vlog.getLevel() >= vlog.LEVEL_DEBUG;
|
|
settings.httpdir = NULL;
|
|
if (httpdir && httpdir[0])
|
|
settings.httpdir = realpath(httpdir, NULL);
|
|
|
|
settings.listen_sock = sock;
|
|
|
|
settings.messager = messager = new GetAPIMessager(settings.passwdfile);
|
|
settings.screenshotCb = screenshotCb;
|
|
settings.adduserCb = adduserCb;
|
|
settings.removeCb = removeCb;
|
|
settings.updateUserCb = updateUserCb;
|
|
settings.addOrUpdateUserCb = addOrUpdateUserCb;
|
|
settings.getUsersCb = getUsersCb;
|
|
settings.bottleneckStatsCb = bottleneckStatsCb;
|
|
settings.frameStatsCb = frameStatsCb;
|
|
settings.resetFrameStatsCb = resetFrameStatsCb;
|
|
|
|
settings.requestFrameStatsNoneCb = requestFrameStatsNoneCb;
|
|
settings.requestFrameStatsOwnerCb = requestFrameStatsOwnerCb;
|
|
settings.requestFrameStatsAllCb = requestFrameStatsAllCb;
|
|
settings.requestFrameStatsOneCb = requestFrameStatsOneCb;
|
|
|
|
settings.ownerConnectedCb = ownerConnectedCb;
|
|
settings.numActiveUsersCb = numActiveUsersCb;
|
|
settings.getClientFrameStatsNumCb = getClientFrameStatsNumCb;
|
|
settings.serverFrameStatsReadyCb = serverFrameStatsReadyCb;
|
|
|
|
settings.clearClipboardCb = clearClipboardCb;
|
|
|
|
openssl_threads();
|
|
|
|
pthread_t tid;
|
|
pthread_create(&tid, NULL, start_server, NULL);
|
|
|
|
uint16_t *nport = (uint16_t *) calloc(1, sizeof(uint16_t));
|
|
if (rfb::Server::udpPort)
|
|
*nport = rfb::Server::udpPort;
|
|
else
|
|
*nport = ntohs(sa.u.sin.sin_port);
|
|
pthread_create(&tid, NULL, udpserver, nport);
|
|
}
|
|
|
|
Socket* WebsocketListener::createSocket(int fd) {
|
|
return new WebSocket(fd);
|
|
}
|
|
|
|
void WebsocketListener::getMyAddresses(std::list<char*>* result) {
|
|
struct addrinfo *ai, *current, hints;
|
|
|
|
initSockets();
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_canonname = NULL;
|
|
hints.ai_addr = NULL;
|
|
hints.ai_next = NULL;
|
|
|
|
// Windows doesn't like NULL for service, so specify something
|
|
if ((getaddrinfo(NULL, "1", &hints, &ai)) != 0)
|
|
return;
|
|
|
|
for (current= ai; current != NULL; current = current->ai_next) {
|
|
switch (current->ai_family) {
|
|
case AF_INET:
|
|
if (!UseIPv4)
|
|
continue;
|
|
break;
|
|
case AF_INET6:
|
|
if (!UseIPv6)
|
|
continue;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
char *addr = new char[INET6_ADDRSTRLEN];
|
|
|
|
getnameinfo(current->ai_addr, current->ai_addrlen, addr, INET6_ADDRSTRLEN,
|
|
NULL, 0, NI_NUMERICHOST);
|
|
|
|
result->push_back(addr);
|
|
}
|
|
|
|
freeaddrinfo(ai);
|
|
}
|
|
|
|
int WebsocketListener::getMyPort() {
|
|
return getSockPort(getFd());
|
|
}
|
|
|
|
|
|
void network::createLocalTcpListeners(std::list<SocketListener*> *listeners,
|
|
int port)
|
|
{
|
|
struct addrinfo ai[2];
|
|
vnc_sockaddr_t sa[2];
|
|
|
|
memset(ai, 0, sizeof(ai));
|
|
memset(sa, 0, sizeof(sa));
|
|
|
|
sa[0].u.sin.sin_family = AF_INET;
|
|
sa[0].u.sin.sin_port = htons (port);
|
|
sa[0].u.sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
|
|
|
|
ai[0].ai_family = sa[0].u.sin.sin_family;
|
|
ai[0].ai_addr = &sa[0].u.sa;
|
|
ai[0].ai_addrlen = sizeof(sa[0].u.sin);
|
|
ai[0].ai_next = &ai[1];
|
|
|
|
sa[1].u.sin6.sin6_family = AF_INET6;
|
|
sa[1].u.sin6.sin6_port = htons (port);
|
|
sa[1].u.sin6.sin6_addr = in6addr_loopback;
|
|
|
|
ai[1].ai_family = sa[1].u.sin6.sin6_family;
|
|
ai[1].ai_addr = &sa[1].u.sa;
|
|
ai[1].ai_addrlen = sizeof(sa[1].u.sin6);
|
|
ai[1].ai_next = NULL;
|
|
|
|
createTcpListeners(listeners, ai);
|
|
}
|
|
|
|
void network::createTcpListeners(std::list<SocketListener*> *listeners,
|
|
const char *addr,
|
|
int port)
|
|
{
|
|
struct addrinfo *ai, hints;
|
|
char service[16];
|
|
int result;
|
|
|
|
initSockets();
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_canonname = NULL;
|
|
hints.ai_addr = NULL;
|
|
hints.ai_next = NULL;
|
|
|
|
snprintf (service, sizeof (service) - 1, "%d", port);
|
|
service[sizeof (service) - 1] = '\0';
|
|
if ((result = getaddrinfo(addr, service, &hints, &ai)) != 0)
|
|
throw GAIException("unable to resolve listening address", result);
|
|
|
|
try {
|
|
createTcpListeners(listeners, ai);
|
|
} catch(...) {
|
|
freeaddrinfo(ai);
|
|
throw;
|
|
}
|
|
|
|
freeaddrinfo(ai);
|
|
}
|
|
|
|
void network::createTcpListeners(std::list<SocketListener*> *listeners,
|
|
const struct addrinfo *ai)
|
|
{
|
|
const struct addrinfo *current;
|
|
std::list<SocketListener*> new_listeners;
|
|
|
|
initSockets();
|
|
|
|
for (current = ai; current != NULL; current = current->ai_next) {
|
|
switch (current->ai_family) {
|
|
case AF_INET:
|
|
if (!UseIPv4)
|
|
continue;
|
|
break;
|
|
|
|
case AF_INET6:
|
|
if (!UseIPv6)
|
|
continue;
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
new_listeners.push_back(new TcpListener(current->ai_addr,
|
|
current->ai_addrlen));
|
|
} catch (SocketException& e) {
|
|
// Ignore this if it is due to lack of address family support on
|
|
// the interface or on the system
|
|
if (e.err != EADDRNOTAVAIL && e.err != EAFNOSUPPORT) {
|
|
// Otherwise, report the error
|
|
while (!new_listeners.empty()) {
|
|
delete new_listeners.back();
|
|
new_listeners.pop_back();
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (new_listeners.empty ())
|
|
throw SocketException("createTcpListeners: no addresses available",
|
|
EADDRNOTAVAIL);
|
|
|
|
listeners->splice (listeners->end(), new_listeners);
|
|
}
|
|
|
|
void network::createWebsocketListeners(std::list<SocketListener*> *listeners,
|
|
const struct addrinfo *ai,
|
|
bool sslonly, const char *cert, const char *certkey,
|
|
bool disablebasicauth,
|
|
const char *httpdir)
|
|
{
|
|
const struct addrinfo *current;
|
|
std::list<SocketListener*> new_listeners;
|
|
|
|
initSockets();
|
|
|
|
for (current = ai; current != NULL; current = current->ai_next) {
|
|
switch (current->ai_family) {
|
|
case AF_INET:
|
|
if (!UseIPv4)
|
|
continue;
|
|
break;
|
|
|
|
case AF_INET6:
|
|
if (!UseIPv6)
|
|
continue;
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
new_listeners.push_back(new WebsocketListener(current->ai_addr,
|
|
current->ai_addrlen,
|
|
sslonly, cert, certkey, disablebasicauth,
|
|
httpdir));
|
|
} catch (SocketException& e) {
|
|
// Ignore this if it is due to lack of address family support on
|
|
// the interface or on the system
|
|
if (e.err != EADDRNOTAVAIL && e.err != EAFNOSUPPORT) {
|
|
// Otherwise, report the error
|
|
while (!new_listeners.empty()) {
|
|
delete new_listeners.back();
|
|
new_listeners.pop_back();
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (new_listeners.empty ())
|
|
throw SocketException("createWebsocketListeners: no addresses available",
|
|
EADDRNOTAVAIL);
|
|
|
|
listeners->splice (listeners->end(), new_listeners);
|
|
}
|
|
|
|
void network::createWebsocketListeners(std::list<SocketListener*> *listeners,
|
|
int port,
|
|
const char *addr,
|
|
bool sslonly,
|
|
const char *cert,
|
|
const char *certkey,
|
|
bool disablebasicauth,
|
|
const char *httpdir)
|
|
{
|
|
if (addr && !strcmp(addr, "local")) {
|
|
struct addrinfo ai[2];
|
|
vnc_sockaddr_t sa[2];
|
|
|
|
memset(ai, 0, sizeof(ai));
|
|
memset(sa, 0, sizeof(sa));
|
|
|
|
sa[0].u.sin.sin_family = AF_INET;
|
|
sa[0].u.sin.sin_port = htons (port);
|
|
sa[0].u.sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
|
|
|
|
ai[0].ai_family = sa[0].u.sin.sin_family;
|
|
ai[0].ai_addr = &sa[0].u.sa;
|
|
ai[0].ai_addrlen = sizeof(sa[0].u.sin);
|
|
ai[0].ai_next = &ai[1];
|
|
|
|
sa[1].u.sin6.sin6_family = AF_INET6;
|
|
sa[1].u.sin6.sin6_port = htons (port);
|
|
sa[1].u.sin6.sin6_addr = in6addr_loopback;
|
|
|
|
ai[1].ai_family = sa[1].u.sin6.sin6_family;
|
|
ai[1].ai_addr = &sa[1].u.sa;
|
|
ai[1].ai_addrlen = sizeof(sa[1].u.sin6);
|
|
ai[1].ai_next = NULL;
|
|
|
|
createWebsocketListeners(listeners, ai, sslonly, cert, certkey, disablebasicauth, httpdir);
|
|
} else {
|
|
struct addrinfo *ai, hints;
|
|
char service[16];
|
|
int result;
|
|
|
|
initSockets();
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_canonname = NULL;
|
|
hints.ai_addr = NULL;
|
|
hints.ai_next = NULL;
|
|
|
|
snprintf (service, sizeof (service) - 1, "%d", port);
|
|
service[sizeof (service) - 1] = '\0';
|
|
if ((result = getaddrinfo(addr, service, &hints, &ai)) != 0)
|
|
throw rdr::Exception("unable to resolve listening address: %s",
|
|
gai_strerror(result));
|
|
|
|
try {
|
|
createWebsocketListeners(listeners, ai, sslonly, cert, certkey, disablebasicauth, httpdir);
|
|
} catch(...) {
|
|
freeaddrinfo(ai);
|
|
throw;
|
|
}
|
|
|
|
freeaddrinfo(ai);
|
|
}
|
|
}
|
|
|
|
|
|
TcpFilter::TcpFilter(const char* spec) {
|
|
rfb::CharArray tmp;
|
|
tmp.buf = rfb::strDup(spec);
|
|
while (tmp.buf) {
|
|
rfb::CharArray first;
|
|
rfb::strSplit(tmp.buf, ',', &first.buf, &tmp.buf);
|
|
if (strlen(first.buf))
|
|
filter.push_back(parsePattern(first.buf));
|
|
}
|
|
}
|
|
|
|
TcpFilter::~TcpFilter() {
|
|
}
|
|
|
|
|
|
static bool
|
|
patternMatchIP(const TcpFilter::Pattern& pattern, vnc_sockaddr_t *sa) {
|
|
switch (pattern.address.u.sa.sa_family) {
|
|
unsigned long address;
|
|
|
|
case AF_INET:
|
|
if (sa->u.sa.sa_family != AF_INET)
|
|
return false;
|
|
|
|
address = sa->u.sin.sin_addr.s_addr;
|
|
if (address == htonl (INADDR_NONE)) return false;
|
|
return ((pattern.address.u.sin.sin_addr.s_addr &
|
|
pattern.mask.u.sin.sin_addr.s_addr) ==
|
|
(address & pattern.mask.u.sin.sin_addr.s_addr));
|
|
|
|
case AF_INET6:
|
|
if (sa->u.sa.sa_family != AF_INET6)
|
|
return false;
|
|
|
|
for (unsigned int n = 0; n < 16; n++) {
|
|
unsigned int bits = (n + 1) * 8;
|
|
unsigned int mask;
|
|
if (pattern.prefixlen > bits)
|
|
mask = 0xff;
|
|
else {
|
|
unsigned int lastbits = 0xff;
|
|
lastbits <<= bits - pattern.prefixlen;
|
|
mask = lastbits & 0xff;
|
|
}
|
|
|
|
if ((pattern.address.u.sin6.sin6_addr.s6_addr[n] & mask) !=
|
|
(sa->u.sin6.sin6_addr.s6_addr[n] & mask))
|
|
return false;
|
|
|
|
if (mask < 0xff)
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
|
|
case AF_UNSPEC:
|
|
// Any address matches
|
|
return true;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
TcpFilter::verifyConnection(Socket* s) {
|
|
rfb::CharArray name;
|
|
vnc_sockaddr_t sa;
|
|
socklen_t sa_size = sizeof(sa);
|
|
|
|
if (getpeername(s->getFd(), &sa.u.sa, &sa_size) != 0)
|
|
return false;
|
|
|
|
name.buf = s->getPeerAddress();
|
|
std::list<TcpFilter::Pattern>::iterator i;
|
|
for (i=filter.begin(); i!=filter.end(); i++) {
|
|
if (patternMatchIP(*i, &sa)) {
|
|
switch ((*i).action) {
|
|
case Accept:
|
|
vlog.debug("ACCEPT %s", name.buf);
|
|
return true;
|
|
case Query:
|
|
vlog.debug("QUERY %s", name.buf);
|
|
s->setRequiresQuery();
|
|
return true;
|
|
case Reject:
|
|
vlog.debug("REJECT %s", name.buf);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
vlog.debug("[REJECT] %s", name.buf);
|
|
return false;
|
|
}
|
|
|
|
|
|
TcpFilter::Pattern TcpFilter::parsePattern(const char* p) {
|
|
TcpFilter::Pattern pattern;
|
|
|
|
rfb::CharArray addr, pref;
|
|
bool prefix_specified;
|
|
int family;
|
|
|
|
initSockets();
|
|
|
|
prefix_specified = rfb::strSplit(&p[1], '/', &addr.buf, &pref.buf);
|
|
if (addr.buf[0] == '\0') {
|
|
// Match any address
|
|
memset (&pattern.address, 0, sizeof (pattern.address));
|
|
pattern.address.u.sa.sa_family = AF_UNSPEC;
|
|
pattern.prefixlen = 0;
|
|
} else {
|
|
struct addrinfo hints;
|
|
struct addrinfo *ai;
|
|
char *p = addr.buf;
|
|
int result;
|
|
memset (&hints, 0, sizeof (hints));
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_flags = AI_NUMERICHOST;
|
|
|
|
// Take out brackets, if present
|
|
if (*p == '[') {
|
|
size_t len;
|
|
p++;
|
|
len = strlen (p);
|
|
if (len > 0 && p[len - 1] == ']')
|
|
p[len - 1] = '\0';
|
|
}
|
|
|
|
if ((result = getaddrinfo (p, NULL, &hints, &ai)) != 0) {
|
|
throw GAIException("unable to resolve host by name", result);
|
|
}
|
|
|
|
memcpy (&pattern.address.u.sa, ai->ai_addr, ai->ai_addrlen);
|
|
freeaddrinfo (ai);
|
|
|
|
family = pattern.address.u.sa.sa_family;
|
|
|
|
if (prefix_specified) {
|
|
if (family == AF_INET &&
|
|
rfb::strContains(pref.buf, '.')) {
|
|
throw Exception("mask no longer supported for filter, "
|
|
"use prefix instead");
|
|
}
|
|
|
|
pattern.prefixlen = (unsigned int) atoi(pref.buf);
|
|
} else {
|
|
switch (family) {
|
|
case AF_INET:
|
|
pattern.prefixlen = 32;
|
|
break;
|
|
case AF_INET6:
|
|
pattern.prefixlen = 128;
|
|
break;
|
|
default:
|
|
throw Exception("unknown address family");
|
|
}
|
|
}
|
|
}
|
|
|
|
family = pattern.address.u.sa.sa_family;
|
|
|
|
if (pattern.prefixlen > (family == AF_INET ? 32: 128))
|
|
throw Exception("invalid prefix length for filter address: %u",
|
|
pattern.prefixlen);
|
|
|
|
// Compute mask from address and prefix length
|
|
memset (&pattern.mask, 0, sizeof (pattern.mask));
|
|
switch (family) {
|
|
unsigned long mask;
|
|
case AF_INET:
|
|
mask = 0;
|
|
for (unsigned int i=0; i<pattern.prefixlen; i++)
|
|
mask |= 1<<(31-i);
|
|
pattern.mask.u.sin.sin_addr.s_addr = htonl(mask);
|
|
break;
|
|
|
|
case AF_INET6:
|
|
for (unsigned int n = 0; n < 16; n++) {
|
|
unsigned int bits = (n + 1) * 8;
|
|
if (pattern.prefixlen > bits)
|
|
pattern.mask.u.sin6.sin6_addr.s6_addr[n] = 0xff;
|
|
else {
|
|
unsigned int lastbits = 0xff;
|
|
lastbits <<= bits - pattern.prefixlen;
|
|
pattern.mask.u.sin6.sin6_addr.s6_addr[n] = lastbits & 0xff;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case AF_UNSPEC:
|
|
// No mask to compute
|
|
break;
|
|
default:
|
|
; /* not reached */
|
|
}
|
|
|
|
switch(p[0]) {
|
|
case '+': pattern.action = TcpFilter::Accept; break;
|
|
case '-': pattern.action = TcpFilter::Reject; break;
|
|
case '?': pattern.action = TcpFilter::Query; break;
|
|
};
|
|
|
|
return pattern;
|
|
}
|
|
|
|
char* TcpFilter::patternToStr(const TcpFilter::Pattern& p) {
|
|
rfb::CharArray addr;
|
|
char buffer[INET6_ADDRSTRLEN + 2];
|
|
|
|
if (p.address.u.sa.sa_family == AF_INET) {
|
|
getnameinfo(&p.address.u.sa, sizeof(p.address.u.sin),
|
|
buffer, sizeof (buffer), NULL, 0, NI_NUMERICHOST);
|
|
addr.buf = rfb::strDup(buffer);
|
|
} else if (p.address.u.sa.sa_family == AF_INET6) {
|
|
buffer[0] = '[';
|
|
getnameinfo(&p.address.u.sa, sizeof(p.address.u.sin6),
|
|
buffer + 1, sizeof (buffer) - 2, NULL, 0, NI_NUMERICHOST);
|
|
strcat(buffer, "]");
|
|
addr.buf = rfb::strDup(buffer);
|
|
} else if (p.address.u.sa.sa_family == AF_UNSPEC)
|
|
addr.buf = rfb::strDup("");
|
|
|
|
char action;
|
|
switch (p.action) {
|
|
case Accept: action = '+'; break;
|
|
case Reject: action = '-'; break;
|
|
default:
|
|
case Query: action = '?'; break;
|
|
};
|
|
size_t resultlen = (1 // action
|
|
+ strlen (addr.buf) // address
|
|
+ 1 // slash
|
|
+ 3 // prefix length, max 128
|
|
+ 1); // terminating nul
|
|
char* result = new char[resultlen];
|
|
if (addr.buf[0] == '\0')
|
|
snprintf(result, resultlen, "%c", action);
|
|
else
|
|
snprintf(result, resultlen, "%c%s/%u", action, addr.buf, p.prefixlen);
|
|
|
|
return result;
|
|
}
|