VirtualBox

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

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

Main+Frontends: move release logging setup to the glue code (to eliminate spreading code duplication), various minor fixes and cleanups

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

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