/******************************************************************************* * Copyright (c) 2006, 2013 Wind River Systems, Inc., Intel Corporation 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 * Contributors: * Liping Ke (Intel Corp.) - initial API and implementation * Sheldon D'souza (Celunite) - LoginThread and readUntil implementation * Liping Ke (Intel Corp.) - For non-login mode, we don't need detect command prompt ******************************************************************************/ package org.eclipse.tcf.internal.rse.shells; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import org.eclipse.core.runtime.IStatus; import org.eclipse.rse.core.model.IPropertySet; import org.eclipse.rse.services.clientserver.PathUtility; import org.eclipse.rse.services.clientserver.messages.CommonMessages; import org.eclipse.rse.services.clientserver.messages.ICommonMessageIds; import org.eclipse.rse.services.clientserver.messages.SimpleSystemMessage; import org.eclipse.rse.services.clientserver.messages.SystemMessage; import org.eclipse.rse.services.clientserver.messages.SystemMessageException; import org.eclipse.rse.services.terminals.AbstractTerminalShell; import org.eclipse.rse.services.terminals.ITerminalService; import org.eclipse.rse.ui.SystemBasePlugin; import org.eclipse.tcf.internal.rse.ITCFSessionProvider; import org.eclipse.tcf.internal.rse.Messages; import org.eclipse.tcf.internal.rse.TCFConnectorService; import org.eclipse.tcf.internal.rse.TCFRSETask; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IToken; import org.eclipse.tcf.services.ITerminals; import org.eclipse.tcf.util.TCFVirtualInputStream; import org.eclipse.tcf.util.TCFVirtualOutputStream; public class TCFTerminalShell extends AbstractTerminalShell { private final ITCFSessionProvider fSessionProvider; private final IChannel fChannel; private String fPtyType; private ITerminals.TerminalContext terminalContext; private String fEncoding; private InputStream fInputStream; private OutputStream fOutputStream; private Writer fOutputStreamWriter; private int fWidth = 0; private int fHeight = 0; private String fContextID; private String inp_id; private String out_id; private boolean connected; private boolean exited; private ITerminals terminals; private int status; private IPropertySet tcfPropertySet = null; private static String defaultEncoding = new InputStreamReader(new ByteArrayInputStream(new byte[0])).getEncoding(); private ITerminals.TerminalsListener listeners = new ITerminals.TerminalsListener(){ public void exited(String terminalId, int exitCode) { if(!terminalContext.getID().equals(terminalId)) return; terminals.removeListener(listeners); connected = false; exited = true; } public void winSizeChanged(String terminalId, int newWidth, int newHeight) { } }; /* LoginThread and readUntil functionality are cloned from TelnetConnectorService * and then modified for our own needs * */ private class LoginThread extends Thread { private String username; private String password; private int status = ITCFSessionProvider.SUCCESS_CODE; public LoginThread(String username, String password) { this.username = username; this.password = password; } public void run() { tcfPropertySet = ((TCFConnectorService)fSessionProvider).getTCFPropertySet(); /* By default, we only support non-login mode. If user open the * switch on the agent side, he can also set the properties in * TCF connection property*/ String login_required = tcfPropertySet.getPropertyValue(TCFConnectorService.PROPERTY_LOGIN_REQUIRED); if (Boolean.valueOf(login_required).booleanValue()) { String login_prompt = tcfPropertySet.getPropertyValue(TCFConnectorService.PROPERTY_LOGIN_PROMPT); String password_prompt =tcfPropertySet.getPropertyValue(TCFConnectorService.PROPERTY_PASSWORD_PROMPT); String command_prompt = tcfPropertySet.getPropertyValue(TCFConnectorService.PROPERTY_COMMAND_PROMPT); String pwd_required = tcfPropertySet.getPropertyValue(TCFConnectorService.PROPERTY_PWD_REQUIRED); status = ITCFSessionProvider.SUCCESS_CODE; if (login_prompt != null && login_prompt.length() > 0) { status = readUntil(login_prompt,fInputStream); write(username + "\n"); //$NON-NLS-1$ } if (Boolean.valueOf(pwd_required).booleanValue()) { if (status == ITCFSessionProvider.SUCCESS_CODE && password_prompt != null && password_prompt.length() > 0) { status = readUntil(password_prompt,fInputStream); write(password + "\n"); //$NON-NLS-1$ } } if (status == ITCFSessionProvider.SUCCESS_CODE && command_prompt != null && command_prompt.length() > 0) { status = readUntil(command_prompt,fInputStream); write("\n"); //$NON-NLS-1$ } } else { status = ITCFSessionProvider.SUCCESS_CODE; } } public int readUntil(String pattern,InputStream in) { try { char lastChar = pattern.charAt(pattern.length() - 1); StringBuffer sb = new StringBuffer(); int ch = in.read(); while (ch >= 0) { char tch = (char) ch; sb.append(tch); if (tch=='t' && sb.indexOf("incorrect") >= 0) { //$NON-NLS-1$ return ITCFSessionProvider.ERROR_CODE; } if (tch=='d' && sb.indexOf("closed") >= 0) { //$NON-NLS-1$ return ITCFSessionProvider.CONNECT_CLOSED; } if (tch == lastChar) { if (sb.toString().endsWith(pattern)) { return ITCFSessionProvider.SUCCESS_CODE; } } ch = in.read(); } } catch (Exception e) { e.printStackTrace(); SystemBasePlugin.logError(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), e); } return ITCFSessionProvider.CONNECT_CLOSED; } public int getLoginStatus() { return this.status; } } public void write(String value) { try { fOutputStream.write(value.getBytes()); } catch (Exception e) { e.printStackTrace(); } } private int login(String username, String password) throws InterruptedException { long millisToEnd = System.currentTimeMillis() + ITCFSessionProvider.TCP_CONNECT_TIMEOUT * 1000; LoginThread checkLogin = new LoginThread(username, password); status = ITCFSessionProvider.ERROR_CODE; checkLogin.start(); while (checkLogin.isAlive() && System.currentTimeMillis()<millisToEnd) checkLogin.join(500); status = checkLogin.getLoginStatus(); checkLogin.join(); return status; } /** * Construct a new TCF connection. * * The TCF channel is immediately connected in the Constructor. * * @param sessionProvider TCF session provider * @param ptyType Terminal type to set, or <code>null</code> if not * relevant * @param encoding The default encoding to use for initial command. * @param environment Environment array to set, or <code>null</code> if * not relevant. * @param initialWorkingDirectory initial directory to open the Terminal in. * 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. * @throws SystemMessageException in case anything goes wrong. Channels and * Streams are all cleaned up again in this case. * @see ITerminalService */ public TCFTerminalShell(final ITCFSessionProvider sessionProvider, final String ptyType, final String encoding, final String[] environment, String initialWorkingDirectory, String commandToRun) throws SystemMessageException { fSessionProvider = sessionProvider; fEncoding = encoding; fPtyType = ptyType; fChannel = fSessionProvider.getChannel(); Exception nestedException = null; try { if (fChannel == null || fChannel.getState() != IChannel.STATE_OPEN) throw new Exception("TCP channel is not connected!");//$NON-NLS-1$ new TCFRSETask<ITerminals.TerminalContext>() { public void run() { terminals = ((TCFConnectorService)sessionProvider).getService(ITerminals.class); fSessionProvider.onStreamsConnecting(); terminals.launch(ptyType, encoding, environment, new ITerminals.DoneLaunch() { @Override public void doneLaunch(IToken token, Exception error, ITerminals.TerminalContext ctx) { if (ctx != null) { terminalContext = ctx; terminals.addListener(listeners); inp_id = ctx.getStdOutID(); out_id = ctx.getStdInID(); fSessionProvider.onStreamsID(inp_id); fSessionProvider.onStreamsID(out_id); try { fInputStream = new TCFVirtualInputStream(fChannel, inp_id, new Runnable() { @Override public void run() { onInputStreamClosed(); } }); fOutputStream = new TCFVirtualOutputStream(fChannel, out_id, true, new Runnable() { @Override public void run() { onOutputStreamClosed(); } }); } catch (Exception x) { error = x; } } fSessionProvider.onStreamsConnected(); if (error != null) error(error); else done(ctx); } }); } }.getS(null, Messages.TCFTerminalService_Name); fPtyType = terminalContext.getPtyType(); fEncoding = terminalContext.getEncoding(); fContextID = terminalContext.getID(); fWidth = terminalContext.getWidth(); fHeight = terminalContext.getHeight(); String user = fSessionProvider.getSessionUserId(); String password = fSessionProvider.getSessionPassword(); status = ITCFSessionProvider.ERROR_CODE; if (fEncoding != null) { fOutputStreamWriter = new BufferedWriter(new OutputStreamWriter(fOutputStream, encoding)); } else { // default encoding == System.getProperty("file.encoding") // TODO should try to determine remote encoding if possible fOutputStreamWriter = new BufferedWriter(new OutputStreamWriter(fOutputStream)); } try { status = login(user, password); } finally { if (status == ITCFSessionProvider.CONNECT_CLOSED) { // Give one time chance of retrying.... } } // give another chance of retrying if (status == ITCFSessionProvider.CONNECT_CLOSED) { status = login(user, password); } if (status == ITCFSessionProvider.SUCCESS_CODE) { connected = true; if (initialWorkingDirectory != null && initialWorkingDirectory.length() > 0 && !initialWorkingDirectory.equals(".") //$NON-NLS-1$ && !initialWorkingDirectory.equals("Command Shell")) //$NON-NLS-1$ //FIXME workaround for bug 153047 { writeToShell("cd " + PathUtility.enQuoteUnix(initialWorkingDirectory)); //$NON-NLS-1$ } if (commandToRun != null && commandToRun.length() > 0) { writeToShell(commandToRun); } } } catch (Exception e) { e.printStackTrace(); nestedException = e; } finally { if (status != ITCFSessionProvider.SUCCESS_CODE) { SystemMessage msg; if (nestedException != null) { msg = new SimpleSystemMessage(org.eclipse.tcf.internal.rse.Activator.PLUGIN_ID, ICommonMessageIds.MSG_EXCEPTION_OCCURRED, IStatus.ERROR, CommonMessages.MSG_EXCEPTION_OCCURRED, nestedException); } else { String strErr; if (status == ITCFSessionProvider.CONNECT_CLOSED) strErr = "Connection closed!";//$NON-NLS-1$ else if (status == ITCFSessionProvider.ERROR_CODE) strErr = "Login Incorrect or meet other unknown error!";//$NON-NLS-1$ else strErr = "Not identified Errors";//$NON-NLS-1$ msg = new SimpleSystemMessage(org.eclipse.tcf.internal.rse.Activator.PLUGIN_ID, ICommonMessageIds.MSG_COMM_AUTH_FAILED, IStatus.ERROR, strErr, "Meet error when trying to login in!");//$NON-NLS-1$ msg.makeSubstitution(((TCFConnectorService)fSessionProvider).getHost().getAliasName()); } throw new SystemMessageException(msg);//$NON-NLS-1$ } } } public void writeToShell(String command) throws IOException { if (isActive()) { if ("#break".equals(command)) { //$NON-NLS-1$ command = "\u0003"; // Unicode 3 == Ctrl+C //$NON-NLS-1$ } else { command += "\r\n"; //$NON-NLS-1$ } fOutputStreamWriter.write(command); } } public void exit() { if (fChannel == null || (fChannel.getState() == IChannel.STATE_CLOSED) || !connected) { return; } try { getOutputStream().close(); getInputStream().close(); } catch (IOException ioe) { ioe.printStackTrace(); } //$NON-NLS-1$ try { new TCFRSETask<Object>() { public void run() { terminalContext.exit(new ITerminals.DoneCommand(){ public void doneCommand(IToken token, Exception error) { if (error != null) error(error); else done(this); } }); } }.getS(null, Messages.TCFShellService_Name); //seems no need block here. need further modification. } catch (SystemMessageException e) { e.printStackTrace(); } //$NON-NLS-1$; } public InputStream getInputStream() { if (!connected) throw new Error("Not connected"); return fInputStream; } public OutputStream getOutputStream() { if (!connected) throw new Error("Not connected"); return fOutputStream; } public boolean isActive() { if (fChannel != null && fChannel.getState() != IChannel.STATE_CLOSED && connected) { return true; } exit(); // shell is not active: check for session lost return false; } public String getPtyType() { return fPtyType; } public void setTerminalSize(int newWidth, int newHeight) { if (fWidth == newWidth && fHeight == newHeight) return; if (fChannel == null || (fChannel.getState() == IChannel.STATE_CLOSED) || !connected) { // do nothing return; } fWidth = newWidth; fHeight = newHeight; try { new TCFRSETask<Object>() { public void run() { if (fChannel != null && connected) { terminals.setWinSize(fContextID, fWidth, fHeight, new ITerminals.DoneCommand(){ public void doneCommand(IToken token, Exception error) { if (error != null) error(error); else done(this); } }); } else { done(this); } } }.getS(null, Messages.TCFShellService_Name); } catch (SystemMessageException e) { e.printStackTrace(); } } public String getDefaultEncoding() { if (fEncoding != null) return fEncoding; return defaultEncoding; } private void onInputStreamClosed() { inp_id = null; if (out_id == null && !exited) { terminalContext.exit(new ITerminals.DoneCommand(){ public void doneCommand(IToken token, Exception error) { } }); } } private void onOutputStreamClosed() { out_id = null; if (inp_id == null && !exited) { terminalContext.exit(new ITerminals.DoneCommand(){ public void doneCommand(IToken token, Exception error) { } }); } } }