def gotKeys(vpn_provider, ovpn_name): # Check to see if we have the key for this connection. If this provider just uses # a single key then the getKey/CertName piece will work this out. If no key is passed # in then we'll just report whether or not any keys exist for this provider if not ovpn_name == "": key_name = getUserDataPath(vpn_provider + "/" + getKeyName(vpn_provider, ovpn_name)) cert_name = getUserDataPath(vpn_provider + "/" + getCertName(vpn_provider, ovpn_name)) debugTrace("Checking for user key " + key_name) debugTrace("Checking for user cert " + cert_name) if xbmcvfs.exists(key_name) and xbmcvfs.exists(cert_name): return True debugTrace("One of the user key and cert files did not exist") return False else: return False
def checkForGitUpdates(vpn_provider, cached): # Download the metadata file, compare it to the existing timestamp and return True if there's an update t = int(time.time()) if getVPNProviderUpdateTime() == 0: setVPNProviderUpdate("false") setVPNProviderUpdateTime(t) else: # Return the value from cache if it's less than a day old if cached and t - getVPNProviderUpdateTime() < 86400: if getVPNProviderUpdate() == "true": return True else: return False setVPNProviderUpdate("false") if vpn_provider == "" or isUserDefined(vpn_provider): return False metadata = getGitMetaData(vpn_provider) if metadata is None: # Can't get to github, trace it but pretend there's no update errorTrace("vpnproviders.py", "No metadata was returned for " + vpn_provider) return False git_timestamp, version, total_files, file_list = parseGitMetaData(metadata) try: last_file = open(getUserDataPath("Downloads" + "/" + vpn_provider + "/METADATA.txt"), 'r') last = last_file.readlines() last_file.close() if last[0] == git_timestamp: return False setVPNProviderUpdate("true") setVPNProviderUpdateTime(t) return True except: # Tried to read the existing file and it likely didn't exist # Return true as this means nothing has been downloaded return True
def getLocationFiles(vpn_provider): # Return the locations files, add any user version to the end of the list locations = glob.glob(getAddonPath(True, vpn_provider + "/LOCATIONS*.txt")) user_locations = getUserDataPath(vpn_provider + "/LOCATIONS.txt") if xbmcvfs.exists(user_locations): locations.append(user_locations.replace(".txt", " User.txt")) return locations
def getTemplateFile(vpn_provider): def_temp = getAddonPath(True, vpn_provider + "/TEMPLATE.txt") user_temp = getUserDataPath(vpn_provider + "/TEMPLATE.txt") if xbmcvfs.exists(user_temp): return user_temp else: return def_temp
def copyUserDefinedFiles(): # Copy everything in the user directory to the addon directory infoTrace("vpnproviders.py", "Copying user defined files from userdata directory") source_path = getUserDataPath((user_def_str) + "/") dest_path = getAddonPath(True, user_def_str + "/") # Get the list of connection profiles and another list of strings to abuse for the selection screen try: files = getUserDataList(user_def_str, "*") if len(files) == 0: errorTrace( "vpnproviders.py", "No User Defined files available to copy from " + source_path) return False for file in files: name = file[file.rfind(getSeparator()) + 1:] dest_file = dest_path + getSeparator() + name xbmcvfs.copy(file, dest_file) if not xbmcvfs.exists(dest_file): raise IOError('Failed to copy user def file ' + file + " to " + dest_file) return True except Exception as e: errorTrace( "vpnproviders.py", "Error copying files from " + source_path + " to " + dest_path) errorTrace("vpnproviders.py", str(e)) return False
def removeDownloadedFiles(): if xbmcvfs.exists(getUserDataPath("Downloads/")): for provider in providers: try: files = getDownloadList(provider, "*") for file in files: xbmcvfs.delete(file) except: pass
def popupImportLog(): dialog_text = "" if xbmcvfs.exists(getImportLogPath()): log_file = open(getImportLogPath(), 'r') log_output = log_file.readlines() log_file.close() for line in log_output: dialog_text = dialog_text + line else: dialog_text = "No import log file available. A log file is only available once the import wizard has been run.\n\n" dialog_text = dialog_text + "The User Defined directory is " + getUserDataPath("UserDefined/") + "\n\n" dialog_text = dialog_text + "More information on using User Defined VPNs can be found on the GitHub wiki for the service.vpn.manager project.\n" showLogBox("Import Wizard Log", dialog_text)
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 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 popupImportLog(): dialog_text = "" if xbmcvfs.exists(getImportLogPath()): log_file = open(getImportLogPath(), 'r') log_output = log_file.readlines() log_file.close() for line in log_output: dialog_text = dialog_text + line else: dialog_text = "No import log file available. A log file is only available once the import wizard has been run.\n\n" dialog_text = dialog_text + "The User Defined directory is " + getUserDataPath( "UserDefined/") + "\n\n" dialog_text = dialog_text + "More information on using User Defined VPNs can be found on the GitHub wiki for the service.vpn.manager project.\n" showLogBox("Import Wizard Log", dialog_text)
def checkForGitUpdates(vpn_provider): # Download the metadata file, compare it to the existing timestamp and return True if there's an update if vpn_provider == "" or isUserDefined(vpn_provider): return False metadata = getGitMetaData(vpn_provider) if metadata is None: return False git_timestamp, version, total_files, file_list = parseGitMetaData(metadata) try: last_file = open(getUserDataPath("Downloads" + "/" + vpn_provider + "/METADATA.txt"), 'r') last = last_file.readlines() last_file.close() if last[0] == git_timestamp: return False return True except: return False
def clearUserData(): # Deleting everything here, but not deleting 'DEFAULT.txt' as # any existing user and password info will get deleted path = getUserDataPath("UserDefined" + "/*.*") debugTrace("Deleting contents of User Defined directory" + path) files = glob.glob(path) try: for file in files: if not file.endswith("DEFAULT.txt") and xbmcvfs.exists(file): debugTrace("Deleting " + file) xbmcvfs.delete(file) except Exception as e: errorTrace("import.py", "Couldn't clear the UserDefined directory") errorTrace("import.py", str(e)) return False return True
def copyUserDefinedFiles(): # Copy everything in the user directory to the addon directory infoTrace("vpnproviders.py", "Copying user defined files from userdata directory") source_path = getUserDataPath((user_def_str)+"/") dest_path = getAddonPath(True, user_def_str + "/") # Get the list of connection profiles and another list of strings to abuse for the selection screen try: files = getUserDataList(user_def_str, "*") if len(files) == 0: errorTrace("vpnproviders.py", "No User Defined files available to copy from " + source_path) return False for file in files: name = file[file.rfind(getSeparator())+1:] dest_file = dest_path + getSeparator() + name xbmcvfs.copy(file, dest_file) if not xbmcvfs.exists(dest_file): raise IOError('Failed to copy user def file ' + file + " to " + dest_file) return True except Exception as e: errorTrace("vpnproviders.py", "Error copying files from " + source_path + " to " + dest_path) errorTrace("vpnproviders.py", str(e)) return False
def getDownloadList(vpn_provider, filter): # Return all user files for a provider (aka directory name...) path = getUserDataPath("Downloads" + "/" + getVPNLocation(vpn_provider) + "/" + filter) debugTrace("Getting list of files in " + path) return sorted(glob.glob(path))
def copyKeyAndCert(vpn_provider, ovpn_name, user_key, user_cert): # Copy the user key and cert to the userdata directory key_dest = getUserDataPath(vpn_provider + "/" + getKeyName(vpn_provider, ovpn_name)) key_source = user_key cert_dest = getUserDataPath(vpn_provider + "/" + getCertName(vpn_provider, ovpn_name)) cert_source = user_cert if key_source == cert_source: # This means that a .ovpn was selected try: debugTrace("Extracing key and cert from " + key_source + " to " + key_dest + " and " + cert_dest) ovpn_file = open(key_source, 'r') ovpn = ovpn_file.readlines() ovpn_file.close() debugTrace("Checking directory path exists for key and cert " + os.path.dirname(key_dest)) if not os.path.exists(os.path.dirname(key_dest)): infoTrace("vpnprovider.py", "Creating " + os.path.dirname(key_dest)) os.makedirs(os.path.dirname(key_dest)) xbmc.sleep(500) # Loop around waiting for the directory to be created. After 10 seconds we'll carry # on and let he open file calls fail and throw an exception t = 0 while not os.path.exists(os.path.dirname(key_dest)): if t == 9: errorTrace("vpnprovider.py", "Waited 10 seconds to create directory but it never appeared") break xbmc.sleep(1000) t += 1 key_file = open(key_dest, 'w') cert_file = open(cert_dest, 'w') key = False cert = False key_count = 0 cert_count = 0 for line in ovpn: line = line.strip(' \t\n\r') if line.startswith("<key>"): key = True elif line.startswith("</key>"): key = False elif line.startswith("<cert>"): cert = True elif line.startswith("</cert>"): cert = False else: if key: key_file.write(line + "\n") key_count += 1 if cert: cert_file.write(line + "\n") cert_count += 1 key_file.close() cert_file.close() if key_count > 0 and cert_count > 0: return True else: # Couldn't extract key and/or cert, delete any remains and return error errorTrace("vpnproviders.py", "Failed to extract user key or cert file from ovpn. Key size was " + str(key_count) + " and cert size was " + str(cert_count)) if xbmcvfs.exists(key_dest): xbmcvfs.delete(key_dest) if xbmcvfs.exists(cert_dest): xbmcvfs.delete(cert_dest) return False except Exception as e: errorTrace("vpnproviders.py", "Failed to copy user key or cert file to userdata") errorTrace("vpnproviders.py", str(e)) return False else: # Individual key and crt files were selected try: debugTrace("Copying key " + key_source + " to " + key_dest) if xbmcvfs.exists(key_dest): xbmcvfs.delete(key_dest) xbmcvfs.copy(key_source, key_dest) if not xbmcvfs.exists(key_dest): raise IOError('Failed to copy key ' + key_source + " to " + key_dest) debugTrace("Copying cert " + cert_source + " to " + cert_dest) if xbmcvfs.exists(cert_dest): xbmcvfs.delete(cert_dest) xbmcvfs.copy(cert_source, cert_dest) if not xbmcvfs.exists(cert_dest): raise IOError('Failed to copy cert ' + cert_source + " to " + cert_dest) return True except Exception as e: errorTrace("vpnproviders.py", "Failed to copy user key or cert file to userdata") errorTrace("vpnproviders.py", str(e)) return False
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
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
def getUserDataPathWrapper(name): # Return the fully qualified user path and file name if getPlatform() == platforms.WINDOWS: return getUserDataPath(name).replace("\\", "\\\\") else: return getUserDataPath(name)
def copyKeyAndCert(vpn_provider, ovpn_name, user_key, user_cert): # Copy the user key and cert to the userdata directory key_dest = getUserDataPath(vpn_provider + "/" + getKeyName(vpn_provider, ovpn_name)) key_source = user_key cert_dest = getUserDataPath(vpn_provider + "/" + getCertName(vpn_provider, ovpn_name)) cert_source = user_cert if key_source == cert_source: # This means that a .ovpn was selected try: debugTrace("Extracing key and cert from " + key_source + " to " + key_dest + " and " + cert_dest) ovpn_file = open(key_source, 'r') ovpn = ovpn_file.readlines() ovpn_file.close() debugTrace("Checking directory path exists for key and cert " + os.path.dirname(key_dest)) if not os.path.exists(os.path.dirname(key_dest)): infoTrace("vpnprovider.py", "Creating " + os.path.dirname(key_dest)) os.makedirs(os.path.dirname(key_dest)) xbmc.sleep(500) # Loop around waiting for the directory to be created. After 10 seconds we'll carry # on and let he open file calls fail and throw an exception t = 0 while not os.path.exists(os.path.dirname(key_dest)): if t == 9: errorTrace( "vpnprovider.py", "Waited 10 seconds to create directory but it never appeared" ) break xbmc.sleep(1000) t += 1 key_file = open(key_dest, 'w') cert_file = open(cert_dest, 'w') key = False cert = False key_count = 0 cert_count = 0 for line in ovpn: line = line.strip(' \t\n\r') if line.startswith("<key>"): key = True elif line.startswith("</key>"): key = False elif line.startswith("<cert>"): cert = True elif line.startswith("</cert>"): cert = False else: if key: key_file.write(line + "\n") key_count += 1 if cert: cert_file.write(line + "\n") cert_count += 1 key_file.close() cert_file.close() if key_count > 0 and cert_count > 0: return True else: # Couldn't extract key and/or cert, delete any remains and return error errorTrace( "vpnproviders.py", "Failed to extract user key or cert file from ovpn. Key size was " + str(key_count) + " and cert size was " + str(cert_count)) if xbmcvfs.exists(key_dest): xbmcvfs.delete(key_dest) if xbmcvfs.exists(cert_dest): xbmcvfs.delete(cert_dest) return False except Exception as e: errorTrace("vpnproviders.py", "Failed to copy user key or cert file to userdata") errorTrace("vpnproviders.py", str(e)) return False else: # Individual key and crt files were selected try: debugTrace("Copying key " + key_source + " to " + key_dest) if xbmcvfs.exists(key_dest): xbmcvfs.delete(key_dest) xbmcvfs.copy(key_source, key_dest) if not xbmcvfs.exists(key_dest): raise IOError('Failed to copy key ' + key_source + " to " + key_dest) debugTrace("Copying cert " + cert_source + " to " + cert_dest) if xbmcvfs.exists(cert_dest): xbmcvfs.delete(cert_dest) xbmcvfs.copy(cert_source, cert_dest) if not xbmcvfs.exists(cert_dest): raise IOError('Failed to copy cert ' + cert_source + " to " + cert_dest) return True except Exception as e: errorTrace("vpnproviders.py", "Failed to copy user key or cert file to userdata") errorTrace("vpnproviders.py", str(e)) return False
def getUserCerts(vpn_provider): # Return the list of key and cert files for a given provider (aka directory name...) path = getUserDataPath(getVPNLocation(vpn_provider) + "/*.crt") debugTrace("Getting certificate files " + path) return (glob.glob(path))
def refreshFromGit(vpn_provider, progress): addon = xbmcaddon.Addon("service.vpn.manager") infoTrace( "vpnproviders.py", "Checking downloaded ovpn files for " + vpn_provider + " with GitHub files") progress_title = "Updating files for " + vpn_provider try: # Create the download directories if required path = getUserDataPath("Downloads/") if not xbmcvfs.exists(path): xbmcvfs.mkdir(path) path = getUserDataPath("Downloads/" + vpn_provider + "/") if not xbmcvfs.exists(path): xbmcvfs.mkdir(path) except Exception as e: # Can't create the download directory errorTrace("vpnproviders.py", "Can't create the download directory " + path) errorTrace("vpnproviders.py", str(e)) return False # Download the metadata file metadata = getGitMetaData(vpn_provider) if metadata == None: return False git_timestamp, version, total_files, file_list = parseGitMetaData(metadata) timestamp = "" try: addon_version = int( addon.getSetting("version_number").replace(".", "")) except: addon_version = int(addon.getAddonInfo("version").replace(".", "")) if addon_version < int(version): errorTrace( "vpnproviders.py", "VPN Manager version is " + str(addon_version) + " and version " + version + " is needed for this VPN.") return False try: # Get the timestamp from the previous update last_file = open( getUserDataPath("Downloads" + "/" + vpn_provider + "/METADATA.txt"), 'r') last = last_file.readlines() last_file.close() # If there's a metadata file in the user directory but we had a problem with Github, just # return True as there's a likelihood that there's something interesting to work with if file_list is None: if progress is not None: progress_message = "Unable to download files, using existing files." progress.update(10, progress_title, progress_message) infoTrace( "vpnproviders.py", "Couldn't download files so using existing files for " + vpn_provider) xbmc.sleep(1000) return True timestamp = last[0] except Exception as e: # If the metadata can't be read and there's nothing we can get from Github, return # badness, otherwise we can read from Github and should just carry on. if file_list is None: errorTrace( "vpnproviders.py", "Couldn't download any files from Github for " + vpn_provider) return False # Check the timestamp and if it's not the same clear out the directory for new files if timestamp == git_timestamp: debugTrace("VPN provider " + vpn_provider + " up to date, timestamp is " + git_timestamp) if progress is not None: progress_message = "VPN provider files don't need updating" progress.update(10, progress_title, progress_message) xbmc.sleep(500) return True else: timestamp = git_timestamp debugTrace("VPN provider " + vpn_provider + " needs updating, deleting existing files") # Clear download files for this VPN existing = glob.glob( getUserDataPath("Downloads" + "/" + vpn_provider + "/*.*")) for file in existing: try: xbmcvfs.delete(file) except: pass # Download and store the updated files error_count = 0 file_count = 0 progress_count = float(1) progress_inc = float(99 / float(total_files)) for file in file_list: try: #debugTrace("Downloading " + file) if progress is not None: progress_count += progress_inc if progress.iscanceled(): return False progress_message = "Downloading " + file progress.update(int(progress_count), progress_title, progress_message) download_url = "https://raw.githubusercontent.com/Zomboided/service.vpn.manager.providers/master/" + vpn_provider + "/" + file download_url = download_url.replace(" ", "%20") git_file = urllib2.urlopen(download_url) file = file.strip(' \n') output = open( getUserDataPath("Downloads" + "/" + vpn_provider + "/" + file), 'w') for line in git_file: output.write(line) output.close() except Exception as e: errorTrace("vpnproviders.py", "Can't download " + file) errorTrace("vpnproviders.py", str(e)) error_count += 1 # Bail after 5 failures as it's likely something bad is happening. Don't fail # immediately because it could just be an error with one or two locations if error_count > 5: return False # Uncomment the next line to make testing NordVPN download easier.... # if file_count == 20: break file_count += 1 debugTrace("Processed " + str(file_count) + " files for " + vpn_provider) # Write the update timestamp debugTrace("Updated VPN provider " + vpn_provider + " new timestamp is " + timestamp) output = open( getUserDataPath("Downloads" + "/" + vpn_provider + "/METADATA.txt"), 'w') output.write(timestamp + "\n") output.close() if progress is not None: progress_message = "VPN provider files have been updated" progress.update(10, progress_title, progress_message) xbmc.sleep(500) return True
def getUserCerts(vpn_provider): # Return the list of key and cert files for a given provider (aka directory name...) path = getUserDataPath(getVPNLocation(vpn_provider)+"/*.crt") debugTrace("Getting certificate files " + path) return (glob.glob(path))
def importWizard(): addon = xbmcaddon.Addon("service.vpn.manager") addon_name = addon.getAddonInfo("name") errorMessage = "" success = False cancel = False xbmcgui.Dialog().ok(addon_name, "The User Defined import wizard helps you set up an unsupported VPN provider. It may not work without additional user intervention. You should review the import log and subsequent VPN logs to debug any problems.") # Warn the user that files will be deleted and kittens will be harmed if xbmcgui.Dialog().yesno(addon_name, "Any existing User Defined settings and files will be deleted. Do you want to continue?", "", ""): removeGeneratedFiles() success = clearUserData() addon.setSetting("vpn_provider", "User Defined") if not success: errorMessage = "Could not clear the UserDefined directory. Check the log." else: success = False errorMessage = "Import wizard has not been run, no settings or files have been changed." # Get the list of files to be used if success: if xbmcgui.Dialog().yesno(addon_name, "Select ALL files needed to connect to the VPN provider, including .ovpn, .key and .crt files. Select a directory (sub directories are ignored) or select multiple files within a directory?.", "", "", "Directory", "Files"): directory_input = False files = xbmcgui.Dialog().browse(1, "Select all VPN provider files", "files", "", False, False, "", True) else: directory_input = True dname = xbmcgui.Dialog().browse(0, "Select a directory containing VPN provider files", "files", "", False, False, "", False) debugTrace("Import from directory " + dname) dirs, files = xbmcvfs.listdir(dname) # Separate the selected files into ovpn files and other files ovpn_files = [] other_files = [] for name in files: if directory_input: name = dname + name debugTrace("Found file " + name) if name.endswith(".ovpn"): ovpn_files.append(name) else: other_files.append(name) if len(ovpn_files) == 0: success = False errorMessage = "No .ovpn files found. You must provide at least one .ovpn file." # Copy and modify the ovpn files if success: # Create some logs to write out later summary = [] detail = [] summary.append("Importing selected files to User Defined directory, " + getUserDataPath("UserDefined/") + "\n") summary.append("at " + time.strftime('%Y-%m-%d %H:%M:%S') + "\n") detail.append("\n=== Import details ===\n\n") update = False rename = False if xbmcgui.Dialog().yesno(addon_name, "Update the .ovpn files to best guess values and determine the best User Defined provider settings (recommended)?", "", ""): update = True detail.append("Updating the .ovpn files to best guess settings\n") if xbmcgui.Dialog().yesno(addon_name, "Rename the .ovpn files to indicate either a UDP or TCP connection type to allow filtering of connections?", "", ""): rename = True detail.append("Files will be renamed to indicate UDP or TCP\n") # Display dialog to show progress of copying files dialog_step = 100/(len(ovpn_files) + len(other_files)) progress = xbmcgui.DialogProgress() progress_title = "Copying User Defined files." progress.create(addon_name,progress_title) prog_step = 0 xbmc.sleep(500) try: dest_path = getUserDataPath("UserDefined/") debugTrace("Checking directory path exists before copying " + dest_path) if not os.path.exists(dest_path): infoTrace("import.py", "Creating " + dest_path) os.makedirs(os.path.dirname(dest_path)) xbmc.sleep(500) # Loop around waiting for the directory to be created. After 10 seconds we'll carry # on and let he open file calls fail and throw an exception t = 0 while not os.path.exists(os.path.dirname(dest_path)): if t == 9: errorTrace("vpnprovider.py", "Waited 10 seconds to create directory but it never appeared") break xbmc.sleep(1000) t += 1 other_files_count = [] for fname in other_files: path, dest_name = os.path.split(fname) dest_name = getUserDataPath("UserDefined/" + dest_name) # Report file being copied, then do it progress_message = "Copying " + fname progress.update(prog_step, progress_title, progress_message) xbmc.sleep(100) prog_step += dialog_step infoTrace("import.py", "Copying " + fname + " to " + dest_name) detail.append("Copying " + fname + " to " + dest_name + "\n") xbmcvfs.copy(fname, dest_name) if not xbmcvfs.exists(dest_name): raise IOError('Failed to copy user def file ' + fname + " to " + dest_name) other_files_count.append(0) if progress.iscanceled(): cancel = True break auth_count = 0 auth_found = 0 cert_count = 0 cert_found = 0 multiple_certs = False last_cert_found = "" ecert_count = 0 key_count = 0 key_found = 0 key_pass_found = 0 key_pass_count = 0 multiple_keys = False last_key_found = "" ekey_count = 0 if not cancel: metadata = getGitMetaData("UserDefined") mods = [] if not metadata == None: for line in metadata: line = line.strip(' \t\n\r') mods.append(line) for oname in ovpn_files: path, dest_name = os.path.split(oname) dest_name = getUserDataPath("UserDefined/" + dest_name) # Update dialog to saywhat's happening if update: progress_message = "Copying and updating " + oname else: progress_message = "Copying " + oname progress.update(prog_step, progress_title, progress_message) xbmc.sleep(100) prog_step += dialog_step # Copy the ovpn file infoTrace("import.py", "Copying " + oname + " to " + dest_name) detail.append("Copying " + oname + " to " + dest_name + "\n") xbmcvfs.copy(oname, dest_name) if not xbmcvfs.exists(dest_name): raise IOError('Failed to copy user def ovpn ' + oname + " to " + dest_name) if update: # Read the copied file in and then overwrite it with any updates needed # Was doing a read from source and write here but this failed on Linux over an smb mount (file not found) auth = False keypass = False infoTrace("import.py", "Updating " + dest_name) detail.append("Updating " + dest_name + "\n") source_file = open(dest_name, 'r') source = source_file.readlines() source_file.close() dest_file = open(dest_name, 'w') proto = "UDP" flags = [False, False, False, False, False] for line in source: line = line.strip(' \t\n\r') old_line = line i = 0 # Look for each non ovpn file uploaded and update it to make sure the path is good for fname in other_files: path, name = os.path.split(fname) if not line.startswith("#"): params = line.split() if len(params) == 2: # Remove the separator in order to get any fully qualified filename as space delimited params[1].replace(getSeparator(), " ") # Add in a leading space for unqualified filenames params[1] = " " + params[1] if params[1].endswith(" " + name): old_line = line line = params[0] + " " + "#PATH" + getSeparatorOutput() + name detail.append(" Found " + name + ", old line was : " + old_line + "\n") detail.append(" New line is " + line + "\n") other_files_count[i] += 1 if line.startswith("auth-user-pass"): auth_found += 1 auth = True if line.startswith("cert "): cert_found += 1 if line.startswith("key "): key_found += 1 if line.startswith("askpass "): key_pass_found += 1 keypass = True i += 1 # Do some tag counting to determine authentication methods to use if not line.startswith("#"): for mod in mods: flag, verb, parms = mod.split(",") if flag == "1" and line.startswith(verb) and parms in line: flags[0] = True if flag == "3" and line.startswith(verb): flags[2] = True if flag == "4" and line.startswith(verb): line = verb + " " + parms if flag == "5" and not flags[4] and verb in line: detail.append(" WARNING, " + parms + "\n") flags[4] = True if line.startswith("auth-user-pass"): auth_count += 1 if not auth: line = "auth-user-pass #PATH" + getSeparatorOutput() + "pass.txt" if line.startswith("cert "): cert_count += 1 if not last_cert_found == old_line: if not last_cert_found == "": multiple_certs = True last_cert_found = old_line if line.startswith("key "): key_count += 1 if not last_key_found == old_line: if not last_key_found == "": multiple_keys = True last_key_found = old_line if line.startswith("askpass"): key_pass_count += 1 if not keypass: line = "askpass #PATH" + getSeparatorOutput() + "key.txt" if line.startswith("proto "): if "tcp" in (line.lower()): proto = "TCP" if line.startswith("<cert>"): ecert_count += 1 if line.startswith("<key>"): ekey_count += 1 if not flags[2]: dest_file.write(line+"\n") flags[2] = False for mod in mods: flag, verb, parms = mod.split(",") if flag == "2": dest_file.write(verb+"\n") dest_file.close() flags[4] = False if flags[0]: if xbmcvfs.exists(dest_name): xbmcvfs.delete(dest_name) detail.append(" wARNING, couldn't import file as it contains errors or is unsupported\n") elif rename: proto = " (" + proto + ").ovpn" new_name = dest_name.replace(".ovpn", proto) if not xbmcvfs.exists(new_name): xbmcvfs.rename(dest_name, new_name) detail.append(" Renamed to " + new_name + "\n") else: detail.append(" WARNING, couldn't rename file to " + new_name + " as a file with that name already exists\n") if progress.iscanceled(): cancel = True break except Exception as e: errorTrace("import.py", "Failed to copy (or update) file") errorTrace("import.py", str(e)) success = False errorMessage = "Failed to copy (or update) selected files. Check the log." progress_message = "Outputting results of import wizard" progress.update(100, progress_title, progress_message) xbmc.sleep(500) # General import results summary.append("\n=== Summary of import ===\n\n") if cancel: summary.append("Import was cancelled\n") else: summary.append("Imported " + str(len(ovpn_files)) + " .ovpn files and " + str(len(other_files)) + " other files.\n") summary.append("\nYou should understand any WARNINGs below, and validate that the .ovpn files imported have been updated correctly.\n\n") summary.append("If the VPN connection fails view the VPN log to determine why, using Google to understand the errors if necessary.\n") summary.append("You can fix problems either by editing your local files and re-importing, or by editing the contents of the User Defined directory.\n\n") if update: # Report on how user names and passwords will be handled if auth_count > 0: if auth_found > 0: # Not using a password as resolved by file addon.setSetting("user_def_credentials", "false") summary.append("The auth-user-pass tag was found " + str(auth_count) + " times, but was resolved using a supplied file so user name and password don't need to be entered.\n") if not auth_found == auth_count: summary.append(" WARNING : The auth-user-pass tag was found " + str(auth_count) + " times, but only resolved using a supplied file " + str(auth_found) + " times. Some connections may not work.\n") else: # Using a password as auth-user-pass tag was found addon.setSetting("user_def_credentials", "true") summary.append("The auth-user-pass tag was found " + str(auth_count) + " times so assuming user name and password authentication is used.\n") if auth_count < len(ovpn_files): summary.append(" WARNING : The auth-user-pass tag was only found in " + str(auth_count) + " .ovpn files, out of " + str(len(ovpn_files)) + ". Some connections may not work.\n") else: # Not using a password as no auth-user-pass tag was found addon.setSetting("user_def_credentials", "false") summary.append("No auth-user-pass tag was found, so assuming user name and password is not needed.\n") # Report on how keys and certs will be handled if (cert_count > 0 or key_count > 0): summary.append("The key tag was found " + str(key_count) + " times, and the cert tag was found " + str(cert_count) + " times.\n") if cert_found > 0 or key_found > 0: # Key and cert resolved by file so not asking user for them addon.setSetting("user_def_keys", "None") summary.append("The key and certificate don't need to be requested as the key tags were resolved using a supplied file " + str(key_found) + " times, and the cert tags were resolved using a supplied file " + str(cert_found) + " times.\n") if (not cert_found == cert_count) or (not key_found == key_count): summary.append(" WARNING : The key or cert tags were not resolved by a supplied file for all occurrences. Some connections may not work.\n") else: if multiple_certs or multiple_keys: # Key and cert tags found with different file names, but no files supplied. Assume multiple files, user supplied addon.setSetting("user_def_keys", "Multiple") summary.append("Found key and cert tags with multiple filenames, but no key or certificate files were supplied. These will be requested during connection.\n") else: # Key and cert tags found with same file names, but no files supplied. Assume single file, user supplied addon.setSetting("user_def_keys", "Single") summary.append("Found key and cert tags all with the same filename, but no key or certificate files were supplied. These will be requested during connection.\n") if cert_count < len(ovpn_files) or key_count < len(ovpn_files): summary.append(" WARNING : The key tag was found " + str(key_count) + " times, and the cert tag was found " + str(cert_count) + " times. Expected to find one of each in all " + str(len(ovpn_files)) + " .ovpn files. Some connections may not work.\n") else: # Embedded key and certs found, so not asking user for them addon.setSetting("user_def_keys", "None") if (ekey_count > 0 or ecert_count > 0): if ekey_count == ecert_count and key_count == len(ovpn_files): summary.append("Using embedded user keys and certificates so keys and certs don't need to be entered.\n") else: summary.append(" WARNING : Using embedded user keys and certificates, but found " + str(ekey_count) + " keys and " + str(ecert_count) + " certificates in " + str(len(ovpn_files)) + " .ovpn files. There should be one of each in all .ovpn files otherwise some connections may not work.\n") else: summary.append("No user key or cert tags were found so assuming this type of authentication is not used.\n") # Report on how key passwords will be handled if key_pass_count > 0: if key_pass_found > 0: # Not using a password as resolved by file addon.setSetting("user_def_key_password", "false") summary.append("The askpass tag was found " + str(auth_count) + " times, but was resolved using a supplied file so the key password doesn't need to be entered.\n") if not key_pass_found == key_pass_count: summary.append(" WARNING : The askpass tag was found " + str(key_pass_count) + " times, but only resolved using a supplied file " + str(key_pass_found) + " times. Some connections may not work.\n") else: # Using a password as auth-user-pass tag was found addon.setSetting("user_def_key_password", "true") summary.append("The askpass tag was found " + str(key_pass_count) + " times so assuming key password authentication is used.\n") if key_pass_count < len(ovpn_files): summary.append(" WARNING : The askpass tag was only found in " + str(key_pass_count) + " .ovpn files, out of " + str(len(ovpn_files)) + ". Some connections may not work, or you may be asked to enter a password when it's not necessary.\n") else: # Not using a password as no askpass tag was found addon.setSetting("user_def_key_password", "false") summary.append("No askpass tag was found, so assuming key password is not needed.\n") # Report how many times each of the non .ovpn files were used i = 0 for oname in other_files: summary.append("File " + oname + " was found and used in .ovpn files " + str(other_files_count[i]) + " times.\n") if not other_files_count[i] == len(ovpn_files): if other_files_count[i] == 0: summary.append(" WARNING : " + oname + " was not used to update any .ovpn files and could be unused.\n") else: summary.append(" WARNING : The number of updates for " + oname + " was different to the number of .ovpn files, " + str(len(ovpn_files)) + ", which could be a problem.\n") i += 1 else: summary.append("None of the files were updated during import.\n") # Open a log file so all changes can be recorded without fouling up the kodi log log_name = getImportLogPath() if xbmcvfs.exists(log_name): xbmcvfs.delete(log_name) log_file = open(log_name, 'w') for line in summary: log_file.write(line) for line in detail: log_file.write(line) log_file.close() progress.close() xbmc.sleep(100) if success: if xbmcgui.Dialog().yesno(addon_name, "Import wizard finished. You should view the import log to review any issues, enter your user ID and password (if necessary) and then try and validate a VPN connection.", "", "", "OK", "Import Log"): popupImportLog() else: xbmcgui.Dialog().ok(addon_name, errorMessage) return success
def refreshFromGit(vpn_provider, progress): addon = xbmcaddon.Addon("service.vpn.manager") infoTrace("vpnproviders.py", "Checking downloaded ovpn files for " + vpn_provider + " with GitHub files") progress_title = "Updating files for " + vpn_provider try: # Create the download directories if required path = getUserDataPath("Downloads/") if not xbmcvfs.exists(path): xbmcvfs.mkdir(path) path = getUserDataPath("Downloads/" + vpn_provider + "/") if not xbmcvfs.exists(path): xbmcvfs.mkdir(path) except Exception as e: # Can't create the download directory errorTrace("vpnproviders.py", "Can't create the download directory " + path) errorTrace("vpnproviders.py", str(e)) return False # Download the metadata file metadata = getGitMetaData(vpn_provider) if metadata == None: return False git_timestamp, version, total_files, file_list = parseGitMetaData(metadata) timestamp = "" try: addon_version = int(addon.getSetting("version_number").replace(".","")) except: addon_version = int(addon.getAddonInfo("version").replace(".", "")) if addon_version < int(version): errorTrace("vpnproviders.py", "VPN Manager version is " + str(addon_version) + " and version " + version + " is needed for this VPN.") return False try: # Get the timestamp from the previous update last_file = open(getUserDataPath("Downloads" + "/" + vpn_provider + "/METADATA.txt"), 'r') last = last_file.readlines() last_file.close() # If there's a metadata file in the user directory but we had a problem with Github, just # return True as there's a likelihood that there's something interesting to work with if file_list is None: if progress is not None: progress_message = "Unable to download files, using existing files." progress.update(10, progress_title, progress_message) infoTrace("vpnproviders.py", "Couldn't download files so using existing files for " + vpn_provider) xbmc.sleep(1000) return True timestamp = last[0] except Exception as e: # If the metadata can't be read and there's nothing we can get from Github, return # badness, otherwise we can read from Github and should just carry on. if file_list is None: errorTrace("vpnproviders.py", "Couldn't download any files from Github for " + vpn_provider) return False # Check the timestamp and if it's not the same clear out the directory for new files if timestamp == git_timestamp: debugTrace("VPN provider " + vpn_provider + " up to date, timestamp is " + git_timestamp) if progress is not None: progress_message = "VPN provider files don't need updating" progress.update(10, progress_title, progress_message) xbmc.sleep(500) return True else: timestamp = git_timestamp debugTrace("VPN provider " + vpn_provider + " needs updating, deleting existing files") # Clear download files for this VPN existing = glob.glob(getUserDataPath("Downloads" + "/" + vpn_provider + "/*.*")) for file in existing: try: xbmcvfs.delete(file) except: pass # Download and store the updated files error_count = 0 file_count = 0 progress_count = float(1) progress_inc = float(99/float(total_files)) for file in file_list: try: #debugTrace("Downloading " + file) if progress is not None: progress_count += progress_inc if progress.iscanceled(): return False progress_message = "Downloading " + file progress.update(int(progress_count), progress_title, progress_message) download_url = "https://raw.githubusercontent.com/Zomboided/service.vpn.manager.providers/master/" + vpn_provider + "/" + file download_url = download_url.replace(" ", "%20") git_file = urllib2.urlopen(download_url) file = file.strip(' \n') output = open(getUserDataPath("Downloads" + "/" + vpn_provider + "/" + file), 'w') for line in git_file: output.write(line) output.close() except Exception as e: errorTrace("vpnproviders.py", "Can't download " + file) errorTrace("vpnproviders.py", str(e)) error_count += 1 # Bail after 5 failures as it's likely something bad is happening. Don't fail # immediately because it could just be an error with one or two locations if error_count > 5: return False # Uncomment the next line to make testing NordVPN download easier.... # if file_count == 20: break file_count += 1 debugTrace("Processed " + str(file_count) + " files for " + vpn_provider) # Write the update timestamp debugTrace("Updated VPN provider " + vpn_provider + " new timestamp is " + timestamp) output = open(getUserDataPath("Downloads" + "/" + vpn_provider + "/METADATA.txt"), 'w') output.write(timestamp + "\n") output.close() if progress is not None: progress_message = "VPN provider files updated, removing old ones" progress.update(10, progress_title, progress_message) # Delete any generated files and reset the connection removeGeneratedFiles() # Adjust 11 below if changing number of conn_max i = 1 while i < 11: addon.setSetting(str(i) + "_vpn_validated", "") addon.setSetting(str(i) + "_vpn_validated_friendly", "") i = i + 1 xbmc.sleep(500) return True
def importWizard(): addon = xbmcaddon.Addon("service.vpn.manager") addon_name = addon.getAddonInfo("name") errorMessage = "" success = False cancel = False xbmcgui.Dialog().ok( addon_name, "The User Defined import wizard helps you set up an unsupported VPN provider. It may not work without additional user intervention. You should review the import log and subsequent VPN logs to debug any problems." ) # Warn the user that files will be deleted and kittens will be harmed if xbmcgui.Dialog().yesno( addon_name, "Any existing User Defined settings and files will be deleted. Do you want to continue?", "", ""): removeGeneratedFiles() success = clearUserData() addon.setSetting("vpn_provider", "User Defined") if not success: errorMessage = "Could not clear the UserDefined directory. Check the log." else: success = False errorMessage = "Import wizard has not been run, no settings or files have been changed." # Get the list of files to be used if success: if xbmcgui.Dialog().yesno( addon_name, "Select ALL files needed to connect to the VPN provider, including .ovpn, .key and .crt files. Select a directory (sub directories are ignored) or select multiple files within a directory?.", "", "", "Directory", "Files"): directory_input = False files = xbmcgui.Dialog().browse(1, "Select all VPN provider files", "files", "", False, False, "", True) else: directory_input = True dname = xbmcgui.Dialog().browse( 0, "Select a directory containing VPN provider files", "files", "", False, False, "", False) debugTrace("Import from directory " + dname) dirs, files = xbmcvfs.listdir(dname) # Separate the selected files into ovpn files and other files ovpn_files = [] other_files = [] for name in files: if directory_input: name = dname + name debugTrace("Found file " + name) if name.endswith(".ovpn"): ovpn_files.append(name) else: other_files.append(name) if len(ovpn_files) == 0: success = False errorMessage = "No .ovpn files found. You must provide at least one .ovpn file." # Copy and modify the ovpn files if success: # Create some logs to write out later summary = [] detail = [] summary.append("Importing selected files to User Defined directory, " + getUserDataPath("UserDefined/") + "\n") summary.append("at " + time.strftime('%Y-%m-%d %H:%M:%S') + "\n") detail.append("\n=== Import details ===\n\n") update = False rename = False if xbmcgui.Dialog().yesno( addon_name, "Update the .ovpn files to best guess values and determine the best User Defined provider settings (recommended)?", "", ""): update = True detail.append("Updating the .ovpn files to best guess settings\n") if xbmcgui.Dialog().yesno( addon_name, "Rename the .ovpn files to indicate either a UDP or TCP connection type to allow filtering of connections?", "", ""): rename = True detail.append("Files will be renamed to indicate UDP or TCP\n") # Display dialog to show progress of copying files dialog_step = 100 / (len(ovpn_files) + len(other_files)) progress = xbmcgui.DialogProgress() progress_title = "Copying User Defined files." progress.create(addon_name, progress_title) prog_step = 0 xbmc.sleep(500) try: dest_path = getUserDataPath("UserDefined/") debugTrace("Checking directory path exists before copying " + dest_path) if not os.path.exists(dest_path): infoTrace("import.py", "Creating " + dest_path) os.makedirs(os.path.dirname(dest_path)) xbmc.sleep(500) # Loop around waiting for the directory to be created. After 10 seconds we'll carry # on and let he open file calls fail and throw an exception t = 0 while not os.path.exists(os.path.dirname(dest_path)): if t == 9: errorTrace( "vpnprovider.py", "Waited 10 seconds to create directory but it never appeared" ) break xbmc.sleep(1000) t += 1 other_files_count = [] for fname in other_files: path, dest_name = os.path.split(fname) dest_name = getUserDataPath("UserDefined/" + dest_name) # Report file being copied, then do it progress_message = "Copying " + fname progress.update(prog_step, progress_title, progress_message) xbmc.sleep(100) prog_step += dialog_step infoTrace("import.py", "Copying " + fname + " to " + dest_name) detail.append("Copying " + fname + " to " + dest_name + "\n") xbmcvfs.copy(fname, dest_name) if not xbmcvfs.exists(dest_name): raise IOError('Failed to copy user def file ' + fname + " to " + dest_name) other_files_count.append(0) if progress.iscanceled(): cancel = True break auth_count = 0 auth_found = 0 cert_count = 0 cert_found = 0 multiple_certs = False last_cert_found = "" ecert_count = 0 key_count = 0 key_found = 0 key_pass_found = 0 key_pass_count = 0 multiple_keys = False last_key_found = "" ekey_count = 0 if not cancel: metadata = getGitMetaData("UserDefined") mods = [] if not metadata == None: for line in metadata: line = line.strip(' \t\n\r') mods.append(line) for oname in ovpn_files: path, dest_name = os.path.split(oname) dest_name = getUserDataPath("UserDefined/" + dest_name) # Update dialog to saywhat's happening if update: progress_message = "Copying and updating " + oname else: progress_message = "Copying " + oname progress.update(prog_step, progress_title, progress_message) xbmc.sleep(100) prog_step += dialog_step # Copy the ovpn file infoTrace("import.py", "Copying " + oname + " to " + dest_name) detail.append("Copying " + oname + " to " + dest_name + "\n") xbmcvfs.copy(oname, dest_name) if not xbmcvfs.exists(dest_name): raise IOError('Failed to copy user def ovpn ' + oname + " to " + dest_name) if update: # Read the copied file in and then overwrite it with any updates needed # Was doing a read from source and write here but this failed on Linux over an smb mount (file not found) auth = False keypass = False infoTrace("import.py", "Updating " + dest_name) detail.append("Updating " + dest_name + "\n") source_file = open(dest_name, 'r') source = source_file.readlines() source_file.close() dest_file = open(dest_name, 'w') proto = "UDP" flags = [False, False, False, False, False] for line in source: line = line.strip(' \t\n\r') old_line = line i = 0 # Look for each non ovpn file uploaded and update it to make sure the path is good for fname in other_files: path, name = os.path.split(fname) if not line.startswith("#"): params = line.split() if len(params) == 2: # Remove the separator in order to get any fully qualified filename as space delimited params[1].replace(getSeparator(), " ") # Add in a leading space for unqualified filenames params[1] = " " + params[1] if params[1].endswith(" " + name): old_line = line line = params[ 0] + " " + "#PATH" + getSeparatorOutput( ) + name detail.append(" Found " + name + ", old line was : " + old_line + "\n") detail.append(" New line is " + line + "\n") other_files_count[i] += 1 if line.startswith( "auth-user-pass"): auth_found += 1 auth = True if line.startswith("cert "): cert_found += 1 if line.startswith("key "): key_found += 1 if line.startswith("askpass "): key_pass_found += 1 keypass = True i += 1 # Do some tag counting to determine authentication methods to use if not line.startswith("#"): for mod in mods: flag, verb, parms = mod.split(",") if flag == "1" and line.startswith( verb) and parms in line: flags[0] = True if flag == "3" and line.startswith(verb): flags[2] = True if flag == "4" and line.startswith(verb): line = verb + " " + parms if flag == "5" and not flags[ 4] and verb in line: detail.append(" WARNING, " + parms + "\n") flags[4] = True if line.startswith("auth-user-pass"): auth_count += 1 if not auth: line = "auth-user-pass #PATH" + getSeparatorOutput( ) + "pass.txt" if line.startswith("cert "): cert_count += 1 if not last_cert_found == old_line: if not last_cert_found == "": multiple_certs = True last_cert_found = old_line if line.startswith("key "): key_count += 1 if not last_key_found == old_line: if not last_key_found == "": multiple_keys = True last_key_found = old_line if line.startswith("askpass"): key_pass_count += 1 if not keypass: line = "askpass #PATH" + getSeparatorOutput( ) + "key.txt" if line.startswith("proto "): if "tcp" in (line.lower()): proto = "TCP" if line.startswith("<cert>"): ecert_count += 1 if line.startswith("<key>"): ekey_count += 1 if not flags[2]: dest_file.write(line + "\n") flags[2] = False for mod in mods: flag, verb, parms = mod.split(",") if flag == "2": dest_file.write(verb + "\n") dest_file.close() flags[4] = False if flags[0]: if xbmcvfs.exists(dest_name): xbmcvfs.delete(dest_name) detail.append( " wARNING, couldn't import file as it contains errors or is unsupported\n" ) elif rename: proto = " (" + proto + ").ovpn" new_name = dest_name.replace(".ovpn", proto) if not xbmcvfs.exists(new_name): xbmcvfs.rename(dest_name, new_name) detail.append(" Renamed to " + new_name + "\n") else: detail.append( " WARNING, couldn't rename file to " + new_name + " as a file with that name already exists\n" ) if progress.iscanceled(): cancel = True break except Exception as e: errorTrace("import.py", "Failed to copy (or update) file") errorTrace("import.py", str(e)) success = False errorMessage = "Failed to copy (or update) selected files. Check the log." progress_message = "Outputting results of import wizard" progress.update(100, progress_title, progress_message) xbmc.sleep(500) # General import results summary.append("\n=== Summary of import ===\n\n") if cancel: summary.append("Import was cancelled\n") else: summary.append("Imported " + str(len(ovpn_files)) + " .ovpn files and " + str(len(other_files)) + " other files.\n") summary.append( "\nYou should understand any WARNINGs below, and validate that the .ovpn files imported have been updated correctly.\n\n" ) summary.append( "If the VPN connection fails view the VPN log to determine why, using Google to understand the errors if necessary.\n" ) summary.append( "You can fix problems either by editing your local files and re-importing, or by editing the contents of the User Defined directory.\n\n" ) if update: # Report on how user names and passwords will be handled if auth_count > 0: if auth_found > 0: # Not using a password as resolved by file addon.setSetting("user_def_credentials", "false") summary.append( "The auth-user-pass tag was found " + str(auth_count) + " times, but was resolved using a supplied file so user name and password don't need to be entered.\n" ) if not auth_found == auth_count: summary.append( " WARNING : The auth-user-pass tag was found " + str(auth_count) + " times, but only resolved using a supplied file " + str(auth_found) + " times. Some connections may not work.\n") else: # Using a password as auth-user-pass tag was found addon.setSetting("user_def_credentials", "true") summary.append( "The auth-user-pass tag was found " + str(auth_count) + " times so assuming user name and password authentication is used.\n" ) if auth_count < len(ovpn_files): summary.append( " WARNING : The auth-user-pass tag was only found in " + str(auth_count) + " .ovpn files, out of " + str(len(ovpn_files)) + ". Some connections may not work.\n") else: # Not using a password as no auth-user-pass tag was found addon.setSetting("user_def_credentials", "false") summary.append( "No auth-user-pass tag was found, so assuming user name and password is not needed.\n" ) # Report on how keys and certs will be handled if (cert_count > 0 or key_count > 0): summary.append("The key tag was found " + str(key_count) + " times, and the cert tag was found " + str(cert_count) + " times.\n") if cert_found > 0 or key_found > 0: # Key and cert resolved by file so not asking user for them addon.setSetting("user_def_keys", "None") summary.append( "The key and certificate don't need to be requested as the key tags were resolved using a supplied file " + str(key_found) + " times, and the cert tags were resolved using a supplied file " + str(cert_found) + " times.\n") if (not cert_found == cert_count) or (not key_found == key_count): summary.append( " WARNING : The key or cert tags were not resolved by a supplied file for all occurrences. Some connections may not work.\n" ) else: if multiple_certs or multiple_keys: # Key and cert tags found with different file names, but no files supplied. Assume multiple files, user supplied addon.setSetting("user_def_keys", "Multiple") summary.append( "Found key and cert tags with multiple filenames, but no key or certificate files were supplied. These will be requested during connection.\n" ) else: # Key and cert tags found with same file names, but no files supplied. Assume single file, user supplied addon.setSetting("user_def_keys", "Single") summary.append( "Found key and cert tags all with the same filename, but no key or certificate files were supplied. These will be requested during connection.\n" ) if cert_count < len(ovpn_files) or key_count < len( ovpn_files): summary.append( " WARNING : The key tag was found " + str(key_count) + " times, and the cert tag was found " + str(cert_count) + " times. Expected to find one of each in all " + str(len(ovpn_files)) + " .ovpn files. Some connections may not work.\n") else: # Embedded key and certs found, so not asking user for them addon.setSetting("user_def_keys", "None") if (ekey_count > 0 or ecert_count > 0): if ekey_count == ecert_count and key_count == len( ovpn_files): summary.append( "Using embedded user keys and certificates so keys and certs don't need to be entered.\n" ) else: summary.append( " WARNING : Using embedded user keys and certificates, but found " + str(ekey_count) + " keys and " + str(ecert_count) + " certificates in " + str(len(ovpn_files)) + " .ovpn files. There should be one of each in all .ovpn files otherwise some connections may not work.\n" ) else: summary.append( "No user key or cert tags were found so assuming this type of authentication is not used.\n" ) # Report on how key passwords will be handled if key_pass_count > 0: if key_pass_found > 0: # Not using a password as resolved by file addon.setSetting("user_def_key_password", "false") summary.append( "The askpass tag was found " + str(auth_count) + " times, but was resolved using a supplied file so the key password doesn't need to be entered.\n" ) if not key_pass_found == key_pass_count: summary.append( " WARNING : The askpass tag was found " + str(key_pass_count) + " times, but only resolved using a supplied file " + str(key_pass_found) + " times. Some connections may not work.\n") else: # Using a password as auth-user-pass tag was found addon.setSetting("user_def_key_password", "true") summary.append( "The askpass tag was found " + str(key_pass_count) + " times so assuming key password authentication is used.\n" ) if key_pass_count < len(ovpn_files): summary.append( " WARNING : The askpass tag was only found in " + str(key_pass_count) + " .ovpn files, out of " + str(len(ovpn_files)) + ". Some connections may not work, or you may be asked to enter a password when it's not necessary.\n" ) else: # Not using a password as no askpass tag was found addon.setSetting("user_def_key_password", "false") summary.append( "No askpass tag was found, so assuming key password is not needed.\n" ) # Report how many times each of the non .ovpn files were used i = 0 for oname in other_files: summary.append("File " + oname + " was found and used in .ovpn files " + str(other_files_count[i]) + " times.\n") if not other_files_count[i] == len(ovpn_files): if other_files_count[i] == 0: summary.append( " WARNING : " + oname + " was not used to update any .ovpn files and could be unused.\n" ) else: summary.append( " WARNING : The number of updates for " + oname + " was different to the number of .ovpn files, " + str(len(ovpn_files)) + ", which could be a problem.\n") i += 1 else: summary.append( "None of the files were updated during import.\n") # Open a log file so all changes can be recorded without fouling up the kodi log log_name = getImportLogPath() if xbmcvfs.exists(log_name): xbmcvfs.delete(log_name) log_file = open(log_name, 'w') for line in summary: log_file.write(line) for line in detail: log_file.write(line) log_file.close() progress.close() xbmc.sleep(100) if success: if xbmcgui.Dialog().yesno( addon_name, "Import wizard finished. You should view the import log to review any issues, enter your user ID and password (if necessary) and then try and validate a VPN connection.", "", "", "OK", "Import Log"): popupImportLog() else: xbmcgui.Dialog().ok(addon_name, errorMessage) return success