opends/resource/schema/02-config.ldif
@@ -2973,7 +2973,8 @@ NAME 'ds-cfg-access-log-publisher' SUP ds-cfg-log-publisher STRUCTURAL MAY ( ds-cfg-suppress-internal-operations $ MAY ( ds-cfg-filtering-policy $ ds-cfg-suppress-internal-operations $ ds-cfg-suppress-synchronization-operations ) X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.23 @@ -3008,8 +3009,7 @@ ds-cfg-buffer-size $ ds-cfg-auto-flush $ ds-cfg-append $ ds-cfg-queue-size $ ds-cfg-filtering-policy ) ds-cfg-queue-size ) X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.26 NAME 'ds-cfg-file-based-debug-log-publisher' opends/src/admin/defn/org/opends/server/admin/std/AccessLogPublisherConfiguration.xml
@@ -24,6 +24,7 @@ ! ! ! Copyright 2007-2008 Sun Microsystems, Inc. ! Portions copyright 2011 ForgeRock AS. ! --> <adm:managed-object name="access-log-publisher" plural-name="access-log-publishers" @@ -49,6 +50,15 @@ <adm:profile name="cli"> <cli:managed-object custom="true" /> </adm:profile> <adm:relation name="access-log-filtering-criteria"> <adm:synopsis> The set of criteria which will be used to filter log records. </adm:synopsis> <adm:one-to-many/> <adm:profile name="ldap"> <ldap:rdn-sequence>cn=Filtering Criteria</ldap:rdn-sequence> </adm:profile> </adm:relation> <adm:property name="java-class" mandatory="true"> <adm:synopsis> The fully-qualified name of the Java class that provides the @@ -68,6 +78,42 @@ </ldap:attribute> </adm:profile> </adm:property> <adm:property name="filtering-policy"> <adm:synopsis> Specifies how filtering criteria should be applied to log records. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>no-filtering</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:enumeration> <adm:value name="no-filtering"> <adm:synopsis> No filtering will be performed, and all records will be logged. </adm:synopsis> </adm:value> <adm:value name="inclusive"> <adm:synopsis> Records must match at least one of the filtering criteria in order to be logged. </adm:synopsis> </adm:value> <adm:value name="exclusive"> <adm:synopsis> Records must not match any of the filtering criteria in order to be logged. </adm:synopsis> </adm:value> </adm:enumeration> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-filtering-policy</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="suppress-internal-operations" advanced="true"> <adm:synopsis> Indicates whether internal operations (for example, operations opends/src/admin/defn/org/opends/server/admin/std/FileBasedAccessLogPublisherConfiguration.xml
@@ -41,15 +41,6 @@ <ldap:superior>ds-cfg-access-log-publisher</ldap:superior> </ldap:object-class> </adm:profile> <adm:relation name="access-log-filtering-criteria"> <adm:synopsis> The set of criteria which will be used to filter log records. </adm:synopsis> <adm:one-to-many/> <adm:profile name="ldap"> <ldap:rdn-sequence>cn=Filtering Criteria</ldap:rdn-sequence> </adm:profile> </adm:relation> <adm:property-override name="java-class" advanced="true"> <adm:default-behavior> <adm:defined> @@ -295,40 +286,4 @@ </ldap:attribute> </adm:profile> </adm:property> <adm:property name="filtering-policy"> <adm:synopsis> Specifies how filtering criteria should be applied to log records. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>no-filtering</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:enumeration> <adm:value name="no-filtering"> <adm:synopsis> No filtering will be performed, and all records will be logged. </adm:synopsis> </adm:value> <adm:value name="inclusive"> <adm:synopsis> Records must match at least one of the filtering criteria in order to be logged. </adm:synopsis> </adm:value> <adm:value name="exclusive"> <adm:synopsis> Records must not match any of the filtering criteria in order to be logged. </adm:synopsis> </adm:value> </adm:enumeration> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-filtering-policy</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opends/src/admin/messages/AccessLogPublisherCfgDefn.properties
@@ -3,6 +3,13 @@ synopsis=Access Log Publishers are responsible for distributing access log messages from the access logger to a destination. description=Access log messages provide information about the types of operations processed by the server. property.enabled.synopsis=Indicates whether the Access Log Publisher is enabled for use. property.filtering-policy.synopsis=Specifies how filtering criteria should be applied to log records. property.filtering-policy.syntax.enumeration.value.exclusive.synopsis=Records must not match any of the filtering criteria in order to be logged. property.filtering-policy.syntax.enumeration.value.inclusive.synopsis=Records must match at least one of the filtering criteria in order to be logged. property.filtering-policy.syntax.enumeration.value.no-filtering.synopsis=No filtering will be performed, and all records will be logged. property.java-class.synopsis=The fully-qualified name of the Java class that provides the Access Log Publisher implementation. property.suppress-internal-operations.synopsis=Indicates whether internal operations (for example, operations that are initiated by plugins) should be logged along with the operations that are requested by users. property.suppress-synchronization-operations.synopsis=Indicates whether access messages that are generated by synchronization operations should be suppressed. relation.access-log-filtering-criteria.user-friendly-name=Access Log Filtering Criteria relation.access-log-filtering-criteria.user-friendly-plural-name=Access Log Filtering Criteria relation.access-log-filtering-criteria.synopsis=The set of criteria which will be used to filter log records. opends/src/admin/messages/FileBasedAuditLogPublisherCfgDefn.properties
@@ -7,6 +7,10 @@ property.auto-flush.description=If the asynchronous writes option is used, the writer is flushed after all the log records in the queue are written. property.buffer-size.synopsis=Specifies the log file buffer size. property.enabled.synopsis=Indicates whether the File Based Audit Log Publisher is enabled for use. property.filtering-policy.synopsis=Specifies how filtering criteria should be applied to log records. property.filtering-policy.syntax.enumeration.value.exclusive.synopsis=Records must not match any of the filtering criteria in order to be logged. property.filtering-policy.syntax.enumeration.value.inclusive.synopsis=Records must match at least one of the filtering criteria in order to be logged. property.filtering-policy.syntax.enumeration.value.no-filtering.synopsis=No filtering will be performed, and all records will be logged. property.java-class.synopsis=The fully-qualified name of the Java class that provides the File Based Audit Log Publisher implementation. property.log-file.synopsis=The file name to use for the log files generated by the File Based Audit Log Publisher. The path to the file is relative to the server root. property.log-file.syntax.string.pattern.synopsis=A path to an existing file that is readable by the server. @@ -24,3 +28,6 @@ property.suppress-internal-operations.synopsis=Indicates whether internal operations (for example, operations that are initiated by plugins) should be logged along with the operations that are requested by users. property.suppress-synchronization-operations.synopsis=Indicates whether access messages that are generated by synchronization operations should be suppressed. property.time-interval.synopsis=Specifies the interval at which to check whether the log files need to be rotated. relation.access-log-filtering-criteria.user-friendly-name=Access Log Filtering Criteria relation.access-log-filtering-criteria.user-friendly-plural-name=Access Log Filtering Criteria relation.access-log-filtering-criteria.synopsis=The set of criteria which will be used to filter log records. opends/src/server/org/opends/server/api/AccessLogPublisher.java
@@ -96,8 +96,7 @@ * @return {@code true} if the provided configuration is acceptable * for this access log publisher, or {@code false} if not. */ public boolean isConfigurationAcceptable( AccessLogPublisherCfg configuration, public boolean isConfigurationAcceptable(T configuration, List<Message> unacceptableReasons) { // This default implementation does not perform any special opends/src/server/org/opends/server/loggers/AbstractTextAccessLogPublisher.java
New file @@ -0,0 +1,1365 @@ /* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 ForgeRock AS */ package org.opends.server.loggers; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.server.util.StaticUtils.toLowerCase; import java.net.InetAddress; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationAddListener; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.server.ConfigurationDeleteListener; import org.opends.server.admin.std.meta.AccessLogFilteringCriteriaCfgDefn.*; import org.opends.server.admin.std.meta.AccessLogPublisherCfgDefn.*; import org.opends.server.admin.std.server.AccessLogFilteringCriteriaCfg; import org.opends.server.admin.std.server.AccessLogPublisherCfg; import org.opends.server.api.AccessLogPublisher; import org.opends.server.api.ClientConnection; import org.opends.server.api.Group; import org.opends.server.authorization.dseecompat.PatternDN; import org.opends.server.config.ConfigException; import org.opends.server.core.*; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.*; /** * 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. */ static final class CriteriaFilter implements Filter { private final AccessLogFilteringCriteriaCfg cfg; private final boolean logConnectRecords; private final boolean logDisconnectRecords; private final EnumSet<OperationType> logOperationRecords; private final AddressMask[] clientAddressEqualTo; private final AddressMask[] clientAddressNotEqualTo; private final PatternDN[] userDNEqualTo; private final PatternDN[] userDNNotEqualTo; private final PatternDN[] targetDNEqualTo; private final PatternDN[] targetDNNotEqualTo; private final DN[] userIsMemberOf; private final DN[] userIsNotMemberOf; private final String attachmentName; /** * Creates a new criteria based filter. * * @param cfg * The access log filter criteria. * @throws DirectoryException * If the configuration cannot be parsed. */ CriteriaFilter(final AccessLogFilteringCriteriaCfg cfg) throws DirectoryException { this.cfg = cfg; // Generate a unique identifier for attaching partial results to // operations. attachmentName = this.getClass().getName() + "#" + hashCode(); // Pre-parse the log record types for more efficient queries. if (cfg.getLogRecordType().isEmpty()) { logConnectRecords = true; logDisconnectRecords = true; logOperationRecords = EnumSet.allOf(OperationType.class); } else { logConnectRecords = cfg.getLogRecordType().contains( LogRecordType.CONNECT); logDisconnectRecords = cfg.getLogRecordType().contains( LogRecordType.DISCONNECT); logOperationRecords = EnumSet.noneOf(OperationType.class); for (final LogRecordType type : cfg.getLogRecordType()) { switch (type) { case ABANDON: logOperationRecords.add(OperationType.ABANDON); break; case ADD: logOperationRecords.add(OperationType.ADD); break; case BIND: logOperationRecords.add(OperationType.BIND); break; case COMPARE: logOperationRecords.add(OperationType.COMPARE); break; case DELETE: logOperationRecords.add(OperationType.DELETE); break; case EXTENDED: logOperationRecords.add(OperationType.EXTENDED); break; case MODIFY: logOperationRecords.add(OperationType.MODIFY); break; case RENAME: logOperationRecords.add(OperationType.MODIFY_DN); break; case SEARCH: logOperationRecords.add(OperationType.SEARCH); break; case UNBIND: logOperationRecords.add(OperationType.UNBIND); break; default: // Ignore CONNECT/DISCONNECT break; } } } clientAddressEqualTo = cfg.getClientAddressEqualTo().toArray( new AddressMask[0]); clientAddressNotEqualTo = cfg.getClientAddressNotEqualTo().toArray( new AddressMask[0]); userDNEqualTo = new PatternDN[cfg.getUserDNEqualTo().size()]; int i = 0; for (final String s : cfg.getUserDNEqualTo()) { userDNEqualTo[i++] = PatternDN.decode(s); } userDNNotEqualTo = new PatternDN[cfg.getUserDNNotEqualTo().size()]; i = 0; for (final String s : cfg.getUserDNNotEqualTo()) { userDNNotEqualTo[i++] = PatternDN.decode(s); } userIsMemberOf = cfg.getUserIsMemberOf().toArray(new DN[0]); userIsNotMemberOf = cfg.getUserIsNotMemberOf().toArray(new DN[0]); targetDNEqualTo = new PatternDN[cfg.getRequestTargetDNEqualTo().size()]; i = 0; for (final String s : cfg.getRequestTargetDNEqualTo()) { targetDNEqualTo[i++] = PatternDN.decode(s); } targetDNNotEqualTo = new PatternDN[cfg.getRequestTargetDNNotEqualTo() .size()]; i = 0; for (final String s : cfg.getRequestTargetDNNotEqualTo()) { targetDNNotEqualTo[i++] = PatternDN.decode(s); } } /** * {@inheritDoc} */ @Override public boolean isConnectLoggable(final ClientConnection connection) { if (!logConnectRecords) { return false; } if (!filterClientConnection(connection)) { return false; } return true; } /** * {@inheritDoc} */ @Override public boolean isDisconnectLoggable(final ClientConnection connection) { if (!logDisconnectRecords) { return false; } if (!filterClientConnection(connection)) { return false; } if (!filterUser(connection)) { return false; } return true; } /** * {@inheritDoc} */ @Override public boolean isRequestLoggable(final Operation operation) { final ClientConnection connection = operation.getClientConnection(); final boolean matches = logOperationRecords.contains(operation .getOperationType()) && filterClientConnection(connection) && filterUser(connection) && filterRequest(operation); // Cache the result so that it does not need to be recomputed for the // response. operation.setAttachment(attachmentName, matches); return matches; } /** * {@inheritDoc} */ @Override public boolean isResponseLoggable(final Operation operation) { // First check the result that was computed for the initial request. Boolean requestMatched = (Boolean) operation .getAttachment(attachmentName); if (requestMatched == null) { // This should not happen. if (debugEnabled()) { TRACER.debugWarning( "Operation attachment %s not found while logging response", attachmentName); } requestMatched = isRequestLoggable(operation); } if (!requestMatched) { return false; } // Check the response parameters. if (!filterResponse(operation)) { return false; } return true; } private boolean filterClientConnection(final ClientConnection connection) { // Check client address. final InetAddress ipAddr = connection.getRemoteAddress(); if (clientAddressNotEqualTo.length > 0) { if (AddressMask.maskListContains(ipAddr, clientAddressNotEqualTo)) { return false; } } if (clientAddressEqualTo.length > 0) { if (!AddressMask.maskListContains(ipAddr, clientAddressEqualTo)) { return false; } } // Check server port. if (!cfg.getClientPortEqualTo().isEmpty()) { if (!cfg.getClientPortEqualTo().contains(connection.getServerPort())) { return false; } } // Check protocol. if (!cfg.getClientProtocolEqualTo().isEmpty()) { if (!cfg.getClientProtocolEqualTo().contains( toLowerCase(connection.getProtocol()))) { return false; } } return true; } private boolean filterRequest(final Operation operation) { // Check target DN. if (targetDNNotEqualTo.length > 0 || targetDNEqualTo.length > 0) { if (!filterRequestTargetDN(operation)) { return false; } } // TODO: check required controls. return true; } private boolean filterRequestTargetDN(final Operation operation) { // 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. DN targetDN = null; ByteString rawTargetDN = null; 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 // operations like abandon/unbind. return true; case ADD: targetDN = ((AddOperation) operation).getEntryDN(); rawTargetDN = ((AddOperation) operation).getRawEntryDN(); 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. targetDN = ((BindOperation) operation).getBindDN(); rawTargetDN = ((BindOperation) operation).getRawBindDN(); break; case COMPARE: targetDN = ((CompareOperation) operation).getEntryDN(); rawTargetDN = ((CompareOperation) operation).getRawEntryDN(); break; case DELETE: targetDN = ((DeleteOperation) operation).getEntryDN(); rawTargetDN = ((DeleteOperation) operation).getRawEntryDN(); break; case MODIFY: targetDN = ((ModifyOperation) operation).getEntryDN(); rawTargetDN = ((ModifyOperation) operation).getRawEntryDN(); break; case MODIFY_DN: targetDN = ((ModifyDNOperation) operation).getEntryDN(); rawTargetDN = ((ModifyDNOperation) operation).getRawEntryDN(); break; case SEARCH: targetDN = ((SearchOperation) operation).getBaseDN(); rawTargetDN = ((SearchOperation) operation).getRawBaseDN(); break; } // Attempt to parse the raw target DN if needed. if (targetDN == null) { try { targetDN = DN.decode(rawTargetDN); } catch (final DirectoryException e) { // The DN raw target DN was invalid. It will never match any // not-equal-to nor equal-to patterns, so return appropriate result. if (targetDNEqualTo.length != 0) { // Invalid DN will never match equal-to patterns. return false; } else { // Invalid DN does not match any not-equal-to patterns. return true; } } } if (targetDNNotEqualTo.length > 0) { for (final PatternDN pattern : targetDNNotEqualTo) { if (pattern.matchesDN(targetDN)) { return false; } } } if (targetDNEqualTo.length > 0) { for (final PatternDN pattern : targetDNNotEqualTo) { if (pattern.matchesDN(targetDN)) { return true; } } } // The target DN did not match. return false; } private boolean filterResponse(final Operation operation) { // Check response code. final Integer resultCode = operation.getResultCode().getIntValue(); if (!cfg.getResponseResultCodeNotEqualTo().isEmpty()) { if (cfg.getResponseResultCodeNotEqualTo().contains(resultCode)) { return false; } } if (!cfg.getResponseResultCodeEqualTo().isEmpty()) { if (!cfg.getResponseResultCodeNotEqualTo().contains(resultCode)) { return false; } } // Check etime. final long etime = operation.getProcessingTime(); final Integer etimeGT = cfg.getResponseEtimeLessThan(); if (etimeGT != null) { if (etime <= ((long) etimeGT)) { return false; } } final Integer etimeLT = cfg.getResponseEtimeLessThan(); if (etimeLT != null) { if (etime >= ((long) etimeLT)) { return false; } } // Check search response fields. if (operation instanceof SearchOperation) { final SearchOperation searchOperation = (SearchOperation) operation; final Boolean isIndexed = cfg.isSearchResponseIsIndexed(); if (isIndexed != null) { boolean wasUnindexed = false; for (final AdditionalLogItem item : operation.getAdditionalLogItems()) { if (item.getKey().equals("unindexed")) { wasUnindexed = true; break; } } if (isIndexed) { if (wasUnindexed) { return false; } } else { if (!wasUnindexed) { return false; } } } final int nentries = searchOperation.getEntriesSent(); final Integer nentriesGT = cfg.getSearchResponseNentriesGreaterThan(); if (nentriesGT != null) { if (nentries <= nentriesGT) { return false; } } final Integer nentriesLT = cfg.getSearchResponseNentriesLessThan(); if (nentriesLT != null) { if (nentries >= nentriesLT) { return false; } } } return true; } private boolean filterUser(final ClientConnection connection) { // Check user DN. if (userDNNotEqualTo.length > 0 || userDNEqualTo.length > 0) { if (!filterUserBindDN(connection)) { return false; } } // Check group membership. if (userIsNotMemberOf.length > 0 || userIsNotMemberOf.length > 0) { if (!filterUserIsMemberOf(connection)) { return false; } } return true; } private boolean filterUserBindDN(final ClientConnection connection) { final DN userDN = connection.getAuthenticationInfo() .getAuthenticationDN(); // Fast-path for unauthenticated clients. if (userDN == null) { return userDNEqualTo.length == 0; } if (userDNNotEqualTo.length > 0) { for (final PatternDN pattern : userDNNotEqualTo) { if (pattern.matchesDN(userDN)) { return false; } } } if (userDNEqualTo.length > 0) { for (final PatternDN pattern : userDNNotEqualTo) { if (pattern.matchesDN(userDN)) { return true; } } } // The user DN did not match. return false; } private boolean filterUserIsMemberOf(final ClientConnection connection) { final Entry userEntry = connection.getAuthenticationInfo() .getAuthenticationEntry(); // Fast-path for unauthenticated clients. if (userEntry == null) { return userIsMemberOf.length == 0; } final GroupManager groupManager = DirectoryServer.getGroupManager(); if (userIsNotMemberOf.length > 0) { for (final DN groupDN : userIsNotMemberOf) { final Group<?> group = groupManager.getGroupInstance(groupDN); try { if ((group != null) && group.isMember(userEntry)) { return false; } } catch (final DirectoryException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } if (userIsMemberOf.length > 0) { for (final DN groupDN : userIsMemberOf) { final Group<?> group = groupManager.getGroupInstance(groupDN); try { if ((group != null) && group.isMember(userEntry)) { return true; } } catch (final DirectoryException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } // The user entry did not match. return false; } } /** * 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. */ boolean isConnectLoggable(ClientConnection connection); /** * 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. */ boolean isDisconnectLoggable(ClientConnection connection); /** * Returns {@code true} if the provided request should be logged. * * @param operation * The request. * @return {@code true} if the provided request should be logged. */ boolean isRequestLoggable(Operation operation); /** * Returns {@code true} if the provided response should be logged. * * @param operation * The response. * @return {@code true} if the provided response should be logged. */ boolean isResponseLoggable(Operation operation); } /** * A filter which performs a logical OR over a set of sub-filters. */ static final class OrFilter implements Filter { private final Filter[] subFilters; /** * Creates a new OR filter. * * @param subFilters * The sub-filters. */ OrFilter(final Filter[] subFilters) { this.subFilters = subFilters; } /** * {@inheritDoc} */ @Override public boolean isConnectLoggable(final ClientConnection connection) { for (final Filter filter : subFilters) { if (filter.isConnectLoggable(connection)) { // Succeed fast. return true; } } return false; } /** * {@inheritDoc} */ @Override public boolean isDisconnectLoggable(final ClientConnection connection) { for (final Filter filter : subFilters) { if (filter.isDisconnectLoggable(connection)) { // Succeed fast. return true; } } return false; } /** * {@inheritDoc} */ @Override public boolean isRequestLoggable(final Operation operation) { for (final Filter filter : subFilters) { if (filter.isRequestLoggable(operation)) { // Succeed fast. return true; } } return false; } /** * {@inheritDoc} */ @Override public boolean isResponseLoggable(final Operation operation) { for (final Filter filter : subFilters) { if (filter.isResponseLoggable(operation)) { // Succeed fast. return true; } } return false; } } /** * The root filter which first checks the logger configuration, delegating to * a sub-filter if needed. */ static final class RootFilter implements Filter { private final Filter subFilter; private final boolean suppressInternalOperations; private final boolean suppressSynchronizationOperations; private final FilteringPolicy policy; /** * 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. */ RootFilter(final boolean suppressInternal, final boolean suppressSynchronization, final FilteringPolicy policy, final Filter subFilter) { this.suppressInternalOperations = suppressInternal; this.suppressSynchronizationOperations = suppressSynchronization; this.policy = policy; this.subFilter = subFilter; } /** * {@inheritDoc} */ @Override public boolean isConnectLoggable(final ClientConnection connection) { final long connectionID = connection.getConnectionID(); if (connectionID >= 0 || !suppressInternalOperations) { switch (policy) { case INCLUSIVE: return subFilter.isConnectLoggable(connection); case EXCLUSIVE: return !subFilter.isConnectLoggable(connection); default: // NO_FILTERING: return true; } } else { return false; } } /** * {@inheritDoc} */ @Override public boolean isDisconnectLoggable(final ClientConnection connection) { final long connectionID = connection.getConnectionID(); if (connectionID >= 0 || !suppressInternalOperations) { switch (policy) { case INCLUSIVE: return subFilter.isDisconnectLoggable(connection); case EXCLUSIVE: return !subFilter.isDisconnectLoggable(connection); default: // NO_FILTERING: return true; } } else { return false; } } /** * {@inheritDoc} */ @Override public boolean isRequestLoggable(final Operation operation) { if (isLoggable(operation)) { switch (policy) { case INCLUSIVE: return subFilter.isRequestLoggable(operation); case EXCLUSIVE: return !subFilter.isRequestLoggable(operation); default: // NO_FILTERING: return true; } } else { return false; } } /** * {@inheritDoc} */ @Override public boolean isResponseLoggable(final Operation operation) { if (isLoggable(operation)) { switch (policy) { case INCLUSIVE: return subFilter.isResponseLoggable(operation); case EXCLUSIVE: return !subFilter.isResponseLoggable(operation); default: // NO_FILTERING: return true; } } else { return false; } } // Determines whether the provided operation should be logged. private boolean isLoggable(final Operation operation) { final long connectionID = operation.getConnectionID(); if (connectionID < 0) { // This is an internal operation. if (operation.isSynchronizationOperation()) { return !suppressSynchronizationOperations; } else { return !suppressInternalOperations; } } return true; } } /** * Configuration change listener. */ private final class ChangeListener implements ConfigurationChangeListener<AccessLogPublisherCfg> { /** * {@inheritDoc} */ @Override public final ConfigChangeResult applyConfigurationChange( final AccessLogPublisherCfg configuration) { // Update the configuration. cfg = configuration; // Rebuild the filter using the new configuration and criteria. buildFilters(); return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ @Override public final boolean isConfigurationChangeAcceptable( final AccessLogPublisherCfg configuration, final List<Message> unacceptableReasons) { return true; } } /** * Filter criteria configuration listener. */ private final class FilterListener implements ConfigurationChangeListener<AccessLogFilteringCriteriaCfg>, ConfigurationAddListener<AccessLogFilteringCriteriaCfg>, ConfigurationDeleteListener<AccessLogFilteringCriteriaCfg> { /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationAdd( final AccessLogFilteringCriteriaCfg configuration) { // Rebuild the filter using the new configuration and criteria. buildFilters(); configuration.addChangeListener(this); return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( final AccessLogFilteringCriteriaCfg configuration) { // Rebuild the filter using the new configuration and criteria. buildFilters(); return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationDelete( final AccessLogFilteringCriteriaCfg configuration) { // Rebuild the filter using the new configuration and criteria. buildFilters(); return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ @Override public boolean isConfigurationAddAcceptable( final AccessLogFilteringCriteriaCfg configuration, final List<Message> unacceptableReasons) { return true; } /** * {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( final AccessLogFilteringCriteriaCfg configuration, final List<Message> unacceptableReasons) { return true; } /** * {@inheritDoc} */ @Override public boolean isConfigurationDeleteAcceptable( final AccessLogFilteringCriteriaCfg configuration, final List<Message> unacceptableReasons) { return true; } } /** * The tracer object for the debug logger. */ protected static final DebugTracer TRACER = getTracer(); private AccessLogPublisherCfg cfg = null; private Filter filter = null; private final ChangeListener changeListener = new ChangeListener(); private final FilterListener filterListener = new FilterListener(); /** * {@inheritDoc} */ @Override public final void close() { try { close0(); } finally { if (cfg != null) { cfg.removeAccessChangeListener(changeListener); for (final String criteriaName : cfg.listAccessLogFilteringCriteria()) { try { cfg.getAccessLogFilteringCriteria(criteriaName) .removeChangeListener(filterListener); } catch (final ConfigException e) { // Ignore. } } cfg.removeAccessLogFilteringCriteriaAddListener(filterListener); cfg.removeAccessLogFilteringCriteriaDeleteListener(filterListener); } } } /** * {@inheritDoc} */ @Override public final DN getDN() { return cfg != null ? cfg.dn() : null; } /** * 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. * @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. */ protected final void initializeFilters(T config) throws ConfigException, InitializationException { // Now initialize filters and listeners. cfg = config; // Rebuild the filter using the new configuration and criteria. buildFilters(); // Add change listeners. for (final String criteriaName : cfg.listAccessLogFilteringCriteria()) { try { cfg.getAccessLogFilteringCriteria(criteriaName).addChangeListener( filterListener); } catch (final ConfigException e) { // Ignore. } } cfg.addAccessLogFilteringCriteriaAddListener(filterListener); cfg.addAccessLogFilteringCriteriaDeleteListener(filterListener); cfg.addAccessChangeListener(changeListener); } /** * Release any resources owned by the sub-implementation. */ protected abstract void close0(); /** * 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. */ protected final boolean isConnectLoggable(final ClientConnection c) { 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. */ protected final boolean isDisconnectLoggable(final ClientConnection c) { return filter.isDisconnectLoggable(c); } /** * Returns {@code true} if the provided request should be logged. * * @param o * The request. * @return {@code true} if the provided request should be logged. */ protected final boolean isRequestLoggable(final Operation o) { 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. */ protected final boolean isResponseLoggable(final Operation o) { return filter.isResponseLoggable(o); } // Build an appropriate set of filters based on the configuration. private void buildFilters() { buildFilters(cfg.isSuppressInternalOperations(), cfg.isSuppressSynchronizationOperations(), cfg.getFilteringPolicy()); } /** * For startup access logger. * * @param suppressInternal * {@code true} if internal operations should be suppressed. */ protected void buildFilters(final boolean suppressInternal) { buildFilters(suppressInternal, false, FilteringPolicy.NO_FILTERING); } private void buildFilters(final boolean suppressInternal, final boolean suppressSynchronization, final FilteringPolicy policy) { final ArrayList<Filter> subFilters = new ArrayList<Filter>(); if (cfg != null) { for (final String criteriaName : cfg.listAccessLogFilteringCriteria()) { try { final AccessLogFilteringCriteriaCfg criteriaCfg = cfg .getAccessLogFilteringCriteria(criteriaName); subFilters.add(new CriteriaFilter(criteriaCfg)); } catch (final ConfigException e) { // TODO: Unable to decode this access log criteria, so log a warning // and continue. } catch (final DirectoryException e) { // TODO: Unable to decode this access log criteria, so log a warning // and continue. } } } final Filter orFilter = new OrFilter(subFilters.toArray(new Filter[0])); filter = new RootFilter(suppressInternal, suppressSynchronization, policy, orFilter); } } opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java
@@ -30,35 +30,24 @@ import static org.opends.messages.ConfigMessages.*; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.server.util.StaticUtils.getFileForPath; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import static org.opends.server.util.StaticUtils.toLowerCase; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.util.*; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import org.opends.messages.Message; import org.opends.messages.MessageBuilder; import org.opends.server.admin.server.ConfigurationAddListener; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.server.ConfigurationDeleteListener; import org.opends.server.admin.std.meta.AccessLogFilteringCriteriaCfgDefn.*; import org.opends.server.admin.std.meta.FileBasedAccessLogPublisherCfgDefn.*; import org.opends.server.admin.std.server.AccessLogFilteringCriteriaCfg; import org.opends.server.admin.std.server.AccessLogPublisherCfg; import org.opends.server.admin.std.server.FileBasedAccessLogPublisherCfg; import org.opends.server.api.AccessLogPublisher; import org.opends.server.api.ClientConnection; import org.opends.server.api.ExtendedOperationHandler; import org.opends.server.api.Group; import org.opends.server.authorization.dseecompat.PatternDN; import org.opends.server.config.ConfigException; import org.opends.server.core.*; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.*; import org.opends.server.util.TimeThread; @@ -68,1041 +57,10 @@ * This class provides the implementation of the access logger used by the * directory server. */ public class TextAccessLogPublisher extends AccessLogPublisher<FileBasedAccessLogPublisherCfg> implements public final class TextAccessLogPublisher extends AbstractTextAccessLogPublisher<FileBasedAccessLogPublisherCfg> implements ConfigurationChangeListener<FileBasedAccessLogPublisherCfg> { /** * Criteria based filter. */ static final class CriteriaFilter implements Filter { private final AccessLogFilteringCriteriaCfg cfg; private final boolean logConnectRecords; private final boolean logDisconnectRecords; private final EnumSet<OperationType> logOperationRecords; private final AddressMask[] clientAddressEqualTo; private final AddressMask[] clientAddressNotEqualTo; private final PatternDN[] userDNEqualTo; private final PatternDN[] userDNNotEqualTo; private final PatternDN[] targetDNEqualTo; private final PatternDN[] targetDNNotEqualTo; private final DN[] userIsMemberOf; private final DN[] userIsNotMemberOf; private final String attachmentName; /** * Creates a new criteria based filter. * * @param cfg * The access log filter criteria. * @throws DirectoryException * If the configuration cannot be parsed. */ CriteriaFilter(final AccessLogFilteringCriteriaCfg cfg) throws DirectoryException { this.cfg = cfg; // Generate a unique identifier for attaching partial results to // operations. attachmentName = this.getClass().getName() + "#" + hashCode(); // Pre-parse the log record types for more efficient queries. if (cfg.getLogRecordType().isEmpty()) { logConnectRecords = true; logDisconnectRecords = true; logOperationRecords = EnumSet.allOf(OperationType.class); } else { logConnectRecords = cfg.getLogRecordType().contains( LogRecordType.CONNECT); logDisconnectRecords = cfg.getLogRecordType().contains( LogRecordType.DISCONNECT); logOperationRecords = EnumSet.noneOf(OperationType.class); for (final LogRecordType type : cfg.getLogRecordType()) { switch (type) { case ABANDON: logOperationRecords.add(OperationType.ABANDON); break; case ADD: logOperationRecords.add(OperationType.ADD); break; case BIND: logOperationRecords.add(OperationType.BIND); break; case COMPARE: logOperationRecords.add(OperationType.COMPARE); break; case DELETE: logOperationRecords.add(OperationType.DELETE); break; case EXTENDED: logOperationRecords.add(OperationType.EXTENDED); break; case MODIFY: logOperationRecords.add(OperationType.MODIFY); break; case RENAME: logOperationRecords.add(OperationType.MODIFY_DN); break; case SEARCH: logOperationRecords.add(OperationType.SEARCH); break; case UNBIND: logOperationRecords.add(OperationType.UNBIND); break; default: // Ignore CONNECT/DISCONNECT break; } } } clientAddressEqualTo = cfg.getClientAddressEqualTo().toArray( new AddressMask[0]); clientAddressNotEqualTo = cfg.getClientAddressNotEqualTo().toArray( new AddressMask[0]); userDNEqualTo = new PatternDN[cfg.getUserDNEqualTo().size()]; int i = 0; for (final String s : cfg.getUserDNEqualTo()) { userDNEqualTo[i++] = PatternDN.decode(s); } userDNNotEqualTo = new PatternDN[cfg.getUserDNNotEqualTo().size()]; i = 0; for (final String s : cfg.getUserDNNotEqualTo()) { userDNNotEqualTo[i++] = PatternDN.decode(s); } userIsMemberOf = cfg.getUserIsMemberOf().toArray(new DN[0]); userIsNotMemberOf = cfg.getUserIsNotMemberOf().toArray(new DN[0]); targetDNEqualTo = new PatternDN[cfg.getRequestTargetDNEqualTo().size()]; i = 0; for (final String s : cfg.getRequestTargetDNEqualTo()) { targetDNEqualTo[i++] = PatternDN.decode(s); } targetDNNotEqualTo = new PatternDN[cfg.getRequestTargetDNNotEqualTo() .size()]; i = 0; for (final String s : cfg.getRequestTargetDNNotEqualTo()) { targetDNNotEqualTo[i++] = PatternDN.decode(s); } } /** * {@inheritDoc} */ public boolean isConnectLoggable(final ClientConnection connection) { if (!logConnectRecords) { return false; } if (!filterClientConnection(connection)) { return false; } return true; } /** * {@inheritDoc} */ public boolean isDisconnectLoggable(final ClientConnection connection) { if (!logDisconnectRecords) { return false; } if (!filterClientConnection(connection)) { return false; } if (!filterUser(connection)) { return false; } return true; } /** * {@inheritDoc} */ public boolean isRequestLoggable(final Operation operation) { final ClientConnection connection = operation.getClientConnection(); final boolean matches = logOperationRecords.contains(operation .getOperationType()) && filterClientConnection(connection) && filterUser(connection) && filterRequest(operation); // Cache the result so that it does not need to be recomputed for the // response. operation.setAttachment(attachmentName, matches); return matches; } /** * {@inheritDoc} */ public boolean isResponseLoggable(final Operation operation) { // First check the result that was computed for the initial request. Boolean requestMatched = (Boolean) operation .getAttachment(attachmentName); if (requestMatched == null) { // This should not happen. if (debugEnabled()) { TRACER.debugWarning( "Operation attachment %s not found while logging response", attachmentName); } requestMatched = isRequestLoggable(operation); } if (!requestMatched) { return false; } // Check the response parameters. if (!filterResponse(operation)) { return false; } return true; } private boolean filterClientConnection(final ClientConnection connection) { // Check client address. final InetAddress ipAddr = connection.getRemoteAddress(); if (clientAddressNotEqualTo.length > 0) { if (AddressMask.maskListContains(ipAddr, clientAddressNotEqualTo)) { return false; } } if (clientAddressEqualTo.length > 0) { if (!AddressMask.maskListContains(ipAddr, clientAddressEqualTo)) { return false; } } // Check server port. if (!cfg.getClientPortEqualTo().isEmpty()) { if (!cfg.getClientPortEqualTo().contains(connection.getServerPort())) { return false; } } // Check protocol. if (!cfg.getClientProtocolEqualTo().isEmpty()) { if (!cfg.getClientProtocolEqualTo().contains( toLowerCase(connection.getProtocol()))) { return false; } } return true; } private boolean filterRequest(final Operation operation) { // Check target DN. if (targetDNNotEqualTo.length > 0 || targetDNEqualTo.length > 0) { if (!filterRequestTargetDN(operation)) { return false; } } // TODO: check required controls. return true; } private boolean filterRequestTargetDN(final Operation operation) { // 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. DN targetDN = null; ByteString rawTargetDN = null; 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 // operations like abandon/unbind. return true; case ADD: targetDN = ((AddOperation) operation).getEntryDN(); rawTargetDN = ((AddOperation) operation).getRawEntryDN(); 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. targetDN = ((BindOperation) operation).getBindDN(); rawTargetDN = ((BindOperation) operation).getRawBindDN(); break; case COMPARE: targetDN = ((CompareOperation) operation).getEntryDN(); rawTargetDN = ((CompareOperation) operation).getRawEntryDN(); break; case DELETE: targetDN = ((DeleteOperation) operation).getEntryDN(); rawTargetDN = ((DeleteOperation) operation).getRawEntryDN(); break; case MODIFY: targetDN = ((ModifyOperation) operation).getEntryDN(); rawTargetDN = ((ModifyOperation) operation).getRawEntryDN(); break; case MODIFY_DN: targetDN = ((ModifyDNOperation) operation).getEntryDN(); rawTargetDN = ((ModifyDNOperation) operation).getRawEntryDN(); break; case SEARCH: targetDN = ((SearchOperation) operation).getBaseDN(); rawTargetDN = ((SearchOperation) operation).getRawBaseDN(); break; } // Attempt to parse the raw target DN if needed. if (targetDN == null) { try { targetDN = DN.decode(rawTargetDN); } catch (final DirectoryException e) { // The DN raw target DN was invalid. It will never match any // not-equal-to nor equal-to patterns, so return appropriate result. if (targetDNEqualTo.length != 0) { // Invalid DN will never match equal-to patterns. return false; } else { // Invalid DN does not match any not-equal-to patterns. return true; } } } if (targetDNNotEqualTo.length > 0) { for (final PatternDN pattern : targetDNNotEqualTo) { if (pattern.matchesDN(targetDN)) { return false; } } } if (targetDNEqualTo.length > 0) { for (final PatternDN pattern : targetDNNotEqualTo) { if (pattern.matchesDN(targetDN)) { return true; } } } // The target DN did not match. return false; } private boolean filterResponse(final Operation operation) { // Check response code. final Integer resultCode = operation.getResultCode().getIntValue(); if (!cfg.getResponseResultCodeNotEqualTo().isEmpty()) { if (cfg.getResponseResultCodeNotEqualTo().contains(resultCode)) { return false; } } if (!cfg.getResponseResultCodeEqualTo().isEmpty()) { if (!cfg.getResponseResultCodeNotEqualTo().contains(resultCode)) { return false; } } // Check etime. final long etime = operation.getProcessingTime(); final Integer etimeGT = cfg.getResponseEtimeLessThan(); if (etimeGT != null) { if (etime <= ((long) etimeGT)) { return false; } } final Integer etimeLT = cfg.getResponseEtimeLessThan(); if (etimeLT != null) { if (etime >= ((long) etimeLT)) { return false; } } // Check search response fields. if (operation instanceof SearchOperation) { final SearchOperation searchOperation = (SearchOperation) operation; final Boolean isIndexed= cfg.isSearchResponseIsIndexed(); if (isIndexed != null) { boolean wasUnindexed = false; for (final AdditionalLogItem item : operation.getAdditionalLogItems()) { if (item.getKey().equals("unindexed")) { wasUnindexed = true; break; } } if (isIndexed) { if (wasUnindexed) { return false; } } else { if (!wasUnindexed) { return false; } } } final int nentries = searchOperation.getEntriesSent(); final Integer nentriesGT = cfg.getSearchResponseNentriesGreaterThan(); if (nentriesGT != null) { if (nentries <= nentriesGT) { return false; } } final Integer nentriesLT = cfg.getSearchResponseNentriesLessThan(); if (nentriesLT != null) { if (nentries >= nentriesLT) { return false; } } } return true; } private boolean filterUser(final ClientConnection connection) { // Check user DN. if (userDNNotEqualTo.length > 0 || userDNEqualTo.length > 0) { if (!filterUserBindDN(connection)) { return false; } } // Check group membership. if (userIsNotMemberOf.length > 0 || userIsNotMemberOf.length > 0) { if (!filterUserIsMemberOf(connection)) { return false; } } return true; } private boolean filterUserBindDN(final ClientConnection connection) { final DN userDN = connection.getAuthenticationInfo() .getAuthenticationDN(); // Fast-path for unauthenticated clients. if (userDN == null) { return userDNEqualTo.length == 0; } if (userDNNotEqualTo.length > 0) { for (final PatternDN pattern : userDNNotEqualTo) { if (pattern.matchesDN(userDN)) { return false; } } } if (userDNEqualTo.length > 0) { for (final PatternDN pattern : userDNNotEqualTo) { if (pattern.matchesDN(userDN)) { return true; } } } // The user DN did not match. return false; } private boolean filterUserIsMemberOf(final ClientConnection connection) { final Entry userEntry = connection.getAuthenticationInfo() .getAuthenticationEntry(); // Fast-path for unauthenticated clients. if (userEntry == null) { return userIsMemberOf.length == 0; } final GroupManager groupManager = DirectoryServer.getGroupManager(); if (userIsNotMemberOf.length > 0) { for (final DN groupDN : userIsNotMemberOf) { final Group<?> group = groupManager.getGroupInstance(groupDN); try { if ((group != null) && group.isMember(userEntry)) { return false; } } catch (final DirectoryException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } if (userIsMemberOf.length > 0) { for (final DN groupDN : userIsMemberOf) { final Group<?> group = groupManager.getGroupInstance(groupDN); try { if ((group != null) && group.isMember(userEntry)) { return true; } } catch (final DirectoryException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } } // The user entry did not match. return false; } } // TODO: update assigned OIDs WIKI page when complete. /** * 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. */ boolean isConnectLoggable(ClientConnection connection); /** * 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. */ boolean isDisconnectLoggable(ClientConnection connection); /** * Returns {@code true} if the provided request should be logged. * * @param operation * The request. * @return {@code true} if the provided request should be logged. */ boolean isRequestLoggable(Operation operation); /** * Returns {@code true} if the provided response should be logged. * * @param operation * The response. * @return {@code true} if the provided response should be logged. */ boolean isResponseLoggable(Operation operation); } /** * A filter which performs a logical OR over a set of sub-filters. */ static final class OrFilter implements Filter { private final Filter[] subFilters; /** * Creates a new OR filter. * * @param subFilters * The sub-filters. */ OrFilter(final Filter[] subFilters) { this.subFilters = subFilters; } /** * {@inheritDoc} */ public boolean isConnectLoggable(final ClientConnection connection) { for (final Filter filter : subFilters) { if (filter.isConnectLoggable(connection)) { // Succeed fast. return true; } } return false; } /** * {@inheritDoc} */ public boolean isDisconnectLoggable(final ClientConnection connection) { for (final Filter filter : subFilters) { if (filter.isDisconnectLoggable(connection)) { // Succeed fast. return true; } } return false; } /** * {@inheritDoc} */ public boolean isRequestLoggable(final Operation operation) { for (final Filter filter : subFilters) { if (filter.isRequestLoggable(operation)) { // Succeed fast. return true; } } return false; } /** * {@inheritDoc} */ public boolean isResponseLoggable(final Operation operation) { for (final Filter filter : subFilters) { if (filter.isResponseLoggable(operation)) { // Succeed fast. return true; } } return false; } } /** * The root filter which first checks the logger configuration, delegating to * a sub-filter if needed. */ static final class RootFilter implements Filter { private final Filter subFilter; private final boolean suppressInternalOperations; private final boolean suppressSynchronizationOperations; private final FilteringPolicy policy; /** * 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. */ RootFilter(final boolean suppressInternal, final boolean suppressSynchronization, final FilteringPolicy policy, final Filter subFilter) { this.suppressInternalOperations = suppressInternal; this.suppressSynchronizationOperations = suppressSynchronization; this.policy = policy; this.subFilter = subFilter; } /** * {@inheritDoc} */ public boolean isConnectLoggable(final ClientConnection connection) { final long connectionID = connection.getConnectionID(); if (connectionID >= 0 || !suppressInternalOperations) { switch (policy) { case INCLUSIVE: return subFilter.isConnectLoggable(connection); case EXCLUSIVE: return !subFilter.isConnectLoggable(connection); default: // NO_FILTERING: return true; } } else { return false; } } /** * {@inheritDoc} */ public boolean isDisconnectLoggable(final ClientConnection connection) { final long connectionID = connection.getConnectionID(); if (connectionID >= 0 || !suppressInternalOperations) { switch (policy) { case INCLUSIVE: return subFilter.isDisconnectLoggable(connection); case EXCLUSIVE: return !subFilter.isDisconnectLoggable(connection); default: // NO_FILTERING: return true; } } else { return false; } } /** * {@inheritDoc} */ public boolean isRequestLoggable(final Operation operation) { if (isLoggable(operation)) { switch (policy) { case INCLUSIVE: return subFilter.isRequestLoggable(operation); case EXCLUSIVE: return !subFilter.isRequestLoggable(operation); default: // NO_FILTERING: return true; } } else { return false; } } /** * {@inheritDoc} */ public boolean isResponseLoggable(final Operation operation) { if (isLoggable(operation)) { switch (policy) { case INCLUSIVE: return subFilter.isResponseLoggable(operation); case EXCLUSIVE: return !subFilter.isResponseLoggable(operation); default: // NO_FILTERING: return true; } } else { return false; } } // Determines whether the provided operation should be logged. private boolean isLoggable(final Operation operation) { final long connectionID = operation.getConnectionID(); if (connectionID < 0) { // This is an internal operation. if (operation.isSynchronizationOperation()) { return !suppressSynchronizationOperations; } else { return !suppressInternalOperations; } } return true; } } /** * Filter criteria configuration listener. */ private final class FilterChangeListener implements ConfigurationChangeListener<AccessLogFilteringCriteriaCfg>, ConfigurationAddListener<AccessLogFilteringCriteriaCfg>, ConfigurationDeleteListener<AccessLogFilteringCriteriaCfg> { /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationAdd( final AccessLogFilteringCriteriaCfg configuration) { // Rebuild the filter using the new configuration and criteria. buildFilters(); configuration.addChangeListener(this); return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange( final AccessLogFilteringCriteriaCfg configuration) { // Rebuild the filter using the new configuration and criteria. buildFilters(); return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationDelete( final AccessLogFilteringCriteriaCfg configuration) { // Rebuild the filter using the new configuration and criteria. buildFilters(); return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ public boolean isConfigurationAddAcceptable( final AccessLogFilteringCriteriaCfg configuration, final List<Message> unacceptableReasons) { return true; } /** * {@inheritDoc} */ public boolean isConfigurationChangeAcceptable( final AccessLogFilteringCriteriaCfg configuration, final List<Message> unacceptableReasons) { return true; } /** * {@inheritDoc} */ public boolean isConfigurationDeleteAcceptable( final AccessLogFilteringCriteriaCfg configuration, final List<Message> unacceptableReasons) { return true; } } /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * The category to use when logging responses. @@ -1134,17 +92,14 @@ final TextAccessLogPublisher startupPublisher = new TextAccessLogPublisher(); startupPublisher.writer = writer; startupPublisher.buildFilters(suppressInternal, false, FilteringPolicy.NO_FILTERING); startupPublisher.buildFilters(suppressInternal); return startupPublisher; } private FileBasedAccessLogPublisherCfg currentConfig = null; private TextWriter writer = null; private Filter filter = null; private FilterChangeListener filterChangeListener = null; private FileBasedAccessLogPublisherCfg cfg = null; @@ -1246,16 +201,13 @@ writer = asyncWriter; } if ((currentConfig.isAsynchronous() && config.isAsynchronous()) && (currentConfig.getQueueSize() != config.getQueueSize())) if ((cfg.isAsynchronous() && config.isAsynchronous()) && (cfg.getQueueSize() != config.getQueueSize())) { adminActionRequired = true; } currentConfig = config; // Rebuild the filter using the new configuration and criteria. buildFilters(); cfg = config; } } catch (final Exception e) @@ -1276,49 +228,12 @@ * {@inheritDoc} */ @Override public void close() protected void close0() { writer.shutdown(); if (currentConfig != null) if (cfg != null) { currentConfig.removeFileBasedAccessChangeListener(this); for (final String criteriaName : currentConfig .listAccessLogFilteringCriteria()) { try { currentConfig.getAccessLogFilteringCriteria(criteriaName) .removeChangeListener(filterChangeListener); } catch (final ConfigException e) { // Ignore. } } currentConfig .removeAccessLogFilteringCriteriaAddListener(filterChangeListener); currentConfig .removeAccessLogFilteringCriteriaDeleteListener(filterChangeListener); } } /** * {@inheritDoc} */ @Override public DN getDN() { if (currentConfig != null) { return currentConfig.dn(); } else { return null; cfg.removeFileBasedAccessChangeListener(this); } } @@ -1386,7 +301,6 @@ final Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg .dn().toString(), String.valueOf(e)); throw new InitializationException(message, e); } catch (final IOException e) { @@ -1396,32 +310,9 @@ } currentConfig = cfg; // Rebuild the filter using the new configuration and criteria. buildFilters(); // Add change listeners. filterChangeListener = new FilterChangeListener(); for (final String criteriaName : currentConfig .listAccessLogFilteringCriteria()) { try { currentConfig.getAccessLogFilteringCriteria(criteriaName) .addChangeListener(filterChangeListener); } catch (final ConfigException e) { // Ignore. } } currentConfig .addAccessLogFilteringCriteriaAddListener(filterChangeListener); currentConfig .addAccessLogFilteringCriteriaDeleteListener(filterChangeListener); currentConfig.addFileBasedAccessChangeListener(this); initializeFilters(cfg); this.cfg = cfg; cfg.addFileBasedAccessChangeListener(this); } @@ -1431,12 +322,10 @@ */ @Override public boolean isConfigurationAcceptable( final AccessLogPublisherCfg configuration, final FileBasedAccessLogPublisherCfg configuration, final List<Message> unacceptableReasons) { final FileBasedAccessLogPublisherCfg config = (FileBasedAccessLogPublisherCfg) configuration; return isConfigurationChangeAcceptable(config, unacceptableReasons); return isConfigurationChangeAcceptable(configuration, unacceptableReasons); } @@ -1485,7 +374,7 @@ @Override public void logAbandonRequest(final AbandonOperation abandonOperation) { if (!filter.isRequestLoggable(abandonOperation)) if (!isRequestLoggable(abandonOperation)) { return; } @@ -1515,7 +404,7 @@ @Override public void logAbandonResult(final AbandonOperation abandonOperation) { if (!filter.isResponseLoggable(abandonOperation)) if (!isResponseLoggable(abandonOperation)) { return; } @@ -1553,7 +442,7 @@ @Override public void logAddRequest(final AddOperation addOperation) { if (!filter.isRequestLoggable(addOperation)) if (!isRequestLoggable(addOperation)) { return; } @@ -1584,7 +473,7 @@ @Override public void logAddResponse(final AddOperation addOperation) { if (!filter.isResponseLoggable(addOperation)) if (!isResponseLoggable(addOperation)) { return; } @@ -1636,7 +525,7 @@ @Override public void logBindRequest(final BindOperation bindOperation) { if (!filter.isRequestLoggable(bindOperation)) if (!isRequestLoggable(bindOperation)) { return; } @@ -1690,7 +579,7 @@ @Override public void logBindResponse(final BindOperation bindOperation) { if (!filter.isResponseLoggable(bindOperation)) if (!isResponseLoggable(bindOperation)) { return; } @@ -1774,7 +663,7 @@ @Override public void logCompareRequest(final CompareOperation compareOperation) { if (!filter.isRequestLoggable(compareOperation)) if (!isRequestLoggable(compareOperation)) { return; } @@ -1806,7 +695,7 @@ @Override public void logCompareResponse(final CompareOperation compareOperation) { if (!filter.isResponseLoggable(compareOperation)) if (!isResponseLoggable(compareOperation)) { return; } @@ -1858,7 +747,7 @@ @Override public void logConnect(final ClientConnection clientConnection) { if (!filter.isConnectLoggable(clientConnection)) if (!isConnectLoggable(clientConnection)) { return; } @@ -1894,7 +783,7 @@ @Override public void logDeleteRequest(final DeleteOperation deleteOperation) { if (!filter.isRequestLoggable(deleteOperation)) if (!isRequestLoggable(deleteOperation)) { return; } @@ -1925,7 +814,7 @@ @Override public void logDeleteResponse(final DeleteOperation deleteOperation) { if (!filter.isResponseLoggable(deleteOperation)) if (!isResponseLoggable(deleteOperation)) { return; } @@ -1982,7 +871,7 @@ public void logDisconnect(final ClientConnection clientConnection, final DisconnectReason disconnectReason, final Message message) { if (!filter.isDisconnectLoggable(clientConnection)) if (!isDisconnectLoggable(clientConnection)) { return; } @@ -2021,7 +910,7 @@ @Override public void logExtendedRequest(final ExtendedOperation extendedOperation) { if (!filter.isRequestLoggable(extendedOperation)) if (!isRequestLoggable(extendedOperation)) { return; } @@ -2066,7 +955,7 @@ @Override public void logExtendedResponse(final ExtendedOperation extendedOperation) { if (!filter.isResponseLoggable(extendedOperation)) if (!isResponseLoggable(extendedOperation)) { return; } @@ -2132,7 +1021,7 @@ @Override public void logModifyDNRequest(final ModifyDNOperation modifyDNOperation) { if (!filter.isRequestLoggable(modifyDNOperation)) if (!isRequestLoggable(modifyDNOperation)) { return; } @@ -2173,7 +1062,7 @@ @Override public void logModifyDNResponse(final ModifyDNOperation modifyDNOperation) { if (!filter.isResponseLoggable(modifyDNOperation)) if (!isResponseLoggable(modifyDNOperation)) { return; } @@ -2225,7 +1114,7 @@ @Override public void logModifyRequest(final ModifyOperation modifyOperation) { if (!filter.isRequestLoggable(modifyOperation)) if (!isRequestLoggable(modifyOperation)) { return; } @@ -2256,7 +1145,7 @@ @Override public void logModifyResponse(final ModifyOperation modifyOperation) { if (!filter.isResponseLoggable(modifyOperation)) if (!isResponseLoggable(modifyOperation)) { return; } @@ -2308,7 +1197,7 @@ @Override public void logSearchRequest(final SearchOperation searchOperation) { if (!filter.isRequestLoggable(searchOperation)) if (!isRequestLoggable(searchOperation)) { return; } @@ -2362,7 +1251,7 @@ @Override public void logSearchResultDone(final SearchOperation searchOperation) { if (!filter.isResponseLoggable(searchOperation)) if (!isResponseLoggable(searchOperation)) { return; } @@ -2418,7 +1307,7 @@ public void logUnbind(final UnbindOperation unbindOperation) { // FIXME: ensure that these are logged in combined mode. if (!filter.isRequestLoggable(unbindOperation)) if (!isRequestLoggable(unbindOperation)) { return; } @@ -2455,50 +1344,6 @@ // Build an appropriate set of filters based on the configuration. private void buildFilters() { buildFilters(currentConfig.isSuppressInternalOperations(), currentConfig.isSuppressSynchronizationOperations(), currentConfig.getFilteringPolicy()); } private void buildFilters(final boolean suppressInternal, final boolean suppressSynchronization, final FilteringPolicy policy) { final ArrayList<Filter> subFilters = new ArrayList<Filter>(); if (currentConfig != null) { for (final String criteriaName : currentConfig .listAccessLogFilteringCriteria()) { try { final AccessLogFilteringCriteriaCfg cfg = currentConfig .getAccessLogFilteringCriteria(criteriaName); subFilters.add(new CriteriaFilter(cfg)); } catch (final ConfigException e) { // TODO: Unable to decode this access log criteria, so log a warning // and continue. } catch (final DirectoryException e) { // TODO: Unable to decode this access log criteria, so log a warning // and continue. } } } final Filter orFilter = new OrFilter(subFilters.toArray(new Filter[0])); filter = new RootFilter(suppressInternal, suppressSynchronization, policy, orFilter); } // Appends additional log items to the provided builder. private void logAdditionalLogItems(final Operation operation, final StringBuilder builder) opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
@@ -30,9 +30,11 @@ import static org.opends.messages.ConfigMessages.*; import static org.opends.server.types.ResultCode.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; import static org.opends.server.types.ResultCode.SUCCESS; import static org.opends.server.util.ServerConstants.EOL; import static org.opends.server.util.StaticUtils.getBytes; import static org.opends.server.util.StaticUtils.getFileForPath; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import java.io.File; import java.io.IOException; @@ -41,15 +43,9 @@ import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.AccessLogPublisherCfg; import org.opends.server.admin.std.server.FileBasedAuditLogPublisherCfg; import org.opends.server.api.AccessLogPublisher; import org.opends.server.config.ConfigException; import org.opends.server.core.AddOperation; import org.opends.server.core.DeleteOperation; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ModifyDNOperation; import org.opends.server.core.ModifyOperation; import org.opends.server.core.*; import org.opends.server.types.*; import org.opends.server.util.Base64; import org.opends.server.util.StaticUtils; @@ -61,18 +57,14 @@ * This class provides the implementation of the audit logger used by * the directory server. */ public class TextAuditLogPublisher extends AccessLogPublisher<FileBasedAuditLogPublisherCfg> implements public final class TextAuditLogPublisher extends AbstractTextAccessLogPublisher<FileBasedAuditLogPublisherCfg> implements ConfigurationChangeListener<FileBasedAuditLogPublisherCfg> { private boolean suppressInternalOperations = true; private boolean suppressSynchronizationOperations = false; private TextWriter writer; private FileBasedAuditLogPublisherCfg currentConfig; private FileBasedAuditLogPublisherCfg cfg; @@ -87,10 +79,6 @@ boolean adminActionRequired = false; ArrayList<Message> messages = new ArrayList<Message>(); suppressInternalOperations = config.isSuppressInternalOperations(); suppressSynchronizationOperations = config .isSuppressSynchronizationOperations(); File logFile = getFileForPath(config.getLogFile()); FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); @@ -157,13 +145,13 @@ writer = asyncWriter; } if ((currentConfig.isAsynchronous() && config.isAsynchronous()) && (currentConfig.getQueueSize() != config.getQueueSize())) if ((cfg.isAsynchronous() && config.isAsynchronous()) && (cfg.getQueueSize() != config.getQueueSize())) { adminActionRequired = true; } currentConfig = config; cfg = config; } } catch (Exception e) @@ -184,28 +172,10 @@ * {@inheritDoc} */ @Override() public void close() protected void close0() { writer.shutdown(); currentConfig.removeFileBasedAuditChangeListener(this); } /** * {@inheritDoc} */ @Override public DN getDN() { if (currentConfig != null) { return currentConfig.dn(); } else { return null; } cfg.removeFileBasedAuditChangeListener(this); } @@ -214,44 +184,43 @@ * {@inheritDoc} */ @Override() public void initializeAccessLogPublisher( FileBasedAuditLogPublisherCfg config) public void initializeAccessLogPublisher(FileBasedAuditLogPublisherCfg cfg) throws ConfigException, InitializationException { File logFile = getFileForPath(config.getLogFile()); File logFile = getFileForPath(cfg.getLogFile()); FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); try { FilePermission perm = FilePermission.decodeUNIXMode(config FilePermission perm = FilePermission.decodeUNIXMode(cfg .getLogFilePermissions()); LogPublisherErrorHandler errorHandler = new LogPublisherErrorHandler( config.dn()); cfg.dn()); boolean writerAutoFlush = config.isAutoFlush() && !config.isAsynchronous(); boolean writerAutoFlush = cfg.isAutoFlush() && !cfg.isAsynchronous(); MultifileTextWriter writer = new MultifileTextWriter( "Multifile Text Writer for " + config.dn().toNormalizedString(), config.getTimeInterval(), fnPolicy, perm, errorHandler, "UTF-8", writerAutoFlush, config.isAppend(), (int) config.getBufferSize()); "Multifile Text Writer for " + cfg.dn().toNormalizedString(), cfg.getTimeInterval(), fnPolicy, perm, errorHandler, "UTF-8", writerAutoFlush, cfg.isAppend(), (int) cfg.getBufferSize()); // Validate retention and rotation policies. for (DN dn : config.getRotationPolicyDNs()) for (DN dn : cfg.getRotationPolicyDNs()) { writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); } for (DN dn : config.getRetentionPolicyDNs()) for (DN dn : cfg.getRetentionPolicyDNs()) { writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); } if (config.isAsynchronous()) if (cfg.isAsynchronous()) { this.writer = new AsyncronousTextWriter("Asyncronous Text Writer for " + config.dn().toNormalizedString(), config.getQueueSize(), config + cfg.dn().toNormalizedString(), cfg.getQueueSize(), cfg .isAutoFlush(), writer); } else @@ -261,7 +230,7 @@ } catch (DirectoryException e) { Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(config.dn() Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg.dn() .toString(), String.valueOf(e)); throw new InitializationException(message, e); @@ -269,18 +238,14 @@ catch (IOException e) { Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(logFile .toString(), config.dn().toString(), String.valueOf(e)); .toString(), cfg.dn().toString(), String.valueOf(e)); throw new InitializationException(message, e); } suppressInternalOperations = config.isSuppressInternalOperations(); suppressSynchronizationOperations = config .isSuppressSynchronizationOperations(); currentConfig = config; config.addFileBasedAuditChangeListener(this); initializeFilters(cfg); this.cfg = cfg; cfg.addFileBasedAuditChangeListener(this); } @@ -289,12 +254,11 @@ * {@inheritDoc} */ @Override public boolean isConfigurationAcceptable(AccessLogPublisherCfg configuration, public boolean isConfigurationAcceptable( FileBasedAuditLogPublisherCfg configuration, List<Message> unacceptableReasons) { FileBasedAuditLogPublisherCfg config = (FileBasedAuditLogPublisherCfg) configuration; return isConfigurationChangeAcceptable(config, unacceptableReasons); return isConfigurationChangeAcceptable(configuration, unacceptableReasons); } @@ -605,31 +569,13 @@ // Determines whether the provided operation should be logged. private boolean isLoggable(Operation operation) { long connectionID = operation.getConnectionID(); if (connectionID < 0) { // This is an internal operation. if (operation.isSynchronizationOperation()) { if (suppressSynchronizationOperations) { return false; } } else { if (suppressInternalOperations) { return false; } } } if (operation.getResultCode() != SUCCESS) { return false; } return true; else { return isResponseLoggable(operation); } } }