import java.awt.*; import java.net.*; import java.awt.event.*; import java.util.Random; import java.util.Vector; import java.util.Enumeration; import java.util.List; import java.util.Arrays; import javax.swing.*; import javax.swing.border.*; import javax.swing.text.*; import javax.crypto.*; import javax.crypto.spec.*; import java.security.*; import javax.swing.text.html.*; /** * ParaDigEm chat client. * * A broadcast-based chat client for meetings that are being held in * Places Of No Infrastructure (PONI). If you are near * infrastructure, you should use Jabber instead. * * Requires swing and the JCE. * * crypto: * * ParaDigEm can send and receive Blowfish in ECB mode. We can't use * chaining because we're on udp, so we can't expect to get the * packets in order, or un-duplicated... or at all. * * There are three keys: null, default, and custom. I'm not sure why * you'd want cleartext (null). The default key is just there to * prevent casual packet snooping. You should use a custom key, but * then you need a key exchange mechanism, so there you go. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Copyright (C) 2002 Christpher R. Wren * * @author Christopher R. Wren * @version 1.0 * */ class ParaDigEm extends JFrame implements ActionListener, WindowListener, Runnable, ComponentListener { /** * The default key */ static String keyString = "fKHbZnN$a"; /** * text stuff */ Document log = null; Document input = null; JTextField textArea = null; SimpleAttributeSet bold = null; SimpleAttributeSet[] color = null; Font basefont = null; /** * socket stuff */ String address = "255.255.255.255"; short port = 6789; InetAddress group = null; DatagramSocket s = null; //MulticastSocket s = null; /** * receive thread and control */ Thread thread = null; boolean done = false; /** * ciphers */ Cipher[] cipherEnc = null; Cipher[] cipherDec = null; Color[] highlight = null; boolean[] view = null; int currentCipher = 1; JRadioButtonMenuItem[] cipherMenu = null; Random shaker = null; String nickname; /** * Make a chat window. * * @param nick the default nickname */ ParaDigEm(String nick) { super("ParaDigEm (" + nick + ")"); nickname = nick; bold = new SimpleAttributeSet(); StyleConstants.setBold(bold, true); highlight = new Color[3]; highlight[0] = Color.red; highlight[1] = new Color(0,128,0); highlight[2] = Color.blue; color = new SimpleAttributeSet[3]; color[0] = new SimpleAttributeSet(); StyleConstants.setForeground(color[0], highlight[0]); StyleConstants.setForeground(color[0], highlight[0]); color[1] = new SimpleAttributeSet(); StyleConstants.setForeground(color[1], highlight[1]); color[2] = new SimpleAttributeSet(); StyleConstants.setForeground(color[2], highlight[2]); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Border blackline = BorderFactory.createLineBorder(Color.black); JTextPane textPane = new JTextPane(); log = (Document) textPane.getDocument(); textPane.setMaximumSize(new Dimension(300,200)); textPane.setBorder(blackline); textPane.setEditable(false); textPane.setFocusable(false); textPane.addComponentListener(this); JScrollPane scroller = new JScrollPane(textPane); getContentPane().add(scroller, BorderLayout.CENTER); textArea = new JTextField(80); textArea.setMaximumSize(new Dimension(300,100)); textArea.setBorder(blackline); textArea.addActionListener(this); basefont = textArea.getFont(); input = textArea.getDocument(); getContentPane().add(textArea, BorderLayout.SOUTH); setFont(basefont.getSize()); //Create the menu bar. JMenuBar menuBar = new JMenuBar(); menuBar.setBackground(Color.white); setJMenuBar(menuBar); //Build the first menu. JMenu menu = new JMenu("Tools"); menu.setBackground(Color.white); menuBar.add(menu); JMenuItem menuItem; menuItem = new JMenuItem("Send"); menuItem.setBackground(Color.white); menuItem.addActionListener(this); menu.add(menuItem); menuItem = new JMenuItem("Clear Log"); menuItem.setBackground(Color.white); menuItem.addActionListener(this); menu.add(menuItem); menuItem = new JMenuItem("Set Nick"); menuItem.setBackground(Color.white); menuItem.addActionListener(this); menu.add(menuItem); menuItem = new JMenuItem("Set Key"); menuItem.setBackground(Color.white); menuItem.addActionListener(this); menu.add(menuItem); menuItem = new JMenuItem("Help"); menuItem.setBackground(Color.white); menuItem.addActionListener(this); menu.add(menuItem); menuItem = new JMenuItem("About"); menuItem.setBackground(Color.white); menuItem.addActionListener(this); menu.add(menuItem); //Build the second menu. menu = new JMenu("View"); menu.setBackground(Color.white); menuBar.add(menu); menuItem = new JRadioButtonMenuItem("View Cleartext", true); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menuItem.setForeground(highlight[0]); menu.add(menuItem); menuItem = new JRadioButtonMenuItem("View Default Key", true); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menuItem.setForeground(highlight[1]); menu.add(menuItem); menuItem = new JRadioButtonMenuItem("View Custom Key", true); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menuItem.setForeground(highlight[2]); menu.add(menuItem); addWindowListener(this); menuItem = new JRadioButtonMenuItem("View Decrypt Failures", true); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menuItem.setForeground(Color.black); menu.add(menuItem); addWindowListener(this); //Build the third menu. ButtonGroup bgroup = new ButtonGroup(); cipherMenu = new JRadioButtonMenuItem[3]; menu = new JMenu("Cipher"); menu.setBackground(Color.white); menuBar.add(menu); menuItem = cipherMenu[0] = new JRadioButtonMenuItem("Cleartext"); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menuItem.setForeground(highlight[0]); menu.add(menuItem); bgroup.add(menuItem); menuItem = cipherMenu[1] = new JRadioButtonMenuItem("Default Key"); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menuItem.setForeground(highlight[1]); menu.add(menuItem); bgroup.add(menuItem); menuItem = cipherMenu[2] = new JRadioButtonMenuItem("Custom Key"); menuItem.addActionListener(this); menuItem.setEnabled(false); menuItem.setBackground(Color.white); menuItem.setForeground(highlight[2]); menu.add(menuItem); bgroup.add(menuItem); addWindowListener(this); //Build the fouth menu. bgroup = new ButtonGroup(); menu = new JMenu("Font"); menu.setBackground(Color.white); menuBar.add(menu); menuItem = new JRadioButtonMenuItem("8 point", basefont.getSize() == 8); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menu.add(menuItem); bgroup.add(menuItem); menuItem = new JRadioButtonMenuItem("10 point", basefont.getSize() == 10); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menu.add(menuItem); bgroup.add(menuItem); menuItem = new JRadioButtonMenuItem("12 point", basefont.getSize() == 12); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menu.add(menuItem); bgroup.add(menuItem); menuItem = new JRadioButtonMenuItem("14 point", basefont.getSize() == 14); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menu.add(menuItem); bgroup.add(menuItem); menuItem = new JRadioButtonMenuItem("16 point", basefont.getSize() == 16); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menu.add(menuItem); bgroup.add(menuItem); menuItem = new JRadioButtonMenuItem("18 point", basefont.getSize() == 18); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menu.add(menuItem); bgroup.add(menuItem); menuItem = new JRadioButtonMenuItem("20 point", basefont.getSize() == 20); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menu.add(menuItem); bgroup.add(menuItem); menuItem = new JRadioButtonMenuItem("24 point", basefont.getSize() == 24); menuItem.addActionListener(this); menuItem.setBackground(Color.white); menu.add(menuItem); bgroup.add(menuItem); addWindowListener(this); setSize(300, 300); validate(); setVisible(true); textArea.requestFocus(); shaker = new Random(); initCipher(); try { //group = InetAddress.getByName("239.135.23.84"); group = InetAddress.getByName(address); //s = new MulticastSocket(port); s = new DatagramSocket(port); s.setBroadcast(true); s.setSoTimeout(1000); // s.joinGroup(group); // s.setTimeToLive(5); // s.setLoopbackMode(true); // if (s.getLoopbackMode()) // System.err.println("loopback enabled."); // else // System.err.println("loopback diabled."); thread = new Thread(this); thread.start(); } catch (Exception e) { System.err.println("Couldn't join multicast group."); } } /** * Mainloop for receive thread */ public void run() { while(!done) { recv(); } } void setFont(int size) { basefont = basefont.deriveFont((float) size); textArea.setFont(basefont); StyleConstants.setFontSize(bold, size); StyleConstants.setFontSize(color[0], size); StyleConstants.setFontSize(color[1], size); StyleConstants.setFontSize(color[2], size); } /** * Setup the cleartext and internal key cipher */ void initCipher() { try { SecretKeySpec key = new SecretKeySpec(keyString.getBytes(), "Blowfish"); cipherDec = new Cipher[3]; cipherEnc = new Cipher[3]; Cipher ciphertmp; cipherDec[0] = new NullCipher(); cipherEnc[0] = new NullCipher(); cipherDec[1] = Cipher.getInstance("Blowfish/ECB/PKCS5Padding"); cipherDec[1].init(Cipher.DECRYPT_MODE, key); cipherEnc[1] = Cipher.getInstance("Blowfish/ECB/PKCS5Padding"); cipherEnc[1].init(Cipher.ENCRYPT_MODE, key); setCurrentCipher(1); cipherDec[2] = null; cipherEnc[2] = null; view = new boolean[4]; view[0] = view[1] = view[2] = view[3] = true; } catch (Exception e) { System.err.println("Couldn't initialize cipher:" + e); } } /** * build the custom ciphers */ void setCustomCipher(String keyString) { try { SecretKeySpec key = new SecretKeySpec(keyString.getBytes(), "Blowfish"); // 2 cipherDec[2] = Cipher.getInstance("Blowfish/ECB/PKCS5Padding"); cipherDec[2].init(Cipher.DECRYPT_MODE, key); cipherEnc[2] = Cipher.getInstance("Blowfish/ECB/PKCS5Padding"); cipherEnc[2].init(Cipher.ENCRYPT_MODE, key); setCurrentCipher(2); cipherMenu[0].setSelected(false); cipherMenu[1].setSelected(false); cipherMenu[2].setSelected(true); cipherMenu[2].setEnabled(true); } catch (Exception e) { System.err.println("Couldn't initialize cipher:" + e); } } /** * switch ciphers. make sure menu and textfield color are consistent */ void setCurrentCipher(int i) { cipherMenu[0].setSelected(false); cipherMenu[1].setSelected(false); cipherMenu[2].setSelected(false); currentCipher = i; textArea.setForeground(highlight[i]); cipherMenu[i].setSelected(true); } /** * is this a typable string? null cipher always succeeds. */ boolean isPrintable(byte[] probe) { boolean ret = true; for (int i = 4; i 0) { nickname = input.getText(0, input.getLength()); input.remove(0, input.getLength()); setTitle("ParaDigEm (" + nickname + ")"); } else { input.remove(0, input.getLength()); input.insertString(0, "type new nickname here, and then 'Set Nick'", null); } } catch (Exception c) { System.err.println("Couldn't set nickname from input:" + c); } } /** * pull the new custom key string from the input buffer. */ void setKey() { try { if (input.getLength() > 0) { String keyString = input.getText(0, input.getLength()); if (keyString.length() > 9) keyString = keyString.substring(0,9); input.remove(0, input.getLength()); setCustomCipher(keyString); } else { input.remove(0, input.getLength()); input.insertString(0, "type new custom key here, and then 'Set Key'", null); } } catch (Exception c) { System.err.println("Couldn't set key from input:" + c); } } /** * print the help banner to the log */ void help() { try { log.insertString(log.getLength(), "help:\n", bold); log.insertString(log.getLength(), " /nick to change nickname\n"+ " /key to change custom key\n"+ " /help for this message\n", null); log.insertString(log.getLength(), " cleartext color\n", color[0]); log.insertString(log.getLength(), " default key color\n", color[1]); log.insertString(log.getLength(), " custom key color\n", color[2]); input.remove(0, input.getLength()); } catch (Exception c) { System.err.println("Couldn't set key from input:" + c); } } void about() { try { log.insertString(log.getLength(), "about:\n", bold); log.insertString(log.getLength(), "ParaDigEm version 1.0 " + "Copyright (C) 2002 christopher R. Wren\n" + "ParaDigEm comes with ABSOLUTELY NO WARRANTY.\n"+ "This is free software, and you are "+ "welcome to redistribute it under certain "+ "conditions.\n"+ "See http://www.fsf.org/licenses/gpl.txt "+ "for more detals\n", null); input.remove(0, input.getLength()); } catch (Exception c) { System.err.println("Couldn't set key from input:" + c); } } /** * dispatch actions form the menu and the input field */ public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof JMenuItem) { JMenuItem source = (JMenuItem)(e.getSource()); if(source.getText().equals("Send")) { send(); } if(source.getText().equals("Clear Log")) { try { log.remove(0, log.getLength()); } catch (Exception x) { } } if(source.getText().equals("Help")) { help(); } if(source.getText().equals("About")) { about(); } if(source.getText().equals("Set Nick")) { setNick(); } if(source.getText().equals("Set Key")) { setKey(); } if(source.getText().equals("Cleartext")) { setCurrentCipher(0); } if(source.getText().equals("Default Key")) { setCurrentCipher(1); } if(source.getText().equals("Custom Key")) { setCurrentCipher(2); } if(source.getText().equals("View Cleartext")) { view[0] = source.isSelected(); } if(source.getText().equals("View Default Key")) { view[1] = source.isSelected(); } if(source.getText().equals("View Custom Key")) { view[2] = source.isSelected(); } if(source.getText().equals("View Decrypt Failures")) { view[3] = source.isSelected(); } if(source.getText().equals("8 point")) { setFont(8); } if(source.getText().equals("10 point")) { setFont(10); } if(source.getText().equals("12 point")) { setFont(12); } if(source.getText().equals("14 point")) { setFont(14); } if(source.getText().equals("16 point")) { setFont(16); } if(source.getText().equals("18 point")) { setFont(18); } if(source.getText().equals("20 point")) { setFont(20); } if(source.getText().equals("24 point")) { setFont(24); } } if (e.getSource() instanceof JTextField) { try{ String command = input.getText(0, input.getLength()); if (command.startsWith("/key")) { input.remove(0, 5); setKey(); } else if (command.startsWith("/nick ")) { input.remove(0, 6); setNick(); } else if (command.startsWith("/help")) { help(); } else { send(); } } catch (Exception x) { } } } //implment WindowListener public void windowActivated(WindowEvent e) { } public void windowClosed(WindowEvent e) { } /** * catch the window close event and tell the thread to die before we go */ public void windowClosing(WindowEvent e) { done = true; dispose(); } public void windowDeactivated(WindowEvent e) { } public void windowDeiconified(WindowEvent e) { } public void windowIconified(WindowEvent e) { } public void windowOpened(WindowEvent e) { } //implment ComponentListener /** * make sure we always scroll to the bottom on output */ public void componentResized(ComponentEvent e) { if (e.getComponent() instanceof JComponent) { JComponent c = (JComponent) e.getComponent(); Rectangle view = new Rectangle(0, c.getHeight()-10, 10,10); c.scrollRectToVisible(view); } } public void componentHidden(ComponentEvent e) {} public void componentMoved(ComponentEvent e) {} public void componentShown(ComponentEvent e) {} public static void main(String args[]) { if (args.length >= 1) { ParaDigEm PDE = new ParaDigEm(args[0]); } else { System.err.println("You might want to specify " + "a nickanme o nthe command line."); try { ParaDigEm PDE = new ParaDigEm(InetAddress.getLocalHost().toString()); } catch (Exception e) { ParaDigEm PDE = new ParaDigEm("Nobody"); } } } }