VirtualBox

source: vbox/trunk/src/VBox/Main/webservice/vboxweb.cpp

Last change on this file was 104205, checked in by vboxsync, 4 weeks ago

Main/webservice/vboxweb.cpp: Use a different approach of socket queueing which is closer to the one used in the examples. Easier to understand than the old one. Also eliminate the code using select() again, because with current gSOAP the service shutdown time is almost as good. The extra complexity isn't worth it for an improvement under a second.

  • Property filesplitter.c set to Makefile.kmk
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 82.8 KB
Line 
1/* $Id: vboxweb.cpp 104205 2024-04-05 17:38:51Z vboxsync $ */
2/** @file
3 * vboxweb.cpp:
4 * hand-coded parts of the webservice server. This is linked with the
5 * generated code in out/.../src/VBox/Main/webservice/methodmaps.cpp
6 * (plus static gSOAP server code) to implement the actual webservice
7 * server, to which clients can connect.
8 */
9
10/*
11 * Copyright (C) 2007-2023 Oracle and/or its affiliates.
12 *
13 * This file is part of VirtualBox base platform packages, as
14 * available from https://www.virtualbox.org.
15 *
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation, in version 3 of the
19 * License.
20 *
21 * This program is distributed in the hope that it will be useful, but
22 * WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 * General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, see <https://www.gnu.org/licenses>.
28 *
29 * SPDX-License-Identifier: GPL-3.0-only
30 */
31
32// shared webservice header
33#include "vboxweb.h"
34
35// vbox headers
36#include <VBox/com/com.h>
37#include <VBox/com/array.h>
38#include <VBox/com/string.h>
39#include <VBox/com/ErrorInfo.h>
40#include <VBox/com/errorprint.h>
41#include <VBox/com/listeners.h>
42#include <VBox/com/NativeEventQueue.h>
43#include <VBox/VBoxAuth.h>
44#include <VBox/version.h>
45#include <VBox/log.h>
46
47#include <iprt/buildconfig.h>
48#include <iprt/ctype.h>
49#include <iprt/getopt.h>
50#include <iprt/initterm.h>
51#include <iprt/ldr.h>
52#include <iprt/message.h>
53#include <iprt/process.h>
54#include <iprt/rand.h>
55#include <iprt/semaphore.h>
56#include <iprt/critsect.h>
57#include <iprt/string.h>
58#include <iprt/thread.h>
59#include <iprt/time.h>
60#include <iprt/path.h>
61#include <iprt/system.h>
62#include <iprt/base64.h>
63#include <iprt/stream.h>
64#include <iprt/asm.h>
65
66#ifdef WITH_OPENSSL
67# include <openssl/opensslv.h>
68#endif
69
70#ifndef RT_OS_WINDOWS
71# include <signal.h>
72#endif
73
74// workaround for compile problems on gcc 4.1
75#ifdef __GNUC__
76#pragma GCC visibility push(default)
77#endif
78
79// gSOAP headers (must come after vbox includes because it checks for conflicting defs)
80#include "soapH.h"
81
82// standard headers
83#include <map>
84#include <list>
85
86#ifdef __GNUC__
87#pragma GCC visibility pop
88#endif
89
90// include generated namespaces table
91#include "vboxwebsrv.nsmap"
92
93RT_C_DECLS_BEGIN
94
95// declarations for the generated WSDL text
96extern const unsigned char g_abVBoxWebWSDL[];
97extern const unsigned g_cbVBoxWebWSDL;
98
99RT_C_DECLS_END
100
101static void WebLogSoapError(struct soap *soap);
102
103/****************************************************************************
104 *
105 * private typedefs
106 *
107 ****************************************************************************/
108
109typedef std::map<uint64_t, ManagedObjectRef*> ManagedObjectsMapById;
110typedef ManagedObjectsMapById::iterator ManagedObjectsIteratorById;
111typedef std::map<uintptr_t, ManagedObjectRef*> ManagedObjectsMapByPtr;
112typedef ManagedObjectsMapByPtr::iterator ManagedObjectsIteratorByPtr;
113
114typedef std::map<uint64_t, WebServiceSession*> WebsessionsMap;
115typedef WebsessionsMap::iterator WebsessionsMapIterator;
116
117typedef std::map<RTTHREAD, com::Utf8Str> ThreadsMap;
118
119static DECLCALLBACK(int) fntWatchdog(RTTHREAD ThreadSelf, void *pvUser);
120
121/****************************************************************************
122 *
123 * Read-only global variables
124 *
125 ****************************************************************************/
126
127static ComPtr<IVirtualBoxClient> g_pVirtualBoxClient = NULL;
128
129// generated strings in methodmaps.cpp
130extern const char *g_pcszISession,
131 *g_pcszIVirtualBox,
132 *g_pcszIVirtualBoxErrorInfo;
133
134// globals for vboxweb command-line arguments
135#define DEFAULT_TIMEOUT_SECS 300
136#define DEFAULT_TIMEOUT_SECS_STRING "300"
137static int g_iWatchdogTimeoutSecs = DEFAULT_TIMEOUT_SECS;
138static int g_iWatchdogCheckInterval = 5;
139
140static const char *g_pcszBindToHost = NULL; // host; NULL = localhost
141static unsigned int g_uBindToPort = 18083; // port
142static unsigned int g_uBacklog = 100; // backlog = max queue size for connections
143
144#ifdef WITH_OPENSSL
145static bool g_fSSL = false; // if SSL is enabled
146static const char *g_pcszKeyFile = NULL; // server key file
147static const char *g_pcszPassword = NULL; // password for server key
148static const char *g_pcszCACert = NULL; // file with trusted CA certificates
149static const char *g_pcszCAPath = NULL; // directory with trusted CA certificates
150static const char *g_pcszDHFile = NULL; // DH file name or DH key length in bits, NULL=use RSA
151static const char *g_pcszRandFile = NULL; // file with random data seed
152static const char *g_pcszSID = "vboxwebsrv"; // server ID for SSL session cache
153#endif /* WITH_OPENSSL */
154
155static unsigned int g_cMaxWorkerThreads = 100; // max. no. of worker threads
156static unsigned int g_cMaxKeepAlive = 100; // maximum number of soap requests in one connection
157
158static const char *g_pcszAuthentication = NULL; // web service authentication
159
160static uint32_t g_cHistory = 10; // enable log rotation, 10 files
161static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; // max 1 day per file
162static uint64_t g_uHistoryFileSize = 100 * _1M; // max 100MB per file
163bool g_fVerbose = false; // be verbose
164
165static bool g_fDaemonize = false; // run in background.
166static volatile bool g_fKeepRunning = true; // controlling the exit
167
168const WSDLT_ID g_EmptyWSDLID; // for NULL MORs
169
170/****************************************************************************
171 *
172 * Writeable global variables
173 *
174 ****************************************************************************/
175
176// The one global SOAP queue created by main().
177class SoapQ;
178static SoapQ *g_pSoapQ = NULL;
179
180// this mutex protects the auth lib and authentication
181static util::WriteLockHandle *g_pAuthLibLockHandle;
182
183// this mutex protects the global VirtualBox reference below
184static util::RWLockHandle *g_pVirtualBoxLockHandle;
185
186static ComPtr<IVirtualBox> g_pVirtualBox = NULL;
187
188// this mutex protects all of the below
189util::WriteLockHandle *g_pWebsessionsLockHandle;
190
191static WebsessionsMap g_mapWebsessions;
192static ULONG64 g_cManagedObjects = 0;
193
194// this mutex protects g_mapThreads
195static util::RWLockHandle *g_pThreadsLockHandle;
196
197// Threads map, so we can quickly map an RTTHREAD struct to a logger prefix
198static ThreadsMap g_mapThreads;
199
200/****************************************************************************
201 *
202 * Command line help
203 *
204 ****************************************************************************/
205
206static const RTGETOPTDEF g_aOptions[]
207 = {
208 { "--help", 'h', RTGETOPT_REQ_NOTHING }, /* for DisplayHelp() */
209#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
210 { "--background", 'b', RTGETOPT_REQ_NOTHING },
211#endif
212 { "--host", 'H', RTGETOPT_REQ_STRING },
213 { "--port", 'p', RTGETOPT_REQ_UINT32 },
214#ifdef WITH_OPENSSL
215 { "--ssl", 's', RTGETOPT_REQ_NOTHING },
216 { "--keyfile", 'K', RTGETOPT_REQ_STRING },
217 { "--passwordfile", 'a', RTGETOPT_REQ_STRING },
218 { "--cacert", 'c', RTGETOPT_REQ_STRING },
219 { "--capath", 'C', RTGETOPT_REQ_STRING },
220 { "--dhfile", 'D', RTGETOPT_REQ_STRING },
221 { "--randfile", 'r', RTGETOPT_REQ_STRING },
222#endif /* WITH_OPENSSL */
223 { "--timeout", 't', RTGETOPT_REQ_UINT32 },
224 { "--check-interval", 'i', RTGETOPT_REQ_UINT32 },
225 { "--threads", 'T', RTGETOPT_REQ_UINT32 },
226 { "--keepalive", 'k', RTGETOPT_REQ_UINT32 },
227 { "--authentication", 'A', RTGETOPT_REQ_STRING },
228 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
229 { "--pidfile", 'P', RTGETOPT_REQ_STRING },
230 { "--logfile", 'F', RTGETOPT_REQ_STRING },
231 { "--logrotate", 'R', RTGETOPT_REQ_UINT32 },
232 { "--logsize", 'S', RTGETOPT_REQ_UINT64 },
233 { "--loginterval", 'I', RTGETOPT_REQ_UINT32 }
234 };
235
236static void DisplayHelp()
237{
238 RTStrmPrintf(g_pStdErr, "\nUsage: vboxwebsrv [options]\n\nSupported options (default values in brackets):\n");
239 for (unsigned i = 0;
240 i < RT_ELEMENTS(g_aOptions);
241 ++i)
242 {
243 std::string str(g_aOptions[i].pszLong);
244 str += ", -";
245 str += g_aOptions[i].iShort;
246 str += ":";
247
248 const char *pcszDescr = "";
249
250 switch (g_aOptions[i].iShort)
251 {
252 case 'h':
253 pcszDescr = "Print this help message and exit.";
254 break;
255
256#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
257 case 'b':
258 pcszDescr = "Run in background (daemon mode).";
259 break;
260#endif
261
262 case 'H':
263 pcszDescr = "The host to bind to (localhost).";
264 break;
265
266 case 'p':
267 pcszDescr = "The port to bind to (18083).";
268 break;
269
270#ifdef WITH_OPENSSL
271 case 's':
272 pcszDescr = "Enable SSL/TLS encryption.";
273 break;
274
275 case 'K':
276 pcszDescr = "Server key and certificate file, PEM format (\"\").";
277 break;
278
279 case 'a':
280 pcszDescr = "File name for password to server key (\"\").";
281 break;
282
283 case 'c':
284 pcszDescr = "CA certificate file, PEM format (\"\").";
285 break;
286
287 case 'C':
288 pcszDescr = "CA certificate path (\"\").";
289 break;
290
291 case 'D':
292 pcszDescr = "DH file name or DH key length in bits (\"\").";
293 break;
294
295 case 'r':
296 pcszDescr = "File containing seed for random number generator (\"\").";
297 break;
298#endif /* WITH_OPENSSL */
299
300 case 't':
301 pcszDescr = "Session timeout in seconds; 0 = disable timeouts (" DEFAULT_TIMEOUT_SECS_STRING ").";
302 break;
303
304 case 'T':
305 pcszDescr = "Maximum number of worker threads to run in parallel (100).";
306 break;
307
308 case 'k':
309 pcszDescr = "Maximum number of requests before a connection will be closed (100).";
310 break;
311
312 case 'A':
313 pcszDescr = "Authentication method for the webservice (\"\").";
314 break;
315
316 case 'i':
317 pcszDescr = "Frequency of timeout checks in seconds (5).";
318 break;
319
320 case 'v':
321 pcszDescr = "Be verbose.";
322 break;
323
324 case 'P':
325 pcszDescr = "Name of the PID file which is created when the daemon was started.";
326 break;
327
328 case 'F':
329 pcszDescr = "Name of file to write log to (no file).";
330 break;
331
332 case 'R':
333 pcszDescr = "Number of log files (0 disables log rotation).";
334 break;
335
336 case 'S':
337 pcszDescr = "Maximum size of a log file to trigger rotation (bytes).";
338 break;
339
340 case 'I':
341 pcszDescr = "Maximum time interval to trigger log rotation (seconds).";
342 break;
343 }
344
345 RTStrmPrintf(g_pStdErr, "%-23s%s\n", str.c_str(), pcszDescr);
346 }
347}
348
349/****************************************************************************
350 *
351 * SoapQ, SoapThread (multithreading)
352 *
353 ****************************************************************************/
354
355class SoapQ;
356
357class SoapThread
358{
359public:
360 /**
361 * Constructor. Creates the new thread and makes it call process() for processing the queue.
362 * @param u Thread number. (So we can count from 1 and be readable.)
363 * @param q SoapQ instance which has the queue to process.
364 */
365 SoapThread(size_t u,
366 SoapQ &q)
367 : m_u(u),
368 m_pQ(&q)
369 {
370 m_strThread = com::Utf8StrFmt("SQW%02d", m_u);
371 int vrc = RTThreadCreate(&m_pThread,
372 fntWrapper,
373 this, // pvUser
374 0, // cbStack
375 RTTHREADTYPE_MAIN_HEAVY_WORKER,
376 0,
377 m_strThread.c_str());
378 if (RT_FAILURE(vrc))
379 {
380 RTMsgError("Cannot start worker thread %d: %Rrc\n", u, vrc);
381 exit(1);
382 }
383 }
384
385 void process();
386
387 static int fnHttpGet(struct soap *soap)
388 {
389 char *s = strchr(soap->path, '?');
390 if (!s || strcmp(s, "?wsdl"))
391 return SOAP_GET_METHOD;
392 soap_response(soap, SOAP_HTML);
393 soap_send_raw(soap, (const char *)g_abVBoxWebWSDL, g_cbVBoxWebWSDL);
394 soap_end_send(soap);
395 return SOAP_OK;
396 }
397
398 /**
399 * Static function that can be passed to RTThreadCreate and that calls
400 * process() on the SoapThread instance passed as the thread parameter.
401 *
402 * @param hThreadSelf
403 * @param pvThread
404 * @return
405 */
406 static DECLCALLBACK(int) fntWrapper(RTTHREAD hThreadSelf, void *pvThread)
407 {
408 RT_NOREF(hThreadSelf);
409 SoapThread *pst = (SoapThread*)pvThread;
410 pst->process();
411 return VINF_SUCCESS;
412 }
413
414 size_t m_u; // thread number
415 com::Utf8Str m_strThread; // thread name ("SoapQWrkXX")
416 SoapQ *m_pQ; // the single SOAP queue that all the threads service
417 RTTHREAD m_pThread; // IPRT thread struct for this thread
418};
419
420/**
421 * SOAP queue encapsulation. There is only one instance of this, to
422 * which add() adds a queue item (called on the main thread),
423 * and from which get() fetch items, called from each queue thread.
424 */
425class SoapQ
426{
427public:
428
429 /**
430 * Constructor. Creates the soap queue.
431 */
432 SoapQ()
433 : m_mutex(util::LOCKCLASS_OBJECTSTATE), // lowest lock order, no other may be held while this is held
434 m_cIdleThreads(0)
435 {
436 RTSemEventMultiCreate(&m_event);
437 }
438
439 ~SoapQ()
440 {
441 /* Tell the threads to terminate. */
442 RTSemEventMultiSignal(m_event);
443 {
444 util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
445 int i = 0;
446 while (m_llAllThreads.size() && i++ <= 30)
447 {
448 qlock.release();
449 RTThreadSleep(1000);
450 RTSemEventMultiSignal(m_event);
451 qlock.acquire();
452 }
453 LogRel(("ending queue processing (%d out of %d threads idle)\n", m_cIdleThreads, m_llAllThreads.size()));
454 }
455
456 RTSemEventMultiDestroy(m_event);
457
458 while (!m_llSocketsQ.empty())
459 {
460 struct soap *pSoap = m_llSocketsQ.front();
461 m_llSocketsQ.pop_front();
462
463 if (pSoap == NULL || !soap_valid_socket(pSoap->socket))
464 continue;
465
466 soap_destroy(pSoap); // clean up class instances
467 soap_end(pSoap); // clean up everything and close socket
468 soap_free(pSoap); // free soap connection
469 }
470 }
471
472 /**
473 * Adds the given soap connection to the SOAP queue and posts the
474 * member event sem to wake up the workers. Called on the main thread
475 * whenever a connection comes in. Creates a new SOAP thread on the
476 * first call or when all existing threads are busy.
477 * @param pSoap connection
478 */
479 size_t add(const struct soap *pSoap)
480 {
481 size_t cItems;
482 util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
483
484 // if no threads have yet been created, or if all threads are busy,
485 // create a new SOAP thread
486 if ( !m_cIdleThreads
487 // but only if we're not exceeding the global maximum (default is 100)
488 && (m_llAllThreads.size() < g_cMaxWorkerThreads)
489 )
490 {
491 SoapThread *pst = new SoapThread(m_llAllThreads.size() + 1,
492 *this);
493 m_llAllThreads.push_back(pst);
494 util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
495 g_mapThreads[pst->m_pThread] = com::Utf8StrFmt("[%3u]", pst->m_u);
496 ++m_cIdleThreads;
497 }
498
499 // enqueue this connection and post eventsem so that one of the threads
500 // (possibly the one just created) can pick it up
501 m_llSocketsQ.push_back(soap_copy(pSoap));
502 cItems = m_llSocketsQ.size();
503 qlock.release();
504
505 // unblock one of the worker threads
506 RTSemEventMultiSignal(m_event);
507
508 return cItems;
509 }
510
511 /**
512 * Blocks the current thread until work comes in; then returns
513 * the SOAP connection which has work to do. This reduces m_cIdleThreads
514 * by one, and the caller MUST call done() when it's done processing.
515 * Called from the worker threads.
516 * @param cIdleThreads out: no. of threads which are currently idle (not counting the caller)
517 * @param cThreads out: total no. of SOAP threads running
518 * @return
519 */
520 struct soap *get(size_t &cIdleThreads, size_t &cThreads)
521 {
522 while (g_fKeepRunning)
523 {
524 // wait for something to happen
525 RTSemEventMultiWait(m_event, RT_INDEFINITE_WAIT);
526
527 if (!g_fKeepRunning)
528 break;
529
530 util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
531 if (!m_llSocketsQ.empty())
532 {
533 struct soap *pSoap = m_llSocketsQ.front();
534 m_llSocketsQ.pop_front();
535 cIdleThreads = --m_cIdleThreads;
536 cThreads = m_llAllThreads.size();
537
538 // reset the multi event only if the queue is now empty; otherwise
539 // another thread will also wake up when we release the mutex and
540 // process another one
541 if (m_llSocketsQ.empty())
542 RTSemEventMultiReset(m_event);
543
544 qlock.release();
545
546 return pSoap;
547 }
548
549 // nothing to do: keep looping
550 }
551 return NULL;
552 }
553
554 /**
555 * To be called by a worker thread after fetching an item from the
556 * queue via get() and having finished its lengthy processing.
557 */
558 void done()
559 {
560 util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
561 ++m_cIdleThreads;
562 }
563
564 /**
565 * To be called by a worker thread when signing off, i.e. no longer
566 * willing to process SOAP connections.
567 */
568 void signoff(SoapThread *th)
569 {
570 {
571 util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
572 size_t c = g_mapThreads.erase(th->m_pThread);
573 AssertReturnVoid(c == 1);
574 }
575 {
576 util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
577 m_llAllThreads.remove(th);
578 --m_cIdleThreads;
579 }
580 }
581
582 util::WriteLockHandle m_mutex;
583 RTSEMEVENTMULTI m_event; // posted by add(), blocked on by get()
584
585 std::list<SoapThread*> m_llAllThreads; // all the threads created by the constructor
586 size_t m_cIdleThreads; // threads which are currently idle (statistics)
587
588 // A std::list abused as a queue; this contains the actual jobs to do,
589 // each entry being a connection from soap_accept() passed through SoapQ::add.
590 std::list<struct soap *> m_llSocketsQ;
591};
592
593/**
594 * Thread function for each of the SOAP queue worker threads. This keeps
595 * running, blocks on the event semaphore in SoapThread.SoapQ and picks
596 * up a connection from the queue therein, which has been put there by
597 * SoapQ::add().
598 */
599void SoapThread::process()
600{
601 LogRel(("New SOAP thread started\n"));
602
603 while (g_fKeepRunning)
604 {
605 // wait for a job to arrive on the queue
606 size_t cIdleThreads = 0, cThreads = 0;
607 struct soap *pSoap = m_pQ->get(cIdleThreads, cThreads);
608
609 if (pSoap == NULL || !soap_valid_socket(pSoap->socket))
610 continue;
611
612 pSoap->fget = fnHttpGet;
613
614 /* The soap.max_keep_alive value can be set to the maximum keep-alive calls allowed,
615 * which is important to avoid a client from holding a thread indefinitely.
616 * http://www.cs.fsu.edu/~engelen/soapdoc2.html#sec:keepalive
617 *
618 * Strings with 8-bit content can hold ASCII (default) or UTF8. The latter is
619 * possible by enabling the SOAP_C_UTFSTRING flag.
620 */
621 soap_set_omode(pSoap, SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING);
622 soap_set_imode(pSoap, SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING);
623 pSoap->max_keep_alive = g_cMaxKeepAlive;
624
625 LogRel(("Processing connection from IP=%RTnaipv4 socket=%d (%d out of %d threads idle)\n",
626 RT_H2N_U32(pSoap->ip), pSoap->socket, cIdleThreads, cThreads));
627
628 // Ensure that we don't get stuck indefinitely for connections using
629 // keepalive, otherwise stale connections tie up worker threads.
630 pSoap->send_timeout = 60;
631 pSoap->recv_timeout = 60;
632 // Limit the maximum SOAP request size to a generous amount, just to
633 // be on the safe side (SOAP is quite wordy when representing arrays,
634 // and some API uses need to deal with large arrays). Good that binary
635 // data is no longer represented by byte arrays...
636 pSoap->recv_maxlength = _16M;
637 // process the request; this goes into the COM code in methodmaps.cpp
638
639 do {
640#ifdef WITH_OPENSSL
641 if (g_fSSL && soap_ssl_accept(pSoap))
642 {
643 WebLogSoapError(pSoap);
644 break;
645 }
646#endif /* WITH_OPENSSL */
647 soap_serve(pSoap);
648 } while (0);
649
650 soap_destroy(pSoap); // clean up class instances
651 soap_end(pSoap); // clean up everything and close connection
652 soap_free(pSoap); // free soap connection
653
654 // tell the queue we're idle again
655 m_pQ->done();
656 }
657 m_pQ->signoff(this);
658}
659
660/****************************************************************************
661 *
662 * VirtualBoxClient event listener
663 *
664 ****************************************************************************/
665
666class VirtualBoxClientEventListener
667{
668public:
669 VirtualBoxClientEventListener()
670 {
671 }
672
673 virtual ~VirtualBoxClientEventListener()
674 {
675 }
676
677 HRESULT init()
678 {
679 return S_OK;
680 }
681
682 void uninit()
683 {
684 }
685
686
687 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
688 {
689 switch (aType)
690 {
691 case VBoxEventType_OnVBoxSVCAvailabilityChanged:
692 {
693 ComPtr<IVBoxSVCAvailabilityChangedEvent> pVSACEv = aEvent;
694 Assert(pVSACEv);
695 BOOL fAvailable = FALSE;
696 pVSACEv->COMGETTER(Available)(&fAvailable);
697 if (!fAvailable)
698 {
699 LogRel(("VBoxSVC became unavailable\n"));
700 {
701 util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
702 g_pVirtualBox.setNull();
703 }
704 {
705 // we're messing with websessions, so lock them
706 util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
707 WEBDEBUG(("SVC unavailable: deleting %d websessions\n", g_mapWebsessions.size()));
708
709 WebsessionsMapIterator it = g_mapWebsessions.begin(),
710 itEnd = g_mapWebsessions.end();
711 while (it != itEnd)
712 {
713 WebServiceSession *pWebsession = it->second;
714 WEBDEBUG(("SVC unavailable: websession %#llx stale, deleting\n", pWebsession->getID()));
715 delete pWebsession;
716 it = g_mapWebsessions.begin();
717 }
718 }
719 }
720 else
721 {
722 LogRel(("VBoxSVC became available\n"));
723 util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
724 HRESULT hrc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam());
725 AssertComRC(hrc);
726 }
727 break;
728 }
729 default:
730 AssertFailed();
731 }
732
733 return S_OK;
734 }
735
736private:
737};
738
739typedef ListenerImpl<VirtualBoxClientEventListener> VirtualBoxClientEventListenerImpl;
740
741VBOX_LISTENER_DECLARE(VirtualBoxClientEventListenerImpl)
742
743/**
744 * Helper for printing SOAP error messages.
745 * @param soap
746 */
747/*static*/
748void WebLogSoapError(struct soap *soap)
749{
750 if (soap_check_state(soap))
751 {
752 LogRel(("Error: soap struct not initialized\n"));
753 return;
754 }
755
756 const char *pcszFaultString = *soap_faultstring(soap);
757 const char **ppcszDetail = soap_faultcode(soap);
758 LogRel(("#### SOAP FAULT: %s [%s]\n",
759 pcszFaultString ? pcszFaultString : "[no fault string available]",
760 (ppcszDetail && *ppcszDetail) ? *ppcszDetail : "no details available"));
761}
762
763/**
764 * Helper for decoding AuthResult.
765 * @param result AuthResult
766 */
767static const char * decodeAuthResult(AuthResult result)
768{
769 switch (result)
770 {
771 case AuthResultAccessDenied: return "access DENIED";
772 case AuthResultAccessGranted: return "access granted";
773 case AuthResultDelegateToGuest: return "delegated to guest";
774 default: return "unknown AuthResult";
775 }
776}
777
778#if defined(WITH_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER))
779/****************************************************************************
780 *
781 * OpenSSL convenience functions for multithread support.
782 * Not required for OpenSSL 1.1+
783 *
784 ****************************************************************************/
785
786static RTCRITSECT *g_pSSLMutexes = NULL;
787
788struct CRYPTO_dynlock_value
789{
790 RTCRITSECT mutex;
791};
792
793static unsigned long CRYPTO_id_function()
794{
795 return (unsigned long)RTThreadNativeSelf();
796}
797
798static void CRYPTO_locking_function(int mode, int n, const char * /*file*/, int /*line*/)
799{
800 if (mode & CRYPTO_LOCK)
801 RTCritSectEnter(&g_pSSLMutexes[n]);
802 else
803 RTCritSectLeave(&g_pSSLMutexes[n]);
804}
805
806static struct CRYPTO_dynlock_value *CRYPTO_dyn_create_function(const char * /*file*/, int /*line*/)
807{
808 static uint32_t s_iCritSectDynlock = 0;
809 struct CRYPTO_dynlock_value *value = (struct CRYPTO_dynlock_value *)RTMemAlloc(sizeof(struct CRYPTO_dynlock_value));
810 if (value)
811 RTCritSectInitEx(&value->mutex, RTCRITSECT_FLAGS_NO_LOCK_VAL,
812 NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE,
813 "openssl-dyn-%u", ASMAtomicIncU32(&s_iCritSectDynlock) - 1);
814
815 return value;
816}
817
818static void CRYPTO_dyn_lock_function(int mode, struct CRYPTO_dynlock_value *value, const char * /*file*/, int /*line*/)
819{
820 if (mode & CRYPTO_LOCK)
821 RTCritSectEnter(&value->mutex);
822 else
823 RTCritSectLeave(&value->mutex);
824}
825
826static void CRYPTO_dyn_destroy_function(struct CRYPTO_dynlock_value *value, const char * /*file*/, int /*line*/)
827{
828 if (value)
829 {
830 RTCritSectDelete(&value->mutex);
831 free(value);
832 }
833}
834
835static int CRYPTO_thread_setup()
836{
837 int num_locks = CRYPTO_num_locks();
838 g_pSSLMutexes = (RTCRITSECT *)RTMemAlloc(num_locks * sizeof(RTCRITSECT));
839 if (!g_pSSLMutexes)
840 return SOAP_EOM;
841
842 for (int i = 0; i < num_locks; i++)
843 {
844 int vrc = RTCritSectInitEx(&g_pSSLMutexes[i], RTCRITSECT_FLAGS_NO_LOCK_VAL,
845 NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE,
846 "openssl-%d", i);
847 if (RT_FAILURE(vrc))
848 {
849 for ( ; i >= 0; i--)
850 RTCritSectDelete(&g_pSSLMutexes[i]);
851 RTMemFree(g_pSSLMutexes);
852 g_pSSLMutexes = NULL;
853 return SOAP_EOM;
854 }
855 }
856
857 CRYPTO_set_id_callback(CRYPTO_id_function);
858 CRYPTO_set_locking_callback(CRYPTO_locking_function);
859 CRYPTO_set_dynlock_create_callback(CRYPTO_dyn_create_function);
860 CRYPTO_set_dynlock_lock_callback(CRYPTO_dyn_lock_function);
861 CRYPTO_set_dynlock_destroy_callback(CRYPTO_dyn_destroy_function);
862
863 return SOAP_OK;
864}
865
866static void CRYPTO_thread_cleanup()
867{
868 if (!g_pSSLMutexes)
869 return;
870
871 CRYPTO_set_id_callback(NULL);
872 CRYPTO_set_locking_callback(NULL);
873 CRYPTO_set_dynlock_create_callback(NULL);
874 CRYPTO_set_dynlock_lock_callback(NULL);
875 CRYPTO_set_dynlock_destroy_callback(NULL);
876
877 int num_locks = CRYPTO_num_locks();
878 for (int i = 0; i < num_locks; i++)
879 RTCritSectDelete(&g_pSSLMutexes[i]);
880
881 RTMemFree(g_pSSLMutexes);
882 g_pSSLMutexes = NULL;
883}
884#endif /* WITH_OPENSSL && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER)) */
885
886/****************************************************************************
887 *
888 * SOAP queue pumper thread
889 *
890 ****************************************************************************/
891
892static void doQueuesLoop()
893{
894#if defined(WITH_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER))
895 if (g_fSSL && CRYPTO_thread_setup())
896 {
897 LogRel(("Failed to set up OpenSSL thread mutex!"));
898 exit(RTEXITCODE_FAILURE);
899 }
900#endif
901
902 // set up gSOAP
903 struct soap soap;
904 soap_init(&soap);
905
906#ifdef WITH_OPENSSL
907 if (g_fSSL && soap_ssl_server_context(&soap, SOAP_SSL_REQUIRE_SERVER_AUTHENTICATION | SOAP_TLSv1, g_pcszKeyFile,
908 g_pcszPassword, g_pcszCACert, g_pcszCAPath,
909 g_pcszDHFile, g_pcszRandFile, g_pcszSID))
910 {
911 WebLogSoapError(&soap);
912 exit(RTEXITCODE_FAILURE);
913 }
914#endif /* WITH_OPENSSL */
915
916 soap.accept_timeout = 60;
917 soap.bind_flags |= SO_REUSEADDR;
918 // avoid EADDRINUSE on bind()
919
920 SOAP_SOCKET m, s; // master and slave sockets
921 m = soap_bind(&soap,
922 g_pcszBindToHost ? g_pcszBindToHost : "localhost", // safe default host
923 g_uBindToPort, // port
924 g_uBacklog); // backlog = max queue size for connections
925 if (m == SOAP_INVALID_SOCKET)
926 WebLogSoapError(&soap);
927 else
928 {
929#ifdef WITH_OPENSSL
930 const char *pszSsl = g_fSSL ? "SSL, " : "";
931#else /* !WITH_OPENSSL */
932 const char *pszSsl = "";
933#endif /*!WITH_OPENSSL */
934 LogRel(("Socket connection successful: host = %s, port = %u, %smaster socket = %d\n",
935 (g_pcszBindToHost) ? g_pcszBindToHost : "default (localhost)",
936 g_uBindToPort, pszSsl, m));
937
938 // initialize thread queue, mutex and eventsem
939 g_pSoapQ = new SoapQ();
940
941 uint64_t cAccepted = 1;
942 while (g_fKeepRunning)
943 {
944 s = soap_accept(&soap);
945 if (!soap_valid_socket(s))
946 {
947 if (soap.errnum != SOAP_EINTR)
948 WebLogSoapError(&soap);
949 continue;
950 }
951
952 // add the connection to the queue and tell worker threads to
953 // pick up the job
954 size_t cItemsOnQ = g_pSoapQ->add(&soap);
955 LogRel(("Connection %llu on socket %d queued for processing (%d items on Q)\n", cAccepted, s, cItemsOnQ));
956 cAccepted++;
957 }
958
959 delete g_pSoapQ;
960 g_pSoapQ = NULL;
961
962 LogRel(("Ending SOAP connection handling\n"));
963
964 }
965 soap_done(&soap); // close master socket and detach environment
966
967#if defined(WITH_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_NUMBER))
968 if (g_fSSL)
969 CRYPTO_thread_cleanup();
970#endif
971}
972
973/**
974 * Thread function for the "queue pumper" thread started from main(). This implements
975 * the loop that takes SOAP calls from HTTP and serves them by handing connections to the
976 * SOAP queue worker threads.
977 */
978static DECLCALLBACK(int) fntQPumper(RTTHREAD hThreadSelf, void *pvUser)
979{
980 RT_NOREF(hThreadSelf, pvUser);
981
982 // store a log prefix for this thread
983 util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
984 g_mapThreads[RTThreadSelf()] = "[ P ]";
985 thrLock.release();
986
987 doQueuesLoop();
988
989 thrLock.acquire();
990 g_mapThreads.erase(RTThreadSelf());
991 return VINF_SUCCESS;
992}
993
994#ifdef RT_OS_WINDOWS
995/**
996 * "Signal" handler for cleanly terminating the event loop.
997 */
998static BOOL WINAPI websrvSignalHandler(DWORD dwCtrlType)
999{
1000 bool fEventHandled = FALSE;
1001 switch (dwCtrlType)
1002 {
1003 /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
1004 * via GenerateConsoleCtrlEvent(). */
1005 case CTRL_BREAK_EVENT:
1006 case CTRL_CLOSE_EVENT:
1007 case CTRL_C_EVENT:
1008 case CTRL_LOGOFF_EVENT:
1009 case CTRL_SHUTDOWN_EVENT:
1010 {
1011 ASMAtomicWriteBool(&g_fKeepRunning, false);
1012 com::NativeEventQueue *pQ = com::NativeEventQueue::getMainEventQueue();
1013 pQ->interruptEventQueueProcessing();
1014 fEventHandled = TRUE;
1015 break;
1016 }
1017 default:
1018 break;
1019 }
1020 return fEventHandled;
1021}
1022#else
1023/**
1024 * Signal handler for cleanly terminating the event loop.
1025 */
1026static void websrvSignalHandler(int iSignal)
1027{
1028 NOREF(iSignal);
1029 ASMAtomicWriteBool(&g_fKeepRunning, false);
1030 com::NativeEventQueue *pQ = com::NativeEventQueue::getMainEventQueue();
1031 pQ->interruptEventQueueProcessing();
1032}
1033#endif
1034
1035
1036/**
1037 * Start up the webservice server. This keeps running and waits
1038 * for incoming SOAP connections; for each request that comes in,
1039 * it calls method implementation code, most of it in the generated
1040 * code in methodmaps.cpp.
1041 *
1042 * @param argc
1043 * @param argv[]
1044 * @return
1045 */
1046int main(int argc, char *argv[])
1047{
1048 // initialize runtime
1049 int vrc = RTR3InitExe(argc, &argv, 0);
1050 if (RT_FAILURE(vrc))
1051 return RTMsgInitFailure(vrc);
1052#ifdef RT_OS_WINDOWS
1053 ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */
1054#endif
1055
1056 // store a log prefix for this thread
1057 g_mapThreads[RTThreadSelf()] = "[M ]";
1058
1059 RTStrmPrintf(g_pStdErr, VBOX_PRODUCT " web service Version " VBOX_VERSION_STRING "\n"
1060 "Copyright (C) 2007-" VBOX_C_YEAR " " VBOX_VENDOR "\n");
1061
1062 int c;
1063 const char *pszLogFile = NULL;
1064 const char *pszPidFile = NULL;
1065 RTGETOPTUNION ValueUnion;
1066 RTGETOPTSTATE GetState;
1067 RTGetOptInit(&GetState, argc, argv, g_aOptions, RT_ELEMENTS(g_aOptions), 1, 0 /*fFlags*/);
1068 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1069 {
1070 switch (c)
1071 {
1072 case 'H':
1073 if (!ValueUnion.psz || !*ValueUnion.psz)
1074 {
1075 /* Normalize NULL/empty string to NULL, which will be
1076 * interpreted as "localhost" below. */
1077 g_pcszBindToHost = NULL;
1078 }
1079 else
1080 g_pcszBindToHost = ValueUnion.psz;
1081 break;
1082
1083 case 'p':
1084 g_uBindToPort = ValueUnion.u32;
1085 break;
1086
1087#ifdef WITH_OPENSSL
1088 case 's':
1089 g_fSSL = true;
1090 break;
1091
1092 case 'K':
1093 g_pcszKeyFile = ValueUnion.psz;
1094 break;
1095
1096 case 'a':
1097 if (ValueUnion.psz[0] == '\0')
1098 g_pcszPassword = NULL;
1099 else
1100 {
1101 PRTSTREAM StrmIn;
1102 if (!strcmp(ValueUnion.psz, "-"))
1103 StrmIn = g_pStdIn;
1104 else
1105 {
1106 vrc = RTStrmOpen(ValueUnion.psz, "r", &StrmIn);
1107 if (RT_FAILURE(vrc))
1108 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open password file (%s, %Rrc)", ValueUnion.psz, vrc);
1109 }
1110 char szPasswd[512];
1111 vrc = RTStrmGetLine(StrmIn, szPasswd, sizeof(szPasswd));
1112 if (RT_FAILURE(vrc))
1113 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to read password (%s, %Rrc)", ValueUnion.psz, vrc);
1114 g_pcszPassword = RTStrDup(szPasswd);
1115 memset(szPasswd, '\0', sizeof(szPasswd));
1116 if (StrmIn != g_pStdIn)
1117 RTStrmClose(StrmIn);
1118 }
1119 break;
1120
1121 case 'c':
1122 g_pcszCACert = ValueUnion.psz;
1123 break;
1124
1125 case 'C':
1126 g_pcszCAPath = ValueUnion.psz;
1127 break;
1128
1129 case 'D':
1130 g_pcszDHFile = ValueUnion.psz;
1131 break;
1132
1133 case 'r':
1134 g_pcszRandFile = ValueUnion.psz;
1135 break;
1136#endif /* WITH_OPENSSL */
1137
1138 case 't':
1139 g_iWatchdogTimeoutSecs = ValueUnion.u32;
1140 break;
1141
1142 case 'i':
1143 g_iWatchdogCheckInterval = ValueUnion.u32;
1144 break;
1145
1146 case 'F':
1147 pszLogFile = ValueUnion.psz;
1148 break;
1149
1150 case 'R':
1151 g_cHistory = ValueUnion.u32;
1152 break;
1153
1154 case 'S':
1155 g_uHistoryFileSize = ValueUnion.u64;
1156 break;
1157
1158 case 'I':
1159 g_uHistoryFileTime = ValueUnion.u32;
1160 break;
1161
1162 case 'P':
1163 pszPidFile = ValueUnion.psz;
1164 break;
1165
1166 case 'T':
1167 g_cMaxWorkerThreads = ValueUnion.u32;
1168 break;
1169
1170 case 'k':
1171 g_cMaxKeepAlive = ValueUnion.u32;
1172 break;
1173
1174 case 'A':
1175 g_pcszAuthentication = ValueUnion.psz;
1176 break;
1177
1178 case 'h':
1179 DisplayHelp();
1180 return 0;
1181
1182 case 'v':
1183 g_fVerbose = true;
1184 break;
1185
1186#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
1187 case 'b':
1188 g_fDaemonize = true;
1189 break;
1190#endif
1191 case 'V':
1192 RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
1193 return 0;
1194
1195 default:
1196 return RTGetOptPrintError(c, &ValueUnion);
1197 }
1198 }
1199
1200 /* create release logger, to stdout */
1201 RTERRINFOSTATIC ErrInfo;
1202 vrc = com::VBoxLogRelCreate("web service", g_fDaemonize ? NULL : pszLogFile,
1203 RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
1204 "all", "VBOXWEBSRV_RELEASE_LOG",
1205 RTLOGDEST_STDOUT, UINT32_MAX /* cMaxEntriesPerGroup */,
1206 g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize,
1207 RTErrInfoInitStatic(&ErrInfo));
1208 if (RT_FAILURE(vrc))
1209 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, vrc);
1210
1211#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
1212 if (g_fDaemonize)
1213 {
1214 /* prepare release logging */
1215 char szLogFile[RTPATH_MAX];
1216
1217 if (!pszLogFile || !*pszLogFile)
1218 {
1219 vrc = com::GetVBoxUserHomeDirectory(szLogFile, sizeof(szLogFile));
1220 if (RT_FAILURE(vrc))
1221 return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not get base directory for logging: %Rrc", vrc);
1222 vrc = RTPathAppend(szLogFile, sizeof(szLogFile), "vboxwebsrv.log");
1223 if (RT_FAILURE(vrc))
1224 return RTMsgErrorExit(RTEXITCODE_FAILURE, "could not construct logging path: %Rrc", vrc);
1225 pszLogFile = szLogFile;
1226 }
1227
1228 vrc = RTProcDaemonizeUsingFork(false /* fNoChDir */, false /* fNoClose */, pszPidFile);
1229 if (RT_FAILURE(vrc))
1230 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to daemonize, vrc=%Rrc. exiting.", vrc);
1231
1232 /* create release logger, to file */
1233 vrc = com::VBoxLogRelCreate("web service", pszLogFile,
1234 RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
1235 "all", "VBOXWEBSRV_RELEASE_LOG",
1236 RTLOGDEST_FILE, UINT32_MAX /* cMaxEntriesPerGroup */,
1237 g_cHistory, g_uHistoryFileTime, g_uHistoryFileSize,
1238 RTErrInfoInitStatic(&ErrInfo));
1239 if (RT_FAILURE(vrc))
1240 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, vrc);
1241 }
1242#endif
1243
1244 // initialize SOAP SSL support if enabled
1245#ifdef WITH_OPENSSL
1246 if (g_fSSL)
1247 soap_ssl_init();
1248#endif /* WITH_OPENSSL */
1249
1250 // initialize COM/XPCOM
1251 HRESULT hrc = com::Initialize();
1252#ifdef VBOX_WITH_XPCOM
1253 if (hrc == NS_ERROR_FILE_ACCESS_DENIED)
1254 {
1255 char szHome[RTPATH_MAX] = "";
1256 com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome));
1257 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1258 "Failed to initialize COM because the global settings directory '%s' is not accessible!", szHome);
1259 }
1260#endif
1261 if (FAILED(hrc))
1262 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to initialize COM! hrc=%Rhrc\n", hrc);
1263
1264 hrc = g_pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
1265 if (FAILED(hrc))
1266 {
1267 RTMsgError("failed to create the VirtualBoxClient object!");
1268 com::ErrorInfo info;
1269 if (!info.isFullAvailable() && !info.isBasicAvailable())
1270 {
1271 com::GluePrintRCMessage(hrc);
1272 RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start.");
1273 }
1274 else
1275 com::GluePrintErrorInfo(info);
1276 return RTEXITCODE_FAILURE;
1277 }
1278
1279 hrc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam());
1280 if (FAILED(hrc))
1281 {
1282 RTMsgError("Failed to get VirtualBox object (hrc=%Rhrc)!", hrc);
1283 return RTEXITCODE_FAILURE;
1284 }
1285
1286 // set the authentication method if requested
1287 if (g_pVirtualBox && g_pcszAuthentication && g_pcszAuthentication[0])
1288 {
1289 ComPtr<ISystemProperties> pSystemProperties;
1290 g_pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
1291 if (pSystemProperties)
1292 pSystemProperties->COMSETTER(WebServiceAuthLibrary)(com::Bstr(g_pcszAuthentication).raw());
1293 }
1294
1295 /* VirtualBoxClient events registration. */
1296 ComPtr<IEventListener> vboxClientListener;
1297 {
1298 ComPtr<IEventSource> pES;
1299 CHECK_ERROR(g_pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
1300 ComObjPtr<VirtualBoxClientEventListenerImpl> clientListener;
1301 clientListener.createObject();
1302 clientListener->init(new VirtualBoxClientEventListener());
1303 vboxClientListener = clientListener;
1304 com::SafeArray<VBoxEventType_T> eventTypes;
1305 eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged);
1306 CHECK_ERROR(pES, RegisterListener(vboxClientListener, ComSafeArrayAsInParam(eventTypes), true));
1307 }
1308
1309 // create the global mutexes
1310 g_pAuthLibLockHandle = new util::WriteLockHandle(util::LOCKCLASS_WEBSERVICE);
1311 g_pVirtualBoxLockHandle = new util::RWLockHandle(util::LOCKCLASS_WEBSERVICE);
1312 g_pWebsessionsLockHandle = new util::WriteLockHandle(util::LOCKCLASS_WEBSERVICE);
1313 g_pThreadsLockHandle = new util::RWLockHandle(util::LOCKCLASS_OBJECTSTATE);
1314
1315 // SOAP queue pumper thread
1316 RTTHREAD threadQPumper;
1317 vrc = RTThreadCreate(&threadQPumper,
1318 fntQPumper,
1319 NULL, // pvUser
1320 0, // cbStack (default)
1321 RTTHREADTYPE_MAIN_WORKER,
1322 RTTHREADFLAGS_WAITABLE,
1323 "SQPmp");
1324 if (RT_FAILURE(vrc))
1325 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot start SOAP queue pumper thread: %Rrc", vrc);
1326
1327 // watchdog thread
1328 RTTHREAD threadWatchdog = NIL_RTTHREAD;
1329 if (g_iWatchdogTimeoutSecs > 0)
1330 {
1331 // start our watchdog thread
1332 vrc = RTThreadCreate(&threadWatchdog,
1333 fntWatchdog,
1334 NULL,
1335 0,
1336 RTTHREADTYPE_MAIN_WORKER,
1337 RTTHREADFLAGS_WAITABLE,
1338 "Watchdog");
1339 if (RT_FAILURE(vrc))
1340 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot start watchdog thread: %Rrc", vrc);
1341 }
1342
1343#ifdef RT_OS_WINDOWS
1344 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)websrvSignalHandler, TRUE /* Add handler */))
1345 {
1346 vrc = RTErrConvertFromWin32(GetLastError());
1347 RTMsgError("Unable to install console control handler, vrc=%Rrc\n", vrc);
1348 }
1349#else
1350 signal(SIGINT, websrvSignalHandler);
1351 signal(SIGTERM, websrvSignalHandler);
1352# ifdef SIGBREAK
1353 signal(SIGBREAK, websrvSignalHandler);
1354# endif
1355#endif
1356
1357 com::NativeEventQueue *pQ = com::NativeEventQueue::getMainEventQueue();
1358 while (g_fKeepRunning)
1359 {
1360 // we have to process main event queue
1361 WEBDEBUG(("Pumping COM event queue\n"));
1362 vrc = pQ->processEventQueue(RT_INDEFINITE_WAIT);
1363 if (RT_FAILURE(vrc))
1364 RTMsgError("processEventQueue -> %Rrc", vrc);
1365 }
1366
1367 LogRel(("requested termination, cleaning up\n"));
1368
1369#ifdef RT_OS_WINDOWS
1370 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)websrvSignalHandler, FALSE /* Remove handler */))
1371 {
1372 vrc = RTErrConvertFromWin32(GetLastError());
1373 RTMsgError("Unable to remove console control handler, vrc=%Rrc\n", vrc);
1374 }
1375#else
1376 signal(SIGINT, SIG_DFL);
1377 signal(SIGTERM, SIG_DFL);
1378# ifdef SIGBREAK
1379 signal(SIGBREAK, SIG_DFL);
1380# endif
1381#endif
1382
1383#ifndef RT_OS_WINDOWS
1384 RTThreadPoke(threadQPumper);
1385#endif
1386 RTThreadWait(threadQPumper, 30000, NULL);
1387 if (threadWatchdog != NIL_RTTHREAD)
1388 {
1389#ifndef RT_OS_WINDOWS
1390 RTThreadPoke(threadWatchdog);
1391#endif
1392 RTThreadWait(threadWatchdog, g_iWatchdogCheckInterval * 1000 + 10000, NULL);
1393 }
1394
1395 /* VirtualBoxClient events unregistration. */
1396 if (vboxClientListener)
1397 {
1398 ComPtr<IEventSource> pES;
1399 CHECK_ERROR(g_pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
1400 if (!pES.isNull())
1401 CHECK_ERROR(pES, UnregisterListener(vboxClientListener));
1402 vboxClientListener.setNull();
1403 }
1404
1405 {
1406 util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
1407 g_pVirtualBox.setNull();
1408 }
1409 {
1410 util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
1411 WebsessionsMapIterator it = g_mapWebsessions.begin(),
1412 itEnd = g_mapWebsessions.end();
1413 while (it != itEnd)
1414 {
1415 WebServiceSession *pWebsession = it->second;
1416 WEBDEBUG(("SVC unavailable: websession %#llx stale, deleting\n", pWebsession->getID()));
1417 delete pWebsession;
1418 it = g_mapWebsessions.begin();
1419 }
1420 }
1421 g_pVirtualBoxClient.setNull();
1422
1423 com::Shutdown();
1424
1425 return 0;
1426}
1427
1428/****************************************************************************
1429 *
1430 * Watchdog thread
1431 *
1432 ****************************************************************************/
1433
1434/**
1435 * Watchdog thread, runs in the background while the webservice is alive.
1436 *
1437 * This gets started by main() and runs in the background to check all websessions
1438 * for whether they have been no requests in a configurable timeout period. In
1439 * that case, the websession is automatically logged off.
1440 */
1441static DECLCALLBACK(int) fntWatchdog(RTTHREAD hThreadSelf, void *pvUser)
1442{
1443 RT_NOREF(hThreadSelf, pvUser);
1444
1445 // store a log prefix for this thread
1446 util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
1447 g_mapThreads[RTThreadSelf()] = "[W ]";
1448 thrLock.release();
1449
1450 WEBDEBUG(("Watchdog thread started\n"));
1451
1452 uint32_t tNextStat = 0;
1453
1454 while (g_fKeepRunning)
1455 {
1456 WEBDEBUG(("Watchdog: sleeping %d seconds\n", g_iWatchdogCheckInterval));
1457 RTThreadSleep(g_iWatchdogCheckInterval * 1000);
1458
1459 uint32_t tNow = RTTimeProgramSecTS();
1460
1461 // we're messing with websessions, so lock them
1462 util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
1463 WEBDEBUG(("Watchdog: checking %d websessions\n", g_mapWebsessions.size()));
1464
1465 WebsessionsMapIterator it = g_mapWebsessions.begin(),
1466 itEnd = g_mapWebsessions.end();
1467 while (it != itEnd)
1468 {
1469 WebServiceSession *pWebsession = it->second;
1470 WEBDEBUG(("Watchdog: tNow: %d, websession timestamp: %d\n", tNow, pWebsession->getLastObjectLookup()));
1471 if (tNow > pWebsession->getLastObjectLookup() + g_iWatchdogTimeoutSecs)
1472 {
1473 WEBDEBUG(("Watchdog: websession %#llx timed out, deleting\n", pWebsession->getID()));
1474 delete pWebsession;
1475 it = g_mapWebsessions.begin();
1476 }
1477 else
1478 ++it;
1479 }
1480
1481 // re-set the authentication method in case it has been changed
1482 if (g_pVirtualBox && g_pcszAuthentication && g_pcszAuthentication[0])
1483 {
1484 ComPtr<ISystemProperties> pSystemProperties;
1485 g_pVirtualBox->COMGETTER(SystemProperties)(pSystemProperties.asOutParam());
1486 if (pSystemProperties)
1487 pSystemProperties->COMSETTER(WebServiceAuthLibrary)(com::Bstr(g_pcszAuthentication).raw());
1488 }
1489
1490 // Log some MOR usage statistics every 5 minutes, but only if there's
1491 // something worth logging (at least one reference or a transition to
1492 // zero references). Avoids useless log spamming in idle webservice.
1493 if (tNow >= tNextStat)
1494 {
1495 size_t cMOR = 0;
1496 it = g_mapWebsessions.begin();
1497 itEnd = g_mapWebsessions.end();
1498 while (it != itEnd)
1499 {
1500 cMOR += it->second->CountRefs();
1501 ++it;
1502 }
1503 static bool fLastZero = false;
1504 if (cMOR || !fLastZero)
1505 LogRel(("Statistics: %zu websessions, %zu references\n",
1506 g_mapWebsessions.size(), cMOR));
1507 fLastZero = (cMOR == 0);
1508 while (tNextStat <= tNow)
1509 tNextStat += 5 * 60; /* 5 minutes */
1510 }
1511 }
1512
1513 thrLock.acquire();
1514 g_mapThreads.erase(RTThreadSelf());
1515
1516 LogRel(("ending Watchdog thread\n"));
1517 return 0;
1518}
1519
1520/****************************************************************************
1521 *
1522 * SOAP exceptions
1523 *
1524 ****************************************************************************/
1525
1526/**
1527 * Helper function to raise a SOAP fault. Called by the other helper
1528 * functions, which raise specific SOAP faults.
1529 *
1530 * @param soap
1531 * @param pcsz
1532 * @param extype
1533 * @param ex
1534 */
1535static void RaiseSoapFault(struct soap *soap,
1536 const char *pcsz,
1537 int extype,
1538 void *ex)
1539{
1540 // raise the fault
1541 soap_sender_fault(soap, pcsz, NULL);
1542
1543 struct SOAP_ENV__Detail *pDetail = (struct SOAP_ENV__Detail*)soap_malloc(soap, sizeof(struct SOAP_ENV__Detail));
1544
1545 // without the following, gSOAP crashes miserably when sending out the
1546 // data because it will try to serialize all fields (stupid documentation)
1547 memset(pDetail, 0, sizeof(struct SOAP_ENV__Detail));
1548
1549 // fill extended info depending on SOAP version
1550 if (soap->version == 2) // SOAP 1.2 is used
1551 {
1552 soap->fault->SOAP_ENV__Detail = pDetail;
1553 soap->fault->SOAP_ENV__Detail->__type = extype;
1554 soap->fault->SOAP_ENV__Detail->fault = ex;
1555 soap->fault->SOAP_ENV__Detail->__any = NULL; // no other XML data
1556 }
1557 else
1558 {
1559 soap->fault->detail = pDetail;
1560 soap->fault->detail->__type = extype;
1561 soap->fault->detail->fault = ex;
1562 soap->fault->detail->__any = NULL; // no other XML data
1563 }
1564}
1565
1566/**
1567 * Raises a SOAP fault that signals that an invalid object was passed.
1568 *
1569 * @param soap
1570 * @param obj
1571 */
1572void RaiseSoapInvalidObjectFault(struct soap *soap,
1573 WSDLT_ID obj)
1574{
1575 _vbox__InvalidObjectFault *ex = soap_new__vbox__InvalidObjectFault(soap, 1);
1576 ex->badObjectID = obj;
1577
1578 std::string str("VirtualBox error: ");
1579 str += "Invalid managed object reference \"" + obj + "\"";
1580
1581 RaiseSoapFault(soap,
1582 str.c_str(),
1583 SOAP_TYPE__vbox__InvalidObjectFault,
1584 ex);
1585}
1586
1587/**
1588 * Return a safe C++ string from the given COM string,
1589 * without crashing if the COM string is empty.
1590 * @param bstr
1591 * @return
1592 */
1593std::string ConvertComString(const com::Bstr &bstr)
1594{
1595 com::Utf8Str ustr(bstr);
1596 return ustr.c_str(); /// @todo r=dj since the length is known, we can probably use a better std::string allocator
1597}
1598
1599/**
1600 * Return a safe C++ string from the given COM UUID,
1601 * without crashing if the UUID is empty.
1602 * @param uuid
1603 * @return
1604 */
1605std::string ConvertComString(const com::Guid &uuid)
1606{
1607 com::Utf8Str ustr(uuid.toString());
1608 return ustr.c_str(); /// @todo r=dj since the length is known, we can probably use a better std::string allocator
1609}
1610
1611/** Code to handle string <-> byte arrays base64 conversion. */
1612std::string Base64EncodeByteArray(ComSafeArrayIn(BYTE, aData))
1613{
1614
1615 com::SafeArray<BYTE> sfaData(ComSafeArrayInArg(aData));
1616 ssize_t cbData = sfaData.size();
1617
1618 if (cbData == 0)
1619 return "";
1620
1621 ssize_t cchOut = RTBase64EncodedLength(cbData);
1622
1623 RTCString aStr;
1624
1625 aStr.reserve(cchOut+1);
1626 int vrc = RTBase64Encode(sfaData.raw(), cbData, aStr.mutableRaw(), aStr.capacity(), NULL);
1627 AssertRC(vrc);
1628 aStr.jolt();
1629
1630 return aStr.c_str();
1631}
1632
1633#define DECODE_STR_MAX _1M
1634void Base64DecodeByteArray(struct soap *soap, const std::string& aStr, ComSafeArrayOut(BYTE, aData), const WSDLT_ID &idThis, const char *pszMethodName, IUnknown *pObj, const com::Guid &iid)
1635{
1636 const char* pszStr = aStr.c_str();
1637 ssize_t cbOut = RTBase64DecodedSize(pszStr, NULL);
1638
1639 if (cbOut > DECODE_STR_MAX)
1640 {
1641 LogRel(("Decode string too long.\n"));
1642 RaiseSoapRuntimeFault(soap, idThis, pszMethodName, E_INVALIDARG, pObj, iid);
1643 }
1644
1645 com::SafeArray<BYTE> result(cbOut);
1646 int vrc = RTBase64Decode(pszStr, result.raw(), cbOut, NULL, NULL);
1647 if (FAILED(vrc))
1648 {
1649 LogRel(("String Decoding Failed. Error code: %Rrc\n", vrc));
1650 RaiseSoapRuntimeFault(soap, idThis, pszMethodName, E_INVALIDARG, pObj, iid);
1651 }
1652
1653 result.detachTo(ComSafeArrayOutArg(aData));
1654}
1655
1656/**
1657 * Raises a SOAP runtime fault.
1658 *
1659 * @param soap
1660 * @param idThis
1661 * @param pcszMethodName
1662 * @param apirc
1663 * @param pObj
1664 * @param iid
1665 */
1666void RaiseSoapRuntimeFault(struct soap *soap,
1667 const WSDLT_ID &idThis,
1668 const char *pcszMethodName,
1669 HRESULT apirc,
1670 IUnknown *pObj,
1671 const com::Guid &iid)
1672{
1673 com::ErrorInfo info(pObj, iid.ref());
1674
1675 WEBDEBUG((" error, raising SOAP exception\n"));
1676
1677 LogRel(("API method name: %s\n", pcszMethodName));
1678 LogRel(("API return code: %#10lx (%Rhrc)\n", apirc, apirc));
1679 if (info.isFullAvailable() || info.isBasicAvailable())
1680 {
1681 const com::ErrorInfo *pInfo = &info;
1682 do
1683 {
1684 LogRel(("COM error info result code: %#10lx (%Rhrc)\n", pInfo->getResultCode(), pInfo->getResultCode()));
1685 LogRel(("COM error info text: %ls\n", pInfo->getText().raw()));
1686
1687 pInfo = pInfo->getNext();
1688 }
1689 while (pInfo);
1690 }
1691
1692 // compose descriptive message
1693 com::Utf8StrFmt str("VirtualBox error: apirc=%#lx", apirc);
1694 if (info.isFullAvailable() || info.isBasicAvailable())
1695 {
1696 const com::ErrorInfo *pInfo = &info;
1697 do
1698 {
1699 str += com::Utf8StrFmt(" %ls (%#lx)", pInfo->getText().raw(), pInfo->getResultCode());
1700 pInfo = pInfo->getNext();
1701 }
1702 while (pInfo);
1703 }
1704
1705 // allocate our own soap fault struct
1706 _vbox__RuntimeFault *ex = soap_new__vbox__RuntimeFault(soap, 1);
1707 ComPtr<IVirtualBoxErrorInfo> pVirtualBoxErrorInfo;
1708 info.getVirtualBoxErrorInfo(pVirtualBoxErrorInfo);
1709 ex->resultCode = apirc;
1710 ex->returnval = createOrFindRefFromComPtr(idThis, g_pcszIVirtualBoxErrorInfo, pVirtualBoxErrorInfo);
1711
1712 RaiseSoapFault(soap,
1713 str.c_str(),
1714 SOAP_TYPE__vbox__RuntimeFault,
1715 ex);
1716}
1717
1718/****************************************************************************
1719 *
1720 * splitting and merging of object IDs
1721 *
1722 ****************************************************************************/
1723
1724/**
1725 * Splits a managed object reference (in string form, as passed in from a SOAP
1726 * method call) into two integers for websession and object IDs, respectively.
1727 *
1728 * @param id
1729 * @param pWebsessId
1730 * @param pObjId
1731 * @return
1732 */
1733static bool SplitManagedObjectRef(const WSDLT_ID &id,
1734 uint64_t *pWebsessId,
1735 uint64_t *pObjId)
1736{
1737 // 64-bit numbers in hex have 16 digits; hence
1738 // the object-ref string must have 16 + "-" + 16 characters
1739 if ( id.length() == 33
1740 && id[16] == '-'
1741 )
1742 {
1743 char psz[34];
1744 memcpy(psz, id.c_str(), 34);
1745 psz[16] = '\0';
1746 if (pWebsessId)
1747 RTStrToUInt64Full(psz, 16, pWebsessId);
1748 if (pObjId)
1749 RTStrToUInt64Full(psz + 17, 16, pObjId);
1750 return true;
1751 }
1752
1753 return false;
1754}
1755
1756/**
1757 * Creates a managed object reference (in string form) from
1758 * two integers representing a websession and object ID, respectively.
1759 *
1760 * @param sz Buffer with at least 34 bytes space to receive MOR string.
1761 * @param websessId
1762 * @param objId
1763 */
1764static void MakeManagedObjectRef(char *sz,
1765 uint64_t websessId,
1766 uint64_t objId)
1767{
1768 RTStrFormatNumber(sz, websessId, 16, 16, 0, RTSTR_F_64BIT | RTSTR_F_ZEROPAD);
1769 sz[16] = '-';
1770 RTStrFormatNumber(sz + 17, objId, 16, 16, 0, RTSTR_F_64BIT | RTSTR_F_ZEROPAD);
1771}
1772
1773/****************************************************************************
1774 *
1775 * class WebServiceSession
1776 *
1777 ****************************************************************************/
1778
1779class WebServiceSessionPrivate
1780{
1781 public:
1782 ManagedObjectsMapById _mapManagedObjectsById;
1783 ManagedObjectsMapByPtr _mapManagedObjectsByPtr;
1784};
1785
1786/**
1787 * Constructor for the websession object.
1788 *
1789 * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
1790 */
1791WebServiceSession::WebServiceSession()
1792 : _uNextObjectID(1), // avoid 0 for no real reason
1793 _fDestructing(false),
1794 _tLastObjectLookup(0)
1795{
1796 _pp = new WebServiceSessionPrivate;
1797 _uWebsessionID = RTRandU64();
1798
1799 // register this websession globally
1800 Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
1801 g_mapWebsessions[_uWebsessionID] = this;
1802}
1803
1804/**
1805 * Destructor. Cleans up and destroys all contained managed object references on the way.
1806 *
1807 * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
1808 */
1809WebServiceSession::~WebServiceSession()
1810{
1811 // delete us from global map first so we can't be found
1812 // any more while we're cleaning up
1813 Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
1814 g_mapWebsessions.erase(_uWebsessionID);
1815
1816 // notify ManagedObjectRef destructor so it won't
1817 // remove itself from the maps; this avoids rebalancing
1818 // the map's tree on every delete as well
1819 _fDestructing = true;
1820
1821 ManagedObjectsIteratorById it,
1822 end = _pp->_mapManagedObjectsById.end();
1823 for (it = _pp->_mapManagedObjectsById.begin();
1824 it != end;
1825 ++it)
1826 {
1827 ManagedObjectRef *pRef = it->second;
1828 delete pRef; // this frees the contained ComPtr as well
1829 }
1830
1831 delete _pp;
1832}
1833
1834/**
1835 * Authenticate the username and password against an authentication authority.
1836 *
1837 * @return 0 if the user was successfully authenticated, or an error code
1838 * otherwise.
1839 */
1840int WebServiceSession::authenticate(const char *pcszUsername,
1841 const char *pcszPassword,
1842 IVirtualBox **ppVirtualBox)
1843{
1844 int vrc = VERR_WEB_NOT_AUTHENTICATED;
1845 ComPtr<IVirtualBox> pVirtualBox;
1846 {
1847 util::AutoReadLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
1848 pVirtualBox = g_pVirtualBox;
1849 }
1850 if (pVirtualBox.isNull())
1851 return vrc;
1852 pVirtualBox.queryInterfaceTo(ppVirtualBox);
1853
1854 util::AutoReadLock lock(g_pAuthLibLockHandle COMMA_LOCKVAL_SRC_POS);
1855
1856 static bool fAuthLibLoaded = false;
1857 static PAUTHENTRY pfnAuthEntry = NULL;
1858 static PAUTHENTRY2 pfnAuthEntry2 = NULL;
1859 static PAUTHENTRY3 pfnAuthEntry3 = NULL;
1860
1861 if (!fAuthLibLoaded)
1862 {
1863 // retrieve authentication library from system properties
1864 ComPtr<ISystemProperties> systemProperties;
1865 pVirtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
1866
1867 com::Bstr authLibrary;
1868 systemProperties->COMGETTER(WebServiceAuthLibrary)(authLibrary.asOutParam());
1869 com::Utf8Str filename = authLibrary;
1870
1871 LogRel(("External authentication library is '%ls'\n", authLibrary.raw()));
1872
1873 if (filename == "null")
1874 // authentication disabled, let everyone in:
1875 fAuthLibLoaded = true;
1876 else
1877 {
1878 RTLDRMOD hlibAuth = 0;
1879 do
1880 {
1881 if (RTPathHavePath(filename.c_str()))
1882 vrc = RTLdrLoad(filename.c_str(), &hlibAuth);
1883 else
1884 vrc = RTLdrLoadAppPriv(filename.c_str(), &hlibAuth);
1885 if (RT_FAILURE(vrc))
1886 {
1887 WEBDEBUG(("%s() Failed to load external authentication library '%s'. Error code: %Rrc\n",
1888 __FUNCTION__, filename.c_str(), vrc));
1889 break;
1890 }
1891
1892 if (RT_FAILURE(vrc = RTLdrGetSymbol(hlibAuth, AUTHENTRY3_NAME, (void**)&pfnAuthEntry3)))
1893 {
1894 WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n",
1895 __FUNCTION__, AUTHENTRY3_NAME, vrc));
1896
1897 if (RT_FAILURE(vrc = RTLdrGetSymbol(hlibAuth, AUTHENTRY2_NAME, (void**)&pfnAuthEntry2)))
1898 {
1899 WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n",
1900 __FUNCTION__, AUTHENTRY2_NAME, vrc));
1901
1902 if (RT_FAILURE(vrc = RTLdrGetSymbol(hlibAuth, AUTHENTRY_NAME, (void**)&pfnAuthEntry)))
1903 WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n",
1904 __FUNCTION__, AUTHENTRY_NAME, vrc));
1905 }
1906 }
1907
1908 if (pfnAuthEntry || pfnAuthEntry2 || pfnAuthEntry3)
1909 fAuthLibLoaded = true;
1910
1911 } while (0);
1912 }
1913 }
1914
1915 if (strlen(pcszUsername) >= _1K)
1916 {
1917 LogRel(("Access denied, excessive username length: %zu\n", strlen(pcszUsername)));
1918 vrc = VERR_WEB_NOT_AUTHENTICATED;
1919 }
1920 else if (strlen(pcszPassword) >= _1K)
1921 {
1922 LogRel(("Access denied, excessive password length: %zu\n", strlen(pcszPassword)));
1923 vrc = VERR_WEB_NOT_AUTHENTICATED;
1924 }
1925 else if (pfnAuthEntry3 || pfnAuthEntry2 || pfnAuthEntry)
1926 {
1927 const char *pszFn;
1928 AuthResult result;
1929 if (pfnAuthEntry3)
1930 {
1931 result = pfnAuthEntry3("webservice", NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL, true, 0);
1932 pszFn = AUTHENTRY3_NAME;
1933 }
1934 else if (pfnAuthEntry2)
1935 {
1936 result = pfnAuthEntry2(NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL, true, 0);
1937 pszFn = AUTHENTRY2_NAME;
1938 }
1939 else
1940 {
1941 result = pfnAuthEntry(NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL);
1942 pszFn = AUTHENTRY_NAME;
1943 }
1944 WEBDEBUG(("%s(): result of %s('%s', [%d]): %d (%s)\n",
1945 __FUNCTION__, pszFn, pcszUsername, strlen(pcszPassword), result, decodeAuthResult(result)));
1946 if (result == AuthResultAccessGranted)
1947 {
1948 LogRel(("Access for user '%s' granted\n", pcszUsername));
1949 vrc = VINF_SUCCESS;
1950 }
1951 else
1952 {
1953 if (result == AuthResultAccessDenied)
1954 LogRel(("Access for user '%s' denied\n", pcszUsername));
1955 vrc = VERR_WEB_NOT_AUTHENTICATED;
1956 }
1957 }
1958 else if (fAuthLibLoaded)
1959 {
1960 // fAuthLibLoaded = true but all pointers are NULL:
1961 // The authlib was "null" and auth was disabled
1962 vrc = VINF_SUCCESS;
1963 }
1964 else
1965 {
1966 WEBDEBUG(("Could not resolve AuthEntry, VRDPAuth2 or VRDPAuth entry point"));
1967 vrc = VERR_WEB_NOT_AUTHENTICATED;
1968 }
1969
1970 lock.release();
1971
1972 return vrc;
1973}
1974
1975/**
1976 * Look up, in this websession, whether a ManagedObjectRef has already been
1977 * created for the given COM pointer.
1978 *
1979 * Note how we require that a ComPtr<IUnknown> is passed, which causes a
1980 * queryInterface call when the caller passes in a different type, since
1981 * a ComPtr<IUnknown> will point to something different than a
1982 * ComPtr<IVirtualBox>, for example. As we store the ComPtr<IUnknown> in
1983 * our private hash table, we must search for one too.
1984 *
1985 * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
1986 *
1987 * @param pObject pointer to a COM object.
1988 * @return The existing ManagedObjectRef that represents the COM object, or NULL if there's none yet.
1989 */
1990ManagedObjectRef* WebServiceSession::findRefFromPtr(const IUnknown *pObject)
1991{
1992 Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
1993
1994 uintptr_t ulp = (uintptr_t)pObject;
1995 // WEBDEBUG((" %s: looking up %#lx\n", __FUNCTION__, ulp));
1996 ManagedObjectsIteratorByPtr it = _pp->_mapManagedObjectsByPtr.find(ulp);
1997 if (it != _pp->_mapManagedObjectsByPtr.end())
1998 {
1999 ManagedObjectRef *pRef = it->second;
2000 WEBDEBUG((" %s: found existing ref %s (%s) for COM obj %#lx\n", __FUNCTION__, pRef->getWSDLID().c_str(), pRef->getInterfaceName(), ulp));
2001 return pRef;
2002 }
2003
2004 return NULL;
2005}
2006
2007/**
2008 * Static method which attempts to find the websession for which the given
2009 * managed object reference was created, by splitting the reference into the
2010 * websession and object IDs and then looking up the websession object.
2011 *
2012 * Preconditions: Caller must have locked g_pWebsessionsLockHandle in read mode.
2013 *
2014 * @param id Managed object reference (with combined websession and object IDs).
2015 * @return
2016 */
2017WebServiceSession *WebServiceSession::findWebsessionFromRef(const WSDLT_ID &id)
2018{
2019 Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
2020
2021 WebServiceSession *pWebsession = NULL;
2022 uint64_t websessId;
2023 if (SplitManagedObjectRef(id,
2024 &websessId,
2025 NULL))
2026 {
2027 WebsessionsMapIterator it = g_mapWebsessions.find(websessId);
2028 if (it != g_mapWebsessions.end())
2029 pWebsession = it->second;
2030 }
2031 return pWebsession;
2032}
2033
2034/**
2035 * Touches the websession to prevent it from timing out.
2036 *
2037 * Each websession has an internal timestamp that records the last request made
2038 * to it from the client that started it. If no request was made within a
2039 * configurable timeframe, then the client is logged off automatically,
2040 * by calling IWebsessionManager::logoff()
2041 */
2042void WebServiceSession::touch()
2043{
2044 _tLastObjectLookup = RTTimeProgramSecTS();
2045}
2046
2047/**
2048 * Counts the number of managed object references in this websession.
2049 */
2050size_t WebServiceSession::CountRefs()
2051{
2052 return _pp->_mapManagedObjectsById.size();
2053}
2054
2055
2056/****************************************************************************
2057 *
2058 * class ManagedObjectRef
2059 *
2060 ****************************************************************************/
2061
2062/**
2063 * Constructor, which assigns a unique ID to this managed object
2064 * reference and stores it in two hashes (living in the associated
2065 * WebServiceSession object):
2066 *
2067 * a) _mapManagedObjectsById, which maps ManagedObjectID's to
2068 * instances of this class; this hash is then used by the
2069 * findObjectFromRef() template function in vboxweb.h
2070 * to quickly retrieve the COM object from its managed
2071 * object ID (mostly in the context of the method mappers
2072 * in methodmaps.cpp, when a web service client passes in
2073 * a managed object ID);
2074 *
2075 * b) _mapManagedObjectsByPtr, which maps COM pointers to
2076 * instances of this class; this hash is used by
2077 * createRefFromObject() to quickly figure out whether an
2078 * instance already exists for a given COM pointer.
2079 *
2080 * This constructor calls AddRef() on the given COM object, and
2081 * the destructor will call Release(). We require two input pointers
2082 * for that COM object, one generic IUnknown* pointer which is used
2083 * as the map key, and a specific interface pointer (e.g. IMachine*)
2084 * which must support the interface given in guidInterface. All
2085 * three values are returned by getPtr(), which gives future callers
2086 * a chance to reuse the specific interface pointer without having
2087 * to call QueryInterface, which can be expensive.
2088 *
2089 * This does _not_ check whether another instance already
2090 * exists in the hash. This gets called only from the
2091 * createOrFindRefFromComPtr() template function in vboxweb.h, which
2092 * does perform that check.
2093 *
2094 * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
2095 *
2096 * @param websession Websession to which the MOR will be added.
2097 * @param pobjUnknown Pointer to IUnknown* interface for the COM object; this will be used in the hashes.
2098 * @param pobjInterface Pointer to a specific interface for the COM object, described by guidInterface.
2099 * @param guidInterface Interface which pobjInterface points to.
2100 * @param pcszInterface String representation of that interface (e.g. "IMachine") for readability and logging.
2101 */
2102ManagedObjectRef::ManagedObjectRef(WebServiceSession &websession,
2103 IUnknown *pobjUnknown,
2104 void *pobjInterface,
2105 const com::Guid &guidInterface,
2106 const char *pcszInterface)
2107 : _websession(websession),
2108 _pobjUnknown(pobjUnknown),
2109 _pobjInterface(pobjInterface),
2110 _guidInterface(guidInterface),
2111 _pcszInterface(pcszInterface)
2112{
2113 Assert(pobjUnknown);
2114 Assert(pobjInterface);
2115
2116 // keep both stubs alive while this MOR exists (matching Release() calls are in destructor)
2117 uint32_t cRefs1 = pobjUnknown->AddRef();
2118 uint32_t cRefs2 = ((IUnknown*)pobjInterface)->AddRef();
2119 _ulp = (uintptr_t)pobjUnknown;
2120
2121 Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
2122 _id = websession.createObjectID();
2123 // and count globally
2124 ULONG64 cTotal = ++g_cManagedObjects; // raise global count and make a copy for the debug message below
2125
2126 char sz[34];
2127 MakeManagedObjectRef(sz, websession._uWebsessionID, _id);
2128 _strID = sz;
2129
2130 websession._pp->_mapManagedObjectsById[_id] = this;
2131 websession._pp->_mapManagedObjectsByPtr[_ulp] = this;
2132
2133 websession.touch();
2134
2135 WEBDEBUG((" * %s: MOR created for %s*=%#p (IUnknown*=%#p; COM refcount now %RI32/%RI32), new ID is %#llx; now %lld objects total\n",
2136 __FUNCTION__,
2137 pcszInterface,
2138 pobjInterface,
2139 pobjUnknown,
2140 cRefs1,
2141 cRefs2,
2142 _id,
2143 cTotal));
2144}
2145
2146/**
2147 * Destructor; removes the instance from the global hash of
2148 * managed objects. Calls Release() on the contained COM object.
2149 *
2150 * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
2151 */
2152ManagedObjectRef::~ManagedObjectRef()
2153{
2154 Assert(g_pWebsessionsLockHandle->isWriteLockOnCurrentThread());
2155 ULONG64 cTotal = --g_cManagedObjects;
2156
2157 Assert(_pobjUnknown);
2158 Assert(_pobjInterface);
2159
2160 // we called AddRef() on both interfaces, so call Release() on
2161 // both as well, but in reverse order
2162 uint32_t cRefs2 = ((IUnknown*)_pobjInterface)->Release();
2163 uint32_t cRefs1 = _pobjUnknown->Release();
2164 WEBDEBUG((" * %s: deleting MOR for ID %#llx (%s; COM refcount now %RI32/%RI32); now %lld objects total\n", __FUNCTION__, _id, _pcszInterface, cRefs1, cRefs2, cTotal));
2165
2166 // if we're being destroyed from the websession's destructor,
2167 // then that destructor is iterating over the maps, so
2168 // don't remove us there! (data integrity + speed)
2169 if (!_websession._fDestructing)
2170 {
2171 WEBDEBUG((" * %s: removing from websession maps\n", __FUNCTION__));
2172 _websession._pp->_mapManagedObjectsById.erase(_id);
2173 if (_websession._pp->_mapManagedObjectsByPtr.erase(_ulp) != 1)
2174 WEBDEBUG((" WARNING: could not find %#llx in _mapManagedObjectsByPtr\n", _ulp));
2175 }
2176}
2177
2178/**
2179 * Static helper method for findObjectFromRef() template that actually
2180 * looks up the object from a given integer ID.
2181 *
2182 * This has been extracted into this non-template function to reduce
2183 * code bloat as we have the actual STL map lookup only in this function.
2184 *
2185 * This also "touches" the timestamp in the websession whose ID is encoded
2186 * in the given integer ID, in order to prevent the websession from timing
2187 * out.
2188 *
2189 * Preconditions: Caller must have locked g_pWebsessionsLockHandle.
2190 *
2191 * @param id
2192 * @param pRef
2193 * @param fNullAllowed
2194 * @return
2195 */
2196int ManagedObjectRef::findRefFromId(const WSDLT_ID &id,
2197 ManagedObjectRef **pRef,
2198 bool fNullAllowed)
2199{
2200 int vrc = VINF_SUCCESS;
2201
2202 do
2203 {
2204 // allow NULL (== empty string) input reference, which should return a NULL pointer
2205 if (!id.length() && fNullAllowed)
2206 {
2207 *pRef = NULL;
2208 return 0;
2209 }
2210
2211 uint64_t websessId;
2212 uint64_t objId;
2213 WEBDEBUG((" %s(): looking up objref %s\n", __FUNCTION__, id.c_str()));
2214 if (!SplitManagedObjectRef(id,
2215 &websessId,
2216 &objId))
2217 {
2218 vrc = VERR_WEB_INVALID_MANAGED_OBJECT_REFERENCE;
2219 break;
2220 }
2221
2222 WebsessionsMapIterator it = g_mapWebsessions.find(websessId);
2223 if (it == g_mapWebsessions.end())
2224 {
2225 WEBDEBUG((" %s: cannot find websession for objref %s\n", __FUNCTION__, id.c_str()));
2226 vrc = VERR_WEB_INVALID_SESSION_ID;
2227 break;
2228 }
2229
2230 WebServiceSession *pWebsession = it->second;
2231 // "touch" websession to prevent it from timing out
2232 pWebsession->touch();
2233
2234 ManagedObjectsIteratorById iter = pWebsession->_pp->_mapManagedObjectsById.find(objId);
2235 if (iter == pWebsession->_pp->_mapManagedObjectsById.end())
2236 {
2237 WEBDEBUG((" %s: cannot find comobj for objref %s\n", __FUNCTION__, id.c_str()));
2238 vrc = VERR_WEB_INVALID_OBJECT_ID;
2239 break;
2240 }
2241
2242 *pRef = iter->second;
2243
2244 } while (0);
2245
2246 return vrc;
2247}
2248
2249/****************************************************************************
2250 *
2251 * interface IManagedObjectRef
2252 *
2253 ****************************************************************************/
2254
2255/**
2256 * This is the hard-coded implementation for the IManagedObjectRef::getInterfaceName()
2257 * that our WSDL promises to our web service clients. This method returns a
2258 * string describing the interface that this managed object reference
2259 * supports, e.g. "IMachine".
2260 *
2261 * @param soap
2262 * @param req
2263 * @param resp
2264 * @return
2265 */
2266int __vbox__IManagedObjectRef_USCOREgetInterfaceName(
2267 struct soap *soap,
2268 _vbox__IManagedObjectRef_USCOREgetInterfaceName *req,
2269 _vbox__IManagedObjectRef_USCOREgetInterfaceNameResponse *resp)
2270{
2271 RT_NOREF(soap);
2272 HRESULT hrc = S_OK; /** @todo r=bird: hrc is not set.... */
2273 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
2274
2275 do
2276 {
2277 // findRefFromId require the lock
2278 util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
2279
2280 ManagedObjectRef *pRef;
2281 if (!ManagedObjectRef::findRefFromId(req->_USCOREthis, &pRef, false))
2282 resp->returnval = pRef->getInterfaceName();
2283
2284 } while (0);
2285
2286 WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
2287 if (FAILED(hrc))
2288 return SOAP_FAULT;
2289 return SOAP_OK;
2290}
2291
2292/**
2293 * This is the hard-coded implementation for the IManagedObjectRef::release()
2294 * that our WSDL promises to our web service clients. This method releases
2295 * a managed object reference and removes it from our stacks.
2296 *
2297 * @param soap
2298 * @param req
2299 * @param resp
2300 * @return
2301 */
2302int __vbox__IManagedObjectRef_USCORErelease(
2303 struct soap *soap,
2304 _vbox__IManagedObjectRef_USCORErelease *req,
2305 _vbox__IManagedObjectRef_USCOREreleaseResponse *resp)
2306{
2307 RT_NOREF(resp);
2308 HRESULT hrc;
2309 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
2310
2311 {
2312 // findRefFromId and the delete call below require the lock
2313 util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
2314
2315 ManagedObjectRef *pRef;
2316 hrc = ManagedObjectRef::findRefFromId(req->_USCOREthis, &pRef, false);
2317 if (hrc == S_OK)
2318 {
2319 WEBDEBUG((" found reference; deleting!\n"));
2320 // this removes the object from all stacks; since
2321 // there's a ComPtr<> hidden inside the reference,
2322 // this should also invoke Release() on the COM
2323 // object
2324 delete pRef;
2325 }
2326 else
2327 RaiseSoapInvalidObjectFault(soap, req->_USCOREthis);
2328 }
2329
2330 WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
2331 if (FAILED(hrc))
2332 return SOAP_FAULT;
2333 return SOAP_OK;
2334}
2335
2336/****************************************************************************
2337 *
2338 * interface IWebsessionManager
2339 *
2340 ****************************************************************************/
2341
2342/**
2343 * Hard-coded implementation for IWebsessionManager::logon. As opposed to the underlying
2344 * COM API, this is the first method that a webservice client must call before the
2345 * webservice will do anything useful.
2346 *
2347 * This returns a managed object reference to the global IVirtualBox object; into this
2348 * reference a websession ID is encoded which remains constant with all managed object
2349 * references returned by other methods.
2350 *
2351 * When the webservice client is done, it should call IWebsessionManager::logoff. This
2352 * will clean up internally (destroy all remaining managed object references and
2353 * related COM objects used internally).
2354 *
2355 * After logon, an internal timeout ensures that if the webservice client does not
2356 * call any methods, after a configurable number of seconds, the webservice will log
2357 * off the client automatically. This is to ensure that the webservice does not
2358 * drown in managed object references and eventually deny service. Still, it is
2359 * a much better solution, both for performance and cleanliness, for the webservice
2360 * client to clean up itself.
2361 *
2362 * @param soap
2363 * @param req
2364 * @param resp
2365 * @return
2366 */
2367int __vbox__IWebsessionManager_USCORElogon(
2368 struct soap *soap,
2369 _vbox__IWebsessionManager_USCORElogon *req,
2370 _vbox__IWebsessionManager_USCORElogonResponse *resp)
2371{
2372 RT_NOREF(soap);
2373 HRESULT hrc = S_OK;
2374 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
2375
2376 do
2377 {
2378 // WebServiceSession constructor tinkers with global MOR map and requires a write lock
2379 util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
2380
2381 // create new websession; the constructor stores the new websession
2382 // in the global map automatically
2383 WebServiceSession *pWebsession = new WebServiceSession();
2384 ComPtr<IVirtualBox> pVirtualBox;
2385
2386 // authenticate the user
2387 if (!(pWebsession->authenticate(req->username.c_str(),
2388 req->password.c_str(),
2389 pVirtualBox.asOutParam())))
2390 {
2391 // fake up a "root" MOR for this websession
2392 char sz[34];
2393 MakeManagedObjectRef(sz, pWebsession->getID(), 0ULL);
2394 WSDLT_ID id = sz;
2395
2396 // in the new websession, create a managed object reference (MOR) for the
2397 // global VirtualBox object; this encodes the websession ID in the MOR so
2398 // that it will be implicitly be included in all future requests of this
2399 // webservice client
2400 resp->returnval = createOrFindRefFromComPtr(id, g_pcszIVirtualBox, pVirtualBox);
2401 WEBDEBUG(("VirtualBox object ref is %s\n", resp->returnval.c_str()));
2402 }
2403 else
2404 hrc = E_FAIL;
2405 } while (0);
2406
2407 WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
2408 if (FAILED(hrc))
2409 return SOAP_FAULT;
2410 return SOAP_OK;
2411}
2412
2413/**
2414 * Returns a new ISession object every time.
2415 *
2416 * No longer connected in any way to logons, one websession can easily
2417 * handle multiple sessions.
2418 */
2419int __vbox__IWebsessionManager_USCOREgetSessionObject(
2420 struct soap*,
2421 _vbox__IWebsessionManager_USCOREgetSessionObject *req,
2422 _vbox__IWebsessionManager_USCOREgetSessionObjectResponse *resp)
2423{
2424 HRESULT hrc = S_OK;
2425 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
2426
2427 do
2428 {
2429 // create a new ISession object
2430 ComPtr<ISession> pSession;
2431 hrc = g_pVirtualBoxClient->COMGETTER(Session)(pSession.asOutParam());
2432 if (FAILED(hrc))
2433 {
2434 WEBDEBUG(("ERROR: cannot create session object!"));
2435 break;
2436 }
2437
2438 // return its MOR
2439 resp->returnval = createOrFindRefFromComPtr(req->refIVirtualBox, g_pcszISession, pSession);
2440 WEBDEBUG(("Session object ref is %s\n", resp->returnval.c_str()));
2441 } while (0);
2442
2443 WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
2444 if (FAILED(hrc))
2445 return SOAP_FAULT;
2446 return SOAP_OK;
2447}
2448
2449/**
2450 * hard-coded implementation for IWebsessionManager::logoff.
2451 *
2452 * @param req
2453 * @param resp
2454 * @return
2455 */
2456int __vbox__IWebsessionManager_USCORElogoff(
2457 struct soap*,
2458 _vbox__IWebsessionManager_USCORElogoff *req,
2459 _vbox__IWebsessionManager_USCORElogoffResponse *resp)
2460{
2461 RT_NOREF(resp);
2462 HRESULT hrc = S_OK;
2463 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
2464
2465 {
2466 // findWebsessionFromRef and the websession destructor require the lock
2467 util::AutoWriteLock lock(g_pWebsessionsLockHandle COMMA_LOCKVAL_SRC_POS);
2468
2469 WebServiceSession *pWebsession = WebServiceSession::findWebsessionFromRef(req->refIVirtualBox);
2470 if (pWebsession)
2471 {
2472 WEBDEBUG(("websession logoff, deleting websession %#llx\n", pWebsession->getID()));
2473 delete pWebsession;
2474 // destructor cleans up
2475
2476 WEBDEBUG(("websession destroyed, %d websessions left open\n", g_mapWebsessions.size()));
2477 }
2478 }
2479
2480 WEBDEBUG(("-- leaving %s, hrc: %#lx\n", __FUNCTION__, hrc));
2481 if (FAILED(hrc))
2482 return SOAP_FAULT;
2483 return SOAP_OK;
2484}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use