1 /*
2  *
3  *  Copyright (C) 1999-2019, OFFIS e.V.
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  This software and supporting documentation were developed by
7  *
8  *    OFFIS e.V.
9  *    R&D Division Health
10  *    Escherweg 2
11  *    D-26121 Oldenburg, Germany
12  *
13  *
14  *  Module:  dcmpstat
15  *
16  *  Authors: Marco Eichelberg
17  *
18  *  Purpose: Presentation State Viewer - Network Send Component (Store SCU)
19  *
20  */
21 
22 #include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
23 
24 BEGIN_EXTERN_C
25 #ifdef HAVE_FCNTL_H
26 #include <fcntl.h>       /* for O_RDONLY */
27 #endif
28 #ifdef HAVE_SYS_TYPES_H
29 #include <sys/types.h>   /* required for sys/stat.h */
30 #endif
31 #ifdef HAVE_SYS_STAT_H
32 #include <sys/stat.h>    /* for stat, fstat */
33 #endif
34 END_EXTERN_C
35 
36 #include "dcmtk/dcmpstat/dvpsdef.h"     /* for constants */
37 #include "dcmtk/dcmpstat/dvpscf.h"      /* for class DVConfiguration */
38 #include "dcmtk/ofstd/ofbmanip.h"       /* for OFBitmanipTemplate */
39 #include "dcmtk/dcmdata/dcuid.h"        /* for dcmtk version name */
40 #include "dcmtk/dcmdata/dcdeftag.h"
41 #include "dcmtk/dcmdata/dcdict.h"
42 #include "dcmtk/dcmnet/diutil.h"
43 #include "dcmtk/dcmdata/cmdlnarg.h"
44 #include "dcmtk/ofstd/ofconapp.h"
45 #include "dcmtk/ofstd/ofstd.h"
46 #include "dcmtk/dcmpstat/dvpshlp.h"     /* for class DVPSHelper */
47 #include "dcmtk/dcmqrdb/dcmqrdbi.h"     /* for LOCK_IMAGE_FILES */
48 #include "dcmtk/dcmqrdb/dcmqrdbs.h"     /* for DcmQueryRetrieveDatabaseStatus */
49 #include "dcmtk/dcmpstat/dvpsmsg.h"
50 
51 #ifdef WITH_OPENSSL
52 #include "dcmtk/dcmtls/tlstrans.h"
53 #include "dcmtk/dcmtls/tlslayer.h"
54 #endif
55 
56 #ifdef WITH_ZLIB
57 #include <zlib.h>        /* for zlibVersion() */
58 #endif
59 
60 #include "dcmtk/ofstd/ofstream.h"
61 
62 #define OFFIS_CONSOLE_APPLICATION "dcmpssnd"
63 
64 static OFLogger dcmpssndLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION);
65 
66 static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v"
67   OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";
68 
69 DVPSIPCClient *messageClient  = NULL; // global pointer to IPC message client, if present
70 
71 /** sends a single DICOM instance over an association which must be already established.
72  *  @param assoc DICOM network association
73  *  @param sopClass SOP Class UID of the image (used for the C-Store-RQ)
74  *  @param sopInstance SOP Instance UID of the image (used for the C-Store-RQ)
75  *  @param imgFile path to the image file to be transmitted
76  *  @return EC_Normal if successful, a different DIMSE code otherwise.
77  */
sendImage(T_ASC_Association * assoc,const char * sopClass,const char * sopInstance,const char * imgFile)78 static OFCondition sendImage(T_ASC_Association *assoc, const char *sopClass, const char *sopInstance, const char *imgFile)
79 {
80     DcmDataset *statusDetail = NULL;
81     T_ASC_PresentationContextID presId=0;
82     T_DIMSE_C_StoreRQ req;
83     T_DIMSE_C_StoreRSP rsp;
84 
85     if (assoc == NULL) return DIMSE_NULLKEY;
86     if ((sopClass == NULL)||(strlen(sopClass) == 0)) return DIMSE_NULLKEY;
87     if ((sopInstance == NULL)||(strlen(sopInstance) == 0)) return DIMSE_NULLKEY;
88     if ((imgFile == NULL)||(strlen(imgFile) == 0)) return DIMSE_NULLKEY;
89 
90 #ifdef LOCK_IMAGE_FILES
91     /* shared lock image file */
92 #ifdef O_BINARY
93     int lockfd = open(imgFile, O_RDONLY | O_BINARY, 0666);
94 #else
95     int lockfd = open(imgFile, O_RDONLY, 0666);
96 #endif
97     if (lockfd < 0)
98     {
99       OFLOG_INFO(dcmpssndLogger, "unable to lock image file '" << imgFile << "'");
100       return DIMSE_BADDATA;
101     }
102     dcmtk_flock(lockfd, LOCK_SH);
103 #endif
104 
105     /* which presentation context should be used */
106     presId = ASC_findAcceptedPresentationContextID(assoc, sopClass);
107     if (presId == 0)
108     {
109       OFLOG_INFO(dcmpssndLogger, "no presentation context for: ("
110         << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass);
111       if (messageClient)
112       {
113         OFString buf("unable to send image: no presentation context for ");
114         const char *sopClassName = dcmFindNameOfUID(sopClass);
115         if (sopClassName == NULL) buf.append(sopClass); else buf.append(sopClassName);
116         buf.append("\n");
117         messageClient->notifySentDICOMObject(DVPSIPCMessage::statusWarning, buf.c_str());
118       }
119       return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
120     }
121 
122     /* start store */
123     OFBitmanipTemplate<char>::zeroMem((char *)&req, sizeof(req));
124     req.MessageID = assoc->nextMsgID++;
125 
126     OFStandard::strlcpy(req.AffectedSOPClassUID, sopClass, sizeof(req.AffectedSOPClassUID));
127     OFStandard::strlcpy(req.AffectedSOPInstanceUID, sopInstance, sizeof(req.AffectedSOPInstanceUID));
128     req.DataSetType = DIMSE_DATASET_PRESENT;
129     req.Priority = DIMSE_PRIORITY_MEDIUM;
130 
131     OFCondition cond = DIMSE_storeUser(assoc, presId, &req,
132         imgFile, NULL, NULL, NULL, DIMSE_BLOCKING, 0, &rsp, &statusDetail);
133 
134 #ifdef LOCK_IMAGE_FILES
135     /* unlock image file */
136     dcmtk_flock(lockfd, LOCK_UN);
137     close(lockfd);
138 #endif
139 
140     if (cond.good())
141     {
142         OFLOG_INFO(dcmpssndLogger, "[MsgID " << req.MessageID << "] Complete [Status: "
143            << DU_cstoreStatusString(rsp.DimseStatus) << "]");
144     } else {
145         OFString temp_str;
146         OFLOG_INFO(dcmpssndLogger, "[MsgID " << req.MessageID << "] Failed [Status: "
147            << DU_cstoreStatusString(rsp.DimseStatus) << "]\n" << DimseCondition::dump(temp_str, cond));
148     }
149     if (statusDetail) delete statusDetail;
150 
151     if (messageClient)
152     {
153       OFOStringStream out;
154       Uint32 operationStatus = DVPSIPCMessage::statusError;
155       if (cond.good())
156       {
157         if (rsp.DimseStatus == STATUS_Success) operationStatus = DVPSIPCMessage::statusOK;
158         else operationStatus = DVPSIPCMessage::statusWarning;
159       }
160       const char *sopClassName = dcmFindNameOfUID(sopClass);
161       const char *successName = "failed";
162       if (operationStatus == DVPSIPCMessage::statusOK) successName = "successful";
163       if (sopClassName==NULL) sopClassName = sopClass;
164       unsigned long fileSize = 0;
165       struct stat fileStat;
166       if (0 == stat(imgFile, &fileStat)) fileSize = fileStat.st_size;
167       out << "DICOM C-STORE transmission " << successName << ": " << OFendl
168           << "\tSOP class UID          : " << sopClassName << OFendl
169           << "\tSOP instance UID       : " << sopInstance << OFendl
170           << "\tSource file path       : " << imgFile << OFendl
171           << "\tSource file size (kB)  : " << (fileSize+1023)/1024 << OFendl
172           << "\tDIMSE presentation ctx : " << (int)presId << OFendl
173           << "\tDIMSE message ID       : " << req.MessageID << OFendl
174           << "\tDIMSE status           : " << DU_cstoreStatusString(rsp.DimseStatus) << OFendl
175           << OFStringStream_ends;
176       OFSTRINGSTREAM_GETSTR(out, theString)
177       messageClient->notifySentDICOMObject(operationStatus, theString);
178       OFSTRINGSTREAM_FREESTR(theString)
179     }
180     return cond;
181 }
182 
183 /** sends a complete DICOM study, series or a single instance
184  *  over an association which must be already established.
185  *  The instances (files) to be transmitted are derived from the database.
186  *  @param handle open database handle
187  *  @param assoc DICOM network association
188  *  @param studyUID Study Instance UID of the study/series/image to be transmitted.
189  *  @param seriesUID Series Instance UID of the series/image to be transmitted.
190  *    If NULL, a complete study is transmitted.
191  *  @param instanceUID SOP Instance UID of the image to be transmitted.
192  *    If NULL, a complete series is transmitted.
193  *  @return EC_Normal if successful, a different DIMSE code otherwise.
194  */
195 
sendStudy(DcmQueryRetrieveIndexDatabaseHandle & handle,T_ASC_Association * assoc,const char * studyUID,const char * seriesUID,const char * instanceUID)196 static OFCondition sendStudy(
197   DcmQueryRetrieveIndexDatabaseHandle &handle,
198   T_ASC_Association *assoc,
199   const char *studyUID,
200   const char *seriesUID,
201   const char *instanceUID)
202 {
203     if ((assoc==NULL)||(studyUID==NULL)) return DIMSE_NULLKEY;
204 
205     /* build query */
206     DcmDataset query;
207     OFCondition cond = DVPSHelper::putStringValue(&query, DCM_StudyInstanceUID, studyUID);
208     if (cond.bad()) return cond;
209     if (seriesUID && instanceUID)
210     {
211       cond = DVPSHelper::putStringValue(&query, DCM_QueryRetrieveLevel, "IMAGE");
212       if (cond.bad()) return cond;
213       cond = DVPSHelper::putStringValue(&query, DCM_SeriesInstanceUID, seriesUID);
214       if (cond.bad()) return cond;
215       cond = DVPSHelper::putStringValue(&query, DCM_SOPInstanceUID, instanceUID);
216       if (cond.bad()) return cond;
217 
218       OFLOG_INFO(dcmpssndLogger, "Sending at IMAGE level:" << OFendl
219                               << "  Study Instance UID : " << studyUID << OFendl
220                               << "  Series Instance UID: " << seriesUID << OFendl
221                               << "  SOP Instance UID   : " << instanceUID);
222     }
223     else if (seriesUID)
224     {
225       cond = DVPSHelper::putStringValue(&query, DCM_QueryRetrieveLevel, "SERIES");
226       if (cond.bad()) return cond;
227       cond = DVPSHelper::putStringValue(&query, DCM_SeriesInstanceUID, seriesUID);
228       if (cond.bad()) return cond;
229 
230       OFLOG_INFO(dcmpssndLogger, "Sending at SERIES level:" << OFendl
231                               << "  Study Instance UID : " << studyUID << OFendl
232                               << "  Series Instance UID: " << seriesUID);
233     }
234     else
235     {
236       cond = DVPSHelper::putStringValue(&query, DCM_QueryRetrieveLevel, "STUDY");
237       if (cond.bad()) return cond;
238 
239       OFLOG_INFO(dcmpssndLogger, "Sending at STUDY level:" << OFendl
240                               << "  Study Instance UID : " << studyUID);
241     }
242 
243     DcmQueryRetrieveDatabaseStatus dbStatus(STATUS_Pending);
244     DIC_UI sopClass;
245     DIC_UI sopInstance;
246     char imgFile[MAXPATHLEN+1];
247     DIC_US nRemaining = 0;
248 
249     cond = handle.startMoveRequest(UID_MOVEStudyRootQueryRetrieveInformationModel, &query, &dbStatus);
250     if (cond.bad()) return cond;
251 
252     while (dbStatus.status() == STATUS_Pending)
253     {
254       cond = handle.nextMoveResponse(sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), imgFile, sizeof(imgFile), &nRemaining, &dbStatus);
255       if (cond.bad()) return cond;
256 
257       if (dbStatus.status() == STATUS_Pending)
258       {
259         cond = sendImage(assoc, sopClass, sopInstance, imgFile);
260         if (cond.bad())
261         {
262           handle.cancelMoveRequest(&dbStatus);
263           return cond;
264         }
265       }
266     }
267     return cond;
268 }
269 
270 /** adds presentation contexts for all storage SOP classes
271  *  to the association parameters.
272  *  If the opt_implicitOnly flag is set, only Implicit VR Little Endian
273  *  is offered as transfer syntax. Otherwise, three xfer syntaxes are offered:
274  *  first the explicit VR with local byte ordering, followed by explicit VR
275  *  with opposite byte ordering, followed by implicit VR little endian.
276  *  @param params parameter set to which presentation contexts are added
277  *  @param opt_implicitOnly flag defining whether only Implicit VR Little Endian
278  *    should be offered as xfer syntax.
279  *  @return EC_Normal upon success, an error code otherwise.
280  */
addAllStoragePresentationContexts(T_ASC_Parameters * params,int opt_implicitOnly)281 static OFCondition addAllStoragePresentationContexts(T_ASC_Parameters *params, int opt_implicitOnly)
282 {
283     OFCondition cond = EC_Normal;
284     int pid = 1;
285 
286     const char* transferSyntaxes[3];
287     int transferSyntaxCount = 0;
288 
289     if (opt_implicitOnly)
290     {
291         transferSyntaxes[0] = UID_LittleEndianImplicitTransferSyntax;
292         transferSyntaxCount = 1;
293     } else {
294         /* gLocalByteOrder is defined in dcxfer.h */
295         if (gLocalByteOrder == EBO_LittleEndian) {
296             /* we are on a little endian machine */
297             transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
298             transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
299         } else {
300             /* we are on a big endian machine */
301             transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax;
302             transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
303         }
304         transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
305         transferSyntaxCount = 3;
306     }
307 
308     for (int i=0; i<numberOfDcmLongSCUStorageSOPClassUIDs && cond.good(); i++) {
309         cond = ASC_addPresentationContext(
310             params, pid, dcmLongSCUStorageSOPClassUIDs[i],
311             transferSyntaxes, transferSyntaxCount);
312         pid += 2;       /* only odd presentation context id's */
313     }
314 
315     return cond;
316 }
317 
318 // ********************************************
319 
320 #define SHORTCOL 3
321 #define LONGCOL 12
322 
main(int argc,char * argv[])323 int main(int argc, char *argv[])
324 {
325     OFStandard::initializeNetwork();
326 #ifdef WITH_OPENSSL
327     DcmTLSTransportLayer::initializeOpenSSL();
328 #endif
329 
330     OFString temp_str;
331 
332     const char *opt_cfgName     = NULL;                /* config file name */
333     const char *opt_target      = NULL;                /* send target name */
334     const char *opt_studyUID    = NULL;                /* study instance UID */
335     const char *opt_seriesUID   = NULL;                /* series instance UID */
336     const char *opt_instanceUID = NULL;                /* instance instance UID */
337 
338     OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION , "Network send for presentation state viewer", rcsid);
339     OFCommandLine cmd;
340     cmd.setOptionColumns(LONGCOL, SHORTCOL);
341     cmd.setParamColumn(LONGCOL + SHORTCOL + 2);
342 
343     cmd.addParam("config-file", "configuration file to be read");
344     cmd.addParam("target",      "symbolic identifier of send target in config file");
345     cmd.addParam("study",       "study instance UID of study in database to be sent");
346     cmd.addParam("series",      "series instance UID (default: send complete study)", OFCmdParam::PM_Optional);
347     cmd.addParam("instance",    "SOP instance UID (default: send complete series)", OFCmdParam::PM_Optional);
348 
349     cmd.addGroup("general options:");
350      cmd.addOption("--help",    "-h", "print this help text and exit", OFCommandLine::AF_Exclusive);
351      cmd.addOption("--version",       "print version information and exit", OFCommandLine::AF_Exclusive);
352      OFLog::addOptions(cmd);
353 
354     /* evaluate command line */
355     prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
356     if (app.parseCommandLine(cmd, argc, argv))
357     {
358       /* check exclusive options first */
359       if (cmd.hasExclusiveOption())
360       {
361         if (cmd.findOption("--version"))
362         {
363             app.printHeader(OFTrue /*print host identifier*/);
364             COUT << OFendl << "External libraries used:";
365 #if !defined(WITH_ZLIB) && !defined(WITH_OPENSSL)
366             COUT << " none" << OFendl;
367 #else
368             COUT << OFendl;
369 #endif
370 #ifdef WITH_ZLIB
371             COUT << "- ZLIB, Version " << zlibVersion() << OFendl;
372 #endif
373 #ifdef WITH_OPENSSL
374             COUT << "- " << DcmTLSTransportLayer::getOpenSSLVersionName() << OFendl;
375 #endif
376             return 0;
377          }
378       }
379 
380       /* command line parameters */
381       cmd.getParam(1, opt_cfgName);
382       cmd.getParam(2, opt_target);
383       cmd.getParam(3, opt_studyUID);
384       if (cmd.getParamCount() >= 4) cmd.getParam(4, opt_seriesUID);
385       if (cmd.getParamCount() >= 5) cmd.getParam(5, opt_instanceUID);
386 
387       OFLog::configureFromCommandLine(cmd, app);
388     }
389 
390     /* print resource identifier */
391     OFLOG_DEBUG(dcmpssndLogger, rcsid << OFendl);
392 
393     if (opt_cfgName)
394     {
395       FILE *cfgfile = fopen(opt_cfgName, "rb");
396       if (cfgfile) fclose(cfgfile); else
397       {
398         OFLOG_FATAL(dcmpssndLogger, "can't open configuration file '" << opt_cfgName << "'");
399         return 10;
400       }
401     } else {
402         OFLOG_FATAL(dcmpssndLogger, "missing configuration file name");
403         return 10;
404     }
405 
406     /* make sure data dictionary is loaded */
407     if (!dcmDataDict.isDictionaryLoaded())
408     {
409         OFLOG_WARN(dcmpssndLogger, "no data dictionary loaded, check environment variable: "
410             << DCM_DICT_ENVIRONMENT_VARIABLE);
411     }
412 
413     DVConfiguration dvi(opt_cfgName);
414 
415     /* get send target from configuration file */
416     const char *targetHostname    = dvi.getTargetHostname(opt_target);
417     const char *targetDescription = dvi.getTargetDescription(opt_target);
418     const char *targetAETitle     = dvi.getTargetAETitle(opt_target);
419     unsigned short targetPort     = dvi.getTargetPort(opt_target);
420     unsigned long  targetMaxPDU   = dvi.getTargetMaxPDU(opt_target);
421     OFBool targetImplicitOnly     = dvi.getTargetImplicitOnly(opt_target);
422     OFBool targetDisableNewVRs    = dvi.getTargetDisableNewVRs(opt_target);
423 
424     unsigned short messagePort    = dvi.getMessagePort();   /* port number for IPC */
425     OFBool keepMessagePortOpen    = dvi.getMessagePortKeepOpen();
426     OFBool useTLS = dvi.getTargetUseTLS(opt_target);
427 
428     Sint32 timeout = dvi.getTargetTimeout(opt_target);
429     if (timeout > 0) dcmConnectionTimeout.set(timeout);
430 
431 #ifdef WITH_OPENSSL
432     /* TLS directory */
433     const char *current = NULL;
434     const char *tlsFolder = dvi.getTLSFolder();
435     if (tlsFolder==NULL) tlsFolder = ".";
436 
437     /* certificate file */
438     OFString tlsCertificateFile(tlsFolder);
439     tlsCertificateFile += PATH_SEPARATOR;
440     current = dvi.getTargetCertificate(opt_target);
441     if (current) tlsCertificateFile += current; else tlsCertificateFile.clear();
442 
443     /* private key file */
444     OFString tlsPrivateKeyFile(tlsFolder);
445     tlsPrivateKeyFile += PATH_SEPARATOR;
446     current = dvi.getTargetPrivateKey(opt_target);
447     if (current) tlsPrivateKeyFile += current; else tlsPrivateKeyFile.clear();
448 
449     /* private key password */
450     const char *tlsPrivateKeyPassword = dvi.getTargetPrivateKeyPassword(opt_target);
451 
452     /* certificate verification */
453     DcmCertificateVerification tlsCertVerification = DCV_requireCertificate;
454     switch (dvi.getTargetPeerAuthentication(opt_target))
455     {
456       case DVPSQ_require:
457         tlsCertVerification = DCV_requireCertificate;
458         break;
459       case DVPSQ_verify:
460         tlsCertVerification = DCV_checkCertificate;
461         break;
462       case DVPSQ_ignore:
463         tlsCertVerification = DCV_ignoreCertificate;
464         break;
465     }
466 
467     /* DH parameter file */
468     OFString tlsDHParametersFile;
469     current = dvi.getTargetDiffieHellmanParameters(opt_target);
470     if (current)
471     {
472       tlsDHParametersFile = tlsFolder;
473       tlsDHParametersFile += PATH_SEPARATOR;
474       tlsDHParametersFile += current;
475     }
476 
477     /* random seed file */
478     OFString tlsRandomSeedFile(tlsFolder);
479     tlsRandomSeedFile += PATH_SEPARATOR;
480     current = dvi.getTargetRandomSeed(opt_target);
481     if (current) tlsRandomSeedFile += current; else tlsRandomSeedFile += "siteseed.bin";
482 
483     /* CA certificate directory */
484     const char *tlsCACertificateFolder = dvi.getTLSCACertificateFolder();
485     if (tlsCACertificateFolder==NULL) tlsCACertificateFolder = ".";
486 
487     /* key file format */
488     DcmKeyFileFormat keyFileFormat = DCF_Filetype_PEM;
489     if (! dvi.getTLSPEMFormat()) keyFileFormat = DCF_Filetype_ASN1;
490 
491 #else
492     if (useTLS)
493     {
494         OFLOG_FATAL(dcmpssndLogger, "not compiled with OpenSSL, cannot use TLS");
495         return 10;
496     }
497 #endif
498 
499     if (targetHostname==NULL)
500     {
501         OFLOG_FATAL(dcmpssndLogger, "no hostname for send target '" << opt_target << "'");
502         return 10;
503     }
504 
505     if (targetAETitle==NULL)
506     {
507         OFLOG_FATAL(dcmpssndLogger, "no aetitle for send target '" << opt_target << "'");
508         return 10;
509     }
510 
511     if (targetPort==0)
512     {
513         OFLOG_FATAL(dcmpssndLogger, "no or invalid port number for send target '" << opt_target << "'");
514         return 10;
515     }
516 
517     if (targetMaxPDU==0) targetMaxPDU = DEFAULT_MAXPDU;
518     else if (targetMaxPDU > ASC_MAXIMUMPDUSIZE)
519     {
520         OFLOG_WARN(dcmpssndLogger, "max PDU size " << targetMaxPDU << " too big for send target '"
521             << opt_target << "', using default: " << DEFAULT_MAXPDU);
522         targetMaxPDU = DEFAULT_MAXPDU;
523     }
524 
525     if (targetDisableNewVRs)
526     {
527         dcmDisableGenerationOfNewVRs();
528     }
529 
530     OFOStringStream verboseParameters;
531 
532     verboseParameters << "Send target parameters:" << OFendl
533         << "\thostname        : " << targetHostname << OFendl
534         << "\tport            : " << targetPort << OFendl
535         << "\tdescription     : ";
536     if (targetDescription) verboseParameters << targetDescription; else verboseParameters << "(none)";
537     verboseParameters << OFendl
538         << "\taetitle         : " << targetAETitle << OFendl
539         << "\tmax pdu         : " << targetMaxPDU << OFendl
540         << "\ttimeout         : " << timeout << OFendl
541         << "\toptions         : ";
542     if (targetImplicitOnly && targetDisableNewVRs) verboseParameters << "implicit xfer syntax only, disable post-1993 VRs";
543     else if (targetImplicitOnly) verboseParameters << "implicit xfer syntax only";
544     else if (targetDisableNewVRs) verboseParameters << "disable post-1993 VRs";
545     else verboseParameters << "none";
546     verboseParameters << OFendl;
547 
548     verboseParameters << "\tTLS             : ";
549     if (useTLS) verboseParameters << "enabled" << OFendl; else verboseParameters << "disabled" << OFendl;
550 
551     /* open database */
552     const char *dbfolder = dvi.getDatabaseFolder();
553 
554     OFLOG_INFO(dcmpssndLogger, "Opening database in directory '" << dbfolder << "'");
555 
556     OFCondition result;
557     DcmQueryRetrieveIndexDatabaseHandle dbhandle(dbfolder, PSTAT_MAXSTUDYCOUNT, PSTAT_STUDYSIZE, result);
558     if (result.bad())
559     {
560       OFLOG_FATAL(dcmpssndLogger, "Unable to access database '" << dbfolder << "'");
561       return 1;
562     }
563 
564 #ifdef WITH_OPENSSL
565 
566     DcmTLSTransportLayer *tLayer = NULL;
567     if (useTLS)
568     {
569       tLayer = new DcmTLSTransportLayer(NET_REQUESTOR, tlsRandomSeedFile.c_str(), OFFalse);
570       if (tLayer == NULL)
571       {
572         OFLOG_FATAL(dcmpssndLogger, "unable to create TLS transport layer");
573         return 1;
574       }
575 
576       // determine TLS profile
577       OFString profileName;
578       const char *profileNamePtr = dvi.getTargetTLSProfile(opt_target);
579       if (profileNamePtr) profileName = profileNamePtr;
580       DcmTLSSecurityProfile tlsProfile = TSP_Profile_BCP195;  // default
581       if (profileName == "BCP195") tlsProfile = TSP_Profile_BCP195;
582       else if (profileName == "BCP195-ND") tlsProfile = TSP_Profile_BCP195_ND;
583       else if (profileName == "BCP195-EX") tlsProfile = TSP_Profile_BCP195_Extended;
584       else if (profileName == "AES") tlsProfile = TSP_Profile_AES;
585       else if (profileName == "BASIC") tlsProfile = TSP_Profile_Basic;
586       else if (profileName == "NULL") tlsProfile = TSP_Profile_IHE_ATNA_Unencrypted;
587       else
588       {
589         OFLOG_WARN(dcmpssndLogger, "unknown TLS profile '" << profileName << "', ignoring");
590       }
591 
592       if (TCS_ok != tLayer->setTLSProfile(tlsProfile))
593       {
594         OFLOG_FATAL(dcmpssndLogger, "unable to select the TLS security profile");
595         return 1;
596       }
597 
598       // activate cipher suites
599       if (TCS_ok != tLayer->activateCipherSuites())
600       {
601         OFLOG_FATAL(dcmpssndLogger, "unable to activate the selected list of TLS ciphersuites");
602         return 1;
603       }
604 
605       if (tlsCACertificateFolder && (TCS_ok != tLayer->addTrustedCertificateDir(tlsCACertificateFolder, keyFileFormat)))
606       {
607         OFLOG_WARN(dcmpssndLogger, "unable to load certificates from directory '" << tlsCACertificateFolder << "', ignoring");
608       }
609       if ((tlsDHParametersFile.size() > 0) && ! (tLayer->setTempDHParameters(tlsDHParametersFile.c_str())))
610       {
611         OFLOG_WARN(dcmpssndLogger, "unable to load temporary DH parameter file '" << tlsDHParametersFile << "', ignoring");
612       }
613       tLayer->setPrivateKeyPasswd(tlsPrivateKeyPassword); // never prompt on console
614 
615       if (!tlsPrivateKeyFile.empty() && !tlsCertificateFile.empty())
616       {
617         if (TCS_ok != tLayer->setPrivateKeyFile(tlsPrivateKeyFile.c_str(), keyFileFormat))
618         {
619           OFLOG_FATAL(dcmpssndLogger, "unable to load private TLS key from '" << tlsPrivateKeyFile<< "'");
620           return 1;
621         }
622         if (TCS_ok != tLayer->setCertificateFile(tlsCertificateFile.c_str(), keyFileFormat))
623         {
624           OFLOG_FATAL(dcmpssndLogger, "unable to load certificate from '" << tlsCertificateFile << "'");
625           return 1;
626         }
627         if (! tLayer->checkPrivateKeyMatchesCertificate())
628         {
629           OFLOG_FATAL(dcmpssndLogger, "private key '" << tlsPrivateKeyFile << "' and certificate '" << tlsCertificateFile << "' do not match");
630           return 1;
631         }
632       }
633 
634       tLayer->setCertificateVerification(tlsCertVerification);
635 
636       // a generated UID contains the process ID and current time.
637       // Adding it to the PRNG seed guarantees that we have different seeds for different processes.
638       char randomUID[65];
639       dcmGenerateUniqueIdentifier(randomUID);
640       tLayer->addPRNGseed(randomUID, strlen(randomUID));
641     }
642 
643     if (useTLS)
644     {
645       OFString cslist;
646       if (tLayer) tLayer->getListOfCipherSuitesForOpenSSL(cslist);
647       verboseParameters << "\tTLS certificate : " << tlsCertificateFile << OFendl
648            << "\tTLS key file    : " << tlsPrivateKeyFile << OFendl
649            << "\tTLS DH params   : " << tlsDHParametersFile << OFendl
650            << "\tTLS PRNG seed   : " << tlsRandomSeedFile << OFendl
651            << "\tTLS CA directory: " << tlsCACertificateFolder << OFendl
652            << "\tTLS ciphersuites: " << cslist << OFendl
653            << "\tTLS key format  : ";
654       if (keyFileFormat == DCF_Filetype_PEM) verboseParameters << "PEM" << OFendl; else verboseParameters << "DER" << OFendl;
655       verboseParameters << "\tTLS cert verify : ";
656       switch (tlsCertVerification)
657       {
658           case DCV_checkCertificate:
659             verboseParameters << "verify" << OFendl;
660             break;
661           case DCV_ignoreCertificate:
662             verboseParameters << "ignore" << OFendl;
663             break;
664           default:
665             verboseParameters << "require" << OFendl;
666             break;
667       }
668     }
669 #endif
670 
671     verboseParameters << OFStringStream_ends;
672     OFSTRINGSTREAM_GETOFSTRING(verboseParameters, verboseParametersString)
673     OFLOG_INFO(dcmpssndLogger, verboseParametersString);
674 
675     /* open network connection */
676     T_ASC_Network *net=NULL;
677     T_ASC_Parameters *params=NULL;
678     DIC_NODENAME peerHost;
679     T_ASC_Association *assoc=NULL;
680 
681     OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, 30, &net);
682     if (cond.bad())
683     {
684       OFLOG_FATAL(dcmpssndLogger, DimseCondition::dump(temp_str, cond));
685       return 1;
686     }
687 
688 #ifdef WITH_OPENSSL
689     if (tLayer)
690     {
691       cond = ASC_setTransportLayer(net, tLayer, 0);
692       if (cond.bad())
693       {
694         OFLOG_FATAL(dcmpssndLogger, DimseCondition::dump(temp_str, cond));
695         return 1;
696       }
697     }
698 #endif
699 
700     cond = ASC_createAssociationParameters(&params, targetMaxPDU);
701     if (cond.bad())
702     {
703       OFLOG_FATAL(dcmpssndLogger, DimseCondition::dump(temp_str, cond));
704       return 1;
705     }
706 
707     cond = ASC_setTransportLayerType(params, useTLS);
708     if (cond.bad())
709     {
710       OFLOG_FATAL(dcmpssndLogger, DimseCondition::dump(temp_str, cond));
711       return 1;
712     }
713 
714     ASC_setAPTitles(params, dvi.getNetworkAETitle(), targetAETitle, NULL);
715 
716     sprintf(peerHost, "%s:%d", targetHostname, (int)targetPort);
717     ASC_setPresentationAddresses(params, OFStandard::getHostName().c_str(), peerHost);
718 
719     cond = addAllStoragePresentationContexts(params, targetImplicitOnly);
720     if (cond.bad())
721     {
722       OFLOG_FATAL(dcmpssndLogger, DimseCondition::dump(temp_str, cond));
723       return 1;
724     }
725 
726     if (messagePort > 0)
727     {
728       messageClient = new DVPSIPCClient(DVPSIPCMessage::clientStoreSCU, verboseParametersString.c_str(), messagePort, keepMessagePortOpen);
729       if (! messageClient->isServerActive())
730       {
731         OFLOG_WARN(dcmpssndLogger, "no IPC message server found at port " << messagePort << ", disabling IPC");
732       }
733     }
734 
735     /* create association */
736     OFLOG_INFO(dcmpssndLogger, "Requesting Association");
737 
738     cond = ASC_requestAssociation(net, params, &assoc);
739     if (cond.bad())
740     {
741         if (cond == DUL_ASSOCIATIONREJECTED)
742         {
743             T_ASC_RejectParameters rej;
744 
745             ASC_getRejectParameters(params, &rej);
746             OFLOG_ERROR(dcmpssndLogger, "Association Rejected" << OFendl << ASC_printRejectParameters(temp_str, &rej));
747             if (messageClient)
748             {
749               // notify about rejected association
750               OFOStringStream out;
751               out << "DIMSE Association Rejected:" << OFendl
752                   << "\t" << ASC_printRejectParameters(temp_str, &rej) << OFendl;
753               out << "\tcalled presentation address: " << assoc->params->DULparams.calledPresentationAddress << OFendl
754                   << "\tcalling AE title: " << assoc->params->DULparams.callingAPTitle << OFendl
755                   << "\tcalled AE title: " << assoc->params->DULparams.calledAPTitle << OFendl;
756               out << ASC_dumpConnectionParameters(temp_str, assoc) << OFendl;
757               out << OFStringStream_ends;
758               OFSTRINGSTREAM_GETSTR(out, theString)
759               if (useTLS)
760                 messageClient->notifyRequestedEncryptedDICOMConnection(DVPSIPCMessage::statusError, theString);
761                 else messageClient->notifyRequestedUnencryptedDICOMConnection(DVPSIPCMessage::statusError, theString);
762               messageClient->notifyApplicationTerminates(DVPSIPCMessage::statusError);
763               OFSTRINGSTREAM_FREESTR(theString)
764               delete messageClient;
765             }
766             return 1;
767         } else {
768           OFLOG_ERROR(dcmpssndLogger, "Association Request Failed " << DimseCondition::dump(temp_str, cond));
769             if (messageClient)
770             {
771               // notify about rejected association
772               OFOStringStream out;
773               out << "DIMSE Association Request Failed:" << OFendl
774                   << "\tcalled presentation address: " << assoc->params->DULparams.calledPresentationAddress << OFendl
775                   << "\tcalling AE title: " << assoc->params->DULparams.callingAPTitle << OFendl
776                   << "\tcalled AE title: " << assoc->params->DULparams.calledAPTitle << OFendl
777                   << ASC_dumpConnectionParameters(temp_str, assoc) << OFendl
778                   << cond.text() << OFendl << OFStringStream_ends;
779               OFSTRINGSTREAM_GETSTR(out, theString)
780               if (useTLS)
781                 messageClient->notifyRequestedEncryptedDICOMConnection(DVPSIPCMessage::statusError, theString);
782                 else messageClient->notifyRequestedUnencryptedDICOMConnection(DVPSIPCMessage::statusError, theString);
783               messageClient->notifyApplicationTerminates(DVPSIPCMessage::statusError);
784               OFSTRINGSTREAM_FREESTR(theString)
785               delete messageClient;
786             }
787             return 1;
788         }
789     }
790 
791     if (ASC_countAcceptedPresentationContexts(params) == 0)
792     {
793       OFLOG_ERROR(dcmpssndLogger, "No Acceptable Presentation Contexts");
794       cond = ASC_abortAssociation(assoc);
795       if (cond.bad())
796       {
797         OFLOG_ERROR(dcmpssndLogger, "Association Abort Failed\n" << DimseCondition::dump(temp_str, cond));
798       }
799       if (messageClient)
800       {
801         // notify about rejected association
802         OFOStringStream out;
803         out << "DIMSE association accepted, but no acceptable presentation contexts - aborting" << OFendl
804             << "\tcalled presentation address: " << assoc->params->DULparams.calledPresentationAddress << OFendl
805             << "\tcalling AE title: " << assoc->params->DULparams.callingAPTitle << OFendl
806             << "\tcalled AE title: " << assoc->params->DULparams.calledAPTitle << OFendl
807             << ASC_dumpConnectionParameters(temp_str, assoc) << OFendl
808             << OFStringStream_ends;
809         OFSTRINGSTREAM_GETSTR(out, theString)
810         if (useTLS)
811           messageClient->notifyRequestedEncryptedDICOMConnection(DVPSIPCMessage::statusError, theString);
812           else messageClient->notifyRequestedUnencryptedDICOMConnection(DVPSIPCMessage::statusError, theString);
813         messageClient->notifyApplicationTerminates(DVPSIPCMessage::statusError);
814         OFSTRINGSTREAM_FREESTR(theString)
815         delete messageClient;
816       }
817       return 1;
818     }
819 
820     OFLOG_INFO(dcmpssndLogger, "Association accepted (Max Send PDV: " << assoc->sendPDVLength << ")");
821 
822     if (messageClient)
823     {
824       // notify about successfully negotiated association
825       OFOStringStream out;
826       out << "DIMSE Association Accepted:" << OFendl
827           << "\tcalled presentation address: " << assoc->params->DULparams.calledPresentationAddress << OFendl
828           << "\tcalling AE title: " << assoc->params->DULparams.callingAPTitle << OFendl
829           << "\tcalled AE title: " << assoc->params->DULparams.calledAPTitle << OFendl
830           << "\tmax send PDV: " << assoc->sendPDVLength << OFendl
831           << "\tpresentation contexts: " << ASC_countAcceptedPresentationContexts(assoc->params) << OFendl;
832       out << ASC_dumpConnectionParameters(temp_str, assoc) << OFendl;
833       out << OFStringStream_ends;
834       OFSTRINGSTREAM_GETSTR(out, theString)
835       if (useTLS)
836         messageClient->notifyRequestedEncryptedDICOMConnection(DVPSIPCMessage::statusOK, theString);
837         else messageClient->notifyRequestedUnencryptedDICOMConnection(DVPSIPCMessage::statusOK, theString);
838       OFSTRINGSTREAM_FREESTR(theString)
839     }
840 
841 
842     /* do the real work */
843     cond = sendStudy(dbhandle, assoc, opt_studyUID, opt_seriesUID, opt_instanceUID);
844 
845     /* tear down association */
846     if (cond.good())
847     {
848         /* release association */
849         OFLOG_INFO(dcmpssndLogger, "Releasing Association");
850         cond = ASC_releaseAssociation(assoc);
851         if (cond.bad())
852         {
853           OFLOG_ERROR(dcmpssndLogger, "Association Release Failed\n" << DimseCondition::dump(temp_str, cond));
854           if (messageClient)
855           {
856             messageClient->notifyApplicationTerminates(DVPSIPCMessage::statusError);
857             delete messageClient;
858           }
859           return 1;
860         }
861         if (messageClient) messageClient->notifyConnectionClosed(DVPSIPCMessage::statusOK);
862     }
863     else if (cond == DUL_PEERREQUESTEDRELEASE)
864     {
865         OFLOG_ERROR(dcmpssndLogger, "Protocol Error: peer requested release (Aborting)");
866         OFLOG_INFO(dcmpssndLogger, "Aborting Association");
867         cond = ASC_abortAssociation(assoc);
868         if (cond.bad())
869         {
870             OFLOG_ERROR(dcmpssndLogger, "Association Abort Failed" << DimseCondition::dump(temp_str, cond));
871             if (messageClient)
872             {
873               messageClient->notifyApplicationTerminates(DVPSIPCMessage::statusError);
874               delete messageClient;
875             }
876             return 1;
877         }
878         if (messageClient) messageClient->notifyConnectionAborted(DVPSIPCMessage::statusError, "Protocol error: peer requested release, aborting association");
879     }
880     else if (cond == DUL_PEERABORTEDASSOCIATION)
881     {
882         OFLOG_INFO(dcmpssndLogger, "Peer Aborted Association");
883         if (messageClient) messageClient->notifyConnectionAborted(DVPSIPCMessage::statusError, "Peer aborted association");
884     }
885     else
886     {
887         OFLOG_ERROR(dcmpssndLogger, "SCU Failed" << DimseCondition::dump(temp_str, cond));
888         OFLOG_INFO(dcmpssndLogger, "Aborting Association");
889         cond = ASC_abortAssociation(assoc);
890         if (cond.bad())
891         {
892             OFLOG_ERROR(dcmpssndLogger, "Association Abort Failed" << DimseCondition::dump(temp_str, cond));
893             if (messageClient)
894             {
895               messageClient->notifyApplicationTerminates(DVPSIPCMessage::statusError);
896               delete messageClient;
897             }
898             return 1;
899         }
900         if (messageClient) messageClient->notifyConnectionAborted(DVPSIPCMessage::statusError, "Storage SCU failed, aborting association");
901     }
902 
903     cond = ASC_destroyAssociation(&assoc);
904     if (cond.bad())
905     {
906       OFLOG_ERROR(dcmpssndLogger, DimseCondition::dump(temp_str, cond));
907       if (messageClient)
908       {
909         messageClient->notifyApplicationTerminates(DVPSIPCMessage::statusError);
910         delete messageClient;
911       }
912       return 1;
913     }
914     cond = ASC_dropNetwork(&net);
915     if (cond.bad())
916     {
917       OFLOG_ERROR(dcmpssndLogger, DimseCondition::dump(temp_str, cond));
918       if (messageClient)
919       {
920         messageClient->notifyApplicationTerminates(DVPSIPCMessage::statusError);
921         delete messageClient;
922       }
923       return 1;
924     }
925 
926     // tell the IPC server that we're going to terminate.
927     // We need to do this before we shutdown WinSock.
928     if (messageClient)
929     {
930       messageClient->notifyApplicationTerminates(DVPSIPCMessage::statusOK);
931       delete messageClient;
932     }
933 
934     OFStandard::shutdownNetwork();
935 
936 #ifdef WITH_OPENSSL
937     if (tLayer)
938     {
939       if (tLayer->canWriteRandomSeed())
940       {
941         if (!tLayer->writeRandomSeed(tlsRandomSeedFile.c_str()))
942         {
943           OFLOG_WARN(dcmpssndLogger, "cannot write back random seed file '" << tlsRandomSeedFile << "', ignoring");
944         }
945       } else {
946         OFLOG_WARN(dcmpssndLogger, "cannot write back random seed, ignoring");
947       }
948     }
949     delete tLayer;
950 #endif
951 
952 #ifdef DEBUG
953     dcmDataDict.clear();  /* useful for debugging with dmalloc */
954 #endif
955 
956     return 0;
957 }
958