1 /**
2  * Orthanc - A Lightweight, RESTful DICOM Store
3  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4  * Department, University Hospital of Liege, Belgium
5  * Copyright (C) 2017-2021 Osimis S.A., Belgium
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation, either version 3 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program. If not, see
19  * <http://www.gnu.org/licenses/>.
20  **/
21 
22 
23 #include "../PrecompiledHeaders.h"
24 #include "DicomServer.h"
25 
26 #include "../Logging.h"
27 #include "../MultiThreading/RunnableWorkersPool.h"
28 #include "../OrthancException.h"
29 #include "../SystemToolbox.h"
30 #include "../Toolbox.h"
31 #include "DicomAssociationParameters.h"
32 #include "Internals/CommandDispatcher.h"
33 
34 #include <boost/thread.hpp>
35 
36 #if ORTHANC_ENABLE_SSL == 1
37 #  include "Internals/DicomTls.h"
38 #endif
39 
40 #if defined(__linux__)
41 #  include <cstdlib>
42 #endif
43 
44 
45 namespace Orthanc
46 {
47   struct DicomServer::PImpl
48   {
49     boost::thread  thread_;
50     T_ASC_Network *network_;
51     std::unique_ptr<RunnableWorkersPool>  workers_;
52 
53 #if ORTHANC_ENABLE_SSL == 1
54     std::unique_ptr<DcmTLSTransportLayer> tls_;
55 #endif
56   };
57 
58 
ServerThread(DicomServer * server,unsigned int maximumPduLength,bool useDicomTls)59   void DicomServer::ServerThread(DicomServer* server,
60                                  unsigned int maximumPduLength,
61                                  bool useDicomTls)
62   {
63     CLOG(INFO, DICOM) << "DICOM server started";
64 
65     while (server->continue_)
66     {
67       /* receive an association and acknowledge or reject it. If the association was */
68       /* acknowledged, offer corresponding services and invoke one or more if required. */
69       std::unique_ptr<Internals::CommandDispatcher> dispatcher(
70         Internals::AcceptAssociation(*server, server->pimpl_->network_, maximumPduLength, useDicomTls));
71 
72       try
73       {
74         if (dispatcher.get() != NULL)
75         {
76           server->pimpl_->workers_->Add(dispatcher.release());
77         }
78       }
79       catch (OrthancException& e)
80       {
81         CLOG(ERROR, DICOM) << "Exception in the DICOM server thread: " << e.What();
82       }
83     }
84 
85     CLOG(INFO, DICOM) << "DICOM server stopping";
86   }
87 
88 
DicomServer()89   DicomServer::DicomServer() :
90     pimpl_(new PImpl),
91     checkCalledAet_(true),
92     aet_("ANY-SCP"),
93     port_(104),
94     continue_(false),
95     associationTimeout_(30),
96     modalities_(NULL),
97     findRequestHandlerFactory_(NULL),
98     moveRequestHandlerFactory_(NULL),
99     getRequestHandlerFactory_(NULL),
100     storeRequestHandlerFactory_(NULL),
101     worklistRequestHandlerFactory_(NULL),
102     storageCommitmentFactory_(NULL),
103     applicationEntityFilter_(NULL),
104     useDicomTls_(false),
105     maximumPduLength_(ASC_DEFAULTMAXPDU),
106     remoteCertificateRequired_(true)
107   {
108   }
109 
~DicomServer()110   DicomServer::~DicomServer()
111   {
112     if (continue_)
113     {
114       CLOG(ERROR, DICOM) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!";
115       Stop();
116     }
117   }
118 
SetPortNumber(uint16_t port)119   void DicomServer::SetPortNumber(uint16_t port)
120   {
121     Stop();
122     port_ = port;
123   }
124 
GetPortNumber() const125   uint16_t DicomServer::GetPortNumber() const
126   {
127     return port_;
128   }
129 
SetAssociationTimeout(uint32_t seconds)130   void DicomServer::SetAssociationTimeout(uint32_t seconds)
131   {
132     CLOG(INFO, DICOM) << "Setting timeout for DICOM connections if Orthanc acts as SCP (server): "
133                       << seconds << " seconds (0 = no timeout)";
134 
135     Stop();
136     associationTimeout_ = seconds;
137   }
138 
GetAssociationTimeout() const139   uint32_t DicomServer::GetAssociationTimeout() const
140   {
141     return associationTimeout_;
142   }
143 
144 
SetCalledApplicationEntityTitleCheck(bool check)145   void DicomServer::SetCalledApplicationEntityTitleCheck(bool check)
146   {
147     Stop();
148     checkCalledAet_ = check;
149   }
150 
HasCalledApplicationEntityTitleCheck() const151   bool DicomServer::HasCalledApplicationEntityTitleCheck() const
152   {
153     return checkCalledAet_;
154   }
155 
SetApplicationEntityTitle(const std::string & aet)156   void DicomServer::SetApplicationEntityTitle(const std::string& aet)
157   {
158     if (aet.size() == 0)
159     {
160       throw OrthancException(ErrorCode_BadApplicationEntityTitle);
161     }
162 
163     if (aet.size() > 16)
164     {
165       throw OrthancException(ErrorCode_BadApplicationEntityTitle);
166     }
167 
168     for (size_t i = 0; i < aet.size(); i++)
169     {
170       if (!(aet[i] == '-' ||
171             aet[i] == '_' ||
172             isdigit(aet[i]) ||
173             (aet[i] >= 'A' && aet[i] <= 'Z')))
174       {
175         CLOG(WARNING, DICOM) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\"";
176         break;
177       }
178     }
179 
180     Stop();
181     aet_ = aet;
182   }
183 
GetApplicationEntityTitle() const184   const std::string& DicomServer::GetApplicationEntityTitle() const
185   {
186     return aet_;
187   }
188 
SetRemoteModalities(IRemoteModalities & modalities)189   void DicomServer::SetRemoteModalities(IRemoteModalities& modalities)
190   {
191     Stop();
192     modalities_ = &modalities;
193   }
194 
GetRemoteModalities() const195   DicomServer::IRemoteModalities& DicomServer::GetRemoteModalities() const
196   {
197     if (modalities_ == NULL)
198     {
199       throw OrthancException(ErrorCode_BadSequenceOfCalls);
200     }
201     else
202     {
203       return *modalities_;
204     }
205   }
206 
SetFindRequestHandlerFactory(IFindRequestHandlerFactory & factory)207   void DicomServer::SetFindRequestHandlerFactory(IFindRequestHandlerFactory& factory)
208   {
209     Stop();
210     findRequestHandlerFactory_ = &factory;
211   }
212 
HasFindRequestHandlerFactory() const213   bool DicomServer::HasFindRequestHandlerFactory() const
214   {
215     return (findRequestHandlerFactory_ != NULL);
216   }
217 
GetFindRequestHandlerFactory() const218   IFindRequestHandlerFactory& DicomServer::GetFindRequestHandlerFactory() const
219   {
220     if (HasFindRequestHandlerFactory())
221     {
222       return *findRequestHandlerFactory_;
223     }
224     else
225     {
226       throw OrthancException(ErrorCode_NoCFindHandler);
227     }
228   }
229 
SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory & factory)230   void DicomServer::SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& factory)
231   {
232     Stop();
233     moveRequestHandlerFactory_ = &factory;
234   }
235 
HasMoveRequestHandlerFactory() const236   bool DicomServer::HasMoveRequestHandlerFactory() const
237   {
238     return (moveRequestHandlerFactory_ != NULL);
239   }
240 
GetMoveRequestHandlerFactory() const241   IMoveRequestHandlerFactory& DicomServer::GetMoveRequestHandlerFactory() const
242   {
243     if (HasMoveRequestHandlerFactory())
244     {
245       return *moveRequestHandlerFactory_;
246     }
247     else
248     {
249       throw OrthancException(ErrorCode_NoCMoveHandler);
250     }
251   }
252 
SetGetRequestHandlerFactory(IGetRequestHandlerFactory & factory)253   void DicomServer::SetGetRequestHandlerFactory(IGetRequestHandlerFactory& factory)
254   {
255     Stop();
256     getRequestHandlerFactory_ = &factory;
257   }
258 
HasGetRequestHandlerFactory() const259   bool DicomServer::HasGetRequestHandlerFactory() const
260   {
261     return (getRequestHandlerFactory_ != NULL);
262   }
263 
GetGetRequestHandlerFactory() const264   IGetRequestHandlerFactory& DicomServer::GetGetRequestHandlerFactory() const
265   {
266     if (HasGetRequestHandlerFactory())
267     {
268       return *getRequestHandlerFactory_;
269     }
270     else
271     {
272       throw OrthancException(ErrorCode_NoCGetHandler);
273     }
274   }
275 
SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory & factory)276   void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory)
277   {
278     Stop();
279     storeRequestHandlerFactory_ = &factory;
280   }
281 
HasStoreRequestHandlerFactory() const282   bool DicomServer::HasStoreRequestHandlerFactory() const
283   {
284     return (storeRequestHandlerFactory_ != NULL);
285   }
286 
GetStoreRequestHandlerFactory() const287   IStoreRequestHandlerFactory& DicomServer::GetStoreRequestHandlerFactory() const
288   {
289     if (HasStoreRequestHandlerFactory())
290     {
291       return *storeRequestHandlerFactory_;
292     }
293     else
294     {
295       throw OrthancException(ErrorCode_NoCStoreHandler);
296     }
297   }
298 
SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory & factory)299   void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory)
300   {
301     Stop();
302     worklistRequestHandlerFactory_ = &factory;
303   }
304 
HasWorklistRequestHandlerFactory() const305   bool DicomServer::HasWorklistRequestHandlerFactory() const
306   {
307     return (worklistRequestHandlerFactory_ != NULL);
308   }
309 
GetWorklistRequestHandlerFactory() const310   IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const
311   {
312     if (HasWorklistRequestHandlerFactory())
313     {
314       return *worklistRequestHandlerFactory_;
315     }
316     else
317     {
318       throw OrthancException(ErrorCode_NoWorklistHandler);
319     }
320   }
321 
SetStorageCommitmentRequestHandlerFactory(IStorageCommitmentRequestHandlerFactory & factory)322   void DicomServer::SetStorageCommitmentRequestHandlerFactory(IStorageCommitmentRequestHandlerFactory& factory)
323   {
324     Stop();
325     storageCommitmentFactory_ = &factory;
326   }
327 
HasStorageCommitmentRequestHandlerFactory() const328   bool DicomServer::HasStorageCommitmentRequestHandlerFactory() const
329   {
330     return (storageCommitmentFactory_ != NULL);
331   }
332 
GetStorageCommitmentRequestHandlerFactory() const333   IStorageCommitmentRequestHandlerFactory& DicomServer::GetStorageCommitmentRequestHandlerFactory() const
334   {
335     if (HasStorageCommitmentRequestHandlerFactory())
336     {
337       return *storageCommitmentFactory_;
338     }
339     else
340     {
341       throw OrthancException(ErrorCode_NoStorageCommitmentHandler);
342     }
343   }
344 
SetApplicationEntityFilter(IApplicationEntityFilter & factory)345   void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory)
346   {
347     Stop();
348     applicationEntityFilter_ = &factory;
349   }
350 
HasApplicationEntityFilter() const351   bool DicomServer::HasApplicationEntityFilter() const
352   {
353     return (applicationEntityFilter_ != NULL);
354   }
355 
GetApplicationEntityFilter() const356   IApplicationEntityFilter& DicomServer::GetApplicationEntityFilter() const
357   {
358     if (HasApplicationEntityFilter())
359     {
360       return *applicationEntityFilter_;
361     }
362     else
363     {
364       throw OrthancException(ErrorCode_NoApplicationEntityFilter);
365     }
366   }
367 
368 
Start()369   void DicomServer::Start()
370   {
371     if (modalities_ == NULL)
372     {
373       throw OrthancException(ErrorCode_BadSequenceOfCalls,
374                              "No list of modalities was provided to the DICOM server");
375     }
376 
377     if (useDicomTls_)
378     {
379       if (ownCertificatePath_.empty() ||
380           ownPrivateKeyPath_.empty())
381       {
382         throw OrthancException(ErrorCode_ParameterOutOfRange,
383                                "DICOM TLS is enabled in Orthanc SCP, but no certificate was provided");
384       }
385     }
386 
387     Stop();
388 
389     /* initialize network, i.e. create an instance of T_ASC_Network*. */
390     OFCondition cond = ASC_initializeNetwork
391       (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_);
392     if (cond.bad())
393     {
394       throw OrthancException(ErrorCode_DicomPortInUse,
395                              " (port = " + boost::lexical_cast<std::string>(port_) +
396                              ") cannot create network: " + std::string(cond.text()));
397     }
398 
399 #if ORTHANC_ENABLE_SSL == 1
400     assert(pimpl_->tls_.get() == NULL);
401 
402     if (useDicomTls_)
403     {
404       CLOG(INFO, DICOM) << "Orthanc SCP will use DICOM TLS";
405 
406       try
407       {
408         pimpl_->tls_.reset(Internals::InitializeDicomTls(
409                              pimpl_->network_, NET_ACCEPTOR, ownPrivateKeyPath_, ownCertificatePath_,
410                              trustedCertificatesPath_, remoteCertificateRequired_));
411       }
412       catch (OrthancException&)
413       {
414         ASC_dropNetwork(&pimpl_->network_);
415         throw;
416       }
417     }
418     else
419     {
420       CLOG(INFO, DICOM) << "Orthanc SCP will *not* use DICOM TLS";
421     }
422 #else
423     CLOG(INFO, DICOM) << "Orthanc SCP will *not* use DICOM TLS";
424 #endif
425 
426     continue_ = true;
427     pimpl_->workers_.reset(new RunnableWorkersPool(4));   // Use 4 workers - TODO as a parameter?
428     pimpl_->thread_ = boost::thread(ServerThread, this, maximumPduLength_, useDicomTls_);
429   }
430 
431 
Stop()432   void DicomServer::Stop()
433   {
434     if (continue_)
435     {
436       continue_ = false;
437 
438       if (pimpl_->thread_.joinable())
439       {
440         pimpl_->thread_.join();
441       }
442 
443       pimpl_->workers_.reset(NULL);
444 
445 #if ORTHANC_ENABLE_SSL == 1
446       pimpl_->tls_.reset(NULL);  // Transport layer must be destroyed before the association itself
447 #endif
448 
449       /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
450       /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
451       OFCondition cond = ASC_dropNetwork(&pimpl_->network_);
452       if (cond.bad())
453       {
454         CLOG(ERROR, DICOM) << "Error while dropping the network: " << cond.text();
455       }
456     }
457   }
458 
459 
IsMyAETitle(const std::string & aet) const460   bool DicomServer::IsMyAETitle(const std::string& aet) const
461   {
462     if (modalities_ == NULL)
463     {
464       throw OrthancException(ErrorCode_BadSequenceOfCalls);
465     }
466 
467     if (!HasCalledApplicationEntityTitleCheck())
468     {
469       // OK, no check on the AET.
470       return true;
471     }
472     else
473     {
474       return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle());
475     }
476   }
477 
478 
SetDicomTlsEnabled(bool enabled)479   void DicomServer::SetDicomTlsEnabled(bool enabled)
480   {
481     Stop();
482     useDicomTls_ = enabled;
483   }
484 
IsDicomTlsEnabled() const485   bool DicomServer::IsDicomTlsEnabled() const
486   {
487     return useDicomTls_;
488   }
489 
SetOwnCertificatePath(const std::string & privateKeyPath,const std::string & certificatePath)490   void DicomServer::SetOwnCertificatePath(const std::string& privateKeyPath,
491                                           const std::string& certificatePath)
492   {
493     Stop();
494 
495     if (!privateKeyPath.empty() &&
496         !certificatePath.empty())
497     {
498       CLOG(INFO, DICOM) << "Setting the TLS certificate for DICOM SCP connections: "
499                         << privateKeyPath << " (key), " << certificatePath << " (certificate)";
500 
501       if (certificatePath.empty())
502       {
503         throw OrthancException(ErrorCode_ParameterOutOfRange, "No path to the default DICOM TLS certificate was provided");
504       }
505 
506       if (privateKeyPath.empty())
507       {
508         throw OrthancException(ErrorCode_ParameterOutOfRange,
509                                "No path to the private key for the default DICOM TLS certificate was provided");
510       }
511 
512       if (!SystemToolbox::IsRegularFile(privateKeyPath))
513       {
514         throw OrthancException(ErrorCode_InexistentFile, "Inexistent file: " + privateKeyPath);
515       }
516 
517       if (!SystemToolbox::IsRegularFile(certificatePath))
518       {
519         throw OrthancException(ErrorCode_InexistentFile, "Inexistent file: " + certificatePath);
520       }
521 
522       ownPrivateKeyPath_ = privateKeyPath;
523       ownCertificatePath_ = certificatePath;
524     }
525     else
526     {
527       ownPrivateKeyPath_.clear();
528       ownCertificatePath_.clear();
529     }
530   }
531 
GetOwnPrivateKeyPath() const532   const std::string& DicomServer::GetOwnPrivateKeyPath() const
533   {
534     return ownPrivateKeyPath_;
535   }
536 
GetOwnCertificatePath() const537   const std::string& DicomServer::GetOwnCertificatePath() const
538   {
539     return ownCertificatePath_;
540   }
541 
SetTrustedCertificatesPath(const std::string & path)542   void DicomServer::SetTrustedCertificatesPath(const std::string& path)
543   {
544     Stop();
545 
546     if (!path.empty())
547     {
548       CLOG(INFO, DICOM) << "Setting the trusted certificates for DICOM SCP connections: " << path;
549 
550       if (!SystemToolbox::IsRegularFile(path))
551       {
552         throw OrthancException(ErrorCode_InexistentFile, "Inexistent file: " + path);
553       }
554 
555       trustedCertificatesPath_ = path;
556     }
557     else
558     {
559       trustedCertificatesPath_.clear();
560     }
561   }
562 
GetTrustedCertificatesPath() const563   const std::string& DicomServer::GetTrustedCertificatesPath() const
564   {
565     return trustedCertificatesPath_;
566   }
567 
GetMaximumPduLength() const568   unsigned int DicomServer::GetMaximumPduLength() const
569   {
570     return maximumPduLength_;
571   }
572 
SetMaximumPduLength(unsigned int pdu)573   void DicomServer::SetMaximumPduLength(unsigned int pdu)
574   {
575     DicomAssociationParameters::CheckMaximumPduLength(pdu);
576 
577     Stop();
578     maximumPduLength_ = pdu;
579   }
580 
SetRemoteCertificateRequired(bool required)581   void DicomServer::SetRemoteCertificateRequired(bool required)
582   {
583     Stop();
584     remoteCertificateRequired_ = required;
585   }
586 
IsRemoteCertificateRequired() const587   bool DicomServer::IsRemoteCertificateRequired() const
588   {
589     return remoteCertificateRequired_;
590   }
591 }
592