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
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
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)
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]))
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]))
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)
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)))
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?
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
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
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
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))
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.")
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.")
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")
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]
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]
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]))
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