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