summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/webservice/vboxweb.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/webservice/vboxweb.cpp')
-rw-r--r--src/VBox/Main/webservice/vboxweb.cpp2513
1 files changed, 2513 insertions, 0 deletions
diff --git a/src/VBox/Main/webservice/vboxweb.cpp b/src/VBox/Main/webservice/vboxweb.cpp
new file mode 100644
index 00000000..eb1c44a0
--- /dev/null
+++ b/src/VBox/Main/webservice/vboxweb.cpp
@@ -0,0 +1,2513 @@
+/* $Id: vboxweb.cpp $ */
+/** @file
+ * vboxweb.cpp:
+ * hand-coded parts of the webservice server. This is linked with the
+ * generated code in out/.../src/VBox/Main/webservice/methodmaps.cpp
+ * (plus static gSOAP server code) to implement the actual webservice
+ * server, to which clients can connect.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+// shared webservice header
+#include "vboxweb.h"
+
+// vbox headers
+#include <VBox/com/com.h>
+#include <VBox/com/array.h>
+#include <VBox/com/string.h>
+#include <VBox/com/ErrorInfo.h>
+#include <VBox/com/errorprint.h>
+#include <VBox/com/listeners.h>
+#include <VBox/com/NativeEventQueue.h>
+#include <VBox/VBoxAuth.h>
+#include <VBox/version.h>
+#include <VBox/log.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/message.h>
+#include <iprt/process.h>
+#include <iprt/rand.h>
+#include <iprt/semaphore.h>
+#include <iprt/critsect.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <iprt/path.h>
+#include <iprt/system.h>
+#include <iprt/base64.h>
+#include <iprt/stream.h>
+#include <iprt/asm.h>
+
+#ifdef WITH_OPENSSL
+# include <openssl/opensslv.h>
+#endif
+
+#ifndef RT_OS_WINDOWS
+# include <signal.h>
+#endif
+
+// workaround for compile problems on gcc 4.1
+#ifdef __GNUC__
+#pragma GCC visibility push(default)
+#endif
+
+// gSOAP headers (must come after vbox includes because it checks for conflicting defs)
+#include "soapH.h"
+
+// standard headers
+#include <map>
+#include <list>
+
+#ifdef __GNUC__
+#pragma GCC visibility pop
+#endif
+
+// include generated namespaces table
+#include "vboxwebsrv.nsmap"
+
+RT_C_DECLS_BEGIN
+
+// declarations for the generated WSDL text
+extern const unsigned char g_abVBoxWebWSDL[];
+extern const unsigned g_cbVBoxWebWSDL;
+
+RT_C_DECLS_END
+
+static void WebLogSoapError(struct soap *soap);
+
+/****************************************************************************
+ *
+ * private typedefs
+ *
+ ****************************************************************************/
+
+typedef std::map<uint64_t, ManagedObjectRef*> ManagedObjectsMapById;
+typedef ManagedObjectsMapById::iterator ManagedObjectsIteratorById;
+typedef std::map<uintptr_t, ManagedObjectRef*> ManagedObjectsMapByPtr;
+typedef ManagedObjectsMapByPtr::iterator ManagedObjectsIteratorByPtr;
+
+typedef std::map<uint64_t, WebServiceSession*> WebsessionsMap;
+typedef WebsessionsMap::iterator WebsessionsMapIterator;
+
+typedef std::map<RTTHREAD, com::Utf8Str> ThreadsMap;
+
+static DECLCALLBACK(int) fntWatchdog(RTTHREAD ThreadSelf, void *pvUser);
+
+/****************************************************************************
+ *
+ * Read-only global variables
+ *
+ ****************************************************************************/
+
+static ComPtr<IVirtualBoxClient> g_pVirtualBoxClient = NULL;
+
+// generated strings in methodmaps.cpp
+extern const char *g_pcszISession,
+ *g_pcszIVirtualBox,
+ *g_pcszIVirtualBoxErrorInfo;
+
+// globals for vboxweb command-line arguments
+#define DEFAULT_TIMEOUT_SECS 300
+#define DEFAULT_TIMEOUT_SECS_STRING "300"
+static int g_iWatchdogTimeoutSecs = DEFAULT_TIMEOUT_SECS;
+static int g_iWatchdogCheckInterval = 5;
+
+static const char *g_pcszBindToHost = NULL; // host; NULL = localhost
+static unsigned int g_uBindToPort = 18083; // port
+static unsigned int g_uBacklog = 100; // backlog = max queue size for requests
+
+#ifdef WITH_OPENSSL
+static bool g_fSSL = false; // if SSL is enabled
+static const char *g_pcszKeyFile = NULL; // server key file
+static const char *g_pcszPassword = NULL; // password for server key
+static const char *g_pcszCACert = NULL; // file with trusted CA certificates
+static const char *g_pcszCAPath = NULL; // directory with trusted CA certificates
+static const char *g_pcszDHFile = NULL; // DH file name or DH key length in bits, NULL=use RSA
+static const char *g_pcszRandFile = NULL; // file with random data seed
+static const char *g_pcszSID = "vboxwebsrv"; // server ID for SSL session cache
+#endif /* WITH_OPENSSL */
+
+static unsigned int g_cMaxWorkerThreads = 100; // max. no. of worker threads
+static unsigned int g_cMaxKeepAlive = 100; // maximum number of soap requests in one connection
+
+static const char *g_pcszAuthentication = NULL; // web service authentication
+
+static uint32_t g_cHistory = 10; // enable log rotation, 10 files
+static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; // max 1 day per file
+static uint64_t g_uHistoryFileSize = 100 * _1M; // max 100MB per file
+bool g_fVerbose = false; // be verbose
+
+static bool g_fDaemonize = false; // run in background.
+static volatile bool g_fKeepRunning = true; // controlling the exit
+
+const WSDLT_ID g_EmptyWSDLID; // for NULL MORs
+
+/****************************************************************************
+ *
+ * Writeable global variables
+ *
+ ****************************************************************************/
+
+// The one global SOAP queue created by main().
+class SoapQ;
+static SoapQ *g_pSoapQ = NULL;
+
+// this mutex protects the auth lib and authentication
+static util::WriteLockHandle *g_pAuthLibLockHandle;
+
+// this mutex protects the global VirtualBox reference below
+static util::RWLockHandle *g_pVirtualBoxLockHandle;
+
+static ComPtr<IVirtualBox> g_pVirtualBox = NULL;
+
+// this mutex protects all of the below
+util::WriteLockHandle *g_pWebsessionsLockHandle;
+
+static WebsessionsMap g_mapWebsessions;
+static ULONG64 g_cManagedObjects = 0;
+
+// this mutex protects g_mapThreads
+static util::RWLockHandle *g_pThreadsLockHandle;
+
+// Threads map, so we can quickly map an RTTHREAD struct to a logger prefix
+static ThreadsMap g_mapThreads;
+
+/****************************************************************************
+ *
+ * Command line help
+ *
+ ****************************************************************************/
+
+static const RTGETOPTDEF g_aOptions[]
+ = {
+ { "--help", 'h', RTGETOPT_REQ_NOTHING }, /* for DisplayHelp() */
+#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
+ { "--background", 'b', RTGETOPT_REQ_NOTHING },
+#endif
+ { "--host", 'H', RTGETOPT_REQ_STRING },
+ { "--port", 'p', RTGETOPT_REQ_UINT32 },
+#ifdef WITH_OPENSSL
+ { "--ssl", 's', RTGETOPT_REQ_NOTHING },
+ { "--keyfile", 'K', RTGETOPT_REQ_STRING },
+ { "--passwordfile", 'a', RTGETOPT_REQ_STRING },
+ { "--cacert", 'c', RTGETOPT_REQ_STRING },
+ { "--capath", 'C', RTGETOPT_REQ_STRING },
+ { "--dhfile", 'D', RTGETOPT_REQ_STRING },
+ { "--randfile", 'r', RTGETOPT_REQ_STRING },
+#endif /* WITH_OPENSSL */
+ { "--timeout", 't', RTGETOPT_REQ_UINT32 },
+ { "--check-interval", 'i', RTGETOPT_REQ_UINT32 },
+ { "--threads", 'T', RTGETOPT_REQ_UINT32 },
+ { "--keepalive", 'k', RTGETOPT_REQ_UINT32 },
+ { "--authentication", 'A', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--pidfile", 'P', RTGETOPT_REQ_STRING },
+ { "--logfile", 'F', RTGETOPT_REQ_STRING },
+ { "--logrotate", 'R', RTGETOPT_REQ_UINT32 },
+ { "--logsize", 'S', RTGETOPT_REQ_UINT64 },
+ { "--loginterval", 'I', RTGETOPT_REQ_UINT32 }
+ };
+
+static void DisplayHelp()
+{
+ RTStrmPrintf(g_pStdErr, "\nUsage: vboxwebsrv [options]\n\nSupported options (default values in brackets):\n");
+ for (unsigned i = 0;
+ i < RT_ELEMENTS(g_aOptions);
+ ++i)
+ {
+ std::string str(g_aOptions[i].pszLong);
+ str += ", -";
+ str += g_aOptions[i].iShort;
+ str += ":";
+
+ const char *pcszDescr = "";
+
+ switch (g_aOptions[i].iShort)
+ {
+ case 'h':
+ pcszDescr = "Print this help message and exit.";
+ break;
+
+#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
+ case 'b':
+ pcszDescr = "Run in background (daemon mode).";
+ break;
+#endif
+
+ case 'H':
+ pcszDescr = "The host to bind to (localhost).";
+ break;
+
+ case 'p':
+ pcszDescr = "The port to bind to (18083).";
+ break;
+
+#ifdef WITH_OPENSSL
+ case 's':
+ pcszDescr = "Enable SSL/TLS encryption.";
+ break;
+
+ case 'K':
+ pcszDescr = "Server key and certificate file, PEM format (\"\").";
+ break;
+
+ case 'a':
+ pcszDescr = "File name for password to server key (\"\").";
+ break;
+
+ case 'c':
+ pcszDescr = "CA certificate file, PEM format (\"\").";
+ break;
+
+ case 'C':
+ pcszDescr = "CA certificate path (\"\").";
+ break;
+
+ case 'D':
+ pcszDescr = "DH file name or DH key length in bits (\"\").";
+ break;
+
+ case 'r':
+ pcszDescr = "File containing seed for random number generator (\"\").";
+ break;
+#endif /* WITH_OPENSSL */
+
+ case 't':
+ pcszDescr = "Session timeout in seconds; 0 = disable timeouts (" DEFAULT_TIMEOUT_SECS_STRING ").";
+ break;
+
+ case 'T':
+ pcszDescr = "Maximum number of worker threads to run in parallel (100).";
+ break;
+
+ case 'k':
+ pcszDescr = "Maximum number of requests before a socket will be closed (100).";
+ break;
+
+ case 'A':
+ pcszDescr = "Authentication method for the webservice (\"\").";
+ break;
+
+ case 'i':
+ pcszDescr = "Frequency of timeout checks in seconds (5).";
+ break;
+
+ case 'v':
+ pcszDescr = "Be verbose.";
+ break;
+
+ case 'P':
+ pcszDescr = "Name of the PID file which is created when the daemon was started.";
+ break;
+
+ case 'F':
+ pcszDescr = "Name of file to write log to (no file).";
+ break;
+
+ case 'R':
+ pcszDescr = "Number of log files (0 disables log rotation).";
+ break;
+
+ case 'S':
+ pcszDescr = "Maximum size of a log file to trigger rotation (bytes).";
+ break;
+
+ case 'I':
+ pcszDescr = "Maximum time interval to trigger log rotation (seconds).";
+ break;
+ }
+
+ RTStrmPrintf(g_pStdErr, "%-23s%s\n", str.c_str(), pcszDescr);
+ }
+}
+
+/****************************************************************************
+ *
+ * SoapQ, SoapThread (multithreading)
+ *
+ ****************************************************************************/
+
+class SoapQ;
+
+class SoapThread
+{
+public:
+ /**
+ * Constructor. Creates the new thread and makes it call process() for processing the queue.
+ * @param u Thread number. (So we can count from 1 and be readable.)
+ * @param q SoapQ instance which has the queue to process.
+ * @param soap struct soap instance from main() which we copy here.
+ */
+ SoapThread(size_t u,
+ SoapQ &q,
+ const struct soap *soap)
+ : m_u(u),
+ m_strThread(com::Utf8StrFmt("SQW%02d", m_u)),
+ m_pQ(&q)
+ {
+ // make a copy of the soap struct for the new thread
+ m_soap = soap_copy(soap);
+ m_soap->fget = fnHttpGet;
+
+ /* The soap.max_keep_alive value can be set to the maximum keep-alive calls allowed,
+ * which is important to avoid a client from holding a thread indefinitely.
+ * http://www.cs.fsu.edu/~engelen/soapdoc2.html#sec:keepalive
+ *
+ * Strings with 8-bit content can hold ASCII (default) or UTF8. The latter is
+ * possible by enabling the SOAP_C_UTFSTRING flag.
+ */
+ soap_set_omode(m_soap, SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING);
+ soap_set_imode(m_soap, SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING);
+ m_soap->max_keep_alive = g_cMaxKeepAlive;
+
+ int vrc = RTThreadCreate(&m_pThread,
+ fntWrapper,
+ this, // pvUser
+ 0, // cbStack
+ RTTHREADTYPE_MAIN_HEAVY_WORKER,
+ 0,
+ m_strThread.c_str());
+ if (RT_FAILURE(vrc))
+ {
+ RTMsgError("Cannot start worker thread %d: %Rrc\n", u, vrc);
+ exit(1);
+ }
+ }
+
+ void process();
+
+ static int fnHttpGet(struct soap *soap)
+ {
+ char *s = strchr(soap->path, '?');
+ if (!s || strcmp(s, "?wsdl"))
+ return SOAP_GET_METHOD;
+ soap_response(soap, SOAP_HTML);
+ soap_send_raw(soap, (const char *)g_abVBoxWebWSDL, g_cbVBoxWebWSDL);
+ soap_end_send(soap);
+ return SOAP_OK;
+ }
+
+ /**
+ * Static function that can be passed to RTThreadCreate and that calls
+ * process() on the SoapThread instance passed as the thread parameter.
+ *
+ * @param hThreadSelf
+ * @param pvThread
+ * @return
+ */
+ static DECLCALLBACK(int) fntWrapper(RTTHREAD hThreadSelf, void *pvThread)
+ {
+ RT_NOREF(hThreadSelf);
+ SoapThread *pst = (SoapThread*)pvThread;
+ pst->process();
+ return VINF_SUCCESS;
+ }
+
+ size_t m_u; // thread number
+ com::Utf8Str m_strThread; // thread name ("SoapQWrkXX")
+ SoapQ *m_pQ; // the single SOAP queue that all the threads service
+ struct soap *m_soap; // copy of the soap structure for this thread (from soap_copy())
+ RTTHREAD m_pThread; // IPRT thread struct for this thread
+};
+
+/**
+ * SOAP queue encapsulation. There is only one instance of this, to
+ * which add() adds a queue item (called on the main thread),
+ * and from which get() fetch items, called from each queue thread.
+ */
+class SoapQ
+{
+public:
+
+ /**
+ * Constructor. Creates the soap queue.
+ * @param pSoap
+ */
+ SoapQ(const struct soap *pSoap)
+ : m_soap(pSoap),
+ m_mutex(util::LOCKCLASS_OBJECTSTATE), // lowest lock order, no other may be held while this is held
+ m_cIdleThreads(0)
+ {
+ RTSemEventMultiCreate(&m_event);
+ }
+
+ ~SoapQ()
+ {
+ /* Tell the threads to terminate. */
+ RTSemEventMultiSignal(m_event);
+ {
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+ int i = 0;
+ while (m_llAllThreads.size() && i++ <= 30)
+ {
+ qlock.release();
+ RTThreadSleep(1000);
+ RTSemEventMultiSignal(m_event);
+ qlock.acquire();
+ }
+ LogRel(("ending queue processing (%d out of %d threads idle)\n", m_cIdleThreads, m_llAllThreads.size()));
+ }
+
+ RTSemEventMultiDestroy(m_event);
+ }
+
+ /**
+ * Adds the given socket to the SOAP queue and posts the
+ * member event sem to wake up the workers. Called on the main thread
+ * whenever a socket has work to do. Creates a new SOAP thread on the
+ * first call or when all existing threads are busy.
+ * @param s Socket from soap_accept() which has work to do.
+ */
+ size_t add(SOAP_SOCKET s)
+ {
+ size_t cItems;
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+
+ // if no threads have yet been created, or if all threads are busy,
+ // create a new SOAP thread
+ if ( !m_cIdleThreads
+ // but only if we're not exceeding the global maximum (default is 100)
+ && (m_llAllThreads.size() < g_cMaxWorkerThreads)
+ )
+ {
+ SoapThread *pst = new SoapThread(m_llAllThreads.size() + 1,
+ *this,
+ m_soap);
+ m_llAllThreads.push_back(pst);
+ util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_mapThreads[pst->m_pThread] = com::Utf8StrFmt("[%3u]", pst->m_u);
+ ++m_cIdleThreads;
+ }
+
+ // enqueue the socket of this connection and post eventsem so that
+ // one of the threads (possibly the one just created) can pick it up
+ m_llSocketsQ.push_back(s);
+ cItems = m_llSocketsQ.size();
+ qlock.release();
+
+ // unblock one of the worker threads
+ RTSemEventMultiSignal(m_event);
+
+ return cItems;
+ }
+
+ /**
+ * Blocks the current thread until work comes in; then returns
+ * the SOAP socket which has work to do. This reduces m_cIdleThreads
+ * by one, and the caller MUST call done() when it's done processing.
+ * Called from the worker threads.
+ * @param cIdleThreads out: no. of threads which are currently idle (not counting the caller)
+ * @param cThreads out: total no. of SOAP threads running
+ * @return
+ */
+ SOAP_SOCKET get(size_t &cIdleThreads, size_t &cThreads)
+ {
+ while (g_fKeepRunning)
+ {
+ // wait for something to happen
+ RTSemEventMultiWait(m_event, RT_INDEFINITE_WAIT);
+
+ if (!g_fKeepRunning)
+ break;
+
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+ if (!m_llSocketsQ.empty())
+ {
+ SOAP_SOCKET socket = m_llSocketsQ.front();
+ m_llSocketsQ.pop_front();
+ cIdleThreads = --m_cIdleThreads;
+ cThreads = m_llAllThreads.size();
+
+ // reset the multi event only if the queue is now empty; otherwise
+ // another thread will also wake up when we release the mutex and
+ // process another one
+ if (m_llSocketsQ.empty())
+ RTSemEventMultiReset(m_event);
+
+ qlock.release();
+
+ return socket;
+ }
+
+ // nothing to do: keep looping
+ }
+ return SOAP_INVALID_SOCKET;
+ }
+
+ /**
+ * To be called by a worker thread after fetching an item from the
+ * queue via get() and having finished its lengthy processing.
+ */
+ void done()
+ {
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+ ++m_cIdleThreads;
+ }
+
+ /**
+ * To be called by a worker thread when signing off, i.e. no longer
+ * willing to process requests.
+ */
+ void signoff(SoapThread *th)
+ {
+ {
+ util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
+ size_t c = g_mapThreads.erase(th->m_pThread);
+ AssertReturnVoid(c == 1);
+ }
+ {
+ util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
+ m_llAllThreads.remove(th);
+ --m_cIdleThreads;
+ }
+ }
+
+ const struct soap *m_soap; // soap structure created by main(), passed to constructor
+
+ util::WriteLockHandle m_mutex;
+ RTSEMEVENTMULTI m_event; // posted by add(), blocked on by get()
+
+ std::list<SoapThread*> m_llAllThreads; // all the threads created by the constructor
+ size_t m_cIdleThreads; // threads which are currently idle (statistics)
+
+ // A std::list abused as a queue; this contains the actual jobs to do,
+ // each int being a socket from soap_accept()
+ std::list<SOAP_SOCKET> m_llSocketsQ;
+};
+
+/**
+ * Thread function for each of the SOAP queue worker threads. This keeps
+ * running, blocks on the event semaphore in SoapThread.SoapQ and picks
+ * up a socket from the queue therein, which has been put there by
+ * beginProcessing().
+ */
+void SoapThread::process()
+{
+ LogRel(("New SOAP thread started\n"));
+
+ while (g_fKeepRunning)
+ {
+ // wait for a socket to arrive on the queue
+ size_t cIdleThreads = 0, cThreads = 0;
+ m_soap->socket = m_pQ->get(cIdleThreads, cThreads);
+
+ if (!soap_valid_socket(m_soap->socket))
+ continue;
+
+ LogRel(("Processing connection from IP=%RTnaipv4 socket=%d (%d out of %d threads idle)\n",
+ RT_H2N_U32(m_soap->ip), m_soap->socket, cIdleThreads, cThreads));
+
+ // Ensure that we don't get stuck indefinitely for connections using
+ // keepalive, otherwise stale connections tie up worker threads.
+ m_soap->send_timeout = 60;
+ m_soap->recv_timeout = 60;
+ // Limit the maximum SOAP request size to a generous amount, just to
+ // be on the safe side (SOAP is quite wordy when representing arrays,
+ // and some API uses need to deal with large arrays). Good that binary
+ // data is no longer represented by byte arrays...
+ m_soap->recv_maxlength = _16M;
+ // process the request; this goes into the COM code in methodmaps.cpp
+ do {
+#ifdef WITH_OPENSSL
+ if (g_fSSL && soap_ssl_accept(m_soap))
+ {
+ WebLogSoapError(m_soap);
+ break;
+ }
+#endif /* WITH_OPENSSL */
+ soap_serve(m_soap);
+ } while (0);
+
+ soap_destroy(m_soap); // clean up class instances
+ soap_end(m_soap); // clean up everything and close socket
+
+ // tell the queue we're idle again
+ m_pQ->done();
+ }
+ m_pQ->signoff(this);
+}
+
+/****************************************************************************
+ *
+ * VirtualBoxClient event listener
+ *
+ ****************************************************************************/
+
+class VirtualBoxClientEventListener
+{
+public:
+ VirtualBoxClientEventListener()
+ {
+ }
+
+ virtual ~VirtualBoxClientEventListener()
+ {
+ }
+
+ HRESULT init()
+ {
+ return S_OK;
+ }
+
+ void uninit()
+ {
+ }
+
+
+ STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
+ {
+ switch (aType)
+ {
+ case VBoxEventType_OnVBoxSVCAvailabilityChanged:
+ {
+ ComPtr<IVBoxSVCAvailabilityChangedEvent> pVSACEv = aEvent;
+ Assert(pVSACEv);
+ BOOL fAvailable = FALSE;
+ pVSACEv->COMGETTER(Available)(&fAvailable);
+ if (!fAvailable)
+ {
+ LogRel(("VBoxSVC became unavailable\n"));
+ {
+ util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_pVirtualBox.setNull();
+ }
+ {
+ // we're messing with websessions, so lock them
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+ WEBDEBUG(("SVC unavailable: deleting %d websessions\n", g_mapWebsessions.size()));
+
+ WebsessionsMapIterator it = g_mapWebsessions.begin(),
+ itEnd = g_mapWebsessions.end();
+ while (it != itEnd)
+ {
+ WebServiceSession *pWebsession = it->second;
+ WEBDEBUG(("SVC unavailable: websession %#llx stale, deleting\n", pWebsession->getID()));
+ delete pWebsession;
+ it = g_mapWebsessions.begin();
+ }
+ }
+ }
+ else
+ {
+ LogRel(("VBoxSVC became available\n"));
+ util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
+ HRESULT hrc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam());
+ AssertComRC(hrc);
+ }
+ break;
+ }
+ default:
+ AssertFailed();
+ }
+
+ return S_OK;
+ }
+
+private:
+};
+
+typedef ListenerImpl<VirtualBoxClientEventListener> VirtualBoxClientEventListenerImpl;
+
+VBOX_LISTENER_DECLARE(VirtualBoxClientEventListenerImpl)
+
+/**
+ * Helper for printing SOAP error messages.
+ * @param soap
+ */
+/*static*/
+void WebLogSoapError(struct soap *soap)
+{
+ if (soap_check_state(soap))
+ {
+ LogRel(("Error: soap struct not initialized\n"));
+ return;
+ }
+
+ const char *pcszFaultString = *soap_faultstring(soap);
+ const char **ppcszDetail = soap_faultcode(soap);
+ LogRel(("#### SOAP FAULT: %s [%s]\n",
+ pcszFaultString ? pcszFaultString : "[no fault string available]",
+ (ppcszDetail && *ppcszDetail) ? *ppcszDetail : "no details available"));
+}
+
+/**
+ * Helper for decoding AuthResult.
+ * @param result AuthResult
+ */
+static const char * decodeAuthResult(AuthResult result)
+{
+ switch (result)
+ {
+ case AuthResultAccessDenied: return "access DENIED";
+ case AuthResultAccessGranted: return "access granted";
+ case AuthResultDelegateToGuest: return "delegated to guest";
+ default: return "unknown AuthResult";
+ }
+}
+
+#if defined(WITH_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER))
+/****************************************************************************
+ *
+ * OpenSSL convenience functions for multithread support.
+ * Not required for OpenSSL 1.1+
+ *
+ ****************************************************************************/
+
+static RTCRITSECT *g_pSSLMutexes = NULL;
+
+struct CRYPTO_dynlock_value
+{
+ RTCRITSECT mutex;
+};
+
+static unsigned long CRYPTO_id_function()
+{
+ return (unsigned long)RTThreadNativeSelf();
+}
+
+static void CRYPTO_locking_function(int mode, int n, const char * /*file*/, int /*line*/)
+{
+ if (mode & CRYPTO_LOCK)
+ RTCritSectEnter(&g_pSSLMutexes[n]);
+ else
+ RTCritSectLeave(&g_pSSLMutexes[n]);
+}
+
+static struct CRYPTO_dynlock_value *CRYPTO_dyn_create_function(const char * /*file*/, int /*line*/)
+{
+ static uint32_t s_iCritSectDynlock = 0;
+ struct CRYPTO_dynlock_value *value = (struct CRYPTO_dynlock_value *)RTMemAlloc(sizeof(struct CRYPTO_dynlock_value));
+ if (value)
+ RTCritSectInitEx(&value->mutex, RTCRITSECT_FLAGS_NO_LOCK_VAL,
+ NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE,
+ "openssl-dyn-%u", ASMAtomicIncU32(&s_iCritSectDynlock) - 1);
+
+ return value;
+}
+
+static void CRYPTO_dyn_lock_function(int mode, struct CRYPTO_dynlock_value *value, const char * /*file*/, int /*line*/)
+{
+ if (mode & CRYPTO_LOCK)
+ RTCritSectEnter(&value->mutex);
+ else
+ RTCritSectLeave(&value->mutex);
+}
+
+static void CRYPTO_dyn_destroy_function(struct CRYPTO_dynlock_value *value, const char * /*file*/, int /*line*/)
+{
+ if (value)
+ {
+ RTCritSectDelete(&value->mutex);
+ free(value);
+ }
+}
+
+static int CRYPTO_thread_setup()
+{
+ int num_locks = CRYPTO_num_locks();
+ g_pSSLMutexes = (RTCRITSECT *)RTMemAlloc(num_locks * sizeof(RTCRITSECT));
+ if (!g_pSSLMutexes)
+ return SOAP_EOM;
+
+ for (int i = 0; i < num_locks; i++)
+ {
+ int vrc = RTCritSectInitEx(&g_pSSLMutexes[i], RTCRITSECT_FLAGS_NO_LOCK_VAL,
+ NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE,
+ "openssl-%d", i);
+ if (RT_FAILURE(vrc))
+ {
+ for ( ; i >= 0; i--)
+ RTCritSectDelete(&g_pSSLMutexes[i]);
+ RTMemFree(g_pSSLMutexes);
+ g_pSSLMutexes = NULL;
+ return SOAP_EOM;
+ }
+ }
+
+ CRYPTO_set_id_callback(CRYPTO_id_function);
+ CRYPTO_set_locking_callback(CRYPTO_locking_function);
+ CRYPTO_set_dynlock_create_callback(CRYPTO_dyn_create_function);
+ CRYPTO_set_dynlock_lock_callback(CRYPTO_dyn_lock_function);
+ CRYPTO_set_dynlock_destroy_callback(CRYPTO_dyn_destroy_function);
+
+ return SOAP_OK;
+}
+
+static void CRYPTO_thread_cleanup()
+{
+ if (!g_pSSLMutexes)
+ return;
+
+ CRYPTO_set_id_callback(NULL);
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_dynlock_create_callback(NULL);
+ CRYPTO_set_dynlock_lock_callback(NULL);
+ CRYPTO_set_dynlock_destroy_callback(NULL);
+
+ int num_locks = CRYPTO_num_locks();
+ for (int i = 0; i < num_locks; i++)
+ RTCritSectDelete(&g_pSSLMutexes[i]);
+
+ RTMemFree(g_pSSLMutexes);
+ g_pSSLMutexes = NULL;
+}
+#endif /* WITH_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER)) */
+
+/****************************************************************************
+ *
+ * SOAP queue pumper thread
+ *
+ ****************************************************************************/
+
+static void doQueuesLoop()
+{
+#if defined(WITH_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER))
+ if (g_fSSL && CRYPTO_thread_setup())
+ {
+ LogRel(("Failed to set up OpenSSL thread mutex!"));
+ exit(RTEXITCODE_FAILURE);
+ }
+#endif
+
+ // set up gSOAP
+ struct soap soap;
+ soap_init(&soap);
+
+#ifdef WITH_OPENSSL
+ if (g_fSSL && soap_ssl_server_context(&soap, SOAP_SSL_REQUIRE_SERVER_AUTHENTICATION | SOAP_TLSv1, g_pcszKeyFile,
+ g_pcszPassword, g_pcszCACert, g_pcszCAPath,
+ g_pcszDHFile, g_pcszRandFile, g_pcszSID))
+ {
+ WebLogSoapError(&soap);
+ exit(RTEXITCODE_FAILURE);
+ }
+#endif /* WITH_OPENSSL */
+
+ soap.bind_flags |= SO_REUSEADDR;
+ // avoid EADDRINUSE on bind()
+
+ SOAP_SOCKET m, s; // master and slave sockets
+ m = soap_bind(&soap,
+ g_pcszBindToHost ? g_pcszBindToHost : "localhost", // safe default host
+ g_uBindToPort, // port
+ g_uBacklog); // backlog = max queue size for requests
+ if (m == SOAP_INVALID_SOCKET)
+ WebLogSoapError(&soap);
+ else
+ {
+#ifdef WITH_OPENSSL
+ const char *pszSsl = g_fSSL ? "SSL, " : "";
+#else /* !WITH_OPENSSL */
+ const char *pszSsl = "";
+#endif /*!WITH_OPENSSL */
+ LogRel(("Socket connection successful: host = %s, port = %u, %smaster socket = %d\n",
+ (g_pcszBindToHost) ? g_pcszBindToHost : "default (localhost)",
+ g_uBindToPort, pszSsl, m));
+
+ // initialize thread queue, mutex and eventsem
+ g_pSoapQ = new SoapQ(&soap);
+
+ uint64_t cAccepted = 1;
+ while (g_fKeepRunning)
+ {
+ struct timeval timeout;
+ fd_set ReadFds, WriteFds, XcptFds;
+ int rv;
+ for (;;)
+ {
+ timeout.tv_sec = 60;
+ timeout.tv_usec = 0;
+ FD_ZERO(&ReadFds);
+ FD_SET(soap.master, &ReadFds);
+ FD_ZERO(&WriteFds);
+ FD_SET(soap.master, &WriteFds);
+ FD_ZERO(&XcptFds);
+ FD_SET(soap.master, &XcptFds);
+ rv = select((int)soap.master + 1, &ReadFds, &WriteFds, &XcptFds, &timeout);
+ if (rv > 0)
+ break; // work is waiting
+ if (rv == 0)
+ continue; // timeout, not necessary to bother gsoap
+ // r < 0, errno
+#if GSOAP_VERSION >= 208103
+ if (soap_socket_errno == SOAP_EINTR)
+#else
+ if (soap_socket_errno(soap.master) == SOAP_EINTR)
+#endif
+ rv = 0; // re-check if we should terminate
+ break;
+ }
+ if (rv == 0)
+ continue;
+
+ // call gSOAP to handle incoming SOAP connection
+ soap.accept_timeout = -1; // 1usec timeout, actual waiting is above
+ s = soap_accept(&soap);
+ if (!soap_valid_socket(s))
+ {
+ if (soap.errnum)
+ WebLogSoapError(&soap);
+ continue;
+ }
+
+ // add the socket to the queue and tell worker threads to
+ // pick up the job
+ size_t cItemsOnQ = g_pSoapQ->add(s);
+ LogRel(("Request %llu on socket %d queued for processing (%d items on Q)\n", cAccepted, s, cItemsOnQ));
+ cAccepted++;
+ }
+
+ delete g_pSoapQ;
+ g_pSoapQ = NULL;
+
+ LogRel(("ending SOAP request handling\n"));
+
+ delete g_pSoapQ;
+ g_pSoapQ = NULL;
+
+ }
+ soap_done(&soap); // close master socket and detach environment
+
+#if defined(WITH_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER))
+ if (g_fSSL)
+ CRYPTO_thread_cleanup();
+#endif
+}
+
+/**
+ * Thread function for the "queue pumper" thread started from main(). This implements
+ * the loop that takes SOAP calls from HTTP and serves them by handing sockets to the
+ * SOAP queue worker threads.
+ */
+static DECLCALLBACK(int) fntQPumper(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf, pvUser);
+
+ // store a log prefix for this thread
+ util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_mapThreads[RTThreadSelf()] = "[ P ]";
+ thrLock.release();
+
+ doQueuesLoop();
+
+ thrLock.acquire();
+ g_mapThreads.erase(RTThreadSelf());
+ return VINF_SUCCESS;
+}
+
+#ifdef RT_OS_WINDOWS
+/**
+ * "Signal" handler for cleanly terminating the event loop.
+ */
+static BOOL WINAPI websrvSignalHandler(DWORD dwCtrlType)
+{
+ bool fEventHandled = FALSE;
+ switch (dwCtrlType)
+ {
+ /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
+ * via GenerateConsoleCtrlEvent(). */
+ case CTRL_BREAK_EVENT:
+ case CTRL_CLOSE_EVENT:
+ case CTRL_C_EVENT:
+ case CTRL_LOGOFF_EVENT:
+ case CTRL_SHUTDOWN_EVENT:
+ {
+ ASMAtomicWriteBool(&g_fKeepRunning, false);
+ com::NativeEventQueue *pQ = com::NativeEventQueue::getMainEventQueue();
+ pQ->interruptEventQueueProcessing();
+ fEventHandled = TRUE;
+ break;
+ }
+ default:
+ break;
+ }
+ return fEventHandled;
+}
+#else
+/**
+ * Signal handler for cleanly terminating the event loop.
+ */
+static void websrvSignalHandler(int iSignal)
+{
+ NOREF(iSignal);
+ ASMAtomicWriteBool(&g_fKeepRunning, false);
+ com::NativeEventQueue *pQ = com::NativeEventQueue::getMainEventQueue();
+ pQ->interruptEventQueueProcessing();
+}
+#endif
+
+
+/**
+ * Start up the webservice server. This keeps running and waits
+ * for incoming SOAP connections; for each request that comes in,
+ * it calls method implementation code, most of it in the generated
+ * code in methodmaps.cpp.
+ *
+ * @param argc
+ * @param argv[]
+ * @return
+ */
+int main(int argc, char *argv[])
+{
+ // initialize runtime
+ int vrc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(vrc))
+ return RTMsgInitFailure(vrc);
+#ifdef RT_OS_WINDOWS
+ ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */
+#endif
+
+ // store a log prefix for this thread
+ g_mapThreads[RTThreadSelf()] = "[M ]";
+
+ RTStrmPrintf(g_pStdErr, VBOX_PRODUCT " web service Version " VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2007-" VBOX_C_YEAR " " VBOX_VENDOR "\n");
+
+ int c;
+ const char *pszLogFile = NULL;
+ const char *pszPidFile = NULL;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, g_aOptions, RT_ELEMENTS(g_aOptions), 1, 0 /*fFlags*/);
+ while ((c = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (c)
+ {
+ case 'H':
+ if (!ValueUnion.psz || !*ValueUnion.psz)
+ {
+ /* Normalize NULL/empty string to NULL, which will be
+ * interpreted as "localhost" below. */
+ g_pcszBindToHost = NULL;
+ }
+ else
+ g_pcszBindToHost = ValueUnion.psz;
+ break;
+
+ case 'p':
+ g_uBindToPort = ValueUnion.u32;
+ break;
+
+#ifdef WITH_OPENSSL
+ case 's':
+ g_fSSL = true;
+ break;
+
+ case 'K':
+ g_pcszKeyFile = ValueUnion.psz;
+ break;
+
+ case 'a':
+ if (ValueUnion.psz[0] == '\0')
+ g_pcszPassword = NULL;
+ else
+ {
+ PRTSTREAM StrmIn;
+ if (!strcmp(ValueUnion.psz, "-"))
+ StrmIn = g_pStdIn;
+ else
+ {
+ vrc = RTStrmOpen(ValueUnion.psz, "r", &StrmIn);
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open password file (%s, %Rrc)", ValueUnion.psz, vrc);
+ }
+ char szPasswd[512];
+ vrc = RTStrmGetLine(StrmIn, szPasswd, sizeof(szPasswd));
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to read password (%s, %Rrc)", ValueUnion.psz, vrc);
+ g_pcszPassword = RTStrDup(szPasswd);
+ memset(szPasswd, '\0', sizeof(szPasswd));
+ if (StrmIn != g_pStdIn)
+ RTStrmClose(StrmIn);
+ }
+ break;
+
+ case 'c':
+ g_pcszCACert = ValueUnion.psz;
+ break;
+
+ case 'C':
+ g_pcszCAPath = ValueUnion.psz;
+ break;
+
+ case 'D':
+ g_pcszDHFile = ValueUnion.psz;
+ break;
+
+ case 'r':
+ g_pcszRandFile = ValueUnion.psz;
+ break;
+#endif /* WITH_OPENSSL */
+
+ case 't':
+ g_iWatchdogTimeoutSecs = ValueUnion.u32;
+ break;
+
+ case 'i':
+ g_iWatchdogCheckInterval = ValueUnion.u32;
+ break;
+
+ case 'F':
+ pszLogFile = ValueUnion.psz;
+ break;
+
+ case 'R':
+ g_cHistory = ValueUnion.u32;
+ break;
+
+ case 'S':
+ g_uHistoryFileSize = ValueUnion.u64;
+ break;
+
+ case 'I':
+ g_uHistoryFileTime = ValueUnion.u32;
+ break;
+
+ case 'P':
+ pszPidFile = ValueUnion.psz;
+ break;
+
+ case 'T':
+ g_cMaxWorkerThreads = ValueUnion.u32;
+ break;
+
+ case 'k':
+ g_cMaxKeepAlive = ValueUnion.u32;
+ break;
+
+ case 'A':
+ g_pcszAuthentication = ValueUnion.psz;
+ break;
+
+ case 'h':
+ DisplayHelp();
+ return 0;
+
+ case 'v':
+ g_fVerbose = true;
+ break;
+
+#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
+ case 'b':
+ g_fDaemonize = true;
+ break;
+#endif
+ case 'V':
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return 0;
+
+ default:
+ return RTGetOptPrintError(c, &ValueUnion);
+ }
+ }
+
+ /* create release logger, to stdout */
+ RTERRINFOSTATIC ErrInfo;
+ vrc = com::VBoxLogRelCreate("web service", g_fDaemonize ? NULL : pszLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ "all", "VBOXWEBSRV_RELEASE_LOG",
+ RTLOGDEST_STDOUT, UINT32_MAX /* cMaxEntriesPerGroup */,
+ g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, vrc);
+
+#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
+ if (g_fDaemonize)
+ {
+ /* prepare release logging */
+ char szLogFile[RTPATH_MAX];
+
+ if (!pszLogFile || !*pszLogFile)
+ {
+ vrc = com::GetVBoxUserHomeDirectory(szLogFile, sizeof(szLogFile));
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not get base directory for logging: %Rrc", vrc);
+ vrc = RTPathAppend(szLogFile, sizeof(szLogFile), "vboxwebsrv.log");
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not construct logging path: %Rrc", vrc);
+ pszLogFile = szLogFile;
+ }
+
+ vrc = RTProcDaemonizeUsingFork(false /* fNoChDir */, false /* fNoClose */, pszPidFile);
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to daemonize, vrc=%Rrc. exiting.", vrc);
+
+ /* create release logger, to file */
+ vrc = com::VBoxLogRelCreate("web service", pszLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ "all", "VBOXWEBSRV_RELEASE_LOG",
+ RTLOGDEST_FILE, UINT32_MAX /* cMaxEntriesPerGroup */,
+ g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, vrc);
+ }
+#endif
+
+ // initialize SOAP SSL support if enabled
+#ifdef WITH_OPENSSL
+ if (g_fSSL)
+ soap_ssl_init();
+#endif /* WITH_OPENSSL */
+
+ // initialize COM/XPCOM
+ HRESULT hrc = com::Initialize();
+#ifdef VBOX_WITH_XPCOM
+ if (hrc == NS_ERROR_FILE_ACCESS_DENIED)
+ {
+ char szHome[RTPATH_MAX] = "";
+ com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome));
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome);
+ }
+#endif
+ if (FAILED(hrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to initialize COM! hrc=%Rhrc\n", hrc);
+
+ hrc = g_pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
+ if (FAILED(hrc))
+ {
+ RTMsgError("failed to create the VirtualBoxClient object!");
+ com::ErrorInfo info;
+ if (!info.isFullAvailable() && !info.isBasicAvailable())
+ {
+ com::GluePrintRCMessage(hrc);
+ RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start.");
+ }
+ else
+ com::GluePrintErrorInfo(info);
+ return RTEXITCODE_FAILURE;
+ }
+
+ hrc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam());
+ if (FAILED(hrc))
+ {
+ RTMsgError("Failed to get VirtualBox object (hrc=%Rhrc)!", hrc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ // set the authentication method if requested
+ if (g_pVirtualBox && g_pcszAuthentication && g_pcszAuthentication[0])
+ {
+ ComPtr<ISystemProperties> pSystemProperties;
+ g_pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (pSystemProperties)
+ pSystemProperties->COMSETTER(WebServiceAuthLibrary)(com::Bstr(g_pcszAuthentication).raw());
+ }
+
+ /* VirtualBoxClient events registration. */
+ ComPtr<IEventListener> vboxClientListener;
+ {
+ ComPtr<IEventSource> pES;
+ CHECK_ERROR(g_pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
+ ComObjPtr<VirtualBoxClientEventListenerImpl> clientListener;
+ clientListener.createObject();
+ clientListener->init(new VirtualBoxClientEventListener());
+ vboxClientListener = clientListener;
+ com::SafeArray<VBoxEventType_T> eventTypes;
+ eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged);
+ CHECK_ERROR(pES, RegisterListener(vboxClientListener, ComSafeArrayAsInParam(eventTypes), true));
+ }
+
+ // create the global mutexes
+ g_pAuthLibLockHandle = new util::WriteLockHandle(util::LOCKCLASS_WEBSERVICE);
+ g_pVirtualBoxLockHandle = new util::RWLockHandle(util::LOCKCLASS_WEBSERVICE);
+ g_pWebsessionsLockHandle = new util::WriteLockHandle(util::LOCKCLASS_WEBSERVICE);
+ g_pThreadsLockHandle = new util::RWLockHandle(util::LOCKCLASS_OBJECTSTATE);
+
+ // SOAP queue pumper thread
+ RTTHREAD threadQPumper;
+ vrc = RTThreadCreate(&threadQPumper,
+ fntQPumper,
+ NULL, // pvUser
+ 0, // cbStack (default)
+ RTTHREADTYPE_MAIN_WORKER,
+ RTTHREADFLAGS_WAITABLE,
+ "SQPmp");
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot start SOAP queue pumper thread: %Rrc", vrc);
+
+ // watchdog thread
+ RTTHREAD threadWatchdog = NIL_RTTHREAD;
+ if (g_iWatchdogTimeoutSecs > 0)
+ {
+ // start our watchdog thread
+ vrc = RTThreadCreate(&threadWatchdog,
+ fntWatchdog,
+ NULL,
+ 0,
+ RTTHREADTYPE_MAIN_WORKER,
+ RTTHREADFLAGS_WAITABLE,
+ "Watchdog");
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot start watchdog thread: %Rrc", vrc);
+ }
+
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)websrvSignalHandler, TRUE /* Add handler */))
+ {
+ vrc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to install console control handler, vrc=%Rrc\n", vrc);
+ }
+#else
+ signal(SIGINT, websrvSignalHandler);
+ signal(SIGTERM, websrvSignalHandler);
+# ifdef SIGBREAK
+ signal(SIGBREAK, websrvSignalHandler);
+# endif
+#endif
+
+ com::NativeEventQueue *pQ = com::NativeEventQueue::getMainEventQueue();
+ while (g_fKeepRunning)
+ {
+ // we have to process main event queue
+ WEBDEBUG(("Pumping COM event queue\n"));
+ vrc = pQ->processEventQueue(RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(vrc))
+ RTMsgError("processEventQueue -> %Rrc", vrc);
+ }
+
+ LogRel(("requested termination, cleaning up\n"));
+
+#ifdef RT_OS_WINDOWS
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)websrvSignalHandler, FALSE /* Remove handler */))
+ {
+ vrc = RTErrConvertFromWin32(GetLastError());
+ RTMsgError("Unable to remove console control handler, vrc=%Rrc\n", vrc);
+ }
+#else
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+# ifdef SIGBREAK
+ signal(SIGBREAK, SIG_DFL);
+# endif
+#endif
+
+#ifndef RT_OS_WINDOWS
+ RTThreadPoke(threadQPumper);
+#endif
+ RTThreadWait(threadQPumper, 30000, NULL);
+ if (threadWatchdog != NIL_RTTHREAD)
+ {
+#ifndef RT_OS_WINDOWS
+ RTThreadPoke(threadWatchdog);
+#endif
+ RTThreadWait(threadWatchdog, g_iWatchdogCheckInterval * 1000 + 10000, NULL);
+ }
+
+ /* VirtualBoxClient events unregistration. */
+ if (vboxClientListener)
+ {
+ ComPtr<IEventSource> pES;
+ CHECK_ERROR(g_pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
+ if (!pES.isNull())
+ CHECK_ERROR(pES, UnregisterListener(vboxClientListener));
+ vboxClientListener.setNull();
+ }
+
+ {
+ util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_pVirtualBox.setNull();
+ }
+ {
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+ WebsessionsMapIterator it = g_mapWebsessions.begin(),
+ itEnd = g_mapWebsessions.end();
+ while (it != itEnd)
+ {
+ WebServiceSession *pWebsession = it->second;
+ WEBDEBUG(("SVC unavailable: websession %#llx stale, deleting\n", pWebsession->getID()));
+ delete pWebsession;
+ it = g_mapWebsessions.begin();
+ }
+ }
+ g_pVirtualBoxClient.setNull();
+
+ com::Shutdown();
+
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * Watchdog thread
+ *
+ ****************************************************************************/
+
+/**
+ * Watchdog thread, runs in the background while the webservice is alive.
+ *
+ * This gets started by main() and runs in the background to check all websessions
+ * for whether they have been no requests in a configurable timeout period. In
+ * that case, the websession is automatically logged off.
+ */
+static DECLCALLBACK(int) fntWatchdog(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf, pvUser);
+
+ // store a log prefix for this thread
+ util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
+ g_mapThreads[RTThreadSelf()] = "[W ]";
+ thrLock.release();
+
+ WEBDEBUG(("Watchdog thread started\n"));
+
+ uint32_t tNextStat = 0;
+
+ while (g_fKeepRunning)
+ {
+ WEBDEBUG(("Watchdog: sleeping %d seconds\n", g_iWatchdogCheckInterval));
+ RTThreadSleep(g_iWatchdogCheckInterval * 1000);
+
+ uint32_t tNow = RTTimeProgramSecTS();
+
+ // we're messing with websessions, so lock them
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+ WEBDEBUG(("Watchdog: checking %d websessions\n", g_mapWebsessions.size()));
+
+ WebsessionsMapIterator it = g_mapWebsessions.begin(),
+ itEnd = g_mapWebsessions.end();
+ while (it != itEnd)
+ {
+ WebServiceSession *pWebsession = it->second;
+ WEBDEBUG(("Watchdog: tNow: %d, websession timestamp: %d\n", tNow, pWebsession->getLastObjectLookup()));
+ if (tNow > pWebsession->getLastObjectLookup() + g_iWatchdogTimeoutSecs)
+ {
+ WEBDEBUG(("Watchdog: websession %#llx timed out, deleting\n", pWebsession->getID()));
+ delete pWebsession;
+ it = g_mapWebsessions.begin();
+ }
+ else
+ ++it;
+ }
+
+ // re-set the authentication method in case it has been changed
+ if (g_pVirtualBox && g_pcszAuthentication && g_pcszAuthentication[0])
+ {
+ ComPtr<ISystemProperties> pSystemProperties;
+ g_pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
+ if (pSystemProperties)
+ pSystemProperties->COMSETTER(WebServiceAuthLibrary)(com::Bstr(g_pcszAuthentication).raw());
+ }
+
+ // Log some MOR usage statistics every 5 minutes, but only if there's
+ // something worth logging (at least one reference or a transition to
+ // zero references). Avoids useless log spamming in idle webservice.
+ if (tNow >= tNextStat)
+ {
+ size_t cMOR = 0;
+ it = g_mapWebsessions.begin();
+ itEnd = g_mapWebsessions.end();
+ while (it != itEnd)
+ {
+ cMOR += it->second->CountRefs();
+ ++it;
+ }
+ static bool fLastZero = false;
+ if (cMOR || !fLastZero)
+ LogRel(("Statistics: %zu websessions, %zu references\n",
+ g_mapWebsessions.size(), cMOR));
+ fLastZero = (cMOR == 0);
+ while (tNextStat <= tNow)
+ tNextStat += 5 * 60; /* 5 minutes */
+ }
+ }
+
+ thrLock.acquire();
+ g_mapThreads.erase(RTThreadSelf());
+
+ LogRel(("ending Watchdog thread\n"));
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * SOAP exceptions
+ *
+ ****************************************************************************/
+
+/**
+ * Helper function to raise a SOAP fault. Called by the other helper
+ * functions, which raise specific SOAP faults.
+ *
+ * @param soap
+ * @param pcsz
+ * @param extype
+ * @param ex
+ */
+static void RaiseSoapFault(struct soap *soap,
+ const char *pcsz,
+ int extype,
+ void *ex)
+{
+ // raise the fault
+ soap_sender_fault(soap, pcsz, NULL);
+
+ struct SOAP_ENV__Detail *pDetail = (struct SOAP_ENV__Detail*)soap_malloc(soap, sizeof(struct SOAP_ENV__Detail));
+
+ // without the following, gSOAP crashes miserably when sending out the
+ // data because it will try to serialize all fields (stupid documentation)
+ memset(pDetail, 0, sizeof(struct SOAP_ENV__Detail));
+
+ // fill extended info depending on SOAP version
+ if (soap->version == 2) // SOAP 1.2 is used
+ {
+ soap->fault->SOAP_ENV__Detail = pDetail;
+ soap->fault->SOAP_ENV__Detail->__type = extype;
+ soap->fault->SOAP_ENV__Detail->fault = ex;
+ soap->fault->SOAP_ENV__Detail->__any = NULL; // no other XML data
+ }
+ else
+ {
+ soap->fault->detail = pDetail;
+ soap->fault->detail->__type = extype;
+ soap->fault->detail->fault = ex;
+ soap->fault->detail->__any = NULL; // no other XML data
+ }
+}
+
+/**
+ * Raises a SOAP fault that signals that an invalid object was passed.
+ *
+ * @param soap
+ * @param obj
+ */
+void RaiseSoapInvalidObjectFault(struct soap *soap,
+ WSDLT_ID obj)
+{
+ _vbox__InvalidObjectFault *ex = soap_new__vbox__InvalidObjectFault(soap, 1);
+ ex->badObjectID = obj;
+
+ std::string str("VirtualBox error: ");
+ str += "Invalid managed object reference \"" + obj + "\"";
+
+ RaiseSoapFault(soap,
+ str.c_str(),
+ SOAP_TYPE__vbox__InvalidObjectFault,
+ ex);
+}
+
+/**
+ * Return a safe C++ string from the given COM string,
+ * without crashing if the COM string is empty.
+ * @param bstr
+ * @return
+ */
+std::string ConvertComString(const com::Bstr &bstr)
+{
+ com::Utf8Str ustr(bstr);
+ return ustr.c_str(); /// @todo r=dj since the length is known, we can probably use a better std::string allocator
+}
+
+/**
+ * Return a safe C++ string from the given COM UUID,
+ * without crashing if the UUID is empty.
+ * @param uuid
+ * @return
+ */
+std::string ConvertComString(const com::Guid &uuid)
+{
+ com::Utf8Str ustr(uuid.toString());
+ return ustr.c_str(); /// @todo r=dj since the length is known, we can probably use a better std::string allocator
+}
+
+/** Code to handle string <-> byte arrays base64 conversion. */
+std::string Base64EncodeByteArray(ComSafeArrayIn(BYTE, aData))
+{
+
+ com::SafeArray<BYTE> sfaData(ComSafeArrayInArg(aData));
+ ssize_t cbData = sfaData.size();
+
+ if (cbData == 0)
+ return "";
+
+ ssize_t cchOut = RTBase64EncodedLength(cbData);
+
+ RTCString aStr;
+
+ aStr.reserve(cchOut+1);
+ int vrc = RTBase64Encode(sfaData.raw(), cbData, aStr.mutableRaw(), aStr.capacity(), NULL);
+ AssertRC(vrc);
+ aStr.jolt();
+
+ return aStr.c_str();
+}
+
+#define DECODE_STR_MAX _1M
+void Base64DecodeByteArray(struct soap *soap, const std::string& aStr, ComSafeArrayOut(BYTE, aData), const WSDLT_ID &idThis, const char *pszMethodName, IUnknown *pObj, const com::Guid &iid)
+{
+ const char* pszStr = aStr.c_str();
+ ssize_t cbOut = RTBase64DecodedSize(pszStr, NULL);
+
+ if (cbOut > DECODE_STR_MAX)
+ {
+ LogRel(("Decode string too long.\n"));
+ RaiseSoapRuntimeFault(soap, idThis, pszMethodName, E_INVALIDARG, pObj, iid);
+ }
+
+ com::SafeArray<BYTE> result(cbOut);
+ int vrc = RTBase64Decode(pszStr, result.raw(), cbOut, NULL, NULL);
+ if (FAILED(vrc))
+ {
+ LogRel(("String Decoding Failed. Error code: %Rrc\n", vrc));
+ RaiseSoapRuntimeFault(soap, idThis, pszMethodName, E_INVALIDARG, pObj, iid);
+ }
+
+ result.detachTo(ComSafeArrayOutArg(aData));
+}
+
+/**
+ * Raises a SOAP runtime fault.
+ *
+ * @param soap
+ * @param idThis
+ * @param pcszMethodName
+ * @param apirc
+ * @param pObj
+ * @param iid
+ */
+void RaiseSoapRuntimeFault(struct soap *soap,
+ const WSDLT_ID &idThis,
+ const char *pcszMethodName,
+ HRESULT apirc,
+ IUnknown *pObj,
+ const com::Guid &iid)
+{
+ com::ErrorInfo info(pObj, iid.ref());
+
+ WEBDEBUG((" error, raising SOAP exception\n"));
+
+ LogRel(("API method name: %s\n", pcszMethodName));
+ LogRel(("API return code: %#10lx (%Rhrc)\n", apirc, apirc));
+ if (info.isFullAvailable() || info.isBasicAvailable())
+ {
+ const com::ErrorInfo *pInfo = &info;
+ do
+ {
+ LogRel(("COM error info result code: %#10lx (%Rhrc)\n", pInfo->getResultCode(), pInfo->getResultCode()));
+ LogRel(("COM error info text: %ls\n", pInfo->getText().raw()));
+
+ pInfo = pInfo->getNext();
+ }
+ while (pInfo);
+ }
+
+ // compose descriptive message
+ com::Utf8StrFmt str("VirtualBox error: apirc=%#lx", apirc);
+ if (info.isFullAvailable() || info.isBasicAvailable())
+ {
+ const com::ErrorInfo *pInfo = &info;
+ do
+ {
+ str += com::Utf8StrFmt(" %ls (%#lx)", pInfo->getText().raw(), pInfo->getResultCode());
+ pInfo = pInfo->getNext();
+ }
+ while (pInfo);
+ }
+
+ // allocate our own soap fault struct
+ _vbox__RuntimeFault *ex = soap_new__vbox__RuntimeFault(soap, 1);
+ ComPtr<IVirtualBoxErrorInfo> pVirtualBoxErrorInfo;
+ info.getVirtualBoxErrorInfo(pVirtualBoxErrorInfo);
+ ex->resultCode = apirc;
+ ex->returnval = createOrFindRefFromComPtr(idThis, g_pcszIVirtualBoxErrorInfo, pVirtualBoxErrorInfo);
+
+ RaiseSoapFault(soap,
+ str.c_str(),
+ SOAP_TYPE__vbox__RuntimeFault,
+ ex);
+}
+
+/****************************************************************************
+ *
+ * splitting and merging of object IDs
+ *
+ ****************************************************************************/
+
+/**
+ * Splits a managed object reference (in string form, as passed in from a SOAP
+ * method call) into two integers for websession and object IDs, respectively.
+ *
+ * @param id
+ * @param pWebsessId
+ * @param pObjId
+ * @return
+ */
+static bool SplitManagedObjectRef(const WSDLT_ID &id,
+ uint64_t *pWebsessId,
+ uint64_t *pObjId)
+{
+ // 64-bit numbers in hex have 16 digits; hence
+ // the object-ref string must have 16 + "-" + 16 characters
+ if ( id.length() == 33
+ && id[16] == '-'
+ )
+ {
+ char psz[34];
+ memcpy(psz, id.c_str(), 34);
+ psz[16] = '\0';
+ if (pWebsessId)
+ RTStrToUInt64Full(psz, 16, pWebsessId);
+ if (pObjId)
+ RTStrToUInt64Full(psz + 17, 16, pObjId);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Creates a managed object reference (in string form) from
+ * two integers representing a websession and object ID, respectively.
+ *
+ * @param sz Buffer with at least 34 bytes space to receive MOR string.
+ * @param websessId
+ * @param objId
+ */
+static void MakeManagedObjectRef(char *sz,
+ uint64_t websessId,
+ uint64_t objId)
+{
+ RTStrFormatNumber(sz, websessId, 16, 16, 0, RTSTR_F_64BIT | RTSTR_F_ZEROPAD);
+ sz[16] = '-';
+ RTStrFormatNumber(sz + 17, objId, 16, 16, 0, RTSTR_F_64BIT | RTSTR_F_ZEROPAD);
+}
+
+/****************************************************************************
+ *
+ * class WebServiceSession
+ *
+ ****************************************************************************/
+
+class WebServiceSessionPrivate
+{
+ public:
+ ManagedObjectsMapById _mapManagedObjectsById;
+ ManagedObjectsMapByPtr _mapManagedObjectsByPtr;
+};
+
+/**
+ * Constructor for the websession object.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ */
+WebServiceSession::WebServiceSession()
+ : _uNextObjectID(1), // avoid 0 for no real reason
+ _fDestructing(false),
+ _tLastObjectLookup(0)
+{
+ _pp = new WebServiceSessionPrivate;
+ _uWebsessionID = RTRandU64();
+
+ // register this websession globally
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+ g_mapWebsessions[_uWebsessionID] = this;
+}
+
+/**
+ * Destructor. Cleans up and destroys all contained managed object references on the way.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ */
+WebServiceSession::~WebServiceSession()
+{
+ // delete us from global map first so we can't be found
+ // any more while we're cleaning up
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+ g_mapWebsessions.erase(_uWebsessionID);
+
+ // notify ManagedObjectRef destructor so it won't
+ // remove itself from the maps; this avoids rebalancing
+ // the map's tree on every delete as well
+ _fDestructing = true;
+
+ ManagedObjectsIteratorById it,
+ end = _pp->_mapManagedObjectsById.end();
+ for (it = _pp->_mapManagedObjectsById.begin();
+ it != end;
+ ++it)
+ {
+ ManagedObjectRef *pRef = it->second;
+ delete pRef; // this frees the contained ComPtr as well
+ }
+
+ delete _pp;
+}
+
+/**
+ * Authenticate the username and password against an authentication authority.
+ *
+ * @return 0 if the user was successfully authenticated, or an error code
+ * otherwise.
+ */
+int WebServiceSession::authenticate(const char *pcszUsername,
+ const char *pcszPassword,
+ IVirtualBox **ppVirtualBox)
+{
+ int vrc = VERR_WEB_NOT_AUTHENTICATED;
+ ComPtr<IVirtualBox> pVirtualBox;
+ {
+ util::AutoReadLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
+ pVirtualBox = g_pVirtualBox;
+ }
+ if (pVirtualBox.isNull())
+ return vrc;
+ pVirtualBox.queryInterfaceTo(ppVirtualBox);
+
+ util::AutoReadLock lock(g_pAuthLibLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ static bool fAuthLibLoaded = false;
+ static PAUTHENTRY pfnAuthEntry = NULL;
+ static PAUTHENTRY2 pfnAuthEntry2 = NULL;
+ static PAUTHENTRY3 pfnAuthEntry3 = NULL;
+
+ if (!fAuthLibLoaded)
+ {
+ // retrieve authentication library from system properties
+ ComPtr<ISystemProperties> systemProperties;
+ pVirtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
+
+ com::Bstr authLibrary;
+ systemProperties->COMGETTER(WebServiceAuthLibrary)(authLibrary.asOutParam());
+ com::Utf8Str filename = authLibrary;
+
+ LogRel(("External authentication library is '%ls'\n", authLibrary.raw()));
+
+ if (filename == "null")
+ // authentication disabled, let everyone in:
+ fAuthLibLoaded = true;
+ else
+ {
+ RTLDRMOD hlibAuth = 0;
+ do
+ {
+ if (RTPathHavePath(filename.c_str()))
+ vrc = RTLdrLoad(filename.c_str(), &hlibAuth);
+ else
+ vrc = RTLdrLoadAppPriv(filename.c_str(), &hlibAuth);
+ if (RT_FAILURE(vrc))
+ {
+ WEBDEBUG(("%s() Failed to load external authentication library '%s'. Error code: %Rrc\n",
+ __FUNCTION__, filename.c_str(), vrc));
+ break;
+ }
+
+ if (RT_FAILURE(vrc = RTLdrGetSymbol(hlibAuth, AUTHENTRY3_NAME, (void**)&pfnAuthEntry3)))
+ {
+ WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n",
+ __FUNCTION__, AUTHENTRY3_NAME, vrc));
+
+ if (RT_FAILURE(vrc = RTLdrGetSymbol(hlibAuth, AUTHENTRY2_NAME, (void**)&pfnAuthEntry2)))
+ {
+ WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n",
+ __FUNCTION__, AUTHENTRY2_NAME, vrc));
+
+ if (RT_FAILURE(vrc = RTLdrGetSymbol(hlibAuth, AUTHENTRY_NAME, (void**)&pfnAuthEntry)))
+ WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n",
+ __FUNCTION__, AUTHENTRY_NAME, vrc));
+ }
+ }
+
+ if (pfnAuthEntry || pfnAuthEntry2 || pfnAuthEntry3)
+ fAuthLibLoaded = true;
+
+ } while (0);
+ }
+ }
+
+ if (strlen(pcszUsername) >= _1K)
+ {
+ LogRel(("Access denied, excessive username length: %zu\n", strlen(pcszUsername)));
+ vrc = VERR_WEB_NOT_AUTHENTICATED;
+ }
+ else if (strlen(pcszPassword) >= _1K)
+ {
+ LogRel(("Access denied, excessive password length: %zu\n", strlen(pcszPassword)));
+ vrc = VERR_WEB_NOT_AUTHENTICATED;
+ }
+ else if (pfnAuthEntry3 || pfnAuthEntry2 || pfnAuthEntry)
+ {
+ const char *pszFn;
+ AuthResult result;
+ if (pfnAuthEntry3)
+ {
+ result = pfnAuthEntry3("webservice", NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL, true, 0);
+ pszFn = AUTHENTRY3_NAME;
+ }
+ else if (pfnAuthEntry2)
+ {
+ result = pfnAuthEntry2(NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL, true, 0);
+ pszFn = AUTHENTRY2_NAME;
+ }
+ else
+ {
+ result = pfnAuthEntry(NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL);
+ pszFn = AUTHENTRY_NAME;
+ }
+ WEBDEBUG(("%s(): result of %s('%s', [%d]): %d (%s)\n",
+ __FUNCTION__, pszFn, pcszUsername, strlen(pcszPassword), result, decodeAuthResult(result)));
+ if (result == AuthResultAccessGranted)
+ {
+ LogRel(("Access for user '%s' granted\n", pcszUsername));
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ if (result == AuthResultAccessDenied)
+ LogRel(("Access for user '%s' denied\n", pcszUsername));
+ vrc = VERR_WEB_NOT_AUTHENTICATED;
+ }
+ }
+ else if (fAuthLibLoaded)
+ {
+ // fAuthLibLoaded = true but all pointers are NULL:
+ // The authlib was "null" and auth was disabled
+ vrc = VINF_SUCCESS;
+ }
+ else
+ {
+ WEBDEBUG(("Could not resolve AuthEntry, VRDPAuth2 or VRDPAuth entry point"));
+ vrc = VERR_WEB_NOT_AUTHENTICATED;
+ }
+
+ lock.release();
+
+ return vrc;
+}
+
+/**
+ * Look up, in this websession, whether a ManagedObjectRef has already been
+ * created for the given COM pointer.
+ *
+ * Note how we require that a ComPtr<IUnknown> is passed, which causes a
+ * queryInterface call when the caller passes in a different type, since
+ * a ComPtr<IUnknown> will point to something different than a
+ * ComPtr<IVirtualBox>, for example. As we store the ComPtr<IUnknown> in
+ * our private hash table, we must search for one too.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ *
+ * @param pObject pointer to a COM object.
+ * @return The existing ManagedObjectRef that represents the COM object, or NULL if there's none yet.
+ */
+ManagedObjectRef* WebServiceSession::findRefFromPtr(const IUnknown *pObject)
+{
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+
+ uintptr_t ulp = (uintptr_t)pObject;
+ // WEBDEBUG((" %s: looking up %#lx\n", __FUNCTION__, ulp));
+ ManagedObjectsIteratorByPtr it = _pp->_mapManagedObjectsByPtr.find(ulp);
+ if (it != _pp->_mapManagedObjectsByPtr.end())
+ {
+ ManagedObjectRef *pRef = it->second;
+ WEBDEBUG((" %s: found existing ref %s (%s) for COM obj %#lx\n", __FUNCTION__, pRef->getWSDLID().c_str(), pRef->getInterfaceName(), ulp));
+ return pRef;
+ }
+
+ return NULL;
+}
+
+/**
+ * Static method which attempts to find the websession for which the given
+ * managed object reference was created, by splitting the reference into the
+ * websession and object IDs and then looking up the websession object.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle in read mode.
+ *
+ * @param id Managed object reference (with combined websession and object IDs).
+ * @return
+ */
+WebServiceSession *WebServiceSession::findWebsessionFromRef(const WSDLT_ID &id)
+{
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+
+ WebServiceSession *pWebsession = NULL;
+ uint64_t websessId;
+ if (SplitManagedObjectRef(id,
+ &websessId,
+ NULL))
+ {
+ WebsessionsMapIterator it = g_mapWebsessions.find(websessId);
+ if (it != g_mapWebsessions.end())
+ pWebsession = it->second;
+ }
+ return pWebsession;
+}
+
+/**
+ * Touches the websession to prevent it from timing out.
+ *
+ * Each websession has an internal timestamp that records the last request made
+ * to it from the client that started it. If no request was made within a
+ * configurable timeframe, then the client is logged off automatically,
+ * by calling IWebsessionManager::logoff()
+ */
+void WebServiceSession::touch()
+{
+ _tLastObjectLookup = RTTimeProgramSecTS();
+}
+
+/**
+ * Counts the number of managed object references in this websession.
+ */
+size_t WebServiceSession::CountRefs()
+{
+ return _pp->_mapManagedObjectsById.size();
+}
+
+
+/****************************************************************************
+ *
+ * class ManagedObjectRef
+ *
+ ****************************************************************************/
+
+/**
+ * Constructor, which assigns a unique ID to this managed object
+ * reference and stores it in two hashes (living in the associated
+ * WebServiceSession object):
+ *
+ * a) _mapManagedObjectsById, which maps ManagedObjectID's to
+ * instances of this class; this hash is then used by the
+ * findObjectFromRef() template function in vboxweb.h
+ * to quickly retrieve the COM object from its managed
+ * object ID (mostly in the context of the method mappers
+ * in methodmaps.cpp, when a web service client passes in
+ * a managed object ID);
+ *
+ * b) _mapManagedObjectsByPtr, which maps COM pointers to
+ * instances of this class; this hash is used by
+ * createRefFromObject() to quickly figure out whether an
+ * instance already exists for a given COM pointer.
+ *
+ * This constructor calls AddRef() on the given COM object, and
+ * the destructor will call Release(). We require two input pointers
+ * for that COM object, one generic IUnknown* pointer which is used
+ * as the map key, and a specific interface pointer (e.g. IMachine*)
+ * which must support the interface given in guidInterface. All
+ * three values are returned by getPtr(), which gives future callers
+ * a chance to reuse the specific interface pointer without having
+ * to call QueryInterface, which can be expensive.
+ *
+ * This does _not_ check whether another instance already
+ * exists in the hash. This gets called only from the
+ * createOrFindRefFromComPtr() template function in vboxweb.h, which
+ * does perform that check.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ *
+ * @param websession Websession to which the MOR will be added.
+ * @param pobjUnknown Pointer to IUnknown* interface for the COM object; this will be used in the hashes.
+ * @param pobjInterface Pointer to a specific interface for the COM object, described by guidInterface.
+ * @param guidInterface Interface which pobjInterface points to.
+ * @param pcszInterface String representation of that interface (e.g. "IMachine") for readability and logging.
+ */
+ManagedObjectRef::ManagedObjectRef(WebServiceSession &websession,
+ IUnknown *pobjUnknown,
+ void *pobjInterface,
+ const com::Guid &guidInterface,
+ const char *pcszInterface)
+ : _websession(websession),
+ _pobjUnknown(pobjUnknown),
+ _pobjInterface(pobjInterface),
+ _guidInterface(guidInterface),
+ _pcszInterface(pcszInterface)
+{
+ Assert(pobjUnknown);
+ Assert(pobjInterface);
+
+ // keep both stubs alive while this MOR exists (matching Release() calls are in destructor)
+ uint32_t cRefs1 = pobjUnknown->AddRef();
+ uint32_t cRefs2 = ((IUnknown*)pobjInterface)->AddRef();
+ _ulp = (uintptr_t)pobjUnknown;
+
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+ _id = websession.createObjectID();
+ // and count globally
+ ULONG64 cTotal = ++g_cManagedObjects; // raise global count and make a copy for the debug message below
+
+ char sz[34];
+ MakeManagedObjectRef(sz, websession._uWebsessionID, _id);
+ _strID = sz;
+
+ websession._pp->_mapManagedObjectsById[_id] = this;
+ websession._pp->_mapManagedObjectsByPtr[_ulp] = this;
+
+ websession.touch();
+
+ WEBDEBUG((" * %s: MOR created for %s*=%#p (IUnknown*=%#p; COM refcount now %RI32/%RI32), new ID is %#llx; now %lld objects total\n",
+ __FUNCTION__,
+ pcszInterface,
+ pobjInterface,
+ pobjUnknown,
+ cRefs1,
+ cRefs2,
+ _id,
+ cTotal));
+}
+
+/**
+ * Destructor; removes the instance from the global hash of
+ * managed objects. Calls Release() on the contained COM object.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ */
+ManagedObjectRef::~ManagedObjectRef()
+{
+ Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
+ ULONG64 cTotal = --g_cManagedObjects;
+
+ Assert(_pobjUnknown);
+ Assert(_pobjInterface);
+
+ // we called AddRef() on both interfaces, so call Release() on
+ // both as well, but in reverse order
+ uint32_t cRefs2 = ((IUnknown*)_pobjInterface)->Release();
+ uint32_t cRefs1 = _pobjUnknown->Release();
+ WEBDEBUG((" * %s: deleting MOR for ID %#llx (%s; COM refcount now %RI32/%RI32); now %lld objects total\n", __FUNCTION__, _id, _pcszInterface, cRefs1, cRefs2, cTotal));
+
+ // if we're being destroyed from the websession's destructor,
+ // then that destructor is iterating over the maps, so
+ // don't remove us there! (data integrity + speed)
+ if (!_websession._fDestructing)
+ {
+ WEBDEBUG((" * %s: removing from websession maps\n", __FUNCTION__));
+ _websession._pp->_mapManagedObjectsById.erase(_id);
+ if (_websession._pp->_mapManagedObjectsByPtr.erase(_ulp) != 1)
+ WEBDEBUG((" WARNING: could not find %#llx in _mapManagedObjectsByPtr\n", _ulp));
+ }
+}
+
+/**
+ * Static helper method for findObjectFromRef() template that actually
+ * looks up the object from a given integer ID.
+ *
+ * This has been extracted into this non-template function to reduce
+ * code bloat as we have the actual STL map lookup only in this function.
+ *
+ * This also "touches" the timestamp in the websession whose ID is encoded
+ * in the given integer ID, in order to prevent the websession from timing
+ * out.
+ *
+ * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
+ *
+ * @param id
+ * @param pRef
+ * @param fNullAllowed
+ * @return
+ */
+int ManagedObjectRef::findRefFromId(const WSDLT_ID &id,
+ ManagedObjectRef **pRef,
+ bool fNullAllowed)
+{
+ int vrc = VINF_SUCCESS;
+
+ do
+ {
+ // allow NULL (== empty string) input reference, which should return a NULL pointer
+ if (!id.length() && fNullAllowed)
+ {
+ *pRef = NULL;
+ return 0;
+ }
+
+ uint64_t websessId;
+ uint64_t objId;
+ WEBDEBUG((" %s(): looking up objref %s\n", __FUNCTION__, id.c_str()));
+ if (!SplitManagedObjectRef(id,
+ &websessId,
+ &objId))
+ {
+ vrc = VERR_WEB_INVALID_MANAGED_OBJECT_REFERENCE;
+ break;
+ }
+
+ WebsessionsMapIterator it = g_mapWebsessions.find(websessId);
+ if (it == g_mapWebsessions.end())
+ {
+ WEBDEBUG((" %s: cannot find websession for objref %s\n", __FUNCTION__, id.c_str()));
+ vrc = VERR_WEB_INVALID_SESSION_ID;
+ break;
+ }
+
+ WebServiceSession *pWebsession = it->second;
+ // "touch" websession to prevent it from timing out
+ pWebsession->touch();
+
+ ManagedObjectsIteratorById iter = pWebsession->_pp->_mapManagedObjectsById.find(objId);
+ if (iter == pWebsession->_pp->_mapManagedObjectsById.end())
+ {
+ WEBDEBUG((" %s: cannot find comobj for objref %s\n", __FUNCTION__, id.c_str()));
+ vrc = VERR_WEB_INVALID_OBJECT_ID;
+ break;
+ }
+
+ *pRef = iter->second;
+
+ } while (0);
+
+ return vrc;
+}
+
+/****************************************************************************
+ *
+ * interface IManagedObjectRef
+ *
+ ****************************************************************************/
+
+/**
+ * This is the hard-coded implementation for the IManagedObjectRef::getInterfaceName()
+ * that our WSDL promises to our web service clients. This method returns a
+ * string describing the interface that this managed object reference
+ * supports, e.g. "IMachine".
+ *
+ * @param soap
+ * @param req
+ * @param resp
+ * @return
+ */
+int __vbox__IManagedObjectRef_USCOREgetInterfaceName(
+ struct soap *soap,
+ _vbox__IManagedObjectRef_USCOREgetInterfaceName *req,
+ _vbox__IManagedObjectRef_USCOREgetInterfaceNameResponse *resp)
+{
+ RT_NOREF(soap);
+ HRESULT hrc = S_OK; /** @todo r=bird: hrc is not set.... */
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ do
+ {
+ // findRefFromId require the lock
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ ManagedObjectRef *pRef;
+ if (!ManagedObjectRef::findRefFromId(req->_USCOREthis, &pRef, false))
+ resp->returnval = pRef->getInterfaceName();
+
+ } while (0);
+
+ WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
+ if (FAILED(hrc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}
+
+/**
+ * This is the hard-coded implementation for the IManagedObjectRef::release()
+ * that our WSDL promises to our web service clients. This method releases
+ * a managed object reference and removes it from our stacks.
+ *
+ * @param soap
+ * @param req
+ * @param resp
+ * @return
+ */
+int __vbox__IManagedObjectRef_USCORErelease(
+ struct soap *soap,
+ _vbox__IManagedObjectRef_USCORErelease *req,
+ _vbox__IManagedObjectRef_USCOREreleaseResponse *resp)
+{
+ RT_NOREF(resp);
+ HRESULT hrc;
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ {
+ // findRefFromId and the delete call below require the lock
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ ManagedObjectRef *pRef;
+ hrc = ManagedObjectRef::findRefFromId(req->_USCOREthis, &pRef, false);
+ if (hrc == S_OK)
+ {
+ WEBDEBUG((" found reference; deleting!\n"));
+ // this removes the object from all stacks; since
+ // there's a ComPtr<> hidden inside the reference,
+ // this should also invoke Release() on the COM
+ // object
+ delete pRef;
+ }
+ else
+ RaiseSoapInvalidObjectFault(soap, req->_USCOREthis);
+ }
+
+ WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
+ if (FAILED(hrc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}
+
+/****************************************************************************
+ *
+ * interface IWebsessionManager
+ *
+ ****************************************************************************/
+
+/**
+ * Hard-coded implementation for IWebsessionManager::logon. As opposed to the underlying
+ * COM API, this is the first method that a webservice client must call before the
+ * webservice will do anything useful.
+ *
+ * This returns a managed object reference to the global IVirtualBox object; into this
+ * reference a websession ID is encoded which remains constant with all managed object
+ * references returned by other methods.
+ *
+ * When the webservice client is done, it should call IWebsessionManager::logoff. This
+ * will clean up internally (destroy all remaining managed object references and
+ * related COM objects used internally).
+ *
+ * After logon, an internal timeout ensures that if the webservice client does not
+ * call any methods, after a configurable number of seconds, the webservice will log
+ * off the client automatically. This is to ensure that the webservice does not
+ * drown in managed object references and eventually deny service. Still, it is
+ * a much better solution, both for performance and cleanliness, for the webservice
+ * client to clean up itself.
+ *
+ * @param soap
+ * @param req
+ * @param resp
+ * @return
+ */
+int __vbox__IWebsessionManager_USCORElogon(
+ struct soap *soap,
+ _vbox__IWebsessionManager_USCORElogon *req,
+ _vbox__IWebsessionManager_USCORElogonResponse *resp)
+{
+ RT_NOREF(soap);
+ HRESULT hrc = S_OK;
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ do
+ {
+ // WebServiceSession constructor tinkers with global MOR map and requires a write lock
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ // create new websession; the constructor stores the new websession
+ // in the global map automatically
+ WebServiceSession *pWebsession = new WebServiceSession();
+ ComPtr<IVirtualBox> pVirtualBox;
+
+ // authenticate the user
+ if (!(pWebsession->authenticate(req->username.c_str(),
+ req->password.c_str(),
+ pVirtualBox.asOutParam())))
+ {
+ // fake up a "root" MOR for this websession
+ char sz[34];
+ MakeManagedObjectRef(sz, pWebsession->getID(), 0ULL);
+ WSDLT_ID id = sz;
+
+ // in the new websession, create a managed object reference (MOR) for the
+ // global VirtualBox object; this encodes the websession ID in the MOR so
+ // that it will be implicitly be included in all future requests of this
+ // webservice client
+ resp->returnval = createOrFindRefFromComPtr(id, g_pcszIVirtualBox, pVirtualBox);
+ WEBDEBUG(("VirtualBox object ref is %s\n", resp->returnval.c_str()));
+ }
+ else
+ hrc = E_FAIL;
+ } while (0);
+
+ WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
+ if (FAILED(hrc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}
+
+/**
+ * Returns a new ISession object every time.
+ *
+ * No longer connected in any way to logons, one websession can easily
+ * handle multiple sessions.
+ */
+int __vbox__IWebsessionManager_USCOREgetSessionObject(
+ struct soap*,
+ _vbox__IWebsessionManager_USCOREgetSessionObject *req,
+ _vbox__IWebsessionManager_USCOREgetSessionObjectResponse *resp)
+{
+ HRESULT hrc = S_OK;
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ do
+ {
+ // create a new ISession object
+ ComPtr<ISession> pSession;
+ hrc = g_pVirtualBoxClient->COMGETTER(Session)(pSession.asOutParam());
+ if (FAILED(hrc))
+ {
+ WEBDEBUG(("ERROR: cannot create session object!"));
+ break;
+ }
+
+ // return its MOR
+ resp->returnval = createOrFindRefFromComPtr(req->refIVirtualBox, g_pcszISession, pSession);
+ WEBDEBUG(("Session object ref is %s\n", resp->returnval.c_str()));
+ } while (0);
+
+ WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
+ if (FAILED(hrc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}
+
+/**
+ * hard-coded implementation for IWebsessionManager::logoff.
+ *
+ * @param req
+ * @param resp
+ * @return
+ */
+int __vbox__IWebsessionManager_USCORElogoff(
+ struct soap*,
+ _vbox__IWebsessionManager_USCORElogoff *req,
+ _vbox__IWebsessionManager_USCORElogoffResponse *resp)
+{
+ RT_NOREF(resp);
+ HRESULT hrc = S_OK;
+ WEBDEBUG(("-- entering %s\n", __FUNCTION__));
+
+ {
+ // findWebsessionFromRef and the websession destructor require the lock
+ util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
+
+ WebServiceSession *pWebsession = WebServiceSession::findWebsessionFromRef(req->refIVirtualBox);
+ if (pWebsession)
+ {
+ WEBDEBUG(("websession logoff, deleting websession %#llx\n", pWebsession->getID()));
+ delete pWebsession;
+ // destructor cleans up
+
+ WEBDEBUG(("websession destroyed, %d websessions left open\n", g_mapWebsessions.size()));
+ }
+ }
+
+ WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
+ if (FAILED(hrc))
+ return SOAP_FAULT;
+ return SOAP_OK;
+}