KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > SwingShell


1 import java.awt.BorderLayout JavaDoc;
2 import java.awt.Color JavaDoc;
3 import java.awt.FlowLayout JavaDoc;
4 import java.awt.Font JavaDoc;
5 import java.awt.event.ActionEvent JavaDoc;
6 import java.awt.event.ActionListener JavaDoc;
7 import java.awt.event.KeyAdapter JavaDoc;
8 import java.awt.event.KeyEvent JavaDoc;
9 import java.io.File JavaDoc;
10 import java.io.IOException JavaDoc;
11 import java.io.InputStream JavaDoc;
12 import java.io.OutputStream JavaDoc;
13
14 import javax.swing.BoxLayout JavaDoc;
15 import javax.swing.JButton JavaDoc;
16 import javax.swing.JDialog JavaDoc;
17 import javax.swing.JFrame JavaDoc;
18 import javax.swing.JLabel JavaDoc;
19 import javax.swing.JOptionPane JavaDoc;
20 import javax.swing.JPanel JavaDoc;
21 import javax.swing.JPasswordField JavaDoc;
22 import javax.swing.JTextArea JavaDoc;
23 import javax.swing.JTextField JavaDoc;
24 import javax.swing.SwingUtilities JavaDoc;
25
26 import ch.ethz.ssh2.Connection;
27 import ch.ethz.ssh2.InteractiveCallback;
28 import ch.ethz.ssh2.KnownHosts;
29 import ch.ethz.ssh2.ServerHostKeyVerifier;
30 import ch.ethz.ssh2.Session;
31
32 /**
33  *
34  * This is a very primitive SSH-2 dumb terminal (Swing based).
35  *
36  * The purpose of this class is to demonstrate:
37  *
38  * - Verifying server hostkeys with an existing known_hosts file
39  * - Displaying fingerprints of server hostkeys
40  * - Adding a server hostkey to a known_hosts file (+hashing the hostname for security)
41  * - Authentication with DSA, RSA, password and keyboard-interactive methods
42  *
43  */

44 public class SwingShell
45 {
46
47     /*
48      * NOTE: to get this feature to work, replace the "tilde" with your home directory,
49      * at least my JVM does not understand it. Need to check the specs.
50      */

51
52     static final String JavaDoc knownHostPath = "~/.ssh/known_hosts";
53     static final String JavaDoc idDSAPath = "~/.ssh/id_dsa";
54     static final String JavaDoc idRSAPath = "~/.ssh/id_rsa";
55
56     JFrame JavaDoc loginFrame = null;
57     JLabel JavaDoc hostLabel;
58     JLabel JavaDoc userLabel;
59     JTextField JavaDoc hostField;
60     JTextField JavaDoc userField;
61     JButton JavaDoc loginButton;
62
63     KnownHosts database = new KnownHosts();
64
65     public SwingShell()
66     {
67         File JavaDoc knownHostFile = new File JavaDoc(knownHostPath);
68         if (knownHostFile.exists())
69         {
70             try
71             {
72                 database.addHostkeys(knownHostFile);
73             }
74             catch (IOException JavaDoc e)
75             {
76             }
77         }
78     }
79
80     /**
81      * This dialog displays a number of text lines and a text field.
82      * The text field can either be plain text or a password field.
83      */

84     class EnterSomethingDialog extends JDialog JavaDoc
85     {
86         private static final long serialVersionUID = 1L;
87
88         JTextField JavaDoc answerField;
89         JPasswordField JavaDoc passwordField;
90
91         final boolean isPassword;
92
93         String JavaDoc answer;
94
95         public EnterSomethingDialog(JFrame JavaDoc parent, String JavaDoc title, String JavaDoc content, boolean isPassword)
96         {
97             this(parent, title, new String JavaDoc[] { content }, isPassword);
98         }
99
100         public EnterSomethingDialog(JFrame JavaDoc parent, String JavaDoc title, String JavaDoc[] content, boolean isPassword)
101         {
102             super(parent, title, true);
103
104             this.isPassword = isPassword;
105
106             JPanel JavaDoc pan = new JPanel JavaDoc();
107             pan.setLayout(new BoxLayout JavaDoc(pan, BoxLayout.Y_AXIS));
108
109             for (int i = 0; i < content.length; i++)
110             {
111                 if ((content[i] == null) || (content[i] == ""))
112                     continue;
113                 JLabel JavaDoc contentLabel = new JLabel JavaDoc(content[i]);
114                 pan.add(contentLabel);
115
116             }
117
118             answerField = new JTextField JavaDoc(20);
119             passwordField = new JPasswordField JavaDoc(20);
120
121             if (isPassword)
122                 pan.add(passwordField);
123             else
124                 pan.add(answerField);
125
126             KeyAdapter JavaDoc kl = new KeyAdapter JavaDoc()
127             {
128                 public void keyTyped(KeyEvent JavaDoc e)
129                 {
130                     if (e.getKeyChar() == '\n')
131                         finish();
132                 }
133             };
134
135             answerField.addKeyListener(kl);
136             passwordField.addKeyListener(kl);
137
138             getContentPane().add(BorderLayout.CENTER, pan);
139
140             setResizable(false);
141             pack();
142             setLocationRelativeTo(null);
143         }
144
145         private void finish()
146         {
147             if (isPassword)
148                 answer = new String JavaDoc(passwordField.getPassword());
149             else
150                 answer = answerField.getText();
151
152             dispose();
153         }
154     }
155
156     /**
157      * TerminalDialog is probably the worst terminal emulator ever written - implementing
158      * a real vt100 is left as an exercise to the reader, i.e., to you =)
159      *
160      */

161     class TerminalDialog extends JDialog JavaDoc
162     {
163         private static final long serialVersionUID = 1L;
164
165         JPanel JavaDoc botPanel;
166         JButton JavaDoc logoffButton;
167         JTextArea JavaDoc terminalArea;
168
169         Session sess;
170         InputStream JavaDoc in;
171         OutputStream JavaDoc out;
172
173         int x, y;
174
175         /**
176          * This thread consumes output from the remote server and displays it in
177          * the terminal window.
178          *
179          */

180         class RemoteConsumer extends Thread JavaDoc
181         {
182             char[][] lines = new char[y][];
183             int posy = 0;
184             int posx = 0;
185
186             private void addText(byte[] data, int len)
187             {
188                 for (int i = 0; i < len; i++)
189                 {
190                     char c = (char) (data[i] & 0xff);
191
192                     if (c == 8) // Backspace, VERASE
193
{
194                         if (posx < 0)
195                             continue;
196                         posx--;
197                         continue;
198                     }
199
200                     if (c == '\r')
201                     {
202                         posx = 0;
203                         continue;
204                     }
205
206                     if (c == '\n')
207                     {
208                         posy++;
209                         if (posy >= y)
210                         {
211                             for (int k = 1; k < y; k++)
212                                 lines[k - 1] = lines[k];
213                             posy--;
214                             lines[y - 1] = new char[x];
215                             for (int k = 0; k < x; k++)
216                                 lines[y - 1][k] = ' ';
217                         }
218                         continue;
219                     }
220
221                     if (c < 32)
222                     {
223                         continue;
224                     }
225
226                     if (posx >= x)
227                     {
228                         posx = 0;
229                         posy++;
230                         if (posy >= y)
231                         {
232                             posy--;
233                             for (int k = 1; k < y; k++)
234                                 lines[k - 1] = lines[k];
235                             lines[y - 1] = new char[x];
236                             for (int k = 0; k < x; k++)
237                                 lines[y - 1][k] = ' ';
238                         }
239                     }
240
241                     if (lines[posy] == null)
242                     {
243                         lines[posy] = new char[x];
244                         for (int k = 0; k < x; k++)
245                             lines[posy][k] = ' ';
246                     }
247
248                     lines[posy][posx] = c;
249                     posx++;
250                 }
251
252                 StringBuffer JavaDoc sb = new StringBuffer JavaDoc(x * y);
253
254                 for (int i = 0; i < lines.length; i++)
255                 {
256                     if (i != 0)
257                         sb.append('\n');
258
259                     if (lines[i] != null)
260                     {
261                         sb.append(lines[i]);
262                     }
263
264                 }
265                 setContent(sb.toString());
266             }
267
268             public void run()
269             {
270                 byte[] buff = new byte[8192];
271
272                 try
273                 {
274                     while (true)
275                     {
276                         int len = in.read(buff);
277                         if (len == -1)
278                             return;
279                         addText(buff, len);
280                     }
281                 }
282                 catch (Exception JavaDoc e)
283                 {
284                 }
285             }
286         }
287
288         public TerminalDialog(JFrame JavaDoc parent, String JavaDoc title, Session sess, int x, int y) throws IOException JavaDoc
289         {
290             super(parent, title, true);
291
292             this.sess = sess;
293
294             in = sess.getStdout();
295             out = sess.getStdin();
296
297             this.x = x;
298             this.y = y;
299
300             botPanel = new JPanel JavaDoc(new FlowLayout JavaDoc(FlowLayout.LEFT));
301
302             logoffButton = new JButton JavaDoc("Logout");
303             botPanel.add(logoffButton);
304
305             logoffButton.addActionListener(new ActionListener JavaDoc()
306             {
307                 public void actionPerformed(ActionEvent JavaDoc e)
308                 {
309                     /* Dispose the dialog, "setVisible(true)" method will return */
310                     dispose();
311                 }
312             });
313
314             Font JavaDoc f = new Font JavaDoc("Monospaced", Font.PLAIN, 16);
315
316             terminalArea = new JTextArea JavaDoc(y, x);
317             terminalArea.setFont(f);
318             terminalArea.setBackground(Color.BLACK);
319             terminalArea.setForeground(Color.ORANGE);
320             /* This is a hack. We cannot disable the caret,
321              * since setting editable to false also changes
322              * the meaning of the TAB key - and I want to use it in bash.
323              * Again - this is a simple DEMO terminal =)
324              */

325             terminalArea.setCaretColor(Color.BLACK);
326
327             KeyAdapter JavaDoc kl = new KeyAdapter JavaDoc()
328             {
329                 public void keyTyped(KeyEvent JavaDoc e)
330                 {
331                     int c = e.getKeyChar();
332
333                     try
334                     {
335                         out.write(c);
336                     }
337                     catch (IOException JavaDoc e1)
338                     {
339                     }
340                     e.consume();
341                 }
342             };
343
344             terminalArea.addKeyListener(kl);
345
346             getContentPane().add(terminalArea, BorderLayout.CENTER);
347             getContentPane().add(botPanel, BorderLayout.PAGE_END);
348
349             setResizable(false);
350             pack();
351             setLocationRelativeTo(parent);
352
353             new RemoteConsumer().start();
354         }
355
356         public void setContent(String JavaDoc lines)
357         {
358             // setText is thread safe, it does not have to be called from
359
// the Swing GUI thread.
360
terminalArea.setText(lines);
361         }
362     }
363
364     /**
365      * This ServerHostKeyVerifier asks the user on how to proceed if a key cannot be found
366      * in the in-memory database.
367      *
368      */

369     class AdvancedVerifier implements ServerHostKeyVerifier
370     {
371         public boolean verifyServerHostKey(String JavaDoc hostname, int port, String JavaDoc serverHostKeyAlgorithm,
372                 byte[] serverHostKey) throws Exception JavaDoc
373         {
374             final String JavaDoc host = hostname;
375             final String JavaDoc algo = serverHostKeyAlgorithm;
376
377             String JavaDoc message;
378
379             /* Check database */
380
381             int result = database.verifyHostkey(hostname, serverHostKeyAlgorithm, serverHostKey);
382
383             switch (result)
384             {
385             case KnownHosts.HOSTKEY_IS_OK:
386                 return true;
387
388             case KnownHosts.HOSTKEY_IS_NEW:
389                 message = "Do you want to accept the hostkey (type " + algo + ") from " + host + " ?\n";
390                 break;
391
392             case KnownHosts.HOSTKEY_HAS_CHANGED:
393                 message = "WARNING! Hostkey for " + host + " has changed!\nAccept anyway?\n";
394                 break;
395
396             default:
397                 throw new IllegalStateException JavaDoc();
398             }
399
400             /* Include the fingerprints in the message */
401
402             String JavaDoc hexFingerprint = KnownHosts.createHexFingerprint(serverHostKeyAlgorithm, serverHostKey);
403             String JavaDoc bubblebabbleFingerprint = KnownHosts.createBubblebabbleFingerprint(serverHostKeyAlgorithm,
404                     serverHostKey);
405
406             message += "Hex Fingerprint: " + hexFingerprint + "\nBubblebabble Fingerprint: " + bubblebabbleFingerprint;
407
408             /* Now ask the user */
409
410             int choice = JOptionPane.showConfirmDialog(loginFrame, message);
411
412             if (choice == JOptionPane.YES_OPTION)
413             {
414                 /* Be really paranoid. We use a hashed hostname entry */
415
416                 String JavaDoc hashedHostname = KnownHosts.createHashedHostname(hostname);
417
418                 /* Add the hostkey to the in-memory database */
419
420                 database.addHostkey(new String JavaDoc[] { hashedHostname }, serverHostKeyAlgorithm, serverHostKey);
421
422                 /* Also try to add the key to a known_host file */
423
424                 try
425                 {
426                     KnownHosts.addHostkeyToFile(new File JavaDoc(knownHostPath), new String JavaDoc[] { hashedHostname },
427                             serverHostKeyAlgorithm, serverHostKey);
428                 }
429                 catch (IOException JavaDoc ignore)
430                 {
431                 }
432
433                 return true;
434             }
435
436             if (choice == JOptionPane.CANCEL_OPTION)
437             {
438                 throw new Exception JavaDoc("The user aborted the server hostkey verification.");
439             }
440
441             return false;
442         }
443     }
444
445     /**
446      * The logic that one has to implement if "keyboard-interactive" autentication shall be
447      * supported.
448      *
449      */

450     class InteractiveLogic implements InteractiveCallback
451     {
452         int promptCount = 0;
453         String JavaDoc lastError;
454
455         public InteractiveLogic(String JavaDoc lastError)
456         {
457             this.lastError = lastError;
458         }
459
460         /* the callback may be invoked several times, depending on how many questions-sets the server sends */
461
462         public String JavaDoc[] replyToChallenge(String JavaDoc name, String JavaDoc instruction, int numPrompts, String JavaDoc[] prompt,
463                 boolean[] echo) throws IOException JavaDoc
464         {
465             String JavaDoc[] result = new String JavaDoc[numPrompts];
466
467             for (int i = 0; i < numPrompts; i++)
468             {
469                 /* Often, servers just send empty strings for "name" and "instruction" */
470
471                 String JavaDoc[] content = new String JavaDoc[] { lastError, name, instruction, prompt[i] };
472
473                 if (lastError != null)
474                 {
475                     /* show lastError only once */
476                     lastError = null;
477                 }
478
479                 EnterSomethingDialog esd = new EnterSomethingDialog(loginFrame, "Keyboard Interactive Authentication",
480                         content, !echo[i]);
481
482                 esd.setVisible(true);
483
484                 if (esd.answer == null)
485                     throw new IOException JavaDoc("Login aborted by user");
486
487                 result[i] = esd.answer;
488                 promptCount++;
489             }
490
491             return result;
492         }
493
494         /* We maintain a prompt counter - this enables the detection of situations where the ssh
495          * server is signaling "authentication failed" even though it did not send a single prompt.
496          */

497
498         public int getPromptCount()
499         {
500             return promptCount;
501         }
502     }
503
504     /**
505      * The SSH-2 connection is established in this thread.
506      * If we would not use a separate thread (e.g., put this code in
507      * the event handler of the "Login" button) then the GUI would not
508      * be responsive (missing window repaints if you move the window etc.)
509      */

510     class ConnectionThread extends Thread JavaDoc
511     {
512         String JavaDoc hostname;
513         String JavaDoc username;
514
515         public ConnectionThread(String JavaDoc hostname, String JavaDoc username)
516         {
517             this.hostname = hostname;
518             this.username = username;
519         }
520
521         public void run()
522         {
523             Connection conn = new Connection(hostname);
524
525             try
526             {
527                 /*
528                  *
529                  * CONNECT AND VERIFY SERVER HOST KEY (with callback)
530                  *
531                  */

532
533                 String JavaDoc[] hostkeyAlgos = database.getPreferredServerHostkeyAlgorithmOrder(hostname);
534
535                 if (hostkeyAlgos != null)
536                     conn.setServerHostKeyAlgorithms(hostkeyAlgos);
537
538                 conn.connect(new AdvancedVerifier());
539
540                 /*
541                  *
542                  * AUTHENTICATION PHASE
543                  *
544                  */

545
546                 boolean enableKeyboardInteractive = true;
547                 boolean enableDSA = true;
548                 boolean enableRSA = true;
549
550                 String JavaDoc lastError = null;
551
552                 while (true)
553                 {
554                     if ((enableDSA || enableRSA) && conn.isAuthMethodAvailable(username, "publickey"))
555                     {
556                         if (enableDSA)
557                         {
558                             File JavaDoc key = new File JavaDoc(idDSAPath);
559
560                             if (key.exists())
561                             {
562                                 EnterSomethingDialog esd = new EnterSomethingDialog(loginFrame, "DSA Authentication",
563                                         new String JavaDoc[] { lastError, "Enter DSA private key password:" }, true);
564                                 esd.setVisible(true);
565
566                                 boolean res = conn.authenticateWithPublicKey(username, key, esd.answer);
567
568                                 if (res == true)
569                                     break;
570
571                                 lastError = "DSA authentication failed.";
572                             }
573                             enableDSA = false; // do not try again
574
}
575
576                         if (enableRSA)
577                         {
578                             File JavaDoc key = new File JavaDoc(idRSAPath);
579
580                             if (key.exists())
581                             {
582                                 EnterSomethingDialog esd = new EnterSomethingDialog(loginFrame, "RSA Authentication",
583                                         new String JavaDoc[] { lastError, "Enter RSA private key password:" }, true);
584                                 esd.setVisible(true);
585
586                                 boolean res = conn.authenticateWithPublicKey(username, key, esd.answer);
587
588                                 if (res == true)
589                                     break;
590
591                                 lastError = "RSA authentication failed.";
592                             }
593                             enableRSA = false; // do not try again
594
}
595
596                         continue;
597                     }
598
599                     if (enableKeyboardInteractive && conn.isAuthMethodAvailable(username, "keyboard-interactive"))
600                     {
601                         InteractiveLogic il = new InteractiveLogic(lastError);
602
603                         boolean res = conn.authenticateWithKeyboardInteractive(username, il);
604
605                         if (res == true)
606                             break;
607
608                         if (il.getPromptCount() == 0)
609                         {
610                             // aha. the server announced that it supports "keyboard-interactive", but when
611
// we asked for it, it just denied the request without sending us any prompt.
612
// That happens with some server versions/configurations.
613
// We just disable the "keyboard-interactive" method and notify the user.
614

615                             lastError = "Keyboard-interactive does not work.";
616
617                             enableKeyboardInteractive = false; // do not try this again
618
}
619                         else
620                         {
621                             lastError = "Keyboard-interactive auth failed."; // try again, if possible
622
}
623
624                         continue;
625                     }
626
627                     if (conn.isAuthMethodAvailable(username, "password"))
628                     {
629                         final EnterSomethingDialog esd = new EnterSomethingDialog(loginFrame,
630                                 "Password Authentication",
631                                 new String JavaDoc[] { lastError, "Enter password for " + username }, true);
632
633                         esd.setVisible(true);
634
635                         if (esd.answer == null)
636                             throw new IOException JavaDoc("Login aborted by user");
637
638                         boolean res = conn.authenticateWithPassword(username, esd.answer);
639
640                         if (res == true)
641                             break;
642
643                         lastError = "Password authentication failed."; // try again, if possible
644

645                         continue;
646                     }
647
648                     throw new IOException JavaDoc("No supported authentication methods available.");
649                 }
650
651                 /*
652                  *
653                  * AUTHENTICATION OK. DO SOMETHING.
654                  *
655                  */

656
657                 Session sess = conn.openSession();
658
659                 int x_width = 90;
660                 int y_width = 30;
661
662                 sess.requestPTY("dumb", x_width, y_width, 0, 0, null);
663                 sess.startShell();
664
665                 TerminalDialog td = new TerminalDialog(loginFrame, username + "@" + hostname, sess, x_width, y_width);
666
667                 /* The following call blocks until the dialog has been closed */
668
669                 td.setVisible(true);
670
671             }
672             catch (IOException JavaDoc e)
673             {
674                 //e.printStackTrace();
675
JOptionPane.showMessageDialog(loginFrame, "Exception: " + e.getMessage());
676             }
677
678             /*
679              *
680              * CLOSE THE CONNECTION.
681              *
682              */

683
684             conn.close();
685
686             /*
687              *
688              * CLOSE THE LOGIN FRAME - APPLICATION WILL BE EXITED (no more frames)
689              *
690              */

691
692             Runnable JavaDoc r = new Runnable JavaDoc()
693             {
694                 public void run()
695                 {
696                     loginFrame.dispose();
697                 }
698             };
699
700             SwingUtilities.invokeLater(r);
701         }
702     }
703
704     void loginPressed()
705     {
706         String JavaDoc hostname = hostField.getText().trim();
707         String JavaDoc username = userField.getText().trim();
708
709         if ((hostname.length() == 0) || (username.length() == 0))
710         {
711             JOptionPane.showMessageDialog(loginFrame, "Please fill out both fields!");
712             return;
713         }
714
715         loginButton.setEnabled(false);
716         hostField.setEnabled(false);
717         userField.setEnabled(false);
718
719         ConnectionThread ct = new ConnectionThread(hostname, username);
720
721         ct.start();
722     }
723
724     void showGUI()
725     {
726         loginFrame = new JFrame JavaDoc("Ganymed SSH2 SwingShell");
727
728         hostLabel = new JLabel JavaDoc("Hostname:");
729         userLabel = new JLabel JavaDoc("Username:");
730
731         hostField = new JTextField JavaDoc("", 20);
732         userField = new JTextField JavaDoc("", 10);
733
734         loginButton = new JButton JavaDoc("Login");
735
736         loginButton.addActionListener(new ActionListener JavaDoc()
737         {
738             public void actionPerformed(java.awt.event.ActionEvent JavaDoc e)
739             {
740                 loginPressed();
741             }
742         });
743
744         JPanel JavaDoc loginPanel = new JPanel JavaDoc();
745
746         loginPanel.add(hostLabel);
747         loginPanel.add(hostField);
748         loginPanel.add(userLabel);
749         loginPanel.add(userField);
750         loginPanel.add(loginButton);
751
752         loginFrame.getRootPane().setDefaultButton(loginButton);
753
754         loginFrame.getContentPane().add(loginPanel, BorderLayout.PAGE_START);
755         //loginFrame.getContentPane().add(textArea, BorderLayout.CENTER);
756

757         loginFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
758
759         loginFrame.pack();
760         loginFrame.setResizable(false);
761         loginFrame.setLocationRelativeTo(null);
762         loginFrame.setVisible(true);
763     }
764
765     void startGUI()
766     {
767         Runnable JavaDoc r = new Runnable JavaDoc()
768         {
769             public void run()
770             {
771                 showGUI();
772             }
773         };
774
775         SwingUtilities.invokeLater(r);
776
777     }
778
779     public static void main(String JavaDoc[] args)
780     {
781         SwingShell client = new SwingShell();
782         client.startGUI();
783     }
784 }
785
Popular Tags