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-----