001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2015-08-24 22:47:39 +0000 (Mon, 24 Aug 2015) $' 
007 * '$Revision: 33633 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.sdm.spa;
031
032import java.awt.Container;
033import java.awt.GridBagConstraints;
034import java.awt.GridBagLayout;
035import java.awt.Insets;
036import java.io.ByteArrayOutputStream;
037import java.util.HashSet;
038import java.util.Hashtable;
039
040import javax.swing.JLabel;
041import javax.swing.JOptionPane;
042import javax.swing.JPanel;
043import javax.swing.JPasswordField;
044import javax.swing.JTextField;
045
046import com.jcraft.jsch.ChannelExec;
047import com.jcraft.jsch.JSch;
048import com.jcraft.jsch.Session;
049import com.jcraft.jsch.UIKeyboardInteractive;
050import com.jcraft.jsch.UserInfo;
051
052import ptolemy.actor.TypedAtomicActor;
053import ptolemy.actor.TypedIOPort;
054import ptolemy.actor.parameters.PortParameter;
055import ptolemy.data.BooleanToken;
056import ptolemy.data.IntToken;
057import ptolemy.data.StringToken;
058import ptolemy.data.Token;
059import ptolemy.data.expr.FileParameter;
060import ptolemy.data.expr.Parameter;
061import ptolemy.data.type.BaseType;
062import ptolemy.kernel.CompositeEntity;
063import ptolemy.kernel.util.Attribute;
064import ptolemy.kernel.util.IllegalActionException;
065import ptolemy.kernel.util.NameDuplicationException;
066
067//////////////////////////////////////////////////////////////////////////
068//// Ssh2Exec
069/**
070 * <p>
071 * Connects to a remote host using Ssh2 protocol.
072 * 
073 * </p>
074 * <p>
075 * Error conditions this actor must respond robustly to:
076 * <ul>
077 * <li>Wrong identity file given.</li>
078 * <li>Host unreachable.</li>
079 * <li>Login unsuccessful.</li>
080 * <li>Session dies prematurely.</li>
081 * </ul>
082 * 
083 * </p>
084 * <p>
085 * This actor will keep the session open until it receives a different username
086 * and host combination.
087 * 
088 * </p>
089 * <p>
090 * Modifications:
091 * <ul>
092 * <li>Added support for password authentication</li>
093 * <li>When no identity is specified, the connection will revert to password
094 * authentication.</li>
095 * <li>The actor retains the password information for user@host, so the user
096 * will only be prompted for passwd once only for each user@host. It is
097 * implemented through a static hashtable.</li>
098 * </ul>
099 * </p>
100 * Reference: Ant version 1.6.2.
101 * 
102 * @author Ilkay Altintas, Xiaowen Xin
103 * @version $Id: Ssh2Exec.java 33633 2015-08-24 22:47:39Z crawl $
104 */
105
106
107/**
108 * 
109 * 
110 * FIXME
111 * THIS ACTOR SHARES DUPLICATE CODE WITH Ssh2Exec. BEFORE MAKING CHANGES HERE
112 * FACTOR OUT THE DUPLICATED CODE FROM BOTH CLASSES.
113 * 
114 * 
115 */
116
117
118public class Ssh2Exec extends TypedAtomicActor {
119
120        /**
121         * Construct an SSH2 actor with the given container and name. Create the
122         * parameters, initialize their values.
123         * 
124         * @param container
125         *            The container.
126         * @param name
127         *            The name of this actor.
128         * @exception IllegalActionException
129         *                If the entity cannot be contained by the proposed
130         *                container.
131         * @exception NameDuplicationException
132         *                If the container already has an actor with this name.
133         */
134        public Ssh2Exec(CompositeEntity container, String name)
135                        throws NameDuplicationException, IllegalActionException {
136                super(container, name);
137
138                // initialize our variables
139                _jsch = new JSch();
140                _setIdentities = new HashSet();
141
142                // create all the ports
143                user = new PortParameter(this, "user");
144                host = new PortParameter(this, "host");
145                paramIdentity = new FileParameter(this, "identity");
146                identity = new TypedIOPort(this, "identity", true, false);
147                command = new TypedIOPort(this, "command", true, false);
148                stdout = new TypedIOPort(this, "stdout", false, true);
149                stderr = new TypedIOPort(this, "stderr", false, true);
150                returncode = new TypedIOPort(this, "returncode", false, true);
151                errors = new TypedIOPort(this, "errors", false, true);
152                streamingMode = new Parameter(this, "streaming mode", new BooleanToken(
153                                false));
154                streamingMode.setTypeEquals(BaseType.BOOLEAN);
155
156                // Set the type constraints.
157                user.setTypeEquals(BaseType.STRING);
158                host.setTypeEquals(BaseType.STRING);
159                identity.setTypeEquals(BaseType.STRING);
160                command.setTypeEquals(BaseType.STRING);
161                stdout.setTypeEquals(BaseType.STRING);
162                stderr.setTypeEquals(BaseType.STRING);
163                returncode.setTypeEquals(BaseType.INT);
164                errors.setTypeEquals(BaseType.STRING);
165
166                _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" "
167                                + "width=\"75\" height=\"50\" style=\"fill:gray\"/>\n"
168                                + "<text x=\"5\" y=\"30\""
169                                + "style=\"font-size:25; fill:yellow; font-family:SansSerif\">"
170                                + "SSH2</text>\n" + "</svg>\n");
171        }
172
173        // //////////////// Public ports and parameters ///////////////////////
174
175        /**
176         * Username on the SSH host to be connected to.
177         */
178        public PortParameter user;
179        /**
180         * Host to connect to.
181         */
182        public PortParameter host;
183        /**
184         * The file path for <i>userName</i>'s ssh identity file if the user wants
185         * to connect without having to enter the password all the time.
186         * 
187         * <p>
188         * The user can browse this file as it is a parameter.
189         * </p>
190         */
191        public FileParameter paramIdentity;
192        /**
193         * The string representation of the file path for <i>userName</i>'s ssh
194         * identity file if the user wants to connect without having to enter the
195         * password all the time.
196         * 
197         * <p>
198         * This is the input option for the identity file.
199         * </p>
200         */
201        public TypedIOPort identity;
202        /**
203         * The command to be executed on the remote host.
204         * 
205         * <p>
206         * It needs to be provided as a string.
207         * </p>
208         */
209        public TypedIOPort command;
210
211        /**
212         * Output of the command as it would output to the standard shell output.
213         */
214        public TypedIOPort stdout;
215        /**
216         * The error that were reported by the remote execution or while connecting.
217         */
218        public TypedIOPort stderr;
219        /**
220         * The return code of the execution.
221         * 
222         * <p>
223         * This port will return <i>0 (zero)</i> if the execution is not succesfull,
224         * and a positive integer if it is successful.
225         * </p>
226         */
227        public TypedIOPort returncode;
228        /**
229         * The string representation of all the errors that happened during the
230         * execution of the actor, if there are any.
231         */
232        public TypedIOPort errors;
233
234        /**
235         * Specifying whether the output should be sent in a streaming mode.
236         */
237        public Parameter streamingMode;
238
239        // /////////////////////////////////////////////////////////////////
240        // // public methods ////
241
242        /**
243         * Callback for changes in attribute values Get the WSDL from the given URL.
244         * 
245         * @param at
246         *            The attribute that changed.
247         * @exception IllegalActionException
248         */
249        public void attributeChanged(Attribute at) throws IllegalActionException {
250                /*
251                 * if ((at == user) || (at == host)) { if (!
252                 * (user.getExpression().equals(""))) { String temp1 =
253                 * user.getExpression(); if (! (host.getExpression().equals(""))) {
254                 * String temp2 = ( (StringToken) host.getToken()).stringValue();
255                 * _attachText( "_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" "
256                 * + "width=\"90\" height=\"60\" style=\"fill:gray\"/>\n" +
257                 * "<text x=\"5\" y=\"15\"" +
258                 * "style=\"font-size:12; fill:yellow; font-family:SansSerif\">" + temp1
259                 * + "\n@\n" + temp2 + "</text>\n" + "</svg>\n"); } else { _attachText(
260                 * "_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " +
261                 * "width=\"90\" height=\"60\" style=\"fill:gray\"/>\n" +
262                 * "<text x=\"5\" y=\"15\"" +
263                 * "style=\"font-size:12; fill:yellow; font-family:SansSerif\">" + temp1
264                 * + "\n@\nunknown_host</text>\n" + "</svg>\n"); } } else { if
265                 * (host.getExpression().equals("")){ _attachText( "_iconDescription",
266                 * "<svg>\n" + "<rect x=\"0\" y=\"0\" " +
267                 * "width=\"75\" height=\"50\" style=\"fill:gray\"/>\n" +
268                 * "<text x=\"5\" y=\"30\"" +
269                 * "style=\"font-size:25; fill:yellow; font-family:SansSerif\">" +
270                 * "SSH2</text>\n" + "</svg>\n"); } else { String temp2 = (
271                 * (StringToken) host.getToken()).stringValue(); _attachText(
272                 * "_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " +
273                 * "width=\"90\" height=\"60\" style=\"fill:gray\"/>\n" +
274                 * "<text x=\"5\" y=\"15\"" +
275                 * "style=\"font-size:12; fill:yellow; font-family:SansSerif\">" +
276                 * "unknown_user\n@\n" + temp2 + "</text>\n" + "</svg>\n"); } }
277                 * 
278                 * }
279                 */
280        } // end-of-attributeChanged
281
282        /**
283         * Send the token in the <i>value</i> parameter to the output.
284         * 
285         * @exception IllegalActionException
286         *                If it is thrown by the send() method sending out the
287         *                token.
288         */
289        public void fire() throws IllegalActionException {
290                super.fire();
291                // by default we will try to connect with private/public keys.
292                boolean isPassAuth = false;
293
294                user.update();
295                host.update();
296                
297                Token token = user.getToken();
298                if(token == null) {
299                        throw new IllegalActionException(this, "No user specified.");
300                }
301                String strUser = ((StringToken) token).stringValue();
302                
303                token = host.getToken();
304                if(token == null) {
305                        throw new IllegalActionException(this, "No host specified.");
306                }
307                String strHost = ((StringToken) token).stringValue();
308                
309                token = command.get(0);
310                if(token == null) {
311                        throw new IllegalActionException(this, "No command specified.");
312                }
313                String strCommand = ((StringToken) token).stringValue();
314
315                token = streamingMode.getToken();
316                if(token == null) {
317                        throw new IllegalActionException(this, "Stream mode not specified.");
318                }
319                streaming = ((BooleanToken) token).booleanValue();
320
321                String strIdentity;
322                if (identity.getWidth() > 0) {
323                        strIdentity = ((StringToken) identity.get(0)).stringValue();
324                } else {
325                        strIdentity = ((StringToken) paramIdentity.getToken())
326                                        .stringValue();
327                }
328
329                if (strIdentity != null && strIdentity.length() > 0) {
330                        // Hack the path because we can't deal with "file:" or "file://"
331                        if (strIdentity.startsWith("file:")) {
332                                strIdentity = strIdentity.substring(5);
333
334                                if (strIdentity.startsWith("//")) {
335                                        strIdentity = strIdentity.substring(2);
336                                }
337                        }
338                } else {
339                        // We are now need to connect with password
340                        isPassAuth = true;
341                }
342
343                try {
344                        if (isPassAuth) {
345                                // Use password authentication, where use will be prompted
346                                // for password. The password should be valid for the session
347
348                                _connect(strUser, strHost);
349                        } else {
350                                _connect(strUser, strHost, strIdentity);
351                        }
352
353                        _exec(strCommand);
354                } catch (IllegalActionException e) {
355                        // caught an exception, so output it to the errors port
356                        stdout.send(0, new StringToken(""));
357                        stderr.send(0, new StringToken(""));
358                        returncode.send(0, new IntToken(0));
359                        errors.send(0, new StringToken(e.getMessage()));
360                        throw e;
361                }
362        }
363
364        /**
365         * Terminate any sessions. This method is invoked exactly once per execution
366         * of an application. None of the other action methods should be be invoked
367         * after it.
368         * 
369         * @exception IllegalActionException
370         *                Not thrown in this base class.
371         */
372        public void wrapup() throws IllegalActionException {
373                _disconnect();
374        }
375
376        // //////////////// Private Methods ///////////////////////
377
378        /**
379         * @throws IllegalActionException
380         *             If the connection fails.
381         *             
382         *             
383         * FIXME See FIXME at top of file
384         */
385        private void _connect(String strUser, String strHost, String strIdentity)
386                        throws IllegalActionException {
387
388                _debug("Connecting with " + strUser + "@" + strHost
389                                + " with identity: " + strIdentity);
390
391                try {
392
393                        strIdentity = strIdentity.trim();
394                        if (!strIdentity.equals("")) {
395                                if (_setIdentities.add(strIdentity)) {
396                                        // we haven't seen this identity before
397                                        _jsch.addIdentity(strIdentity);
398                                }
399                        }
400
401                        if (!strUser.equals(_strOldUser) || !strHost.equals(_strOldHost)
402                                        || !_session.isConnected()) {
403
404                                if (null != _session && _session.isConnected()) {
405                                        _disconnect();
406                                }
407
408                                _session = _jsch.getSession(strUser, strHost, 22);
409                                _strOldUser = strUser;
410                                _strOldHost = strHost;
411
412                                // username and passphrase will be given via UserInfo interface.
413                                UserInfo ui = new MyUserInfo();
414                                _session.setUserInfo(ui);
415                                _session.connect(30000);
416                        }
417
418                } catch (Exception e) {
419                        // a couple of possible exception messages that could happen here:
420                        // 1. java.io.FileNotFoundException
421                        // 2. session is down
422                        System.err.println("Exception caught in " + this.getFullName());
423                        System.err.println("I was trying to connect with " + strUser + "@"
424                                        + strHost + " with identity: " + strIdentity);
425                        e.printStackTrace();
426                        throw new IllegalActionException("Exception caught in "
427                                        + this.getFullName() + "\n(" + e.getClass().getName()
428                                        + ")\n" + e.getMessage());
429                }
430        }
431
432        /**
433         * Connect with password 1. When connected for the first time, it will
434         * prompt for password. 2. When execute for the same user@host, it should
435         * use the stored password. 3. When connect to a different user@host, it can
436         * prompt password again.
437         * 
438         * @throws IllegalActionException
439         *             If the connection fails.
440         *             
441         * FIXME See FIXME at top of file
442         */
443        private void _connect(String strUser, String strHost)
444                        throws IllegalActionException {
445
446                _debug("Connecting with " + strUser + "@" + strHost + " with password.");
447                try {
448
449                        if (!strUser.equals(_strOldUser) || !strHost.equals(_strOldHost)
450                                        || !_session.isConnected()) {
451
452                                if (null != _session && _session.isConnected()) {
453                                        _disconnect();
454                                }
455
456                                _session = _jsch.getSession(strUser, strHost, 22);
457                                _strOldUser = strUser;
458                                _strOldHost = strHost;
459
460                                // username and passphrase will be given via UserInfo interface.
461
462                                // check whether ui is already set
463                                UserInfo ui;
464                                ui = (UserInfo) hash.get(strUser + "@" + strHost);
465                                if (ui == null) {
466                                        ui = new MyUserInfo();
467                                }
468
469                                // If it is already there. We will use that.
470                                // Hopefully we can use the info for connect to the
471                                // same user@host
472
473                                _session.setUserInfo(ui);
474                                _session.connect();
475                                // add to the hashtable
476                                hash.put(strUser + "@" + strHost, ui);
477
478                        }
479
480                } catch (Exception e) {
481                        // a couple of possible exception messages that could happen here:
482                        // 1. java.io.FileNotFoundException
483                        // 2. session is down
484                        System.err.println("Exception caught in " + this.getFullName());
485                        System.err.println("I was trying to connect with " + strUser + "@"
486                                        + strHost + " with password.");
487                        e.printStackTrace();
488                        throw new IllegalActionException("Exception caught in "
489                                        + this.getFullName() + "\n(" + e.getClass().getName()
490                                        + ")\n" + e.getMessage());
491                }
492        }
493
494        /**
495         * 
496         * @throws IllegalActionException
497         *             if disconnect fails.
498         *             
499         * FIXME See FIXME at top of file
500         *             
501         */
502        private void _disconnect() throws IllegalActionException {
503                if (null == _session) {
504                        // no session, so nothing to disconnect
505                        return;
506                }
507
508                try {
509                        _session.disconnect();
510                } catch (Exception e) {
511                        System.err.println("Exception caught in " + this.getFullName());
512                        e.printStackTrace();
513                        throw new IllegalActionException("Exception caught in "
514                                        + this.getFullName() + "\n(" + e.getClass().getName()
515                                        + ")\n" + e.getMessage());
516                }
517        }
518
519        /**
520         * 
521         * @throws IllegalActionException
522         */
523        private void _exec(String execCommand) throws IllegalActionException {
524                if (null == _session) {
525                        // no session, so way to execute
526                        return;
527                }
528
529                try {
530                        final ChannelExec channel = (ChannelExec) _session
531                                        .openChannel("exec");
532                        channel.setCommand(execCommand);
533
534                        streamOut = new ByteArrayOutputStream();
535                        ByteArrayOutputStream streamErr = new ByteArrayOutputStream();
536
537                        channel.setOutputStream(streamOut);
538                        channel.setErrStream(streamErr);
539
540                        channel.connect();
541
542                        // wait for it to finish
543                        _thread = new Thread() {
544                                public void run() {
545                                        int offset = 0;
546                                        String current = "";
547                                        while (!channel.isEOF()) {
548                                                if (_thread == null) {
549                                                        return;
550                                                }
551                                                try {
552                                                        sleep(500);
553                                                        if (streaming) {
554                                                                // System.out.println(streamOut.size());
555                                                                byte[] stream = streamOut.toByteArray();
556                                                                int len = stream.length;
557                                                                current += new String(stream, offset, len
558                                                                                - offset);
559                                                                // System.out.println(current);
560                                                                offset = len;
561                                                                current = _sendStreamOutput(current);
562                                                                // stdout.send(0,new StringToken(current));
563                                                        }
564
565                                                } catch (Exception e) {
566                                                        System.out.println(e.getMessage());
567                                                }
568                                        }
569                                        try {
570                                                if (streaming) {
571                                                        byte[] stream = streamOut.toByteArray();
572                                                        int len = stream.length;
573                                                        current += new String(stream, offset, len - offset);
574                                                        // System.out.println(current);
575                                                        offset = len;
576                                                        current = _sendStreamOutput(current);
577                                                        if (!current.equals("")) {
578                                                                // there was some output that wasn't sent yet.
579                                                                stdout.send(0, new StringToken(current));
580                                                        }
581                                                }
582                                        } catch (Exception ex) {
583                                        }
584                                }
585                        };
586
587                        _thread.start();
588                        _thread.join(0); // set this to a different value to time out
589
590                        if (_thread.isAlive()) {
591                                // ran out of time
592                                _thread = null;
593                                throw new IllegalActionException("In " + this.getFullName()
594                                                + ": Remote operation timed out!");
595                        }
596                        // completed successfully
597
598                        // this is the wrong test if the remote OS is OpenVMS,
599                        // but there doesn't seem to be a way to detect it.
600                        int ec = channel.getExitStatus();
601                        channel.disconnect();
602
603                        if (!streaming) {
604                                stdout.send(0, new StringToken(streamOut.toString()));
605                        }
606                        stderr.send(0, new StringToken(streamErr.toString()));
607                        returncode.send(0, new IntToken(ec));
608                        errors.send(0, new StringToken(""));
609
610                } catch (Exception e) {
611                        System.err.println("Exception caught in " + this.getFullName());
612                        e.printStackTrace();
613                        throw new IllegalActionException("Exception caught in "
614                                        + this.getFullName() + "\n(" + e.getClass().getName()
615                                        + ")\n" + e.getMessage());
616                }
617        }
618
619        /**
620         * This function streams the output line by line. If the current stream
621         * doesn't contain any line breaks and it is longer then a certain
622         * threshold, sends the stream content. Otherwise, if there is no line end,
623         * then it returns the content of the last line.
624         * 
625         * @param currentStream
626         *       */
627        public String _sendStreamOutput(String currentStream)
628                        throws IllegalActionException {
629                String line;
630                int crInd;
631                while ((crInd = currentStream.indexOf("\n")) > -1) {
632                        line = currentStream.substring(0, crInd);
633                        // System.out.println(line);
634                        try {
635                                currentStream = currentStream.substring(crInd + 1);
636                        } catch (Exception ex) {
637                                // reached end of string.
638                                // System.out.println(currentStream);
639                                currentStream = "";
640                        }
641                        stdout.send(0, new StringToken(line));
642                }
643
644                // if the whole output is a single line, stream it once the string
645                // length reaches a certain threshold.
646                if (currentStream.length() > 2048) {
647                        stdout.send(0, new StringToken(currentStream));
648                } else {
649                        // this line doesn't reach the threshold and contains no line break
650                        // it will be concatenated to the further output.
651                        return currentStream;
652                }
653                return "";
654        }
655
656        // //////////////// Private variables ///////////////////////
657
658        private JSch _jsch = null;
659        private Session _session = null;
660        private Thread _thread = null;
661        private HashSet _setIdentities = null;
662        private String _strOldUser = null;
663        private String _strOldHost = null;
664
665        // ////////////////Public Static ///////////////////////
666        // Used to store connection info
667        public static Hashtable hash = new Hashtable();
668
669        // //////////////// Inner classes ///////////////////////
670
671        public static class MyUserInfo implements UserInfo, UIKeyboardInteractive {
672
673                public String getPassword() {
674                        return passwd;
675                }
676
677                String passwd = null;
678                JTextField passwordField = (JTextField) new JPasswordField(20);
679
680                public boolean promptYesNo(String str) {
681                        // This method gets called to answer the question similar to
682                        // "are you sure you want to connect to host whose key
683                        // is not in database ..."
684                        return true;
685                }
686
687                public String getPassphrase() {
688                        return null;
689                }
690
691                public boolean promptPassphrase(String message) {
692                        return false;
693                }
694
695                public boolean promptPassword(String message) {
696                        if (passwd != null) {
697                                return true;
698                        }
699
700                        Object[] ob = { passwordField };
701                        int result = JOptionPane.showConfirmDialog(null, ob, message,
702                                        JOptionPane.OK_CANCEL_OPTION);
703                        if (result == JOptionPane.OK_OPTION) {
704                                passwd = passwordField.getText();
705                                return true;
706                        } else {
707                                return false;
708                        }
709                }
710
711                public void showMessage(String message) {
712                        // This method gets called when the server sends over a MOTD.
713                        // MessageHandler.message(message);
714                }
715
716                //
717                // Extensions for supporting keyboard-interactive logins
718                // Norbert Podhorszki pnorbert@cs.ucdavis.edu
719                // Taken from example
720                // http://www.jcraft.com/jsch/examples/UserAuthKI.java
721                //
722
723                final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1,
724                                GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
725                                new Insets(0, 0, 0, 0), 0, 0);
726                private Container panel;
727
728                public String[] promptKeyboardInteractive(String destination,
729                                String name, String instruction, String[] prompt, boolean[] echo) {
730
731                        // System.out.println("SSH: promptKI called\n"+
732                        // "\tDestination: " + destination +
733                        // "\n\tName: " + name +
734                        // "\n\tinstruction: " + instruction +
735                        // "\n\tpromptlen: " + prompt.length);
736
737                        panel = new JPanel();
738                        panel.setLayout(new GridBagLayout());
739
740                        gbc.weightx = 1.0;
741                        gbc.gridwidth = GridBagConstraints.REMAINDER;
742                        gbc.gridx = 0;
743                        panel.add(new JLabel(instruction), gbc);
744                        gbc.gridy++;
745
746                        gbc.gridwidth = GridBagConstraints.RELATIVE;
747
748                        JTextField[] texts = new JTextField[prompt.length];
749                        for (int i = 0; i < prompt.length; i++) {
750                                gbc.fill = GridBagConstraints.NONE;
751                                gbc.gridx = 0;
752                                gbc.weightx = 1;
753                                panel.add(new JLabel(prompt[i]), gbc);
754
755                                gbc.gridx = 1;
756                                gbc.fill = GridBagConstraints.HORIZONTAL;
757                                gbc.weighty = 1;
758                                if (echo[i]) {
759                                        texts[i] = new JTextField(20);
760                                } else {
761                                        texts[i] = new JPasswordField(20);
762                                }
763                                panel.add(texts[i], gbc);
764                                gbc.gridy++;
765                        }
766
767                        if (JOptionPane.showConfirmDialog(null, panel, destination + ": "
768                                        + name, JOptionPane.OK_CANCEL_OPTION,
769                                        JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) {
770                                String[] response = new String[prompt.length];
771                                for (int i = 0; i < prompt.length; i++) {
772                                        response[i] = texts[i].getText();
773                                }
774                                return response;
775                        } else {
776                                return null; // cancel
777                        }
778                }
779
780        }
781
782        private ByteArrayOutputStream streamOut;
783        private boolean streaming = false;
784}
785
786// vim: sw=4 ts=4 et