/* audit.c - GnuPG's audit subsystem
 *	Copyright (C) 2007, 2009 Free Software Foundation, Inc.
 *
 * This file is part of GnuPG.
 *
 * GnuPG is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * GnuPG is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>

#include "util.h"
#include "i18n.h"
#include "audit.h"
#include "audit-events.h"

/* A list to maintain a list of helptags.  */
struct helptag_s
{
  struct helptag_s *next;
  const char *name;
};
typedef struct helptag_s *helptag_t;


/* One log entry.  */
struct log_item_s
{
  audit_event_t event; /* The event.  */
  gpg_error_t err;     /* The logged error code.  */
  int intvalue;        /* A logged integer value.  */
  char *string;        /* A malloced string or NULL.  */
  ksba_cert_t cert;    /* A certifciate or NULL. */
  int have_err:1;
  int have_intvalue:1;
};
typedef struct log_item_s *log_item_t;



/* The main audit object.  */
struct audit_ctx_s
{
  const char *failure;  /* If set a description of the internal failure.  */
  audit_type_t type;

  log_item_t log;       /* The table with the log entries.  */
  size_t logsize;       /* The allocated size for LOG.  */
  size_t logused;       /* The used size of LOG.  */

  estream_t outstream;  /* The current output stream.  */
  int use_html;         /* The output shall be HTML formatted.  */
  int indentlevel;      /* Current level of indentation.  */
  helptag_t helptags;   /* List of help keys.  */
};




static void writeout_para (audit_ctx_t ctx,
                           const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
static void writeout_li (audit_ctx_t ctx, const char *oktext,
                         const char *format, ...) GPGRT_ATTR_PRINTF(3,4);
static void writeout_rem (audit_ctx_t ctx,
                          const char *format, ...) GPGRT_ATTR_PRINTF(2,3);


/* Add NAME to the list of help tags.  NAME needs to be a const string
   an this function merly stores this pointer.  */
static void
add_helptag (audit_ctx_t ctx, const char *name)
{
  helptag_t item;

  for (item=ctx->helptags; item; item = item->next)
    if (!strcmp (item->name, name))
      return;  /* Already in the list.  */
  item = xtrycalloc (1, sizeof *item);
  if (!item)
    return;  /* Don't care about memory problems.  */
  item->name = name;
  item->next = ctx->helptags;
  ctx->helptags = item;
}


/* Remove all help tags from the context.  */
static void
clear_helptags (audit_ctx_t ctx)
{
  while (ctx->helptags)
    {
      helptag_t tmp = ctx->helptags->next;
      xfree (ctx->helptags);
      ctx->helptags = tmp;
    }
}



static const char *
event2str (audit_event_t event)
{
  /* We need the cast so that compiler does not complain about an
     always true comparison (>= 0) for an unsigned value.  */
  int idx = eventstr_msgidxof ((int)event);
  if (idx == -1)
    return "Unknown event";
  else
    return eventstr_msgstr + eventstr_msgidx[idx];
}



/* Create a new audit context.  In case of an error NULL is returned
   and errno set appropriately. */
audit_ctx_t
audit_new (void)
{
  audit_ctx_t ctx;

  ctx = xtrycalloc (1, sizeof *ctx);

  return ctx;
}


/* Release an audit context.  Passing NULL for CTX is allowed and does
   nothing.  */
void
audit_release (audit_ctx_t ctx)
{
  int idx;
  if (!ctx)
    return;
  if (ctx->log)
    {
      for (idx=0; idx < ctx->logused; idx++)
        {
          if (ctx->log[idx].string)
            xfree (ctx->log[idx].string);
          if (ctx->log[idx].cert)
            ksba_cert_release (ctx->log[idx].cert);
        }
      xfree (ctx->log);
    }
  clear_helptags (ctx);
  xfree (ctx);
}


/* Set the type for the audit operation.  If CTX is NULL, this is a
   dummy function.  */
void
audit_set_type (audit_ctx_t ctx, audit_type_t type)
{
  if (!ctx || ctx->failure)
    return;  /* Audit not enabled or an internal error has occurred. */

  if (ctx->type && ctx->type != type)
    {
      ctx->failure = "conflict in type initialization";
      return;
    }
  ctx->type = type;
}


/* Create a new log item and put it into the table.  Return that log
   item on success; return NULL on memory failure and mark that in
   CTX. */
static log_item_t
create_log_item (audit_ctx_t ctx)
{
  log_item_t item, table;
  size_t size;

  if (!ctx->log)
    {
      size = 10;
      table = xtrymalloc (size * sizeof *table);
      if (!table)
        {
          ctx->failure = "Out of memory in create_log_item";
          return NULL;
        }
      ctx->log = table;
      ctx->logsize = size;
      item = ctx->log + 0;
      ctx->logused = 1;
    }
  else if (ctx->logused >= ctx->logsize)
    {
      size = ctx->logsize + 10;
      table = xtryrealloc (ctx->log, size * sizeof *table);
      if (!table)
        {
          ctx->failure = "Out of memory while reallocating in create_log_item";
          return NULL;
        }
      ctx->log = table;
      ctx->logsize = size;
      item = ctx->log + ctx->logused++;
    }
  else
    item = ctx->log + ctx->logused++;

  item->event = AUDIT_NULL_EVENT;
  item->err = 0;
  item->have_err = 0;
  item->intvalue = 0;
  item->have_intvalue = 0;
  item->string = NULL;
  item->cert = NULL;

  return item;

}

/* Add a new event to the audit log.  If CTX is NULL, this function
   does nothing.  */
void
audit_log (audit_ctx_t ctx, audit_event_t event)
{
  log_item_t item;

  if (!ctx || ctx->failure)
    return;  /* Audit not enabled or an internal error has occurred. */
  if (!event)
    {
      ctx->failure = "Invalid event passed to audit_log";
      return;
    }
  if (!(item = create_log_item (ctx)))
    return;
  item->event = event;
}

/* Add a new event to the audit log.  If CTX is NULL, this function
   does nothing.  This version also adds the result of the operation
   to the log.  */
void
audit_log_ok (audit_ctx_t ctx, audit_event_t event, gpg_error_t err)
{
  log_item_t item;

  if (!ctx || ctx->failure)
    return;  /* Audit not enabled or an internal error has occurred. */
  if (!event)
    {
      ctx->failure = "Invalid event passed to audit_log_ok";
      return;
    }
  if (!(item = create_log_item (ctx)))
    return;
  item->event = event;
  item->err = err;
  item->have_err = 1;
}


/* Add a new event to the audit log.  If CTX is NULL, this function
   does nothing.  This version also add the integer VALUE to the log.  */
void
audit_log_i (audit_ctx_t ctx, audit_event_t event, int value)
{
  log_item_t item;

  if (!ctx || ctx->failure)
    return;  /* Audit not enabled or an internal error has occurred. */
  if (!event)
    {
      ctx->failure = "Invalid event passed to audit_log_i";
      return;
    }
  if (!(item = create_log_item (ctx)))
    return;
  item->event = event;
  item->intvalue = value;
  item->have_intvalue = 1;
}


/* Add a new event to the audit log.  If CTX is NULL, this function
   does nothing.  This version also add the integer VALUE to the log.  */
void
audit_log_s (audit_ctx_t ctx, audit_event_t event, const char *value)
{
  log_item_t item;
  char *tmp;

  if (!ctx || ctx->failure)
    return;  /* Audit not enabled or an internal error has occurred. */
  if (!event)
    {
      ctx->failure = "Invalid event passed to audit_log_s";
      return;
    }
  tmp = xtrystrdup (value? value : "");
  if (!tmp)
    {
      ctx->failure = "Out of memory in audit_event";
      return;
    }
  if (!(item = create_log_item (ctx)))
    {
      xfree (tmp);
      return;
    }
  item->event = event;
  item->string = tmp;
}

/* Add a new event to the audit log.  If CTX is NULL, this function
   does nothing.  This version also adds the certificate CERT and the
   result of an operation to the log.  */
void
audit_log_cert (audit_ctx_t ctx, audit_event_t event,
                ksba_cert_t cert, gpg_error_t err)
{
  log_item_t item;

  if (!ctx || ctx->failure)
    return;  /* Audit not enabled or an internal error has occurred. */
  if (!event)
    {
      ctx->failure = "Invalid event passed to audit_log_cert";
      return;
    }
  if (!(item = create_log_item (ctx)))
    return;
  item->event = event;
  item->err = err;
  item->have_err = 1;
  if (cert)
    {
      ksba_cert_ref (cert);
      item->cert = cert;
    }
}


/* Write TEXT to the outstream.  */
static void
writeout (audit_ctx_t ctx, const char *text)
{
  if (ctx->use_html)
    {
      for (; *text; text++)
        {
          if (*text == '<')
            es_fputs ("&lt;", ctx->outstream);
          else if (*text == '&')
            es_fputs ("&amp;", ctx->outstream);
          else
            es_putc (*text, ctx->outstream);
        }
    }
  else
    es_fputs (text, ctx->outstream);
}


/* Write TEXT to the outstream using a variable argument list.  */
static void
writeout_v (audit_ctx_t ctx, const char *format, va_list arg_ptr)
{
  char *buf;

  gpgrt_vasprintf (&buf, format, arg_ptr);
  if (buf)
    {
      writeout (ctx, buf);
      xfree (buf);
    }
  else
    writeout (ctx, "[!!Out of core!!]");
}


/* Write TEXT as a paragraph.  */
static void
writeout_para (audit_ctx_t ctx, const char *format, ...)
{
  va_list arg_ptr;

  if (ctx->use_html)
    es_fputs ("<p>", ctx->outstream);
  va_start (arg_ptr, format) ;
  writeout_v (ctx, format, arg_ptr);
  va_end (arg_ptr);
  if (ctx->use_html)
    es_fputs ("</p>\n", ctx->outstream);
  else
    es_fputc ('\n', ctx->outstream);
}


static void
enter_li (audit_ctx_t ctx)
{
  if (ctx->use_html)
    {
      if (!ctx->indentlevel)
        {
          es_fputs ("<table border=\"0\">\n"
                    "  <colgroup>\n"
                    "    <col width=\"80%\" />\n"
                    "    <col width=\"20%\" />\n"
                    "   </colgroup>\n",
                    ctx->outstream);
        }
    }
  ctx->indentlevel++;
}


static void
leave_li (audit_ctx_t ctx)
{
  ctx->indentlevel--;
  if (ctx->use_html)
    {
      if (!ctx->indentlevel)
        es_fputs ("</table>\n", ctx->outstream);
    }
}


/* Write TEXT as a list element.  If OKTEXT is not NULL, append it to
   the last line. */
static void
writeout_li (audit_ctx_t ctx, const char *oktext, const char *format, ...)
{
  va_list arg_ptr;
  const char *color = NULL;

  if (ctx->use_html && format && oktext)
    {
      if (!strcmp (oktext, "Yes")
          || !strcmp (oktext, "good") )
        color = "green";
      else if (!strcmp (oktext, "No")
               || !strcmp (oktext, "bad") )
        color = "red";
    }

  if (format && oktext)
    {
      const char *s = NULL;

      if (!strcmp (oktext, "Yes"))
        oktext = _("Yes");
      else if (!strcmp (oktext, "No"))
        oktext = _("No");
      else if (!strcmp (oktext, "good"))
        {
          /* TRANSLATORS: Copy the prefix between the vertical bars
             verbatim.  It will not be printed.  */
          oktext = _("|audit-log-result|Good");
        }
      else if (!strcmp (oktext, "bad"))
        oktext = _("|audit-log-result|Bad");
      else if (!strcmp (oktext, "unsupported"))
        oktext = _("|audit-log-result|Not supported");
      else if (!strcmp (oktext, "no-cert"))
        oktext = _("|audit-log-result|No certificate");
      else if (!strcmp (oktext, "disabled"))
        oktext = _("|audit-log-result|Not enabled");
      else if (!strcmp (oktext, "error"))
        oktext = _("|audit-log-result|Error");
      else if (!strcmp (oktext, "not-used"))
        oktext = _("|audit-log-result|Not used");
      else if (!strcmp (oktext, "okay"))
        oktext = _("|audit-log-result|Okay");
      else if (!strcmp (oktext, "skipped"))
        oktext = _("|audit-log-result|Skipped");
      else if (!strcmp (oktext, "some"))
        oktext = _("|audit-log-result|Some");
      else
        s = "";

      /* If we have set a prefix, skip it.  */
      if (!s && *oktext == '|' && (s=strchr (oktext+1,'|')))
        oktext = s+1;
    }

  if (ctx->use_html)
    {
      int i;

      es_fputs ("  <tr><td><table><tr><td>", ctx->outstream);
      if (color)
        es_fprintf (ctx->outstream, "<font color=\"%s\">*</font>", color);
      else
        es_fputs ("*", ctx->outstream);
      for (i=1; i < ctx->indentlevel; i++)
        es_fputs ("&nbsp;&nbsp;", ctx->outstream);
      es_fputs ("</td><td>", ctx->outstream);
    }
  else
    es_fprintf (ctx->outstream, "* %*s", (ctx->indentlevel-1)*2, "");
  if (format)
    {
      va_start (arg_ptr, format) ;
      writeout_v (ctx, format, arg_ptr);
      va_end (arg_ptr);
    }
  if (ctx->use_html)
    es_fputs ("</td></tr></table>", ctx->outstream);
  if (format && oktext)
    {
      if (ctx->use_html)
        {
          es_fputs ("</td><td>", ctx->outstream);
          if (color)
            es_fprintf (ctx->outstream, "<font color=\"%s\">", color);
        }
      else
        writeout (ctx, ":         ");
      writeout (ctx, oktext);
      if (color)
        es_fputs ("</font>", ctx->outstream);
    }

  if (ctx->use_html)
    es_fputs ("</td></tr>\n", ctx->outstream);
  else
    es_fputc ('\n', ctx->outstream);
}


/* Write a remark line.  */
static void
writeout_rem (audit_ctx_t ctx, const char *format, ...)
{
  va_list arg_ptr;

  if (ctx->use_html)
    {
      int i;

      es_fputs ("  <tr><td><table><tr><td>*", ctx->outstream);
      for (i=1; i < ctx->indentlevel; i++)
        es_fputs ("&nbsp;&nbsp;", ctx->outstream);
      es_fputs ("&nbsp;&nbsp;&nbsp;</td><td> (", ctx->outstream);

    }
  else
    es_fprintf (ctx->outstream, "* %*s  (", (ctx->indentlevel-1)*2, "");
  if (format)
    {
      va_start (arg_ptr, format) ;
      writeout_v (ctx, format, arg_ptr);
      va_end (arg_ptr);
    }
  if (ctx->use_html)
    es_fputs (")</td></tr></table></td></tr>\n", ctx->outstream);
  else
    es_fputs (")\n", ctx->outstream);
}


/* Return the first log item for EVENT.  If STOPEVENT is not 0 never
   look behind that event in the log. If STARTITEM is not NULL start
   search _after_that item.  */
static log_item_t
find_next_log_item (audit_ctx_t ctx, log_item_t startitem,
                    audit_event_t event, audit_event_t stopevent)
{
  int idx;

  for (idx=0; idx < ctx->logused; idx++)
    {
      if (startitem)
        {
          if (ctx->log + idx == startitem)
            startitem = NULL;
        }
      else if (stopevent && ctx->log[idx].event == stopevent)
        break;
      else if (ctx->log[idx].event == event)
        return ctx->log + idx;
    }
  return NULL;
}


static log_item_t
find_log_item (audit_ctx_t ctx, audit_event_t event, audit_event_t stopevent)
{
  return find_next_log_item (ctx, NULL, event, stopevent);
}


/* Helper to a format a serial number.  */
static char *
format_serial (ksba_const_sexp_t sn)
{
  const char *p = (const char *)sn;
  unsigned long n;
  char *endp;

  if (!p)
    return NULL;
  if (*p != '(')
    BUG (); /* Not a valid S-expression. */
  n = strtoul (p+1, &endp, 10);
  p = endp;
  if (*p != ':')
    BUG (); /* Not a valid S-expression. */
  return bin2hex (p+1, n, NULL);
}


/* Return a malloced string with the serial number and the issuer DN
   of the certificate.  */
static char *
get_cert_name (ksba_cert_t cert)
{
  char *result;
  ksba_sexp_t sn;
  char *issuer, *p;

  if (!cert)
    return xtrystrdup ("[no certificate]");

  issuer = ksba_cert_get_issuer (cert, 0);
  sn = ksba_cert_get_serial (cert);
  if (issuer && sn)
    {
      p = format_serial (sn);
      if (!p)
        result = xtrystrdup ("[invalid S/N]");
      else
        {
          result = xtrymalloc (strlen (p) + strlen (issuer) + 2 + 1);
          if (result)
            {
              *result = '#';
              strcpy (stpcpy (stpcpy (result+1, p),"/"), issuer);
            }
          xfree (p);
        }
    }
  else
    result = xtrystrdup ("[missing S/N or issuer]");
  ksba_free (sn);
  xfree (issuer);
  return result;
}

/* Return a malloced string with the serial number and the issuer DN
   of the certificate.  */
static char *
get_cert_subject (ksba_cert_t cert, int idx)
{
  char *result;
  char *subject;

  if (!cert)
    return xtrystrdup ("[no certificate]");

  subject = ksba_cert_get_subject (cert, idx);
  if (subject)
    {
      result = xtrymalloc (strlen (subject) + 1 + 1);
      if (result)
        {
          *result = '/';
          strcpy (result+1, subject);
        }
    }
  else
    result = NULL;
  xfree (subject);
  return result;
}


/* List the given certificiate.  If CERT is NULL, this is a NOP.  */
static void
list_cert (audit_ctx_t ctx, ksba_cert_t cert, int with_subj)
{
  char *name;
  int idx;

  name = get_cert_name (cert);
  writeout_rem (ctx, "%s", name);
  xfree (name);
  if (with_subj)
    {
      enter_li (ctx);
      for (idx=0; (name = get_cert_subject (cert, idx)); idx++)
        {
          writeout_rem (ctx, "%s", name);
          xfree (name);
        }
      leave_li (ctx);
    }
}


/* List the chain of certificates from STARTITEM up to STOPEVENT.  The
   certificates are written out as comments.  */
static void
list_certchain (audit_ctx_t ctx, log_item_t startitem, audit_event_t stopevent)
{
  log_item_t item;

  startitem = find_next_log_item (ctx, startitem, AUDIT_CHAIN_BEGIN,stopevent);
  writeout_li (ctx, startitem? "Yes":"No", _("Certificate chain available"));
  if (!startitem)
    return;

  item = find_next_log_item (ctx, startitem,
                             AUDIT_CHAIN_ROOTCERT, AUDIT_CHAIN_END);
  if (!item)
    writeout_rem (ctx, "%s", _("root certificate missing"));
  else
    {
      list_cert (ctx, item->cert, 0);
    }
  item = startitem;
  while ( ((item = find_next_log_item (ctx, item,
                                       AUDIT_CHAIN_CERT, AUDIT_CHAIN_END))))
    {
      list_cert (ctx, item->cert, 1);
    }
}



/* Process an encrypt operation's log.  */
static void
proc_type_encrypt (audit_ctx_t ctx)
{
  log_item_t loopitem, item;
  int recp_no, idx;
  char numbuf[35];
  int algo;
  char *name;

  item = find_log_item (ctx, AUDIT_ENCRYPTION_DONE, 0);
  writeout_li (ctx, item?"Yes":"No", "%s", _("Data encryption succeeded"));

  enter_li (ctx);

  item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
  writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));

  item = find_log_item (ctx, AUDIT_SESSION_KEY, 0);
  writeout_li (ctx, item? "Yes":"No", "%s", _("Session key created"));
  if (item)
    {
      algo = gcry_cipher_map_name (item->string);
      if (algo)
        writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
      else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2"))
        writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2");
      else if (item->string)
        writeout_rem (ctx, _("unsupported algorithm: %s"), item->string);
      else
        writeout_rem (ctx, _("seems to be not encrypted"));
    }

  item = find_log_item (ctx, AUDIT_GOT_RECIPIENTS, 0);
  snprintf (numbuf, sizeof numbuf, "%d",
            item && item->have_intvalue? item->intvalue : 0);
  writeout_li (ctx, numbuf, "%s", _("Number of recipients"));

  /* Loop over all recipients.  */
  loopitem = NULL;
  recp_no = 0;
  while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_ENCRYPTED_TO, 0)))
    {
      recp_no++;
      writeout_li (ctx, NULL, _("Recipient %d"), recp_no);
      if (loopitem->cert)
        {
          name = get_cert_name (loopitem->cert);
          writeout_rem (ctx, "%s", name);
          xfree (name);
          enter_li (ctx);
          for (idx=0; (name = get_cert_subject (loopitem->cert, idx)); idx++)
            {
              writeout_rem (ctx, "%s", name);
              xfree (name);
            }
          leave_li (ctx);
        }
    }

  leave_li (ctx);
}



/* Process a sign operation's log.  */
static void
proc_type_sign (audit_ctx_t ctx)
{
  log_item_t item, loopitem;
  int signer, idx;
  const char *result;
  ksba_cert_t cert;
  char *name;
  int lastalgo;

  item = find_log_item (ctx, AUDIT_SIGNING_DONE, 0);
  writeout_li (ctx, item?"Yes":"No", "%s", _("Data signing succeeded"));

  enter_li (ctx);

  item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
  writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
  /* Write remarks with the data hash algorithms.  We use a very
     simple scheme to avoid some duplicates.  */
  loopitem = NULL;
  lastalgo = 0;
  while ((loopitem = find_next_log_item
          (ctx, loopitem, AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG)))
    {
      if (loopitem->intvalue && loopitem->intvalue != lastalgo)
        writeout_rem (ctx, _("data hash algorithm: %s"),
                      gcry_md_algo_name (loopitem->intvalue));
      lastalgo = loopitem->intvalue;
    }

  /* Loop over all signer.  */
  loopitem = NULL;
  signer = 0;
  while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0)))
    {
      signer++;

      item = find_next_log_item (ctx, loopitem, AUDIT_SIGNED_BY, AUDIT_NEW_SIG);
      if (!item)
        result = "error";
      else if (!item->err)
        result = "okay";
      else if (gpg_err_code (item->err) == GPG_ERR_CANCELED)
        result = "skipped";
      else
        result = gpg_strerror (item->err);
      cert = item? item->cert : NULL;

      writeout_li (ctx, result, _("Signer %d"), signer);
      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG);
      if (item)
        writeout_rem (ctx, _("attr hash algorithm: %s"),
                      gcry_md_algo_name (item->intvalue));

      if (cert)
        {
          name = get_cert_name (cert);
          writeout_rem (ctx, "%s", name);
          xfree (name);
          enter_li (ctx);
          for (idx=0; (name = get_cert_subject (cert, idx)); idx++)
            {
              writeout_rem (ctx, "%s", name);
              xfree (name);
            }
          leave_li (ctx);
        }
    }

  leave_li (ctx);
}



/* Process a decrypt operation's log.  */
static void
proc_type_decrypt (audit_ctx_t ctx)
{
  log_item_t loopitem, item;
  int algo, recpno;
  char *name;
  char numbuf[35];
  int idx;

  item = find_log_item (ctx, AUDIT_DECRYPTION_RESULT, 0);
  writeout_li (ctx, item && !item->err?"Yes":"No",
               "%s", _("Data decryption succeeded"));

  enter_li (ctx);

  item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
  writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));

  item = find_log_item (ctx, AUDIT_DATA_CIPHER_ALGO, 0);
  algo = item? item->intvalue : 0;
  writeout_li (ctx, algo?"Yes":"No", "%s", _("Encryption algorithm supported"));
  if (algo)
    writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));

  item = find_log_item (ctx, AUDIT_BAD_DATA_CIPHER_ALGO, 0);
  if (item && item->string)
    {
      algo = gcry_cipher_map_name (item->string);
      if (algo)
        writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
      else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2"))
        writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2");
      else if (item->string)
        writeout_rem (ctx, _("unsupported algorithm: %s"), item->string);
      else
        writeout_rem (ctx, _("seems to be not encrypted"));
    }


  for (recpno = 0, item = NULL;
       (item = find_next_log_item (ctx, item, AUDIT_NEW_RECP, 0)); recpno++)
    ;
  snprintf (numbuf, sizeof numbuf, "%d", recpno);
  writeout_li (ctx, numbuf, "%s", _("Number of recipients"));

  /* Loop over all recipients.  */
  loopitem = NULL;
  while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_RECP, 0)))
    {
      const char *result;

      recpno = loopitem->have_intvalue? loopitem->intvalue : -1;

      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_RECP_RESULT, AUDIT_NEW_RECP);
      if (!item)
        result = "not-used";
      else if (!item->err)
        result = "okay";
      else if (gpg_err_code (item->err) == GPG_ERR_CANCELED)
        result = "skipped";
      else
        result = gpg_strerror (item->err);

      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_RECP_NAME, AUDIT_NEW_RECP);
      writeout_li (ctx, result, _("Recipient %d"), recpno);
      if (item && item->string)
        writeout_rem (ctx, "%s", item->string);

      /* If we have a certificate write out more infos.  */
      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_SAVE_CERT, AUDIT_NEW_RECP);
      if (item && item->cert)
        {
          enter_li (ctx);
          for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
            {
              writeout_rem (ctx, "%s", name);
              xfree (name);
            }
          leave_li (ctx);
        }
    }

  leave_li (ctx);
}



/* Process a verification operation's log.  */
static void
proc_type_verify (audit_ctx_t ctx)
{
  log_item_t loopitem, item;
  int signo, count, idx, n_good, n_bad;
  char numbuf[35];
  const char *result;

  /* If there is at least one signature status we claim that the
     verification succeeded.  This does not mean that the data has
     verified okay.  */
  item = find_log_item (ctx, AUDIT_SIG_STATUS, 0);
  writeout_li (ctx, item?"Yes":"No", "%s", _("Data verification succeeded"));
  enter_li (ctx);

  item = find_log_item (ctx, AUDIT_GOT_DATA, AUDIT_NEW_SIG);
  writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
  if (!item)
    goto leave;

  item = find_log_item (ctx, AUDIT_NEW_SIG, 0);
  writeout_li (ctx, item? "Yes":"No", "%s", _("Signature available"));
  if (!item)
    goto leave;

  /* Print info about the used data hashing algorithms.  */
  for (idx=0, n_good=n_bad=0; idx < ctx->logused; idx++)
    {
      item = ctx->log + idx;
      if (item->event == AUDIT_NEW_SIG)
        break;
      else if (item->event == AUDIT_DATA_HASH_ALGO)
        n_good++;
      else if (item->event == AUDIT_BAD_DATA_HASH_ALGO)
        n_bad++;
    }
  item = find_log_item (ctx, AUDIT_DATA_HASHING, AUDIT_NEW_SIG);
  if (!item || item->err || !n_good)
    result = "No";
  else if (n_good && !n_bad)
    result = "Yes";
  else
    result = "Some";
  writeout_li (ctx, result, "%s", _("Parsing data succeeded"));
  if (n_good || n_bad)
    {
      for (idx=0; idx < ctx->logused; idx++)
        {
          item = ctx->log + idx;
          if (item->event == AUDIT_NEW_SIG)
            break;
          else if (item->event == AUDIT_DATA_HASH_ALGO)
            writeout_rem (ctx, _("data hash algorithm: %s"),
                          gcry_md_algo_name (item->intvalue));
          else if (item->event == AUDIT_BAD_DATA_HASH_ALGO)
            writeout_rem (ctx, _("bad data hash algorithm: %s"),
                          item->string? item->string:"?");
        }
    }


  /* Loop over all signatures.  */
  loopitem = find_log_item (ctx, AUDIT_NEW_SIG, 0);
  assert (loopitem);
  do
    {
      signo = loopitem->have_intvalue? loopitem->intvalue : -1;

      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_SIG_STATUS, AUDIT_NEW_SIG);
      writeout_li (ctx, item? item->string:"?", _("Signature %d"), signo);
      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_SIG_NAME, AUDIT_NEW_SIG);
      if (item)
        writeout_rem (ctx, "%s", item->string);

      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG);
      if (item)
        writeout_rem (ctx, _("data hash algorithm: %s"),
                      gcry_md_algo_name (item->intvalue));
      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG);
      if (item)
        writeout_rem (ctx, _("attr hash algorithm: %s"),
                      gcry_md_algo_name (item->intvalue));

      enter_li (ctx);

      /* List the certificate chain.  */
      list_certchain (ctx, loopitem, AUDIT_NEW_SIG);

      /* Show the result of the chain validation.  */
      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_CHAIN_STATUS, AUDIT_NEW_SIG);
      if (item && item->have_err)
        {
          writeout_li (ctx, item->err? "No":"Yes",
                       _("Certificate chain valid"));
          if (item->err)
            writeout_rem (ctx, "%s", gpg_strerror (item->err));
        }

      /* Show whether the root certificate is fine.  */
      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_ROOT_TRUSTED, AUDIT_CHAIN_STATUS);
      if (item)
        {
          writeout_li (ctx, item->err?"No":"Yes", "%s",
                       _("Root certificate trustworthy"));
          if (item->err)
            {
              add_helptag (ctx, "gpgsm.root-cert-not-trusted");
              writeout_rem (ctx, "%s", gpg_strerror (item->err));
              list_cert (ctx, item->cert, 0);
            }
        }

      /* Show result of the CRL/OCSP check.  */
      item = find_next_log_item (ctx, loopitem,
                                 AUDIT_CRL_CHECK, AUDIT_NEW_SIG);
      if (item)
        {
          const char *ok;
          switch (gpg_err_code (item->err))
            {
            case 0:                    ok = "good"; break;
            case GPG_ERR_TRUE:         ok = "n/a";  break;
            case GPG_ERR_CERT_REVOKED: ok = "bad"; break;
            case GPG_ERR_NOT_ENABLED:  ok = "disabled"; break;
            case GPG_ERR_NO_CRL_KNOWN:
              ok = _("no CRL found for certificate");
              break;
            case GPG_ERR_CRL_TOO_OLD:
              ok = _("the available CRL is too old");
              break;
            default: ok = gpg_strerror (item->err); break;
            }

          writeout_li (ctx, ok, "%s", _("CRL/OCSP check of certificates"));
          if (item->err
              && gpg_err_code (item->err) != GPG_ERR_CERT_REVOKED
              && gpg_err_code (item->err) != GPG_ERR_NOT_ENABLED)
            add_helptag (ctx, "gpgsm.crl-problem");
        }

      leave_li (ctx);
    }
  while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0)));


 leave:
  /* Always list the certificates stored in the signature.  */
  item = NULL;
  count = 0;
  while ( ((item = find_next_log_item (ctx, item,
                                       AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
    count++;
  snprintf (numbuf, sizeof numbuf, "%d", count);
  writeout_li (ctx, numbuf, _("Included certificates"));
  item = NULL;
  while ( ((item = find_next_log_item (ctx, item,
                                       AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
    {
      char *name = get_cert_name (item->cert);
      writeout_rem (ctx, "%s", name);
      xfree (name);
      enter_li (ctx);
      for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
        {
          writeout_rem (ctx, "%s", name);
          xfree (name);
        }
      leave_li (ctx);
    }
  leave_li (ctx);
}




/* Print the formatted audit result.    THIS IS WORK IN PROGRESS.  */
void
audit_print_result (audit_ctx_t ctx, estream_t out, int use_html)
{
  int idx;
  size_t n;
  log_item_t item;
  helptag_t helptag;
  const char *s;
  int show_raw = 0;
  char *orig_codeset;

  if (!ctx)
    return;

  orig_codeset = i18n_switchto_utf8 ();

  /* We use an environment variable to include some debug info in the
     log.  */
  if ((s = getenv ("gnupg_debug_audit")))
    show_raw = 1;

  assert (!ctx->outstream);
  ctx->outstream = out;
  ctx->use_html = use_html;
  ctx->indentlevel = 0;
  clear_helptags (ctx);

  if (use_html)
    es_fputs ("<div class=\"" GNUPG_NAME "AuditLog\">\n", ctx->outstream);

  if (!ctx->log || !ctx->logused)
    {
      writeout_para (ctx, _("No audit log entries."));
      goto leave;
    }

  if (show_raw)
    {
      int maxlen;

      for (idx=0,maxlen=0; idx < DIM (eventstr_msgidx); idx++)
        {
          n = strlen (eventstr_msgstr + eventstr_msgidx[idx]);
          if (n > maxlen)
            maxlen = n;
        }

      if (use_html)
        es_fputs ("<pre>\n", out);
      for (idx=0; idx < ctx->logused; idx++)
        {
          es_fprintf (out, "log: %-*s",
                      maxlen, event2str (ctx->log[idx].event));
          if (ctx->log[idx].have_intvalue)
            es_fprintf (out, " i=%d", ctx->log[idx].intvalue);
          if (ctx->log[idx].string)
            {
              es_fputs (" s='", out);
              writeout (ctx, ctx->log[idx].string);
              es_fputs ("'", out);
            }
          if (ctx->log[idx].cert)
            es_fprintf (out, " has_cert");
          if (ctx->log[idx].have_err)
            {
              es_fputs (" err='", out);
              writeout (ctx, gpg_strerror (ctx->log[idx].err));
              es_fputs ("'", out);
            }
          es_fputs ("\n", out);
        }
      if (use_html)
        es_fputs ("</pre>\n", out);
      else
        es_fputs ("\n", out);
    }

  enter_li (ctx);
  switch (ctx->type)
    {
    case AUDIT_TYPE_NONE:
      writeout_li (ctx, NULL, _("Unknown operation"));
      break;
    case AUDIT_TYPE_ENCRYPT:
      proc_type_encrypt (ctx);
      break;
    case AUDIT_TYPE_SIGN:
      proc_type_sign (ctx);
      break;
    case AUDIT_TYPE_DECRYPT:
      proc_type_decrypt (ctx);
      break;
    case AUDIT_TYPE_VERIFY:
      proc_type_verify (ctx);
      break;
    }
  item = find_log_item (ctx, AUDIT_AGENT_READY, 0);
  if (item && item->have_err)
    {
      writeout_li (ctx, item->err? "No":"Yes", "%s", _("Gpg-Agent usable"));
      if (item->err)
        {
          writeout_rem (ctx, "%s", gpg_strerror (item->err));
          add_helptag (ctx, "gnupg.agent-problem");
        }
    }
  item = find_log_item (ctx, AUDIT_DIRMNGR_READY, 0);
  if (item && item->have_err)
    {
      writeout_li (ctx, item->err? "No":"Yes", "%s", _("Dirmngr usable"));
      if (item->err)
        {
          writeout_rem (ctx, "%s", gpg_strerror (item->err));
          add_helptag (ctx, "gnupg.dirmngr-problem");
        }
    }
  leave_li (ctx);


  /* Show the help from the collected help tags.  */
  if (ctx->helptags)
    {
      if (use_html)
        {
          es_fputs ("<hr/>\n", ctx->outstream);
          if (ctx->helptags->next)
            es_fputs ("<ul>\n", ctx->outstream);
        }
      else
        es_fputs ("\n\n", ctx->outstream);
    }
  for (helptag = ctx->helptags; helptag; helptag = helptag->next)
    {
      char *text;

      if (use_html && ctx->helptags->next)
        es_fputs ("<li>\n", ctx->outstream);

      text = gnupg_get_help_string (helptag->name, 0);
      if (text)
        {
          writeout_para (ctx, "%s", text);
          xfree (text);
        }
      else
        writeout_para (ctx, _("No help available for '%s'."), helptag->name);
      if (use_html && ctx->helptags->next)
        es_fputs ("</li>\n", ctx->outstream);
      if (helptag->next)
        es_fputs ("\n", ctx->outstream);
    }
  if (use_html && ctx->helptags && ctx->helptags->next)
    es_fputs ("</ul>\n", ctx->outstream);

 leave:
  if (use_html)
    es_fputs ("</div>\n", ctx->outstream);
  ctx->outstream = NULL;
  ctx->use_html = 0;
  clear_helptags (ctx);
  i18n_switchback (orig_codeset);
}
