blob: 1802fa9da705c27fdd22f9d02cddb0e8649a9f70 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Oak Ridge National Laboratory and others.
* 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
*******************************************************************************/
package org.eclipse.remote.internal.proxy.core;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.util.Arrays;
import java.util.Base64;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jsch.core.IJSchService;
import org.eclipse.remote.core.IRemoteConnection;
import org.eclipse.remote.core.IRemoteConnectionHostService;
import org.eclipse.remote.core.IRemoteConnectionWorkingCopy;
import org.eclipse.remote.core.IUserAuthenticatorService;
import org.eclipse.remote.core.exception.RemoteConnectionException;
import org.eclipse.remote.internal.jsch.core.JSchUserInfo;
import org.eclipse.remote.proxy.protocol.core.StreamChannelManager;
import org.osgi.framework.Bundle;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
public class ProxyConnectionBootstrap {
private final IJSchService jSchService;
private Session session;
private ChannelExec exec;
private class Context {
private State state;
private String osName;
private String osArch;
private final SubMonitor monitor;
private final BufferedReader reader;
private final BufferedWriter writer;
public Context(BufferedReader reader, BufferedWriter writer, IProgressMonitor monitor) {
this.reader = reader;
this.writer = writer;
this.monitor = SubMonitor.convert(monitor);
setState(States.INIT);
}
State getState() {
return state;
}
SubMonitor getMonitor() {
return monitor;
}
void setState(State state) {
this.state = state;
}
String getOSName() {
return osName;
}
void setOSName(String osName) {
this.osName = osName;
}
String getOSArch() {
return osArch;
}
void setOSArch(String osArch) {
this.osArch = osArch;
}
}
private interface State {
/**
* @return true to keep processing, false to read more data.
*/
boolean process(Context context) throws IOException;
}
private enum States implements State {
INIT {
@Override
public boolean process(Context context) throws IOException {
context.getMonitor().subTask("Initializing");
String line = context.reader.readLine();
context.getMonitor().worked(1);
if (line.equals("running")) {
context.setState(States.CHECK);
return true;
}
return false;
}
},
CHECK {
@Override
public boolean process(Context context) throws IOException {
context.getMonitor().subTask("Validating environment");
String bundleName = "org.eclipse.remote.proxy.server.core";
Bundle serverBundle = Platform.getBundle(bundleName);
if (serverBundle == null) {
throw new IOException("Unable to locate server bundle " + bundleName);
}
context.writer.write("check " + serverBundle.getVersion() + "\n");
context.writer.flush();
String line = context.reader.readLine();
while (line != null) {
context.getMonitor().worked(2);
String[] parts = line.split(":");
switch (parts[0]) {
case "ok":
String[] status = parts[1].split("/");
context.setOSName(status[1]);
context.setOSArch(status[2]);
context.setState(status[0].equals("proxy") ? States.START : States.DOWNLOAD);
return true;
case "fail":
System.out.println("fail:"+parts[1]);
return false;
case "debug":
System.err.println(line);
break;
default:
System.err.println("Invalid response from bootstrap script: " + line);
return false;
}
line = context.reader.readLine();
}
return false;
}
},
DOWNLOAD {
@Override
public boolean process(Context context) throws IOException {
context.getMonitor().subTask("Updating server proxy");
String bundleName = "org.eclipse.remote.proxy.server." + context.getOSName() + "." + context.getOSArch();
Bundle serverBundle = Platform.getBundle(bundleName);
if (serverBundle == null) {
throw new IOException("Unable to locate server bundle " + bundleName);
}
URL fileURL = FileLocator.find(serverBundle, new Path("proxy.server.tar.gz"), null);
if (fileURL == null) {
return false;
}
File file = new File(FileLocator.toFileURL(fileURL).getFile());
long count = file.length() / 510;
context.writer.write("download " + count + "\n");
context.writer.flush();
context.getMonitor().worked(2);
if (downloadFile(file, context.writer, context.getMonitor().newChild(5))) {
String line = context.reader.readLine();
while (line != null) {
String[] parts = line.split(":");
switch (parts[0]) {
case "ok":
context.setState(States.START);
return true;
case "fail":
System.out.println("fail:"+parts[1]);
return false;
case "debug":
System.err.println(line);
break;
default:
System.err.println("Invalid response from bootstrap script: " + line);
return false;
}
line = context.reader.readLine();
}
}
return false;
}
private boolean downloadFile(File file, BufferedWriter writer, IProgressMonitor monitor) {
SubMonitor subMon = SubMonitor.convert(monitor, 10);
try {
Base64.Encoder encoder = Base64.getEncoder();
FileInputStream in = new FileInputStream(file);
byte[] buf = new byte[510]; // Multiple of 3
int n;
while ((n = in.read(buf)) >= 0) {
if (n < 510) {
writer.write(encoder.encodeToString(Arrays.copyOf(buf, n)) + "\n");
} else {
writer.write(encoder.encodeToString(buf));
}
subMon.setWorkRemaining(8);
}
writer.flush();
in.close();
return true;
} catch (IOException e) {
return false;
}
}
},
START {
@Override
public boolean process(Context context) throws IOException {
context.getMonitor().subTask("Starting server");
context.writer.write("start\n");
context.writer.flush();
return false; // Finished
}
}
}
public ProxyConnectionBootstrap() {
jSchService = Activator.getService(IJSchService.class);
}
public StreamChannelManager run(IRemoteConnection connection, IProgressMonitor monitor) throws RemoteConnectionException {
SubMonitor subMon = SubMonitor.convert(monitor, 20);
try {
final Channel chan = openChannel(connection, subMon.newChild(10));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(chan.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(chan.getInputStream()));
subMon.beginTask("Checking server installation", 10);
subMon.subTask("Loading bootstrap shell");
URL fileURL = FileLocator.find(Activator.getDefault().getBundle(), new Path("bootstrap.sh"), null);
if (fileURL == null) {
throw new RemoteConnectionException("Unable to locate bootstrap shell");
}
File file = new File(FileLocator.toFileURL(fileURL).getFile());
BufferedReader scriptReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String line;
while ((line = scriptReader.readLine()) != null) {
writer.write(line + "\n");
}
scriptReader.close();
writer.flush();
subMon.worked(2);
Context context = new Context(reader, writer, subMon.newChild(8));
while (context.getState().process(context)) {
// do state machine
}
if (context.getState() != States.START) {
context.writer.write("exit\n");
context.writer.flush();
throw new RemoteConnectionException("Unable to start server");
}
new Thread("server error stream") {
@Override
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(chan.getExtInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.err.println("server: "+ line);
}
} catch (IOException e) {
// Ignore and terminate thread
}
}
}.start();
return new StreamChannelManager(chan.getInputStream(), chan.getOutputStream());
} catch (IOException | CoreException e) {
throw new RemoteConnectionException(e.getMessage());
}
}
private Channel openChannel(IRemoteConnection connection, IProgressMonitor monitor) throws RemoteConnectionException {
IRemoteConnectionWorkingCopy wc = connection.getWorkingCopy();
IRemoteConnectionHostService hostService = wc.getService(IRemoteConnectionHostService.class);
IUserAuthenticatorService authService = wc.getService(IUserAuthenticatorService.class);
try {
session = jSchService.createSession(hostService.getHostname(), hostService.getPort(), hostService.getUsername());
session.setUserInfo(new JSchUserInfo(hostService, authService));
if (hostService.usePassword()) {
session.setConfig("PreferredAuthentications", "password,keyboard-interactive,gssapi-with-mic,publickey"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
session.setConfig("PreferredAuthentications", "publickey,gssapi-with-mic,password,keyboard-interactive"); //$NON-NLS-1$ //$NON-NLS-2$
}
String password = hostService.getPassword();
if (!password.isEmpty()) {
session.setPassword(password);
}
jSchService.connect(session, hostService.getTimeout() * 1000, monitor);
if (monitor.isCanceled()) {
throw new RemoteConnectionException("User canceled connection open");
}
exec = (ChannelExec) session.openChannel("exec"); //$NON-NLS-1$
exec.setCommand("/bin/sh -l");
exec.connect();
return exec;
} catch (JSchException e) {
throw new RemoteConnectionException(e.getMessage());
}
}
}