001: /*
002: * SSHTools - Java SSH2 API
003: *
004: * Copyright (C) 2002-2003 Lee David Painter and Contributors.
005: *
006: * Contributions made by:
007: *
008: * Brett Smith
009: * Richard Pernavas
010: * Erwin Bolwidt
011: *
012: * This program is free software; you can redistribute it and/or
013: * modify it under the terms of the GNU General Public License
014: * as published by the Free Software Foundation; either version 2
015: * of the License, or (at your option) any later version.
016: *
017: * This program is distributed in the hope that it will be useful,
018: * but WITHOUT ANY WARRANTY; without even the implied warranty of
019: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
020: * GNU General Public License for more details.
021: *
022: * You should have received a copy of the GNU General Public License
023: * along with this program; if not, write to the Free Software
024: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
025: */
026: package com.sshtools.ant;
027:
028: import com.sshtools.j2ssh.*;
029: import com.sshtools.j2ssh.authentication.*;
030: import com.sshtools.j2ssh.configuration.*;
031: import com.sshtools.j2ssh.session.*;
032: import com.sshtools.j2ssh.transport.*;
033: import com.sshtools.j2ssh.transport.cipher.*;
034: import com.sshtools.j2ssh.transport.hmac.*;
035: import com.sshtools.j2ssh.transport.publickey.*;
036:
037: import org.apache.tools.ant.*;
038:
039: import java.io.*;
040:
041: import java.util.*;
042:
043: public class Ssh extends Task {
044: protected String host;
045: protected int port = 22;
046: protected String username;
047: protected String password;
048: protected String keyfile;
049: protected String passphrase;
050: protected String cipher;
051: protected String mac;
052: protected String fingerprint;
053: protected String logfile = null;
054: protected boolean verifyhost = true;
055: protected boolean always = false;
056: protected SshClient ssh;
057: protected Vector tasks = new Vector();
058: protected String sshtoolsHome;
059: protected String newline = "\n";
060:
061: public Ssh() {
062: super ();
063: }
064:
065: protected void validate() throws BuildException {
066: if (host == null) {
067: throw new BuildException(
068: "You must provide a host to connect to!");
069: }
070:
071: if (username == null) {
072: throw new BuildException(
073: "You must supply a username for authentication!");
074: }
075:
076: if ((password == null) && (keyfile == null)) {
077: throw new BuildException(
078: "You must supply either a password or keyfile/passphrase to authenticate!");
079: }
080:
081: if (verifyhost && (fingerprint == null)) {
082: throw new BuildException(
083: "Public key fingerprint required to verify the host");
084: }
085: }
086:
087: protected void connectAndAuthenticate() throws BuildException {
088: if (sshtoolsHome != null) {
089: System.setProperty("sshtools.home", sshtoolsHome);
090: }
091:
092: log("Initializing J2SSH");
093:
094: try {
095: ConfigurationLoader.initialize(false);
096: log("Creating connection to " + host + ":"
097: + String.valueOf(port));
098:
099: if (ssh == null) {
100: ssh = new SshClient();
101:
102: SshConnectionProperties properties = new SshConnectionProperties();
103: properties.setHost(host);
104: properties.setPort(port);
105: properties.setUsername(username);
106:
107: if (cipher != null) {
108: if (SshCipherFactory.getSupportedCiphers()
109: .contains(cipher)) {
110: properties.setPrefSCEncryption(cipher);
111: properties.setPrefCSEncryption(cipher);
112: } else {
113: this
114: .log(cipher
115: + " is not a supported cipher, using default "
116: + SshCipherFactory
117: .getDefaultCipher());
118: }
119: }
120:
121: if (mac != null) {
122: if (SshHmacFactory.getSupportedMacs().contains(mac)) {
123: properties.setPrefCSMac(mac);
124: properties.setPrefSCMac(mac);
125: } else {
126: this
127: .log(mac
128: + " is not a supported mac, using default "
129: + SshHmacFactory
130: .getDefaultHmac());
131: }
132: }
133:
134: log("Connecting....");
135: ssh.connect(properties,
136: new AbstractKnownHostsKeyVerification(new File(
137: System.getProperty("user.home"), ".ssh"
138: + File.separator
139: + "known_hosts")
140: .getAbsolutePath()) {
141: public void onUnknownHost(String hostname,
142: SshPublicKey key)
143: throws InvalidHostFileException {
144: if (Ssh.this .verifyhost) {
145: if (key
146: .getFingerprint()
147: .equalsIgnoreCase(
148: Ssh.this .fingerprint)) {
149: allowHost(hostname, key, always);
150: }
151: } else {
152: allowHost(hostname, key, always);
153: }
154: }
155:
156: public void onHostKeyMismatch(
157: String hostname,
158: SshPublicKey allowed,
159: SshPublicKey supplied)
160: throws InvalidHostFileException {
161: if (Ssh.this .verifyhost) {
162: if (supplied
163: .getFingerprint()
164: .equalsIgnoreCase(
165: Ssh.this .fingerprint)) {
166: allowHost(hostname, supplied,
167: always);
168: }
169: } else {
170: allowHost(hostname, supplied,
171: always);
172: }
173: }
174:
175: public void onDeniedHost(String host) {
176: log("The server host key is denied!");
177: }
178: });
179:
180: int result;
181: boolean authenticated = false;
182: log("Authenticating " + username);
183:
184: if (keyfile != null) {
185: log("Performing public key authentication");
186:
187: PublicKeyAuthenticationClient pk = new PublicKeyAuthenticationClient();
188:
189: // Open up the private key file
190: SshPrivateKeyFile file = SshPrivateKeyFile
191: .parse(new File(keyfile));
192:
193: // If the private key is passphrase protected then ask for the passphrase
194: if (file.isPassphraseProtected()
195: && (passphrase == null)) {
196: throw new BuildException(
197: "Private key file is passphrase protected, please supply a valid passphrase!");
198: }
199:
200: // Get the key
201: SshPrivateKey key = file.toPrivateKey(passphrase);
202: pk.setUsername(username);
203: pk.setKey(key);
204:
205: // Try the authentication
206: result = ssh.authenticate(pk);
207:
208: if (result == AuthenticationProtocolState.COMPLETE) {
209: authenticated = true;
210: } else if (result == AuthenticationProtocolState.PARTIAL) {
211: log("Public key authentication completed, attempting password authentication");
212: } else {
213: throw new BuildException(
214: "Public Key Authentication failed!");
215: }
216: }
217:
218: if ((password != null) && (authenticated == false)) {
219: log("Performing password authentication");
220:
221: PasswordAuthenticationClient pwd = new PasswordAuthenticationClient();
222: pwd.setUsername(username);
223: pwd.setPassword(password);
224: result = ssh.authenticate(pwd);
225:
226: if (result == AuthenticationProtocolState.COMPLETE) {
227: log("Authentication complete");
228: } else if (result == AuthenticationProtocolState.PARTIAL) {
229: throw new BuildException(
230: "Password Authentication succeeded but further authentication required!");
231: } else {
232: throw new BuildException(
233: "Password Authentication failed!");
234: }
235: }
236: }
237: } catch (IOException ex) {
238: throw new BuildException(ex);
239: }
240: }
241:
242: protected void disconnect() throws BuildException {
243: try {
244: log("Disconnecting from " + host);
245: ssh.disconnect();
246: } catch (Exception ex) {
247: throw new BuildException(ex);
248: }
249: }
250:
251: public void execute() throws org.apache.tools.ant.BuildException {
252: validate();
253: connectAndAuthenticate();
254: executeSubTasks();
255: disconnect();
256: }
257:
258: protected void executeSubTasks() throws BuildException {
259: Iterator it = tasks.iterator();
260: SshSubTask task;
261:
262: while (it.hasNext()) {
263: task = (SshSubTask) it.next();
264: task.setParent(this );
265: task.execute(ssh);
266: }
267: }
268:
269: public void setUsername(String username) {
270: this .username = username;
271: }
272:
273: public void setPassword(String password) {
274: this .password = password;
275: }
276:
277: public void setPort(int port) {
278: this .port = port;
279: }
280:
281: public void setNewline(String newline) {
282: this .newline = newline;
283: }
284:
285: public void setHost(String host) {
286: this .host = host;
287: }
288:
289: public void setKeyfile(String keyfile) {
290: this .keyfile = keyfile;
291: }
292:
293: public void setPassphrase(String passphrase) {
294: this .passphrase = passphrase;
295: }
296:
297: public void setCipher(String cipher) {
298: this .cipher = cipher;
299: }
300:
301: public void setMac(String mac) {
302: this .mac = mac;
303: }
304:
305: public void setLogfile(String logfile) {
306: this .logfile = logfile;
307: }
308:
309: public void setFingerprint(String fingerprint) {
310: this .fingerprint = fingerprint;
311: }
312:
313: public void setVerifyhost(boolean verifyhost) {
314: this .verifyhost = verifyhost;
315: }
316:
317: public void setAlways(boolean always) {
318: this .always = always;
319: }
320:
321: public void setSshtoolshome(String sshtoolsHome) {
322: this .sshtoolsHome = sshtoolsHome;
323: }
324:
325: protected boolean hasMoreSftpTasks() {
326: Iterator it = tasks.iterator();
327:
328: while (it.hasNext()) {
329: if (it.next().getClass().equals(Sftp.class)) {
330: return true;
331: }
332: }
333:
334: return false;
335: }
336:
337: public SshSubTask createShell() {
338: SshSubTask task = new Shell();
339: tasks.addElement(task);
340:
341: return task;
342: }
343:
344: public SshSubTask createExec() {
345: SshSubTask task = new Exec();
346: tasks.addElement(task);
347:
348: return task;
349: }
350:
351: public SshSubTask createSftp() {
352: SshSubTask task = new Sftp();
353: tasks.addElement(task);
354:
355: return task;
356: }
357:
358: public class Exec extends Shell {
359: private String cmd;
360:
361: public void execute(SshClient ssh) throws BuildException {
362: this .validate();
363:
364: try {
365: log("Executing command " + cmd);
366:
367: // Create the session channel
368: SessionChannelClient session = ssh.openSessionChannel();
369: output = new SessionOutputReader(session);
370:
371: // Allocate a pseudo terminal is one has been requested
372: allocatePseudoTerminal(session);
373:
374: // Execute the command
375: if (session.executeCommand(cmd)) {
376: performTasks(session);
377: } else {
378: throw new BuildException(
379: "The command failed to start");
380: }
381: } catch (IOException ex) {
382: }
383: }
384:
385: public void setCmd(String cmd) {
386: this .cmd = cmd;
387: }
388: }
389:
390: public class Shell extends SshSubTask implements PseudoTerminal {
391: private String term = null;
392: private int cols = 80;
393: private int rows = 34;
394: private int width = 0;
395: private int height = 0;
396: private String terminalModes = "";
397: private Vector commands = new Vector();
398: private SessionChannelClient session;
399: protected SessionOutputReader output;
400:
401: public void execute(SshClient ssh) throws BuildException {
402: this .validate();
403:
404: try {
405: // Create the session channel
406: session = ssh.openSessionChannel();
407:
408: // Add an event listener so we can filter the output for our read commands
409: // This is much easier than reading from an InputStream that could potentailly block
410: output = new SessionOutputReader(session);
411:
412: // Allocate a pseudo terminal is one has been requested
413: allocatePseudoTerminal(session);
414:
415: // Start the shell
416: if (session.startShell()) {
417: performTasks(session);
418: } else {
419: throw new BuildException(
420: "The session failed to start");
421: }
422: } catch (IOException ex) {
423: }
424: }
425:
426: protected void validate() throws BuildException {
427: if (ssh == null) {
428: throw new BuildException("Invalid SSH session");
429: }
430:
431: if (!ssh.isConnected()) {
432: throw new BuildException(
433: "The SSH session is not connected");
434: }
435: }
436:
437: protected void allocatePseudoTerminal(
438: SessionChannelClient session) throws BuildException {
439: try {
440: if (term != null) {
441: if (!session.requestPseudoTerminal(this )) {
442: throw new BuildException(
443: "The server failed to allocate a pseudo terminal");
444: }
445: }
446: } catch (IOException ex) {
447: throw new BuildException(ex);
448: }
449: }
450:
451: protected void performTasks(SessionChannelClient session)
452: throws BuildException {
453: if (commands.size() > 0) {
454: Iterator it = commands.iterator();
455: Object obj;
456:
457: while (it.hasNext()) {
458: obj = it.next();
459:
460: if (obj instanceof Write) {
461: ((Write) obj).execute();
462: } else if (obj instanceof Read) {
463: ((Read) obj).execute();
464: } else {
465: throw new BuildException(
466: "Unexpected shell operation "
467: + obj.toString());
468: }
469: }
470: } else {
471: try {
472: output
473: .echoLineByLineToClose(new SessionOutputEcho() {
474: public void echo(String echo) {
475: log(echo);
476: }
477: });
478: } catch (InterruptedException ex) {
479: throw new BuildException(ex);
480: }
481: }
482: }
483:
484: public void setTerm(String term) {
485: this .term = term;
486: }
487:
488: public void setCols(int cols) {
489: this .cols = cols;
490: }
491:
492: public void setRows(int rows) {
493: this .rows = rows;
494: }
495:
496: /**
497: * PseduoTermainal interface
498: */
499: public String getTerm() {
500: return term;
501: }
502:
503: public int getColumns() {
504: return cols;
505: }
506:
507: public int getRows() {
508: return rows;
509: }
510:
511: public int getWidth() {
512: return width;
513: }
514:
515: public int getHeight() {
516: return height;
517: }
518:
519: public String getEncodedTerminalModes() {
520: return terminalModes;
521: }
522:
523: /**
524: * Reading/Writing to the session/command
525: */
526: public Write createWrite() {
527: Write write = new Write();
528: commands.add(write);
529:
530: return write;
531: }
532:
533: public Read createRead() {
534: Read read = new Read();
535: commands.add(read);
536:
537: return read;
538: }
539:
540: public class Read {
541: protected String taskString = "";
542: private int timeout = 0;
543: private boolean echo = true;
544:
545: public void execute() throws BuildException {
546: try {
547: output.markCurrentPosition();
548:
549: if (output.waitForString(taskString, timeout,
550: new SessionOutputEcho() {
551: public void echo(String msg) {
552: if (echo) {
553: log(msg);
554: }
555: }
556: })) {
557: } else {
558: throw new BuildException(
559: "Timeout waiting for string "
560: + taskString);
561: }
562: } catch (InterruptedException ex) {
563: throw new BuildException(ex);
564: }
565: }
566:
567: /**
568: * the message as nested text
569: */
570: public void addText(String s) {
571: setString(Ssh.this .getProject().replaceProperties(s));
572: }
573:
574: public void setTimeout(int timeout) {
575: this .timeout = timeout;
576: }
577:
578: public void setEcho(boolean echo) {
579: this .echo = echo;
580: }
581:
582: /**
583: * the message as an attribute
584: */
585: public void setString(String s) {
586: taskString += s;
587: }
588: }
589:
590: public class Write {
591: protected boolean echo = true;
592: protected String taskString = "";
593: protected boolean newline = true;
594:
595: public void execute() throws BuildException {
596: try {
597: if (echo) {
598: log(taskString);
599: }
600:
601: session.getOutputStream().write(
602: taskString.getBytes());
603:
604: if (newline) {
605: session.getOutputStream().write(
606: Ssh.this .newline.getBytes());
607: }
608: } catch (IOException ex) {
609: throw new BuildException(ex);
610: }
611: }
612:
613: /**
614: * the message as nested text
615: */
616: public void addText(String s) {
617: setString(Ssh.this .getProject().replaceProperties(s));
618: }
619:
620: /**
621: * the message as an attribute
622: */
623: public void setString(String s) {
624: taskString += s;
625: }
626:
627: public void setEcho(boolean echo) {
628: this .echo = echo;
629: }
630:
631: public void setNewline(boolean newline) {
632: this.newline = newline;
633: }
634: }
635: }
636: }
|