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