summaryrefslogblamecommitdiffstats
path: root/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/NetrulesConfigurator.java
blob: 5d14f3fc5e2e84268e879f66ec2da5ae957e8011 (plain) (tree)
1
2
3
4
5
6
7
8
                                            


                       

                                     

                                 




                                 
                           

                               
                             
                                  
                             
                                        
                                             
                                              
                                           
                              
                                       

                                




                                                  
                                                                 
                                                    

                                                 
                                                        
                             

   
                                                
   
                                                                                                                     

                                                                           




                                                                                          
 

                                                                               

                                                                                    
           
                                                             










                                                                                                
 
                                       
                        



                                                                                 
                                                   
                                                  

                         

                                                                        
                                                                              


































                                                                                                                                        
                                                                      
                                 

                                                                    

                         

         
           

                                                                                  
           
                                                                 
           




                                         
                                                       



















                                                             

         
           

                                                                                 
           

                                                              
           
                                                            



                                                                            

         
           


                                                                                 



















                                                                                          

                                                                 
                                                                    
                                                   
                                                                     




                                    
                                                                   
           

                                               

                                                                              
           

                                                                     



                                                                   
 
                                        
                                                                                                     



                                                                                                       

                                                                                                     
                                                                      

                                              
                                               

                                                                               
                                                                                 
                                                 
                                                                                     
                                                                          







                                                                                                            
                         


                                                                                           



                                                                                           
                                                               





                                                                                                                                
                         

                                                                  
 


                                                                                     
                                                                           


                                                                                                                          







                                                                                           





                                                                                          
                         


                                                                                                         
                 



                                                           





                                                                                                                          
                 







                                                                                                

                                 











                                                                                

         








                                                                                    

                                                                         
           
                                                             
                                              
                                                       
                 
                                          






                                                                                
                         
                                                             
                 






























































                                                                                                                               
                 
                            

         


   
                                        
   
                                                 
 
                                                                          
                                                                                                             

                                                                                                  



                                                                                                                  
 
                                                 
                                              
 
                                             
 
                                                                 


                                                        
                                                                            
                                                 
                                
 






                                                                                 
                                                                       








                                                                           
 
                                   
         








                                                                                         
 
package org.openslx.dozmod.gui.configurator;

import java.awt.Color;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.TabSet;
import javax.swing.text.TabStop;

import org.apache.log4j.Logger;
import org.openslx.bwlp.thrift.iface.NetDirection;
import org.openslx.bwlp.thrift.iface.NetRule;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.changemonitor.GenericControlWindow;
import org.openslx.dozmod.gui.control.WordWrapLabel;
import org.openslx.dozmod.gui.helper.GridManager;
import org.openslx.dozmod.gui.helper.MessageType;
import org.openslx.dozmod.gui.helper.TextChangeListener;
import org.openslx.util.Util;

/**
 * Widget for netrules configuration of lectures
 */
public class NetrulesConfigurator extends NetrulesConfiguratorLayout implements GenericControlWindow<List<NetRule>> {

	private static final long serialVersionUID = -3497629601818983994L;
	private final static Logger LOGGER = Logger.getLogger(NetrulesConfigurator.class);
	
	private boolean checkChange = false;
	private List<NetRule> currentState;
	private List<ChangeListener> listeners;

	/**
	 * Character defining how the rules are parsed, e.g. for whitespace \\s
	 * Example: "8.8.8.8 80 in" would be split in -hostname "8.8.8.8" -port "80"
	 * -direction "in"
	 */
	private static final String FIELD_DELIMITER = "\\s+";
	
	private static final Color FOREGROUND_TEXT_COLOR;
	
	static {
		Color fgOrigColor = UIManager.getDefaults().getColor("ColorChooser.foreground");
		if (fgOrigColor == null) {
			// use black as fallback
			fgOrigColor = Color.BLACK;
		}
		FOREGROUND_TEXT_COLOR = fgOrigColor;
	}

	public NetrulesConfigurator() {
		super();

		final TextChangeListener docListener = new TextChangeListener() {
			@Override
			public void changed() {
				checkChange = true;
				fireChangeEvent();
			}
		};
		final SimpleAttributeSet as = new SimpleAttributeSet();
		StyleConstants.setForeground(as, FOREGROUND_TEXT_COLOR);
		tpNetworkRules.getDocument().addDocumentListener(docListener);
		tpNetworkRules.addKeyListener(new KeyAdapter() {
			@Override
			public void keyTyped(KeyEvent e) {
				SwingUtilities.invokeLater(new Runnable() {
					@Override
					public void run() {
						int pos = tpNetworkRules.getCaretPosition();
						if (pos < 0) {
							return;
						}
						// Odd: On windows, getText() returns the text with CRLF line breaks,
						// regardless of what you put into the text box.
						// The offsets passed to setCharAttrs() below and the caret position
						// you get from getCaretPosition() however have to adhere to the
						// text version with just LF, exactly how we created the document.
						String text = tpNetworkRules.getText().replace("\r", "");
						if (pos >= text.length()) {
							return;
						}
						int start = text.lastIndexOf('\n', pos == 0 ? 0 : pos - 1);
						int end = text.indexOf('\n', pos);
						if (start == -1) {
							start = 0;
						}
						if (end == -1) {
							end = text.length() - 1;
						}
						if (end <= start) {
							return;
						}
						tpNetworkRules.getStyledDocument().setCharacterAttributes(start, end - start, as, true);
					}
				});
			}
		});
		btnCheckRules.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				getState(false);
			}
		});
	}

	/**
	 * Gets the state of the widget as a list of netrules. Internally it first
	 * transforms the input text in a list of netrules.
	 * 
	 * @return the list of rules as parsed by parseNetRules()
	 */
	@Override
	public List<NetRule> getState() {
		return getState(true);
	}
	
	public List<NetRule> getState(boolean silent) {
		if (checkChange || !silent) {
			currentState = parseNetRules(silent);
		}
		return currentState;
	}

	@Override
	public void addChangeListener(ChangeListener l) {
		if (listeners == null) {
			listeners = new ArrayList<>();
		}
		listeners.add(l);
	}
	
	private void fireChangeEvent() {
		if (listeners == null)
			return;
		for (ChangeListener cl : listeners) {
			cl.stateChanged(null);
		}
	}

	/**
	 * Sets the state of this widget to the given list of netrules. This will
	 * internally transform the list to its string representation
	 * 
	 * @param netrules
	 *            as a list of NetRule to set the state to
	 */
	public void setState(final List<NetRule> netrules) {
		checkChange = true;
		currentState = netrules;
		this.tpNetworkRules.setText(decodeNetRulesToText(netrules));
		fireChangeEvent();
	}

	/**
	 * "Decodes" the given list of NetRule to a single String. This should be
	 * used to set the text in the TextPane for the network rules
	 * 
	 * @param netRulesList
	 *            list of NetRule to decode
	 * @return String representation of the list of rules
	 */
	public static String decodeNetRulesToText(final List<NetRule> netRulesList) {
		if (netRulesList == null || netRulesList.isEmpty())
			return "";

		String decodedRules = "";
		Iterator<NetRule> it = netRulesList.iterator();
		while (it.hasNext()) {
			String currentLine = "";
			NetRule currentRule = it.next();
			// simple test for validity (since this comes from the server it
			// should be correct anyways)
			if (currentRule.host.isEmpty() || currentRule.port > 65535) {
				LOGGER.error("Invalid rule! Ignoring: " + currentRule.host
						+ ":" + currentRule.port);
				continue;
			}
			currentLine += currentRule.host + " \t ";
			currentLine += currentRule.port + " \t ";
			currentLine += currentRule.direction.name();
			decodedRules += currentLine
					+ (it.hasNext() ? "\n" : "");
		}
		return decodedRules;
	}

	/**
	 * Parses the given rawNetRules String to a list of NetRule
	 * 
	 * @param rawNetRules
	 *            the raw text to be parsed
	 * @return list of netrules if successful. If any errors occured while
	 *         parsing, null is returned.
	 */
	public List<NetRule> parseNetRules(boolean silent) {
		String rawNetRules = tpNetworkRules.getText().trim();
		List<NetRule> rulesList = new ArrayList<NetRule>();
		if (rawNetRules.isEmpty()) {
			return rulesList;
		}

		// split it line by line
		boolean invalid = false; // True if the rules are invalid and null should be returned
		DefaultStyledDocument newdoc = null;
		if (!silent) {
			newdoc = new DefaultStyledDocument(); // Used to build new document with colors
		}
		StringBuilder errors = new StringBuilder(); // Error messages to show (if not silent)
		int lineNo = 0; // Show line numbers in error messages
		for (String ruleLine : rawNetRules.split("[\r\n]+")) {
			if (silent && invalid)
				return null;
			Color lineColor = null;
			LOGGER.debug("Parsing rule: " + ruleLine);
			// split the fields and check if we have 3 as expected.
			String[] fields = ruleLine.trim().split(FIELD_DELIMITER);
			if (fields.length != 3) {
				lineNo += addLine(newdoc, ruleLine, Color.RED, true);
				// log numbers for fields independently...
				LOGGER.debug("Invalid number of fields! Expected 3, got: " + fields.length);
				if (fields.length > 3) {
					errors.append("Zeile " + lineNo + ": Zu viele Felder.\n");
				} else {
					errors.append("Zeile " + lineNo + ": Zu wenig Felder.\n");
				}
				invalid = true;
				continue;
			}
			// Have 3 fields, pretty up
			String ruleDirection = fields[2].toUpperCase();
			ruleLine = fields[0] + " \t " + fields[1] + " \t " + ruleDirection;

			// start to check fields one by one from the last to the first ....
			// check net direction: accept either 'in' or 'out' (case
			// insensitive)
			// TODO support combined 'in/out' rules
			if (!ruleDirection.equals("IN") && !ruleDirection.equals("OUT")) {
				lineNo += addLine(newdoc, ruleLine, Color.RED, true);
				LOGGER.debug("Invalid net direction! Expected 'in' or out'. Got: " + ruleDirection);
				errors.append("Zeile " + lineNo + ": Ungültige Richtung. Bitte nutzen Sie 'IN' bzw. 'OUT'.\n");
				invalid = true;
				continue;
			}
			// check port: accept if >= 0 and <= 65535
			int port = Util.parseInt(fields[1], -1);

			// port = 0 means match only host (all protocols and ports)
			if (port < 0 || port > 65535) {
				lineNo += addLine(newdoc, ruleLine, Color.RED, true);
				LOGGER.debug("Invalid port! Got: " + port);
				errors.append("Zeile " + lineNo + ": Ungültiger Port. Gültiger Bereich ist 0-65535.\n");
				invalid = true;
				continue;
			}
			// check hostname: bit more to do here
			// for IPs and/or resolvable hostnames we make use of java.net's
			// InetAddress.getByName() method which checks the validity of
			// an IP-Address represented by a string or if the given hostname
			// is resolvable. If any of these happen, we have a valid hostname.
			// Non-resolvable hostnames are handled differently, see after the
			// try/catch-block
			String checkRes = checkHostnameSimple(fields[0]);
			if (checkRes != null) {
				lineNo += addLine(newdoc, ruleLine, Color.RED, true);
				errors.append("Zeile " + lineNo + ": " + checkRes + "\n");
				invalid = true;
				continue;
			}
			// Made it to here - line is valid
			lineNo += addLine(newdoc, ruleLine, lineColor, false);
			rulesList.add(new NetRule(NetDirection.valueOf(ruleDirection), fields[0], port));
		}
		if (newdoc != null) {
			tpNetworkRules.setDocument(newdoc);
			resetTabStops();
		}
		if (!silent && errors.length() != 0) {
			Gui.showMessageBox("Fehler beim Auswerten der angegebenen Netzwerkregeln.\n\n" + errors.toString()
					+ "\nBitte geben Sie die Regeln zeilenweise im Format\n"
					+ "<host> <port> <IN|OUT>\n"
					+ "an.",
					MessageType.ERROR, null, null);
		}
		if (invalid) {
			return null;
		}
		// Success
		return rulesList;
	}

	private int addLine(DefaultStyledDocument doc, String line, Color color, boolean bold) {
		if (doc == null)
			return 0;
		if (color == null) {
			color = FOREGROUND_TEXT_COLOR;
		}
		SimpleAttributeSet attrs = new SimpleAttributeSet();
		StyleConstants.setForeground(attrs, color);
		StyleConstants.setBold(attrs, bold);
		try {
			doc.insertString(doc.getLength(), line + "\n", attrs);
		} catch (BadLocationException e) {
			LOGGER.warn("Cannot append to new textbox document", e);
		}
		return 1;
	}

	/**
	 * Very simple hostname check for the given String. This will only check for
	 * some requirements of valid hostnames as stated in
	 * http://tools.ietf.org/html/rfc1034#section-3.1 To recap: max length of
	 * the whole hostname must be < 254 ASCII characters, all domain labels
	 * (between two dots) must be between 1 and 63 chars long and domain labels
	 * can only contain digits, letters and hyphen. (Note: we also accept
	 * forward slash to accept subnets!)
	 * 
	 * @param hostname the hostname to check for syntactical validity
	 * @return null if valid, error string otherwise
	 */
	private String checkHostnameSimple(String hostname) {
		if (hostname.length() > 254) {
			return "Hostname ist zu lang.";
		}
		boolean allNumeric = true;
		int netmask = -1;
		String[] domainLabels = null;
		int ls = hostname.lastIndexOf('/');
		if (ls != -1) {
			netmask = Util.parseInt(hostname.substring(ls + 1), -1);
			if (netmask == -1) {
				return "Ungültige Netzmaske.";
			}
			hostname = hostname.substring(0, ls);
		}
		if (hostname.matches("^\\[.*\\]$")) {
			hostname = hostname.substring(1, hostname.length() - 2);
		} else {
			// split by '.' to get domain levels
			domainLabels = hostname.split("\\.");
		}
		if (domainLabels == null || (domainLabels.length <= 1 && hostname.indexOf(':') != -1)) {
			// v6
			if ((hostname.startsWith(":") && !hostname.startsWith("::"))
					|| (hostname.endsWith(":") && ! hostname.endsWith("::"))) {
				return "IPv6-Adresse darf nicht mit einem Doppelpunkt beginnen oder enden.";
			}
			int numCompressed = (hostname.length() - hostname.replace("::", "").length()) / 2;
			if (numCompressed > 1) {
				return "IPv6-Adresse darf nicht mehr als einen komprimierten Teil enthalten.";
			}
			if (netmask > 128) {
				return "IPv6 Netzmaske kann nicht größer 128 Bit sein.";
			}
			domainLabels = hostname.split(":");
			if (domainLabels.length > 8) {
				return "IPv6-Adresse enthält zu viele Hextets."; // Yes it's called that apparently
			}
			for (String domainLabel : domainLabels) {
				if (domainLabel.isEmpty())
					continue;
				try {
					int test = Integer.parseInt(domainLabel, 16);
					if (test < 0 || test > 65535) {
						return "IPv6-Adresse enthält ungültiges Hextet.";
					}
				} catch (Exception e) {
					return "IPv6-Adresse enthält nicht-hexadezimale Zeichen."; 
				}
			}
			if (!allNumeric
					|| ((domainLabels.length == 8 || numCompressed > 0) && (netmask < -1 || netmask > 128))
					|| (domainLabels.length < 8 && numCompressed == 0 && (netmask < 0 || netmask > 128))) {
				return "Fehlerhafte IPv6-Adresse/Netzmaske.";
			}
		} else {
			// v4 or hostname
			if (netmask > 32) {
				return "IPv4 Netzmaske kann nicht größer 32 Bit sein.";
			}
			for (String domainLabel : domainLabels) {
				if (domainLabel.length() > 63) {
					// fail since domain level should be max 63 chars
					return "Domain-Ebene '" + domainLabel + "' länger als 63 Zeichen.";
				}
				int i = Util.parseInt(domainLabel, -1);
				if (i < 0 || i > 255) {
					allNumeric = false;
				}
				// checking for valid chars is pointless with punycode
			}
			if (allNumeric) {
				if ((domainLabels.length == 4 && (netmask < -1 || netmask > 32))
						|| domainLabels.length > 4
						|| (domainLabels.length < 4 && (netmask < 0 || netmask > 32))) {
					return "Fehlerhafte IPv4-Adresse/Netzmaske.";
				}
			}
		}
		return null;
	}

}

/**
 * Internal layout class for this widget
 */
class NetrulesConfiguratorLayout extends JPanel {

	private static final long serialVersionUID = 5266120380443817325L;
	private final static String STR_RULES_DESCRIPTION = "Wenn Sie den Internetzugriff deaktiviert haben,"
			+ " können Sie hier Ausnahmen definieren (Whitelist)."
			+ " Bitte definieren Sie Ihre Regeln im Format\n<host> <port> <in|out>.\n"
			+ "Sie können Port 0 angeben, was sämtlichen TCP und UDP Ports eines Hosts entspricht.";
	private final static String STR_RULES_ADD = "Wenn Sie Internetzugriff aktivieren,"
			+ " hat diese Liste den gegenteiligen Effekt (Blacklist).";
	private final static String STR_TITLE = "Netzwerkregeln";

	protected final JTextPane tpNetworkRules;
	protected final JButton btnCheckRules;

	public NetrulesConfiguratorLayout() {

		GridManager grid = new GridManager(this, 2, true,
				new Insets(5, 5, 5, 5));

		// middle panel for network rules
		this.setBorder(BorderFactory.createTitledBorder(STR_TITLE));
		tpNetworkRules = new JTextPane();
		resetTabStops();

		grid
				.add(new WordWrapLabel(STR_RULES_DESCRIPTION), 2)
				.fill(true, false).expand(true, false);
		grid.nextRow();

		grid
				.add(new WordWrapLabel(STR_RULES_ADD))
				.fill(true, false).expand(true, false);
		btnCheckRules = new JButton("Regeln überprüfen");
		grid.add(btnCheckRules);
		grid.nextRow();

		grid.add(new JScrollPane(tpNetworkRules,
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), 2)
				.fill(true, true).expand(true, true);
		grid.nextRow();

		grid.finish(false);
	}

	protected void resetTabStops() {
		Style tabs = tpNetworkRules.getLogicalStyle();
		StyleConstants.setTabSet(tabs,  new TabSet(new TabStop[] {
				new TabStop(300, TabStop.ALIGN_RIGHT, TabStop.LEAD_NONE),
				new TabStop(310, TabStop.ALIGN_LEFT, TabStop.LEAD_NONE),
		}));
		tpNetworkRules.setLogicalStyle(tabs);
	}
}