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