示例#1
0
class GlobalSchema(RegistrySchema):
    def convert_schema_to_registry(self, data_in, data_out):
        super().convert_schema_to_registry(data_in, data_out)
        shares = data_in.pop('shares')
        """
        When guest access permitted on any share:
        1) enable anonymous IPC$ share access and anonymous access to SAMR
           and LSADCERPC services.
        2) map to guest on bad user.
        """
        guest_enabled = any(filter(lambda x: x['guestok'], shares))

        data_out.update({
            'disable spoolss': {
                'parsed': True
            },
            'dns proxy': {
                'parsed': False
            },
            'load printers': {
                'parsed': False
            },
            'max log size': {
                'parsed': 5120
            },
            'printcap name': {
                'parsed': '/dev/null'
            },
            'restrict anonymous': {
                'parsed': 0 if guest_enabled else 2
            },
        })

        if guest_enabled:
            data_out['map to guest'] = {'parsed': 'Bad User'}

        ds_state = data_in.pop('ds_state')
        if ds_state['ldap'] in ['LEAVING', 'DISABLED']:
            data_out.update({
                'passdb backend': {
                    'parsed': 'tdbsam:/root/samba/private/passdb.tdb'
                },
            })
        return

    def smb_proto_transform(entry, conf):
        val = conf.pop(entry.smbconf, entry.default)
        if val == entry.default:
            return val

        return val['raw'] == "NT1"

    def set_min_protocol(entry, val, data_in, data_out):
        data_out[entry.smbconf] = {"parsed": "NT1" if val else "SMB2_10"}
        return

    def log_level_transform(entry, conf):
        conf.pop('logging', None)
        val = conf.pop(entry.smbconf, entry.default)
        if val == entry.default:
            return val

        if val['raw'].startswith("syslog@"):
            val = val['raw'][len("syslog@")]

        return LOGLEVEL_MAP.get(val['raw'].split()[0])

    def set_log_level(entry, val, data_in, data_out):
        loglevelint = LOGLEVEL_MAP.inv.get(val, 1)
        loglevel = f"{loglevelint} auth_json_audit:3@/var/log/samba4/auth_audit.log"
        if data_in['syslog']:
            logging = f'syslog@{"3" if loglevelint > 3 else val} file'
        else:
            logging = "file"
        data_out.update({
            "log level": {
                "parsed": loglevel
            },
            "logging": {
                "parsed": logging
            },
        })
        return

    def bind_ip_transform(entry, conf):
        val = conf.pop(entry.smbconf, entry.default)
        if val == entry.default:
            return val

        if type(val) == dict:
            bind_ips = val['raw'].split()
        else:
            bind_ips = val

        if bind_ips:
            bind_ips.remove("127.0.0.1")

        return bind_ips

    def set_bind_ips(entry, val, data_in, data_out):
        if val:
            val.insert(0, "127.0.0.1")
            data_out['interfaces'] = {"parsed": val}

        data_out['bind interfaces only'] = {"parsed": True}
        return

    def mask_transform(entry, conf):
        val = conf.pop(entry.smbconf, entry.default)
        if val == entry.default:
            return val

        if val['raw'] == "0775":
            return ""

        return val['raw']

    def set_mask(entry, val, data_in, data_out):
        if not val:
            val = entry.default

        data_out[entry.smbconf] = {"parsed": val}
        return

    schema = [
        RegObj("netbiosname_local", "netbios name", ""),
        RegObj("workgroup", "workgroup", "WORKGROUP"),
        RegObj("netbiosalias", "netbios aliases", []),
        RegObj("description", "server string", ""),
        RegObj("enable_smb1",
               "server min protocol",
               False,
               smbconf_parser=smb_proto_transform,
               schema_parser=set_min_protocol),
        RegObj("unixcharset", "unix charset", "UTF8"),
        RegObj("syslog", "syslog only", False),
        RegObj("localmaster", "local master", False),
        RegObj("loglevel",
               "log level",
               "MINIMUM",
               smbconf_parser=log_level_transform,
               schema_parser=set_log_level),
        RegObj("guest", "guest account", "nobody"),
        RegObj("filemask",
               "create mask",
               "0775",
               smbconf_parser=mask_transform,
               schema_parser=set_mask),
        RegObj("dirmask",
               "directory mask",
               "0775",
               smbconf_parser=mask_transform,
               schema_parser=set_mask),
        RegObj("ntlmv1_auth", "ntlm auth", False),
        RegObj("bindip",
               "interfaces", [],
               smbconf_parser=bind_ip_transform,
               schema_parser=set_bind_ips),
    ]

    def __init__(self):
        super().__init__(self.schema)
示例#2
0
class ShareSchema(RegistrySchema):
    def convert_registry_to_schema(self, data_in, data_out):
        """
        This converts existing smb.conf shares into schema used
        by middleware. It is only used in clustered configuration.
        """
        data_aux = {}
        super().convert_registry_to_schema(data_in, data_out)
        for k, v in data_in.items():
            if type(v) != dict:
                continue

            if k in ['vfs objects', 'ea support']:
                continue

            data_aux[k] = v['raw']

        if data_aux and data_out['purpose'] not in [
                'NO_SHARE', 'DEFAULT_SHARE'
        ]:
            preset = self.middleware.call_sync('sharing.smb.presets')
            purpose = preset[data_out['purpose']]
            preset_aux = self.middleware.call_sync(
                'sharing.smb.auxsmbconf_dict', purpose['params']['auxsmbconf'])
            for k, v in preset_aux.items():
                if data_aux[k] == v:
                    data_aux.pop(k)

        data_out['auxsmbconf'] = '\n'.join(
            [f'{k}={v}' if v is not None else k for k, v in data_aux.items()])
        data_out['enabled'] = True
        data_out['locked'] = False

        return

    def convert_schema_to_registry(self, data_in, data_out):
        """
        Convert middleware schema SMB shares to an SMB service definition
        """
        def order_vfs_objects(vfs_objects, is_clustered, fruit_enabled,
                              purpose):
            vfs_objects_special = ('catia', 'fruit', 'streams_xattr',
                                   'shadow_copy_zfs', 'acl_xattr',
                                   'nfs4acl_xattr', 'glusterfs', 'winmsa',
                                   'recycle', 'crossrename', 'zfs_core',
                                   'aio_fbsd', 'io_uring')

            invalid_vfs_objects = ['zfsacl', 'ixnas', 'noacl', 'zfs_space']
            cluster_safe_objects = [
                'catia', 'fruit', 'streams_xattr', 'acl_xattr', 'recycle',
                'glusterfs', 'io_ring'
            ]

            vfs_objects_ordered = []

            if fruit_enabled and 'fruit' not in vfs_objects:
                vfs_objects.append('fruit')

            if is_clustered:
                for obj in vfs_objects.copy():
                    if obj in cluster_safe_objects:
                        continue
                    vfs_objects.remove(obj)

            if 'fruit' in vfs_objects:
                if 'streams_xattr' not in vfs_objects:
                    vfs_objects.append('streams_xattr')

            if purpose == 'ENHANCED_TIMEMACHINE':
                vfs_objects.append('tmprotect')
            elif purpose == 'WORM_DROPBOX':
                vfs_objects.append('worm')

            for obj in vfs_objects:
                if obj in invalid_vfs_objects:
                    raise ValueError(f'[{obj}] is an invalid VFS object')

                if obj not in vfs_objects_special:
                    vfs_objects_ordered.append(obj)

            for obj in vfs_objects_special:
                if obj in vfs_objects:
                    vfs_objects_ordered.append(obj)

            return vfs_objects_ordered

        data_out['vfs objects'] = {"parsed": ["io_uring"]}
        data_out['ea support'] = {"parsed": False}
        data_in['fruit_enabled'] = self.middleware.call_sync(
            "smb.config")['aapl_extensions']
        is_clustered = bool(data_in['cluster_volname'])
        self.middleware.call_sync('sharing.smb.apply_presets', data_in)

        super().convert_schema_to_registry(data_in, data_out)

        ordered_vfs_objects = order_vfs_objects(
            data_out['vfs objects']['parsed'],
            is_clustered,
            data_in['fruit_enabled'],
            data_in['purpose'],
        )
        data_out['vfs objects']['parsed'] = ordered_vfs_objects
        """
        Some presets contain values that users can override via aux
        parameters. Set them prior to aux parameter processing.
        """
        if data_in['purpose'] not in ['NO_SHARE', 'DEFAULT_SHARE']:
            preset = self.middleware.call_sync('sharing.smb.presets')
            purpose = preset[data_in['purpose']]
            for param in purpose['params']['auxsmbconf'].splitlines():
                auxparam, val = param.split('=', 1)
                data_out[auxparam.strip()] = {"raw": val.strip()}

        for param in data_in['auxsmbconf'].splitlines():
            if not param.strip():
                continue
            try:
                auxparam, val = param.split('=', 1)
                """
                vfs_fruit must be added to all shares if fruit is enabled.
                Support for SMB2 AAPL extensions is determined on first tcon
                to server, and so if they aren't appended to any vfs objects
                overrides via auxiliary parameters, then users may experience
                unexpected behavior.
                """
                if auxparam.strip() == "vfs objects":
                    vfsobjects = val.strip().split()
                    if data_in['shadowcopy']:
                        vfsobjects.append('shadow_copy_zfs')
                    data_out['vfs objects'] = {
                        "parsed":
                        order_vfs_objects(vfsobjects, is_clustered,
                                          data_in['fruit_enabled'], None)
                    }
                else:
                    data_out[auxparam.strip()] = {"raw": val.strip()}

            except ValueError:
                raise

            except Exception:
                self.middleware.logger.debug(
                    "[%s] contains invalid auxiliary parameter: [%s]",
                    data_in['auxsmbconf'], param)

        self._normalize_config(data_out)
        return

    def path_local_get(entry, conf):
        path = conf.get('path', {"raw": ""})
        glusterfs_volume = conf.get('glusterfs:volume', {"raw": ""})
        if not glusterfs_volume['raw']:
            return str(path['raw'])

        return f'CLUSTER:{glusterfs_volume["raw"]}{path["raw"]}'

    def path_local_set(entry, val, data_in, data_out):
        return

    def path_get(entry, conf):
        val = conf.pop(entry.smbconf, entry.default)
        if type(val) != dict:
            return val

        path = val['parsed']
        if path == "":
            """
            Empty path is valid for homes shares.
            """
            return path

        path_suffix = conf.get("tn:path_suffix", {"raw": ""})
        """
        remove any path suffix from path before returning.
        """
        if path_suffix['raw']:
            suffix_len = len(path_suffix['raw'].split('/'))
            path = path.rsplit('/', suffix_len)[0]

        return path

    def path_set(entry, val, data_in, data_out):
        if not val:
            data_out["path"] = {"parsed": ""}
            return

        path_suffix = data_in["path_suffix"]
        if path_suffix:
            path = '/'.join([val, path_suffix])
        else:
            path = val

        data_out['path'] = {"parsed": path}

    def durable_get(entry, conf):
        """
        Durable handles are inverse of "posix locking" parmaeter.
        """
        val = conf.pop(entry.smbconf, entry.default)
        if type(val) != dict:
            return val

        kernel_oplocks = conf.get('kernel oplocks', {'parsed': False})
        if not kernel_oplocks['parsed']:
            conf.pop('kernel oplocks', None)

        kernel_share_modes = conf.get('kernel share modes', {'parsed': True})
        if not kernel_share_modes['parsed']:
            conf.pop('kernel share modes', None)

        return not val['parsed']

    def durable_set(entry, val, data_in, data_out):
        data_out['posix locking'] = {"parsed": not val}
        data_out['kernel share modes'] = {"parsed": not val}
        data_out['kernel oplocks'] = {"parsed": not val}
        return

    def recycle_get(entry, conf):
        """
        Recycle bin has multiple associated parameters, remove them
        so that they don't appear as auxiliary parameters (unless
        they deviate from our defaults).
        """
        vfs_objects = conf.get("vfs objects", [])
        if "recycle" not in vfs_objects['parsed']:
            return False

        conf.pop("recycle:repository", "")
        for parm in ["keeptree", "versions", "touch"]:
            to_check = f"recycle:{parm}"
            if conf[to_check]["parsed"]:
                conf.pop(to_check)

        if conf["recycle:directory_mode"]['raw'] == "0777":
            conf.pop("recycle:directory_mode")

        if conf["recycle:subdir_mode"]['raw'] == "0700":
            conf.pop("recycle:subdir_mode")

        return True

    def recycle_set(entry, val, data_in, data_out):
        if not val:
            return

        ad_enabled = entry.middleware.call_sync(
            "activedirectory.get_state") != "DISABLED"
        data_out.update({
            "recycle:repository": {
                "parsed": ".recycle/%D/%U" if ad_enabled else ".recycle/%U"
            },
            "recycle:keeptree": {
                "parsed": True
            },
            "recycle:versions": {
                "parsed": True
            },
            "recycle:touch": {
                "parsed": True
            },
            "recycle:directory_mode": {
                "parsed": "0777"
            },
            "recycle:subdir_mode": {
                "parsed": "0700"
            },
        })
        data_out['vfs objects']['parsed'].extend(["recycle", "crossrename"])

        return

    def shadowcopy_get(entry, conf):
        vfs_objects = conf.get("vfs objects", [])
        return "shadow_copy_zfs" in vfs_objects

    def shadowcopy_set(entry, val, data_in, data_out):
        if not val:
            return

        data_out['vfs objects']['parsed'].append("shadow_copy_zfs")
        return

    def tmquot_get(entry, conf):
        val = conf.pop(entry.smbconf, entry.default)
        if type(val) != dict:
            return 0

        return int(val['raw'])

    def acl_get(entry, conf):
        conf.pop("nfs4:chown", None)
        val = conf.pop(entry.smbconf, entry.default)
        if type(val) != dict:
            return val

        return val['parsed']

    def acl_set(entry, val, data_in, data_out):
        if not val:
            data_out['nt acl support'] = {"parsed": False}

        if data_in['cluster_volname']:
            data_out['vfs objects']['parsed'].append("acl_xattr")
            return

        try:
            acltype = entry.middleware.call_sync('filesystem.path_get_acltype',
                                                 data_in['path'])
        except OSError:
            entry.middleware.logger.warning(
                "%s: failed to determine acltype for path.",
                data_in['path'],
                exc_info=True)
            acltype = "DISABLED"

        if acltype == "NFS4":
            data_out['vfs objects']['parsed'].append("nfs4acl_xattr")
            data_out.update({
                "nfs4acl_xattr:nfs4_id_numeric": {
                    "parsed": True
                },
                "nfs4acl_xattr:validate_mode": {
                    "parsed": False
                },
                "nfs4acl_xattr:xattr_name": {
                    "parsed": "system.nfs4_acl_xdr"
                },
                "nfs4acl_xattr:encoding": {
                    "parsed": "xdr"
                },
                "nfs4:chown": {
                    "parsed": True
                }
            })
        elif acltype == 'POSIX1E':
            data_out['vfs objects']['parsed'].append("acl_xattr")

        else:
            entry.middleware.logger.debug(
                "ACLs are disabled on path %s. Disabling NT ACL support.",
                data_out['path'])
            data_out['nt acl support'] = {"parsed": False}

        return

    def fsrvp_get(entry, conf):
        vfs_objects = conf.get("vfs objects", [])
        return "zfs_fsrvp" in vfs_objects

    def fsrvp_set(entry, val, data_in, data_out):
        if not val:
            return

        data_out['vfs objects']['parsed'].append("zfs_fsrvp")
        return

    def streams_get(entry, conf):
        vfs_objects = conf.get("vfs objects", [])
        return "streams_xattr" in vfs_objects

    def streams_set(entry, val, data_in, data_out):
        """
        vfs_fruit requires streams_xattr to be enabled
        """
        if not val and not data_in['fruit_enabled']:
            return

        data_out['vfs objects']['parsed'].append("streams_xattr")
        if not data_in.get("cluster_volname"):
            data_out['smbd max xattr size'] = {"parsed": 2097152}

        if data_in['fruit_enabled']:
            data_out["fruit:metadata"] = {"parsed": "stream"}
            data_out["fruit:resource"] = {"parsed": "stream"}

        return

    def cluster_get(entry, conf):
        val = conf.pop(entry.smbconf, entry.default)
        if type(val) != dict:
            return val

        if not val['parsed']:
            return ''

        conf.pop("glusterfs:logfile", "")
        return val['parsed']

    def cluster_set(entry, val, data_in, data_out):
        """
        Add glusterfs to vfs objects, set cluster volname, and
        set logging path.
        """
        if not val:
            data_out['vfs objects']['parsed'].append("zfs_core")
            return

        data_out['vfs objects']['parsed'].append("glusterfs")
        data_out[entry.smbconf] = {"parsed": val}
        data_out["glusterfs:logfile"] = {
            "parsed": f'/var/log/samba4/glusterfs-{val}.log'
        }
        return

    def mangling_get(entry, conf):
        encoding = conf.get("fruit: encoding", None)
        if encoding and encoding['raw'] == "native":
            return True

        mapping = conf.get("catia: mappings", None)
        return bool(mapping)

    def mangling_set(entry, val, data_in, data_out):
        if not val:
            return

        data_out['vfs objects']['parsed'].append("catia")

        fruit_enabled = data_in.get("fruit_enabled")
        if fruit_enabled:
            data_out.update({
                'fruit:encoding': {
                    "parsed": 'native'
                },
                'mangled names': {
                    "parsed": False
                },
            })
        else:
            data_out.update({
                'catia:mappings': {
                    "parsed": ','.join(FRUIT_CATIA_MAPS)
                },
                'mangled names': {
                    "parsed": False
                },
            })
        return

    def afp_get(entry, conf):
        val = conf.pop(entry.smbconf, entry.default)
        if type(val) != dict:
            return val

        if not val['parsed']:
            return False

        conf.pop('fruit:encoding', None)
        conf.pop('fruit:metadata', None)
        conf.pop('fruit:resource', None)
        conf.pop('streams_xattr:store_prefix', None)
        conf.pop('streams_xattr:store_stream_type', None)
        conf.pop('streams_xattr:xattr_compat', None)
        return True

    def afp_set(entry, val, data_in, data_out):
        if not val:
            return

        if 'fruit' not in data_out['vfs objects']['parsed']:
            data_out['vfs objects']['parsed'].append("fruit")

        if 'catia' not in data_out['vfs objects']['parsed']:
            data_out['vfs objects']['parsed'].append("catia")

        data_out['fruit:encoding'] = {"parsed": 'native'}
        data_out['fruit:metadata'] = {"parsed": 'netatalk'}
        data_out['fruit:resource'] = {"parsed": 'file'}
        data_out['streams_xattr:prefix'] = {"parsed": 'user.'}
        data_out['streams_xattr:store_stream_type'] = {"parsed": False}
        data_out['streams_xattr:xattr_compat'] = {"parsed": True}
        return

    schema = [
        RegObj("purpose", "tn:purpose", ""),
        RegObj("path_local",
               None,
               "",
               smbconf_parser=path_local_get,
               schema_parser=path_local_set),
        RegObj("path",
               "path",
               "",
               smbconf_parser=path_get,
               schema_parser=path_set),
        RegObj("path_suffix", "tn:path_suffix", ""),
        RegObj("home", "tn:home", False),
        RegObj("vuid", "tn:vuid", ''),
        RegObj("comment", "comment", ""),
        RegObj("guestok", "guest ok", False),
        RegObj("hostsallow", "hosts allow", []),
        RegObj("hostsdeny", "hosts deny", []),
        RegObj("abe", "access based share enum", False),
        RegObj("ro", "read only", True),
        RegObj("browsable", "browseable", True),
        RegObj("timemachine", "fruit:time machine", True),
        RegObj("timemachine_quota",
               "fruit:time machine max size",
               "",
               smbconf_parser=tmquot_get),
        RegObj("durablehandle",
               "posix locking",
               True,
               smbconf_parser=durable_get,
               schema_parser=durable_set),
        RegObj("recyclebin",
               None,
               False,
               smbconf_parser=recycle_get,
               schema_parser=recycle_set),
        RegObj("shadowcopy",
               None,
               True,
               smbconf_parser=shadowcopy_get,
               schema_parser=shadowcopy_set),
        RegObj("acl",
               "nt acl support",
               True,
               smbconf_parser=acl_get,
               schema_parser=acl_set),
        RegObj("aapl_name_mangling",
               None,
               False,
               smbconf_parser=mangling_get,
               schema_parser=mangling_set),
        RegObj("fsrvp",
               None,
               False,
               smbconf_parser=fsrvp_get,
               schema_parser=fsrvp_set),
        RegObj("streams",
               None,
               True,
               smbconf_parser=streams_get,
               schema_parser=streams_set),
        RegObj("afp",
               "tn:afp",
               False,
               smbconf_parser=afp_get,
               schema_parser=afp_set),
        RegObj("cluster_volname",
               "glusterfs:volume",
               "",
               smbconf_parser=cluster_get,
               schema_parser=cluster_set),
    ]

    def __init__(self, middleware):
        self.middleware = middleware
        for entry in self.schema:
            entry.middleware = middleware

        super().__init__(self.schema)