00001
00002 import java.awt.Dimension;
00003 import java.awt.Font;
00004 import java.awt.Image;
00005 import java.awt.Point;
00006 import java.awt.Toolkit;
00007 import java.awt.event.ActionEvent;
00008 import java.awt.event.ActionListener;
00009 import java.awt.event.FocusEvent;
00010 import java.awt.event.FocusListener;
00011 import java.awt.event.KeyEvent;
00012 import java.awt.event.KeyListener;
00013 import java.awt.event.WindowEvent;
00014 import java.io.BufferedReader;
00015 import java.io.BufferedWriter;
00016 import java.io.FileWriter;
00017 import java.io.IOException;
00018 import java.io.InputStream;
00019 import java.io.InputStreamReader;
00020 import java.net.InetAddress;
00021 import java.net.MalformedURLException;
00022 import java.net.URL;
00023 import java.net.UnknownHostException;
00024 import java.text.SimpleDateFormat;
00025 import java.util.Calendar;
00026 import java.util.LinkedList;
00027 import java.util.List;
00028
00029 import javax.swing.GroupLayout;
00030 import javax.swing.JCheckBox;
00031 import javax.swing.JEditorPane;
00032 import javax.swing.JFrame;
00033 import javax.swing.JLabel;
00034 import javax.swing.JScrollPane;
00035 import javax.swing.JSeparator;
00036 import javax.swing.JTextField;
00037 import javax.swing.Timer;
00038 import javax.swing.UIManager;
00039 import javax.swing.WindowConstants;
00040 import javax.swing.plaf.ColorUIResource;
00041
00042 import crypto.CipherEngine;
00043 import crypto.PublicEncryptor;
00044 import crypto.SymmetricCipher;
00045
00046 import pbx.PBXClient;
00047
00048 import protocol.CallContext;
00049 import protocol.DatagramChannel;
00050 import protocol.RemotePeer;
00051 import protocol.VoicePDU;
00052
00053 import audio.AudioInterface;
00054
00055 import ui.JImageButton;
00056 import ui.JSecState;
00057 import utils.Log;
00058
00059
00060
00061
00062
00063
00064
00065
00066 public class CryptoPhoneApp extends JFrame
00067 implements ActionListener, KeyListener, PBXClient.Context, Log.AttentionContext
00068 {
00069
00070
00071
00072 private static final long serialVersionUID = -1830703904673318918L;
00073
00074
00075
00076
00077 private static final String appTitle = "IP1-10: Kryptofon";
00078
00079
00080
00081
00082 private static final String defaultInputMsg =
00083 "<type in message, command or command arguments here>";
00084
00085
00086
00087
00088
00089 private static final String defaultLogAreaDumpFilename = "mykf-log-area-";
00090
00091
00092
00093
00094 private String serverName = "atlas.dsv.su.se";
00095
00096
00097
00098
00099 private int serverPort = 9494;
00100
00101
00102
00103
00104 private PBXClient pbxChannel = null;
00105
00106
00107
00108
00109 private String pbxChannelStatus = "";
00110
00111
00112
00113
00114 private Timer mainTimer = null;
00115
00116
00117
00118
00119
00120 private int reconnectTimeout = -1;
00121
00122
00123
00124
00125 private int reconnectRetryCount = 0;
00126
00127
00128
00129
00130 private int localUdpPort = 47000;
00131
00132
00133
00134
00135
00136 private DatagramChannel udpChannel = null;
00137
00138
00139
00140
00141 private AudioInterface audioInterface = null;
00142
00143
00144
00145
00146 private PBXClient.ControlMessage lastMessageFromPBX = null;
00147
00148
00149
00150
00151 PublicEncryptor remotePublicKey = null;
00152
00153
00154
00155
00156
00157 private String currentInvite = null;
00158
00159
00160
00161
00162
00163 private int inviteTimeout = -1;
00164
00165
00166
00167
00168
00169 private boolean monitorIfPeerIsSendingVoice = false;
00170
00171
00172
00173
00174 private JEditorPane logArea;
00175
00176
00177
00178 private JSecState securityState;
00179 private JImageButton sendButton;
00180 private JImageButton listPeersButton;
00181 private JImageButton dialButton;
00182 private JImageButton secureDialButton;
00183 private JImageButton hangupButton;
00184 private JLabel imsgLabel;
00185 private JTextField inputMsg;
00186 private JLabel idLabel;
00187 private JTextField userId;
00188 private JCheckBox autoAnswer;
00189
00190
00191
00192
00193
00194
00195 public CryptoPhoneApp( String args[] )
00196 {
00197 super( appTitle );
00198
00199 setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
00200
00201
00202
00203
00204
00205 Font textFont = new Font( Font.SANS_SERIF, Font.PLAIN, 14 );
00206 UIManager.put( "ToolTip.background", new ColorUIResource( 0xFF, 0xFF, 0xC7 ) );
00207
00208
00209
00210 List<Image> icons = new LinkedList<Image> ();
00211 icons.add( JImageButton.loadIcon( this, "favicon48.png" ).getImage () );
00212 icons.add( JImageButton.loadIcon( this, "favicon24.png" ).getImage () );
00213 icons.add( JImageButton.loadIcon( this, "favicon16.png" ).getImage () );
00214 setIconImages( icons );
00215
00216
00217
00218 inputMsg = new JTextField( defaultInputMsg, 30 );
00219 inputMsg.setFont( textFont );
00220 inputMsg.selectAll ();
00221 inputMsg.setToolTipText(
00222 "<html><head></head><body><p>"
00223 + "Enter a text message or command here, then press <tt>Enter</tt><br/>"
00224 + "If you are going to use command buttons or command mnemonics,<br/>"
00225 + "then put command arguments here (or leave this field empty)."
00226 + "</p></body></html>"
00227 );
00228
00229 imsgLabel = new JLabel ();
00230 imsgLabel.setFont( textFont );
00231 imsgLabel.setDisplayedMnemonic( KeyEvent.VK_I );
00232 imsgLabel.setLabelFor( inputMsg );
00233
00234 securityState = new JSecState( this );
00235
00236 sendButton = new JImageButton( this,
00237 "Send message", "chat.png", "chat2.png" );
00238 sendButton.setMnemonic( KeyEvent.VK_ENTER );
00239
00240 listPeersButton = new JImageButton( this,
00241 "List kryptofon users (Alt + L)", "listPeers.png", "listPeers2.png" );
00242 listPeersButton.setMnemonic( KeyEvent.VK_L );
00243
00244 dialButton = new JImageButton( this,
00245 "Make a call (Alt+C)", "dial.png", "dial2.png" );
00246 dialButton.setMnemonic( KeyEvent.VK_C );
00247
00248 secureDialButton = new JImageButton( this,
00249 "Make a secure call (Alt+S)", "secureDial.png", "secureDial2.png" );
00250 secureDialButton.setMnemonic( KeyEvent.VK_S );
00251
00252 hangupButton = new JImageButton( this,
00253 "Clear or reject the call (Alt+H)", "hangup.png", "hangup2.png" );
00254 hangupButton.setMnemonic( KeyEvent.VK_H );
00255
00256 userId = new JTextField ();
00257 userId.setFont( textFont );
00258 userId.setText( getUserId () );
00259 userId.setToolTipText(
00260 "<html><head></head><body><p>"
00261 + "Enter name, which will be used to identify you to other kryptofon peers.<br/>"
00262 + "The name should be unique and possibly without whitespaces."
00263 + "</p></body></html>"
00264 );
00265
00266 idLabel = new JLabel( "My Name:" );
00267 idLabel.setFont( textFont );
00268 idLabel.setDisplayedMnemonic( KeyEvent.VK_N );
00269 idLabel.setLabelFor( userId );
00270
00271 autoAnswer = new JCheckBox( "Autoanswer" );
00272 autoAnswer.setMnemonic( KeyEvent.VK_A );
00273 autoAnswer.setToolTipText( "Toggle auto-answer mode (Alt+A)" );
00274
00275 logArea = new JEditorPane ();
00276 logArea.setFont( textFont );
00277 logArea.setEditable( false );
00278 logArea.setToolTipText(
00279 "<html><head></head><body><p>"
00280 + "<strong>Log Area</strong><br/>"
00281 + "Use <tt>:dump</tt> command to save contents into a file"
00282 + "</p></body></html>"
00283 );
00284
00285 clearLogArea ();
00286
00287
00288
00289 createLayout ();
00290
00291
00292
00293 inputMsg.requestFocus ();
00294
00295
00296
00297 createEventListeners ();
00298
00299
00300
00301
00302
00303 Dimension win = new Dimension( 1024, 600 );
00304 Dimension scsz = Toolkit.getDefaultToolkit().getScreenSize();
00305 win.width = Math.min( win.width, scsz.width );
00306 win.height = Math.min( win.height, scsz.height - 40 );
00307 setSize( win );
00308 setMinimumSize( new Dimension( 600, 400 ) );
00309
00310
00311
00312 setLocation( ( scsz.width - win.width )/2, ( scsz.height - 40 - win.height )/2 );
00313
00314
00315
00316
00317
00318 Log.setEnabled( Log.ALL , false );
00319 Log.setEnabled( Log.VERB , false );
00320 Log.setEnabled( Log.PDU , false );
00321 Log.setEnabled( Log.AUDIO , false );
00322 Log.setEnabled( Log.DEBUG , false );
00323 Log.setEnabled( Log.ATTN , true );
00324 Log.setEnabled( Log.TRACE , true );
00325 Log.setEnabled( Log.INFO , true );
00326 Log.setEnabled( Log.WARN , true );
00327 Log.setEnabled( Log.ERROR , true );
00328
00329 Log.attn = this;
00330
00331
00332
00333 displayUsage ();
00334
00335
00336
00337
00338
00339 if ( args.length >= 1 )
00340 {
00341 serverName = args[ 0 ];
00342 if( args.length >= 2 ) try {
00343 serverPort = Integer.parseInt( args[ 1 ] );
00344 } catch ( NumberFormatException e ) {
00345 Log.warn( "Using the default destination TCP port: " + serverPort );
00346 }
00347 }
00348
00349
00350
00351
00352 CipherEngine.initialize ();
00353
00354
00355
00356 startKryptofonServices ();
00357
00358
00359
00360 pbxChannel = new PBXClient( serverName, serverPort, this );
00361 pbxChannel.start ();
00362
00363
00364
00365 mainTimer = new Timer( 1000, this );
00366 mainTimer.start ();
00367
00368 Log.trace( "Created instance of the " + this.getClass().toString () );
00369 }
00370
00371
00372
00373
00374
00375
00376 private void createLayout ()
00377 {
00378
00379
00380
00381 GroupLayout layout = new GroupLayout( getContentPane () );
00382 getContentPane().setLayout( layout );
00383 layout.setAutoCreateContainerGaps( true );
00384 layout.setAutoCreateGaps( true );
00385
00386 JScrollPane logPane = new JScrollPane ();
00387 logPane.setViewportView( logArea );
00388
00389 JSeparator vertS1 = new JSeparator( JSeparator.VERTICAL );
00390 JSeparator vertS2 = new JSeparator( JSeparator.VERTICAL );
00391
00392 int textH = 26;
00393 int iconH = 32;
00394 int iconW = 32;
00395
00396 layout.setHorizontalGroup
00397 (
00398 layout
00399 .createParallelGroup( GroupLayout.Alignment.LEADING )
00400 .addGroup
00401 (
00402 layout
00403 .createSequentialGroup ()
00404 .addGroup
00405 (
00406 layout
00407 .createParallelGroup( GroupLayout.Alignment.LEADING )
00408 .addGroup
00409 (
00410 GroupLayout.Alignment.TRAILING,
00411 layout
00412 .createSequentialGroup ()
00413 .addComponent( securityState )
00414 .addGap( 5 )
00415 .addComponent( imsgLabel )
00416 .addGap( 0 )
00417 .addComponent( inputMsg )
00418 .addComponent( sendButton, iconW, iconW, iconW )
00419 .addGap( 15 )
00420 .addComponent( vertS1, 4, 4, 4 )
00421 .addGap( 10 )
00422 .addComponent( listPeersButton, iconW, iconW, iconW )
00423 .addComponent( dialButton, iconW, iconW, iconW )
00424 .addComponent( secureDialButton, iconW, iconW, iconW )
00425 .addComponent( hangupButton, iconW, iconW, iconW )
00426 .addGap( 15 )
00427
00428
00429 .addComponent( vertS2, 4, 4, 4 )
00430 .addGap( 10 )
00431 .addComponent( idLabel )
00432 .addGap( 5 )
00433 .addComponent( userId )
00434 )
00435 .addComponent( logPane )
00436 )
00437 )
00438 );
00439
00440 layout.setVerticalGroup
00441 (
00442 layout
00443 .createParallelGroup( GroupLayout.Alignment.LEADING )
00444 .addGroup
00445 (
00446 layout
00447 .createSequentialGroup ()
00448 .addGroup
00449 (
00450 layout
00451 .createParallelGroup( GroupLayout.Alignment.CENTER )
00452 .addComponent( securityState )
00453 .addComponent( imsgLabel )
00454 .addComponent( inputMsg, textH, textH, textH )
00455 .addComponent( sendButton, iconH, iconH, iconH )
00456 .addComponent( vertS1, iconH, iconH, iconH )
00457 .addComponent( listPeersButton, iconH, iconH, iconH )
00458 .addComponent( dialButton, iconH, iconH, iconH )
00459 .addComponent( secureDialButton, iconH, iconH, iconH )
00460 .addComponent( hangupButton, iconH, iconH, iconH )
00461 .addComponent( vertS2, iconH, iconH, iconH )
00462 .addComponent( idLabel )
00463 .addComponent( userId, textH, textH, textH )
00464
00465 )
00466 .addComponent( logPane )
00467 )
00468 );
00469
00470 pack();
00471 }
00472
00473
00474
00475
00476
00477
00478 private void createEventListeners ()
00479 {
00480 addWindowListener( new java.awt.event.WindowAdapter () {
00481 public void windowClosing( java.awt.event.WindowEvent evt ) {
00482 formWindowClosing( evt );
00483 }
00484 } );
00485
00486 inputMsg.addKeyListener( this );
00487 userId.addKeyListener( this );
00488 autoAnswer.addKeyListener( this );
00489 logArea.addKeyListener( this );
00490
00491 sendButton.addKeyListener( this );
00492 listPeersButton.addKeyListener( this );
00493 dialButton.addKeyListener( this );
00494 secureDialButton.addKeyListener( this );
00495 hangupButton.addKeyListener( this );
00496
00497 sendButton.addActionListener( this );
00498 listPeersButton.addActionListener( this );
00499 dialButton.addActionListener( this );
00500 secureDialButton.addActionListener( this );
00501 hangupButton.addActionListener( this );
00502
00503 inputMsg.addFocusListener(
00504 new FocusListener () {
00505 public void focusGained( FocusEvent evt ) {
00506 inputMsg.selectAll ();
00507 }
00508 public void focusLost( FocusEvent evt ) {
00509 }
00510 }
00511 );
00512
00513 userId.addFocusListener(
00514 new FocusListener () {
00515 public void focusGained( FocusEvent evt ) {
00516 userId.selectAll ();
00517 }
00518 public void focusLost( FocusEvent evt ) {
00519 synchronized( userId ) {
00520 userId.setText( getUserId () );
00521 }
00522 }
00523 }
00524 );
00525 }
00526
00527
00528
00529
00530
00531
00532 public void startKryptofonServices ()
00533 {
00534 audioInterface = new audio.AudioInterfacePCM ();
00535 udpChannel = new DatagramChannel( localUdpPort );
00536
00537
00538
00539
00540
00541
00542
00543
00544
00545
00546 int udpDifference = udpChannel.getLocalPort () - localUdpPort;
00547 if ( udpDifference > 0 ) {
00548 userId.setText( getUserId() + "-" + ( udpDifference + 1 ) );
00549 Point winPos = getLocation ();
00550 winPos.x += 40 * udpDifference; winPos.y += 40 * udpDifference;
00551 setLocation( winPos );
00552 }
00553 }
00554
00555
00556
00557
00558 public void stopKryptofonServices ()
00559 {
00560
00561
00562 executeCommand( ":bye", null );
00563
00564
00565
00566 if ( udpChannel != null ) {
00567 udpChannel.stop ();
00568 }
00569
00570
00571
00572 if ( audioInterface != null ) {
00573 audioInterface.stopPlay ();
00574 audioInterface.stopRecording ();
00575 audioInterface.cleanUp ();
00576 }
00577
00578
00579
00580 udpChannel = null;
00581 audioInterface = null;
00582 }
00583
00584
00585
00586
00587
00588
00589 private void sendButton_Clicked ()
00590 {
00591 if ( defaultInputMsg.equalsIgnoreCase( inputMsg.getText () ) ) {
00592 return;
00593 }
00594
00595 parseInputMessage ();
00596 }
00597
00598
00599
00600
00601 private void listPeersButton_Clicked ()
00602 {
00603 executeCommand( ":list", null );
00604 }
00605
00606
00607
00608
00609 private void dialButton_Clicked ()
00610 {
00611
00612
00613
00614 if ( this.lastMessageFromPBX == null ) {
00615 executeCommand( ":invite", tokenizeInputMessage () );
00616 } else {
00617 acceptIncomingCall( false );
00618 }
00619 }
00620
00621
00622
00623
00624 private void secureDialButton_Clicked ()
00625 {
00626
00627
00628
00629 if ( this.lastMessageFromPBX == null ) {
00630 executeCommand( ":invite+", tokenizeInputMessage () );
00631 } else {
00632 acceptIncomingCall( true );
00633 }
00634 }
00635
00636
00637
00638
00639 private void hangupButton_Clicked ()
00640 {
00641 executeCommand( ":bye", tokenizeInputMessage () );
00642 }
00643
00644
00645
00646
00647 private void formWindowClosing( WindowEvent evt )
00648 {
00649 stopKryptofonServices ();
00650
00651 if ( pbxChannel != null ) {
00652 pbxChannel.close ();
00653 }
00654
00655 System.exit( 0 );
00656 }
00657
00658
00659
00660
00661
00662 public void keyPressed( KeyEvent ke ) {
00663
00664 int keyCode = ke.getKeyCode ();
00665
00666 if( ke.getSource () == inputMsg && keyCode == KeyEvent.VK_ENTER )
00667 {
00668 if ( defaultInputMsg.equalsIgnoreCase( inputMsg.getText () ) ) {
00669 return;
00670 }
00671
00672 parseInputMessage ();
00673 }
00674 else if( ke.getSource () == userId && keyCode == KeyEvent.VK_ENTER )
00675 {
00676 inputMsg.requestFocus ();
00677 }
00678 else if( keyCode == KeyEvent.VK_F1 )
00679 {
00680 executeCommand( ":help", null );
00681 }
00682 }
00683
00684
00685
00686
00687 public void keyReleased( KeyEvent ke )
00688 {
00689
00690 }
00691
00692
00693
00694
00695 public void keyTyped( KeyEvent ke )
00696 {
00697
00698 }
00699
00700
00701
00702
00703 public void actionPerformed( ActionEvent ae )
00704 {
00705 if ( ae.getSource () == mainTimer ) {
00706 mainTimerEvent ();
00707 } else if ( ae.getSource () == sendButton ) {
00708 sendButton_Clicked ();
00709 } else if ( ae.getSource () == listPeersButton ) {
00710 listPeersButton_Clicked ();
00711 } else if ( ae.getSource () == dialButton ) {
00712 dialButton_Clicked ();
00713 } else if ( ae.getSource () == secureDialButton ) {
00714 secureDialButton_Clicked ();
00715 } else if ( ae.getSource () == hangupButton ) {
00716 hangupButton_Clicked ();
00717 }
00718 }
00719
00720
00721
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731
00732
00733
00734
00735
00736
00737
00738 private void mainTimerEvent ()
00739 {
00740
00741
00742
00743 RemotePeer peer = udpChannel.getRemotePeer();
00744 if ( monitorIfPeerIsSendingVoice
00745 && peer != null && udpChannel.isPearDead( 2500 ) )
00746 {
00747 monitorIfPeerIsSendingVoice = false;
00748
00749 report( "logWarn", "Warning: Not receiving voice from '"
00750 + peer.getRemoteUserId () + "'; Maybe it's dead?" );
00751 }
00752
00753
00754
00755
00756 if ( inviteTimeout >= 0 )
00757 {
00758 if ( --inviteTimeout < 0 )
00759 {
00760 report( "logError", "It seems that kryptofon user '" + currentInvite
00761 + "' is not connected." );
00762 report( "logInfo", "Use :list to query available users..." );
00763 logMessage( "<hr/>" );
00764
00765 inviteTimeout = -1;
00766 currentInvite = null;
00767 }
00768 }
00769
00770
00771
00772
00773 final int maxRetryCount = 3;
00774 final int reconnectDelay = 2;
00775
00776 if ( pbxChannel != null && pbxChannel.isAlive () ) {
00777 reconnectTimeout = -1;
00778 return;
00779 }
00780
00781 if ( reconnectRetryCount >= maxRetryCount ) {
00782
00783 if ( reconnectRetryCount == maxRetryCount ) {
00784
00785 ++reconnectRetryCount;
00786
00787 logMessage( "<hr/><div class='logDiv'>"
00788 + "<span class='logError'>Press ENTER to quit or type<br/><br/>"
00789 + " :open [ <hostname> [ <port> ] ]<br/><br/>"
00790 + "to open new connection...</span><br/><br/></div>"
00791 );
00792 setPbxStatus( "Dead" );
00793
00794
00795 serverName = "atlas.dsv.su.se";
00796 serverPort = 9494;
00797 }
00798
00799 return;
00800 }
00801
00802 if ( reconnectTimeout < 0 ) {
00803 setPbxStatus( "Disconnected" );
00804 logMessage( "<hr/><div class='logDiv'>Reconnecting in "
00805 + reconnectDelay + " seconds...</div>"
00806 );
00807 reconnectTimeout = reconnectDelay;
00808 return;
00809 }
00810
00811 if ( --reconnectTimeout > 0 ) {
00812 return;
00813 }
00814
00815 logMessage( "<div class='logDiv'>Retry #"
00816 + ( ++reconnectRetryCount )
00817 + " of max "
00818 + maxRetryCount
00819 + ":<br/></div>"
00820 );
00821
00822 reconnectTimeout = -1;
00823
00824 pbxChannel = new PBXClient( serverName, serverPort , this );
00825 pbxChannel.start ();
00826 }
00827
00828
00829
00830
00831
00832
00833 public void logMessage( final String str )
00834 {
00835 java.awt.EventQueue.invokeLater(
00836 new Runnable() {
00837 public void run() {
00838 synchronized( logArea )
00839 {
00840
00841
00842 String html = logArea.getText ();
00843 html = html.replace( "</body>", str + "\n</body>" );
00844 logArea.setText( html );
00845 }
00846 }
00847 }
00848 );
00849 }
00850
00851
00852
00853
00854
00855
00856 @Override
00857 public void attention( String message )
00858 {
00859 if ( message.startsWith( "Error" ) ) {
00860 report( "logError", message );
00861 } else {
00862 report( "logInfo", message );
00863 }
00864 }
00865
00866
00867
00868
00869
00870
00871 @Override
00872 public String getUserId ()
00873 {
00874 String newId = null;
00875
00876 synchronized( userId )
00877 {
00878 String oldId = userId.getText ().trim();
00879
00880
00881 if ( oldId.isEmpty () ) {
00882 oldId = System.getProperty( "user.name" );
00883 }
00884
00885
00886 if ( oldId.isEmpty () ) {
00887 oldId = "[Anonymous]";
00888 }
00889
00890
00891 newId = oldId.replaceAll( "\\s{1,}", "-" );
00892 }
00893
00894 return newId;
00895 }
00896
00897
00898
00899
00900 @Override
00901 public void setPbxStatus( String str )
00902 {
00903 final String strCopy = new String( str );
00904
00905 java.awt.EventQueue.invokeLater(
00906 new Runnable() {
00907 public void run() {
00908 synchronized( pbxChannelStatus )
00909 {
00910 pbxChannelStatus = appTitle + "; " + strCopy;
00911 setTitle( pbxChannelStatus );
00912 }
00913 }
00914 }
00915 );
00916 }
00917
00918
00919
00920
00921 @Override
00922 public void report( String cssClass, String str )
00923 {
00924 logMessage(
00925 "<div class='logDiv'>" + Log.nowMillis ()
00926 + " <span class='" + cssClass + "'>"
00927 + Log.EscapeHTML( str )
00928 + "</span></div>"
00929 );
00930 }
00931
00932
00933
00934
00935 @Override
00936 public void reportIncomingTextMessage( String cssClass,
00937 String userId, String message )
00938 {
00939 logMessage(
00940 "<div class='logDiv'>" + Log.nowMillis ()
00941 + " "
00942 + Log.EscapeHTML( userId ).trim ()
00943 + ": <span class='" + cssClass + "'>"
00944 + Log.EscapeHTML( message ).trim ()
00945 + "</span></div>"
00946 );
00947 }
00948
00949
00950
00951
00952 @Override
00953 public void onInvite( final PBXClient.ControlMessage m )
00954 {
00955 java.awt.EventQueue.invokeLater(
00956 new Runnable() {
00957 public void run() {
00958 deferredOnInvite( m );
00959 }
00960 }
00961 );
00962 }
00963
00964
00965
00966
00967 @Override
00968 public void onRing( final PBXClient.ControlMessage m )
00969 {
00970 java.awt.EventQueue.invokeLater(
00971 new Runnable() {
00972 public void run() {
00973 deferredOnRing( m );
00974 }
00975 }
00976 );
00977 }
00978
00979
00980
00981
00982 @Override
00983 public void onAccept( final PBXClient.ControlMessage m )
00984 {
00985 java.awt.EventQueue.invokeLater(
00986 new Runnable() {
00987 public void run() {
00988 deferredOnAccept( m );
00989 }
00990 }
00991 );
00992 }
00993
00994
00995
00996
00997 @Override
00998 public void onBye( final PBXClient.ControlMessage m )
00999 {
01000 java.awt.EventQueue.invokeLater(
01001 new Runnable() {
01002 public void run() {
01003 deferredOnBye( m );
01004 }
01005 }
01006 );
01007 }
01008
01009
01010
01011
01012 @Override
01013 public void onInstantMessage( final PBXClient.ControlMessage m )
01014 {
01015 java.awt.EventQueue.invokeLater(
01016 new Runnable() {
01017 public void run() {
01018 deferredOnInstantMessage( m );
01019 }
01020 }
01021 );
01022 }
01023
01024
01025
01026
01027
01028
01029 private String[] tokenizeInputMessage ()
01030 {
01031 String message = inputMsg.getText();
01032
01033 if ( defaultInputMsg.equalsIgnoreCase( message ) ) {
01034 return new String[0];
01035 }
01036
01037
01038
01039
01040 String[] words = message.trim().split( "\\s{1,}" );
01041
01042 if ( words.length >= 1 && words[0].isEmpty () ) {
01043 return new String[0];
01044 }
01045
01046 return words;
01047 }
01048
01049
01050
01051
01052
01053
01054 private void parseInputMessage ()
01055 {
01056
01057
01058
01059 String[] words = tokenizeInputMessage ();
01060
01061 String cmd = words.length >= 1 ? words[0].toLowerCase () : "";
01062
01063
01064
01065 if ( cmd.startsWith( ":" ) )
01066 {
01067 int argCount = words.length - 1;
01068 int argOffset = 1;
01069
01070
01071
01072
01073 if ( cmd.equals( ":" ) && words.length >= 2 ) {
01074 cmd = cmd + words[1];
01075 argCount = words.length - 2;
01076 argOffset = 2;
01077 }
01078
01079
01080 String[] args = new String[ argCount ];
01081 System.arraycopy( words, argOffset, args, 0, argCount );
01082
01083
01084 if ( executeCommand( cmd, args ) ) {
01085 inputMsg.setText( "" );
01086 }
01087 return;
01088 }
01089
01090 if ( pbxChannel == null || ! pbxChannel.isAlive () )
01091 {
01092 formWindowClosing( null );
01093 System.exit( 0 );
01094 }
01095
01096
01097
01098 String inputInstantMessage = inputMsg.getText ();
01099
01100 if ( defaultInputMsg.equalsIgnoreCase( inputInstantMessage ) )
01101 {
01102
01103 return;
01104 }
01105 else if ( getUserId().isEmpty () )
01106 {
01107 pbxChannel.send( inputInstantMessage );
01108 inputMsg.setText( "" );
01109 return;
01110 }
01111
01112 sendInstantMessage( inputInstantMessage, false );
01113
01114 inputMsg.setText( "" );
01115 }
01116
01117
01118
01119
01120
01121 private void sendInstantMessage( String message, boolean forceUnencrypted )
01122 {
01123
01124
01125 RemotePeer remotePeer = udpChannel.getRemotePeer ();
01126 SymmetricCipher cipher = udpChannel.getUsedSymmetricCipher ();
01127 if ( forceUnencrypted || remotePeer == null || cipher == null )
01128 {
01129
01130
01131 pbxChannel.send( message, getUserId () );
01132 inputMsg.setText( "" );
01133 return;
01134 }
01135
01136
01137
01138
01139
01140
01141 String secret = cipher.encrypt( message );
01142 if ( secret != null )
01143 {
01144 pbxChannel.sendInstantMessage( remotePeer.getRemoteUserId(), secret );
01145
01146 reportIncomingTextMessage( "secureMessage",
01147 getUserId() + " [encrypted]", message );
01148 }
01149 }
01150
01151
01152
01153
01154
01155
01156
01157
01158
01159
01160
01161
01162
01163
01164
01165
01166
01167
01168
01169
01170
01171
01172
01173
01174
01175
01176
01177
01178
01179
01180
01181
01182
01183
01184
01185
01186
01187
01188 private boolean executeCommand( String cmd, String[] args )
01189 {
01190 cmd = cmd.trim().toLowerCase();
01191
01192 boolean executed = false;
01193
01194 if ( args == null ) {
01195 args = new String[0];
01196 }
01197
01198
01199
01200
01201
01202
01203
01204
01205
01206 if ( cmd.equals( ":list" ) || cmd.matches( "^:li(st?)?$" ) )
01207 {
01208 pbxChannel.sendListPeers( args.length >= 1 ? args[0] : null );
01209
01210 executed = true;
01211 }
01212
01213 else if ( cmd.equals( ":who" ) )
01214 {
01215 pbxChannel.send( "wwhhoo" );
01216
01217 executed = true;
01218 }
01219
01220 else if ( cmd.equals( ":bye" )
01221 || cmd.matches( "^:by(e)?$" )
01222 || cmd.matches( "^:ha(n(g(up?)?)?)?$" ) )
01223 {
01224 RemotePeer remotePeer = udpChannel.getRemotePeer ();
01225
01226 if ( remotePeer != null )
01227 {
01228 report( "logInfo", "***** Call Ended *****" );
01229 logMessage( "<hr/>" );
01230
01231
01232
01233 pbxChannel.sendBye( remotePeer.getRemoteUserId (),
01234 pbxChannel.getLocalAddress (), udpChannel.getLocalPort () );
01235 }
01236 else if ( currentInvite != null )
01237 {
01238 report( "logInfo", "Invite cancelled." );
01239 logMessage( "<hr/>" );
01240
01241
01242
01243 pbxChannel.sendBye( currentInvite,
01244 pbxChannel.getLocalAddress (), udpChannel.getLocalPort () );
01245 }
01246 else if ( this.lastMessageFromPBX != null )
01247 {
01248 PBXClient.ControlMessage m = this.lastMessageFromPBX;
01249
01250 String verboseRemote = m.getVerboseRemote ();
01251
01252 report( "logInfo", "Rejecting invite from " + verboseRemote );
01253 logMessage( "<hr/>" );
01254
01255
01256
01257 pbxChannel.sendBye( m.peerUserId,
01258 pbxChannel.getLocalAddress (), udpChannel.getLocalPort () );
01259
01260 this.lastMessageFromPBX = null;
01261 }
01262
01263 udpChannel.removePeer ();
01264 audioInterface.stopRinging ();
01265 userId.setEnabled( true );
01266
01267 lastMessageFromPBX = null;
01268 remotePublicKey = null;
01269
01270 securityState.setState( JSecState.State.UNSECURED );
01271 setTitle( pbxChannelStatus );
01272
01273 inviteTimeout = -1;
01274 currentInvite = null;
01275
01276 executed = true;
01277 }
01278
01279 else if ( cmd.equals( ":invite+" )
01280 || cmd.matches( "^:inv(i(te?)?)?\\+$" )
01281 || cmd.matches( "^:ca(ll?)?\\+$" ) )
01282 {
01283
01284
01285 if ( udpChannel.hasRemotePeer () || currentInvite != null )
01286 {
01287 report( "logError", "Call in progress. Hang up first!" );
01288
01289 executed = true;
01290 }
01291 else if ( args.length >= 1 && pbxChannel.isAlive () )
01292 {
01293 logMessage( "<hr/>" );
01294 report( "logInfo", "Inviting '" + args[0]
01295 + "' to encrypted voice call..." );
01296
01297 currentInvite = args[0];
01298 inviteTimeout = 3;
01299
01300 userId.setEnabled( false );
01301
01302 pbxChannel.sendInvite( currentInvite, pbxChannel.getLocalAddress (),
01303 udpChannel.getLocalPort (), CipherEngine.getSignedPublicKey () );
01304
01305 executed = true;
01306 }
01307 }
01308
01309 else if ( cmd.equals( ":invite" )
01310 || cmd.matches( "^:inv(i(te?)?)?$" )
01311 || cmd.matches( "^:ca(ll?)?$" ) )
01312 {
01313
01314
01315 if ( udpChannel.hasRemotePeer () || currentInvite != null )
01316 {
01317 report( "logError", "Call in progress. Hang up first!" );
01318
01319 executed = true;
01320 }
01321 else if ( args.length >= 1 && pbxChannel.isAlive () )
01322 {
01323 logMessage( "<hr/>" );
01324 report( "logInfo", "Inviting '" + args[0]
01325 + "' to un-encrypted voice call..." );
01326
01327 currentInvite = args[0];
01328 inviteTimeout = 3;
01329
01330 userId.setEnabled( false );
01331
01332 pbxChannel.sendInvite( args[0], pbxChannel.getLocalAddress (),
01333 udpChannel.getLocalPort (), null );
01334
01335 executed = true;
01336 }
01337 }
01338
01339 else if ( cmd.equals( ":accept" )
01340 || cmd.matches( "^:acc(e(pt?)?)?$" )
01341 || cmd.matches( "^:ans(w(er?)?)?$" ) )
01342 {
01343 acceptIncomingCall( true );
01344
01345 executed = true;
01346 }
01347
01348 else if ( cmd.equals( ":broadcast" )
01349 || cmd.matches( "^:br(o(a(d(c(a(st?)?)?)?)?)?)?$" ) )
01350 {
01351 if ( args.length >= 1 )
01352 {
01353 StringBuffer msg = new StringBuffer ();
01354 for ( String s : args ) {
01355 if ( msg.length () > 0 ) {
01356 msg.append( " " );
01357 }
01358 msg.append( s );
01359 }
01360
01361 sendInstantMessage( msg.toString () , true );
01362
01363 executed = true;
01364 }
01365 }
01366
01367 else if ( cmd.equals( ":mykey" )
01368 || cmd.matches( "^:my(k(ey?)?)?$" ) )
01369 {
01370 sendInstantMessage( "\f========= BEGIN PUBLIC KEY =========\f\f"
01371 + CipherEngine.getNamedPublicKey ()
01372 + "\f\f========= END PUBLIC KEY ==========="
01373 , false );
01374 }
01375
01376 else if ( cmd.equals( ":close" )
01377 || cmd.matches( "^:clo(se?)?$" ) )
01378 {
01379 logMessage( "<hr/>" );
01380 reconnectRetryCount = Integer.MAX_VALUE;
01381 pbxChannel.close ();
01382
01383 executed = true;
01384 }
01385
01386 else if ( cmd.equals( ":open" )
01387 || cmd.matches( "^:op(en?)?$" ) )
01388 {
01389
01390
01391 if ( args.length >= 1 )
01392 {
01393
01394
01395 serverName = args[0];
01396 serverPort = 2000;
01397
01398 if( args.length >= 2 ) try {
01399 serverPort = Integer.parseInt( args[1] );
01400 } catch ( NumberFormatException e ) {
01401 report( "logError", "The port must be integer." );
01402 return false;
01403 }
01404 }
01405
01406 logMessage( "<hr/>" );
01407
01408 reconnectRetryCount = Integer.MAX_VALUE;
01409 pbxChannel.close ();
01410
01411 pbxChannel = new PBXClient( serverName, serverPort , this );
01412 pbxChannel.start ();
01413 reconnectRetryCount = 0;
01414
01415 executed = true;
01416 }
01417
01418 else if ( cmd.equals( ":exit" )
01419 || cmd.matches( "^:ex(it?)?$" ) || cmd.matches( "^:qu(it?)?$" ) )
01420 {
01421 stopKryptofonServices ();
01422 pbxChannel.close ();
01423 System.exit( 0 );
01424 }
01425
01426 else if ( cmd.equals( ":reauth" ) )
01427 {
01428 CipherEngine.reloadAuthorizedPublicKeys ();
01429
01430 executed = true;
01431 }
01432
01433 else if ( cmd.equals( ":newsecret" ) )
01434 {
01435 String algorithm = "Blowfish";
01436 int keySize = 32;
01437
01438 if ( args.length >= 1 ) {
01439 algorithm = args[0];
01440 }
01441
01442 if ( args.length >= 2 ) {
01443 try {
01444 keySize = Integer.parseInt( args[1] );
01445 } catch ( NumberFormatException e ) {
01446 report( "logError", "The key size must be integer." );
01447 return false;
01448 }
01449 }
01450
01451 if ( ! CipherEngine.generateNewSecret( algorithm, keySize, true ) )
01452 {
01453
01454
01455 CipherEngine.generateNewSecret( "Blowfish", 32, false );
01456 return false;
01457 }
01458
01459 executed = true;
01460 }
01461
01462 else if ( cmd.equals( ":cls" )
01463 || cmd.matches( "^:cl(ear)?s(c(r(e(en?)?)?)?)?$" ) )
01464 {
01465 clearLogArea ();
01466 displayUsage ();
01467
01468 executed = true;
01469 }
01470
01471 else if ( cmd.equals( ":help" )
01472 || cmd.matches( "^:he(lp?)?$" ) )
01473 {
01474 displayHelp ();
01475
01476 executed = true;
01477 }
01478
01479 else if ( cmd.equals( ":dump" )
01480 || cmd.matches( "^:du(mp?)?$" ) )
01481 {
01482 dumpLogArea( args.length >= 1 ? args[0] : null );
01483
01484 executed = true;
01485 }
01486
01487
01488 return executed;
01489 }
01490
01491
01492
01493
01494
01495
01496
01497
01498
01499
01500
01501 public void deferredOnInvite( final PBXClient.ControlMessage m )
01502 {
01503 if ( m.peerPort < 1 || m.peerPort > 65535 ) {
01504 return;
01505 }
01506
01507 String verboseRemote = m.getVerboseRemote ();
01508
01509
01510
01511 if ( udpChannel.hasRemotePeer () )
01512 {
01513
01514
01515 pbxChannel.sendBye( m.peerUserId, "0.0.0.0", 0 );
01516 return;
01517 }
01518
01519 this.lastMessageFromPBX = m;
01520
01521 logMessage( "<hr/>" );
01522 report( "logInfo", "User " + verboseRemote + " is inviting us..." );
01523
01524 if ( m.secret == null ) {
01525 setTitle( appTitle + "; Incoming PLAIN call from " + verboseRemote );
01526 } else {
01527 setTitle( appTitle + "; Incoming ENCRYPTED call from " + verboseRemote );
01528 }
01529
01530 this.audioInterface.startRinging ();
01531 userId.setEnabled( false );
01532
01533 if ( autoAnswer.isSelected () )
01534 {
01535 report( "logInfo", "Auto-answering the call..." );
01536 acceptIncomingCall( true );
01537 }
01538 else
01539 {
01540 tryToVerifyInvitingCall( false );
01541 report( "logInfo", "Respond with :accept to answer the call!" );
01542
01543
01544
01545 pbxChannel.sendRing( m.peerUserId, pbxChannel.getLocalAddress (),
01546 udpChannel.getLocalPort (), CipherEngine.getSignedPublicKey () );
01547 }
01548 }
01549
01550
01551
01552
01553
01554 public void deferredOnRing( final PBXClient.ControlMessage m )
01555 {
01556 if ( m.peerPort < 1 || m.peerPort > 65535 ) {
01557 return;
01558 }
01559
01560 String verboseRemote = m.getVerboseRemote ();
01561
01562
01563
01564 if ( udpChannel.hasRemotePeer () ) {
01565 return;
01566 }
01567
01568
01569
01570 if ( currentInvite == null || ! currentInvite.equalsIgnoreCase( m.peerUserId ) ) {
01571 return;
01572 }
01573
01574
01575
01576
01577
01578 remotePublicKey = null;
01579
01580 if ( m.secret != null )
01581 {
01582 remotePublicKey = new PublicEncryptor( m.secret, m.peerUserId );
01583 if ( ! remotePublicKey.isActive () ) {
01584 remotePublicKey = null;
01585 }
01586 }
01587
01588
01589
01590 report( "logInfo", "User " + verboseRemote + " is alerted..." );
01591
01592 if ( remotePublicKey != null && remotePublicKey.isActive () )
01593 {
01594 if ( ! remotePublicKey.isVerified () )
01595 {
01596 report( "logError", "Reply from " + verboseRemote
01597 + " could not be authenticated." );
01598 }
01599 else
01600 {
01601 report( "logOk", "Reply from " + verboseRemote
01602 + " authenticated with public key '"
01603 + remotePublicKey.getVerificatorName () + "'" );
01604 }
01605 }
01606
01607 inviteTimeout = -1;
01608
01609 this.audioInterface.startRinging ();
01610 userId.setEnabled( false );
01611 }
01612
01613
01614
01615
01616
01617
01618
01619
01620
01621
01622
01623
01624 public void deferredOnAccept( final PBXClient.ControlMessage m )
01625 {
01626 if ( m.peerPort < 1 || m.peerPort > 65535 ) {
01627 return;
01628 }
01629
01630 String verboseRemote = m.getVerboseRemote ();
01631
01632
01633
01634 if ( udpChannel.hasRemotePeer () ) {
01635 return;
01636 }
01637
01638 inviteTimeout = -1;
01639 currentInvite = null;
01640
01641
01642
01643 InetAddress peerAddr = null;
01644 try {
01645 peerAddr = InetAddress.getByName( m.peerAddr );
01646 } catch( UnknownHostException e ) {
01647 Log.exception( Log.ERROR, e );
01648 this.lastMessageFromPBX = null;
01649 report( "logError", "Unknown remote host '" + m.peerAddr
01650 + "'; clearing the call..." );
01651 return;
01652 }
01653
01654 report( "logInfo", "User " + verboseRemote + " has accepted our invite" );
01655
01656
01657
01658 SymmetricCipher cipher = null;
01659 udpChannel.useSymmetricCipher( null );
01660
01661 if ( m.secret != null )
01662 {
01663 cipher = CipherEngine.deserializeEncryptedSecretKey( m.secret );
01664
01665 if ( cipher.isActive () ) {
01666 udpChannel.useSymmetricCipher( cipher );
01667 } else {
01668 cipher = null;
01669 }
01670 }
01671
01672
01673
01674 RemotePeer remotePeer = new RemotePeer( this.udpChannel, m.peerUserId,
01675 peerAddr, m.peerPort );
01676
01677 AudioInterface codec = this.audioInterface.getByFormat( VoicePDU.ALAW );
01678
01679 CallContext call = new CallContext( remotePeer, codec );
01680 call.setCallEstablished( true );
01681 monitorIfPeerIsSendingVoice = true;
01682
01683 if ( cipher != null && cipher.isActive () )
01684 {
01685 if ( ! cipher.isVerified () )
01686 {
01687 securityState.setState( JSecState.State.UNVERIFIED );
01688 report( "logError", "Secret key from " + verboseRemote
01689 + " could not be authenticated." );
01690 }
01691 else
01692 {
01693 securityState.setState( JSecState.State.VERIFIED );
01694 report( "logOk", "Secret key from " + verboseRemote
01695 + " authenticated with public key '"
01696 + cipher.getVerificatorName () + "'" );
01697 }
01698
01699 report( "logOk", "***** Encrypted call established *****" );
01700 setTitle( appTitle + "; Established ENCRYPTED call with " + verboseRemote );
01701 }
01702 else
01703 {
01704 securityState.setState( JSecState.State.UNSECURED );
01705 report( "logError", "***** Un-encrypted call established *****" );
01706 setTitle( appTitle + "; Established PLAIN call with " + verboseRemote );
01707 }
01708 }
01709
01710
01711
01712
01713
01714 public void deferredOnBye( final PBXClient.ControlMessage m )
01715 {
01716 String verboseRemote = m.getVerboseRemote ();
01717
01718
01719
01720 if ( ! udpChannel.hasRemotePeer () && currentInvite != null )
01721 {
01722 report( "logInfo", "User " + verboseRemote + " rejected our invite" );
01723 }
01724 else
01725 {
01726 report( "logInfo", "User " + verboseRemote + " is clearing the call" );
01727 udpChannel.removePeer ();
01728 }
01729
01730 audioInterface.stopRinging ();
01731 userId.setEnabled( true );
01732
01733 lastMessageFromPBX = null;
01734 remotePublicKey = null;
01735
01736 report( "logInfo", "***** Call Ended *****" );
01737 logMessage( "<hr/>" );
01738
01739 securityState.setState( JSecState.State.UNSECURED );
01740 setTitle( pbxChannelStatus );
01741
01742 inviteTimeout = -1;
01743 currentInvite = null;
01744 }
01745
01746
01747
01748
01749
01750 public void deferredOnInstantMessage( final PBXClient.ControlMessage m )
01751 {
01752
01753
01754 SymmetricCipher cipher = udpChannel.getUsedSymmetricCipher ();
01755 if ( cipher == null ) {
01756 return;
01757 }
01758
01759
01760
01761 String clearText = cipher.decrypt( m.secret );
01762 if ( clearText != null )
01763 {
01764
01765
01766 reportIncomingTextMessage( "secureMessage",
01767 m.peerUserId + " [encrypted]", clearText );
01768 }
01769 }
01770
01771
01772
01773
01774
01775 private PublicEncryptor tryToVerifyInvitingCall( boolean silent )
01776 {
01777 if ( this.lastMessageFromPBX == null ) {
01778 return null;
01779 }
01780
01781 PBXClient.ControlMessage m = this.lastMessageFromPBX;
01782 String remoteId = m.getVerboseRemote ();
01783
01784
01785
01786 if ( udpChannel.hasRemotePeer () ) {
01787 return null;
01788 }
01789
01790
01791
01792
01793
01794 PublicEncryptor pubKey = null;
01795
01796 if ( m.secret != null )
01797 {
01798 pubKey = new PublicEncryptor( m.secret, m.peerUserId );
01799 if ( ! pubKey.isActive () ) {
01800 pubKey = null;
01801 }
01802 }
01803
01804 if ( silent ) {
01805 return pubKey;
01806 }
01807
01808
01809
01810 if ( pubKey != null && pubKey.isActive () )
01811 {
01812 if ( ! pubKey.isVerified () )
01813 {
01814 securityState.setState( JSecState.State.UNVERIFIED );
01815 report( "logError", "Invite from " + remoteId
01816 + " could not be authenticated." );
01817 }
01818 else
01819 {
01820 securityState.setState( JSecState.State.VERIFIED );
01821 report( "logOk", "Invite from " + remoteId
01822 + " authenticated with public key '"
01823 + pubKey.getVerificatorName () + "'" );
01824 }
01825 }
01826 else
01827 {
01828 securityState.setState( JSecState.State.UNSECURED );
01829 report( "logError", "The call will be without encryption." );
01830 }
01831
01832 return pubKey;
01833 }
01834
01835
01836
01837
01838
01839
01840
01841
01842
01843
01844
01845
01846
01847
01848
01849
01850
01851
01852
01853
01854
01855
01856
01857 private void acceptIncomingCall( boolean securedIfPossible )
01858 {
01859 if ( this.lastMessageFromPBX == null ) {
01860 return;
01861 }
01862
01863 PBXClient.ControlMessage m = this.lastMessageFromPBX;
01864 String verboseRemote = m.getVerboseRemote ();
01865
01866
01867
01868 if ( udpChannel.hasRemotePeer () ) {
01869 return;
01870 }
01871
01872
01873
01874 InetAddress peerAddr = null;
01875 try {
01876 peerAddr = InetAddress.getByName( m.peerAddr );
01877 } catch( UnknownHostException e ) {
01878 Log.exception( Log.ERROR, e );
01879 this.lastMessageFromPBX = null;
01880 report( "logError", "Unknown remote host '" + m.peerAddr
01881 + "'; clearing the call..." );
01882 return;
01883 }
01884
01885
01886
01887
01888
01889 String mySecret = null;
01890 remotePublicKey = null;
01891 udpChannel.useSymmetricCipher( null );
01892
01893 if ( securedIfPossible )
01894 {
01895 remotePublicKey = tryToVerifyInvitingCall( true );
01896
01897 if ( remotePublicKey != null && remotePublicKey.isActive () )
01898 {
01899 mySecret = remotePublicKey.encryptAndSerialize( CipherEngine.getSignedSecretKey() );
01900
01901 udpChannel.useSymmetricCipher( CipherEngine.getCipher () );
01902 }
01903 }
01904
01905
01906
01907 pbxChannel.sendAccept( m.peerUserId, pbxChannel.getLocalAddress (),
01908 udpChannel.getLocalPort (), mySecret );
01909
01910
01911
01912
01913 RemotePeer remotePeer = new RemotePeer( this.udpChannel,
01914 m.peerUserId, peerAddr, m.peerPort );
01915
01916 AudioInterface codec = this.audioInterface.getByFormat( VoicePDU.ALAW );
01917
01918 CallContext call = new CallContext( remotePeer, codec );
01919 call.setCallEstablished( true );
01920 monitorIfPeerIsSendingVoice = true;
01921
01922
01923
01924 if ( remotePublicKey != null && remotePublicKey.isActive () )
01925 {
01926 if ( ! remotePublicKey.isVerified () ) {
01927 securityState.setState( JSecState.State.UNVERIFIED );
01928 } else {
01929 securityState.setState( JSecState.State.VERIFIED );
01930 }
01931
01932 report( "logOk", "***** Encrypted call established *****" );
01933 setTitle( appTitle + "; Established ENCRYPTED call with " + verboseRemote );
01934 }
01935 else
01936 {
01937 securityState.setState( JSecState.State.UNSECURED );
01938 report( "logError", "***** Un-encrypted call established *****" );
01939 setTitle( appTitle + "; Established PLAIN call with " + verboseRemote );
01940 }
01941
01942 this.lastMessageFromPBX = null;
01943 }
01944
01945
01946
01947
01948
01949
01950 public void dumpLogArea( String fileName )
01951 {
01952 if ( fileName == null || fileName.isEmpty () ) {
01953 Calendar cal = Calendar.getInstance ();
01954 SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd-HHmmssSSS" );
01955 fileName = defaultLogAreaDumpFilename + sdf.format( cal.getTime () ) + ".html";
01956 }
01957
01958 try
01959 {
01960 BufferedWriter out = new BufferedWriter( new FileWriter( fileName ) );
01961
01962 synchronized( logArea )
01963 {
01964 out.write( logArea.getText () );
01965 }
01966
01967 out.close ();
01968
01969 report( "logInfo", "Dumped log area into '" + fileName + "'" );
01970 }
01971 catch( IOException e )
01972 {
01973 report( "logError", "Failed to dump log area: " + e.getMessage () );
01974 }
01975 }
01976
01977
01978
01979
01980
01981
01982 public StringBuffer getContentsFromResourceOrFile( String path )
01983 {
01984 StringBuffer sb = new StringBuffer ();
01985
01986 try
01987 {
01988
01989
01990 URL url = getClass().getResource( path );
01991
01992
01993
01994 if ( url == null ) {
01995 url = new URL( "file:" + path );
01996 }
01997
01998
01999
02000 if ( url != null )
02001 {
02002 InputStream in = url.openStream ();
02003 BufferedReader dis = new BufferedReader( new InputStreamReader( in ) );
02004
02005 String line;
02006 while ( ( line = dis.readLine () ) != null )
02007 {
02008 sb.append( line + "\n" );
02009 }
02010
02011 in.close ();
02012 }
02013 }
02014 catch( MalformedURLException e )
02015 {
02016 Log.exception( Log.TRACE, e );
02017 sb = null;
02018 }
02019 catch( IOException e )
02020 {
02021 Log.exception( Log.TRACE, e );
02022 sb = null;
02023 }
02024
02025 return sb;
02026 }
02027
02028
02029
02030
02031 public void clearLogArea ()
02032 {
02033 logArea.setContentType( "text/html" );
02034 StringBuffer sb = getContentsFromResourceOrFile( "resources/empty.html" );
02035 if ( sb != null ) {
02036 logArea.setText( sb.toString () );
02037 } else {
02038 logArea.setText( "<html><head></head><body></body></html>" );
02039 }
02040 }
02041
02042
02043
02044
02045 public void displayUsage ()
02046 {
02047 StringBuffer sb = getContentsFromResourceOrFile( "resources/usage.html" );
02048
02049 if ( sb != null && sb.length () > 0 ) {
02050 logMessage( sb.toString () );
02051 }
02052 }
02053
02054
02055
02056
02057 public void displayHelp ()
02058 {
02059 StringBuffer sb = getContentsFromResourceOrFile( "resources/help.html" );
02060
02061 if ( sb != null && sb.length () > 0 ) {
02062 logMessage( sb.toString () );
02063 }
02064 }
02065
02066
02067
02068
02069
02070
02071
02072 public static void main( final String args[] )
02073 {
02074
02075
02076 java.awt.EventQueue.invokeLater(
02077 new Runnable() {
02078 public void run() {
02079 new CryptoPhoneApp( args ).setVisible( true );
02080 }
02081 }
02082 );
02083 }
02084 }
02085
02086
02087
02088
02089
02090
02091
02092
02093
02094
02095
02096
02097
02098
02099
02100
02101
02102
02103
02104
02105
02106
02107
02108
02109
02110
02111
02112
02113
02114
02115
02116
02117
02118
02119
02120
02121
02122
02123
02124
02125