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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.loggers; 028 import org.opends.messages.Message; 029 030 031 import java.io.File; 032 import java.io.IOException; 033 import java.util.*; 034 035 import org.opends.server.admin.std.server.FileBasedAccessLogPublisherCfg; 036 import org.opends.server.admin.std.server.AccessLogPublisherCfg; 037 import org.opends.server.admin.server.ConfigurationChangeListener; 038 import org.opends.server.api.*; 039 import org.opends.server.config.ConfigException; 040 import org.opends.server.core.AbandonOperation; 041 import org.opends.server.core.AddOperation; 042 import org.opends.server.core.BindOperation; 043 import org.opends.server.core.CompareOperation; 044 import org.opends.server.core.DeleteOperation; 045 import org.opends.server.core.DirectoryServer; 046 import org.opends.server.core.ExtendedOperation; 047 import org.opends.server.core.ModifyDNOperation; 048 import org.opends.server.core.ModifyOperation; 049 import org.opends.server.core.SearchOperation; 050 import org.opends.server.core.UnbindOperation; 051 import org.opends.server.types.*; 052 import org.opends.server.util.Base64; 053 import org.opends.server.util.StaticUtils; 054 import org.opends.server.util.TimeThread; 055 056 import static org.opends.messages.ConfigMessages.*; 057 058 import static org.opends.server.types.ResultCode.*; 059 import static org.opends.server.util.ServerConstants.*; 060 import static org.opends.server.util.StaticUtils.*; 061 062 063 /** 064 * This class provides the implementation of the audit logger used by 065 * the directory server. 066 */ 067 public class TextAuditLogPublisher 068 extends AccessLogPublisher<FileBasedAccessLogPublisherCfg> 069 implements ConfigurationChangeListener<FileBasedAccessLogPublisherCfg> 070 { 071 private boolean suppressInternalOperations = true; 072 073 private boolean suppressSynchronizationOperations = false; 074 075 private TextWriter writer; 076 077 private FileBasedAccessLogPublisherCfg currentConfig; 078 079 /** 080 * {@inheritDoc} 081 */ 082 public boolean isConfigurationAcceptable(AccessLogPublisherCfg configuration, 083 List<Message> unacceptableReasons) 084 { 085 FileBasedAccessLogPublisherCfg config = 086 (FileBasedAccessLogPublisherCfg) configuration; 087 return isConfigurationChangeAcceptable(config, unacceptableReasons); 088 } 089 090 /** 091 * {@inheritDoc} 092 */ 093 @Override() 094 public void initializeAccessLogPublisher( 095 FileBasedAccessLogPublisherCfg config) 096 throws ConfigException, InitializationException 097 { 098 File logFile = getFileForPath(config.getLogFile()); 099 FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 100 101 try 102 { 103 FilePermission perm = 104 FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 105 106 LogPublisherErrorHandler errorHandler = 107 new LogPublisherErrorHandler(config.dn()); 108 109 boolean writerAutoFlush = 110 config.isAutoFlush() && !config.isAsynchronous(); 111 112 MultifileTextWriter writer = 113 new MultifileTextWriter("Multifile Text Writer for " + 114 config.dn().toNormalizedString(), 115 config.getTimeInterval(), 116 fnPolicy, 117 perm, 118 errorHandler, 119 "UTF-8", 120 writerAutoFlush, 121 config.isAppend(), 122 (int)config.getBufferSize()); 123 124 // Validate retention and rotation policies. 125 for(DN dn : config.getRotationPolicyDNs()) 126 { 127 writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 128 } 129 130 for(DN dn: config.getRetentionPolicyDNs()) 131 { 132 writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 133 } 134 135 if(config.isAsynchronous()) 136 { 137 this.writer = new AsyncronousTextWriter("Asyncronous Text Writer for " + 138 config.dn().toNormalizedString(), config.getQueueSize(), 139 config.isAutoFlush(), 140 writer); 141 } 142 else 143 { 144 this.writer = writer; 145 } 146 } 147 catch(DirectoryException e) 148 { 149 Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get( 150 config.dn().toString(), String.valueOf(e)); 151 throw new InitializationException(message, e); 152 153 } 154 catch(IOException e) 155 { 156 Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get( 157 logFile.toString(), config.dn().toString(), String.valueOf(e)); 158 throw new InitializationException(message, e); 159 160 } 161 162 suppressInternalOperations = config.isSuppressInternalOperations(); 163 suppressSynchronizationOperations = 164 config.isSuppressSynchronizationOperations(); 165 166 currentConfig = config; 167 168 config.addFileBasedAccessChangeListener(this); 169 } 170 171 172 173 /** 174 * {@inheritDoc} 175 */ 176 public boolean isConfigurationChangeAcceptable( 177 FileBasedAccessLogPublisherCfg config, List<Message> unacceptableReasons) 178 { 179 // Make sure the permission is valid. 180 try 181 { 182 FilePermission filePerm = 183 FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 184 if(!filePerm.isOwnerWritable()) 185 { 186 Message message = ERR_CONFIG_LOGGING_INSANE_MODE.get( 187 config.getLogFilePermissions()); 188 unacceptableReasons.add(message); 189 return false; 190 } 191 } 192 catch(DirectoryException e) 193 { 194 Message message = ERR_CONFIG_LOGGING_MODE_INVALID.get( 195 config.getLogFilePermissions(), String.valueOf(e)); 196 unacceptableReasons.add(message); 197 return false; 198 } 199 200 return true; 201 } 202 203 /** 204 * {@inheritDoc} 205 */ 206 public ConfigChangeResult applyConfigurationChange( 207 FileBasedAccessLogPublisherCfg config) 208 { 209 // Default result code. 210 ResultCode resultCode = ResultCode.SUCCESS; 211 boolean adminActionRequired = false; 212 ArrayList<Message> messages = new ArrayList<Message>(); 213 214 suppressInternalOperations = config.isSuppressInternalOperations(); 215 suppressSynchronizationOperations = 216 config.isSuppressSynchronizationOperations(); 217 218 File logFile = getFileForPath(config.getLogFile()); 219 FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 220 221 try 222 { 223 FilePermission perm = 224 FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 225 226 boolean writerAutoFlush = 227 config.isAutoFlush() && !config.isAsynchronous(); 228 229 TextWriter currentWriter; 230 // Determine the writer we are using. If we were writing asyncronously, 231 // we need to modify the underlaying writer. 232 if(writer instanceof AsyncronousTextWriter) 233 { 234 currentWriter = ((AsyncronousTextWriter)writer).getWrappedWriter(); 235 } 236 else 237 { 238 currentWriter = writer; 239 } 240 241 if(currentWriter instanceof MultifileTextWriter) 242 { 243 MultifileTextWriter mfWriter = (MultifileTextWriter)currentWriter; 244 245 mfWriter.setNamingPolicy(fnPolicy); 246 mfWriter.setFilePermissions(perm); 247 mfWriter.setAppend(config.isAppend()); 248 mfWriter.setAutoFlush(writerAutoFlush); 249 mfWriter.setBufferSize((int)config.getBufferSize()); 250 mfWriter.setInterval(config.getTimeInterval()); 251 252 mfWriter.removeAllRetentionPolicies(); 253 mfWriter.removeAllRotationPolicies(); 254 255 for(DN dn : config.getRotationPolicyDNs()) 256 { 257 mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 258 } 259 260 for(DN dn: config.getRetentionPolicyDNs()) 261 { 262 mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 263 } 264 265 if(writer instanceof AsyncronousTextWriter && !config.isAsynchronous()) 266 { 267 // The asynronous setting is being turned off. 268 AsyncronousTextWriter asyncWriter = ((AsyncronousTextWriter)writer); 269 writer = mfWriter; 270 asyncWriter.shutdown(false); 271 } 272 273 if(!(writer instanceof AsyncronousTextWriter) && 274 config.isAsynchronous()) 275 { 276 // The asynronous setting is being turned on. 277 AsyncronousTextWriter asyncWriter = 278 new AsyncronousTextWriter("Asyncronous Text Writer for " + 279 config.dn().toNormalizedString(), config.getQueueSize(), 280 config.isAutoFlush(), 281 mfWriter); 282 writer = asyncWriter; 283 } 284 285 if((currentConfig.isAsynchronous() && config.isAsynchronous()) && 286 (currentConfig.getQueueSize() != config.getQueueSize())) 287 { 288 adminActionRequired = true; 289 } 290 291 currentConfig = config; 292 } 293 } 294 catch(Exception e) 295 { 296 Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get( 297 config.dn().toString(), 298 stackTraceToSingleLineString(e)); 299 resultCode = DirectoryServer.getServerErrorResultCode(); 300 messages.add(message); 301 302 } 303 304 return new ConfigChangeResult(resultCode, adminActionRequired, messages); 305 } 306 307 308 309 /** 310 * {@inheritDoc} 311 */ 312 @Override() 313 public void close() 314 { 315 writer.shutdown(); 316 currentConfig.removeFileBasedAccessChangeListener(this); 317 } 318 319 320 321 /** 322 * {@inheritDoc} 323 */ 324 @Override() 325 public void logConnect(ClientConnection clientConnection) 326 { 327 } 328 329 330 331 /** 332 * {@inheritDoc} 333 */ 334 @Override() 335 public void logDisconnect(ClientConnection clientConnection, 336 DisconnectReason disconnectReason, 337 Message message) 338 { 339 } 340 341 342 343 /** 344 * {@inheritDoc} 345 */ 346 @Override() 347 public void logAbandonRequest(AbandonOperation abandonOperation) 348 { 349 } 350 351 352 353 /** 354 * {@inheritDoc} 355 */ 356 @Override() 357 public void logAbandonResult(AbandonOperation abandonOperation) 358 { 359 } 360 361 362 363 /** 364 * {@inheritDoc} 365 */ 366 @Override() 367 public void logAddRequest(AddOperation addOperation) 368 { 369 } 370 371 372 373 /** 374 * {@inheritDoc} 375 */ 376 @Override() 377 public void logAddResponse(AddOperation addOperation) 378 { 379 long connectionID = addOperation.getConnectionID(); 380 if (connectionID < 0) 381 { 382 // This is an internal operation. 383 if (addOperation.isSynchronizationOperation()) 384 { 385 if (suppressSynchronizationOperations) 386 { 387 return; 388 } 389 } 390 else 391 { 392 if (suppressInternalOperations) 393 { 394 return; 395 } 396 } 397 } 398 ResultCode code = addOperation.getResultCode(); 399 400 if(code == SUCCESS) 401 { 402 StringBuilder buffer = new StringBuilder(50); 403 buffer.append("# "); 404 buffer.append(TimeThread.getLocalTime()); 405 buffer.append("; conn="); 406 buffer.append(addOperation.getConnectionID()); 407 buffer.append("; op="); 408 buffer.append(addOperation.getOperationID()); 409 buffer.append(EOL); 410 411 buffer.append("dn:"); 412 encodeValue(addOperation.getEntryDN().toString(), buffer); 413 buffer.append(EOL); 414 415 buffer.append("changetype: add"); 416 buffer.append(EOL); 417 418 for (String ocName : addOperation.getObjectClasses().values()) 419 { 420 buffer.append("objectClass: "); 421 buffer.append(ocName); 422 buffer.append(EOL); 423 } 424 425 for (List<Attribute> attrList : addOperation.getUserAttributes().values()) 426 { 427 for (Attribute a : attrList) 428 { 429 for (AttributeValue v : a.getValues()) 430 { 431 buffer.append(a.getName()); 432 buffer.append(":"); 433 encodeValue(v.getValue(), buffer); 434 buffer.append(EOL); 435 } 436 } 437 } 438 439 for (List<Attribute> attrList : 440 addOperation.getOperationalAttributes().values()) 441 { 442 for (Attribute a : attrList) 443 { 444 for (AttributeValue v : a.getValues()) 445 { 446 buffer.append(a.getName()); 447 buffer.append(":"); 448 encodeValue(v.getValue(), buffer); 449 buffer.append(EOL); 450 } 451 } 452 } 453 454 writer.writeRecord(buffer.toString()); 455 } 456 } 457 458 459 460 /** 461 * {@inheritDoc} 462 */ 463 @Override() 464 public void logBindRequest(BindOperation bindOperation) 465 { 466 } 467 468 469 470 /** 471 * {@inheritDoc} 472 */ 473 @Override() 474 public void logBindResponse(BindOperation bindOperation) 475 { 476 } 477 478 479 480 /** 481 * {@inheritDoc} 482 */ 483 @Override() 484 public void logCompareRequest(CompareOperation compareOperation) 485 { 486 } 487 488 489 490 /** 491 * {@inheritDoc} 492 */ 493 @Override() 494 public void logCompareResponse(CompareOperation compareOperation) 495 { 496 } 497 498 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override() 504 public void logDeleteRequest(DeleteOperation deleteOperation) 505 { 506 } 507 508 509 510 /** 511 * {@inheritDoc} 512 */ 513 @Override() 514 public void logDeleteResponse(DeleteOperation deleteOperation) 515 { 516 long connectionID = deleteOperation.getConnectionID(); 517 if (connectionID < 0) 518 { 519 // This is an internal operation. 520 if (deleteOperation.isSynchronizationOperation()) 521 { 522 if (suppressSynchronizationOperations) 523 { 524 return; 525 } 526 } 527 else 528 { 529 if (suppressInternalOperations) 530 { 531 return; 532 } 533 } 534 } 535 ResultCode code = deleteOperation.getResultCode(); 536 537 if(code == SUCCESS) 538 { 539 StringBuilder buffer = new StringBuilder(50); 540 buffer.append("# "); 541 buffer.append(TimeThread.getLocalTime()); 542 buffer.append("; conn="); 543 buffer.append(deleteOperation.getConnectionID()); 544 buffer.append("; op="); 545 buffer.append(deleteOperation.getOperationID()); 546 buffer.append(EOL); 547 548 buffer.append("dn:"); 549 encodeValue(deleteOperation.getEntryDN().toString(), buffer); 550 buffer.append(EOL); 551 552 buffer.append("changetype: delete"); 553 buffer.append(EOL); 554 555 writer.writeRecord(buffer.toString()); 556 } 557 558 } 559 560 561 562 /** 563 * {@inheritDoc} 564 */ 565 @Override() 566 public void logExtendedRequest(ExtendedOperation extendedOperation) 567 { 568 } 569 570 571 572 /** 573 * {@inheritDoc} 574 */ 575 @Override() 576 public void logExtendedResponse(ExtendedOperation extendedOperation) 577 { 578 } 579 580 581 582 /** 583 * {@inheritDoc} 584 */ 585 @Override() 586 public void logModifyRequest(ModifyOperation modifyOperation) 587 { 588 } 589 590 591 592 /** 593 * {@inheritDoc} 594 */ 595 @Override() 596 public void logModifyResponse(ModifyOperation modifyOperation) 597 { 598 long connectionID = modifyOperation.getConnectionID(); 599 if (connectionID < 0) 600 { 601 // This is an internal operation. 602 if (modifyOperation.isSynchronizationOperation()) 603 { 604 if (suppressSynchronizationOperations) 605 { 606 return; 607 } 608 } 609 else 610 { 611 if (suppressInternalOperations) 612 { 613 return; 614 } 615 } 616 } 617 ResultCode code = modifyOperation.getResultCode(); 618 619 if(code == SUCCESS) 620 { 621 StringBuilder buffer = new StringBuilder(50); 622 buffer.append("# "); 623 buffer.append(TimeThread.getLocalTime()); 624 buffer.append("; conn="); 625 buffer.append(modifyOperation.getConnectionID()); 626 buffer.append("; op="); 627 buffer.append(modifyOperation.getOperationID()); 628 buffer.append(EOL); 629 630 buffer.append("dn:"); 631 encodeValue(modifyOperation.getEntryDN().toString(), buffer); 632 buffer.append(EOL); 633 634 buffer.append("changetype: modify"); 635 buffer.append(EOL); 636 637 boolean first = true; 638 for (Modification mod : modifyOperation.getModifications()) 639 { 640 if (first) 641 { 642 first = false; 643 } 644 else 645 { 646 buffer.append("-"); 647 buffer.append(EOL); 648 } 649 650 switch (mod.getModificationType()) 651 { 652 case ADD: 653 buffer.append("add: "); 654 break; 655 case DELETE: 656 buffer.append("delete: "); 657 break; 658 case REPLACE: 659 buffer.append("replace: "); 660 break; 661 case INCREMENT: 662 buffer.append("increment: "); 663 break; 664 default: 665 continue; 666 } 667 668 Attribute a = mod.getAttribute(); 669 buffer.append(a.getName()); 670 buffer.append(EOL); 671 672 for (AttributeValue v : a.getValues()) 673 { 674 buffer.append(a.getName()); 675 buffer.append(":"); 676 encodeValue(v.getValue(), buffer); 677 buffer.append(EOL); 678 } 679 } 680 681 writer.writeRecord(buffer.toString()); 682 } 683 } 684 685 686 687 /** 688 * {@inheritDoc} 689 */ 690 @Override() 691 public void logModifyDNRequest(ModifyDNOperation modifyDNOperation) 692 { 693 } 694 695 696 697 /** 698 * {@inheritDoc} 699 */ 700 @Override() 701 public void logModifyDNResponse(ModifyDNOperation modifyDNOperation) 702 { 703 long connectionID = modifyDNOperation.getConnectionID(); 704 if (connectionID < 0) 705 { 706 // This is an internal operation. 707 if (modifyDNOperation.isSynchronizationOperation()) 708 { 709 if (suppressSynchronizationOperations) 710 { 711 return; 712 } 713 } 714 else 715 { 716 if (suppressInternalOperations) 717 { 718 return; 719 } 720 } 721 } 722 ResultCode code = modifyDNOperation.getResultCode(); 723 724 if(code == SUCCESS) 725 { 726 StringBuilder buffer = new StringBuilder(50); 727 buffer.append("# "); 728 buffer.append(TimeThread.getLocalTime()); 729 buffer.append("; conn="); 730 buffer.append(modifyDNOperation.getConnectionID()); 731 buffer.append("; op="); 732 buffer.append(modifyDNOperation.getOperationID()); 733 buffer.append(EOL); 734 735 buffer.append("dn:"); 736 encodeValue(modifyDNOperation.getEntryDN().toString(), buffer); 737 buffer.append(EOL); 738 739 buffer.append("changetype: moddn"); 740 buffer.append(EOL); 741 742 buffer.append("newrdn:"); 743 encodeValue(modifyDNOperation.getNewRDN().toString(), buffer); 744 buffer.append(EOL); 745 746 buffer.append("deleteoldrdn: "); 747 if (modifyDNOperation.deleteOldRDN()) 748 { 749 buffer.append("1"); 750 } 751 else 752 { 753 buffer.append("0"); 754 } 755 buffer.append(EOL); 756 757 DN newSuperior = modifyDNOperation.getNewSuperior(); 758 if (newSuperior != null) 759 { 760 buffer.append("newsuperior:"); 761 encodeValue(newSuperior.toString(), buffer); 762 buffer.append(EOL); 763 } 764 765 writer.writeRecord(buffer.toString()); 766 } 767 } 768 769 770 771 /** 772 * {@inheritDoc} 773 */ 774 @Override() 775 public void logSearchRequest(SearchOperation searchOperation) 776 { 777 } 778 779 780 781 /** 782 * {@inheritDoc} 783 */ 784 @Override() 785 public void logSearchResultEntry(SearchOperation searchOperation, 786 SearchResultEntry searchEntry) 787 { 788 } 789 790 791 792 /** 793 * {@inheritDoc} 794 */ 795 @Override() 796 public void logSearchResultReference(SearchOperation searchOperation, 797 SearchResultReference searchReference) 798 { 799 } 800 801 802 803 /** 804 * {@inheritDoc} 805 */ 806 @Override() 807 public void logSearchResultDone(SearchOperation searchOperation) 808 { 809 } 810 811 812 813 /** 814 * {@inheritDoc} 815 */ 816 @Override() 817 public void logUnbind(UnbindOperation unbindOperation) 818 { 819 } 820 821 822 823 /** 824 * Appends the appropriately-encoded attribute value to the provided buffer. 825 * 826 * @param str The ASN.1 octet string containing the value to append. 827 * @param buffer The buffer to which to append the value. 828 */ 829 private void encodeValue(ByteString str, StringBuilder buffer) 830 { 831 byte[] byteVal = str.value(); 832 if(StaticUtils.needsBase64Encoding(byteVal)) 833 { 834 buffer.append(": "); 835 buffer.append(Base64.encode(byteVal)); 836 } else 837 { 838 buffer.append(" "); 839 str.toString(buffer); 840 } 841 } 842 843 844 845 /** 846 * Appends the appropriately-encoded attribute value to the provided buffer. 847 * 848 * @param str The string containing the value to append. 849 * @param buffer The buffer to which to append the value. 850 */ 851 private void encodeValue(String str, StringBuilder buffer) 852 { 853 if(StaticUtils.needsBase64Encoding(str)) 854 { 855 buffer.append(": "); 856 buffer.append(Base64.encode(getBytes(str))); 857 } else 858 { 859 buffer.append(" "); 860 buffer.append(str); 861 } 862 } 863 864 /** 865 * {@inheritDoc} 866 */ 867 public DN getDN() 868 { 869 if(currentConfig != null) 870 { 871 return currentConfig.dn(); 872 } 873 else 874 { 875 return null; 876 } 877 } 878 } 879