Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLazar Kirchev2011-10-19 07:58:59 +0000
committerLazar Kirchev2011-10-19 07:58:59 +0000
commit61b96b6e8095d80eab1707f38d6f4be9ec8d5023 (patch)
tree82263ef56d2c486afb97dc5b3af89ae4c8f94d48 /bundles/org.eclipse.equinox.console.ssh
parent98303bb1e9a65c56740a6e59da463c2c832a0c45 (diff)
downloadrt.equinox.bundles-61b96b6e8095d80eab1707f38d6f4be9ec8d5023.tar.gz
rt.equinox.bundles-61b96b6e8095d80eab1707f38d6f4be9ec8d5023.tar.xz
rt.equinox.bundles-61b96b6e8095d80eab1707f38d6f4be9ec8d5023.zip
Decouple SSH functionality in separate bundle and fix tests accordingly.
Diffstat (limited to 'bundles/org.eclipse.equinox.console.ssh')
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/.classpath7
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/.project28
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/.settings/org.eclipse.jdt.core.prefs8
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/META-INF/MANIFEST.MF26
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/about.html28
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/build.properties5
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/RolePrincipal.java67
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/SecureStorageLoginModule.java135
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/UserPrincipal.java140
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/Activator.java83
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshCommand.java299
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshInputHandler.java30
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshInputScanner.java76
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshServ.java81
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshSession.java136
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshShell.java154
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshShellFactory.java63
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/UserAdminCommand.java447
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/storage/DigestUtil.java69
-rwxr-xr-xbundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/storage/SecureUserStore.java614
20 files changed, 2496 insertions, 0 deletions
diff --git a/bundles/org.eclipse.equinox.console.ssh/.classpath b/bundles/org.eclipse.equinox.console.ssh/.classpath
new file mode 100755
index 00000000..ad32c83a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.eclipse.equinox.console.ssh/.project b/bundles/org.eclipse.equinox.console.ssh/.project
new file mode 100755
index 00000000..a3f47b54
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.equinox.console.ssh</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.equinox.console.ssh/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.equinox.console.ssh/.settings/org.eclipse.jdt.core.prefs
new file mode 100755
index 00000000..8b11af0a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+#Tue Oct 18 14:13:09 EEST 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/bundles/org.eclipse.equinox.console.ssh/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.console.ssh/META-INF/MANIFEST.MF
new file mode 100755
index 00000000..4efb1501
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/META-INF/MANIFEST.MF
@@ -0,0 +1,26 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Console ssh support plug-in
+Bundle-SymbolicName: org.eclipse.equinox.console.ssh
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.eclipse.equinox.console.ssh.Activator
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Import-Package: javax.security.auth;resolution:=optional,
+ javax.security.auth.callback;resolution:=optional,
+ javax.security.auth.login;resolution:=optional,
+ javax.security.auth.spi;resolution:=optional,
+ org.apache.felix.service.command;status=provisional;version="0.8.0",
+ org.apache.sshd;version="0.5.0";resolution:=optional,
+ org.apache.sshd.common;version="0.5.0";resolution:=optional,
+ org.apache.sshd.server;version="0.5.0";resolution:=optional,
+ org.apache.sshd.server.jaas;version="0.5.0";resolution:=optional,
+ org.apache.sshd.server.keyprovider;version="0.5.0";resolution:=optional,
+ org.apache.sshd.server.session;version="0.5.0";resolution:=optional,
+ org.apache.sshd.server.shell;version="0.5.0";resolution:=optional,
+ org.eclipse.equinox.console.common,
+ org.eclipse.equinox.console.common.terminal,
+ org.osgi.framework;version="1.7.0",
+ org.osgi.service.cm;resolution:=optional,
+ org.osgi.util.tracker
+Export-Package: org.eclipse.equinox.console.jaas
diff --git a/bundles/org.eclipse.equinox.console.ssh/about.html b/bundles/org.eclipse.equinox.console.ssh/about.html
new file mode 100755
index 00000000..359fab5a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/about.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>January 10, 2011</p>
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;). A copy of the EPL is available
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.console.ssh/build.properties b/bundles/org.eclipse.equinox.console.ssh/build.properties
new file mode 100755
index 00000000..dfb921b1
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ about.html,\
+ .
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/RolePrincipal.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/RolePrincipal.java
new file mode 100755
index 00000000..c5f8c4ad
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/RolePrincipal.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.jaas;
+
+import java.security.Principal;
+
+/**
+ * This class represents a user role
+ *
+ */
+public class RolePrincipal implements Principal {
+ private String roleName;
+
+ public RolePrincipal(String roleName) {
+ this.roleName = roleName;
+ }
+
+ public String getName() {
+ return roleName;
+ }
+
+ public boolean equals(Object role) {
+
+ if (role == null) {
+ return false;
+ }
+
+ if (this == role) {
+ return true;
+ }
+
+ if (!(role instanceof RolePrincipal)) {
+ return false;
+ }
+
+ RolePrincipal otherRole = (RolePrincipal) role;
+ if (roleName != null) {
+ if (roleName.equals(otherRole.roleName)) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ if (otherRole.roleName == null) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 73 * result + (roleName == null ? 0 : roleName.hashCode());
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/SecureStorageLoginModule.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/SecureStorageLoginModule.java
new file mode 100755
index 00000000..941fb17e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/SecureStorageLoginModule.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.jaas;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.eclipse.equinox.console.storage.DigestUtil;
+import org.eclipse.equinox.console.storage.SecureUserStore;
+
+/**
+ * This class implements a JAAS LoginModule, which performs username/password
+ * based authentication. It reads the user data from the store.
+ *
+ */
+public class SecureStorageLoginModule implements LoginModule {
+
+ private volatile Subject subject;
+ private volatile CallbackHandler callbackHandler;
+ private volatile UserPrincipal userPrincipal;
+ private volatile boolean isSuccess;
+
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map<String, ?> sharedState, Map<String, ?> options) {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ }
+
+ public boolean login() throws LoginException {
+ NameCallback nameCallback = new NameCallback("username: ");
+ PasswordCallback passwordCallback = new PasswordCallback("password: ", false);
+ try {
+ callbackHandler.handle(new Callback[]{nameCallback, passwordCallback});
+ } catch (IOException e) {
+ throw new FailedLoginException("Cannot get username and password");
+ } catch (UnsupportedCallbackException e) {
+ throw new FailedLoginException("Cannot get username and password");
+ }
+
+ String username = nameCallback.getName();
+ char[] password = passwordCallback.getPassword();
+
+ userPrincipal = getUserInfo(username);
+
+ try {
+ isSuccess = userPrincipal.authenticate(DigestUtil.encrypt(new String(password)).toCharArray());
+ } catch (Exception e) {
+ throw new FailedLoginException("Wrong credentials");
+ }
+
+ if (isSuccess == true) {
+ return isSuccess;
+ } else {
+ throw new FailedLoginException("Wrong credentials");
+ }
+ }
+
+ public boolean commit() throws LoginException {
+ if (isSuccess == true) {
+ synchronized (this) {
+ subject.getPrincipals().add(userPrincipal);
+ subject.getPrincipals().addAll(userPrincipal.getRoles());
+ }
+ return true;
+ } else {
+ userPrincipal.destroy();
+ userPrincipal = null;
+ return false;
+ }
+ }
+
+ public boolean abort() throws LoginException {
+ userPrincipal.destroy();
+ userPrincipal = null;
+ return true;
+ }
+
+ public boolean logout() throws LoginException {
+ synchronized (this) {
+ subject.getPrincipals().remove(userPrincipal);
+ subject.getPrincipals().removeAll(userPrincipal.getRoles());
+ }
+ subject = null;
+ userPrincipal.destroy();
+ userPrincipal = null;
+ return true;
+ }
+
+ private UserPrincipal getUserInfo(String username) throws FailedLoginException {
+ try {
+ if (!SecureUserStore.existsUser(username)) {
+ throw new FailedLoginException("Wrong credentials");
+ }
+
+ String password = SecureUserStore.getPassword(username);
+ if (password == null) {
+ throw new FailedLoginException("Corrupted user");
+ }
+
+ String roles = SecureUserStore.getRoles(username);
+ if (roles == null) {
+ roles = "";
+ }
+
+ UserPrincipal userPrincipal = new UserPrincipal(username, password);
+ for (String role : roles.split(",")) {
+ userPrincipal.addRole(new RolePrincipal(role));
+ }
+
+ return userPrincipal;
+ } catch (Exception e) {
+ throw new FailedLoginException(e.getMessage());
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/UserPrincipal.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/UserPrincipal.java
new file mode 100755
index 00000000..7af4162a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/jaas/UserPrincipal.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.jaas;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class represents a user with password and roles
+ *
+ */
+public class UserPrincipal implements Principal {
+ private String username;
+ private char[] password;
+ private Set<RolePrincipal> rolePrincipals = new HashSet<RolePrincipal>();
+
+ public UserPrincipal(String username, String password) {
+ this.username = username;
+ this.password = new char[password.length()];
+ System.arraycopy(password.toCharArray(), 0, this.password, 0, this.password.length);
+ }
+
+ public String getName() {
+ return username;
+ }
+
+ public boolean authenticate(char[] password) {
+ if (password == null) {
+ return false;
+ }
+
+ if (this.password == null) {
+ return false;
+ }
+
+ if (this.password.length != password.length) {
+ return false;
+ }
+
+ for(int i = 0; i < this.password.length; i++) {
+ if(this.password[i] != password[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public Set<RolePrincipal> getRoles() {
+ return rolePrincipals;
+ }
+
+ public synchronized void addRole(RolePrincipal rolePrincipal) {
+ rolePrincipals.add(rolePrincipal);
+ }
+
+ public boolean equals(Object userPrincipal) {
+ if (userPrincipal == null) {
+ return false;
+ }
+
+ if (this == userPrincipal) {
+ return true;
+ }
+
+ if (! (userPrincipal instanceof UserPrincipal)) {
+ return false;
+ }
+
+ UserPrincipal otherUser = (UserPrincipal) userPrincipal;
+ if (username != null) {
+ if (!username.equals(otherUser.username)) {
+ return false;
+ }
+ } else {
+ if (otherUser.username != null) {
+ return false;
+ }
+ }
+
+ if (password != null) {
+ if (otherUser.password == null) {
+ return false;
+ }
+
+ if (password.length != otherUser.password.length) {
+ return false;
+ }
+
+ for(int i = 0; i < password.length; i++) {
+ if (password[i] != otherUser.password[i]) {
+ return false;
+ }
+ }
+ } else {
+ if (otherUser.username != null) {
+ return false;
+ }
+ }
+
+ if (rolePrincipals != null) {
+ if (!(rolePrincipals.equals(otherUser.rolePrincipals))) {
+ return false;
+ }
+ } else {
+ if (otherUser.rolePrincipals != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public void destroy() {
+ for(int i = 0; i < password.length; i++) {
+ password[i] = ' ';
+ }
+
+ password = null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 73 * result + (username == null ? 0 : username.hashCode());
+ result = 73 * result + (password == null ? 0 : new String(password).hashCode());
+ result = 73 * result + (rolePrincipals == null ? 0 : rolePrincipals.hashCode());
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/Activator.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/Activator.java
new file mode 100755
index 00000000..d982bac9
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/Activator.java
@@ -0,0 +1,83 @@
+package org.eclipse.equinox.console.ssh;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+public class Activator implements BundleActivator {
+
+ private static BundleContext context;
+ private static SshCommand sshConnection = null;
+ private static boolean isFirstProcessor = true;
+
+ private ServiceTracker<CommandProcessor, SshCommand> commandProcessorTracker;
+
+ public static class ProcessorCustomizer implements ServiceTrackerCustomizer<CommandProcessor, SshCommand> {
+
+ private final BundleContext context;
+
+ public ProcessorCustomizer(BundleContext context) {
+ this.context = context;
+ }
+
+ public SshCommand addingService(ServiceReference<CommandProcessor> reference) {
+ CommandProcessor processor = context.getService(reference);
+ if (processor == null)
+ return null;
+
+ if (isFirstProcessor) {
+ isFirstProcessor = false;
+ sshConnection = new SshCommand(processor, context);
+ sshConnection.start();
+ } else {
+ sshConnection.addCommandProcessor(processor);
+ }
+
+ return sshConnection;
+ }
+
+ public void modifiedService(
+ ServiceReference<CommandProcessor> reference,
+ SshCommand service) {
+ // nothing
+ }
+
+ public void removedService(ServiceReference<CommandProcessor> reference, SshCommand service) {
+ CommandProcessor processor = context.getService(reference);
+ service.removeCommandProcessor(processor);
+ }
+ }
+
+ static BundleContext getContext() {
+ return context;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext bundleContext) throws Exception {
+ context = bundleContext;
+ commandProcessorTracker = new ServiceTracker<CommandProcessor, SshCommand>(context, CommandProcessor.class, new ProcessorCustomizer(context));
+ commandProcessorTracker.open();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext bundleContext) throws Exception {
+ Activator.context = null;
+ commandProcessorTracker.close();
+
+ try {
+ sshConnection.ssh(new String[]{"stop"});
+ } catch (Exception e) {
+ // expected if the ssh server is not started
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshCommand.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshCommand.java
new file mode 100755
index 00000000..64440adb
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshCommand.java
@@ -0,0 +1,299 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.ssh;
+
+import java.io.IOException;
+import java.net.BindException;
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.Descriptor;
+import org.eclipse.equinox.console.storage.DigestUtil;
+import org.eclipse.equinox.console.storage.SecureUserStore;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+/**
+ * This class implements a command for starting/stopping a simple ssh server.
+ *
+ */
+public class SshCommand {
+ private String defaultHost = null;
+ private int defaultPort;
+ private List<CommandProcessor> processors = new ArrayList<CommandProcessor>();
+ private String host = null;
+ private int port;
+ private SshServ sshServ;
+ private BundleContext context;
+ private ServiceRegistration<?> configuratorRegistration;
+ private boolean isEnabled = false;
+ private final Object lock = new Object();
+
+ private static final String DEFAULT_USER = "equinox";
+ private static final String DEFAULT_PASSWORD = "equinox";
+ private static final String DEFAULT_USER_STORE_PROPERTY = "osgi.console.ssh.useDefaultSecureStorage";
+ private static final String HOST = "host";
+ private static final String PORT = "port";
+ private static final String USE_CONFIG_ADMIN_PROP = "osgi.console.useConfigAdmin";
+ private static final String SSH_PID = "osgi.console.ssh";
+ private static final String ENABLED = "enabled";
+
+ public SshCommand(CommandProcessor processor, BundleContext context) {
+ processors.add(processor);
+ this.context = context;
+
+ if ("true".equals(context.getProperty(USE_CONFIG_ADMIN_PROP))) {
+ Dictionary<String, String> sshProperties = new Hashtable<String, String>();
+ sshProperties.put(Constants.SERVICE_PID, SSH_PID);
+ try {
+ synchronized (lock) {
+ configuratorRegistration = context.registerService(ManagedService.class.getName(), new SshConfigurator(), sshProperties);
+ }
+ } catch (NoClassDefFoundError e) {
+ System.out.println("Configuration Admin not available!");
+ return;
+ }
+ } else {
+ parseHostAndPort();
+ }
+ }
+
+ private void parseHostAndPort() {
+ String sshPort = null;
+ String consolePropValue = context.getProperty(SSH_PID);
+ if(consolePropValue != null) {
+ int index = consolePropValue.lastIndexOf(":");
+ if (index > -1) {
+ defaultHost = consolePropValue.substring(0, index);
+ }
+ sshPort = consolePropValue.substring(index + 1);
+ isEnabled = true;
+ }
+ if (sshPort != null && !"".equals(sshPort)) {
+ try {
+ defaultPort = Integer.parseInt(sshPort);
+ } catch (NumberFormatException e) {
+ // do nothing
+ }
+ }
+ }
+
+ public synchronized void start() {
+ Dictionary<String, Object> properties = new Hashtable<String, Object>();
+ properties.put("osgi.command.scope", "equinox");
+ properties.put("osgi.command.function", new String[] {"ssh"});
+ if ((port > 0 || defaultPort > 0) && isEnabled == true) {
+ try{
+ ssh(new String[]{"start"});
+ } catch (Exception e) {
+ System.out.println("Cannot start ssh. Reason: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ context.registerService(SshCommand.class.getName(), this, properties);
+ }
+
+ @Descriptor("start/stop a ssh server")
+ public synchronized void ssh(String[] arguments) throws Exception {
+ String command = null;
+ String newHost = null;
+ int newPort = 0;
+
+ for(int i = 0; i < arguments.length; i++) {
+ if("-?".equals(arguments[i]) || "-help".equals(arguments[i])) {
+ printHelp();
+ return;
+ } else if("start".equals(arguments[i])) {
+ command = "start";
+ } else if ("stop".equals(arguments[i])) {
+ command = "stop";
+ } else if ("-port".equals(arguments[i]) && (arguments.length > i + 1)) {
+ i++;
+ newPort = Integer.parseInt(arguments[i]);
+ } else if ("-host".equals(arguments[i]) && (arguments.length > i + 1)) {
+ i++;
+ newHost = arguments[i];
+ } else {
+ throw new Exception("Unrecognized ssh command/option " + arguments[i]);
+ }
+ }
+
+ if (command == null) {
+ throw new Exception("No ssh command specified");
+ }
+
+ if (newPort != 0) {
+ port = newPort;
+ } else if (port == 0) {
+ port = defaultPort;
+ }
+
+ if (port == 0) {
+ throw new Exception("No ssh port specified");
+ }
+
+ if (newHost != null) {
+ host = newHost;
+ } else {
+ host = defaultHost;
+ }
+
+ if ("start".equals(command)) {
+ if (sshServ != null) {
+ throw new IllegalStateException("ssh is already running on port " + port);
+ }
+
+ checkPortAvailable(port);
+
+ sshServ = new SshServ(processors, context, host, port);
+ sshServ.setName("equinox ssh");
+
+ if ("true".equals(context.getProperty(DEFAULT_USER_STORE_PROPERTY))) {
+ try {
+ checkUserStore();
+ registerUserAdmin();
+ } catch (NoClassDefFoundError e) {
+ System.out.println("If you want to use secure storage, please install Equinox security bundle and its dependencies");
+ sshServ = null;
+ return;
+ } catch (IOException e) {
+ e.printStackTrace();
+ sshServ = null;
+ return;
+ }
+ }
+
+ try {
+ sshServ.start();
+ } catch (RuntimeException e) {
+ sshServ = null;
+ return;
+ }
+ } else if ("stop".equals(command)) {
+ if (sshServ == null) {
+ throw new IllegalStateException("ssh is not running.");
+ }
+
+ sshServ.stopSshServer();
+ sshServ = null;
+ }
+ }
+
+ public synchronized void addCommandProcessor(CommandProcessor processor) {
+ processors.add(processor);
+ sshServ.addCommandProcessor(processor);
+ }
+
+ public synchronized void removeCommandProcessor(CommandProcessor processor) {
+ processors.remove(processor);
+ sshServ.removeCommandProcessor(processor);
+ }
+
+ private void checkPortAvailable(int port) throws Exception {
+ ServerSocket socket = null;
+ try {
+ socket = new ServerSocket(port);
+ } catch (BindException e) {
+ throw new Exception ("Port " + port + " already in use");
+ } finally {
+ if (socket != null) {
+ socket.close();
+ }
+ }
+ }
+
+ /*
+ * Register user administration commands
+ */
+ private void registerUserAdmin() {
+ Dictionary<String, Object> properties = new Hashtable<String, Object>();
+ properties.put("osgi.command.scope", "equinox");
+ properties.put("osgi.command.function", new String[] {"addUser", "addUser", "deleteUser", "resetPassword", "setPassword", "addRoles", "removeRoles", "listUsers"});
+ context.registerService(UserAdminCommand.class.getName(), new UserAdminCommand(), properties);
+ }
+
+ /*
+ * Create user store if not available. Add the default user, if there is no other user in the store.
+ */
+ private void checkUserStore() throws Exception {
+ SecureUserStore.initStorage();
+ if(SecureUserStore.getUserNames().length == 0) {
+ SecureUserStore.putUser(DEFAULT_USER, DigestUtil.encrypt(DEFAULT_PASSWORD), null );
+ }
+ }
+
+ private void printHelp() {
+ StringBuffer help = new StringBuffer();
+ help.append("ssh - start simple ssh server");
+ help.append("\n");
+ help.append("Usage: ssh start | stop [-port port] [-host host]");
+ help.append("\n");
+ help.append("\t");
+ help.append("-port");
+ help.append("\t");
+ help.append("listen port (default=");
+ help.append(defaultPort);
+ help.append(")");
+ help.append("\n");
+ help.append("\t");
+ help.append("-host");
+ help.append("\t");
+ help.append("local host address to listen on (default is none - listen on all network interfaces)");
+ help.append("\n");
+ help.append("\t");
+ help.append("-?, -help");
+ help.append("\t");
+ help.append("show help");
+ System.out.println(help.toString());
+ }
+
+ class SshConfigurator implements ManagedService {
+ @SuppressWarnings("rawtypes")
+ private Dictionary properties;
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public synchronized void updated(Dictionary props) throws ConfigurationException {
+ if (props != null) {
+ this.properties = props;
+ properties.put(Constants.SERVICE_PID, SSH_PID);
+ } else {
+ return;
+ }
+
+ defaultPort = Integer.parseInt(((String)properties.get(PORT)));
+ defaultHost = (String)properties.get(HOST);
+ if (properties.get(ENABLED) == null) {
+ isEnabled = false;
+ } else {
+ isEnabled = Boolean.parseBoolean((String)properties.get(ENABLED));
+ }
+ synchronized (lock) {
+ configuratorRegistration.setProperties(properties);
+ }
+ if (sshServ == null && isEnabled == true) {
+ try {
+ ssh(new String[]{"start"});
+ } catch (Exception e) {
+ System.out.println("Cannot start ssh: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ }
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshInputHandler.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshInputHandler.java
new file mode 100755
index 00000000..d1b2eb76
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshInputHandler.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.ssh;
+
+import java.io.InputStream;
+
+import org.eclipse.equinox.console.common.ConsoleInputStream;
+import org.eclipse.equinox.console.common.ConsoleOutputStream;
+import org.eclipse.equinox.console.common.InputHandler;
+
+/**
+ * This class customizes the generic handler with a concrete content processor,
+ * which provides ssh protocol handling.
+ *
+ */
+public class SshInputHandler extends InputHandler {
+ public SshInputHandler(InputStream input, ConsoleInputStream in, ConsoleOutputStream out) {
+ super(input, in, out);
+ inputScanner = new SshInputScanner(in, out);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshInputScanner.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshInputScanner.java
new file mode 100755
index 00000000..f0c88a93
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshInputScanner.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.ssh;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.equinox.console.common.ConsoleInputStream;
+import org.eclipse.equinox.console.common.KEYS;
+import org.eclipse.equinox.console.common.Scanner;
+import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings;
+
+/**
+ * This class performs preprocessing of the input from the ssh server in order to echo the visible
+ * characters back to the console.
+ *
+ */
+public class SshInputScanner extends Scanner {
+
+ public SshInputScanner(ConsoleInputStream toShell, OutputStream toTelnet) {
+ super(toShell, toTelnet);
+ TerminalTypeMappings currentMapping = supportedEscapeSequences.get(DEFAULT_TTYPE);
+ currentEscapesToKey = currentMapping.getEscapesToKey();
+ escapes = currentMapping.getEscapes();
+ setBackspace(currentMapping.getBackspace());
+ setDel(currentMapping.getDel());
+ }
+
+ @Override
+ public void scan(int b) throws IOException {
+ b &= 0xFF;
+
+ if (isEsc) {
+ scanEsc(b);
+ } else {
+ switch (b) {
+ case ESC:
+ startEsc();
+ toShell.add(new byte[]{(byte) b});
+ break;
+ default:
+ if (b >= SPACE && b < MAX_CHAR) {
+ echo((byte) b);
+ flush();
+ }
+ toShell.add(new byte[]{(byte) b});
+ }
+ }
+ }
+
+ @Override
+ protected void scanEsc(int b) throws IOException {
+ esc += (char) b;
+ toShell.add(new byte[]{(byte) b});
+ KEYS key = checkEscape(esc);
+ if (key == KEYS.UNFINISHED) {
+ return;
+ }
+ if (key == KEYS.UNKNOWN) {
+ isEsc = false;
+ scan(b);
+ return;
+ }
+ isEsc = false;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshServ.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshServ.java
new file mode 100755
index 00000000..088a31f5
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshServ.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.ssh;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.sshd.SshServer;
+import org.apache.sshd.server.PasswordAuthenticator;
+import org.apache.sshd.server.jaas.JaasPasswordAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.osgi.framework.BundleContext;
+
+/**
+ * This class configures and start an ssh server
+ *
+ */
+public class SshServ extends Thread {
+ private int port;
+ private String host;
+ private SshServer sshServer = null;
+ private SshShellFactory shellFactory = null;
+
+ private static final String SSH_KEYSTORE_PROP = "ssh.server.keystore";
+ private static final String SSH_KEYSTORE_PROP_DEFAULT = "hostkey.ser";
+ private static final String EQUINOX_CONSOLE_DOMAIN = "equinox_console";
+
+ public SshServ(List<CommandProcessor> processors, BundleContext context, String host, int port) {
+ this.host = host;
+ this.port = port;
+ shellFactory = new SshShellFactory(processors, context);
+ }
+
+ public void run() throws RuntimeException {
+ sshServer = SshServer.setUpDefaultServer();
+ if (host != null) {
+ sshServer.setHost(host);
+ }
+ sshServer.setPort(port);
+ sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(System.getProperty(SSH_KEYSTORE_PROP, SSH_KEYSTORE_PROP_DEFAULT)));
+ sshServer.setShellFactory(shellFactory);
+ sshServer.setPasswordAuthenticator(createJaasPasswordAuthenticator());
+ try {
+ sshServer.start();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public synchronized void stopSshServer() {
+ try {
+ sshServer.stop(true);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public synchronized void addCommandProcessor(CommandProcessor processor) {
+ shellFactory.addCommandProcessor(processor);
+ }
+
+ public synchronized void removeCommandProcessor(CommandProcessor processor) {
+ shellFactory.removeCommandProcessor(processor);
+ }
+
+ private PasswordAuthenticator createJaasPasswordAuthenticator() {
+ JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator();
+ jaasPasswordAuthenticator.setDomain(EQUINOX_CONSOLE_DOMAIN);
+ return jaasPasswordAuthenticator;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshSession.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshSession.java
new file mode 100755
index 00000000..2420278f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshSession.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.console.ssh;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Map;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.eclipse.equinox.console.common.ConsoleInputStream;
+import org.eclipse.equinox.console.common.ConsoleOutputStream;
+import org.eclipse.equinox.console.common.KEYS;
+import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings;
+import org.eclipse.equinox.console.storage.SecureUserStore;
+import org.eclipse.equinox.console.common.ConsoleInputHandler;
+import org.eclipse.equinox.console.common.ConsoleInputScanner;
+import org.osgi.framework.BundleContext;
+
+/**
+ * This class manages a ssh connection. It is responsible for wrapping the original io streams
+ * from the socket, and starting a CommandSession to execute commands from the ssh.
+ *
+ */
+public class SshSession extends Thread implements Closeable {
+ private CommandProcessor processor;
+ private BundleContext context;
+ private SshShell sshShell;
+ private InputStream in;
+ private OutputStream out;
+ private TerminalTypeMappings currentMappings;
+ private Map<String, KEYS> currentEscapesToKey;
+
+ private static final String PROMPT = "prompt";
+ private static final String OSGI_PROMPT = "osgi> ";
+ private static final String SCOPE = "SCOPE";
+ private static final String EQUINOX_SCOPE = "equinox:*";
+ private static final String INPUT_SCANNER = "INPUT_SCANNER";
+ private static final String SSH_INPUT_SCANNER = "SSH_INPUT_SCANNER";
+ private static final String USER_STORAGE_PROPERTY_NAME = "osgi.console.ssh.useDefaultSecureStorage";
+ private static final String DEFAULT_USER = "equinox";
+ private static final String CLOSEABLE = "CLOSEABLE";
+ private static final int ADD_USER_COUNTER_LIMIT = 2;
+
+ public SshSession(CommandProcessor processor, BundleContext context, SshShell sshShell, InputStream in, OutputStream out, TerminalTypeMappings currentMappings, Map<String, KEYS> currentExcapesToKey) {
+ this.processor = processor;
+ this.context = context;
+ this.sshShell = sshShell;
+ this.in = in;
+ this.out = out;
+ this.currentMappings = currentMappings;
+ this.currentEscapesToKey = currentExcapesToKey;
+ }
+
+ public void run() {
+ ConsoleInputStream input = new ConsoleInputStream();
+ ConsoleOutputStream outp = new ConsoleOutputStream(out);
+ SshInputHandler inputHandler = new SshInputHandler(in, input, outp);
+ inputHandler.getScanner().setBackspace(currentMappings.getBackspace());
+ inputHandler.getScanner().setDel(currentMappings.getDel());
+ inputHandler.getScanner().setCurrentEscapesToKey(currentEscapesToKey);
+ inputHandler.getScanner().setEscapes(currentMappings.getEscapes());
+ inputHandler.start();
+
+ ConsoleInputStream inp = new ConsoleInputStream();
+ ConsoleInputHandler consoleInputHandler = new ConsoleInputHandler(input, inp, outp);
+ consoleInputHandler.getScanner().setBackspace(currentMappings.getBackspace());
+ consoleInputHandler.getScanner().setDel(currentMappings.getDel());
+ consoleInputHandler.getScanner().setCurrentEscapesToKey(currentEscapesToKey);
+ consoleInputHandler.getScanner().setEscapes(currentMappings.getEscapes());
+ ((ConsoleInputScanner)consoleInputHandler.getScanner()).setContext(context);
+ consoleInputHandler.start();
+
+ final CommandSession session;
+ final PrintStream output = new PrintStream(outp);
+
+ session = processor.createSession(inp, output, output);
+ session.put(SCOPE, EQUINOX_SCOPE);
+ session.put(PROMPT, OSGI_PROMPT);
+ session.put(INPUT_SCANNER, consoleInputHandler.getScanner());
+ session.put(SSH_INPUT_SCANNER, inputHandler.getScanner());
+ // Store this closeable object in the session, so that the disconnect command can close it
+ session.put(CLOSEABLE, this);
+ ((ConsoleInputScanner)consoleInputHandler.getScanner()).setSession(session);
+
+ try {
+ if ("true".equals(context.getProperty(USER_STORAGE_PROPERTY_NAME))) {
+ String[] names = SecureUserStore.getUserNames();
+ for (String name : names) {
+ // if the default user is the only user, request creation of a new user and delete the default
+ if (DEFAULT_USER.equals(name)) {
+ if (names.length == 1) {
+ session.getConsole().println("Currently the default user is the only one; since it will be deleted after first login, create a new user:");
+ boolean isUserAdded =false;
+ int count = 0;
+ while (!isUserAdded && count < ADD_USER_COUNTER_LIMIT ){
+ isUserAdded = ((Boolean) session.execute("addUser")).booleanValue();
+ count++;
+ }
+ if (!isUserAdded) {
+ break;
+ }
+ }
+ if (SecureUserStore.existsUser(name)) {
+ SecureUserStore.deleteUser(name);
+ }
+ break;
+ }
+ }
+ }
+ session.execute("gosh --login --noshutdown");
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ session.close();
+ }
+
+ }
+
+ public void close() throws IOException {
+ this.interrupt();
+ sshShell.removeSession(this);
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshShell.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshShell.java
new file mode 100755
index 00000000..f1f4839e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshShell.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.ssh;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.eclipse.equinox.console.common.KEYS;
+import org.eclipse.equinox.console.common.terminal.ANSITerminalTypeMappings;
+import org.eclipse.equinox.console.common.terminal.SCOTerminalTypeMappings;
+import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings;
+import org.eclipse.equinox.console.common.terminal.VT100TerminalTypeMappings;
+import org.eclipse.equinox.console.common.terminal.VT220TerminalTypeMappings;
+import org.eclipse.equinox.console.common.terminal.VT320TerminalTypeMappings;
+import org.osgi.framework.BundleContext;
+
+/**
+ * This class manages a ssh connection. It is responsible for starting a sessions to execute commands
+ * from the ssh. If there are multiple CommandProcessors, a session is started for each of them.
+ *
+ */
+public class SshShell implements Command {
+
+ private List<CommandProcessor> processors;
+ private BundleContext context;
+ private InputStream in;
+ private OutputStream out;
+ private ExitCallback callback;
+ private Map<CommandProcessor, SshSession> commandProcessorToConsoleThreadMap = new HashMap<CommandProcessor, SshSession>();
+
+ private final Map<String, TerminalTypeMappings> supportedEscapeSequences;
+ private static final String DEFAULT_TTYPE = File.separatorChar == '/' ? "XTERM" : "ANSI";
+ private TerminalTypeMappings currentMappings;
+ private Map<String, KEYS> currentEscapesToKey;
+ private static final String TERMINAL_PROPERTY = "TERM";
+
+ public SshShell(List<CommandProcessor> processors, BundleContext context) {
+ this.processors = processors;
+ this.context = context;
+ supportedEscapeSequences = new HashMap<String, TerminalTypeMappings> ();
+ supportedEscapeSequences.put("ANSI", new ANSITerminalTypeMappings());
+ supportedEscapeSequences.put("WINDOWS", new ANSITerminalTypeMappings());
+ supportedEscapeSequences.put("VT100", new VT100TerminalTypeMappings());
+ VT220TerminalTypeMappings vtMappings = new VT220TerminalTypeMappings();
+ supportedEscapeSequences.put("VT220", vtMappings);
+ supportedEscapeSequences.put("XTERM", vtMappings);
+ supportedEscapeSequences.put("VT320", new VT320TerminalTypeMappings());
+ supportedEscapeSequences.put("SCO", new SCOTerminalTypeMappings());
+
+ currentMappings = supportedEscapeSequences.get(DEFAULT_TTYPE);
+ currentEscapesToKey = currentMappings.getEscapesToKey();
+ }
+
+ public void setInputStream(InputStream in) {
+ this.in = in;
+ }
+
+ public void setOutputStream(OutputStream out) {
+ this.out = out;
+ }
+
+ public void setErrorStream(OutputStream err) {
+ // do nothing
+ }
+
+ public void setExitCallback(ExitCallback callback) {
+ this.callback = callback;
+ }
+
+ public synchronized void start(Environment env) throws IOException {
+ String term = env.getEnv().get(TERMINAL_PROPERTY);
+ TerminalTypeMappings mapping = supportedEscapeSequences.get(term.toUpperCase());
+ if(mapping != null) {
+ currentMappings = mapping;
+ currentEscapesToKey = mapping.getEscapesToKey();
+ }
+
+ for (CommandProcessor processor : processors) {
+ createNewSession(processor);
+ }
+ }
+
+ public synchronized void addCommandProcessor(CommandProcessor processor) {
+ createNewSession(processor);
+ }
+
+ public synchronized void removeCommandProcessor(CommandProcessor processor) {
+ Thread consoleSession = commandProcessorToConsoleThreadMap.get(processor);
+ if (consoleSession != null) {
+ consoleSession.interrupt();
+ }
+ }
+
+ private void createNewSession(CommandProcessor processor) {
+ SshSession consoleSession = startNewConsoleSession(processor);
+ commandProcessorToConsoleThreadMap.put(processor, consoleSession);
+ }
+
+ public void destroy() {
+ return;
+ }
+
+ public void onExit() {
+ if (commandProcessorToConsoleThreadMap.values() != null) {
+ for (Thread consoleSession : commandProcessorToConsoleThreadMap.values()) {
+ consoleSession.interrupt();
+ }
+ }
+ callback.onExit(0);
+ }
+
+ public void removeSession(SshSession session) {
+ CommandProcessor processorToRemove = null;
+ for (CommandProcessor processor : commandProcessorToConsoleThreadMap.keySet()) {
+ if (session.equals(commandProcessorToConsoleThreadMap.get(processor))) {
+ processorToRemove = processor;
+ break;
+ }
+ }
+
+ if (processorToRemove != null) {
+ commandProcessorToConsoleThreadMap.remove(processorToRemove);
+ }
+
+ if (commandProcessorToConsoleThreadMap.size() == 0) {
+ onExit();
+ }
+ }
+
+ private SshSession startNewConsoleSession(CommandProcessor processor) {
+ SshSession consoleSession = new SshSession(processor, context, this, in, out, currentMappings, currentEscapesToKey);
+ consoleSession.start();
+ return consoleSession;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshShellFactory.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshShellFactory.java
new file mode 100755
index 00000000..82b7964d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshShellFactory.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.ssh;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.server.Command;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Shell factory used by the SSH server to create a SSH shell
+ *
+ */
+public class SshShellFactory implements Factory<Command> {
+
+ private List<CommandProcessor> processors;
+ private BundleContext context;
+ private Set<SshShell> shells = new HashSet<SshShell>();
+
+ public SshShellFactory(List<CommandProcessor> processors, BundleContext context) {
+ this.processors = processors;
+ this.context = context;
+ }
+
+ public synchronized Command create() {
+ SshShell shell = new SshShell(processors, context);
+ shells.add(shell);
+ return shell;
+ }
+
+ public synchronized void addCommandProcessor (CommandProcessor processor) {
+ processors.add(processor);
+ for (SshShell shell : shells) {
+ shell.addCommandProcessor(processor);
+ }
+ }
+
+ public synchronized void removeCommandProcessor (CommandProcessor processor) {
+ processors.remove(processor);
+ for (SshShell shell : shells) {
+ shell.removeCommandProcessor(processor);
+ }
+ }
+
+ public void exit() {
+ for(SshShell shell : shells) {
+ shell.onExit();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/UserAdminCommand.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/UserAdminCommand.java
new file mode 100755
index 00000000..cdf9b466
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/UserAdminCommand.java
@@ -0,0 +1,447 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.ssh;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Descriptor;
+import org.eclipse.equinox.console.common.Scanner;
+import org.eclipse.equinox.console.storage.DigestUtil;
+import org.eclipse.equinox.console.storage.SecureUserStore;
+import org.eclipse.equinox.console.common.ConsoleInputScanner;
+
+/**
+ * This class provides commands for administering users: adding, removing and listing users; setting or changing password;
+ * resetting password; adding and removing roles
+ *
+ *
+ */
+public class UserAdminCommand {
+ private static final String INPUT_SCANNER = "INPUT_SCANNER";
+ private static final String SSH_INPUT_SCANNER = "SSH_INPUT_SCANNER";
+ private static final String DEFAULT_USER = "equinox";
+ private static final int MINIMAL_PASSWORD_LENGTH = 8;
+ private static final int PASSWORD_INPUT_TRIALS_LIMIT = 3;
+
+ /**
+ * Command for adding a user
+ *
+ * @param args command line arguments in the format -username <username> -password <password> -roles <comma-separated list of user roles (optional)>
+ * @throws Exception
+ */
+ @Descriptor("Add user with password and roles")
+ public void addUser(@Descriptor("-username <username>\r\n-password <password>\r\n-roles <comma-separated list of user roles (optional)>") String[] args) throws Exception {
+ String username = null;
+ String password = null;
+ String roles = "";
+
+ for (int i = 0; i < args.length; i++) {
+ if ("-username".equals(args[i]) && i < args.length - 1) {
+ username = args[i + 1];
+ i++;
+ } else if ("-password".equals(args[i]) && i < args.length - 1) {
+ password = args[i + 1];
+ i++;
+ } else if ("-roles".equals(args[i]) && i < args.length - 1) {
+ roles = args[i + 1];
+ i++;
+ }
+ }
+
+ if (! validateUsername(username)) {
+ throw new Exception("Invalid username");
+ }
+
+ if (password == null) {
+ throw new Exception("Password not specified");
+ }
+
+ if (password.length() < MINIMAL_PASSWORD_LENGTH) {
+ throw new Exception("Password should be at least 8 symblos");
+ }
+
+ SecureUserStore.putUser(username, DigestUtil.encrypt(password), roles);
+
+ if(SecureUserStore.existsUser(DEFAULT_USER)) {
+ SecureUserStore.deleteUser(DEFAULT_USER);
+ }
+ }
+
+ /**
+ * Command for setting or changing the password of a user.
+ *
+ * @param args command-line arguments in the format -username <username> -password <password>
+ * @throws Exception
+ */
+ @Descriptor("Set or change password")
+ public void setPassword(@Descriptor("-username <username>\r\n-password <password>") String[] args) throws Exception {
+ String username = null;
+ String password = null;
+
+ for (int i = 0; i < args.length; i++) {
+ if ("-username".equals(args[i]) && i < args.length - 1) {
+ username = args[i + 1];
+ i++;
+ } else if ("-password".equals(args[i]) && i < args.length - 1) {
+ password = args[i + 1];
+ i++;
+ }
+ }
+
+ if (! validateUsername(username)) {
+ throw new Exception("Invalid username");
+ }
+
+ if (password == null) {
+ throw new Exception("Password not specified");
+ }
+
+ if (password.length() < MINIMAL_PASSWORD_LENGTH) {
+ throw new Exception("Password should be at least 8 symblos");
+ }
+
+ SecureUserStore.setPassword(username, DigestUtil.encrypt(password));
+ }
+
+ /**
+ * Command for adding a user. The command interactively asks for username, password and roles; the
+ * input plain text password is encrypted before storing.
+ *
+ * @param session
+ * @return true if the user was successfully added
+ *
+ * @throws Exception
+ */
+ @Descriptor("Add user with password and roles interactively")
+ public boolean addUser(final CommandSession session) throws Exception {
+
+ ConsoleInputScanner inputScanner = (ConsoleInputScanner) session.get(INPUT_SCANNER);
+ Scanner scanner = (Scanner) session.get(SSH_INPUT_SCANNER);
+
+ try {
+ // switch off the history so that username, password and roles will not be saved in console history
+ if (scanner != null) {
+ inputScanner.toggleHistoryEnabled(false);
+ }
+ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ String username = readUsername(reader);
+ if (!validateUsername(username)) {
+ System.out.println("Invalid username");
+ return false;
+ }
+
+ if (SecureUserStore.existsUser(username)) {
+ System.out.println("Username already exists");
+ return false;
+ }
+
+ // switch off the echo so that the password will not be printed in the console
+ if (scanner != null) {
+ scanner.toggleEchoEnabled(false);
+ }
+ String password = readPassword(reader);
+ if (password == null){
+ return false;
+ }
+ if (scanner != null) {
+ scanner.toggleEchoEnabled(true);
+ }
+
+ String roles = readRoles(reader);
+ if (roles == null) {
+ return false;
+ }
+
+ SecureUserStore.putUser(username, DigestUtil.encrypt(password), roles);
+
+ if(SecureUserStore.existsUser(DEFAULT_USER)) {
+ SecureUserStore.deleteUser(DEFAULT_USER);
+ }
+ } finally {
+ if (scanner != null) {
+ inputScanner.toggleHistoryEnabled(true);
+ scanner.toggleEchoEnabled(true);
+ }
+ }
+
+ return true;
+ }
+
+ @Descriptor("Delete user")
+ public void deleteUser(@Descriptor("username of the user to be deleted") String username) throws Exception {
+ if (SecureUserStore.existsUser(username)) {
+ SecureUserStore.deleteUser(username);
+ }
+ }
+
+ /**
+ * Command to remove the password for a user
+ *
+ * @param username user to remove the password for
+ * @throws Exception
+ */
+ @Descriptor("Reset password")
+ public void resetPassword(@Descriptor("username of the user whose password will be reset") String username) throws Exception {
+ if (!SecureUserStore.existsUser(username)) {
+ throw new Exception("Such user does not exist");
+ }
+
+ SecureUserStore.resetPassword(username);
+ }
+
+ /**
+ * Command to set or change the password for a user; the command asks interactively for the new password; the
+ * input plain text password is encrypted before storing.
+ *
+ * @param session
+ * @param username the user whose password will be changed
+ * @throws Exception
+ */
+ @Descriptor("Set or change password")
+ public void setPassword(final CommandSession session, @Descriptor("Username of the user whose password will be changed") String username) throws Exception {
+ if ("".equals(username)) {
+ System.out.println("Username not specified");
+ return;
+ }
+
+ if (!SecureUserStore.existsUser(username)) {
+ throw new Exception("Such user does not exist");
+ }
+
+ ConsoleInputScanner inputScanner = (ConsoleInputScanner) session.get(INPUT_SCANNER);
+ Scanner scanner = (Scanner) session.get(SSH_INPUT_SCANNER);
+
+ try {
+ // switch off echo and history so that the password is neither echoed to the console, nor saved in history
+ if (scanner != null) {
+ inputScanner.toggleHistoryEnabled(false);
+ scanner.toggleEchoEnabled(false);
+ }
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ String password = readPassword(reader);
+ if (password == null) {
+ return;
+ }
+
+ SecureUserStore.setPassword(username, DigestUtil.encrypt(password));
+ } finally {
+ if (scanner != null) {
+ inputScanner.toggleHistoryEnabled(true);
+ scanner.toggleEchoEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Command to add roles to a user
+ *
+ * @param args command line arguments in the format -username <username>\r\n-roles <comma-separated list of roles to add>
+ * @throws Exception
+ */
+ @Descriptor("Add roles to user")
+ public void addRoles(@Descriptor("-username <username>\r\n-roles <comma-separated list of roles to add>") String[] args) throws Exception {
+ String username = null;
+ String roles = "";
+
+ for (int i = 0; i < args.length; i++) {
+ if ("-username".equals(args[i]) && i < args.length - 1) {
+ username = args[i + 1];
+ i++;
+ } else if ("-roles".equals(args[i]) && i < args.length - 1) {
+ roles = args[i + 1];
+ i++;
+ }
+ }
+
+ if (username == null) {
+ throw new Exception("Username not specified");
+ }
+
+ if("".equals(roles)) {
+ return;
+ }
+
+ if (!SecureUserStore.existsUser(username)) {
+ throw new Exception("Such user does not exist");
+ }
+
+ SecureUserStore.addRoles(username, roles);
+ }
+
+ /**
+ * Command to remove roles for a particular user
+ *
+ * @param args command line arguments in the format -username <username>\r\n-roles <comma-separated list of roles to remove>
+ * @throws Exception
+ */
+ @Descriptor("Remove user roles")
+ public void removeRoles(@Descriptor("-username <username>\r\n-roles <comma-separated list of roles to remove>") String[] args) throws Exception {
+ String username = null;
+ String roles = "";
+
+ for (int i = 0; i < args.length; i++) {
+ if ("-username".equals(args[i]) && i < args.length - 1) {
+ username = args[i + 1];
+ i++;
+ } else if ("-roles".equals(args[i]) && i < args.length - 1) {
+ roles = args[i + 1];
+ i++;
+ }
+ }
+
+ if (username == null) {
+ throw new Exception("Username not specified");
+ }
+
+ if("".equals(roles)) {
+ return;
+ }
+
+ if (!SecureUserStore.existsUser(username)) {
+ throw new Exception("Such user does not exist");
+ }
+
+ SecureUserStore.removeRoles(username, roles);
+ }
+
+ /**
+ * Command to list available users
+ *
+ * @throws Exception
+ */
+ @Descriptor("Lists available users")
+ public void listUsers() throws Exception {
+
+ String[] users = SecureUserStore.getUserNames();
+
+ if(users.length == 0) {
+ System.out.println("No users available");
+ return;
+ }
+
+ for(String user : users) {
+ System.out.println(user);
+ }
+ }
+
+ private String readPassword(BufferedReader reader) {
+ String password = null;
+ int count = 0;
+
+ while (password == null && count < PASSWORD_INPUT_TRIALS_LIMIT){
+ System.out.print("password: ");
+ System.out.flush();
+
+ try {
+ password = reader.readLine();
+ } catch (IOException e) {
+ System.out.println("Error while reading password");
+ return null;
+ }
+
+
+ if (password == null || "".equals(password)) {
+ System.out.println("Password not specified");
+ password = null;
+ } else if (password.length() < MINIMAL_PASSWORD_LENGTH) {
+ System.out.println("Password should be at least 8 symblos");
+ password = null;
+ }
+
+ count++;
+ }
+
+ if (password == null) {
+ return null;
+ }
+
+ String passwordConfirmation = null;
+ count = 0;
+
+ while (passwordConfirmation == null && count < PASSWORD_INPUT_TRIALS_LIMIT){
+ System.out.print("Confirm password: ");
+ System.out.flush();
+
+ try {
+ passwordConfirmation = reader.readLine();
+ if (!password.equals(passwordConfirmation)) {
+ System.out.println("The passwords do not match!");
+ passwordConfirmation = null;
+ }
+ } catch (IOException e) {
+ System.out.println("Error while reading password");
+ return null;
+ }
+
+ count++;
+ }
+ if (passwordConfirmation == null){
+ return null;
+ }
+ return password;
+ }
+
+ private String readUsername (BufferedReader reader) {
+ System.out.print("username: ");
+ System.out.flush();
+ String username = null;
+
+ try {
+ username = reader.readLine();
+ } catch (IOException e) {
+ System.out.println("Error while reading username");
+ return null;
+ }
+
+ if (username == null || "".equals(username)) {
+ System.out.println("Username not specified");
+ return null;
+ }
+
+ return username;
+ }
+
+ private String readRoles (BufferedReader reader){
+ //roles input validation
+ System.out.print("roles: ");
+ System.out.flush();
+ String roles = null;
+ try {
+ roles = reader.readLine();
+ } catch (IOException e) {
+ System.out.println("Error while reading roles");
+ return null;
+ }
+
+ if (roles == null) {
+ roles = "";
+ }
+ return roles;
+ }
+
+ private static boolean validateUsername (String username){
+ if( username == null){
+ return false;
+ }else{
+ Pattern allowedChars = Pattern.compile("[A-Za-z0-9_.]+");
+ Matcher matcher = allowedChars.matcher(username);
+ return matcher.matches();
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/storage/DigestUtil.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/storage/DigestUtil.java
new file mode 100755
index 00000000..fec76ba7
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/storage/DigestUtil.java
@@ -0,0 +1,69 @@
+package org.eclipse.equinox.console.storage;
+
+import java.security.MessageDigest;
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * This class provides utility method for one-way hashing of strings
+ *
+ */
+public class DigestUtil {
+ private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
+ private static final String MD5 = "MD5";
+ private static final String SHA1 = "SHA1";
+
+ /**
+ * Create a one-way hash of an input strings. First a MD5 hash of the input string
+ * is calculated and appended to the string, and then the new string is hashed with SHA1
+ *
+ * @param originalText the string to be hashed
+ * @return hashed string
+ * @throws Exception
+ */
+ public static String encrypt(String originalText)throws Exception{
+ try {
+ String password_salt = appendSalt(originalText);
+ byte[] sha_digest;
+
+ sha_digest = getDigest(password_salt.getBytes(), SHA1);
+ return asHex(sha_digest);
+ } catch (NoSuchAlgorithmException e) {
+ throw new Exception ("Encryption Failed!");
+ }
+ }
+
+ private static String appendSalt(String inputPassword) throws NoSuchAlgorithmException{
+ byte [] salt = getDigest(inputPassword.getBytes(), MD5);
+ return inputPassword + asHex(salt);
+ }
+
+ //byte array into hexademical string
+ private static String asHex(byte[] buf)
+ {
+ char[] chars = new char[2 * buf.length];
+ for (int i = 0; i < buf.length; ++i)
+ {
+ chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4];
+ chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F];
+ }
+ return new String(chars);
+ }
+
+ //generate digest byte[]
+ private static byte[] getDigest(byte[] inputData, String algorithm) throws NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ md.update(inputData);
+ return md.digest();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/storage/SecureUserStore.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/storage/SecureUserStore.java
new file mode 100755
index 00000000..dc6bde77
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/storage/SecureUserStore.java
@@ -0,0 +1,614 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.equinox.console.storage;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * This class implements a storage for users, passwords and roles. The data is stored in a
+ * properties-like file in the format /ssh/<username>/password=<password> and
+ * /ssh/<username>/roles=<comma_separated_list_of_roles>
+ *
+ *
+ */
+public class SecureUserStore {
+
+ private static final String USER_STORE_FILE_NAME = "org.eclipse.equinox.console.jaas.file";
+ private static final String PASSWORD_KEY = "password";
+ private static final String ROLES_KEY = "roles";
+ private static final String SSH_PREFIX = "/ssh";
+ private static final String DELIMITER = "/";
+ private static final int USERNAME_INDEX = 2;
+ private static final int KEY_ELEMENTS_COUNT = 4;
+
+ /**
+ * Gets the usernames of all users.
+ *
+ * @return String array containing the usernames
+ */
+ public static String[] getUserNames() {
+ String userFileLoc = null;
+ InputStream in = null;
+
+ try {
+ userFileLoc = getFileLocation();
+ in = new FileInputStream(userFileLoc);
+ Properties users = null;
+ try {
+ users = populateUserStore(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc);
+ }
+ Set<String> userNames = new HashSet<String>();
+ for (Object key : users.keySet()) {
+ if (!(key instanceof String)) {
+ continue;
+ }
+ String[] parts = ((String) key).split(DELIMITER);
+ // since the key starts with DELIMITER, the first element of key.split(DELIMITER) is an empty string
+ // that is why the result is {"", "ssh", "<username>", "password"} or {"", "ssh", "<username>", "roles"}
+ if (parts.length < KEY_ELEMENTS_COUNT) {
+ continue;
+ }
+ userNames.add(parts[USERNAME_INDEX]);
+ }
+
+ return userNames.toArray(new String[0]);
+
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("File " + userFileLoc + " does not exist");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+ }
+ }
+
+ public static String getPassword(String username) {
+ return getProperty(username, PASSWORD_KEY);
+ }
+
+ public static String getRoles(String username) {
+ return getProperty(username, ROLES_KEY);
+ }
+
+ /**
+ * Stores a user entry to the store.
+ *
+ * @param username the name of the user
+ * @param password the password of the user
+ * @param roles comma-separated list of the roles of the user
+ */
+ public static void putUser(String username, String password, String roles) {
+ String userFileLoc = null;
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ userFileLoc = getFileLocation();
+ in = new FileInputStream(userFileLoc);
+ Properties users = null;
+
+ try {
+ users = populateUserStore(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc);
+ }
+
+ if (existsUser(username, users)){
+ throw new IllegalArgumentException("The user already exists!");
+ }
+
+ if (roles == null) {
+ roles = "";
+ }
+
+ String userPassKey = constructPropertyName(username, PASSWORD_KEY);
+ String userRolesKey = constructPropertyName(username, ROLES_KEY);
+ users.put(userPassKey, password);
+ users.put(userRolesKey, roles);
+
+ out = new FileOutputStream(userFileLoc);
+ try {
+ users.store(out, null);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc);
+ }
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("File " + userFileLoc + " does not exist");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds roles for a particular user
+ *
+ * @param username user to add roles to
+ * @param roles comma-separated list of new roles for the user
+ */
+ public static void addRoles(String username, String roles) {
+ String userFileLoc = null;
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ if (roles == null || roles.length() == 0) {
+ return;
+ }
+
+ userFileLoc = getFileLocation();
+ in = new FileInputStream(userFileLoc);
+ Properties users = null;
+
+ try {
+ users = populateUserStore(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc);
+ }
+
+ String userRolesKey = constructPropertyName(username, ROLES_KEY);
+ String currentRoles = (String)users.remove(userRolesKey);
+ Set<String> rolesSet = new HashSet<String>();
+
+ if (currentRoles.length() > 0) {
+ for (String role : currentRoles.split(",")) {
+ rolesSet.add(role);
+ }
+ }
+
+ for (String role : roles.split(",")) {
+ rolesSet.add(role);
+ }
+
+ StringBuilder builder = new StringBuilder();
+ for (String role : rolesSet) {
+ builder.append(role);
+ builder.append(",");
+ }
+ builder.deleteCharAt(builder.lastIndexOf(","));
+
+ users.put(userRolesKey, builder.toString());
+
+ out = new FileOutputStream(userFileLoc);
+ try {
+ users.store(out, null);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc);
+ }
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("File " + userFileLoc + " does not exist");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes roles from a user
+ *
+ * @param username user to remove roles from
+ * @param rolesToRemove comma-separated list of roles to be removed
+ */
+ public static void removeRoles(String username, String rolesToRemove) {
+ String userFileLoc = null;
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ if(rolesToRemove == null || rolesToRemove.length() == 0) {
+ return;
+ }
+
+ userFileLoc = getFileLocation();
+ in = new FileInputStream(userFileLoc);
+ Properties users = null;
+
+ try {
+ users = populateUserStore(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc);
+ }
+
+ String userRolesKey = constructPropertyName(username, ROLES_KEY);
+ String currentRoles = (String)users.remove(userRolesKey);
+ Set<String> rolesSet = new HashSet<String>();
+
+ for (String role : currentRoles.split(",")) {
+ rolesSet.add(role);
+ }
+
+ for (String role : rolesToRemove.split(",")) {
+ rolesSet.remove(role);
+ }
+
+ StringBuilder builder = new StringBuilder();
+ for (String role : rolesSet) {
+ builder.append(role);
+ builder.append(",");
+ }
+ builder.deleteCharAt(builder.lastIndexOf(","));
+
+ users.put(userRolesKey, builder.toString());
+
+ out = new FileOutputStream(userFileLoc);
+ try {
+ users.store(out, null);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc);
+ }
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("File " + userFileLoc + " does not exist");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes an entry for the user from the store.
+ *
+ * @param username user to be removed
+ */
+ public static void deleteUser(String username) {
+ String userFileLoc = null;
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ userFileLoc = getFileLocation();
+ in = new FileInputStream(userFileLoc);
+ Properties users = null;
+
+ try {
+ users = populateUserStore(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc);
+ }
+
+ if (!existsUser(username, users)){
+ throw new IllegalArgumentException("The user does not exist!");
+ }
+
+// Set<Object> keys = users.keySet();
+// for (Object key : keys) {
+// if ((key instanceof String) && ((String) key).contains(DELIMITER + username + DELIMITER)) {
+// users.remove(key);
+// }
+// }
+ String rolesProperty = constructPropertyName(username, ROLES_KEY);
+ String passwordProperty = constructPropertyName(username, PASSWORD_KEY);
+
+ users.remove(rolesProperty);
+ users.remove(passwordProperty);
+
+ out = new FileOutputStream(userFileLoc);
+ try {
+ users.store(out, null);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc);
+ }
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("File " + userFileLoc + " does not exist");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes the password for a user
+ *
+ * @param username user to reset the password
+ */
+ public static void resetPassword(String username) {
+ String userFileLoc = null;
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ userFileLoc = getFileLocation();
+ in = new FileInputStream(userFileLoc);
+ Properties users = null;
+
+ try {
+ users = populateUserStore(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc);
+ }
+
+ if (!existsUser(username, users)){
+ throw new IllegalArgumentException("The user does not exist!");
+ }
+
+ for (Object key : users.keySet()) {
+ if (key instanceof String && ((String) key).contains(DELIMITER + username + DELIMITER + PASSWORD_KEY)) {
+ users.remove(key);
+ break;
+ }
+ }
+
+ out = new FileOutputStream(userFileLoc);
+ try {
+ users.store(out, null);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc);
+ }
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("File " + userFileLoc + " does not exist");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets or changes the password for a user
+ *
+ * @param username user to set tha password for
+ * @param password the new password
+ */
+ public static void setPassword(String username, String password) {
+ String userFileLoc = null;
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ userFileLoc = getFileLocation();
+ in = new FileInputStream(userFileLoc);
+ Properties users = null;
+
+ try {
+ users = populateUserStore(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc);
+ }
+
+ if (!existsUser(username, users)){
+ throw new IllegalArgumentException("The user does not exist!");
+ }
+
+ String passwordPropertyName = constructPropertyName(username, PASSWORD_KEY);
+ for (Object key : users.keySet()) {
+ if ((key instanceof String) && ((String) key).contains(passwordPropertyName)) {
+ users.remove(key);
+ break;
+ }
+ }
+
+ users.put(passwordPropertyName, password);
+
+ out = new FileOutputStream(userFileLoc);
+ try {
+ users.store(out, null);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc);
+ }
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("File " + userFileLoc + " does not exist");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ /**
+ * CHecks if an entry for a user exists in the store
+ *
+ * @param username user to check
+ * @return true if there is an entry for this user in the store, false otherwise
+ */
+ public static boolean existsUser(String username) {
+ String userFileLoc = null;
+ InputStream in = null;
+ try {
+ userFileLoc = getFileLocation();
+ in = new FileInputStream(userFileLoc);
+ Properties users = null;
+
+ try {
+ users = populateUserStore(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc);
+ }
+
+ return existsUser(username, users);
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("File " + userFileLoc + " does not exist");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates the store file if it does not exist
+ *
+ * @throws IOException
+ */
+ public static void initStorage() throws IOException {
+ String userFileLoc = getFileLocation();
+ File file = new File(userFileLoc);
+ if (!file.exists()) {
+ OutputStream out = null;
+ try {
+ Properties props = new Properties();
+ out = new FileOutputStream(file);
+ props.store(out, null);
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+ }
+
+ private static String getProperty(String username, String propertyName) {
+ String userFileLoc = null;
+ InputStream in = null;
+ try {
+ userFileLoc = getFileLocation();
+ in = new FileInputStream(userFileLoc);
+ Properties users = null;
+
+ try {
+ users = populateUserStore(in);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc);
+ }
+
+ return users.getProperty(constructPropertyName(username, propertyName));
+ } catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("File " + userFileLoc + " does not exist");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //do nothing
+ }
+ }
+ }
+ }
+
+ private static Properties populateUserStore(InputStream in) throws IOException {
+ Properties userProperties = new Properties();
+ userProperties.load(in);
+ return userProperties;
+ }
+
+ private static String getFileLocation(){
+ String userFileLoc = System.getProperty(USER_STORE_FILE_NAME);
+ if (userFileLoc == null) {
+ throw new IllegalArgumentException("Property " + USER_STORE_FILE_NAME + " is not set; cannot use JAAS authentication");
+ }
+
+ return userFileLoc;
+ }
+
+ private static String constructPropertyName(String user, String propertyName) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(SSH_PREFIX);
+ builder.append(DELIMITER);
+ builder.append(user);
+ builder.append(DELIMITER);
+ builder.append(propertyName);
+ return builder.toString();
+ }
+
+ private static boolean existsUser(String username, Properties users) {
+ for (Object user : users.keySet()) {
+ if (user instanceof String && ((String) user).contains(DELIMITER + username + DELIMITER)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}

Back to the top