Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ FROM eclipse-temurin:21.0.9_10-jre-noble AS smoketest-harness
COPY --from=builder /app/server/setup/server-lib /opt/engine/server-lib
COPY --from=builder /app/server/setup/extensions /opt/engine/extensions
COPY --from=builder /app/server/setup/conf /opt/engine/conf
# The launcher resolves cli-lib/ relative to its working directory, so the CLI has
# to keep the layout it has in the distribution.
COPY --from=builder /app/server/setup/cli-lib /opt/engine/cli-lib
COPY --from=builder /app/server/setup/mirth-cli-launcher.jar /opt/engine/mirth-cli-launcher.jar
COPY --from=builder /app/smoketest/build/install/smoketest-harness /harness

ENTRYPOINT ["/bin/bash", "/harness/run-harness.sh"]
Expand Down
1 change: 1 addition & 0 deletions ci/run-harness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ java \
-Doie.baseUrl="$OIE_BASE_URL" \
-Doie.configuration="$OIE_CONFIGURATION" \
-Doie.password="$OIE_PASSWORD" \
-Doie.cliHome="$ENGINE_HOME" \
${OIE_DB_DRIVER:+-Doie.db.driver="$OIE_DB_DRIVER"} \
${OIE_DB_URL:+-Doie.db.url="$OIE_DB_URL"} \
${OIE_DB_USERNAME:+-Doie.db.username="$OIE_DB_USERNAME"} \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

package com.mirth.connect.client.ui;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;

import org.apache.commons.lang3.StringUtils;

/**
Expand All @@ -15,66 +19,56 @@ public class CommandLineOptions {
private final String password;
private final String protocols;
private final String cipherSuites;
private final String pinnedClientTrust;

/**
* Parse command line arguments for Mirth client.
*/
public CommandLineOptions(String[] args) {
if (args == null) {
args = new String[0];
}

String server = "https://localhost:8443";
String version = "";
String username = "";
String password = "";
String protocols = "";
String cipherSuites = "";
String pinnedClientTrust = "";

if (args == null) {
args = new String[0];
}
Deque<String> remaining = new ArrayDeque<String>(Arrays.asList(args));
int idx = 0;
while (true) {
String arg = remaining.pollFirst();
if (arg == null) {
break;
}

if (args.length > 0) {
server = args[0];
}
if (args.length > 1) {
version = args[1];
}
if (args.length > 2) {
if (StringUtils.equalsIgnoreCase(args[2], "-ssl")) {
// <server> <version> -ssl [<protocols> [<ciphersuites> [<username> [<password>]]]]
if (args.length > 3) {
protocols = args[3];
}
if (args.length > 4) {
cipherSuites = args[4];
}
if (args.length > 5) {
username = args[5];
}
if (args.length > 6) {
password = args[6];
}
if (StringUtils.equalsIgnoreCase(arg, "-ssl")) {
protocols = StringUtils.defaultString(remaining.pollFirst());
cipherSuites = StringUtils.defaultString(remaining.pollFirst());
} else if (StringUtils.equalsIgnoreCase(arg, "-trust")) {
pinnedClientTrust = StringUtils.defaultString(remaining.pollFirst());
} else {
// <server> <version> <username> [<password> [-ssl [<protocols> [<ciphersuites>]]]]
username = args[2];
if (args.length > 3) {
password = args[3];
}
if (args.length > 4 && StringUtils.equalsIgnoreCase(args[4], "-ssl")) {
if (args.length > 5) {
protocols = args[5];
}
if (args.length > 6) {
cipherSuites = args[6];
}
switch (idx) {
case 0 -> server = arg;
case 1 -> version = arg;
case 2 -> username = arg;
case 3 -> password = arg;
default -> {} // Explicitly ignore extra arguments
}
idx++;
}
}

this.server = server;
this.version = version;
this.username = username;
this.password = password;
this.protocols = protocols;
this.cipherSuites = cipherSuites;
this.protocols = StringUtils.defaultString(protocols);
this.cipherSuites = StringUtils.defaultString(cipherSuites);
this.pinnedClientTrust = StringUtils.defaultString(pinnedClientTrust);
}

public String getServer() {
Expand All @@ -100,4 +94,8 @@ public String getProtocols() {
public String getCipherSuites() {
return cipherSuites;
}

public String getPinnedClientTrust() {
return pinnedClientTrust;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ public Void doInBackground() {

try {
String server = serverName.getText();
client = new Client(server, PlatformUI.HTTPS_PROTOCOLS, PlatformUI.HTTPS_CIPHER_SUITES);
client = new Client(server, PlatformUI.HTTPS_PROTOCOLS, PlatformUI.HTTPS_CIPHER_SUITES, PlatformUI.PINNED_CLIENT_TRUST);
PlatformUI.SERVER_URL = server;

// Attempt to login
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ public static void main(String[] args) {
if (StringUtils.isNotBlank(opts.getCipherSuites())) {
PlatformUI.HTTPS_CIPHER_SUITES = StringUtils.split(opts.getCipherSuites(), ',');
}
PlatformUI.PINNED_CLIENT_TRUST = opts.getPinnedClientTrust();
PlatformUI.SERVER_URL = opts.getServer();
PlatformUI.CLIENT_VERSION = opts.getVersion();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class PlatformUI {
public static String SERVER_DATABASE;
public static String USER_NAME;
public static String CLIENT_VERSION;
public static String PINNED_CLIENT_TRUST;
public static String SERVER_VERSION;
public static String BUILD_DATE;
public static Color DEFAULT_BACKGROUND_COLOR = ServerSettings.DEFAULT_COLOR;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public void testParseSslForm() {
assertEquals("secret", opts.getPassword());
assertEquals("TLSv1.2,TLSv1.3", opts.getProtocols());
assertEquals("TLS_RSA_WITH_AES_128_GCM_SHA256", opts.getCipherSuites());
assertEquals("", opts.getPinnedClientTrust());
}

@Test
Expand All @@ -33,6 +34,49 @@ public void testParseUsernameFormWithSsl() {
assertEquals("pw", opts.getPassword());
assertEquals("TLSv1.2", opts.getProtocols());
assertEquals("CIPHER", opts.getCipherSuites());
assertEquals("", opts.getPinnedClientTrust());
}

@Test
public void testParseSslFormWithPinnedClientTrust() {
String[] args = new String[] { "https://example:8443", "1.0", "-ssl", "TLSv1.2,TLSv1.3", "TLS_RSA_WITH_AES_128_GCM_SHA256", "alice", "secret", "-trust", "abcdef1234,5489349" };
CommandLineOptions opts = new CommandLineOptions(args);

assertEquals("https://example:8443", opts.getServer());
assertEquals("1.0", opts.getVersion());
assertEquals("alice", opts.getUsername());
assertEquals("secret", opts.getPassword());
assertEquals("TLSv1.2,TLSv1.3", opts.getProtocols());
assertEquals("TLS_RSA_WITH_AES_128_GCM_SHA256", opts.getCipherSuites());
assertEquals("abcdef1234,5489349", opts.getPinnedClientTrust());
}

@Test
public void testParseUsernameFormWithPinnedClientTrust() {
String[] args = new String[] { "https://example:8443", "1.0", "bob", "pw", "-trust", "localhost,a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3" };
CommandLineOptions opts = new CommandLineOptions(args);

assertEquals("https://example:8443", opts.getServer());
assertEquals("1.0", opts.getVersion());
assertEquals("bob", opts.getUsername());
assertEquals("pw", opts.getPassword());
assertEquals("", opts.getProtocols());
assertEquals("", opts.getCipherSuites());
assertEquals("localhost,a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3", opts.getPinnedClientTrust());
}

@Test
public void testParsePinnedClientTrustAfterredentials() {
String[] args = new String[] { "https://example:8443", "1.0", "bob", "pw", "-trust", "localhost,a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3" };
CommandLineOptions opts = new CommandLineOptions(args);

assertEquals("https://example:8443", opts.getServer());
assertEquals("1.0", opts.getVersion());
assertEquals("bob", opts.getUsername());
assertEquals("pw", opts.getPassword());
assertEquals("", opts.getProtocols());
assertEquals("", opts.getCipherSuites());
assertEquals("localhost,a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3", opts.getPinnedClientTrust());
}

@Test
Expand All @@ -45,6 +89,7 @@ public void testNullArgsUsesDefaults() {
assertEquals("", opts.getPassword());
assertEquals("", opts.getProtocols());
assertEquals("", opts.getCipherSuites());
assertEquals("", opts.getPinnedClientTrust());
}

@Test
Expand All @@ -58,5 +103,6 @@ public void testNormal() {
assertEquals("", opts.getPassword());
assertEquals("", opts.getProtocols());
assertEquals("", opts.getCipherSuites());
assertEquals("", opts.getPinnedClientTrust());
}
}
7 changes: 6 additions & 1 deletion command/conf/mirth-cli-config.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
address=https://127.0.0.1:8443
user=admin
password=admin
version=0.0.0
version=0.0.0

# Comma-separated: pki, localhost, a SHA-256 certificate thumbprint, or
# insecure_trust_all_certs. The default reaches a server on this machine but no
# other; a remote or self-signed server needs an entry here.
#trust=pki,localhost
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
import com.mirth.connect.util.MessageImporter;
import com.mirth.connect.util.MessageImporter.MessageImportException;
import com.mirth.connect.util.MessageImporter.MessageImportInvalidPathException;
import com.mirth.connect.util.MirthSSLUtil;
import com.mirth.connect.util.messagewriter.AttachmentSource;
import com.mirth.connect.util.messagewriter.MessageWriter;
import com.mirth.connect.util.messagewriter.MessageWriterException;
Expand Down Expand Up @@ -123,6 +124,7 @@ private void run(String[] args) {
Option scriptOption = OptionBuilder.withArgName("script").hasArg().withDescription("script file").create("s");
Option versionOption = OptionBuilder.withArgName("version").hasArg().withDescription("version").create("v");
Option configOption = OptionBuilder.withArgName("config file").hasArg().withDescription("path to default configuration [default: mirth-cli-config.properties]").create("c");
Option trustOption = OptionBuilder.withArgName("trust").hasArg().withDescription("how to trust the server's certificate: any comma-separated combination of pki, localhost, a SHA-256 thumbprint, or insecure_trust_all_certs [default: pki,localhost]").create("trust");
Option helpOption = new Option("h", "help");
Option debugOption = new Option("d", "debug");

Expand All @@ -133,6 +135,7 @@ private void run(String[] args) {
options.addOption(passwordOption);
options.addOption(scriptOption);
options.addOption(versionOption);
options.addOption(trustOption);
options.addOption(helpOption);
options.addOption(debugOption);

Expand Down Expand Up @@ -175,9 +178,10 @@ private void run(String[] args) {
String user = line.getOptionValue("u", config.getString("user"));
String password = line.getOptionValue("p", config.getString("password"));
String script = line.getOptionValue("s", config.getString("script"));
String trust = line.getOptionValue("trust", config.getString("trust"));

if ((server != null) && (user != null) && (password != null)) {
runShell(server, user, password, script, line.hasOption("d"));
runShell(server, user, password, script, trust, line.hasOption("d"));
} else {
new HelpFormatter().printHelp("Shell", options);
error("all of address, user, password, and version options must be supplied as arguments or in the default configuration file", null);
Expand All @@ -189,9 +193,11 @@ private void run(String[] args) {
}
}

private void runShell(String server, String user, String password, String script, boolean debug) {
private void runShell(String server, String user, String password, String script, String trust, boolean debug) {
try {
client = new Client(server);
// A null trust takes the client default of pki,localhost, which reaches a
// server on this machine but no other.
client = new Client(server, MirthSSLUtil.DEFAULT_HTTPS_CLIENT_PROTOCOLS, MirthSSLUtil.DEFAULT_HTTPS_CIPHER_SUITES, trust);
this.debug = debug;

LoginStatus loginStatus = client.login(user, password);
Expand All @@ -216,14 +222,19 @@ private void runShell(String server, String user, String password, String script
runConsole();
}
client.logout();
client.close();
out.println("Disconnected from server.");
} catch (ClientException ce) {
ce.printStackTrace();
} catch (IOException ioe) {
error("Could not load script file.", ioe);
} catch (URISyntaxException e) {
error("Invalid server address.", e);
} finally {
// The client's connection monitor is a non-daemon thread, so an unclosed
// client keeps the JVM alive instead of letting it exit.
if (client != null) {
client.close();
}
}
}

Expand Down
13 changes: 13 additions & 0 deletions server/conf/mirth.properties
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ server.includecustomlib = false

# administrator
administrator.maxheapsize = 512m
# Controls how the client should validate the server's SSL certificate.
# Comma separated list of options:
# - pki
# Trust CA's and peer trust based on the client JVM config. Requires
# the server hostname to match the certificate CN or SAN. This is the
# "normal" trust required by most SSL clients.
# - webserver
# Trust this server's certificate specifically. Do not validate hostname.
# - <thumbprint>
# Trust the specified SHA-256 certificate thumbprint. Do not validate hostname.
# - insecure_trust_all_certs
# Trust all certificates without validation. (not recommended)
administrator.pinnedclienttrust = pki,webserver

# properties file that will store the configuration map and be loaded during server startup
configurationmap.path = ${dir.appdata}/configuration.properties
Expand Down
Loading
Loading