VirtualBox

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

Last change on this file since 40293 was 40151, checked in by vboxsync, 13 years ago

Main/webservice: fix potential NULL dereference on VBoxSVC crash, and use the newest authentication entry point available in the auth library.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette