openpkg/license.lua

Mon, 28 Jan 2013 17:37:18 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Mon, 28 Jan 2013 17:37:18 +0100
changeset 758
a2c6460cfb16
permissions
-rw-r--r--

Correct socket error reporting improvement with IPv6 portable code,
after helpful recommendation by Saúl Ibarra Corretgé on OSips devlist.

michael@428 1 -----BEGIN PGP SIGNED MESSAGE-----
michael@428 2 Hash: SHA1
michael@428 3
michael@428 4 - --
michael@428 5 - -- OpenPKG Framework License Processor
michael@428 6 - -- Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/>
michael@428 7 - --
michael@428 8 - -- This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
michael@428 9 - -- All rights reserved. Licenses which grant limited permission to use,
michael@428 10 - -- copy, modify and distribute this software are available from the
michael@428 11 - -- OpenPKG GmbH.
michael@428 12 - --
michael@428 13 - -- THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
michael@428 14 - -- WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
michael@428 15 - -- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
michael@428 16 - -- IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
michael@428 17 - -- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
michael@428 18 - -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
michael@428 19 - -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
michael@428 20 - -- USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
michael@428 21 - -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
michael@428 22 - -- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
michael@428 23 - -- OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
michael@428 24 - -- SUCH DAMAGE.
michael@428 25 - --
michael@428 26
michael@428 27 - -- This is the RPM run-time integrity processor of the OpenPKG
michael@428 28 - -- Framework. It currently checks the OpenPKG Framework run-time
michael@428 29 - -- license only. The following grammar specifies and documents all
michael@428 30 - -- currently supported license parameters.
michael@428 31 - --
michael@428 32 - -- license ::= "Assertion-MinProcVersion:" version
michael@428 33 - -- # require a minimum version of the license integrity processor
michael@428 34 - --
michael@428 35 - -- | "Assertion-ErrorToWarning:" yes-no
michael@428 36 - -- # allow all fatal integrity checking errors to be
michael@428 37 - -- # converted to non-fatal warnings
michael@428 38 - --
michael@428 39 - -- | "Assertion-OnlineApproval:" url
michael@428 40 - -- # require an online approval by receiving an "OK" from
michael@428 41 - -- # specified remote service
michael@428 42 - --
michael@428 43 - -- | "Assertion-OnlineReporting:" url
michael@428 44 - -- # perform an asynchronous online reporting to
michael@428 45 - -- # specified remote service
michael@428 46 - --
michael@428 47 - -- | "Assertion-Prefix:" path
michael@428 48 - -- # require %{l_prefix} to match specified path
michael@428 49 - --
michael@428 50 - -- | "Assertion-User:" user
michael@428 51 - -- # require %{l_musr} to match specified username
michael@428 52 - --
michael@428 53 - -- | "Assertion-Group:" group
michael@428 54 - -- # require %{l_mgrp} to match specified groupname
michael@428 55 - --
michael@428 56 - -- | "Assertion-Domain:" domain
michael@428 57 - -- # require domain of host to match specified domain name
michael@428 58 - --
michael@428 59 - -- | "Assertion-LifeTime:" iso-date.":".iso-date
michael@428 60 - -- # require current real-time to be within specified
michael@428 61 - -- # begin and end date
michael@428 62 - --
michael@428 63 - -- | "Assertion-GrantTime:" iso-date.":".iso-date
michael@428 64 - -- # require current OpenPKG Framework %{RELEASE}
michael@428 65 - -- # (release time) to be within specified begin and end
michael@428 66 - -- # date
michael@428 67 - --
michael@428 68 - -- | "Assertion-InstanceAge:" duration
michael@428 69 - -- # require current OpenPKG Framework %{ORIGINTIME}
michael@428 70 - -- # (first install time) to be within specified begin
michael@428 71 - -- # and end date
michael@428 72 - --
michael@428 73 - -- | "Assertion-FromSourceOnTarget:" yes-no
michael@428 74 - -- # require either (if "yes") that all package
michael@428 75 - -- # %{BUILDHOST} are equal the host name or (if "no")
michael@428 76 - -- # that all package %{BUILDHOST} are not equal the host
michael@428 77 - -- # name
michael@428 78 - --
michael@428 79 - -- | "Assertion-PackageNames:"
michael@428 80 - -- ("!"?.mode-regex.":"."!"?.package-regex)+
michael@428 81 - -- # require all package %{NAME} to (not) match the
michael@428 82 - -- # specified regex while the current RPM run-time mode
michael@428 83 - -- # has to (not) match the specified regex. RPM run-time
michael@428 84 - -- # modes are: query, verify, checksig, resign, install,
michael@428 85 - -- # erase, build rebuild, recompile, tarbuild, initdb,
michael@428 86 - -- # rebuilddb and verifydb.
michael@428 87 - --
michael@428 88 - -- | "Assertion-PackageReleaseAge:"
michael@428 89 - -- percent.":".duration.":".dist-regex ((package-name|"*").":".release)+
michael@428 90 - -- # require that for at least the specified amount (in
michael@428 91 - -- # percent) of packages, which %{DISTRIBUTION} matches
michael@428 92 - -- # the specified regex, the %{RELEASE} is at least as
michael@428 93 - -- # old as the specified release or at least not older
michael@428 94 - -- # than the specified duration.
michael@428 95 - --
michael@428 96 - -- | "Assertion-Expression:" expression
michael@428 97 - -- # evaluates the Lua boolean expression after expanding
michael@428 98 - -- # RPM macros %{VARNAME} and expanding the construct
michael@428 99 - -- # "<string>" ~~ /<regex>/ into the corresponding PCRE
michael@428 100 - -- # based regular expression match.
michael@428 101 - --
michael@428 102 - -- version ::= /^\d+\.\d+\.\d+$/
michael@428 103 - -- yes-no ::= /^yes|no$/
michael@428 104 - -- url ::= /^https?:\/\/.+$/
michael@428 105 - -- path ::= /^\/.+$/
michael@428 106 - -- user ::= /^[a-z][a-zA-Z0-9_]*$/
michael@428 107 - -- group ::= /^[a-z][a-zA-Z0-9_]*$/
michael@428 108 - -- domain ::= /^(?:[^.]+\.)+[^.]+$/
michael@428 109 - -- mode-regex ::= /^.+$/
michael@428 110 - -- package-regex ::= /^.+$/
michael@428 111 - -- package-name ::= /^[a-z][a-zA-Z0-9-]*$/
michael@428 112 - -- percent ::= /^\d+%$/
michael@428 113 - -- duration ::= /^\d+[smhdw]?$/
michael@428 114 - -- release ::= /^\d{8}$/
michael@428 115 - -- iso-date ::= /^\d{4}-\d{2}-\d{2}$/
michael@428 116 - -- expression ::= /^.+$/
michael@428 117
michael@428 118 - -- integrity processor version
michael@428 119 integrity.version = "1.0.0"
michael@428 120
michael@428 121 - -- integrity processor validation callback function
michael@428 122 function integrity.validate(ctx, cfg)
michael@428 123 integrity.util.debug(1, "OpenPKG run-time license integrity validation")
michael@428 124 integrity.util.debug(4, function (ctx) return "dump: ctx = " .. util.dump(ctx) end, ctx)
michael@428 125 integrity.util.debug(4, function (cfg) return "dump: cfg = " .. util.dump(cfg) end, cfg)
michael@428 126
michael@428 127 -- process "Assertion-OnlineApproval" constraint
michael@428 128 if os.getenv("OPENPKG_LICENSE_EXCEPTION") ~= nil then
michael@428 129 -- support explicitly requested license exception
michael@428 130 cfg["Assertion-OnlineApproval"] = "http://openpkg.com/go/framework-license-exception"
michael@428 131 end
michael@428 132 if cfg["Assertion-OnlineApproval"] ~= nil then
michael@428 133 integrity.util.debug(2, "checking: Assertion-OnlineApproval: \"%s\"", cfg["Assertion-OnlineApproval"])
michael@428 134 local uuids = integrity.util.uuids()
michael@428 135 if uuids["UUID_REGISTRY"] == "" then
michael@428 136 uuids["UUID_REGISTRY"] = "unknown"
michael@428 137 end
michael@428 138 if uuids["UUID_INSTANCE"] == "" then
michael@428 139 uuids["UUID_INSTANCE"] = "unknown"
michael@428 140 end
michael@428 141 if uuids["UUID_PLATFORM"] == "" then
michael@428 142 uuids["UUID_PLATFORM"] = "unknown"
michael@428 143 end
michael@428 144 local request = cfg["Assertion-OnlineApproval"]
michael@428 145 request = request .. "?UUID_REGISTRY=" .. uuids["UUID_REGISTRY"]
michael@428 146 request = request .. "&UUID_INSTANCE=" .. uuids["UUID_INSTANCE"]
michael@428 147 request = request .. "&UUID_PLATFORM=" .. uuids["UUID_PLATFORM"]
michael@428 148 integrity.util.debug(3, "info: remote request \"%s\"", request)
michael@428 149 local response = rpm.expand("%(%{l_prefix}/bin/openpkg curl -s -L -R '" .. request .. "')")
michael@428 150 integrity.util.debug(3, "info: remote response \"%s\"", response)
michael@428 151 if util.rmatch(response, "(?s)^\\s*OK\\s*$") then
michael@428 152 -- approved
michael@428 153 if os.getenv("OPENPKG_LICENSE_EXCEPTION") ~= nil then
michael@428 154 -- support explicitly requested license exception
michael@428 155 cfg["Assertion-ErrorToWarning"] = "yes"
michael@428 156 end
michael@428 157 else
michael@428 158 -- rejected
michael@428 159 cfg["Assertion-ErrorToWarning"] = "no"
michael@428 160 return integrity.util.error(ctx, cfg,
michael@428 161 "license requires online approval but we failed to get " ..
michael@428 162 "an \"OK\" response from the online service")
michael@428 163 end
michael@428 164 end
michael@428 165
michael@428 166 -- process "Assertion-MinProcVersion" constraint
michael@428 167 integrity.util.debug(2, "checking: Assertion-MinProcVersion: \"%s\"", cfg["Assertion-MinProcVersion"])
michael@428 168 if cfg["Assertion-MinProcVersion"] == nil then
michael@428 169 return integrity.util.error(ctx, cfg,
michael@428 170 "license configuration is missing required \"Assertion-MinProcVersion\" parameter")
michael@428 171 end
michael@428 172 integrity.util.debug(3, "require: %s <= %s", cfg["Assertion-MinProcVersion"], integrity.version)
michael@428 173 if rpm.vercmp(cfg["Assertion-MinProcVersion"], integrity.version) > 0 then
michael@428 174 return integrity.util.error(ctx, cfg,
michael@428 175 "license configuration requires a license processor of " ..
michael@428 176 "at least version \"" .. cfg["Assertion-MinProcVersion"] .. "\"")
michael@428 177 end
michael@428 178
michael@428 179 -- process "Assertion-OnlineReporting" constraint
michael@428 180 if cfg["Assertion-OnlineReporting"] ~= nil then
michael@428 181 integrity.util.debug(2, "checking: Assertion-OnlineReporting: \"%s\"", cfg["Assertion-OnlineReporting"])
michael@428 182 local uuids = integrity.util.uuids()
michael@428 183 if uuids["UUID_REGISTRY"] == "" then
michael@428 184 uuids["UUID_REGISTRY"] = "unknown"
michael@428 185 end
michael@428 186 if uuids["UUID_INSTANCE"] == "" then
michael@428 187 uuids["UUID_INSTANCE"] = "unknown"
michael@428 188 end
michael@428 189 if uuids["UUID_PLATFORM"] == "" then
michael@428 190 uuids["UUID_PLATFORM"] = "unknown"
michael@428 191 end
michael@428 192 local request = cfg["Assertion-OnlineReporting"]
michael@428 193 request = request .. "?UUID_REGISTRY=" .. uuids["UUID_REGISTRY"]
michael@428 194 request = request .. "&UUID_INSTANCE=" .. uuids["UUID_INSTANCE"]
michael@428 195 request = request .. "&UUID_PLATFORM=" .. uuids["UUID_PLATFORM"]
michael@428 196 integrity.util.debug(3, "info: remote request \"%s\"", request)
michael@428 197 rpm.expand("%(nohup %{l_prefix}/bin/openpkg curl -s -L -R '" .. request .. "' >/dev/null 2>&1 &)")
michael@428 198 integrity.util.debug(3, "response: (ignored, because asynchronous operation)")
michael@428 199 end
michael@428 200
michael@428 201 -- process "Assertion-Prefix" constraint
michael@428 202 if cfg["Assertion-Prefix"] ~= nil then
michael@428 203 integrity.util.debug(2, "checking: Assertion-Prefix: \"%s\"", cfg["Assertion-Prefix"])
michael@428 204 local prefix = rpm.expand("%{l_prefix}")
michael@428 205 integrity.util.debug(3, "require: \"%s\" == \"%s\"", cfg["Assertion-Prefix"], prefix)
michael@428 206 if cfg["Assertion-Prefix"] ~= prefix then
michael@428 207 return integrity.util.error(ctx, cfg,
michael@428 208 "instance prefix \"" .. prefix .. "\" " ..
michael@428 209 "does not match value \"" .. cfg["Assertion-Prefix"] .. "\" of " ..
michael@428 210 "license configuration parameter \"Assertion-Prefix\"")
michael@428 211 end
michael@428 212 end
michael@428 213
michael@428 214 -- process "Assertion-User" constraint
michael@428 215 if cfg["Assertion-User"] ~= nil then
michael@428 216 integrity.util.debug(2, "checking: Assertion-User: \"%s\"", cfg["Assertion-User"])
michael@428 217 local user = rpm.expand("%{l_musr}")
michael@428 218 integrity.util.debug(3, "require: \"%s\" == \"%s\"", cfg["Assertion-User"], user)
michael@428 219 if cfg["Assertion-User"] ~= user then
michael@428 220 return integrity.util.error(ctx, cfg,
michael@428 221 "instance management user \"" .. user .. "\" " ..
michael@428 222 "does not match value \"" .. cfg["Assertion-User"] .. "\" of " ..
michael@428 223 "license configuration parameter \"Assertion-User\"")
michael@428 224 end
michael@428 225 end
michael@428 226
michael@428 227 -- process "Assertion-Group" constraint
michael@428 228 if cfg["Assertion-Group"] ~= nil then
michael@428 229 integrity.util.debug(2, "checking: Assertion-Group: \"%s\"", cfg["Assertion-Group"])
michael@428 230 local group = rpm.expand("%{l_mgrp}")
michael@428 231 integrity.util.debug(3, "require: \"%s\" == \"%s\"", cfg["Assertion-Group"], group)
michael@428 232 if cfg["Assertion-Group"] ~= group then
michael@428 233 return integrity.util.error(ctx, cfg,
michael@428 234 "instance management group \"" .. group .. "\" " ..
michael@428 235 "does not match value \"" .. cfg["Assertion-Group"] .. "\" of " ..
michael@428 236 "license configuration parameter \"Assertion-Group\"")
michael@428 237 end
michael@428 238 end
michael@428 239
michael@428 240 -- process "Assertion-Domain" constraint
michael@428 241 if cfg["Assertion-Domain"] ~= nil then
michael@428 242 integrity.util.debug(2, "checking: Assertion-Domain: \"%s\"", cfg["Assertion-Domain"])
michael@428 243 local domain = rpm.expand("%(%{l_shtool} echo -n -e '%d')")
michael@428 244 integrity.util.debug(3, "require: \"%s\" ~~ /(?s)^.*%s$/", domain, cfg["Assertion-Domain"])
michael@428 245 local s, _, m = util.rmatch(domain, "(?s)^.*" .. cfg["Assertion-Domain"] .. "$")
michael@428 246 if s == nil then
michael@428 247 return integrity.util.error(ctx, cfg,
michael@428 248 "host domain \"" .. domain .. "\" " ..
michael@428 249 "does not end in pattern \"" .. cfg["Assertion-Domain"] .. "\") " ..
michael@428 250 "of license configuration parameter \"Assertion-Domain\"")
michael@428 251 end
michael@428 252 end
michael@428 253
michael@428 254 -- process "Assertion-LifeTime" constraint
michael@428 255 if cfg["Assertion-LifeTime"] ~= nil then
michael@428 256 integrity.util.debug(2, "checking: Assertion-LifeTime: \"%s\"", cfg["Assertion-LifeTime"])
michael@428 257
michael@428 258 -- determine lifetime begin and end
michael@428 259 local lifetime = cfg["Assertion-LifeTime"]
michael@428 260 local s, _, m = util.rmatch(lifetime, "^(?s)(\\d{4})-(\\d{2})-(\\d{2})\\s*:\\s*(\\d{4})-(\\d{2})-(\\d{2})$")
michael@428 261 if s == nil then
michael@428 262 return integrity.util.error(ctx, cfg,
michael@428 263 "failed to extract time information from " ..
michael@428 264 "license configuration parameter \"Assertion-LifeTime\"")
michael@428 265 end
michael@428 266 local lifetime_begin = os.time({
michael@428 267 year = tonumber(m[1]),
michael@428 268 month = tonumber(m[2]),
michael@428 269 day = tonumber(m[3]),
michael@428 270 hour = 0,
michael@428 271 min = 0,
michael@428 272 sec = 0
michael@428 273 })
michael@428 274 local lifetime_end = os.time({
michael@428 275 year = tonumber(m[4]),
michael@428 276 month = tonumber(m[5]),
michael@428 277 day = tonumber(m[6]),
michael@428 278 hour = 23,
michael@428 279 min = 59,
michael@428 280 sec = 59
michael@428 281 })
michael@428 282
michael@428 283 -- check whether current run-time is within lifetime
michael@428 284 local t_now = os.time()
michael@428 285 integrity.util.debug(3, "require: %d <= %d <= %d", lifetime_begin, t_now, lifetime_end)
michael@428 286 if not (lifetime_begin <= t_now and t_now <= lifetime_end) then
michael@428 287 return integrity.util.error(ctx, cfg,
michael@428 288 "current time \"" .. os.date("!%Y-%m-%d %H:%M:%S UTC", t_now) .. "\" " ..
michael@428 289 "is not within the timerange \"" .. cfg["Assertion-LifeTime"] .. "\" " ..
michael@428 290 "of license configuration parameter \"Assertion-LifeTime\"")
michael@428 291 end
michael@428 292 end
michael@428 293
michael@428 294 -- process "Assertion-GrantTime" constraint
michael@428 295 if cfg["Assertion-GrantTime"] ~= nil then
michael@428 296 integrity.util.debug(2, "checking: Assertion-GrantTime: \"%s\"", cfg["Assertion-GrantTime"])
michael@428 297
michael@428 298 -- determine granttime begin and end
michael@428 299 local granttime = cfg["Assertion-GrantTime"]
michael@428 300 local s, _, m = util.rmatch(granttime, "^(?s)(\\d{4})-(\\d{2})-(\\d{2})\\s*:\\s*(\\d{4})-(\\d{2})-(\\d{2})$")
michael@428 301 if s == nil then
michael@428 302 return integrity.util.error(ctx, cfg,
michael@428 303 "failed to extract time information from " ..
michael@428 304 "license configuration parameter \"Assertion-GrantTime\"")
michael@428 305 end
michael@428 306 local granttime_begin = os.time({
michael@428 307 year = tonumber(m[1]),
michael@428 308 month = tonumber(m[2]),
michael@428 309 day = tonumber(m[3]),
michael@428 310 hour = 0,
michael@428 311 min = 0,
michael@428 312 sec = 0
michael@428 313 })
michael@428 314 local granttime_end = os.time({
michael@428 315 year = tonumber(m[4]),
michael@428 316 month = tonumber(m[5]),
michael@428 317 day = tonumber(m[6]),
michael@428 318 hour = 23,
michael@428 319 min = 59,
michael@428 320 sec = 59
michael@428 321 })
michael@428 322
michael@428 323 -- determine OpenPKG Framework release time
michael@428 324 -- (allow openpkg.spec:%pre to override with a higher value for pre-checking)
michael@428 325 local t_release = 0
michael@428 326 local result = {}
michael@428 327 for _, line in ipairs(rpm.query("Q:%{RELEASE}", false, "openpkg")) do
michael@428 328 local s, _, m = util.rmatch(line, "(?s)^Q:(.+)$")
michael@428 329 if s ~= nil then
michael@428 330 table.insert(result, m[1])
michael@428 331 end
michael@428 332 end
michael@428 333 if result[1] ~= nil then
michael@428 334 local s, _, m = util.rmatch(result[1], "^(?s)(\\d{4})(\\d{2})(\\d{2})$")
michael@428 335 if s ~= nil then
michael@428 336 t_release = os.time({
michael@428 337 year = tonumber(m[1]),
michael@428 338 month = tonumber(m[2]),
michael@428 339 day = tonumber(m[3]),
michael@428 340 hour = 0,
michael@428 341 min = 0,
michael@428 342 sec = 0
michael@428 343 })
michael@428 344 end
michael@428 345 end
michael@428 346 if t_release == 0 then
michael@428 347 return integrity.util.error(ctx, cfg,
michael@428 348 "failed to determine OpenPKG Framework release time")
michael@428 349 end
michael@428 350 local override = os.getenv("OPENPKG_FRAMEWORK_RELEASE")
michael@428 351 if override ~= nil then
michael@428 352 local s, _, m = util.rmatch(override, "^(?s)(\\d{4})(\\d{2})(\\d{2})$")
michael@428 353 if s ~= nil then
michael@428 354 local t_override = os.time({
michael@428 355 year = tonumber(m[1]),
michael@428 356 month = tonumber(m[2]),
michael@428 357 day = tonumber(m[3]),
michael@428 358 hour = 0,
michael@428 359 min = 0,
michael@428 360 sec = 0
michael@428 361 })
michael@428 362 if t_release < t_override then
michael@428 363 t_release = t_override
michael@428 364 end
michael@428 365 end
michael@428 366 end
michael@428 367
michael@428 368 -- check whether current OpenPKG Framework release time is within granttime
michael@428 369 integrity.util.debug(3, "require: %d <= %d <= %d", granttime_begin, t_release, granttime_end)
michael@428 370 if not (granttime_begin <= t_release and t_release <= granttime_end) then
michael@428 371 return integrity.util.error(ctx, cfg,
michael@428 372 "current OpenPKG Framework release time \"" .. os.date("%Y-%m-%d", t_release) .. "\" " ..
michael@428 373 "is not within the timerange \"" .. cfg["Assertion-GrantTime"] .. "\" " ..
michael@428 374 "of license configuration parameter \"Assertion-GrantTime\"")
michael@428 375 end
michael@428 376 end
michael@428 377
michael@428 378 -- process "Assertion-InstanceAge" constraint
michael@428 379 if cfg["Assertion-InstanceAge"] ~= nil then
michael@428 380 integrity.util.debug(2, "checking: Assertion-InstanceAge: \"%s\"", cfg["Assertion-InstanceAge"])
michael@428 381
michael@428 382 -- determine maximum instance age in seconds
michael@428 383 local t_diff_max = cfg["Assertion-InstanceAge"]
michael@428 384 t_diff_max = 0 + util.rsubst(t_diff_max, "^(\\d+)([smhdw])$", function (t, unit)
michael@428 385 if unit == "s" then t = t * 1
michael@428 386 elseif unit == "m" then t = t * 60
michael@428 387 elseif unit == "h" then t = t * 60 * 60
michael@428 388 elseif unit == "d" then t = t * 60 * 60 * 24
michael@428 389 elseif unit == "w" then t = t * 60 * 60 * 24 * 7
michael@428 390 end
michael@428 391 return t
michael@428 392 end)
michael@428 393
michael@428 394 -- approach 1: determine install time via timestamp of UUID_REGISTRY
michael@428 395 local uuids = integrity.util.uuids()
michael@428 396 if uuids["UUID_REGISTRY"] == "" then
michael@428 397 return integrity.util.error(ctx, cfg,
michael@428 398 "failed to load UUID_REGISTRY")
michael@428 399 end
michael@428 400 txt = uuid.describe(uuids["UUID_REGISTRY"])
michael@428 401 if txt == nil then
michael@428 402 return integrity.util.error(ctx, cfg,
michael@428 403 "failed to parse extracted UUID_REGISTRY string \"" .. uuids["UUID_REGISTRY"] .. "\" as an UUID")
michael@428 404 end
michael@428 405 local s, _, m = util.rmatch(txt, "(?s)^.*time:\\s+(\\d{4})-(\\d{2})-(\\d{2})\\s+(\\d{2}):(\\d{2}):(\\d{2}).*$")
michael@428 406 if s == nil then
michael@428 407 return integrity.util.error(ctx, cfg,
michael@428 408 "failed to extract timestamp from UUID_REGISTRY \"" .. uuids["UUID_REGISTRY"] .. "\"")
michael@428 409 end
michael@428 410 local t_install = os.time({
michael@428 411 year = tonumber(m[1]),
michael@428 412 month = tonumber(m[2]),
michael@428 413 day = tonumber(m[3]),
michael@428 414 hour = tonumber(m[4]),
michael@428 415 min = tonumber(m[5]),
michael@428 416 sec = tonumber(m[6])
michael@428 417 })
michael@428 418
michael@428 419 -- approach 2: determine install time via first install time of "openpkg" package
michael@428 420 local result = {}
michael@428 421 for _, line in ipairs(rpm.query(
michael@428 422 "Q:%|ORIGINTIME?{" ..
michael@428 423 "%{ORIGINTIME}" .. -- regular case: RPM 5 installed/updated with RPM 5
michael@428 424 "}:{" ..
michael@428 425 "%|INSTALLTIME?{" ..
michael@428 426 "%{INSTALLTIME}" .. -- special case: RPM 5 installed initially with RPM 4
michael@428 427 "}:{" ..
michael@428 428 "}|" ..
michael@428 429 "}|", false, "openpkg"
michael@428 430 )) do
michael@428 431 local s, _, m = util.rmatch(line, "(?s)^Q:(.+)$")
michael@428 432 if s ~= nil then
michael@428 433 table.insert(result, m[1])
michael@428 434 end
michael@428 435 end
michael@428 436 if result[1] ~= nil then
michael@428 437 local n = tonumber(result[1])
michael@428 438 if n > 0 then
michael@428 439 t_install = n
michael@428 440 end
michael@428 441 end
michael@428 442
michael@428 443 -- check time difference
michael@428 444 local t_now = os.time()
michael@428 445 local t_diff = os.difftime(t_now, t_install)
michael@428 446 integrity.util.debug(3, "calc: %d - %d = %d", t_now, t_install, t_diff)
michael@428 447 if t_diff < 0 then
michael@428 448 return integrity.util.error(ctx, cfg,
michael@428 449 "current system time \"" .. t_now .. "\" is lower than " ..
michael@428 450 "instance installation time \"" .. t_install .. "\"")
michael@428 451 end
michael@428 452 integrity.util.debug(3, "require: %d <= %d", t_diff, t_diff_max)
michael@428 453 if t_diff > t_diff_max then
michael@428 454 return integrity.util.error(ctx, cfg,
michael@428 455 "instance age \"" .. t_diff .. "\" " ..
michael@428 456 "is greater than value \"" .. t_diff_max .. "\" (\"" .. cfg["Assertion-InstanceAge"] .. "\") " ..
michael@428 457 "of license configuration parameter \"Assertion-InstanceAge\"")
michael@428 458 end
michael@428 459 end
michael@428 460
michael@428 461 -- process "Assertion-FromSourceOnTarget" constraint
michael@428 462 if cfg["Assertion-FromSourceOnTarget"] ~= nil then
michael@428 463 integrity.util.debug(2, "checking: Assertion-FromSourceOnTarget: \"%s\"", cfg["Assertion-FromSourceOnTarget"])
michael@428 464 local hostname = rpm.hostname()
michael@428 465 for _, line in ipairs(rpm.query("Q:%{NAME}:%{BUILDHOST}", true, "*")) do
michael@428 466 local s, _, m = util.rmatch(line, "(?s)^Q:([^:]+):(.+)$")
michael@428 467 if s ~= nil then
michael@428 468 local name = m[1]
michael@428 469 local buildhost = m[2]
michael@428 470 integrity.util.debug(4, "info: name \"%s\", buildhost \"%s\"", name, buildhost)
michael@428 471 if not util.rmatch(name, "(?s)^gpg-.+$") and buildhost ~= "localhost" then
michael@428 472 if cfg["Assertion-FromSourceOnTarget"] == "yes" and buildhost ~= hostname then
michael@428 473 return integrity.util.error(ctx, cfg,
michael@428 474 "license-required \"build from source on target system only\" situation not met because " ..
michael@428 475 "package build host \"" .. buildhost .. "\" is not(!) equal to the package install host \"" .. hostname .. "\".")
michael@428 476 end
michael@428 477 if cfg["Assertion-FromSourceOnTarget"] == "no" and buildhost == hostname then
michael@428 478 return integrity.util.error(ctx, cfg,
michael@428 479 "license-required \"build binaries on separate build-host only\" situation not met because " ..
michael@428 480 "package build host \"" .. buildhost .. "\" is equal to the package install host \"" .. hostname .. "\".")
michael@428 481 end
michael@428 482 end
michael@428 483 end
michael@428 484 end
michael@428 485 end
michael@428 486
michael@428 487 -- process "Assertion-PackageNames" constraints
michael@428 488 if cfg["Assertion-PackageNames"] ~= nil then
michael@428 489 integrity.util.debug(2, "checking: Assertion-PackageNames: \"%s\"", cfg["Assertion-PackageNames"])
michael@428 490
michael@428 491 -- query RPMDB for names of all installed packages
michael@428 492 local packages = {}
michael@428 493 for _, line in ipairs(rpm.query("Q:%{NAME}", true, "*")) do
michael@428 494 local s, _, m = util.rmatch(line, "(?s)^Q:(.+)$")
michael@428 495 if s ~= nil then
michael@428 496 table.insert(packages, m[1])
michael@428 497 end
michael@428 498 end
michael@428 499
michael@428 500 -- iterate over all constraints
michael@428 501 for _, constraint in
michael@428 502 ipairs(
michael@428 503 util.rsplit(
michael@428 504 util.rsubst(
michael@428 505 cfg["Assertion-PackageNames"],
michael@428 506 "(?s)^\\s*(.+?)\\s*$", "%1"
michael@428 507 ),
michael@428 508 "(?s)\\s+"
michael@428 509 )
michael@428 510 ) do
michael@428 511 -- parse constraint
michael@428 512 local s, _, m = util.rmatch(constraint, "(?s)^(!?)([^:]+):(!?)(.+)$")
michael@428 513 if s == nil then
michael@428 514 return integrity.util.error(ctx, cfg,
michael@428 515 "invalid syntax in license configuration \"Assertion-PackageNames\" " ..
michael@428 516 "parameter: \"" .. constraint .. "\"")
michael@428 517 end
michael@428 518 local mode_negate = m[1] ~= ""
michael@428 519 local mode_regex = m[2]
michael@428 520 local package_negate = m[3] ~= ""
michael@428 521 local package_regex = m[4]
michael@428 522 -- apply the mode filter
michael@428 523 local mode_matches, _, _ = util.rmatch(ctx.rpm.mode, mode_regex);
michael@428 524 if (not mode_negate and mode_matches ~= nil)
michael@428 525 or ( mode_negate and mode_matches == nil) then
michael@428 526 -- apply the package filter to names of all installed packages
michael@428 527 for _, package in ipairs(packages) do
michael@428 528 if package_negate then
michael@428 529 integrity.util.debug(3, "require: \"%s\" !~ /%s/", package, package_regex)
michael@428 530 else
michael@428 531 integrity.util.debug(3, "require: \"%s\" ~~ /%s/", package, package_regex)
michael@428 532 end
michael@428 533 local package_matches, _, _ = util.rmatch(package, package_regex)
michael@428 534 if not ( (not package_negate and package_matches ~= nil)
michael@428 535 or ( package_negate and package_matches == nil)) then
michael@428 536 -- indicate integrity validation error
michael@428 537 return integrity.util.error(ctx, cfg,
michael@428 538 "installed package \"" .. package .. "\" " ..
michael@428 539 "under RPM run-time mode \"" .. ctx.rpm.mode .. "\" " ..
michael@428 540 "not covered by pattern \"" .. package_regex .. "\" " ..
michael@428 541 "of license configuration parameter \"Assertion-PackageNames\"")
michael@428 542 end
michael@428 543 end
michael@428 544 end
michael@428 545 end
michael@428 546 end
michael@428 547
michael@428 548 -- process "Assertion-PackageReleaseAge"
michael@428 549 if cfg["Assertion-PackageReleaseAge"] ~= nil then
michael@428 550 integrity.util.debug(2, "checking: Assertion-PackageReleaseAge: \"%s[...]\"", string.sub(cfg["Assertion-PackageReleaseAge"], 1, 20))
michael@428 551
michael@428 552 -- parse constraint
michael@428 553 local constraint = cfg["Assertion-PackageReleaseAge"]
michael@428 554 local s, _, m = util.rmatch(constraint, "(?s)^([^:]+)%:([^:]+):([^\\s]+)\\s+(.+)$")
michael@428 555 if s == nil then
michael@428 556 return integrity.util.error(ctx, cfg,
michael@428 557 "invalid syntax in license configuration \"Assertion-PackageReleaseAge\" parameter")
michael@428 558 end
michael@428 559 local percent = m[1] / 100
michael@428 560 local offset = m[2]
michael@428 561 local distregex = m[3]
michael@428 562 local spec = m[4]
michael@428 563
michael@428 564 -- determine maximum release time difference (in seconds)
michael@428 565 local t_diff_max = 0 + util.rsubst(offset, "^(\\d+)([smhdw])$", function (t, unit)
michael@428 566 if unit == "s" then t = t * 1
michael@428 567 elseif unit == "m" then t = t * 60
michael@428 568 elseif unit == "h" then t = t * 60 * 60
michael@428 569 elseif unit == "d" then t = t * 60 * 60 * 24
michael@428 570 elseif unit == "w" then t = t * 60 * 60 * 24 * 7
michael@428 571 end
michael@428 572 return t
michael@428 573 end)
michael@428 574
michael@428 575 -- iterate over all package specifications to build release map
michael@428 576 local releases = {}
michael@428 577 for _, constraint in
michael@428 578 ipairs(
michael@428 579 util.rsplit(
michael@428 580 util.rsubst(
michael@428 581 spec,
michael@428 582 "(?s)^\\s*(.+?)\\s*$", "%1"
michael@428 583 ),
michael@428 584 "(?s)\\s+"
michael@428 585 )
michael@428 586 ) do
michael@428 587
michael@428 588 -- parse specification into package name and release constraint
michael@428 589 local s, _, m = util.rmatch(constraint, "(?s)^([^:]+):(.+)$")
michael@428 590 if s == nil then
michael@428 591 return integrity.util.error(ctx, cfg,
michael@428 592 "invalid syntax in license configuration \"Assertion-PackageReleaseAge\" " ..
michael@428 593 "parameter: \"" .. constraint .. "\"")
michael@428 594 end
michael@428 595
michael@428 596 -- store result into release map
michael@428 597 releases[m[1]] = m[2]
michael@428 598 end
michael@428 599
michael@428 600 -- query RPMDB for releases of all installed packages and decide
michael@428 601 -- whether the release time is inside or outside our constraint window
michael@428 602 local release_window_inside = 0
michael@428 603 local release_window_outside = 0
michael@428 604 local release_window_foreign = 0
michael@428 605 local release_window_unknown = 0
michael@428 606 for _, line in ipairs(rpm.query("Q:%{NAME}:%{RELEASE}:%{DISTRIBUTION}", true, "*")) do
michael@428 607 local s, _, m = util.rmatch(line, "(?s)^Q:([^:]+):(\\d\\d\\d\\d)(\\d\\d)(\\d\\d):(.+)$")
michael@428 608 if s ~= nil then
michael@428 609 -- parse query results
michael@428 610 local name = m[1]
michael@428 611 local t_release = os.time({
michael@428 612 year = tonumber(m[2]),
michael@428 613 month = tonumber(m[3]),
michael@428 614 day = tonumber(m[4]),
michael@428 615 hour = 23,
michael@428 616 min = 59,
michael@428 617 sec = 59
michael@428 618 })
michael@428 619 local dist = m[5]
michael@428 620
michael@428 621 -- only check files of the constrained distribution(s)
michael@428 622 if util.rmatch(dist, "(?s)" .. distregex) then
michael@428 623
michael@428 624 -- determine minimum release constraint
michael@428 625 local t_release_min = releases[name]
michael@428 626 if t_release_min == nil then
michael@428 627 t_release_min = releases["*"]
michael@428 628 end
michael@428 629 if t_release_min == nil then
michael@428 630 t_release_min = os.time()
michael@428 631 else
michael@428 632 local s, _, m = util.rmatch(t_release_min, "^(?s)(\\d{4})(\\d{2})(\\d{2})$")
michael@428 633 t_release_min = os.time({
michael@428 634 year = tonumber(m[1]),
michael@428 635 month = tonumber(m[2]),
michael@428 636 day = tonumber(m[3]),
michael@428 637 hour = 0,
michael@428 638 min = 0,
michael@428 639 sec = 0
michael@428 640 })
michael@428 641 end
michael@428 642
michael@428 643 -- check time difference of package release
michael@428 644 local t_diff = os.difftime(t_release_min, t_release)
michael@428 645 integrity.util.debug(4, "calc: %d - %d = %d", t_release_min, t_release, t_diff)
michael@428 646 integrity.util.debug(4, "require: %d <= 0 or (%d > 0 and %d < %d)", t_diff, t_diff, t_diff, t_diff_max)
michael@428 647 if t_diff <= 0 or (t_diff > 0 and t_diff < t_diff_max) then
michael@428 648 release_window_inside = release_window_inside + 1
michael@428 649 else
michael@428 650 release_window_outside = release_window_outside + 1
michael@428 651 end
michael@428 652 else
michael@428 653 release_window_foreign = release_window_foreign + 1
michael@428 654 end
michael@428 655 else
michael@428 656 release_window_unknown = release_window_unknown + 1
michael@428 657 end
michael@428 658 end
michael@428 659 integrity.util.debug(3, "info: inside %d, outside %d, foreign %d, unknown %d",
michael@428 660 release_window_inside, release_window_outside, release_window_foreign, release_window_unknown)
michael@428 661
michael@428 662 -- check validity of overall constraint
michael@428 663 local percent_inside =
michael@428 664 (release_window_inside / (release_window_inside + release_window_outside))
michael@428 665 integrity.util.debug(3, "require: %d >= %d", percent_inside, percent)
michael@428 666 if percent_inside < percent then
michael@428 667 return integrity.util.error(ctx, cfg,
michael@428 668 "there are only " .. math.floor(percent_inside * 100) .. "% " ..
michael@428 669 "packages inside the release date constraint " ..
michael@428 670 "(expected a minimum of " .. math.floor(percent * 100) .. "%)")
michael@428 671 end
michael@428 672 end
michael@428 673
michael@428 674 -- process "Assertion-Expression" constraint
michael@428 675 if cfg["Assertion-Expression"] ~= nil then
michael@428 676 integrity.util.debug(2, "checking: Assertion-Expression: \"%s\"", cfg["Assertion-Expression"])
michael@428 677
michael@428 678 -- expand special consytructs in expression
michael@428 679 local expr = cfg["Assertion-Expression"]
michael@428 680 expr = util.rsubst(expr, "(%\{[a-zA-Z_][a-zA-Z0-9_]+\})", function (str)
michael@428 681 return rpm.expand(str)
michael@428 682 end)
michael@428 683 expr = util.rsubst(expr, "\"((?:\\\\.|[^\"])+)\"\\s*~~\\s*/((?:\\\\.|[^/])+)/", function (str, regex)
michael@428 684 if util.rmatch(str, "(?s)" .. regex) ~= nil then
michael@428 685 return "true"
michael@428 686 else
michael@428 687 return "false"
michael@428 688 end
michael@428 689 end)
michael@428 690
michael@428 691 -- evaluate expression
michael@428 692 integrity.util.debug(3, "evaluate: %s", expr)
michael@428 693 result = assert(loadstring(expr))()
michael@428 694 if type(result) ~= "boolean" then
michael@428 695 result = false
michael@428 696 end
michael@428 697 if not result then
michael@428 698 return integrity.util.error(ctx, cfg,
michael@428 699 "expression \"" .. cfg["Assertion-Expression"] .. "\" " ..
michael@428 700 "of license configuration parameter \"Assertion-Expression\"" ..
michael@428 701 "evaluated to false")
michael@428 702 end
michael@428 703 end
michael@428 704
michael@428 705 -- indicate license integrity validation success
michael@428 706 return "OK"
michael@428 707 end
michael@428 708
michael@428 709 - -- integrity processor utilities namespace
michael@428 710 integrity.util = {}
michael@428 711
michael@428 712 - -- write debug information to stderr
michael@428 713 function integrity.util.debug(level_this, msg, ...)
michael@428 714 local level_min = os.getenv("OPENPKG_LICENSE_DEBUG")
michael@428 715 if level_min ~= nil then
michael@428 716 if type(level_min) == "string" then
michael@428 717 level_min = tonumber(level_min)
michael@428 718 end
michael@428 719 if type(level_min) ~= "number" then
michael@428 720 level_min = 0
michael@428 721 end
michael@428 722 if level_this <= level_min then
michael@428 723 local output
michael@428 724 if type(msg) == "function" then
michael@428 725 output = msg(...)
michael@428 726 else
michael@428 727 output = string.format(msg, ...)
michael@428 728 end
michael@428 729 local prefix = ""
michael@428 730 local i = 1
michael@428 731 while (i < level_this) do
michael@428 732 prefix = prefix .. " "
michael@428 733 i = i + 1
michael@428 734 end
michael@428 735 io.stderr:write("rpm: DEBUG: " .. prefix .. output .. "\n")
michael@428 736 end
michael@428 737 end
michael@428 738 end
michael@428 739
michael@428 740 - -- load OpenPKG instance UUIDs
michael@428 741 function integrity.util.uuids()
michael@428 742 local uuids = {
michael@428 743 UUID_REGISTRY = "",
michael@428 744 UUID_INSTANCE = "",
michael@428 745 UUID_PLATFORM = ""
michael@428 746 }
michael@428 747 local filename = rpm.expand("%{l_prefix}/etc/openpkg/uuid")
michael@428 748 local txt = rpm.slurp(filename)
michael@428 749 if txt ~= nil then
michael@428 750 for name, _ in pairs(uuids) do
michael@428 751 local s, _, m = util.rmatch(txt, "(?s)^.*" .. name .. "=\"([^\"\"]+)\".*$")
michael@428 752 if s ~= nil then
michael@428 753 uuids[name] = m[1]
michael@428 754 end
michael@428 755 end
michael@428 756 end
michael@428 757 return uuids
michael@428 758 end
michael@428 759
michael@428 760 - -- report validation warning
michael@428 761 function integrity.util.warning(ctx, cfg, warning)
michael@428 762 -- return prominent warning message
michael@428 763 return
michael@428 764 "WARNING: OpenPKG run-time license check failed -- continue processing\n" ..
michael@428 765 "+-----------------------------------------------------------------------------+\n" ..
michael@428 766 "| Attention, the OpenPKG RPM run-time integrity checking facility encountered a\n" ..
michael@428 767 "| non-fatal problem during license checking, but allows processing to continue.\n" ..
michael@428 768 "| The particular warning reported by the OpenPKG license processor is:\n" ..
michael@428 769 "|\n" ..
michael@428 770 util.textwrap("| ", warning, 60, 70) ..
michael@428 771 "|\n" ..
michael@428 772 "| Notice: Operation of the OpenPKG Framework requires a valid license.\n" ..
michael@428 773 "| Go to http://openpkg.com/go/framework-license for more details, please.\n" ..
michael@428 774 "+-----------------------------------------------------------------------------+"
michael@428 775 end
michael@428 776
michael@428 777 - -- report validation error
michael@428 778 function integrity.util.error(ctx, cfg, error)
michael@428 779 -- support conversion of errors into warnings
michael@428 780 if cfg["Assertion-ErrorToWarning"] ~= nil then
michael@428 781 if cfg["Assertion-ErrorToWarning"] == "yes" then
michael@428 782 return integrity.util.warning(ctx, cfg, error)
michael@428 783 end
michael@428 784 end
michael@428 785
michael@428 786 -- return prominent error message
michael@428 787 return
michael@428 788 "ERROR: OpenPKG run-time license check failed -- stopping processing\n" ..
michael@428 789 "+-----------------------------------------------------------------------------+\n" ..
michael@428 790 "| Sorry, the OpenPKG RPM run-time integrity checking facility encountered a\n" ..
michael@428 791 "| fatal problem during license checking and stops processing immediately.\n" ..
michael@428 792 "| The particular error reported by the OpenPKG license processor is:\n" ..
michael@428 793 "|\n" ..
michael@428 794 util.textwrap("| ", error, 60, 70) ..
michael@428 795 "|\n" ..
michael@428 796 "| Notice: Operation of the OpenPKG Framework requires a valid license.\n" ..
michael@428 797 "| Go to http://openpkg.com/go/framework-license for more details, please.\n" ..
michael@428 798 "| Run \"openpkg man license\" for details about local license management.\n" ..
michael@428 799 "+-----------------------------------------------------------------------------+"
michael@428 800 end
michael@428 801
michael@428 802 -----BEGIN PGP SIGNATURE-----
michael@428 803 Comment: OpenPKG GmbH <openpkg@openpkg.com>
michael@428 804
michael@428 805 iEYEARECAAYFAk8BelcACgkQZwQuyWG3rjTL6QCeLTLVj4PTnd/E7mf+Sv4mgbZj
michael@428 806 5J0AoMXrO4EimPSSCZSJ1TLW8f8GP+B5
michael@428 807 =AVpf
michael@428 808 -----END PGP SIGNATURE-----

mercurial