Beispiel #1
0
    def __init__(self, *configs):
        self.validators = {}
        self.default_validator = None
        self.storages = {}
        self.default_storage = None
        self._max_size = None

        self.auth = Authenticator(self)
        for config in configs:
            self.parse(config)
Beispiel #2
0
class Configurator():
    def __init__(self, *configs):
        self.validators = {}
        self.default_validator = None
        self.storages = {}
        self.default_storage = None
        self._max_size = None

        self.auth = Authenticator(self)
        for config in configs:
            self.parse(config)

    @property
    def keyfile(self):
        return os.path.join(self.account_dir, 'account.pem')

    @property
    def registration_file(self):
        return os.path.join(self.account_dir, 'registration.json')

    @property
    def max_size(self):
        return self._max_size or 4096

    def parse(self, config):
        config = self.read_data(config)
        self.parse_account_config(config.pop('account'))
        self.parser_mgmt_config(config.pop('mgmt'))
        special_group_re = re.compile('^(?P<type>(auth|verification|storage)) (?P<opener>"?)(?P<name>.+)(?P=opener)$')
        for group, options in config.items():
            match = special_group_re.match(group)
            if match:
                if match.group('type') == 'auth':
                    self.auth.parse_block(match.group('name'), options)
                elif match.group('type') == 'verification':
                    self.parse_verification_group(match.group('name'), options)
                else:
                    self.parse_storage_group(match.group('name'), options)
            else:
                warnings.warn('Unknown section name: {0}'.format(group),
                              UnusedSectionWarning, stacklevel=2)
        self.setup_default_validator()
        self.setup_default_storage()

    @staticmethod
    def read_data(config):
        """ Reads the given file name. It assumes that the file has a INI file
            syntax. The parser returns the data without comments and fill
            characters. It supports multiple option with the same name per
            section but not multiple sections with the same name.

            :param str filename: path to INI file
            :return: a dictionary - the key is the section name value, the
                option is a array of (option name, option value) tuples"""
        sections = {}
        with config as f:
            section = None
            options = None
            for line in f:
                line = line.strip()
                # ignore comments:
                if line.startswith('#'):
                    continue
                if not line:
                    continue
                # handle section header:
                if line.startswith('[') and line.endswith(']'):
                    if section:  # save old section data
                        sections[section] = options
                    section = line[1:-1]
                    options = []
                    continue
                if section is None:
                    warnings.warn('Option without sections: {0}'.format(line),
                                  UnusedOptionWarning, stacklevel=2)
                    continue
                option, value = line.split('=', 1)
                options.append((option.strip(), value.strip()))
            if section:  # save old section data
                sections[section] = options
        return sections

    def parse_account_config(self, config):
        self.acme_server = None
        for option, value in config:
            if option == 'acme-server':
                if self.acme_server is not None:
                    raise SingletonOptionRedifined(
                        section='account',
                        option='acme_server',
                        old=self.acme_server,
                        new=value)
                self.acme_server = value
            elif option == 'dir':
                self.account_dir = value
            else:
                warnings.warn('Option unknown [{}]{} = {}'.format('account', option, value),
                              UnusedOptionWarning, stacklevel=2)
        if self.acme_server is None:
            self.acme_server = 'https://acme-staging.api.letsencrypt.org/directory'

    def parser_mgmt_config(self, config):
        self.mgmt_listeners = None
        for option, value in config:
            if option == 'max-size':
                suffixes = {'k': 1024, 'm': 1024*1024}
                for suffix, mul in suffixes.items():
                    if value.endswith(suffix):
                        self._max_size = int(value[:len(suffix)]) * mul
                        break
                else:
                    self._max_size = int(value)
            elif option == 'default-verification':
                if value == '':
                    self.default_validator = False
                else:
                    self.default_validator = value.strip()
            elif option == 'default-storage':
                if value == '':
                    self.default_storage = False
                else:
                    self.default_storage = value.strip()
            elif option == 'listener':
                if self.mgmt_listeners is None:
                    self.mgmt_listeners = []
                if value == '':  # disable listener
                    continue
                if ':' not in value:
                    raise ConfigurationError('unix socket are currenlty not supported as listeners')
                host, port = value.rsplit(':', 1)
                if host[0] == '[' and host[-1] == ']':
                    host = host[1:-1]
                self.mgmt_listeners += socket.getaddrinfo(host, int(port), proto=socket.IPPROTO_TCP)
            else:
                warnings.warn('Option unknown [{}]{} = {}'.format('listeners', option, value),
                              UnusedOptionWarning, stacklevel=2)
        if self.mgmt_listeners is None:
            self.mgmt_listeners = socket.getaddrinfo('127.0.0.1', 1313, proto=socket.IPPROTO_TCP) \
                + socket.getaddrinfo('::1', 1313, proto=socket.IPPROTO_TCP)

    def parse_verification_group(self, name, options):
        option, value = options.pop(0)
        if option != 'type':
            raise ConfigurationError('A verification must start with the type value!')
        from acmems.challenges import setup
        self.validators[name] = setup(value, options)

    def setup_default_validator(self):
        if self.default_validator is None:
            self.default_validator = 'http'
            from acmems.challenges import setup
            self.validators['http'] = setup('http01', ())
        if self.default_validator is not False:
            self.default_validator = self.validators[self.default_validator]

    def parse_storage_group(self, name, options):
        option, value = options.pop(0)
        if option != 'type':
            raise ConfigurationError('A storage must start with the type value!')
        from acmems.storages import setup
        self.storages[name] = setup(value, options)

    def setup_default_storage(self):
        if self.default_storage is None:
            self.default_storage = 'none'
            from acmems.storages import setup
            self.storages[self.default_storage] = setup('none', ())
        if self.default_storage is not False:
            self.default_storage = self.storages[self.default_storage]