1 /******************************************************************************* 2 * Copyright (c) 2011, 2017 SAP AG and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Lazar Kirchev, SAP AG - initial API and implementation 13 *******************************************************************************/ 14 15 package org.eclipse.equinox.console.ssh; 16 17 import java.io.IOException; 18 import java.net.BindException; 19 import java.net.ServerSocket; 20 import java.util.ArrayList; 21 import java.util.Dictionary; 22 import java.util.Hashtable; 23 import java.util.List; 24 25 import org.apache.felix.service.command.CommandProcessor; 26 import org.apache.felix.service.command.Descriptor; 27 import org.eclipse.equinox.console.storage.DigestUtil; 28 import org.eclipse.equinox.console.storage.SecureUserStore; 29 import org.osgi.framework.BundleContext; 30 import org.osgi.framework.Constants; 31 import org.osgi.framework.ServiceRegistration; 32 import org.osgi.service.cm.ConfigurationException; 33 import org.osgi.service.cm.ManagedService; 34 35 /** 36 * This class implements a command for starting/stopping a simple ssh server. 37 * 38 */ 39 public class SshCommand { 40 private String defaultHost = null; 41 private int defaultPort; 42 private List<CommandProcessor> processors = new ArrayList<>(); 43 private String host = null; 44 private int port; 45 private SshServ sshServ; 46 private BundleContext context; 47 private ServiceRegistration<?> configuratorRegistration; 48 private boolean isEnabled = false; 49 private final Object lock = new Object(); 50 51 private static final String DEFAULT_USER = "equinox"; 52 private static final String DEFAULT_PASSWORD = "equinox"; 53 private static final String DEFAULT_USER_STORE_PROPERTY = "osgi.console.ssh.useDefaultSecureStorage"; 54 private static final String HOST = "host"; 55 private static final String PORT = "port"; 56 private static final String USE_CONFIG_ADMIN_PROP = "osgi.console.useConfigAdmin"; 57 private static final String SSH_PID = "osgi.console.ssh"; 58 private static final String ENABLED = "enabled"; 59 SshCommand(CommandProcessor processor, BundleContext context)60 public SshCommand(CommandProcessor processor, BundleContext context) { 61 processors.add(processor); 62 this.context = context; 63 64 if ("true".equals(context.getProperty(USE_CONFIG_ADMIN_PROP))) { 65 Dictionary<String, String> sshProperties = new Hashtable<>(); 66 sshProperties.put(Constants.SERVICE_PID, SSH_PID); 67 try { 68 synchronized (lock) { 69 configuratorRegistration = context.registerService(ManagedService.class.getName(), new SshConfigurator(), sshProperties); 70 } 71 } catch (NoClassDefFoundError e) { 72 System.out.println("Configuration Admin not available!"); 73 return; 74 } 75 } else { 76 parseHostAndPort(); 77 } 78 } 79 parseHostAndPort()80 private void parseHostAndPort() { 81 String sshPort = null; 82 String consolePropValue = context.getProperty(SSH_PID); 83 if(consolePropValue != null) { 84 int index = consolePropValue.lastIndexOf(":"); 85 if (index > -1) { 86 defaultHost = consolePropValue.substring(0, index); 87 } 88 sshPort = consolePropValue.substring(index + 1); 89 isEnabled = true; 90 } 91 if (sshPort != null && !"".equals(sshPort)) { 92 try { 93 defaultPort = Integer.parseInt(sshPort); 94 } catch (NumberFormatException e) { 95 // do nothing 96 } 97 } 98 } 99 startService()100 public synchronized void startService() { 101 Dictionary<String, Object> properties = new Hashtable<>(); 102 properties.put("osgi.command.scope", "equinox"); 103 properties.put("osgi.command.function", new String[] {"ssh"}); 104 if ((port > 0 || defaultPort > 0) && isEnabled == true) { 105 try{ 106 ssh(new String[]{"start"}); 107 } catch (Exception e) { 108 System.out.println("Cannot start ssh. Reason: " + e.getMessage()); 109 e.printStackTrace(); 110 } 111 } 112 context.registerService(SshCommand.class.getName(), this, properties); 113 } 114 115 @Descriptor("start/stop a ssh server") ssh(String[] arguments)116 public synchronized void ssh(String[] arguments) throws Exception { 117 String command = null; 118 String newHost = null; 119 int newPort = 0; 120 121 for(int i = 0; i < arguments.length; i++) { 122 if("-?".equals(arguments[i]) || "-help".equals(arguments[i])) { 123 printHelp(); 124 return; 125 } else if("start".equals(arguments[i])) { 126 command = "start"; 127 } else if ("stop".equals(arguments[i])) { 128 command = "stop"; 129 } else if ("-port".equals(arguments[i]) && (arguments.length > i + 1)) { 130 i++; 131 newPort = Integer.parseInt(arguments[i]); 132 } else if ("-host".equals(arguments[i]) && (arguments.length > i + 1)) { 133 i++; 134 newHost = arguments[i]; 135 } else { 136 throw new Exception("Unrecognized ssh command/option " + arguments[i]); 137 } 138 } 139 140 if (command == null) { 141 throw new Exception("No ssh command specified"); 142 } 143 144 if (newPort != 0) { 145 port = newPort; 146 } else if (port == 0) { 147 port = defaultPort; 148 } 149 150 if (port == 0) { 151 throw new Exception("No ssh port specified"); 152 } 153 154 if (newHost != null) { 155 host = newHost; 156 } else { 157 host = defaultHost; 158 } 159 160 if ("start".equals(command)) { 161 if (sshServ != null) { 162 throw new IllegalStateException("ssh is already running on port " + port); 163 } 164 165 checkPortAvailable(port); 166 167 sshServ = new SshServ(processors, context, host, port); 168 sshServ.setName("equinox ssh"); 169 170 if ("true".equals(context.getProperty(DEFAULT_USER_STORE_PROPERTY))) { 171 try { 172 checkUserStore(); 173 registerUserAdmin(); 174 } catch (NoClassDefFoundError e) { 175 System.out.println("If you want to use secure storage, please install Equinox security bundle and its dependencies"); 176 sshServ = null; 177 return; 178 } catch (IOException e) { 179 e.printStackTrace(); 180 sshServ = null; 181 return; 182 } 183 } 184 185 try { 186 sshServ.start(); 187 } catch (RuntimeException e) { 188 sshServ = null; 189 return; 190 } 191 } else if ("stop".equals(command)) { 192 if (sshServ == null) { 193 System.out.println("ssh is not running."); 194 return; 195 } 196 197 sshServ.stopSshServer(); 198 sshServ = null; 199 } 200 } 201 addCommandProcessor(CommandProcessor processor)202 public synchronized void addCommandProcessor(CommandProcessor processor) { 203 processors.add(processor); 204 if (sshServ != null) { 205 sshServ.addCommandProcessor(processor); 206 } 207 } 208 removeCommandProcessor(CommandProcessor processor)209 public synchronized void removeCommandProcessor(CommandProcessor processor) { 210 processors.remove(processor); 211 if (sshServ != null) { 212 sshServ.removeCommandProcessor(processor); 213 } 214 } 215 checkPortAvailable(int port)216 private void checkPortAvailable(int port) throws Exception { 217 try (ServerSocket socket = new ServerSocket(port)){ 218 } catch (BindException e) { 219 throw new Exception ("Port " + port + " already in use"); 220 } 221 } 222 223 /* 224 * Register user administration commands 225 */ registerUserAdmin()226 private void registerUserAdmin() { 227 Dictionary<String, Object> properties = new Hashtable<>(); 228 properties.put("osgi.command.scope", "equinox"); 229 properties.put("osgi.command.function", new String[] {"addUser", "addUser", "deleteUser", "resetPassword", "setPassword", "addRoles", "removeRoles", "listUsers"}); 230 context.registerService(UserAdminCommand.class.getName(), new UserAdminCommand(), properties); 231 } 232 233 /* 234 * Create user store if not available. Add the default user, if there is no other user in the store. 235 */ checkUserStore()236 private void checkUserStore() throws Exception { 237 SecureUserStore.initStorage(); 238 if(SecureUserStore.getUserNames().length == 0) { 239 SecureUserStore.putUser(DEFAULT_USER, DigestUtil.encrypt(DEFAULT_PASSWORD), null ); 240 } 241 } 242 printHelp()243 private void printHelp() { 244 StringBuilder help = new StringBuilder(); 245 help.append("ssh - start simple ssh server"); 246 help.append("\n"); 247 help.append("Usage: ssh start | stop [-port port] [-host host]"); 248 help.append("\n"); 249 help.append("\t"); 250 help.append("-port"); 251 help.append("\t"); 252 help.append("listen port (default="); 253 help.append(defaultPort); 254 help.append(")"); 255 help.append("\n"); 256 help.append("\t"); 257 help.append("-host"); 258 help.append("\t"); 259 help.append("local host address to listen on (default is none - listen on all network interfaces)"); 260 help.append("\n"); 261 help.append("\t"); 262 help.append("-?, -help"); 263 help.append("\t"); 264 help.append("show help"); 265 System.out.println(help.toString()); 266 } 267 268 class SshConfigurator implements ManagedService { 269 private Dictionary<String, Object> properties; 270 @Override updated(Dictionary<String, ?> props)271 public synchronized void updated(Dictionary<String, ?> props) throws ConfigurationException { 272 if (props != null) { 273 @SuppressWarnings("unchecked") 274 Dictionary<String, Object> unchecked = (Dictionary<String, Object>) props; 275 properties = unchecked; 276 properties.put(Constants.SERVICE_PID, SSH_PID); 277 } else { 278 return; 279 } 280 281 defaultPort = Integer.parseInt(((String)properties.get(PORT))); 282 defaultHost = (String)properties.get(HOST); 283 if (properties.get(ENABLED) == null) { 284 isEnabled = false; 285 } else { 286 isEnabled = Boolean.parseBoolean((String)properties.get(ENABLED)); 287 } 288 synchronized (lock) { 289 configuratorRegistration.setProperties(properties); 290 } 291 if (sshServ == null && isEnabled == true) { 292 try { 293 ssh(new String[]{"start"}); 294 } catch (Exception e) { 295 System.out.println("Cannot start ssh: " + e.getMessage()); 296 e.printStackTrace(); 297 } 298 } 299 } 300 301 } 302 } 303