mod_incidents_handling/incidents_handling/incidents_handling.lib.lua
changeset 913 f42837829d5f
child 1343 7dbde05b48a9
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