michael@428: -----BEGIN PGP SIGNED MESSAGE-----
michael@428: Hash: SHA1
michael@428:
michael@428: - --
michael@428: - -- OpenPKG Framework License Processor
michael@428: - -- Copyright (c) 2000-2012 OpenPKG GmbH
michael@428: - --
michael@428: - -- This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
michael@428: - -- All rights reserved. Licenses which grant limited permission to use,
michael@428: - -- copy, modify and distribute this software are available from the
michael@428: - -- OpenPKG GmbH.
michael@428: - --
michael@428: - -- THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
michael@428: - -- WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
michael@428: - -- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
michael@428: - -- IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
michael@428: - -- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
michael@428: - -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
michael@428: - -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
michael@428: - -- USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
michael@428: - -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
michael@428: - -- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
michael@428: - -- OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
michael@428: - -- SUCH DAMAGE.
michael@428: - --
michael@428:
michael@428: - -- This is the RPM run-time integrity processor of the OpenPKG
michael@428: - -- Framework. It currently checks the OpenPKG Framework run-time
michael@428: - -- license only. The following grammar specifies and documents all
michael@428: - -- currently supported license parameters.
michael@428: - --
michael@428: - -- license ::= "Assertion-MinProcVersion:" version
michael@428: - -- # require a minimum version of the license integrity processor
michael@428: - --
michael@428: - -- | "Assertion-ErrorToWarning:" yes-no
michael@428: - -- # allow all fatal integrity checking errors to be
michael@428: - -- # converted to non-fatal warnings
michael@428: - --
michael@428: - -- | "Assertion-OnlineApproval:" url
michael@428: - -- # require an online approval by receiving an "OK" from
michael@428: - -- # specified remote service
michael@428: - --
michael@428: - -- | "Assertion-OnlineReporting:" url
michael@428: - -- # perform an asynchronous online reporting to
michael@428: - -- # specified remote service
michael@428: - --
michael@428: - -- | "Assertion-Prefix:" path
michael@428: - -- # require %{l_prefix} to match specified path
michael@428: - --
michael@428: - -- | "Assertion-User:" user
michael@428: - -- # require %{l_musr} to match specified username
michael@428: - --
michael@428: - -- | "Assertion-Group:" group
michael@428: - -- # require %{l_mgrp} to match specified groupname
michael@428: - --
michael@428: - -- | "Assertion-Domain:" domain
michael@428: - -- # require domain of host to match specified domain name
michael@428: - --
michael@428: - -- | "Assertion-LifeTime:" iso-date.":".iso-date
michael@428: - -- # require current real-time to be within specified
michael@428: - -- # begin and end date
michael@428: - --
michael@428: - -- | "Assertion-GrantTime:" iso-date.":".iso-date
michael@428: - -- # require current OpenPKG Framework %{RELEASE}
michael@428: - -- # (release time) to be within specified begin and end
michael@428: - -- # date
michael@428: - --
michael@428: - -- | "Assertion-InstanceAge:" duration
michael@428: - -- # require current OpenPKG Framework %{ORIGINTIME}
michael@428: - -- # (first install time) to be within specified begin
michael@428: - -- # and end date
michael@428: - --
michael@428: - -- | "Assertion-FromSourceOnTarget:" yes-no
michael@428: - -- # require either (if "yes") that all package
michael@428: - -- # %{BUILDHOST} are equal the host name or (if "no")
michael@428: - -- # that all package %{BUILDHOST} are not equal the host
michael@428: - -- # name
michael@428: - --
michael@428: - -- | "Assertion-PackageNames:"
michael@428: - -- ("!"?.mode-regex.":"."!"?.package-regex)+
michael@428: - -- # require all package %{NAME} to (not) match the
michael@428: - -- # specified regex while the current RPM run-time mode
michael@428: - -- # has to (not) match the specified regex. RPM run-time
michael@428: - -- # modes are: query, verify, checksig, resign, install,
michael@428: - -- # erase, build rebuild, recompile, tarbuild, initdb,
michael@428: - -- # rebuilddb and verifydb.
michael@428: - --
michael@428: - -- | "Assertion-PackageReleaseAge:"
michael@428: - -- percent.":".duration.":".dist-regex ((package-name|"*").":".release)+
michael@428: - -- # require that for at least the specified amount (in
michael@428: - -- # percent) of packages, which %{DISTRIBUTION} matches
michael@428: - -- # the specified regex, the %{RELEASE} is at least as
michael@428: - -- # old as the specified release or at least not older
michael@428: - -- # than the specified duration.
michael@428: - --
michael@428: - -- | "Assertion-Expression:" expression
michael@428: - -- # evaluates the Lua boolean expression after expanding
michael@428: - -- # RPM macros %{VARNAME} and expanding the construct
michael@428: - -- # "" ~~ // into the corresponding PCRE
michael@428: - -- # based regular expression match.
michael@428: - --
michael@428: - -- version ::= /^\d+\.\d+\.\d+$/
michael@428: - -- yes-no ::= /^yes|no$/
michael@428: - -- url ::= /^https?:\/\/.+$/
michael@428: - -- path ::= /^\/.+$/
michael@428: - -- user ::= /^[a-z][a-zA-Z0-9_]*$/
michael@428: - -- group ::= /^[a-z][a-zA-Z0-9_]*$/
michael@428: - -- domain ::= /^(?:[^.]+\.)+[^.]+$/
michael@428: - -- mode-regex ::= /^.+$/
michael@428: - -- package-regex ::= /^.+$/
michael@428: - -- package-name ::= /^[a-z][a-zA-Z0-9-]*$/
michael@428: - -- percent ::= /^\d+%$/
michael@428: - -- duration ::= /^\d+[smhdw]?$/
michael@428: - -- release ::= /^\d{8}$/
michael@428: - -- iso-date ::= /^\d{4}-\d{2}-\d{2}$/
michael@428: - -- expression ::= /^.+$/
michael@428:
michael@428: - -- integrity processor version
michael@428: integrity.version = "1.0.0"
michael@428:
michael@428: - -- integrity processor validation callback function
michael@428: function integrity.validate(ctx, cfg)
michael@428: integrity.util.debug(1, "OpenPKG run-time license integrity validation")
michael@428: integrity.util.debug(4, function (ctx) return "dump: ctx = " .. util.dump(ctx) end, ctx)
michael@428: integrity.util.debug(4, function (cfg) return "dump: cfg = " .. util.dump(cfg) end, cfg)
michael@428:
michael@428: -- process "Assertion-OnlineApproval" constraint
michael@428: if os.getenv("OPENPKG_LICENSE_EXCEPTION") ~= nil then
michael@428: -- support explicitly requested license exception
michael@428: cfg["Assertion-OnlineApproval"] = "http://openpkg.com/go/framework-license-exception"
michael@428: end
michael@428: if cfg["Assertion-OnlineApproval"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-OnlineApproval: \"%s\"", cfg["Assertion-OnlineApproval"])
michael@428: local uuids = integrity.util.uuids()
michael@428: if uuids["UUID_REGISTRY"] == "" then
michael@428: uuids["UUID_REGISTRY"] = "unknown"
michael@428: end
michael@428: if uuids["UUID_INSTANCE"] == "" then
michael@428: uuids["UUID_INSTANCE"] = "unknown"
michael@428: end
michael@428: if uuids["UUID_PLATFORM"] == "" then
michael@428: uuids["UUID_PLATFORM"] = "unknown"
michael@428: end
michael@428: local request = cfg["Assertion-OnlineApproval"]
michael@428: request = request .. "?UUID_REGISTRY=" .. uuids["UUID_REGISTRY"]
michael@428: request = request .. "&UUID_INSTANCE=" .. uuids["UUID_INSTANCE"]
michael@428: request = request .. "&UUID_PLATFORM=" .. uuids["UUID_PLATFORM"]
michael@428: integrity.util.debug(3, "info: remote request \"%s\"", request)
michael@428: local response = rpm.expand("%(%{l_prefix}/bin/openpkg curl -s -L -R '" .. request .. "')")
michael@428: integrity.util.debug(3, "info: remote response \"%s\"", response)
michael@428: if util.rmatch(response, "(?s)^\\s*OK\\s*$") then
michael@428: -- approved
michael@428: if os.getenv("OPENPKG_LICENSE_EXCEPTION") ~= nil then
michael@428: -- support explicitly requested license exception
michael@428: cfg["Assertion-ErrorToWarning"] = "yes"
michael@428: end
michael@428: else
michael@428: -- rejected
michael@428: cfg["Assertion-ErrorToWarning"] = "no"
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "license requires online approval but we failed to get " ..
michael@428: "an \"OK\" response from the online service")
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-MinProcVersion" constraint
michael@428: integrity.util.debug(2, "checking: Assertion-MinProcVersion: \"%s\"", cfg["Assertion-MinProcVersion"])
michael@428: if cfg["Assertion-MinProcVersion"] == nil then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "license configuration is missing required \"Assertion-MinProcVersion\" parameter")
michael@428: end
michael@428: integrity.util.debug(3, "require: %s <= %s", cfg["Assertion-MinProcVersion"], integrity.version)
michael@428: if rpm.vercmp(cfg["Assertion-MinProcVersion"], integrity.version) > 0 then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "license configuration requires a license processor of " ..
michael@428: "at least version \"" .. cfg["Assertion-MinProcVersion"] .. "\"")
michael@428: end
michael@428:
michael@428: -- process "Assertion-OnlineReporting" constraint
michael@428: if cfg["Assertion-OnlineReporting"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-OnlineReporting: \"%s\"", cfg["Assertion-OnlineReporting"])
michael@428: local uuids = integrity.util.uuids()
michael@428: if uuids["UUID_REGISTRY"] == "" then
michael@428: uuids["UUID_REGISTRY"] = "unknown"
michael@428: end
michael@428: if uuids["UUID_INSTANCE"] == "" then
michael@428: uuids["UUID_INSTANCE"] = "unknown"
michael@428: end
michael@428: if uuids["UUID_PLATFORM"] == "" then
michael@428: uuids["UUID_PLATFORM"] = "unknown"
michael@428: end
michael@428: local request = cfg["Assertion-OnlineReporting"]
michael@428: request = request .. "?UUID_REGISTRY=" .. uuids["UUID_REGISTRY"]
michael@428: request = request .. "&UUID_INSTANCE=" .. uuids["UUID_INSTANCE"]
michael@428: request = request .. "&UUID_PLATFORM=" .. uuids["UUID_PLATFORM"]
michael@428: integrity.util.debug(3, "info: remote request \"%s\"", request)
michael@428: rpm.expand("%(nohup %{l_prefix}/bin/openpkg curl -s -L -R '" .. request .. "' >/dev/null 2>&1 &)")
michael@428: integrity.util.debug(3, "response: (ignored, because asynchronous operation)")
michael@428: end
michael@428:
michael@428: -- process "Assertion-Prefix" constraint
michael@428: if cfg["Assertion-Prefix"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-Prefix: \"%s\"", cfg["Assertion-Prefix"])
michael@428: local prefix = rpm.expand("%{l_prefix}")
michael@428: integrity.util.debug(3, "require: \"%s\" == \"%s\"", cfg["Assertion-Prefix"], prefix)
michael@428: if cfg["Assertion-Prefix"] ~= prefix then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "instance prefix \"" .. prefix .. "\" " ..
michael@428: "does not match value \"" .. cfg["Assertion-Prefix"] .. "\" of " ..
michael@428: "license configuration parameter \"Assertion-Prefix\"")
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-User" constraint
michael@428: if cfg["Assertion-User"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-User: \"%s\"", cfg["Assertion-User"])
michael@428: local user = rpm.expand("%{l_musr}")
michael@428: integrity.util.debug(3, "require: \"%s\" == \"%s\"", cfg["Assertion-User"], user)
michael@428: if cfg["Assertion-User"] ~= user then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "instance management user \"" .. user .. "\" " ..
michael@428: "does not match value \"" .. cfg["Assertion-User"] .. "\" of " ..
michael@428: "license configuration parameter \"Assertion-User\"")
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-Group" constraint
michael@428: if cfg["Assertion-Group"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-Group: \"%s\"", cfg["Assertion-Group"])
michael@428: local group = rpm.expand("%{l_mgrp}")
michael@428: integrity.util.debug(3, "require: \"%s\" == \"%s\"", cfg["Assertion-Group"], group)
michael@428: if cfg["Assertion-Group"] ~= group then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "instance management group \"" .. group .. "\" " ..
michael@428: "does not match value \"" .. cfg["Assertion-Group"] .. "\" of " ..
michael@428: "license configuration parameter \"Assertion-Group\"")
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-Domain" constraint
michael@428: if cfg["Assertion-Domain"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-Domain: \"%s\"", cfg["Assertion-Domain"])
michael@428: local domain = rpm.expand("%(%{l_shtool} echo -n -e '%d')")
michael@428: integrity.util.debug(3, "require: \"%s\" ~~ /(?s)^.*%s$/", domain, cfg["Assertion-Domain"])
michael@428: local s, _, m = util.rmatch(domain, "(?s)^.*" .. cfg["Assertion-Domain"] .. "$")
michael@428: if s == nil then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "host domain \"" .. domain .. "\" " ..
michael@428: "does not end in pattern \"" .. cfg["Assertion-Domain"] .. "\") " ..
michael@428: "of license configuration parameter \"Assertion-Domain\"")
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-LifeTime" constraint
michael@428: if cfg["Assertion-LifeTime"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-LifeTime: \"%s\"", cfg["Assertion-LifeTime"])
michael@428:
michael@428: -- determine lifetime begin and end
michael@428: local lifetime = cfg["Assertion-LifeTime"]
michael@428: local s, _, m = util.rmatch(lifetime, "^(?s)(\\d{4})-(\\d{2})-(\\d{2})\\s*:\\s*(\\d{4})-(\\d{2})-(\\d{2})$")
michael@428: if s == nil then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "failed to extract time information from " ..
michael@428: "license configuration parameter \"Assertion-LifeTime\"")
michael@428: end
michael@428: local lifetime_begin = os.time({
michael@428: year = tonumber(m[1]),
michael@428: month = tonumber(m[2]),
michael@428: day = tonumber(m[3]),
michael@428: hour = 0,
michael@428: min = 0,
michael@428: sec = 0
michael@428: })
michael@428: local lifetime_end = os.time({
michael@428: year = tonumber(m[4]),
michael@428: month = tonumber(m[5]),
michael@428: day = tonumber(m[6]),
michael@428: hour = 23,
michael@428: min = 59,
michael@428: sec = 59
michael@428: })
michael@428:
michael@428: -- check whether current run-time is within lifetime
michael@428: local t_now = os.time()
michael@428: integrity.util.debug(3, "require: %d <= %d <= %d", lifetime_begin, t_now, lifetime_end)
michael@428: if not (lifetime_begin <= t_now and t_now <= lifetime_end) then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "current time \"" .. os.date("!%Y-%m-%d %H:%M:%S UTC", t_now) .. "\" " ..
michael@428: "is not within the timerange \"" .. cfg["Assertion-LifeTime"] .. "\" " ..
michael@428: "of license configuration parameter \"Assertion-LifeTime\"")
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-GrantTime" constraint
michael@428: if cfg["Assertion-GrantTime"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-GrantTime: \"%s\"", cfg["Assertion-GrantTime"])
michael@428:
michael@428: -- determine granttime begin and end
michael@428: local granttime = cfg["Assertion-GrantTime"]
michael@428: local s, _, m = util.rmatch(granttime, "^(?s)(\\d{4})-(\\d{2})-(\\d{2})\\s*:\\s*(\\d{4})-(\\d{2})-(\\d{2})$")
michael@428: if s == nil then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "failed to extract time information from " ..
michael@428: "license configuration parameter \"Assertion-GrantTime\"")
michael@428: end
michael@428: local granttime_begin = os.time({
michael@428: year = tonumber(m[1]),
michael@428: month = tonumber(m[2]),
michael@428: day = tonumber(m[3]),
michael@428: hour = 0,
michael@428: min = 0,
michael@428: sec = 0
michael@428: })
michael@428: local granttime_end = os.time({
michael@428: year = tonumber(m[4]),
michael@428: month = tonumber(m[5]),
michael@428: day = tonumber(m[6]),
michael@428: hour = 23,
michael@428: min = 59,
michael@428: sec = 59
michael@428: })
michael@428:
michael@428: -- determine OpenPKG Framework release time
michael@428: -- (allow openpkg.spec:%pre to override with a higher value for pre-checking)
michael@428: local t_release = 0
michael@428: local result = {}
michael@428: for _, line in ipairs(rpm.query("Q:%{RELEASE}", false, "openpkg")) do
michael@428: local s, _, m = util.rmatch(line, "(?s)^Q:(.+)$")
michael@428: if s ~= nil then
michael@428: table.insert(result, m[1])
michael@428: end
michael@428: end
michael@428: if result[1] ~= nil then
michael@428: local s, _, m = util.rmatch(result[1], "^(?s)(\\d{4})(\\d{2})(\\d{2})$")
michael@428: if s ~= nil then
michael@428: t_release = os.time({
michael@428: year = tonumber(m[1]),
michael@428: month = tonumber(m[2]),
michael@428: day = tonumber(m[3]),
michael@428: hour = 0,
michael@428: min = 0,
michael@428: sec = 0
michael@428: })
michael@428: end
michael@428: end
michael@428: if t_release == 0 then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "failed to determine OpenPKG Framework release time")
michael@428: end
michael@428: local override = os.getenv("OPENPKG_FRAMEWORK_RELEASE")
michael@428: if override ~= nil then
michael@428: local s, _, m = util.rmatch(override, "^(?s)(\\d{4})(\\d{2})(\\d{2})$")
michael@428: if s ~= nil then
michael@428: local t_override = os.time({
michael@428: year = tonumber(m[1]),
michael@428: month = tonumber(m[2]),
michael@428: day = tonumber(m[3]),
michael@428: hour = 0,
michael@428: min = 0,
michael@428: sec = 0
michael@428: })
michael@428: if t_release < t_override then
michael@428: t_release = t_override
michael@428: end
michael@428: end
michael@428: end
michael@428:
michael@428: -- check whether current OpenPKG Framework release time is within granttime
michael@428: integrity.util.debug(3, "require: %d <= %d <= %d", granttime_begin, t_release, granttime_end)
michael@428: if not (granttime_begin <= t_release and t_release <= granttime_end) then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "current OpenPKG Framework release time \"" .. os.date("%Y-%m-%d", t_release) .. "\" " ..
michael@428: "is not within the timerange \"" .. cfg["Assertion-GrantTime"] .. "\" " ..
michael@428: "of license configuration parameter \"Assertion-GrantTime\"")
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-InstanceAge" constraint
michael@428: if cfg["Assertion-InstanceAge"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-InstanceAge: \"%s\"", cfg["Assertion-InstanceAge"])
michael@428:
michael@428: -- determine maximum instance age in seconds
michael@428: local t_diff_max = cfg["Assertion-InstanceAge"]
michael@428: t_diff_max = 0 + util.rsubst(t_diff_max, "^(\\d+)([smhdw])$", function (t, unit)
michael@428: if unit == "s" then t = t * 1
michael@428: elseif unit == "m" then t = t * 60
michael@428: elseif unit == "h" then t = t * 60 * 60
michael@428: elseif unit == "d" then t = t * 60 * 60 * 24
michael@428: elseif unit == "w" then t = t * 60 * 60 * 24 * 7
michael@428: end
michael@428: return t
michael@428: end)
michael@428:
michael@428: -- approach 1: determine install time via timestamp of UUID_REGISTRY
michael@428: local uuids = integrity.util.uuids()
michael@428: if uuids["UUID_REGISTRY"] == "" then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "failed to load UUID_REGISTRY")
michael@428: end
michael@428: txt = uuid.describe(uuids["UUID_REGISTRY"])
michael@428: if txt == nil then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "failed to parse extracted UUID_REGISTRY string \"" .. uuids["UUID_REGISTRY"] .. "\" as an UUID")
michael@428: end
michael@428: local s, _, m = util.rmatch(txt, "(?s)^.*time:\\s+(\\d{4})-(\\d{2})-(\\d{2})\\s+(\\d{2}):(\\d{2}):(\\d{2}).*$")
michael@428: if s == nil then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "failed to extract timestamp from UUID_REGISTRY \"" .. uuids["UUID_REGISTRY"] .. "\"")
michael@428: end
michael@428: local t_install = os.time({
michael@428: year = tonumber(m[1]),
michael@428: month = tonumber(m[2]),
michael@428: day = tonumber(m[3]),
michael@428: hour = tonumber(m[4]),
michael@428: min = tonumber(m[5]),
michael@428: sec = tonumber(m[6])
michael@428: })
michael@428:
michael@428: -- approach 2: determine install time via first install time of "openpkg" package
michael@428: local result = {}
michael@428: for _, line in ipairs(rpm.query(
michael@428: "Q:%|ORIGINTIME?{" ..
michael@428: "%{ORIGINTIME}" .. -- regular case: RPM 5 installed/updated with RPM 5
michael@428: "}:{" ..
michael@428: "%|INSTALLTIME?{" ..
michael@428: "%{INSTALLTIME}" .. -- special case: RPM 5 installed initially with RPM 4
michael@428: "}:{" ..
michael@428: "}|" ..
michael@428: "}|", false, "openpkg"
michael@428: )) do
michael@428: local s, _, m = util.rmatch(line, "(?s)^Q:(.+)$")
michael@428: if s ~= nil then
michael@428: table.insert(result, m[1])
michael@428: end
michael@428: end
michael@428: if result[1] ~= nil then
michael@428: local n = tonumber(result[1])
michael@428: if n > 0 then
michael@428: t_install = n
michael@428: end
michael@428: end
michael@428:
michael@428: -- check time difference
michael@428: local t_now = os.time()
michael@428: local t_diff = os.difftime(t_now, t_install)
michael@428: integrity.util.debug(3, "calc: %d - %d = %d", t_now, t_install, t_diff)
michael@428: if t_diff < 0 then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "current system time \"" .. t_now .. "\" is lower than " ..
michael@428: "instance installation time \"" .. t_install .. "\"")
michael@428: end
michael@428: integrity.util.debug(3, "require: %d <= %d", t_diff, t_diff_max)
michael@428: if t_diff > t_diff_max then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "instance age \"" .. t_diff .. "\" " ..
michael@428: "is greater than value \"" .. t_diff_max .. "\" (\"" .. cfg["Assertion-InstanceAge"] .. "\") " ..
michael@428: "of license configuration parameter \"Assertion-InstanceAge\"")
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-FromSourceOnTarget" constraint
michael@428: if cfg["Assertion-FromSourceOnTarget"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-FromSourceOnTarget: \"%s\"", cfg["Assertion-FromSourceOnTarget"])
michael@428: local hostname = rpm.hostname()
michael@428: for _, line in ipairs(rpm.query("Q:%{NAME}:%{BUILDHOST}", true, "*")) do
michael@428: local s, _, m = util.rmatch(line, "(?s)^Q:([^:]+):(.+)$")
michael@428: if s ~= nil then
michael@428: local name = m[1]
michael@428: local buildhost = m[2]
michael@428: integrity.util.debug(4, "info: name \"%s\", buildhost \"%s\"", name, buildhost)
michael@428: if not util.rmatch(name, "(?s)^gpg-.+$") and buildhost ~= "localhost" then
michael@428: if cfg["Assertion-FromSourceOnTarget"] == "yes" and buildhost ~= hostname then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "license-required \"build from source on target system only\" situation not met because " ..
michael@428: "package build host \"" .. buildhost .. "\" is not(!) equal to the package install host \"" .. hostname .. "\".")
michael@428: end
michael@428: if cfg["Assertion-FromSourceOnTarget"] == "no" and buildhost == hostname then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "license-required \"build binaries on separate build-host only\" situation not met because " ..
michael@428: "package build host \"" .. buildhost .. "\" is equal to the package install host \"" .. hostname .. "\".")
michael@428: end
michael@428: end
michael@428: end
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-PackageNames" constraints
michael@428: if cfg["Assertion-PackageNames"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-PackageNames: \"%s\"", cfg["Assertion-PackageNames"])
michael@428:
michael@428: -- query RPMDB for names of all installed packages
michael@428: local packages = {}
michael@428: for _, line in ipairs(rpm.query("Q:%{NAME}", true, "*")) do
michael@428: local s, _, m = util.rmatch(line, "(?s)^Q:(.+)$")
michael@428: if s ~= nil then
michael@428: table.insert(packages, m[1])
michael@428: end
michael@428: end
michael@428:
michael@428: -- iterate over all constraints
michael@428: for _, constraint in
michael@428: ipairs(
michael@428: util.rsplit(
michael@428: util.rsubst(
michael@428: cfg["Assertion-PackageNames"],
michael@428: "(?s)^\\s*(.+?)\\s*$", "%1"
michael@428: ),
michael@428: "(?s)\\s+"
michael@428: )
michael@428: ) do
michael@428: -- parse constraint
michael@428: local s, _, m = util.rmatch(constraint, "(?s)^(!?)([^:]+):(!?)(.+)$")
michael@428: if s == nil then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "invalid syntax in license configuration \"Assertion-PackageNames\" " ..
michael@428: "parameter: \"" .. constraint .. "\"")
michael@428: end
michael@428: local mode_negate = m[1] ~= ""
michael@428: local mode_regex = m[2]
michael@428: local package_negate = m[3] ~= ""
michael@428: local package_regex = m[4]
michael@428: -- apply the mode filter
michael@428: local mode_matches, _, _ = util.rmatch(ctx.rpm.mode, mode_regex);
michael@428: if (not mode_negate and mode_matches ~= nil)
michael@428: or ( mode_negate and mode_matches == nil) then
michael@428: -- apply the package filter to names of all installed packages
michael@428: for _, package in ipairs(packages) do
michael@428: if package_negate then
michael@428: integrity.util.debug(3, "require: \"%s\" !~ /%s/", package, package_regex)
michael@428: else
michael@428: integrity.util.debug(3, "require: \"%s\" ~~ /%s/", package, package_regex)
michael@428: end
michael@428: local package_matches, _, _ = util.rmatch(package, package_regex)
michael@428: if not ( (not package_negate and package_matches ~= nil)
michael@428: or ( package_negate and package_matches == nil)) then
michael@428: -- indicate integrity validation error
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "installed package \"" .. package .. "\" " ..
michael@428: "under RPM run-time mode \"" .. ctx.rpm.mode .. "\" " ..
michael@428: "not covered by pattern \"" .. package_regex .. "\" " ..
michael@428: "of license configuration parameter \"Assertion-PackageNames\"")
michael@428: end
michael@428: end
michael@428: end
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-PackageReleaseAge"
michael@428: if cfg["Assertion-PackageReleaseAge"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-PackageReleaseAge: \"%s[...]\"", string.sub(cfg["Assertion-PackageReleaseAge"], 1, 20))
michael@428:
michael@428: -- parse constraint
michael@428: local constraint = cfg["Assertion-PackageReleaseAge"]
michael@428: local s, _, m = util.rmatch(constraint, "(?s)^([^:]+)%:([^:]+):([^\\s]+)\\s+(.+)$")
michael@428: if s == nil then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "invalid syntax in license configuration \"Assertion-PackageReleaseAge\" parameter")
michael@428: end
michael@428: local percent = m[1] / 100
michael@428: local offset = m[2]
michael@428: local distregex = m[3]
michael@428: local spec = m[4]
michael@428:
michael@428: -- determine maximum release time difference (in seconds)
michael@428: local t_diff_max = 0 + util.rsubst(offset, "^(\\d+)([smhdw])$", function (t, unit)
michael@428: if unit == "s" then t = t * 1
michael@428: elseif unit == "m" then t = t * 60
michael@428: elseif unit == "h" then t = t * 60 * 60
michael@428: elseif unit == "d" then t = t * 60 * 60 * 24
michael@428: elseif unit == "w" then t = t * 60 * 60 * 24 * 7
michael@428: end
michael@428: return t
michael@428: end)
michael@428:
michael@428: -- iterate over all package specifications to build release map
michael@428: local releases = {}
michael@428: for _, constraint in
michael@428: ipairs(
michael@428: util.rsplit(
michael@428: util.rsubst(
michael@428: spec,
michael@428: "(?s)^\\s*(.+?)\\s*$", "%1"
michael@428: ),
michael@428: "(?s)\\s+"
michael@428: )
michael@428: ) do
michael@428:
michael@428: -- parse specification into package name and release constraint
michael@428: local s, _, m = util.rmatch(constraint, "(?s)^([^:]+):(.+)$")
michael@428: if s == nil then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "invalid syntax in license configuration \"Assertion-PackageReleaseAge\" " ..
michael@428: "parameter: \"" .. constraint .. "\"")
michael@428: end
michael@428:
michael@428: -- store result into release map
michael@428: releases[m[1]] = m[2]
michael@428: end
michael@428:
michael@428: -- query RPMDB for releases of all installed packages and decide
michael@428: -- whether the release time is inside or outside our constraint window
michael@428: local release_window_inside = 0
michael@428: local release_window_outside = 0
michael@428: local release_window_foreign = 0
michael@428: local release_window_unknown = 0
michael@428: for _, line in ipairs(rpm.query("Q:%{NAME}:%{RELEASE}:%{DISTRIBUTION}", true, "*")) do
michael@428: local s, _, m = util.rmatch(line, "(?s)^Q:([^:]+):(\\d\\d\\d\\d)(\\d\\d)(\\d\\d):(.+)$")
michael@428: if s ~= nil then
michael@428: -- parse query results
michael@428: local name = m[1]
michael@428: local t_release = os.time({
michael@428: year = tonumber(m[2]),
michael@428: month = tonumber(m[3]),
michael@428: day = tonumber(m[4]),
michael@428: hour = 23,
michael@428: min = 59,
michael@428: sec = 59
michael@428: })
michael@428: local dist = m[5]
michael@428:
michael@428: -- only check files of the constrained distribution(s)
michael@428: if util.rmatch(dist, "(?s)" .. distregex) then
michael@428:
michael@428: -- determine minimum release constraint
michael@428: local t_release_min = releases[name]
michael@428: if t_release_min == nil then
michael@428: t_release_min = releases["*"]
michael@428: end
michael@428: if t_release_min == nil then
michael@428: t_release_min = os.time()
michael@428: else
michael@428: local s, _, m = util.rmatch(t_release_min, "^(?s)(\\d{4})(\\d{2})(\\d{2})$")
michael@428: t_release_min = os.time({
michael@428: year = tonumber(m[1]),
michael@428: month = tonumber(m[2]),
michael@428: day = tonumber(m[3]),
michael@428: hour = 0,
michael@428: min = 0,
michael@428: sec = 0
michael@428: })
michael@428: end
michael@428:
michael@428: -- check time difference of package release
michael@428: local t_diff = os.difftime(t_release_min, t_release)
michael@428: integrity.util.debug(4, "calc: %d - %d = %d", t_release_min, t_release, t_diff)
michael@428: integrity.util.debug(4, "require: %d <= 0 or (%d > 0 and %d < %d)", t_diff, t_diff, t_diff, t_diff_max)
michael@428: if t_diff <= 0 or (t_diff > 0 and t_diff < t_diff_max) then
michael@428: release_window_inside = release_window_inside + 1
michael@428: else
michael@428: release_window_outside = release_window_outside + 1
michael@428: end
michael@428: else
michael@428: release_window_foreign = release_window_foreign + 1
michael@428: end
michael@428: else
michael@428: release_window_unknown = release_window_unknown + 1
michael@428: end
michael@428: end
michael@428: integrity.util.debug(3, "info: inside %d, outside %d, foreign %d, unknown %d",
michael@428: release_window_inside, release_window_outside, release_window_foreign, release_window_unknown)
michael@428:
michael@428: -- check validity of overall constraint
michael@428: local percent_inside =
michael@428: (release_window_inside / (release_window_inside + release_window_outside))
michael@428: integrity.util.debug(3, "require: %d >= %d", percent_inside, percent)
michael@428: if percent_inside < percent then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "there are only " .. math.floor(percent_inside * 100) .. "% " ..
michael@428: "packages inside the release date constraint " ..
michael@428: "(expected a minimum of " .. math.floor(percent * 100) .. "%)")
michael@428: end
michael@428: end
michael@428:
michael@428: -- process "Assertion-Expression" constraint
michael@428: if cfg["Assertion-Expression"] ~= nil then
michael@428: integrity.util.debug(2, "checking: Assertion-Expression: \"%s\"", cfg["Assertion-Expression"])
michael@428:
michael@428: -- expand special consytructs in expression
michael@428: local expr = cfg["Assertion-Expression"]
michael@428: expr = util.rsubst(expr, "(%\{[a-zA-Z_][a-zA-Z0-9_]+\})", function (str)
michael@428: return rpm.expand(str)
michael@428: end)
michael@428: expr = util.rsubst(expr, "\"((?:\\\\.|[^\"])+)\"\\s*~~\\s*/((?:\\\\.|[^/])+)/", function (str, regex)
michael@428: if util.rmatch(str, "(?s)" .. regex) ~= nil then
michael@428: return "true"
michael@428: else
michael@428: return "false"
michael@428: end
michael@428: end)
michael@428:
michael@428: -- evaluate expression
michael@428: integrity.util.debug(3, "evaluate: %s", expr)
michael@428: result = assert(loadstring(expr))()
michael@428: if type(result) ~= "boolean" then
michael@428: result = false
michael@428: end
michael@428: if not result then
michael@428: return integrity.util.error(ctx, cfg,
michael@428: "expression \"" .. cfg["Assertion-Expression"] .. "\" " ..
michael@428: "of license configuration parameter \"Assertion-Expression\"" ..
michael@428: "evaluated to false")
michael@428: end
michael@428: end
michael@428:
michael@428: -- indicate license integrity validation success
michael@428: return "OK"
michael@428: end
michael@428:
michael@428: - -- integrity processor utilities namespace
michael@428: integrity.util = {}
michael@428:
michael@428: - -- write debug information to stderr
michael@428: function integrity.util.debug(level_this, msg, ...)
michael@428: local level_min = os.getenv("OPENPKG_LICENSE_DEBUG")
michael@428: if level_min ~= nil then
michael@428: if type(level_min) == "string" then
michael@428: level_min = tonumber(level_min)
michael@428: end
michael@428: if type(level_min) ~= "number" then
michael@428: level_min = 0
michael@428: end
michael@428: if level_this <= level_min then
michael@428: local output
michael@428: if type(msg) == "function" then
michael@428: output = msg(...)
michael@428: else
michael@428: output = string.format(msg, ...)
michael@428: end
michael@428: local prefix = ""
michael@428: local i = 1
michael@428: while (i < level_this) do
michael@428: prefix = prefix .. " "
michael@428: i = i + 1
michael@428: end
michael@428: io.stderr:write("rpm: DEBUG: " .. prefix .. output .. "\n")
michael@428: end
michael@428: end
michael@428: end
michael@428:
michael@428: - -- load OpenPKG instance UUIDs
michael@428: function integrity.util.uuids()
michael@428: local uuids = {
michael@428: UUID_REGISTRY = "",
michael@428: UUID_INSTANCE = "",
michael@428: UUID_PLATFORM = ""
michael@428: }
michael@428: local filename = rpm.expand("%{l_prefix}/etc/openpkg/uuid")
michael@428: local txt = rpm.slurp(filename)
michael@428: if txt ~= nil then
michael@428: for name, _ in pairs(uuids) do
michael@428: local s, _, m = util.rmatch(txt, "(?s)^.*" .. name .. "=\"([^\"\"]+)\".*$")
michael@428: if s ~= nil then
michael@428: uuids[name] = m[1]
michael@428: end
michael@428: end
michael@428: end
michael@428: return uuids
michael@428: end
michael@428:
michael@428: - -- report validation warning
michael@428: function integrity.util.warning(ctx, cfg, warning)
michael@428: -- return prominent warning message
michael@428: return
michael@428: "WARNING: OpenPKG run-time license check failed -- continue processing\n" ..
michael@428: "+-----------------------------------------------------------------------------+\n" ..
michael@428: "| Attention, the OpenPKG RPM run-time integrity checking facility encountered a\n" ..
michael@428: "| non-fatal problem during license checking, but allows processing to continue.\n" ..
michael@428: "| The particular warning reported by the OpenPKG license processor is:\n" ..
michael@428: "|\n" ..
michael@428: util.textwrap("| ", warning, 60, 70) ..
michael@428: "|\n" ..
michael@428: "| Notice: Operation of the OpenPKG Framework requires a valid license.\n" ..
michael@428: "| Go to http://openpkg.com/go/framework-license for more details, please.\n" ..
michael@428: "+-----------------------------------------------------------------------------+"
michael@428: end
michael@428:
michael@428: - -- report validation error
michael@428: function integrity.util.error(ctx, cfg, error)
michael@428: -- support conversion of errors into warnings
michael@428: if cfg["Assertion-ErrorToWarning"] ~= nil then
michael@428: if cfg["Assertion-ErrorToWarning"] == "yes" then
michael@428: return integrity.util.warning(ctx, cfg, error)
michael@428: end
michael@428: end
michael@428:
michael@428: -- return prominent error message
michael@428: return
michael@428: "ERROR: OpenPKG run-time license check failed -- stopping processing\n" ..
michael@428: "+-----------------------------------------------------------------------------+\n" ..
michael@428: "| Sorry, the OpenPKG RPM run-time integrity checking facility encountered a\n" ..
michael@428: "| fatal problem during license checking and stops processing immediately.\n" ..
michael@428: "| The particular error reported by the OpenPKG license processor is:\n" ..
michael@428: "|\n" ..
michael@428: util.textwrap("| ", error, 60, 70) ..
michael@428: "|\n" ..
michael@428: "| Notice: Operation of the OpenPKG Framework requires a valid license.\n" ..
michael@428: "| Go to http://openpkg.com/go/framework-license for more details, please.\n" ..
michael@428: "| Run \"openpkg man license\" for details about local license management.\n" ..
michael@428: "+-----------------------------------------------------------------------------+"
michael@428: end
michael@428:
michael@428: -----BEGIN PGP SIGNATURE-----
michael@428: Comment: OpenPKG GmbH
michael@428:
michael@428: iEYEARECAAYFAk8BelcACgkQZwQuyWG3rjTL6QCeLTLVj4PTnd/E7mf+Sv4mgbZj
michael@428: 5J0AoMXrO4EimPSSCZSJ1TLW8f8GP+B5
michael@428: =AVpf
michael@428: -----END PGP SIGNATURE-----