def expect_option(entry, section, option, alert=True, ored_options=None): if option not in entry: if alert: if ored_options is not None: option = "' or '".join(ored_options) Output.error("expected '{}' option in {} section.".format(option, section)) return False return True
def save_current_section(): try: name = current_section_values["name"] del(current_section_values["name"]) cfg.setdefault(current_section_name, {}) cfg[current_section_name].setdefault(name, {}) cfg[current_section_name][name].update(current_section_values) except KeyError: Output.error("Expected name option not found at line {}. Skipping that section.".format(section_line_nb))
def set_username(self, args): if args.username is not None: validation_answer = validate_username(args.username) if validation_answer == "ok": Output.cli( "\033[01;37m*** ENACdrives username set to {} ***\033[00m". format(args.username)) conf.save_username(args.username) else: Output.error(validation_answer) self.execution_status(2)
def cifs_umount(mount): try: Output.verbose("Doing umount of {0}".format( mount.settings["Windows_letter"])) win32wnet.WNetCancelConnection2(mount.settings["Windows_letter"], 0, False) cifs_uncache_is_mounted(mount) except pywintypes.error as e: if e.winerror == 2401: # (2401, "WNetCancelConnection2", "There are open files on the connection.") mount.ui.notify_user(e.strerror) elif e.winerror == 2250: # (2250, 'WNetCancelConnection2', 'This network connection does not exist.') mount.ui.notify_user(e.strerror) else: Output.error("failed : {0}".format(e)) debug_send("umount:\n{0}".format(e))
def cifs_is_mounted(mount, cb=None): """ evaluate if this mount is mounted if cb is None make it synchronously (CLI) else make it asynchronously (GUI) """ def _cb_gvfs(success, output, exit_code): # Output.debug("lin_stack._cb_gvfs") # Output.debug("-> gvfs-mount -l : \n{0}\n\n".format(output)) share_format1 = re.sub(r"\$", r"\\$", mount.settings["server_share"]) share_format2 = re.sub(r" ", r"%20", mount.settings["server_share"]) share_format2 = re.sub(r"\$", r"\\$", share_format2) i_search = r"{share_format1} .+ {server_name} -> smb://{realm_domain};{realm_username}@{server_name}/{share_format2}".format( share_format1=share_format1, share_format2=share_format2, **mount.settings) for l in output.split("\n"): if re.search(i_search, l, flags=re.IGNORECASE): # Output.debug(l) if cb is None: return True else: cb(True) return if cb is None: return False else: cb(False) def _target_mountcifs(): return os.path.ismount(mount.settings["local_path"]) # Output.debug("lin_stack.cifs_is_mounted") if mount.settings["Linux_CIFS_method"] == "gvfs": if LIN_CONST.GVFS_GENERATION <= 3: if LIN_CONST.CMD_GVFS_MOUNT is None: Output.error("'gvfs-mount' not installed.") return _cb_gvfs(False, "'gvfs-mount' not installed.", 1) cmd = [LIN_CONST.CMD_GVFS_MOUNT, "-l"] else: if LIN_CONST.CMD_GIO_MOUNT is None: Output.error("'gio' not installed.") return _cb_gvfs(False, "'gio' not installed.", 1) cmd = [LIN_CONST.CMD_GIO_MOUNT, "mount", "-l"] # Output.debug("cmd: " + " ".join(cmd)) if cb is None: return _cb_gvfs(**BlockingProcess.run( cmd, env=dict(os.environ, LANG="C", LC_ALL="C", LANGUAGE="C"), cache=True, )) else: NonBlockingQtProcess( cmd, _cb_gvfs, env=dict(os.environ, LANG="C", LC_ALL="C", LANGUAGE="C"), cache=True, ) else: # "mount.cifs" if cb is None: return _target_mountcifs() else: NonBlockingQtThread( "os.path.ismounted.{}".format(mount.settings["local_path"]), _target_mountcifs, cb)
def cifs_mount(mount): remote = r"\\{server_name}\{server_path}".format(**mount.settings) local = mount.settings["Windows_letter"] Output.normal("remote={}\nlocal={}".format(remote, local)) # 1st attempt without password try: Output.verbose("1st attempt without password") win32wnet.WNetAddConnection2( win32netcon.RESOURCETYPE_DISK, local, remote, ) Output.verbose("succeeded") cifs_uncache_is_mounted(mount) return True except pywintypes.error as e: if e.winerror == 5: # (5, 'WNetAddConnection2', 'Access is denied.') pass elif e.winerror == 86: # (86, "WNetAddConnection2", "The specified network password is not correct.") pass elif e.winerror == 1326: # (1326, "WNetAddConnection2", "Logon failure: unknown user name or bad password.") pass elif e.winerror == 31: # (31, 'WNetAddConnection2', 'A device attached to the system is not functioning.') pass elif e.winerror == 53: # (53, 'WNetAddConnection2', 'The network path was not found.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 55: # (55, 'WNetAddConnection2', 'The specified network resource or device is no longer available.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 64: # (64, 'WNetAddConnection2', 'Le nom réseau spécifié n’est plus disponible.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 67: # (67, 'WNetAddConnection2', 'The network name cannot be found.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 71: # (71, 'WNetAddConnection2', 'No more connections can be made to this remote computer at this time because there are already as many connections as the computer can accept.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 85: # (85, 'WNetAddConnection2', 'The local device name is already in use.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 121: # (121, 'WNetAddConnection2', 'The semaphore timeout period has expired.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1202: # (1202, 'WNetAddConnection2', 'The local device name has a remembered connection to another network resource.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1208: # (1208, 'WNetAddConnection2', 'An extended error has occurred.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1219: # (1219, 'WNetAddConnection2', 'Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1222: # (1222, 'WNetAddConnection2', 'The network is not present or not started.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1265: # (1265, 'WNetAddConnection2', 'The system detected a possible attempt to compromise security. Please ensure that you can contact the server that authenticated you.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1272: # (1272, 'WNetAddConnection2', "You can't access this shared folder because your organization's security policies block unauthenticated guest access. These policies help protect your PC from unsafe or malicious devices on the network.") mount.ui.notify_user(e.strerror) return False elif e.winerror == 1311: # (1311, 'WNetAddConnection2', 'There are currently no logon servers available to service the logon request.') # (1311, 'WNetAddConnection2', "We can't sign you in with this credential because your domain isn't available. Make sure your device is connected to your organization's network and try again. If you previously signed in on this device with another credential, you can sign in with that credential.") mount.ui.notify_user(e.strerror) return False elif e.winerror == 1331: # (1331, 'WNetAddConnection2', "This user can't sign in because this account is currently disabled.") mount.ui.notify_user(e.strerror) return False elif e.winerror == 1907: # (1907, 'WNetAddConnection2', "The user's password must be changed before signing in.") pass else: Output.error("failed : {0}".format(e)) debug_send("mount without password:\n{0}".format(e)) # 2nd attempt with password wrong_password = False for _ in range(3): try: pw = mount.key_chain.get_password(mount.settings["realm"], wrong_password) wrong_password = False Output.verbose("New attempt with password") win32wnet.WNetAddConnection2( win32netcon.RESOURCETYPE_DISK, local, remote, None, r"{0}\{1}".format(mount.settings["realm_domain"], mount.settings["realm_username"]), pw, 0) mount.key_chain.ack_password(mount.settings["realm"]) Output.verbose("succeeded") cifs_uncache_is_mounted(mount) return True except pywintypes.error as e: if e.winerror == 86: # (86, "WNetAddConnection2", "The specified network password is not correct.") mount.key_chain.invalidate_if_no_ack_password( mount.settings["realm"]) wrong_password = True elif e.winerror == 1326: # (1326, "WNetAddConnection2", "Logon failure: unknown user name or bad password.") mount.key_chain.invalidate_if_no_ack_password( mount.settings["realm"]) wrong_password = True elif e.winerror == 5: # (5, 'WNetAddConnection2', 'Access is denied.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 31: # (31, 'WNetAddConnection2', 'A device attached to the system is not functioning.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 53: # (53, 'WNetAddConnection2', 'The network path was not found.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 55: # (55, 'WNetAddConnection2', 'The specified network resource or device is no longer available.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 64: # (64, 'WNetAddConnection2', 'Le nom réseau spécifié n’est plus disponible.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 67: # (67, 'WNetAddConnection2', 'The network name cannot be found.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 71: # (71, 'WNetAddConnection2', 'No more connections can be made to this remote computer at this time because there are already as many connections as the computer can accept.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 85: # (85, 'WNetAddConnection2', 'The local device name is already in use.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 121: # (121, 'WNetAddConnection2', 'The semaphore timeout period has expired.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1202: # (1202, 'WNetAddConnection2', 'The local device name has a remembered connection to another network resource.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1208: # (1208, 'WNetAddConnection2', 'An extended error has occurred.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1219: # (1219, 'WNetAddConnection2', 'Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1222: # (1222, 'WNetAddConnection2', 'The network is not present or not started.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1265: # (1265, 'WNetAddConnection2', 'The system detected a possible attempt to compromise security. Please ensure that you can contact the server that authenticated you.') mount.ui.notify_user(e.strerror) return False elif e.winerror == 1272: # (1272, 'WNetAddConnection2', "You can't access this shared folder because your organization's security policies block unauthenticated guest access. These policies help protect your PC from unsafe or malicious devices on the network.") mount.ui.notify_user(e.strerror) return False elif e.winerror == 1311: # (1311, 'WNetAddConnection2', 'There are currently no logon servers available to service the logon request.') # (1311, 'WNetAddConnection2', "We can't sign you in with this credential because your domain isn't available. Make sure your device is connected to your organization's network and try again. If you previously signed in on this device with another credential, you can sign in with that credential.") mount.ui.notify_user(e.strerror) return False elif e.winerror == 1331: # (1331, 'WNetAddConnection2', "This user can't sign in because this account is currently disabled.") mount.ui.notify_user(e.strerror) return False elif e.winerror == 1907: # (1907, 'WNetAddConnection2', "The user's password must be changed before signing in.") mount.ui.notify_user(e.strerror) return False else: Output.error("failed : {0}".format(e)) debug_send("mount with password:\n{0}".format(e)) mount.ui.notify_user(e.strerror) except CancelOperationException: Output.verbose("Operation cancelled.") return False return False
def validate_config(cfg): """ Validates that there is everything necessary in the config to do the job. Will output error message otherwise """ def expect_option(entry, section, option, alert=True, ored_options=None): if option not in entry: if alert: if ored_options is not None: option = "' or '".join(ored_options) Output.error("expected '{}' option in {} section.".format(option, section)) return False return True invalid_cifs_m = [] invalid_realm = [] invalid_network = [] invalid_msg = [] expected_realms = {} expected_networks_by_CIFS_m = {} for m_name in cfg.get("CIFS_mount", {}): # If not defined, deduct local_path from m_name if "local_path" not in cfg["CIFS_mount"][m_name]: cfg["CIFS_mount"][m_name]["local_path"] = "{MNT_DIR}/" + m_name # If not defined, deduct label from m_name if "label" not in cfg["CIFS_mount"][m_name]: cfg["CIFS_mount"][m_name]["label"] = m_name # If specified, extract server_name and server_path from unc if "unc" in cfg["CIFS_mount"][m_name]: m = re.match(r"[\\/]{2}([^\\/]+)[\\/](.*)$", cfg["CIFS_mount"][m_name]["unc"]) if m: cfg["CIFS_mount"][m_name]["server_name"] = m.group(1) cfg["CIFS_mount"][m_name]["server_path"] = m.group(2) else: Output.error("unrecognized 'unc' option in CIFS_mount section ({}).".format(cfg["CIFS_mount"][m_name]["unc"])) is_ok = ( expect_option(cfg["CIFS_mount"][m_name], "CIFS_mount", "label") and expect_option(cfg["CIFS_mount"][m_name], "CIFS_mount", "server_name") and expect_option(cfg["CIFS_mount"][m_name], "CIFS_mount", "server_path") and expect_option(cfg["CIFS_mount"][m_name], "CIFS_mount", "local_path") and (expect_option(cfg.get("global", {}), "CIFS_mount", "realm", alert=False) or expect_option(cfg["CIFS_mount"][m_name], "CIFS_mount", "realm")) ) if is_ok: if "realm" in cfg["CIFS_mount"][m_name]: realm = cfg["CIFS_mount"][m_name]["realm"] else: realm = cfg["global"]["realm"] expected_realms.setdefault(realm, []) expected_realms[realm].append(m_name) net = cfg["CIFS_mount"][m_name].get("network") if net is not None: expected_networks_by_CIFS_m.setdefault(net, []) expected_networks_by_CIFS_m[net].append(m_name) else: Output.error("Removing incomplete CIFS_mount '{}'.".format(m_name)) invalid_cifs_m.append(m_name) for realm in expected_realms: if realm not in cfg.get("realm", []): Output.error("Missing realm '{}'.".format(realm)) for m_name in expected_realms[realm]: Output.error("Removing CIFS_mount '{}' depending on realm '{}'.".format(m_name, realm)) invalid_cifs_m.append(m_name) for realm in cfg.get("realm", []): is_ok = ( expect_option(cfg["realm"][realm], "realm", "username") and expect_option(cfg["realm"][realm], "realm", "domain") ) if not is_ok: Output.error("Removing incomplete realm '{}'.".format(realm)) invalid_realm.append(realm) for m_name in expected_realms.get(realm, []): Output.error("Removing CIFS_mount '{}' depending on realm '{}'.".format(m_name, realm)) invalid_cifs_m.append(m_name) for net in expected_networks_by_CIFS_m: if net not in cfg.get("network", []): Output.error("Missing network '{}'.".format(net)) for m_name in expected_networks_by_CIFS_m[net]: Output.error("Removing 'require_network' to CIFS_mount '{}'.".format(m_name)) del(cfg["CIFS_mount"][m_name]["require_network"]) for net in cfg.get("network", []): is_ok = ( (expect_option(cfg["network"][net], "network", "ping", alert=False) or expect_option(cfg["network"][net], "network", "cifs", ored_options=("ping", "cifs"))) and expect_option(cfg["network"][net], "network", "error_msg") ) if not is_ok: Output.error("Removing incomplete network '{}'.".format(net)) invalid_network.append(net) had_change = True while had_change: had_change = False for net in cfg.get("network", []): if net in invalid_network: continue parent_network = cfg["network"][net].get("parent") if parent_network is None: continue if ( parent_network in invalid_network or parent_network not in cfg["network"]): invalid_network.append(net) had_change = True for msg in cfg.get("msg", []): is_ok = ( expect_option(cfg["msg"][msg], "msg", "text") ) cfg["msg"][msg].setdefault("icon", "none") if not is_ok: Output.error("Removing incomplete msg '{}'.".format(msg)) invalid_msg.append(msg) for m_name in invalid_cifs_m: del(cfg["CIFS_mount"][m_name]) for realm in invalid_realm: del(cfg["realm"][realm]) for net in invalid_network: del(cfg["network"][net]) for m_name in expected_networks_by_CIFS_m.get(net, []): Output.error("Removing 'require_network' to CIFS_mount '{}'.".format(m_name)) del(cfg["CIFS_mount"][m_name]["require_network"]) for msg in invalid_msg: del(cfg["msg"][msg]) return cfg
def read_config_source(src): """ Readlines on src [global] username = bancal Linux_CIFS_method = gvfs Linux_mountcifs_filemode = 0770 Linux_mountcifs_dirmode = 0770 Linux_mountcifs_options = rw,nobrl,noserverino,iocharset=utf8,sec=ntlm Linux_gvfs_symlink = true [msg] name = name this msg text = This is a warning notification icon = warning [network] name = Internet ping = www.epfl.ch ping = enacit.epfl.ch error_msg = Error, you are not connected to the network. You won't be able to mount this resource. [network] name = Epfl parent = Internet cifs = files0.epfl.ch cifs = files1.epfl.ch cifs = files8.epfl.ch cifs = files9.epfl.ch cifs = enac1files.epfl.ch error_msg = Error, you are not connected to the intranet of EPFL. Run a VPN client to be able to mount this resource. [realm] name = EPFL domain = INTRANET username = bancal [CIFS_mount] name = private label = bancal@files9 require_network = Epfl realm = EPFL server_name = files9.epfl.ch server_path = data/bancal local_path = {MNT_DIR}/bancal_on_files9 # {MNT_DIR} # {HOME_DIR} # {DESKTOP_DIR} # {LOCAL_USERNAME} # {LOCAL_GROUPNAME} bookmark = false # default : False Linux_CIFS_method = gvfs # mount.cifs : Linux's mount.cifs (requires sudo ability) # gvfs : Linux's gvfs-mount Linux_mountcifs_filemode = 0770 Linux_mountcifs_dirmode = 0770 Linux_mountcifs_options = rw,nobrl,noserverino,iocharset=utf8,sec=ntlm Linux_gvfs_symlink = yes # Enables the creation of a symbolic link to "local_path" after mount with gvfs method. # default : False Windows_letter = Z: # Drive letter to use for the mount And return cfg as {'CIFS_mount': { 'private': { 'Linux_CIFS_method': 'gvfs', 'Linux_gvfs_symlink': True, 'Linux_mountcifs_dirmode': '0770', 'Linux_mountcifs_filemode': '0770', 'Linux_mountcifs_options': 'rw,nobrl,noserverino,iocharset=utf8,sec=ntlm', 'Windows_letter': 'Z:', 'label': 'bancal@files9', 'local_path': '{MNT_DIR}/bancal_on_files9', 'realm': 'EPFL', 'server_name': 'files9.epfl.ch', 'server_path': 'data/bancal', 'bookmark': False, 'require_network': 'Epfl'}}, 'network': { 'Internet': { 'ping': ['www.epfl.ch', 'enacit.epfl.ch'], 'error_msg': 'Error, you are not connected to the network. You won't be able to mount this resource.' }, 'Epfl': { 'ping': ['files0.epfl.ch', 'files1.epfl.ch', 'files8.epfl.ch', 'files9.epfl.ch'], 'parent': 'Internet', 'error_msg': 'Error, you are not connected to the intranet of EPFL. Run a VPN client to be able to mount this resource.' } } 'global': { 'username': '******', 'Linux_CIFS_method': 'gvfs', 'Linux_gvfs_symlink': True, 'Linux_mountcifs_dirmode': '0770', 'Linux_mountcifs_filemode': '0770', 'Linux_mountcifs_options': 'rw,nobrl,noserverino,iocharset=utf8,sec=ntlm'}, 'realm': { 'EPFL': { 'domain': 'INTRANET', 'username': '******'}}} If >50% of the lines have unexpected content ... then raise ConfigException() """ def save_current_section(): try: name = current_section_values["name"] del(current_section_values["name"]) cfg.setdefault(current_section_name, {}) cfg[current_section_name].setdefault(name, {}) cfg[current_section_name][name].update(current_section_values) except KeyError: Output.error("Expected name option not found at line {}. Skipping that section.".format(section_line_nb)) multi_entries_sections = ("msg", "CIFS_mount", "realm", "network") allowed_options = { "global": ( "username", "entries_order", "require_network", "realm", "Linux_CIFS_method", "Linux_mountcifs_filemode", "Linux_mountcifs_dirmode", "Linux_mountcifs_options", "Linux_gvfs_symlink", ), "msg": ( "name", "text", "icon", ), "CIFS_mount": ( "name", "label", "require_network", "realm", "unc", "server_name", "server_path", "local_path", "bookmark", "Linux_CIFS_method", "Linux_mountcifs_filemode", "Linux_mountcifs_dirmode", "Linux_mountcifs_options", "Linux_gvfs_symlink", "Windows_letter", ), "realm": ( "name", "domain", "username", ), "network": ( "name", "parent", "ping", "cifs", "error_msg", ) } cfg = {} current_section_name = "" current_section_values = {} line_nb = 0 section_line_nb = 0 nb_unexpected_lines = 0 for line in src.readlines(): if type(line) == bytes: line = bytes_decode(line) line_nb += 1 l = line l = re.sub(r"#.*", "", l) # remove comments l = l.strip() # remove white spaces if l == "": continue # Output.debug(l) # New section if l.startswith("["): try: new_section = re.match(r"\[(\S+)\]$", l).groups()[0] except AttributeError: Output.error("Unexpected content at line {}:\n{}".format(line_nb, line)) nb_unexpected_lines += 1 continue if current_section_name in multi_entries_sections and current_section_values != {}: # Save previous section content save_current_section() if new_section in allowed_options: current_section_name = new_section current_section_values = {} section_line_nb = line_nb else: Output.error("Unexpected section name '{}' at line {}:\n{}".format(new_section, line_nb, line)) current_section_name = "" nb_unexpected_lines += 1 continue if current_section_name == "": Output.error("Unexpected content at line {}:\n{}".format(line_nb, line)) nb_unexpected_lines += 1 continue # New option try: k, v = re.match(r"([^=]*)=(.*)", l).groups() k, v = k.strip(), v.strip() except AttributeError: nb_unexpected_lines += 1 continue if k not in allowed_options[current_section_name]: Output.error("Unexpected option at line {}:\n{}".format(line_nb, line)) nb_unexpected_lines += 1 continue try: if current_section_name in multi_entries_sections: # This is a multi entries section type if current_section_name == "network" and k == "ping": # network.ping is a list current_section_values.setdefault(k, []) current_section_values[k].append(validate_value(k, v)) elif current_section_name == "network" and k == "cifs": # network.cifs is a list current_section_values.setdefault(k, []) current_section_values[k].append(validate_value(k, v)) else: # other are literals current_section_values[k] = validate_value(k, v) else: # This is a single entry section type cfg.setdefault(current_section_name, {})[k] = validate_value(k, v) except ConfigException as e: Output.error(str(e)) # Output.debug("'{}' = '{}'".format(k, v)) if current_section_name in multi_entries_sections and current_section_values != {}: # Save last section content save_current_section() if nb_unexpected_lines > (line_nb / 2): Output.warning("Found {}/{} ({}%) unexpected lines. Skipping this source.".format(nb_unexpected_lines, line_nb, nb_unexpected_lines*100.0/line_nb)) raise ConfigException("Too many unexpected lines found. Skipping this source.") elif nb_unexpected_lines > 0: Output.warning("Found {}/{} ({}%) unexpected lines.".format(nb_unexpected_lines, line_nb, nb_unexpected_lines*100.0/line_nb)) return cfg