#!/usr/bin/env python
###########################################################################
# guidedog.py - description                                               #
# ------------------------------                                          #
# begin     : Wed Oct 11 2006                                             #
# copyright : (C) 2006 by Simon Edwards                                   #
# email     : simon@simonzone.com                                         #
#                                                                         #
###########################################################################
#                                                                         #
#   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.                                   #
#                                                                         #
###########################################################################

import sys
import os
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyKDE4.kdecore import *
from PyKDE4.kdeui import *
from PyKDE4.kio import *
import re
import guidedogwindow_ui
from guidedogdoc import *
from commandrunner import *

SYSTEM_RC_GUIDEDOG = "/etc/rc.guidedog"

############################################################################
def lockgui(func):
    """Decorator to ensure that only one slot is activated at a time.
    
    This ensures that any programmatic updates to the GUI are not immediately
    picked up by other slots, causing loops.
    
    The line "self.updating_gui=False" should be part of the class' init code.
    """
    def wrapper(self,*__args,**__kw):
        if self.updating_gui:
            return
        self.updating_gui = True
        try:
            return func(self,*__args,**__kw)
        finally:
            self.updating_gui = False
        
    wrapper.__name__ = func.__name__
    wrapper.__dict__ = func.__dict__
    wrapper.__doc__ = func.__doc__
    return wrapper

############################################################################
class AddressValidator(QValidator):
    def __init__(self,parent=None):
        QValidator.__init__(self,parent)

    def validate(self,input,pos):
        sanity = re.compile("^[0-9a-zA-Z./-]*$")
        domainnametest = re.compile("^([a-zA-Z][a-zA-Z0-9-]*\\.)+[a-zA-Z][a-zA-Z0-9-]*$")
        iptest = re.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
        ipmaskedtest = re.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)/([0-9]+)$")
        ipmasked2test = re.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)/([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
    
        if input.isNull():
            return (QValidator.Intermediate,pos)
            
        input = unicode(input)
        # Smoke text
        if sanity.match(input) is None:
            return (QValidator.Invalid,pos)
        
        if len(input)==0:
            return (QValidator.Intermediate,pos)
    
        # Test against the domainname regexp.
        if domainnametest.match(input):
            return (QValidator.Acceptable,pos)
    
        # Ok, now lets try the IP address regexp.
        iptest_match = iptest.match(input)
        if iptest_match:
            ipbyte = int(iptest_match.group(1))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(iptest_match.group(2))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(iptest_match.group(3))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(iptest_match.group(4))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            return (QValidator.Acceptable,pos)
    
        # Ok, now lets try the IP address regexp.
        ipmaskedtest_match = ipmaskedtest.match(input)
        if ipmaskedtest_match:
            ipbyte = int(ipmaskedtest_match.group(1))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmaskedtest_match.group(2))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)
                
            ipbyte = int(ipmaskedtest_match.group(3))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmaskedtest_match.group(4))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmaskedtest_match.group(5))
            if ipbyte<0 or ipbyte>32:
                return (QValidator.Intermediate,pos)

            return (QValidator.Acceptable,pos)
            
        ipmasked2test_match = ipmasked2test.match(input)
        if ipmasked2test_match:
            ipbyte = int(ipmasked2test_match.group(1))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(2))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(3))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(4))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(5))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(6))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(7))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(8))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            return (QValidator.Acceptable,pos)

        return (QValidator.Intermediate,pos)
    
    def fixup(self, qinput):
        # This is real DWIM (Do What I Mean) code.
        # Somehow it is meant to take what the user entered and work out
        # what they meant and then correct the entered string.
        # It's just a bunch of guesses, hunches and heristics.
        
        if qinput.isNull() or qinput=="": # Just in case.
            qinput.truncate(0)
            qinput += "0.0.0.0"
            return
        
        input = unicode(qinput)
    
        # Filter out any bad characters.
        clean = ""
        slashcount = 0
        for c in input:
            if c=='/':
                if slashcount==0:
                    clean += '/'
                    slashcount += 1
                
            elif c.isdigit() or c=='.' or c=='-' or c.isalpha():
                clean += c
        
        # No dots at the start or end please.
        clean = clean.lstrip('.').rstrip('.')
    
        # Remove double dots.
        
        while '..' in clean:
            clean = clean.replace('..','.')
    
        # Do we still have a string?
        if len(clean)==0:
            return QString("0.0.0.0")  # This should not match much.
    
        # Look at the first character and take a guess as to
        # what kind of value the user attempted to enter.
        if clean[0].isdigit():
            # Ok, we expect some kind of IP address maybe with a netmask.
            
            # Kill any funny chars.
            clean = ''.join([c for c in clean if not c.isalpha()])
            clean = clean.lstrip('.').rstrip('.') # No dots at the start or end please.
    
            # Remove double dots.
            while '..' in clean:
                clean = clean.replace('..','.')
    
            mask = ""
            pos = clean.find('/')
            if pos!=-1:
                mask = clean[pos+1:]
                clean = clean[:pos];
    
            i = 0
            tmp = ""
            snarfnumber = re.compile("^([0-9]+)")
            
            while True:
                snarfnumber_match = snarfnumber.match(clean)
                if snarfnumber_match is None or i==4:
                    break
            
                ipbyte = int(snarfnumber_match.group(1))
                if ipbyte>255:
                    ipbyte = 255
                i += 1
                
                tmp += str(ipbyte) + "."

                clean = clean[len(snarfnumber_match.group(1)):]

                for y in range(len(clean)):
                    if clean[y].isdigit():
                        break
                clean = clean[y:]
            
            while i<4:
                tmp = tmp + "0."
                i += 1
                
            tmp = tmp.rstrip(".")
            
            if mask!="":    # We still have not consumed all the input.
                            # There must be some kind of netmask left.
                if '.' in mask:     # It must be a single number netmask.
                    tmp += "/"
                    ipbyte = int(mask)
                    if ipbyte>32:
                        ipbyte = 32
                    
                    tmp += str(ipbyte)
                else:
                    # Expecting a dotted quad netmask.
                    tmp += "/"
                    
                    i = 0
                    tmp = ""
                    snarfnumber = re.compile("^([0-9]+)")
                    
                    while True:
                        snarfnumber_match = snarfnumber.match(clean)
                        if snarfnumber_match is None or i==4:
                            break
                    
                        ipbyte = int(snarfnumber_match.group(1))
                        if ipbyte>255:
                            ipbyte = 255
                        i += 1
                        
                        tmp += str(ipbyte) + "."
        
                        clean = clean[len(snarfnumber_match.group(1)):]
        
                        for y in range(len(clean)):
                            if clean[y].isdigit():
                                break
                        clean = clean[y:]
                    
                    while i<4:
                        tmp = tmp + "0."
                        i += 1
                        
                    tmp = tmp.rstrip(".")
            clean = tmp
    
        pos = 0;
        (rc,pos) = self.validate(QString(clean), pos)
        if rc!=QValidator.Acceptable:
            input ="0.0.0.0"
        else:
            input = clean

        qinput.truncate(0)
        qinput += clean

############################################################################
class IPValidator(QValidator):
    def __init__(self,parent=None):
        QValidator.__init__(self,parent)

    def validate(self,input,pos):
        sanity = re.compile("^[0-9./]*$")
        iptest = re.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
        ipmaskedtest = re.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)/([0-9]+)$")
        ipmasked2test = re.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)/([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
    
        if input.isNull():
            return (QValidator.Intermediate,pos)
            
        input = unicode(input)
        # Smoke text
        if sanity.match(input) is None:
            return (QValidator.Invalid,pos)
        
        if len(input)==0:
            return (QValidator.Intermediate,pos)
    
        # Ok, now lets try the IP address regexp.
        iptest_match = iptest.match(input)
        if iptest_match:
            ipbyte = int(iptest_match.group(1))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(iptest_match.group(2))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(iptest_match.group(3))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(iptest_match.group(4))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            return (QValidator.Acceptable,pos)
    
        # Ok, now lets try the IP address regexp.
        ipmaskedtest_match = ipmaskedtest.match(input)
        if ipmaskedtest_match:
            ipbyte = int(ipmaskedtest_match.group(1))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmaskedtest_match.group(2))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)
                
            ipbyte = int(ipmaskedtest_match.group(3))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmaskedtest_match.group(4))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmaskedtest_match.group(5))
            if ipbyte<0 or ipbyte>32:
                return (QValidator.Intermediate,pos)

            return (QValidator.Acceptable,pos)
            
        ipmasked2test_match = ipmasked2test.match(input)
        if ipmasked2test_match:
            ipbyte = int(ipmasked2test_match.group(1))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(2))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(3))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(4))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(5))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(6))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(7))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            ipbyte = int(ipmasked2test_match.group(8))
            if ipbyte<0 or ipbyte>255:
                return (QValidator.Intermediate,pos)

            return (QValidator.Acceptable,pos)

        return (QValidator.Intermediate,pos)
    
    def fixup(self, qinput):
        # This is real DWIM (Do What I Mean) code.
        # Somehow it is meant to take what the user entered and work out
        # what they meant and then correct the entered string.
        # It's just a bunch of guesses, hunches and heristics.
        
        if qinput.isNull() or qinput=="": # Just in case.
            qinput.truncate(0)
            qinput += "0.0.0.0"
            return
        
        input = unicode(qinput)
    
        # Filter out any bad characters.
        clean = ""
        slashcount = 0
        for c in input:
            if c=='/':
                if slashcount==0:
                    clean += '/'
                    slashcount += 1
                
            elif c.isdigit() or c=='.' or c=='-' or c.isalpha():
                clean += c
        
        # No dots at the start or end please.
        clean = clean.lstrip('.').rstrip('.')
    
        # Remove double dots.
        
        while '..' in clean:
            clean = clean.replace('..','.')
    
        # Do we still have a string?
        if len(clean)==0:
            return QString("0.0.0.0")  # This should not match much.
    
        # Look at the first character and take a guess as to
        # what kind of value the user attempted to enter.
        if clean[0].isdigit():
            # Ok, we expect some kind of IP address maybe with a netmask.
            
            # Kill any funny chars.
            clean = ''.join([c for c in clean if not c.isalpha()])
            clean = clean.lstrip('.').rstrip('.') # No dots at the start or end please.
    
            # Remove double dots.
            while '..' in clean:
                clean = clean.replace('..','.')
    
            mask = ""
            pos = clean.find('/')
            if pos!=-1:
                mask = clean[pos+1:]
                clean = clean[:pos];
    
            i = 0
            tmp = ""
            snarfnumber = re.compile("^([0-9]+)")
            
            while True:
                snarfnumber_match = snarfnumber.match(clean)
                if snarfnumber_match is None or i==4:
                    break
            
                ipbyte = int(snarfnumber_match.group(1))
                if ipbyte>255:
                    ipbyte = 255
                i += 1
                
                tmp += str(ipbyte) + "."

                clean = clean[len(snarfnumber_match.group(1)):]

                for y in range(len(clean)):
                    if clean[y].isdigit():
                        break
                clean = clean[y:]
            
            while i<4:
                tmp = tmp + "0."
                i += 1
                
            tmp = tmp.rstrip(".")
            
            if mask!="":    # We still have not consumed all the input.
                            # There must be some kind of netmask left.
                if '.' in mask:     # It must be a single number netmask.
                    tmp += "/"
                    ipbyte = int(mask)
                    if ipbyte>32:
                        ipbyte = 32
                    
                    tmp += str(ipbyte)
                else:
                    # Expecting a dotted quad netmask.
                    tmp += "/"
                    
                    i = 0
                    tmp = ""
                    snarfnumber = re.compile("^([0-9]+)")
                    
                    while True:
                        snarfnumber_match = snarfnumber.match(clean)
                        if snarfnumber_match is None or i==4:
                            break
                    
                        ipbyte = int(snarfnumber_match.group(1))
                        if ipbyte>255:
                            ipbyte = 255
                        i += 1
                        
                        tmp += str(ipbyte) + "."
        
                        clean = clean[len(snarfnumber_match.group(1)):]
        
                        for y in range(len(clean)):
                            if clean[y].isdigit():
                                break
                        clean = clean[y:]
                    
                    while i<4:
                        tmp = tmp + "0."
                        i += 1
                        
                    tmp = tmp.rstrip(".")
            clean = tmp
    
        pos = 0;
        (rc,pos) = self.validate(QString(clean), pos)
        if rc!=QValidator.Acceptable:
            input ="0.0.0.0"
        else:
            input = clean

        qinput.truncate(0)
        qinput += clean
        
############################################################################
class GuidedogApp(KDialog, guidedogwindow_ui.Ui_Dialog):
    def __init__(self):
        KDialog.__init__(self)
        self.setCaption("Guidedog")
        self.setButtons(KDialog.ButtonCode(KDialog.Ok | KDialog.Cancel | KDialog.Apply | KDialog.User1))
        
        self.setButtonText(KDialog.User1, i18n("About"))
        self.aboutus = KAboutApplicationDialog(KCmdLineArgs.aboutData(),self)
        
        self.updating_gui = False
        self.doc = None
        
        top = QWidget(self)
        self.setMainWidget(top)
        self.setupUi(top)
        
        self.waspreviousconfiguration = False
        self.systemconfigmodified = False

        # Connect the signals to the slots.
        self.connect(self,SIGNAL("okClicked()"),self.slotOk)
        self.connect(self,SIGNAL("cancelClicked()"),self.slotCancel)
        self.connect(self,SIGNAL("applyClicked()"),self.slotApply)
        self.connect(self,SIGNAL("user1Clicked()"),self.slotAbout)
                
        self.connect(self.routingcheckbox,SIGNAL("toggled(bool)"),self.slotRouting)
        self.connect(self.masqueradecheckbox,SIGNAL("toggled(bool)"),self.slotMasquerade)
        self.connect(self.masqueradeftpcheckbox,SIGNAL("toggled(bool)"),self.slotMasqueradeFTP)
        self.connect(self.masqueradeirccheckbox,SIGNAL("toggled(bool)"),self.slotMasqueradeIRC)
        self.connect(self.nomasqlistbox,SIGNAL("currentRowChanged(int)"),self.slotNoMasqueradeListBox)
        self.connect(self.newnomasqbutton,SIGNAL("clicked()"),self.slotNewAddressButton)
        self.connect(self.deleteenomasqbutton,SIGNAL("clicked()"),self.slotDeleteAddressButton)
        
        self.nomasqlineedit.setValidator(AddressValidator(self.nomasqlineedit))
        self.connect(self.nomasqlineedit,SIGNAL("textChanged(const QString &)"),self.slotAddressLineEdit)
        self.connect(self.nomasqlineedit,SIGNAL("returnPressed()"),self.slotAddressLineEditReturn)
        self.connect(self.forwardlistbox,SIGNAL("currentRowChanged(int)"),self.slotForwardListBox)
        self.connect(self.newforwardbutton,SIGNAL("clicked()"),self.slotNewForwardButton)
        self.connect(self.deleteforwardbutton,SIGNAL("clicked()"),self.slotDeleteForwardButton)
        self.connect(self.portprotocolcombobox,SIGNAL("activated(int)"),self.slotPortProtocolComboBox)
        self.connect(self.originalportspinbox,SIGNAL("valueChanged(int)"),self.slotOriginalPortSpinBox)
        self.connect(self.originalthismachineradio,SIGNAL("toggled(bool)"),self.slotOriginalMachineRadio)
        self.connect(self.originalspecifyradio,SIGNAL("toggled(bool)"),self.slotOriginalSpecifyRadio)
        
        self.originalspecifylineedit.setValidator(IPValidator(self.originalspecifylineedit))
        self.connect(self.originalspecifylineedit,SIGNAL("textChanged(const QString &)"),self.slotOriginalSpecifyLineEdit)
        self.connect(self.newportspinbox,SIGNAL("valueChanged(int)"),self.slotNewPortSpinBox)
        self.connect(self.newthismachineradio,SIGNAL("toggled(bool)"),self.slotNewMachineRadio)
        self.connect(self.newspecifyradio,SIGNAL("toggled(bool)"),self.slotNewSpecifyRadio)
        
        self.newspecifylineedit.setValidator(IPValidator(self.newspecifylineedit))
        self.connect(self.newspecifylineedit,SIGNAL("textChanged(const QString &)"),self.slotNewSpecifyLineEdit)
        self.connect(self.commentlineedit,SIGNAL("textChanged(const QString &)"),self.slotCommentLineEdit)
        self.connect(self.disablecheckbox,SIGNAL("toggled(bool)"),self.slotDisableGuidedog)
        self.connect(self.descriptionedit,SIGNAL("textChanged()"),self.slotDescriptionChanged)
        self.connect(self.importbutton,SIGNAL("clicked()"),self.slotImportButton)
        self.connect(self.exportbutton,SIGNAL("clicked()"),self.slotExportButton)
        
    def initialise(self,god):
        self.superusermode = god
        if not self.superusermode:
            self.enableButtonOk(False)
            self.enableButtonApply(False)
            
        self.doc = GuidedogDoc()
        self.readOptions();
        self.openDefault();
        self.updatinggui = True
        self.syncGUIFromDoc()
        self.updatinggui = False

        return True
        
    def readOptions(self):
        config = KGlobal.config()
        general_group = config.group("General")
        size = general_group.readEntry("Geometry",QVariant(QSize(640,640))).toSize()
        if not size.isEmpty():
            self.resize(size)

        self.command_runner_size = general_group.readEntry("CommandrunnerGeometry",QVariant(QSize(640,640))).toSize()
        if self.command_runner_size.isEmpty():
            self.command_runner_size.setWidth(400)
            self.command_runner_size.setHeight(300)
        
    def saveOptions(self):
        config = KGlobal.config()

        general_group = config.group("General")
        size = self.size()
        general_group.writeEntry("Geometry",QVariant(self.size()))
        general_group.writeEntry("CommandrunnerGeometry",QVariant(self.command_runner_size))
        config.sync()
        
    def syncGUIFromDoc(self):
        self.routingcheckbox.setChecked(self.doc.isRouting())
        self.masqueradecheckbox.setChecked(self.doc.isMasquerade())
        self.masqueradeftpcheckbox.setChecked(self.doc.isMasqueradeFTP())
        self.masqueradeirccheckbox.setChecked(self.doc.isMasqueradeIRC())
        self.descriptionedit.setPlainText(self.doc.description)
        self.disablecheckbox.setChecked(self.doc.isDisabled())
    
        # Populate the nomasq list box.
        self.nomasqlistbox.clear()
        for item in self.doc.nomasqueradelist:
            self.nomasqlistbox.addItem(item.getAddress())
        
        # Select the first item.
        if len(self.doc.nomasqueradelist)!=0:
            self.nomasqlistbox.setCurrentRow(0)
            self.nomasqlineedit.setText(self.doc.nomasqueradelist[self.nomasqlistbox.currentRow()].getAddress())
    
        # Populate the forward rule box.
        self.forwardlistbox.clear()
        for item in self.doc.forwardrulelist:
            self.forwardlistbox.addItem(item.getSummary())
        
        # Select the first item.    
        if len(self.doc.forwardrulelist)!=0:
            self.forwardlistbox.setCurrentRow(0)
            
            if self.forwardlistbox.currentRow()!=-1:
                self.setForwardRule(self.doc.forwardrulelist[self.forwardlistbox.currentRow()])
            else:
                self.setForwardRule(None)
        
        self.enableGUIStuff()
    
    def setForwardRule(self,rule):  # GuidedogPortForwardRule *rule
        if rule is None:
            self.originalportspinbox.setValue(0)
            self.portprotocolcombobox.setCurrentIndex(0)
            self.originalthismachineradio.setChecked(True)
            self.originalspecifyradio.setChecked(False)
            self.originalspecifylineedit.setText("")
            self.newportspinbox.setValue(0)
            self.newthismachineradio.setChecked(True)
            self.newspecifyradio.setChecked(True)
            self.newspecifylineedit.setText("")
            self.commentlineedit.setText("")
        else:
            self.originalportspinbox.setValue(rule.originalport);
            self.portprotocolcombobox.setCurrentIndex([0,1][rule.iptype==PortForwardRule.IPPROTO_UDP]);
            self.originalthismachineradio.setChecked(not rule.specifyoriginal)
            self.originalspecifyradio.setChecked(rule.specifyoriginal)
            self.originalspecifylineedit.setText(rule.originaladdress.getAddress())
            self.newportspinbox.setValue(rule.newport)
            self.newthismachineradio.setChecked(not rule.specifynewaddress)
            self.newspecifyradio.setChecked(rule.specifynewaddress)
            self.newspecifylineedit.setText(rule.newaddress.getAddress())
            self.commentlineedit.setText(rule.comment)
        
    def enableGUIStuff(self):
        active = not self.doc.isDisabled()
        self.routingcheckbox.setEnabled(active)
        self.masqueradecheckbox.setEnabled(self.doc.isRouting() and active)
        self.masqueradeftpcheckbox.setEnabled(self.doc.isRouting() and self.doc.isMasquerade() and active)
        self.masqueradeirccheckbox.setEnabled(self.doc.isRouting() and self.doc.isMasquerade() and active)
        gotaddys = len(self.doc.nomasqueradelist)!=0
        self.nomasqlistbox.setEnabled(self.doc.isRouting() and self.doc.isMasquerade() and active and gotaddys)
        self.nomasqlineedit.setEnabled(self.doc.isRouting() and self.doc.isMasquerade() and active and gotaddys)
        self.newnomasqbutton.setEnabled(self.doc.isRouting() and self.doc.isMasquerade() and active)
        self.deleteenomasqbutton.setEnabled(self.doc.isRouting() and self.doc.isMasquerade() and active and gotaddys)
    
        gotrules = len(self.doc.forwardrulelist)!=0
        self.forwardlistbox.setEnabled(active and gotrules)
        self.newforwardbutton.setEnabled(active)
        self.deleteforwardbutton.setEnabled(active and gotrules)
    
        self.originalportspinbox.setEnabled(active and gotrules)
        self.portprotocolcombobox.setEnabled(active and gotrules)
        self.originalthismachineradio.setEnabled(active and gotrules)
        self.originalspecifyradio.setEnabled(active and gotrules)
        self.originalspecifylineedit.setEnabled(active and gotrules)
    
        self.newportspinbox.setEnabled(active and gotrules)
        self.newthismachineradio.setEnabled(active and gotrules)
        self.newspecifyradio.setEnabled(active and gotrules)
        self.newspecifylineedit.setEnabled(active and gotrules)
    
        self.commentlineedit.setEnabled(active and gotrules)
    
        if gotrules:
            rule = self.doc.forwardrulelist[self.forwardlistbox.currentRow()]
            self.originalspecifylineedit.setEnabled(active and rule.specifyoriginal)
            self.newspecifylineedit.setEnabled(active and rule.specifynewaddress)
        
    @lockgui
    def slotOk(self):
        SYSTEM_RC_GUIDEDOG
        (rc,errorstring) = self.doc.saveScript(SYSTEM_RC_GUIDEDOG)
        if not rc:
            QMessageBox.critical(self,i18n("An error occurred while writing the script to disk.\n\n"
                "(Detailed message: \"%1\")",errorstring))
            return
        
        if self.applyScript(True):
            self.saveOptions()
            self.accept()
   
    @lockgui
    def slotCancel(self):
        if self.waspreviousconfiguration and self.systemconfigmodified:
            # This is where things become complex.
            # Should we try to restore things to how they were before this program started?
            response = KMessageBox.warningYesNoCancel(self,i18n(
                    "The system's routing configuration has been modified.\n\n"
                    "Shall I restore it to the previous configuration?\n\n"
                    "These changes may disrupt current network connections."))
            
            if response==KMessageBox.Yes:
                # "Yes, revert to the previous settings."
                self.openDefault()
                if self.applyScript(False):
                    self.saveOptions()
                    self.accept()

            elif response==KMessageBox.No:
                # "Just leave the settings alone and piss off!!"
                self.saveOptions()
                self.accept()

            elif KMessageBox.Cancel:
                # "Forget I ever pressed the Cancel button."
                pass
            
        else:
            # Simple Cancel.
            self.saveOptions()
            self.accept()
    
    @lockgui
    def slotApply(self):
        self.applyScript(True)

    @lockgui
    def slotAbout(self,):
        self.aboutus.exec_()
    
    @lockgui
    def slotRouting(self,on):
        self.doc.setRouting(on)
        self.enableGUIStuff()
    
    @lockgui
    def slotMasquerade(self, on):
        self.doc.setMasquerade(on)
        self.enableGUIStuff()

    @lockgui
    def slotMasqueradeFTP(self, on):
        self.updatinggui = True
        self.doc.setMasqueradeFTP(on)

    @lockgui
    def slotMasqueradeIRC(self, on):
        self.doc.setMasqueradeIRC(on)
    
    @lockgui
    def slotForwardListBox(self, row):
        rule = self.doc.forwardrulelist[row]
        self.setForwardRule(rule)
        self.enableGUIStuff()

    @lockgui
    def slotDisableGuidedog(self, on):
        self.doc.setDisabled(on)
        self.syncGUIFromDoc()
    
    @lockgui
    def slotImportButton(self):
        filename = KFileDialog.getOpenFileName(KUrl(), "*", self, i18n("Import Configuration"))
        if filename.isEmpty():
            self.updatinggui = False
            return
    
        tmpdoc = GuidedogDoc()
        (rc,errorstring) = tmpdoc.openScript(filename)
        if not rc:
            # Stick up a good ol' error message.
            KMessageBox.error(self,i18n("Guidedog was unable to read the file at %1 as being a Guidedog script.\n"
                "This probably means that this file in not actually a Guidedog script.\n\n"
                "(Detailed message \"%2\")",filename,errorstring),i18n("Error during import"))
            self.updatinggui = False
            return
    
        # That loaded ok. Re-configure the GUI.
    
        # Switcherroo
        self.doc = tmpdoc
        self.syncGUIFromDoc()

    @lockgui
    def slotExportButton(self):
        filename = KFileDialog.getSaveFileName(KUrl(), "*", self, i18n("Export Configuration"))
        if filename.isEmpty():
            self.updatinggui = False
            return
    
        (rc,errorstring) = self.doc.saveScript(filename)
        if not rc:
            KMessageBox.error(self,i18n("An error occurred while writing the script to %1.\n\n"
                "(Detailed message: \"%2\")",filename,errorstring))
    
    @lockgui
    def slotDescriptionChanged(self):
        self.doc.description = self.descriptionedit.toPlainText()
    
    @lockgui
    def slotNoMasqueradeListBox(self, row):
        self.nomasqlineedit.setText(self.doc.nomasqueradelist[row].getAddress())
    
    @lockgui
    def slotNewAddressButton(self):
        self.doc.nomasqueradelist.append(IPRange("new.address"))
        self.nomasqlistbox.addItem((self.doc.nomasqueradelist[-1]).getAddress())
        self.nomasqlineedit.setText((self.doc.nomasqueradelist[-1]).getAddress())
        self.nomasqlistbox.setCurrentRow(self.nomasqlistbox.count()-1)
        self.enableGUIStuff()

    @lockgui
    def slotDeleteAddressButton(self):
        thisiprange = self.doc.nomasqueradelist[self.nomasqlistbox.currentRow()]
    
        i = self.nomasqlistbox.currentRow()
        if i>=0:
            self.nomasqlistbox.takeItem(i)
            del self.doc.nomasqueradelist[i]
            if len(self.doc.nomasqueradelist)!=0:
                self.nomasqlineedit.setText(self.doc.nomasqueradelist[0].getAddress())
                self.nomasqlistbox.setCurrentRow(0)
            else:
                self.nomasqlineedit.setText("")
                self.nomasqlistbox.setDisabled(True)
                self.deleteenomasqbutton.setDisabled(True)
                self.nomasqlineedit.setDisabled(True)
            
            if self.nomasqlistbox.currentRow()==-1:
                self.nomasqlistbox.setCurrentRow(0)
        
        self.enableGUIStuff()
    
    @lockgui
    def slotAddressLineEdit(self, s):
        i = self.nomasqlistbox.currentRow()
        self.doc.nomasqueradelist[i].setAddress(unicode(s))
        self.nomasqlistbox.currentItem().setText(s)
    
    @lockgui
    def slotAddressLineEditReturn(self):
        pass
    
    @lockgui
    def slotNewForwardButton(self):
        newrule = PortForwardRule()
        self.doc.forwardrulelist.append(newrule)
        self.forwardlistbox.addItem(newrule.getSummary())
        self.setForwardRule(newrule)
        self.forwardlistbox.setCurrentRow(self.forwardlistbox.count()-1)
        self.enableGUIStuff()
    
    @lockgui
    def slotDeleteForwardButton(self):
        rule = self.doc.forwardrulelist[self.forwardlistbox.currentRow()]
    
        i = self.forwardlistbox.currentRow()
        if i>=0:
            self.forwardlistbox.takeItem(i)
            del self.doc.forwardrulelist[i]
            self.forwardlistbox.setCurrentRow(0)
            if self.forwardlistbox.currentRow()==-1:
                self.setForwardRule(None)
            else:
                self.setForwardRule(self.doc.forwardrulelist[self.forwardlistbox.currentRow()])
            self.enableGUIStuff()
    
    @lockgui
    def slotOriginalMachineRadio(self, on):
        self.originalthismachineradio.setChecked(True)
        self.originalspecifyradio.setChecked(False)
    
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule!=0:
            rule.specifyoriginal = False
            self.forwardlistbox.currentItem().setText(rule.getSummary())
        
        self.enableGUIStuff()
    
    @lockgui
    def slotOriginalSpecifyRadio(self, on):
        self.originalspecifyradio.setChecked(True)
        self.originalthismachineradio.setChecked(False)
    
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule is not None:
            rule.specifyoriginal = True
            self.forwardlistbox.currentItem().setText(rule.getSummary())
        
        self.enableGUIStuff()
    
    @lockgui
    def slotNewMachineRadio(self, on):
        self.newthismachineradio.setChecked(True)
        self.newspecifyradio.setChecked(False)
    
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule is not None:
            rule.specifynewaddress = False
            self.forwardlistbox.item(i).setText(rule.getSummary())
        
        self.enableGUIStuff()
    
    @lockgui
    def slotNewSpecifyRadio(self, on):
        self.newspecifyradio.setChecked(True)
        self.newthismachineradio.setChecked(False)
    
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule is not None:
            rule.specifynewaddress = True
            self.forwardlistbox.item(i).setText(rule.getSummary())
        
        self.enableGUIStuff()
    
    @lockgui
    def slotOriginalPortSpinBox(self, x):
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule is not None:
            rule.originalport = x;
            # Update the forward listbox.
            self.forwardlistbox.currentItem().setText(rule.getSummary())

    @lockgui
    def slotNewPortSpinBox(self,x):
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule is not None:
            rule.newport = x
            # Update the forward listbox.
            self.forwardlistbox.item(i).setText(rule.getSummary())
    
    @lockgui
    def slotPortProtocolComboBox(self,x):
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule is not None:
            rule.iptype = [PortForwardRule.IPPROTO_UDP,PortForwardRule.IPPROTO_TCP][x==0]
            # Update the forward listbox.
            self.forwardlistbox.currentItem().setText(rule.getSummary())
    
    @lockgui
    def slotOriginalSpecifyLineEdit(self,s):
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule is not None:
            rule.originaladdress.setAddress(unicode(s))
            # Update the forward listbox.
            self.forwardlistbox.currentItem().setText(rule.getSummary())
    
    @lockgui
    def slotNewSpecifyLineEdit(self, s):
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule is not None:
            rule.newaddress.setAddress(unicode(s))
            # Update the forward listbox.
            self.forwardlistbox.currentItem().setText(rule.getSummary())
    
    @lockgui
    def slotCommentLineEdit(self, s):
        i = self.forwardlistbox.currentRow()
        rule = self.doc.forwardrulelist[i]
        if rule is not None:
            rule.comment = unicode(s)
            # Update the forward listbox.
            self.forwardlistbox.currentItem().setText(rule.getSummary())
    
    def applyScript(self, warnfirst):
        tmpfile = KTemporaryFile()
        cr = CommandRunner(self)
        if not self.command_runner_size.isEmpty():
            cr.resize(self.command_runner_size)
    
        if not self.doc.isDisabled():
            # Normal apply.
            
            if not warnfirst or KMessageBox.warningContinueCancel(self,
                    i18n("You are about to modify the system's routing configuration.\n"
                    "These changes may disrupt current network connections.\n\n"
                    "Do you wish to continue?"))==KMessageBox.Continue:    
                    
                    # Is our temp file working?
#                if(tmpfile.status()!=0) {
#                    QMessageBox.critical(this,i18n("An error occurred while trying to modify system's routing configuration.\nThe operating system has this to report about the error: %1")
#                        .arg(strerror(tmpfile.status())));
#                    return False
#                }
                # Write the firewall script into the temp file.
                tmpfile.open()
                (rc,errorstring) = self.doc.writeScript(QTextStream(tmpfile))
                if not rc:
                    return False
                tmpfilename = tmpfile.fileName()
                
                # Close and flush the file.
                tmpfile.close()
                if tmpfile.error()!=QFile.NoError:
                    KMessageBox.error(self,
                        i18n("An error occurred while modifying the routing configuration.\n"
                        "The operating system has this to report about the error: %1",tmpfile.errorString()))
                    return False

                # Now we run the tmp script in our super friendly window.
                cr.setCaption(i18n("Modify Routing Configuration"))
                cr.setHeading(i18n("Configuring...\n\nOutput:"))
                cr.run("export GUIDEDOG_VERBOSE=1;/bin/sh '"+unicode(tmpfilename)+"'")
                self.systemconfigmodified = True
                self.command_runner_size = cr.size()
                return True
            
            return False
        else:
            # If we are to actually disable the routing because the user has set
            # the Disable checkbox in the advanced section, then we use a different
            # warning question and use a different method to reset the network stack.
            if not warnfirst or KMessageBox.warningYesNo(self,
                    i18n("You are about to reset the system's routing configuration.\n"
                    "These changes may also disrupt current network connections.\n\n"
                    "Do you wish to continue?"))==KMessageBox.Yes:
    
                if self.resetSystemConfiguration():
                    return False
                else:
                    self.systemconfigmodified = True
                    return True
        return False

    def resetSystemConfiguration(self):
        cr = CommandRunner(self)
        if not self.command_runner_size.isEmpty():
            cr.resize(self.command_runner_size)
    
        cr.setCaption(i18n("Resetting system configuration"))
        cr.setHeading(i18n("Resetting system configuration...\n\nOutput:"))
        cr.run(QString(
            "echo \"Using ipchains.\"\n"
            "echo \"Resetting nat table rules.\"\n"
            "PATH=/bin:/sbin:/usr/bin:/usr/sbin\n"
            "/sbin/sysctl -w net.ipv4.ip_forward=0\n"
            "iptables -t nat -F\n"
            "iptables -t nat -X\n"
            "iptables -t nat -P PREROUTING ACCEPT\n"
            "iptables -t nat -P POSTROUTING ACCEPT\n"
            "iptables -t nat -P OUTPUT ACCEPT\n"
            "echo \"Finished.\"\n"))
        self.command_runner_size = cr.size()
        return True
    
    def openDefault(self):
        filename = SYSTEM_RC_GUIDEDOG
        fileinfo = QFileInfo(SYSTEM_RC_GUIDEDOG)
    
        if not self.superusermode:
            return  # Sorry, if you are not root then you get no default firewall.
    
        if not fileinfo.exists():
            # There doesn't appear to be a previous Guarddog firewall script.
            # Just warn the user about the ramifications.
            KMessageBox.information(self,i18n("Guidedog was unable to find a Guidedog script at %1.\n"
            "This is probably ok, it just means that this is the first time Guidedog has been run on this system.\n"
            "But please be aware that the settings shown may not represent the system's current routing configuration.\n"
            "Your Guidedog settings will take effect once you use the 'Apply' button or exit Guidedog using 'Ok'.",
            filename))
        else:
            (rc,errorstring) = self.doc.openScript(filename)
            if not rc:
                self.doc.factoryDefaults()
                # We were unable to open the guarddog firewall.
                KMessageBox.error(self,
                    i18n("Guidedog was unable to read the file at %1 as being a Guidedog script.\n"
                    "Please be aware that the settings shown may not represent the system's current routing configuration.\n\n"
                    "(Detailed message \"%2\")",filename,errorstring))
            else:
                self.waspreviousconfiguration = True
        
def main():
    global kapp

    appName     = "guidedog"
    catalog     = "guidedog"
    programName = ki18n ("Guidedog")
    version     = "1.1.0"
    description = ki18n ("Advanced Networking utility")
    license     = KAboutData.License_GPL
    copyright   = ki18n ("(c) 2001-2008 Simon Edwards")
    text        = ki18n ("Utility for easily configuring advanced networking features.")
    homePage    = "http://www.simonzone.com/software/guidedog/"
    bugEmail    = "simon@simonzone.com"
    aboutData   = KAboutData (appName, catalog, programName, version, description,
                                license, copyright, text, homePage, bugEmail)

    KCmdLineArgs.init(sys.argv, aboutData)
    KCmdLineArgs.StdCmdLineArgs()
    kapp = KApplication()

    guidedogapp = GuidedogApp()
    if guidedogapp.initialise(os.getuid()==0):
        if os.getuid()!=0:
            KMessageBox.information(None,i18n(
                "Since you do not have superuser privilages, Guidedog is running with reduced functionality."
                " Scripts may be Imported/Exported, but  the system's networking settings may not be changed."))
        guidedogapp.show()
        sys.exit(kapp.exec_())

if __name__=="__main__":
    main()
