1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/openpkg/license.lua Tue Jul 31 12:23:42 2012 +0200 1.3 @@ -0,0 +1,808 @@ 1.4 +-----BEGIN PGP SIGNED MESSAGE----- 1.5 +Hash: SHA1 1.6 + 1.7 +- -- 1.8 +- -- OpenPKG Framework License Processor 1.9 +- -- Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/> 1.10 +- -- 1.11 +- -- This software is property of the OpenPKG GmbH, DE MUC HRB 160208. 1.12 +- -- All rights reserved. Licenses which grant limited permission to use, 1.13 +- -- copy, modify and distribute this software are available from the 1.14 +- -- OpenPKG GmbH. 1.15 +- -- 1.16 +- -- THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED 1.17 +- -- WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 1.18 +- -- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 1.19 +- -- IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR 1.20 +- -- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 1.21 +- -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 1.22 +- -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 1.23 +- -- USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 1.24 +- -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 1.25 +- -- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 1.26 +- -- OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 1.27 +- -- SUCH DAMAGE. 1.28 +- -- 1.29 + 1.30 +- -- This is the RPM run-time integrity processor of the OpenPKG 1.31 +- -- Framework. It currently checks the OpenPKG Framework run-time 1.32 +- -- license only. The following grammar specifies and documents all 1.33 +- -- currently supported license parameters. 1.34 +- -- 1.35 +- -- license ::= "Assertion-MinProcVersion:" version 1.36 +- -- # require a minimum version of the license integrity processor 1.37 +- -- 1.38 +- -- | "Assertion-ErrorToWarning:" yes-no 1.39 +- -- # allow all fatal integrity checking errors to be 1.40 +- -- # converted to non-fatal warnings 1.41 +- -- 1.42 +- -- | "Assertion-OnlineApproval:" url 1.43 +- -- # require an online approval by receiving an "OK" from 1.44 +- -- # specified remote service 1.45 +- -- 1.46 +- -- | "Assertion-OnlineReporting:" url 1.47 +- -- # perform an asynchronous online reporting to 1.48 +- -- # specified remote service 1.49 +- -- 1.50 +- -- | "Assertion-Prefix:" path 1.51 +- -- # require %{l_prefix} to match specified path 1.52 +- -- 1.53 +- -- | "Assertion-User:" user 1.54 +- -- # require %{l_musr} to match specified username 1.55 +- -- 1.56 +- -- | "Assertion-Group:" group 1.57 +- -- # require %{l_mgrp} to match specified groupname 1.58 +- -- 1.59 +- -- | "Assertion-Domain:" domain 1.60 +- -- # require domain of host to match specified domain name 1.61 +- -- 1.62 +- -- | "Assertion-LifeTime:" iso-date.":".iso-date 1.63 +- -- # require current real-time to be within specified 1.64 +- -- # begin and end date 1.65 +- -- 1.66 +- -- | "Assertion-GrantTime:" iso-date.":".iso-date 1.67 +- -- # require current OpenPKG Framework %{RELEASE} 1.68 +- -- # (release time) to be within specified begin and end 1.69 +- -- # date 1.70 +- -- 1.71 +- -- | "Assertion-InstanceAge:" duration 1.72 +- -- # require current OpenPKG Framework %{ORIGINTIME} 1.73 +- -- # (first install time) to be within specified begin 1.74 +- -- # and end date 1.75 +- -- 1.76 +- -- | "Assertion-FromSourceOnTarget:" yes-no 1.77 +- -- # require either (if "yes") that all package 1.78 +- -- # %{BUILDHOST} are equal the host name or (if "no") 1.79 +- -- # that all package %{BUILDHOST} are not equal the host 1.80 +- -- # name 1.81 +- -- 1.82 +- -- | "Assertion-PackageNames:" 1.83 +- -- ("!"?.mode-regex.":"."!"?.package-regex)+ 1.84 +- -- # require all package %{NAME} to (not) match the 1.85 +- -- # specified regex while the current RPM run-time mode 1.86 +- -- # has to (not) match the specified regex. RPM run-time 1.87 +- -- # modes are: query, verify, checksig, resign, install, 1.88 +- -- # erase, build rebuild, recompile, tarbuild, initdb, 1.89 +- -- # rebuilddb and verifydb. 1.90 +- -- 1.91 +- -- | "Assertion-PackageReleaseAge:" 1.92 +- -- percent.":".duration.":".dist-regex ((package-name|"*").":".release)+ 1.93 +- -- # require that for at least the specified amount (in 1.94 +- -- # percent) of packages, which %{DISTRIBUTION} matches 1.95 +- -- # the specified regex, the %{RELEASE} is at least as 1.96 +- -- # old as the specified release or at least not older 1.97 +- -- # than the specified duration. 1.98 +- -- 1.99 +- -- | "Assertion-Expression:" expression 1.100 +- -- # evaluates the Lua boolean expression after expanding 1.101 +- -- # RPM macros %{VARNAME} and expanding the construct 1.102 +- -- # "<string>" ~~ /<regex>/ into the corresponding PCRE 1.103 +- -- # based regular expression match. 1.104 +- -- 1.105 +- -- version ::= /^\d+\.\d+\.\d+$/ 1.106 +- -- yes-no ::= /^yes|no$/ 1.107 +- -- url ::= /^https?:\/\/.+$/ 1.108 +- -- path ::= /^\/.+$/ 1.109 +- -- user ::= /^[a-z][a-zA-Z0-9_]*$/ 1.110 +- -- group ::= /^[a-z][a-zA-Z0-9_]*$/ 1.111 +- -- domain ::= /^(?:[^.]+\.)+[^.]+$/ 1.112 +- -- mode-regex ::= /^.+$/ 1.113 +- -- package-regex ::= /^.+$/ 1.114 +- -- package-name ::= /^[a-z][a-zA-Z0-9-]*$/ 1.115 +- -- percent ::= /^\d+%$/ 1.116 +- -- duration ::= /^\d+[smhdw]?$/ 1.117 +- -- release ::= /^\d{8}$/ 1.118 +- -- iso-date ::= /^\d{4}-\d{2}-\d{2}$/ 1.119 +- -- expression ::= /^.+$/ 1.120 + 1.121 +- -- integrity processor version 1.122 +integrity.version = "1.0.0" 1.123 + 1.124 +- -- integrity processor validation callback function 1.125 +function integrity.validate(ctx, cfg) 1.126 + integrity.util.debug(1, "OpenPKG run-time license integrity validation") 1.127 + integrity.util.debug(4, function (ctx) return "dump: ctx = " .. util.dump(ctx) end, ctx) 1.128 + integrity.util.debug(4, function (cfg) return "dump: cfg = " .. util.dump(cfg) end, cfg) 1.129 + 1.130 + -- process "Assertion-OnlineApproval" constraint 1.131 + if os.getenv("OPENPKG_LICENSE_EXCEPTION") ~= nil then 1.132 + -- support explicitly requested license exception 1.133 + cfg["Assertion-OnlineApproval"] = "http://openpkg.com/go/framework-license-exception" 1.134 + end 1.135 + if cfg["Assertion-OnlineApproval"] ~= nil then 1.136 + integrity.util.debug(2, "checking: Assertion-OnlineApproval: \"%s\"", cfg["Assertion-OnlineApproval"]) 1.137 + local uuids = integrity.util.uuids() 1.138 + if uuids["UUID_REGISTRY"] == "" then 1.139 + uuids["UUID_REGISTRY"] = "unknown" 1.140 + end 1.141 + if uuids["UUID_INSTANCE"] == "" then 1.142 + uuids["UUID_INSTANCE"] = "unknown" 1.143 + end 1.144 + if uuids["UUID_PLATFORM"] == "" then 1.145 + uuids["UUID_PLATFORM"] = "unknown" 1.146 + end 1.147 + local request = cfg["Assertion-OnlineApproval"] 1.148 + request = request .. "?UUID_REGISTRY=" .. uuids["UUID_REGISTRY"] 1.149 + request = request .. "&UUID_INSTANCE=" .. uuids["UUID_INSTANCE"] 1.150 + request = request .. "&UUID_PLATFORM=" .. uuids["UUID_PLATFORM"] 1.151 + integrity.util.debug(3, "info: remote request \"%s\"", request) 1.152 + local response = rpm.expand("%(%{l_prefix}/bin/openpkg curl -s -L -R '" .. request .. "')") 1.153 + integrity.util.debug(3, "info: remote response \"%s\"", response) 1.154 + if util.rmatch(response, "(?s)^\\s*OK\\s*$") then 1.155 + -- approved 1.156 + if os.getenv("OPENPKG_LICENSE_EXCEPTION") ~= nil then 1.157 + -- support explicitly requested license exception 1.158 + cfg["Assertion-ErrorToWarning"] = "yes" 1.159 + end 1.160 + else 1.161 + -- rejected 1.162 + cfg["Assertion-ErrorToWarning"] = "no" 1.163 + return integrity.util.error(ctx, cfg, 1.164 + "license requires online approval but we failed to get " .. 1.165 + "an \"OK\" response from the online service") 1.166 + end 1.167 + end 1.168 + 1.169 + -- process "Assertion-MinProcVersion" constraint 1.170 + integrity.util.debug(2, "checking: Assertion-MinProcVersion: \"%s\"", cfg["Assertion-MinProcVersion"]) 1.171 + if cfg["Assertion-MinProcVersion"] == nil then 1.172 + return integrity.util.error(ctx, cfg, 1.173 + "license configuration is missing required \"Assertion-MinProcVersion\" parameter") 1.174 + end 1.175 + integrity.util.debug(3, "require: %s <= %s", cfg["Assertion-MinProcVersion"], integrity.version) 1.176 + if rpm.vercmp(cfg["Assertion-MinProcVersion"], integrity.version) > 0 then 1.177 + return integrity.util.error(ctx, cfg, 1.178 + "license configuration requires a license processor of " .. 1.179 + "at least version \"" .. cfg["Assertion-MinProcVersion"] .. "\"") 1.180 + end 1.181 + 1.182 + -- process "Assertion-OnlineReporting" constraint 1.183 + if cfg["Assertion-OnlineReporting"] ~= nil then 1.184 + integrity.util.debug(2, "checking: Assertion-OnlineReporting: \"%s\"", cfg["Assertion-OnlineReporting"]) 1.185 + local uuids = integrity.util.uuids() 1.186 + if uuids["UUID_REGISTRY"] == "" then 1.187 + uuids["UUID_REGISTRY"] = "unknown" 1.188 + end 1.189 + if uuids["UUID_INSTANCE"] == "" then 1.190 + uuids["UUID_INSTANCE"] = "unknown" 1.191 + end 1.192 + if uuids["UUID_PLATFORM"] == "" then 1.193 + uuids["UUID_PLATFORM"] = "unknown" 1.194 + end 1.195 + local request = cfg["Assertion-OnlineReporting"] 1.196 + request = request .. "?UUID_REGISTRY=" .. uuids["UUID_REGISTRY"] 1.197 + request = request .. "&UUID_INSTANCE=" .. uuids["UUID_INSTANCE"] 1.198 + request = request .. "&UUID_PLATFORM=" .. uuids["UUID_PLATFORM"] 1.199 + integrity.util.debug(3, "info: remote request \"%s\"", request) 1.200 + rpm.expand("%(nohup %{l_prefix}/bin/openpkg curl -s -L -R '" .. request .. "' >/dev/null 2>&1 &)") 1.201 + integrity.util.debug(3, "response: (ignored, because asynchronous operation)") 1.202 + end 1.203 + 1.204 + -- process "Assertion-Prefix" constraint 1.205 + if cfg["Assertion-Prefix"] ~= nil then 1.206 + integrity.util.debug(2, "checking: Assertion-Prefix: \"%s\"", cfg["Assertion-Prefix"]) 1.207 + local prefix = rpm.expand("%{l_prefix}") 1.208 + integrity.util.debug(3, "require: \"%s\" == \"%s\"", cfg["Assertion-Prefix"], prefix) 1.209 + if cfg["Assertion-Prefix"] ~= prefix then 1.210 + return integrity.util.error(ctx, cfg, 1.211 + "instance prefix \"" .. prefix .. "\" " .. 1.212 + "does not match value \"" .. cfg["Assertion-Prefix"] .. "\" of " .. 1.213 + "license configuration parameter \"Assertion-Prefix\"") 1.214 + end 1.215 + end 1.216 + 1.217 + -- process "Assertion-User" constraint 1.218 + if cfg["Assertion-User"] ~= nil then 1.219 + integrity.util.debug(2, "checking: Assertion-User: \"%s\"", cfg["Assertion-User"]) 1.220 + local user = rpm.expand("%{l_musr}") 1.221 + integrity.util.debug(3, "require: \"%s\" == \"%s\"", cfg["Assertion-User"], user) 1.222 + if cfg["Assertion-User"] ~= user then 1.223 + return integrity.util.error(ctx, cfg, 1.224 + "instance management user \"" .. user .. "\" " .. 1.225 + "does not match value \"" .. cfg["Assertion-User"] .. "\" of " .. 1.226 + "license configuration parameter \"Assertion-User\"") 1.227 + end 1.228 + end 1.229 + 1.230 + -- process "Assertion-Group" constraint 1.231 + if cfg["Assertion-Group"] ~= nil then 1.232 + integrity.util.debug(2, "checking: Assertion-Group: \"%s\"", cfg["Assertion-Group"]) 1.233 + local group = rpm.expand("%{l_mgrp}") 1.234 + integrity.util.debug(3, "require: \"%s\" == \"%s\"", cfg["Assertion-Group"], group) 1.235 + if cfg["Assertion-Group"] ~= group then 1.236 + return integrity.util.error(ctx, cfg, 1.237 + "instance management group \"" .. group .. "\" " .. 1.238 + "does not match value \"" .. cfg["Assertion-Group"] .. "\" of " .. 1.239 + "license configuration parameter \"Assertion-Group\"") 1.240 + end 1.241 + end 1.242 + 1.243 + -- process "Assertion-Domain" constraint 1.244 + if cfg["Assertion-Domain"] ~= nil then 1.245 + integrity.util.debug(2, "checking: Assertion-Domain: \"%s\"", cfg["Assertion-Domain"]) 1.246 + local domain = rpm.expand("%(%{l_shtool} echo -n -e '%d')") 1.247 + integrity.util.debug(3, "require: \"%s\" ~~ /(?s)^.*%s$/", domain, cfg["Assertion-Domain"]) 1.248 + local s, _, m = util.rmatch(domain, "(?s)^.*" .. cfg["Assertion-Domain"] .. "$") 1.249 + if s == nil then 1.250 + return integrity.util.error(ctx, cfg, 1.251 + "host domain \"" .. domain .. "\" " .. 1.252 + "does not end in pattern \"" .. cfg["Assertion-Domain"] .. "\") " .. 1.253 + "of license configuration parameter \"Assertion-Domain\"") 1.254 + end 1.255 + end 1.256 + 1.257 + -- process "Assertion-LifeTime" constraint 1.258 + if cfg["Assertion-LifeTime"] ~= nil then 1.259 + integrity.util.debug(2, "checking: Assertion-LifeTime: \"%s\"", cfg["Assertion-LifeTime"]) 1.260 + 1.261 + -- determine lifetime begin and end 1.262 + local lifetime = cfg["Assertion-LifeTime"] 1.263 + local s, _, m = util.rmatch(lifetime, "^(?s)(\\d{4})-(\\d{2})-(\\d{2})\\s*:\\s*(\\d{4})-(\\d{2})-(\\d{2})$") 1.264 + if s == nil then 1.265 + return integrity.util.error(ctx, cfg, 1.266 + "failed to extract time information from " .. 1.267 + "license configuration parameter \"Assertion-LifeTime\"") 1.268 + end 1.269 + local lifetime_begin = os.time({ 1.270 + year = tonumber(m[1]), 1.271 + month = tonumber(m[2]), 1.272 + day = tonumber(m[3]), 1.273 + hour = 0, 1.274 + min = 0, 1.275 + sec = 0 1.276 + }) 1.277 + local lifetime_end = os.time({ 1.278 + year = tonumber(m[4]), 1.279 + month = tonumber(m[5]), 1.280 + day = tonumber(m[6]), 1.281 + hour = 23, 1.282 + min = 59, 1.283 + sec = 59 1.284 + }) 1.285 + 1.286 + -- check whether current run-time is within lifetime 1.287 + local t_now = os.time() 1.288 + integrity.util.debug(3, "require: %d <= %d <= %d", lifetime_begin, t_now, lifetime_end) 1.289 + if not (lifetime_begin <= t_now and t_now <= lifetime_end) then 1.290 + return integrity.util.error(ctx, cfg, 1.291 + "current time \"" .. os.date("!%Y-%m-%d %H:%M:%S UTC", t_now) .. "\" " .. 1.292 + "is not within the timerange \"" .. cfg["Assertion-LifeTime"] .. "\" " .. 1.293 + "of license configuration parameter \"Assertion-LifeTime\"") 1.294 + end 1.295 + end 1.296 + 1.297 + -- process "Assertion-GrantTime" constraint 1.298 + if cfg["Assertion-GrantTime"] ~= nil then 1.299 + integrity.util.debug(2, "checking: Assertion-GrantTime: \"%s\"", cfg["Assertion-GrantTime"]) 1.300 + 1.301 + -- determine granttime begin and end 1.302 + local granttime = cfg["Assertion-GrantTime"] 1.303 + local s, _, m = util.rmatch(granttime, "^(?s)(\\d{4})-(\\d{2})-(\\d{2})\\s*:\\s*(\\d{4})-(\\d{2})-(\\d{2})$") 1.304 + if s == nil then 1.305 + return integrity.util.error(ctx, cfg, 1.306 + "failed to extract time information from " .. 1.307 + "license configuration parameter \"Assertion-GrantTime\"") 1.308 + end 1.309 + local granttime_begin = os.time({ 1.310 + year = tonumber(m[1]), 1.311 + month = tonumber(m[2]), 1.312 + day = tonumber(m[3]), 1.313 + hour = 0, 1.314 + min = 0, 1.315 + sec = 0 1.316 + }) 1.317 + local granttime_end = os.time({ 1.318 + year = tonumber(m[4]), 1.319 + month = tonumber(m[5]), 1.320 + day = tonumber(m[6]), 1.321 + hour = 23, 1.322 + min = 59, 1.323 + sec = 59 1.324 + }) 1.325 + 1.326 + -- determine OpenPKG Framework release time 1.327 + -- (allow openpkg.spec:%pre to override with a higher value for pre-checking) 1.328 + local t_release = 0 1.329 + local result = {} 1.330 + for _, line in ipairs(rpm.query("Q:%{RELEASE}", false, "openpkg")) do 1.331 + local s, _, m = util.rmatch(line, "(?s)^Q:(.+)$") 1.332 + if s ~= nil then 1.333 + table.insert(result, m[1]) 1.334 + end 1.335 + end 1.336 + if result[1] ~= nil then 1.337 + local s, _, m = util.rmatch(result[1], "^(?s)(\\d{4})(\\d{2})(\\d{2})$") 1.338 + if s ~= nil then 1.339 + t_release = os.time({ 1.340 + year = tonumber(m[1]), 1.341 + month = tonumber(m[2]), 1.342 + day = tonumber(m[3]), 1.343 + hour = 0, 1.344 + min = 0, 1.345 + sec = 0 1.346 + }) 1.347 + end 1.348 + end 1.349 + if t_release == 0 then 1.350 + return integrity.util.error(ctx, cfg, 1.351 + "failed to determine OpenPKG Framework release time") 1.352 + end 1.353 + local override = os.getenv("OPENPKG_FRAMEWORK_RELEASE") 1.354 + if override ~= nil then 1.355 + local s, _, m = util.rmatch(override, "^(?s)(\\d{4})(\\d{2})(\\d{2})$") 1.356 + if s ~= nil then 1.357 + local t_override = os.time({ 1.358 + year = tonumber(m[1]), 1.359 + month = tonumber(m[2]), 1.360 + day = tonumber(m[3]), 1.361 + hour = 0, 1.362 + min = 0, 1.363 + sec = 0 1.364 + }) 1.365 + if t_release < t_override then 1.366 + t_release = t_override 1.367 + end 1.368 + end 1.369 + end 1.370 + 1.371 + -- check whether current OpenPKG Framework release time is within granttime 1.372 + integrity.util.debug(3, "require: %d <= %d <= %d", granttime_begin, t_release, granttime_end) 1.373 + if not (granttime_begin <= t_release and t_release <= granttime_end) then 1.374 + return integrity.util.error(ctx, cfg, 1.375 + "current OpenPKG Framework release time \"" .. os.date("%Y-%m-%d", t_release) .. "\" " .. 1.376 + "is not within the timerange \"" .. cfg["Assertion-GrantTime"] .. "\" " .. 1.377 + "of license configuration parameter \"Assertion-GrantTime\"") 1.378 + end 1.379 + end 1.380 + 1.381 + -- process "Assertion-InstanceAge" constraint 1.382 + if cfg["Assertion-InstanceAge"] ~= nil then 1.383 + integrity.util.debug(2, "checking: Assertion-InstanceAge: \"%s\"", cfg["Assertion-InstanceAge"]) 1.384 + 1.385 + -- determine maximum instance age in seconds 1.386 + local t_diff_max = cfg["Assertion-InstanceAge"] 1.387 + t_diff_max = 0 + util.rsubst(t_diff_max, "^(\\d+)([smhdw])$", function (t, unit) 1.388 + if unit == "s" then t = t * 1 1.389 + elseif unit == "m" then t = t * 60 1.390 + elseif unit == "h" then t = t * 60 * 60 1.391 + elseif unit == "d" then t = t * 60 * 60 * 24 1.392 + elseif unit == "w" then t = t * 60 * 60 * 24 * 7 1.393 + end 1.394 + return t 1.395 + end) 1.396 + 1.397 + -- approach 1: determine install time via timestamp of UUID_REGISTRY 1.398 + local uuids = integrity.util.uuids() 1.399 + if uuids["UUID_REGISTRY"] == "" then 1.400 + return integrity.util.error(ctx, cfg, 1.401 + "failed to load UUID_REGISTRY") 1.402 + end 1.403 + txt = uuid.describe(uuids["UUID_REGISTRY"]) 1.404 + if txt == nil then 1.405 + return integrity.util.error(ctx, cfg, 1.406 + "failed to parse extracted UUID_REGISTRY string \"" .. uuids["UUID_REGISTRY"] .. "\" as an UUID") 1.407 + end 1.408 + local s, _, m = util.rmatch(txt, "(?s)^.*time:\\s+(\\d{4})-(\\d{2})-(\\d{2})\\s+(\\d{2}):(\\d{2}):(\\d{2}).*$") 1.409 + if s == nil then 1.410 + return integrity.util.error(ctx, cfg, 1.411 + "failed to extract timestamp from UUID_REGISTRY \"" .. uuids["UUID_REGISTRY"] .. "\"") 1.412 + end 1.413 + local t_install = os.time({ 1.414 + year = tonumber(m[1]), 1.415 + month = tonumber(m[2]), 1.416 + day = tonumber(m[3]), 1.417 + hour = tonumber(m[4]), 1.418 + min = tonumber(m[5]), 1.419 + sec = tonumber(m[6]) 1.420 + }) 1.421 + 1.422 + -- approach 2: determine install time via first install time of "openpkg" package 1.423 + local result = {} 1.424 + for _, line in ipairs(rpm.query( 1.425 + "Q:%|ORIGINTIME?{" .. 1.426 + "%{ORIGINTIME}" .. -- regular case: RPM 5 installed/updated with RPM 5 1.427 + "}:{" .. 1.428 + "%|INSTALLTIME?{" .. 1.429 + "%{INSTALLTIME}" .. -- special case: RPM 5 installed initially with RPM 4 1.430 + "}:{" .. 1.431 + "}|" .. 1.432 + "}|", false, "openpkg" 1.433 + )) do 1.434 + local s, _, m = util.rmatch(line, "(?s)^Q:(.+)$") 1.435 + if s ~= nil then 1.436 + table.insert(result, m[1]) 1.437 + end 1.438 + end 1.439 + if result[1] ~= nil then 1.440 + local n = tonumber(result[1]) 1.441 + if n > 0 then 1.442 + t_install = n 1.443 + end 1.444 + end 1.445 + 1.446 + -- check time difference 1.447 + local t_now = os.time() 1.448 + local t_diff = os.difftime(t_now, t_install) 1.449 + integrity.util.debug(3, "calc: %d - %d = %d", t_now, t_install, t_diff) 1.450 + if t_diff < 0 then 1.451 + return integrity.util.error(ctx, cfg, 1.452 + "current system time \"" .. t_now .. "\" is lower than " .. 1.453 + "instance installation time \"" .. t_install .. "\"") 1.454 + end 1.455 + integrity.util.debug(3, "require: %d <= %d", t_diff, t_diff_max) 1.456 + if t_diff > t_diff_max then 1.457 + return integrity.util.error(ctx, cfg, 1.458 + "instance age \"" .. t_diff .. "\" " .. 1.459 + "is greater than value \"" .. t_diff_max .. "\" (\"" .. cfg["Assertion-InstanceAge"] .. "\") " .. 1.460 + "of license configuration parameter \"Assertion-InstanceAge\"") 1.461 + end 1.462 + end 1.463 + 1.464 + -- process "Assertion-FromSourceOnTarget" constraint 1.465 + if cfg["Assertion-FromSourceOnTarget"] ~= nil then 1.466 + integrity.util.debug(2, "checking: Assertion-FromSourceOnTarget: \"%s\"", cfg["Assertion-FromSourceOnTarget"]) 1.467 + local hostname = rpm.hostname() 1.468 + for _, line in ipairs(rpm.query("Q:%{NAME}:%{BUILDHOST}", true, "*")) do 1.469 + local s, _, m = util.rmatch(line, "(?s)^Q:([^:]+):(.+)$") 1.470 + if s ~= nil then 1.471 + local name = m[1] 1.472 + local buildhost = m[2] 1.473 + integrity.util.debug(4, "info: name \"%s\", buildhost \"%s\"", name, buildhost) 1.474 + if not util.rmatch(name, "(?s)^gpg-.+$") and buildhost ~= "localhost" then 1.475 + if cfg["Assertion-FromSourceOnTarget"] == "yes" and buildhost ~= hostname then 1.476 + return integrity.util.error(ctx, cfg, 1.477 + "license-required \"build from source on target system only\" situation not met because " .. 1.478 + "package build host \"" .. buildhost .. "\" is not(!) equal to the package install host \"" .. hostname .. "\".") 1.479 + end 1.480 + if cfg["Assertion-FromSourceOnTarget"] == "no" and buildhost == hostname then 1.481 + return integrity.util.error(ctx, cfg, 1.482 + "license-required \"build binaries on separate build-host only\" situation not met because " .. 1.483 + "package build host \"" .. buildhost .. "\" is equal to the package install host \"" .. hostname .. "\".") 1.484 + end 1.485 + end 1.486 + end 1.487 + end 1.488 + end 1.489 + 1.490 + -- process "Assertion-PackageNames" constraints 1.491 + if cfg["Assertion-PackageNames"] ~= nil then 1.492 + integrity.util.debug(2, "checking: Assertion-PackageNames: \"%s\"", cfg["Assertion-PackageNames"]) 1.493 + 1.494 + -- query RPMDB for names of all installed packages 1.495 + local packages = {} 1.496 + for _, line in ipairs(rpm.query("Q:%{NAME}", true, "*")) do 1.497 + local s, _, m = util.rmatch(line, "(?s)^Q:(.+)$") 1.498 + if s ~= nil then 1.499 + table.insert(packages, m[1]) 1.500 + end 1.501 + end 1.502 + 1.503 + -- iterate over all constraints 1.504 + for _, constraint in 1.505 + ipairs( 1.506 + util.rsplit( 1.507 + util.rsubst( 1.508 + cfg["Assertion-PackageNames"], 1.509 + "(?s)^\\s*(.+?)\\s*$", "%1" 1.510 + ), 1.511 + "(?s)\\s+" 1.512 + ) 1.513 + ) do 1.514 + -- parse constraint 1.515 + local s, _, m = util.rmatch(constraint, "(?s)^(!?)([^:]+):(!?)(.+)$") 1.516 + if s == nil then 1.517 + return integrity.util.error(ctx, cfg, 1.518 + "invalid syntax in license configuration \"Assertion-PackageNames\" " .. 1.519 + "parameter: \"" .. constraint .. "\"") 1.520 + end 1.521 + local mode_negate = m[1] ~= "" 1.522 + local mode_regex = m[2] 1.523 + local package_negate = m[3] ~= "" 1.524 + local package_regex = m[4] 1.525 + -- apply the mode filter 1.526 + local mode_matches, _, _ = util.rmatch(ctx.rpm.mode, mode_regex); 1.527 + if (not mode_negate and mode_matches ~= nil) 1.528 + or ( mode_negate and mode_matches == nil) then 1.529 + -- apply the package filter to names of all installed packages 1.530 + for _, package in ipairs(packages) do 1.531 + if package_negate then 1.532 + integrity.util.debug(3, "require: \"%s\" !~ /%s/", package, package_regex) 1.533 + else 1.534 + integrity.util.debug(3, "require: \"%s\" ~~ /%s/", package, package_regex) 1.535 + end 1.536 + local package_matches, _, _ = util.rmatch(package, package_regex) 1.537 + if not ( (not package_negate and package_matches ~= nil) 1.538 + or ( package_negate and package_matches == nil)) then 1.539 + -- indicate integrity validation error 1.540 + return integrity.util.error(ctx, cfg, 1.541 + "installed package \"" .. package .. "\" " .. 1.542 + "under RPM run-time mode \"" .. ctx.rpm.mode .. "\" " .. 1.543 + "not covered by pattern \"" .. package_regex .. "\" " .. 1.544 + "of license configuration parameter \"Assertion-PackageNames\"") 1.545 + end 1.546 + end 1.547 + end 1.548 + end 1.549 + end 1.550 + 1.551 + -- process "Assertion-PackageReleaseAge" 1.552 + if cfg["Assertion-PackageReleaseAge"] ~= nil then 1.553 + integrity.util.debug(2, "checking: Assertion-PackageReleaseAge: \"%s[...]\"", string.sub(cfg["Assertion-PackageReleaseAge"], 1, 20)) 1.554 + 1.555 + -- parse constraint 1.556 + local constraint = cfg["Assertion-PackageReleaseAge"] 1.557 + local s, _, m = util.rmatch(constraint, "(?s)^([^:]+)%:([^:]+):([^\\s]+)\\s+(.+)$") 1.558 + if s == nil then 1.559 + return integrity.util.error(ctx, cfg, 1.560 + "invalid syntax in license configuration \"Assertion-PackageReleaseAge\" parameter") 1.561 + end 1.562 + local percent = m[1] / 100 1.563 + local offset = m[2] 1.564 + local distregex = m[3] 1.565 + local spec = m[4] 1.566 + 1.567 + -- determine maximum release time difference (in seconds) 1.568 + local t_diff_max = 0 + util.rsubst(offset, "^(\\d+)([smhdw])$", function (t, unit) 1.569 + if unit == "s" then t = t * 1 1.570 + elseif unit == "m" then t = t * 60 1.571 + elseif unit == "h" then t = t * 60 * 60 1.572 + elseif unit == "d" then t = t * 60 * 60 * 24 1.573 + elseif unit == "w" then t = t * 60 * 60 * 24 * 7 1.574 + end 1.575 + return t 1.576 + end) 1.577 + 1.578 + -- iterate over all package specifications to build release map 1.579 + local releases = {} 1.580 + for _, constraint in 1.581 + ipairs( 1.582 + util.rsplit( 1.583 + util.rsubst( 1.584 + spec, 1.585 + "(?s)^\\s*(.+?)\\s*$", "%1" 1.586 + ), 1.587 + "(?s)\\s+" 1.588 + ) 1.589 + ) do 1.590 + 1.591 + -- parse specification into package name and release constraint 1.592 + local s, _, m = util.rmatch(constraint, "(?s)^([^:]+):(.+)$") 1.593 + if s == nil then 1.594 + return integrity.util.error(ctx, cfg, 1.595 + "invalid syntax in license configuration \"Assertion-PackageReleaseAge\" " .. 1.596 + "parameter: \"" .. constraint .. "\"") 1.597 + end 1.598 + 1.599 + -- store result into release map 1.600 + releases[m[1]] = m[2] 1.601 + end 1.602 + 1.603 + -- query RPMDB for releases of all installed packages and decide 1.604 + -- whether the release time is inside or outside our constraint window 1.605 + local release_window_inside = 0 1.606 + local release_window_outside = 0 1.607 + local release_window_foreign = 0 1.608 + local release_window_unknown = 0 1.609 + for _, line in ipairs(rpm.query("Q:%{NAME}:%{RELEASE}:%{DISTRIBUTION}", true, "*")) do 1.610 + local s, _, m = util.rmatch(line, "(?s)^Q:([^:]+):(\\d\\d\\d\\d)(\\d\\d)(\\d\\d):(.+)$") 1.611 + if s ~= nil then 1.612 + -- parse query results 1.613 + local name = m[1] 1.614 + local t_release = os.time({ 1.615 + year = tonumber(m[2]), 1.616 + month = tonumber(m[3]), 1.617 + day = tonumber(m[4]), 1.618 + hour = 23, 1.619 + min = 59, 1.620 + sec = 59 1.621 + }) 1.622 + local dist = m[5] 1.623 + 1.624 + -- only check files of the constrained distribution(s) 1.625 + if util.rmatch(dist, "(?s)" .. distregex) then 1.626 + 1.627 + -- determine minimum release constraint 1.628 + local t_release_min = releases[name] 1.629 + if t_release_min == nil then 1.630 + t_release_min = releases["*"] 1.631 + end 1.632 + if t_release_min == nil then 1.633 + t_release_min = os.time() 1.634 + else 1.635 + local s, _, m = util.rmatch(t_release_min, "^(?s)(\\d{4})(\\d{2})(\\d{2})$") 1.636 + t_release_min = os.time({ 1.637 + year = tonumber(m[1]), 1.638 + month = tonumber(m[2]), 1.639 + day = tonumber(m[3]), 1.640 + hour = 0, 1.641 + min = 0, 1.642 + sec = 0 1.643 + }) 1.644 + end 1.645 + 1.646 + -- check time difference of package release 1.647 + local t_diff = os.difftime(t_release_min, t_release) 1.648 + integrity.util.debug(4, "calc: %d - %d = %d", t_release_min, t_release, t_diff) 1.649 + integrity.util.debug(4, "require: %d <= 0 or (%d > 0 and %d < %d)", t_diff, t_diff, t_diff, t_diff_max) 1.650 + if t_diff <= 0 or (t_diff > 0 and t_diff < t_diff_max) then 1.651 + release_window_inside = release_window_inside + 1 1.652 + else 1.653 + release_window_outside = release_window_outside + 1 1.654 + end 1.655 + else 1.656 + release_window_foreign = release_window_foreign + 1 1.657 + end 1.658 + else 1.659 + release_window_unknown = release_window_unknown + 1 1.660 + end 1.661 + end 1.662 + integrity.util.debug(3, "info: inside %d, outside %d, foreign %d, unknown %d", 1.663 + release_window_inside, release_window_outside, release_window_foreign, release_window_unknown) 1.664 + 1.665 + -- check validity of overall constraint 1.666 + local percent_inside = 1.667 + (release_window_inside / (release_window_inside + release_window_outside)) 1.668 + integrity.util.debug(3, "require: %d >= %d", percent_inside, percent) 1.669 + if percent_inside < percent then 1.670 + return integrity.util.error(ctx, cfg, 1.671 + "there are only " .. math.floor(percent_inside * 100) .. "% " .. 1.672 + "packages inside the release date constraint " .. 1.673 + "(expected a minimum of " .. math.floor(percent * 100) .. "%)") 1.674 + end 1.675 + end 1.676 + 1.677 + -- process "Assertion-Expression" constraint 1.678 + if cfg["Assertion-Expression"] ~= nil then 1.679 + integrity.util.debug(2, "checking: Assertion-Expression: \"%s\"", cfg["Assertion-Expression"]) 1.680 + 1.681 + -- expand special consytructs in expression 1.682 + local expr = cfg["Assertion-Expression"] 1.683 + expr = util.rsubst(expr, "(%\{[a-zA-Z_][a-zA-Z0-9_]+\})", function (str) 1.684 + return rpm.expand(str) 1.685 + end) 1.686 + expr = util.rsubst(expr, "\"((?:\\\\.|[^\"])+)\"\\s*~~\\s*/((?:\\\\.|[^/])+)/", function (str, regex) 1.687 + if util.rmatch(str, "(?s)" .. regex) ~= nil then 1.688 + return "true" 1.689 + else 1.690 + return "false" 1.691 + end 1.692 + end) 1.693 + 1.694 + -- evaluate expression 1.695 + integrity.util.debug(3, "evaluate: %s", expr) 1.696 + result = assert(loadstring(expr))() 1.697 + if type(result) ~= "boolean" then 1.698 + result = false 1.699 + end 1.700 + if not result then 1.701 + return integrity.util.error(ctx, cfg, 1.702 + "expression \"" .. cfg["Assertion-Expression"] .. "\" " .. 1.703 + "of license configuration parameter \"Assertion-Expression\"" .. 1.704 + "evaluated to false") 1.705 + end 1.706 + end 1.707 + 1.708 + -- indicate license integrity validation success 1.709 + return "OK" 1.710 +end 1.711 + 1.712 +- -- integrity processor utilities namespace 1.713 +integrity.util = {} 1.714 + 1.715 +- -- write debug information to stderr 1.716 +function integrity.util.debug(level_this, msg, ...) 1.717 + local level_min = os.getenv("OPENPKG_LICENSE_DEBUG") 1.718 + if level_min ~= nil then 1.719 + if type(level_min) == "string" then 1.720 + level_min = tonumber(level_min) 1.721 + end 1.722 + if type(level_min) ~= "number" then 1.723 + level_min = 0 1.724 + end 1.725 + if level_this <= level_min then 1.726 + local output 1.727 + if type(msg) == "function" then 1.728 + output = msg(...) 1.729 + else 1.730 + output = string.format(msg, ...) 1.731 + end 1.732 + local prefix = "" 1.733 + local i = 1 1.734 + while (i < level_this) do 1.735 + prefix = prefix .. " " 1.736 + i = i + 1 1.737 + end 1.738 + io.stderr:write("rpm: DEBUG: " .. prefix .. output .. "\n") 1.739 + end 1.740 + end 1.741 +end 1.742 + 1.743 +- -- load OpenPKG instance UUIDs 1.744 +function integrity.util.uuids() 1.745 + local uuids = { 1.746 + UUID_REGISTRY = "", 1.747 + UUID_INSTANCE = "", 1.748 + UUID_PLATFORM = "" 1.749 + } 1.750 + local filename = rpm.expand("%{l_prefix}/etc/openpkg/uuid") 1.751 + local txt = rpm.slurp(filename) 1.752 + if txt ~= nil then 1.753 + for name, _ in pairs(uuids) do 1.754 + local s, _, m = util.rmatch(txt, "(?s)^.*" .. name .. "=\"([^\"\"]+)\".*$") 1.755 + if s ~= nil then 1.756 + uuids[name] = m[1] 1.757 + end 1.758 + end 1.759 + end 1.760 + return uuids 1.761 +end 1.762 + 1.763 +- -- report validation warning 1.764 +function integrity.util.warning(ctx, cfg, warning) 1.765 + -- return prominent warning message 1.766 + return 1.767 + "WARNING: OpenPKG run-time license check failed -- continue processing\n" .. 1.768 + "+-----------------------------------------------------------------------------+\n" .. 1.769 + "| Attention, the OpenPKG RPM run-time integrity checking facility encountered a\n" .. 1.770 + "| non-fatal problem during license checking, but allows processing to continue.\n" .. 1.771 + "| The particular warning reported by the OpenPKG license processor is:\n" .. 1.772 + "|\n" .. 1.773 + util.textwrap("| ", warning, 60, 70) .. 1.774 + "|\n" .. 1.775 + "| Notice: Operation of the OpenPKG Framework requires a valid license.\n" .. 1.776 + "| Go to http://openpkg.com/go/framework-license for more details, please.\n" .. 1.777 + "+-----------------------------------------------------------------------------+" 1.778 +end 1.779 + 1.780 +- -- report validation error 1.781 +function integrity.util.error(ctx, cfg, error) 1.782 + -- support conversion of errors into warnings 1.783 + if cfg["Assertion-ErrorToWarning"] ~= nil then 1.784 + if cfg["Assertion-ErrorToWarning"] == "yes" then 1.785 + return integrity.util.warning(ctx, cfg, error) 1.786 + end 1.787 + end 1.788 + 1.789 + -- return prominent error message 1.790 + return 1.791 + "ERROR: OpenPKG run-time license check failed -- stopping processing\n" .. 1.792 + "+-----------------------------------------------------------------------------+\n" .. 1.793 + "| Sorry, the OpenPKG RPM run-time integrity checking facility encountered a\n" .. 1.794 + "| fatal problem during license checking and stops processing immediately.\n" .. 1.795 + "| The particular error reported by the OpenPKG license processor is:\n" .. 1.796 + "|\n" .. 1.797 + util.textwrap("| ", error, 60, 70) .. 1.798 + "|\n" .. 1.799 + "| Notice: Operation of the OpenPKG Framework requires a valid license.\n" .. 1.800 + "| Go to http://openpkg.com/go/framework-license for more details, please.\n" .. 1.801 + "| Run \"openpkg man license\" for details about local license management.\n" .. 1.802 + "+-----------------------------------------------------------------------------+" 1.803 +end 1.804 + 1.805 +-----BEGIN PGP SIGNATURE----- 1.806 +Comment: OpenPKG GmbH <openpkg@openpkg.com> 1.807 + 1.808 +iEYEARECAAYFAk8BelcACgkQZwQuyWG3rjTL6QCeLTLVj4PTnd/E7mf+Sv4mgbZj 1.809 +5J0AoMXrO4EimPSSCZSJ1TLW8f8GP+B5 1.810 +=AVpf 1.811 +-----END PGP SIGNATURE-----