/**
* Copyright (c) 2009--2015 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.manager.audit;
import com.redhat.rhn.common.conf.Config;
import com.redhat.rhn.common.db.datasource.DataResult;
import com.redhat.rhn.frontend.dto.AuditDto;
import com.redhat.rhn.frontend.dto.AuditMachineDto;
import com.redhat.rhn.frontend.dto.AuditReviewDto;
import org.apache.log4j.Logger;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* AuditManager
* @version $Rev$
*/
public class AuditManager /* extends BaseManager */ {
private static Logger log = Logger.getLogger(AuditManager.class);
private AuditManager() {
}
private static String logDirStr = Config.get().getString("web.audit.logdir",
"/var/spacewalk/systemlogs");
private static File logDir = new File(logDirStr);
private static File reviewFile = new File(logDirStr + "/audit-review.log");
/**
* Mark a machine/start/end as reviewed.
* @param machine Machine name
* @param start Start time in ms from epoch
* @param end End time in ms from epoch
* @param username User marking the review
* @throws IOException Thrown when the audit review log isn't writeable
*/
public static void markReviewed(String machine, Long start, Long end,
String username) throws IOException {
FileWriter fwr = new FileWriter(reviewFile, true); // append!
fwr.write(machine + "," + (start / 1000) + "," + (end / 1000) + "," +
username + "," + (new Date().getTime() / 1000) + "\n");
fwr.close();
}
/**
* Retrieve the audits for a machine, possibly filtering by types and time
* @param types The types to look for (e.g. "DAEMON_START"); can be null
* @param machine The machine name
* @param start The start time; can be null
* @param end The end time; can be null
* @return The set of matching audit logs
*/
public static DataResult getAuditLogs(String[] types, String machine,
Long start, Long end) {
DataResult dr = null;
List l;
Long fileStart, fileEnd;
if (types == null) {
types = new String[0];
}
if (start == null) {
start = 0L;
}
if (end == null) {
end = Long.MAX_VALUE;
}
try {
DataResult<AuditReviewDto> aureviewsections = getMachineReviewSections(machine);
if (aureviewsections != null) {
for (AuditReviewDto aureview : getMachineReviewSections(machine)) {
fileStart = aureview.getStart().getTime();
fileEnd = aureview.getEnd().getTime();
if (fileEnd < start || fileStart > end) {
continue;
}
File auditLog = new File(
logDirStr + "/" + aureview.getName() + "/audit/audit-" +
(fileStart / 1000) + "-" +
(fileEnd / 1000) + ".parsed");
l = readAuditFile(auditLog, types, start, end);
if (dr == null) {
dr = new DataResult(l);
}
else {
dr.addAll(l);
}
}
}
}
catch (IOException ioex) {
log.warn("AAAAHHHH IOException", ioex);
}
if (dr == null || dr.size() == 0) {
return null;
}
return dr;
}
/**
* Return the various audit type-mappings we have defined
* @return A mapping between the set name and the audit types
*/
public static Map<String, String[]> getAuditTypeMap() {
Map<String, String[]> types = new HashMap<String, String[]>();
types.put("default", new String[]{
"USER",
"USER_AUTH",
"USER_MGMT",
"USER_ERR",
"USER_LOGIN",
"USER_LOGOUT",
"ADD_USER",
"DEL_USER",
"ADD_GROUP",
"DEL_GROUP",
"DAEMON_START",
"DAEMON_END",
"DAEMON_ABORT",
"DAEMON_CONFIG",
"DAEMON_ROTATE",
"DAEMON_RESUME",
"CONFIG_CHANGE",
"MAC_POLICY_LOAD",
"MAC_STATUS",
"MAC_CONFIG_CHANGE",
"ANOM_PROMISCUOUS",
"ANOM_ABEND",
"ANOM_LOGIN_FAILURES",
"ANOM_LOGIN_TIME",
"ANOM_LOGIN_SESSIONS",
"ANOM_LOGIN_ACCT",
"ANOM_LOGIN_LOCATION",
"ANOM_MAX_DAC",
"ANOM_MAX_MAC",
"ANOM_AMTU_FAIL",
"ANOM_RBAC_FAIL",
"ANOM_RBAC_INTEGRITY_FAIL",
"ANOM_CRYPTO_FAIL",
"ANOM_ACCESS_FS",
"ANOM_EXEC",
"ANOM_MK_EXEC",
"ANOM_ADD_ACCT",
"ANOM_DEL_ACCT",
"ANOM_MOD_ACCT",
"USER_ROLE_CHANGE",
"ROLE_ASSIGN",
"ROLE_REMOVE",
"LABEL_OVERRIDE",
"LABEL_LEVEL_CHANGE",
"USER_LABELED_EXPORT",
"USER_UNLABELED_EXPORT",
"DEV_ALLOC",
"DEV_DEALLOC",
"FS_RELABEL"
});
types.put("login", new String[]{
"ANOM_LOGIN_ACCT",
"ANOM_LOGIN_FAILURES",
"ANOM_LOGIN_LOCATION",
"ANOM_LOGIN_SESSIONS",
"ANOM_LOGIN_TIME",
"ANOM_ROOT_TRANS",
"USER_AUTH",
"USER_ERR",
"USER_LOGIN",
"USER_ROLE_CHANGE"
});
return types;
}
/**
* Get the time for the first unreviewed log for the specified machine
* @param machineName The machine to find review times for
* @return An AuditReviewDto for the machine's first unreviewed section
*/
public static AuditReviewDto getFirstUnreviewed(String machineName) {
AuditReviewDto firstUnreviewed = null;
DataResult<AuditReviewDto> dr = getMachineReviewSections(machineName);
for (AuditReviewDto aurev : dr) {
if (aurev.getReviewedBy() == null) { // an unreviewed log!
if (firstUnreviewed == null ||
aurev.getStart().getTime() <
firstUnreviewed.getStart().getTime()) {
firstUnreviewed = aurev;
}
}
}
return firstUnreviewed;
}
/**
* Get the last time-of-review for the specified machine
* @param machineName The machine to find review times for
* @return An AuditReviewDto for the machine's last review time
*/
public static AuditReviewDto getLastReview(String machineName) {
AuditReviewDto lastReviewed = null;
DataResult<AuditReviewDto> dr = getMachineReviewSections(machineName);
for (AuditReviewDto aurev : dr) {
if (aurev.getReviewedOn() != null) {
if (lastReviewed == null ||
aurev.getReviewedOn().getTime() >
lastReviewed.getReviewedOn().getTime()) {
lastReviewed = aurev;
}
}
}
return lastReviewed;
}
/**
* Retrieve the set of all machines we know about
* @return The set of machines
*/
public static DataResult<AuditMachineDto> getMachines() {
AuditReviewDto aurev;
DataResult<AuditMachineDto> dr;
Date lastReview, firstUnreviewed;
LinkedList<AuditMachineDto> hosts = new LinkedList<AuditMachineDto>();
if (logDir == null || !logDir.canRead()) {
return new DataResult<AuditMachineDto>(hosts);
}
for (File host : logDir.listFiles()) {
if (host.isDirectory()) {
aurev = getLastReview(host.getName());
if (aurev != null) {
lastReview = aurev.getReviewedOn();
}
else {
lastReview = null;
}
aurev = getFirstUnreviewed(host.getName());
if (aurev != null) {
firstUnreviewed = aurev.getStart();
}
else {
firstUnreviewed = null;
}
hosts.add(new AuditMachineDto(
host.getName(), lastReview, firstUnreviewed));
}
}
Collections.sort(hosts);
dr = new DataResult<AuditMachineDto>(hosts);
return dr;
}
/**
* Retrieve the set of audit sections, possibly for a specified machine
* @param machineName The machine to get review sections for; can be null
* @return The set of review sections
*/
public static DataResult<AuditReviewDto> getMachineReviewSections(
String machineName) {
long start, end;
DataResult<AuditReviewDto> dr, rec;
File hostDir;
LinkedList<AuditReviewDto> aurevs = new LinkedList<AuditReviewDto>();
Matcher fnmatch;
Pattern fnregex = Pattern.compile("audit-(\\d+)-(\\d+).parsed");
// if machineName is null, look up all review sections by recursion
if (machineName == null || machineName.length() == 0) {
dr = null;
for (AuditMachineDto aumachine : getMachines()) {
if (aumachine.getName() != null) {
rec = getMachineReviewSections(aumachine.getName());
if (dr == null) {
dr = rec;
}
else {
dr.addAll(rec);
}
}
}
return dr;
}
// otherwise, just look up this one machine
hostDir = new File(logDirStr + "/" + machineName + "/audit");
if (!hostDir.exists()) {
return new DataResult(new LinkedList());
}
for (String auditLog : hostDir.list()) {
fnmatch = fnregex.matcher(auditLog);
if (fnmatch.matches()) { // found a matching audit file
start = Long.parseLong(fnmatch.group(1)) * 1000;
end = Long.parseLong(fnmatch.group(2)) * 1000;
try { // but is it reviewed yet?
aurevs.add(getReviewInfo(machineName, start, end));
}
catch (IOException ioex) { // on error, assume unreviewed
aurevs.add(new AuditReviewDto(machineName, new Date(start),
new Date(end), null, null));
}
}
}
Collections.sort(aurevs);
dr = new DataResult<AuditReviewDto>(aurevs);
return dr;
}
/**
* Retrieve the review info for a specified machine/time
* @param machine The machine name
* @param start The start time in ms from the epoch
* @param end The end time in ms from the epoch
* @throws IOException Throws when the audit review file is unreadable
* @return An AuditReviewDto, possibly with review info set
*/
public static AuditReviewDto getReviewInfo(String machine, long start,
long end) throws IOException {
BufferedReader brdr;
Date reviewedOn = null;
String str, part1, reviewedBy = null;
String[] revInfo;
part1 = machine + "," + (start / 1000) + "," + (end / 1000) + ",";
brdr = new BufferedReader(new FileReader(reviewFile));
while ((str = brdr.readLine()) != null) {
if (str.startsWith(part1)) {
revInfo = str.split(",");
reviewedBy = revInfo[3];
reviewedOn = new Date(Long.parseLong(revInfo[4]) * 1000);
break;
}
}
brdr.close();
return new AuditReviewDto(machine, new Date(start), new Date(end),
reviewedBy, reviewedOn);
}
private static List readAuditFile(File aufile, String[] types, Long start,
Long end) throws IOException {
int milli = 0, serial = -1;
BufferedReader brdr;
LinkedHashMap<String, String> hmap;
LinkedList<AuditDto> events;
Long time = -1L;
String node = null, str, strtime = null;
brdr = new BufferedReader(new FileReader(aufile));
events = new LinkedList<AuditDto>();
hmap = new LinkedHashMap<String, String>();
for (str = brdr.readLine(); str != null; str = brdr.readLine()) {
if (str.equals("")) {
strtime = hmap.remove("seconds");
try {
serial = Integer.parseInt(hmap.remove("serial"));
}
catch (NumberFormatException nfex) {
serial = -1;
}
try {
time = Long.parseLong(strtime) * 1000;
}
catch (NumberFormatException nfex) {
time = 0L;
}
if (time >= start && time <= end) {
for (String type : types) {
if (type.equals(hmap.get("type"))) {
events.add(new AuditDto(
serial, new Date(time), milli, node, hmap));
break;
}
}
}
hmap.clear();
}
else if (str.indexOf('=') >= 0) {
hmap.put(
str.substring(0, str.indexOf('=')).trim(),
str.substring(str.indexOf('=') + 1).trim());
}
else {
log.debug("unknown string: " + str);
}
}
brdr.close();
return events;
}
}