/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2011-2014 ForgeRock AS
*/
/**
* This class provides the base implementation of the access loggers used by the
* directory server.
*
* @param <T>
* The type of access log publisher configuration.
*/
public abstract class AbstractTextAccessLogPublisher
<T extends AccessLogPublisherCfg> extends AccessLogPublisher<T>
{
/**
* Criteria based filter.
*/
{
private final boolean logConnectRecords;
private final boolean logDisconnectRecords;
private final int[] clientPorts;
/**
* Creates a new criteria based filter.
*
* @param cfg
* The access log filter criteria.
* @throws ConfigException
* If the configuration cannot be parsed.
*/
throws ConfigException
{
// Generate a unique identifier for attaching partial results to
// operations.
// Pre-parse the log record types for more efficient queries.
{
logConnectRecords = true;
logDisconnectRecords = true;
}
else
{
{
switch (type)
{
case ABANDON:
break;
case ADD:
break;
case BIND:
break;
case COMPARE:
break;
case DELETE:
break;
case EXTENDED:
break;
case MODIFY:
break;
case RENAME:
break;
case SEARCH:
break;
case UNBIND:
break;
default: // Ignore CONNECT/DISCONNECT
break;
}
}
}
// The list of ports is likely to be small and a simple array lookup will
// be more efficient, avoiding auto-boxing conversions as well.
int i = 0;
{
clientPorts[i++] = port;
}
i = 0;
{
}
}
throws ConfigException
{
int i = 0;
{
try
{
}
catch (final DirectoryException e)
{
}
}
return results;
}
/**
* {@inheritDoc}
*/
{
if (!logConnectRecords)
{
return false;
}
if (!filterClientConnection(connection))
{
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
{
if (!logDisconnectRecords)
{
return false;
}
if (!filterClientConnection(connection))
{
return false;
}
if (!filterUser(connection))
{
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
{
.getOperationType())
// Cache the result so that it does not need to be recomputed for the
// response.
return matches;
}
/**
* {@inheritDoc}
*/
{
// First check the result that was computed for the initial request.
if (requestMatched == null)
{
// This should not happen.
if (debugEnabled())
{
"Operation attachment %s not found while logging response",
}
}
if (!requestMatched)
{
return false;
}
// Check the response parameters.
if (!filterResponse(operation))
{
return false;
}
return true;
}
{
// Check protocol.
{
boolean found = false;
for (final String p : clientProtocols)
{
{
found = true;
break;
}
}
if (!found)
{
return false;
}
}
// Check server port.
{
boolean found = false;
for (final int p : clientPorts)
{
if (port == p)
{
found = true;
break;
}
}
if (!found)
{
return false;
}
}
// Check client address.
if (!clientAddressNotEqualTo.isEmpty()
{
return false;
}
if (!clientAddressEqualTo.isEmpty()
{
return false;
}
return true;
}
{
// Check target DN.
{
if (!filterRequestTargetDN(operation))
{
return false;
}
}
// TODO: check required controls.
return true;
}
{
// Obtain both the parsed and unparsed target DNs. Requests are logged
// before parsing so usually only the raw unparsed target DN will be
// present, and it may even be invalid.
switch (operation.getOperationType())
{
case ABANDON:
case UNBIND:
// These operations don't have parameters which we can filter so
// always match them.
return true;
case EXTENDED:
// These operations could have parameters which can be filtered but
// we'd need to decode the request in order to find out. This is
// beyond the scope of the access log. Therefore, treat extended
return true;
case ADD:
break;
case BIND:
// For SASL bind operations the bind DN, if provided, will require the
// SASL credentials to be decoded which is beyond the scope of the
// access log.
break;
case COMPARE:
break;
case DELETE:
break;
case MODIFY:
break;
case MODIFY_DN:
break;
case SEARCH:
break;
}
// Attempt to parse the raw target DN if needed.
{
try
{
}
catch (final DirectoryException e)
{
// The DN raw target DN was invalid:
// Invalid DN will never match equal-to patterns,
// Invalid DN does not match any not-equal-to patterns,
// so return appropriate result.
}
}
}
{
{
{
{
return false;
}
}
}
{
{
{
return true;
}
}
return false;
}
return true;
}
{
// Check response code.
{
return false;
}
{
return false;
}
// Check etime.
{
{
return false;
}
}
{
{
return false;
}
}
// Check search response fields.
if (operation instanceof SearchOperation)
{
{
boolean wasUnindexed = false;
{
{
wasUnindexed = true;
break;
}
}
if (isIndexed)
{
if (wasUnindexed)
{
return false;
}
}
else
{
if (!wasUnindexed)
{
return false;
}
}
}
if (nentriesGT != null)
{
if (nentries <= nentriesGT)
{
return false;
}
}
if (nentriesLT != null)
{
if (nentries >= nentriesLT)
{
return false;
}
}
}
return true;
}
{
// Check user DN.
{
if (!filterUserBindDN(connection))
{
return false;
}
}
// Check group membership.
{
if (!filterUserIsMemberOf(connection))
{
return false;
}
}
return true;
}
{
// Fast-path for unauthenticated clients.
{
}
}
{
// Fast-path for unauthenticated clients.
{
}
{
{
try
{
{
return false;
}
}
catch (final DirectoryException e)
{
if (debugEnabled())
{
}
}
}
}
{
{
try
{
{
return true;
}
}
catch (final DirectoryException e)
{
if (debugEnabled())
{
}
}
}
return false;
}
return true;
}
}
/**
* Log message filter predicate.
*/
static interface Filter
{
/**
* Returns {@code true} if the provided client connect should be logged.
*
* @param connection
* The client connection.
* @return {@code true} if the provided client connect should be logged.
*/
/**
* Returns {@code true} if the provided client disconnect should be logged.
*
* @param connection
* The client connection.
* @return {@code true} if the provided client disconnect should be logged.
*/
/**
* Returns {@code true} if the provided request should be logged.
*
* @param operation
* The request.
* @return {@code true} if the provided request should be logged.
*/
/**
* Returns {@code true} if the provided response should be logged.
*
* @param operation
* The response.
* @return {@code true} if the provided response should be logged.
*/
}
/**
* A filter which performs a logical OR over a set of sub-filters.
*/
{
/**
* Creates a new OR filter.
*
* @param subFilters
* The sub-filters.
*/
{
this.subFilters = subFilters;
}
/**
* {@inheritDoc}
*/
{
{
{
// Succeed fast.
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
{
{
{
// Succeed fast.
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
{
{
{
// Succeed fast.
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
{
{
{
// Succeed fast.
return true;
}
}
return false;
}
}
/**
* The root filter which first checks the logger configuration, delegating to
* a sub-filter if needed.
*/
{
private final boolean suppressInternalOperations;
private final boolean suppressSynchronizationOperations;
/**
* Creates a new root filter.
*
* @param suppressInternal
* Indicates whether internal operations should be suppressed.
* @param suppressSynchronization
* Indicates whether sync operations should be suppressed.
* @param policy
* The filtering policy.
* @param subFilter
* The sub-filters.
*/
{
}
/**
* {@inheritDoc}
*/
{
{
switch (policy)
{
case INCLUSIVE:
case EXCLUSIVE:
default: // NO_FILTERING:
return true;
}
}
else
{
return false;
}
}
/**
* {@inheritDoc}
*/
{
{
switch (policy)
{
case INCLUSIVE:
case EXCLUSIVE:
default: // NO_FILTERING:
return true;
}
}
else
{
return false;
}
}
/**
* {@inheritDoc}
*/
{
if (isLoggable(operation))
{
switch (policy)
{
case INCLUSIVE:
case EXCLUSIVE:
default: // NO_FILTERING:
return true;
}
}
else
{
return false;
}
}
/**
* {@inheritDoc}
*/
{
if (isLoggable(operation))
{
switch (policy)
{
case INCLUSIVE:
case EXCLUSIVE:
default: // NO_FILTERING:
return true;
}
}
else
{
return false;
}
}
/**
* Determines whether the provided operation should be logged.
*
* @param operation
* the operation to check
* @return true if the operation is loggable, false otherwise
*/
{
{
return !suppressSynchronizationOperations;
}
else if (operation.isInnerOperation())
{
return !suppressInternalOperations;
}
return true;
}
}
/**
* Configuration change listener.
*/
private final class ChangeListener implements
{
/**
* {@inheritDoc}
*/
{
// Update the configuration.
cfg = configuration;
// Rebuild the filter using the new configuration and criteria.
buildFilters();
}
/**
* {@inheritDoc}
*/
public final boolean isConfigurationChangeAcceptable(
{
return true;
}
}
/**
* Filter criteria configuration listener.
*/
private final class FilterListener implements
{
/**
* {@inheritDoc}
*/
{
// Rebuild the filter using the new configuration and criteria.
buildFilters();
configuration.addChangeListener(this);
}
/**
* {@inheritDoc}
*/
{
// Rebuild the filter using the new configuration and criteria.
buildFilters();
}
/**
* {@inheritDoc}
*/
{
// Rebuild the filter using the new configuration and criteria.
buildFilters();
}
/**
* {@inheritDoc}
*/
public boolean isConfigurationAddAcceptable(
{
}
/**
* {@inheritDoc}
*/
public boolean isConfigurationChangeAcceptable(
{
}
/**
* {@inheritDoc}
*/
public boolean isConfigurationDeleteAcceptable(
{
// Always allow criteria to be deleted.
return true;
}
private boolean validateConfiguration(
{
try
{
new CriteriaFilter(configuration);
return true;
}
catch (final ConfigException e)
{
return false;
}
}
}
/**
* The tracer object for the debug logger.
*/
/**
* {@inheritDoc}
*/
public final void close()
{
try
{
close0();
}
finally
{
{
{
try
{
}
catch (final ConfigException e)
{
// Ignore.
}
}
}
}
}
/**
* {@inheritDoc}
*/
{
}
/**
* For startup access logger.
*
* @param suppressInternal
* {@code true} if internal operations should be suppressed.
*/
{
}
/**
* Release any resources owned by the sub-implementation.
*/
protected abstract void close0();
/**
* Initializes the filter configuration. This method must be called by
* sub-classes during initialization.
*
* @param config
* The access publisher configuration that contains the information
* to use to initialize this access publisher.
* @throws ConfigException
* If an unrecoverable problem arises in the process of performing
* the initialization as a result of the server configuration.
* @throws InitializationException
* If a problem occurs during initialization that is not related to
* the server configuration.
*/
{
// Now initialize filters and listeners.
// Rebuild the filter using the new configuration and criteria.
buildFilters();
// Add change listeners.
{
try
{
}
catch (final ConfigException e)
{
// Ignore.
}
}
}
/**
* Returns {@code true} if the provided client connect should be logged.
*
* @param c
* The client connection.
* @return {@code true} if the provided client connect should be logged.
*/
{
return filter.isConnectLoggable(c);
}
/**
* Returns {@code true} if the provided client disconnect should be logged.
*
* @param c
* The client connection.
* @return {@code true} if the provided client disconnect should be logged.
*/
{
return filter.isDisconnectLoggable(c);
}
/**
* Perform any initialization required by the sub-implementation.
*
* @param config
* The access publisher configuration that contains the information
* to use to initialize this access publisher.
* @param unacceptableReasons
* A list that may be used to hold the reasons that the provided
* configuration is not acceptable.
* @return {@code true} if the provided configuration is acceptable for this
* access log publisher, or {@code false} if not.
*/
{
{
try
{
new CriteriaFilter(criteriaCfg);
}
catch (final ConfigException e)
{
return false;
}
}
return true;
}
/**
* Returns {@code true} if the provided request should be logged.
*
* @param o
* The request.
* @return {@code true} if the provided request should be logged.
*/
{
return filter.isRequestLoggable(o);
}
/**
* Returns {@code true} if the provided response should be logged.
*
* @param o
* The response.
* @return {@code true} if the provided response should be logged.
*/
{
return filter.isResponseLoggable(o);
}
// Build an appropriate set of filters based on the configuration.
private void buildFilters()
{
}
{
{
{
try
{
}
catch (final ConfigException e)
{
// This should not happen if the configuration has already been
// validated.
throw new RuntimeException(e);
}
}
}
orFilter);
}
}