/** * Copyright 2003-2016 SSHTOOLS Limited. All Rights Reserved. * * For product documentation visit https://www.sshtools.com/ * * This file is part of J2SSH Maverick. * * J2SSH Maverick is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * J2SSH Maverick is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with J2SSH Maverick. If not, see <http://www.gnu.org/licenses/>. */ package com.sshtools.ssh2; import java.io.IOException; import java.io.InputStream; import com.sshtools.events.Event; import com.sshtools.events.EventServiceImplementation; import com.sshtools.events.J2SSHEventCodes; import com.sshtools.logging.Log; import com.sshtools.ssh.ChannelAdapter; import com.sshtools.ssh.PseudoTerminalModes; import com.sshtools.ssh.SshChannel; import com.sshtools.ssh.SshClient; import com.sshtools.ssh.SshException; import com.sshtools.ssh.SshSession; import com.sshtools.ssh.message.SshChannelMessage; import com.sshtools.ssh.message.SshMessage; import com.sshtools.util.ByteArrayReader; import com.sshtools.util.ByteArrayWriter; /** * This class implements the SSH2 session channel, unlike SSH1 multiple sessions * can be opened on the same SSH connection. * * @author Lee David Painter */ public class Ssh2Session extends Ssh2Channel implements SshSession { final static int SSH_EXTENDED_DATA_STDERR = 1; ChannelInputStream stderr; boolean flowControlEnabled = false; int exitcode = EXITCODE_NOT_RECEIVED; String exitsignalinfo = ""; Ssh2Client client; /** * Construct a session channel. * * @param windowsize * the initial/maximum window space available * @param packetsize * the maximum packet size */ public Ssh2Session(int windowsize, int packetsize, Ssh2Client client) { super(SESSION_CHANNEL, windowsize, packetsize); this.client = client; stderr = createExtendedDataStream(); } public SshClient getClient() { return client; } protected void processExtendedData(int typecode, int length, SshChannelMessage msg) throws SshException { super.processExtendedData(typecode, length, msg); if (typecode == SSH_EXTENDED_DATA_STDERR) { stderr.addMessage(length, msg); } } public InputStream getStderrInputStream() { return stderr; } public boolean requestPseudoTerminal(String term, int cols, int rows, int width, int height) throws SshException { return requestPseudoTerminal(term, cols, rows, width, height, new byte[] { 0 }); } public boolean requestPseudoTerminal(String term, int cols, int rows, int width, int height, PseudoTerminalModes terminalModes) throws SshException { return requestPseudoTerminal(term, cols, rows, width, height, terminalModes.toByteArray()); } public boolean requestPseudoTerminal(String term, int cols, int rows, int width, int height, byte[] modes) throws SshException { ByteArrayWriter request = new ByteArrayWriter(); try { request.writeString(term); request.writeInt(cols); request.writeInt(rows); request.writeInt(width); request.writeInt(height); request.writeBinaryString(modes); return sendRequest("pty-req", true, request.toByteArray()); } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { request.close(); } catch (IOException e) { } } } public boolean startShell() throws SshException { if (Log.isDebugEnabled()) { addChannelEventListener(new CommandLogger()); } boolean success = sendRequest("shell", true, null); if (success) { EventServiceImplementation.getInstance().fireEvent( new Event(this, J2SSHEventCodes.EVENT_SHELL_SESSION_STARTED, true)); } else { EventServiceImplementation .getInstance() .fireEvent( new Event( this, J2SSHEventCodes.EVENT_SHELL_SESSION_FAILED_TO_START, false)); } return success; } public boolean executeCommand(String cmd) throws SshException { if (Log.isDebugEnabled()) { addChannelEventListener(new CommandLogger()); } ByteArrayWriter request = new ByteArrayWriter(); try { request.writeString(cmd); boolean success = sendRequest("exec", true, request.toByteArray()); if (success) { EventServiceImplementation.getInstance().fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SHELL_COMMAND, true)).addAttribute( J2SSHEventCodes.ATTRIBUTE_COMMAND, cmd)); } else { EventServiceImplementation.getInstance().fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SHELL_COMMAND, false)).addAttribute( J2SSHEventCodes.ATTRIBUTE_COMMAND, cmd)); } return success; } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { request.close(); } catch (IOException e) { } } } public boolean executeCommand(String cmd, String charset) throws SshException { ByteArrayWriter request = new ByteArrayWriter(); try { request.writeString(cmd, charset); boolean success = sendRequest("exec", true, request.toByteArray()); if (success) { EventServiceImplementation.getInstance().fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SHELL_COMMAND, true)).addAttribute( J2SSHEventCodes.ATTRIBUTE_COMMAND, cmd)); } else { EventServiceImplementation.getInstance().fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SHELL_COMMAND, false)).addAttribute( J2SSHEventCodes.ATTRIBUTE_COMMAND, cmd)); } return success; } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { request.close(); } catch (IOException e) { } } } /** * SSH2 supports special subsystems that are identified by a name rather * than a command string, an example of an SSH2 subsystem is SFTP. * * @param subsystem * the name of the subsystem, for example "sftp" * @return <code>true</code> if the subsystem was started, otherwise * <code>false</code> * @throws SshException */ public boolean startSubsystem(String subsystem) throws SshException { ByteArrayWriter request = new ByteArrayWriter(); try { request.writeString(subsystem); boolean success = sendRequest("subsystem", true, request.toByteArray()); if (success) { EventServiceImplementation.getInstance().fireEvent( (new Event(this, J2SSHEventCodes.EVENT_SUBSYSTEM_STARTED, true)) .addAttribute( J2SSHEventCodes.ATTRIBUTE_COMMAND, subsystem)); } else { EventServiceImplementation .getInstance() .fireEvent( (new Event( this, J2SSHEventCodes.EVENT_SUBSYSTEM_STARTED, false)).addAttribute( J2SSHEventCodes.ATTRIBUTE_COMMAND, subsystem)); } return success; } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { request.close(); } catch (IOException e) { } } } /** * Send a request for X Forwarding. * * @param singleconnection * @param protocol * @param cookie * @param display * @return boolean * @throws SshException */ boolean requestX11Forwarding(boolean singleconnection, String protocol, String cookie, int screen) throws SshException { ByteArrayWriter request = new ByteArrayWriter(); try { request.writeBoolean(singleconnection); request.writeString(protocol); request.writeString(cookie); request.writeInt(screen); return sendRequest("x11-req", true, request.toByteArray()); } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { request.close(); } catch (IOException e) { } } } /** * The SSH2 session supports the setting of environments variables however * in our experiance no server to date allows unconditional setting of * variables. This method should be called before the command is started. */ public boolean setEnvironmentVariable(String name, String value) throws SshException { ByteArrayWriter request = new ByteArrayWriter(); try { request.writeString(name); request.writeString(value); return sendRequest("env", true, request.toByteArray()); } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { request.close(); } catch (IOException e) { } } } public void changeTerminalDimensions(int cols, int rows, int width, int height) throws SshException { ByteArrayWriter request = new ByteArrayWriter(); try { request.writeInt(cols); request.writeInt(rows); request.writeInt(height); request.writeInt(width); sendRequest("window-change", false, request.toByteArray()); } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { request.close(); } catch (IOException e) { } } } /** * On many systems it is possible to determine whether a pseudo-terminal is * using control-S/ control-Q flow control. When flow control is allowed it * is often esirable to do the flow control at the client end to speed up * responses to user requests. If this method returns <code>true</code> the * client is allowed to do flow control using control-S and control-Q * * @return boolean */ public boolean isFlowControlEnabled() { return flowControlEnabled; } /** * Send a signal to the remote process. A signal can be delivered to the * remote process using this method, some systems may not implement signals. * The signal name should be one of the following values: <blockquote> * * <pre> * ABRT * ALRM * FPE * HUP * ILL * INT * KILL * PIPE * QUIT * SEGV * TERM * USR1 * USR2 * </pre> * * </blockquote> * * @param signal * @throws IOException */ public void signal(String signal) throws SshException { ByteArrayWriter request = new ByteArrayWriter(); try { request.writeString(signal); sendRequest("signal", false, request.toByteArray()); } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { request.close(); } catch (IOException e) { } } } /** * This overidden method handles the "exit-status", "exit-signal" and * "xon-xoff" channel requests. */ protected void channelRequest(String requesttype, boolean wantreply, byte[] requestdata) throws SshException { try { if (requesttype.equals("exit-status")) { if (requestdata != null) { exitcode = (int) ByteArrayReader.readInt(requestdata, 0); } } if (requesttype.equals("exit-signal")) { if (requestdata != null) { ByteArrayReader bar = new ByteArrayReader(requestdata, 0, requestdata.length); try { exitsignalinfo = "Signal=" + bar.readString() + " CoreDump=" + String.valueOf(bar.read() != 0) + " Message=" + bar.readString(); } finally { try { bar.close(); } catch (IOException e) { } } } } if (requesttype.equals("xon-xoff")) { flowControlEnabled = (requestdata != null && requestdata[0] != 0); } super.channelRequest(requesttype, wantreply, requestdata); } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } } public int exitCode() { return exitcode; } protected void checkCloseStatus(boolean remoteClosed) { if (!remoteClosed) { try { if (Log.isDebugEnabled()) { Log.debug(this, "Waiting for remote channel close id=" + channelid + " rid=" + remoteid); } SshMessage message = ms.nextMessage(CHANNEL_CLOSE_MESSAGES, Integer.parseInt(System.getProperty( "maverick.remoteCloseTimeoutMs", "5000"))); if (message != null) { remoteClosed = true; if (Log.isDebugEnabled()) { Log.debug(this, "Remote channel is closed id=" + channelid + " rid=" + remoteid); } } else { if (Log.isDebugEnabled()) { Log.debug(this, "Remote channel IS NOT closed id=" + channelid + " rid=" + remoteid); } } } catch (Exception e) { } } super.checkCloseStatus(remoteClosed); } /** * Determine whether the remote process was signalled. * * @return <code>true</code> if a signal was received, otherwise * <code>false</code> */ public boolean hasExitSignal() { return !exitsignalinfo.equals(""); } /** * Get the exit signal information, may be an empty string. * * @return String */ public String getExitSignalInfo() { return exitsignalinfo; } class CommandLogger extends ChannelAdapter { public void dataReceived(SshChannel channel, byte[] buf, int off, int len) { Log.info(this, "Session IN: " + new String(buf, off, len)); } public void dataSent(SshChannel channel, byte[] buf, int off, int len) { Log.info(this, "Session OUT: " + new String(buf, off, len)); } } }