|
1 -----BEGIN PGP SIGNED MESSAGE----- |
|
2 Hash: SHA1 |
|
3 |
|
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 - -- |
|
26 |
|
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 ::= /^.+$/ |
|
117 |
|
118 - -- integrity processor version |
|
119 integrity.version = "1.0.0" |
|
120 |
|
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) |
|
126 |
|
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 |
|
165 |
|
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 |
|
178 |
|
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 |
|
200 |
|
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 |
|
213 |
|
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 |
|
226 |
|
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 |
|
239 |
|
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 |
|
253 |
|
254 -- process "Assertion-LifeTime" constraint |
|
255 if cfg["Assertion-LifeTime"] ~= nil then |
|
256 integrity.util.debug(2, "checking: Assertion-LifeTime: \"%s\"", cfg["Assertion-LifeTime"]) |
|
257 |
|
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 }) |
|
282 |
|
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 |
|
293 |
|
294 -- process "Assertion-GrantTime" constraint |
|
295 if cfg["Assertion-GrantTime"] ~= nil then |
|
296 integrity.util.debug(2, "checking: Assertion-GrantTime: \"%s\"", cfg["Assertion-GrantTime"]) |
|
297 |
|
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 }) |
|
322 |
|
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 |
|
367 |
|
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 |
|
377 |
|
378 -- process "Assertion-InstanceAge" constraint |
|
379 if cfg["Assertion-InstanceAge"] ~= nil then |
|
380 integrity.util.debug(2, "checking: Assertion-InstanceAge: \"%s\"", cfg["Assertion-InstanceAge"]) |
|
381 |
|
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) |
|
393 |
|
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 }) |
|
418 |
|
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 |
|
442 |
|
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 |
|
460 |
|
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 |
|
486 |
|
487 -- process "Assertion-PackageNames" constraints |
|
488 if cfg["Assertion-PackageNames"] ~= nil then |
|
489 integrity.util.debug(2, "checking: Assertion-PackageNames: \"%s\"", cfg["Assertion-PackageNames"]) |
|
490 |
|
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 |
|
499 |
|
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 |
|
547 |
|
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)) |
|
551 |
|
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] |
|
563 |
|
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) |
|
574 |
|
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 |
|
587 |
|
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 |
|
595 |
|
596 -- store result into release map |
|
597 releases[m[1]] = m[2] |
|
598 end |
|
599 |
|
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] |
|
620 |
|
621 -- only check files of the constrained distribution(s) |
|
622 if util.rmatch(dist, "(?s)" .. distregex) then |
|
623 |
|
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 |
|
642 |
|
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) |
|
661 |
|
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 |
|
673 |
|
674 -- process "Assertion-Expression" constraint |
|
675 if cfg["Assertion-Expression"] ~= nil then |
|
676 integrity.util.debug(2, "checking: Assertion-Expression: \"%s\"", cfg["Assertion-Expression"]) |
|
677 |
|
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) |
|
690 |
|
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 |
|
704 |
|
705 -- indicate license integrity validation success |
|
706 return "OK" |
|
707 end |
|
708 |
|
709 - -- integrity processor utilities namespace |
|
710 integrity.util = {} |
|
711 |
|
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 |
|
739 |
|
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 |
|
759 |
|
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 |
|
776 |
|
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 |
|
785 |
|
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 |
|
801 |
|
802 -----BEGIN PGP SIGNATURE----- |
|
803 Comment: OpenPKG GmbH <openpkg@openpkg.com> |
|
804 |
|
805 iEYEARECAAYFAk8BelcACgkQZwQuyWG3rjTL6QCeLTLVj4PTnd/E7mf+Sv4mgbZj |
|
806 5J0AoMXrO4EimPSSCZSJ1TLW8f8GP+B5 |
|
807 =AVpf |
|
808 -----END PGP SIGNATURE----- |