2019-11-15 05:22:18 -05:00
|
|
|
""" apacheconfig implementation of the ParserNode interfaces """
|
|
|
|
|
|
2020-01-23 21:01:14 -05:00
|
|
|
import glob
|
|
|
|
|
|
2020-01-28 15:46:11 -05:00
|
|
|
from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module
|
|
|
|
|
|
2020-01-23 21:01:14 -05:00
|
|
|
from certbot.compat import os
|
|
|
|
|
|
2020-01-06 10:19:33 -05:00
|
|
|
from certbot_apache._internal import assertions
|
|
|
|
|
from certbot_apache._internal import interfaces
|
|
|
|
|
from certbot_apache._internal import parsernode_util as util
|
2019-11-15 05:22:18 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ApacheParserNode(interfaces.ParserNode):
|
|
|
|
|
""" apacheconfig implementation of ParserNode interface.
|
|
|
|
|
|
|
|
|
|
Expects metadata `ac_ast` to be passed in, where `ac_ast` is the AST provided
|
|
|
|
|
by parsing the equivalent configuration text using the apacheconfig library.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
|
ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable
|
|
|
|
|
super(ApacheParserNode, self).__init__(**kwargs)
|
|
|
|
|
self.ancestor = ancestor
|
|
|
|
|
self.filepath = filepath
|
|
|
|
|
self.dirty = dirty
|
|
|
|
|
self.metadata = metadata
|
|
|
|
|
self._raw = self.metadata["ac_ast"]
|
|
|
|
|
|
|
|
|
|
def save(self, msg): # pragma: no cover
|
|
|
|
|
pass
|
|
|
|
|
|
2019-12-02 09:25:39 -05:00
|
|
|
def find_ancestors(self, name): # pylint: disable=unused-variable
|
|
|
|
|
"""Find ancestor BlockNodes with a given name"""
|
|
|
|
|
return [ApacheBlockNode(name=assertions.PASS,
|
|
|
|
|
parameters=assertions.PASS,
|
|
|
|
|
ancestor=self,
|
|
|
|
|
filepath=assertions.PASS,
|
|
|
|
|
metadata=self.metadata)]
|
|
|
|
|
|
2019-11-15 05:22:18 -05:00
|
|
|
|
|
|
|
|
class ApacheCommentNode(ApacheParserNode):
|
|
|
|
|
""" apacheconfig implementation of CommentNode interface """
|
|
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
|
comment, kwargs = util.commentnode_kwargs(kwargs) # pylint: disable=unused-variable
|
|
|
|
|
super(ApacheCommentNode, self).__init__(**kwargs)
|
|
|
|
|
self.comment = comment
|
|
|
|
|
|
|
|
|
|
def __eq__(self, other): # pragma: no cover
|
|
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
|
return (self.comment == other.comment and
|
|
|
|
|
self.dirty == other.dirty and
|
|
|
|
|
self.ancestor == other.ancestor and
|
|
|
|
|
self.metadata == other.metadata and
|
|
|
|
|
self.filepath == other.filepath)
|
|
|
|
|
return False
|
|
|
|
|
|
2020-01-28 15:46:11 -05:00
|
|
|
|
|
|
|
|
def _load_included_file(filename, metadata):
|
|
|
|
|
with open(filename) as f:
|
|
|
|
|
ast = metadata['loader'].loads(f.read())
|
|
|
|
|
metadata = metadata.copy()
|
|
|
|
|
metadata['ac_ast'] = ast
|
|
|
|
|
return ApacheBlockNode(name=assertions.PASS,
|
|
|
|
|
ancestor=None,
|
|
|
|
|
filepath=filename,
|
|
|
|
|
metadata=metadata)
|
|
|
|
|
|
|
|
|
|
|
2019-11-15 05:22:18 -05:00
|
|
|
class ApacheDirectiveNode(ApacheParserNode):
|
|
|
|
|
""" apacheconfig implementation of DirectiveNode interface """
|
|
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
|
name, parameters, enabled, kwargs = util.directivenode_kwargs(kwargs)
|
|
|
|
|
super(ApacheDirectiveNode, self).__init__(**kwargs)
|
|
|
|
|
self.name = name
|
|
|
|
|
self.parameters = parameters
|
|
|
|
|
self.enabled = enabled
|
|
|
|
|
self.include = None
|
|
|
|
|
|
2020-01-28 15:46:11 -05:00
|
|
|
# LoadModule processing
|
2020-01-28 13:13:21 -05:00
|
|
|
if self.name and self.name.lower() in ["loadmodule"]:
|
|
|
|
|
mod_name, mod_filename = self.parameters
|
|
|
|
|
self.metadata["apache_vars"]["modules"].add(mod_name)
|
|
|
|
|
self.metadata["apache_vars"]["modules"].add(
|
|
|
|
|
os.path.basename(mod_filename)[:-2] + "c")
|
|
|
|
|
|
2020-01-23 21:01:14 -05:00
|
|
|
# Include processing
|
|
|
|
|
if self.name and self.name.lower() in ["include", "includeoptional"]:
|
|
|
|
|
value = self.parameters[0]
|
|
|
|
|
path = os.path.join(self.metadata['serverroot'], value)
|
|
|
|
|
filepaths = glob.glob(path)
|
|
|
|
|
for filepath in filepaths:
|
|
|
|
|
if filepath not in self.metadata['parsed_files']:
|
2020-01-28 15:46:11 -05:00
|
|
|
node = _load_included_file(filepath, self.metadata)
|
2020-01-23 21:01:14 -05:00
|
|
|
self.metadata['parsed_files'][filepath] = node
|
|
|
|
|
self.include = set(filepaths)
|
|
|
|
|
|
2019-11-15 05:22:18 -05:00
|
|
|
def __eq__(self, other): # pragma: no cover
|
|
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
|
return (self.name == other.name and
|
|
|
|
|
self.filepath == other.filepath and
|
|
|
|
|
self.parameters == other.parameters and
|
|
|
|
|
self.enabled == other.enabled and
|
|
|
|
|
self.dirty == other.dirty and
|
|
|
|
|
self.ancestor == other.ancestor and
|
|
|
|
|
self.metadata == other.metadata)
|
|
|
|
|
return False
|
|
|
|
|
|
2020-01-06 10:19:33 -05:00
|
|
|
def set_parameters(self, _parameters):
|
2019-11-15 05:22:18 -05:00
|
|
|
"""Sets the parameters for DirectiveNode"""
|
2020-01-23 21:01:14 -05:00
|
|
|
self.parameters = tuple(_parameters)
|
|
|
|
|
self._raw.value = tuple(" ".join(_parameters))
|
|
|
|
|
|
|
|
|
|
|
2020-01-28 15:46:11 -05:00
|
|
|
def _recursive_generator(node, exclude=True, files_visited=None):
|
|
|
|
|
"""Recursive generator over all children of given block node, expanding includes.
|
2020-01-28 13:13:21 -05:00
|
|
|
|
2020-01-28 15:46:11 -05:00
|
|
|
:param ApacheBlockNode node: node whose children will be yielded by this generator
|
|
|
|
|
:param bool exclude: If True, excludes nodes disabled by conditional blocks like
|
|
|
|
|
IfDefine and IfModule.
|
|
|
|
|
:param dict files_visited: bookkeeping dict for recursion to ensure we don't visit
|
|
|
|
|
the same file twice (to avoid double-counting nodes)
|
|
|
|
|
"""
|
2020-01-23 21:01:14 -05:00
|
|
|
if not files_visited:
|
|
|
|
|
files_visited = set([node.filepath])
|
|
|
|
|
for child in node.children:
|
|
|
|
|
yield child
|
|
|
|
|
if isinstance(child, ApacheBlockNode):
|
2020-01-28 15:46:11 -05:00
|
|
|
if not exclude or child.enabled:
|
|
|
|
|
for subchild in _recursive_generator(child, exclude, files_visited):
|
2020-01-28 13:13:21 -05:00
|
|
|
yield subchild
|
2020-01-23 21:01:14 -05:00
|
|
|
if isinstance(child, ApacheDirectiveNode) and child.include:
|
|
|
|
|
for filename in child.include:
|
|
|
|
|
if filename not in files_visited:
|
|
|
|
|
files_visited.add(filename)
|
|
|
|
|
file_ast = node.metadata['parsed_files'][filename]
|
2020-01-28 15:46:11 -05:00
|
|
|
for subchild in _recursive_generator(file_ast, exclude, files_visited):
|
2020-01-23 21:01:14 -05:00
|
|
|
yield subchild
|
2019-11-15 05:22:18 -05:00
|
|
|
|
|
|
|
|
|
2020-01-28 15:46:11 -05:00
|
|
|
def _is_enabled(block_node, apache_vars):
|
|
|
|
|
"""Returns False if this block disables its children given loaded Apache data.
|
|
|
|
|
|
|
|
|
|
Checks to see whether this block_node is a conditional IfDefine or IfModule,
|
|
|
|
|
and returns what its argument evaluates to.
|
|
|
|
|
|
|
|
|
|
:param ApacheBlockNode block_node: block node to check.
|
|
|
|
|
:param dict apache_vars: dict that includes set of loaded modules and variables, under keys
|
|
|
|
|
"modules" and "defines", respectively.
|
|
|
|
|
"""
|
|
|
|
|
filters = {
|
|
|
|
|
"ifdefine": apache_vars["defines"],
|
|
|
|
|
"ifmodule": apache_vars["modules"]
|
|
|
|
|
}
|
|
|
|
|
if not block_node.name or block_node.name.lower() not in filters:
|
|
|
|
|
return True
|
|
|
|
|
loaded_set = filters[block_node.name.lower()]
|
|
|
|
|
name = block_node.parameters[0]
|
|
|
|
|
expect_loaded = not name.startswith("!")
|
|
|
|
|
name = name.lstrip("!")
|
|
|
|
|
loaded = (name in loaded_set)
|
|
|
|
|
return expect_loaded == loaded
|
|
|
|
|
|
|
|
|
|
|
2019-11-15 05:22:18 -05:00
|
|
|
class ApacheBlockNode(ApacheDirectiveNode):
|
|
|
|
|
""" apacheconfig implementation of BlockNode interface """
|
|
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
|
super(ApacheBlockNode, self).__init__(**kwargs)
|
2020-01-23 21:01:14 -05:00
|
|
|
self._raw_children = self._raw
|
|
|
|
|
children = []
|
|
|
|
|
|
2020-01-28 15:46:11 -05:00
|
|
|
self.enabled = self.enabled and _is_enabled(self, self.metadata["apache_vars"])
|
2020-01-23 21:01:14 -05:00
|
|
|
for raw_node in self._raw_children:
|
2020-01-28 15:46:11 -05:00
|
|
|
node = None # type: Optional[ApacheParserNode]
|
2020-01-23 21:01:14 -05:00
|
|
|
metadata = self.metadata.copy()
|
|
|
|
|
metadata['ac_ast'] = raw_node
|
|
|
|
|
if raw_node.typestring == "comment":
|
|
|
|
|
node = ApacheCommentNode(comment=raw_node.name[2:],
|
|
|
|
|
metadata=metadata, ancestor=self,
|
|
|
|
|
filepath=self.filepath)
|
2020-01-28 15:46:11 -05:00
|
|
|
elif raw_node.typestring == "block":
|
2020-01-23 21:01:14 -05:00
|
|
|
parameters = util.parameters_from_string(raw_node.arguments)
|
|
|
|
|
node = ApacheBlockNode(name=raw_node.tag, parameters=parameters,
|
|
|
|
|
metadata=metadata, ancestor=self,
|
|
|
|
|
filepath=self.filepath, enabled=self.enabled)
|
|
|
|
|
else:
|
|
|
|
|
parameters = ()
|
2020-01-28 15:46:11 -05:00
|
|
|
if raw_node.value:
|
2020-01-23 21:01:14 -05:00
|
|
|
parameters = util.parameters_from_string(raw_node.value)
|
|
|
|
|
node = ApacheDirectiveNode(name=raw_node.name, parameters=parameters,
|
|
|
|
|
metadata=metadata, ancestor=self,
|
|
|
|
|
filepath=self.filepath, enabled=self.enabled)
|
|
|
|
|
children.append(node)
|
|
|
|
|
self.children = tuple(children)
|
|
|
|
|
|
2020-01-28 15:46:11 -05:00
|
|
|
def __eq__(self, other): # pragma: no cover
|
2019-11-15 05:22:18 -05:00
|
|
|
if isinstance(other, self.__class__):
|
|
|
|
|
return (self.name == other.name and
|
|
|
|
|
self.filepath == other.filepath and
|
|
|
|
|
self.parameters == other.parameters and
|
|
|
|
|
self.children == other.children and
|
|
|
|
|
self.enabled == other.enabled and
|
|
|
|
|
self.dirty == other.dirty and
|
|
|
|
|
self.ancestor == other.ancestor and
|
|
|
|
|
self.metadata == other.metadata)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def add_child_block(self, name, parameters=None, position=None): # pylint: disable=unused-argument
|
|
|
|
|
"""Adds a new BlockNode to the sequence of children"""
|
|
|
|
|
new_block = ApacheBlockNode(name=assertions.PASS,
|
|
|
|
|
parameters=assertions.PASS,
|
|
|
|
|
ancestor=self,
|
|
|
|
|
filepath=assertions.PASS,
|
|
|
|
|
metadata=self.metadata)
|
|
|
|
|
self.children += (new_block,)
|
|
|
|
|
return new_block
|
|
|
|
|
|
|
|
|
|
def add_child_directive(self, name, parameters=None, position=None): # pylint: disable=unused-argument
|
|
|
|
|
"""Adds a new DirectiveNode to the sequence of children"""
|
|
|
|
|
new_dir = ApacheDirectiveNode(name=assertions.PASS,
|
|
|
|
|
parameters=assertions.PASS,
|
|
|
|
|
ancestor=self,
|
|
|
|
|
filepath=assertions.PASS,
|
|
|
|
|
metadata=self.metadata)
|
|
|
|
|
self.children += (new_dir,)
|
|
|
|
|
return new_dir
|
|
|
|
|
|
|
|
|
|
# pylint: disable=unused-argument
|
|
|
|
|
def add_child_comment(self, comment="", position=None): # pragma: no cover
|
|
|
|
|
|
|
|
|
|
"""Adds a new CommentNode to the sequence of children"""
|
|
|
|
|
new_comment = ApacheCommentNode(comment=assertions.PASS,
|
|
|
|
|
ancestor=self,
|
|
|
|
|
filepath=assertions.PASS,
|
|
|
|
|
metadata=self.metadata)
|
|
|
|
|
self.children += (new_comment,)
|
|
|
|
|
return new_comment
|
|
|
|
|
|
2020-01-28 13:13:21 -05:00
|
|
|
def find_blocks(self, name, exclude=True): # pylint: disable=unused-argument
|
2019-11-15 05:22:18 -05:00
|
|
|
"""Recursive search of BlockNodes from the sequence of children"""
|
2020-01-23 21:01:14 -05:00
|
|
|
blocks = []
|
2020-01-28 15:46:11 -05:00
|
|
|
for child in _recursive_generator(self, exclude=exclude):
|
2020-01-23 21:01:14 -05:00
|
|
|
if isinstance(child, ApacheBlockNode) and child.name.lower() == name.lower():
|
|
|
|
|
blocks.append(child)
|
|
|
|
|
return blocks
|
2019-11-15 05:22:18 -05:00
|
|
|
|
|
|
|
|
def find_directives(self, name, exclude=True): # pylint: disable=unused-argument
|
|
|
|
|
"""Recursive search of DirectiveNodes from the sequence of children"""
|
2020-01-23 21:01:14 -05:00
|
|
|
directives = []
|
2020-01-28 15:46:11 -05:00
|
|
|
for child in _recursive_generator(self, exclude=exclude):
|
2020-01-23 21:01:14 -05:00
|
|
|
if isinstance(child, ApacheDirectiveNode) and child.name.lower() == name.lower():
|
|
|
|
|
directives.append(child)
|
|
|
|
|
return directives
|
2019-11-15 05:22:18 -05:00
|
|
|
|
2020-01-28 15:46:11 -05:00
|
|
|
def find_comments(self, comment): # pylint: disable=unused-argument
|
2019-11-15 05:22:18 -05:00
|
|
|
"""Recursive search of DirectiveNodes from the sequence of children"""
|
2020-01-23 21:01:14 -05:00
|
|
|
comments = []
|
2020-01-28 15:46:11 -05:00
|
|
|
for child in _recursive_generator(self):
|
2020-01-23 21:01:14 -05:00
|
|
|
if isinstance(child, ApacheCommentNode) and comment in child.comment:
|
|
|
|
|
comments.append(child)
|
|
|
|
|
return comments
|
2019-11-15 05:22:18 -05:00
|
|
|
|
|
|
|
|
def delete_child(self, child): # pragma: no cover
|
|
|
|
|
"""Deletes a ParserNode from the sequence of children"""
|
2020-01-06 10:19:33 -05:00
|
|
|
return
|
2019-11-15 05:22:18 -05:00
|
|
|
|
|
|
|
|
def unsaved_files(self): # pragma: no cover
|
|
|
|
|
"""Returns a list of unsaved filepaths"""
|
|
|
|
|
return [assertions.PASS]
|
|
|
|
|
|
2019-12-10 13:20:00 -05:00
|
|
|
def parsed_paths(self): # pragma: no cover
|
|
|
|
|
"""Returns a list of parsed configuration file paths"""
|
|
|
|
|
return [assertions.PASS]
|
|
|
|
|
|
2019-11-15 05:22:18 -05:00
|
|
|
|
|
|
|
|
interfaces.CommentNode.register(ApacheCommentNode)
|
|
|
|
|
interfaces.DirectiveNode.register(ApacheDirectiveNode)
|
|
|
|
|
interfaces.BlockNode.register(ApacheBlockNode)
|