/*
* Copyright (C) 2007-2014 Christian Bockermann <chris@jwall.org>
*
* This file is part of the web-audit library.
*
* web-audit library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The web-audit library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.jwall.web.audit;
import java.io.File;
import java.io.Serializable;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.jwall.audit.EventType;
import org.jwall.web.audit.io.AuditEventParser;
import org.jwall.web.audit.io.MessageParser;
import org.jwall.web.audit.io.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* This class defines an audit-event of <a target="_blank"
* href="http://www.modsecurity.org">modsecurity</a>.
*
* Currently this class stores the audit-event data as an internal string and
* has a few hash tables to hold specific properties/fields/parameters of the
* parsed request. However this will hopefully be turned into an interface to
* achieve independence of the actual implementations of an AuditEvent.
*
* @author Christian Bockermann <chris@jwall.org>
*/
public class ModSecurityAuditEvent implements Comparable<AuditEvent>,
Serializable, AuditEvent {
/** id for serialization */
private static final long serialVersionUID = 7828115034635641145L;
private static Logger log = LoggerFactory
.getLogger(ModSecurityAuditEvent.class);
/** a count of all events alive */
protected static Long evtCount = Long.valueOf(0L);
/** the format in which the date is printed out */
public final static SimpleDateFormat fmt = new SimpleDateFormat(
"d/MMM/y:HH:mm:ss Z", Locale.ENGLISH);
/** the date, this event has been created */
private Date createdAt;
/** a unique event_id */
protected String eventId = "";
/** this holds the real data */
private String[] sections;
Map<String, List<String>> collections = null;
/** a sessionId, to be externally set by a session-tracker */
String sessionId;
private URL url = null;
private File file = null;
private long offset = -1L;
private long size = -1L;
final AuditEventType type;
AuditEventMessage[] messages = null;
protected ModSecurityAuditEvent(AuditEventType type) {
this.type = type;
collections = new LinkedHashMap<String, List<String>>();
}
/**
* This clone-constructor creates a copy of the given audit-event.
*
* @param evt
* The event to clone.
*/
public ModSecurityAuditEvent(ModSecurityAuditEvent evt) throws Exception {
this(evt.sections, evt.type);
}
public String[] getSections() {
return sections;
}
public EventType getType() {
return EventType.AUDIT;
}
/**
* This creates an instance of this class by parsing all fields from the
* given string-array.
*
* @param data
* An array containing all audit-sections.
* @throws Exception
* in case parsing failed.
*/
public ModSecurityAuditEvent(String[] data, AuditEventType type)
throws ParseException {
this(type);
sections = data.clone();
if (data.length < 1 || data[0] == null || "".equals(data[0]))
throw new ParseException("No audit-header found !");
try {
log.debug("Parsing request-header...");
AuditEventParser.parseHttpHeader(collections,
sections[ModSecurity.SECTION_REQUEST_HEADER]);
log.debug("Parsing response header...");
AuditEventParser.parseHttpHeader(collections,
sections[ModSecurity.SECTION_FINAL_RESPONSE_HEADER]);
log.debug("Parsing audit-log trailer...");
AuditEventParser.parseAuditTrailer(collections,
sections[ModSecurity.SECTION_AUDIT_TRAILER]);
} catch (ParseException e) {
if (data != null && data.length > 0)
log.debug("AuditHeader of event to be parsed: '" + data[0]
+ "'");
else
log.debug("ScriptEvent doesn't even contain an audit-header!");
if (log.isDebugEnabled()) {
StringBuffer b = new StringBuffer();
for (int i = 0; i < data.length; i++) {
if (data[i] != null && !"".equals(data[i])) {
String d = data[i];
b.append("--SECTION-" + ModSecurity.SECTIONS.charAt(i)
+ "--\r\n");
b.append(d);
}
}
b.append("--SECTION-Z--\r\n");
b.append("\r\n-------------------------------\r\n");
log.debug(b.toString());
}
}
parseSections(sections);
if (collections.get(ModSecurity.QUERY_STRING) != null) {
log.debug("Parsing parameters from query string: "
+ collections.get(ModSecurity.QUERY_STRING).get(0));
AuditEventParser.parseParameters(collections,
ModSecurity.QUERY_STRING,
collections.get(ModSecurity.QUERY_STRING).get(0));
}
if (data.length >= ModSecurity.SECTION_FORM_DATA
&& data[ModSecurity.SECTION_FORM_DATA] != null) {
log.debug("Parsing parameters from section-form-data...");
AuditEventParser.parseParameters(collections,
ModSecurity.REQUEST_BODY,
data[ModSecurity.SECTION_REQUEST_BODY]);
}
if (data.length >= ModSecurity.SECTION_AUDIT_TRAILER
&& data[ModSecurity.SECTION_AUDIT_TRAILER] != null)
AuditEventParser.parseAuditTrailer(collections,
sections[ModSecurity.SECTION_AUDIT_TRAILER]);
size = 0;
for (String sec : sections) {
this.size += sec.length();
}
sessionId = "";
synchronized (evtCount) {
evtCount++;
}
}
/**
*
* @param id
* @param data
* @param f
* @param off
* @param size
* @param type
* @throws ParseException
*/
public ModSecurityAuditEvent(String id, String[] data, File f, long off,
long size, AuditEventType type) throws ParseException {
this(data, type);
eventId = id;
file = f;
offset = off;
this.size = size;
AuditEventParser.setValue(collections, ModSecurity.EVENT_ID, id);
}
/**
* This constructor is used, when reading audit-events from a file. This way
* you can make sure, that the id of the audit-event is the same as the one
* in the file.
*
* @param id
* The id that the new event should contain.
* @param data
* The section-data.
* @throws Exception
* In case anything goes wrong (Parsing, etc...)
*/
public ModSecurityAuditEvent(String id, String[] data, AuditEventType type)
throws Exception {
this(data, type);
eventId = id;
}
public Long getTimestamp() {
if (createdAt == null)
createdAt = new Date(System.currentTimeMillis());
return createdAt.getTime();
}
protected void parseSections(String[] sections) throws ParseException {
createdAt = AuditEventParser
.parseDate(sections[ModSecurity.SECTION_AUDIT_LOG_HEADER]);
//
// extract remote-addr, remote-port, server-addr, server-port from
// AuditLog-Header
// 0 1 2 3 4 5 6
// s[] = [date, TZ], ID, Client-IP, Client-Port, Server-IP, Server-Port
String[] s = sections[ModSecurity.SECTION_AUDIT_LOG_HEADER].trim()
.split(" ");
if (s.length < 7)
throw new ParseException("Error while parsing AuditLog-Header: "
+ sections[ModSecurity.SECTION_AUDIT_LOG_HEADER]);
eventId = Long.toHexString(createdAt.getTime());
AuditEventParser.addValue(collections, ModSecurity.TX_ID, s[2]);
AuditEventParser.addValue(collections, ModSecurity.REMOTE_ADDR, s[3]);
AuditEventParser.addValue(collections, ModSecurity.REMOTE_HOST, s[3]);
AuditEventParser.addValue(collections, ModSecurity.REMOTE_PORT, s[4]);
AuditEventParser.addValue(collections, ModSecurity.SERVER_ADDR, s[5]);
AuditEventParser.addValue(collections, ModSecurity.SERVER_NAME, s[5]);
if (s[6].equals("www"))
s[6] = "80";
AuditEventParser.addValue(collections, ModSecurity.SERVER_PORT, s[6]);
AuditEventParser.addValue(collections, ModSecurity.REMOTE_USER, "-");
if (sections.length >= ModSecurity.SECTION_AUDIT_TRAILER
&& sections[ModSecurity.SECTION_AUDIT_TRAILER] != null) {
AuditEventParser.addValue(collections,
ModSecurity.AUDIT_LOG_TRAILER,
sections[ModSecurity.SECTION_AUDIT_TRAILER]);
AuditEventParser.parseAuditTrailer(collections,
sections[ModSecurity.SECTION_AUDIT_TRAILER]);
}
AuditEventParser.setValue(collections, ModSecurity.REQUEST_HEADER,
sections[ModSecurity.SECTION_REQUEST_HEADER]);
if (sections[ModSecurity.SECTION_REQUEST_BODY] != null)
AuditEventParser.setValue(collections, ModSecurity.REQUEST_BODY,
sections[ModSecurity.SECTION_REQUEST_BODY]);
if (sections[ModSecurity.SECTION_FINAL_RESPONSE_HEADER] != null)
AuditEventParser.setValue(collections, ModSecurity.RESPONSE_HEADER,
sections[ModSecurity.SECTION_FINAL_RESPONSE_HEADER]);
if (ModSecurity.SECTION_META_INF > 0
&& sections[ModSecurity.SECTION_META_INF] != null)
AuditEventParser.parseMetaInfSection(collections,
sections[ModSecurity.SECTION_META_INF]);
}
/**
* @see org.modsecurity.audit.AuditEvent#getEventId()
*/
public String getEventId() {
String id = get(ModSecurity.TX_ID);
if (id != null)
return id;
return eventId;
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#getSection(int)
*/
public String getSection(int i) {
if (i >= 0 && i < sections.length)
return sections[i];
return null;
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#getAuditHeader()
*/
public String getAuditHeader() {
return sections[0];
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#getRequestHeader()
*/
public String getRequestHeader() {
return sections[ModSecurity.SECTION_REQUEST_HEADER];
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#getRequestBody()
*/
public String getRequestBody() {
return sections[ModSecurity.SECTION_REQUEST_BODY];
}
/**
* @see org.modsecurity.audit.AuditEvent#getResponseHeader()
* @deprecated
*/
public String getResponseHeader() {
return sections[ModSecurity.SECTION_FINAL_RESPONSE_HEADER];
}
/**
* @deprecated
*/
public String getAuditLogTrailer() {
return sections[ModSecurity.SECTION_AUDIT_TRAILER];
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#getDate()
*/
public Date getDate() {
return new Date(createdAt.getTime());
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#getSessionId()
*/
public String getSessionId() {
return sessionId;
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#setSessionId(java.lang.String)
*/
public void setSessionId(String id) {
sessionId = id;
AuditEventParser.setValue(collections, ModSecurity.SESSIONID, id);
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#isSet(java.lang.String)
*/
public boolean isSet(String var) {
if (var == null)
return false;
String v = var;
if (!ModSecurity.isCaseSensitive(v)) {
v = var.toUpperCase();
}
return collections.get(v) != null;
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#get(java.lang.String)
*/
public String get(String variable) {
String var = variable;
if (var == null)
return "";
if ("UNIQUE_ID".equals(variable)) // this is for backwards-compatibility
return get(ModSecurity.TX_ID);
if (!ModSecurity.isCaseSensitive(variable)) {
var = variable.toUpperCase();
}
if (ModSecurity.__UNDEFINED__.equals(var))
return "";
if (var.startsWith("&")) {
String col = var.substring(1);
return "" + getAll(col).size();
}
if (collections.containsKey(var)) {
if (collections.get(var).isEmpty())
return "";
return collections.get(var).get(0);
}
return "";
}
public List<String> getAll(String v) {
String var = v;
if (!ModSecurity.isCaseSensitive(v)) {
var = v.toUpperCase();
}
if (collections.containsKey(var))
return collections.get(var);
List<String> vals = new LinkedList<String>();
String val = this.get(var);
if (!"".equals(val))
vals.add(val);
return vals;
}
public List<String> getVariables() {
List<String> names = new LinkedList<String>();
names.addAll(collections.keySet());
return names;
}
public void set(String var, String val) {
AuditEventParser.setValue(collections, var, val);
}
/**
* Override <code>Object.toString()</code> by returning a String of all
* sections.
*/
public String toString() {
StringBuffer s = new StringBuffer();
for (int i = 0; i < sections.length; i++) {
if (sections[i].length() > 0) {
s.append("--" + this.getEventId() + "-"
+ ModSecurity.SECTIONS.charAt(i) + "--\n");
s.append(sections[i]);
}
}
s.append("--" + getEventId() + "-Z--\n");
return s.toString();
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#compareTo(java.lang.Object)
*/
public int compareTo(AuditEvent o) {
if (o instanceof ModSecurityAuditEvent)
return compareTo((ModSecurityAuditEvent) o);
return -1;
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#compareTo(org.modsecurity.audit.
* AuditEventImpl)
*/
public int compareTo(ModSecurityAuditEvent o) {
if (this.eventId.compareTo(o.eventId) == 0)
return 0;
int c = getDate().compareTo(o.getDate());
if (c == 0)
return eventId.compareTo(o.eventId);
else
return c;
}
public boolean equals(ModSecurityAuditEvent o) {
return 0 == this.compareTo(o);
}
//
//
// some private methods for parsing, etc.
//
//
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#getRawData()
*/
public String[] getRawData() {
return sections;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
synchronized (evtCount) {
evtCount--;
}
}
public Long getCount() {
return evtCount;
}
/*
* (non-Javadoc)
*
* @see org.modsecurity.audit.AuditEvent#getURL()
*/
public URL getRequestURL() {
return url;
}
/*
* (non-Javadoc)
*
* @see org.jwall.web.audit.AuditEvent#getFile()
*/
public File getFile() {
return file;
}
/*
* (non-Javadoc)
*
* @see org.jwall.web.audit.AuditEvent#getOffset()
*/
public long getOffset() {
return offset;
}
public long getSize() {
return size;
}
public void recycle() {
}
public void tag(String tag) {
List<String> tags = this.collections.get(TAGS);
if (tags == null) {
tags = new LinkedList<String>();
collections.put(TAGS, tags);
}
if (!tags.contains(tag))
tags.add(tag);
}
public void untag(String tag) {
List<String> tags = collections.get(TAGS);
if (tags != null)
tags.remove(tag);
}
public Set<String> getTags() {
LinkedHashSet<String> tags = new LinkedHashSet<String>();
List<String> taglist = collections.get(TAGS);
if (taglist != null)
tags.addAll(taglist);
return tags;
}
public static Long getInstanceCount() {
return evtCount;
}
@Override
public void setAll(String variable, List<String> values) {
if (collections == null)
collections = new LinkedHashMap<String, List<String>>();
if (ModSecurity.isCaseSensitive(variable))
collections.put(variable, values);
else
collections.put(variable.toUpperCase(), values);
}
@Override
public AuditEventType getAuditEventType() {
return type;
}
@Override
public AuditEventMessage[] getEventMessages() {
if (messages != null)
return messages;
List<AuditEventMessage> msgs = MessageParser.parseMessages(this);
messages = msgs.toArray(new AuditEventMessage[msgs.size()]);
return messages;
}
}