summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Sulfrian <asulfrian@zedat.fu-berlin.de>2019-03-09 07:00:04 +0100
committerAlexander Sulfrian <asulfrian@zedat.fu-berlin.de>2019-03-09 07:00:04 +0100
commit5514a9a5a1fbf0204927300a5af73c251cdf3799 (patch)
tree9c9b9dff77fdb2aa5ee01e9dba10c8c5449fb911
parentb13d00e372b7731a87c8b4d81dfd5454214b8cfa (diff)
downloadkvm-5514a9a5a1fbf0204927300a5af73c251cdf3799.tar.gz
kvm-5514a9a5a1fbf0204927300a5af73c251cdf3799.tar.bz2
kvm-5514a9a5a1fbf0204927300a5af73c251cdf3799.zip
Add first version of the applet launcher
-rw-r--r--.gitignore8
-rw-r--r--build.gradle24
-rw-r--r--settings.gradle5
-rw-r--r--src/main/java/de/spline/kvm/Launcher.java88
-rw-r--r--src/main/java/de/spline/kvm/StandaloneApplet.java121
-rw-r--r--src/main/java/de/spline/kvm/events/EventMulticaster.java29
-rw-r--r--src/main/java/de/spline/kvm/events/LifetimeAdapter.java9
-rw-r--r--src/main/java/de/spline/kvm/events/LifetimeListener.java8
-rw-r--r--src/main/java/de/spline/kvm/utils/Kvm.java61
-rw-r--r--src/main/java/de/spline/kvm/utils/ReflectionUtils.java84
-rw-r--r--src/main/java/de/spline/kvm/utils/SSLUtils.java47
-rw-r--r--src/main/java/nn/pp/rc/JSSE14_SSLConnector.java26
-rw-r--r--src/main/java/nn/pp/rc/u.java55
-rw-r--r--src/main/resources/dfn-g2.jksbin0 -> 1526 bytes
14 files changed, 565 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a8e8291
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+# JetBrains IntelliJ IDEA
+.idea/
+out/
+*.iml
+
+# Gradle
+.gradle/
+build/
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..9719996
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,24 @@
+plugins {
+ id 'java'
+}
+
+sourceCompatibility = '1.8'
+targetCompatibility = '1.8'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.gistlabs:mechanize:0.13.1'
+ compile fileTree('lib') { include '*.jar' }
+}
+
+jar {
+ manifest {
+ attributes 'Main-Class': 'de.spline.kvm.Launcher'
+ }
+
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..7614b58
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,5 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ */
+
+rootProject.name = 'kvm'
diff --git a/src/main/java/de/spline/kvm/Launcher.java b/src/main/java/de/spline/kvm/Launcher.java
new file mode 100644
index 0000000..d27ed95
--- /dev/null
+++ b/src/main/java/de/spline/kvm/Launcher.java
@@ -0,0 +1,88 @@
+package de.spline.kvm;
+
+import javax.net.ssl.HttpsURLConnection;
+import java.io.Console;
+import java.util.Map;
+
+import de.spline.kvm.events.LifetimeAdapter;
+import de.spline.kvm.utils.Kvm;
+import de.spline.kvm.utils.SSLUtils;
+
+public class Launcher
+{
+ private static final String DEFAULT_USERNAME = "super";
+
+ private String host;
+
+ public Launcher(String host)
+ {
+ this.host = host;
+ }
+
+ public void run()
+ {
+ HttpsURLConnection.setDefaultSSLSocketFactory(SSLUtils.getSocketFactory());
+ Kvm kvm = new Kvm("https://" + host);
+
+ Console console = System.console();
+ if (console == null) {
+ System.err.println("Unable to access the console to query login data.");
+ System.exit(1);
+ }
+
+ String username = readUsername(console);
+ String password = readPassword(console, username);
+ kvm.login(username, password);
+
+ Map<String, String> params = kvm.getAppletParameters();
+ StandaloneApplet applet = new StandaloneApplet(host, params);
+
+ applet.addLifetimeListener(new LifetimeAdapter()
+ {
+ @Override
+ public void appletStopped()
+ {
+ kvm.logout();
+ }
+ });
+
+ applet.init();
+ applet.start();
+ }
+
+ public static void main(String[] args)
+ {
+ String host = "kvm.spline.inf.fu-berlin.de";
+ if (args.length > 0) {
+ host = args[0];
+ }
+
+ Launcher launcher = new Launcher(host);
+ launcher.run();
+ }
+
+ private String readUsername(Console console)
+ {
+ String input = console.readLine("Username for %s [%s]: ", host, DEFAULT_USERNAME);
+
+ if (input == null) {
+ return null;
+ }
+
+ if (input.isEmpty()) {
+ return DEFAULT_USERNAME;
+ }
+
+ return input;
+ }
+
+ private String readPassword(Console console, String username)
+ {
+ char[] password = console.readPassword("Password for %s@%s: ", username, host);
+ if (password == null) {
+ return null;
+ }
+
+ return String.valueOf(password);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/spline/kvm/StandaloneApplet.java b/src/main/java/de/spline/kvm/StandaloneApplet.java
new file mode 100644
index 0000000..cf8a5d5
--- /dev/null
+++ b/src/main/java/de/spline/kvm/StandaloneApplet.java
@@ -0,0 +1,121 @@
+package de.spline.kvm;
+
+import java.awt.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Locale;
+import java.util.Map;
+
+import de.spline.kvm.events.EventMulticaster;
+import de.spline.kvm.events.LifetimeListener;
+import de.spline.kvm.utils.ReflectionUtils;
+import nn.pp.rc.RemoteConsoleApplet;
+import nn.pp.rc.ServerConsolePanelBase;
+
+/**
+ * This is a class, to run an applet without an browser. It just overrides the
+ * necessary methods to run the applet. The applet "window" displayed in tbe
+ * browser is not drawn, but the interesting stuff (aka. the remote console)
+ * is in a separate Frame.
+ */
+public class StandaloneApplet extends RemoteConsoleApplet
+{
+ protected String host;
+ protected Map<String, String> parameters;
+
+ protected LifetimeListener lifetimeListener = null;
+
+ public StandaloneApplet(String host, Map<String, String> parameters)
+ {
+ this.host = host;
+ this.parameters = parameters;
+ }
+
+ /**
+ * Add a listener for the lifetime events of the applet.
+ *
+ * @param listener Listener instance to add
+ */
+ public void addLifetimeListener(LifetimeListener listener)
+ {
+ lifetimeListener = EventMulticaster.add(listener, lifetimeListener);
+ }
+
+ /**
+ * Remove a listener for the lifetime events of the applet.
+ *
+ * @param listener Listener instance to remove
+ */
+ public void removeLifetimeListener(LifetimeListener listener)
+ {
+ lifetimeListener = EventMulticaster.remove(listener, lifetimeListener);
+ }
+
+ @Override
+ public Image getImage(URL url)
+ {
+ return getToolkit().getImage(url);
+ }
+
+ @Override
+ public URL getCodeBase()
+ {
+ URL url = null;
+
+ try {
+ url = new URL("https", host, 443, "");
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+
+ return url;
+ }
+
+ @Override
+ public URL getDocumentBase()
+ {
+ return getCodeBase();
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ return Locale.getDefault();
+ }
+
+ /**
+ * This are the parameters of the applet. The applet usually get the values
+ * from html tags inside the applet tag. We have parsed this tags into a
+ * map and simply return these values here.
+ *
+ * @param name Name of the parameter to get
+ * @return Value of the parameter
+ */
+ @Override
+ public String getParameter(String name)
+ {
+ return parameters.get(name.toUpperCase());
+ }
+
+ @Override
+ public void init()
+ {
+ super.init();
+
+ // The initial label is removed during init and this is causing
+ // NullPointerExceptions when the popup should be displayed, so we
+ // simply add the label again.
+ ServerConsolePanelBase consolePanel = ReflectionUtils.getConsolePanel(this);
+ PopupMenu menu = ReflectionUtils.getOptionMenu(consolePanel);
+ menu.setLabel("Options");
+ }
+
+ @Override
+ public void stop()
+ {
+ super.stop();
+ destroy();
+
+ lifetimeListener.appletStopped();
+ }
+}
diff --git a/src/main/java/de/spline/kvm/events/EventMulticaster.java b/src/main/java/de/spline/kvm/events/EventMulticaster.java
new file mode 100644
index 0000000..7f216bd
--- /dev/null
+++ b/src/main/java/de/spline/kvm/events/EventMulticaster.java
@@ -0,0 +1,29 @@
+package de.spline.kvm.events;
+
+import java.awt.AWTEventMulticaster;
+import java.util.EventListener;
+
+public class EventMulticaster extends AWTEventMulticaster implements LifetimeListener
+{
+ public EventMulticaster(EventListener a, EventListener b)
+ {
+ super(a, b);
+ }
+
+ @Override
+ public void appletStopped()
+ {
+ ((LifetimeListener) a).appletStopped();
+ ((LifetimeListener) b).appletStopped();
+ }
+
+ public static LifetimeListener add(LifetimeListener a, LifetimeListener b)
+ {
+ return (LifetimeListener) AWTEventMulticaster.addInternal(a, b);
+ }
+
+ public static LifetimeListener remove(LifetimeListener a, LifetimeListener b)
+ {
+ return (LifetimeListener) AWTEventMulticaster.removeInternal(a, b);
+ }
+}
diff --git a/src/main/java/de/spline/kvm/events/LifetimeAdapter.java b/src/main/java/de/spline/kvm/events/LifetimeAdapter.java
new file mode 100644
index 0000000..2fc2618
--- /dev/null
+++ b/src/main/java/de/spline/kvm/events/LifetimeAdapter.java
@@ -0,0 +1,9 @@
+package de.spline.kvm.events;
+
+public abstract class LifetimeAdapter implements LifetimeListener
+{
+ @Override
+ public void appletStopped()
+ {
+ }
+}
diff --git a/src/main/java/de/spline/kvm/events/LifetimeListener.java b/src/main/java/de/spline/kvm/events/LifetimeListener.java
new file mode 100644
index 0000000..77c89d8
--- /dev/null
+++ b/src/main/java/de/spline/kvm/events/LifetimeListener.java
@@ -0,0 +1,8 @@
+package de.spline.kvm.events;
+
+import java.util.EventListener;
+
+public interface LifetimeListener extends EventListener
+{
+ void appletStopped();
+}
diff --git a/src/main/java/de/spline/kvm/utils/Kvm.java b/src/main/java/de/spline/kvm/utils/Kvm.java
new file mode 100644
index 0000000..896150a
--- /dev/null
+++ b/src/main/java/de/spline/kvm/utils/Kvm.java
@@ -0,0 +1,61 @@
+package de.spline.kvm.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.gistlabs.mechanize.MechanizeAgent;
+import com.gistlabs.mechanize.Resource;
+import com.gistlabs.mechanize.document.Document;
+import com.gistlabs.mechanize.document.node.Node;
+import com.gistlabs.mechanize.parameters.Parameters;
+
+public class Kvm
+{
+ protected String host;
+ protected MechanizeAgent agent;
+
+ public Kvm(String host)
+ {
+ this.host = host;
+ this.agent = new MechanizeAgent();
+ }
+
+ public void login(String username, String password)
+ {
+ Parameters param = new Parameters();
+ param.add("login", username);
+ param.add("password", password);
+ param.add("action_login", "Login");
+
+ Resource resource = agent.post(host + "/auth.asp", param);
+ if (resource.asString().contains("Authentication failed.")) {
+ System.err.println("Invalid login!");
+ System.exit(1);
+ }
+ }
+
+ public Map<String, String> getAppletParameters()
+ {
+ Document doc = agent.get(host + "/title_app.asp");
+ if (!doc.asString().contains("<param name=\"APPLET_ID\"")) {
+ logout();
+
+ System.err.println("Error getting applet parameters.");
+ System.exit(1);
+ }
+
+ Map<String, String> params = new HashMap<>();
+ for (Node node : doc.getRoot().findAll("html body applet param")) {
+ String name = node.getAttribute("name");
+ String value = node.getAttribute("value");
+ params.put(name.toUpperCase(), value);
+ }
+
+ return params;
+ }
+
+ public void logout()
+ {
+ agent.get(host + "/logout");
+ }
+}
diff --git a/src/main/java/de/spline/kvm/utils/ReflectionUtils.java b/src/main/java/de/spline/kvm/utils/ReflectionUtils.java
new file mode 100644
index 0000000..03f3a9f
--- /dev/null
+++ b/src/main/java/de/spline/kvm/utils/ReflectionUtils.java
@@ -0,0 +1,84 @@
+package de.spline.kvm.utils;
+
+import java.awt.PopupMenu;
+import java.lang.reflect.Field;
+
+import nn.pp.rc.RemoteConsoleApplet;
+import nn.pp.rc.ServerConsolePanelBase;
+
+public class ReflectionUtils
+{
+ /**
+ * Set a field of an object with reflection. This ignores all access
+ * permissions (f.e. private) and accesses the field by name (so it works
+ * for strange field names).
+ *
+ * @param cls
+ * @param instance
+ * @param field
+ * @param value
+ */
+ public static void setField(Class<?> cls, Object instance, String field, Object value)
+ {
+ try {
+ Field f = cls.getDeclaredField(field);
+ f.setAccessible(true);
+ f.set(instance, value);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Get a field of an object with reflection. This ignores all access
+ * permissions (f.e. private) and accesses the field by name (so it works
+ * for strange field names).
+ *
+ * @param cls
+ * @param instance
+ * @param field
+ * @return
+ */
+ public static Object getField(Class<?> cls, Object instance, String field)
+ {
+ try {
+ Field f = cls.getDeclaredField(field);
+ f.setAccessible(true);
+ return f.get(instance);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the ServerConsolePanelBase field from the applet instance. We need
+ * to use reflection here, because the obfuscation has produced strange
+ * field names and we cannot access it using normal methods.
+ *
+ * @param applet RemoteConsoleApplet instance
+ * @return ServerConsolePanelBase instance of this applet
+ */
+ public static ServerConsolePanelBase getConsolePanel(RemoteConsoleApplet applet)
+ {
+ return (ServerConsolePanelBase) ReflectionUtils.getField(RemoteConsoleApplet.class, applet, "else");
+ }
+
+ /**
+ * Get the popup option menu from a ServerConsolePanelBase instance. The
+ * field name is okay-ish here, but we also need to use reflection here
+ * because this field in private.
+ *
+ * (We use this option menu to fix a silly bug: The menu is not displayed,
+ * because the "label" is removed after initialization and this is causing
+ * NullPointerExceptions.)
+ *
+ * @param consolePanel ServerConsolePanelBase instance
+ * @return options menu
+ */
+ public static PopupMenu getOptionMenu(ServerConsolePanelBase consolePanel)
+ {
+ return (PopupMenu)ReflectionUtils.getField(ServerConsolePanelBase.class, consolePanel, "G");
+ }
+}
diff --git a/src/main/java/de/spline/kvm/utils/SSLUtils.java b/src/main/java/de/spline/kvm/utils/SSLUtils.java
new file mode 100644
index 0000000..75bb1dd
--- /dev/null
+++ b/src/main/java/de/spline/kvm/utils/SSLUtils.java
@@ -0,0 +1,47 @@
+package de.spline.kvm.utils;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+
+public class SSLUtils
+{
+ public static SSLSocketFactory getSocketFactory()
+ {
+ SSLContext ctx = null;
+
+ try {
+ ctx = SSLContext.getInstance("TLS");
+ ctx.init(null, getTrustManagers(), null);
+ }
+ catch (GeneralSecurityException | IOException e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+
+ return ctx.getSocketFactory();
+ }
+
+ private static TrustManager[] getTrustManagers()
+ throws GeneralSecurityException, IOException
+ {
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(loadKeyStore());
+ return trustManagerFactory.getTrustManagers();
+ }
+
+ private static KeyStore loadKeyStore()
+ throws GeneralSecurityException, IOException
+ {
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ InputStream in = ClassLoader.getSystemResourceAsStream("dfn-g2.jks");
+ keyStore.load(in, "changeit".toCharArray());
+ return keyStore;
+ }
+}
diff --git a/src/main/java/nn/pp/rc/JSSE14_SSLConnector.java b/src/main/java/nn/pp/rc/JSSE14_SSLConnector.java
new file mode 100644
index 0000000..487b1b9
--- /dev/null
+++ b/src/main/java/nn/pp/rc/JSSE14_SSLConnector.java
@@ -0,0 +1,26 @@
+package nn.pp.rc;
+
+import javax.net.ssl.SSLSocketFactory;
+
+import de.spline.kvm.utils.ReflectionUtils;
+import de.spline.kvm.utils.SSLUtils;
+
+/**
+ * We replace the JSSE14_SSLConnector with our own version.
+ *
+ * The default class from the applet uses a broken (outdated?)
+ * X509PluginTrustManager. So we simply use your own SSLSocketFactory here,
+ * that already has a correct setup for TLS connections of the remote console.
+ */
+public class JSSE14_SSLConnector extends aq
+{
+ public JSSE14_SSLConnector() {
+ // Just visual stuff to beautify the display name of this connector
+ ReflectionUtils.setField(aq.class, this, "do", "TLS");
+ }
+
+ public SSLSocketFactory a()
+ {
+ return SSLUtils.getSocketFactory();
+ }
+}
diff --git a/src/main/java/nn/pp/rc/u.java b/src/main/java/nn/pp/rc/u.java
new file mode 100644
index 0000000..c5c08d4
--- /dev/null
+++ b/src/main/java/nn/pp/rc/u.java
@@ -0,0 +1,55 @@
+package nn.pp.rc;
+
+import java.applet.Applet;
+import java.awt.Frame;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+/**
+ * This is a simple class to override the nn.pp.rc.u class from the applet.
+ *
+ * We need to do this, because the original class is doing some nasty things in
+ * the processWindowEvent method. This class has the same name (and the same
+ * package) and is somewhere before the applet in the classpath. So we cannot
+ * import the original class here, but the class is so simple, that we can
+ * implement it from scratch.
+ */
+public class u extends Frame
+{
+ ServerConsolePanelBase consolePanel;
+
+ public u(Applet applet, String title, ServerConsolePanelBase consolePanel)
+ {
+ super(title + " Remote Console");
+ this.consolePanel = consolePanel;
+ this.add(consolePanel, "Center");
+
+ this.addWindowListener(new WindowAdapter()
+ {
+ @Override
+ public void windowClosing(WindowEvent windowEvent)
+ {
+ applet.stop();
+ }
+ });
+ }
+
+ /**
+ * Close this frame.
+ */
+ public void a()
+ {
+ this.setVisible(false);
+ this.setState(0);
+ this.dispose();
+
+ // "Close" the console panel
+ consolePanel.r();
+ }
+
+ public void a(String path, String target)
+ {
+ // Nothing here, because we do not need the redirection to another page
+ // when the applet exists.
+ }
+}
diff --git a/src/main/resources/dfn-g2.jks b/src/main/resources/dfn-g2.jks
new file mode 100644
index 0000000..032a560
--- /dev/null
+++ b/src/main/resources/dfn-g2.jks
Binary files differ