0001: /*
0002: * SshSession.java
0003: *
0004: * Copyright (C) 2002-2003 Peter Graves
0005: * $Id: SshSession.java,v 1.14 2004/09/13 00:49:30 piso Exp $
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License
0009: * as published by the Free Software Foundation; either version 2
0010: * of the License, or (at your option) any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015: * GNU General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * along with this program; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0020: */
0021:
0022: package org.armedbear.j;
0023:
0024: import gnu.regexp.RE;
0025: import gnu.regexp.REException;
0026: import gnu.regexp.REMatch;
0027: import gnu.regexp.UncheckedRE;
0028: import java.io.IOException;
0029: import java.io.OutputStreamWriter;
0030: import java.util.ArrayList;
0031: import java.util.List;
0032: import javax.swing.SwingUtilities;
0033:
0034: public final class SshSession implements Constants {
0035: public static final int DEFAULT_PORT = 22;
0036:
0037: private static final int TRY_AGAIN = 0;
0038: private static final int AUTHENTICATED = 1;
0039: private static final int PASSWORD = 2;
0040: private static final int PASSPHRASE = 3;
0041: private static final int YES = 4;
0042: private static final int NO = 5;
0043:
0044: private static final String PROMPT = "$ ";
0045:
0046: private static ArrayList sessionList;
0047:
0048: private static CleanupThread cleanupThread;
0049:
0050: private String hostName;
0051: private String userName;
0052: private String password;
0053: private String passphrase;
0054: private int port;
0055:
0056: private boolean locked;
0057:
0058: private Process process;
0059:
0060: private OutputStreamWriter stdin;
0061: private StdoutThread stdoutThread;
0062: private StderrThread stderrThread;
0063:
0064: private FastStringBuffer output = new FastStringBuffer();
0065:
0066: private boolean connected;
0067:
0068: private String loginDirectory;
0069:
0070: private boolean echo;
0071:
0072: // tcsh doesn't like "\cd", so we may change it to "cd" later.
0073: private String cd = "\\cd";
0074:
0075: private Buffer outputBuffer;
0076:
0077: private String passwordTitle;
0078: private String passwordPrompt;
0079:
0080: private SshSession(SshFile file, boolean locked) {
0081: hostName = file.getHostName();
0082: Debug.assertTrue(hostName != null);
0083: userName = file.getUserName();
0084: password = file.getPassword();
0085: port = file.getPort();
0086: this .locked = locked;
0087: register(this );
0088: }
0089:
0090: private SshSession(SshSession other, boolean locked) {
0091: hostName = other.getHostName();
0092: Debug.assertTrue(hostName != null);
0093: userName = other.getUserName();
0094: password = other.getPassword();
0095: passphrase = other.getPassphrase();
0096: port = other.getPort();
0097: this .locked = locked;
0098: register(this );
0099: }
0100:
0101: private static synchronized void register(SshSession session) {
0102: if (sessionList == null)
0103: sessionList = new ArrayList();
0104: sessionList.add(session);
0105: if (cleanupThread == null) {
0106: cleanupThread = new CleanupThread(cleanupRunnable);
0107: cleanupThread.start();
0108: }
0109: Log.debug("leaving register() session count = "
0110: + sessionList.size());
0111: }
0112:
0113: private static synchronized void unregister(SshSession session) {
0114: if (sessionList == null) {
0115: Debug.bug();
0116: return;
0117: }
0118: if (!sessionList.contains(session))
0119: Debug.bug();
0120: sessionList.remove(session);
0121: Log.debug("leaving unregister() session count = "
0122: + sessionList.size());
0123: }
0124:
0125: public static synchronized SshSession getSession(SshFile file) {
0126: if (file == null) {
0127: Debug.bug();
0128: return null;
0129: }
0130: if (file.getHostName() == null) {
0131: Debug.bug();
0132: return null;
0133: }
0134: if (file.getUserName() == null) {
0135: Debug.bug();
0136: file.setUserName(System.getProperty("user.name"));
0137: }
0138: SshSession session = lockSession(file);
0139: if (session != null)
0140: return session;
0141: // No idle session found for this file. Try to find a session to clone.
0142: session = findSession(file);
0143: if (session != null)
0144: return new SshSession(session, true);
0145: // No session to clone.
0146: return new SshSession(file, true);
0147: }
0148:
0149: // Called only from synchronized methods.
0150: private static SshSession lockSession(SshFile file) {
0151: if (sessionList != null) {
0152: for (int i = sessionList.size(); i-- > 0;) {
0153: SshSession session = (SshSession) sessionList.get(i);
0154: if (session.getUserName().equals(file.getUserName())) {
0155: if (session.getHostName()
0156: .equals(file.getHostName())) {
0157: if (session.getPort() == file.getPort()) {
0158: if (session.lock()) {
0159: return session;
0160: }
0161: }
0162: }
0163: }
0164: }
0165: }
0166: return null;
0167: }
0168:
0169: // Called only from synchronized methods.
0170: private static SshSession findSession(SshFile file) {
0171: if (sessionList != null) {
0172: for (int i = sessionList.size(); i-- > 0;) {
0173: SshSession session = (SshSession) sessionList.get(i);
0174: if (session.getUserName().equals(file.getUserName())) {
0175: if (session.getHostName()
0176: .equals(file.getHostName())) {
0177: if (session.getPort() == file.getPort()) {
0178: return session;
0179: }
0180: }
0181: }
0182: }
0183: }
0184: return null;
0185: }
0186:
0187: public final synchronized boolean isLocked() {
0188: return locked;
0189: }
0190:
0191: private synchronized boolean lock() {
0192: if (locked)
0193: return false;
0194: locked = true;
0195: return true;
0196: }
0197:
0198: public synchronized void unlock() {
0199: if (locked)
0200: locked = false;
0201: else
0202: Debug.bug("SshSession.unlock session was not locked");
0203: synchronized (SshSession.class) {
0204: if (sessionList != null)
0205: Log.debug("unlock session count = "
0206: + sessionList.size());
0207: else
0208: Debug.bug("SshSession.unlock no session list");
0209: }
0210: }
0211:
0212: public final String getHostName() {
0213: return hostName;
0214: }
0215:
0216: public final String getUserName() {
0217: return userName;
0218: }
0219:
0220: public final String getPassword() {
0221: return password;
0222: }
0223:
0224: public final void setPassword(String password) {
0225: this .password = password;
0226: }
0227:
0228: public final String getPassphrase() {
0229: return passphrase;
0230: }
0231:
0232: public final int getPort() {
0233: return port;
0234: }
0235:
0236: public final String getLoginDirectory() {
0237: return loginDirectory;
0238: }
0239:
0240: public void setOutputBuffer(Buffer buf) {
0241: outputBuffer = buf;
0242: }
0243:
0244: public boolean isDirectory(String canonicalPath) {
0245: SshFile file = new SshFile(hostName, canonicalPath, userName,
0246: null, port);
0247: if (DirectoryCache.getDirectoryCache().getListing(file) != null)
0248: return true;
0249: if (!connect())
0250: return false;
0251: try {
0252: return changeDirectory(canonicalPath);
0253: } catch (Exception e) {
0254: Log.error(e);
0255: }
0256: // An exception occurred. We're confused. Wait a moment.
0257: try {
0258: Thread.sleep(1000);
0259: } catch (InterruptedException e) {
0260: Log.error(e);
0261: }
0262: // Try again.
0263: try {
0264: Log.warn("isDirectory() retrying after exception ...");
0265: // Send an empty command to flush the channel.
0266: command("");
0267: return changeDirectory(canonicalPath);
0268: } catch (Exception e) {
0269: // Another exception. Give up.
0270: Log.error(e);
0271: return false;
0272: }
0273: }
0274:
0275: private String stat(String canonicalPath) {
0276: FastStringBuffer sb = new FastStringBuffer("stat -t \"");
0277: sb.append(canonicalPath);
0278: sb.append('"');
0279: String cmd = sb.toString();
0280: String response = command(cmd);
0281: Log.debug(cmd + " " + response);
0282: if (response != null && response.startsWith(canonicalPath))
0283: return response.substring(canonicalPath.length()).trim();
0284: return null;
0285: }
0286:
0287: // Determines file type from string returned by stat.
0288: private static int getType(String s) {
0289: if (s != null) {
0290: List tokens = Utilities.tokenize(s);
0291: if (tokens.size() == 14) {
0292: String token = (String) tokens.get(2);
0293: Log.debug("token = |" + token + "|");
0294: try {
0295: int n = Integer.parseInt(token, 16);
0296: Log.debug("n = " + Integer.toString(n, 8));
0297: if ((n & 0040000) == 0040000)
0298: return File.TYPE_DIRECTORY;
0299: if ((n & 0120000) == 0120000)
0300: return File.TYPE_LINK;
0301: if ((n & 0100000) == 0100000)
0302: return File.TYPE_FILE;
0303: } catch (NumberFormatException e) {
0304: }
0305: }
0306: }
0307: return File.TYPE_UNKNOWN;
0308: }
0309:
0310: // Throws an exception if we don't recognize the response.
0311: private boolean changeDirectory(String canonicalPath)
0312: throws Exception {
0313: FastStringBuffer sb = new FastStringBuffer(cd);
0314: sb.append(" \"");
0315: sb.append(canonicalPath);
0316: sb.append('"');
0317: String cmd = sb.toString();
0318: Log.debug("changeDirectory cmd = |" + cmd + "|");
0319: String response = command(cmd);
0320: Log.debug("changeDirectory response = |" + response + "|");
0321: if (response == null)
0322: throw new Exception(); // Lost connection.
0323: if (response.indexOf("Command not found") >= 0) {
0324: if (cd.equals("\\cd")) {
0325: // tcsh doesn't like "\cd". Try again with "cd".
0326: cd = "cd";
0327: response = command(cd + " \"" + canonicalPath + '"');
0328: if (response == null)
0329: throw new Exception(); // Lost connection.
0330: } else
0331: return false;
0332: }
0333: if (response.equals(PROMPT)) {
0334: // No news is good news.
0335: Log.debug("changeDirectory succeeded");
0336: return true;
0337: }
0338: String lower = response.toLowerCase();
0339: if (lower.indexOf("not a directory") >= 0)
0340: return false;
0341: else if (lower.indexOf("no such file or directory") >= 0)
0342: return false;
0343: // If the directory name is very long (> 80 chars or so), bash will
0344: // wrap (i.e. mangle) the response. Try to detect that situation.
0345: int index = response.indexOf('\r');
0346: if (index < 0)
0347: index = response.indexOf('\n');
0348: if (index >= 0) {
0349: String beginning = response.substring(0, index);
0350: Log.debug("beginning = |" + beginning + "|");
0351: if (cmd.startsWith(beginning)) {
0352: Log.debug("cmd starts with beginning!");
0353: index = response.lastIndexOf((char) 8);
0354: if (index >= 0) {
0355: Log.debug("backspace found!");
0356: String end = response.substring(index + 1);
0357: Log.debug("end = |" + end + "|");
0358: if (cmd.endsWith(end)) {
0359: Log.debug("cmd ends with end!");
0360: return true;
0361: }
0362: }
0363: }
0364: }
0365: // Unknown response. We must be confused.
0366: throw new Exception();
0367: }
0368:
0369: public boolean exists(String canonicalPath) {
0370: if (connect()) {
0371: String response = lsld(canonicalPath);
0372: if (response != null && response.length() > 0) {
0373: char c = response.charAt(0);
0374: if (c == 'd' || c == '-')
0375: return true;
0376: }
0377: }
0378: return false;
0379: }
0380:
0381: public static String getDirectoryListing(File file) {
0382: if (!(file instanceof SshFile)) {
0383: Debug.assertTrue(false);
0384: return null;
0385: }
0386: String listing = DirectoryCache.getDirectoryCache().getListing(
0387: file);
0388: if (listing == null) {
0389: SshSession session = getSession((SshFile) file);
0390: if (session != null) {
0391: listing = session.retrieveDirectoryListing(file);
0392: session.unlock();
0393: if (listing != null)
0394: DirectoryCache.getDirectoryCache().put(file,
0395: listing);
0396: }
0397: }
0398: return listing;
0399: }
0400:
0401: public String retrieveDirectoryListing(File file) {
0402: if (!(file instanceof SshFile)) {
0403: Debug.bug();
0404: return null;
0405: }
0406: if (!isLocked()) {
0407: Debug.bug();
0408: return null;
0409: }
0410: if (connect()) {
0411: try {
0412: // Do it this way to support symlinks.
0413: if (changeDirectory(file.canonicalPath()))
0414: // Change directory succeeded. Do ls -la here.
0415: return lsla();
0416: } catch (Exception e) {
0417: Log.error(e);
0418: }
0419: }
0420: return null;
0421: }
0422:
0423: public synchronized boolean chmod(SshFile file, int permissions) {
0424: if (permissions != 0 && connect()) {
0425: FastStringBuffer sb = new FastStringBuffer("chmod ");
0426: sb.append(Integer.toString(permissions, 8));
0427: sb.append(' ');
0428: sb.append(file.canonicalPath());
0429: final String response = command(sb.toString());
0430: // Look for error message in response.
0431: if (response != null && response.indexOf("chmod:") < 0) {
0432: // Success! Remove old entry (if any) from directory cache.
0433: DirectoryCache.getDirectoryCache().purge(file);
0434: return true;
0435: }
0436: }
0437: return false;
0438: }
0439:
0440: public synchronized boolean isConnected() {
0441: return connected;
0442: }
0443:
0444: public synchronized boolean connect() {
0445: if (connected) {
0446: Log.debug("SshSession.connect(): already connected");
0447: return true;
0448: }
0449: FastStringBuffer sb = new FastStringBuffer("jpty ssh ");
0450: if (userName != null && userName.length() > 0) {
0451: sb.append("-l ");
0452: sb.append(userName);
0453: sb.append(' ');
0454: }
0455: if (port != DEFAULT_PORT) {
0456: sb.append("-p ");
0457: sb.append(port);
0458: sb.append(' ');
0459: }
0460: sb.append(hostName);
0461: try {
0462: process = Runtime.getRuntime().exec(sb.toString());
0463: } catch (Throwable t) {
0464: Log.error(t);
0465: return false;
0466: }
0467: try {
0468: stdin = new OutputStreamWriter(process.getOutputStream());
0469: int timeout = Editor.preferences().getIntegerProperty(
0470: Property.SSH_TIMEOUT);
0471: Log.debug("ssh timeout is " + timeout + " ms");
0472: stdoutThread = new StdoutThread();
0473: stdoutThread.setTimeOut(timeout);
0474: stderrThread = new StderrThread();
0475: stderrThread.setTimeOut(timeout);
0476: stdoutThread.start();
0477: stderrThread.start();
0478: } catch (Throwable t) {
0479: Log.error(t);
0480: return false;
0481: }
0482: if (!authenticate()) {
0483: killProcess();
0484: String message = output.toString().trim();
0485: if (message.length() == 0)
0486: message = "Authentication failed";
0487: MessageDialog.showMessageDialog(message, "Error");
0488: return false;
0489: }
0490: if (!connected) {
0491: killProcess();
0492: MessageDialog.showMessageDialog("Lost connection", "Error");
0493: }
0494: return connected;
0495: }
0496:
0497: private void initializeConnection() {
0498: boolean oldEcho = echo;
0499: echo = true;
0500: String response = command("exec /bin/sh");
0501: Log.debug("response = |" + response + "|");
0502: FastStringBuffer sb = new FastStringBuffer("PS1='");
0503: sb.append(PROMPT);
0504: sb.append('\'');
0505: response = command(sb.toString());
0506: Log.debug("response = |" + response + "|");
0507: Log.debug("PROMPT = |" + PROMPT + "|");
0508: command("unset MAILCHECK");
0509: loginDirectory = getCurrentDirectory();
0510: connected = (loginDirectory != null);
0511: echo = oldEcho;
0512: }
0513:
0514: private boolean authenticate() {
0515: output.setLength(0);
0516: int response;
0517: while ((response = checkInitialResponse()) == TRY_AGAIN) {
0518: Log.debug("authenticate() TRY_AGAIN");
0519: try {
0520: final long TIMEOUT = 30000; // 30 seconds
0521: long start = System.currentTimeMillis();
0522: wait(TIMEOUT);
0523: if (System.currentTimeMillis() - start > TIMEOUT)
0524: return false;
0525: } catch (InterruptedException e) {
0526: Log.debug("SshSession.connect interrupted!");
0527: return false;
0528: }
0529: }
0530: if (response == AUTHENTICATED) {
0531: initializeConnection();
0532: return connected;
0533: }
0534: if (response == PASSWORD)
0535: return authenticateWithPassword();
0536: if (response == PASSPHRASE)
0537: return authenticateWithPassphrase();
0538: return false;
0539: }
0540:
0541: private boolean authenticateWithPassword() {
0542: if (password == null) {
0543: password = Netrc.getPassword(hostName, userName);
0544: if (password == null) {
0545: if (SwingUtilities.isEventDispatchThread())
0546: getPasswordRunnable.run();
0547: else {
0548: try {
0549: SwingUtilities
0550: .invokeAndWait(getPasswordRunnable);
0551: } catch (Exception e) {
0552: Log.error(e);
0553: }
0554: }
0555: if (password == null) {
0556: killProcess();
0557: return false;
0558: }
0559: }
0560: }
0561: return _authenticate(password);
0562: }
0563:
0564: private boolean authenticateWithPassphrase() {
0565: if (passphrase == null) {
0566: if (SwingUtilities.isEventDispatchThread())
0567: getPassphraseRunnable.run();
0568: else {
0569: try {
0570: SwingUtilities.invokeAndWait(getPassphraseRunnable);
0571: } catch (Exception e) {
0572: Log.error(e);
0573: }
0574: }
0575: if (passphrase == null) {
0576: killProcess();
0577: return false;
0578: }
0579: }
0580: return _authenticate(passphrase);
0581: }
0582:
0583: private boolean _authenticate(String pass) {
0584: output.setLength(0);
0585: boolean oldEcho = echo;
0586: echo = true;
0587: sendPass(pass);
0588: if (checkAuthenticationResponse()) {
0589: Log.debug("authenticate SUCCEEDED!");
0590: initializeConnection();
0591: echo = oldEcho;
0592: return connected;
0593: } else {
0594: Log.debug("authenticate FAILED!");
0595: echo = oldEcho;
0596: return false;
0597: }
0598: }
0599:
0600: private int checkInitialResponse() {
0601: final String s = output.toString().trim();
0602: String check;
0603: int index = s.lastIndexOf("\r\n");
0604: if (index >= 0) {
0605: check = s.substring(index + 2);
0606: } else {
0607: index = s.lastIndexOf('\n');
0608: if (index >= 0)
0609: check = s.substring(index + 1);
0610: else
0611: check = s;
0612: }
0613: Log.debug("check = |" + check + "|");
0614: String lower = check.toLowerCase();
0615: if (lower.indexOf("connection refused") >= 0)
0616: return NO;
0617: if (lower.endsWith("password:")) {
0618: passwordTitle = "Password";
0619: passwordPrompt = check;
0620: return PASSWORD;
0621: }
0622: if (s.startsWith("Password:") && lower.endsWith("response:")) {
0623: passwordTitle = "Password";
0624: passwordPrompt = check;
0625: return PASSWORD;
0626: }
0627: if (lower.startsWith("enter passphrase ")
0628: && lower.endsWith(":")) {
0629: // We don't want to use the password from .netrc in this situation.
0630: password = null;
0631: passwordTitle = "Passphrase";
0632: passwordPrompt = check;
0633: return PASSPHRASE;
0634: }
0635: RE promptRE = getPromptRE();
0636: if (promptRE.getMatch(lower) != null)
0637: return AUTHENTICATED;
0638: return TRY_AGAIN;
0639: }
0640:
0641: private boolean checkAuthenticationResponse() {
0642: String s = output.toString();
0643: Log.debug("checkAuthenticationResponse output = |" + output
0644: + "|");
0645: int result = checkResponse(s);
0646: Log.debug("checkAuthenticationResponse result = " + result);
0647: while (result == TRY_AGAIN) {
0648: try {
0649: wait();
0650: } catch (InterruptedException e) {
0651: Log.error(e);
0652: return false;
0653: }
0654: s = output.toString();
0655: Log.debug("checkAuthenticationResponse output = |" + output
0656: + "|");
0657: result = checkResponse(s);
0658: Log.debug("checkAuthenticationResponse result = " + result);
0659: }
0660: Log.debug("checkAuthenticationResponse returning "
0661: + (result == YES));
0662: return result == YES;
0663: }
0664:
0665: // Helper for checkAuthenticationResponse().
0666: private int checkResponse(String s) {
0667: if (s.toLowerCase().indexOf("denied") >= 0)
0668: return NO;
0669: String prompt = null;
0670: for (int i = s.length(); i-- > 0;) {
0671: char c = s.charAt(i);
0672: if (c == '\r' || c == '\n') {
0673: prompt = s.substring(i + 1);
0674: break;
0675: }
0676: }
0677: if (prompt == null)
0678: prompt = s;
0679: Log.debug("prompt = |" + reveal(prompt) + "|");
0680: RE promptRE = getPromptRE();
0681: Debug.assertTrue(promptRE != null);
0682: REMatch match = promptRE.getMatch(prompt);
0683: if (match != null)
0684: return YES;
0685: return TRY_AGAIN;
0686: }
0687:
0688: private RE _promptRE;
0689:
0690: public RE getPromptRE() {
0691: if (_promptRE == null) {
0692: try {
0693: _promptRE = new RE(Editor.preferences()
0694: .getStringProperty(Property.SSH_PROMPT_PATTERN));
0695: } catch (REException e) {
0696: Log.error(e);
0697: _promptRE = new UncheckedRE(
0698: DEFAULT_SHELL_PROMPT_PATTERN);
0699: }
0700: }
0701: Debug.assertTrue(_promptRE != null);
0702: return _promptRE;
0703: }
0704:
0705: private Runnable getPasswordRunnable = new Runnable() {
0706: public void run() {
0707: final Editor editor = Editor.currentEditor();
0708: editor.setDefaultCursor();
0709: password = PasswordDialog.showPasswordDialog(editor,
0710: passwordPrompt, passwordTitle);
0711: editor.setWaitCursor();
0712: }
0713: };
0714:
0715: private Runnable getPassphraseRunnable = new Runnable() {
0716: public void run() {
0717: final Editor editor = Editor.currentEditor();
0718: editor.setDefaultCursor();
0719: passphrase = PasswordDialog.showPasswordDialog(editor,
0720: passwordPrompt, passwordTitle);
0721: editor.setWaitCursor();
0722: }
0723: };
0724:
0725: private String getCurrentDirectory() {
0726: final String s = command("pwd");
0727: if (s == null)
0728: return null; // Lost connection.
0729: int index = s.indexOf("\r\n");
0730: if (index < 0)
0731: index = s.indexOf('\n');
0732: final String dir = index >= 0 ? s.substring(0, index) : s;
0733: Log.debug("getCurrentDirectory() returning |" + dir + "|");
0734: return dir;
0735: }
0736:
0737: private void disconnect() {
0738: if (connected) {
0739: try {
0740: stdin.write("exit\n");
0741: } catch (Exception e) {
0742: Log.error(e);
0743: }
0744: killProcess();
0745: }
0746: }
0747:
0748: private void killProcess() {
0749: Process p = process; // Avoid races.
0750: if (p != null) {
0751: try {
0752: Log.debug("calling Process.destroy()");
0753: p.destroy();
0754: Log.debug("calling Process.waitFor()");
0755: p.waitFor();
0756: } catch (InterruptedException e) {
0757: Log.error(e);
0758: }
0759: process = null;
0760: synchronized (this ) {
0761: if (stdin != null) {
0762: try {
0763: stdin.close();
0764: } catch (IOException e) {
0765: Log.error(e);
0766: }
0767: stdin = null;
0768: }
0769: if (stdoutThread != null) {
0770: stdoutThread.cancel();
0771: stdoutThread = null;
0772: }
0773: if (stderrThread != null) {
0774: stderrThread.cancel();
0775: stderrThread = null;
0776: }
0777: }
0778: }
0779: connected = false;
0780: }
0781:
0782: public synchronized final void dispose() {
0783: Log.debug("SshSession.dispose");
0784: if (connected)
0785: disconnect();
0786: unregister(this );
0787: }
0788:
0789: private synchronized String command(String cmd) {
0790: if (!write(cmd.concat("\n")))
0791: return null;
0792: output.setLength(0);
0793: while (output.length() == 0) {
0794: try {
0795: wait();
0796: } catch (InterruptedException e) {
0797: Log.error(e);
0798: }
0799: }
0800: String s = output.toString();
0801: // Strip echo of original command.
0802: if (s.startsWith(cmd)) {
0803: int i = cmd.length();
0804: while (i < s.length()) {
0805: char c = s.charAt(i);
0806: if (c == '\r' || c == '\n')
0807: ++i;
0808: else
0809: break;
0810: }
0811: if (i > cmd.length())
0812: s = s.substring(i);
0813: }
0814: // Strip prompt.
0815: int index = s.lastIndexOf("\r\n");
0816: if (index >= 0) {
0817: s = s.substring(0, index);
0818: } else {
0819: index = s.lastIndexOf('\n');
0820: if (index >= 0)
0821: s = s.substring(0, index);
0822: }
0823: return s;
0824: }
0825:
0826: private static final RE totalRE = new UncheckedRE(
0827: "\\n?[^0-9]+ [0-9]+");
0828:
0829: private synchronized String lsla() {
0830: boolean valid = false;
0831: if (!write("\\ls -la\n"))
0832: return null;
0833: output.setLength(0);
0834: String s = null;
0835: for (int i = 0; i < 2; i++) {
0836: if (i > 0)
0837: Log.debug("lsla retry " + i);
0838: try {
0839: wait();
0840: } catch (InterruptedException e) {
0841: Log.error(e);
0842: return null;
0843: }
0844: s = output.toString();
0845: REMatch match = totalRE.getMatch(s);
0846: Log.debug("match = |" + match + "|");
0847: if (match == null) {
0848: Log.error("lsla no \"total\" line");
0849: continue;
0850: }
0851: s = s.substring(match.getEndIndex());
0852: int index = s.indexOf('\n');
0853: if (index < 0) {
0854: // Shouldn't happen.
0855: Log.error("lsla no '\\n'");
0856: continue;
0857: }
0858: s = s.substring(index + 1);
0859: valid = true;
0860: break;
0861: }
0862: if (!valid) {
0863: Log.error("lsla output not valid - returning null");
0864: return null;
0865: }
0866: // Strip prompt.
0867: int index = s.lastIndexOf("\r\n");
0868: if (index >= 0)
0869: s = s.substring(0, index);
0870: else {
0871: index = s.lastIndexOf('\n');
0872: if (index >= 0)
0873: s = s.substring(0, index);
0874: }
0875: return s;
0876: }
0877:
0878: // Do ls -ld on one file or directory.
0879: private synchronized String lsld(String path) {
0880: Debug.assertTrue(path != null);
0881: Debug.assertTrue(path.length() != 0);
0882: FastStringBuffer sb = new FastStringBuffer("\\ls -ld \"");
0883: sb.append(path);
0884: sb.append('"');
0885: sb.append('\n');
0886: if (!write(sb.toString()))
0887: return null;
0888: output.setLength(0);
0889: while (output.length() == 0) {
0890: try {
0891: wait();
0892: } catch (InterruptedException e) {
0893: Log.error(e);
0894: }
0895: }
0896: String s = output.toString();
0897:
0898: // Strip echo of original command.
0899: if (s.startsWith("ls -ld ")) {
0900: // Strip through end of line.
0901: int index = s.indexOf('\n');
0902: if (index < 0)
0903: return null; // Shouldn't happen.
0904: s = s.substring(index + 1);
0905: }
0906:
0907: // Skip lines starting with '<'.
0908: while (s.length() > 0 && s.charAt(0) == '<') {
0909: int index = s.indexOf('\n');
0910: if (index < 0)
0911: return null; // Shouldn't happen.
0912: s = s.substring(index + 1);
0913: }
0914:
0915: // Now we've arrived at the line we want. Strip "\r\n" or '\n'.
0916: int index = s.indexOf("\r\n");
0917: if (index >= 0) {
0918: s = s.substring(0, index);
0919: } else {
0920: index = s.lastIndexOf('\n');
0921: if (index >= 0)
0922: s = s.substring(0, index);
0923: }
0924: return s;
0925: }
0926:
0927: // Password or passphrase.
0928: private void sendPass(String pass) {
0929: Debug.assertTrue(pass != null);
0930: try {
0931: stdin.write(pass);
0932: stdin.write("\n");
0933: stdin.flush();
0934: if (outputBuffer != null) {
0935: FastStringBuffer sb = new FastStringBuffer("==> ");
0936: for (int i = pass.length(); i-- > 0;)
0937: sb.append('*');
0938: sb.append('\n');
0939: writeToOutputBuffer(sb.toString());
0940: }
0941: } catch (Exception e) {
0942: Log.error(e);
0943: }
0944: }
0945:
0946: private boolean write(String s) {
0947: try {
0948: if (echo
0949: || Editor.preferences().getBooleanProperty(
0950: Property.SSH_ECHO))
0951: Log.debug("==> |" + s + "|");
0952: if (outputBuffer != null)
0953: writeToOutputBuffer("==> " + s);
0954: stdin.write(s);
0955: stdin.flush();
0956: return true;
0957: } catch (IOException e) {
0958: Log.error(e);
0959: killProcess();
0960: return false;
0961: }
0962: }
0963:
0964: private void writeToOutputBuffer(final String s) {
0965: Runnable r = new Runnable() {
0966: public void run() {
0967: // Avoid race (and NPE) if setOutputBuffer(null) gets called in
0968: // another thread.
0969: final Buffer buf = outputBuffer;
0970: if (buf == null)
0971: return;
0972: try {
0973: buf.lockWrite();
0974: } catch (InterruptedException e) {
0975: Log.debug(e);
0976: return;
0977: }
0978: try {
0979: buf.append(s);
0980: buf.renumber();
0981: } finally {
0982: buf.unlockWrite();
0983: }
0984: for (EditorIterator it = new EditorIterator(); it
0985: .hasNext();) {
0986: Editor ed = it.nextEditor();
0987: if (ed.getBuffer() == buf) {
0988: ed.setDot(buf.getEnd());
0989: ed.moveCaretToDotCol();
0990: ed.setUpdateFlag(REPAINT);
0991: ed.updateDisplay();
0992: }
0993: }
0994: }
0995: };
0996: SwingUtilities.invokeLater(r);
0997: }
0998:
0999: public void checkLogin() {
1000: if (userName == null)
1001: userName = System.getProperty("user.name");
1002: if (password == null) {
1003: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
1004: Buffer buf = it.nextBuffer();
1005: if (buf.getFile() instanceof SshFile) {
1006: SshFile f = (SshFile) buf.getFile();
1007: if (f.hostName != null
1008: && f.hostName.equals(hostName)) {
1009: if (f.getUserName() != null
1010: && f.getUserName().equals(userName)) {
1011: password = f.getPassword();
1012: break;
1013: }
1014: }
1015: }
1016: }
1017: }
1018: }
1019:
1020: private static synchronized void cleanup() {
1021: // Walk buffer list in event dispatch thread.
1022: if (!SwingUtilities.isEventDispatchThread()) {
1023: Debug.bug();
1024: return;
1025: }
1026: if (sessionList != null) {
1027: for (int i = sessionList.size(); i-- > 0;) {
1028: SshSession session = (SshSession) sessionList.get(i);
1029: if (session.isLocked())
1030: continue;
1031: String hostName = session.getHostName();
1032: boolean inUse = false;
1033: for (BufferIterator it = new BufferIterator(); it
1034: .hasNext();) {
1035: Buffer buf = it.nextBuffer();
1036: if (buf.getFile() instanceof SshFile) {
1037: if (hostName
1038: .equals(buf.getFile().getHostName())) {
1039: inUse = true;
1040: break;
1041: }
1042: }
1043: }
1044: if (!inUse)
1045: session.dispose();
1046: }
1047: if (sessionList.size() == 0) {
1048: sessionList = null;
1049: if (cleanupThread != null) {
1050: cleanupThread.cancel();
1051: cleanupThread = null;
1052: }
1053: }
1054: }
1055: }
1056:
1057: private static final Runnable cleanupRunnable = new Runnable() {
1058: public void run() {
1059: cleanup();
1060: }
1061: };
1062:
1063: private String stdOutFilter(String s) {
1064: return s;
1065: }
1066:
1067: private synchronized void stdOutUpdate(final String s) {
1068: if (echo
1069: || Editor.preferences().getBooleanProperty(
1070: Property.SSH_ECHO))
1071: Log.debug("<== |" + s + "|");
1072: if (outputBuffer != null)
1073: writeToOutputBuffer(s);
1074: output.append(s);
1075: notify();
1076: }
1077:
1078: private String stdErrFilter(String s) {
1079: return s;
1080: }
1081:
1082: private void stdErrUpdate(final String s) {
1083: Log.debug("stderr: |" + s + "|");
1084: }
1085:
1086: private static String reveal(String s) {
1087: FastStringBuffer sb = new FastStringBuffer();
1088: final int length = s.length();
1089: for (int i = 0; i < length; i++) {
1090: char c = s.charAt(i);
1091: switch (c) {
1092: case '\r':
1093: sb.append("\\r");
1094: break;
1095: case '\n':
1096: sb.append("\\n");
1097: break;
1098: case '\t':
1099: sb.append("\\t");
1100: break;
1101: default:
1102: sb.append(c);
1103: break;
1104: }
1105: }
1106: return sb.toString();
1107: }
1108:
1109: class StdoutThread extends ReaderThread {
1110: // If this constructor is private, we run into jikes 1.15 bug #2256.
1111: StdoutThread() {
1112: super (process.getInputStream());
1113: }
1114:
1115: public String filter(String s) {
1116: return stdOutFilter(s);
1117: }
1118:
1119: public void update(String s) {
1120: stdOutUpdate(s);
1121: }
1122: }
1123:
1124: class StderrThread extends ReaderThread {
1125: // If this constructor is private, we run into jikes 1.15 bug #2256.
1126: StderrThread() {
1127: super (process.getErrorStream());
1128: }
1129:
1130: public String filter(String s) {
1131: return stdErrFilter(s);
1132: }
1133:
1134: public void update(String s) {
1135: stdErrUpdate(s);
1136: }
1137: }
1138: }
|