More code for the audit log.
[gnupg.git] / common / audit.c
1 /* audit.c - GnuPG's audit subsystem
2  *      Copyright (C) 2007 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdarg.h>
24 #include <assert.h>
25
26 #include "util.h"
27 #include "i18n.h"
28 #include "audit.h"
29 #include "audit-events.h"
30
31 /* One log entry.  */
32 struct log_item_s
33 {
34   audit_event_t event; /* The event.  */
35   gpg_error_t err;     /* The logged error code.  */
36   int intvalue;        /* A logged interger value.  */
37   char *string;        /* A malloced string or NULL.  */
38   ksba_cert_t cert;    /* A certifciate or NULL. */
39   int have_err:1;
40   int have_intvalue:1;
41 };
42 typedef struct log_item_s *log_item_t;
43
44
45
46 /* The main audit object.  */
47 struct audit_ctx_s
48 {
49   const char *failure;  /* If set a description of the internal failure.  */
50   audit_type_t type;
51   
52   log_item_t log;       /* The table with the log entries.  */
53   size_t logsize;       /* The allocated size for LOG.  */
54   size_t logused;       /* The used size of LOG.  */
55
56   estream_t outstream;  /* The current output stream.  */
57   int use_html;         /* The output shall be HTML formatted.  */
58   int indentlevel;      /* Current level of indentation.  */
59 };
60
61
62
63 \f
64 static void writeout_li (audit_ctx_t ctx, const char *oktext,
65                          const char *format, ...) JNLIB_GCC_A_PRINTF(3,4);
66 static void writeout_rem (audit_ctx_t ctx, 
67                           const char *format, ...) JNLIB_GCC_A_PRINTF(2,3);
68
69
70 \f
71 static const char *
72 event2str (audit_event_t event)
73 {
74   int idx = eventstr_msgidxof (event);
75   if (idx == -1)
76     return "Unknown event";
77   else
78     return eventstr_msgstr + eventstr_msgidx[idx];
79 }
80
81
82
83 /* Create a new audit context.  In case of an error NULL is returned
84    and errno set appropriately. */ 
85 audit_ctx_t
86 audit_new (void)
87 {
88   audit_ctx_t ctx;
89
90   ctx = xtrycalloc (1, sizeof *ctx);
91
92   return ctx;
93 }
94
95
96 /* Release an audit context.  Passing NULL for CTX is allowed and does
97    nothing.  */
98 void
99 audit_release (audit_ctx_t ctx)
100 {
101   int idx;
102   if (!ctx)
103     return;
104   if (ctx->log)
105     {
106       for (idx=0; idx < ctx->logused; idx++)
107         {
108           if (ctx->log[idx].string)
109             xfree (ctx->log[idx].string);
110           if (ctx->log[idx].cert)
111             ksba_cert_release (ctx->log[idx].cert);
112         }
113       xfree (ctx->log);
114     }
115   xfree (ctx);
116 }
117
118
119 /* Set the type for the audit operation.  If CTX is NULL, this is a
120    dummy fucntion.  */
121 void
122 audit_set_type (audit_ctx_t ctx, audit_type_t type)
123 {
124   if (!ctx || ctx->failure)
125     return;  /* Audit not enabled or an internal error has occurred. */
126
127   if (ctx->type && ctx->type != type)
128     {
129       ctx->failure = "conflict in type initialization";
130       return;
131     }
132   ctx->type = type;
133 }
134
135
136 /* Create a new log item and put it into the table.  Return that log
137    item on success; return NULL on memory failure and mark that in
138    CTX. */
139 static log_item_t
140 create_log_item (audit_ctx_t ctx)
141 {
142   log_item_t item, table;
143   size_t size;
144
145   if (!ctx->log)
146     {
147       size = 10;
148       table = xtrymalloc (size * sizeof *table);
149       if (!table)
150         {
151           ctx->failure = "Out of memory in create_log_item";
152           return NULL;
153         }
154       ctx->log = table;
155       ctx->logsize = size;
156       item = ctx->log + 0;
157       ctx->logused = 1;
158     }
159   else if (ctx->logused >= ctx->logsize)
160     {
161       size = ctx->logsize + 10;
162       table = xtryrealloc (ctx->log, size * sizeof *table);
163       if (!table)
164         {
165           ctx->failure = "Out of memory while reallocating in create_log_item";
166           return NULL;
167         }
168       ctx->log = table;
169       ctx->logsize = size;
170       item = ctx->log + ctx->logused++;
171     }
172   else
173     item = ctx->log + ctx->logused++;
174
175   item->event = AUDIT_NULL_EVENT;
176   item->err = 0;
177   item->have_err = 0;
178   item->intvalue = 0;
179   item->have_intvalue = 0;
180   item->string = NULL;
181   item->cert = NULL;
182
183   return item;
184  
185 }
186
187 /* Add a new event to the audit log.  If CTX is NULL, this function
188    does nothing.  */
189 void
190 audit_log (audit_ctx_t ctx, audit_event_t event)
191 {
192   log_item_t item;
193
194   if (!ctx || ctx->failure)
195     return;  /* Audit not enabled or an internal error has occurred. */
196   if (!event)
197     {
198       ctx->failure = "Invalid event passed to audit_log";
199       return;
200     }
201   if (!(item = create_log_item (ctx)))
202     return;
203   item->event = event;
204 }
205
206 /* Add a new event to the audit log.  If CTX is NULL, this function
207    does nothing.  This version also adds the result of the oepration
208    to the log.. */
209 void
210 audit_log_ok (audit_ctx_t ctx, audit_event_t event, gpg_error_t err)
211 {
212   log_item_t item;
213
214   if (!ctx || ctx->failure)
215     return;  /* Audit not enabled or an internal error has occurred. */
216   if (!event)
217     {
218       ctx->failure = "Invalid event passed to audit_log_ok";
219       return;
220     }
221   if (!(item = create_log_item (ctx)))
222     return;
223   item->event = event;
224   item->err = err;
225   item->have_err = 1;
226 }
227
228
229 /* Add a new event to the audit log.  If CTX is NULL, this function
230    does nothing.  This version also add the integer VALUE to the log.  */
231 void
232 audit_log_i (audit_ctx_t ctx, audit_event_t event, int value)
233 {
234   log_item_t item;
235
236   if (!ctx || ctx->failure)
237     return;  /* Audit not enabled or an internal error has occurred. */
238   if (!event)
239     {
240       ctx->failure = "Invalid event passed to audit_log_i";
241       return;
242     }
243   if (!(item = create_log_item (ctx)))
244     return;
245   item->event = event;
246   item->intvalue = value;
247   item->have_intvalue = 1;
248 }
249
250
251 /* Add a new event to the audit log.  If CTX is NULL, this function
252    does nothing.  This version also add the integer VALUE to the log.  */
253 void
254 audit_log_s (audit_ctx_t ctx, audit_event_t event, const char *value)
255 {
256   log_item_t item;
257   char *tmp;
258
259   if (!ctx || ctx->failure)
260     return;  /* Audit not enabled or an internal error has occurred. */
261   if (!event)
262     {
263       ctx->failure = "Invalid event passed to audit_log_s";
264       return;
265     }
266   tmp = xtrystrdup (value? value : "");
267   if (!tmp)
268     {
269       ctx->failure = "Out of memory in audit_event";
270       return;
271     }
272   if (!(item = create_log_item (ctx)))
273     {
274       xfree (tmp);
275       return;
276     }
277   item->event = event;
278   item->string = tmp;
279 }
280
281 /* Add a new event to the audit log.  If CTX is NULL, this function
282    does nothing.  This version also adds the certificate CERT and the
283    result of an operation to the log.  */
284 void
285 audit_log_cert (audit_ctx_t ctx, audit_event_t event, 
286                 ksba_cert_t cert, gpg_error_t err)
287 {
288   log_item_t item;
289
290   if (!ctx || ctx->failure)
291     return;  /* Audit not enabled or an internal error has occurred. */
292   if (!event)
293     {
294       ctx->failure = "Invalid event passed to audit_log_cert";
295       return;
296     }
297   if (!(item = create_log_item (ctx)))
298     return;
299   item->event = event;
300   item->err = err;
301   item->have_err = 1;
302   if (cert)
303     {
304       ksba_cert_ref (cert); 
305       item->cert = cert;
306     }
307 }
308
309
310 /* Write TEXT to the outstream.  */
311 static void 
312 writeout (audit_ctx_t ctx, const char *text)
313 {
314   if (ctx->use_html)
315     {
316       for (; *text; text++)
317         {
318           if (*text == '<')
319             es_fputs ("&lt;", ctx->outstream);
320           else if (*text == '&')
321             es_fputs ("&amp;", ctx->outstream);
322           else
323             es_putc (*text, ctx->outstream);
324         }
325     }
326   else
327     es_fputs (text, ctx->outstream);
328 }
329
330
331 /* Write TEXT to the outstream using a variable argument list.  */
332 static void 
333 writeout_v (audit_ctx_t ctx, const char *format, va_list arg_ptr)
334 {
335   char *buf;
336
337   estream_vasprintf (&buf, format, arg_ptr);
338   if (buf)
339     {
340       writeout (ctx, buf);
341       xfree (buf);
342     }
343   else
344     writeout (ctx, "[!!Out of core!!]");
345 }
346
347
348 /* Write TEXT as a paragraph.  */
349 static void
350 writeout_para (audit_ctx_t ctx, const char *text)
351 {
352   if (ctx->use_html)
353     es_fputs ("<p>", ctx->outstream);
354   writeout (ctx, text);
355   if (ctx->use_html)
356     es_fputs ("</p>\n", ctx->outstream);
357   else
358     es_fputc ('\n', ctx->outstream);
359 }
360
361
362 static void
363 enter_li (audit_ctx_t ctx)
364 {
365   if (ctx->use_html)
366     {
367       if (!ctx->indentlevel)
368         {
369           es_fputs ("<table border=\"0\">\n"
370                     "  <colgroup>\n"
371                     "    <col width=\"80%\" />\n"
372                     "    <col width=\"20%\" />\n"
373                     "   </colgroup>\n",
374                     ctx->outstream);
375         }
376     }
377   ctx->indentlevel++;
378 }
379
380
381 static void
382 leave_li (audit_ctx_t ctx)
383 {
384   ctx->indentlevel--;
385   if (ctx->use_html)
386     {
387       if (!ctx->indentlevel)
388         es_fputs ("</table>\n", ctx->outstream);
389     }
390 }
391
392   
393 /* Write TEXT as a list element.  If OKTEXT is not NULL, append it to
394    the last line. */
395 static void
396 writeout_li (audit_ctx_t ctx, const char *oktext, const char *format, ...)
397 {
398   va_list arg_ptr;
399   const char *color = NULL;
400
401   if (ctx->use_html && format && oktext)
402     {
403       if (!strcmp (oktext, "OK") || !strcmp (oktext, "Yes"))
404         color = "green";
405       else if (!strcmp (oktext, "FAIL") || !strcmp (oktext, "No"))
406         color = "red";
407     }
408
409   if (ctx->use_html)
410     {
411       int i;
412
413       es_fputs ("  <tr><td><table><tr><td>", ctx->outstream);
414       if (color)
415         es_fprintf (ctx->outstream, "<font color=\"%s\">*</font>", color);
416       else
417         es_fputs ("*", ctx->outstream);
418       for (i=1; i < ctx->indentlevel; i++)
419         es_fputs ("&nbsp;&nbsp;", ctx->outstream);
420       es_fputs ("</td><td>", ctx->outstream);
421     }
422   else
423     es_fprintf (ctx->outstream, "* %*s", (ctx->indentlevel-1)*2, "");
424   if (format)
425     {
426       va_start (arg_ptr, format) ;
427       writeout_v (ctx, format, arg_ptr);
428       va_end (arg_ptr);
429     }
430   if (ctx->use_html)
431     es_fputs ("</td></tr></table>", ctx->outstream);
432   if (format && oktext)
433     {
434       if (ctx->use_html)
435         {
436           es_fputs ("</td><td>", ctx->outstream);
437           if (color)
438             es_fprintf (ctx->outstream, "<font color=\"%s\">", color);
439         }
440       else  
441         writeout (ctx, ":         ");
442       writeout (ctx, oktext);
443       if (color)
444         es_fputs ("</font>", ctx->outstream);
445     }
446   
447   if (ctx->use_html)
448     es_fputs ("</td></tr>\n", ctx->outstream);
449   else
450     es_fputc ('\n', ctx->outstream);
451 }
452
453
454 /* Write a remark line.  */
455 static void
456 writeout_rem (audit_ctx_t ctx, const char *format, ...)
457 {
458   va_list arg_ptr;
459
460   if (ctx->use_html)
461     {
462       int i;
463
464       es_fputs ("  <tr><td><table><tr><td>*", ctx->outstream);
465       for (i=1; i < ctx->indentlevel; i++)
466         es_fputs ("&nbsp;&nbsp;", ctx->outstream);
467       es_fputs ("&nbsp;&nbsp;&nbsp;</td><td> (", ctx->outstream);
468
469     }
470   else
471     es_fprintf (ctx->outstream, "* %*s  (", (ctx->indentlevel-1)*2, "");
472   if (format)
473     {
474       va_start (arg_ptr, format) ;
475       writeout_v (ctx, format, arg_ptr);
476       va_end (arg_ptr);
477     }
478   if (ctx->use_html)
479     es_fputs (")</td></tr></table></td></tr>\n", ctx->outstream);
480   else
481     es_fputs (")\n", ctx->outstream);
482 }
483
484
485 /* Return the first log item for EVENT.  If STOPEVENT is not 0 never
486    look behind that event in the log. If STARTITEM is not NULL start
487    search _after_that item.  */
488 static log_item_t
489 find_next_log_item (audit_ctx_t ctx, log_item_t startitem, 
490                     audit_event_t event, audit_event_t stopevent)
491 {
492   int idx;
493
494   for (idx=0; idx < ctx->logused; idx++)
495     {
496       if (startitem)
497         {
498           if (ctx->log + idx == startitem)
499             startitem = NULL;
500         }
501       else if (stopevent && ctx->log[idx].event == stopevent)
502         break;
503       else if (ctx->log[idx].event == event)
504         return ctx->log + idx;
505     }
506   return NULL;
507 }
508
509
510 static log_item_t
511 find_log_item (audit_ctx_t ctx, audit_event_t event, audit_event_t stopevent)
512 {
513   return find_next_log_item (ctx, NULL, event, stopevent);
514 }
515
516
517 /* Helper to a format a serial number.  */
518 static char *
519 format_serial (ksba_const_sexp_t sn)
520 {
521   const char *p = (const char *)sn;
522   unsigned long n;
523   char *endp;
524
525   if (!p)
526     return NULL;
527   if (*p != '(')
528     BUG (); /* Not a valid S-expression. */
529   n = strtoul (p+1, &endp, 10);
530   p = endp;
531   if (*p != ':')
532     BUG (); /* Not a valid S-expression. */
533   return bin2hex (p+1, n, NULL);
534 }
535
536
537 /* Return a malloced string with the serial number and the issuer DN
538    of the certificate.  */
539 static char *
540 get_cert_name (ksba_cert_t cert)
541 {
542   char *result;
543   ksba_sexp_t sn;
544   char *issuer, *p;
545
546   if (!cert)
547     return xtrystrdup ("[no certificate]");
548
549   issuer = ksba_cert_get_issuer (cert, 0);
550   sn = ksba_cert_get_serial (cert);
551   if (issuer && sn)
552     {
553       p = format_serial (sn);
554       if (!p)
555         result = xtrystrdup ("[invalid S/N]");
556       else
557         {
558           result = xtrymalloc (strlen (p) + strlen (issuer) + 2 + 1);
559           if (result)
560             {
561               *result = '#';
562               strcpy (stpcpy (stpcpy (result+1, p),"/"), issuer);
563             }
564           xfree (p);
565         }
566     }
567   else
568     result = xtrystrdup ("[missing S/N or issuer]");
569   ksba_free (sn);
570   xfree (issuer);
571   return result;
572 }
573
574 /* Return a malloced string with the serial number and the issuer DN
575    of the certificate.  */
576 static char *
577 get_cert_subject (ksba_cert_t cert, int idx)
578 {
579   char *result;
580   char *subject;
581
582   if (!cert)
583     return xtrystrdup ("[no certificate]");
584
585   subject = ksba_cert_get_subject (cert, idx);
586   if (subject)
587     {
588       result = xtrymalloc (strlen (subject) + 1 + 1);
589       if (result)
590         {
591           *result = '/';
592           strcpy (result+1, subject);
593         }
594     }
595   else
596     result = NULL;
597   xfree (subject);
598   return result;
599 }
600
601
602 /* List the chain of certificates from STARTITEM up to STOPEVENT.  The
603    certifcates are written out as comments.  */
604 static void
605 list_certchain (audit_ctx_t ctx, log_item_t startitem, audit_event_t stopevent)
606 {
607   log_item_t item;
608   char *name;
609   int idx;
610
611   startitem = find_next_log_item (ctx, startitem, AUDIT_CHAIN_BEGIN,stopevent);
612   if (!startitem)
613     {
614       writeout_li (ctx, gpg_strerror (GPG_ERR_MISSING_CERT)
615                    , _("Certificate chain"));
616       return; 
617     }
618   writeout_li (ctx, "OK", _("Certificate chain"));
619   item = find_next_log_item (ctx, startitem, 
620                              AUDIT_CHAIN_ROOTCERT, AUDIT_CHAIN_END);
621   if (!item)
622     writeout_rem (ctx, "%s", _("root certificate missing"));
623   else
624     {
625       name = get_cert_name (item->cert);
626       writeout_rem (ctx, "%s", name);
627       xfree (name);
628     }
629   item = startitem;
630   while ( ((item = find_next_log_item (ctx, item, 
631                                        AUDIT_CHAIN_CERT, AUDIT_CHAIN_END))))
632     {
633       name = get_cert_name (item->cert);
634       writeout_rem (ctx, "%s", name);
635       xfree (name);
636       enter_li (ctx);
637       for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
638         {
639           writeout_rem (ctx, "%s", name);
640           xfree (name);
641         }
642       leave_li (ctx);
643     }
644 }
645
646
647 \f
648 /* Process a verification operation.  */
649 static void
650 proc_type_verify (audit_ctx_t ctx)
651 {
652   log_item_t loopitem, item;
653   int signo, count, idx;
654   char numbuf[35];
655
656   enter_li (ctx);
657   
658   writeout_li (ctx, "fixme", "%s", _("Signature verification"));
659   enter_li (ctx);
660
661   writeout_li (ctx, "fixme", "%s", _("Gpg-Agent ready"));
662   writeout_li (ctx, "fixme", "%s", _("Dirmngr ready"));
663
664   item = find_log_item (ctx, AUDIT_GOT_DATA, AUDIT_NEW_SIG);
665   writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
666   if (!item)
667     goto leave;
668
669   item = find_log_item (ctx, AUDIT_NEW_SIG, 0);
670   writeout_li (ctx, item? "Yes":"No", "%s", _("Signature available"));
671   if (!item)
672     goto leave;
673
674   item = find_log_item (ctx, AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG);
675   if (item)
676     writeout_li (ctx, "OK", "%s", _("Parsing signature"));
677   else 
678     {
679       item = find_log_item (ctx, AUDIT_BAD_DATA_HASH_ALGO, AUDIT_NEW_SIG);
680       if (item)
681         {
682           writeout_li (ctx,"FAIL", "%s",  _("Parsing signature"));
683           writeout_rem (ctx, _("Bad hash algorithm: %s"), 
684                         item->string? item->string:"?");
685         }
686       else
687         writeout_li (ctx, "FAIL", "%s", _("Parsing signature") );
688       goto leave;
689     }
690
691   /* Loop over all signatures.  */
692   loopitem = find_log_item (ctx, AUDIT_NEW_SIG, 0);
693   assert (loopitem);
694   do
695     {
696       signo = loopitem->have_intvalue? loopitem->intvalue : -1;
697
698       item = find_next_log_item (ctx, loopitem,
699                                  AUDIT_SIG_STATUS, AUDIT_NEW_SIG);
700       writeout_li (ctx, item? item->string:"?", _("Signature %d"), signo);
701       item = find_next_log_item (ctx, loopitem,
702                                  AUDIT_SIG_NAME, AUDIT_NEW_SIG);
703       if (item)
704         writeout_rem (ctx, "%s", item->string);
705       enter_li (ctx);
706       
707       /* List the certificate chain.  */
708       list_certchain (ctx, loopitem, AUDIT_NEW_SIG);
709
710       /* Show the result of the chain validation.  */
711       item = find_next_log_item (ctx, loopitem,
712                                  AUDIT_CHAIN_STATUS, AUDIT_NEW_SIG);
713       if (item && item->have_err)
714         {
715           writeout_li (ctx, item->err? "FAIL":"OK", 
716                        _("Validation of certificate chain"));
717           if (item->err)
718             writeout_rem (ctx, "%s", gpg_strerror (item->err));
719         }
720       
721       /* Show whether the root certificate is fine.  */
722       writeout_li (ctx, "No", "%s", _("Root certificate trustworthy"));
723
724       /* Show result of the CRL/OCSP check.  */
725       writeout_li (ctx, "-", "%s", _("CRL/OCSP check of certificates"));
726
727
728       leave_li (ctx);
729     }
730   while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0)));
731
732
733  leave:
734   /* Always list the certificates stored in the signature.  */
735   item = NULL;
736   count = 0;
737   while ( ((item = find_next_log_item (ctx, item, 
738                                        AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
739     count++;
740   snprintf (numbuf, sizeof numbuf, "%d", count);
741   writeout_li (ctx, numbuf, _("Included certificates"));
742   item = NULL;
743   while ( ((item = find_next_log_item (ctx, item, 
744                                        AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
745     {
746       char *name = get_cert_name (item->cert);
747       writeout_rem (ctx, "%s", name);
748       xfree (name);
749       enter_li (ctx);
750       for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
751         {
752           writeout_rem (ctx, "%s", name);
753           xfree (name);
754         }
755       leave_li (ctx);
756     }
757
758   leave_li (ctx);
759   leave_li (ctx);
760 }
761
762
763
764 \f
765 /* Print the formatted audit result.    THIS IS WORK IN PROGRESS.  */
766 void
767 audit_print_result (audit_ctx_t ctx, estream_t out, int use_html)
768 {
769   int idx;
770   int maxlen;
771   size_t n;
772
773   if (getenv ("use_html"))
774     use_html = 1;
775
776   if (!ctx)
777     return;
778
779   assert (!ctx->outstream);
780   ctx->outstream = out;
781   ctx->use_html = use_html;
782   ctx->indentlevel = 0;
783
784   if (use_html)
785     es_fputs ("<div class=\"GnuPGAuditLog\">\n", ctx->outstream);
786
787   if (!ctx->log || !ctx->logused)
788     {
789       writeout_para (ctx, _("No audit log entries."));
790       goto leave;
791     }
792
793   for (idx=0,maxlen=0; idx < DIM (eventstr_msgidx); idx++)
794     {
795       n = strlen (eventstr_msgstr + eventstr_msgidx[idx]);    
796       if (n > maxlen)
797         maxlen = n;
798     }
799
800   if (use_html)
801     es_fputs ("<pre>\n", out);
802   for (idx=0; idx < ctx->logused; idx++)
803     {
804       es_fprintf (out, "log: %-*s", 
805                   maxlen, event2str (ctx->log[idx].event));
806       if (ctx->log[idx].have_intvalue)
807         es_fprintf (out, " i=%d", ctx->log[idx].intvalue); 
808       if (ctx->log[idx].string)
809         {
810           es_fputs (" s=`", out); 
811           writeout (ctx, ctx->log[idx].string); 
812           es_fputs ("'", out); 
813         }
814       if (ctx->log[idx].cert)
815         es_fprintf (out, " has_cert"); 
816       if (ctx->log[idx].have_err)
817         {
818           es_fputs (" err=`", out);
819           writeout (ctx, gpg_strerror (ctx->log[idx].err)); 
820           es_fputs ("'", out);
821         }
822       es_fputs ("\n", out);
823     }
824   if (use_html)
825     es_fputs ("</pre>\n", out);
826   else
827     es_fputs ("\n", out);
828
829   switch (ctx->type)
830     {
831     case AUDIT_TYPE_NONE:
832       writeout_para (ctx, _("Audit of this operation is not supported."));
833       break;
834     case AUDIT_TYPE_VERIFY:
835       proc_type_verify (ctx);
836       break;
837     }
838
839  leave:
840   if (use_html)
841     es_fputs ("</div>\n", ctx->outstream);
842   ctx->outstream = NULL;
843   ctx->use_html = 0;
844 }
845