def run(): """this is the main function of the program""" # Startup Process config = Config("config/easywall.ini") loglevel = config.get_value("LOG", "level") to_stdout = config.get_value("LOG", "to_stdout") to_files = config.get_value("LOG", "to_files") logpath = config.get_value("LOG", "filepath") logfile = config.get_value("LOG", "filename") log = Log(loglevel, to_stdout, to_files, logpath, logfile) info("Starting up easywall...") ensure_rules_files(config) event_handler = ModifiedHandler() observer = Observer() observer.schedule(event_handler, config.get_value("RULES", "filepath")) observer.start() info("easywall is up and running.") # waiting for file modifications try: while True: sleep(2) except KeyboardInterrupt: info("KeyboardInterrupt received, starting shutdown") finally: shutdown(observer, config, log)
class Log(object): """This class is a wrapper class around the logging module""" def __init__(self, configpath): self.config = Config(configpath) self.loglevel = self.get_level(self.config.get_value("LOG", "level")) # create logger root = logging.getLogger() root.handlers.clear() # workaround for default stdout handler root.setLevel(self.loglevel) # create formatter and add it to the handlers formatter = logging.Formatter( '[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s') # create console handler -> logs are always written to stdout if bool(self.config.get_value("LOG", "to_stdout")): std_handler = logging.StreamHandler(stdout) std_handler.setLevel(self.loglevel) std_handler.setFormatter(formatter) root.addHandler(std_handler) # create file handler if enabled in configuration if bool(self.config.get_value("LOG", "to_files")): # create log filepath if not exists create_folder_if_not_exists( self.config.get_value("LOG", "filepath")) file_handler = logging.FileHandler( self.config.get_value("LOG", "filepath") + "/" + self.config.get_value("LOG", "filename") ) file_handler.setLevel(self.loglevel) file_handler.setFormatter(formatter) root.addHandler(file_handler) def close_logging(self): """This function gently closes all handlers before exiting the software""" root = logging.getLogger() for handler in root.handlers: handler.close() root.removeFilter(handler) def get_level(self, log_level): """This internal function determines the log_level of the logging class""" level = logging.NOTSET if log_level == "debug": level = logging.DEBUG elif log_level == "info": level = logging.INFO elif log_level == "warning": level = logging.WARNING elif log_level == "error": level = logging.ERROR elif log_level == "critical": level = logging.CRITICAL return level
class Main(): """TODO: Doku.""" def __init__(self) -> None: """TODO: Doku.""" self.cfg = Config(CONFIG_PATH) self.cfg_log = Config(LOG_CONFIG_PATH) loglevel = self.cfg_log.get_value("LOG", "level") to_stdout = self.cfg_log.get_value("LOG", "to_stdout") to_files = self.cfg_log.get_value("LOG", "to_files") logpath = self.cfg_log.get_value("LOG", "filepath") logfile = self.cfg_log.get_value("LOG", "filename") self.log = Log(str(loglevel), bool(to_stdout), bool(to_files), str(logpath), str(logfile)) info("starting easywall") self.easywall = Easywall(self.cfg) self.event_handler = ModifiedHandler(self.apply) self.observer = Observer() self.stop_flag = False info("easywall has been started") def apply(self, filename: str) -> None: """TODO: Doku.""" info("starting apply process from easywall") delete_file_if_exists(filename) self.easywall.apply() def start_observer(self) -> None: """ Keep the main process running until it should be stopped. if someone is pressing ctrl + C the software will initiate the stop process """ self.observer.schedule(self.event_handler, ".") self.observer.start() try: while not self.stop_flag: sleep(2) except KeyboardInterrupt: info("KeyboardInterrupt received, starting shutdown") finally: self.shutdown() def shutdown(self) -> None: """Stop all threads and shut the software down gracefully.""" info("starting shutdown") self.observer.stop() delete_file_if_exists(".acceptance") self.observer.join() info("shutdown completed") self.log.close_logging()
def create_rule_files(cfg: Config): """ the function checks if the rule files exist and creates them if they don't exist """ filepath = cfg.get_value("RULES", "filepath") create_folder_if_not_exists(filepath) filename = "" for ruletype in ["blacklist", "whitelist", "tcp", "udp", "custom"]: filename = cfg.get_value("RULES", ruletype) create_file_if_not_exists("{}/{}".format(filepath, filename))
class Main(object): """ TODO: Doku """ def __init__(self, debug: bool = False) -> None: self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(str(loglevel), bool(to_stdout), bool(to_files), str(logpath), str(logfile)) self.login_attempts = self.cfg.get_value("WEB", "login_attempts") self.login_bantime = self.cfg.get_value("WEB", "login_bantime") self.ip_ban = IpBan(app=APP, ban_count=self.login_attempts, ban_seconds=self.login_bantime, ipc=True) self.ip_ban.url_pattern_add('^/static.*$', match_type='regex') info("starting easywall-web") self.rules_handler = RulesHandler() self.rules_handler.ensure_files_exist() if debug is True: port = self.cfg.get_value("WEB", "bindport") host = self.cfg.get_value("WEB", "bindip") APP.config.from_object('easywall_web.__main__.DevelopmentConfig') APP.run(str(host), str(port)) else: APP.config.from_object('easywall_web.__main__.ProductionConfig')
class Acceptance(object): """ the class contains function for checking the user acceptance after applying new firewall rules """ def __init__(self): """the init function creates some class variables""" self.config = Config("config/easywall.ini") self.enabled = bool(self.config.get_value("ACCEPTANCE", "enabled")) self.filename = self.config.get_value("ACCEPTANCE", "filename") debug("Acceptance Process initialized. Status: " + str(self.enabled) + " Filename: " + self.filename) def reset(self): """the function is called then the user did not accept the changes""" if self.enabled: create_file_if_not_exists(self.filename) write_into_file(self.filename, "false") debug("Acceptance has been reset.") def check(self): """the function checks for acceptance and executes the next steps""" if self.enabled: seconds = int(self.config.get_value("ACCEPTANCE", "time")) debug( "Starting Acceptance Check... waiting for " + str(seconds) + " seconds") while seconds > 0: sleep(1) seconds = seconds - 1 with open(self.filename, 'r') as accfile: accepted = accfile.read() accepted = accepted.replace("\n", "") if accepted == "true": debug("Acceptance Process Result: Accepted") return True else: debug( "Acceptance Process Result: Not Accepted (file content: " + accepted + ")") return False else: debug("Acceptance is disabled. Skipping check.") return True
def run(): """ this is the first and main function of the program """ config = Config(CONFIG_PATH) loglevel = config.get_value("LOG", "level") to_stdout = config.get_value("LOG", "to_stdout") to_files = config.get_value("LOG", "to_files") logpath = config.get_value("LOG", "filepath") logfile = config.get_value("LOG", "filename") log = Log(loglevel, to_stdout, to_files, logpath, logfile) info("executing startup sequence...") create_rule_files(config) event_handler = ModifiedHandler() observer = Observer() observer.schedule(event_handler, config.get_value("RULES", "filepath")) observer.start() info("startup sequence successfully finished") start_observer(observer, config, log)
class TestConfig(unittest.TestCase): """ this class contains all test functions for the config module """ def setUp(self): content = """[TEST] teststring = string testboolean = true testint = 1 testfloat = 1.1 """ create_file_if_not_exists("test.ini") write_into_file("test.ini", content) self.config = Config("test.ini") def tearDown(self): delete_file_if_exists("test.ini") def test_get_value_error(self): self.assertEqual(self.config.get_value("TEST", "notexistent"), "") def test_get_value_bool(self): self.assertEqual(self.config.get_value("TEST", "testboolean"), True) def test_get_value_int(self): self.assertEqual(self.config.get_value("TEST", "testint"), 1) def test_get_value_float(self): self.assertEqual(self.config.get_value("TEST", "testfloat"), float(1.1)) def test_get_sections(self): self.assertIn("TEST", self.config.get_sections()) def test_set_value_success(self): self.assertEqual(self.config.set_value("TEST", "teststring", "erfolg"), True) def test_set_value_fail_section(self): self.assertEqual(self.config.set_value("TEST2", "asd", "asd"), False)
def shutdown(observer: Observer, config: Config, log: Log): """ the function stops all threads and shuts the software down gracefully """ info("starting shutdown...") observer.stop() delete_file_if_exists(".running") delete_file_if_exists(config.get_value("ACCEPTANCE", "filename")) observer.join() info("shutdown successfully completed") log.close_logging() exit(0)
class Main(object): """ TODO: Doku """ def __init__(self, debug=False): APP.secret_key = os.urandom(12) self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(loglevel, to_stdout, to_files, logpath, logfile) info("starting easywall-web") self.is_first_run = not folder_exists("rules") self.rules_handler = RulesHandler() if self.is_first_run: self.rules_handler.rules_firstrun() if debug is True: port = self.cfg.get_value("WEB", "bindport") host = self.cfg.get_value("WEB", "bindip") APP.run(host, port, debug)
class Main(object): """ TODO: Doku """ def __init__(self, debug: bool = False) -> None: self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(str(loglevel), bool(to_stdout), bool(to_files), str(logpath), str(logfile)) info("starting easywall-web") self.is_first_run = not folder_exists("rules") self.rules_handler = RulesHandler() if self.is_first_run: self.rules_handler.ensure_files_exist() if debug is True: port = self.cfg.get_value("WEB", "bindport") host = self.cfg.get_value("WEB", "bindip") APP.config.from_object('easywall_web.__main__.DevelopmentConfig') APP.run(str(host), str(port)) else: APP.config.from_object('easywall_web.__main__.ProductionConfig')
class TestPasswd(unittest.TestCase): """TODO: Doku.""" def setUp(self) -> None: """TODO: Doku.""" prepare_configuration() def tearDown(self) -> None: """TODO: Doku.""" restore_configuration() @patch("builtins.input") @patch("getpass.getpass") def test_init(self, input: Any, getpass: Any) -> None: """TODO: Doku.""" input.return_value = "test" getpass.return_value = "test" from easywall.web.passwd import Passwd Passwd() self.config = Config(WEB_CONFIG_PATH) self.assertEqual(self.config.get_value("WEB", "username"), "test")
def run(): """this is the main function of the program""" # Startup Process masterlog = Log("config/easywall.ini") logging.info("Starting up easywall...") masterconfig = Config("config/easywall.ini") ensure_rules_files(masterconfig) event_handler = ModifiedHandler() observer = Observer() observer.schedule( event_handler, masterconfig.get_value("RULES", "filepath")) observer.start() logging.info("easywall is up and running.") # waiting for file modifications try: while True: time.sleep(2) except KeyboardInterrupt: shutdown(observer, masterconfig, masterlog) shutdown(observer, masterconfig, masterlog)
class Main(object): """ TODO: Doku """ def __init__(self): self.cfg = Config(CONFIG_PATH) loglevel = self.cfg.get_value("LOG", "level") to_stdout = self.cfg.get_value("LOG", "to_stdout") to_files = self.cfg.get_value("LOG", "to_files") logpath = self.cfg.get_value("LOG", "filepath") logfile = self.cfg.get_value("LOG", "filename") self.log = Log(loglevel, to_stdout, to_files, logpath, logfile) info("starting easywall") self.is_first_run = not folder_exists("rules") self.rules_handler = RulesHandler() if self.is_first_run: self.rules_handler.rules_firstrun() self.easywall = Easywall(self.cfg) self.event_handler = ModifiedHandler(self.apply) self.observer = Observer() self.stop_flag = False info("easywall has been started") def apply(self, filename: str) -> None: """ TODO: Doku """ info("starting apply process from easywall") delete_file_if_exists(filename) self.easywall.apply() def start_observer(self) -> None: """ this function is called to keep the main process running if someone is pressing ctrl + C the software will initiate the stop process """ self.observer.schedule(self.event_handler, ".") self.observer.start() try: while not self.stop_flag: sleep(2) except KeyboardInterrupt: info("KeyboardInterrupt received, starting shutdown") finally: self.shutdown() def shutdown(self) -> None: """ the function stops all threads and shuts the software down gracefully """ info("starting shutdown") self.observer.stop() delete_file_if_exists(".acceptance") self.observer.join() info("shutdown completed") self.log.close_logging()
class Easywall(object): """ the class contains the main functions for the easywall core such as applying a new configuration or listening on rule file changes """ def __init__(self, configpath: str): info("Applying new configuration.") self.create_running_file() self.config = Config(configpath) self.iptables = Iptables(configpath) self.acceptance = Acceptance(configpath) self.ipv6 = self.config.get_value("IPV6", "enabled") self.filepath = None self.filename = None self.date = None self.apply() self.delete_running_file() def apply(self): """the function applies the configuration from the rule files""" self.acceptance.reset() # save current ruleset and reset iptables for clean setup self.iptables.save() self.iptables.reset() # drop intbound traffic and allow outbound traffic self.iptables.add_policy("INPUT", "DROP") self.iptables.add_policy("OUTPUT", "ACCEPT") # allow loopback access self.iptables.add_append("INPUT", "-i lo -j ACCEPT") # allow established or related connections self.iptables.add_append( "INPUT", "-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT") # Block remote packets claiming to be from a loopback address. self.iptables.add_append( "INPUT", "-s 127.0.0.0/8 ! -i lo -j DROP", False, True) self.iptables.add_append("INPUT", "-s ::1/128 ! -i lo -j DROP", True) # Apply ICMP Rules self.apply_icmp() # Block IP-addresses from blacklist self.apply_blacklist() # Allow IP-addresses from whitelist self.apply_whitelist() # Allow TCP Ports self.apply_rules("tcp") # Allow UDP Ports self.apply_rules("udp") # log and reject all other packages self.iptables.add_append( "INPUT", "-j LOG --log-prefix \" easywall[other]: \"") self.iptables.add_append("INPUT", "-j REJECT") self.check_acceptance() def apply_icmp(self): """the function applies the icmp rules""" for icmptype in [0, 3, 8, 11]: self.iptables.add_append( "INPUT", "-p icmp --icmp-type " + str(icmptype) + " -m conntrack --ctstate NEW -j ACCEPT", False, True) if self.ipv6 is True: for icmptype in [ 1, 2, 3, 4, 128, 133, 134, 135, 136, 137, 141, 142, 151, 152, 153]: self.iptables.add_append( "INPUT", "-p ipv6-icmp --icmpv6-type " + str(icmptype) + " -j ACCEPT", True) def apply_blacklist(self): """the function applies the blacklist rules from the rules file""" for ipaddr in self.get_rule_list("blacklist"): if ":" in ipaddr: self.iptables.add_append( "INPUT", "-s " + ipaddr + " -j LOG --log-prefix \" easywall[blacklist]: \"", True) self.iptables.add_append( "INPUT", "-s " + ipaddr + " -j DROP", True) else: self.iptables.add_append( "INPUT", "-s " + ipaddr + " -j LOG --log-prefix \" easywall[blacklist]: \"", False, True) self.iptables.add_append( "INPUT", "-s " + ipaddr + " -j DROP", False, True) def apply_whitelist(self): """the function applies the whitelist rules from the rules file""" for ipaddr in self.get_rule_list("whitelist"): if ":" in ipaddr: self.iptables.add_append( "INPUT", "-s " + ipaddr + " -j ACCEPT", True) else: self.iptables.add_append( "INPUT", "-s " + ipaddr + " -j ACCEPT", False, True) def apply_rules(self, ruletype): """the function applies the rules from the rules file""" for port in self.get_rule_list(ruletype): if ":" in port: self.iptables.add_append( "INPUT", "-p " + ruletype + " --match multiport --dports " + port + " -m conntrack --ctstate NEW -j ACCEPT") else: self.iptables.add_append( "INPUT", "-p " + ruletype + " --dport " + port + " -m conntrack --ctstate NEW -j ACCEPT") def check_acceptance(self): """the function checks for accetance of the new applied configuration""" info("Checking acceptance.") if self.acceptance.check() is False: info("Configuration not accepted, rolling back.") self.iptables.restore() else: self.rotate_backup() self.iptables.save() info("New configuration was applied.") def get_rule_list(self, ruletype): """the function retrieves the rules from the rules file""" rule_list = [] with open(self.config.get_value("RULES", "filepath") + "/" + self.config.get_value("RULES", ruletype), 'r') as rulesfile: for rule in rulesfile.read().split('\n'): if rule.strip() != "": rule_list.append(rule) return rule_list def rotate_backup(self): """the function rotates the backup files to have a clean history of files""" self.filepath = self.config.get_value("BACKUP", "filepath") self.filename = self.config.get_value("BACKUP", "ipv4filename") self.date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") debug("rotating backup files in folder " + self.filepath + " -> add prefix " + self.date) self.rename_backup_file() if self.ipv6 is True: self.filename = self.config.get_value("BACKUP", "ipv6filename") self.rename_backup_file() def rename_backup_file(self): """the function renames a backup file""" rename_file("{}/{}".format(self.filepath, self.filename), "{}/{}_{}".format(self.filepath, self.date, self.filename)) def create_running_file(self): """the function creates a file in the main directory called .running""" create_file_if_not_exists(".running") def delete_running_file(self): """the function deletes a file in the main directory called .running""" delete_file_if_exists(".running")
@app.route('/login', methods=['POST']) def login_post_route(): """The function calls the corresponding function from the appropriate module""" return login_post() @app.route("/logout") def logout_route(): """The function calls the corresponding function from the appropriate module""" return logout() @app.errorhandler(404) def page_not_found_route(error): """The function calls the corresponding function from the appropriate module""" return page_not_found(error) if __name__ == '__main__': # debugging mode app.secret_key = os.urandom(12) PORT = int(CFG.get_value("WEB", "bindport")) HOST = CFG.get_value("WEB", "bindip") DEBUG = True app.run(HOST, PORT, DEBUG) else: # production mode app.secret_key = os.urandom(12)
class Iptables(object): """the class contains functions that interact with the iptables software""" def __init__(self): """the init function creates some useful class variables""" debug("Setting up iptables...") self.config = Config("config/easywall.ini") self.ipv6 = bool(self.config.get_value("IPV6", "enabled")) self.iptables_bin = self.config.get_value("EXEC", "iptables") self.iptables_bin_save = self.config.get_value("EXEC", "iptables-save") self.iptables_bin_restore = self.config.get_value( "EXEC", "iptables-restore") if self.ipv6 is True: debug("IPV6 is enabled") self.ip6tables_bin = self.config.get_value("EXEC", "ip6tables") self.ip6tables_bin_save = self.config.get_value( "EXEC", "ip6tables-save") self.ip6tables_bin_restore = self.config.get_value( "EXEC", "ip6tables-restore") def add_policy(self, chain, target): """the function creates a new policy in iptables""" debug("adding policy for chain " + chain + " and target " + target) if target == "ACCEPT" or target == "DROP": execute_os_command(self.iptables_bin + " -P " + chain + " " + target) if self.ipv6 is True: execute_os_command(self.ip6tables_bin + " -P " + chain + " " + target) else: error("Invalid Target for addPolicy " + target) def add_chain(self, chain): """the function creates a new chain in iptables""" debug("adding chain " + chain) execute_os_command(self.iptables_bin + " -N " + chain) if self.ipv6 is True: execute_os_command(self.ip6tables_bin + " -N " + chain) def add_append(self, chain, rule, onlyv6=False, onlyv4=False): """the function creates a new append in iptables""" if onlyv4 is True or (onlyv6 is False and onlyv4 is False): debug("adding append for ipv4, chain: " + chain + ", rule: " + rule) execute_os_command(self.iptables_bin + " -A " + chain + " " + rule) if self.ipv6 is True and (onlyv6 is True or (onlyv6 is False and onlyv4 is False)): debug("adding append for ipv6, chain: " + chain + ", rule: " + rule) execute_os_command(self.ip6tables_bin + " -A " + chain + " " + rule) def flush(self, chain=""): """the function flushes a iptables chain or all chains""" debug("flushing iptables chain: " + chain) execute_os_command(self.iptables_bin + " -F " + chain) if self.ipv6 is True: execute_os_command(self.ip6tables_bin + " -F " + chain) def delete_chain(self, chain=""): """the function deletes a chain in iptables""" debug("deleting chain " + chain) execute_os_command(self.iptables_bin + " -X " + chain) if self.ipv6 is True: execute_os_command(self.ip6tables_bin + " -X " + chain) def reset(self): """the function resets iptables to a clean state""" debug("resetting iptables to empty configuration") self.add_policy("INPUT", "ACCEPT") self.add_policy("OUTPUT", "ACCEPT") self.add_policy("FORWARD", "ACCEPT") self.flush() self.delete_chain() def list(self): """the function lists all iptables rules""" execute_os_command(self.iptables_bin + " -L") def save(self): """the function saves the current iptables state into a file""" debug("Starting Firewall Rule Backup...") # Create Backup Directory if not exists filepath = self.config.get_value("BACKUP", "filepath") create_folder_if_not_exists(filepath) # backing up ipv4 iptables rules debug("Backing up ipv4 rules...") filename = self.config.get_value("BACKUP", "ipv4filename") open(filepath + "/" + filename, 'w') self.save_execute(self.iptables_bin_save, filepath, filename) # backing up ipv6 iptables rules if self.ipv6 is True: debug("Backing up ipv6 rules...") filename = self.config.get_value("BACKUP", "ipv6filename") open(filepath + "/" + filename, 'w') self.save_execute(self.ip6tables_bin_save, filepath, filename) def save_execute(self, binary, filepath, filename): """the function executes the save of iptables into a file""" execute_os_command(binary + " | while read IN ; do echo $IN >> " + filepath + "/" + filename + " ; done") def restore(self): """the function restores iptables rules from a file""" debug("Starting Firewall Rule Restore...") filepath = self.config.get_value("BACKUP", "filepath") create_folder_if_not_exists(filepath) debug("Restoring ipv4 rules...") filename = self.config.get_value("BACKUP", "ipv4filename") execute_os_command(self.iptables_bin_restore + " < " + filepath + "/" + filename) if self.ipv6 is True: debug("Restoring ipv6 rules...") filename = self.config.get_value("BACKUP", "ipv6filename") execute_os_command(self.ip6tables_bin_restore + " < " + filepath + "/" + filename)
class Webutils(object): """Create a couple of shared functions used in the route functions.""" def __init__(self) -> None: """TODO: Doku.""" self.cfg = Config("config/web.ini") self.cfg_easywall = Config(CONFIG_PATH) self.cfg_log = Config(LOG_CONFIG_PATH) def check_login(self, request: Request) -> bool: """Check if the user/session is logged in.""" if not session.get('logged_in'): return False if request.remote_addr != session.get('ip_address'): return False return True def check_first_run(self) -> bool: """Check if the webinterface is run for the first time.""" username = self.cfg.get_value("WEB", "username") password = self.cfg.get_value("WEB", "password") if username == "" or password == "": return True return False # ------------------------- # Payload Operations def get_default_payload(self, title: str, css: str = "easywall") -> DefaultPayload: """Create a object of information that are needed on every page.""" payload = DefaultPayload() payload.year = datetime.today().year payload.title = title payload.customcss = css payload.machine = self.get_machine_infos() payload.latest_version = str(self.cfg.get_value("VERSION", "version")) payload.current_version = file_get_contents(".version") payload.commit_sha = str(self.cfg.get_value("VERSION", "sha")) payload.commit_date = self.get_commit_date( str(self.cfg.get_value("VERSION", "date"))) payload.config_mismatch = self.get_config_version_mismatch("core") payload.web_config_mismatch = self.get_config_version_mismatch("web") return payload def get_machine_infos(self) -> dict: """Retrieve some information about the host and returns them as a list.""" infos = {} infos["Machine"] = platform.machine() infos["Hostname"] = platform.node() infos["Platform"] = platform.platform() infos["Python Build"] = "".join(platform.python_build()) infos["Python Compiler"] = platform.python_compiler() infos["Python Implementation"] = platform.python_implementation() infos["Python Version"] = platform.python_version() infos["Release"] = platform.release() infos["Libc Version"] = "".join(platform.libc_ver()) return infos def get_config_version_mismatch(self, cfgtype: str) -> bool: """TODO: Doku.""" if cfgtype == "core": cfg1 = Config("config/easywall.sample.ini") cfg2 = Config("config/easywall.ini") elif cfgtype == "web": cfg1 = Config("config/web.sample.ini") cfg2 = Config("config/web.ini") for section in cfg1.get_sections(): if section not in cfg2.get_sections(): return True for key in cfg1.get_keys(section): if key not in cfg2.get_keys(section): return True return False # ------------------------- # Update Info Operations def get_commit_date(self, datestring: str) -> str: """ Compare a datetime with the current date. for comparing the datestring parameter is in UTC timezone """ date1 = datetime.strptime(str(datestring), "%Y-%m-%dT%H:%M:%SZ") date1 = date1.replace(tzinfo=timezone.utc).astimezone(tz=None).replace( tzinfo=None) date2 = datetime.now() return time_duration_diff(date1, date2) def update_last_commit_infos(self) -> None: """ Retrieve the last commit information after a specific waiting time. after retrieving the information they are saved into the config file """ currtime = int(time.time()) lasttime = int(self.cfg.get_value("VERSION", "timestamp")) waitseconds = 3600 # 60 minutes × 60 seconds if currtime > (lasttime + waitseconds): commit = self.get_latest_commit() self.cfg.set_value("VERSION", "version", self.get_latest_version()) self.cfg.set_value("VERSION", "sha", commit["sha"]) self.cfg.set_value("VERSION", "date", commit["commit"]["author"]["date"]) self.cfg.set_value("VERSION", "timestamp", str(currtime)) def get_latest_commit(self) -> Any: """ Retrieve the informations of the last commit from github as json. also converts the information into a python object for example the object contains the last commit date and the last commit sha This function should not be called very often, because GitHub has a rate limit implemented """ url = "https://api.github.com/repos/jpylypiw/easywall/commits/master" req = urllib.request.Request( url, data=None, headers={'User-Agent': 'easywall by github.com/jpylypiw/easywall'}) if req.get_full_url().lower().startswith("https"): response = urllib.request.urlopen(req) else: raise ValueError from None return json.loads(response.read().decode('utf-8')) def get_latest_version(self) -> str: """Retrieve the latest version from github and returns the version string.""" url = "https://raw.githubusercontent.com/jpylypiw/easywall/master/.version" req = urllib.request.Request( url, data=None, headers={'User-Agent': 'easywall by github.com/jpylypiw/easywall'}) if req.get_full_url().lower().startswith("https"): response = urllib.request.urlopen(req) else: raise ValueError from None data = response.read() return str(data.decode('utf-8')) # ------------------------- # Acceptance Operations def get_last_accept_time(self) -> str: """ Retrieve the modify time of the acceptance file. also compares the time to the current time """ timestamp = str(self.cfg_easywall.get_value("ACCEPTANCE", "timestamp")) if timestamp == "": return "never" timestamp_datetime = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S.%f') now = datetime.now() return time_duration_diff(timestamp_datetime, now) def get_acceptance_status(self) -> str: """Get the status of the current acceptance.""" filepath = ".acceptance_status" if file_exists(filepath): return file_get_contents(filepath) return ""
class TestConfig(unittest.TestCase): """ TODO: Doku """ def setUp(self) -> None: content = """[TEST] teststring = string testboolean = true testint = 1 testfloat = 1.1 """ create_file_if_not_exists("test.ini") write_into_file("test.ini", content) self.config = Config("test.ini") def tearDown(self) -> None: delete_file_if_exists("test.ini") def test_constructor_file_not_found(self) -> None: """ TODO: Doku """ with self.assertRaises(FileNotFoundError): Config("test2.ini") def test_constructor_file_not_read(self) -> None: """ TODO: Doku """ create_file_if_not_exists("test.ini") content = """[DEFAULT] goodcontent = test badcontent """ write_into_file("test.ini", content) with self.assertRaises(ParsingError): Config("test.ini") def test_get_value_error(self) -> None: """ TODO: Doku """ self.assertEqual(self.config.get_value("TEST", "notexistent"), "") def test_get_value_bool(self) -> None: """ TODO: Doku """ self.assertEqual(self.config.get_value("TEST", "testboolean"), True) def test_get_value_int(self) -> None: """ TODO: Doku """ self.assertEqual(self.config.get_value("TEST", "testint"), 1) def test_get_value_float(self) -> None: """ TODO: Doku """ self.assertEqual(self.config.get_value( "TEST", "testfloat"), float(1.1)) def test_get_sections(self) -> None: """ TODO: Doku """ self.assertIn("TEST", self.config.get_sections()) def test_set_value_success(self) -> None: """ TODO: Doku """ self.assertEqual(self.config.set_value( "TEST", "teststring", "erfolg"), True) def test_set_value_fail_section(self) -> None: """ TODO: Doku """ self.assertEqual(self.config.set_value("TEST2", "asd", "asd"), False)
class Webutils(object): """the class is called in the route modules and contains non route-specific functions""" def __init__(self): self.cfg = Config("config/web.ini") def check_login(self): """the function checks if the user/session is logged in""" if not session.get('logged_in'): return False return True # ------------------------- # Payload Operations def get_default_payload(self, title, css="easywall"): """the function creates a object of information that are needed on every page""" payload = DefaultPayload() payload.year = datetime.today().year payload.title = title payload.customcss = css payload.machine = self.get_machine_infos() payload.latest_version = self.cfg.get_value("VERSION", "version") payload.current_version = file_get_contents("{}/../.version".format(get_abs_path_of_filepath(__file__))) payload.commit_sha = self.cfg.get_value("VERSION", "sha") payload.commit_date = self.get_commit_date(self.cfg.get_value("VERSION", "date")) return payload def get_machine_infos(self): """the function retrieves some information about the host and returns them as a list""" infos = {} infos["Machine"] = platform.machine() infos["Hostname"] = platform.node() infos["Platform"] = platform.platform() infos["Python Build"] = platform.python_build() infos["Python Compiler"] = platform.python_compiler() infos["Python Implementation"] = platform.python_implementation() infos["Python Version"] = platform.python_version() infos["Release"] = platform.release() infos["Libc Version"] = platform.libc_ver() return infos # ------------------------- # Update Info Operations def get_commit_date(self, datestring): """ the function compares a datetime with the current date for comparing the datestring parameter is in UTC timezone """ date1 = datetime.strptime(str(datestring), "%Y-%m-%dT%H:%M:%SZ") date1 = date1.replace( tzinfo=timezone.utc).astimezone( tz=None).replace( tzinfo=None) date2 = datetime.now() return time_duration_diff(date1, date2) def update_last_commit_infos(self): """ the function retrieves the last commit information after a specific waiting time after retrieving the information they are saved into the config file """ currtime = int(time.time()) lasttime = int(self.cfg.get_value("VERSION", "timestamp")) waitseconds = 3600 # 60 minutes × 60 seconds if currtime > (lasttime + waitseconds): commit = self.get_latest_commit() self.cfg.set_value("VERSION", "version", self.get_latest_version()) self.cfg.set_value("VERSION", "sha", commit["sha"]) self.cfg.set_value("VERSION", "date", commit["commit"]["author"]["date"]) self.cfg.set_value("VERSION", "timestamp", currtime) def get_latest_commit(self): """ retrieves the informations of the last commit from github as json and converts the information into a python object for example the object contains the last commit date and the last commit sha This function should not be called very often, because GitHub has a rate limit implemented """ url = "https://api.github.com/repos/jpylypiw/easywall-web/commits/master" req = urllib.request.Request( url, data=None, headers={ 'User-Agent': 'easywall by github.com/jpylypiw/easywall-web' } ) response = urllib.request.urlopen(req) return json.loads(response.read().decode('utf-8')) def get_latest_version(self): """ the function retrieves the latest version from github and returns the version string """ url = "https://raw.githubusercontent.com/jpylypiw/easywall-web/master/.version" req = urllib.request.Request( url, data=None, headers={ 'User-Agent': 'easywall by github.com/jpylypiw/easywall-web' } ) response = urllib.request.urlopen(req) data = response.read() return data.decode('utf-8') # ------------------------- # Rule Operations def get_rule_status(self, ruletype): """ the function checks if a custom / temporary rulefile exists and returns "custom" when a temporary rulefile exists or "production" when no file exists """ filepath = self.get_rule_file_path(ruletype, True) if not os.path.exists(filepath): filepath = self.get_rule_file_path(ruletype) if not os.path.exists(filepath): return "error" return "production" return "custom" def get_rule_file_path(self, ruletype, tmp=False): """ the function reads the configuration and returns the relative or absolute path to the rulefile for the ruletype """ filename = self.cfg.get_value("RULES", ruletype) if tmp: filepath = self.cfg.get_value("WEB", "rules_tmp_path") else: filepath = self.cfg.get_value("RULES", "filepath") # workaround because the easywall dir is one dir up - this is not pretty if filepath.startswith("."): filepath = "../" + filepath create_folder_if_not_exists(filepath) return filepath + "/" + filename def get_rule_list(self, ruletype): """ the function reads a file into the ram and returns a list of all rows in a list for example you get all the ip addresses of the blacklist in a array """ rule_list = [] status = self.get_rule_status(ruletype) filepath = self.get_rule_file_path(ruletype) if status == "custom": filepath = self.get_rule_file_path(ruletype, True) with open(filepath, 'r') as rulesfile: for rule in rulesfile.read().split('\n'): if rule.strip() != "": rule_list.append(rule) return rule_list def save_rule_list(self, ruletype, rulelist, to_production=False): """ the function writes a list of strings into a rulesfile for example it saves the blacklist rules into the blacklist temporary rulesfile """ filepath = self.get_rule_file_path(ruletype, True) state = self.get_rule_status(ruletype) if to_production: filepath = self.get_rule_file_path(ruletype) try: rulelist = list(filter(None, rulelist)) if not to_production or to_production and state == "custom": with open(filepath, mode='wt', encoding='utf-8') as rulesfile: rulesfile.write('\n'.join(rulelist)) except Exception as exc: print("{}".format(exc)) return False return True # ------------------------- # Acceptance Operations def apply_rule_list(self, ruletype): """ the function copys the rulefile from the temporary path to the permanent path this is used to copy the rules from web to easywall folder """ rule_list = self.get_rule_list(ruletype) self.save_rule_list(ruletype, rule_list, True) def get_last_accept_time(self): """ the function retrieves the modify time of the acceptance file and compares the time to the current time """ filepath = "../" + self.cfg.get_value("ACCEPTANCE", "filename") if os.path.exists(filepath): mtime = os.path.getmtime(filepath) mtime = datetime.utcfromtimestamp(mtime) mtime = mtime.replace( tzinfo=timezone.utc).astimezone( tz=None).replace( tzinfo=None) now = datetime.now() return time_duration_diff(mtime, now) else: return "never" def check_acceptance_running(self): """ the function checks if there is a running file. """ filepath = "../.running" if os.path.exists(filepath): return True return False
class Webutils(object): """the class is called in the route modules and contains non route-specific functions""" def __init__(self): self.cfg = Config("config/web.ini") self.cfg_easywall = Config(CONFIG_PATH) def check_login(self): """the function checks if the user/session is logged in""" if not session.get('logged_in'): return False return True # ------------------------- # Payload Operations def get_default_payload(self, title, css="easywall"): """the function creates a object of information that are needed on every page""" payload = DefaultPayload() payload.year = datetime.today().year payload.title = title payload.customcss = css payload.machine = self.get_machine_infos() payload.latest_version = self.cfg.get_value("VERSION", "version") payload.current_version = file_get_contents(".version") payload.commit_sha = self.cfg.get_value("VERSION", "sha") payload.commit_date = self.get_commit_date( self.cfg.get_value("VERSION", "date")) return payload def get_machine_infos(self): """the function retrieves some information about the host and returns them as a list""" infos = {} infos["Machine"] = platform.machine() infos["Hostname"] = platform.node() infos["Platform"] = platform.platform() infos["Python Build"] = platform.python_build() infos["Python Compiler"] = platform.python_compiler() infos["Python Implementation"] = platform.python_implementation() infos["Python Version"] = platform.python_version() infos["Release"] = platform.release() infos["Libc Version"] = platform.libc_ver() return infos # ------------------------- # Update Info Operations def get_commit_date(self, datestring): """ the function compares a datetime with the current date for comparing the datestring parameter is in UTC timezone """ date1 = datetime.strptime(str(datestring), "%Y-%m-%dT%H:%M:%SZ") date1 = date1.replace(tzinfo=timezone.utc).astimezone(tz=None).replace( tzinfo=None) date2 = datetime.now() return time_duration_diff(date1, date2) def update_last_commit_infos(self): """ the function retrieves the last commit information after a specific waiting time after retrieving the information they are saved into the config file """ currtime = int(time.time()) lasttime = self.cfg.get_value("VERSION", "timestamp") waitseconds = 3600 # 60 minutes × 60 seconds if currtime > (lasttime + waitseconds): commit = self.get_latest_commit() self.cfg.set_value("VERSION", "version", self.get_latest_version()) self.cfg.set_value("VERSION", "sha", commit["sha"]) self.cfg.set_value("VERSION", "date", commit["commit"]["author"]["date"]) self.cfg.set_value("VERSION", "timestamp", str(currtime)) def get_latest_commit(self): """ retrieves the informations of the last commit from github as json and converts the information into a python object for example the object contains the last commit date and the last commit sha This function should not be called very often, because GitHub has a rate limit implemented """ url = "https://api.github.com/repos/jpylypiw/easywall/commits/master" req = urllib.request.Request( url, data=None, headers={'User-Agent': 'easywall by github.com/jpylypiw/easywall'}) response = urllib.request.urlopen(req) return json.loads(response.read().decode('utf-8')) def get_latest_version(self) -> str: """ the function retrieves the latest version from github and returns the version string """ url = "https://raw.githubusercontent.com/jpylypiw/easywall/master/.version" req = urllib.request.Request( url, data=None, headers={'User-Agent': 'easywall by github.com/jpylypiw/easywall'}) response = urllib.request.urlopen(req) data = response.read() return data.decode('utf-8') # ------------------------- # Acceptance Operations def get_last_accept_time(self): """ the function retrieves the modify time of the acceptance file and compares the time to the current time """ timestamp = self.cfg_easywall.get_value("ACCEPTANCE", "timestamp") if timestamp == "": return "never" timestamp = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S.%f') now = datetime.now() return time_duration_diff(timestamp, now) def get_acceptance_status(self): """ get the status of the current acceptance """ filepath = ".acceptance_status" if file_exists(filepath): return file_get_contents(filepath) return ""