Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[dsdp-tm-dev] RSE SSH X11 Portforwarding

Hi,

I've been working on a patch that adds X11 forwarding over SSH to the RSE. I've submitted a patch to the bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=195170 . I just wanted to post here to let people know about it and get some feedback. Hopefully with the aim of getting it into the next version of the RSE.

Legal Message: I,John-Paul Stanford, declare that I developed attached code from scratch, without referencing any 3rd party materials except material licensed under the EPL. I am authorized by my employer to make this contribution under the EPL.

Cheers,
John-Paul.

-- IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium.  Thank you.
### Eclipse Workspace Patch 1.0
#P org.eclipse.rse.connectorservice.ssh
Index: src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorResources.properties
===================================================================
RCS file: /cvsroot/dsdp/org.eclipse.tm.rse/plugins/org.eclipse.rse.connectorservice.ssh/src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorResources.properties,v
retrieving revision 1.6
diff -u -r1.6 SshConnectorResources.properties
--- src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorResources.properties	19 Mar 2009 23:16:03 -0000	1.6
+++ src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorResources.properties	7 Jul 2010 08:52:05 -0000
@@ -41,3 +41,8 @@
 SSH_SETTINGS_LABEL=SSH Settings
 PROPERTY_LABEL_TIMEOUT=timeout (sec)
 PROPERTY_LABEL_KEEPALIVE=keepalive (sec)
+PROPERTY_LABEL_X_PORT_FORWARDING=X port forwarding
+PROPERTY_LABEL_X_DISPLAY_LOCATION=X display location
+
+UnableToFindXAuthorityFile=Unable to find .Xauthority file
+UnableToParseXDisplay=Unable to parse the X display {0}
\ No newline at end of file
Index: src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorResources.java
===================================================================
RCS file: /cvsroot/dsdp/org.eclipse.tm.rse/plugins/org.eclipse.rse.connectorservice.ssh/src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorResources.java,v
retrieving revision 1.6
diff -u -r1.6 SshConnectorResources.java
--- src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorResources.java	19 Mar 2009 23:16:03 -0000	1.6
+++ src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorResources.java	7 Jul 2010 08:52:05 -0000
@@ -19,7 +19,8 @@
 import org.eclipse.osgi.util.NLS;
 
 public class SshConnectorResources extends NLS {
-	private static final String BUNDLE_NAME = "org.eclipse.rse.internal.connectorservice.ssh.SshConnectorResources"; //$NON-NLS-1$
+	private static final String BUNDLE_NAME = "org.eclipse.rse.internal.connectorservice.ssh.SshConnectorResources"; //$NON-NLS-1$    
+    
 	static {
 		NLS.initializeMessages(BUNDLE_NAME, SshConnectorResources.class);
 	}
@@ -50,5 +51,10 @@
 	public static String SSH_SETTINGS_LABEL;
 	public static String PROPERTY_LABEL_TIMEOUT;
 	public static String PROPERTY_LABEL_KEEPALIVE;
+	public static String PROPERTY_LABEL_X_PORT_FORWARDING;
+	public static String PROPERTY_LABEL_X_DISPLAY_LOCATION;
+	
+	public static String UnableToFindXAuthorityFile;
+	public static String UnableToParseXDisplay;
 
 }
Index: src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorService.java
===================================================================
RCS file: /cvsroot/dsdp/org.eclipse.tm.rse/plugins/org.eclipse.rse.connectorservice.ssh/src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorService.java,v
retrieving revision 1.26
diff -u -r1.26 SshConnectorService.java
--- src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorService.java	19 Mar 2009 23:16:03 -0000	1.26
+++ src/org/eclipse/rse/internal/connectorservice/ssh/SshConnectorService.java	7 Jul 2010 08:52:06 -0000
@@ -23,7 +23,12 @@
 
 package org.eclipse.rse.internal.connectorservice.ssh;
 
+import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Iterator;
+import java.util.List;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
@@ -79,6 +84,8 @@
 	private static final String PROPERTY_SET_SSH_SETTINGS = "SSH Settings"; //$NON-NLS-1$
 	private static final String PROPERTY_KEY_TIMEOUT = "timeout(sec)"; //$NON-NLS-1$
 	private static final String PROPERTY_KEY_KEEPALIVE = "keepalive(sec)"; //$NON-NLS-1$
+	private static final String PROPERTY_KEY_X_PORT_FORWARDING = "x-port-forwarding"; //$NON-NLS-1$
+	private static final String PROPERTY_KEY_X_DISPLAY_LOCATION = "x-display-location"; //$NON-NLS-1$
 
 	private Session session;
 	private SessionLostHandler fSessionLostHandler;
@@ -101,8 +108,9 @@
 	 * (for non-interactive usage, for instance), or in order to change
 	 * the Jsch config (for instance, in order to switch off strict
 	 * host key checking or in order to add specific ciphers).
+	 * @throws SystemOperationFailedException Thronw if thier is a problem setting up X11 Forwarding 
 	 */
-	protected Session createSession(String username, String password, String hostname, int port, UserInfo wrapperUI, IProgressMonitor monitor) throws JSchException {
+	protected Session createSession(String username, String password, String hostname, int port, UserInfo wrapperUI, IProgressMonitor monitor) throws JSchException, SystemOperationFailedException {
 		IJSchService service = Activator.getDefault().getJSchService();
 		if (service == null)
 			return null;
@@ -135,6 +143,14 @@
 			session.setServerAliveInterval(keepalive);
 		}
 
+		boolean xPortForwading = false;
+		String xPortForwadingStr = propertySet.getPropertyValue(PROPERTY_KEY_X_PORT_FORWARDING);
+		xPortForwading = Boolean.parseBoolean(xPortForwadingStr);
+		session.setConfig(PROPERTY_KEY_X_PORT_FORWARDING, xPortForwadingStr);
+		if (xPortForwading) {
+		    String xDisplayLocation = propertySet.getPropertyValue(PROPERTY_KEY_X_DISPLAY_LOCATION);
+		    setupX11Display(session,xDisplayLocation);
+		}
 
 		session.setServerAliveCountMax(6); //give up after 6 tries (remote will be dead after 30 min)
 		if (password != null)
@@ -143,7 +159,40 @@
 		return session;
 	}
 
-	static void shutdown() {
+	private void setupX11Display(Session session, String xDisplayLocation) throws JSchException, SystemOperationFailedException {
+	    try {	        
+	        int pos = xDisplayLocation.indexOf(':');
+	        if (pos == -1) {
+	            throw new SystemOperationFailedException(Activator.PLUGIN_ID, NLS.bind(SshConnectorResources.UnableToParseXDisplay,xDisplayLocation));
+	        }
+	        InetAddress xhost=InetAddress.getByName(xDisplayLocation.substring(0,pos ));
+	        int xport;
+	        try {
+	            xport = Integer.parseInt(xDisplayLocation.substring(pos+1));
+	        }
+	        catch (NumberFormatException e) {
+	            throw new SystemOperationFailedException(Activator.PLUGIN_ID, NLS.bind(SshConnectorResources.UnableToParseXDisplay,xDisplayLocation), e);	             
+	        }
+    	    XAuthReader reader = new XAuthReader();
+    	    List entries = reader.parse();
+    	    Iterator it = entries.iterator();
+    	    while (it.hasNext()) {
+    	        XAuthEntry auth = (XAuthEntry) it.next();
+    	        if (auth.getAddressType()==XAuthEntry.ADDRESS_TYPE_IPV4 &&
+    	            auth.getCookieType().equals("MIT-MAGIC-COOKIE-1") && //$NON-NLS-1$
+    	            auth.getAddress().equals(xhost) && auth.getPort() == xport) {
+    	            session.setX11Host(xhost.getHostAddress()); 
+    	            session.setX11Port(6000+xport);
+    	            session.setX11Cookie(auth.getValue());    	            
+    	        }    	        
+    	    }
+	    }
+	    catch (IOException e) {
+	        throw new SystemOperationFailedException(Activator.PLUGIN_ID, NLS.bind(SshConnectorResources.UnableToParseXDisplay,xDisplayLocation), e);
+	    }
+    }
+
+    static void shutdown() {
 		//TODO: Store all Jsch sessions in a pool and disconnect them on shutdown
 	}
 
@@ -178,7 +227,7 @@
 		try {
 			session = createSession(user, password, host, getSshPort(),
 					userInfo, monitor);
-
+			
 			//java.util.Hashtable config=new java.util.Hashtable();
 			//config.put("StrictHostKeyChecking", "no");
 			//session.setConfig(config);
@@ -639,6 +688,28 @@
 			p = propertySet.addProperty(PROPERTY_KEY_KEEPALIVE,"300", PropertyType.getIntegerPropertyType()); //$NON-NLS-1$
 		}
 		p.setLabel(SshConnectorResources.PROPERTY_LABEL_KEEPALIVE);
+		
+		//X port forwarding
+		p = propertySet.getProperty(PROPERTY_KEY_X_PORT_FORWARDING);
+		if (p==null) {
+		    p = propertySet.addProperty(PROPERTY_KEY_X_PORT_FORWARDING,"false", PropertyType.getBooleanPropertyType()); //$NON-NLS-1$
+		}
+		p.setLabel(SshConnectorResources.PROPERTY_LABEL_X_PORT_FORWARDING);
+		
+		// X display location
+		p = propertySet.getProperty(PROPERTY_KEY_X_DISPLAY_LOCATION);
+		if (p==null) {
+		    String hostname = null;
+		    try {
+		        hostname = InetAddress.getLocalHost().getHostName();
+		    }
+		    catch (UnknownHostException e) {
+		        hostname = "localhost"; //$NON-NLS-1$
+		    }
+		    p = propertySet.addProperty(PROPERTY_KEY_X_DISPLAY_LOCATION,hostname+":0", PropertyType.getStringPropertyType()); //$NON-NLS-1$
+        }
+        p.setLabel(SshConnectorResources.PROPERTY_LABEL_X_DISPLAY_LOCATION);
+		
 		return propertySet;
 	}
 
Index: src/org/eclipse/rse/internal/connectorservice/ssh/XAuthReader.java
===================================================================
RCS file: src/org/eclipse/rse/internal/connectorservice/ssh/XAuthReader.java
diff -N src/org/eclipse/rse/internal/connectorservice/ssh/XAuthReader.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/rse/internal/connectorservice/ssh/XAuthReader.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,141 @@
+package org.eclipse.rse.internal.connectorservice.ssh;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * This class is used to parse the X11 .Xauthority file. 
+ */
+public class XAuthReader {    
+    
+    private File file;
+    static final String HEXES = "0123456789abcdef"; //$NON-NLS-1$
+
+    
+    /**
+     * Construct the reader.
+     * @param file The path to the .Xauthority file to parse
+     */
+    public XAuthReader(File file) {
+        this.file = file;
+    }
+    
+    /**
+     * Construct the reader. It will look Xauthority file specified by the XAUTHORITY environment 
+     * variable or .Xauthority  in  the user's home directory.
+     * @throws IOException Thrown if the .Xauthority can't be found.
+     */
+    public XAuthReader() throws IOException {
+        String value = System.getenv("XAUTHORITY"); //$NON-NLS-1$
+        File file = null;
+        if (value!=null) {
+            file = new File(value);           
+        }
+        if (file == null || (!file.exists())) {
+            file = new File(System.getProperty("user.home")+File.separator+".Xauthority");  //$NON-NLS-1$//$NON-NLS-2$
+        }
+        if (!file.exists()) {
+            throw new IOException(SshConnectorResources.UnableToFindXAuthorityFile);
+        }
+        this.file = file;
+    }
+    
+    /**
+     * Used to parse the .Xauthority file and get a list of {@link XAuthEntry} found within the file.
+     * @return A list of {@link XAuthEntry} entries found within the file.
+     * @throws IOException Thrown if their is a problem reading the file
+     */
+    public List parse() throws IOException {
+        List entries = new ArrayList();
+        FileInputStream in = null;
+        try {
+            in = new FileInputStream (this.file);
+            
+            XAuthEntry entry = null; 
+            while ((entry=readEntry(in))!=null) {
+                entries.add(entry);                
+            }                       
+        }
+        finally {
+            if (in!=null) {
+                in.close();
+            }
+        }        
+        return entries;
+    }
+
+    private XAuthEntry readEntry(FileInputStream in) throws IOException {
+        XAuthEntry entry = new XAuthEntry();
+        int type = read16BitInt(in);
+        if (type==-1) {
+            return null;
+        }
+        entry.setAddressType(type);
+        entry.setAddress(readAddress(type,in));                       
+        String port = readString(in);
+        entry.setPort(Integer.parseInt(port));                       
+        entry.setCookieType(readString(in));            
+        byte[] cookie = readByteArray(in);
+        String strCookie = getHex(cookie);
+        entry.setValue(strCookie);
+        return entry;
+    }
+
+    private byte[] readByteArray(FileInputStream in) throws IOException {
+        int length = read16BitInt(in);
+        byte b[] = new byte[length];
+        in.read(b);
+        return b;
+    }
+        
+    private static String getHex(byte[] raw) {
+        if (raw == null) {
+            return null;
+        }
+        final StringBuilder hex = new StringBuilder(2 * raw.length);
+        for (int i=0;i<raw.length;i++) {
+            final byte b = raw[i];
+            hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
+        }
+        return hex.toString();
+    }
+    
+    private String readString(FileInputStream in) throws IOException {
+        byte[] b = readByteArray(in);
+        return new String(b);        
+    }
+
+    private InetAddress readAddress(int type, FileInputStream in) throws IOException {
+        if (type==XAuthEntry.ADDRESS_TYPE_IPV4) {
+            byte[] b = readByteArray(in);        
+            return InetAddress.getByAddress(b);
+        }    
+        else if (type==XAuthEntry.ADDRESS_TYPE_IPV6) {
+            byte[] b = readByteArray(in);  
+            return InetAddress.getByAddress(b);
+        }
+        else if (type==XAuthEntry.ADDRESS_TYPE_SOCKET) {
+            return InetAddress.getByName(readString(in));
+        }       
+        return null;
+    }       
+    
+    private int read16BitInt(FileInputStream in) throws IOException {
+        int value = 0;
+        byte b[] = new byte[2];
+        if (in.read(b)>0) {
+            for (int i = 0; i < b.length; i++) {
+                int shift = (b.length - 1 - i) * 8;
+                value += (b[i] & 0x000000FF) << shift;
+            }
+            return value;
+        }
+        else {
+            return -1;
+        }
+    }
+}
Index: src/org/eclipse/rse/internal/connectorservice/ssh/XAuthEntry.java
===================================================================
RCS file: src/org/eclipse/rse/internal/connectorservice/ssh/XAuthEntry.java
diff -N src/org/eclipse/rse/internal/connectorservice/ssh/XAuthEntry.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/rse/internal/connectorservice/ssh/XAuthEntry.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,88 @@
+package org.eclipse.rse.internal.connectorservice.ssh;
+import java.net.InetAddress;
+
+/**
+ * Used to store the information from a entry in a .Xauthority file.
+ */
+public class XAuthEntry {
+
+    /** Address is of type IPV4 */
+    public final static int ADDRESS_TYPE_IPV4 = 0;
+    /** Address is of type IPV6 */
+    public final static int ADDRESS_TYPE_IPV6 = 6;
+    /** Address is of type unix socket */
+    public final static int ADDRESS_TYPE_SOCKET =256;
+    
+    private InetAddress address;
+    private int port;
+    private String cookieType;
+    private String value;
+    private int addressType;
+    
+    public InetAddress getAddress() {
+        return address;
+    }
+    
+    public void setAddress(InetAddress address) {
+        this.address = address;
+    }
+    
+    public int getPort() {
+        return port;
+    }
+    
+    public void setPort(int port) {
+        this.port = port;
+    }
+    
+    public String getCookieType() {
+        return cookieType;
+    }
+    
+    public void setCookieType(String type) {
+        this.cookieType = type;
+    }
+    
+    public String getValue() {
+        return value;
+    }
+    
+    public void setValue(String value) {
+        this.value = value;
+    }   
+    
+    
+    public int getAddressType() {
+        return addressType;
+    }
+    
+    public void setAddressType(int addressType) {
+        this.addressType = addressType;
+    }
+    
+    /**
+     * Will output the entry to a string in the format returned by the command
+     * &quot;xauth list&quot;.
+     * @return Entry converted to a string
+     */
+    public String toString() {
+        StringBuffer result = new StringBuffer();
+        switch (addressType) {
+            case ADDRESS_TYPE_IPV6:
+            case ADDRESS_TYPE_IPV4: 
+                result.append(address.getCanonicalHostName());
+                break;
+            case ADDRESS_TYPE_SOCKET:
+                result.append(address.getHostName()+"/unix"); //$NON-NLS-1$
+                break;                       
+        }        
+        
+        result.append(":"); //$NON-NLS-1$
+        result.append(port);
+        result.append("  "); //$NON-NLS-1$
+        result.append(cookieType);
+        result.append("  "); //$NON-NLS-1$
+        result.append(value);
+        return result.toString();
+    }
+}
#P org.eclipse.rse.services.ssh
Index: src/org/eclipse/rse/internal/services/ssh/terminal/SshTerminalShell.java
===================================================================
RCS file: /cvsroot/dsdp/org.eclipse.tm.rse/plugins/org.eclipse.rse.services.ssh/src/org/eclipse/rse/internal/services/ssh/terminal/SshTerminalShell.java,v
retrieving revision 1.3
diff -u -r1.3 SshTerminalShell.java
--- src/org/eclipse/rse/internal/services/ssh/terminal/SshTerminalShell.java	14 Dec 2008 18:28:46 -0000	1.3
+++ src/org/eclipse/rse/internal/services/ssh/terminal/SshTerminalShell.java	7 Jul 2010 08:52:10 -0000
@@ -38,7 +38,8 @@
  */
 public class SshTerminalShell extends AbstractTerminalShell {
 
-	private ISshSessionProvider fSessionProvider;
+	private static final String PROPERTY_KEY_X_PORT_FORWARDING = "x-port-forwarding"; //$NON-NLS-1$
+    private ISshSessionProvider fSessionProvider;
 	private Channel fChannel;
 	private String fEncoding;
 	private InputStream fInputStream;
@@ -46,6 +47,7 @@
 	private Writer fOutputStreamWriter;
 	private int fWidth = 0;
 	private int fHeight = 0;
+
 	private static String defaultEncoding = new java.io.InputStreamReader(new java.io.ByteArrayInputStream(new byte[0])).getEncoding();
 
 	/**
@@ -63,7 +65,7 @@
 	 *            Use <code>null</code> or empty String ("") to start in a
 	 *            default directory. Empty String will typically start in the
 	 *            home directory.
-	 * @param commandToRun initial command to send.
+	 * @param commandToRun initial command to send.	
 	 * @throws SystemMessageException in case anything goes wrong. Channels and
 	 *             Streams are all cleaned up again in this case.
 	 * @see ITerminalService
@@ -71,9 +73,18 @@
 	public SshTerminalShell(ISshSessionProvider sessionProvider, String ptyType, String encoding, String[] environment, String initialWorkingDirectory,
 			String commandToRun) throws SystemMessageException {
 		try {
-			fSessionProvider = sessionProvider;
+			fSessionProvider = sessionProvider;			
 			fEncoding = encoding;
-		    fChannel = fSessionProvider.getSession().openChannel("shell"); //$NON-NLS-1$
+		    fChannel = fSessionProvider.getSession().openChannel("shell"); //$NON-NLS-1$		    
+		    
+		    // Setup X11 Port forwarding
+		    boolean xPortForwading = false;
+		    String xPortForwadingStr = fSessionProvider.getSession().getConfig(PROPERTY_KEY_X_PORT_FORWARDING);
+		    if (xPortForwadingStr!=null) {
+		        xPortForwading = Boolean.parseBoolean(xPortForwadingStr);
+		    }		    
+		    fChannel.setXForwarding(xPortForwading);
+		    
 			if (ptyType != null && (fChannel instanceof ChannelShell)) {
 			    //By default, jsch always creates a vt100 connection sized
 			    //80x24 / 640x480 (dimensions can be changed).
@@ -241,4 +252,5 @@
 		}
 	}
 
+
 }

Back to the top