Mercurial > prosody-modules
comparison mod_incidents_handling/incidents_handling/incidents_handling.lib.lua @ 913:f42837829d5f
mod_incidents_handling: commiting its auxiliary library.
author | Marco Cirillo <maranda@lightwitch.org> |
---|---|
date | Sun, 17 Feb 2013 19:29:39 +0100 |
parents | |
children | 7dbde05b48a9 |
comparison
equal
deleted
inserted
replaced
912:d814cc183c40 | 913:f42837829d5f |
---|---|
1 -- This contains the auxiliary functions for the Incidents Handling module. | |
2 -- (C) 2012-2013, Marco Cirillo (LW.Org) | |
3 | |
4 local pairs, ipairs, os_date, string, table, tonumber = pairs, ipairs, os.date, string, table, tonumber | |
5 | |
6 local dataforms_new = require "util.dataforms".new | |
7 local st = require "util.stanza" | |
8 | |
9 local xmlns_inc = "urn:xmpp:incident:2" | |
10 local xmlns_iodef = "urn:ietf:params:xml:ns:iodef-1.0" | |
11 local my_host = nil | |
12 | |
13 -- // Util and Functions // | |
14 | |
15 local function ft_str() | |
16 local d = os_date("%FT%T%z"):gsub("^(.*)(%+%d+)", function(dt, z) | |
17 if z == "+0000" then return dt.."Z" else return dt..z end | |
18 end) | |
19 return d | |
20 end | |
21 | |
22 local function get_incident_layout(i_type) | |
23 local layout = { | |
24 title = (i_type == "report" and "Incident report form") or (i_type == "request" and "Request for assistance with incident form"), | |
25 instructions = "Started/Ended Time, Contacts, Sources and Targets of the attack are mandatory. See RFC 5070 for further format instructions.", | |
26 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" }, | |
27 | |
28 { name = "name", type = "hidden", value = my_host }, | |
29 { name = "entity", type ="text-single", label = "Remote entity to query" }, | |
30 { name = "started", type = "text-single", label = "Incident Start Time" }, | |
31 { name = "ended", type = "text-single", label = "Incident Ended Time" }, | |
32 { name = "reported", type = "hidden", value = ft_str() }, | |
33 { name = "description", type = "text-single", label = "Description", | |
34 desc = "Description syntax is: <lang (in xml:lang format)> <short description>" }, | |
35 { name = "contacts", type = "text-multi", label = "Contacts", | |
36 desc = "Contacts entries format is: <address> <type> <role> - separated by new lines" }, | |
37 { name = "related", type = "text-multi", label = "Related Incidents", | |
38 desc = "Related incidents entries format is: <CSIRT's FQDN> <Incident ID> - separated by new lines" }, | |
39 { name = "impact", type = "text-single", label = "Impact Assessment", | |
40 desc = "Impact assessment format is: <severity> <completion> <type>" }, | |
41 { name = "sources", type = "text-multi", label = "Attack Sources", | |
42 desc = "Attack sources format is: <address> <category> <count> <count-type>" }, | |
43 { name = "targets", type = "text-multi", label = "Attack Targets", | |
44 desc = "Attack target format is: <address> <category> <noderole>" } | |
45 } | |
46 | |
47 if i_type == "request" then | |
48 table.insert(layout, { | |
49 name = "expectation", | |
50 type = "list-single", | |
51 label = "Expected action from remote entity", | |
52 value = { | |
53 { value = "nothing", label = "No action" }, | |
54 { value = "contact-sender", label = "Contact us, regarding the incident" }, | |
55 { value = "investigate", label = "Investigate the entities listed into the incident" }, | |
56 { value = "block-host", label = "Block the involved accounts" }, | |
57 { value = "other", label = "Other action, filling the description field is required" } | |
58 }}) | |
59 table.insert(layout, { name = "description", type = "text-single", label = "Description" }) | |
60 end | |
61 | |
62 return dataforms_new(layout) | |
63 end | |
64 | |
65 local function render_list(incidents) | |
66 local layout = { | |
67 title = "Stored Incidents List", | |
68 instructions = "You can select and view incident reports here, if a followup/response is possible it'll be noted in the step after selection.", | |
69 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" }, | |
70 { | |
71 name = "ids", | |
72 type = "list-single", | |
73 label = "Stored Incidents", | |
74 value = {} | |
75 } | |
76 } | |
77 | |
78 -- Render stored incidents list | |
79 | |
80 for id in pairs(incidents) do | |
81 table.insert(layout[2].value, { value = id, label = id }) | |
82 end | |
83 | |
84 return dataforms_new(layout) | |
85 end | |
86 | |
87 local function insert_fixed(t, item) table.insert(t, { type = "fixed", value = item }) end | |
88 | |
89 local function render_single(incident) | |
90 local layout = { | |
91 title = string.format("Incident ID: %s - Friendly Name: %s", incident.data.id.text, incident.data.id.name), | |
92 instructions = incident.data.desc.text, | |
93 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" } | |
94 } | |
95 | |
96 insert_fixed(layout, "Start Time: "..incident.data.start_time) | |
97 insert_fixed(layout, "End Time: "..incident.data.end_time) | |
98 insert_fixed(layout, "Report Time: "..incident.data.report_time) | |
99 | |
100 insert_fixed(layout, "Contacts --") | |
101 for _, contact in ipairs(incident.data.contacts) do | |
102 insert_fixed(layout, string.format("Role: %s Type: %s", contact.role, contact.type)) | |
103 if contact.jid then insert_fixed(layout, "--> JID: "..contact.jid..(contact.xmlns and ", XMLNS: "..contact.xmlns or "")) end | |
104 if contact.email then insert_fixed(layout, "--> E-Mail: "..contact.email) end | |
105 if contact.telephone then insert_fixed(layout, "--> Telephone: "..contact.telephone) end | |
106 if contact.postaladdr then insert_fixed(layout, "--> Postal Address: "..contact.postaladdr) end | |
107 end | |
108 | |
109 insert_fixed(layout, "Related Activity --") | |
110 for _, related in ipairs(incident.data.related) do | |
111 insert_fixed(layout, string.format("Name: %s ID: %s", related.name, related.text)) | |
112 end | |
113 | |
114 insert_fixed(layout, "Assessment --") | |
115 insert_fixed(layout, string.format("Language: %s Severity: %s Completion: %s Type: %s", | |
116 incident.data.assessment.lang, incident.data.assessment.severity, incident.data.assessment.completion, incident.data.assessment.type)) | |
117 | |
118 insert_fixed(layout, "Sources --") | |
119 for _, source in ipairs(incident.data.event_data.sources) do | |
120 insert_fixed(layout, string.format("Address: %s Counter: %s", source.address.text, source.counter.value)) | |
121 end | |
122 | |
123 insert_fixed(layout, "Targets --") | |
124 for _, target in ipairs(incident.data.event_data.targets) do | |
125 insert_fixed(layout, string.format("For NodeRole: %s", (target.noderole.cat == "ext-category" and target.noderole.ext) or targets.noderole.cat)) | |
126 for _, address in ipairs(target.addresses) do | |
127 insert_fixed(layout, string.format("---> Address: %s Type: %s", address.text, (address.cat == "ext-category" and address.ext) or address.cat)) | |
128 end | |
129 end | |
130 | |
131 if incident.data.expectation then | |
132 insert_fixed(layout, "Expected Action: "..incident.data.expectation.action) | |
133 if incident.data.expectation.desc then | |
134 insert_fixed(layout, "Expected Action Description: "..incident.data.expectation.desc) | |
135 end | |
136 end | |
137 | |
138 if incident.type == "request" and incident.status == "open" then | |
139 table.insert(layout, { name = "response-datetime", type = "hidden", value = ft_str() }) | |
140 table.insert(layout, { name = "response", type = "text-single", label = "Respond to the request" }) | |
141 end | |
142 | |
143 return dataforms_new(layout) | |
144 end | |
145 | |
146 local function get_type(var, typ) | |
147 if typ == "counter" then | |
148 local count_type, count_ext = var, nil | |
149 if count_type ~= "byte" or count_type ~= "packet" or count_type ~= "flow" or count_type ~= "session" or | |
150 count_type ~= "alert" or count_type ~= "message" or count_type ~= "event" or count_type ~= "host" or | |
151 count_type ~= "site" or count_type ~= "organization" then | |
152 count_ext = count_type | |
153 count_type = "ext-type" | |
154 end | |
155 return count_type, count_ext | |
156 elseif typ == "category" then | |
157 local cat, cat_ext = var, nil | |
158 if cat ~= "asn" or cat ~= "atm" or cat ~= "e-mail" or cat ~= "ipv4-addr" or | |
159 cat ~= "ipv4-net" or cat ~= "ipv4-net-mask" or cat ~= "ipv6-addr" or cat ~= "ipv6-net" or | |
160 cat ~= "ipv6-net-mask" or cat ~= "mac" then | |
161 cat_ext = cat | |
162 cat = "ext-category" | |
163 end | |
164 return cat, cat_ext | |
165 elseif type == "noderole" then | |
166 local noderole_ext = nil | |
167 if cat ~= "client" or cat ~= "server-internal" or cat ~= "server-public" or cat ~= "www" or | |
168 cat ~= "mail" or cat ~= "messaging" or cat ~= "streaming" or cat ~= "voice" or | |
169 cat ~= "file" or cat ~= "ftp" or cat ~= "p2p" or cat ~= "name" or | |
170 cat ~= "directory" or cat ~= "credential" or cat ~= "print" or cat ~= "application" or | |
171 cat ~= "database" or cat ~= "infra" or cat ~= "log" then | |
172 noderole_ext = true | |
173 end | |
174 return noderole_ext | |
175 end | |
176 end | |
177 | |
178 local function do_tag_mapping(tag, object) | |
179 if tag.name == "IncidentID" then | |
180 object.id = { text = tag:get_text(), name = tag.attr.name } | |
181 elseif tag.name == "StartTime" then | |
182 object.start_time = tag:get_text() | |
183 elseif tag.name == "EndTime" then | |
184 object.end_time = tag:get_text() | |
185 elseif tag.name == "ReportTime" then | |
186 object.report_time = tag:get_text() | |
187 elseif tag.name == "Description" then | |
188 object.desc = { text = tag:get_text(), lang = tag.attr["xml:lang"] } | |
189 elseif tag.name == "Contact" then | |
190 local jid = tag:get_child("AdditionalData").tags[1] | |
191 local email = tag:get_child("Email") | |
192 local telephone = tag:get_child("Telephone") | |
193 local postaladdr = tag:get_child("PostalAddress") | |
194 if not object.contacts then | |
195 object.contacts = {} | |
196 object.contacts[1] = { | |
197 role = tag.attr.role, | |
198 ext_role = (tag.attr["ext-role"] and true) or nil, | |
199 type = tag.attr.type, | |
200 ext_type = (tag.attr["ext-type"] and true) or nil, | |
201 xmlns = jid.attr.xmlns, | |
202 jid = jid:get_text(), | |
203 email = email, | |
204 telephone = telephone, | |
205 postaladdr = postaladdr | |
206 } | |
207 else | |
208 object.contacts[#object.contacts + 1] = { | |
209 role = tag.attr.role, | |
210 ext_role = (tag.attr["ext-role"] and true) or nil, | |
211 type = tag.attr.type, | |
212 ext_type = (tag.attr["ext-type"] and true) or nil, | |
213 xmlns = jid.attr.xmlns, | |
214 jid = jid:get_text(), | |
215 email = email, | |
216 telephone = telephone, | |
217 postaladdr = postaladdr | |
218 } | |
219 end | |
220 elseif tag.name == "RelatedActivity" then | |
221 object.related = {} | |
222 for _, t in ipairs(tag.tags) do | |
223 if tag.name == "IncidentID" then | |
224 object.related[#object.related + 1] = { text = t:get_text(), name = tag.attr.name } | |
225 end | |
226 end | |
227 elseif tag.name == "Assessment" then | |
228 local impact = tag:get_child("Impact") | |
229 object.assessment = { lang = impact.attr.lang, severity = impact.attr.severity, completion = impact.attr.completion, type = impact.attr.type } | |
230 elseif tag.name == "EventData" then | |
231 local source = tag:get_child("Flow").tags[1] | |
232 local target = tag:get_child("Flow").tags[2] | |
233 local expectation = tag:get_child("Flow").tags[3] | |
234 object.event_data = { sources = {}, targets = {} } | |
235 for _, t in ipairs(source.tags) do | |
236 local addr = t:get_child("Address") | |
237 local cntr = t:get_child("Counter") | |
238 object.event_data.sources[#object.event_data.sources + 1] = { | |
239 address = { cat = addr.attr.category, ext = addr.attr["ext-category"], text = addr:get_text() }, | |
240 counter = { type = cntr.attr.type, ext_type = cntr.attr["ext-type"], value = cntr:get_text() } | |
241 } | |
242 end | |
243 for _, entry in ipairs(target.tags) do | |
244 local noderole = { cat = entry:get_child("NodeRole").attr.category, ext = entry:get_child("NodeRole").attr["ext-category"] } | |
245 local current = #object.event_data.targets + 1 | |
246 object.event_data.targets[current] = { addresses = {}, noderole = noderole } | |
247 for _, tag in ipairs(entry.tags) do | |
248 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"] } | |
249 end | |
250 end | |
251 if expectation then | |
252 object.event_data.expectation = { | |
253 action = expectation.attr.action, | |
254 desc = expectation:get_child("Description") and expectation:get_child("Description"):get_text() | |
255 } | |
256 end | |
257 elseif tag.name == "History" then | |
258 object.history = {} | |
259 for _, t in ipairs(tag.tags) do | |
260 object.history[#object.history + 1] = { | |
261 action = t.attr.action, | |
262 date = t:get_child("DateTime"):get_text(), | |
263 desc = t:get_chilld("Description"):get_text() | |
264 } | |
265 end | |
266 end | |
267 end | |
268 | |
269 local function stanza_parser(stanza) | |
270 local object = {} | |
271 | |
272 if stanza:get_child("report", xmlns_inc) then | |
273 local report = st.clone(stanza):get_child("report", xmlns_inc):get_child("Incident", xmlns_iodef) | |
274 for _, tag in ipairs(report.tags) do do_tag_mapping(tag, object) end | |
275 elseif stanza:get_child("request", xmlns_inc) then | |
276 local request = st.clone(stanza):get_child("request", xmlns_inc):get_child("Incident", xmlns_iodef) | |
277 for _, tag in ipairs(request.tags) do do_tag_mapping(tag, object) end | |
278 elseif stanza:get_child("response", xmlns_inc) then | |
279 local response = st.clone(stanza):get_child("response", xmlns_inc):get_child("Incident", xmlns_iodef) | |
280 for _, tag in ipairs(response.tags) do do_tag_mapping(tag, object) end | |
281 end | |
282 | |
283 return object | |
284 end | |
285 | |
286 local function stanza_construct(id) | |
287 if not id then return nil | |
288 else | |
289 local object = incidents[id].data | |
290 local s_type = incidents[id].type | |
291 local stanza = st.iq():tag(s_type or "report", { xmlns = xmlns_inc }) | |
292 stanza:tag("Incident", { xmlns = xmlns_iodef, purpose = incidents[id].purpose }) | |
293 :tag("IncidentID", { name = object.id.name }):text(object.id.text):up() | |
294 :tag("StartTime"):text(object.start_time):up() | |
295 :tag("EndTime"):text(object.end_time):up() | |
296 :tag("ReportTime"):text(object.report_time):up() | |
297 :tag("Description", { ["xml:lang"] = object.desc.lang }):text(object.desc.text):up():up(); | |
298 | |
299 local incident = stanza:get_child(s_type, xmlns_inc):get_child("Incident", xmlns_iodef) | |
300 | |
301 for _, contact in ipairs(object.contacts) do | |
302 incident:tag("Contact", { role = (contact.ext_role and "ext-role") or contact.role, | |
303 ["ext-role"] = (contact.ext_role and contact.role) or nil, | |
304 type = (contact.ext_type and "ext-type") or contact.type, | |
305 ["ext-type"] = (contact.ext_type and contact.type) or nil }) | |
306 :tag("Email"):text(contact.email):up() | |
307 :tag("Telephone"):text(contact.telephone):up() | |
308 :tag("PostalAddress"):text(contact.postaladdr):up() | |
309 :tag("AdditionalData") | |
310 :tag("jid", { xmlns = contact.xmlns }):text(contact.jid):up():up():up() | |
311 | |
312 end | |
313 | |
314 incident:tag("RelatedActivity"):up(); | |
315 | |
316 for _, related in ipairs(object.related) do | |
317 incident:get_child("RelatedActivity") | |
318 :tag("IncidentID", { name = related.name }):text(related.text):up(); | |
319 end | |
320 | |
321 incident:tag("Assessment") | |
322 :tag("Impact", { | |
323 lang = object.assessment.lang, | |
324 severity = object.assessment.severity, | |
325 completion = object.assessment.completion, | |
326 type = object.assessment.type | |
327 }):up():up(); | |
328 | |
329 incident:tag("EventData") | |
330 :tag("Flow") | |
331 :tag("System", { category = "source" }):up() | |
332 :tag("System", { category = "target" }):up():up():up(); | |
333 | |
334 local e_data = incident:get_child("EventData") | |
335 | |
336 local sources = e_data:get_child("Flow").tags[1] | |
337 local targets = e_data:get_child("Flow").tags[2] | |
338 | |
339 for _, source in ipairs(object.event_data.sources) do | |
340 sources:tag("Node") | |
341 :tag("Address", { category = source.address.cat, ["ext-category"] = source.address.ext }) | |
342 :text(source.address.text):up() | |
343 :tag("Counter", { type = source.counter.type, ["ext-type"] = source.counter.ext_type }) | |
344 :text(source.counter.value):up():up(); | |
345 end | |
346 | |
347 for _, target in ipairs(object.event_data.targets) do | |
348 targets:tag("Node"):up() ; local node = targets.tags[#targets.tags] | |
349 for _, address in ipairs(target.addresses) do | |
350 node:tag("Address", { category = address.cat, ["ext-category"] = address.ext }):text(address.text):up(); | |
351 end | |
352 node:tag("NodeRole", { category = target.noderole.cat, ["ext-category"] = target.noderole.ext }):up(); | |
353 end | |
354 | |
355 if object.event_data.expectation then | |
356 e_data:tag("Expectation", { action = object.event_data.expectation.action }):up(); | |
357 if object.event_data.expectation.desc then | |
358 local expectation = e_data:get_child("Expectation") | |
359 expectation:tag("Description"):text(object.event_data.expectation.desc):up(); | |
360 end | |
361 end | |
362 | |
363 if object.history then | |
364 local history = incident:tag("History"):up(); | |
365 | |
366 for _, item in ipairs(object.history) do | |
367 history:tag("HistoryItem", { action = item.action }) | |
368 :tag("DateTime"):text(item.date):up() | |
369 :tag("Description"):text(item.desc):up():up(); | |
370 end | |
371 end | |
372 | |
373 -- Sanitize contact empty tags | |
374 for _, tag in ipairs(incident) do | |
375 if tag.name == "Contact" then | |
376 for i, check in ipairs(tag) do | |
377 if (check.name == "Email" or check.name == "PostalAddress" or check.name == "Telephone") and | |
378 not check:get_text() then | |
379 table.remove(tag, i) | |
380 end | |
381 end | |
382 end | |
383 end | |
384 | |
385 if s_type == "request" then stanza.attr.type = "get" | |
386 elseif s_type == "response" then stanza.attr.type = "set" | |
387 else stanza.attr.type = "set" end | |
388 | |
389 return stanza | |
390 end | |
391 end | |
392 | |
393 | |
394 _M = {} -- wraps methods into the library. | |
395 _M.ft_str = ft_str | |
396 _M.get_incident_layout = get_incident_layout | |
397 _M.render_list = render_list | |
398 _M.render_single = render_single | |
399 _M.get_type = get_type | |
400 _M.stanza_parser = stanza_parser | |
401 _M.stanza_construct = stanza_construct | |
402 _M.set_my_host = function(host) my_host = host end | |
403 | |
404 return _M | |
405 |