TLS and Graphics, what more?
This commit is contained in:
@@ -227,8 +227,12 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
|
||||
private void showConnectDialog() {
|
||||
ConnectDialog dialog = new ConnectDialog(this);
|
||||
dialog.setInitialHost(lastHost);
|
||||
dialog.setInitialPort(lastPort);
|
||||
String initialHost = lastHost != null && !lastHost.isEmpty() ? lastHost : org.pubvm.j3270.config.Settings.getAutoConnectHost();
|
||||
int initialPort = lastPort > 0 ? lastPort : org.pubvm.j3270.config.Settings.getAutoConnectPort();
|
||||
dialog.setInitialHost(initialHost);
|
||||
dialog.setInitialPort(initialPort);
|
||||
dialog.setInitialTls(org.pubvm.j3270.config.Settings.getAutoConnectTls());
|
||||
dialog.setInitialVerifyCert(org.pubvm.j3270.config.Settings.getAutoConnectVerifyCert());
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.isConfirmed()) {
|
||||
@@ -255,10 +259,19 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
// Save for auto-connect
|
||||
org.pubvm.j3270.config.Settings.setAutoConnectHost(config.getHost());
|
||||
org.pubvm.j3270.config.Settings.setAutoConnectPort(config.getPort());
|
||||
org.pubvm.j3270.config.Settings.setAutoConnectTls(config.isUseTls());
|
||||
org.pubvm.j3270.config.Settings.setAutoConnectVerifyCert(config.isTlsVerifyCert());
|
||||
|
||||
// Disconnect existing connection
|
||||
disconnect();
|
||||
|
||||
// Wire interactive certificate verifier if none configured
|
||||
if (config.getCertificateVerifier() == null) {
|
||||
config.setCertificateVerifier((chain, authType, exception) ->
|
||||
org.pubvm.j3270.ui.UntrustedCertificateDialog.showPrompt(this, config.getHost(), config.getPort(), chain, exception)
|
||||
);
|
||||
}
|
||||
|
||||
client = new Telnet3270Client(config);
|
||||
client.addConnectionListener(this);
|
||||
client.addScreenUpdateListener(this);
|
||||
@@ -266,7 +279,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
terminalPanel.setClient(client);
|
||||
statusBar.setClient(client);
|
||||
|
||||
setTitle("j3270 — " + config.getHost() + ":" + config.getPort());
|
||||
String tlsIndicator = config.isUseTls() ? (config.isTlsVerifyCert() ? " [TLS]" : " [TLS/Unverified]") : "";
|
||||
setTitle("j3270 — " + config.getHost() + ":" + config.getPort() + tlsIndicator);
|
||||
|
||||
// Resize window to match model
|
||||
terminalPanel.guardedPack();
|
||||
@@ -426,11 +440,24 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
|
||||
public static void main(String[] args) {
|
||||
boolean debug = false;
|
||||
boolean cliTls = false;
|
||||
boolean cliNoVerifyCert = false;
|
||||
org.lib3270j.graphics.GraphicsMode cliGraphicsMode = null;
|
||||
String configFile = null;
|
||||
java.util.List<String> remainingArgs = new java.util.ArrayList<>();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if ("--debug".equals(args[i]) || "-d".equals(args[i])) {
|
||||
debug = true;
|
||||
} else if ("--tls".equals(args[i]) || "--ssl".equals(args[i]) || "-s".equals(args[i])) {
|
||||
cliTls = true;
|
||||
} else if ("--no-verify-cert".equals(args[i]) || "--insecure".equals(args[i]) || "-k".equals(args[i])) {
|
||||
cliNoVerifyCert = true;
|
||||
} else if (args[i].startsWith("--graphics=")) {
|
||||
cliGraphicsMode = org.lib3270j.graphics.GraphicsMode.fromString(args[i].substring(11));
|
||||
} else if (("--graphics".equals(args[i]) || "-g".equals(args[i])) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||
cliGraphicsMode = org.lib3270j.graphics.GraphicsMode.fromString(args[++i]);
|
||||
} else if ("--no-graphics".equals(args[i])) {
|
||||
cliGraphicsMode = org.lib3270j.graphics.GraphicsMode.NONE;
|
||||
} else if (("-c".equals(args[i]) || "--config".equals(args[i])) && i + 1 < args.length) {
|
||||
configFile = args[++i];
|
||||
} else {
|
||||
@@ -439,33 +466,41 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
}
|
||||
|
||||
// Configure logging for application packages
|
||||
Level logLevel = debug ? Level.ALL : Level.INFO;
|
||||
Logger globalRoot = Logger.getLogger("");
|
||||
for (java.util.logging.Handler h : globalRoot.getHandlers()) {
|
||||
globalRoot.removeHandler(h);
|
||||
}
|
||||
globalRoot.setLevel(Level.WARNING);
|
||||
globalRoot.setLevel(Level.ALL);
|
||||
|
||||
ConsoleHandler handler = new ConsoleHandler();
|
||||
handler.setLevel(debug ? Level.ALL : Level.INFO);
|
||||
handler.setFormatter(new SimpleFormatter());
|
||||
java.util.logging.Filter appFilter = record -> {
|
||||
String name = record.getLoggerName();
|
||||
return name != null && (name.startsWith("org.lib3270j") || name.startsWith("org.pubvm.j3270"));
|
||||
};
|
||||
|
||||
Logger appLogger = Logger.getLogger("org.pubvm.j3270");
|
||||
appLogger.setLevel(Level.ALL);
|
||||
appLogger.addHandler(handler);
|
||||
appLogger.setUseParentHandlers(false);
|
||||
ConsoleHandler consoleHandler = new ConsoleHandler();
|
||||
consoleHandler.setLevel(logLevel);
|
||||
consoleHandler.setFormatter(new SimpleFormatter());
|
||||
consoleHandler.setFilter(appFilter);
|
||||
globalRoot.addHandler(consoleHandler);
|
||||
|
||||
Logger libLogger = Logger.getLogger("org.lib3270j");
|
||||
libLogger.setLevel(Level.ALL);
|
||||
libLogger.addHandler(handler);
|
||||
libLogger.setUseParentHandlers(false);
|
||||
Logger.getLogger("org.pubvm").setLevel(logLevel);
|
||||
Logger.getLogger("org.pubvm.j3270").setLevel(logLevel);
|
||||
Logger.getLogger("org.lib3270j").setLevel(logLevel);
|
||||
|
||||
try {
|
||||
java.util.logging.FileHandler fileHandler = new java.util.logging.FileHandler("j3270.log", 10 * 1024 * 1024, 1, false);
|
||||
java.util.logging.FileHandler fileHandler = new java.util.logging.FileHandler("j3270.log", 10 * 1024 * 1024, 1, false) {
|
||||
@Override
|
||||
public synchronized void publish(java.util.logging.LogRecord record) {
|
||||
super.publish(record);
|
||||
flush();
|
||||
}
|
||||
};
|
||||
fileHandler.setLevel(Level.ALL);
|
||||
fileHandler.setFormatter(new SimpleFormatter());
|
||||
appLogger.addHandler(fileHandler);
|
||||
libLogger.addHandler(fileHandler);
|
||||
log.info("Logging protocol trace to j3270.log");
|
||||
fileHandler.setFilter(appFilter);
|
||||
globalRoot.addHandler(fileHandler);
|
||||
log.info("Logging protocol trace to j3270.log (debug=" + debug + ", level=" + logLevel + ")");
|
||||
} catch (Exception e) {
|
||||
System.err.println("Could not create j3270.log: " + e.getMessage());
|
||||
}
|
||||
@@ -492,18 +527,22 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
System.setProperty("apple.laf.useScreenMenuBar", "true");
|
||||
System.setProperty("apple.awt.application.name", "j3270");
|
||||
|
||||
final boolean finalTls = cliTls;
|
||||
final boolean finalNoVerify = cliNoVerifyCert;
|
||||
final org.lib3270j.graphics.GraphicsMode finalGraphicsMode = cliGraphicsMode;
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
J3270App app = new J3270App();
|
||||
app.setVisible(true);
|
||||
|
||||
// If host:port given on command line, connect directly
|
||||
if (!remainingArgs.isEmpty()) {
|
||||
String host = remainingArgs.get(0);
|
||||
int port = 23;
|
||||
String hostArg = remainingArgs.get(0);
|
||||
int port = finalTls ? 992 : 23;
|
||||
if (remainingArgs.size() >= 2) {
|
||||
try {
|
||||
port = Integer.parseInt(remainingArgs.get(1));
|
||||
} catch (NumberFormatException e) {
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
TerminalModel model = TerminalModel.IBM_3279_4;
|
||||
@@ -511,10 +550,21 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
try {
|
||||
int modelNum = Integer.parseInt(remainingArgs.get(2));
|
||||
model = TerminalModel.forModel(modelNum, true);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
ConnectionConfig config = new ConnectionConfig(host, port, model);
|
||||
ConnectionConfig config = ConnectionConfig.parseHostString(hostArg, port, model);
|
||||
if (finalTls) {
|
||||
config.setUseTls(true);
|
||||
}
|
||||
if (finalNoVerify) {
|
||||
config.setTlsVerifyCert(false);
|
||||
}
|
||||
if (finalGraphicsMode != null) {
|
||||
config.setGraphicsMode(finalGraphicsMode);
|
||||
} else {
|
||||
config.setGraphicsMode(org.pubvm.j3270.config.Settings.getGraphicsMode());
|
||||
}
|
||||
app.connect(config);
|
||||
} else {
|
||||
org.pubvm.j3270.config.Settings.StartupBehavior behavior = org.pubvm.j3270.config.Settings
|
||||
@@ -526,8 +576,17 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
case AUTO_CONNECT:
|
||||
String host = org.pubvm.j3270.config.Settings.getAutoConnectHost();
|
||||
int port = org.pubvm.j3270.config.Settings.getAutoConnectPort();
|
||||
boolean tls = org.pubvm.j3270.config.Settings.getAutoConnectTls();
|
||||
boolean verify = org.pubvm.j3270.config.Settings.getAutoConnectVerifyCert();
|
||||
if (host != null && !host.isEmpty()) {
|
||||
app.connect(new ConnectionConfig(host, port, TerminalModel.IBM_3279_4));
|
||||
ConnectionConfig config = new ConnectionConfig(host, port, TerminalModel.IBM_3279_4, tls);
|
||||
config.setTlsVerifyCert(verify);
|
||||
if (finalGraphicsMode != null) {
|
||||
config.setGraphicsMode(finalGraphicsMode);
|
||||
} else {
|
||||
config.setGraphicsMode(org.pubvm.j3270.config.Settings.getGraphicsMode());
|
||||
}
|
||||
app.connect(config);
|
||||
} else {
|
||||
SwingUtilities.invokeLater(app::showConnectDialog);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,31 @@ public class Settings {
|
||||
prefs.putInt("autoConnectPort", port);
|
||||
}
|
||||
|
||||
public static boolean getAutoConnectTls() {
|
||||
return prefs.getBoolean("autoConnectTls", false);
|
||||
}
|
||||
|
||||
public static void setAutoConnectTls(boolean tls) {
|
||||
prefs.putBoolean("autoConnectTls", tls);
|
||||
}
|
||||
|
||||
public static boolean getAutoConnectVerifyCert() {
|
||||
return prefs.getBoolean("autoConnectVerifyCert", true);
|
||||
}
|
||||
|
||||
public static void setAutoConnectVerifyCert(boolean verify) {
|
||||
prefs.putBoolean("autoConnectVerifyCert", verify);
|
||||
}
|
||||
|
||||
public static org.lib3270j.graphics.GraphicsMode getGraphicsMode() {
|
||||
String modeStr = prefs.get("graphicsMode", org.lib3270j.graphics.GraphicsMode.NONE.name());
|
||||
return org.lib3270j.graphics.GraphicsMode.fromString(modeStr);
|
||||
}
|
||||
|
||||
public static void setGraphicsMode(org.lib3270j.graphics.GraphicsMode mode) {
|
||||
prefs.put("graphicsMode", (mode != null ? mode : org.lib3270j.graphics.GraphicsMode.NONE).name());
|
||||
}
|
||||
|
||||
public static Color getColorOverride(int index, Color defaultColor) {
|
||||
String hex = prefs.get("color_" + index, null);
|
||||
try {
|
||||
@@ -194,15 +219,33 @@ public class Settings {
|
||||
break;
|
||||
|
||||
case "behavior":
|
||||
case "connection":
|
||||
switch (key) {
|
||||
case "startupBehavior":
|
||||
setStartupBehavior(StartupBehavior.valueOf(value.toUpperCase()));
|
||||
break;
|
||||
case "autoConnectHost": setAutoConnectHost(value); break;
|
||||
case "autoConnectPort": setAutoConnectPort(Integer.parseInt(value)); break;
|
||||
case "autoConnectHost":
|
||||
case "host":
|
||||
setAutoConnectHost(value);
|
||||
break;
|
||||
case "autoConnectPort":
|
||||
case "port":
|
||||
setAutoConnectPort(Integer.parseInt(value));
|
||||
break;
|
||||
case "autoConnectTls":
|
||||
case "tls":
|
||||
case "ssl":
|
||||
case "useTls":
|
||||
setAutoConnectTls(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "autoConnectVerifyCert":
|
||||
case "verifyCert":
|
||||
case "tlsVerifyCert":
|
||||
setAutoConnectVerifyCert(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "blockSelectMode": setBlockSelectMode(Boolean.parseBoolean(value)); break;
|
||||
default:
|
||||
log.warning("Unknown behavior key: " + key);
|
||||
log.warning("Unknown behavior/connection key: " + key);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -222,6 +265,14 @@ public class Settings {
|
||||
setKeyBinding(key, value);
|
||||
break;
|
||||
|
||||
case "graphics":
|
||||
if ("graphicsMode".equalsIgnoreCase(key) || "mode".equalsIgnoreCase(key)) {
|
||||
setGraphicsMode(org.lib3270j.graphics.GraphicsMode.fromString(value));
|
||||
} else {
|
||||
log.warning("Unknown graphics key: " + key);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Allow bare keys outside any section — treat as raw prefs
|
||||
log.fine("Setting raw preference: " + key + " = " + value);
|
||||
@@ -256,10 +307,17 @@ public class Settings {
|
||||
if (acHost != null && !acHost.isEmpty()) {
|
||||
w.println("autoConnectHost = " + acHost);
|
||||
w.println("autoConnectPort = " + getAutoConnectPort());
|
||||
w.println("autoConnectTls = " + getAutoConnectTls());
|
||||
w.println("autoConnectVerifyCert = " + getAutoConnectVerifyCert());
|
||||
}
|
||||
w.println("blockSelectMode = " + getBlockSelectMode());
|
||||
w.println();
|
||||
|
||||
// [graphics]
|
||||
w.println("[graphics]");
|
||||
w.println("graphicsMode = " + getGraphicsMode().name());
|
||||
w.println();
|
||||
|
||||
// [colors]
|
||||
w.println("[colors]");
|
||||
// Host colors 0-15
|
||||
|
||||
@@ -14,7 +14,10 @@ public class ConnectDialog extends JDialog {
|
||||
private JTextField hostField;
|
||||
private JTextField portField;
|
||||
private JComboBox<TerminalModel> modelCombo;
|
||||
private JComboBox<org.lib3270j.graphics.GraphicsMode> graphicsCombo;
|
||||
private JTextField luField;
|
||||
private JCheckBox tlsCheckBox;
|
||||
private JCheckBox verifyCertCheckBox;
|
||||
private boolean confirmed;
|
||||
private ConnectionConfig result;
|
||||
|
||||
@@ -93,6 +96,56 @@ public class ConnectDialog extends JDialog {
|
||||
luField = createDarkField(12);
|
||||
mainPanel.add(luField, gbc);
|
||||
|
||||
// Graphics Mode
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 4;
|
||||
gbc.weightx = 0;
|
||||
JLabel graphicsLabel = new JLabel("Graphics:");
|
||||
graphicsLabel.setForeground(fg);
|
||||
graphicsLabel.setFont(labelFont);
|
||||
mainPanel.add(graphicsLabel, gbc);
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
graphicsCombo = new JComboBox<>(org.lib3270j.graphics.GraphicsMode.values());
|
||||
graphicsCombo.setSelectedItem(org.pubvm.j3270.config.Settings.getGraphicsMode());
|
||||
graphicsCombo.setBackground(new Color(45, 45, 45));
|
||||
graphicsCombo.setForeground(fg);
|
||||
graphicsCombo.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
mainPanel.add(graphicsCombo, gbc);
|
||||
|
||||
// TLS / SSL Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 5;
|
||||
gbc.weightx = 1.0;
|
||||
tlsCheckBox = new JCheckBox("Enable TLS/SSL");
|
||||
tlsCheckBox.setBackground(new Color(30, 30, 30));
|
||||
tlsCheckBox.setForeground(fg);
|
||||
tlsCheckBox.setFont(labelFont);
|
||||
tlsCheckBox.setFocusPainted(false);
|
||||
tlsCheckBox.addActionListener(e -> {
|
||||
boolean isTls = tlsCheckBox.isSelected();
|
||||
verifyCertCheckBox.setEnabled(isTls);
|
||||
String currentPort = portField.getText().trim();
|
||||
if (isTls && "23".equals(currentPort)) {
|
||||
portField.setText("992");
|
||||
} else if (!isTls && "992".equals(currentPort)) {
|
||||
portField.setText("23");
|
||||
}
|
||||
});
|
||||
mainPanel.add(tlsCheckBox, gbc);
|
||||
|
||||
// Verify Certificate Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 6;
|
||||
verifyCertCheckBox = new JCheckBox("Verify Server Certificate");
|
||||
verifyCertCheckBox.setBackground(new Color(30, 30, 30));
|
||||
verifyCertCheckBox.setForeground(new Color(160, 160, 160));
|
||||
verifyCertCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
verifyCertCheckBox.setSelected(true);
|
||||
verifyCertCheckBox.setEnabled(false);
|
||||
verifyCertCheckBox.setFocusPainted(false);
|
||||
mainPanel.add(verifyCertCheckBox, gbc);
|
||||
|
||||
// Buttons
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||
buttonPanel.setBackground(new Color(30, 30, 30));
|
||||
@@ -116,7 +169,7 @@ public class ConnectDialog extends JDialog {
|
||||
buttonPanel.add(connectBtn);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 4;
|
||||
gbc.gridy = 7;
|
||||
gbc.gridwidth = 2;
|
||||
mainPanel.add(buttonPanel, gbc);
|
||||
|
||||
@@ -158,6 +211,9 @@ public class ConnectDialog extends JDialog {
|
||||
if (!lu.isEmpty()) {
|
||||
result.setLuName(lu);
|
||||
}
|
||||
result.setGraphicsMode((org.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem());
|
||||
result.setUseTls(tlsCheckBox.isSelected());
|
||||
result.setTlsVerifyCert(verifyCertCheckBox.isSelected());
|
||||
confirmed = true;
|
||||
dispose();
|
||||
}
|
||||
@@ -178,4 +234,13 @@ public class ConnectDialog extends JDialog {
|
||||
public void setInitialPort(int port) {
|
||||
portField.setText(String.valueOf(port));
|
||||
}
|
||||
|
||||
public void setInitialTls(boolean tls) {
|
||||
tlsCheckBox.setSelected(tls);
|
||||
verifyCertCheckBox.setEnabled(tls);
|
||||
}
|
||||
|
||||
public void setInitialVerifyCert(boolean verify) {
|
||||
verifyCertCheckBox.setSelected(verify);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public class SettingsDialog extends JDialog {
|
||||
// Appearance tab
|
||||
private JComboBox<String> fontBox;
|
||||
private JSpinner fontSizeSpinner;
|
||||
private JComboBox<org.lib3270j.graphics.GraphicsMode> graphicsModeBox;
|
||||
|
||||
// Behavior tab
|
||||
private JComboBox<Settings.StartupBehavior> startupBehaviorBox;
|
||||
@@ -264,8 +265,21 @@ public class SettingsDialog extends JDialog {
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
panel.add(fontSizeSpinner, gbc);
|
||||
|
||||
// Fill remaining space
|
||||
// Graphics Mode
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 2;
|
||||
gbc.fill = GridBagConstraints.NONE;
|
||||
JLabel graphicsLabel = new JLabel("Graphics Mode:");
|
||||
panel.add(graphicsLabel, gbc);
|
||||
|
||||
graphicsModeBox = new JComboBox<>(org.lib3270j.graphics.GraphicsMode.values());
|
||||
graphicsModeBox.setSelectedItem(Settings.getGraphicsMode());
|
||||
gbc.gridx = 1;
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
panel.add(graphicsModeBox, gbc);
|
||||
|
||||
// Fill remaining space
|
||||
gbc.gridy = 3;
|
||||
gbc.weighty = 1.0;
|
||||
panel.add(Box.createGlue(), gbc);
|
||||
|
||||
@@ -611,6 +625,9 @@ public class SettingsDialog extends JDialog {
|
||||
Settings.setFontFamily(fontFam);
|
||||
}
|
||||
Settings.setFontSize((Integer) fontSizeSpinner.getValue());
|
||||
if (graphicsModeBox.getSelectedItem() != null) {
|
||||
Settings.setGraphicsMode((org.lib3270j.graphics.GraphicsMode) graphicsModeBox.getSelectedItem());
|
||||
}
|
||||
|
||||
// Apply Behavior
|
||||
Settings.StartupBehavior behavior = (Settings.StartupBehavior) startupBehaviorBox.getSelectedItem();
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.awt.*;
|
||||
public class StatusBar extends JPanel {
|
||||
|
||||
private final JLabel connectionStatus;
|
||||
private final JLabel tlsStatus;
|
||||
private final JLabel cursorPosition;
|
||||
private final JLabel luName;
|
||||
private final JLabel lockStatus;
|
||||
@@ -37,6 +38,7 @@ public class StatusBar extends JPanel {
|
||||
Font oiaFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
|
||||
|
||||
connectionStatus = createLabel("Not Connected", oiaFont, OIA_DIM);
|
||||
tlsStatus = createLabel("", oiaFont, OIA_FG);
|
||||
luName = createLabel("", oiaFont, OIA_FG);
|
||||
lockStatus = createLabel("", oiaFont, OIA_ALERT);
|
||||
modelInfo = createLabel("", oiaFont, OIA_DIM);
|
||||
@@ -44,6 +46,8 @@ public class StatusBar extends JPanel {
|
||||
|
||||
add(Box.createHorizontalStrut(6));
|
||||
add(connectionStatus);
|
||||
add(Box.createHorizontalStrut(10));
|
||||
add(tlsStatus);
|
||||
add(Box.createHorizontalStrut(12));
|
||||
add(luName);
|
||||
add(Box.createHorizontalStrut(12));
|
||||
@@ -110,6 +114,26 @@ public class StatusBar extends JPanel {
|
||||
break;
|
||||
}
|
||||
|
||||
// TLS Status
|
||||
if (state.isConnected() && client.getConfig().isUseTls()) {
|
||||
boolean verified = client.getConfig().isTlsVerifyCert();
|
||||
javax.net.ssl.SSLSession session = client.getSslSession();
|
||||
String cipher = session != null ? session.getCipherSuite() : "TLS";
|
||||
String protocol = session != null ? session.getProtocol() : "TLS";
|
||||
if (verified) {
|
||||
tlsStatus.setText("🔒 TLS");
|
||||
tlsStatus.setForeground(OIA_FG);
|
||||
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verified)");
|
||||
} else {
|
||||
tlsStatus.setText("🔓 TLS (Unverified)");
|
||||
tlsStatus.setForeground(new Color(255, 180, 80));
|
||||
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verification Bypassed)");
|
||||
}
|
||||
} else {
|
||||
tlsStatus.setText("");
|
||||
tlsStatus.setToolTipText(null);
|
||||
}
|
||||
|
||||
// LU name
|
||||
String lu = "";
|
||||
if (client.getConnectionState().isTn3270e()) {
|
||||
|
||||
@@ -921,13 +921,21 @@ public class TerminalPanel extends JPanel {
|
||||
g2.setColor(bgColor);
|
||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||
|
||||
// Draw character
|
||||
char ch = ea.ucs4;
|
||||
if (ch > 0x20 && ch != 0xFF) {
|
||||
Font f = bold ? terminalFont.deriveFont(Font.BOLD) : terminalFont;
|
||||
g2.setFont(f);
|
||||
g2.setColor(fgColor);
|
||||
g2.drawString(String.valueOf(ch), x, y + fontAscent);
|
||||
// Draw character or Programmed Symbol
|
||||
int cs = (ea.cs != 0) ? (ea.cs & 0xFF) : (currentFieldEa != null ? (currentFieldEa.cs & 0xFF) : 0);
|
||||
boolean drawnAsPs = false;
|
||||
if (cs >= 0x40 && client.getProgramSymbolManager() != null) {
|
||||
drawnAsPs = client.getProgramSymbolManager().drawSymbol(g2, cs, ea.ec & 0xFF, x, y, cellWidth, cellHeight, fgColor, bgColor);
|
||||
}
|
||||
|
||||
if (!drawnAsPs) {
|
||||
char ch = ea.ucs4;
|
||||
if (ch > 0x20 && ch != 0xFF) {
|
||||
Font f = bold ? terminalFont.deriveFont(Font.BOLD) : terminalFont;
|
||||
g2.setFont(f);
|
||||
g2.setColor(fgColor);
|
||||
g2.drawString(String.valueOf(ch), x, y + fontAscent);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw underline
|
||||
@@ -945,6 +953,14 @@ public class TerminalPanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw Vector Graphics Plane overlay if present
|
||||
if (client.getGraphicsPlane() != null && client.getGraphicsPlane().hasContent()) {
|
||||
int gridW = cols * cellWidth;
|
||||
int gridH = rows * cellHeight;
|
||||
client.getGraphicsPlane().resize(gridW, gridH);
|
||||
g2.drawImage(client.getGraphicsPlane().getImage(), ox, oy, gridW, gridH, null);
|
||||
}
|
||||
|
||||
// Draw cursor
|
||||
if (cursorVisible && client.getConnectionState().isFullSession()) {
|
||||
int curAddr = sb.getCursorAddress();
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package org.pubvm.j3270.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Modal dialog for inspecting and confirming untrusted TLS/SSL server certificates.
|
||||
*/
|
||||
public class UntrustedCertificateDialog extends JDialog {
|
||||
|
||||
private boolean accepted = false;
|
||||
|
||||
public UntrustedCertificateDialog(Frame parent, String host, int port, X509Certificate[] chain, CertificateException exception) {
|
||||
super(parent, "Untrusted SSL/TLS Certificate", true);
|
||||
buildUI(host, port, chain, exception);
|
||||
pack();
|
||||
setLocationRelativeTo(parent);
|
||||
setResizable(false);
|
||||
}
|
||||
|
||||
private void buildUI(String host, int port, X509Certificate[] chain, CertificateException exception) {
|
||||
JPanel mainPanel = new JPanel(new BorderLayout(12, 12));
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
|
||||
mainPanel.setBackground(new Color(30, 30, 30));
|
||||
|
||||
// Header
|
||||
JPanel headerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0));
|
||||
headerPanel.setBackground(new Color(30, 30, 30));
|
||||
JLabel iconLabel = new JLabel("⚠️");
|
||||
iconLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 28));
|
||||
headerPanel.add(iconLabel);
|
||||
|
||||
JPanel titleBox = new JPanel(new GridLayout(2, 1, 0, 2));
|
||||
titleBox.setBackground(new Color(30, 30, 30));
|
||||
JLabel titleLabel = new JLabel("Untrusted SSL Certificate");
|
||||
titleLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 16));
|
||||
titleLabel.setForeground(new Color(255, 180, 80));
|
||||
titleBox.add(titleLabel);
|
||||
|
||||
JLabel subtitleLabel = new JLabel("The server certificate for " + host + ":" + port + " could not be verified.");
|
||||
subtitleLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||
subtitleLabel.setForeground(new Color(180, 180, 180));
|
||||
titleBox.add(subtitleLabel);
|
||||
headerPanel.add(titleBox);
|
||||
mainPanel.add(headerPanel, BorderLayout.NORTH);
|
||||
|
||||
// Certificate Details Area
|
||||
X509Certificate cert = (chain != null && chain.length > 0) ? chain[0] : null;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (exception != null) {
|
||||
sb.append("Validation Error:\n ").append(exception.getMessage() != null ? exception.getMessage() : exception.toString()).append("\n\n");
|
||||
}
|
||||
if (cert != null) {
|
||||
sb.append("Subject:\n ").append(cert.getSubjectX500Principal().getName()).append("\n\n");
|
||||
sb.append("Issuer:\n ").append(cert.getIssuerX500Principal().getName()).append("\n\n");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
|
||||
sb.append("Validity:\n From: ").append(sdf.format(cert.getNotBefore()))
|
||||
.append("\n To: ").append(sdf.format(cert.getNotAfter())).append("\n\n");
|
||||
sb.append("Serial Number:\n ").append(cert.getSerialNumber().toString(16).toUpperCase()).append("\n\n");
|
||||
sb.append("SHA-256 Fingerprint:\n ").append(computeFingerprint(cert, "SHA-256")).append("\n\n");
|
||||
sb.append("SHA-1 Fingerprint:\n ").append(computeFingerprint(cert, "SHA-1"));
|
||||
} else {
|
||||
sb.append("No peer certificate information available.");
|
||||
}
|
||||
|
||||
JTextArea detailsArea = new JTextArea(sb.toString());
|
||||
detailsArea.setEditable(false);
|
||||
detailsArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
detailsArea.setBackground(new Color(20, 20, 20));
|
||||
detailsArea.setForeground(new Color(210, 210, 210));
|
||||
detailsArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(detailsArea);
|
||||
scrollPane.setPreferredSize(new Dimension(520, 260));
|
||||
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(60, 60, 60)));
|
||||
mainPanel.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
// Buttons
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
|
||||
buttonPanel.setBackground(new Color(30, 30, 30));
|
||||
|
||||
JButton cancelBtn = new JButton("Cancel Connection");
|
||||
cancelBtn.setBackground(new Color(60, 60, 60));
|
||||
cancelBtn.setForeground(new Color(220, 220, 220));
|
||||
cancelBtn.addActionListener(e -> {
|
||||
accepted = false;
|
||||
dispose();
|
||||
});
|
||||
|
||||
JButton trustBtn = new JButton("Connect Anyway");
|
||||
trustBtn.setBackground(new Color(180, 100, 40));
|
||||
trustBtn.setForeground(Color.WHITE);
|
||||
trustBtn.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13));
|
||||
trustBtn.addActionListener(e -> {
|
||||
accepted = true;
|
||||
dispose();
|
||||
});
|
||||
|
||||
buttonPanel.add(cancelBtn);
|
||||
buttonPanel.add(trustBtn);
|
||||
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||
|
||||
setContentPane(mainPanel);
|
||||
getRootPane().setDefaultButton(trustBtn);
|
||||
}
|
||||
|
||||
public boolean isAccepted() {
|
||||
return accepted;
|
||||
}
|
||||
|
||||
private static String computeFingerprint(X509Certificate cert, String algorithm) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(algorithm);
|
||||
byte[] digest = md.digest(cert.getEncoded());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < digest.length; i++) {
|
||||
if (i > 0) sb.append(':');
|
||||
sb.append(String.format("%02X", digest[i]));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
return "Unable to compute fingerprint: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the prompt modal dialog on the Swing EDT and return user choice.
|
||||
*/
|
||||
public static boolean showPrompt(Frame parent, String host, int port, X509Certificate[] chain, CertificateException exception) {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
UntrustedCertificateDialog dialog = new UntrustedCertificateDialog(parent, host, port, chain, exception);
|
||||
dialog.setVisible(true);
|
||||
return dialog.isAccepted();
|
||||
} else {
|
||||
AtomicBoolean result = new AtomicBoolean(false);
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
UntrustedCertificateDialog dialog = new UntrustedCertificateDialog(parent, host, port, chain, exception);
|
||||
dialog.setVisible(true);
|
||||
result.set(dialog.isAccepted());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return result.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user