Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.file.Files;
Expand Down Expand Up @@ -68,7 +69,7 @@ public class Shell implements Runnable {
@CommandLine.Option(names = "--history", description = "File where shell history is being stored.")
protected File historyFile;

private static final String DEFAULT_HISTORY_FILE = "history-file";
public static final String DEFAULT_HISTORY_FILE = "history-file";

public Shell(CommandLine commandLine) {
}
Expand All @@ -82,7 +83,7 @@ public void run() {
connect.setUser(user).setPassword(password).setBrokerURL(brokerURL);
connect.run();
}
runShell(false, historyFile);
runShell(false, historyFile, null);
}

private static ThreadLocal<AtomicBoolean> IN_SHELL = ThreadLocal.withInitial(() -> new AtomicBoolean(false));
Expand All @@ -106,19 +107,32 @@ public static void setConnected(boolean connected) {
}

public static void runShell(boolean printBanner) {
runShell(printBanner, null);
runShell(printBanner, null, null);
}

public static void runShell(boolean printBanner, File historyFile) {
public static void runShell(boolean printBanner, File historyFile, InputStream pipedInput) {
try {
setInShell();

String artemisInstance = System.getProperty("artemis.instance");

boolean isInstance = artemisInstance != null;

if (isInstance && historyFile == null) {
historyFile = inquiryDefaultHistory(historyFile, artemisInstance);
if (historyFile == null) {
String historyFilePath = System.getProperty("artemis.shell.history");
if (historyFilePath == null) {
historyFilePath = System.getenv("ARTEMIS_SHELL_HISTORY");
}
if (historyFilePath != null) {
historyFile = new File(historyFilePath);
} else {
historyFile = inquiryDefaultHistory(historyFile, artemisInstance);
}
}

if (pipedInput != null) {
runPipedMode(isInstance, pipedInput);
return;
}

Supplier<Path> workDir = () -> Paths.get(System.getProperty("user.dir"));
Expand Down Expand Up @@ -168,7 +182,11 @@ public static void runShell(boolean printBanner, File historyFile) {
printBanner();
}

if (historyFile != null) {
if (historyFile == null) {
File preferenceFile = new File(artemisInstance + "/etc/" + DEFAULT_HISTORY_FILE);
System.out.println(org.apache.activemq.artemis.cli.Terminal.WARNING_COLOR_UNICODE + "Shell history disabled as recorded in " + preferenceFile.getAbsolutePath() + org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE);
System.out.println();
} else {
System.out.println(org.apache.activemq.artemis.cli.Terminal.WARNING_COLOR_UNICODE + "Shell history being saved at " + historyFile.getAbsolutePath() + org.apache.activemq.artemis.cli.Terminal.CLEAR_UNICODE);
System.out.println();
}
Expand Down Expand Up @@ -254,6 +272,7 @@ private static void loadHistory(File historyFile, LineReader reader) {
} else {
defaultHistoryFile.createNewFile();
}
setHistoryFilePermissions(defaultHistoryFile);
}

if (historyFile == null) {
Expand Down Expand Up @@ -324,4 +343,34 @@ private static void setHistoryFilePermissions(File historyFile) {
}
}

private static void runPipedMode(boolean isInstance, InputStream is) throws Exception {

try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();

// Skip empty lines and comments
if (line.isEmpty() || line.startsWith("#")) {
continue;
}

// Exit command
if (line.equals("exit") || line.equals("quit")) {
break;
}

try {
// Rebuild command for each execution to avoid state issues
CommandLine commandLine = Artemis.buildCommand(isInstance, !isInstance, false);
String[] args = line.split("\\s+");
commandLine.execute(args);
} catch (Exception e) {
System.err.println("Error executing command: " + line);
e.printStackTrace();
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ if [ -f "$ARTEMIS_OOME_DUMP" ] ; then
mv $ARTEMIS_OOME_DUMP $ARTEMIS_OOME_DUMP.bkp
fi

# Set shell history argument if ARTEMIS_SHELL_HISTORY is defined
if [ -n "$ARTEMIS_SHELL_HISTORY" ]; then
SHELL_HISTORY_ARG="-Dartemis.shell.history=${ARTEMIS_SHELL_HISTORY}"
fi

exec "$JAVACMD" \
$LOGGING_ARGS \
$JAVA_ARGS \
Expand All @@ -132,6 +137,7 @@ exec "$JAVACMD" \
-Djava.io.tmpdir="$ARTEMIS_INSTANCE/tmp" \
-Ddata.dir="$ARTEMIS_DATA_DIR" \
-Dartemis.instance.etc="$ARTEMIS_INSTANCE_ETC" \
$SHELL_HISTORY_ARG \
$DEBUG_ARGS \
$JAVA_ARGS_APPEND \
org.apache.activemq.artemis.boot.Artemis "$@"
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ ARTEMIS_HOME='${artemis.home}'
ARTEMIS_INSTANCE='@artemis.instance@'
ARTEMIS_DATA_DIR='${artemis.instance.data}'

if [ -z "$ARTEMIS_SHELL_HISTORY" ]; then
ARTEMIS_SHELL_HISTORY=~/.artemis_history
fi

if [ -z "$LOGGING_ARGS" ]; then
LOGGING_ARGS="-Dlog4j2.configurationFile=log4j2-utility.properties"
fi
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.activemq.artemis.cli.commands;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.activemq.artemis.cli.Shell;
import org.apache.activemq.artemis.tests.util.ArtemisTestCase;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

public class ShellTest extends ArtemisTestCase {

@TempDir
File temporaryFolder;

@Test
public void testDefaultAllowHistory() throws Exception {
testDefaultHistory(true);
}

@Test
public void testDefaultDontAllowHistory() throws Exception {
testDefaultHistory(false);
}

public void testDefaultHistory(boolean allowHistory) throws Exception {
System.setProperty("artemis.instance", temporaryFolder.getAbsolutePath());
File etcFolder = new File(temporaryFolder, "etc");
assertTrue(etcFolder.mkdirs());

String input;
if (allowHistory) {
input = "Y\n";
} else {
input = "N\n";
}

File shellHistory = new File(etcFolder, Shell.DEFAULT_HISTORY_FILE);
executeShell(input, shellHistory);

if (!allowHistory) {
String historyContent = new String(Files.readAllBytes(shellHistory.toPath()), StandardCharsets.UTF_8);
assertTrue(historyContent.contains("NO_HISTORY"), "History file should contain NO_HISTORY marker");
}
}

private static void executeShell(String input, File outputHistory) throws InterruptedException, IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));

try {
CountDownLatch shellFinished = new CountDownLatch(1);

Thread shellThread = new Thread(() -> {
InputStream originlInput = System.in;
try {
// Run the shell with custom streams for testing
System.setIn(inputStream);
Shell.runShell(false, null, inputStream);
} finally {
shellFinished.countDown();
System.setIn(originlInput);
}
});

shellThread.start();

// Wait for shell to finish
assertTrue(shellFinished.await(30, TimeUnit.SECONDS), "Shell did not finish in time");
shellThread.join(5000);

assertTrue(outputHistory.exists());

// Validate file permissions are 600 (owner read/write only)
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(outputHistory.toPath());
assertTrue(permissions.contains(PosixFilePermission.OWNER_READ), "File should be readable by owner");
assertTrue(permissions.contains(PosixFilePermission.OWNER_WRITE), "File should be writable by owner");
// Explicitly verify group and others cannot read
assertFalse(permissions.contains(PosixFilePermission.GROUP_READ), "File should not be readable by group");
assertFalse(permissions.contains(PosixFilePermission.GROUP_WRITE), "File should not be writable by group");
assertFalse(permissions.contains(PosixFilePermission.GROUP_EXECUTE), "File should not be executable by group");
assertFalse(permissions.contains(PosixFilePermission.OTHERS_READ), "File should not be readable by others");
assertFalse(permissions.contains(PosixFilePermission.OTHERS_WRITE), "File should not be writable by others");
assertFalse(permissions.contains(PosixFilePermission.OTHERS_EXECUTE), "File should not be executable by others");

} finally {
System.clearProperty("artemis.instance");
}
}

}