/* * 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.channel; import java.io.IOException; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.ConnectException; import java.net.InetSocketAddress; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.future.IoFutureListener; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; import org.apache.mina.transport.socket.nio.NioSocketConnector; import org.apache.sshd.client.future.DefaultOpenFuture; import org.apache.sshd.client.future.OpenFuture; import org.apache.sshd.common.Channel; import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.SshConstants; import org.apache.sshd.common.channel.ChannelOutputStream; import org.apache.sshd.common.future.CloseFuture; import org.apache.sshd.common.future.SshFuture; import org.apache.sshd.common.future.SshFutureListener; import org.apache.sshd.common.util.Buffer; import org.apache.sshd.common.util.LogUtils; import org.apache.sshd.server.ForwardingFilter; import org.apache.sshd.server.session.ServerSession; /** * TODO Add javadoc * * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> */ public class ChannelDirectTcpip extends AbstractServerChannel { public static class Factory implements NamedFactory<Channel> { public String getName() { return "direct-tcpip"; } public Channel create() { return new ChannelDirectTcpip(); } } private IoConnector connector; private IoSession ioSession; private OutputStream out; public ChannelDirectTcpip() { } protected OpenFuture doInit(Buffer buffer) { final OpenFuture f = new DefaultOpenFuture(this); String hostToConnect = buffer.getString(); int portToConnect = buffer.getInt(); InetSocketAddress address; try { address = new InetSocketAddress(hostToConnect, portToConnect); } catch (RuntimeException e) { address = null; } final ServerSession serverSession = (ServerSession)getSession(); final ForwardingFilter filter = serverSession.getServerFactoryManager().getForwardingFilter(); if (address == null || filter == null || !filter.canConnect(address, serverSession)) { super.close(true); f.setException(new OpenChannelException(SshConstants.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "connect denied")); return f; } String originatorIpAddress = buffer.getString(); int originatorPort = buffer.getInt(); LogUtils.info(log, "Receiving request for direct tcpip: hostToConnect={0}, portToConnect={1}, originatorIpAddress={2}, originatorPort={3}", hostToConnect, portToConnect, originatorIpAddress, originatorPort); connector = new NioSocketConnector(); out = new ChannelOutputStream(this, remoteWindow, log, SshConstants.Message.SSH_MSG_CHANNEL_DATA); IoHandler handler = new IoHandlerAdapter() { @Override public void messageReceived(IoSession session, Object message) throws Exception { IoBuffer ioBuffer = (IoBuffer) message; int r = ioBuffer.remaining(); byte[] b = new byte[r]; ioBuffer.get(b, 0, r); out.write(b, 0, r); out.flush(); } @Override public void sessionClosed(IoSession session) throws Exception { sendEof(); } }; connector.setHandler(handler); ConnectFuture future = connector.connect(address); future.addListener(new IoFutureListener<ConnectFuture>() { public void operationComplete(ConnectFuture future) { if (future.isConnected()) { ioSession = future.getSession(); f.setOpened(); } else if (future.getException() != null) { closeImmediately0(); if (future.getException() instanceof ConnectException) { f.setException(new OpenChannelException( SshConstants.SSH_OPEN_CONNECT_FAILED, future.getException().getMessage(), future.getException())); } else { f.setException(future.getException()); } } } }); return f; } private void closeImmediately0() { // We need to close the channel immediately to remove it from the // server session's channel table and *not* send a packet to the // client. A notification was already sent by our caller, or will // be sent after we return. // super.close(true); // We also need to dispose of the connector, but unfortunately we // are being invoked by the connector thread or the connector's // own processor thread. Disposing of the connector within either // causes deadlock. Instead create a new thread to dispose of the // connector in the background. // new Thread("ChannelDirectTcpip-ConnectorCleanup") { @Override public void run() { connector.dispose(); } }.start(); } public CloseFuture close(boolean immediately) { return super.close(immediately).addListener(new SshFutureListener() { public void operationComplete(SshFuture sshFuture) { closeImmediately0(); } }); } @Override public void handleEof() throws IOException { super.handleEof(); close(false); } protected void doWriteData(byte[] data, int off, int len) throws IOException { IoBuffer buf = IoBuffer.allocate(len); buf.put(data, off, len); buf.flip(); ioSession.write(buf); } protected void doWriteExtendedData(byte[] data, int off, int len) throws IOException { throw new UnsupportedOperationException("DirectTcpip channel does not support extended data"); } public void handleRequest(Buffer buffer) throws IOException { LogUtils.info(log,"Received SSH_MSG_CHANNEL_REQUEST on channel {0}", id); String type = buffer.getString(); LogUtils.info(log,"Received channel request: {0}", type); buffer = session.createBuffer(SshConstants.Message.SSH_MSG_CHANNEL_FAILURE, 0); buffer.putInt(recipient); session.writePacket(buffer); } }