Mercurial > prosody-modules
comparison mod_incidents_handling/mod_incidents_handling.lua @ 906:62434bed2873
mod_incidents_handling: initial commit, only about half tested, util functions (parsers, etc) should be working and so do adhoc commands. All the fixed-type fields into the adhoc cmds' output are currently stripped by Prosody until someone *fills* line 56 of util.dataforms.
author | Marco Cirillo <maranda@lightwitch.org> |
---|---|
date | Sun, 10 Feb 2013 22:40:18 +0000 |
parents | |
children | c06369259aee |
comparison
equal
deleted
inserted
replaced
905:eae665bc2122 | 906:62434bed2873 |
---|---|
1 -- This plugin implements XEP-268 (Incidents Handling) | |
2 -- (C) 2012, Marco Cirillo (LW.Org) | |
3 | |
4 -- Note: Only part of the IODEF specifications are supported. | |
5 | |
6 module:depends("adhoc") | |
7 | |
8 local datamanager = require "util.datamanager" | |
9 local dataforms_new = require "util.dataforms".new | |
10 local st = require "util.stanza" | |
11 local id_gen = require "util.uuid".generate | |
12 | |
13 local pairs, ipairs, os_date, os_time, string, table, tonumber = pairs, ipairs, os.date, os.time, string, table, tonumber | |
14 | |
15 local xmlns_inc = "urn:xmpp:incident:2" | |
16 local xmlns_iodef = "urn:ietf:params:xml:ns:iodef-1.0" | |
17 | |
18 incidents = {} | |
19 local my_host = module:get_host() | |
20 | |
21 local expire_time = module:get_option_number("incidents_expire_time", 0) | |
22 | |
23 -- Incidents Table Methods | |
24 | |
25 local _inc_mt = {} ; _inc_mt.__index = _inc_mt | |
26 | |
27 function _inc_mt:init() | |
28 self:clean() ; self:save() | |
29 end | |
30 | |
31 function _inc_mt:clean() | |
32 if expire_time > 0 then | |
33 for id, incident in pairs(self) do | |
34 if ((os_time() - incident.time) > expire_time) and incident.status ~= "open" then | |
35 incident = nil | |
36 end | |
37 end | |
38 end | |
39 end | |
40 | |
41 function _inc_mt:save() | |
42 if not datamanager.store("incidents", my_host, "incidents_store", incidents) then | |
43 module:log("error", "Failed to save the incidents store!") | |
44 end | |
45 end | |
46 | |
47 function _inc_mt:add(stanza, report) | |
48 local data = stanza_parser(stanza) | |
49 local new_object = { | |
50 time = os_time(), | |
51 status = (not report and "open") or nil, | |
52 data = data | |
53 } | |
54 | |
55 self[data.id.text] = new_object | |
56 self:clean() ; self:save() | |
57 end | |
58 | |
59 function _inc_mt:new_object(fields, formtype) | |
60 local _contacts, _related, _impact, _sources, _targets = fields.contacts, fields.related, fields.impact, fields.sources, fields.targets | |
61 local fail = false | |
62 | |
63 -- Process contacts | |
64 local contacts = {} | |
65 for _, contact in ipairs(_contacts.tags) do | |
66 local address, atype, role, type = contact:get_text():match("^(%w+)%s(%w+)%s(%w+)%s(%w+)$") | |
67 if not address or not atype or not arole or not type then fail = true ; break end | |
68 contacts[#contacts + 1] = { | |
69 role = role, | |
70 ext_role = (role ~= "creator" or role ~= "admin" or role ~= "tech" or role ~= "irt" or role ~= "cc" and true) or nil, | |
71 type = type, | |
72 ext_type = (type ~= "person" or type ~= "organization" and true) or nil, | |
73 jid = (atype == "jid" and address) or nil, | |
74 email = (atype == "email" and address) or nil, | |
75 telephone = (atype == "telephone" and address) or nil, | |
76 postaladdr = (atype == "postaladdr" and address) or nil | |
77 } | |
78 end | |
79 | |
80 local related = {} | |
81 for _, related in ipairs(_related.tags) do | |
82 local fqdn, id = related:get_text():match("^(%w+)%s(%w+)$") | |
83 if fqdn and id then related[#related + 1] = { text = id, name = fqdn } end | |
84 end | |
85 | |
86 local _severity, _completion, _type = _impact:get_child("value"):get_text():match("^(%w+)%s(%w+)%s(%w+)$") | |
87 local impact = { lang = "en", severity = _severity, completion = _completion, type = _type } | |
88 | |
89 local sources = {} | |
90 for _, source in ipairs(_sources.tags) do | |
91 local address, cat, count, count_type = source:get_text():match("^(%w+)%s(%w+)%s(%w+)%s(%w+)$") | |
92 if not address or not cat or not count or not count_type then fail = true ; break end | |
93 local cat, cat_ext = get_type(cat, "category") | |
94 local count_type, count_ext = get_type(count_type, "counter") | |
95 | |
96 sources[#sources + 1] = { | |
97 address = { cat = cat, ext = cat_ext, text = address }, | |
98 counter = { type = count_type, ext_type = count_ext, value = count } | |
99 } | |
100 end | |
101 | |
102 local targets = {} | |
103 for _, target in ipairs(_targets.tags) do | |
104 local address, cat, noderole = target:get_text():match("^(%w+)%s(%w+)%s(%w+)$") | |
105 if not address or not cat or not node_role then fail = true ; break end | |
106 local cat, cat_ext = get_type(cat, "category") | |
107 | |
108 targets[#targets + 1] = { | |
109 addresses = { text = address, cat = cat, ext = cat_ext } | |
110 } | |
111 end | |
112 | |
113 local new_object = {} | |
114 if not fail then | |
115 new_object["time"] = Os_time() | |
116 new_object["status"] = (formtype == "request" and "open") or nil | |
117 new_object["data"] = { | |
118 id = { text = id_gen(), name = my_host }, | |
119 contacts = contacts, | |
120 related = related, | |
121 impact = impact, | |
122 sources = sources, | |
123 targets = targets | |
124 } | |
125 | |
126 self[new_object.data.id.text] = new_object | |
127 return new_object.data.id.text | |
128 else return false end | |
129 end | |
130 | |
131 -- // Util and Functions // | |
132 | |
133 local function ft_str() | |
134 local d = os_date("%FT%T%z"):gsub("^(.*)(%+%d+)", function(dt, z) | |
135 if z == "+0000" then return dt.."Z" else return dt..z end | |
136 end) | |
137 return d | |
138 end | |
139 | |
140 local function get_incident_layout(i_type) | |
141 local layout = { | |
142 title = (i_type == "report" and "Incident report form") or (i_type == "request" and "Request for assistance with incident form"), | |
143 instructions = "Started/Ended Time, Contacts, Sources and Targets of the attack are mandatory. See RFC 5072 for further format instructions.", | |
144 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" }, | |
145 | |
146 { name = "name", type = "hidden", value = my_host }, | |
147 { name = "entity", type ="text-single", label = "Remote entity to query" }, | |
148 { name = "started", type = "text-single", label = "Incident Start Time" }, | |
149 { name = "ended", type = "text-single", label = "Incident Ended Time" }, | |
150 { name = "reported", type = "hidden", value = ft_str() }, | |
151 { type = "fixed", value = "Contacts entries format is: <address> <role> [type (email or jid, def. is jid)] - separated by new lines" }, | |
152 { name = "contacts", type = "text-multi", label = "Contacts" }, | |
153 { type = "fixed", value = "Related incidents entries format is: <CSIRT's FQDN> <Incident ID> - separated by new lines" }, | |
154 { name = "related", type = "text-multi", label = "Related Incidents" }, | |
155 { type = "fixed", value = "Impact assessment format is: <severity> <completion> <type>" }, | |
156 { name = "impact", type = "text-single", label = "Impact Assessment" }, | |
157 { type = "fixed", value = "Attack sources format is: <address> <category> <count> <count-type>" }, | |
158 { name = "sources", type = "text-multi", label = "Attack Sources" }, | |
159 { type = "fixed", value = "Attack target format is: <address> <category> <noderole>" }, | |
160 { name = "targets", type = "text-multi", label = "Attack Sources" } | |
161 } | |
162 | |
163 if i_type == "request" then | |
164 table.insert(layout, { | |
165 name = "expectation", | |
166 type = "list-single", | |
167 label = "Expected action from remote entity", | |
168 value = { | |
169 { value = "nothing", label = "No action" }, | |
170 { value = "contact-sender", label = "Contact us, regarding the incident" }, | |
171 { value = "investigate", label = "Investigate the entities listed into the incident" }, | |
172 { value = "block-host", label = "Block the involved accounts" }, | |
173 { value = "other", label = "Other action, filling the description field is required" } | |
174 }}) | |
175 table.insert(layout, { name = "description", type = "text-single", label = "Description" }) | |
176 end | |
177 | |
178 return dataforms_new(layout) | |
179 end | |
180 | |
181 local function render_list(incidents) | |
182 local layout = { | |
183 title = "Stored Incidents List", | |
184 instructions = "You can select and view incident reports here, if a followup/response is possible it'll be noted in the step after selection.", | |
185 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" }, | |
186 { | |
187 name = "ids", | |
188 type = "list-single", | |
189 label = "Stored Incidents", | |
190 value = {} | |
191 } | |
192 } | |
193 | |
194 -- Render stored incidents list | |
195 | |
196 for id in pairs(incidents) do | |
197 table.insert(layout[2].value, { value = id, label = id }) | |
198 end | |
199 | |
200 return dataforms_new(layout) | |
201 end | |
202 | |
203 local function insert_fixed(t, item) table.insert(t, { type = "fixed", value = item }) end | |
204 | |
205 local function render_single(incident) | |
206 local layout = { | |
207 title = string.format("Incident ID: %s - Friendly Name: %s", incident.data.id.text, incident.data.id.name), | |
208 instructions = incident.data.desc.text, | |
209 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" } | |
210 } | |
211 | |
212 insert_fixed(layout, "Start Time: "..incident.data.start_time) | |
213 insert_fixed(layout, "End Time: "..incident.data.end_time) | |
214 insert_fixed(layout, "Report Time: "..incident.data.report_time) | |
215 | |
216 insert_fixed(layout, "Contacts --") | |
217 for _, contact in ipairs(incident.data.contacts) do | |
218 insert_fixed(layout, string.format("Role: %s Type: %s", contact.role, contact.type)) | |
219 if contact.jid then insert_fixed(layout, "--> JID: "..contact.jid..(contact.xmlns and ", XMLNS: "..contact.xmlns or "")) end | |
220 if contact.email then insert_fixed(layout, "--> E-Mail: "..contact.email) end | |
221 if contact.telephone then insert_fixed(layout, "--> Telephone: "..contact.telephone) end | |
222 if contact.postaladdr then insert_fixed(layout, "--> Postal Address: "..contact.postaladdr) end | |
223 end | |
224 | |
225 insert_fixed(layout, "Related Activity --") | |
226 for _, related in ipairs(incident.data.related) do | |
227 insert_fixed(layout, string.format("Name: %s ID: %s", related.name, related.text)) | |
228 end | |
229 | |
230 insert_fixed(layout, "Assessment --") | |
231 insert_fixed(layout, string.format("Language: %s Severity: %s Completion: %s Type: %s", | |
232 incident.data.assessment.lang, incident.data.assessment.severity, incident.data.assessment.completion, incident.data.assessment.type)) | |
233 | |
234 insert_fixed(layout, "Sources --") | |
235 for _, source in ipairs(incident.data.event_data.sources) do | |
236 insert_fixed(layout, string.format("Address: %s Counter: %s", source.address.text, source.counter.value)) | |
237 end | |
238 | |
239 insert_fixed(layout, "Targets --") | |
240 for _, target in ipairs(incident.data.event_data.targets) do | |
241 insert_fixed(layout, string.format("Address: %s Type: %s", target.address, target.ext)) | |
242 end | |
243 | |
244 if incident.data.expectation then | |
245 insert_fixed(layout, "Expected Action: "..incident.data.expectation.action) | |
246 if incident.data.expectation.desc then | |
247 insert_fixed(layout, "Expected Action Description: "..incident.data.expectation.desc) | |
248 end | |
249 end | |
250 | |
251 if incident.type == "request" and incident.status == "open" then | |
252 table.insert(layout, { name = "response-datetime", type = "hidden", value = ft_str() }) | |
253 table.insert(layout, { name = "response", type = "text-single", label = "Respond to the request" }) | |
254 end | |
255 | |
256 return dataforms_new(layout) | |
257 end | |
258 | |
259 local function get_type(var, typ) | |
260 if typ == "counter" then | |
261 local count_type, count_ext = var, nil | |
262 if count_type ~= "byte" or count_type ~= "packet" or count_type ~= "flow" or count_type ~= "session" or | |
263 count_type ~= "alert" or count_type ~= "message" or count_type ~= "event" or count_type ~= "host" or | |
264 count_type ~= "site" or count_type ~= "organization" then | |
265 count_ext = count_type | |
266 count_type = "ext-type" | |
267 end | |
268 return count_type, count_ext | |
269 elseif typ == "category" then | |
270 local cat, cat_ext = var, nil | |
271 if cat ~= "asn" or cat ~= "atm" or cat ~= "e-mail" or cat ~= "ipv4-addr" or | |
272 cat ~= "ipv4-net" or cat ~= "ipv4-net-mask" or cat ~= "ipv6-addr" or cat ~= "ipv6-net" or | |
273 cat ~= "ipv6-net-mask" or cat ~= "mac" then | |
274 cat_ext = cat | |
275 cat = "ext-category" | |
276 end | |
277 return cat, cat_ext | |
278 end | |
279 end | |
280 | |
281 local function do_tag_mapping(tag, object) | |
282 if tag.name == "IncidentID" then | |
283 object.id = { text = tag:get_text(), name = tag.attr.name } | |
284 elseif tag.name == "StartTime" then | |
285 object.start_time = tag:get_text() | |
286 elseif tag.name == "EndTime" then | |
287 object.end_time = tag:get_text() | |
288 elseif tag.name == "ReportTime" then | |
289 object.report_time = tag:get_text() | |
290 elseif tag.name == "Description" then | |
291 object.desc = { text = tag:get_text(), lang = tag.attr["xml:lang"] } | |
292 elseif tag.name == "Contact" then | |
293 local jid = tag:get_child("AdditionalData").tags[1] | |
294 local email = tag:get_child("Email") | |
295 local telephone = tag:get_child("Telephone") | |
296 local postaladdr = tag:get_child("PostalAddress") | |
297 if not object.contacts then | |
298 object.contacts = {} | |
299 object.contacts[1] = { | |
300 role = tag.attr.role, | |
301 ext_role = (tag.attr["ext-role"] and true) or nil, | |
302 type = tag.attr.type, | |
303 ext_type = (tag.attr["ext-type"] and true) or nil, | |
304 xmlns = jid.attr.xmlns, | |
305 jid = jid:get_text(), | |
306 email = email, | |
307 telephone = telephone, | |
308 postaladdr = postaladdr | |
309 } | |
310 else | |
311 object.contacts[#object.contacts + 1] = { | |
312 role = tag.attr.role, | |
313 ext_role = (tag.attr["ext-role"] and true) or nil, | |
314 type = tag.attr.type, | |
315 ext_type = (tag.attr["ext-type"] and true) or nil, | |
316 xmlns = jid.attr.xmlns, | |
317 jid = jid:get_text(), | |
318 email = email, | |
319 telephone = telephone, | |
320 postaladdr = postaladdr | |
321 } | |
322 end | |
323 elseif tag.name == "RelatedActivity" then | |
324 object.related = {} | |
325 for _, t in ipairs(tag.tags) do | |
326 if tag.name == "IncidentID" then | |
327 object.related[#object.related + 1] = { text = t:get_text(), name = tag.attr.name } | |
328 end | |
329 end | |
330 elseif tag.name == "Assessment" then | |
331 local impact = tag:get_child("Impact") | |
332 object.assessment = { lang = impact.attr.lang, severity = impact.attr.severity, completion = impact.attr.completion, type = impact.attr.type } | |
333 elseif tag.name == "EventData" then | |
334 local source = tag:get_child("Flow").tags[1] | |
335 local target = tag:get_child("Flow").tags[2] | |
336 local expectation = tag:get_child("Flow").tags[3] | |
337 object.event_data = { sources = {}, targets = {} } | |
338 for _, t in ipairs(source.tags) do | |
339 local addr = t:get_child("Address") | |
340 local cntr = t:get_child("Counter") | |
341 object.event_data.sources[#object.event_data.sources + 1] = { | |
342 address = { cat = addr.attr.category, ext = addr.attr["ext-category"], text = addr:get_text() }, | |
343 counter = { type = cntr.attr.type, ext_type = cntr.attr["ext-type"], value = cntr:get_text() } | |
344 } | |
345 end | |
346 for _, entry in ipairs(target.tags) do | |
347 local noderole = { cat = entry:get_child("NodeRole").attr.category, ext = entry:get_child("NodeRole").attr["ext-category"] } | |
348 local current = #object.event_data.targets + 1 | |
349 object.event_data.targets[current] = { addresses = {}, noderole = noderole } | |
350 for _, tag in ipairs(entry.tags) do | |
351 object.event_data.targets[current].addresses[#object.event_data.targets[current].addresses + 1] = { text = tag:get_text(), cat = tag.attr.category, ext = tag.attr["ext-category"] } | |
352 end | |
353 end | |
354 if expectation then | |
355 object.event_data.expectation = { | |
356 action = expectation.attr.action, | |
357 desc = expectation:get_child("Description") and expectation:get_child("Description"):get_text() | |
358 } | |
359 end | |
360 elseif tag.name == "History" then | |
361 object.history = {} | |
362 for _, t in ipairs(tag.tags) do | |
363 object.history[#object.history + 1] = { | |
364 action = t.attr.action, | |
365 date = t:get_child("DateTime"):get_text(), | |
366 desc = t:get_chilld("Description"):get_text() | |
367 } | |
368 end | |
369 end | |
370 end | |
371 | |
372 local function stanza_parser(stanza) | |
373 local object = {} | |
374 | |
375 if stanza:get_child("report", xmlns_inc) then | |
376 local report = st.clone(stanza):get_child("report", xmlns_inc):get_child("Incident", xmlns_iodef) | |
377 for _, tag in ipairs(report.tags) do do_tag_mapping(tag, object) end | |
378 elseif stanza:get_child("request", xmlns_inc) then | |
379 local request = st.clone(stanza):get_child("request", xmlns_inc):get_child("Incident", xmlns_iodef) | |
380 for _, tag in ipairs(request.tags) do do_tag_mapping(tag, object) end | |
381 elseif stanza:get_child("response", xmlns_inc) then | |
382 local response = st.clone(stanza):get_child("response", xmlns_inc):get_child("Incident", xmlns_iodef) | |
383 for _, tag in ipairs(response.tags) do do_tag_mapping(tag, object) end | |
384 end | |
385 | |
386 return object | |
387 end | |
388 | |
389 local function stanza_construct(id) | |
390 if not id then return nil | |
391 else | |
392 local object = incidents[id].data | |
393 local s_type = incidents[id].type | |
394 local stanza = st.iq():tag(s_type or "report", { xmlns = xmlns_inc }) | |
395 stanza:tag("Incident", { xmlns = xmlns_iodef, purpose = incidents[id].purpose }) | |
396 :tag("IncidentID", { name = object.id.name }):text(object.id.text):up() | |
397 :tag("StartTime"):text(object.start_time):up() | |
398 :tag("EndTime"):text(object.end_time):up() | |
399 :tag("ReportTime"):text(object.report_time):up() | |
400 :tag("Description", { ["xml:lang"] = object.desc.lang }):text(object.desc.text):up():up(); | |
401 | |
402 local incident = stanza:get_child(s_type, xmlns_inc):get_child("Incident", xmlns_iodef) | |
403 | |
404 for _, contact in ipairs(object.contacts) do | |
405 incident:tag("Contact", { role = (contact.ext_role and "ext-role") or contact.role, | |
406 ["ext-role"] = (contact.ext_role and contact.role) or nil, | |
407 type = (contact.ext_type and "ext-type") or contact.type, | |
408 ["ext-type"] = (contact.ext_type and contact.type) or nil }) | |
409 :tag("Email"):text(contact.email):up() | |
410 :tag("Telephone"):text(contact.telephone):up() | |
411 :tag("PostalAddress"):text(contact.postaladdr):up() | |
412 :tag("AdditionalData") | |
413 :tag("jid", { xmlns = contact.xmlns }):text(contact.jid):up():up():up() | |
414 | |
415 end | |
416 | |
417 incident:tag("RelatedActivity"):up(); | |
418 | |
419 for _, related in ipairs(object.relateds) do | |
420 incident:get_child("RelatedActivity") | |
421 :tag("IncidentID", { name = related.name }):text(related.text):up(); | |
422 end | |
423 | |
424 incident:tag("Assessment") | |
425 :tag("Impact", { | |
426 lang = object.assessment.lang, | |
427 severity = object.assessment.severity, | |
428 completion = object.assessment.completion, | |
429 type = object.assessment.type | |
430 }):up():up(); | |
431 | |
432 incident:tag("EventData") | |
433 :tag("Flow") | |
434 :tag("System", { category = "source" }):up() | |
435 :tag("System", { category = "target" }):up():up():up(); | |
436 | |
437 local e_data = incident:get_child("EventData") | |
438 | |
439 local sources = e_data:get_child("Flow").tags[1] | |
440 local targets = e_data:get_child("Flow").tags[2] | |
441 | |
442 for _, source in ipairs(object.event_data.sources) do | |
443 sources:tag("Node") | |
444 :tag("Address", { category = source.address.cat, ["ext-category"] = source.address.ext }) | |
445 :text(source.address.text):up() | |
446 :tag("Counter", { type = source.counter.type, ["ext-type"] = source.counter.ext_type }) | |
447 :text(source.counter.value):up():up(); | |
448 end | |
449 | |
450 for _, target in ipairs(object.event_data.targets) do | |
451 targets:tag("Node"):up() ; local node = targets.tags[#targets.tags] | |
452 for _, address in ipairs(target.addresses) do | |
453 node:tag("Address", { category = address.cat, ["ext-category"] = address.ext }):text(address.text):up(); | |
454 end | |
455 node:tag("NodeRole", { category = target.noderole.cat, ["ext-category"] = target.noderole.ext }):up(); | |
456 end | |
457 | |
458 if object.event_data.expectation then | |
459 e_data:tag("Expectation", { action = object.event_data.expectation.action }):up(); | |
460 if object.event_data.expectation.desc then | |
461 local expectation = e_data:get_child("Expectation") | |
462 expectation:tag("Description"):text(object.event_data.expectation.desc):up(); | |
463 end | |
464 end | |
465 | |
466 if object.history then | |
467 local history = incident:tag("History"):up(); | |
468 | |
469 for _, item in ipairs(object.history) do | |
470 history:tag("HistoryItem", { action = item.action }) | |
471 :tag("DateTime"):text(item.date):up() | |
472 :tag("Description"):text(item.desc):up():up(); | |
473 end | |
474 end | |
475 | |
476 -- Sanitize contact empty tags | |
477 for _, tag in ipairs(incident) do | |
478 if tag.name == "Contact" then | |
479 for i, check in ipairs(tag) do | |
480 if (check.name == "Email" or check.name == "PostalAddress" or check.name == "Telephone") and | |
481 not check:get_text() then | |
482 table.remove(tag, i) | |
483 end | |
484 end | |
485 end | |
486 end | |
487 | |
488 return stanza | |
489 end | |
490 end | |
491 | |
492 local function report_handler(event) | |
493 local origin, stanza = event.origin, event.stanza | |
494 | |
495 incidents:add(stanza, true) | |
496 return origin.send(st.reply(stanza)) | |
497 end | |
498 | |
499 local function inquiry_handler(event) | |
500 local origin, stanza = event.origin, event.stanza | |
501 | |
502 local inc_id = stanza:get_child("inquiry", xmlns_inc):get_child("Incident", xmlns_iodef):get_child("IncidentID"):get_text() | |
503 if incidents[inc_id] then | |
504 local report_iq = stanza_construct(incidents[inc_id]) | |
505 report_iq.attr.from = stanza.attr.to | |
506 report_iq.attr.to = stanza.attr.from | |
507 report_iq.attr.type = "set" | |
508 | |
509 origin.send(st.reply(stanza)) | |
510 return origin.send(report_iq) | |
511 else | |
512 return origin.send(st.error_reply(stanza)) | |
513 end | |
514 end | |
515 | |
516 local function request_handler(event) | |
517 local origin, stanza = event.origin, event.stanza | |
518 | |
519 local req_id = stanza:get_child("request", xmlns_inc):get_child("Incident", xmlns_iodef):get_child("IncidentID"):get_text() | |
520 if not incidents[req_id] then | |
521 return origin.send(st.error_reply(stanza)) | |
522 else | |
523 return origin.send(st.reply(stanza)) | |
524 end | |
525 end | |
526 | |
527 local function response_handler(event) | |
528 local origin, stanza = event.origin, event.stanza | |
529 | |
530 local res_id = stanza:get_child("response", xmlns_inc):get_child("Incident", xmlns_iodef):get_child("IncidentID"):get_text() | |
531 if incidents[res_id] then | |
532 incidents[res_id] = nil | |
533 incidents:add(stanza, true) | |
534 return origin.send(st.reply(stanza)) | |
535 else | |
536 return origin.send(st.error_reply(stanza)) | |
537 end | |
538 end | |
539 | |
540 -- // Adhoc Commands // | |
541 | |
542 local function list_incidents_command_handler(self, data, state) | |
543 local list_incidents_layout = render_list(incidents) | |
544 | |
545 if state then | |
546 if state.step == 1 then | |
547 if data.action == "cancel" then | |
548 return { status = "canceled" } | |
549 elseif data.action == "back" then | |
550 return { status = "executing", actions = { "next", "cancel", default = "next" }, form = list_incident_layout }, {} | |
551 end | |
552 | |
553 local single_incident_layout = state.form_layout | |
554 local fields = single_incident_layout:data(data.form) | |
555 | |
556 if fields.response then | |
557 local iq_send = stanza_construct(incidents[state.id]) | |
558 | |
559 incidents[state.id].status = "closed" | |
560 module:send(iq_send) | |
561 return { status = "completed", info = "Response sent." } | |
562 else | |
563 return { status = "completed" } | |
564 end | |
565 else | |
566 if data.action == "cancel" then return { status = "canceled" } end | |
567 local fields = list_incidents_layout:data(data.form) | |
568 | |
569 if fields.ids then | |
570 local single_incident_layout = render_single(incident[fields.ids]) | |
571 return { status = "executing", actions = { "back", "cancel", "complete", default = "complete" }, form = single_incident_layout }, { step = 1, form_layout = single_incident_layout, id = fields.ids } | |
572 else | |
573 return { status = "completed", error = { message = "You need to select the report ID to continue." } } | |
574 end | |
575 end | |
576 else | |
577 return { status = "executing", actions = { "next", "cancel", default = "next" }, form = list_incidents_layout }, {} | |
578 end | |
579 end | |
580 | |
581 local function send_inquiry_command_handler(self, data, state) | |
582 local send_inquiry_layout = dataforms_new{ | |
583 title = "Send an inquiry about an incident report to a host"; | |
584 instructions = "Please specify both the server host and the incident ID."; | |
585 | |
586 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" }; | |
587 { name = "server", type = "text-single", label = "Server to inquiry" }; | |
588 { name = "hostname", type = "text-single", label = "Involved incident host" }; | |
589 { name = "id", type = "text-single", label = "Incident ID" }; | |
590 } | |
591 | |
592 if state then | |
593 if data.action == "cancel" then return { status = "canceled" } end | |
594 local fields = send_inquiry_layout:data(data.form) | |
595 | |
596 if not fields.hostname or not fields.id or not fields.server then | |
597 return { status = "completed", error = { message = "You must supply the server to quest, the involved incident host and the incident ID." } } | |
598 else | |
599 local iq_send = st.iq({ from = my_host, to = data.server, type = "get" }) | |
600 :tag("inquiry", { xmlns = xmlns_inc }) | |
601 :tag("Incident", { xmlns = xmlns_iodef, purpose = "traceback" }) | |
602 :tag("IncidentID", { name = data.hostname }):text(fields.id):up():up():up() | |
603 | |
604 module:send(iq_send) | |
605 return { status = "completed", info = "Inquiry sent, if an answer can be obtained from the remote server it'll be listed between incidents." } | |
606 end | |
607 else | |
608 return { status = "executing", form = send_inquiry_layout }, "executing" | |
609 end | |
610 end | |
611 | |
612 local function rr_command_handler(self, data, state, formtype) | |
613 local send_layout = get_incident_layout(formtype) | |
614 local err_no_fields = { status = "completed", error = { message = "You need to fill all fields." } } | |
615 local err_proc = { status = "completed", error = { message = "There was an error processing your request, check out the syntax" } } | |
616 | |
617 if state then | |
618 if data.action == "cancel" then return { status = "canceled" } end | |
619 local fields = send_layout:data(data.form) | |
620 | |
621 if fields.started and fields.ended and fields.reported and fields.contacts and | |
622 fields.related and fields.impact and fields.sources and fields.target and | |
623 fields.entity then | |
624 if formtype == "request" and not fields.expectation then return err_no_fields end | |
625 local id = incidents:new_object(fields, formtype) | |
626 if not id then return err_proc end | |
627 | |
628 local stanza = stanza_construct(id) | |
629 stanza.attr.from = my_host | |
630 stanza.attr.to = fields.entity | |
631 module:send(stanza) | |
632 | |
633 return { status = "completed" } | |
634 else | |
635 return err_no_fields | |
636 end | |
637 else | |
638 return { status = "executing", form = send_layout }, "executing" | |
639 end | |
640 end | |
641 | |
642 local function send_report_command_handler(self, data, state) | |
643 return rr_command_handler(self, data, state, "report") | |
644 end | |
645 | |
646 local function send_request_command_handler(self, data, state) | |
647 return rr_command_handler(self, data, state, "request") | |
648 end | |
649 | |
650 local adhoc_new = module:require "adhoc".new | |
651 local list_incidents_descriptor = adhoc_new("List Incidents", xmlns_inc.."#list", list_incidents_command_handler, "admin") | |
652 local send_inquiry_descriptor = adhoc_new("Send Incident Inquiry", xmlns_inc.."#send_inquiry", send_inquiry_command_handler, "admin") | |
653 local send_report_descriptor = adhoc_new("Send Incident Report", xmlns_inc.."#send_report", send_report_command_handler, "admin") | |
654 local send_request_descriptor = adhoc_new("Send Incident Request", xmlns_inc.."#send_request", send_request_command_handler, "admin") | |
655 module:provides("adhoc", list_incidents_descriptor) | |
656 module:provides("adhoc", send_inquiry_descriptor) | |
657 module:provides("adhoc", send_report_descriptor) | |
658 module:provides("adhoc", send_request_descriptor) | |
659 | |
660 -- // Hooks // | |
661 | |
662 module:hook("iq-set/host/urn:xmpp:incident:2:report", report_handler) | |
663 module:hook("iq-get/host/urn:xmpp:incident:2:inquiry", inquiry_handler) | |
664 module:hook("iq-get/host/urn:xmpp:incident:2:request", request_handler) | |
665 module:hook("iq-set/host/urn:xmpp:incident:2:response", response_handler) | |
666 | |
667 -- // Module Methods // | |
668 | |
669 module.load = function() | |
670 if datamanager.load("incidents", my_host, "incidents_store") then incidents = datamanager.load("incidents", my_host, "incidents_store") end | |
671 setmetatable(incidents, _inc_mt) ; incidents:init() | |
672 end | |
673 | |
674 module.save = function() | |
675 return { incidents = incidents } | |
676 end | |
677 | |
678 module.restore = function(data) | |
679 incidents = data.incidents or {} | |
680 setmetatable(incidents, _inc_mt) ; incidents:init() | |
681 end |