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