def getUserDataPathWrapper(name): # Return the fully qualified user path and file name if generateVPNs(): return "/storage/.kodi/userdata/addon_data/service.vpn.manager/" + name else: if getPlatform() == platforms.WINDOWS: return getUserDataPath(name).replace("\\", "\\\\") else: return getUserDataPath(name)
def getAddonPathWrapper(name): # Return the fully qualified add-on path and file name if generateVPNs(): return "/storage/.kodi/addons/service.vpn.manager/" + name else: if getPlatform() == platforms.WINDOWS: return getAddonPath(True, name).replace("\\", "\\\\") else: return getAddonPath(True, name)
def getFriendlyProfileName(vpn_provider, ovpn_connection): # Make the VPN profile names more readable to the user to select from regex_str = getRegexPattern(vpn_provider) # Deal with some Windows nonsense if getPlatform() == platforms.WINDOWS: regex_str = regex_str.replace(r"/", r"\\") # Return friendly version of string match = re.search(regex_str, ovpn_connection) return match.group(1)
def getUserDataPathWrapper(path): # This function resets the VPN profiles to the standard VPN Manager install # location as per OpenELEC, or to the platform install location force_default_install = fakeConnection() if force_default_install: return "/storage/.kodi/userdata/addon_data/service.vpn.manager/" + path else: if getPlatform() == platforms.WINDOWS: return getUserDataPath(path).replace("\\", "\\\\") else: return getUserDataPath(path)
def getUpParam(provider): ext = "sh" if getPlatform() == platforms.WINDOWS: ext = "bat" filename = getUserDataPathWrapper(getVPNLocation(provider) + "/up." + ext) if xbmcvfs.exists(filename): return "up " + filename filename = getAddonPathWrapper(getVPNLocation(provider) + "/up." + ext) if xbmcvfs.exists(filename): return "up " + filename if xbmcaddon.Addon("service.vpn.manager").getSetting( "use_default_up_down") == "true": filename = getAddonPathWrapper("up." + ext) if xbmcvfs.exists(filename): return "up " + filename return ""
def getFriendlyProfileList(vpn_provider, ovpn_connections): # Munge a ovpn full path name is something more friendly connections = [] regex_str = getRegexPattern(vpn_provider) # Deal with some Windows nonsense if getPlatform() == platforms.WINDOWS: regex_str = regex_str.replace(r"/", r"\\") # Produce a compiled pattern and interate around the list of connections pattern = re.compile(regex_str) for connection in ovpn_connections: connections.append(pattern.search(connection).group(1)) return connections
def writeDefaultUpFile(): p = getPlatform() if p == platforms.LINUX or p == platforms.RPI: infoTrace("vpnproviders.py", "Writing default up script") up = open(getAddonPath(True, "up.sh"), 'w') up.write("#!/bin/bash\n") up.write("iptables -F\n") up.write( "iptables -A INPUT -i tun0 -m state --state ESTABLISHED,RELATED -j ACCEPT\n" ) up.write("iptables -A INPUT -i tun0 -j DROP\n") up.close() command = "chmod +x " + getAddonPath(True, "up.sh") if useSudo(): command = "sudo " + command infoTrace("vpnproviders.py", "Fixing default up.sh " + command) os.system(command)
def getBestPathWrapper(name): # This function will return the path to the user version of a given file # if it exists, otherwise it'll return the path the default add-on version # This is just about resetting the ovpn documents if neccesary if generateVPNs(): return "/storage/.kodi/addons/service.vpn.manager/" + path else: filename = getUserDataPath(name) if not xbmcvfs.exists(filename): filename = getAddonPath(True, name) else: infoTrace("vpnprovider.py", "Using userdata override " + filename) if getPlatform() == platforms.WINDOWS: return filename.replace("\\", "\\\\") else: return filename
def getSeparatorOutput(): if getPlatform() == platforms.WINDOWS: # Need to double escape Windows as we output this as another slash gets striped as it gets read back in again return "\\\\" else: return "/"
def getAddonPathWrapper(name): # Return the fully qualified add-on path and file name if getPlatform() == platforms.WINDOWS: return getAddonPath(True, name).replace("\\", "\\\\") else: return getAddonPath(True, name)
# Get the arguments passed in base_url = sys.argv[0] addon_handle = int(sys.argv[1]) args = sys.argv[2].split("?", ) action = "" params = "" # If an argument has been passed in, the first character will be a ?, so the first list element is empty inc = 0 for token in args: if inc == 1 : action = token if inc > 1 : params = params + token inc = inc + 1 # Don't seem to need to do this on *nix platforms as the filename will be different if getPlatform() == platforms.WINDOWS: params = params.replace("/", "\\") debugTrace("Parsed arguments to action=" + action + " params=" + params) def topLevel(): # Build the top level menu with URL callbacks to this plugin debugTrace("Displaying the top level menu") url = base_url + "?settings" li = xbmcgui.ListItem("Add-on Settings", iconImage=getIconPath()+"settings.png") xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li) url = base_url + "?display" li = xbmcgui.ListItem("Display VPN status", iconImage=getIconPath()+"display.png") xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li) if addon.getSetting("vpn_system_menu_item") == "true": url = base_url + "?system"
def generateOVPNFiles(vpn_provider, alternative_locations_name): # Generate the OVPN files for a VPN provider using the template and update with location info infoTrace( "vpnproviders.py", "Generating OVPN files for " + vpn_provider + " using list " + alternative_locations_name) # See if there's a port override going on addon = xbmcaddon.Addon("service.vpn.manager") if addon.getSetting("default_udp") == "true": portUDP = "" else: portUDP = addon.getSetting("alternative_udp_port") if addon.getSetting("default_tcp") == "true": portTCP = "" else: portTCP = addon.getSetting("alternative_tcp_port") # Get the logging level verb_value = addon.getSetting("openvpn_verb") if verb_value == "": verb_value = "1" addon.setSetting("openvpn_verb", verb_value) # Load ovpn template try: template_path = getTemplateFile(vpn_provider) debugTrace("Opening template file " + template_path) template_file = open(template_path, 'r') template = template_file.readlines() template_file.close() except Exception as e: errorTrace("vpnproviders.py", "Couldn't open the template file for " + vpn_provider) errorTrace("vpnproviders.py", str(e)) return False # Open a translate file try: debugTrace("Opening translate file for " + vpn_provider) translate_file = open( getAddonPath(True, vpn_provider + "/TRANSLATE.txt"), 'w') debugTrace("Opened translate file for " + vpn_provider) except Exception as e: errorTrace("vpnproviders.py", "Couldn't open the translate file for " + vpn_provider) errorTrace("vpnproviders.py", str(e)) return False if getPlatform() == platforms.WINDOWS and addon.getSetting( "block_outside_dns") == "true": template.append("block-outside-dns") if addon.getSetting("force_ping") == "true": template.append("ping #PINGSPEED") template.append("ping-exit #PINGEXIT") template.append("ping-timer-rem") if addon.getSetting("up_down_script") == "true": template.append("script-security 2") template.append(getUpParam(vpn_provider)) template.append(getDownParam(vpn_provider)) # Load locations file if not alternative_locations_name == "": if alternative_locations_name == "User": locations_name = getUserDataPath(vpn_provider + "/LOCATIONS.txt") else: locations_name = getAddonPath( True, vpn_provider + "/LOCATIONS " + alternative_locations_name + ".txt") else: locations_name = getAddonPath(True, vpn_provider + "/LOCATIONS.txt") try: debugTrace("Opening locations file for " + vpn_provider + "\n" + locations_name) locations_file = open(locations_name, 'r') debugTrace("Opened locations file for " + vpn_provider) locations = locations_file.readlines() locations_file.close() except Exception as e: errorTrace( "vpnproviders.py", "Couldn't open the locations file for " + vpn_provider + "\n" + locations_name) errorTrace("vpnproviders.py", str(e)) translate_file.close() return False # For each location, generate an OVPN file using the template for location in locations: try: location_values = location.split(",") geo = location_values[0] servers = location_values[1].split() proto = location_values[2] ports = (location_values[3].strip(' \t\n\r')).split() port = "" # Initialise the set of values that can be modified by the location file tuples ca_cert = "ca.crt" ta_key = "ta.key" crl_pem = "crl.pem" dh_parm = "dh.pem" user1 = "" user2 = "" user_key = getUserDataPathWrapper(vpn_provider + "/" + getKeyName(vpn_provider, geo)) user_cert = getUserDataPathWrapper(vpn_provider + "/" + getCertName(vpn_provider, geo)) user_pass = getUserDataPathWrapper( vpn_provider + "/" + getKeyPassName(vpn_provider, geo)) remove_flags = "" if proto == "udp": ping_speed = "5" ping_exit = "30" else: ping_speed = "10" ping_exit = "60" if len(location_values) > 4: # The final location value is a list of multiple x=y declarations. # These need to be parsed out and modified. modifier_tuples = (location_values[4].strip(' \t\n\r')).split() # Loop through all of the values splitting them into name value pairs for modifier in modifier_tuples: pair = modifier.split("=") if "#CERT" in pair[0]: ca_cert = pair[1].strip() if "#REMOVE" in pair[0]: remove_flags = pair[1].strip() if "#TLSKEY" in pair[0]: ta_key = pair[1].strip() if "#USERKEY" in pair[0]: user_key = pair[1].strip() if "#USERCERT" in pair[0]: user_cert = pair[1].strip() if "#USERPASS" in pair[0]: user_pass = pair[1].strip() if "#CRLVERIFY" in pair[0]: crl_pem = pair[1].strip() if "#DH" in pair[0]: dh_parm = pair[1].strip() if "#USER1" in pair[0]: user1 = pair[1].strip() if "#USER2" in pair[0]: user2 = pair[1].strip() if "#PINGSPEED" in pair[0]: ping_speed = pair[1].strip() if "#PINGEXIT" in pair[0]: ping_exit = pair[1].strip() if proto == "udp" and not portUDP == "": port = portUDP if proto == "tcp" and not portTCP == "": port = portTCP if port == "" and len(ports) == 1: port = ports[0] except Exception as e: errorTrace( "vpnproviders.py", "Location file for " + vpn_provider + " invalid on line\n" + location) errorTrace("vpnproviders.py", str(e)) translate_file.close() return False try: ovpn_file = open( getAddonPath(True, vpn_provider + "/" + geo + ".ovpn"), 'w') translate_location = geo if proto == "tcp": servprot = "tcp-client" else: servprot = proto # Do a replace on the tags in the template with data from the location file for line in template: output_line = line.strip(' \t\n\r') # Must check to see if there's a remove tag on the line before looking for other tags if "#REMOVE" in output_line: if output_line[output_line.index("#REMOVE") + 7] in remove_flags: # Remove the line if it's a flag this location doesn't care about output_line = "" else: # Delete the tag if this location doesn't want this line removed output_line = output_line.replace( "#REMOVE" + output_line[output_line.index("#REMOVE") + 7], "") output_line = output_line.replace("#PROTO", proto) output_line = output_line.replace("#SERVPROT", servprot) # If there are multiple servers then we'll need to duplicate the server # line (which starts with 'remote ') and fix the server. The rest of the # code will deal with the port which is the same for all lines (although # this assumption might not be true for all VPN providers...) if output_line.startswith("remote "): server_template = output_line server_lines = "" i = 0 for server in servers: if i == 0: translate_server = server if not server_lines == "": server_lines = server_lines + "\n" server_lines = server_lines + server_template.replace( "#SERVER", server) if port == "": server_lines = server_lines.replace( "#PORT", ports[i]) i = i + 1 if i > 1: translate_server = translate_server + " & " + str( i - 1) + " more" output_line = server_lines # There might be other places we use server and port, so still the do the replace output_line = output_line.replace("#SERVER", servers[0]) output_line = output_line.replace("#PORT", port) # Pass is always generated by the add-on so will be in the addon directory if "#PASS" in output_line: output_line = output_line.replace( "#PASS", getAddonPathWrapper(vpn_provider + "/" + "pass.txt")) # These flags are files that can be over ridden in the user data directory if "#CERT" in output_line: output_line = output_line.replace( "#CERT", getBestPathWrapper(vpn_provider + "/" + ca_cert)) if "#TLSKEY" in output_line: output_line = output_line.replace( "#TLSKEY", getBestPathWrapper(vpn_provider + "/" + ta_key)) if "#CRLVERIFY" in output_line: output_line = output_line.replace( "#CRLVERIFY", getBestPathWrapper(vpn_provider + "/" + crl_pem)) if "#DH" in output_line: output_line = output_line.replace( "#DH", getBestPathWrapper(vpn_provider + "/" + dh_parm)) # User files are managed by the add-on so will be in the user directory (set above) output_line = output_line.replace("#USERKEY", user_key) output_line = output_line.replace("#USERCERT", user_cert) output_line = output_line.replace("#USERPASS", user_pass) # Path is the add-on path, not the user directory if "#PATH" in output_line: output_line = output_line.replace( "#PATH", getAddonPathWrapper(vpn_provider + "/")) output_line = output_line.replace("#USER1", user1) output_line = output_line.replace("#USER2", user2) output_line = output_line.replace("#PINGSPEED", ping_speed) output_line = output_line.replace("#PINGEXIT", ping_exit) # Overwrite the verb value with the one in the settings if output_line.startswith("verb "): output_line = "verb " + verb_value # This is a little hack to remove a tag that doesn't work with TCP but is needed for UDP # Could do this with a #REMOVE, but doing it here is less error prone. if "explicit-exit-notify" in line and proto == "tcp": output_line = "" if not output_line == "": ovpn_file.write(output_line + "\n") ovpn_file.close() debugTrace("Wrote location " + geo + " " + proto) translate_file.write(translate_location + "," + translate_server + " (" + proto.upper() + ")\n") except Exception as e: errorTrace( "vpnproviders.py", "Can't write a location file for " + vpn_provider + " failed on line\n" + location) errorTrace("vpnproviders.py", str(e)) translate_file.close() return False # Write the location to server translation file translate_file.close() # Flag that the files have been generated writeGeneratedFile(vpn_provider) return True
reboot_timer = 0 seconds_to_reboot_check = 0 reboot_time = "" reboot_day = "" last_file_check_time = 0 last_cycle = "" delay = 5 connection_errors = 0 stop = False vpn_setup = True vpn_provider = "" playing = False infoTrace("service.py", "Starting VPN monitor service, platform is " + str(getPlatform()) + ", version is " + addon.getAddonInfo("version")) infoTrace("service.py", "Kodi build is " + xbmc.getInfoLabel('System.BuildVersion')) while not monitor.abortRequested(): if stopRequested() or stop: if not stop: # Acknowledge that we've stopped so that the config can do things # Also shorten the delay so that we can be more responsive and kill any cycle attempt debugTrace("Service received a stop request") ackStop() stop = True delay = 2 clearVPNCycle() elif startRequested(): debugTrace("Service received a start request")
# Retry time in seconds connection_retry_time = 3600 timer = 0 cycle_timer = 0 last_cycle = "" delay = 5 connection_errors = 0 stop = False vpn_setup = True vpn_provider = "" playing = False infoTrace( "service.py", "Starting VPN monitor service, platform is " + str(getPlatform()) + ", version is " + addon.getAddonInfo("version")) infoTrace("service.py", "Kodi build is " + xbmc.getInfoLabel('System.BuildVersion')) while not monitor.abortRequested(): if stopRequested() or stop: if not stop: # Acknowledge that we've stopped so that the config can do things # Also shorten the delay so that we can be more responsive and kill any cycle attempt debugTrace("Service received a stop request") ackStop() stop = True delay = 2 clearVPNCycle() elif startRequested():
def updateVPNFiles(vpn_provider): # If the OVPN files aren't generated then they need to be updated with location info infoTrace("vpnproviders.py", "Updating VPN profiles for " + vpn_provider) # Get the list of VPN profile files ovpn_connections = getAddonList(vpn_provider, "*.ovpn") # See if there's a port override going on addon = xbmcaddon.Addon("service.vpn.manager") if addon.getSetting("default_udp") == "true": portUDP = "" else: portUDP = addon.getSetting("alternative_udp_port") if addon.getSetting("default_tcp") == "true": portTCP = "" else: portTCP = addon.getSetting("alternative_tcp_port") # Get the logging level verb_value = addon.getSetting("openvpn_verb") if verb_value == "": verb_value = "1" addon.setSetting("openvpn_verb", verb_value) # Open a translate file try: debugTrace("Opening translate file for " + vpn_provider) translate_file = open( getAddonPath(True, vpn_provider + "/TRANSLATE.txt"), 'w') debugTrace("Opened translate file for " + vpn_provider) except Exception as e: errorTrace("vpnproviders.py", "Couldn't open the translate file for " + vpn_provider) errorTrace("vpnproviders.py", str(e)) return False for connection in ovpn_connections: try: f = open(connection, 'r+') debugTrace("Processing file " + connection) lines = f.readlines() f.seek(0) f.truncate() # Get the profile friendly name in case we need to generate key/cert names name = connection[connection.rfind(getSeparator()) + 1:connection.rfind(".ovpn")] translate_location = name translate_server = "" server_count = 0 found_up = False found_down = False found_script_sec = False found_block_dns = False found_ping = False proto = "udp" # Update the necessary values in the ovpn file for line in lines: line = line.strip(' \t\n\r') # Update path to pass.txt if not isUserDefined(vpn_provider) or addon.getSetting( "user_def_credentials") == "true": if line.startswith("auth-user-pass"): line = "auth-user-pass " + getAddonPathWrapper( vpn_provider + "/" + "pass.txt") # Update port numbers if line.startswith("remote "): server_count += 1 tokens = line.split() port = "" for newline in lines: if newline.startswith("proto "): if "tcp" in newline: proto = "tcp" if not portTCP == "": port = portTCP break if "udp" in newline: proto = "udp" if not portUDP == "": port = portUDP break if not port == "": line = "remote " + tokens[1] + " " + port + "\n" if translate_server == "": translate_server = tokens[1] # Update user cert and key if not isUserDefined(vpn_provider) and usesUserKeys( vpn_provider): if line.startswith("cert "): line = "cert " + getUserDataPathWrapper( vpn_provider + "/" + getCertName(vpn_provider, name)) if line.startswith("key "): line = "key " + getUserDataPathWrapper( vpn_provider + "/" + getKeyName(vpn_provider, name)) # Update key password (if there is one) if not isUserDefined(vpn_provider) or usesKeyPass( vpn_provider): if line.startswith("askpass"): line = "askpass " + getUserDataPathWrapper( vpn_provider + "/" + getKeyPass(vpn_provider)) # For user defined profile we need to replace any path tags with the addon dir path if isUserDefined(vpn_provider): line = line.replace("#PATH", getAddonPathWrapper(vpn_provider)) # Set the logging level if line.startswith("verb "): line = "verb " + verb_value if line.startswith("up "): found_up = True if line.startswith("down "): found_down = True if line.startswith("script-security "): found_script_sec = True if line.startswith("block-outside-dns"): found_block_dns = True if line.startswith("ping"): found_ping = True f.write(line + "\n") if not found_block_dns and getPlatform( ) == platforms.WINDOWS and addon.getSetting( "block_outside_dns") == "true": f.write("block-outside-dns\n") if addon.getSetting("up_down_script") == "true": if not found_script_sec: f.write("script-security 2\n") if not found_up: f.write(getUpParam(vpn_provider) + "\n") if not found_down: f.write(getDownParam(vpn_provider) + "\n") if not found_ping and addon.getSetting("force_ping") == "true": if proto == "tcp": f.write("ping 10\n") f.write("ping-exit 60\n") else: f.write("ping 5\n") f.write("ping-exit 30\n") f.write("ping-timer-rem\n") f.close() if server_count > 1: translate_server = translate_server + " & " + str( server_count - 1) + " more" translate_file.write(translate_location + "," + translate_server + " (" + proto.upper() + ")\n") except Exception as e: errorTrace("vpnproviders.py", "Failed to update ovpn file") errorTrace("vpnproviders.py", str(e)) translate_file.close() return False # Write the location to server translation file translate_file.close() # Flag that the files have been generated writeGeneratedFile(vpn_provider) return True
# Get the arguments passed in base_url = sys.argv[0] addon_handle = int(sys.argv[1]) args = sys.argv[2].split("?", ) action = "" params = "" # If an argument has been passed in, the first character will be a ?, so the first list element is empty inc = 0 for token in args: if inc == 1: action = token if inc > 1: params = params + token inc = inc + 1 # Don't seem to need to do this on *nix platforms as the filename will be different if getPlatform() == platforms.WINDOWS: params = params.replace("/", "\\") debugTrace("Parsed arguments to action=" + action + " params=" + params) def topLevel(): # Build the top level menu with URL callbacks to this plugin debugTrace("Displaying the top level menu") url = base_url + "?settings" li = xbmcgui.ListItem("Add-on Settings", iconImage=getIconPath() + "settings.png") xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li) url = base_url + "?display" li = xbmcgui.ListItem("Display VPN status", iconImage=getIconPath() + "display.png") xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li)
def updateVPNFiles(vpn_provider): # If the OVPN files aren't generated then they need to be updated with location info infoTrace("vpnproviders.py", "Updating VPN profiles for " + vpn_provider) # Get the list of VPN profile files ovpn_connections = getAddonList(vpn_provider, "*.ovpn") # See if there's a port override going on addon = xbmcaddon.Addon("service.vpn.manager") if addon.getSetting("default_udp") == "true": portUDP = "" else: portUDP = addon.getSetting("alternative_udp_port") if addon.getSetting("default_tcp") == "true": portTCP = "" else: portTCP = addon.getSetting("alternative_tcp_port") # Get the logging level verb_value = addon.getSetting("openvpn_verb") if verb_value == "": verb_value = "1" addon.setSetting("openvpn_verb", verb_value) for connection in ovpn_connections: try: f = open(connection, 'r+') debugTrace("Processing file " + connection) lines = f.readlines() f.seek(0) f.truncate() # Get the profile friendly name in case we need to generate key/cert names name = connection[connection.rfind(getSeparator()) + 1:connection.rfind(".ovpn")] if getPlatform() == platforms.WINDOWS and addon.getSetting( "block_outside_dns") == "true": lines.append("block-outside-dns") # Update the necessary values in the ovpn file for line in lines: # Update path to pass.txt if not isUserDefined(vpn_provider) or addon.getSetting( "user_def_credentials") == "true": if line.startswith("auth-user-pass"): line = "auth-user-pass " + getAddonPathWrapper( vpn_provider + "/" + "pass.txt\n") # Update port numbers if line.startswith("remote "): port = "" for newline in lines: if "proto " in newline: if "tcp" in newline and not portTCP == "": port = portTCP if "udp" in newline and not portUDP == "": port = portUDP if not port == "": tokens = line.split() line = "remote " + tokens[1] + " " + port + "\n" # Update user cert and key if usesUserKeys(vpn_provider): if line.startswith("cert "): line = "cert " + getUserDataPathWrapper( vpn_provider + "/" + getCertName(vpn_provider, name) + "\n") if line.startswith("key "): line = "key " + getUserDataPathWrapper( vpn_provider + "/" + getKeyName(vpn_provider, name) + "\n") # For user defined profile we need to replace any path tags with the addon dir path if isUserDefined(vpn_provider): line = line.replace("#PATH", getAddonPathWrapper(vpn_provider)) # Set the logging level if line.startswith("verb "): line = "verb " + verb_value + "\n" f.write(line) f.close() except: errorTrace("vpnproviders.py", "Failed to update ovpn file") return False # Flag that the files have been generated writeGeneratedFile(vpn_provider) return True