1 /*
2  *
3  *  Copyright (C) 1996-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:  dcmnet
15  *
16  *  Author:  Andrew Hewett
17  *
18  *  Purpose: Storage Service Class User (C-STORE operation)
19  *
20  */
21 
22 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
23 
24 #define INCLUDE_CSTDLIB
25 #define INCLUDE_CSTDIO
26 #define INCLUDE_CSTRING
27 #define INCLUDE_CCTYPE
28 #include "dcmtk/ofstd/ofstdinc.h"
29 
30 BEGIN_EXTERN_C
31 #ifdef HAVE_SYS_FILE_H
32 #include <sys/file.h>
33 #endif
34 END_EXTERN_C
35 
36 #include "dcmtk/ofstd/ofstd.h"
37 #include "dcmtk/ofstd/ofconapp.h"
38 #include "dcmtk/ofstd/ofstring.h"
39 #include "dcmtk/ofstd/ofstream.h"
40 #include "dcmtk/dcmnet/dicom.h"      /* for DICOM_APPLICATION_REQUESTOR */
41 #include "dcmtk/dcmnet/dimse.h"
42 #include "dcmtk/dcmnet/diutil.h"
43 #include "dcmtk/dcmnet/dcmtrans.h"   /* for dcmSocketSend/ReceiveTimeout */
44 #include "dcmtk/dcmnet/dcasccfg.h"   /* for class DcmAssociationConfiguration */
45 #include "dcmtk/dcmnet/dcasccff.h"   /* for class DcmAssociationConfigurationFile */
46 #include "dcmtk/dcmdata/dcdatset.h"
47 #include "dcmtk/dcmdata/dcmetinf.h"
48 #include "dcmtk/dcmdata/dcfilefo.h"
49 #include "dcmtk/dcmdata/dcuid.h"
50 #include "dcmtk/dcmdata/dcdict.h"
51 #include "dcmtk/dcmdata/dcdeftag.h"
52 #include "dcmtk/dcmdata/cmdlnarg.h"
53 #include "dcmtk/dcmdata/dcuid.h"     /* for dcmtk version name */
54 #include "dcmtk/dcmdata/dcostrmz.h"  /* for dcmZlibCompressionLevel */
55 #include "dcmtk/dcmtls/tlsopt.h"     /* for DcmTLSOptions */
56 
57 #ifdef ON_THE_FLY_COMPRESSION
58 #include "dcmtk/dcmjpeg/djdecode.h"  /* for JPEG decoders */
59 #include "dcmtk/dcmjpeg/djencode.h"  /* for JPEG encoders */
60 #include "dcmtk/dcmjpls/djdecode.h"  /* for JPEG-LS decoders */
61 #include "dcmtk/dcmjpls/djencode.h"  /* for JPEG-LS encoders */
62 #include "dcmtk/dcmdata/dcrledrg.h"  /* for RLE decoder */
63 #include "dcmtk/dcmdata/dcrleerg.h"  /* for RLE encoder */
64 #include "dcmtk/dcmjpeg/dipijpeg.h"  /* for dcmimage JPEG plugin */
65 #endif
66 
67 #ifdef WITH_ZLIB
68 #include <zlib.h>          /* for zlibVersion() */
69 #endif
70 
71 #if defined (HAVE_WINDOWS_H) || defined(HAVE_FNMATCH_H)
72 #define PATTERN_MATCHING_AVAILABLE
73 #endif
74 
75 #define OFFIS_CONSOLE_APPLICATION "storescu"
76 
77 static OFLogger storescuLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION);
78 
79 static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v"
80   OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";
81 
82 /* default application titles */
83 #define APPLICATIONTITLE     "STORESCU"
84 #define PEERAPPLICATIONTITLE "ANY-SCP"
85 
86 static OFBool opt_showPresentationContexts = OFFalse;
87 static OFBool opt_abortAssociation = OFFalse;
88 static OFCmdUnsignedInt opt_maxReceivePDULength = ASC_DEFAULTMAXPDU;
89 static OFCmdUnsignedInt opt_maxSendPDULength = 0;
90 static E_TransferSyntax opt_networkTransferSyntax = EXS_Unknown;
91 static E_FileReadMode opt_readMode = ERM_autoDetect;
92 
93 static OFBool opt_scanDir = OFFalse;
94 static OFBool opt_recurse = OFFalse;
95 static OFBool opt_renameFile = OFFalse;
96 static const char *opt_scanPattern = "";
97 
98 static OFBool opt_haltOnUnsuccessfulStore = OFTrue;
99 static OFBool unsuccessfulStoreEncountered = OFFalse;
100 static int lastStatusCode = STATUS_Success;
101 
102 static OFBool opt_proposeOnlyRequiredPresentationContexts = OFFalse;
103 static OFBool opt_combineProposedTransferSyntaxes = OFFalse;
104 
105 static OFCmdUnsignedInt opt_repeatCount = 1;
106 static OFCmdUnsignedInt opt_inventPatientCount = 25;
107 static OFCmdUnsignedInt opt_inventStudyCount = 50;
108 static OFCmdUnsignedInt opt_inventSeriesCount = 100;
109 static OFBool opt_inventSOPInstanceInformation = OFFalse;
110 static OFBool opt_correctUIDPadding = OFFalse;
111 static OFString patientNamePrefix("OFFIS^TEST_PN_");   // PatientName is PN (maximum 16 chars)
112 static OFString patientIDPrefix("PID_"); // PatientID is LO (maximum 64 chars)
113 static OFString studyIDPrefix("SID_");   // StudyID is SH (maximum 16 chars)
114 static OFString accessionNumberPrefix;   // AccessionNumber is SH (maximum 16 chars)
115 static const char *opt_configFile = NULL;
116 static const char *opt_profileName = NULL;
117 T_DIMSE_BlockingMode opt_blockMode = DIMSE_BLOCKING;
118 int opt_dimse_timeout = 0;
119 int opt_acse_timeout = 30;
120 OFCmdSignedInt opt_socket_timeout = 60;
121 
122 #ifdef WITH_ZLIB
123 static OFCmdUnsignedInt opt_compressionLevel = 0;
124 #endif
125 
126 // User Identity Negotiation
127 static T_ASC_UserIdentityNegotiationMode opt_identMode = ASC_USER_IDENTITY_NONE;
128 static OFString opt_user;
129 static OFString opt_password;
130 static OFString opt_identFile;
131 static OFBool opt_identResponse = OFFalse;
132 
133 static OFCondition
134 addStoragePresentationContexts(T_ASC_Parameters *params, OFList<OFString> &sopClasses);
135 
136 static OFCondition
137 cstore(T_ASC_Association *assoc, const OFString &fname);
138 
139 static OFBool
140 findSOPClassAndInstanceInFile(
141   const char *fname,
142   char *sopClass,
143   size_t sopClassSize,
144   char *sopInstance,
145   size_t sopInstanceSize);
146 
147 static OFCondition
148 configureUserIdentityRequest(T_ASC_Parameters *params);
149 
150 static OFCondition
151 checkUserIdentityResponse(T_ASC_Parameters *params);
152 
153 /* helper macro for converting stream output to a string */
154 #define CONVERT_TO_STRING(output, string) \
155     optStream.str(""); \
156     optStream.clear(); \
157     optStream << output << OFStringStream_ends; \
158     OFSTRINGSTREAM_GETOFSTRING(optStream, string)
159 
160 #define SHORTCOL 4
161 #define LONGCOL 19
162 
main(int argc,char * argv[])163 int main(int argc, char *argv[])
164 {
165   OFOStringStream optStream;
166 
167   const char *opt_peer = NULL;
168   OFCmdUnsignedInt opt_port = 104;
169   const char *opt_peerTitle = PEERAPPLICATIONTITLE;
170   const char *opt_ourTitle = APPLICATIONTITLE;
171 
172   OFList<OFString> fileNameList;       // list of files to transfer to SCP
173   OFList<OFString> sopClassUIDList;    // the list of SOP classes
174   OFList<OFString> sopInstanceUIDList; // the list of SOP instances
175 
176   T_ASC_Network *net;
177   T_ASC_Parameters *params;
178   DIC_NODENAME peerHost;
179   T_ASC_Association *assoc;
180   DcmAssociationConfiguration asccfg;  // handler for association configuration profiles
181   DcmTLSOptions tlsOptions(NET_REQUESTOR);
182 
183   OFStandard::initializeNetwork();
184 #ifdef WITH_OPENSSL
185   DcmTLSTransportLayer::initializeOpenSSL();
186 #endif
187 
188   OFString temp_str;
189   OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION , "DICOM storage (C-STORE) SCU", rcsid);
190   OFCommandLine cmd;
191 
192   cmd.setParamColumn(LONGCOL + SHORTCOL + 4);
193   cmd.addParam("peer", "hostname of DICOM peer");
194   cmd.addParam("port", "tcp/ip port number of peer");
195   cmd.addParam("dcmfile-in", "DICOM file or directory to be transmitted", OFCmdParam::PM_MultiMandatory);
196 
197   cmd.setOptionColumns(LONGCOL, SHORTCOL);
198   cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
199    cmd.addOption("--help",                    "-h",      "print this help text and exit", OFCommandLine::AF_Exclusive);
200    cmd.addOption("--version",                            "print version information and exit", OFCommandLine::AF_Exclusive);
201    OFLog::addOptions(cmd);
202    cmd.addOption("--verbose-pc",              "+v",      "show presentation contexts in verbose mode");
203 
204   cmd.addGroup("input options:");
205     cmd.addSubGroup("input file format:");
206       cmd.addOption("--read-file",            "+f",      "read file format or data set (default)");
207       cmd.addOption("--read-file-only",       "+fo",     "read file format only");
208       cmd.addOption("--read-dataset",         "-f",      "read data set without file meta information");
209     cmd.addSubGroup("input files:");
210       cmd.addOption("--scan-directories",     "+sd",     "scan directories for input files (dcmfile-in)");
211 #ifdef PATTERN_MATCHING_AVAILABLE
212       cmd.addOption("--scan-pattern",         "+sp",  1, "[p]attern: string (only with --scan-directories)",
213                                                          "pattern for filename matching (wildcards)");
214 #endif
215       cmd.addOption("--no-recurse",           "-r",      "do not recurse within directories (default)");
216       cmd.addOption("--recurse",              "+r",      "recurse within specified directories");
217       cmd.addOption("--no-rename",            "-rn",     "do not rename processed files (default)");
218       cmd.addOption("--rename",               "+rn",     "append .done/.bad to processed files");
219   cmd.addGroup("network options:");
220     cmd.addSubGroup("application entity titles:");
221       cmd.addOption("--aetitle",              "-aet", 1, "[a]etitle: string", "set my calling AE title (default: " APPLICATIONTITLE ")");
222       cmd.addOption("--call",                 "-aec", 1, "[a]etitle: string", "set called AE title of peer (default: " PEERAPPLICATIONTITLE ")");
223     cmd.addSubGroup("association negotiation profile from configuration file:");
224       cmd.addOption("--config-file",          "-xf",  2, "[f]ilename, [p]rofile: string",
225                                                          "use profile p from config file f");
226     cmd.addSubGroup("proposed transmission transfer syntaxes (not with --config-file):");
227       cmd.addOption("--propose-uncompr",      "-x=",     "propose all uncompressed TS, explicit VR\nwith local byte ordering first (default)");
228       cmd.addOption("--propose-little",       "-xe",     "propose all uncompressed TS, explicit VR\nlittle endian first");
229       cmd.addOption("--propose-big",          "-xb",     "propose all uncompressed TS, explicit VR\nbig endian first");
230       cmd.addOption("--propose-implicit",     "-xi",     "propose implicit VR little endian TS only");
231       cmd.addOption("--propose-lossless",     "-xs",     "propose default JPEG lossless TS\nand all uncompressed transfer syntaxes");
232       cmd.addOption("--propose-jpeg8",        "-xy",     "propose default JPEG lossy TS for 8 bit data\nand all uncompressed transfer syntaxes");
233       cmd.addOption("--propose-jpeg12",       "-xx",     "propose default JPEG lossy TS for 12 bit data\nand all uncompressed transfer syntaxes");
234       cmd.addOption("--propose-j2k-lossless", "-xv",     "propose JPEG 2000 lossless TS\nand all uncompressed transfer syntaxes");
235       cmd.addOption("--propose-j2k-lossy",    "-xw",     "propose JPEG 2000 lossy TS\nand all uncompressed transfer syntaxes");
236       cmd.addOption("--propose-jls-lossless", "-xt",     "propose JPEG-LS lossless TS\nand all uncompressed transfer syntaxes");
237       cmd.addOption("--propose-jls-lossy",    "-xu",     "propose JPEG-LS lossy TS\nand all uncompressed transfer syntaxes");
238       cmd.addOption("--propose-mpeg2",        "-xm",     "propose MPEG2 Main Profile @ Main Level TS");
239       cmd.addOption("--propose-mpeg2-high",   "-xh",     "propose MPEG2 Main Profile @ High Level TS");
240       cmd.addOption("--propose-mpeg4",        "-xn",     "propose MPEG4 AVC/H.264 HP / Level 4.1 TS");
241       cmd.addOption("--propose-mpeg4-bd",     "-xl",     "propose MPEG4 AVC/H.264 BD-compatible TS");
242       cmd.addOption("--propose-mpeg4-2-2d",   "-x2",     "propose MPEG4 AVC/H.264 HP / Level 4.2 TS (2D)");
243       cmd.addOption("--propose-mpeg4-2-3d",   "-x3",     "propose MPEG4 AVC/H.264 HP / Level 4.2 TS (3D)");
244       cmd.addOption("--propose-mpeg4-2-st",   "-xo",     "propose MPEG4 AVC/H.264 Stereo / Level 4.2 TS");
245       cmd.addOption("--propose-hevc",         "-x4",     "propose HEVC/H.265 Main Profile / Level 5.1 TS");
246       cmd.addOption("--propose-hevc10",       "-x5",     "propose HEVC/H.265 Main 10 Profile / Level 5.1 TS");
247       cmd.addOption("--propose-rle",          "-xr",     "propose RLE lossless TS\nand all uncompressed transfer syntaxes");
248 #ifdef WITH_ZLIB
249       cmd.addOption("--propose-deflated",     "-xd",     "propose deflated expl. VR little endian TS\nand all uncompressed transfer syntaxes");
250 #endif
251       cmd.addOption("--required",             "-R",      "propose only required presentation contexts\n(default: propose all supported)");
252       cmd.addOption("--combine",              "+C",      "combine proposed transfer syntaxes\n(default: separate pres. context for each TS)");
253     cmd.addSubGroup("post-1993 value representations:");
254       cmd.addOption("--enable-new-vr",        "+u",      "enable support for new VRs (UN/UT) (default)");
255       cmd.addOption("--disable-new-vr",       "-u",      "disable support for new VRs, convert to OB");
256 #ifdef WITH_ZLIB
257     cmd.addSubGroup("deflate compression level (only with --propose-deflated or --config-file):");
258       cmd.addOption("--compression-level",    "+cl",  1, "[l]evel: integer (default: 6)",
259                                                          "0=uncompressed, 1=fastest, 9=best compression");
260 #endif
261     cmd.addSubGroup("user identity negotiation:");
262       cmd.addOption("--user",                 "-usr", 1, "[u]ser name: string",
263                                                          "authenticate using user name u");
264       cmd.addOption("--password",             "-pwd", 1, "[p]assword: string (only with --user)",
265                                                          "authenticate using password p");
266       cmd.addOption("--empty-password",       "-epw",    "send empty password (only with --user)");
267       cmd.addOption("--kerberos",             "-kt",  1, "[f]ilename: string",
268                                                          "read kerberos ticket from file f");
269       cmd.addOption("--saml",                         1, "[f]ilename: string",
270                                                          "read SAML request from file f");
271       cmd.addOption("--jwt",                          1, "[f]ilename: string",
272                                                          "read JWT data from file f");
273       cmd.addOption("--pos-response",         "-rsp",    "expect positive response");
274     cmd.addSubGroup("other network options:");
275       cmd.addOption("--timeout",              "-to",  1, "[s]econds: integer (default: unlimited)", "timeout for connection requests");
276       CONVERT_TO_STRING("[s]econds: integer (default: " << opt_socket_timeout << ")", optString1);
277       cmd.addOption("--socket-timeout",       "-ts",  1, optString1.c_str(), "timeout for network socket (0 for none)");
278       CONVERT_TO_STRING("[s]econds: integer (default: " << opt_acse_timeout << ")", optString2);
279       cmd.addOption("--acse-timeout",         "-ta",  1, optString2.c_str(), "timeout for ACSE messages");
280       cmd.addOption("--dimse-timeout",        "-td",  1, "[s]econds: integer (default: unlimited)", "timeout for DIMSE messages");
281       CONVERT_TO_STRING("[n]umber of bytes: integer (" << ASC_MINIMUMPDUSIZE << ".." << ASC_MAXIMUMPDUSIZE << ")", optString3);
282       CONVERT_TO_STRING("set max receive pdu to n bytes (default: " << opt_maxReceivePDULength << ")", optString4);
283       cmd.addOption("--max-pdu",              "-pdu", 1, optString3.c_str(), optString4.c_str());
284       cmd.addOption("--max-send-pdu",                 1, optString3.c_str(), "restrict max send pdu to n bytes");
285       cmd.addOption("--repeat",                       1, "[n]umber: integer", "repeat n times");
286       cmd.addOption("--abort",                           "abort association instead of releasing it");
287       cmd.addOption("--no-halt",              "-nh",     "do not halt if unsuccessful store encountered\n(default: do halt)");
288       cmd.addOption("--uid-padding",          "-up",     "silently correct space-padded UIDs");
289 
290       cmd.addOption("--invent-instance",      "+II",     "invent a new SOP instance UID for every image\nsent");
291       CONVERT_TO_STRING("invent a new series UID after n images" << OFendl << "have been sent (default: " << opt_inventSeriesCount << ")", optString5);
292       cmd.addOption("--invent-series",        "+IR",  1, "[n]umber: integer (implies --invent-instance)", optString5.c_str());
293       CONVERT_TO_STRING("invent a new study UID after n series" << OFendl << "have been sent (default: " << opt_inventStudyCount << ")", optString6);
294       cmd.addOption("--invent-study",         "+IS",  1, "[n]umber: integer (implies --invent-instance)", optString6.c_str());
295       CONVERT_TO_STRING("invent a new patient ID and name after n studies" << OFendl << "have been sent (default: " << opt_inventPatientCount << ")", optString7);
296       cmd.addOption("--invent-patient",       "+IP",  1, "[n]umber: integer (implies --invent-instance)", optString7.c_str());
297 
298     // add TLS specific command line options if (and only if) we are compiling with OpenSSL
299     tlsOptions.addTLSCommandlineOptions(cmd);
300 
301     /* evaluate command line */
302     prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
303     if (app.parseCommandLine(cmd, argc, argv))
304     {
305       /* check exclusive options first */
306       if (cmd.hasExclusiveOption())
307       {
308         if (cmd.findOption("--version"))
309         {
310           app.printHeader(OFTrue /*print host identifier*/);
311           COUT << OFendl << "External libraries used:";
312 #if !defined(WITH_ZLIB) && !(ON_THE_FLY_COMPRESSION) && !defined(WITH_OPENSSL)
313           COUT << " none" << OFendl;
314 #else
315           COUT << OFendl;
316 #endif
317 #ifdef WITH_ZLIB
318           COUT << "- ZLIB, Version " << zlibVersion() << OFendl;
319 #endif
320 #ifdef ON_THE_FLY_COMPRESSION
321           COUT << "- " << DiJPEGPlugin::getLibraryVersionString() << OFendl;
322           COUT << "- " << DJLSDecoderRegistration::getLibraryVersionString() << OFendl;
323 #endif
324           // print OpenSSL version if (and only if) we are compiling with OpenSSL
325           tlsOptions.printLibraryVersion();
326           return 0;
327         }
328 
329         // check if the command line contains the --list-ciphers option
330         if (tlsOptions.listOfCiphersRequested(cmd))
331         {
332             tlsOptions.printSupportedCiphersuites(app, COUT);
333             return 0;
334         }
335       }
336 
337       /* command line parameters */
338 
339       cmd.getParam(1, opt_peer);
340       app.checkParam(cmd.getParamAndCheckMinMax(2, opt_port, 1, 65535));
341 
342       OFLog::configureFromCommandLine(cmd, app);
343       if (cmd.findOption("--verbose-pc"))
344       {
345         app.checkDependence("--verbose-pc", "verbose mode", storescuLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL));
346         opt_showPresentationContexts = OFTrue;
347       }
348 
349       cmd.beginOptionBlock();
350       if (cmd.findOption("--read-file")) opt_readMode = ERM_autoDetect;
351       if (cmd.findOption("--read-file-only")) opt_readMode = ERM_fileOnly;
352       if (cmd.findOption("--read-dataset")) opt_readMode = ERM_dataset;
353       cmd.endOptionBlock();
354 
355       if (cmd.findOption("--scan-directories")) opt_scanDir = OFTrue;
356 #ifdef PATTERN_MATCHING_AVAILABLE
357       if (cmd.findOption("--scan-pattern"))
358       {
359         app.checkDependence("--scan-pattern", "--scan-directories", opt_scanDir);
360         app.checkValue(cmd.getValue(opt_scanPattern));
361       }
362 #endif
363       cmd.beginOptionBlock();
364       if (cmd.findOption("--no-recurse")) opt_recurse = OFFalse;
365       if (cmd.findOption("--recurse"))
366       {
367         app.checkDependence("--recurse", "--scan-directories", opt_scanDir);
368         opt_recurse = OFTrue;
369       }
370       cmd.endOptionBlock();
371 
372       cmd.beginOptionBlock();
373       if (cmd.findOption("--no-rename")) opt_renameFile = OFFalse;
374       if (cmd.findOption("--rename")) opt_renameFile = OFTrue;
375       cmd.endOptionBlock();
376 
377       if (cmd.findOption("--aetitle")) app.checkValue(cmd.getValue(opt_ourTitle));
378       if (cmd.findOption("--call")) app.checkValue(cmd.getValue(opt_peerTitle));
379 
380       cmd.beginOptionBlock();
381       if (cmd.findOption("--propose-uncompr")) opt_networkTransferSyntax = EXS_Unknown;
382       if (cmd.findOption("--propose-little")) opt_networkTransferSyntax = EXS_LittleEndianExplicit;
383       if (cmd.findOption("--propose-big")) opt_networkTransferSyntax = EXS_BigEndianExplicit;
384       if (cmd.findOption("--propose-implicit")) opt_networkTransferSyntax = EXS_LittleEndianImplicit;
385       if (cmd.findOption("--propose-lossless")) opt_networkTransferSyntax = EXS_JPEGProcess14SV1;
386       if (cmd.findOption("--propose-jpeg8")) opt_networkTransferSyntax = EXS_JPEGProcess1;
387       if (cmd.findOption("--propose-jpeg12")) opt_networkTransferSyntax = EXS_JPEGProcess2_4;
388       if (cmd.findOption("--propose-j2k-lossless")) opt_networkTransferSyntax = EXS_JPEG2000LosslessOnly;
389       if (cmd.findOption("--propose-j2k-lossy")) opt_networkTransferSyntax = EXS_JPEG2000;
390       if (cmd.findOption("--propose-jls-lossless")) opt_networkTransferSyntax = EXS_JPEGLSLossless;
391       if (cmd.findOption("--propose-jls-lossy")) opt_networkTransferSyntax = EXS_JPEGLSLossy;
392       if (cmd.findOption("--propose-mpeg2")) opt_networkTransferSyntax = EXS_MPEG2MainProfileAtMainLevel;
393       if (cmd.findOption("--propose-mpeg2-high")) opt_networkTransferSyntax = EXS_MPEG2MainProfileAtHighLevel;
394       if (cmd.findOption("--propose-mpeg4")) opt_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_1;
395       if (cmd.findOption("--propose-mpeg4-bd")) opt_networkTransferSyntax = EXS_MPEG4BDcompatibleHighProfileLevel4_1;
396       if (cmd.findOption("--propose-mpeg4-2-2d")) opt_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_2_For2DVideo;
397       if (cmd.findOption("--propose-mpeg4-2-3d")) opt_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_2_For3DVideo;
398       if (cmd.findOption("--propose-mpeg4-2-st")) opt_networkTransferSyntax = EXS_MPEG4StereoHighProfileLevel4_2;
399       if (cmd.findOption("--propose-hevc")) opt_networkTransferSyntax = EXS_HEVCMainProfileLevel5_1;
400       if (cmd.findOption("--propose-hevc10")) opt_networkTransferSyntax = EXS_HEVCMain10ProfileLevel5_1;
401       if (cmd.findOption("--propose-rle")) opt_networkTransferSyntax = EXS_RLELossless;
402 #ifdef WITH_ZLIB
403       if (cmd.findOption("--propose-deflated")) opt_networkTransferSyntax = EXS_DeflatedLittleEndianExplicit;
404 #endif
405       cmd.endOptionBlock();
406 
407       if (cmd.findOption("--required")) opt_proposeOnlyRequiredPresentationContexts = OFTrue;
408       if (cmd.findOption("--combine")) opt_combineProposedTransferSyntaxes = OFTrue;
409 
410       if (cmd.findOption("--config-file"))
411       {
412         // check conflicts with other command line options
413         app.checkConflict("--config-file", "--propose-little", opt_networkTransferSyntax == EXS_LittleEndianExplicit);
414         app.checkConflict("--config-file", "--propose-big", opt_networkTransferSyntax == EXS_BigEndianExplicit);
415         app.checkConflict("--config-file", "--propose-implicit", opt_networkTransferSyntax == EXS_LittleEndianImplicit);
416         app.checkConflict("--config-file", "--propose-lossless", opt_networkTransferSyntax == EXS_JPEGProcess14SV1);
417         app.checkConflict("--config-file", "--propose-jpeg8", opt_networkTransferSyntax == EXS_JPEGProcess1);
418         app.checkConflict("--config-file", "--propose-jpeg12", opt_networkTransferSyntax == EXS_JPEGProcess2_4);
419         app.checkConflict("--config-file", "--propose-j2k-lossless", opt_networkTransferSyntax == EXS_JPEG2000LosslessOnly);
420         app.checkConflict("--config-file", "--propose-j2k-lossy", opt_networkTransferSyntax == EXS_JPEG2000);
421         app.checkConflict("--config-file", "--propose-jls-lossless", opt_networkTransferSyntax == EXS_JPEGLSLossless);
422         app.checkConflict("--config-file", "--propose-jls-lossy", opt_networkTransferSyntax == EXS_JPEGLSLossy);
423         app.checkConflict("--config-file", "--propose-mpeg2", opt_networkTransferSyntax == EXS_MPEG2MainProfileAtMainLevel);
424         app.checkConflict("--config-file", "--propose-mpeg2-high", opt_networkTransferSyntax == EXS_MPEG2MainProfileAtHighLevel);
425         app.checkConflict("--config-file", "--propose-mpeg4", opt_networkTransferSyntax == EXS_MPEG4HighProfileLevel4_1);
426         app.checkConflict("--config-file", "--propose-mpeg4-bd", opt_networkTransferSyntax == EXS_MPEG4BDcompatibleHighProfileLevel4_1);
427         app.checkConflict("--config-file", "--propose-mpeg4-2-2d", opt_networkTransferSyntax == EXS_MPEG4HighProfileLevel4_2_For2DVideo);
428         app.checkConflict("--config-file", "--propose-mpeg4-2-3d", opt_networkTransferSyntax == EXS_MPEG4HighProfileLevel4_2_For3DVideo);
429         app.checkConflict("--config-file", "--propose-mpeg4-2-st", opt_networkTransferSyntax == EXS_MPEG4StereoHighProfileLevel4_2);
430         app.checkConflict("--config-file", "--propose-hevc", opt_networkTransferSyntax == EXS_HEVCMainProfileLevel5_1);
431         app.checkConflict("--config-file", "--propose-hevc10", opt_networkTransferSyntax == EXS_HEVCMain10ProfileLevel5_1);
432         app.checkConflict("--config-file", "--propose-rle", opt_networkTransferSyntax == EXS_RLELossless);
433 #ifdef WITH_ZLIB
434         app.checkConflict("--config-file", "--propose-deflated", opt_networkTransferSyntax == EXS_DeflatedLittleEndianExplicit);
435 #endif
436         app.checkConflict("--config-file", "--required", opt_proposeOnlyRequiredPresentationContexts);
437         app.checkConflict("--config-file", "--combine", opt_combineProposedTransferSyntaxes);
438 
439         app.checkValue(cmd.getValue(opt_configFile));
440         app.checkValue(cmd.getValue(opt_profileName));
441 
442         // read configuration file. The profile name is checked later.
443         OFCondition cond = DcmAssociationConfigurationFile::initialize(asccfg, opt_configFile);
444         if (cond.bad())
445         {
446           OFLOG_ERROR(storescuLogger, "reading config file: " << cond.text());
447           return 1;
448         }
449       }
450 
451 #ifdef WITH_ZLIB
452       if (cmd.findOption("--compression-level"))
453       {
454         app.checkDependence("--compression-level", "--propose-deflated or --config-file",
455           (opt_networkTransferSyntax == EXS_DeflatedLittleEndianExplicit) || (opt_configFile != NULL));
456         app.checkValue(cmd.getValueAndCheckMinMax(opt_compressionLevel, 0, 9));
457         dcmZlibCompressionLevel.set(OFstatic_cast(int, opt_compressionLevel));
458       }
459 #endif
460 
461       cmd.beginOptionBlock();
462       if (cmd.findOption("--enable-new-vr")) dcmEnableGenerationOfNewVRs();
463       if (cmd.findOption("--disable-new-vr")) dcmDisableGenerationOfNewVRs();
464       cmd.endOptionBlock();
465 
466       if (cmd.findOption("--timeout"))
467       {
468         OFCmdSignedInt opt_timeout = 0;
469         app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
470         dcmConnectionTimeout.set(OFstatic_cast(Sint32, opt_timeout));
471       }
472 
473       if (cmd.findOption("--socket-timeout"))
474         app.checkValue(cmd.getValueAndCheckMin(opt_socket_timeout, -1));
475       // always set the timeout values since the global default might be different
476       dcmSocketSendTimeout.set(OFstatic_cast(Sint32, opt_socket_timeout));
477       dcmSocketReceiveTimeout.set(OFstatic_cast(Sint32, opt_socket_timeout));
478 
479       if (cmd.findOption("--acse-timeout"))
480       {
481         OFCmdSignedInt opt_timeout = 0;
482         app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
483         opt_acse_timeout = OFstatic_cast(int, opt_timeout);
484       }
485 
486       if (cmd.findOption("--dimse-timeout"))
487       {
488         OFCmdSignedInt opt_timeout = 0;
489         app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
490         opt_dimse_timeout = OFstatic_cast(int, opt_timeout);
491         opt_blockMode = DIMSE_NONBLOCKING;
492       }
493 
494       if (cmd.findOption("--max-pdu"))
495         app.checkValue(cmd.getValueAndCheckMinMax(opt_maxReceivePDULength, ASC_MINIMUMPDUSIZE, ASC_MAXIMUMPDUSIZE));
496 
497       if (cmd.findOption("--max-send-pdu"))
498       {
499         app.checkValue(cmd.getValueAndCheckMinMax(opt_maxSendPDULength, ASC_MINIMUMPDUSIZE, ASC_MAXIMUMPDUSIZE));
500         dcmMaxOutgoingPDUSize.set(OFstatic_cast(Uint32, opt_maxSendPDULength));
501       }
502 
503       if (cmd.findOption("--repeat"))  app.checkValue(cmd.getValueAndCheckMin(opt_repeatCount, 1));
504       if (cmd.findOption("--abort"))   opt_abortAssociation = OFTrue;
505       if (cmd.findOption("--no-halt")) opt_haltOnUnsuccessfulStore = OFFalse;
506       if (cmd.findOption("--uid-padding")) opt_correctUIDPadding = OFTrue;
507 
508       if (cmd.findOption("--invent-instance")) opt_inventSOPInstanceInformation = OFTrue;
509       if (cmd.findOption("--invent-series"))
510       {
511         opt_inventSOPInstanceInformation = OFTrue;
512         app.checkValue(cmd.getValueAndCheckMin(opt_inventSeriesCount, 1));
513       }
514       if (cmd.findOption("--invent-study"))
515       {
516         opt_inventSOPInstanceInformation = OFTrue;
517         app.checkValue(cmd.getValueAndCheckMin(opt_inventStudyCount, 1));
518       }
519       if (cmd.findOption("--invent-patient"))
520       {
521         opt_inventSOPInstanceInformation = OFTrue;
522         app.checkValue(cmd.getValueAndCheckMin(opt_inventPatientCount, 1));
523       }
524 
525       // evaluate (most of) the TLS command line options (if we are compiling with OpenSSL)
526       tlsOptions.parseArguments(app, cmd);
527 
528       // User Identity Negotiation
529       cmd.beginOptionBlock();
530       if (cmd.findOption("--user"))
531       {
532         app.checkValue(cmd.getValue(opt_user));
533         opt_identMode = ASC_USER_IDENTITY_USER;
534       }
535       if (cmd.findOption("--kerberos"))
536       {
537         app.checkValue(cmd.getValue(opt_identFile));
538         opt_identMode = ASC_USER_IDENTITY_KERBEROS;
539       }
540       if (cmd.findOption("--saml"))
541       {
542         app.checkValue(cmd.getValue(opt_identFile));
543         opt_identMode = ASC_USER_IDENTITY_SAML;
544       }
545       if (cmd.findOption("--jwt"))
546       {
547         app.checkValue(cmd.getValue(opt_identFile));
548         opt_identMode = ASC_USER_IDENTITY_JWT;
549       }
550       cmd.endOptionBlock();
551       cmd.beginOptionBlock();
552       if (cmd.findOption("--password"))
553       {
554         app.checkDependence("--password", "--user", opt_identMode == ASC_USER_IDENTITY_USER);
555         app.checkValue(cmd.getValue(opt_password));
556         opt_identMode = ASC_USER_IDENTITY_USER_PASSWORD;
557       }
558       if (cmd.findOption("--empty-password"))
559       {
560         app.checkDependence("--empty-password", "--user", opt_identMode == ASC_USER_IDENTITY_USER);
561         opt_password= "";
562         opt_identMode = ASC_USER_IDENTITY_USER_PASSWORD;
563       }
564       cmd.endOptionBlock();
565       if (cmd.findOption("--pos-response"))
566       {
567          app.checkDependence("--pos-response", "--user, --kerberos or --saml", opt_identMode != ASC_USER_IDENTITY_NONE);
568          opt_identResponse = OFTrue;
569       }
570    }
571 
572     /* print resource identifier */
573     OFLOG_DEBUG(storescuLogger, rcsid << OFendl);
574 
575     /* make sure data dictionary is loaded */
576     if (!dcmDataDict.isDictionaryLoaded())
577     {
578       OFLOG_WARN(storescuLogger, "no data dictionary loaded, check environment variable: "
579           << DCM_DICT_ENVIRONMENT_VARIABLE);
580     }
581 
582     /* finally, create list of input files */
583     const char *paramString = NULL;
584     const int paramCount = cmd.getParamCount();
585     OFList<OFString> inputFiles;
586     if (opt_scanDir)
587       OFLOG_INFO(storescuLogger, "determining input files ...");
588     /* iterate over all input filenames/directories */
589     for (int i = 3; i <= paramCount; i++)
590     {
591       cmd.getParam(i, paramString);
592       /* search directory recursively (if required) */
593       if (OFStandard::dirExists(paramString))
594       {
595         if (opt_scanDir)
596           OFStandard::searchDirectoryRecursively(paramString, inputFiles, opt_scanPattern, "" /*dirPrefix*/, opt_recurse);
597         else
598           OFLOG_WARN(storescuLogger, "ignoring directory because option --scan-directories is not set: " << paramString);
599       } else
600         inputFiles.push_back(paramString);
601     }
602     /* check whether there are any input files at all */
603     if (inputFiles.empty())
604     {
605       OFLOG_FATAL(storescuLogger, "no input files to be sent");
606       exit(1);
607     }
608 
609     /* check input files */
610     OFString errormsg;
611     DcmFileFormat dfile;
612     char sopClassUID[128];
613     char sopInstanceUID[128];
614     OFBool ignoreName;
615     const char *currentFilename = NULL;
616     OFListIterator(OFString) if_iter = inputFiles.begin();
617     OFListIterator(OFString) if_last = inputFiles.end();
618     OFLOG_INFO(storescuLogger, "checking input files ...");
619     /* iterate over all input filenames */
620     while (if_iter != if_last)
621     {
622       ignoreName = OFFalse;
623       currentFilename = (*if_iter).c_str();
624       if (OFStandard::fileExists(currentFilename))
625       {
626         if (opt_proposeOnlyRequiredPresentationContexts)
627         {
628           if (!findSOPClassAndInstanceInFile(currentFilename, sopClassUID, sizeof(sopClassUID), sopInstanceUID, sizeof(sopInstanceUID)))
629           {
630             ignoreName = OFTrue;
631             errormsg = "missing SOP class (or instance) in file: ";
632             errormsg += currentFilename;
633             if (opt_haltOnUnsuccessfulStore)
634             {
635               OFLOG_FATAL(storescuLogger, errormsg);
636               exit(1);
637             }
638             else
639               OFLOG_WARN(storescuLogger, errormsg << ", ignoring file");
640           }
641           else if (!dcmIsaStorageSOPClassUID(sopClassUID, ESSC_All))
642           {
643             ignoreName = OFTrue;
644             errormsg = "unknown storage SOP class in file: ";
645             errormsg += currentFilename;
646             errormsg += ": ";
647             errormsg += sopClassUID;
648             if (opt_haltOnUnsuccessfulStore)
649             {
650               OFLOG_FATAL(storescuLogger, errormsg);
651               exit(1);
652             }
653             else
654               OFLOG_WARN(storescuLogger, errormsg << ", ignoring file");
655           }
656           else
657           {
658             sopClassUIDList.push_back(sopClassUID);
659             sopInstanceUIDList.push_back(sopInstanceUID);
660           }
661         }
662         if (!ignoreName) fileNameList.push_back(currentFilename);
663       }
664       else
665       {
666         errormsg = "cannot access file: ";
667         errormsg += currentFilename;
668         if (opt_haltOnUnsuccessfulStore)
669         {
670           OFLOG_FATAL(storescuLogger, errormsg);
671           exit(1);
672         }
673         else
674           OFLOG_WARN(storescuLogger, errormsg << ", ignoring file");
675       }
676       ++if_iter;
677     }
678 
679 #ifdef ON_THE_FLY_COMPRESSION
680     // register global JPEG decompression codecs
681     DJDecoderRegistration::registerCodecs();
682 
683     // register global JPEG compression codecs
684     DJEncoderRegistration::registerCodecs();
685 
686     // register JPEG-LS decompression codecs
687     DJLSDecoderRegistration::registerCodecs();
688 
689     // register JPEG-LS compression codecs
690     DJLSEncoderRegistration::registerCodecs();
691 
692     // register RLE compression codec
693     DcmRLEEncoderRegistration::registerCodecs();
694 
695     // register RLE decompression codec
696     DcmRLEDecoderRegistration::registerCodecs();
697 #endif
698 
699     /* initialize network, i.e. create an instance of T_ASC_Network*. */
700     OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, opt_acse_timeout, &net);
701     if (cond.bad()) {
702       OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
703       return 1;
704     }
705 
706     /* initialize association parameters, i.e. create an instance of T_ASC_Parameters*. */
707     cond = ASC_createAssociationParameters(&params, opt_maxReceivePDULength);
708     if (cond.bad()) {
709       OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
710       return 1;
711     }
712 
713     /* create a secure transport layer if requested and OpenSSL is available */
714     cond = tlsOptions.createTransportLayer(net, params, app, cmd);
715     if (cond.bad()) {
716         OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
717         return 1;
718     }
719 
720     /* sets this application's title and the called application's title in the params */
721     /* structure. The default values to be set here are "STORESCU" and "ANY-SCP". */
722     ASC_setAPTitles(params, opt_ourTitle, opt_peerTitle, NULL);
723 
724     /* Figure out the presentation addresses and copy the */
725     /* corresponding values into the association parameters.*/
726     sprintf(peerHost, "%s:%d", opt_peer, OFstatic_cast(int, opt_port));
727     ASC_setPresentationAddresses(params, OFStandard::getHostName().c_str(), peerHost);
728 
729     /* Configure User Identity Negotiation*/
730     if (opt_identMode != ASC_USER_IDENTITY_NONE)
731     {
732       cond = configureUserIdentityRequest(params);
733       if (cond.bad())
734         return 1;
735     }
736 
737     if (opt_profileName)
738     {
739       /* perform name mangling for config file key */
740       OFString sprofile;
741       const unsigned char *c = OFreinterpret_cast(const unsigned char *, opt_profileName);
742       while (*c)
743       {
744         if (!isspace(*c)) sprofile += OFstatic_cast(char, toupper(*c));
745         ++c;
746       }
747 
748       /* set presentation contexts as defined in config file */
749       cond = asccfg.setAssociationParameters(sprofile.c_str(), *params);
750     }
751     else
752     {
753       /* Set the presentation contexts which will be negotiated */
754       /* when the network connection will be established */
755       cond = addStoragePresentationContexts(params, sopClassUIDList);
756     }
757 
758     if (cond.bad()) {
759       OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
760       return 1;
761     }
762 
763     /* dump presentation contexts if required */
764     if (opt_showPresentationContexts)
765       OFLOG_INFO(storescuLogger, "Request Parameters:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ));
766     else
767       OFLOG_DEBUG(storescuLogger, "Request Parameters:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ));
768 
769     /* create association, i.e. try to establish a network connection to another */
770     /* DICOM application. This call creates an instance of T_ASC_Association*. */
771     OFLOG_INFO(storescuLogger, "Requesting Association");
772     cond = ASC_requestAssociation(net, params, &assoc);
773     if (cond.bad()) {
774       if (cond == DUL_ASSOCIATIONREJECTED) {
775         T_ASC_RejectParameters rej;
776 
777         ASC_getRejectParameters(params, &rej);
778         OFLOG_FATAL(storescuLogger, "Association Rejected:" << OFendl << ASC_printRejectParameters(temp_str, &rej));
779         return 1;
780       } else {
781         OFLOG_FATAL(storescuLogger, "Association Request Failed: " << DimseCondition::dump(temp_str, cond));
782         return 1;
783       }
784     }
785 
786     /* dump the connection parameters if in debug mode*/
787     OFLOG_DEBUG(storescuLogger, ASC_dumpConnectionParameters(temp_str, assoc));
788 
789     /* dump the presentation contexts which have been accepted/refused */
790     if (opt_showPresentationContexts)
791       OFLOG_INFO(storescuLogger, "Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_AC));
792     else
793       OFLOG_DEBUG(storescuLogger, "Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_AC));
794 
795     /* count the presentation contexts which have been accepted by the SCP */
796     /* If there are none, finish the execution */
797     if (ASC_countAcceptedPresentationContexts(params) == 0) {
798       OFLOG_FATAL(storescuLogger, "No Acceptable Presentation Contexts");
799       return 1;
800     }
801 
802     /* check user authentication response (if applicable) */
803     cond = checkUserIdentityResponse(params);
804     if (cond.bad())
805     {
806       OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
807       return 1;
808     }
809     /* dump general information concerning the establishment of the network connection if required */
810     OFLOG_INFO(storescuLogger, "Association Accepted (Max Send PDV: " << assoc->sendPDVLength << ")");
811 
812     /* do the real work, i.e. for all files which were specified in the */
813     /* command line, transmit the encapsulated DICOM objects to the SCP. */
814     cond = EC_Normal;
815     OFListIterator(OFString) iter = fileNameList.begin();
816     OFListIterator(OFString) enditer = fileNameList.end();
817 
818     while ((iter != enditer) && cond.good())
819     {
820       cond = cstore(assoc, *iter);
821       ++iter;
822     }
823 
824     /* tear down association, i.e. terminate network connection to SCP */
825     if (cond == EC_Normal)
826     {
827       if (opt_abortAssociation) {
828         OFLOG_INFO(storescuLogger, "Aborting Association");
829         cond = ASC_abortAssociation(assoc);
830         if (cond.bad()) {
831           OFLOG_ERROR(storescuLogger, "Association Abort Failed: " << DimseCondition::dump(temp_str, cond));
832           return 1;
833         }
834       } else {
835         /* release association */
836         OFLOG_INFO(storescuLogger, "Releasing Association");
837         cond = ASC_releaseAssociation(assoc);
838         if (cond.bad())
839         {
840           OFLOG_ERROR(storescuLogger, "Association Release Failed: " << DimseCondition::dump(temp_str, cond));
841           return 1;
842         }
843       }
844     }
845     else if (cond == DUL_PEERREQUESTEDRELEASE)
846     {
847       OFLOG_ERROR(storescuLogger, "Protocol Error: Peer requested release (Aborting)");
848       OFLOG_INFO(storescuLogger, "Aborting Association");
849       cond = ASC_abortAssociation(assoc);
850       if (cond.bad()) {
851         OFLOG_ERROR(storescuLogger, "Association Abort Failed: " << DimseCondition::dump(temp_str, cond));
852         return 1;
853       }
854     }
855     else if (cond == DUL_PEERABORTEDASSOCIATION)
856     {
857       OFLOG_INFO(storescuLogger, "Peer Aborted Association");
858     }
859     else
860     {
861       OFLOG_ERROR(storescuLogger, "Store SCU Failed: " << DimseCondition::dump(temp_str, cond));
862       OFLOG_INFO(storescuLogger, "Aborting Association");
863       cond = ASC_abortAssociation(assoc);
864       if (cond.bad()) {
865         OFLOG_ERROR(storescuLogger, "Association Abort Failed: " << DimseCondition::dump(temp_str, cond));
866         return 1;
867       }
868     }
869 
870     /* destroy the association, i.e. free memory of T_ASC_Association* structure. This */
871     /* call is the counterpart of ASC_requestAssociation(...) which was called above. */
872     cond = ASC_destroyAssociation(&assoc);
873     if (cond.bad()) {
874       OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
875       return 1;
876     }
877     /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
878     /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
879     cond = ASC_dropNetwork(&net);
880     if (cond.bad()) {
881       OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
882       return 1;
883     }
884 
885     OFStandard::shutdownNetwork();
886 
887     cond = tlsOptions.writeRandomSeed();
888     if (cond.bad()) {
889         // failure to write back the random seed is a warning, not an error
890         OFLOG_WARN(storescuLogger, DimseCondition::dump(temp_str, cond));
891     }
892 
893     int exitCode = 0;
894     if (opt_haltOnUnsuccessfulStore && unsuccessfulStoreEncountered) {
895       if (lastStatusCode == STATUS_Success) {
896         // there must have been some kind of general network error
897         exitCode = 0xff;
898       } else {
899         exitCode = (lastStatusCode >> 8); // only the least significant byte is relevant as exit code
900       }
901     }
902 
903 #ifdef ON_THE_FLY_COMPRESSION
904     // deregister JPEG codecs
905     DJDecoderRegistration::cleanup();
906     DJEncoderRegistration::cleanup();
907 
908     // deregister JPEG-LS codecs
909     DJLSDecoderRegistration::cleanup();
910     DJLSEncoderRegistration::cleanup();
911 
912     // deregister RLE codecs
913     DcmRLEDecoderRegistration::cleanup();
914     DcmRLEEncoderRegistration::cleanup();
915 #endif
916 
917 #ifdef DEBUG
918     dcmDataDict.clear();  /* useful for debugging with dmalloc */
919 #endif
920     return exitCode;
921 }
922 
923 
924 static OFBool
isaListMember(OFList<OFString> & lst,OFString & s)925 isaListMember(OFList<OFString> &lst, OFString &s)
926 {
927   OFListIterator(OFString) cur = lst.begin();
928   OFListIterator(OFString) end = lst.end();
929 
930   OFBool found = OFFalse;
931   while (cur != end && !found) {
932     found = (s == *cur);
933     ++cur;
934   }
935 
936   return found;
937 }
938 
939 static OFCondition
addPresentationContext(T_ASC_Parameters * params,int presentationContextId,const OFString & abstractSyntax,const OFString & transferSyntax,T_ASC_SC_ROLE proposedRole=ASC_SC_ROLE_DEFAULT)940 addPresentationContext(T_ASC_Parameters *params,
941   int presentationContextId,
942   const OFString &abstractSyntax,
943   const OFString &transferSyntax,
944   T_ASC_SC_ROLE proposedRole = ASC_SC_ROLE_DEFAULT)
945 {
946   const char *c_p = transferSyntax.c_str();
947   OFCondition cond = ASC_addPresentationContext(params, presentationContextId,
948     abstractSyntax.c_str(), &c_p, 1, proposedRole);
949   return cond;
950 }
951 
952 static OFCondition
addPresentationContext(T_ASC_Parameters * params,int presentationContextId,const OFString & abstractSyntax,const OFList<OFString> & transferSyntaxList,T_ASC_SC_ROLE proposedRole=ASC_SC_ROLE_DEFAULT)953 addPresentationContext(T_ASC_Parameters *params,
954   int presentationContextId,
955   const OFString &abstractSyntax,
956   const OFList<OFString> &transferSyntaxList,
957   T_ASC_SC_ROLE proposedRole = ASC_SC_ROLE_DEFAULT)
958 {
959   // create an array of supported/possible transfer syntaxes
960   const char **transferSyntaxes = new const char*[transferSyntaxList.size()];
961   int transferSyntaxCount = 0;
962   OFListConstIterator(OFString) s_cur = transferSyntaxList.begin();
963   OFListConstIterator(OFString) s_end = transferSyntaxList.end();
964   while (s_cur != s_end) {
965     transferSyntaxes[transferSyntaxCount++] = (*s_cur).c_str();
966     ++s_cur;
967   }
968 
969   OFCondition cond = ASC_addPresentationContext(params, presentationContextId,
970     abstractSyntax.c_str(), transferSyntaxes, transferSyntaxCount, proposedRole);
971 
972   delete[] transferSyntaxes;
973   return cond;
974 }
975 
976 static OFCondition
addStoragePresentationContexts(T_ASC_Parameters * params,OFList<OFString> & sopClasses)977 addStoragePresentationContexts(T_ASC_Parameters *params,
978   OFList<OFString> &sopClasses)
979 {
980   /*
981    * Each SOP Class will be proposed in two presentation contexts (unless
982    * the opt_combineProposedTransferSyntaxes global variable is true).
983    * The command line specified a preferred transfer syntax to use.
984    * This preferred transfer syntax will be proposed in one
985    * presentation context and a set of alternative (fallback) transfer
986    * syntaxes will be proposed in a different presentation context.
987    *
988    * Generally, we prefer to use Explicitly encoded transfer syntaxes
989    * and if running on a Little Endian machine we prefer
990    * LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
991    * Some SCP implementations will just select the first transfer
992    * syntax they support (this is not part of the standard) so
993    * organise the proposed transfer syntaxes to take advantage
994    * of such behaviour.
995    */
996 
997   // Which transfer syntax was preferred on the command line
998   OFString preferredTransferSyntax;
999   if (opt_networkTransferSyntax == EXS_Unknown) {
1000     /* gLocalByteOrder is defined in dcxfer.h */
1001     if (gLocalByteOrder == EBO_LittleEndian) {
1002       /* we are on a little endian machine */
1003       preferredTransferSyntax = UID_LittleEndianExplicitTransferSyntax;
1004     } else {
1005       /* we are on a big endian machine */
1006       preferredTransferSyntax = UID_BigEndianExplicitTransferSyntax;
1007     }
1008   } else {
1009     DcmXfer xfer(opt_networkTransferSyntax);
1010     preferredTransferSyntax = xfer.getXferID();
1011   }
1012 
1013   OFListIterator(OFString) s_cur;
1014   OFListIterator(OFString) s_end;
1015 
1016   OFList<OFString> fallbackSyntaxes;
1017   // - If little endian implicit is preferred, we don't need any fallback syntaxes
1018   //   because it is the default transfer syntax and all applications must support it.
1019   // - If MPEG2, MPEG4 or HEVC is preferred, we don't want to propose any fallback solution
1020   //   because this is not required and we cannot decompress the movie anyway.
1021   if ((opt_networkTransferSyntax != EXS_LittleEndianImplicit) &&
1022       (opt_networkTransferSyntax != EXS_MPEG2MainProfileAtMainLevel) &&
1023       (opt_networkTransferSyntax != EXS_MPEG2MainProfileAtHighLevel) &&
1024       (opt_networkTransferSyntax != EXS_MPEG4HighProfileLevel4_1) &&
1025       (opt_networkTransferSyntax != EXS_MPEG4BDcompatibleHighProfileLevel4_1) &&
1026       (opt_networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For2DVideo) &&
1027       (opt_networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For3DVideo) &&
1028       (opt_networkTransferSyntax != EXS_MPEG4StereoHighProfileLevel4_2) &&
1029       (opt_networkTransferSyntax != EXS_HEVCMainProfileLevel5_1) &&
1030       (opt_networkTransferSyntax != EXS_HEVCMain10ProfileLevel5_1))
1031   {
1032     fallbackSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
1033     fallbackSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
1034     fallbackSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
1035     // Remove the preferred syntax from the fallback list
1036     fallbackSyntaxes.remove(preferredTransferSyntax);
1037   }
1038 
1039   // create a list of transfer syntaxes combined from the preferred and fallback syntaxes
1040   OFList<OFString> combinedSyntaxes;
1041   s_cur = fallbackSyntaxes.begin();
1042   s_end = fallbackSyntaxes.end();
1043   combinedSyntaxes.push_back(preferredTransferSyntax);
1044   while (s_cur != s_end)
1045   {
1046     if (!isaListMember(combinedSyntaxes, *s_cur)) combinedSyntaxes.push_back(*s_cur);
1047     ++s_cur;
1048   }
1049 
1050   if (!opt_proposeOnlyRequiredPresentationContexts) {
1051     // add the (short list of) known storage SOP classes to the list
1052     // the array of Storage SOP Class UIDs comes from dcuid.h
1053     for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++)
1054       sopClasses.push_back(dcmShortSCUStorageSOPClassUIDs[i]);
1055   }
1056 
1057   // thin out the SOP classes to remove any duplicates
1058   OFList<OFString> sops;
1059   s_cur = sopClasses.begin();
1060   s_end = sopClasses.end();
1061   while (s_cur != s_end) {
1062     if (!isaListMember(sops, *s_cur)) {
1063       sops.push_back(*s_cur);
1064     }
1065     ++s_cur;
1066   }
1067 
1068   // add a presentations context for each SOP class / transfer syntax pair
1069   OFCondition cond = EC_Normal;
1070   int pid = 1; // presentation context id
1071   s_cur = sops.begin();
1072   s_end = sops.end();
1073   while (s_cur != s_end && cond.good()) {
1074 
1075     if (pid > 255) {
1076       OFLOG_ERROR(storescuLogger, "Too many presentation contexts");
1077       return ASC_BADPRESENTATIONCONTEXTID;
1078     }
1079 
1080     if (opt_combineProposedTransferSyntaxes) {
1081       cond = addPresentationContext(params, pid, *s_cur, combinedSyntaxes);
1082       pid += 2;   /* only odd presentation context id's */
1083     } else {
1084 
1085       // SOP class with preferred transfer syntax
1086       cond = addPresentationContext(params, pid, *s_cur, preferredTransferSyntax);
1087       pid += 2;   /* only odd presentation context id's */
1088 
1089       if (fallbackSyntaxes.size() > 0) {
1090         if (pid > 255) {
1091           OFLOG_ERROR(storescuLogger, "Too many presentation contexts");
1092           return ASC_BADPRESENTATIONCONTEXTID;
1093         }
1094 
1095         // SOP class with fallback transfer syntax
1096         cond = addPresentationContext(params, pid, *s_cur, fallbackSyntaxes);
1097         pid += 2; /* only odd presentation context id's */
1098       }
1099     }
1100     ++s_cur;
1101   }
1102 
1103   return cond;
1104 }
1105 
1106 static int
secondsSince1970()1107 secondsSince1970()
1108 {
1109   time_t t = time(NULL);
1110   return OFstatic_cast(int, t);
1111 }
1112 
1113 static OFString
intToString(int i)1114 intToString(int i)
1115 {
1116   char numbuf[32];
1117   sprintf(numbuf, "%d", i);
1118   return numbuf;
1119 }
1120 
1121 static OFString
makeUID(OFString basePrefix,int counter)1122 makeUID(OFString basePrefix, int counter)
1123 {
1124   OFString prefix = basePrefix + "." + intToString(counter);
1125   char uidbuf[65];
1126   OFString uid = dcmGenerateUniqueIdentifier(uidbuf, prefix.c_str());
1127   return uid;
1128 }
1129 
1130 static OFBool
updateStringAttributeValue(DcmItem * dataset,const DcmTagKey & key,OFString & value)1131 updateStringAttributeValue(DcmItem *dataset, const DcmTagKey &key, OFString &value)
1132 {
1133   DcmStack stack;
1134   DcmTag tag(key);
1135 
1136   OFCondition cond = EC_Normal;
1137   cond = dataset->search(key, stack, ESM_fromHere, OFFalse);
1138   if (cond != EC_Normal) {
1139     OFLOG_ERROR(storescuLogger, "updateStringAttributeValue: cannot find: " << tag.getTagName()
1140          << " " << key << ": " << cond.text());
1141     return OFFalse;
1142   }
1143 
1144   DcmElement *elem = OFstatic_cast(DcmElement *, stack.top());
1145 
1146   DcmVR vr(elem->ident());
1147   if (elem->getLength() > vr.getMaxValueLength()) {
1148     OFLOG_ERROR(storescuLogger, "updateStringAttributeValue: INTERNAL ERROR: " << tag.getTagName()
1149          << " " << key << ": value too large (max " << vr.getMaxValueLength()
1150          << ") for " << vr.getVRName() << " value: " << value);
1151     return OFFalse;
1152   }
1153 
1154   cond = elem->putOFStringArray(value);
1155   if (cond != EC_Normal) {
1156     OFLOG_ERROR(storescuLogger, "updateStringAttributeValue: cannot put string in attribute: " << tag.getTagName()
1157          << " " << key << ": " << cond.text());
1158     return OFFalse;
1159   }
1160 
1161   return OFTrue;
1162 }
1163 
1164 static void
replaceSOPInstanceInformation(DcmDataset * dataset)1165 replaceSOPInstanceInformation(DcmDataset *dataset)
1166 {
1167   static OFCmdUnsignedInt patientCounter = 0;
1168   static OFCmdUnsignedInt studyCounter = 0;
1169   static OFCmdUnsignedInt seriesCounter = 0;
1170   static OFCmdUnsignedInt imageCounter = 0;
1171   static OFString seriesInstanceUID;
1172   static OFString seriesNumber;
1173   static OFString studyInstanceUID;
1174   static OFString studyID;
1175   static OFString accessionNumber;
1176   static OFString patientID;
1177   static OFString patientName;
1178 
1179   if (seriesInstanceUID.empty()) seriesInstanceUID=makeUID(SITE_SERIES_UID_ROOT, OFstatic_cast(int, seriesCounter));
1180   if (seriesNumber.empty()) seriesNumber = intToString(OFstatic_cast(int, seriesCounter));
1181   if (studyInstanceUID.empty()) studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, OFstatic_cast(int, studyCounter));
1182   if (studyID.empty()) studyID = studyIDPrefix + intToString(OFstatic_cast(int, secondsSince1970())) + intToString(OFstatic_cast(int, studyCounter));
1183   if (accessionNumber.empty()) accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, studyCounter));
1184   if (patientID.empty()) patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
1185   if (patientName.empty()) patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
1186 
1187   if (imageCounter >= opt_inventSeriesCount) {
1188     imageCounter = 0;
1189     seriesCounter++;
1190     seriesInstanceUID = makeUID(SITE_SERIES_UID_ROOT, OFstatic_cast(int, seriesCounter));
1191     seriesNumber = intToString(OFstatic_cast(int, seriesCounter));
1192   }
1193   if (seriesCounter >= opt_inventStudyCount) {
1194     seriesCounter = 0;
1195     studyCounter++;
1196     studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, OFstatic_cast(int, studyCounter));
1197     studyID = studyIDPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, studyCounter));
1198     accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, studyCounter));
1199   }
1200   if (studyCounter >= opt_inventPatientCount) {
1201     // we create as many patients as necessary */
1202     studyCounter = 0;
1203     patientCounter++;
1204     patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
1205     patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
1206   }
1207 
1208   OFString sopInstanceUID = makeUID(SITE_INSTANCE_UID_ROOT, OFstatic_cast(int, imageCounter));
1209   OFString imageNumber = intToString(OFstatic_cast(int, imageCounter));
1210 
1211   OFLOG_INFO(storescuLogger, "Inventing Identifying Information ("
1212          << "pa" << patientCounter << ", st" << studyCounter
1213          << ", se" << seriesCounter << ", im" << imageCounter << "):");
1214   OFLOG_INFO(storescuLogger, "  PatientName=" << patientName);
1215   OFLOG_INFO(storescuLogger, "  PatientID=" << patientID);
1216   OFLOG_INFO(storescuLogger, "  StudyInstanceUID=" << studyInstanceUID);
1217   OFLOG_INFO(storescuLogger, "  StudyID=" << studyID);
1218   OFLOG_INFO(storescuLogger, "  SeriesInstanceUID=" << seriesInstanceUID);
1219   OFLOG_INFO(storescuLogger, "  SeriesNumber=" << seriesNumber);
1220   OFLOG_INFO(storescuLogger, "  SOPInstanceUID=" << sopInstanceUID);
1221   OFLOG_INFO(storescuLogger, "  ImageNumber=" << imageNumber);
1222 
1223   updateStringAttributeValue(dataset, DCM_PatientName, patientName);
1224   updateStringAttributeValue(dataset, DCM_PatientID, patientID);
1225   updateStringAttributeValue(dataset, DCM_StudyInstanceUID, studyInstanceUID);
1226   updateStringAttributeValue(dataset, DCM_StudyID, studyID);
1227   updateStringAttributeValue(dataset, DCM_SeriesInstanceUID, seriesInstanceUID);
1228   updateStringAttributeValue(dataset, DCM_SeriesNumber, seriesNumber);
1229   updateStringAttributeValue(dataset, DCM_SOPInstanceUID, sopInstanceUID);
1230   updateStringAttributeValue(dataset, DCM_InstanceNumber, imageNumber);
1231 
1232   imageCounter++;
1233 }
1234 
1235 static void
progressCallback(void *,T_DIMSE_StoreProgress * progress,T_DIMSE_C_StoreRQ * req)1236 progressCallback(void * /*callbackData*/,
1237   T_DIMSE_StoreProgress *progress,
1238   T_DIMSE_C_StoreRQ * req)
1239 {
1240   if (progress->state == DIMSE_StoreBegin)
1241   {
1242     OFString str;
1243     OFLOG_DEBUG(storescuLogger, DIMSE_dumpMessage(str, *req, DIMSE_OUTGOING));
1244   }
1245 
1246   // We can't use oflog for the pdu output, but we use a special logger for
1247   // generating this output. If it is set to level "INFO" we generate the
1248   // output, if it's set to "DEBUG" then we'll assume that there is debug output
1249   // generated for each PDU elsewhere.
1250   OFLogger progressLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION ".progress");
1251   if (progressLogger.getChainedLogLevel() == OFLogger::INFO_LOG_LEVEL) {
1252     switch (progress->state) {
1253       case DIMSE_StoreBegin:
1254         COUT << "XMIT: "; break;
1255       case DIMSE_StoreEnd:
1256         COUT << OFendl; break;
1257       default:
1258         COUT << "."; break;
1259     }
1260     COUT.flush();
1261   }
1262 }
1263 
1264 static void
renameFile(const char * fname,const char * fext)1265 renameFile(const char *fname, const char *fext)
1266 {
1267   if (!opt_renameFile) return;
1268   OFString fnewname(fname);
1269   fnewname += fext;
1270   if (OFStandard::renameFile(fname, fnewname))
1271     OFLOG_DEBUG(storescuLogger, "renamed file '" << fname << "' to '" << fnewname << "'");
1272   else
1273     OFLOG_WARN(storescuLogger, "cannot rename file '" << fname << "' to '" << fnewname << "'");
1274 }
1275 
1276 static OFCondition
storeSCU(T_ASC_Association * assoc,const char * fname)1277 storeSCU(T_ASC_Association *assoc, const char *fname)
1278   /*
1279    * This function will read all the information from the given file,
1280    * figure out a corresponding presentation context which will be used
1281    * to transmit the information over the network to the SCP, and it
1282    * will finally initiate the transmission of all data to the SCP.
1283    *
1284    * Parameters:
1285    *   assoc - [in] The association (network connection to another DICOM application).
1286    *   fname - [in] Name of the file which shall be processed.
1287    */
1288 {
1289   DIC_US msgId = assoc->nextMsgID++;
1290   T_ASC_PresentationContextID presID;
1291   T_DIMSE_C_StoreRQ req;
1292   T_DIMSE_C_StoreRSP rsp;
1293   DIC_UI sopClass;
1294   DIC_UI sopInstance;
1295   DcmDataset *statusDetail = NULL;
1296 
1297   unsuccessfulStoreEncountered = OFTrue; // assumption
1298 
1299   OFLOG_INFO(storescuLogger, "Sending file: " << fname);
1300 
1301   /* read information from file. After the call to DcmFileFormat::loadFile(...) the information */
1302   /* which is encapsulated in the file will be available through the DcmFileFormat object. */
1303   /* In detail, it will be available through calls to DcmFileFormat::getMetaInfo() (for */
1304   /* meta header information) and DcmFileFormat::getDataset() (for data set information). */
1305   DcmFileFormat dcmff;
1306   OFCondition cond = dcmff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, opt_readMode);
1307 
1308   /* figure out if an error occurred while the file was read */
1309   if (cond.bad())
1310   {
1311     OFLOG_ERROR(storescuLogger, "Bad DICOM file: " << fname << ": " << cond.text());
1312     renameFile(fname, ".bad");
1313     return cond;
1314   }
1315 
1316   /* if required, invent new SOP instance information for the current data set (user option) */
1317   if (opt_inventSOPInstanceInformation) {
1318     replaceSOPInstanceInformation(dcmff.getDataset());
1319   }
1320 
1321   /* figure out which SOP class and SOP instance is encapsulated in the file */
1322   if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(),
1323     sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), opt_correctUIDPadding))
1324   {
1325     OFLOG_ERROR(storescuLogger, "No SOP Class or Instance UID in file: " << fname);
1326     renameFile(fname, ".bad");
1327     return DIMSE_BADDATA;
1328   }
1329 
1330   /* figure out which of the accepted presentation contexts should be used */
1331   DcmXfer filexfer(dcmff.getDataset()->getOriginalXfer());
1332 
1333   /* special case: if the file uses an unencapsulated transfer syntax (uncompressed
1334    * or deflated explicit VR) and we prefer deflated explicit VR, then try
1335    * to find a presentation context for deflated explicit VR first.
1336    */
1337   if (filexfer.isNotEncapsulated() &&
1338     opt_networkTransferSyntax == EXS_DeflatedLittleEndianExplicit)
1339   {
1340     filexfer = EXS_DeflatedLittleEndianExplicit;
1341   }
1342 
1343   if (filexfer.getXfer() != EXS_Unknown)
1344     presID = ASC_findAcceptedPresentationContextID(assoc, sopClass, filexfer.getXferID());
1345   else
1346     presID = ASC_findAcceptedPresentationContextID(assoc, sopClass);
1347   if (presID == 0)
1348   {
1349     const char *modalityName = dcmSOPClassUIDToModality(sopClass);
1350     if (!modalityName) modalityName = dcmFindNameOfUID(sopClass);
1351     if (!modalityName) modalityName = "unknown SOP class";
1352     OFLOG_ERROR(storescuLogger, "No presentation context for: (" << modalityName << ") " << sopClass);
1353     renameFile(fname, ".bad");
1354     return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
1355   }
1356 
1357   T_ASC_PresentationContext pc;
1358   ASC_findAcceptedPresentationContext(assoc->params, presID, &pc);
1359   DcmXfer netTransfer(pc.acceptedTransferSyntax);
1360 
1361   /* if required, dump general information concerning transfer syntaxes */
1362   if (storescuLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL))
1363   {
1364     DcmXfer fileTransfer(dcmff.getDataset()->getOriginalXfer());
1365     OFLOG_INFO(storescuLogger, "Converting transfer syntax: " << fileTransfer.getXferName()
1366       << " -> " << netTransfer.getXferName());
1367   }
1368 
1369 #ifdef ON_THE_FLY_COMPRESSION
1370   cond = dcmff.getDataset()->chooseRepresentation(netTransfer.getXfer(), NULL);
1371   if (cond.bad())
1372   {
1373     OFLOG_ERROR(storescuLogger, "No conversion to transfer syntax " << netTransfer.getXferName() << " possible!");
1374     renameFile(fname, ".bad");
1375     return cond;
1376   }
1377 #endif
1378 
1379   /* prepare the transmission of data */
1380   bzero(OFreinterpret_cast(char *, &req), sizeof(req));
1381   req.MessageID = msgId;
1382   OFStandard::strlcpy(req.AffectedSOPClassUID, sopClass, sizeof(req.AffectedSOPClassUID));
1383   OFStandard::strlcpy(req.AffectedSOPInstanceUID, sopInstance, sizeof(req.AffectedSOPInstanceUID));
1384   req.DataSetType = DIMSE_DATASET_PRESENT;
1385   req.Priority = DIMSE_PRIORITY_MEDIUM;
1386 
1387   /* if required, dump some more general information */
1388   OFLOG_INFO(storescuLogger, "Sending Store Request (MsgID " << msgId << ", "
1389     << dcmSOPClassUIDToModality(sopClass, "OT") << ")");
1390 
1391   /* finally conduct transmission of data */
1392   cond = DIMSE_storeUser(assoc, presID, &req,
1393     NULL, dcmff.getDataset(), progressCallback, NULL,
1394     opt_blockMode, opt_dimse_timeout,
1395     &rsp, &statusDetail, NULL, OFstatic_cast(long, OFStandard::getFileSize(fname)));
1396 
1397   /*
1398    * If store command completed normally, with a status
1399    * of success or some warning then the image was accepted.
1400    */
1401   if (cond == EC_Normal && (rsp.DimseStatus == STATUS_Success || DICOM_WARNING_STATUS(rsp.DimseStatus)))
1402   {
1403     unsuccessfulStoreEncountered = OFFalse;
1404     renameFile(fname, ".done");
1405   }
1406   else
1407   {
1408     renameFile(fname, ".bad");
1409   }
1410 
1411   /* remember the response's status for later transmissions of data */
1412   lastStatusCode = rsp.DimseStatus;
1413 
1414   /* dump some more general information */
1415   if (cond == EC_Normal)
1416   {
1417     OFString temp_str;
1418     if (storescuLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1419     {
1420       OFLOG_INFO(storescuLogger, "Received Store Response");
1421       OFLOG_DEBUG(storescuLogger, DIMSE_dumpMessage(temp_str, rsp, DIMSE_INCOMING, NULL, presID));
1422     } else {
1423       OFLOG_INFO(storescuLogger, "Received Store Response (" << DU_cstoreStatusString(rsp.DimseStatus) << ")");
1424     }
1425   }
1426   else
1427   {
1428     OFString temp_str;
1429     OFLOG_ERROR(storescuLogger, "Store Failed, file: " << fname << ":" << OFendl << DimseCondition::dump(temp_str, cond));
1430   }
1431 
1432   /* dump status detail information if there is some */
1433   if (statusDetail != NULL) {
1434     OFLOG_DEBUG(storescuLogger, "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
1435     delete statusDetail;
1436   }
1437   /* return */
1438   return cond;
1439 }
1440 
1441 
1442 static OFCondition
cstore(T_ASC_Association * assoc,const OFString & fname)1443 cstore(T_ASC_Association *assoc, const OFString &fname)
1444   /*
1445    * This function will process the given file as often as is specified by opt_repeatCount.
1446    * "Process" in this case means "read file, send C-STORE-RQ, receive C-STORE-RSP".
1447    *
1448    * Parameters:
1449    *   assoc - [in] The association (network connection to another DICOM application).
1450    *   fname - [in] Name of the file which shall be processed.
1451    */
1452 {
1453   OFCondition cond = EC_Normal;
1454 
1455   /* opt_repeatCount specifies how many times a certain file shall be processed */
1456   int n = OFstatic_cast(int, opt_repeatCount);
1457 
1458   /* as long as no error occurred and the counter does not equal 0 */
1459   while ((cond.good()) && n-- && !(opt_haltOnUnsuccessfulStore && unsuccessfulStoreEncountered))
1460   {
1461     /* process file (read file, send C-STORE-RQ, receive C-STORE-RSP) */
1462     cond = storeSCU(assoc, fname.c_str());
1463   }
1464 
1465   // we don't want to return an error code if --no-halt was specified.
1466   if (!opt_haltOnUnsuccessfulStore)
1467   {
1468     cond = EC_Normal;
1469   }
1470 
1471   /* return result value */
1472   return cond;
1473 }
1474 
1475 
1476 static OFBool
findSOPClassAndInstanceInFile(const char * fname,char * sopClass,size_t sopClassSize,char * sopInstance,size_t sopInstanceSize)1477 findSOPClassAndInstanceInFile(
1478   const char *fname,
1479   char *sopClass,
1480   size_t sopClassSize,
1481   char *sopInstance,
1482   size_t sopInstanceSize)
1483 {
1484     DcmFileFormat ff;
1485     if (!ff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, opt_readMode).good())
1486         return OFFalse;
1487 
1488     /* look in the meta-header first */
1489     OFBool found = DU_findSOPClassAndInstanceInDataSet(ff.getMetaInfo(), sopClass, sopClassSize, sopInstance, sopInstanceSize, opt_correctUIDPadding);
1490 
1491     if (!found)
1492       found = DU_findSOPClassAndInstanceInDataSet(ff.getDataset(), sopClass, sopClassSize, sopInstance, sopInstanceSize, opt_correctUIDPadding);
1493 
1494     return found;
1495 }
1496 
1497 
1498 static OFCondition
configureUserIdentityRequest(T_ASC_Parameters * params)1499 configureUserIdentityRequest(T_ASC_Parameters *params)
1500 {
1501   OFCondition cond = EC_Normal;
1502   switch (opt_identMode)
1503   {
1504     case ASC_USER_IDENTITY_USER:
1505     {
1506       cond = ASC_setIdentRQUserOnly(params, opt_user, opt_identResponse);
1507       return cond;
1508     }
1509     case ASC_USER_IDENTITY_USER_PASSWORD:
1510     {
1511       cond = ASC_setIdentRQUserPassword(params, opt_user, opt_password, opt_identResponse);
1512       return cond;
1513     }
1514     case ASC_USER_IDENTITY_KERBEROS:
1515     case ASC_USER_IDENTITY_SAML:
1516     case ASC_USER_IDENTITY_JWT:
1517     {
1518       OFFile identFile;
1519       if (!identFile.fopen(opt_identFile.c_str(), "rb"))
1520       {
1521         OFString openerror;
1522         identFile.getLastErrorString(openerror);
1523         OFLOG_ERROR(storescuLogger, "Unable to open Kerberos, SAML or JWT file: " << openerror);
1524         return EC_IllegalCall;
1525       }
1526       // determine file size
1527       offile_off_t result = identFile.fseek(0, SEEK_END);
1528       if (result != 0)
1529         return EC_IllegalParameter;
1530       offile_off_t filesize = identFile.ftell();
1531       identFile.rewind();
1532       if (filesize > 65535)
1533       {
1534         OFLOG_INFO(storescuLogger, "Kerberos, SAML or JWT file is larger than 65535 bytes, bytes after that position are ignored");
1535         filesize = 65535;
1536       }
1537 
1538       char *buf = new char[OFstatic_cast(unsigned int, filesize)];
1539       size_t bytesRead = identFile.fread(buf, 1, OFstatic_cast(size_t, filesize));
1540       identFile.fclose();
1541       if (bytesRead == 0)
1542       {
1543         OFLOG_ERROR(storescuLogger, "Unable to read Kerberos, SAML or JWT info from file: File empty?");
1544         delete[] buf;
1545         return EC_IllegalCall;
1546       }
1547       // Casting to Uint16 should be safe since it is checked above that file
1548       // size does not exceed 65535 bytes.
1549       if (opt_identMode == ASC_USER_IDENTITY_KERBEROS)
1550         cond = ASC_setIdentRQKerberos(params, buf, OFstatic_cast(Uint16,bytesRead), opt_identResponse);
1551       else if (opt_identMode == ASC_USER_IDENTITY_SAML)
1552         cond = ASC_setIdentRQSaml(params, buf, OFstatic_cast(Uint16,bytesRead), opt_identResponse);
1553       else // JWT
1554         cond = ASC_setIdentRQJwt(params, buf, OFstatic_cast(Uint16,bytesRead), opt_identResponse);
1555       delete[] buf;
1556       break;
1557     }
1558     default:
1559     {
1560       cond = EC_IllegalCall;
1561     }
1562   }
1563   if (cond.bad())
1564   {
1565     OFString temp_str;
1566     OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
1567   }
1568   return cond;
1569 }
1570 
1571 static OFCondition
checkUserIdentityResponse(T_ASC_Parameters * params)1572 checkUserIdentityResponse(T_ASC_Parameters *params)
1573 {
1574   if (params == NULL)
1575     return ASC_NULLKEY;
1576 
1577   /* So far it is only checked whether a requested, positive response was
1578      actually received */
1579 
1580   // In case we sent no user identity request, there are no checks at all
1581   if ((opt_identMode == ASC_USER_IDENTITY_NONE) || (!opt_identResponse))
1582     return EC_Normal;
1583 
1584   // If positive response was requested, we expect a corresponding response
1585   if ((opt_identMode == ASC_USER_IDENTITY_USER) || (opt_identMode == ASC_USER_IDENTITY_USER_PASSWORD))
1586   {
1587     UserIdentityNegotiationSubItemAC *rsp = params->DULparams.ackUserIdentNeg;
1588     if (rsp == NULL)
1589     {
1590       OFLOG_ERROR(storescuLogger, "User Identity Negotiation failed: Positive response requested but none received");
1591       return ASC_USERIDENTIFICATIONFAILED;
1592     }
1593   }
1594   return EC_Normal;
1595 }
1596