/*
** This file is part of Filius, a network construction and simulation software.
**
** Originally created at the University of Siegen, Institute "Didactics of
** Informatics and E-Learning" by a students' project group:
** members (2006-2007):
** André Asschoff, Johannes Bade, Carsten Dittich, Thomas Gerding,
** Nadja Haßler, Ernst Johannes Klebert, Michell Weyer
** supervisors:
** Stefan Freischlad (maintainer until 2009), Peer Stechert
** Project is maintained since 2010 by Christian Eibl <filius@c.fameibl.de>
** and Stefan Freischlad
** Filius 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) version 3.
**
** Filius 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 Filius. If not, see <http://www.gnu.org/licenses/>.
*/
package filius.software.firewall;
import java.util.LinkedList;
import java.util.StringTokenizer;
import java.util.Vector;
import filius.Main;
import filius.hardware.NetzwerkInterface;
import filius.hardware.knoten.InternetKnoten;
import filius.rahmenprogramm.EingabenUeberpruefung;
import filius.rahmenprogramm.I18n;
import filius.software.Anwendung;
import filius.software.system.InternetKnotenBetriebssystem;
import filius.software.transportschicht.Segment;
import filius.software.transportschicht.SocketSchnittstelle;
import filius.software.transportschicht.TCPSocket;
import filius.software.transportschicht.TcpSegment;
import filius.software.transportschicht.UDPSocket;
import filius.software.vermittlungsschicht.IpPaket;
import filius.software.vermittlungsschicht.VermittlungsProtokoll;
/**
* Die Firewall kann in zwei verschiedenen Modi betrieben werden.
* <p> Als <b>Personal Firewall</b> werden lediglich Port-Regeln ausgewertet.
* Eine Port-Regel spezifiziert zugelassene TCP-/UDP-Ports und ob
* diese nur von IP-Adressen im lokalen Rechnernetz oder global
* kontaktiert werden koennen. </p>
* <p> Wenn die Firewall in einem <b>Gateway</b> betrieben wird, gibt es
* vier verschiedene Regeltypen. Alle Regeln spezifizieren - im Gegensatz
* zum Betrieb als Personal Firewall - Dateneinheiten, die nicht zugelassen
* werden. Geprueft werden:
* <ol>
* <li> Sender-IP-Adresse </li>
* <li> Absender-IP-Adresse </li>
* <li> TCP-/UDP-Port </li>
* <li> ACK(=0)+SYN(=1)-Bit der TCP-Segmente (indiziert Initialisierung
* des Verbindungsaufbaus)</li>
* </ol>
*/
public class Firewall extends Anwendung implements I18n {
public static int PERSONAL = 1, GATEWAY = 2;
public static String ABSENDER_FILTER = "Quelle", EMPFAENGER_FILTER = "Ziel";
private boolean aktiviert = true;
private int bitRegel = 0;
private LinkedList<String> absenderFilter, empfaengerFilter;
/** Liste mit Portregeln besetehen aus dem jeweiligen TCP/UDP-Port
* und einem Flag, das angibt, ob diese Ausschlussregel nur auf IP-Pakete
* aller Absender (oder nur auf Absender ausserhalb des eigenen lokalen
* Rechnernetzes) angewendet wird. Das Flag wird nur im Betrieb als Personal
* Firewall ausgewertet!
*/
private LinkedList<Object[]> portList;
/** Das Verhalten der Firewall ist abhaengig davon, ob sie
* als Personal Firewall oder als Gateway benutzt wird.
*/
private int modus;
private LinkedList<FirewallThread> threads = new LinkedList<FirewallThread>();
private Vector<Integer> inactiveNics = new Vector<Integer>();
/** Konstruktor initialisiert Listen mit Regeln für die Firewall. Außerdem setzt ein
Firewall-Beobachter Nachrichten ins Logfenster
*/
public Firewall(){
super();
Main.debug.println("INVOKED-2 ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), constr: Firewall()");
setModus(PERSONAL);
absenderFilter = new LinkedList<String>();
empfaengerFilter = new LinkedList<String>();
portList = new LinkedList<Object[]>();
}
/** startet die Anwendung Firewall.
*/
public void starten(){
if (this.holeNetzwerkInterfaces() != null) {
Main.debug.println("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
+ " (Firewall), starten()");
super.starten();
for (NetzwerkInterface nic : this.holeNetzwerkInterfaces()) {
this.starteFirewallThread(nic);
}
}
}
private void starteFirewallThread(NetzwerkInterface nic) {
FirewallThread thread = new FirewallThread(this, nic);
thread.starten();
this.threads.add(thread);
}
/**
* ruft die Methoden zum ordnungsgemäßen Stoppen aller existierenden Threads auf
*/
public void beenden() {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), beenden()");
super.beenden();
this.beendeFirewallThread(null);
}
private void beendeFirewallThread(NetzwerkInterface nic) {
for (FirewallThread thread : this.threads) {
if (nic == null) {
thread.beenden();
}
else if (nic == thread.getNetzwerkInterface()) {
thread.beenden();
break;
}
}
}
/**
* <p> Untersucht im <b>Modus Gateway</b> das uebergebene IP-Paket nach
* einer Uebereinstimmung mit einer Regel, die gefiltert werden muss.
* Wenn das zutrifft wird der Rueckgabewert auf true gesetzt. </p>
* <p> Im <b>Modus PERSONAL</b> wird zunaechst geprueft, ob das Paket
* einer Ausnahmeregelung entspricht und damit weitergegeben werden kann.
* Wenn dies nicht der Fall ist, wird noch geprueft, ob das enthaltene
* Segment an einen geoeffnet Client-Socket gerichtet ist. Dann wird es
* dennoch weitergeleitet. </p>
*/
public boolean pruefePaketVerwerfen(IpPaket ipPaket){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), pruefePaketVerwerfen("+ipPaket+")");
Segment segment;
InternetKnoten knoten;
SocketSchnittstelle socket;
boolean verwerfen = false;
// Nur wenn Firewall eingeschaltet ist, passiert was:
if(isAktiviert()){
Main.debug.println("INFO ("+this.hashCode()+"): Firewall ist eingeschaltet. Pruefung beginnt!");
if (modus == PERSONAL) {
segment = (Segment)ipPaket.getSegment();
// gibt es eine Regel, die diesen Port als Ausnahme
// fuer den Datenaustausch zulaesst?
if (!pruefePortOffen(""+segment.getZielPort(), ipPaket.getSender())) {
verwerfen = true;
// ist die Zieladresse eine Adresse dieses Knotens
// und der Zielport ein Client-Socket?
// -> dann wird das Segment trotzdem weitergeleitet
knoten = (InternetKnoten)getSystemSoftware().getKnoten();
if(knoten.getNetzwerkInterfaceByIp(ipPaket.getEmpfaenger()) != null) {
if (ipPaket.getProtocol() == IpPaket.TCP) {
try {
socket = getSystemSoftware().holeTcp().holeSocket(segment.getZielPort());
if (socket instanceof TCPSocket) {
verwerfen = false;
}
}
catch (Exception e) {}
}
else if (ipPaket.getProtocol() == IpPaket.UDP) {
try {
socket = getSystemSoftware().holeUdp().holeSocket(segment.getZielPort());
if (socket instanceof UDPSocket) {
verwerfen = false;
}
}
catch (Exception e) {}
}
}
}
}
else {
//Pruefen von Ip-Adressen:
if(pruefeIPEmpfaenger(ipPaket.getEmpfaenger())){
verwerfen = true;
benachrichtigeBeobachter(messages.getString("sw_firewall_msg1")+verwerfen);
}
if(pruefeIPSender(ipPaket.getSender())){
verwerfen = true;
}
//Port untersuchen:
segment = (Segment)ipPaket.getSegment();
//Main.debug.println("Firewall: Filtern nach Ziel-Port: "+ segment.getZielPort());
if(pruefePortGeschlossen(Integer.toString(segment.getZielPort()))){
verwerfen = true;
benachrichtigeBeobachter(messages.getString("sw_firewall_msg2")+ Integer.toString(segment.getQuellPort()));
}
//Syn-Ack-Bit prüfen:
if(getBitRegel() == 1 && segment instanceof TcpSegment){
if(((TcpSegment)segment).isSyn()&& !((TcpSegment)segment).isAck()){
verwerfen = true;
benachrichtigeBeobachter(messages.getString("sw_firewall_msg3"));
}
}
}
}
//Ende der Untersuchung durch die Firewall
return verwerfen;
}
/** Ueberprueft, ob die uebergebene IP-Adresse einem Adressbereich in
* den Empfaenger-Regeln entspricht.
* @return ob die uebergebene Adresse in einem Adressbereich einer Regel liegt
*/
private boolean pruefeIPEmpfaenger(String anfragendeIP){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), pruefeIPEmpfaenger("+anfragendeIP+")");
boolean regelVorhanden = false;
//Main.debug.println("Firewall: prüfe nun IP-Empfänger");
//Der Fall von Range (Bereich) wird hier bearbeitet.
//Im Array sind Spalte 1 und 2 eingetragen
for(int i = 0; i<empfaengerFilter.size() && !regelVorhanden; i++){
String tmp = (String)empfaengerFilter.get(i);
String [] tmpArray = tmp.split("#");
if(tmpArray[0].equals(anfragendeIP)){
regelVorhanden = true;
}
else if(!tmpArray[1].trim().equals("")){
if(inPruefbereich(tmpArray[0], tmpArray[1], anfragendeIP)){
regelVorhanden = true;
}
}
}
return regelVorhanden;
}
/**
* Ueberprueft, ob die uebergebene IP-Adresse einem Adressbereich in
* den Absender-Regeln entspricht.
* @return ob die uebergebene Adresse in einem Adressbereich einer Regel liegt
*/
private boolean pruefeIPSender(String anfragendeIP){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), pruefeIPSender("+anfragendeIP+")");
boolean regelVorhanden = false;
for(int i = 0; i<absenderFilter.size() && !regelVorhanden; i++){
String tmp = (String)absenderFilter.get(i);
String [] tmpArray = tmp.split("#");
if(tmpArray[0].equals(anfragendeIP)){
Main.debug.println("INFO ("+this.hashCode()+"): Quelle "+anfragendeIP+" gefunden, daher blockieren");
regelVorhanden = true;
}
else if(!tmpArray[1].trim().equals("")){
if(inPruefbereich(tmpArray[0], tmpArray[1], anfragendeIP) ==true){
regelVorhanden = true;
}
}
}
return regelVorhanden;
}
/**
* Ueberprueft, ob der uebergebene Port einer Regel entspricht.
* Diese Art der Regel fuehrt immer dazu, dass das Paket verworfen
* wird!
* @return ob zum uebergebenen Port eine Regel existiert
*/
private boolean pruefePortGeschlossen(String port){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), pruefePortGeschlossen("+port+")");
Object[] regel;
String tmp;
boolean regelVorhanden = false;
//prüfe 1.Spalte in der Liste
for(int i = 0; i<portList.size(); i++){
regel = (Object[]) portList.get(i);
tmp = (String)regel[0];
if(tmp.equals(port)){
regelVorhanden = true;
}
}
return regelVorhanden;
}
/**
* Ueberprueft, ob uebergebener Port und uebergebene IP-Adresse einer
* Port-Regel entsprechen. Diese Art der Regel fuehrt immer dazu, dass das
* Paket akzeptiert wird! <br />
* Das ist der Fall, wenn zum Port ein Eintrag vorhanden
* ist und entweder die Adressbereiche nicht beruecksichtigt werden oder
* die Adresse zum gleichen Rechnernetz gehoert.
* @return ob zu uebergebenem Paar aus Port und IP-Adresse eine Regel existiert
*/
private boolean pruefePortOffen(String port, String ipAdresse) {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), pruefePortOffen("+port+","+ipAdresse+")");
Object[] regel;
String tmp;
boolean gleichesNetzwerk, regelVorhanden = false;
//prüfe 1.Spalte in der Liste
for(int i = 0; i<portList.size(); i++){
regel = (Object[]) portList.get(i);
tmp = (String)regel[0];
if(tmp.equals(port)){
if ((Boolean)regel[1]) {
gleichesNetzwerk = VermittlungsProtokoll.gleichesRechnernetz(ipAdresse,
getSystemSoftware().holeIPAdresse(),
getSystemSoftware().holeNetzmaske());
regelVorhanden = gleichesNetzwerk;
}
else {
regelVorhanden = true;
}
}
}
return regelVorhanden;
}
/**
* fuegt eine IP-Adressenregel in die Liste mit IP-Adressen ein
*/
public void eintragHinzufuegen(String von, String bis, String typ){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), eintragHinzufuegen("+von+","+bis+","+typ+")");
if (!EingabenUeberpruefung.isGueltig(von, EingabenUeberpruefung.musterIpAdresse)
|| !EingabenUeberpruefung.isGueltig(bis, EingabenUeberpruefung.musterIpAdresseAuchLeer)) {
return;
}
if(bis.equals("")){
bis = " ";
}
String tmp = von + "#" + bis +"#" + typ;
if (typ.equals(ABSENDER_FILTER)) {
absenderFilter.add(tmp);
//Main.debug.println("Erfolgreich hinzugefügt: "+tmp );
benachrichtigeBeobachter(messages.getString("sw_firewall_msg4")+von + " - "+bis);
}
else if (typ.equals(EMPFAENGER_FILTER)) {
empfaengerFilter.add(tmp);
//Main.debug.println("Erfolgreich hinzugefügt: "+tmp );
benachrichtigeBeobachter(messages.getString("sw_firewall_msg4")+von + " - "+bis);
}
}
/**
* fuegt eine Portregel in die Liste mit Portregeln ein
*/
public void eintragHinzufuegenPort(String port){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), eintragHinzufuegenPort("+port+")");
Object[] regel;
if (port != null) {
regel = new Object[2];
regel[0] = port;
regel[1] = Boolean.FALSE;
portList.add(regel);
//Main.debug.println("Erfolgreich hinzugefuegt: "+port+ "Listenindex: "+ portList.indexOf(regel));
benachrichtigeBeobachter(messages.getString("sw_firewall_msg5")+port);
}
}
public void eintragHinzufuegenPort(String port, boolean unterscheideNetzwerk) {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), eintragHinzufuegenPort("+port+","+unterscheideNetzwerk+")");
Object[] regel;
if (port != null) {
regel = new Object[2];
regel[0] = port;
regel[1] = new Boolean(unterscheideNetzwerk);
portList.add(regel);
//Main.debug.println("Erfolgreich hinzugefuegt: "+port+ "Listenindex: "+ portList.indexOf(regel));
benachrichtigeBeobachter(messages.getString("sw_firewall_msg5")+port);
}
}
/**
* entfernt eine IP-Adressen-Regel aus dem Regelkatalog
*/
public void entferneAbsenderRegel(int nummer){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), entferneAbsenderRegel("+nummer+")");
if(nummer >= 0 && nummer < absenderFilter.size()){ //damit keine zu große Zahl in GUI eingegeben werden kann
benachrichtigeBeobachter(messages.getString("sw_firewall_msg6")+ absenderFilter.get(nummer)); //Log
//Main.debug.println("Entfernt aus Liste:" + nummer);
//Main.debug.println(absenderFilter.get(nummer));
absenderFilter.remove(nummer);
}
}
/**
* entfernt eine IP-Adressen-Regel aus dem Regelkatalog
*/
public void entferneEmpfaengerRegel(int nummer){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), entferneEmpfaengerRegel("+nummer+")");
if(nummer >= 0 && nummer < empfaengerFilter.size()){ //damit keine zu große Zahl in GUI eingegeben werden kann
benachrichtigeBeobachter(messages.getString("sw_firewall_msg6")+ empfaengerFilter.get(nummer)); //Log
//Main.debug.println("Entfernt aus Liste:" + nummer);
//Main.debug.println(empfaengerFilter.get(nummer));
empfaengerFilter.remove(nummer);
}
}
/**
* entfernt eine Portregel aus dem Regelkatalog
*/
public void entferneRegelPort(int nummer){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), entferneRegel("+nummer+")");
//Main.debug.println("Listenfeld wird gelöscht: " +nummer);
benachrichtigeBeobachter(messages.getString("sw_firewall_msg7")+ ((Object[])portList.get(nummer))[0]); //Log
if(nummer >= 0 && nummer < portList.size()){ //damit keine zu große Zahl in GUI eingegeben werden kann
portList.remove(nummer);
}
}
/**
*
* @param untereGrenze
* @param obereGrenze
* @param pruefWert
* @return wandelt zunächst die Adressen in Zahlen um.
* Dann wird geprueft, ob der pruefWert inmitten der oberen und unteren Grenze liegt
*/
private boolean inPruefbereich(String untereGrenze, String obereGrenze, String pruefWert){
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (Firewall), inPruefbereich("+untereGrenze+","+obereGrenze+","+pruefWert+")");
boolean pruef = false;
StringTokenizer untereTokens = new StringTokenizer(untereGrenze, ".");
double ersteZahl = Integer.parseInt(untereTokens.nextToken());
ersteZahl = ersteZahl * 16777216; // entspricht 2^24
double zweiteZahl = Integer.parseInt(untereTokens.nextToken());
zweiteZahl = zweiteZahl * 65536; //entspricht 2^16
double dritteZahl = Integer.parseInt(untereTokens.nextToken());
dritteZahl = dritteZahl * 256;
double vierteZahl = Integer.parseInt(untereTokens.nextToken());
double untereSumme = ersteZahl + zweiteZahl+ dritteZahl + vierteZahl;
//Main.debug.println("Firewall: untereSumme = "+untereSumme);
//String obereGrenze in eine Zahl umrechnen:
StringTokenizer obereTokens = new StringTokenizer(obereGrenze, ".");
double ersteObereZahl = Integer.parseInt(obereTokens.nextToken());
ersteObereZahl = ersteObereZahl * 16777216; // entspricht 2^24
double zweiteObereZahl = Integer.parseInt(obereTokens.nextToken());
zweiteObereZahl = zweiteObereZahl * 65536; //entspricht 2^16
double dritteObereZahl = Integer.parseInt(obereTokens.nextToken());
dritteObereZahl = dritteObereZahl * 256; //entspricht 2^8
double vierteObereZahl = Integer.parseInt(obereTokens.nextToken());
double obereSumme = ersteObereZahl + zweiteObereZahl+ dritteObereZahl + vierteObereZahl;
//Main.debug.println("Firewall: obereSumme = "+obereSumme);
//String pruefWert in eine Zahl umrechnen:
StringTokenizer pruefTokens = new StringTokenizer(pruefWert, ".");
double erstePruefZahl = Integer.parseInt(pruefTokens.nextToken());
erstePruefZahl = erstePruefZahl * 16777216; // entspricht 2^24
double zweitePruefZahl = Integer.parseInt(pruefTokens.nextToken());
zweitePruefZahl = zweitePruefZahl * 65536; //entspricht 2^16
double drittePruefZahl = Integer.parseInt(pruefTokens.nextToken());
drittePruefZahl = drittePruefZahl * 256; //entspricht 2^8
double viertePruefZahl = Integer.parseInt(pruefTokens.nextToken());
double pruefZahlSumme = erstePruefZahl + zweitePruefZahl+ drittePruefZahl + viertePruefZahl;
//Main.debug.println("Firewall: pruefZahlSumme = "+pruefZahlSumme);
//pruefen ob der pruefWert zwischen Obersumme und Untersumme liegt
if(pruefZahlSumme >= untereSumme && pruefZahlSumme <= obereSumme){
pruef = true;
}
return pruef;
}
/**
* Methode fuer den Zugriff auf das Betriebssystem, auf dem diese Anwendung
* laeuft.
*
* @param bs
*/
public void setSystemSoftware(InternetKnotenBetriebssystem bs) {
super.setSystemSoftware(bs);
}
//getter & setter:
public int getBitRegel() {
return bitRegel;
}
public void setBitRegel(int bitRegel) {
this.bitRegel = bitRegel;
}
public boolean isAktiviert() {
return aktiviert;
}
public void setAktiviert(boolean aktiviert) {
benachrichtigeBeobachter("Firewall "+(aktiviert?"aktiviert":"deaktiviert"));
this.aktiviert = aktiviert;
}
public LinkedList<String> getEmpfaengerFilterList() {
return empfaengerFilter;
}
public void setEmpfaengerFilterList(LinkedList<String> ipList) {
this.empfaengerFilter = ipList;
}
public LinkedList<String> getAbsenderFilterList() {
return absenderFilter;
}
public void setAbsenderFilterList(LinkedList<String> ipList) {
this.absenderFilter = ipList;
}
public LinkedList<Object[]> getPortList() {
return portList;
}
public void setPortList(LinkedList<Object[]> portList) {
this.portList = portList;
}
public void setModus(int modus) {
this.modus = modus;
}
public int getModus() {
return modus;
}
public void setzeNetzwerkInterfaces(LinkedList<NetzwerkInterface> netzwerkInterfaces) {
LinkedList<NetzwerkInterface> allNics = this.getAllNetworkInterfaces();
this.inactiveNics.removeAllElements();
for (NetzwerkInterface nic : allNics) {
if (netzwerkInterfaces.indexOf(nic) == -1) {
this.inactiveNics.add(new Integer(allNics.indexOf(nic)));
}
}
}
public LinkedList<NetzwerkInterface> holeNetzwerkInterfaces() {
LinkedList<NetzwerkInterface> allNics = this.getAllNetworkInterfaces();
LinkedList<NetzwerkInterface> result = new LinkedList<NetzwerkInterface>();
for (NetzwerkInterface nic : allNics) {
try {
if (!this.inactiveNics.contains(new Integer(allNics.indexOf(nic)))) {
result.addLast(nic);
}
} catch (IndexOutOfBoundsException e) {
}
}
return result;
}
private LinkedList<NetzwerkInterface> getAllNetworkInterfaces() {
InternetKnoten host = (InternetKnoten) this.getSystemSoftware().getKnoten();
return host.getNetzwerkInterfaces();
}
public boolean hinzuNetzwerkInterface(NetzwerkInterface nic) {
int idx = this.getAllNetworkInterfaces().indexOf(nic);
if (this.inactiveNics.contains(new Integer(idx))) {
this.inactiveNics.remove(new Integer(idx));
if (this.running) {
this.starteFirewallThread(nic);
}
return true;
}
else {
return false;
}
}
public boolean entferneNetzwerkInterface(NetzwerkInterface nic) {
int idx = this.getAllNetworkInterfaces().indexOf(nic);
if (! this.inactiveNics.contains(new Integer(idx))) {
this.inactiveNics.add(new Integer(idx));
if (this.running) {
this.beendeFirewallThread(nic);
}
return true;
}
else {
return false;
}
}
public Vector<Integer> getInactiveNics() {
return inactiveNics;
}
public void setInactiveNics(Vector<Integer> inactiveNics) {
this.inactiveNics = inactiveNics;
}
}