From 1d93e58174e8d6a30fe5b29dd8a874e7b3c28a40 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 12 Jan 2019 12:48:32 +0100 Subject: [PATCH 01/28] base latency check order on previous results --- qomui/latency.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/qomui/latency.py b/qomui/latency.py index 9779f51..8240df4 100755 --- a/qomui/latency.py +++ b/qomui/latency.py @@ -15,9 +15,15 @@ def __init__(self, server_dict, interface): self.server_dict = server_dict self.interface = interface + def sort_by_latency(self, server): + try: + return float(server[1]["latency"]) + except KeyError: + return 999 + def run(self): try: - for k,v in self.server_dict.items(): + for k,v in sorted(self.server_dict.items(), key=self.sort_by_latency): try: ip = v["ip"] except KeyError: From 603e36acd0739bdd5cea466bfaf839406e213c71 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 12 Jan 2019 12:51:25 +0100 Subject: [PATCH 02/28] add fast/random option to profiles --- qomui/profiles.py | 3 ++- qomui/qomui_gui.py | 40 +++++++++++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/qomui/profiles.py b/qomui/profiles.py index 6c3d9d1..d2d4d68 100755 --- a/qomui/profiles.py +++ b/qomui/profiles.py @@ -101,6 +101,7 @@ def popBoxes(self): self.modeBox.addItem("Random") self.modeBox.addItem("Fastest") + self.modeBox.addItem("Fast/Random") if self.selected != 0: self.countries_selected = self.selected["countries"] @@ -124,7 +125,7 @@ def popchoiceTable(self): if cols == 0: cols = 1 - n = n + len(self.providers) + n = n + len(self.providers) rows = int(n / cols) + (n % cols> 0) + int(np / cols) + (np % cols> 0) + 3 self.choiceTable.setRowCount(rows) self.choiceTable.setColumnCount(cols) diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index 943489c..2fcbd62 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -86,7 +86,7 @@ class QomuiGui(QtWidgets.QWidget): "auto_update" ] - routes = { + routes = { "gateway" : "None", "gateway_6" : "None", "interface" : "None", @@ -192,10 +192,10 @@ def dbus_call(self, cmd, *args): try: if self.config_dict["bypass"] == 1: self.dbus_call("bypass", {**self.routes, **utils.get_user_group()}) - + except KeyError: pass - + retry = self.dbus_call(cmd, *args) return retry @@ -962,7 +962,7 @@ def notify(self, header, text, icon="Question"): except (CalledProcessError, FileNotFoundError): self.logger.warning("Desktop notifications not available") - + def messageBox(self, header, text, buttons=[], icon="Question"): box = QtWidgets.QMessageBox(self) box.setText(header) @@ -1235,12 +1235,12 @@ def connect_last_server(self): try: if self.ovpn_dict["random"] == "on": self.choose_random_server() - + except KeyError: if "profile" in self.ovpn_dict.keys(): self.connect_profile(self.ovpn_dict["profile"]) - + else: self.establish_connection(self.ovpn_dict) @@ -1610,7 +1610,7 @@ def display_profile(self, number): getattr(self, "{}_widget".format(number)).connect_profile.connect(self.connect_profile) self.verticalLayout_58.insertWidget(0, getattr(self, "{}_widget".format(number))) name = self.profile_dict[number]["name"] - + def connect_profile(self, p): result = None profile = self.profile_dict[p] @@ -1644,6 +1644,32 @@ def connect_profile(self, p): fastest = lat result = s + elif profile["mode"] == "Fast/Random": + l_list = [] + s_list = [] + counter = {} + max_length = int(len(temp_list) * 0.20) + for s in temp_list: + country = self.server_dict[s]["country"] + try: + lat = float(self.server_dict[s]["latency"]) + except KeyError: + lat = 1000 + + bisect.insort(l_list, lat) + s_list.insert(l_list.index(lat), (s, country)) + + s_list = s_list[:max_length + 1] + from collections import Counter + occs = [v for k, v in Counter(e[1] for e in s_list).items()] + max_occ = sum(occs) / len(occs) + for s,c in s_list: + counter[c] = counter.get(c, 0) + 1 + if counter[c] <= max_occ: + s_list.remove((s,c)) + + result = random.choice(s_list)[0] + elif profile["mode"] == "Random": result = random.choice(temp_list) From b5d7b6e38ed7bf7796db7da4120ca250e784fb11 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 12 Jan 2019 13:31:08 +0100 Subject: [PATCH 03/28] updated readme.md --- README.md | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b89892a..29a72fe 100755 --- a/README.md +++ b/README.md @@ -4,18 +4,15 @@ Qomui (Qt OpenVPN Management UI) is an easy-to-use OpenVPN/WireGuard gui for GNU/Linux with some unique features such as provider-independent support for double-hop connections. Qomui supports multiple providers with added convenience when using AirVPN, PIA, ProtonVPN, Windscribe or Mullvad. ### Features -- should work with all VPN providers that offer OpenVPN config files -- automatic download function for Mullvad, Private Internet Access, Windscribe, ProtonVPN and AirVPN -- support for OpenVPN over SSL and SSH for AirVPN and OpenVPN over SSL for Windscribe (Stealth Mode) +- works with all VPN providers that offer OpenVPN/WireGuard config files +- easy-to-use gui written in PyQt5 +- automatic download function for Mullvad, Private Internet Access, Windscribe, ProtonVPN and AirVPN including support for OpenVPN over SSL and SSH for AirVPN and OpenVPN over SSL for Windscribe (Stealth Mode) - allows double-hop VPN connections (VPN chains) between different providers -- gui written in PyQt including option to minimize application to system tray +- killswitch & leak protection via an iptables-based, configurable firewall that blocks all outgoing network traffic in case the VPN connection breaks down +- provides the possibility to allow applications to bypass the VPN tunnel, open a second VPN tunnel or use the VPN only for specific applications +- supports WireGuard +- command-line interface - security-conscious separation of the gui and a D-Bus service that handles commands that require root privileges -- protection against DNS leaks/ipv6 leaks -- iptables-based, configurable firewall that blocks all outgoing network traffic in case the VPN connection breaks down -- allow applications to bypass the VPN tunnel, open a second VPN tunnel or use the VPN only for specific applications -- experimental support for WireGuard -- command-line interface -- automatic weekly updates of server configurations for supported providers - experimental ### Screenshots Screenshots were taken on Arch Linux/Plasma Arc Dark Theme - Qomui will adapt to your theme.
@@ -43,9 +40,22 @@ Qomui contains two components: qomui-gui and qomui-service (and qomui-cli: see b Current configurations for AirVPN, Mullvad, ProtonVPN, PIA and Windscribe can be automatically downloaded via the provider tab. Qomui will update these once a week if you choose to enable the respective setting in the options tab. For all other providers you can conveniently add a config file folder. Qomui will automatically resolve host names, determine the location of servers (using geoip-database) and save your username and password (in a file readable only by root). -Once you added server configurations, you can browse and filter them in the server tab. Furthermore, you can mark servers as favourites and connect to one of them randomly. To see a list of all favourited servers click on the star in the upper right. - -### Firewall - Network lock +Once you added server configurations, you can browse and filter them in the server tab. Furthermore, you can mark servers as favourites and connect to one of them randomly. To see a list of all favourited servers click on the star in the upper right. There is also an option to create connection profiles in the respective tab. Profiles will select a server automatically based on the criteria you set. Criteria include protocol (OpenVPN or WireGuard), countries, providers and one of the following selection modes: +- ***Random:*** Chooses a random server among all servers matching the profile +- ***Fastest:*** Chooses the fastest server matching the profile based on latency. For this option to work properly the "Perform latency checks" option needs to be ticked. +- ***Fast/Random:*** Chooses a random server among the fastest twenty percent. If you profile includes more than one country, the algorithm also tries to increase the chance to select a server from a different country next. + +### Options +- ***Autoconnect/reconnect:*** Automatically connect to the last server/last profile once a new internet connection has been detected or after the OpenVPN process has died unexpectedly +- ***Start minimized:*** Hides the application window on startup. This only works if your Desktop Environment supports tray icons. +- ***Auto-update:*** Updates server configurations for supported providers automatically every five days +- ***Perform latency check:*** Checks server latency and sorts servers accordingly +- ***Disable IPv6:*** Completely disables the IPv6 stack systemwide. This is not recommended unless you know what you are doing. +- ***Allow OpenVPN bypass:*** See bypass section below +- ***Activate Firewall:*** See firewall section below +- ***Alternative DNS Servers***: Enforces the usage of custom DNS servers instead of those by your provider. The DNS servers set here will also be used for bypass mode if you don't launch a secondary VPN tunnel. + +### Firewall (Killswitch) It is highly recommended to activate the firewall to prevent against ipv6 and DNS leaks. By default, once qomui-service has been started, all internet connectivity outside the VPN tunnel will be blocked whether or not the gui is running. Hence, your system will be always protected if you enable qomui-service via systemd. Depending on your distribution, it might be necessary to disable preinstalled firewall services such as ufw or firewalld to avoid conflicts. Alternatively, the "Edit firewall" dialog in the options tab offers a setting to enable/disable the firewall only if you start/quit the gui. You can also add custom iptables rules there. ### Double-Hop @@ -67,7 +77,7 @@ The bypass feature also allows you to open a second OpenVPN tunnel (this does cu You can add WireGuard config files from any provider as easily as OpenVPN files. WireGuard configs for Mullvad are now downloaded automatically alongside their OpenVPN configs as long as WireGuard is installed. If you choose to manually import WireGuard config files, Qomui will automatically recognize the type of file. As of now, WireGuard will not be installed automatically with DEB and RPM packages. You can find the official installation guidelines for different distributions [here](https://www.wireguard.com/install/). ### Cli -The cli interface is still experimental and missing some features, e.g. automatic reconnects. Avoid using the cli and the Gui concurrently. +The cli interface is still experimental and missing some features, e.g. automatic reconnects. Avoid using the cli and the gui concurrently. #### Example usage From e1ed7d60cc56d1538917ef8dc672e3385f1f78ff Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 12 Jan 2019 13:35:27 +0100 Subject: [PATCH 04/28] updated readme.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 29a72fe..7181ec5 100755 --- a/README.md +++ b/README.md @@ -43,16 +43,16 @@ Current configurations for AirVPN, Mullvad, ProtonVPN, PIA and Windscribe can be Once you added server configurations, you can browse and filter them in the server tab. Furthermore, you can mark servers as favourites and connect to one of them randomly. To see a list of all favourited servers click on the star in the upper right. There is also an option to create connection profiles in the respective tab. Profiles will select a server automatically based on the criteria you set. Criteria include protocol (OpenVPN or WireGuard), countries, providers and one of the following selection modes: - ***Random:*** Chooses a random server among all servers matching the profile - ***Fastest:*** Chooses the fastest server matching the profile based on latency. For this option to work properly the "Perform latency checks" option needs to be ticked. -- ***Fast/Random:*** Chooses a random server among the fastest twenty percent. If you profile includes more than one country, the algorithm also tries to increase the chance to select a server from a different country next. +- ***Fast/Random:*** Chooses a random server among the fastest twenty percent. If you profile includes more than one country, the algorithm also increases the chance to select a server from a different country next. ### Options -- ***Autoconnect/reconnect:*** Automatically connect to the last server/last profile once a new internet connection has been detected or after the OpenVPN process has died unexpectedly -- ***Start minimized:*** Hides the application window on startup. This only works if your Desktop Environment supports tray icons. -- ***Auto-update:*** Updates server configurations for supported providers automatically every five days -- ***Perform latency check:*** Checks server latency and sorts servers accordingly +- ***Autoconnect/reconnect:*** Automatically connect to the last server/last profile once a new internet connection has been detected or after the OpenVPN process has died unexpectedly. +- ***Start minimized:*** Hides the application window on startup. This only works if your desktop environment supports tray icons. +- ***Auto-update:*** Updates server configurations for supported providers automatically every five days. +- ***Perform latency check:*** Checks server latency and sorts servers accordingly. - ***Disable IPv6:*** Completely disables the IPv6 stack systemwide. This is not recommended unless you know what you are doing. -- ***Allow OpenVPN bypass:*** See bypass section below -- ***Activate Firewall:*** See firewall section below +- ***Allow OpenVPN bypass:*** See bypass section below. +- ***Activate Firewall:*** See firewall section below. - ***Alternative DNS Servers***: Enforces the usage of custom DNS servers instead of those by your provider. The DNS servers set here will also be used for bypass mode if you don't launch a secondary VPN tunnel. ### Firewall (Killswitch) From 15f1612229eea1851c04a6fcd05a56dc7a0deedd Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 12 Jan 2019 20:28:17 +0100 Subject: [PATCH 05/28] fixed Windscribe autoupdate & added support for custom Airvpn keys --- qomui/monitor.py | 0 qomui/qomui_gui.py | 17 ++++++++++++++++- qomui/qomui_service.py | 9 +++++++-- qomui/update.py | 31 ++++++++++++++++++++----------- qomui/widgets.py | 1 + 5 files changed, 44 insertions(+), 14 deletions(-) mode change 100644 => 100755 qomui/monitor.py diff --git a/qomui/monitor.py b/qomui/monitor.py old mode 100644 new mode 100755 diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index 2fcbd62..972ee14 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -561,6 +561,9 @@ def setupUi(self, Form): self.addProviderPassEdit.setEchoMode(QtWidgets.QLineEdit.Password) self.addProviderPassEdit.setObjectName(_fromUtf8("addProviderPassEdit")) self.gridLayout_3.addWidget(self.addProviderPassEdit, 1, 0, 1, 2) + self.airvpnKeyEdit = QtWidgets.QLineEdit(self.providerTab) + self.airvpnKeyEdit.setObjectName(_fromUtf8("airvpnKeyEdit")) + self.gridLayout_3.addWidget(self.airvpnKeyEdit, 2, 0, 1, 2) self.verticalLayout_30.addLayout(self.gridLayout_3) self.delProviderLabel = QtWidgets.QLabel(self.providerTab) self.delProviderLabel.setFont(bold_font) @@ -1083,6 +1086,7 @@ def tab_switch(self): self.tabWidget.setCurrentIndex(3) elif button == "Provider": self.tabWidget.setCurrentIndex(4) + self.providerChosen() elif button == "Bypass": self.tabWidget.setCurrentIndex(5) self.bypassVpnBox.clear() @@ -1459,6 +1463,7 @@ def providerChosen(self): } if provider in SUPPORTED_PROVIDERS: + self.airvpnKeyEdit.setVisible(False) self.addProviderEdit.setVisible(False) self.addProviderUserEdit.setPlaceholderText(_translate("Form", p_txt[provider][0], None)) self.addProviderPassEdit.setPlaceholderText(_translate("Form", p_txt[provider][1], None)) @@ -1466,8 +1471,12 @@ def providerChosen(self): self.addProviderDownloadBt.setText(_translate("Form", "Update", None)) else: self.addProviderDownloadBt.setText(_translate("Form", "Download", None)) + if provider == "Airvpn": + self.airvpnKeyEdit.setPlaceholderText(_translate("Form", "Key/Device: Default", None)) + self.airvpnKeyEdit.setVisible(True) else: + self.airvpnKeyEdit.setVisible(False) self.addProviderEdit.setVisible(True) self.addProviderEdit.setPlaceholderText(_translate("Form", "Specify name of provider", @@ -1517,9 +1526,15 @@ def add_server_configs(self): "username" : username, "password" : password, "folderpath" : folderpath, - "homedir" : HOMEDIR + "homedir" : HOMEDIR, } + if provider == "Airvpn": + if self.airvpnKeyEdit.text() != "": + credentials["key"] = self.airvpnKeyEdit.text() + else: + credentials["key"] = "Default" + self.addProviderUserEdit.setText("") self.addProviderPassEdit.setText("") self.dbus_call("import_thread", credentials) diff --git a/qomui/qomui_service.py b/qomui/qomui_service.py index 446b12f..e9f0481 100755 --- a/qomui/qomui_service.py +++ b/qomui/qomui_service.py @@ -67,8 +67,8 @@ def __init__(self): self.filehandler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) self.logger.setLevel(logging.DEBUG) self.logger.info("Dbus-service successfully initialized") - - #Clean slate after (re-)starting + + #Clean slate after (re-)starting try: check_call(["killall", "openvpn"]) self.logger.debug("Killed all running instances of OpenVPN") @@ -372,6 +372,9 @@ def import_thread(self, credentials): except FileNotFoundError: self.logger.error("Could not find {} - Aborting update".format(auth_file)) + if provider == "Airvpn": + credentials["key"] = self.config["airvpn_key"] + except KeyError: pass @@ -403,6 +406,8 @@ def downloaded(self, content): if provider in SUPPORTED_PROVIDERS: with open('{}/config.json'.format(ROOTDIR), 'w') as save_config: self.config["{}_last".format(provider)] = str(datetime.utcnow()) + if provider == "Airvpn": + self.config["airvpn_key"] = content["airvpn_key"] json.dump(self.config, save_config) with open('{}/{}.json'.format(self.homedir, provider), 'w') as p: diff --git a/qomui/update.py b/qomui/update.py index a5c2fbf..cc7135b 100755 --- a/qomui/update.py +++ b/qomui/update.py @@ -56,6 +56,11 @@ def __init__(self, credentials, folderpath=None): self.folderpath = credentials["folderpath"] self.temp_path = "{}/{}".format(TEMPDIR, self.provider) + try: + self.key = credentials["key"] + except KeyError: + pass + def run(self): self.started.emit(self.provider) self.log.emit(("debug", "Started new thread to import {}".format(self.provider))) @@ -138,7 +143,15 @@ def airvpn(self): with open("{}/{}".format(self.temp_path, a), "w") as c: c.write(cert_xml_root.attrib[a]) - user_key = cert_xml_root[0][0] + key_index = 0 + keys_available = len(cert_xml_root[0]) + for k in range(keys_available): + for n in cert_xml_root[0][k].attrib: + if n == "name" and cert_xml_root[0][k].attrib[n] == self.key: + key_index = k + + user_key = cert_xml_root[0][key_index] + for a in user_key.attrib: if a == "crt" or a == "key": with open("{}/{}".format(self.temp_path, a), "w") as c: @@ -205,7 +218,8 @@ def airvpn(self): airvpn_data = { "server" : self.airvpn_servers, "protocol" : self.airvpn_protocols, - "provider" : "Airvpn" + "provider" : "Airvpn", + "airvpn_key" : self.key } self.copy_certs(self.provider) @@ -357,7 +371,7 @@ def mullvad(self): wg.writelines(wg_conf) else: - m = "Authentication failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) + m = "Mullvad: Authentication failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) self.remove_temp_dir(self.provider) self.failed.emit(m) auth = 1 @@ -511,21 +525,16 @@ def windscribe(self): userpass = json.loads(get_cred.content.decode("utf-8")) try: - self.username = userpass["username"] - self.password = userpass["password"] self.log.emit(("info", "Created Windscribe credentials for OpenVPN")) - self.windscribe_get_servers() - - """ with open("{}/windscribe_userpass.txt".format(CERTDIR), "w") as cd: cd.write("{}\n{}\n".format(userpass["username"], userpass["password"])) self.log.emit(("debug", "Windscribe OpenVPN credentials written to {}/windscribe_userpass.txt".format(CERTDIR))) - """ - except KeyError: + self.windscribe_get_servers() + except KeyError: self.log.emit(("info", "Windscribe: Login failed")) - m = "Authentication failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) + m = "Windscribe: Authentication failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) self.remove_temp_dir(self.provider) self.failed.emit(m) diff --git a/qomui/widgets.py b/qomui/widgets.py index 3f46a35..af5d37e 100755 --- a/qomui/widgets.py +++ b/qomui/widgets.py @@ -1074,3 +1074,4 @@ def accept_change(self): self.modified.emit(change_dict) self.hide() + From 979a5fbcef6df2a5f046e66f77f8c78cfb58d395 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 19 Jan 2019 14:57:09 +0100 Subject: [PATCH 06/28] added AzireVPN --- qomui/firewall.py | 63 +++++++++++--------- qomui/qomui_gui.py | 7 ++- qomui/qomui_service.py | 2 +- qomui/tunnel.py | 2 +- qomui/update.py | 127 ++++++++++++++++++++++++++++++++++++++++- qomui/widgets.py | 2 +- 6 files changed, 169 insertions(+), 34 deletions(-) diff --git a/qomui/firewall.py b/qomui/firewall.py index df0d1b6..c2dfde8 100755 --- a/qomui/firewall.py +++ b/qomui/firewall.py @@ -120,34 +120,42 @@ def apply_rules(opt, block_lan=0, preserve=0): logging.info("iptables: deactivated firewall") def save_existing_rules(fw_rules): - existing_rules = check_output(["iptables", "-S"]).decode("utf-8") - for line in existing_rules.split('\n'): - rpl = line.replace("/32", "") - rule = shlex.split(rpl) - if len(rule) != 0: - match = 0 - omit = fw_rules["ipv4rules"] + fw_rules["flush"] + fw_rules["ipv4local"] - for x in omit: - if Counter(x) == Counter(rule): - match = 1 - if match == 0 and rule not in saved_rules: - saved_rules.append(rule) - match = 0 + try: + existing_rules = check_output(["iptables", "-S"]).decode("utf-8") + for line in existing_rules.split('\n'): + rpl = line.replace("/32", "") + rule = shlex.split(rpl) + if len(rule) != 0: + match = 0 + omit = fw_rules["ipv4rules"] + fw_rules["flush"] + fw_rules["ipv4local"] + for x in omit: + if Counter(x) == Counter(rule): + match = 1 + if match == 0 and rule not in saved_rules: + saved_rules.append(rule) + match = 0 + + except (CalledProcessError, FileNotFoundError) as e: + logging.error("ip4tables: Could not read active rules - {}".format(e)) def save_existing_rules_6(fw_rules): - existing_rules = check_output(["ip6tables", "-S"]).decode("utf-8") - for line in existing_rules.split('\n'): - rpl = line.replace("/32", "") - rule = shlex.split(rpl) - if len(rule) != 0: - match = 0 - omit = fw_rules["ipv6rules"] + fw_rules["flushv6"] + fw_rules["ipv6local"] - for x in omit: - if Counter(x) == Counter(rule): - match = 1 - if match == 0 and rule not in saved_rules_6: - saved_rules_6.append(rule) - match = 0 + try: + existing_rules = check_output(["ip6tables", "-S"]).decode("utf-8") + for line in existing_rules.split('\n'): + rpl = line.replace("/32", "") + rule = shlex.split(rpl) + if len(rule) != 0: + match = 0 + omit = fw_rules["ipv6rules"] + fw_rules["flushv6"] + fw_rules["ipv6local"] + for x in omit: + if Counter(x) == Counter(rule): + match = 1 + if match == 0 and rule not in saved_rules_6: + saved_rules_6.append(rule) + match = 0 + + except (CalledProcessError, FileNotFoundError) as e: + logging.error("ip6tables: Could not read active rules - {}".format(e)) def allow_dest_ip(ip, action): rule = [action, 'OUTPUT', '-d', ip, '-j', 'ACCEPT'] @@ -207,10 +215,11 @@ def save_iptables(): else: logging.debug("Saved iptables rule") + def restore_iptables(): infile = open("{}/iptables_before.rules".format(ROOTDIR), "r") restore = Popen(["iptables-restore"], stdin=infile, stderr=PIPE) - save.wait() + restore.wait() if restore.stderr: logging.debug("Failed to restore iptables rules") diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index 972ee14..42130b9 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -38,7 +38,7 @@ def _translate(context, text, disambig): ROOTDIR = "/usr/share/qomui" HOMEDIR = "{}/.qomui".format(os.path.expanduser("~")) -SUPPORTED_PROVIDERS = ["Airvpn", "Mullvad", "ProtonVPN", "PIA", "Windscribe"] +SUPPORTED_PROVIDERS = ["Airvpn", "AzireVPN", "Mullvad", "PIA", "ProtonVPN", "Windscribe"] JSON_FILE_LIST = [("config_dict", "{}/config.json".format(ROOTDIR)), ("server_dict", "{}/server.json".format(HOMEDIR)), ("protocol_dict", "{}/protocol.json".format(HOMEDIR)), @@ -1458,6 +1458,7 @@ def providerChosen(self): "Airvpn" : ("Username", "Password"), "PIA" : ("Username", "Password"), "Windscribe" : ("Username", "Password"), + "AzireVPN" : ("Username", "Password"), "Mullvad" : ("Account Number", "N.A. - Leave empty"), "ProtonVPN" : ("OpenVPN username", "OpenVPN password") } @@ -1527,6 +1528,7 @@ def add_server_configs(self): "password" : password, "folderpath" : folderpath, "homedir" : HOMEDIR, + "update" : "1" } if provider == "Airvpn": @@ -2484,7 +2486,8 @@ def update_check(self): "provider" : provider, "credentials" : "unknown", "folderpath" : "None", - "homedir" : HOMEDIR + "homedir" : HOMEDIR, + "update" : "0" } if self.config_dict["auto_update"] == 1: diff --git a/qomui/qomui_service.py b/qomui/qomui_service.py index e9f0481..eee1b9c 100755 --- a/qomui/qomui_service.py +++ b/qomui/qomui_service.py @@ -27,7 +27,7 @@ OPATH = "/org/qomui/service" IFACE = "org.qomui.service" BUS_NAME = "org.qomui.service" -SUPPORTED_PROVIDERS = ["Airvpn", "Mullvad", "ProtonVPN", "PIA", "Windscribe"] +SUPPORTED_PROVIDERS = ["Airvpn", "AzireVPN", "Mullvad", "PIA", "ProtonVPN", "Windscribe"] class GuiLogHandler(logging.Handler): def __init__(self, send_log, parent=None): diff --git a/qomui/tunnel.py b/qomui/tunnel.py index e7e4a20..b45b0ad 100755 --- a/qomui/tunnel.py +++ b/qomui/tunnel.py @@ -12,7 +12,7 @@ from qomui import firewall, dns_manager ROOTDIR = "/usr/share/qomui" -SUPPORTED_PROVIDERS = ["Airvpn", "Mullvad", "ProtonVPN", "PIA", "Windscribe"] +SUPPORTED_PROVIDERS = ["Airvpn", "Mullvad", "ProtonVPN", "PIA", "Windscribe", "AzireVPN"] class TunnelThread(QtCore.QThread): log = QtCore.pyqtSignal(tuple) diff --git a/qomui/update.py b/qomui/update.py index cc7135b..e6cb0f4 100755 --- a/qomui/update.py +++ b/qomui/update.py @@ -27,7 +27,7 @@ def _fromUtf8(s): ROOTDIR = "/usr/share/qomui" TEMPDIR = "/usr/share/qomui/temp" CERTDIR = "/usr/share/qomui/certs" -SUPPORTED_PROVIDERS = ["Airvpn", "Mullvad", "ProtonVPN", "PIA", "Windscribe"] +SUPPORTED_PROVIDERS = ["Airvpn", "AzireVPN", "Mullvad", "PIA", "ProtonVPN", "Windscribe"] def country_translate(cc): try: @@ -54,6 +54,7 @@ def __init__(self, credentials, folderpath=None): self.password = credentials["password"] self.provider = credentials["provider"] self.folderpath = credentials["folderpath"] + self.update = credentials["update"] self.temp_path = "{}/{}".format(TEMPDIR, self.provider) try: @@ -857,6 +858,108 @@ def import_configs(self): self.copy_certs(self.provider) self.finished.emit(custom_dict) + def azirevpn(self): + az_path = "{}/AzireVPN".format(ROOTDIR) + self.az_servers = {} + if not os.path.exists(az_path): + os.makedirs(az_path) + + try: + self.log.emit(("info", "Downloading AzireVPN OpenVPN configs")) + az_api_url = "https://api.azirevpn.com/v1/locations" + az_servers = json.loads(requests.get(az_api_url, timeout=2).content.decode("utf-8")) + + for s in az_servers["locations"]: + name = s["name"] + "-openvpn" + "-azirevpn" + wg_name = s["name"] + "-wireguard" + "-azirevpn" + country = country_translate(s["iso"]) + ip = resolve(s["endpoints"]["openvpn"][0]["hostname"]) + self.log.emit(("info", "Importing {}".format(name))) + self.az_servers[name] = { + "name": name, + "provider" : self.provider, + "city" : s["city"], + "ip" : ip, + "country" : country, + "tunnel" : "OpenVPN" + } + + crt_url = s["openvpn-ca"] + crt_file = "{}/{}.crt".format(az_path, name) + crt = requests.get(az_api_url, timeout=2).content.decode("utf-8") + with open(crt_file, "w") as c: + c.write(crt) + + tls_url = s["openvpn-tls-key"] + tls_file = "{}/{}.key".format(az_path, name) + tls = requests.get(az_api_url, timeout=2).content.decode("utf-8") + with open(tls_file, "w") as t: + t.write(tls) + + try: + wg_file = "{}/{}.conf".format(az_path, wg_name) + wg_api_url = s["endpoints"]["wireguard"] + wg_keys = self.gen_wg_key(wg_file) + + if wg_keys is not None or self.update == "1": + datas = {'username' : str(self.username), + 'password' : str(self.password), + 'pubkey' : wg_keys[1] + } + + pub_up = json.loads(requests.post(wg_api_url, data=datas, timeout=5).content.decode("utf-8")) + wg_ip = resolve(pub_up["data"]["Endpoint"].split(":")[0]) + wg_conf = [ + "[Interface]\n", + "PrivateKey = {}\n".format(wg_keys[0]), + "Address = {}\n".format(pub_up["data"]["Address"]), + "DNS = {}\n".format(pub_up["data"]["DNS"]), + "\n", + "[Peer]\n", + "Endpoint = {}:51820\n".format(wg_ip), + "AllowedIPs = 0.0.0.0/0, ::/0\n" + ] + + self.az_servers[wg_name] = { + "name": wg_name, + "provider" : self.provider, + "city" : s["city"], + "ip" : wg_ip, + "country" : country, + "tunnel" : "WireGuard" + } + + with open(wg_file, "w") as wg: + wg.writelines(wg_conf) + + + except (CalledProcessError, FileNotFoundError) as e: + self.log.emit(("info", "WireGuard is not installed/not found - skipping")) + + except (requests.exceptions.RequestException, json.JSONDecodeError): + self.log.emit(("info", "Network error: Uploading WireGuard public key failed")) + + except requests.exceptions.RequestException as e: + self.log.emit(("error", "Network error: Unable to retrieve data from api.azirevpn.com")) + self.remove_temp_dir(self.provider) + self.failed.emit("Network error&No internet connection&{}".format(self.provider)) + + else: + az_protocols = { + "protocol_1" : {"protocol": "UDP", "port": "1194"}, + "protocol_2" : {"protocol": "TCP", "port": "1194"}, + "protocol_3" : {"protocol": "UDP", "port": "443"}, + "protocol_4" : {"protocol": "TCP", "port": "443"} + } + + azire_dict = { + "server" : self.az_servers, + "protocol" : az_protocols, + "provider" : "AzireVPN" + } + + self.finished.emit(azire_dict) + def sanity_check(self, path): unrelated_files = 0 @@ -873,6 +976,26 @@ def sanity_check(self, path): return unrelated_files + def gen_wg_key(self, config): + #check if key already exists + if os.path.exists("{}/{}.conf".format(ROOTDIR, config)): + self.log.emit(("debug", "WireGuard keys for {} have already been generated".format(config.split("/")[-1]))) + wg_keys = None + + else: + self.log.emit(("info", "Generating WireGuard keys for {}".format(config.split("/")[-1]))) + + try: + private_key = check_output(["wg", "genkey"]).decode("utf-8").split("\n")[0] + pubgen = run(["wg", "pubkey"], stdout=PIPE, input=private_key, encoding='ascii') + public_key = pubgen.stdout.split("\n")[0] + wg_keys = (private_key, public_key) + except (CalledProcessError, FileNotFoundError) as e: + wg_keys = None + self.log.emit(("info", "WireGuard is not installed/not found - skipping")) + + return wg_keys + def copy_certs(self, provider): oldmask = os.umask(0o077) @@ -968,7 +1091,7 @@ def copy_certs(self, provider): def remove_temp_dir(self, provider): try: shutil.rmtree(self.temp_path) - self.log.emit(("debug", "Removed temporary directory")) + self.log.emit(("debug", "Removed temporary download directory for {}".format(provider))) except FileNotFoundError: pass diff --git a/qomui/widgets.py b/qomui/widgets.py index af5d37e..3a4d8ab 100755 --- a/qomui/widgets.py +++ b/qomui/widgets.py @@ -31,7 +31,7 @@ def _translate(context, text, disambig): ROOTDIR = "/usr/share/qomui" HOMEDIR = "{}/.qomui".format(os.path.expanduser("~")) -SUPPORTED_PROVIDERS = ["Airvpn", "Mullvad", "ProtonVPN", "PIA", "Windscribe"] +SUPPORTED_PROVIDERS = ["Airvpn", "AzireVPN", "Mullvad", "PIA", "ProtonVPN", "Windscribe"] class favouriteButton(QtWidgets.QAbstractButton): def __init__(self, parent=None): From 6a98789b5b1d6a25653d4c233d2db257c8b02178 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 19 Jan 2019 15:04:44 +0100 Subject: [PATCH 07/28] moved scripts to own directory --- qomui/tunnel.py | 12 +++++++----- {resources => scripts}/bypass_route.sh | 0 {resources => scripts}/bypass_up.sh | 0 {resources => scripts}/hop.sh | 0 {resources => scripts}/hop_down.sh | 0 setup.py | 10 ++++++---- 6 files changed, 13 insertions(+), 9 deletions(-) rename {resources => scripts}/bypass_route.sh (100%) rename {resources => scripts}/bypass_up.sh (100%) rename {resources => scripts}/hop.sh (100%) rename {resources => scripts}/hop_down.sh (100%) diff --git a/qomui/tunnel.py b/qomui/tunnel.py index b45b0ad..47de3ea 100755 --- a/qomui/tunnel.py +++ b/qomui/tunnel.py @@ -232,9 +232,9 @@ def write_config(self, ovpn_dict, edit="temp", path=None): if "bypass" in ovpn_dict: edit = "bypass" if ovpn_dict["bypass"] == "1": - config.append("iproute /usr/share/qomui/bypass_route.sh\n") + config.append("iproute {}/scripts/bypass_route.sh\n".format(ROOTDIR)) config.append("script-security 2\n") - config.append("route-up /usr/share/qomui/bypass_up.sh\n") + config.append("route-up {}/scripts/bypass_up.sh\n".format(ROOTDIR)) for line, value in enumerate(config): if value.startswith("proto ") is True: @@ -363,10 +363,12 @@ def ovpn(self, ovpn_file, h, cwd_ovpn): '--config', '{}'.format(ovpn_file), '--route-nopull', '--script-security', '2', - '--up', '/usr/share/qomui/hop.sh -f {} {}'.format(self.hop_dict["ip"], - self.server_dict["ip"] + '--up', '{}/scripts/hop.sh -f {} {}'.format( + ROOTDIR, + self.hop_dict["ip"], + self.server_dict["ip"] ), - '--down', '/usr/share/qomui/hop_down.sh {}'.format(self.hop_dict["ip"]) + '--down', '{}/scripts/hop_down.sh {}'.format(ROOTDIR, self.hop_dict["ip"]) ] elif h == "2": diff --git a/resources/bypass_route.sh b/scripts/bypass_route.sh similarity index 100% rename from resources/bypass_route.sh rename to scripts/bypass_route.sh diff --git a/resources/bypass_up.sh b/scripts/bypass_up.sh similarity index 100% rename from resources/bypass_up.sh rename to scripts/bypass_up.sh diff --git a/resources/hop.sh b/scripts/hop.sh similarity index 100% rename from resources/hop.sh rename to scripts/hop.sh diff --git a/resources/hop_down.sh b/scripts/hop_down.sh similarity index 100% rename from resources/hop_down.sh rename to scripts/hop_down.sh diff --git a/setup.py b/setup.py index e07ba70..dc0faef 100644 --- a/setup.py +++ b/setup.py @@ -24,14 +24,16 @@ 'resources/Mullvad_config', 'resources/ssl_config', 'resources/qomui.png', - 'resources/hop.sh', - 'resources/hop_down.sh', - 'resources/bypass_up.sh', - 'resources/bypass_route.sh', 'resources/airvpn_api.pem', 'resources/airvpn_cacert.pem', 'VERSION'] ), + ('/usr/share/qomui/scripts/', [ + 'scripts/hop.sh', + 'scripts/hop_down.sh', + 'scripts/bypass_up.sh', + 'scripts/bypass_route.sh' + ]), ('/usr/share/qomui/flags/', glob.glob('resources/flags/*')) ] From a75eeb3fa78f6ec1fbdfcf7932a86f51691de117 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 19 Jan 2019 15:09:52 +0100 Subject: [PATCH 08/28] backwards compatibility with Qt < 5.7 --- qomui/qomui_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index 42130b9..0324e5d 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -1124,7 +1124,7 @@ def systemtray(self): def pop_tray_menu(self): self.trayMenu.clear() - self.visibility_action = QtWidgets.QAction() + self.visibility_action = QtWidgets.QAction(self) self.visibility_action.setText("Hide") self.trayMenu.addAction(self.visibility_action) self.trayMenu.addSeparator() From 124e0bb1e20e1fb31b5b10b912da718477b2114c Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 19 Jan 2019 15:18:31 +0100 Subject: [PATCH 09/28] changed log directory --- qomui/qomui_service.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/qomui/qomui_service.py b/qomui/qomui_service.py index eee1b9c..b92673c 100755 --- a/qomui/qomui_service.py +++ b/qomui/qomui_service.py @@ -24,6 +24,7 @@ from qomui import firewall, bypass, update, dns_manager, tunnel ROOTDIR = "/usr/share/qomui" +LOGDIR = "/usr/share/qomui/logs" OPATH = "/org/qomui/service" IFACE = "org.qomui.service" BUS_NAME = "org.qomui.service" @@ -54,6 +55,10 @@ class QomuiDbus(dbus.service.Object): interface = "eth0" def __init__(self): + + if not os.path.exists(LOGDIR): + os.makedirs(LOGDIR) + self.sys_bus = dbus.SystemBus() self.bus_name = dbus.service.BusName(BUS_NAME, bus=self.sys_bus) dbus.service.Object.__init__(self, self.bus_name, OPATH) @@ -61,8 +66,8 @@ def __init__(self): self.gui_handler = GuiLogHandler(self.send_log) self.gui_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) self.logger.addHandler(self.gui_handler) - self.filehandler = logging.handlers.RotatingFileHandler("{}/qomui.log".format(ROOTDIR), - maxBytes=2*1024*1024, backupCount=1) + self.filehandler = logging.handlers.RotatingFileHandler("{}/qomui.log".format(LOGDIR), + maxBytes=2*1024*1024, backupCount=3) self.logger.addHandler(self.filehandler) self.filehandler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) self.logger.setLevel(logging.DEBUG) From 40c38cee778a73f1280bb3d1300108f496ed798a Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 19 Jan 2019 18:43:58 +0100 Subject: [PATCH 10/28] changed directory for certificates&keys --- qomui/qomui_service.py | 2 +- qomui/tunnel.py | 25 +++- qomui/update.py | 259 +++++++++++++++++++----------------- qomui/utils.py | 2 +- qomui/widgets.py | 2 +- resources/Airvpn_config | 6 +- resources/Mullvad_config | 4 +- resources/PIA_config | 6 +- resources/ProtonVPN_config | 6 +- resources/Windscribe_config | 6 +- 10 files changed, 176 insertions(+), 142 deletions(-) diff --git a/qomui/qomui_service.py b/qomui/qomui_service.py index b92673c..20fc44f 100755 --- a/qomui/qomui_service.py +++ b/qomui/qomui_service.py @@ -350,7 +350,7 @@ def change_ovpn_config(self, provider, certpath): f_source = "{}/{}".format(certpath, f) if provider in SUPPORTED_PROVIDERS: - f_dest = "{}/{}".format(ROOTDIR, f) + f_dest = "{}/{}/openvpn.conf".format(ROOTDIR, provider) else: f_dest = "{}/{}/{}".format(ROOTDIR, provider, f) diff --git a/qomui/tunnel.py b/qomui/tunnel.py index 47de3ea..d755f1a 100755 --- a/qomui/tunnel.py +++ b/qomui/tunnel.py @@ -52,7 +52,7 @@ def wireguard(self): oldmask = os.umask(0o077) path = "{}/wg_qomui.conf".format(ROOTDIR) if self.server_dict["provider"] == "Mullvad": - with open("{}/certs/mullvad_wg.conf".format(ROOTDIR), "r") as wg: + with open("{}/Mullvad/mullvad_wg.conf".format(ROOTDIR), "r") as wg: conf = wg.readlines() conf.insert(8, "PublicKey = {}\n".format(self.server_dict["public_key"])) conf.insert(9, "Endpoint = {}:{}\n".format(self.server_dict["ip"], self.server_dict["port"])) @@ -151,6 +151,9 @@ def openvpn(self): elif provider == "ProtonVPN": self.write_config(self.server_dict) + elif provider == "AzireVPN": + self.write_config(self.server_dict) + else: config_file = "{}/{}".format(ROOTDIR, self.server_dict["path"]) @@ -200,9 +203,15 @@ def write_config(self, ovpn_dict, edit="temp", path=None): ip = ovpn_dict["ip"] port = ovpn_dict["port"] protocol = ovpn_dict["protocol"] + compat = 1 if path is None: - ovpn_file = "{}/{}_config".format(ROOTDIR, provider) + if os.path.exists("{}/{}/openvpn.conf".format(ROOTDIR, provider)): + ovpn_file = "{}/{}/openvpn.conf".format(ROOTDIR, provider) + else: + #Ensure compatibility with older versions + ovpn_file = "{}/{}_config".format(ROOTDIR, provider) + compat = 0 else: ovpn_file = path @@ -268,6 +277,18 @@ def write_config(self, ovpn_dict, edit="temp", path=None): except KeyError: config.append("tls-auth {}/certs/ta.key 1 \n".format(ROOTDIR)) + elif provider == "AzireVPN": + ca = "ca {}/{}/{}.crt\n".format(ROOTDIR, provider, ovpn_dict["name"]) + tls = "tls-auth {}/{}/{}.key 1\n".format(ROOTDIR, provider, ovpn_dict["name"]) + config.append(ca) + config.append(tls) + + #Ensure compatibility with older versions: + if compat == 0: + for i, l in enumerate(config): + config[i] = l.replace("{}/".format(provider), "certs/") + + with open("{}/{}.ovpn".format(ROOTDIR, edit), "w") as ovpn_dump: ovpn_dump.writelines(config) ovpn_dump.close() diff --git a/qomui/update.py b/qomui/update.py index e6cb0f4..e180287 100755 --- a/qomui/update.py +++ b/qomui/update.py @@ -26,7 +26,6 @@ def _fromUtf8(s): ROOTDIR = "/usr/share/qomui" TEMPDIR = "/usr/share/qomui/temp" -CERTDIR = "/usr/share/qomui/certs" SUPPORTED_PROVIDERS = ["Airvpn", "AzireVPN", "Mullvad", "PIA", "ProtonVPN", "Windscribe"] def country_translate(cc): @@ -65,19 +64,12 @@ def __init__(self, credentials, folderpath=None): def run(self): self.started.emit(self.provider) self.log.emit(("debug", "Started new thread to import {}".format(self.provider))) - if os.path.exists("{}/{}".format(TEMPDIR, self.provider)): - shutil.rmtree("{}/{}".format(TEMPDIR, self.provider)) - os.makedirs("{}/{}".format(TEMPDIR, self.provider)) - - else: - os.makedirs("{}/{}".format(TEMPDIR, self.provider)) - - if not os.path.exists(CERTDIR): - os.makedirs(CERTDIR) + if os.path.exists(self.temp_path): + shutil.rmtree(self.temp_path) + os.makedirs(self.temp_path) if self.provider in SUPPORTED_PROVIDERS: getattr(self, self.provider.lower())() - else: self.add_folder() @@ -102,6 +94,16 @@ def airvpn(self): "version" : "999" } + certificates = { + "ssh_ppk": "sshtunnel.key", + "ssl_crt": "stunnel.crt", + "ca" : "ca.crt", + "ta" : "ta.key", + "key" : "user.key", + "crt" : "user.crt", + "tls_crypt" : "tls-crypt.key" + } + #Loading public RSA key with open("{}/airvpn_api.pem".format(ROOTDIR), "rb") as pem: rsa_pub_key = serialization.load_pem_public_key( @@ -141,7 +143,7 @@ def airvpn(self): if cert_xml_root.attrib[a] == "Wrong login/password.": raise ValueError("Wrong credentials") if a != "login" and a != "expirationdate": - with open("{}/{}".format(self.temp_path, a), "w") as c: + with open("{}/{}".format(self.temp_path, certificates[a]), "w") as c: c.write(cert_xml_root.attrib[a]) key_index = 0 @@ -155,7 +157,7 @@ def airvpn(self): for a in user_key.attrib: if a == "crt" or a == "key": - with open("{}/{}".format(self.temp_path, a), "w") as c: + with open("{}/{}".format(self.temp_path, certificates[a]), "w") as c: c.write(user_key.attrib[a]) data_params["act"] = "manifest" @@ -273,8 +275,8 @@ def mullvad(self): self.password = "m" self.log.emit(("info", "Downloading certificates for Mullvad")) auth = 0 + certificates = {"ca.crt":"mullvad_ca.crt" ,"api_root_ca.pem":"mullvad_crl.pem"} with requests.Session() as self.session: - try: certfiles = ["ca.crt", "api_root_ca.pem"] git_raw = "https://raw.githubusercontent.com/mullvad/mullvadvpn-app/master/dist-assets/" @@ -282,7 +284,7 @@ def mullvad(self): for c in certfiles: certificate = self.session.get("{}{}".format(git_raw, c), timeout=2) - with open("{}/{}".format(self.temp_path, c), 'w') as cert_file: + with open("{}/{}".format(self.temp_path, certificates[c]), 'w') as cert_file: cert_file.write(certificate.content.decode("utf-8")) page = self.session.get('https://www.mullvad.net/en/servers/', timeout=2) @@ -478,6 +480,25 @@ def pia(self): "tunnel" : "OpenVPN" } + certificates = { + "{}/strong/crl.rsa.4096.pem".format(self.temp_path) :"{}/pia_crl.rsa.4096.pem".format(self.temp_path), + "{}/strong/ca.rsa.4096.crt".format(self.temp_path) :"{}/pia_ca.rsa.4096.crt".format(self.temp_path) + } + + for orig, dest in certificates: + try: + shutil.copyfile(orig, dest) + + except FileNotFoundError as e: + self.log.emit(("error", e)) + + try: + shutil.rmtree("{}/ip") + shutil.rmtree("{}/strong") + + except FileNotFoundError as e: + self.log.emit(("error", e)) + self.copy_certs(self.provider) self.finished.emit(pia_dict) @@ -527,7 +548,7 @@ def windscribe(self): try: self.log.emit(("info", "Created Windscribe credentials for OpenVPN")) - with open("{}/windscribe_userpass.txt".format(CERTDIR), "w") as cd: + with open("{}/windscribe_userpass.txt".format(self.temp_path), "w") as cd: cd.write("{}\n{}\n".format(userpass["username"], userpass["password"])) self.log.emit(("debug", "Windscribe OpenVPN credentials written to {}/windscribe_userpass.txt".format(CERTDIR))) @@ -859,88 +880,113 @@ def import_configs(self): self.finished.emit(custom_dict) def azirevpn(self): - az_path = "{}/AzireVPN".format(ROOTDIR) self.az_servers = {} - if not os.path.exists(az_path): - os.makedirs(az_path) try: - self.log.emit(("info", "Downloading AzireVPN OpenVPN configs")) - az_api_url = "https://api.azirevpn.com/v1/locations" - az_servers = json.loads(requests.get(az_api_url, timeout=2).content.decode("utf-8")) + + try: + self.log.emit(("info", "Downloading AzireVPN OpenVPN configs")) + az_api_url = "https://api.azirevpn.com/v1/locations" + az_servers = json.loads(requests.get(az_api_url, timeout=2).content.decode("utf-8")) + + except requests.exceptions.RequestException as e: + az_servers = {"locations" : []} + self.log.emit(("error", "Network error: Unable to retrieve data from api.azirevpn.com")) + self.remove_temp_dir(self.provider) + self.failed.emit("Network error&No internet connection&{}".format(self.provider)) for s in az_servers["locations"]: name = s["name"] + "-openvpn" + "-azirevpn" wg_name = s["name"] + "-wireguard" + "-azirevpn" country = country_translate(s["iso"]) - ip = resolve(s["endpoints"]["openvpn"][0]["hostname"]) + hostname = s["endpoints"]["openvpn"][0]["hostname"] + ip = resolve(hostname) self.log.emit(("info", "Importing {}".format(name))) - self.az_servers[name] = { - "name": name, - "provider" : self.provider, - "city" : s["city"], - "ip" : ip, - "country" : country, - "tunnel" : "OpenVPN" - } - crt_url = s["openvpn-ca"] - crt_file = "{}/{}.crt".format(az_path, name) - crt = requests.get(az_api_url, timeout=2).content.decode("utf-8") - with open(crt_file, "w") as c: - c.write(crt) + if ip != "Failed to resolve": + self.az_servers[name] = { + "name": name, + "provider" : self.provider, + "city" : s["city"], + "ip" : ip, + "country" : country, + "tunnel" : "OpenVPN" + } + + crt_url = s["openvpn-ca"] + crt_file = "{}/{}.crt".format(self.temp_path, name) + crt = requests.get(crt_url, timeout=2).content.decode("utf-8") + with open(crt_file, "w") as c: + c.write(crt) - tls_url = s["openvpn-tls-key"] - tls_file = "{}/{}.key".format(az_path, name) - tls = requests.get(az_api_url, timeout=2).content.decode("utf-8") - with open(tls_file, "w") as t: - t.write(tls) + tls_url = s["openvpn-tls-key"] + tls_file = "{}/{}.key".format(self.temp_path, name) + tls = requests.get(tls_url, timeout=2).content.decode("utf-8") + with open(tls_file, "w") as t: + t.write(tls) + + else: + self.log.emit(("Error: Could not resolve {} - skipping".format(hostname))) try: - wg_file = "{}/{}.conf".format(az_path, wg_name) + wg_file = "{}/{}.conf".format(self.temp_path, wg_name) wg_api_url = s["endpoints"]["wireguard"] wg_keys = self.gen_wg_key(wg_file) if wg_keys is not None or self.update == "1": - datas = {'username' : str(self.username), + data = { + 'username' : str(self.username), 'password' : str(self.password), 'pubkey' : wg_keys[1] } - pub_up = json.loads(requests.post(wg_api_url, data=datas, timeout=5).content.decode("utf-8")) - wg_ip = resolve(pub_up["data"]["Endpoint"].split(":")[0]) - wg_conf = [ - "[Interface]\n", - "PrivateKey = {}\n".format(wg_keys[0]), - "Address = {}\n".format(pub_up["data"]["Address"]), - "DNS = {}\n".format(pub_up["data"]["DNS"]), - "\n", - "[Peer]\n", - "Endpoint = {}:51820\n".format(wg_ip), - "AllowedIPs = 0.0.0.0/0, ::/0\n" - ] - - self.az_servers[wg_name] = { - "name": wg_name, - "provider" : self.provider, - "city" : s["city"], - "ip" : wg_ip, - "country" : country, - "tunnel" : "WireGuard" - } + pub_up = requests.post(wg_api_url, data=data, timeout=5) + if pub_up.status_code == 200: + api_resp = json.loads(pub_up.content.decode("utf-8")) + + if api_resp["status"] != "error": + wg_ip = resolve(api_resp["data"]["Endpoint"].split(":")[0]) + wg_conf = [ + "[Interface]\n", + "PrivateKey = {}\n".format(wg_keys[0]), + "Address = {}\n".format(api_resp["data"]["Address"]), + "DNS = {}\n".format(api_resp["data"]["DNS"]), + "\n", + "[Peer]\n", + "PublicKey = {}\n".format(api_resp["data"]["PublicKey"]), + "Endpoint = {}:51820\n".format(wg_ip), + "AllowedIPs = 0.0.0.0/0, ::/0\n" + ] + + self.az_servers[wg_name] = { + "name": wg_name, + "provider" : self.provider, + "city" : s["city"], + "ip" : wg_ip, + "country" : country, + "tunnel" : "WireGuard", + "path" : "{}/{}.conf".format(self.provider, wg_name) + } - with open(wg_file, "w") as wg: - wg.writelines(wg_conf) + with open(wg_file, "w") as wg: + wg.writelines(wg_conf) + else: + m = "AzireVPN: Authentication failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) + self.log.emit(("error", m)) + self.remove_temp_dir(self.provider) + self.failed.emit(m) except (CalledProcessError, FileNotFoundError) as e: self.log.emit(("info", "WireGuard is not installed/not found - skipping")) - except (requests.exceptions.RequestException, json.JSONDecodeError): + except (requests.exceptions.RequestException, json.JSONDecodeError) as e: + self.log.emit(("debug", e)) self.log.emit(("info", "Network error: Uploading WireGuard public key failed")) - except requests.exceptions.RequestException as e: - self.log.emit(("error", "Network error: Unable to retrieve data from api.azirevpn.com")) + except Exception as e: + self.log.emit(("debug", e)) + self.log.emit(("error", "An unexpected error occured: Aborting")) self.remove_temp_dir(self.provider) self.failed.emit("Network error&No internet connection&{}".format(self.provider)) @@ -957,7 +1003,8 @@ def azirevpn(self): "protocol" : az_protocols, "provider" : "AzireVPN" } - + + self.copy_certs(self.provider) self.finished.emit(azire_dict) def sanity_check(self, path): @@ -978,7 +1025,7 @@ def sanity_check(self, path): def gen_wg_key(self, config): #check if key already exists - if os.path.exists("{}/{}.conf".format(ROOTDIR, config)): + if os.path.exists(config): self.log.emit(("debug", "WireGuard keys for {} have already been generated".format(config.split("/")[-1]))) wg_keys = None @@ -997,57 +1044,30 @@ def gen_wg_key(self, config): return wg_keys def copy_certs(self, provider): - oldmask = os.umask(0o077) - - if not os.path.exists("{}/certs".format(ROOTDIR)): - os.makedirs("{}/certs".format(ROOTDIR)) + provider_dir = "{}/{}".format(ROOTDIR, provider) + if not os.path.exists(provider_dir): + os.makedirs(provider_dir) - with open("{}/{}-auth.txt".format(CERTDIR, self.provider) , "w") as passfile: + oldmask = os.umask(0o077) + with open("{}/{}-auth.txt".format(provider_dir, self.provider) , "w") as passfile: passfile.write('{}\n{}\n'.format(self.username, self.password)) if provider in SUPPORTED_PROVIDERS: - self.Airvpn_files = [ - ("ssh_ppk", "sshtunnel.key"), - ("ssl_crt", "stunnel.crt"), - ("ca", "ca.crt"), - ("ta", "ta.key"), - ("key", "user.key"), - ("crt", "user.crt"), - ("tls_crypt", "tls-crypt.key") - ] - - self.Mullvad_files = [ - ("ca.crt", "mullvad_ca.crt"), - ("api_root_ca.pem", "mullvad_crl.pem"), - ("mullvad_wg.conf", "mullvad_wg.conf") - ] - - self.PIA_files = [ - ("strong/crl.rsa.4096.pem", "pia_crl.rsa.4096.pem"), - ("strong/ca.rsa.4096.crt", "pia_ca.rsa.4096.crt") - ] - - self.Windscribe_files = [ - ("ca.crt", "ca_ws.crt"), - ("ta.key", "ta_ws.key"), - ] + for f in os.listdir(self.temp_path): + shutil.copyfile("{}/{}".format(self.temp_path, f), "{}/{}".format(provider_dir, f)) + Popen(['chown', 'root', '{}/{}'.format(provider_dir, f)]) + Popen(['chmod', '0600', '{}/{}'.format(provider_dir, f)]) - self.ProtonVPN_files = [ - ("proton_ca.crt", "proton_ca.crt"), - ("proton_ta.key", "proton_ta.key") - ] - - for cert in getattr(self, "{}_files".format(provider)): - - try: - origin = "{}/{}".format(self.temp_path, cert[0]) - dest = "{}/{}".format(CERTDIR, cert[1]) - shutil.copyfile(origin, dest) - self.log.emit(("debug", "Copied {} to {}".format(origin, dest))) - - except FileNotFoundError: - self.log.emit(("error", "Copying {} to {} failed: No such file".format(cert, CERTDIR))) + try: + openvpn_orig_conf = "{}/{}_config".format(ROOTDIR, provider) + openvpn_dest_conf = "{}/{}/openvpn.conf".format(ROOTDIR, provider) + if not os.path.exists(openvpn_dest_conf): + shutil.copyfile(openvpn_orig_conf, openvpn_dest_conf) + Popen(['chmod', '0655', openvpn_dest_conf]) + + except FileNotFoundError: + self.log.emit(("error", "{} does not exist".format(openvpn_orig_conf))) else: path = "{}/copy/".format(self.temp_path) @@ -1078,13 +1098,6 @@ def copy_certs(self, provider): shutil.copytree(f_source, f_dest) self.log.emit(("debug", "copied folder {} to {}".format(f, f_dest))) - #doesn't work if importing existing provider - #shutil.copytree("{}/copy/".format(self.temp_path), "{}/{}/".format(ROOTDIR, provider)) - - for key in [f for f in os.listdir("{}/certs".format(ROOTDIR))]: - Popen(['chown', 'root', '{}/certs/{}'.format(ROOTDIR, key)]) - Popen(['chmod', '0600', '{}/certs/{}'.format(ROOTDIR, key)]) - os.umask(oldmask) self.remove_temp_dir(self.provider) diff --git a/qomui/utils.py b/qomui/utils.py index 59ab7cc..d7a2eff 100755 --- a/qomui/utils.py +++ b/qomui/utils.py @@ -5,7 +5,7 @@ import getpass import pwd -SUPPORTED_PROVIDERS = ["Airvpn", "Mullvad", "ProtonVPN", "PIA", "Windscribe"] +SUPPORTED_PROVIDERS = ["Airvpn", "AzireVPN", "Mullvad", "ProtonVPN", "PIA", "Windscribe"] def get_user_group(): username = getpass.getuser() diff --git a/qomui/widgets.py b/qomui/widgets.py index 3a4d8ab..3717afb 100755 --- a/qomui/widgets.py +++ b/qomui/widgets.py @@ -997,7 +997,7 @@ def load_config_file(self): def display_config(self): if self.provider in SUPPORTED_PROVIDERS: - config = "{}/{}_config".format(ROOTDIR, self.provider) + config = "{}/{}/openvpn.conf".format(ROOTDIR, self.provider) else: config = "{}/{}".format(ROOTDIR, self.server_info["path"]) diff --git a/resources/Airvpn_config b/resources/Airvpn_config index 8d85aa6..e28e642 100644 --- a/resources/Airvpn_config +++ b/resources/Airvpn_config @@ -12,7 +12,7 @@ cipher AES-256-CBC comp-lzo no auth-nocache verb 3 -ca /usr/share/qomui/certs/ca.crt -cert /usr/share/qomui/certs/user.crt -key /usr/share/qomui/certs/user.key +ca /usr/share/qomui/Airvpn/ca.crt +cert /usr/share/qomui/Airvpn/user.crt +key /usr/share/qomui/Airvpn/user.key diff --git a/resources/Mullvad_config b/resources/Mullvad_config index 6584137..1418b4a 100644 --- a/resources/Mullvad_config +++ b/resources/Mullvad_config @@ -15,7 +15,7 @@ sndbuf 524288 rcvbuf 524288 verb 3 -auth-user-pass /usr/share/qomui/certs/Mullvad-auth.txt -ca /usr/share/qomui/certs/mullvad_ca.crt +auth-user-pass /usr/share/qomui/Mullvad/Mullvad-auth.txt +ca /usr/share/qomui/Mullvad/mullvad_ca.crt tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA diff --git a/resources/PIA_config b/resources/PIA_config index 7b54dcb..2c7b522 100644 --- a/resources/PIA_config +++ b/resources/PIA_config @@ -10,11 +10,11 @@ cipher aes-256-cbc auth sha256 tls-client remote-cert-tls server -auth-user-pass /usr/share/qomui/certs/PIA-auth.txt +auth-user-pass /usr/share/qomui/PIA/PIA-auth.txt comp-lzo no verb 3 reneg-sec 0 -crl-verify /usr/share/qomui/certs/pia_crl.rsa.4096.pem -ca /usr/share/qomui/certs/pia_ca.rsa.4096.crt +crl-verify /usr/share/qomui/PIA/pia_crl.rsa.4096.pem +ca /usr/share/qomui/PIA/pia_ca.rsa.4096.crt disable-occ diff --git a/resources/ProtonVPN_config b/resources/ProtonVPN_config index 8154220..a11a4bd 100644 --- a/resources/ProtonVPN_config +++ b/resources/ProtonVPN_config @@ -19,7 +19,7 @@ pull fast-io key-direction 1 -auth-user-pass /usr/share/qomui/certs/ProtonVPN-auth.txt -ca /usr/share/qomui/certs/proton_ca.crt -tls-auth /usr/share/qomui/certs/proton_ta.key +auth-user-pass /usr/share/qomui/ProtonVPN/ProtonVPN-auth.txt +ca /usr/share/qomui/ProtonVPN/proton_ca.crt +tls-auth /usr/share/qomui/ProtonVPN/proton_ta.key diff --git a/resources/Windscribe_config b/resources/Windscribe_config index cc89e51..b9a2e08 100644 --- a/resources/Windscribe_config +++ b/resources/Windscribe_config @@ -16,7 +16,7 @@ persist-key persist-tun key-direction 1 -auth-user-pass /usr/share/qomui/certs/Windscribe-auth.txt -ca /usr/share/qomui/certs/ca_ws.crt -tls-auth /usr/share/qomui/certs/ta_ws.key +auth-user-pass /usr/share/qomui/Windscribe/Windscribe-auth.txt +ca /usr/share/qomui/Windscribe/ca.crt +tls-auth /usr/share/qomui/Windscribe/ta.key From c0dc8e23fabf4b0486637afbc6adbe8d8f7751dd Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 19 Jan 2019 20:31:52 +0100 Subject: [PATCH 11/28] auto-update interval changed --- qomui/qomui_gui.py | 57 ++++++++++++++++++++++++------------------ qomui/qomui_service.py | 2 +- qomui/update.py | 20 +++++++++------ qomui/widgets.py | 2 +- 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index 0324e5d..6dceb23 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -74,6 +74,7 @@ class QomuiGui(QtWidgets.QWidget): bypass_dict = {} config_dict = {} packetmanager = None + queue = [] tunnel_list = ["OpenVPN", "WireGuard"] config_list = [ "firewall", @@ -2470,32 +2471,40 @@ def timeout(self, tunnel, name): def update_check(self): QtWidgets.QApplication.restoreOverrideCursor() - for provider in SUPPORTED_PROVIDERS: - if provider in self.provider_list: - try: - get_last = self.config_dict["{}_last".format(provider)] - last_update = datetime.strptime(get_last, '%Y-%m-%d %H:%M:%S.%f') - time_now = datetime.utcnow() - delta = time_now.date() - last_update.date() - days_since = delta.days - self.logger.info("Last {} update: {} days ago".format(provider, days_since)) - - if days_since >= 5: - credentials = { - "provider" : provider, - "credentials" : "unknown", - "folderpath" : "None", - "homedir" : HOMEDIR, - "update" : "0" - } - - if self.config_dict["auto_update"] == 1: - self.logger.info("Updating {}".format(provider)) - self.dbus_call("import_thread", credentials) + if not self.queue: + self.queue = [p for p in SUPPORTED_PROVIDERS if p in self.provider_list] - except KeyError: - self.logger.debug("Update timestamp for {} not found".format(provider)) + print(self.queue) + provider = self.queue[0] + print(provider) + + try: + get_last = self.config_dict["{}_last".format(provider)] + last_update = datetime.strptime(get_last, '%Y-%m-%d %H:%M:%S.%f') + time_now = datetime.utcnow() + delta = time_now.date() - last_update.date() + days_since = delta.days + self.logger.info("Last {} update: {} days ago".format(provider, days_since)) + + if days_since >= 0: + credentials = { + "provider" : provider, + "credentials" : "unknown", + "folderpath" : "None", + "homedir" : HOMEDIR, + "update" : "0" + } + + if self.config_dict["auto_update"] == 1: + self.logger.info("Updating {}".format(provider)) + self.dbus_call("import_thread", credentials) + + except KeyError: + self.logger.debug("Update timestamp for {} not found".format(provider)) + + finally: + self.queue.remove(provider) def reconnect(self): if self.tunnel_active == 1: diff --git a/qomui/qomui_service.py b/qomui/qomui_service.py index 20fc44f..9f14090 100755 --- a/qomui/qomui_service.py +++ b/qomui/qomui_service.py @@ -367,7 +367,7 @@ def import_thread(self, credentials): if credentials["credentials"] == "unknown": try: - auth_file = "{}/certs/{}-auth.txt".format(ROOTDIR, provider) + auth_file = "{}/{}/{}-auth.txt".format(ROOTDIR, provider, provider) with open(auth_file, "r") as auth: up = auth.read().split("\n") diff --git a/qomui/update.py b/qomui/update.py index e180287..4ded622 100755 --- a/qomui/update.py +++ b/qomui/update.py @@ -59,7 +59,7 @@ def __init__(self, credentials, folderpath=None): try: self.key = credentials["key"] except KeyError: - pass + self.key = "Default" def run(self): self.started.emit(self.provider) @@ -95,7 +95,7 @@ def airvpn(self): } certificates = { - "ssh_ppk": "sshtunnel.key", + "ssh_key": "sshtunnel.key", "ssl_crt": "stunnel.crt", "ca" : "ca.crt", "ta" : "ta.key", @@ -143,8 +143,11 @@ def airvpn(self): if cert_xml_root.attrib[a] == "Wrong login/password.": raise ValueError("Wrong credentials") if a != "login" and a != "expirationdate": - with open("{}/{}".format(self.temp_path, certificates[a]), "w") as c: - c.write(cert_xml_root.attrib[a]) + try: + with open("{}/{}".format(self.temp_path, certificates[a]), "w") as c: + c.write(cert_xml_root.attrib[a]) + except KeyError: + pass key_index = 0 keys_available = len(cert_xml_root[0]) @@ -229,13 +232,14 @@ def airvpn(self): self.finished.emit(airvpn_data) except ValueError as e: - self.log.emit(("debug", e)) + self.log.emit(("debug", e.args)) m = "Airvpn download failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) self.remove_temp_dir(self.provider) self.failed.emit(m) + except Exception as e: - self.log.emit(("debug", e)) + self.log.emit(("debug", e.args)) self.log.emit(("info", "Airvpn: Request failed - aborting")) self.remove_temp_dir(self.provider) self.failed.emit("Sorry, something went wrong") @@ -550,7 +554,7 @@ def windscribe(self): self.log.emit(("info", "Created Windscribe credentials for OpenVPN")) with open("{}/windscribe_userpass.txt".format(self.temp_path), "w") as cd: cd.write("{}\n{}\n".format(userpass["username"], userpass["password"])) - self.log.emit(("debug", "Windscribe OpenVPN credentials written to {}/windscribe_userpass.txt".format(CERTDIR))) + self.log.emit(("debug", "Windscribe OpenVPN credentials written to {}/windscribe_userpass.txt".format(self.temp_path))) self.windscribe_get_servers() @@ -988,7 +992,7 @@ def azirevpn(self): self.log.emit(("debug", e)) self.log.emit(("error", "An unexpected error occured: Aborting")) self.remove_temp_dir(self.provider) - self.failed.emit("Network error&No internet connection&{}".format(self.provider)) + self.failed.emit("AzireVPN import failed&An unknown error occured&{}".format(self.provider)) else: az_protocols = { diff --git a/qomui/widgets.py b/qomui/widgets.py index 3717afb..428e92e 100755 --- a/qomui/widgets.py +++ b/qomui/widgets.py @@ -610,7 +610,7 @@ def run(self): time_measure = time.time() elapsed = time_measure - start_time - if int(elapsed) % 3600 == 0: + if int(elapsed) % 900 == 0: self.check.emit() return_time = self.time_format(int(elapsed)) From b3ddd58513694a93bf6d4c47d32d945ffa517e4b Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 19 Jan 2019 22:23:52 +0100 Subject: [PATCH 12/28] removed print statements --- qomui/qomui_gui.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index 6dceb23..fa4439f 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -1400,7 +1400,7 @@ def save_options(self, temp_config, firewall=None): try: check_call(update_cmd) self.logger.info("Configuration changes applied successfully") - self.dbus_call("load_firewall", 1) + self.dbus_call("load_firewall", 0) self.dbus_call("bypass", {**self.routes, **utils.get_user_group()}) self.notify( "Qomui: configuration changed", @@ -2474,10 +2474,8 @@ def update_check(self): if not self.queue: self.queue = [p for p in SUPPORTED_PROVIDERS if p in self.provider_list] - - print(self.queue) + provider = self.queue[0] - print(provider) try: get_last = self.config_dict["{}_last".format(provider)] @@ -2487,7 +2485,7 @@ def update_check(self): days_since = delta.days self.logger.info("Last {} update: {} days ago".format(provider, days_since)) - if days_since >= 0: + if days_since >= 5: credentials = { "provider" : provider, "credentials" : "unknown", From bd4670f223e7ee827824065bf1f5f0ae18a0736c Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sun, 20 Jan 2019 00:14:34 +0100 Subject: [PATCH 13/28] firewall not deactivating on gui exit --- qomui/firewall.py | 24 +++++++++----- qomui/qomui_gui.py | 15 ++++----- qomui/qomui_service.py | 75 +++++++++++++++--------------------------- 3 files changed, 49 insertions(+), 65 deletions(-) diff --git a/qomui/firewall.py b/qomui/firewall.py index c2dfde8..243e772 100755 --- a/qomui/firewall.py +++ b/qomui/firewall.py @@ -73,10 +73,12 @@ def apply_rules(opt, block_lan=0, preserve=0): save_existing_rules_6(fw_rules) for rule in fw_rules["flush"]: - add_rule(rule) + if opt != 2: + add_rule(rule) for rule in fw_rules["flushv6"]: - add_rule_6(rule) + if opt != 2: + add_rule_6(rule) logging.info("iptables: flushed existing rules") @@ -210,6 +212,7 @@ def save_iptables(): outfile.flush() if save.stderr: + print(save.stderr) logging.debug("Failed to save current iptables rules") else: @@ -217,15 +220,18 @@ def save_iptables(): def restore_iptables(): - infile = open("{}/iptables_before.rules".format(ROOTDIR), "r") - restore = Popen(["iptables-restore"], stdin=infile, stderr=PIPE) - restore.wait() + try: + #infile = open("{}/iptables_before.rules".format(ROOTDIR), "r") + restore = Popen(["iptables-restore", "{}/iptables_before.rules".format(ROOTDIR)], stderr=PIPE) - if restore.stderr: - logging.debug("Failed to restore iptables rules") + if restore.stderr: + logging.debug("Failed to restore iptables rules") - else: - logging.debug("restored previous iptables rules") + else: + logging.debug("Restored previous iptables rules") + + except (CalledProcessError, FileNotFoundError): + logging.debug("FileNotFoundError: Failed to restore iptables rules") diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index fa4439f..4b57d59 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -1154,6 +1154,10 @@ def activate_window(self): def shutdown(self): self.tray.hide() self.kill() + self.disconnect_bypass() + self.dbus_call("load_firewall", 2) + with open ("{}/server.json".format(HOMEDIR), "w") as s: + json.dump(self.server_dict, s) sys.exit() def restoreUi(self, reason): @@ -1191,12 +1195,7 @@ def closeEvent(self, event): self.hide() elif ret == 0: - self.tray.hide() - self.kill() - self.disconnect_bypass() - self.dbus_call("load_firewall", 2) - with open ("{}/server.json".format(HOMEDIR), "w") as s: - json.dump(self.server_dict, s) + self.shutdown() self.exit_event.accept() def change_timeout(self): @@ -1400,7 +1399,7 @@ def save_options(self, temp_config, firewall=None): try: check_call(update_cmd) self.logger.info("Configuration changes applied successfully") - self.dbus_call("load_firewall", 0) + self.dbus_call("load_firewall", 1) self.dbus_call("bypass", {**self.routes, **utils.get_user_group()}) self.notify( "Qomui: configuration changed", @@ -2474,7 +2473,7 @@ def update_check(self): if not self.queue: self.queue = [p for p in SUPPORTED_PROVIDERS if p in self.provider_list] - + provider = self.queue[0] try: diff --git a/qomui/qomui_service.py b/qomui/qomui_service.py index 9f14090..e12793d 100755 --- a/qomui/qomui_service.py +++ b/qomui/qomui_service.py @@ -154,7 +154,7 @@ def add_pid(self, pid): #get fw configuration - might be called from gui after config change @dbus.service.method(BUS_NAME, in_signature='i', out_signature='') - def load_firewall(self, activate): + def load_firewall(self, stage): try: with open('{}/config.json'.format(ROOTDIR), 'r') as c: self.config = json.load(c) @@ -166,55 +166,34 @@ def load_firewall(self, activate): try: self.logger.setLevel(self.config["log_level"].upper()) - - except KeyError: - pass - - try: - if self.config["fw_gui_only"] == 0: - activate = 1 - - except KeyError: - activate = 1 - - try: - if self.config["preserve_rules"] == 1: - preserve = 1 - else: - preserve = 0 - - except KeyError: - preserve = 0 - - try: - if self.config["block_lan"] == 1: - block_lan = 1 - else: - block_lan = 0 - - except KeyError: - block_lan = 0 - - try: - if activate == 1: - firewall.save_iptables() - firewall.apply_rules(self.config["firewall"], block_lan=block_lan, preserve=preserve) - - elif activate == 2: - if self.config["fw_gui_only"] == 1: - firewall.restore_iptables() - firewall.apply_rules(0, block_lan=0, preserve=preserve) - - try: - bypass.delete_cgroup(self.default_interface_4, self.default_interface_6) - - except AttributeError: - pass - self.disable_ipv6(self.config["ipv6_disable"]) - + fw = self.config["firewall"] + gui_only = self.config["fw_gui_only"] + block_lan=self.config["block_lan"] + preserve=self.config["preserve_rules"] + + if fw == 1 and gui_only == 0: + opt = 1 + elif gui_only == 1 and stage == 1: + firewall.save_iptables() + opt = fw + elif gui_only == 1 and stage == 2: + firewall.restore_iptables() + opt = 2 + elif fw == 0 and stage == 1: + opt = 0 + firewall.restore_iptables() + else: + opt = 2 + + if opt < 2: + firewall.apply_rules( + opt, + block_lan=block_lan, + preserve=preserve + ) except KeyError: - self.logger.warning('Could not read all values from config file') + self.logger.warning('Malformed config file') #default dns is always set to the alternative servers self.dns = self.config["alt_dns1"] From 486ff2e413b2901b46ef169fb892095a21efcbac Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sun, 20 Jan 2019 00:38:02 +0100 Subject: [PATCH 14/28] only check for existing iptables rules if respective option is active --- qomui/firewall.py | 36 +++++++++++++----------------------- qomui/qomui_gui.py | 1 - 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/qomui/firewall.py b/qomui/firewall.py index 243e772..f29b584 100755 --- a/qomui/firewall.py +++ b/qomui/firewall.py @@ -69,8 +69,6 @@ def add_rule_6(rule): def apply_rules(opt, block_lan=0, preserve=0): fw_rules = get_config() - save_existing_rules(fw_rules) - save_existing_rules_6(fw_rules) for rule in fw_rules["flush"]: if opt != 2: @@ -82,12 +80,12 @@ def apply_rules(opt, block_lan=0, preserve=0): logging.info("iptables: flushed existing rules") - for rule in saved_rules: - if preserve == 1: + if preserve == 1: + save_existing_rules(fw_rules) + save_existing_rules_6(fw_rules) + for rule in saved_rules: add_rule(rule) - - for rule in saved_rules_6: - if preserve == 1: + for rule in saved_rules_6: add_rule_6(rule) if opt == 1: @@ -206,29 +204,21 @@ def check_firewall_services(): return detected_firewall def save_iptables(): - outfile = open("{}/iptables_before.rules".format(ROOTDIR), "w") - save = Popen(["iptables-save"], stdout=outfile, stderr=PIPE) - save.wait() - outfile.flush() - - if save.stderr: - print(save.stderr) - logging.debug("Failed to save current iptables rules") - - else: + try: + outfile = open("{}/iptables_before.rules".format(ROOTDIR), "w") + save = Popen(["iptables-save"], stdout=outfile, stderr=PIPE) + save.wait() + outfile.flush() logging.debug("Saved iptables rule") + except (CalledProcessError, FileNotFoundError): + logging.debug("Failed to save current iptables rules") def restore_iptables(): try: #infile = open("{}/iptables_before.rules".format(ROOTDIR), "r") restore = Popen(["iptables-restore", "{}/iptables_before.rules".format(ROOTDIR)], stderr=PIPE) - - if restore.stderr: - logging.debug("Failed to restore iptables rules") - - else: - logging.debug("Restored previous iptables rules") + logging.debug("Restored previous iptables rules") except (CalledProcessError, FileNotFoundError): logging.debug("FileNotFoundError: Failed to restore iptables rules") diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index 4b57d59..b6a0569 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -146,7 +146,6 @@ def __init__(self, parent = None): self.check_other_instance() self.load_saved_files() self.systemtray() - self.net_mon_thread = monitor.NetMon() self.net_mon_thread.log.connect(self.log_from_thread) self.net_mon_thread.net_state_change.connect(self.network_change) From 2405eef179802cc24c0e51bdc85994ead5e449dd Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sun, 20 Jan 2019 11:18:44 +0100 Subject: [PATCH 15/28] added ipv6 checks --- qomui/bypass.py | 23 ++---- qomui/firewall.py | 186 +++++++++++++++++++++++++++------------------- 2 files changed, 115 insertions(+), 94 deletions(-) diff --git a/qomui/bypass.py b/qomui/bypass.py index 23def17..bb90b19 100755 --- a/qomui/bypass.py +++ b/qomui/bypass.py @@ -45,12 +45,9 @@ def create_cgroup(user, group, interface, gw=None, gw_6=None, default_int=None) rt_tables.write("11 bypass_qomui\n") logging.debug("Bypass: Created new routing table") - for rule in cgroup_iptables: - firewall.add_rule(rule) - + firewall.batch_rule(cgroup_iptables) if gw_6 != "None" and default_int == interface: - for rule in cgroup_iptables: - firewall.add_rule_6(rule) + firewall.batch_rule_6(cgroup_iptables) else: logging.debug("Blocking ipv6 via bypass_qomui") @@ -61,9 +58,7 @@ def create_cgroup(user, group, interface, gw=None, gw_6=None, default_int=None) cgroup_iptables.insert(2, ["-I", "OUTPUT", "1", "-m", "cgroup", "--cgroup", "0x00110011", "-j", "DROP"]) cgroup_iptables.pop(3) cgroup_iptables.insert(3, ["-I", "INPUT", "1", "-m", "cgroup", "--cgroup", "0x00110011", "-j", "DROP"]) - - for rule in cgroup_iptables: - firewall.add_rule_6(rule) + firewall.batch_rule_6(cgroup_iptables) try: check_call(["ip", "rule", "add", "fwmark", "11", "table", "bypass_qomui"]) @@ -126,11 +121,8 @@ def delete_cgroup(interface): except CalledProcessError: pass - for rule in cgroup_iptables_del: - firewall.add_rule(rule) - - for rule in cgroup_iptables_del: - firewall.add_rule_6(rule) + firewall.batch_rule(cgroup_iptables_del) + firewall.batch_rule_6(cgroup_iptables_del) try: os.rmdir(cgroup_path) @@ -152,8 +144,7 @@ def set_bypass_vpn(interface, interface_cmd, tun, tun_cmd): "-o", interface, "-j", "MASQUERADE" ]] - for rule in postroutes: - firewall.add_rule(rule) - firewall.add_rule_6(rule) + firewall.batch_rule(postroutes) + firewall.batch_rule_6(postroutes) diff --git a/qomui/firewall.py b/qomui/firewall.py index f29b584..dcf2f7c 100755 --- a/qomui/firewall.py +++ b/qomui/firewall.py @@ -12,6 +12,23 @@ ip_cmd = ["iptables", "--wait",] ip6_cmd = ["ip6tables", "--wait",] devnull = open(os.devnull, 'w') +ip6_available = True + + + +def check_ipv6(): + try: + ipv6_info = open("/proc/net/if_inet6" , "r").read() + if ipv6_info: + return True + + else: + logging.info("ipv6 stack not available") + return False + + except (OSError, FileNotFoundError) as e: + logging.debug("Unable to determine whether ipv6 is available") + return True def add_rule(rule): a = 1 @@ -40,85 +57,74 @@ def add_rule(rule): if "-D" not in rule: logging.warning("iptables: failed to apply {}".format(rule)) -def add_rule_6(rule): - a = 1 - try: - check = rule[:] - if "-D" not in check: - if check[0] == "-A": - check[0] = "-C" - elif check[0] == "-I": - check[0] = "-C" - check.pop(2) - elif check[2] == "-A": - check[2] = "-C" - check_call(ip6_cmd + check, stdout=devnull, stderr=devnull) - logging.debug("ipt6ables: {} already exists".format(rule)) - a = 0 - except (IndexError, CalledProcessError): - pass +def add_rule_6(rule, check=0): + if check == 1 or check_ipv6() is True: + a = 1 + try: + check = rule[:] + if "-D" not in check: + if check[0] == "-A": + check[0] = "-C" + elif check[0] == "-I": + check[0] = "-C" + check.pop(2) + elif check[2] == "-A": + check[2] = "-C" + check_call(ip6_cmd + check, stdout=devnull, stderr=devnull) + logging.debug("ipt6ables: {} already exists".format(rule)) + a = 0 + except (IndexError, CalledProcessError): + pass - try: - if a == 1: - check_call(ip6_cmd + rule, stdout=devnull, stderr=devnull) - logging.debug("ip6tables: applied {}".format(rule)) + try: + if a == 1: + check_call(ip6_cmd + rule, stdout=devnull, stderr=devnull) + logging.debug("ip6tables: applied {}".format(rule)) - except CalledProcessError: - if "-D" not in rule: - logging.warning("ip6tables: failed to apply {}".format(rule)) + except CalledProcessError: + if "-D" not in rule: + logging.warning("ip6tables: failed to apply {}".format(rule)) def apply_rules(opt, block_lan=0, preserve=0): fw_rules = get_config() - - for rule in fw_rules["flush"]: - if opt != 2: - add_rule(rule) - - for rule in fw_rules["flushv6"]: - if opt != 2: - add_rule_6(rule) + if opt != 2: + batch_rule(fw_rules["flush"]) + batch_rule_6(fw_rules["flushv6"]) logging.info("iptables: flushed existing rules") if preserve == 1: save_existing_rules(fw_rules) save_existing_rules_6(fw_rules) - for rule in saved_rules: - add_rule(rule) - for rule in saved_rules_6: - add_rule_6(rule) + batch_rule(saved_rules) + batch_rule_6(saved_rules_6) if opt == 1: - for rule in fw_rules["defaults"]: - add_rule(rule) + batch_rule(fw_rules["defaults"]) + batch_rule_6(fw_rules["defaultsv6"]) if block_lan == 0: - for rule in fw_rules["ipv4local"]: - add_rule(rule) - - for rule in fw_rules["ipv6local"]: - add_rule_6(rule) - - for rule in fw_rules["defaultsv6"]: - add_rule_6(rule) - - for rule in fw_rules["ipv4rules"]: - add_rule(rule) - - for rule in fw_rules["ipv6rules"]: - add_rule_6(rule) + batch_rule(fw_rules["ipv4local"]) + batch_rule_6(fw_rules["ipv6local"]) + batch_rule(fw_rules["ipv4rules"]) + batch_rule_6(fw_rules["ipv6rules"]) logging.info("iptables: activated firewall") elif opt == 0: - for rule in fw_rules["unsecure"]: - add_rule(rule) - - for rule in fw_rules["unsecurev6"]: - add_rule_6(rule) - + batch_rule(fw_rules["unsecure"]) + batch_rule_6(["unsecurev6"]) logging.info("iptables: deactivated firewall") +def batch_rule(rules): + for rule in rules: + add_rule(rule) + +def batch_rule_6(rules): + if check_ipv6() is True: + for rule in rules: + add_rule_6(rule, check=1) + def save_existing_rules(fw_rules): try: existing_rules = check_output(["iptables", "-S"]).decode("utf-8") @@ -139,23 +145,24 @@ def save_existing_rules(fw_rules): logging.error("ip4tables: Could not read active rules - {}".format(e)) def save_existing_rules_6(fw_rules): - try: - existing_rules = check_output(["ip6tables", "-S"]).decode("utf-8") - for line in existing_rules.split('\n'): - rpl = line.replace("/32", "") - rule = shlex.split(rpl) - if len(rule) != 0: - match = 0 - omit = fw_rules["ipv6rules"] + fw_rules["flushv6"] + fw_rules["ipv6local"] - for x in omit: - if Counter(x) == Counter(rule): - match = 1 - if match == 0 and rule not in saved_rules_6: - saved_rules_6.append(rule) - match = 0 - - except (CalledProcessError, FileNotFoundError) as e: - logging.error("ip6tables: Could not read active rules - {}".format(e)) + if check_ipv6() is True: + try: + existing_rules = check_output(["ip6tables", "-S"]).decode("utf-8") + for line in existing_rules.split('\n'): + rpl = line.replace("/32", "") + rule = shlex.split(rpl) + if len(rule) != 0: + match = 0 + omit = fw_rules["ipv6rules"] + fw_rules["flushv6"] + fw_rules["ipv6local"] + for x in omit: + if Counter(x) == Counter(rule): + match = 1 + if match == 0 and rule not in saved_rules_6: + saved_rules_6.append(rule) + match = 0 + + except (CalledProcessError, FileNotFoundError) as e: + logging.error("ip6tables: Could not read active rules - {}".format(e)) def allow_dest_ip(ip, action): rule = [action, 'OUTPUT', '-d', ip, '-j', 'ACCEPT'] @@ -165,7 +172,8 @@ def allow_dest_ip(ip, action): add_rule(rule) elif len(ip.split(":")) >= 4: - add_rule_6(rule) + if check_ipv6() is True: + add_rule_6(rule) except: pass @@ -174,7 +182,7 @@ def get_config(): with open("{}/firewall.json".format(ROOTDIR), "r") as f: return json.load(f) except (FileNotFoundError, json.decoder.JSONDecodeError) as e: - logging.debug("Loading default firewall configuration") + logging.info("Loading default firewall configuration") try: with open("{}/firewall_default.json".format(ROOTDIR), "r") as f: return json.load(f) @@ -209,11 +217,23 @@ def save_iptables(): save = Popen(["iptables-save"], stdout=outfile, stderr=PIPE) save.wait() outfile.flush() - logging.debug("Saved iptables rule") + logging.debug("Saved iptables rules") except (CalledProcessError, FileNotFoundError): logging.debug("Failed to save current iptables rules") + if check_ipv6() is True: + + try: + outfile6 = open("{}/ip6tables_before.rules".format(ROOTDIR), "w") + save6 = Popen(["ip6tables-save"], stdout=outfile, stderr=PIPE) + save6.wait() + outfile6.flush() + logging.debug("Saved ip6tables rules") + + except (CalledProcessError, FileNotFoundError): + logging.debug("Failed to save current ip6tables rules") + def restore_iptables(): try: #infile = open("{}/iptables_before.rules".format(ROOTDIR), "r") @@ -223,6 +243,16 @@ def restore_iptables(): except (CalledProcessError, FileNotFoundError): logging.debug("FileNotFoundError: Failed to restore iptables rules") + if check_ipv6() is True: + + try: + #infile = open("{}/iptables_before.rules".format(ROOTDIR), "r") + restore = Popen(["ip6tables-restore", "{}/ip6tables_before.rules".format(ROOTDIR)], stderr=PIPE) + logging.debug("Restored previous ip6tables rules") + + except (CalledProcessError, FileNotFoundError): + logging.debug("FileNotFoundError: Failed to restore ip6tables rules") + From 1e4045f6af4d1d3423742e81728d7c205b15de67 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sun, 20 Jan 2019 11:23:31 +0100 Subject: [PATCH 16/28] use new batch method for multiple iptables rules in all instances --- qomui/qomui_service.py | 8 ++------ qomui/tunnel.py | 6 ++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/qomui/qomui_service.py b/qomui/qomui_service.py index e12793d..ed27776 100755 --- a/qomui/qomui_service.py +++ b/qomui/qomui_service.py @@ -246,13 +246,9 @@ def disconnect(self, env): ["-D", "INPUT", "-i", "wg_qomui", "-j", "ACCEPT"], ["-D", "OUTPUT", "-o", "wg_qomui", "-j", "ACCEPT"] ] - - for rule in wg_rules: - firewall.add_rule_6(rule) - firewall.add_rule(rule) - + firewall.batch_rule_6(wg_rules) + firewall.batch_rule(wg_rules) tunnel.exe_custom_scripts("down", self.wg_provider, self.config) - self.wg_connect = 0 elif env == "bypass": diff --git a/qomui/tunnel.py b/qomui/tunnel.py index d755f1a..b7cb7cc 100755 --- a/qomui/tunnel.py +++ b/qomui/tunnel.py @@ -308,10 +308,8 @@ def wg(self, wg_file): ["-I", "OUTPUT", "2", "-o", "wg_qomui", "-j", "ACCEPT"] ] - for rule in wg_rules: - firewall.add_rule_6(rule) - firewall.add_rule(rule) - + firewall.batch_rule_6(wg_rules) + firewall.batch_rule(wg_rules) time.sleep(1) try: From 88656b0eb8e8e15b397fd77ee7974f3c0bd974f5 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 2 Feb 2019 12:51:38 +0100 Subject: [PATCH 17/28] merged add_rule methods --- qomui/firewall.py | 78 +++++++++++++++-------------------------------- 1 file changed, 24 insertions(+), 54 deletions(-) diff --git a/qomui/firewall.py b/qomui/firewall.py index dcf2f7c..f0d8459 100755 --- a/qomui/firewall.py +++ b/qomui/firewall.py @@ -9,8 +9,8 @@ ROOTDIR = "/usr/share/qomui" saved_rules = [] saved_rules_6 = [] -ip_cmd = ["iptables", "--wait",] -ip6_cmd = ["ip6tables", "--wait",] +#ip_cmd = ["iptables", "--wait",] +#ip6_cmd = ["ip6tables", "--wait",] devnull = open(os.devnull, 'w') ip6_available = True @@ -30,60 +30,30 @@ def check_ipv6(): logging.debug("Unable to determine whether ipv6 is available") return True -def add_rule(rule): - a = 1 - try: - check = rule[:] - if "-D" not in check: - if check[0] == "-A": - check[0] = "-C" - elif check[0] == "-I": - check[0] = "-C" - check.pop(2) - elif check[2] == "-A": - check[2] = "-C" - check_call(ip_cmd + check, stdout=devnull, stderr=devnull) - logging.debug("iptables: {} already exists".format(rule)) - a = 0 - except (IndexError, CalledProcessError): - pass +def add_rule(rule, check=0, ipt="ip4"): + if ipt == "ip4": + ip_cmd = ["iptables", "--wait",] + else: + ip_cmd = ["ip6tables", "--wait",] - try: - if a == 1: - check_call(ip_cmd + rule, stdout=devnull, stderr=devnull) - logging.debug("iptables: applied {}".format(rule)) + if ipt == "ip4" or check == 1 or check_ipv6() is True: + #check if rule already exists and only set it otherwise + try: + if len(rule) > 3 or "-D" not in rule: + check = ["-C" if x == "-A" or x == "-I" else x for x in rule] + check_call(ip_cmd + check, stdout=devnull, stderr=devnull) + logging.debug("iptables: {} already exists".format(rule)) - except CalledProcessError: - if "-D" not in rule: - logging.warning("iptables: failed to apply {}".format(rule)) + else: + raise IndexError -def add_rule_6(rule, check=0): - if check == 1 or check_ipv6() is True: - a = 1 - try: - check = rule[:] - if "-D" not in check: - if check[0] == "-A": - check[0] = "-C" - elif check[0] == "-I": - check[0] = "-C" - check.pop(2) - elif check[2] == "-A": - check[2] = "-C" - check_call(ip6_cmd + check, stdout=devnull, stderr=devnull) - logging.debug("ipt6ables: {} already exists".format(rule)) - a = 0 except (IndexError, CalledProcessError): - pass - - try: - if a == 1: - check_call(ip6_cmd + rule, stdout=devnull, stderr=devnull) - logging.debug("ip6tables: applied {}".format(rule)) + try: + check_call(ip_cmd + rule, stdout=devnull, stderr=devnull) + logging.debug("iptables: applied {}".format(rule)) - except CalledProcessError: - if "-D" not in rule: - logging.warning("ip6tables: failed to apply {}".format(rule)) + except CalledProcessError: + logging.warning("iptables: failed to apply {}".format(rule)) def apply_rules(opt, block_lan=0, preserve=0): fw_rules = get_config() @@ -123,7 +93,7 @@ def batch_rule(rules): def batch_rule_6(rules): if check_ipv6() is True: for rule in rules: - add_rule_6(rule, check=1) + add_rule(rule, check=1, ipt="ip6") def save_existing_rules(fw_rules): try: @@ -173,9 +143,9 @@ def allow_dest_ip(ip, action): elif len(ip.split(":")) >= 4: if check_ipv6() is True: - add_rule_6(rule) + add_rule(rule, ipt="ip6") except: - pass + logging.error("{} is not a valid ip address".format(ip)) def get_config(): try: From 7bbacceb9c59600754ebfdec50d3baf2e2155477 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 2 Feb 2019 18:28:42 +0100 Subject: [PATCH 18/28] fixed download method bugs & only update WireGuard keys if not present yet --- qomui/dns_manager.py | 6 +- qomui/qomui_gui.py | 2 +- qomui/qomui_service.py | 5 +- qomui/update.py | 361 ++++++++++++++++++++++------------------- 4 files changed, 198 insertions(+), 176 deletions(-) diff --git a/qomui/dns_manager.py b/qomui/dns_manager.py index 851517e..2d149db 100755 --- a/qomui/dns_manager.py +++ b/qomui/dns_manager.py @@ -56,12 +56,12 @@ def dns_request_exception(action, dns_1, dns_2, port): logging.info("iptables: removing exception for DNS requests") for p in protocols: - rules.append([action, 'OUTPUT', '1', '-p', p, '--dport', port, '-j', 'ACCEPT']) - rules.append([action, 'INPUT', '1', '-p', p, '--sport', port, '-j', 'ACCEPT']) + rules.append([action, 'OUTPUT', '-p', p, '--dport', port, '-j', 'ACCEPT']) + rules.append([action, 'INPUT', '-p', p, '--sport', port, '-j', 'ACCEPT']) for rule in rules: firewall.add_rule(rule) - firewall.add_rule_6(rule) + firewall.add_rule(rule, ipt="ip6") #set_dns(dns_1, server_2=dns_2) diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index b6a0569..f2fc00e 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -692,7 +692,7 @@ def setupUi(self, Form): self.horizontalLayout_10.addWidget(self.delBypassAppBt) self.verticalLayout_8.addLayout(self.horizontalLayout_10) self.tabWidget.addWidget(self.bypassTab) - + self.aboutTab = QtWidgets.QWidget() self.aboutTab.setObjectName(_fromUtf8("aboutTab")) self.tabWidget.addWidget(self.aboutTab) diff --git a/qomui/qomui_service.py b/qomui/qomui_service.py index ed27776..0db6f8b 100755 --- a/qomui/qomui_service.py +++ b/qomui/qomui_service.py @@ -266,7 +266,9 @@ def kill_pid(self, i): except CalledProcessError: self.logger.debug("OS: process {} does not exist anymore".format(i)) + #OBSOLETE - moved to update.py #allow downloading from provider api/site even if firewall is activated and no connection is active + """ def allow_provider_ip(self, provider): server = [] @@ -302,6 +304,7 @@ def allow_provider_ip(self, provider): except CalledProcessError as e: self.logger.error("{}: Could not resolve {}".format(e, s)) + """ #save and restore content of /etc/resolv.conf @dbus.service.method(BUS_NAME, in_signature='', out_signature='') @@ -336,8 +339,6 @@ def change_ovpn_config(self, provider, certpath): def import_thread(self, credentials): provider = credentials["provider"] self.homedir = credentials["homedir"] - self.allow_provider_ip(provider) - try: if credentials["credentials"] == "unknown": diff --git a/qomui/update.py b/qomui/update.py index 4ded622..95d3955 100755 --- a/qomui/update.py +++ b/qomui/update.py @@ -17,6 +17,7 @@ from bs4 import BeautifulSoup from subprocess import PIPE, Popen, check_output, CalledProcessError, run +from qomui import firewall try: _fromUtf8 = QtCore.QString.fromUtf8 @@ -27,6 +28,7 @@ def _fromUtf8(s): ROOTDIR = "/usr/share/qomui" TEMPDIR = "/usr/share/qomui/temp" SUPPORTED_PROVIDERS = ["Airvpn", "AzireVPN", "Mullvad", "PIA", "ProtonVPN", "Windscribe"] +ALLOWED_IPS = [] def country_translate(cc): try: @@ -82,6 +84,9 @@ def airvpn(self): from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization, hashes, asymmetric, ciphers + self.log.emit(("info", "Creating temporary rule to access Airvpn API")) + firewall.allow_dest_ip("54.93.175.114", "-I") + ALLOWED_IPS.append("54.93.175.114") self.airvpn_servers = {} self.airvpn_protocols = {} self.backend = default_backend() @@ -137,7 +142,6 @@ def airvpn(self): cert_xml = decrypt_user.update(cert_xml.content) + decrypt_user.finalize() parser = etree.XMLParser(recover=True) cert_xml_root = et.fromstring(cert_xml, parser=parser) - self.temp_path = "{}/{}".format(TEMPDIR, self.provider) for a in cert_xml_root.attrib: if cert_xml_root.attrib[a] == "Wrong login/password.": @@ -237,7 +241,7 @@ def airvpn(self): self.remove_temp_dir(self.provider) self.failed.emit(m) - + except Exception as e: self.log.emit(("debug", e.args)) self.log.emit(("info", "Airvpn: Request failed - aborting")) @@ -275,6 +279,7 @@ def call_air_api(self, payload): self.failed.emit("Network error&No internet connection&{}".format(self.provider)) def mullvad(self): + self.allow_ip(["api.mullvad.net", "mullvad.net", "raw.githubusercontent.com"]) self.mullvad_servers = {} self.password = "m" self.log.emit(("info", "Downloading certificates for Mullvad")) @@ -352,36 +357,35 @@ def mullvad(self): "tunnel" : "WireGuard" } + wg_file = "mullvad_wg.conf" + wg_keys = self.gen_wg_key(wg_file) + if wg_keys is not None: + data = [('account', self.username), + ('pubkey', wg_keys[1]) + ] + + pub_up = self.session.post("https://api.mullvad.net/wg/", data=data) + if pub_up.status_code < 400: + wg_address = pub_up.content.decode("utf-8").split("\n")[0] + + wg_conf = [ + "[Interface]\n", + "DNS = 193.138.219.228\n", + "\n", + "[Peer]\n", + "AllowedIPs = 0.0.0.0/0, ::/0\n" + ] + + with open("{}/{}".format(self.temp_path, wg_file), "w") as wg: + wg_conf.insert(1, "PrivateKey = {}\n".format(wg_keys[0])) + wg_conf.insert(2, "Address = {}\n".format(wg_address)) + wg.writelines(wg_conf) - private_key = check_output(["wg", "genkey"]).decode("utf-8").split("\n")[0] - pubgen = run(["wg", "pubkey"], stdout=PIPE, input=private_key, encoding='ascii') - pubkey = pubgen.stdout.split("\n")[0] - data = [('account', self.username), - ('pubkey', pubkey) - ] - - pub_up = self.session.post("https://api.mullvad.net/wg/", data=data) - if pub_up.status_code < 400: - wg_address = pub_up.content.decode("utf-8").split("\n")[0] - - wg_conf = [ - "[Interface]\n", - "DNS = 193.138.219.228\n", - "\n", - "[Peer]\n", - "AllowedIPs = 0.0.0.0/0, ::/0\n" - ] - - with open("{}/mullvad_wg.conf".format(self.temp_path), "w") as wg: - wg_conf.insert(1, "PrivateKey = {}\n".format(private_key)) - wg_conf.insert(2, "Address = {}\n".format(wg_address)) - wg.writelines(wg_conf) - - else: - m = "Mullvad: Authentication failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) - self.remove_temp_dir(self.provider) - self.failed.emit(m) - auth = 1 + else: + m = "Mullvad: Authentication failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) + self.remove_temp_dir(self.provider) + self.failed.emit(m) + auth = 1 except (CalledProcessError, FileNotFoundError) as e: @@ -416,6 +420,7 @@ def cc_translate(self, country_raw): return country def pia(self): + self.allow_ip(["www.privateinternetaccess.com"]) self.pia_servers = {} self.pia_protocols = {} self.log.emit(("info", "Downloading PIA config files")) @@ -489,17 +494,17 @@ def pia(self): "{}/strong/ca.rsa.4096.crt".format(self.temp_path) :"{}/pia_ca.rsa.4096.crt".format(self.temp_path) } - for orig, dest in certificates: + for orig, dest in certificates.items(): try: shutil.copyfile(orig, dest) - + except FileNotFoundError as e: self.log.emit(("error", e)) try: - shutil.rmtree("{}/ip") - shutil.rmtree("{}/strong") - + shutil.rmtree("{}/ip".format(self.temp_path)) + shutil.rmtree("{}/strong".format(self.temp_path)) + except FileNotFoundError as e: self.log.emit(("error", e)) @@ -512,6 +517,7 @@ def pia(self): self.failed.emit("Network error&No internet connection&{}".format(self.provider)) def windscribe(self): + self.allow_ip(["windscribe.com", "assets.windscribe.com", "res.windscribe.com"]) self.windscribe_servers = {} self.windscribe_protocols = {} self.header = { @@ -645,6 +651,7 @@ def windscribe_get_servers(self): self.finished.emit(ws_dict) def protonvpn(self): + self.allow_ip(["api.protonmail.ch"]) self.proton_servers = {} self.log.emit(("info", "Downloading ProtonVPN server configs")) @@ -742,6 +749,135 @@ def protonvpn(self): self.remove_temp_dir(self.provider) self.failed.emit("Network error&No internet connection&{}".format(self.provider)) + def azirevpn(self): + self.az_servers = {} + self.allow_ip(["azirevpn.net"]) + + try: + + try: + self.log.emit(("info", "Downloading AzireVPN OpenVPN configs")) + az_api_url = "https://api.azirevpn.com/v1/locations" + az_servers = json.loads(requests.get(az_api_url, timeout=2).content.decode("utf-8")) + + except requests.exceptions.RequestException as e: + az_servers = {"locations" : []} + self.log.emit(("error", "Network error: Unable to retrieve data from api.azirevpn.com")) + self.remove_temp_dir(self.provider) + self.failed.emit("Network error&No internet connection&{}".format(self.provider)) + + for s in az_servers["locations"]: + name = s["name"] + "-openvpn" + "-azirevpn" + wg_name = s["name"] + "-wireguard" + "-azirevpn" + country = country_translate(s["iso"]) + hostname = s["endpoints"]["openvpn"][0]["hostname"] + ip = resolve(hostname)[0] + self.log.emit(("info", "Importing {}".format(name))) + + if ip != "Failed to resolve": + self.az_servers[name] = { + "name": name, + "provider" : self.provider, + "city" : s["city"], + "ip" : ip, + "country" : country, + "tunnel" : "OpenVPN" + } + + crt_url = s["openvpn-ca"] + crt_file = "{}/{}.crt".format(self.temp_path, name) + crt = requests.get(crt_url, timeout=2).content.decode("utf-8") + with open(crt_file, "w") as c: + c.write(crt) + + tls_url = s["openvpn-tls-key"] + tls_file = "{}/{}.key".format(self.temp_path, name) + tls = requests.get(tls_url, timeout=2).content.decode("utf-8") + with open(tls_file, "w") as t: + t.write(tls) + + else: + self.log.emit(("Error: Could not resolve {} - skipping".format(hostname))) + + try: + wg_file = "{}.conf".format(wg_name) + wg_api_url = s["endpoints"]["wireguard"] + wg_keys = self.gen_wg_key(wg_file) + + if wg_keys is not None: + data = { + 'username' : str(self.username), + 'password' : str(self.password), + 'pubkey' : wg_keys[1] + } + + pub_up = requests.post(wg_api_url, data=data, timeout=5) + if pub_up.status_code == 200: + api_resp = json.loads(pub_up.content.decode("utf-8")) + + if api_resp["status"] != "error": + wg_ip = resolve(api_resp["data"]["Endpoint"].split(":")[0])[0] + wg_conf = [ + "[Interface]\n", + "PrivateKey = {}\n".format(wg_keys[0]), + "Address = {}\n".format(api_resp["data"]["Address"]), + "DNS = {}\n".format(api_resp["data"]["DNS"]), + "\n", + "[Peer]\n", + "PublicKey = {}\n".format(api_resp["data"]["PublicKey"]), + "Endpoint = {}:51820\n".format(wg_ip), + "AllowedIPs = 0.0.0.0/0, ::/0\n" + ] + + self.az_servers[wg_name] = { + "name": wg_name, + "provider" : self.provider, + "city" : s["city"], + "ip" : wg_ip, + "country" : country, + "tunnel" : "WireGuard", + "path" : "{}/{}.conf".format(self.provider, wg_name) + } + + with open("{}/{}".format(self.temp_path, wg_file), "w") as wg: + wg.writelines(wg_conf) + + else: + m = "AzireVPN: Authentication failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) + self.log.emit(("error", m)) + self.remove_temp_dir(self.provider) + self.failed.emit(m) + + except (CalledProcessError, FileNotFoundError) as e: + self.log.emit(("info", "WireGuard is not installed/not found - skipping")) + + except (requests.exceptions.RequestException, json.JSONDecodeError) as e: + self.log.emit(("debug", e)) + self.log.emit(("info", "Network error: Uploading WireGuard public key failed")) + + except Exception as e: + self.log.emit(("debug", e)) + self.log.emit(("error", "An unexpected error occured: Aborting")) + self.remove_temp_dir(self.provider) + self.failed.emit("AzireVPN import failed&An unknown error occured&{}".format(self.provider)) + + else: + az_protocols = { + "protocol_1" : {"protocol": "UDP", "port": "1194"}, + "protocol_2" : {"protocol": "TCP", "port": "1194"}, + "protocol_3" : {"protocol": "UDP", "port": "443"}, + "protocol_4" : {"protocol": "TCP", "port": "443"} + } + + azire_dict = { + "server" : self.az_servers, + "protocol" : az_protocols, + "provider" : "AzireVPN" + } + + self.copy_certs(self.provider) + self.finished.emit(azire_dict) + def add_folder(self): self.conf_files = [f for f in os.listdir(self.folderpath) if f.endswith('.ovpn') or f.endswith('.conf')] self.cert_files = [f for f in os.listdir(self.folderpath) if f.endswith('.ovpn') or f.endswith('.conf')] @@ -796,7 +932,7 @@ def import_configs(self): else: server = line.split(" ")[1] - ip = resolve(server) + ip = resolve(server)[0] if ip != "Failed to resolve": modify[index] = "remote {} {}\n".format(ip, port) @@ -837,7 +973,7 @@ def import_configs(self): ip = result.group() else: - ip = resolve(server) + ip = resolve(server)[0] if ip != "Failed to resolve": modify[index] = "Endpoint = {}:{}\n".format(ip, port) @@ -883,134 +1019,6 @@ def import_configs(self): self.copy_certs(self.provider) self.finished.emit(custom_dict) - def azirevpn(self): - self.az_servers = {} - - try: - - try: - self.log.emit(("info", "Downloading AzireVPN OpenVPN configs")) - az_api_url = "https://api.azirevpn.com/v1/locations" - az_servers = json.loads(requests.get(az_api_url, timeout=2).content.decode("utf-8")) - - except requests.exceptions.RequestException as e: - az_servers = {"locations" : []} - self.log.emit(("error", "Network error: Unable to retrieve data from api.azirevpn.com")) - self.remove_temp_dir(self.provider) - self.failed.emit("Network error&No internet connection&{}".format(self.provider)) - - for s in az_servers["locations"]: - name = s["name"] + "-openvpn" + "-azirevpn" - wg_name = s["name"] + "-wireguard" + "-azirevpn" - country = country_translate(s["iso"]) - hostname = s["endpoints"]["openvpn"][0]["hostname"] - ip = resolve(hostname) - self.log.emit(("info", "Importing {}".format(name))) - - if ip != "Failed to resolve": - self.az_servers[name] = { - "name": name, - "provider" : self.provider, - "city" : s["city"], - "ip" : ip, - "country" : country, - "tunnel" : "OpenVPN" - } - - crt_url = s["openvpn-ca"] - crt_file = "{}/{}.crt".format(self.temp_path, name) - crt = requests.get(crt_url, timeout=2).content.decode("utf-8") - with open(crt_file, "w") as c: - c.write(crt) - - tls_url = s["openvpn-tls-key"] - tls_file = "{}/{}.key".format(self.temp_path, name) - tls = requests.get(tls_url, timeout=2).content.decode("utf-8") - with open(tls_file, "w") as t: - t.write(tls) - - else: - self.log.emit(("Error: Could not resolve {} - skipping".format(hostname))) - - try: - wg_file = "{}/{}.conf".format(self.temp_path, wg_name) - wg_api_url = s["endpoints"]["wireguard"] - wg_keys = self.gen_wg_key(wg_file) - - if wg_keys is not None or self.update == "1": - data = { - 'username' : str(self.username), - 'password' : str(self.password), - 'pubkey' : wg_keys[1] - } - - pub_up = requests.post(wg_api_url, data=data, timeout=5) - if pub_up.status_code == 200: - api_resp = json.loads(pub_up.content.decode("utf-8")) - - if api_resp["status"] != "error": - wg_ip = resolve(api_resp["data"]["Endpoint"].split(":")[0]) - wg_conf = [ - "[Interface]\n", - "PrivateKey = {}\n".format(wg_keys[0]), - "Address = {}\n".format(api_resp["data"]["Address"]), - "DNS = {}\n".format(api_resp["data"]["DNS"]), - "\n", - "[Peer]\n", - "PublicKey = {}\n".format(api_resp["data"]["PublicKey"]), - "Endpoint = {}:51820\n".format(wg_ip), - "AllowedIPs = 0.0.0.0/0, ::/0\n" - ] - - self.az_servers[wg_name] = { - "name": wg_name, - "provider" : self.provider, - "city" : s["city"], - "ip" : wg_ip, - "country" : country, - "tunnel" : "WireGuard", - "path" : "{}/{}.conf".format(self.provider, wg_name) - } - - with open(wg_file, "w") as wg: - wg.writelines(wg_conf) - - else: - m = "AzireVPN: Authentication failed&Perhaps the credentials you entered are wrong&{}".format(self.provider) - self.log.emit(("error", m)) - self.remove_temp_dir(self.provider) - self.failed.emit(m) - - except (CalledProcessError, FileNotFoundError) as e: - self.log.emit(("info", "WireGuard is not installed/not found - skipping")) - - except (requests.exceptions.RequestException, json.JSONDecodeError) as e: - self.log.emit(("debug", e)) - self.log.emit(("info", "Network error: Uploading WireGuard public key failed")) - - except Exception as e: - self.log.emit(("debug", e)) - self.log.emit(("error", "An unexpected error occured: Aborting")) - self.remove_temp_dir(self.provider) - self.failed.emit("AzireVPN import failed&An unknown error occured&{}".format(self.provider)) - - else: - az_protocols = { - "protocol_1" : {"protocol": "UDP", "port": "1194"}, - "protocol_2" : {"protocol": "TCP", "port": "1194"}, - "protocol_3" : {"protocol": "UDP", "port": "443"}, - "protocol_4" : {"protocol": "TCP", "port": "443"} - } - - azire_dict = { - "server" : self.az_servers, - "protocol" : az_protocols, - "provider" : "AzireVPN" - } - - self.copy_certs(self.provider) - self.finished.emit(azire_dict) - def sanity_check(self, path): unrelated_files = 0 @@ -1029,7 +1037,7 @@ def sanity_check(self, path): def gen_wg_key(self, config): #check if key already exists - if os.path.exists(config): + if os.path.exists("{}/{}/{}".format(ROOTDIR, self.provider, config)) and self.update == "0": self.log.emit(("debug", "WireGuard keys for {} have already been generated".format(config.split("/")[-1]))) wg_keys = None @@ -1047,7 +1055,19 @@ def gen_wg_key(self, config): return wg_keys + def allow_ip(self, hosts): + for host in hosts: + self.log.emit(("info", "Creating temporary rule to access {}".format(host))) + ips = resolve(host) + for i in ips: + if i != "" and i != "Failed to resolve": + firewall.allow_dest_ip(i, "-I") + ALLOWED_IPS.append(i) + def copy_certs(self, provider): + for i in ALLOWED_IPS: + firewall.allow_dest_ip(i, "-D") + provider_dir = "{}/{}".format(ROOTDIR, provider) if not os.path.exists(provider_dir): os.makedirs(provider_dir) @@ -1069,7 +1089,7 @@ def copy_certs(self, provider): if not os.path.exists(openvpn_dest_conf): shutil.copyfile(openvpn_orig_conf, openvpn_dest_conf) Popen(['chmod', '0655', openvpn_dest_conf]) - + except FileNotFoundError: self.log.emit(("error", "{} does not exist".format(openvpn_orig_conf))) @@ -1117,13 +1137,14 @@ def resolve(host): try: dig_cmd = ["dig", "+time=2", "+tries=2", "{}".format(host), "+short"] ip = check_output(dig_cmd).decode("utf-8") - ip = ip.split("\n")[0] + ip = ip.split("\n") except (FileNotFoundError, CalledProcessError): - ip = "Failed to resolve" + ip = ["Failed to resolve"] return ip + class UpdateCheck(QtCore.QThread): release_found = QtCore.pyqtSignal(str) log = QtCore.pyqtSignal(tuple) From 410cfa6735d730cb3d9f50067cacfa1923e70c6f Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 2 Feb 2019 18:44:20 +0100 Subject: [PATCH 19/28] added missing variable to batch_rule_6 --- qomui/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qomui/firewall.py b/qomui/firewall.py index f0d8459..f6103e1 100755 --- a/qomui/firewall.py +++ b/qomui/firewall.py @@ -83,7 +83,7 @@ def apply_rules(opt, block_lan=0, preserve=0): elif opt == 0: batch_rule(fw_rules["unsecure"]) - batch_rule_6(["unsecurev6"]) + batch_rule_6(fw_rules["unsecurev6"]) logging.info("iptables: deactivated firewall") def batch_rule(rules): From 7a4ca84b519c14e760da1bd9bf3346139a7f754d Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 2 Feb 2019 20:06:24 +0100 Subject: [PATCH 20/28] catch dbus exceptions more precisely --- qomui/qomui_gui.py | 59 ++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py index f2fc00e..b240bee 100755 --- a/qomui/qomui_gui.py +++ b/qomui/qomui_gui.py @@ -105,7 +105,6 @@ def __init__(self, parent = None): try: self.qomui_dbus = self.dbus.get_object('org.qomui.service', '/org/qomui/service') - self.logger.debug('Successfully connected to qomui-service via DBus') except dbus.exceptions.DBusException: self.logger.error('DBus Error: Qomui-Service is currently not available') @@ -140,6 +139,7 @@ def __init__(self, parent = None): 600, 750 )) + self.logger.debug('Successfully connected to qomui-service via DBus') self.dbus_call("disconnect", "main") self.dbus_call("disconnect", "bypass") self.dbus_call("save_default_dns") @@ -170,34 +170,43 @@ def dbus_call(self, cmd, *args): return call except dbus.exceptions.DBusException as e: - self.logger.error("Dbus Error: {}".format(e)) - self.notify("Qomui: Dbus Error", "No reply from qomui-service. It may have crashed.", icon="Warning") - ret = self.messageBox( - "Error: Qomui-service is not available", - "Do you want restart it or quit Qomui?", - buttons = [ - ("Quit", "NoRole"), - ("Restart", "YesRole") - ], - icon = "Question" - ) - - if ret == 0: - sys.exit(1) + print(e) + if e.get_dbus_name() == "org.freedesktop.DBus.Error.ServiceUnknown": + self.notify("Qomui: Dbus Error", "No reply from qomui-service. It may have crashed.", icon="Warning") + ret = self.messageBox( + "Error: Qomui-service is not available", + "Do you want restart it or quit Qomui?", + buttons = [ + ("Quit", "NoRole"), + ("Restart", "YesRole") + ], + icon = "Question" + ) + + if ret == 0: + sys.exit(1) + + elif ret == 1: + self.initialize_service("restart") + time.sleep(3) - elif ret == 1: - self.initialize_service("restart") - time.sleep(3) + try: + if self.config_dict["bypass"] == 1: + self.dbus_call("bypass", {**self.routes, **utils.get_user_group()}) - try: - if self.config_dict["bypass"] == 1: - self.dbus_call("bypass", {**self.routes, **utils.get_user_group()}) + except KeyError: + pass - except KeyError: - pass + retry = self.dbus_call(cmd, *args) + return retry + + else: + self.logger.error("Dbus Error: {}".format(e)) + self.notify("Qomui: Dbus Error", "An error occured. See log for details", icon="Warning") - retry = self.dbus_call(cmd, *args) - return retry + except Exception as e: + self.logger.error("Dbus Error: {}".format(e)) + self.notify("Qomui: Dbus Error", "An unknown error occured. See log for details", icon="Warning") def initialize_service(self, *args): try: From 3a1ca953da3fc5ea9d50d00ae6143e0fb226f154 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 2 Feb 2019 22:16:49 +0100 Subject: [PATCH 21/28] ensure compatibility with older versions --- qomui/tunnel.py | 12 ++++++------ resources/Airvpn_config_old | 17 +++++++++++++++++ resources/Mullvad_config_old | 21 +++++++++++++++++++++ resources/PIA_config_old | 20 ++++++++++++++++++++ resources/ProtonVPN_config_old | 24 ++++++++++++++++++++++++ resources/Windscribe_config_old | 23 +++++++++++++++++++++++ 6 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 resources/Airvpn_config_old create mode 100644 resources/Mullvad_config_old create mode 100644 resources/PIA_config_old create mode 100644 resources/ProtonVPN_config_old create mode 100644 resources/Windscribe_config_old diff --git a/qomui/tunnel.py b/qomui/tunnel.py index b7cb7cc..610ce1a 100755 --- a/qomui/tunnel.py +++ b/qomui/tunnel.py @@ -100,7 +100,7 @@ def openvpn(self): elif value.startswith("accept") is True: ssl_config[line] = "accept = 127.0.0.1:{}\n".format(self.air_ssl_port) ssl_config.append("verify = 3\n") - ssl_config.append("CAfile = /usr/share/qomui/certs/stunnel.crt") + ssl_config.append("CAfile = /usr/share/qomui/Airvpn/stunnel.crt") with open("{}/temp.ssl".format(ROOTDIR), "w") as ssl_dump: ssl_dump.writelines(ssl_config) ssl_dump.close() @@ -210,7 +210,7 @@ def write_config(self, ovpn_dict, edit="temp", path=None): ovpn_file = "{}/{}/openvpn.conf".format(ROOTDIR, provider) else: #Ensure compatibility with older versions - ovpn_file = "{}/{}_config".format(ROOTDIR, provider) + ovpn_file = "{}/{}_config_old".format(ROOTDIR, provider) compat = 0 else: ovpn_file = path @@ -268,14 +268,14 @@ def write_config(self, ovpn_dict, edit="temp", path=None): try: if ovpn_dict["tlscrypt"] == "on": - config.append("tls-crypt {}/certs/tls-crypt.key \n".format(ROOTDIR)) + config.append("tls-crypt {}/Airvpn/tls-crypt.key \n".format(ROOTDIR)) config.append("auth sha512") else: - config.append("tls-auth {}/certs/ta.key 1 \n".format(ROOTDIR)) + config.append("tls-auth {}/Airvpn/ta.key 1 \n".format(ROOTDIR)) except KeyError: - config.append("tls-auth {}/certs/ta.key 1 \n".format(ROOTDIR)) + config.append("tls-auth {}/Airvpn/ta.key 1 \n".format(ROOTDIR)) elif provider == "AzireVPN": ca = "ca {}/{}/{}.crt\n".format(ROOTDIR, provider, ovpn_dict["name"]) @@ -543,7 +543,7 @@ def ssl(self, ip): #using pexpect instead of subprocess to accept SHA fingerprint def ssh(self, ip, port): - cmd_ssh = "ssh -i {}/certs/sshtunnel.key -L 1412:127.0.0.1:2018 sshtunnel@{} -p {} -N -T -v".format(ROOTDIR, ip, port) + cmd_ssh = "ssh -i {}/Airvpn/sshtunnel.key -L 1412:127.0.0.1:2018 sshtunnel@{} -p {} -N -T -v".format(ROOTDIR, ip, port) ssh_exe = pexpect.spawn(cmd_ssh) ssh_newkey = b'Are you sure you want to continue connecting' ssh_success = 'Forced command' diff --git a/resources/Airvpn_config_old b/resources/Airvpn_config_old new file mode 100644 index 0000000..b1bc115 --- /dev/null +++ b/resources/Airvpn_config_old @@ -0,0 +1,17 @@ +client +dev tun +proto +remote +resolv-retry infinite +nobind +persist-key +persist-tun +push-peer-info +remote-cert-tls server +cipher AES-256-CBC +comp-lzo no +auth-nocache +verb 3 +ca /usr/share/qomui/certs/ca.crt +cert /usr/share/qomui/certs/user.crt +key /usr/share/qomui/certs/user.key diff --git a/resources/Mullvad_config_old b/resources/Mullvad_config_old new file mode 100644 index 0000000..6584137 --- /dev/null +++ b/resources/Mullvad_config_old @@ -0,0 +1,21 @@ +client +dev tun +proto +remote +resolv-retry infinite +nobind +persist-key +persist-tun +remote-cert-tls server +cipher AES-256-CBC +comp-lzo +fast-io +ping-restart 60 +sndbuf 524288 +rcvbuf 524288 +verb 3 + +auth-user-pass /usr/share/qomui/certs/Mullvad-auth.txt +ca /usr/share/qomui/certs/mullvad_ca.crt + +tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA diff --git a/resources/PIA_config_old b/resources/PIA_config_old new file mode 100644 index 0000000..4910c29 --- /dev/null +++ b/resources/PIA_config_old @@ -0,0 +1,20 @@ +client +dev tun +proto +remote +resolv-retry infinite +nobind +persist-key +persist-tun +cipher aes-256-cbc +auth sha256 +tls-client +remote-cert-tls server +auth-user-pass /usr/share/qomui/PIA/PIA-auth.txt +comp-lzo no +verb 3 +reneg-sec 0 +crl-verify /usr/share/qomui/certs/pia_crl.rsa.4096.pem +ca /usr/share/qomui/certs/pia_ca.rsa.4096.crt +disable-occ + diff --git a/resources/ProtonVPN_config_old b/resources/ProtonVPN_config_old new file mode 100644 index 0000000..4834fb4 --- /dev/null +++ b/resources/ProtonVPN_config_old @@ -0,0 +1,24 @@ +client +dev tun +proto +remote +resolv-retry infinite +nobind +cipher AES-256-CBC +auth SHA512 +comp-lzo +verb 3 +tun-mtu 1500 +tun-mtu-extra 32 +mssfix 1450 +persist-key +persist-tun +reneg-sec 0 +remote-cert-tls server +pull +fast-io +key-direction 1 + +auth-user-pass /usr/share/qomui/certs/ProtonVPN-auth.txt +ca /usr/share/qomui/certs/proton_ca.crt +tls-auth /usr/share/qomui/certs/proton_ta.key diff --git a/resources/Windscribe_config_old b/resources/Windscribe_config_old new file mode 100644 index 0000000..8de9dcb --- /dev/null +++ b/resources/Windscribe_config_old @@ -0,0 +1,23 @@ +client +dev tun +proto +remote +nobind +resolv-retry infinite +auth SHA512 +auth-nocache +cipher AES-256-GCM +ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM +comp-lzo +verb 3 +mute-replay-warnings +remote-cert-tls server +persist-key +persist-tun +key-direction 1 + +auth-user-pass /usr/share/qomui/certs/Windscribe-auth.txt +ca /usr/share/qomui/certs/ca_ws.crt +tls-auth /usr/share/qomui/certs/ta_ws.key + + From 5f5b93bc533157de5b7d0c946ae6da3e512dcc0b Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 2 Feb 2019 22:33:36 +0100 Subject: [PATCH 22/28] preparing release 0.8.2 --- CHANGELOG.md | 17 +++++++++++++++++ README.md | 31 +++++++++++++++++-------------- VERSION | 2 +- qomui/widgets.py | 8 ++++++-- setup.py | 9 +++++++-- 5 files changed, 48 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e73c294..f8220f2 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ ## Changelog +version 0.8.2: +- [new] added AzireVPN +- [new] option to specify key for Airvpn +- [change] fast/random option added to profiles +- [change] order of latency checks now based on previous results +- [change] tidied up directory structure +- [change] checks if ipv6 is available +- [change] auto-updates for different providers won't run concurrently +- [change] check if IPv6 is available before setting iptables rules +- [change] don't generate new WireGuard keys on auto-update +- [change] allow importing new servers if firewall is active but VPN is not +- [bugfix] detection of service crashes is not reliable +- [bugfix] Windscribe auto-update fails because of authentication error +- [bugfix] compatibility with older Qt5 versions +- [bugfix] previous iptables rules not always restored +- [bugfix] crashes if ipv6 stack not available + version 0.8.1: - [change] option to restart qomui-service from gui if it crashes - [change] added exceptions for all DBus calls diff --git a/README.md b/README.md index 7181ec5..64b0171 100755 --- a/README.md +++ b/README.md @@ -107,23 +107,26 @@ Qomui has been my first ever programming experience and a practical challenge fo ### Changelog +#### version 0.8.2: +- [new] added AzireVPN +- [new] option to specify key for Airvpn +- [change] fast/random option added to profiles +- [change] order of latency checks now based on previous results +- [change] tidied up directory structure +- [change] checks if ipv6 is available +- [change] auto-updates for different providers won't run concurrently +- [change] check if IPv6 is available before setting iptables rules +- [change] don't generate new WireGuard keys on auto-update +- [change] allow importing new servers if firewall is active but VPN is not +- [bugfix] detection of service crashes is not reliable +- [bugfix] Windscribe auto-update fails because of authentication error +- [bugfix] compatibility with older Qt5 versions +- [bugfix] previous iptables rules not always restored +- [bugfix] crashes if ipv6 stack not available + #### version 0.8.1: - [change] option to restart qomui-service from gui if it crashes - [change] added exceptions for all DBus calls - [change] improved support for non-systemd distributions - [change] detecting and closing simultaneously running instances - [bugfix] Airvpn auto-download fixed - -#### version 0.8.0: -- [new] connection profiles -- [new] support for custom scripts -- [change] configurations for Airvpn are now downloaded directly -- [change] removed minimize option if system tray not available -- [change] download new Mullvad config/certificates -- [change] added scroll areas to some tabs -- [change] added options for profiles to tray menu -- [change] window state now recognized correctly -- [bugfix] improved stability and reliability of network detection -- [bugfix] manually imported WireGuard servers don't connect -- [bugfix] Qomui crashes when downloading Airvpn configs -- [bugfix] fixed Mullvad & Windscribe configs diff --git a/VERSION b/VERSION index c18d72b..100435b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.1 \ No newline at end of file +0.8.2 diff --git a/qomui/widgets.py b/qomui/widgets.py index 428e92e..9c8b564 100755 --- a/qomui/widgets.py +++ b/qomui/widgets.py @@ -11,6 +11,7 @@ import configparser import requests import shlex +import logging from PyQt5 import QtCore, QtWidgets, QtGui from qomui import update @@ -864,14 +865,17 @@ def get_desktop_files(self): else: name = c["Desktop Entry"]["Name"] icon = c["Desktop Entry"]["Icon"] + logging.debug("Adding {} to bypass app list".format(name)) self.bypassAppList.append((name, icon, desktop_file)) except KeyError: name = c["Desktop Entry"]["Name"] icon = c["Desktop Entry"]["Icon"] + logging.debug("Adding {} to bypass app list".format(name)) self.bypassAppList.append((name, icon, desktop_file)) - except: - pass + except Exception as e: + logging.error(e) + logging.error("Failed to add {} to bypass app list".format(name)) self.bypassAppList = sorted(self.bypassAppList) self.pop_AppList() diff --git a/setup.py b/setup.py index dc0faef..0a84962 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ import glob import os -VERSION = "0.8.1" +VERSION = "0.8.2" data_files = [ ('/usr/share/qomui', ['resources/countries.json']), ('/usr/share/applications/', ['resources/qomui.desktop']), @@ -19,9 +19,14 @@ 'resources/PIA_config', 'resources/ProtonVPN_config', 'resources/Windscribe_config', + 'resources/Airvpn_config_old', + 'resources/Mullvad_config', + 'resources/Mullvad_config_old', + 'resources/PIA_config_old', + 'resources/ProtonVPN_config_old', + 'resources/Windscribe_config_old', 'resources/default_config.json', 'resources/firewall_default.json', - 'resources/Mullvad_config', 'resources/ssl_config', 'resources/qomui.png', 'resources/airvpn_api.pem', From 47bc3fe509472d024022aa3fd504b74b1bc137d9 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 2 Feb 2019 23:19:19 +0100 Subject: [PATCH 23/28] added AzireVPN OpenVPN config --- VERSION | 2 +- resources/AzireVPN_config | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 resources/AzireVPN_config diff --git a/VERSION b/VERSION index 100435b..53a48a1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.2 +0.8.2 \ No newline at end of file diff --git a/resources/AzireVPN_config b/resources/AzireVPN_config new file mode 100644 index 0000000..52abe0d --- /dev/null +++ b/resources/AzireVPN_config @@ -0,0 +1,19 @@ +client +dev tun +proto +remote +resolv-retry infinite +nobind +persist-key +persist-tun +remote-cert-tls server +reneg-sec 0 +keepalive 10 60 +mute-replay-warnings +explicit-exit-notify 3 +cipher AES-256-CBC +auth SHA512 +tls-version-min 1.2 +verb 3 + +auth-user-pass /usr/share/qomui/AzireVPN/AzireVPN-auth.txt From 880a2166282e07e99dcbe4d7aa977fda5a1d5ea7 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 2 Feb 2019 23:39:03 +0100 Subject: [PATCH 24/28] updated changelog --- CHANGELOG.md | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8220f2..316af93 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ version 0.8.2: - [change] check if IPv6 is available before setting iptables rules - [change] don't generate new WireGuard keys on auto-update - [change] allow importing new servers if firewall is active but VPN is not +- [bugfix] firewall not deactivating after gui exit (if the respective option is set) - [bugfix] detection of service crashes is not reliable - [bugfix] Windscribe auto-update fails because of authentication error - [bugfix] compatibility with older Qt5 versions diff --git a/README.md b/README.md index 64b0171..c3c5882 100755 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ Qomui has been my first ever programming experience and a practical challenge fo - [change] check if IPv6 is available before setting iptables rules - [change] don't generate new WireGuard keys on auto-update - [change] allow importing new servers if firewall is active but VPN is not +- [bugfix] firewall not deactivating after gui exit (if the respective option is set) - [bugfix] detection of service crashes is not reliable - [bugfix] Windscribe auto-update fails because of authentication error - [bugfix] compatibility with older Qt5 versions From cb3b36f91701a6a898ba64410cac71c45fa87720 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sat, 2 Feb 2019 23:41:39 +0100 Subject: [PATCH 25/28] updated changelog --- CHANGELOG.md | 2 ++ README.md | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 316af93..f0b06c0 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ version 0.8.2: - [bugfix] compatibility with older Qt5 versions - [bugfix] previous iptables rules not always restored - [bugfix] crashes if ipv6 stack not available +- [bugfix] OpenVPN config changes overwritten on update + version 0.8.1: - [change] option to restart qomui-service from gui if it crashes diff --git a/README.md b/README.md index c3c5882..ded7eaf 100755 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ Qomui has been my first ever programming experience and a practical challenge fo - [bugfix] compatibility with older Qt5 versions - [bugfix] previous iptables rules not always restored - [bugfix] crashes if ipv6 stack not available +- [bugfix] OpenVPN config changes overwritten on update #### version 0.8.1: - [change] option to restart qomui-service from gui if it crashes From 276c5285a85a1c8035e3efcd5054ccc1770a7430 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sun, 3 Feb 2019 00:15:07 +0100 Subject: [PATCH 26/28] added AZireVPN config to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 0a84962..9a19c9c 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ ('/usr/share/icons/hicolor/scalable/apps/', ['resources/qomui.svg', 'resources/qomui_off.svg']), ('/usr/share/qomui/', [ + 'resources/AzireVPN_config', 'resources/Airvpn_config', 'resources/PIA_config', 'resources/ProtonVPN_config', From 6c4bbcf41c881168b40b8668f8807094629163df Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sun, 3 Feb 2019 01:34:02 +0100 Subject: [PATCH 27/28] updated changelog --- CHANGELOG.md | 4 ++++ README.md | 5 +++++ setup.py | 1 + 3 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b06c0..6e44331 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,11 @@ version 0.8.2: - [bugfix] previous iptables rules not always restored - [bugfix] crashes if ipv6 stack not available - [bugfix] OpenVPN config changes overwritten on update +- [bugfix] Debian packages remove /usr/share/qomui directory on update +##### Additional notes: +- Re-importing config files from supported providers is strongly recommended as they are now saved in a different location. +- If you are using Debian/Ubuntu packages Qomui must be removed (sudo apt purge qomui) and then installed again due to a bug in the postrm script. version 0.8.1: - [change] option to restart qomui-service from gui if it crashes diff --git a/README.md b/README.md index ded7eaf..5d1a886 100755 --- a/README.md +++ b/README.md @@ -125,6 +125,11 @@ Qomui has been my first ever programming experience and a practical challenge fo - [bugfix] previous iptables rules not always restored - [bugfix] crashes if ipv6 stack not available - [bugfix] OpenVPN config changes overwritten on update +- [bugfix] Debian packages remove /usr/share/qomui directory on update + +##### Additional notes: +- Re-importing config files from supported providers is strongly recommended as they are now saved in a different location. +- If you are using Debian/Ubuntu packages Qomui must be removed (sudo apt purge qomui) and then installed again due to a bug in the postrm script. #### version 0.8.1: - [change] option to restart qomui-service from gui if it crashes diff --git a/setup.py b/setup.py index 9a19c9c..f1c06ea 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ import os VERSION = "0.8.2" + data_files = [ ('/usr/share/qomui', ['resources/countries.json']), ('/usr/share/applications/', ['resources/qomui.desktop']), From 7c276f290d25b1b957ea8e023735069b42811620 Mon Sep 17 00:00:00 2001 From: corrad1nho Date: Sun, 3 Feb 2019 01:45:57 +0100 Subject: [PATCH 28/28] increased AzireVPN timeout --- qomui/update.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qomui/update.py b/qomui/update.py index 95d3955..02bbab6 100755 --- a/qomui/update.py +++ b/qomui/update.py @@ -28,7 +28,6 @@ def _fromUtf8(s): ROOTDIR = "/usr/share/qomui" TEMPDIR = "/usr/share/qomui/temp" SUPPORTED_PROVIDERS = ["Airvpn", "AzireVPN", "Mullvad", "PIA", "ProtonVPN", "Windscribe"] -ALLOWED_IPS = [] def country_translate(cc): try: @@ -57,6 +56,7 @@ def __init__(self, credentials, folderpath=None): self.folderpath = credentials["folderpath"] self.update = credentials["update"] self.temp_path = "{}/{}".format(TEMPDIR, self.provider) + self.allowed_ips = [] try: self.key = credentials["key"] @@ -86,7 +86,7 @@ def airvpn(self): self.log.emit(("info", "Creating temporary rule to access Airvpn API")) firewall.allow_dest_ip("54.93.175.114", "-I") - ALLOWED_IPS.append("54.93.175.114") + self.allowed_ips.append("54.93.175.114") self.airvpn_servers = {} self.airvpn_protocols = {} self.backend = default_backend() @@ -811,7 +811,7 @@ def azirevpn(self): 'pubkey' : wg_keys[1] } - pub_up = requests.post(wg_api_url, data=data, timeout=5) + pub_up = requests.post(wg_api_url, data=data, timeout=10) if pub_up.status_code == 200: api_resp = json.loads(pub_up.content.decode("utf-8")) @@ -851,9 +851,9 @@ def azirevpn(self): except (CalledProcessError, FileNotFoundError) as e: self.log.emit(("info", "WireGuard is not installed/not found - skipping")) - except (requests.exceptions.RequestException, json.JSONDecodeError) as e: - self.log.emit(("debug", e)) - self.log.emit(("info", "Network error: Uploading WireGuard public key failed")) + except (requests.exceptions.RequestException, json.JSONDecodeError) as e: + self.log.emit(("debug", e)) + self.log.emit(("info", "Network error: Uploading WireGuard public key failed")) except Exception as e: self.log.emit(("debug", e)) @@ -1062,10 +1062,10 @@ def allow_ip(self, hosts): for i in ips: if i != "" and i != "Failed to resolve": firewall.allow_dest_ip(i, "-I") - ALLOWED_IPS.append(i) + self.allowed_ips.append(i) def copy_certs(self, provider): - for i in ALLOWED_IPS: + for i in self.allowed_ips: firewall.allow_dest_ip(i, "-D") provider_dir = "{}/{}".format(ROOTDIR, provider)