Beispiel #1
0
def _get_runtime_cfg(command: List[str]) -> str:
    """
    Get runtime configuration info.

    :param command: Command to run

    :returns: stdout from command

    """
    try:
        proc = subprocess.run(
            command,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            universal_newlines=True,
            check=False,
            env=util.env_no_snap_for_external_calls())
        stdout, stderr = proc.stdout, proc.stderr

    except (OSError, ValueError):
        logger.error(
            "Error running command %s for runtime parameters!%s",
            command, os.linesep)
        raise errors.MisconfigurationError(
            "Error accessing loaded Apache parameters: {0}".format(
                command))
    # Small errors that do not impede
    if proc.returncode != 0:
        logger.warning("Error in checking parameter list: %s", stderr)
        raise errors.MisconfigurationError(
            "Apache is unable to check whether or not the module is "
            "loaded because Apache is misconfigured.")

    return stdout
Beispiel #2
0
    def _get_runtime_cfg(self):  # pylint: disable=no-self-use
        """Get runtime configuration info.

        :returns: stdout from DUMP_RUN_CFG

        """
        try:
            proc = subprocess.Popen(constants.os_constant("define_cmd"),
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
            stdout, stderr = proc.communicate()

        except (OSError, ValueError):
            logger.error("Error running command %s for runtime parameters!%s",
                         constants.os_constant("define_cmd"), os.linesep)
            raise errors.MisconfigurationError(
                "Error accessing loaded Apache parameters: %s",
                constants.os_constant("define_cmd"))
        # Small errors that do not impede
        if proc.returncode != 0:
            logger.warn("Error in checking parameter list: %s", stderr)
            raise errors.MisconfigurationError(
                "Apache is unable to check whether or not the module is "
                "loaded because Apache is misconfigured.")

        return stdout
Beispiel #3
0
def nginx_restart(nginx_ctl, nginx_conf="/etc/nginx.conf"):
    """Restarts the Nginx Server.

    .. todo:: Nginx restart is fatal if the configuration references
        non-existent SSL cert/key files. Remove references to /etc/letsencrypt
        before restart.

    :param str nginx_ctl: Path to the Nginx binary.

    """
    try:
        proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"],
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        stdout, stderr = proc.communicate()

        if proc.returncode != 0:
            # Maybe Nginx isn't running
            nginx_proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf],
                                          stdout=subprocess.PIPE,
                                          stderr=subprocess.PIPE)
            stdout, stderr = nginx_proc.communicate()

            if nginx_proc.returncode != 0:
                # Enter recovery routine...
                raise errors.MisconfigurationError(
                    "nginx restart failed:\n%s\n%s" % (stdout, stderr))

    except (OSError, ValueError):
        raise errors.MisconfigurationError("nginx restart failed")
    # Nginx can take a moment to recognize a newly added TLS SNI servername, so sleep
    # for a second. TODO: Check for expected servername and loop until it
    # appears or return an error if looping too long.
    time.sleep(1)
Beispiel #4
0
def _add_directive(block, directive, replace):
    """Adds or replaces a single directive in a config block.

    See _add_directives for more documentation.

    """
    directive = nginxparser.UnspacedList(directive)
    def is_whitespace_or_comment(directive):
        """Is this directive either a whitespace or comment directive?"""
        return len(directive) == 0 or directive[0] == '#'
    if is_whitespace_or_comment(directive):
        # whitespace or comment
        block.append(directive)
        return

    location = _find_location(block, directive[0])

    if replace:
        if location is not None:
            block[location] = directive
            comment_directive(block, location)
            return
    # Append directive. Fail if the name is not a repeatable directive name,
    # and there is already a copy of that directive with a different value
    # in the config file.

    # handle flat include files

    directive_name = directive[0]
    def can_append(loc, dir_name):
        """ Can we append this directive to the block? """
        return loc is None or (isinstance(dir_name, six.string_types)
            and dir_name in REPEATABLE_DIRECTIVES)

    err_fmt = 'tried to insert directive "{0}" but found conflicting "{1}".'

    # Give a better error message about the specific directive than Nginx's "fail to restart"
    if directive_name == INCLUDE:
        # in theory, we might want to do this recursively, but in practice, that's really not
        # necessary because we know what file we're talking about (and if we don't recurse, we
        # just give a worse error message)
        included_directives = _parse_ssl_options(directive[1])

        for included_directive in included_directives:
            included_dir_loc = _find_location(block, included_directive[0])
            included_dir_name = included_directive[0]
            if not is_whitespace_or_comment(included_directive) \
                and not can_append(included_dir_loc, included_dir_name):
                if block[included_dir_loc] != included_directive:
                    raise errors.MisconfigurationError(err_fmt.format(included_directive,
                        block[included_dir_loc]))
                else:
                    _comment_out_directive(block, included_dir_loc, directive[1])

    if can_append(location, directive_name):
        block.append(directive)
        comment_directive(block, len(block) - 1)
    elif block[location] != directive:
        raise errors.MisconfigurationError(err_fmt.format(directive, block[location]))
Beispiel #5
0
def _add_directive(block, directive, insert_at_top):
    if not isinstance(directive, nginxparser.UnspacedList):
        directive = nginxparser.UnspacedList(directive)
    if _is_whitespace_or_comment(directive):
        # whitespace or comment
        block.append(directive)
        return

    location = _find_location(block, directive[0])

    # Append or prepend directive. Fail if the name is not a repeatable directive name,
    # and there is already a copy of that directive with a different value
    # in the config file.

    # handle flat include files

    directive_name = directive[0]

    def can_append(loc, dir_name):
        """ Can we append this directive to the block? """
        return loc is None or (isinstance(dir_name, str)
                               and dir_name in REPEATABLE_DIRECTIVES)

    err_fmt = 'tried to insert directive "{0}" but found conflicting "{1}".'

    # Give a better error message about the specific directive than Nginx's "fail to restart"
    if directive_name == INCLUDE:
        # in theory, we might want to do this recursively, but in practice, that's really not
        # necessary because we know what file we're talking about (and if we don't recurse, we
        # just give a worse error message)
        included_directives = _parse_ssl_options(directive[1])

        for included_directive in included_directives:
            included_dir_loc = _find_location(block, included_directive[0])
            included_dir_name = included_directive[0]
            if (not _is_whitespace_or_comment(included_directive)
                    and not can_append(included_dir_loc, included_dir_name)):
                if block[included_dir_loc] != included_directive:
                    raise errors.MisconfigurationError(
                        err_fmt.format(included_directive,
                                       block[included_dir_loc]))
                _comment_out_directive(block, included_dir_loc, directive[1])

    if can_append(location, directive_name):
        if insert_at_top:
            # Add a newline so the comment doesn't comment
            # out existing directives
            block.insert(0, nginxparser.UnspacedList('\n'))
            block.insert(0, directive)
            comment_directive(block, 0)
        else:
            block.append(directive)
            comment_directive(block, len(block) - 1)
    elif block[location] != directive:
        raise errors.MisconfigurationError(
            err_fmt.format(directive, block[location]))
Beispiel #6
0
def nginx_restart(nginx_ctl, nginx_conf, sleep_duration):
    """Restarts the Nginx Server.

    .. todo:: Nginx restart is fatal if the configuration references
        non-existent SSL cert/key files. Remove references to /etc/letsencrypt
        before restart.

    :param str nginx_ctl: Path to the Nginx binary.
    :param str nginx_conf: Path to the Nginx configuration file.
    :param int sleep_duration: How long to sleep after sending the reload signal.

    """
    try:
        reload_output = u""  # type: Text
        with tempfile.TemporaryFile() as out:
            proc = subprocess.Popen(
                [nginx_ctl, "-c", nginx_conf, "-s", "reload"],
                env=util.env_no_snap_for_external_calls(),
                stdout=out,
                stderr=out)
            proc.communicate()
            out.seek(0)
            reload_output = out.read().decode("utf-8")

        if proc.returncode != 0:
            logger.debug("nginx reload failed:\n%s", reload_output)
            # Maybe Nginx isn't running - try start it
            # Write to temporary files instead of piping because of communication issues on Arch
            # https://github.com/certbot/certbot/issues/4324
            with tempfile.TemporaryFile() as out:
                nginx_proc = subprocess.Popen(
                    [nginx_ctl, "-c", nginx_conf],
                    stdout=out,
                    stderr=out,
                    env=util.env_no_snap_for_external_calls())
                nginx_proc.communicate()
                if nginx_proc.returncode != 0:
                    out.seek(0)
                    # Enter recovery routine...
                    raise errors.MisconfigurationError(
                        "nginx restart failed:\n%s" %
                        out.read().decode("utf-8"))

    except (OSError, ValueError):
        raise errors.MisconfigurationError("nginx restart failed")
    # Nginx can take a significant duration of time to fully apply a new config, depending
    # on size and contents (https://github.com/certbot/certbot/issues/7422). Lacking a way
    # to reliably identify when this process is complete, we provide the user with control
    # over how long Certbot will sleep after reloading the configuration.
    if sleep_duration > 0:
        time.sleep(sleep_duration)
Beispiel #7
0
    def _modify_server_directives(self, vhost, block_func):
        filename = vhost.filep
        try:
            result = self.parsed[filename]
            for index in vhost.path:
                result = result[index]
            if not isinstance(result, list) or len(result) != 2:
                raise errors.MisconfigurationError("Not a server block.")
            result = result[1]
            block_func(result)

            self._update_vhost_based_on_new_directives(vhost, result)
        except errors.MisconfigurationError as err:
            raise errors.MisconfigurationError("Problem in %s: %s" % (filename, str(err)))
Beispiel #8
0
    def perform(self, achalls):  # pylint: disable=missing-docstring
        renewer = self.config.verb == "renew"
        if any(
                util.already_listening(port, renewer)
                for port in self._necessary_ports):
            raise errors.MisconfigurationError(
                "At least one of the (possibly) required ports is "
                "already taken.")

        try:
            return self.perform2(achalls)
        except errors.StandaloneBindError as error:
            display = zope.component.getUtility(interfaces.IDisplay)

            if error.socket_error.errno == socket.errno.EACCES:
                display.notification(
                    "Could not bind TCP port {0} because you don't have "
                    "the appropriate permissions (for example, you "
                    "aren't running this program as "
                    "root).".format(error.port))
            elif error.socket_error.errno == socket.errno.EADDRINUSE:
                display.notification(
                    "Could not bind TCP port {0} because it is already in "
                    "use by another process on this system (such as a web "
                    "server). Please stop the program in question and then "
                    "try again.".format(error.port))
            else:
                raise  # XXX: How to handle unknown errors in binding?
Beispiel #9
0
def enhance(config, plugins):
    """Add security enhancements to existing configuration

    :param config: Configuration object
    :type config: interfaces.IConfig

    :param plugins: List of plugins
    :type plugins: `list` of `str`

    :returns: `None`
    :rtype: None

    """
    supported_enhancements = ["hsts", "redirect", "uir", "staple"]
    # Check that at least one enhancement was requested on command line
    oldstyle_enh = any(getattr(config, enh) for enh in supported_enhancements)
    if not enhancements.are_requested(config) and not oldstyle_enh:
        msg = ("Please specify one or more enhancement types to configure. To list "
               "the available enhancement types, run:\n\n%s --help enhance\n")
        logger.warning(msg, sys.argv[0])
        raise errors.MisconfigurationError("No enhancements requested, exiting.")

    try:
        installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "enhance")
    except errors.PluginSelectionError as e:
        return str(e)

    if not enhancements.are_supported(config, installer):
        raise errors.NotSupportedError("One ore more of the requested enhancements "
                                       "are not supported by the selected installer")

    certname_question = ("Which certificate would you like to use to enhance "
                         "your configuration?")
    config.certname = cert_manager.get_certnames(
        config, "enhance", allow_multiple=False,
        custom_prompt=certname_question)[0]
    cert_domains = cert_manager.domains_for_certname(config, config.certname)
    if config.noninteractive_mode:
        domains = cert_domains
    else:
        domain_question = ("Which domain names would you like to enable the "
                           "selected enhancements for?")
        domains = display_ops.choose_values(cert_domains, domain_question)
        if not domains:
            raise errors.Error("User cancelled the domain selection. No domains "
                               "defined, exiting.")

    lineage = cert_manager.lineage_for_certname(config, config.certname)
    if not config.chain_path:
        config.chain_path = lineage.chain_path
    if oldstyle_enh:
        le_client = _init_le_client(config, authenticator=None, installer=installer)
        le_client.enhance_config(domains, config.chain_path, redirect_default=False)
    if enhancements.are_requested(config):
        enhancements.enable(lineage, domains, installer, config)

    return None
Beispiel #10
0
 def parse(self, parse_this, add_spaces=False):
     """ Parses a list of string types into this object.
     If add_spaces is set, adds whitespace tokens between adjacent non-whitespace tokens."""
     if add_spaces:
         parse_this = _space_list(parse_this)
     if not isinstance(parse_this, list) or \
             any([not isinstance(elem, six.string_types) for elem in parse_this]):
         raise errors.MisconfigurationError("Sentence parsing expects a list of string types.")
     self._data = parse_this
Beispiel #11
0
 def parse(self, raw_list, add_spaces=False):
     """ Parses a list of string types into this object.
     If add_spaces is set, adds whitespace tokens between adjacent non-whitespace tokens."""
     if add_spaces:
         raw_list = _space_list(raw_list)
     if not isinstance(raw_list, list) or \
             any(not isinstance(elem, str) for elem in raw_list):
         raise errors.MisconfigurationError("Sentence parsing expects a list of string types.")
     self._data = raw_list
Beispiel #12
0
    def _mod_config(self, ll_addrs):
        """Modifies Nginx config to include challenge server blocks.

        :param list ll_addrs: list of lists of
            :class:`certbot_nginx.obj.Addr` to apply

        :raises .MisconfigurationError:
            Unable to find a suitable HTTP block in which to include
            authenticator hosts.

        """
        # Add the 'include' statement for the challenges if it doesn't exist
        # already in the main config
        included = False
        include_directive = ['\n', 'include', ' ', self.challenge_conf]
        root = self.configurator.parser.config_root

        bucket_directive = ['\n', 'server_names_hash_bucket_size', ' ', '128']

        main = self.configurator.parser.parsed[root]
        for line in main:
            if line[0] == ['http']:
                body = line[1]
                found_bucket = False
                posn = 0
                for inner_line in body:
                    if inner_line[0] == bucket_directive[1]:
                        if int(inner_line[1]) < int(bucket_directive[3]):
                            body[posn] = bucket_directive
                        found_bucket = True
                    posn += 1
                if not found_bucket:
                    body.insert(0, bucket_directive)
                if include_directive not in body:
                    body.insert(0, include_directive)
                included = True
                break
        if not included:
            raise errors.MisconfigurationError(
                'Certbot could not find an HTTP block to include '
                'TLS-SNI-01 challenges in %s.' % root)
        config = [
            self._make_server_block(pair[0], pair[1])
            for pair in six.moves.zip(self.achalls, ll_addrs)
        ]
        config = nginxparser.UnspacedList(config)

        self.configurator.reverter.register_file_creation(
            True, self.challenge_conf)

        with open(self.challenge_conf, "w") as new_conf:
            nginxparser.dump(config, new_conf)

        logger.debug("Generated server block:\n%s", str(config))
Beispiel #13
0
def _choose_parser(parent, list_):
    """ Choose a parser from type(parent).parsing_hooks, depending on whichever hook
    returns True first. """
    hooks = Parsable.parsing_hooks()
    if parent:
        hooks = type(parent).parsing_hooks()
    for type_ in hooks:
        if type_.should_parse(list_):
            return type_(parent)
    raise errors.MisconfigurationError(
        "None of the parsing hooks succeeded, so we don't know how to parse this set of lists.")
Beispiel #14
0
    def test(self):
        """Make sure the configuration is valid.

        :raises .MisconfigurationError: if the config is invalid
        """
        try:
            self._call(["check"])
        except subprocess.CalledProcessError as e:
            logger.debug("Could not check postfix configuration:\n%s", e)
            raise errors.MisconfigurationError(
                "Postfix failed internal configuration check.")
Beispiel #15
0
    def restart(self):  # type: ignore
        """Restart or refresh the server content.

        :raises .PluginError: when server cannot be restarted

        """
        print("*********** ReStart Initiated ********* ")
        try:
            if self.conf("service-name"):
                logger.debug("Restarting tomcat as a service")
                if platform.system() in ('Windows'):
                    value = subprocess.call('''sc stop ''' +
                                            self.conf("service-name"),
                                            shell=True)
                    time.sleep(10)
                    value = subprocess.call('''sc start ''' +
                                            self.conf("service-name"),
                                            shell=True)

                else:
                    value = subprocess.call('''service ''' +
                                            self.conf("service-name") +
                                            ''' stop ''',
                                            shell=True)
                    time.sleep(10)
                    value = subprocess.call('''service ''' +
                                            self.conf("service-name") +
                                            ''' start ''',
                                            shell=True)

            else:
                os.environ["CATALINA_HOME"] = (os.path.dirname(
                    os.path.dirname(filesystem.realpath(self.conf("ctl")))))
                os.environ["CATALINA_BASE"] = (os.path.dirname(
                    filesystem.realpath(self.conf("server-root"))))
                logger.debug("Restarting tomcat as a process")
                if platform.system() in ('Windows'):
                    value = subprocess.call(self.conf("ctl"), shell=True)
                    time.sleep(5)
                    with open(os.devnull, 'w') as FNULL:
                        value = subprocess.call(self.conf("ctl").replace(
                            "shutdown.bat", "startup.bat"),
                                                stdout=FNULL,
                                                stderr=FNULL,
                                                shell=True)
                else:
                    value = subprocess.call(self.conf("ctl"), shell=True)
                    time.sleep(5)
                    value = subprocess.call(self.conf("ctl").replace(
                        "shutdown.sh", "startup.sh"),
                                            shell=True)
        except (OSError, ValueError):
            raise errors.MisconfigurationError("Tomcat restart failed")
Beispiel #16
0
 def parse(self, raw_list, add_spaces=False):
     """ Parses a list of statements.
     Expects all elements in `raw_list` to be parseable by `type(self).parsing_hooks`,
     with an optional whitespace string at the last index of `raw_list`.
     """
     if not isinstance(raw_list, list):
         raise errors.MisconfigurationError("Statements parsing expects a list!")
     # If there's a trailing whitespace in the list of statements, keep track of it.
     if raw_list and isinstance(raw_list[-1], six.string_types) and raw_list[-1].isspace():
         self._trailing_whitespace = raw_list[-1]
         raw_list = raw_list[:-1]
     self._data = [parse_raw(elem, self, add_spaces) for elem in raw_list]
Beispiel #17
0
 def parse(self, parse_this, add_spaces=False):
     """ Parses a list of statements.
     Expects all elements in `parse_this` to be parseable by `type(self).parsing_hooks`,
     with an optional whitespace string at the last index of `parse_this`.
     """
     if not isinstance(parse_this, list):
         raise errors.MisconfigurationError("Statements parsing expects a list!")
     # If there's a trailing whitespace in the list of statements, keep track of it.
     if len(parse_this) > 0 and isinstance(parse_this[-1], six.string_types) \
                            and parse_this[-1].isspace():
         self._trailing_whitespace = parse_this[-1]
         parse_this = parse_this[:-1]
     self._data = [parse_raw(elem, self, add_spaces) for elem in parse_this]
Beispiel #18
0
def _add_directive(block, directive, replace):
    """Adds or replaces a single directive in a config block.

    See _add_directives for more documentation.

    """
    directive = nginxparser.UnspacedList(directive)
    if len(directive) == 0 or directive[0] == '#':
        # whitespace or comment
        block.append(directive)
        return

    # Find the index of a config line where the name of the directive matches
    # the name of the directive we want to add. If no line exists, use None.
    location = next((index for index, line in enumerate(block)
                     if line and line[0] == directive[0]), None)
    if replace:
        if location is None:
            raise errors.MisconfigurationError(
                'expected directive for {0} in the Nginx '
                'config but did not find it.'.format(directive[0]))
        block[location] = directive
        _comment_directive(block, location)
    else:
        # Append directive. Fail if the name is not a repeatable directive name,
        # and there is already a copy of that directive with a different value
        # in the config file.
        directive_name = directive[0]
        directive_value = directive[1]
        if location is None or (isinstance(directive_name, str)
                                and directive_name in REPEATABLE_DIRECTIVES):
            block.append(directive)
            _comment_directive(block, len(block) - 1)
        elif block[location][1] != directive_value:
            raise errors.MisconfigurationError(
                'tried to insert directive "{0}" but found '
                'conflicting "{1}".'.format(directive, block[location]))
Beispiel #19
0
    def choose_vhosts(self, target_name, create_if_no_match=False):
        """Chooses a virtual host based on the given domain name.

        .. note:: This makes the vhost SSL-enabled if it isn't already. Follows
            Nginx's server block selection rules preferring blocks that are
            already SSL.

        .. todo:: This should maybe return list if no obvious answer
            is presented.

        .. todo:: The special name "$hostname" corresponds to the machine's
            hostname. Currently we just ignore this.

        :param str target_name: domain name
        :param bool create_if_no_match: If we should create a new vhost from default
            when there is no match found. If we can't choose a default, raise a
            MisconfigurationError.

        :returns: ssl vhosts associated with name
        :rtype: list of :class:`~certbot_nginx.obj.VirtualHost`

        """
        if util.is_wildcard_domain(target_name):
            # Ask user which VHosts to support.
            vhosts = self._choose_vhosts_wildcard(target_name, prefer_ssl=True)
        else:
            vhosts = self._choose_vhost_single(target_name)
        if not vhosts:
            if create_if_no_match:
                # result will not be [None] because it errors on failure
                vhosts = [
                    self._vhost_from_duplicated_default(
                        target_name, True, str(self.config.https_port))
                ]
            else:
                # No matches. Raise a misconfiguration error.
                raise errors.MisconfigurationError((
                    "Cannot find a VirtualHost matching domain %s. "
                    "In order for Certbot to correctly perform the challenge "
                    "please add a corresponding server_name directive to your "
                    "nginx configuration for every domain on your certificate: "
                    "https://nginx.org/en/docs/http/server_names.html") %
                                                   (target_name))
        # Note: if we are enhancing with ocsp, vhost should already be ssl.
        for vhost in vhosts:
            if not vhost.ssl:
                self._make_server_ssl(vhost)

        return vhosts