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