Skip to main content

Source code file content

Revision: 3248 (of 3248)

17699319 we could make downgrade easier in the case of incorporated packages
» Project Revision History

» Checkout URL

pkg-gate / src / depot.py

Size: 46902 bytes, 1 line
#!/usr/bin/python2.7
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
#

from __future__ import print_function

# pkg.depotd - package repository daemon

# XXX The prototype pkg.depotd combines both the version management server that
# answers to pkgsend(1) sessions and the HTTP file server that answers to the
# various GET operations that a pkg(1) client makes.  This split is expected to
# be made more explicit, by constraining the pkg(1) operations such that they
# can be served as a typical HTTP/HTTPS session.  Thus, pkg.depotd will reduce
# to a special purpose HTTP/HTTPS server explicitly for the version management
# operations, and must manipulate the various state files--catalogs, in
# particular--such that the pkg(1) pull client can operately accurately with
# only a basic HTTP/HTTPS server in place.

# XXX Although we pushed the evaluation of next-version, etc. to the pull
# client, we should probably provide a query API to do same on the server, for
# dumb clients (like a notification service).

# The default path for static and other web content.
CONTENT_PATH_DEFAULT = "/usr/share/lib/pkg"
# cherrypy has a max_request_body_size parameter that determines whether the
# server should abort requests with REQUEST_ENTITY_TOO_LARGE when the request
# body is larger than the specified size (in bytes).  The maximum size supported
# by cherrypy is 2048 * 1024 * 1024 - 1 (just short of 2048MB), but the default
# here is purposefully conservative.
MAX_REQUEST_BODY_SIZE = 512 * 1024 * 1024
# The default host/port(s) to serve data from.
HOST_DEFAULT = "0.0.0.0"
PORT_DEFAULT = 80
SSL_PORT_DEFAULT = 443
# The minimum number of threads allowed.
THREADS_MIN = 1
# The default number of threads to start.
THREADS_DEFAULT = 60
# The maximum number of threads that can be started.
THREADS_MAX = 5000
# The default server socket timeout in seconds. We want this to be longer than
# the normal default of 10 seconds to accommodate clients with poor quality
# connections.
SOCKET_TIMEOUT_DEFAULT = 60

import getopt
import gettext
import locale
import logging
import os
import os.path
import OpenSSL.crypto as crypto
import subprocess
import sys
import tempfile

from imp import reload
from six.moves.urllib.parse import urlparse, urlunparse

try:
        import cherrypy
        version = cherrypy.__version__.split('.')
        # comparison requires same type, therefore list conversion is needed
        if list(map(int, version)) < [3, 1, 0]:
                raise ImportError
        elif list(map(int, version)) >= [3, 2, 0]:
                raise ImportError
except ImportError:
        print("""cherrypy 3.1.0 or greater (but less than """
            """3.2.0) is required to use this program.""", file=sys.stderr)
        sys.exit(2)

import cherrypy.process.servers
from cherrypy.process.plugins import Daemonizer

from pkg.misc import msg, emsg, setlocale
from pkg.client.debugvalues import DebugValues

import pkg
import pkg.client.api_errors as api_errors
import pkg.config as cfg
import pkg.portable.util as os_util
import pkg.search_errors as search_errors
import pkg.server.depot as ds
import pkg.server.depotresponse as dr
import pkg.server.repository as sr


class LogSink(object):
        """This is a dummy object that we can use to discard log entries
        without relying on non-portable interfaces such as /dev/null."""

        def write(self, *args, **kwargs):
                """Discard the bits."""
                pass

        def flush(self, *args, **kwargs):
                """Discard the bits."""
                pass


def usage(text=None, retcode=2, full=False):
        """Optionally emit a usage message and then exit using the specified
        exit code."""

        if text:
                emsg(text)

        if not full:
                # The full usage message isn't desired.
                emsg(_("Try `pkg.depotd --help or -?' for more "
                    "information."))
                sys.exit(retcode)

        print("""\
Usage: /usr/lib/pkg.depotd [-a address] [-d inst_root] [-p port] [-s threads]
           [-t socket_timeout] [--cfg] [--content-root]
           [--disable-ops op[/1][,...]] [--debug feature_list]
           [--image-root dir] [--log-access dest] [--log-errors dest]
           [--mirror] [--nasty] [--nasty-sleep] [--proxy-base url]
           [--readonly] [--ssl-cert-file] [--ssl-dialog] [--ssl-key-file]
           [--sort-file-max-size size] [--writable-root dir]

        -a address      The IP address on which to listen for connections.  The
                        default value is 0.0.0.0 (INADDR_ANY) which will listen
                        on all active interfaces.  To listen on all active IPv6
                        interfaces, use '::'.
        -d inst_root    The file system path at which the server should find its
                        repository data.  Required unless PKG_REPO has been set
                        in the environment.
        -p port         The port number on which the instance should listen for
                        incoming package requests.  The default value is 80 if
                        ssl certificate and key information has not been
                        provided; otherwise, the default value is 443.
        -s threads      The number of threads that will be started to serve
                        requests.  The default value is 10.
        -t timeout      The maximum number of seconds the server should wait for
                        a response from a client before closing a connection.
                        The default value is 60.
        --cfg           The pathname of the file to use when reading and writing
                        depot configuration data, or a fully qualified service
                        fault management resource identifier (FMRI) of the SMF
                        service or instance to read configuration data from.
        --content-root  The file system path to the directory containing the
                        the static and other web content used by the depot's
                        browser user interface.  The default value is
                        '/usr/share/lib/pkg'.
        --disable-ops   A comma separated list of operations that the depot
                        should not configure.  If, for example, you wanted
                        to omit loading search v1, 'search/1' should be
                        provided as an argument, or to disable all search
                        operations, simply 'search'.
        --debug         The name of a debug feature to enable; or a whitespace
                        or comma separated list of features to enable.
                        Possible values are: headers, hash=sha1+sha256,
                        hash=sha256, hash=sha1+sha512_256, hash=sha512_256
        --image-root    The path to the image whose file information will be
                        used as a cache for file data.
        --log-access    The destination for any access related information
                        logged by the depot process.  Possible values are:
                        stderr, stdout, none, or an absolute pathname.  The
                        default value is stdout if stdout is a tty; otherwise
                        the default value is none.
        --log-errors    The destination for any errors or other information
                        logged by the depot process.  Possible values are:
                        stderr, stdout, none, or an absolute pathname.  The
                        default value is stderr.
        --mirror        Package mirror mode; publishing and metadata operations
                        disallowed.  Cannot be used with --readonly or
                        --rebuild.
        --nasty         Instruct the server to misbehave.  At random intervals
                        it will time-out, send bad responses, hang up on
                        clients, and generally be hostile.  The option
                        takes a value (1 to 100) for how nasty the server
                        should be.
        --nasty-sleep   In nasty mode (see --nasty), how many seconds to
                        randomly sleep when a random sleep occurs.
        --proxy-base    The url to use as the base for generating internal
                        redirects and content.
        --readonly      Read-only operation; modifying operations disallowed.
                        Cannot be used with --mirror or --rebuild.
        --ssl-cert-file The absolute pathname to a PEM-encoded Certificate file.
                        This option must be used with --ssl-key-file.  Usage of
                        this option will cause the depot to only respond to SSL
                        requests on the provided port.
        --ssl-dialog    Specifies what method should be used to obtain the
                        passphrase needed to decrypt the file specified by
                        --ssl-key-file.  Supported values are: builtin,
                        exec:/path/to/program, smf, or an SMF FMRI.  The
                        default value is builtin.  If smf is specified, an
                        SMF FMRI must be provided using the --cfg option.
        --ssl-key-file  The absolute pathname to a PEM-encoded Private Key file.
                        This option must be used with --ssl-cert-file.  Usage of
                        this option will cause the depot to only respond to SSL
                        requests on the provided port.
        --sort-file-max-size
                        The maximum size of the indexer sort file. Used to
                        limit the amount of RAM the depot uses for indexing,
                        or increase it for speed.
        --writable-root The path to a directory to which the program has write
                        access.  Used with --readonly to allow server to
                        create needed files, such as search indices, without
                        needing write access to the package information.
Options:
        --help or -?

Environment:
        PKG_REPO                Used as default inst_root if -d not provided.
        PKG_DEPOT_CONTENT       Used as default content_root if --content-root
                                not provided.""")
        sys.exit(retcode)

class OptionError(Exception):
        """Option exception. """

        def __init__(self, *args):
                Exception.__init__(self, *args)

if __name__ == "__main__":

        setlocale(locale.LC_ALL, "")
        gettext.install("pkg", "/usr/share/locale",
            codeset=locale.getpreferredencoding())

        add_content = False
        exit_ready = False
        rebuild = False
        reindex = False
        nasty = False

        # Track initial configuration values.
        ivalues = { "pkg": {}, "nasty": {} }
        if "PKG_REPO" in os.environ:
                ivalues["pkg"]["inst_root"] = os.environ["PKG_REPO"]

        try:
                content_root = os.environ["PKG_DEPOT_CONTENT"]
                ivalues["pkg"]["content_root"] = content_root
        except KeyError:
                try:
                        content_root = os.path.join(os.environ['PKG_HOME'],
                            'share/lib/pkg')
                        ivalues["pkg"]["content_root"] = content_root
                except KeyError:
                        pass

        opt = None
        addresses = set()
        debug_features = []
        disable_ops = []
        repo_props = {}
        socket_path = ""
        user_cfg = None
        try:
                long_opts = ["add-content", "cfg=", "cfg-file=",
                    "content-root=", "debug=", "disable-ops=", "exit-ready",
                    "help", "image-root=", "log-access=", "log-errors=",
                    "llmirror", "mirror", "nasty=", "nasty-sleep=",
                    "proxy-base=", "readonly", "rebuild", "refresh-index",
                    "set-property=", "ssl-cert-file=", "ssl-dialog=",
                    "ssl-key-file=", "sort-file-max-size=", "writable-root="]

                opts, pargs = getopt.getopt(sys.argv[1:], "a:d:np:s:t:?",
                    long_opts)

                show_usage = False
                for opt, arg in opts:
                        if opt == "-a":
                                addresses.add(arg)
                        elif opt == "-n":
                                sys.exit(0)
                        elif opt == "-d":
                                ivalues["pkg"]["inst_root"] = arg
                        elif opt == "-p":
                                ivalues["pkg"]["port"] = arg
                        elif opt == "-s":
                                threads = int(arg)
                                if threads < THREADS_MIN:
                                        raise OptionError(
                                            "minimum value is {0:d}".format(
                                            THREADS_MIN))
                                if threads > THREADS_MAX:
                                        raise OptionError(
                                            "maximum value is {0:d}".format(
                                            THREADS_MAX))
                                ivalues["pkg"]["threads"] = threads
                        elif opt == "-t":
                                ivalues["pkg"]["socket_timeout"] = arg
                        elif opt == "--add-content":
                                add_content = True
                        elif opt == "--cfg":
                                user_cfg  = arg
                        elif opt == "--cfg-file":
                                ivalues["pkg"]["cfg_file"] = arg
                        elif opt == "--content-root":
                                ivalues["pkg"]["content_root"] = arg
                        elif opt == "--debug":
                                if arg is None or arg == "":
                                        continue

                                # A list of features can be specified using a
                                # "," or any whitespace character as separators.
                                if "," in arg:
                                        features = arg.split(",")
                                else:
                                        features = arg.split()
                                debug_features.extend(features)

                                # We also allow key=value debug flags, which
                                # get set in pkg.client.debugvalues
                                for feature in features:
                                        try:
                                                key, val = feature.split("=", 1)
                                                DebugValues.set_value(key, val)
                                        except (AttributeError, ValueError):
                                                pass

                        elif opt == "--disable-ops":
                                if arg is None or arg == "":
                                        raise OptionError(
                                            "An argument must be specified.")

                                disableops = arg.split(",")
                                for s in disableops:
                                        if "/" in s:
                                                op, ver = s.rsplit("/", 1)
                                        else:
                                                op = s
                                                ver = "*"

                                        if op not in \
                                            ds.DepotHTTP.REPO_OPS_DEFAULT:
                                                raise OptionError(
                                                    "Invalid operation "
                                                    "'{0}'.".format(s))
                                        disable_ops.append(s)
                        elif opt == "--exit-ready":
                                exit_ready = True
                        elif opt == "--image-root":
                                ivalues["pkg"]["image_root"] = arg
                        elif opt.startswith("--log-"):
                                prop = "log_{0}".format(opt.lstrip("--log-"))
                                ivalues["pkg"][prop] = arg
                        elif opt in ("--help", "-?"):
                                show_usage = True
                        elif opt == "--mirror":
                                ivalues["pkg"]["mirror"] = True
                        elif opt == "--llmirror":
                                ivalues["pkg"]["mirror"] = True
                                ivalues["pkg"]["ll_mirror"] = True
                                ivalues["pkg"]["readonly"] = True
                        elif opt == "--nasty":
                                # ValueError is caught by caller.
                                nasty_value = int(arg)
                                if (nasty_value > 100 or nasty_value < 1):
                                        raise OptionError("Invalid value "
                                            "for nasty option.\n Please "
                                            "choose a value between 1 and 100.")
                                nasty = True
                                ivalues["nasty"]["nasty_level"] = nasty_value
                        elif opt == "--nasty-sleep":
                                # ValueError is caught by caller.
                                sleep_value = int(arg)
                                ivalues["nasty"]["nasty_sleep"] = sleep_value
                        elif opt == "--proxy-base":
                                # Attempt to decompose the url provided into
                                # its base parts.  This is done so we can
                                # remove any scheme information since we
                                # don't need it.
                                scheme, netloc, path, params, query, \
                                    fragment = urlparse(arg,
                                    "http", allow_fragments=0)

                                if not netloc:
                                        raise OptionError("Unable to "
                                            "determine the hostname from "
                                            "the provided URL; please use a "
                                            "fully qualified URL.")

                                scheme = scheme.lower()
                                if scheme not in ("http", "https"):
                                        raise OptionError("Invalid URL; http "
                                            "and https are the only supported "
                                            "schemes.")

                                # Rebuild the url with the sanitized components.
                                ivalues["pkg"]["proxy_base"] = \
                                    urlunparse((scheme, netloc, path,
                                    params, query, fragment))
                        elif opt == "--readonly":
                                ivalues["pkg"]["readonly"] = True
                        elif opt == "--rebuild":
                                rebuild = True
                        elif opt == "--refresh-index":
                                # Note: This argument is for internal use
                                # only.
                                #
                                # This flag is purposefully omitted in usage.
                                # The supported way to forcefully reindex is to
                                # kill any pkg.depot using that directory,
                                # remove the index directory, and restart the
                                # pkg.depot process. The index will be rebuilt
                                # automatically on startup.
                                reindex = True
                                exit_ready = True
                        elif opt == "--set-property":
                                try:
                                        prop, p_value = arg.split("=", 1)
                                        p_sec, p_name = prop.split(".", 1)
                                except ValueError:
                                        usage(_("property arguments must be of "
                                            "the form '<section.property>="
                                            "<value>'."))
                                repo_props.setdefault(p_sec, {})
                                repo_props[p_sec][p_name] = p_value
                        elif opt == "--ssl-cert-file":
                                if arg == "none" or arg == "":
                                        # Assume this is an override to clear
                                        # the value.
                                        arg = ""
                                elif not os.path.isabs(arg):
                                        raise OptionError("The path to "
                                           "the Certificate file must be "
                                           "absolute.")
                                elif not os.path.exists(arg):
                                        raise OptionError("The specified "
                                            "file does not exist.")
                                elif not os.path.isfile(arg):
                                        raise OptionError("The specified "
                                            "pathname is not a file.")
                                ivalues["pkg"]["ssl_cert_file"] = arg
                        elif opt == "--ssl-key-file":
                                if arg == "none" or arg == "":
                                        # Assume this is an override to clear
                                        # the value.
                                        arg = ""
                                elif not os.path.isabs(arg):
                                        raise OptionError("The path to "
                                           "the Private Key file must be "
                                           "absolute.")
                                elif not os.path.exists(arg):
                                        raise OptionError("The specified "
                                            "file does not exist.")
                                elif not os.path.isfile(arg):
                                        raise OptionError("The specified "
                                            "pathname is not a file.")
                                ivalues["pkg"]["ssl_key_file"] = arg
                        elif opt == "--ssl-dialog":
                                if arg != "builtin" and \
                                    arg != "smf" and not \
                                    arg.startswith("exec:/") and not \
                                    arg.startswith("svc:"):
                                        raise OptionError("Invalid value "
                                            "specified.  Expected: builtin, "
                                            "exec:/path/to/program, smf, or "
                                            "an SMF FMRI.")

                                if arg.startswith("exec:"):
                                        if os_util.get_canonical_os_type() != \
                                          "unix":
                                                # Don't allow a somewhat
                                                # insecure authentication method
                                                # on some platforms.
                                                raise OptionError("exec is "
                                                    "not a supported dialog "
                                                    "type for this operating "
                                                    "system.")

                                        f = os.path.abspath(arg.split(
                                            "exec:")[1])
                                        if not os.path.isfile(f):
                                                raise OptionError("Invalid "
                                                    "file path specified for "
                                                    "exec.")
                                ivalues["pkg"]["ssl_dialog"] = arg
                        elif opt == "--sort-file-max-size":
                                ivalues["pkg"]["sort_file_max_size"] = arg
                        elif opt == "--writable-root":
                                ivalues["pkg"]["writable_root"] = arg

                # Set accumulated values.
                if debug_features:
                        ivalues["pkg"]["debug"] = debug_features
                if disable_ops:
                        ivalues["pkg"]["disable_ops"] = disable_ops
                if addresses:
                        ivalues["pkg"]["address"] = list(addresses)

                if DebugValues:
                        reload(pkg.digest)

                # Build configuration object.
                dconf = ds.DepotConfig(target=user_cfg, overrides=ivalues)
        except getopt.GetoptError as _e:
                usage("pkg.depotd: {0}".format(_e.msg))
        except api_errors.ApiException as _e:
                usage("pkg.depotd: {0}".format(str(_e)))
        except OptionError as _e:
                usage("pkg.depotd: option: {0} -- {1}".format(opt, _e))
        except (ArithmeticError, ValueError):
                usage("pkg.depotd: illegal option value: {0} specified " \
                    "for option: {1}".format(arg, opt))

        if show_usage:
                usage(retcode=0, full=True)

        if not dconf.get_property("pkg", "log_errors"):
                dconf.set_property("pkg", "log_errors", "stderr")

        # If stdout is a tty, then send access output there by default instead
        # of discarding it.
        if not dconf.get_property("pkg", "log_access"):
                if os.isatty(sys.stdout.fileno()):
                        dconf.set_property("pkg", "log_access", "stdout")
                else:
                        dconf.set_property("pkg", "log_access", "none")

        # Check for invalid option combinations.
        image_root = dconf.get_property("pkg", "image_root")
        inst_root = dconf.get_property("pkg", "inst_root")
        mirror = dconf.get_property("pkg", "mirror")
        ll_mirror = dconf.get_property("pkg", "ll_mirror")
        readonly = dconf.get_property("pkg", "readonly")
        writable_root = dconf.get_property("pkg", "writable_root")
        if rebuild and add_content:
                usage("--add-content cannot be used with --rebuild")
        if rebuild and reindex:
                usage("--refresh-index cannot be used with --rebuild")
        if (rebuild or add_content) and (readonly or mirror):
                usage("--readonly and --mirror cannot be used with --rebuild "
                    "or --add-content")
        if reindex and mirror:
                usage("--mirror cannot be used with --refresh-index")
        if reindex and readonly and not writable_root:
                usage("--readonly can only be used with --refresh-index if "
                    "--writable-root is used")
        if image_root and not ll_mirror:
                usage("--image-root can only be used with --llmirror.")
        if image_root and writable_root:
                usage("--image_root and --writable-root cannot be used "
                    "together.")
        if image_root and inst_root:
                usage("--image-root and -d cannot be used together.")

        # If the image format changes this may need to be reexamined.
        if image_root:
                inst_root = os.path.join(image_root, "var", "pkg")

        # Set any values using defaults if they weren't provided.

        # Only use the first value for now; multiple bind addresses may be
        # supported later.
        address = dconf.get_property("pkg", "address")
        if address:
                address = address[0]
        elif not address:
                dconf.set_property("pkg", "address", [HOST_DEFAULT])
                address = dconf.get_property("pkg", "address")[0]

        if not inst_root:
                usage("Either PKG_REPO or -d must be provided")

        content_root = dconf.get_property("pkg", "content_root")
        if not content_root:
                dconf.set_property("pkg", "content_root", CONTENT_PATH_DEFAULT)
                content_root = dconf.get_property("pkg", "content_root")

        port = dconf.get_property("pkg", "port")
        ssl_cert_file = dconf.get_property("pkg", "ssl_cert_file")
        ssl_key_file = dconf.get_property("pkg", "ssl_key_file")
        if (ssl_cert_file and not ssl_key_file) or (ssl_key_file and not
            ssl_cert_file):
                usage("The --ssl-cert-file and --ssl-key-file options must "
                    "must both be provided when using either option.")
        elif not port:
                if ssl_cert_file and ssl_key_file:
                        dconf.set_property("pkg", "port", SSL_PORT_DEFAULT)
                else:
                        dconf.set_property("pkg", "port", PORT_DEFAULT)
                port = dconf.get_property("pkg", "port")

        socket_timeout = dconf.get_property("pkg", "socket_timeout")
        if not socket_timeout:
                dconf.set_property("pkg", "socket_timeout",
                    SOCKET_TIMEOUT_DEFAULT)
                socket_timeout = dconf.get_property("pkg", "socket_timeout")

        threads = dconf.get_property("pkg", "threads")
        if not threads:
                dconf.set_property("pkg", "threads", THREADS_DEFAULT)
                threads = dconf.get_property("pkg", "threads")

        # If the program is going to reindex, the port is irrelevant since
        # the program will not bind to a port.
        if not exit_ready:
                try:
                        cherrypy.process.servers.check_port(address, port)
                except Exception as e:
                        emsg("pkg.depotd: unable to bind to the specified "
                            "port: {0:d}. Reason: {1}".format(port, e))
                        sys.exit(1)
        else:
                # Not applicable if we're not going to serve content
                dconf.set_property("pkg", "content_root", "")

        # Any relative paths should be made absolute using pkg_root.  'pkg_root'
        # is a special property that was added to enable internal deployment of
        # multiple disparate versions of the pkg.depotd software.
        pkg_root = dconf.get_property("pkg", "pkg_root")

        repo_config_file = dconf.get_property("pkg", "cfg_file")
        if repo_config_file and not os.path.isabs(repo_config_file):
                repo_config_file = os.path.join(pkg_root, repo_config_file)

        if content_root and not os.path.isabs(content_root):
                content_root = os.path.join(pkg_root, content_root)

        if inst_root and not os.path.isabs(inst_root):
                inst_root = os.path.join(pkg_root, inst_root)

        if ssl_cert_file:
                if ssl_cert_file == "none":
                        ssl_cert_file = None
                elif not os.path.isabs(ssl_cert_file):
                        ssl_cert_file = os.path.join(pkg_root, ssl_cert_file)

        if ssl_key_file:
                if ssl_key_file == "none":
                        ssl_key_file = None
                elif not os.path.isabs(ssl_key_file):
                        ssl_key_file = os.path.join(pkg_root, ssl_key_file)

        if writable_root and not os.path.isabs(writable_root):
                writable_root = os.path.join(pkg_root, writable_root)

        # Setup SSL if requested.
        key_data = None
        ssl_dialog = dconf.get_property("pkg", "ssl_dialog")
        if not exit_ready and ssl_cert_file and ssl_key_file and \
            ssl_dialog != "builtin":
                cmdline = None
                def get_ssl_passphrase(*ignored):
                        p = None
                        try:
                                p = subprocess.Popen(cmdline, shell=True,
                                        stdout=subprocess.PIPE,
                                        stderr=None)
                                p.wait()
                        except Exception as __e:
                                emsg("pkg.depotd: an error occurred while "
                                    "executing [{0}]; unable to obtain the "
                                    "passphrase needed to decrypt the SSL "
                                    "private key file: {1}".format(cmdline,
                                    __e))
                                sys.exit(1)
                        return p.stdout.read().strip("\n")

                if ssl_dialog.startswith("exec:"):
                        exec_path = ssl_dialog.split("exec:")[1]
                        if not os.path.isabs(exec_path):
                                exec_path = os.path.join(pkg_root, exec_path)
                        cmdline = "{0} {1} {2:d}".format(exec_path, "''", port)
                elif ssl_dialog == "smf" or ssl_dialog.startswith("svc:"):
                        if ssl_dialog == "smf":
                                # Assume the configuration target was an SMF
                                # FMRI and let svcprop fail with an error if
                                # it wasn't.
                                svc_fmri = dconf.target
                        else:
                                svc_fmri = ssl_dialog
                        cmdline = "/usr/bin/svcprop -p " \
                            "pkg_secure/ssl_key_passphrase {0}".format(svc_fmri)

                # The key file requires decryption, but the user has requested
                # exec-based authentication, so it will have to be decoded first
                # to an un-named temporary file.
                try:
                        with open(ssl_key_file, "rb") as key_file:
                                pkey = crypto.load_privatekey(
                                    crypto.FILETYPE_PEM, key_file.read(),
                                    get_ssl_passphrase)

                        key_data = tempfile.TemporaryFile()
                        key_data.write(crypto.dump_privatekey(
                            crypto.FILETYPE_PEM, pkey))
                        key_data.seek(0)
                except EnvironmentError as _e:
                        emsg("pkg.depotd: unable to read the SSL private key "
                            "file: {0}".format(_e))
                        sys.exit(1)
                except crypto.Error as _e:
                        emsg("pkg.depotd: authentication or cryptography "
                            "failure while attempting to decode\nthe SSL "
                            "private key file: {0}".format(_e))
                        sys.exit(1)
                else:
                        # Redirect the server to the decrypted key file.
                        ssl_key_file = "/dev/fd/{0:d}".format(key_data.fileno())

        # Setup our global configuration.
        gconf = {
            "checker.on": True,
            "environment": "production",
            "log.screen": False,
            "server.max_request_body_size": MAX_REQUEST_BODY_SIZE,
            "server.shutdown_timeout": 0,
            "server.socket_host": address,
            "server.socket_port": port,
            "server.socket_timeout": socket_timeout,
            "server.ssl_certificate": ssl_cert_file,
            "server.ssl_private_key": ssl_key_file,
            "server.thread_pool": threads,
            "tools.log_headers.on": True,
            "tools.encode.on": True
        }

        if "headers" in dconf.get_property("pkg", "debug"):
                # Despite its name, this only logs headers when there is an
                # error; it's redundant with the debug feature enabled.
                gconf["tools.log_headers.on"] = False

                # Causes the headers of every request to be logged to the error
                # log; even if an exception occurs.
                gconf["tools.log_headers_always.on"] = True
                cherrypy.tools.log_headers_always = cherrypy.Tool(
                    "on_start_resource",
                    cherrypy.lib.cptools.log_request_headers)

        log_cfg = {
            "access": dconf.get_property("pkg", "log_access"),
            "errors": dconf.get_property("pkg", "log_errors")
        }

        # If stdin is not a tty and the pkgdepot controller isn't being used,
        # then assume process will be daemonized and redirect output.
        if not os.environ.get("PKGDEPOT_CONTROLLER") and \
            not os.isatty(sys.stdin.fileno()):
                # Ensure log handlers are setup to use the file descriptors for
                # stdout and stderr as the Daemonizer (used for test suite and
                # SMF service) requires this.
                if log_cfg["access"] == "stdout":
                        log_cfg["access"] = "/dev/fd/{0:d}".format(
                            sys.stdout.fileno())
                elif log_cfg["access"] == "stderr":
                        log_cfg["access"] = "/dev/fd/{0:d}".format(
                            sys.stderr.fileno())
                elif log_cfg["access"] == "none":
                        log_cfg["access"] = "/dev/null"

                if log_cfg["errors"] == "stderr":
                        log_cfg["errors"] = "/dev/fd/{0:d}".format(
                            sys.stderr.fileno())
                elif log_cfg["errors"] == "stdout":
                        log_cfg["errors"] = "/dev/fd/{0:d}".format(
                            sys.stdout.fileno())
                elif log_cfg["errors"] == "none":
                        log_cfg["errors"] = "/dev/null"

        log_type_map = {
            "errors": {
                "param": "log.error_file",
                "attr": "error_log"
            },
            "access": {
                "param": "log.access_file",
                "attr": "access_log"
            }
        }

        for log_type in log_type_map:
                dest = log_cfg[log_type]
                if dest in ("stdout", "stderr", "none"):
                        if dest == "none":
                                h = logging.StreamHandler(LogSink())
                        else:
                                h = logging.StreamHandler(eval("sys.{0}".format(
                                    dest)))

                        h.setLevel(logging.DEBUG)
                        h.setFormatter(cherrypy._cplogging.logfmt)
                        log_obj = eval("cherrypy.log.{0}".format(
                            log_type_map[log_type]["attr"]))
                        log_obj.addHandler(h)
                        # Since we've replaced cherrypy's log handler with our
                        # own, we don't want the output directed to a file.
                        dest = ""
                elif dest:
                        if not os.path.isabs(dest):
                                dest = os.path.join(pkg_root, dest)
                gconf[log_type_map[log_type]["param"]] = dest

        cherrypy.config.update(gconf)

        # Now that our logging, etc. has been setup, it's safe to perform any
        # remaining preparation.

        # Initialize repository state.
        if not readonly:
                # Not readonly, so assume a new repository should be created.
                try:
                        sr.repository_create(inst_root, properties=repo_props)
                except sr.RepositoryExistsError:
                        # Already exists, nothing to do.
                        pass
                except (api_errors.ApiException, sr.RepositoryError) as _e:
                        emsg("pkg.depotd: {0}".format(_e))
                        sys.exit(1)

        try:
                sort_file_max_size = dconf.get_property("pkg",
                    "sort_file_max_size")

                repo = sr.Repository(cfgpathname=repo_config_file,
                    log_obj=cherrypy, mirror=mirror, properties=repo_props,
                    read_only=readonly, root=inst_root,
                    sort_file_max_size=sort_file_max_size,
                    writable_root=writable_root)
        except (RuntimeError, sr.RepositoryError) as _e:
                emsg("pkg.depotd: {0}".format(_e))
                sys.exit(1)
        except search_errors.IndexingException as _e:
                emsg("pkg.depotd: {0}".format(str(_e)), "INDEX")
                sys.exit(1)
        except api_errors.ApiException as _e:
                emsg("pkg.depotd: {0}".format(str(_e)))
                sys.exit(1)

        if not rebuild and not add_content and not repo.mirror and \
            not (repo.read_only and not repo.writable_root):
                # Automatically update search indexes on startup if not already
                # told to, and not in readonly/mirror mode.
                reindex = True

        if reindex:
                try:
                        # Only execute a index refresh here if --exit-ready was
                        # requested; it will be handled later in the setup
                        # process for other cases.
                        if repo.root and exit_ready:
                                repo.refresh_index()
                except (sr.RepositoryError, search_errors.IndexingException,
                    api_errors.ApiException) as e:
                        emsg(str(e), "INDEX")
                        sys.exit(1)
        elif rebuild:
                try:
                        repo.rebuild(build_index=True)
                except sr.RepositoryError as e:
                        emsg(str(e), "REBUILD")
                        sys.exit(1)
                except (search_errors.IndexingException,
                    api_errors.UnknownErrors,
                    api_errors.PermissionsException) as e:
                        emsg(str(e), "INDEX")
                        sys.exit(1)
        elif add_content:
                try:
                        repo.add_content()
                        repo.refresh_index()
                except sr.RepositoryError as e:
                        emsg(str(e), "ADD_CONTENT")
                        sys.exit(1)
                except (search_errors.IndexingException,
                    api_errors.UnknownErrors,
                    api_errors.PermissionsException) as e:
                        emsg(str(e), "INDEX")
                        sys.exit(1)

        # Ready to start depot; exit now if requested.
        if exit_ready:
                sys.exit(0)

        # Next, initialize depot.
        if nasty:
                depot = ds.NastyDepotHTTP(repo, dconf)
        else:
                depot = ds.DepotHTTP(repo, dconf)

        # Now build our site configuration.
        conf = {
            "/": {
                # We have to override cherrypy's default response_class so that
                # we have access to the write() callable to stream data
                # directly to the client.
                "wsgi.response_class": dr.DepotResponse,
            },
            "/robots.txt": {
                "tools.staticfile.on": True,
                "tools.staticfile.filename": os.path.join(depot.web_root,
                    "robots.txt")
            },
        }

        proxy_base = dconf.get_property("pkg", "proxy_base")
        if proxy_base:
                # This changes the base URL for our server, and is primarily
                # intended to allow our depot process to operate behind Apache
                # or some other webserver process.
                #
                # Visit the following URL for more information:
                #    http://cherrypy.org/wiki/BuiltinTools#tools.proxy
                proxy_conf = {
                        "tools.proxy.on": True,
                        "tools.proxy.local": "",
                        "tools.proxy.base": proxy_base
                }

                # Now merge or add our proxy configuration information into the
                # existing configuration.
                for entry in proxy_conf:
                        conf["/"][entry] = proxy_conf[entry]

        if ll_mirror:
                ds.DNSSD_Plugin(cherrypy.engine, gconf).subscribe()

        if reindex:
                # Tell depot to update search indexes when possible;
                # this is done as a background task so that packages
                # can be served immediately while search indexes are
                # still being updated.
                depot._queue_refresh_index()

        # If stdin is not a tty and the pkgdepot controller isn't being used,
        # then assume process should be daemonized.
        if not os.environ.get("PKGDEPOT_CONTROLLER") and \
            not os.isatty(sys.stdin.fileno()):
                # Translate the values in log_cfg into paths.
                Daemonizer(cherrypy.engine, stderr=log_cfg["errors"],
                    stdout=log_cfg["access"]).subscribe()

        try:
                root = cherrypy.Application(depot)
                cherrypy.quickstart(root, config=conf)
        except Exception as _e:
                emsg("pkg.depotd: unknown error starting depot server, " \
                    "illegal option value specified?")
                emsg(_e)
                sys.exit(1)
 
 
Close
loading
Please Confirm
Close