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