Author: gnodet Date: Wed Mar 31 14:06:44 2010 New Revision: 929545 URL: http://svn.apache.org/viewvc?rev=929545&view=rev Log: SSHD-81: Support for X11 forwarding on the server side
Added: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/x11/ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java?rev=929545&r1=929544&r2=929545&view=diff ============================================================================== --- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java (original) +++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java Wed Mar 31 14:06:44 2010 @@ -50,6 +50,7 @@ import org.apache.sshd.server.SessionAwa import org.apache.sshd.server.Signal; import org.apache.sshd.server.SignalListener; import org.apache.sshd.server.session.ServerSession; +import org.apache.sshd.server.x11.X11ForwardSupport; /** * TODO Add javadoc @@ -498,7 +499,19 @@ public class ChannelSession extends Abst return true; } - // TODO: start x11 forwarding + String display = ((ServerSession) session).createX11Display(buffer.getBoolean(), buffer.getString(), + buffer.getString(), buffer.getInt()); + if (display == null) { + if (wantReply) { + buffer = session.createBuffer(SshConstants.Message.SSH_MSG_CHANNEL_FAILURE, 0); + buffer.putInt(recipient); + session.writePacket(buffer); + } + return true; + } + + addEnvVariable(X11ForwardSupport.ENV_DISPLAY, display); + if (wantReply) { buffer = session.createBuffer(SshConstants.Message.SSH_MSG_CHANNEL_SUCCESS, 0); buffer.putInt(recipient); Modified: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java?rev=929545&r1=929544&r2=929545&view=diff ============================================================================== --- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java (original) +++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java Wed Mar 31 14:06:44 2010 @@ -41,6 +41,7 @@ import org.apache.sshd.common.util.Buffe import org.apache.sshd.server.ServerFactoryManager; import org.apache.sshd.server.UserAuth; import org.apache.sshd.server.channel.OpenChannelException; +import org.apache.sshd.server.x11.X11ForwardSupport; /** * @@ -68,6 +69,7 @@ public class ServerSession extends Abstr private boolean allowMoreSessions = true; private final TcpipForwardSupport tcpipForward; private final AgentForwardSupport agentForward; + private final X11ForwardSupport x11Forward; private List<NamedFactory<UserAuth>> userAuthFactories; @@ -81,6 +83,7 @@ public class ServerSession extends Abstr authTimeout = getIntProperty(FactoryManager.AUTH_TIMEOUT, authTimeout); tcpipForward = new TcpipForwardSupport(this); agentForward = new AgentForwardSupport(this); + x11Forward = new X11ForwardSupport (this); log.info("Session created..."); sendServerIdentification(); sendKexInit(); @@ -91,6 +94,7 @@ public class ServerSession extends Abstr unscheduleAuthTimer(); tcpipForward.close(); agentForward.close(); + x11Forward.close (); return super.close(immediately); } @@ -458,5 +462,8 @@ public class ServerSession extends Abstr return agentForward.initialize(); } + public String createX11Display(boolean singleConnection, String authenticationProtocol, String authenticationCookie, int screen) throws IOException { + return x11Forward.createDisplay(singleConnection, authenticationProtocol, authenticationCookie, screen); + } } Added: mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java?rev=929545&view=auto ============================================================================== --- mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java (added) +++ mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/x11/X11ForwardSupport.java Wed Mar 31 14:06:44 2010 @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sshd.server.x11; + +import java.io.IOException; +import java.net.BindException; +import java.net.InetSocketAddress; +import java.util.EnumSet; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.service.IoAcceptor; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoEventType; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.executor.ExecutorFilter; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import org.apache.sshd.client.channel.AbstractClientChannel; +import org.apache.sshd.client.future.DefaultOpenFuture; +import org.apache.sshd.client.future.OpenFuture; +import org.apache.sshd.common.SshConstants; +import org.apache.sshd.common.SshException; +import org.apache.sshd.common.channel.ChannelOutputStream; +import org.apache.sshd.common.util.Buffer; +import org.apache.sshd.server.session.ServerSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class X11ForwardSupport extends IoHandlerAdapter { + + private static String xauthCommand = System.getProperty("sshd.xauthCommand", "xauth"); + + public static final int X11_DISPLAY_OFFSET = 10; + public static final int MAX_DISPLAYS = 1000; + + /** + * Key for the user DISPLAY variable + */ + public static final String ENV_DISPLAY = "DISPLAY"; + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + private final ServerSession session; + private IoAcceptor acceptor; + + public X11ForwardSupport(ServerSession session) { + super(); + this.session = session; + } + + public synchronized void initialize() { + if (this.acceptor == null) { + NioSocketAcceptor acceptor = new NioSocketAcceptor(); + acceptor.setHandler(this); + acceptor.setReuseAddress(true); + acceptor.getFilterChain().addLast( + "executor", + new ExecutorFilter(EnumSet.complementOf( + EnumSet.of(IoEventType.SESSION_CREATED)).toArray( + new IoEventType[0]))); + this.acceptor = acceptor; + } + } + + public synchronized void close() { + if (acceptor != null) { + acceptor.dispose(); + acceptor = null; + } + } + + public synchronized String createDisplay(boolean singleConnection, + String authenticationProtocol, String authenticationCookie, + int screen) throws IOException { + + initialize(); + + int displayNumber, port; + InetSocketAddress addr = null; + + for (displayNumber = X11_DISPLAY_OFFSET; displayNumber < MAX_DISPLAYS; displayNumber++) { + port = 6000 + displayNumber; + try { + addr = new InetSocketAddress("127.0.0.1", port); + acceptor.bind(addr); + break; + } catch (BindException bindErr) { + // try until bind succesful or max is reached + } + } + + if (displayNumber >= MAX_DISPLAYS) { + log.error("Failed to allocate internet-domain X11 display socket."); + if (acceptor.getLocalAddresses().isEmpty()) { + close(); + } + return null; + } + + // only support non windows systems + String os = System.getProperty("os.name").toLowerCase(); + if (os.indexOf("windows") < 0) { + try { + String authDisplay = "unix:" + displayNumber + "." + screen; + Process p = new ProcessBuilder(xauthCommand, "remove", authDisplay).start(); + int result = p.waitFor(); + if (result == 0) { + p = new ProcessBuilder(xauthCommand, "add", authDisplay, authenticationProtocol, authenticationCookie).start(); + result = p.waitFor(); + } + } catch (Exception e) { + log.error("Could not run xauth", e); + return null; + } + return "localhost:" + displayNumber + "." + screen; + } else { + return null; + } + } + + @Override + public void sessionCreated(IoSession session) throws Exception { + ChannelForwardedX11 channel = new ChannelForwardedX11(session); + session.setAttribute(ChannelForwardedX11.class, channel); + this.session.registerChannel(channel); + OpenFuture future = channel.open().await(); + Throwable t = future.getException(); + if (t instanceof Exception) { + throw (Exception) t; + } else if (t != null) { + throw new Exception(t); + } + } + + @Override + public void sessionClosed(IoSession session) throws Exception { + ChannelForwardedX11 channel = (ChannelForwardedX11) session.getAttribute(ChannelForwardedX11.class); + channel.close(false); + } + + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + ChannelForwardedX11 channel = (ChannelForwardedX11) session.getAttribute(ChannelForwardedX11.class); + IoBuffer ioBuffer = (IoBuffer) message; + int r = ioBuffer.remaining(); + byte[] b = new byte[r]; + ioBuffer.get(b, 0, r); + channel.getOut().write(b, 0, r); + channel.getOut().flush(); + } + + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + session.close(false); + } + + public static class ChannelForwardedX11 extends AbstractClientChannel { + private final IoSession serverSession; + + public ChannelForwardedX11(IoSession serverSession) { + super("x11"); + this.serverSession = serverSession; + } + + public synchronized OpenFuture open() throws Exception { + InetSocketAddress remote = (InetSocketAddress) serverSession.getRemoteAddress(); + if (closeFuture.isClosed()) { + throw new SshException("Session has been closed"); + } + openFuture = new DefaultOpenFuture(lock); + log.info("Send SSH_MSG_CHANNEL_OPEN on channel {}", id); + Buffer buffer = session.createBuffer(SshConstants.Message.SSH_MSG_CHANNEL_OPEN, 0); + buffer.putString(type); + buffer.putInt(id); + buffer.putInt(localWindow.getSize()); + buffer.putInt(localWindow.getPacketSize()); + buffer.putString(remote.getHostName()); + buffer.putInt(remote.getPort()); + session.writePacket(buffer); + return openFuture; + } + + @Override + protected synchronized void doOpen() throws Exception { + out = new ChannelOutputStream(this, remoteWindow, log, SshConstants.Message.SSH_MSG_CHANNEL_DATA); + } + + @Override + protected synchronized void doClose() { + serverSession.close(false); + super.doClose(); + } + + protected synchronized void doWriteData(byte[] data, int off, int len) throws IOException { + IoBuffer buf = IoBuffer.allocate(len); + buf.put(data, off, len); + buf.flip(); + localWindow.consumeAndCheck(len); + serverSession.write(buf); + } + + @Override + public void handleEof() throws IOException { + super.handleEof(); + serverSession.close(false); + } + } + +}