001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS. 026 */ 027package org.opends.server.loggers; 028 029import static org.opends.messages.ConfigMessages.*; 030import static org.forgerock.opendj.ldap.ResultCode.*; 031import static org.opends.server.util.ServerConstants.*; 032import static org.opends.server.util.StaticUtils.*; 033 034import java.io.File; 035import java.io.IOException; 036import java.util.List; 037 038import org.forgerock.i18n.LocalizableMessage; 039import org.opends.server.admin.server.ConfigurationChangeListener; 040import org.opends.server.admin.std.server.FileBasedAuditLogPublisherCfg; 041import org.forgerock.opendj.config.server.ConfigChangeResult; 042import org.forgerock.opendj.config.server.ConfigException; 043import org.opends.server.core.*; 044import org.opends.server.types.*; 045import org.forgerock.opendj.ldap.ByteSequence; 046import org.forgerock.opendj.ldap.ByteString; 047import org.opends.server.util.Base64; 048import org.opends.server.util.StaticUtils; 049import org.opends.server.util.TimeThread; 050 051/** 052 * This class provides the implementation of the audit logger used by 053 * the directory server. 054 */ 055public final class TextAuditLogPublisher extends 056 AbstractTextAccessLogPublisher<FileBasedAuditLogPublisherCfg> implements 057 ConfigurationChangeListener<FileBasedAuditLogPublisherCfg> 058{ 059 060 private TextWriter writer; 061 private FileBasedAuditLogPublisherCfg cfg; 062 063 /** {@inheritDoc} */ 064 @Override 065 public ConfigChangeResult applyConfigurationChange( 066 FileBasedAuditLogPublisherCfg config) 067 { 068 final ConfigChangeResult ccr = new ConfigChangeResult(); 069 070 File logFile = getFileForPath(config.getLogFile()); 071 FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 072 073 try 074 { 075 FilePermission perm = FilePermission.decodeUNIXMode(config 076 .getLogFilePermissions()); 077 078 boolean writerAutoFlush = config.isAutoFlush() 079 && !config.isAsynchronous(); 080 081 TextWriter currentWriter; 082 // Determine the writer we are using. If we were writing 083 // asynchronously, 084 // we need to modify the underlying writer. 085 if (writer instanceof AsynchronousTextWriter) 086 { 087 currentWriter = ((AsynchronousTextWriter) writer).getWrappedWriter(); 088 } 089 else 090 { 091 currentWriter = writer; 092 } 093 094 if (currentWriter instanceof MultifileTextWriter) 095 { 096 MultifileTextWriter mfWriter = (MultifileTextWriter) currentWriter; 097 098 mfWriter.setNamingPolicy(fnPolicy); 099 mfWriter.setFilePermissions(perm); 100 mfWriter.setAppend(config.isAppend()); 101 mfWriter.setAutoFlush(writerAutoFlush); 102 mfWriter.setBufferSize((int) config.getBufferSize()); 103 mfWriter.setInterval(config.getTimeInterval()); 104 105 mfWriter.removeAllRetentionPolicies(); 106 mfWriter.removeAllRotationPolicies(); 107 108 for (DN dn : config.getRotationPolicyDNs()) 109 { 110 mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 111 } 112 113 for (DN dn : config.getRetentionPolicyDNs()) 114 { 115 mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 116 } 117 118 if (writer instanceof AsynchronousTextWriter 119 && !config.isAsynchronous()) 120 { 121 // The asynchronous setting is being turned off. 122 AsynchronousTextWriter asyncWriter = (AsynchronousTextWriter) writer; 123 writer = mfWriter; 124 asyncWriter.shutdown(false); 125 } 126 127 if (!(writer instanceof AsynchronousTextWriter) 128 && config.isAsynchronous()) 129 { 130 // The asynchronous setting is being turned on. 131 writer = new AsynchronousTextWriter("Asynchronous Text Writer for " + config.dn(), 132 config.getQueueSize(), config.isAutoFlush(), mfWriter); 133 } 134 135 if (cfg.isAsynchronous() && config.isAsynchronous() 136 && cfg.getQueueSize() != config.getQueueSize()) 137 { 138 ccr.setAdminActionRequired(true); 139 } 140 141 cfg = config; 142 } 143 } 144 catch (Exception e) 145 { 146 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 147 ccr.addMessage(ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get( 148 config.dn(), stackTraceToSingleLineString(e))); 149 } 150 151 return ccr; 152 } 153 154 155 156 /** {@inheritDoc} */ 157 @Override 158 protected void close0() 159 { 160 writer.shutdown(); 161 cfg.removeFileBasedAuditChangeListener(this); 162 } 163 164 165 166 /** {@inheritDoc} */ 167 @Override 168 public void initializeLogPublisher(FileBasedAuditLogPublisherCfg cfg, ServerContext serverContext) 169 throws ConfigException, InitializationException 170 { 171 File logFile = getFileForPath(cfg.getLogFile()); 172 FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 173 174 try 175 { 176 FilePermission perm = FilePermission.decodeUNIXMode(cfg 177 .getLogFilePermissions()); 178 179 LogPublisherErrorHandler errorHandler = new LogPublisherErrorHandler( 180 cfg.dn()); 181 182 boolean writerAutoFlush = cfg.isAutoFlush() 183 && !cfg.isAsynchronous(); 184 185 MultifileTextWriter writer = new MultifileTextWriter("Multifile Text Writer for " + cfg.dn(), 186 cfg.getTimeInterval(), fnPolicy, perm, errorHandler, "UTF-8", 187 writerAutoFlush, cfg.isAppend(), (int) cfg.getBufferSize()); 188 189 // Validate retention and rotation policies. 190 for (DN dn : cfg.getRotationPolicyDNs()) 191 { 192 writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 193 } 194 195 for (DN dn : cfg.getRetentionPolicyDNs()) 196 { 197 writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 198 } 199 200 if (cfg.isAsynchronous()) 201 { 202 this.writer = new AsynchronousTextWriter("Asynchronous Text Writer for " + cfg.dn(), 203 cfg.getQueueSize(), cfg.isAutoFlush(), writer); 204 } 205 else 206 { 207 this.writer = writer; 208 } 209 } 210 catch (DirectoryException e) 211 { 212 throw new InitializationException( 213 ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg.dn(), e), e); 214 } 215 catch (IOException e) 216 { 217 throw new InitializationException( 218 ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(logFile, cfg.dn(), e), e); 219 } 220 221 initializeFilters(cfg); 222 this.cfg = cfg; 223 cfg.addFileBasedAuditChangeListener(this); 224 } 225 226 227 228 /** {@inheritDoc} */ 229 @Override 230 public boolean isConfigurationAcceptable( 231 FileBasedAuditLogPublisherCfg configuration, 232 List<LocalizableMessage> unacceptableReasons) 233 { 234 return isFilterConfigurationAcceptable(configuration, unacceptableReasons) 235 && isConfigurationChangeAcceptable(configuration, unacceptableReasons); 236 } 237 238 239 240 /** {@inheritDoc} */ 241 @Override 242 public boolean isConfigurationChangeAcceptable( 243 FileBasedAuditLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons) 244 { 245 // Make sure the permission is valid. 246 try 247 { 248 FilePermission filePerm = FilePermission.decodeUNIXMode(config 249 .getLogFilePermissions()); 250 if (!filePerm.isOwnerWritable()) 251 { 252 LocalizableMessage message = ERR_CONFIG_LOGGING_INSANE_MODE.get(config 253 .getLogFilePermissions()); 254 unacceptableReasons.add(message); 255 return false; 256 } 257 } 258 catch (DirectoryException e) 259 { 260 unacceptableReasons.add(ERR_CONFIG_LOGGING_MODE_INVALID.get(config.getLogFilePermissions(), e)); 261 return false; 262 } 263 264 return true; 265 } 266 267 268 269 /** {@inheritDoc} */ 270 @Override 271 public void logAddResponse(AddOperation addOperation) 272 { 273 if (!isLoggable(addOperation)) 274 { 275 return; 276 } 277 278 StringBuilder buffer = new StringBuilder(50); 279 appendHeader(addOperation, buffer); 280 281 buffer.append("dn:"); 282 encodeValue(addOperation.getEntryDN().toString(), buffer); 283 buffer.append(EOL); 284 285 buffer.append("changetype: add"); 286 buffer.append(EOL); 287 288 for (String ocName : addOperation.getObjectClasses().values()) 289 { 290 buffer.append("objectClass: "); 291 buffer.append(ocName); 292 buffer.append(EOL); 293 } 294 295 for (List<Attribute> attrList : addOperation.getUserAttributes().values()) 296 { 297 for (Attribute a : attrList) 298 { 299 append(buffer, a); 300 } 301 } 302 303 for (List<Attribute> attrList : addOperation.getOperationalAttributes() 304 .values()) 305 { 306 for (Attribute a : attrList) 307 { 308 append(buffer, a); 309 } 310 } 311 312 writer.writeRecord(buffer.toString()); 313 } 314 315 316 317 /** {@inheritDoc} */ 318 @Override 319 public void logDeleteResponse(DeleteOperation deleteOperation) 320 { 321 if (!isLoggable(deleteOperation)) 322 { 323 return; 324 } 325 326 StringBuilder buffer = new StringBuilder(50); 327 appendHeader(deleteOperation, buffer); 328 329 buffer.append("dn:"); 330 encodeValue(deleteOperation.getEntryDN().toString(), buffer); 331 buffer.append(EOL); 332 333 buffer.append("changetype: delete"); 334 buffer.append(EOL); 335 336 writer.writeRecord(buffer.toString()); 337 } 338 339 340 341 /** {@inheritDoc} */ 342 @Override 343 public void logModifyDNResponse(ModifyDNOperation modifyDNOperation) 344 { 345 if (!isLoggable(modifyDNOperation)) 346 { 347 return; 348 } 349 350 StringBuilder buffer = new StringBuilder(50); 351 appendHeader(modifyDNOperation, buffer); 352 353 buffer.append("dn:"); 354 encodeValue(modifyDNOperation.getEntryDN().toString(), buffer); 355 buffer.append(EOL); 356 357 buffer.append("changetype: moddn"); 358 buffer.append(EOL); 359 360 buffer.append("newrdn:"); 361 encodeValue(modifyDNOperation.getNewRDN().toString(), buffer); 362 buffer.append(EOL); 363 364 buffer.append("deleteoldrdn: "); 365 if (modifyDNOperation.deleteOldRDN()) 366 { 367 buffer.append("1"); 368 } 369 else 370 { 371 buffer.append("0"); 372 } 373 buffer.append(EOL); 374 375 DN newSuperior = modifyDNOperation.getNewSuperior(); 376 if (newSuperior != null) 377 { 378 buffer.append("newsuperior:"); 379 encodeValue(newSuperior.toString(), buffer); 380 buffer.append(EOL); 381 } 382 383 writer.writeRecord(buffer.toString()); 384 } 385 386 387 388 /** {@inheritDoc} */ 389 @Override 390 public void logModifyResponse(ModifyOperation modifyOperation) 391 { 392 if (!isLoggable(modifyOperation)) 393 { 394 return; 395 } 396 397 StringBuilder buffer = new StringBuilder(50); 398 appendHeader(modifyOperation, buffer); 399 400 buffer.append("dn:"); 401 encodeValue(modifyOperation.getEntryDN().toString(), buffer); 402 buffer.append(EOL); 403 404 buffer.append("changetype: modify"); 405 buffer.append(EOL); 406 407 boolean first = true; 408 for (Modification mod : modifyOperation.getModifications()) 409 { 410 if (first) 411 { 412 first = false; 413 } 414 else 415 { 416 buffer.append("-"); 417 buffer.append(EOL); 418 } 419 420 switch (mod.getModificationType().asEnum()) 421 { 422 case ADD: 423 buffer.append("add: "); 424 break; 425 case DELETE: 426 buffer.append("delete: "); 427 break; 428 case REPLACE: 429 buffer.append("replace: "); 430 break; 431 case INCREMENT: 432 buffer.append("increment: "); 433 break; 434 default: 435 continue; 436 } 437 438 Attribute a = mod.getAttribute(); 439 buffer.append(a.getName()); 440 buffer.append(EOL); 441 442 append(buffer, a); 443 } 444 445 writer.writeRecord(buffer.toString()); 446 } 447 448 449 450 private void append(StringBuilder buffer, Attribute a) 451 { 452 for (ByteString v : a) 453 { 454 buffer.append(a.getName()); 455 buffer.append(":"); 456 encodeValue(v, buffer); 457 buffer.append(EOL); 458 } 459 } 460 461 462 463 /** Appends the common log header information to the provided buffer. */ 464 private void appendHeader(Operation operation, StringBuilder buffer) 465 { 466 buffer.append("# "); 467 buffer.append(TimeThread.getLocalTime()); 468 buffer.append("; conn="); 469 buffer.append(operation.getConnectionID()); 470 buffer.append("; op="); 471 buffer.append(operation.getOperationID()); 472 buffer.append(EOL); 473 } 474 475 476 477 /** 478 * Appends the appropriately-encoded attribute value to the provided 479 * buffer. 480 * 481 * @param str 482 * The ASN.1 octet string containing the value to append. 483 * @param buffer 484 * The buffer to which to append the value. 485 */ 486 private void encodeValue(ByteSequence str, StringBuilder buffer) 487 { 488 if(StaticUtils.needsBase64Encoding(str)) 489 { 490 buffer.append(": "); 491 buffer.append(Base64.encode(str)); 492 } 493 else 494 { 495 buffer.append(" "); 496 buffer.append(str.toString()); 497 } 498 } 499 500 501 502 /** 503 * Appends the appropriately-encoded attribute value to the provided 504 * buffer. 505 * 506 * @param str 507 * The string containing the value to append. 508 * @param buffer 509 * The buffer to which to append the value. 510 */ 511 private void encodeValue(String str, StringBuilder buffer) 512 { 513 if (StaticUtils.needsBase64Encoding(str)) 514 { 515 buffer.append(": "); 516 buffer.append(Base64.encode(getBytes(str))); 517 } 518 else 519 { 520 buffer.append(" "); 521 buffer.append(str); 522 } 523 } 524 525 526 527 /** Determines whether the provided operation should be logged. */ 528 private boolean isLoggable(Operation operation) 529 { 530 return operation.getResultCode() == SUCCESS 531 && isResponseLoggable(operation); 532 } 533}