2007-11-19 16:03:50 +00:00
|
|
|
|
/* audit.c - GnuPG's audit subsystem
|
2009-12-02 18:33:59 +00:00
|
|
|
|
* Copyright (C) 2007, 2009 Free Software Foundation, Inc.
|
2007-11-19 16:03:50 +00:00
|
|
|
|
*
|
|
|
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#include <stdlib.h>
|
2007-12-06 15:55:03 +00:00
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <assert.h>
|
2007-11-19 16:03:50 +00:00
|
|
|
|
|
|
|
|
|
#include "util.h"
|
2007-12-06 15:55:03 +00:00
|
|
|
|
#include "i18n.h"
|
2007-11-19 16:03:50 +00:00
|
|
|
|
#include "audit.h"
|
|
|
|
|
#include "audit-events.h"
|
|
|
|
|
|
2007-12-06 19:02:42 +00:00
|
|
|
|
/* A list to maintain a list of helptags. */
|
|
|
|
|
struct helptag_s
|
|
|
|
|
{
|
|
|
|
|
struct helptag_s *next;
|
|
|
|
|
const char *name;
|
|
|
|
|
};
|
|
|
|
|
typedef struct helptag_s *helptag_t;
|
|
|
|
|
|
|
|
|
|
|
2007-11-19 16:03:50 +00:00
|
|
|
|
/* One log entry. */
|
|
|
|
|
struct log_item_s
|
|
|
|
|
{
|
|
|
|
|
audit_event_t event; /* The event. */
|
|
|
|
|
gpg_error_t err; /* The logged error code. */
|
2009-12-02 18:33:59 +00:00
|
|
|
|
int intvalue; /* A logged integer value. */
|
2007-11-19 16:03:50 +00:00
|
|
|
|
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. */
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
estream_t outstream; /* The current output stream. */
|
|
|
|
|
int use_html; /* The output shall be HTML formatted. */
|
|
|
|
|
int indentlevel; /* Current level of indentation. */
|
2007-12-06 19:02:42 +00:00
|
|
|
|
helptag_t helptags; /* List of help keys. */
|
2007-11-19 16:03:50 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
|
2007-12-06 19:02:42 +00:00
|
|
|
|
static void writeout_para (audit_ctx_t ctx,
|
|
|
|
|
const char *format, ...) JNLIB_GCC_A_PRINTF(2,3);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
static void writeout_li (audit_ctx_t ctx, const char *oktext,
|
|
|
|
|
const char *format, ...) JNLIB_GCC_A_PRINTF(3,4);
|
|
|
|
|
static void writeout_rem (audit_ctx_t ctx,
|
|
|
|
|
const char *format, ...) JNLIB_GCC_A_PRINTF(2,3);
|
|
|
|
|
|
2007-11-19 16:03:50 +00:00
|
|
|
|
|
2007-12-06 19:02:42 +00:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
|
2007-11-19 16:03:50 +00:00
|
|
|
|
static const char *
|
|
|
|
|
event2str (audit_event_t event)
|
|
|
|
|
{
|
2008-10-20 13:53:23 +00:00
|
|
|
|
/* 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);
|
2007-11-19 16:03:50 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
2007-12-06 19:02:42 +00:00
|
|
|
|
clear_helptags (ctx);
|
2007-11-19 16:03:50 +00:00
|
|
|
|
xfree (ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Set the type for the audit operation. If CTX is NULL, this is a
|
|
|
|
|
dummy fucntion. */
|
|
|
|
|
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
|
2009-07-23 15:18:58 +00:00
|
|
|
|
does nothing. This version also adds the result of the operation
|
|
|
|
|
to the log. */
|
2007-11-19 16:03:50 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
/* 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 ("<", ctx->outstream);
|
|
|
|
|
else if (*text == '&')
|
|
|
|
|
es_fputs ("&", 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;
|
|
|
|
|
|
|
|
|
|
estream_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
|
2007-12-06 19:02:42 +00:00
|
|
|
|
writeout_para (audit_ctx_t ctx, const char *format, ...)
|
2007-12-06 15:55:03 +00:00
|
|
|
|
{
|
2007-12-06 19:02:42 +00:00
|
|
|
|
va_list arg_ptr;
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
if (ctx->use_html)
|
|
|
|
|
es_fputs ("<p>", ctx->outstream);
|
2007-12-06 19:02:42 +00:00
|
|
|
|
va_start (arg_ptr, format) ;
|
|
|
|
|
writeout_v (ctx, format, arg_ptr);
|
|
|
|
|
va_end (arg_ptr);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2009-01-19 16:15:30 +00:00
|
|
|
|
if (!strcmp (oktext, "Yes")
|
|
|
|
|
|| !strcmp (oktext, "good") )
|
2007-12-06 15:55:03 +00:00
|
|
|
|
color = "green";
|
2009-01-19 16:15:30 +00:00
|
|
|
|
else if (!strcmp (oktext, "No")
|
|
|
|
|
|| !strcmp (oktext, "bad") )
|
2007-12-06 15:55:03 +00:00
|
|
|
|
color = "red";
|
|
|
|
|
}
|
|
|
|
|
|
2008-11-20 21:54:47 +00:00
|
|
|
|
if (format && oktext)
|
|
|
|
|
{
|
2009-01-19 16:15:30 +00:00
|
|
|
|
const char *s = NULL;
|
|
|
|
|
|
2008-11-20 21:54:47 +00:00
|
|
|
|
if (!strcmp (oktext, "Yes"))
|
|
|
|
|
oktext = _("Yes");
|
|
|
|
|
else if (!strcmp (oktext, "No"))
|
|
|
|
|
oktext = _("No");
|
2009-01-19 16:15:30 +00:00
|
|
|
|
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");
|
2009-07-23 15:18:58 +00:00
|
|
|
|
else if (!strcmp (oktext, "disabled"))
|
|
|
|
|
oktext = _("|audit-log-result|Not enabled");
|
2009-01-19 16:15:30 +00:00
|
|
|
|
else if (!strcmp (oktext, "error"))
|
|
|
|
|
oktext = _("|audit-log-result|Error");
|
2009-12-02 18:33:59 +00:00
|
|
|
|
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");
|
2009-01-19 16:15:30 +00:00
|
|
|
|
else
|
|
|
|
|
s = "";
|
|
|
|
|
|
|
|
|
|
/* If we have set a prefix, skip it. */
|
|
|
|
|
if (!s && *oktext == '|' && (s=strchr (oktext+1,'|')))
|
|
|
|
|
oktext = s+1;
|
2008-11-20 21:54:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
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 (" ", 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 (" ", 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></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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2007-12-12 10:28:30 +00:00
|
|
|
|
/* 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
/* List the chain of certificates from STARTITEM up to STOPEVENT. The
|
|
|
|
|
certifcates 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);
|
2007-12-12 10:28:30 +00:00
|
|
|
|
writeout_li (ctx, startitem? "Yes":"No", _("Certificate chain available"));
|
2007-12-06 15:55:03 +00:00
|
|
|
|
if (!startitem)
|
2007-12-12 10:28:30 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
item = find_next_log_item (ctx, startitem,
|
|
|
|
|
AUDIT_CHAIN_ROOTCERT, AUDIT_CHAIN_END);
|
|
|
|
|
if (!item)
|
|
|
|
|
writeout_rem (ctx, "%s", _("root certificate missing"));
|
|
|
|
|
else
|
|
|
|
|
{
|
2007-12-12 10:28:30 +00:00
|
|
|
|
list_cert (ctx, item->cert, 0);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
}
|
|
|
|
|
item = startitem;
|
|
|
|
|
while ( ((item = find_next_log_item (ctx, item,
|
|
|
|
|
AUDIT_CHAIN_CERT, AUDIT_CHAIN_END))))
|
|
|
|
|
{
|
2007-12-12 10:28:30 +00:00
|
|
|
|
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)
|
2010-12-02 15:49:02 +00:00
|
|
|
|
writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
|
2007-12-12 10:28:30 +00:00
|
|
|
|
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)
|
2007-12-06 15:55:03 +00:00
|
|
|
|
{
|
2007-12-12 10:28:30 +00:00
|
|
|
|
name = get_cert_name (loopitem->cert);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
writeout_rem (ctx, "%s", name);
|
|
|
|
|
xfree (name);
|
2007-12-12 10:28:30 +00:00
|
|
|
|
enter_li (ctx);
|
|
|
|
|
for (idx=0; (name = get_cert_subject (loopitem->cert, idx)); idx++)
|
|
|
|
|
{
|
|
|
|
|
writeout_rem (ctx, "%s", name);
|
|
|
|
|
xfree (name);
|
|
|
|
|
}
|
|
|
|
|
leave_li (ctx);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
|
|
|
|
leave_li (ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Process a sign operation's log. */
|
|
|
|
|
static void
|
|
|
|
|
proc_type_sign (audit_ctx_t ctx)
|
|
|
|
|
{
|
2009-12-02 18:33:59 +00:00
|
|
|
|
log_item_t item, loopitem;
|
|
|
|
|
int signer, idx;
|
|
|
|
|
const char *result;
|
|
|
|
|
ksba_cert_t cert;
|
|
|
|
|
char *name;
|
|
|
|
|
int lastalgo;
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
2009-12-02 18:33:59 +00:00
|
|
|
|
item = find_log_item (ctx, AUDIT_SIGNING_DONE, 0);
|
2007-12-12 10:28:30 +00:00
|
|
|
|
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"));
|
2009-12-02 18:33:59 +00:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
2009-12-02 18:33:59 +00:00
|
|
|
|
/* 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
|
|
|
|
leave_li (ctx);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2007-12-12 10:28:30 +00:00
|
|
|
|
/* Process a decrypt operation's log. */
|
|
|
|
|
static void
|
|
|
|
|
proc_type_decrypt (audit_ctx_t ctx)
|
|
|
|
|
{
|
2009-12-02 18:33:59 +00:00
|
|
|
|
log_item_t loopitem, item;
|
|
|
|
|
int algo, recpno;
|
|
|
|
|
char *name;
|
|
|
|
|
char numbuf[35];
|
|
|
|
|
int idx;
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
2009-12-02 18:33:59 +00:00
|
|
|
|
item = find_log_item (ctx, AUDIT_DECRYPTION_RESULT, 0);
|
|
|
|
|
writeout_li (ctx, item && !item->err?"Yes":"No",
|
|
|
|
|
"%s", _("Data decryption succeeded"));
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
|
|
|
|
enter_li (ctx);
|
|
|
|
|
|
|
|
|
|
item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
|
|
|
|
|
writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
|
|
|
|
|
|
2009-12-02 18:33:59 +00:00
|
|
|
|
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)
|
2010-12-02 15:49:02 +00:00
|
|
|
|
writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
|
2009-12-02 18:33:59 +00:00
|
|
|
|
|
|
|
|
|
item = find_log_item (ctx, AUDIT_BAD_DATA_CIPHER_ALGO, 0);
|
|
|
|
|
if (item && item->string)
|
|
|
|
|
{
|
|
|
|
|
algo = gcry_cipher_map_name (item->string);
|
|
|
|
|
if (algo)
|
2010-12-02 15:49:02 +00:00
|
|
|
|
writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
|
2009-12-02 18:33:59 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
|
|
|
|
leave_li (ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Process a verification operation's log. */
|
2007-12-06 15:55:03 +00:00
|
|
|
|
static void
|
|
|
|
|
proc_type_verify (audit_ctx_t ctx)
|
|
|
|
|
{
|
|
|
|
|
log_item_t loopitem, item;
|
2009-12-02 18:33:59 +00:00
|
|
|
|
int signo, count, idx, n_good, n_bad;
|
2007-12-06 15:55:03 +00:00
|
|
|
|
char numbuf[35];
|
2009-12-02 18:33:59 +00:00
|
|
|
|
const char *result;
|
2007-12-06 15:55:03 +00:00
|
|
|
|
|
2007-12-12 10:28:30 +00:00
|
|
|
|
/* If there is at least one signature status we claim that the
|
2009-12-02 18:33:59 +00:00
|
|
|
|
verification succeeded. This does not mean that the data has
|
2007-12-12 10:28:30 +00:00
|
|
|
|
verified okay. */
|
|
|
|
|
item = find_log_item (ctx, AUDIT_SIG_STATUS, 0);
|
|
|
|
|
writeout_li (ctx, item?"Yes":"No", "%s", _("Data verification succeeded"));
|
2007-12-06 15:55:03 +00:00
|
|
|
|
enter_li (ctx);
|
2007-11-19 16:03:50 +00:00
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2009-12-02 18:33:59 +00:00
|
|
|
|
/* Print info about the used data hashing algorithms. */
|
|
|
|
|
for (idx=0, n_good=n_bad=0; idx < ctx->logused; idx++)
|
2007-12-06 15:55:03 +00:00
|
|
|
|
{
|
2009-12-02 18:33:59 +00:00
|
|
|
|
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++;
|
2007-12-06 15:55:03 +00:00
|
|
|
|
}
|
2009-12-02 18:33:59 +00:00
|
|
|
|
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:"?");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
|
|
|
|
|
/* 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);
|
2009-12-02 18:33:59 +00:00
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
2007-12-12 10:28:30 +00:00
|
|
|
|
writeout_li (ctx, item->err? "No":"Yes",
|
|
|
|
|
_("Certificate chain valid"));
|
2007-12-06 15:55:03 +00:00
|
|
|
|
if (item->err)
|
|
|
|
|
writeout_rem (ctx, "%s", gpg_strerror (item->err));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Show whether the root certificate is fine. */
|
2007-12-12 10:28:30 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2007-12-06 15:55:03 +00:00
|
|
|
|
|
|
|
|
|
/* Show result of the CRL/OCSP check. */
|
2009-07-23 15:18:58 +00:00
|
|
|
|
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_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");
|
|
|
|
|
}
|
2007-12-06 15:55:03 +00:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2007-11-19 16:03:50 +00:00
|
|
|
|
/* Print the formatted audit result. THIS IS WORK IN PROGRESS. */
|
|
|
|
|
void
|
2007-12-06 15:55:03 +00:00
|
|
|
|
audit_print_result (audit_ctx_t ctx, estream_t out, int use_html)
|
2007-11-19 16:03:50 +00:00
|
|
|
|
{
|
|
|
|
|
int idx;
|
|
|
|
|
size_t n;
|
2007-12-12 10:28:30 +00:00
|
|
|
|
log_item_t item;
|
2007-12-06 19:02:42 +00:00
|
|
|
|
helptag_t helptag;
|
2007-12-12 10:28:30 +00:00
|
|
|
|
const char *s;
|
|
|
|
|
int show_raw = 0;
|
2008-02-13 16:47:14 +00:00
|
|
|
|
char *orig_codeset;
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
2007-11-19 16:03:50 +00:00
|
|
|
|
if (!ctx)
|
2007-12-06 15:55:03 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2008-02-13 16:47:14 +00:00
|
|
|
|
orig_codeset = i18n_switchto_utf8 ();
|
|
|
|
|
|
2007-12-12 10:28:30 +00:00
|
|
|
|
/* We use an environment variable to include some debug info in the
|
|
|
|
|
log. */
|
|
|
|
|
if ((s = getenv ("gnupg_debug_audit")))
|
2009-12-02 18:33:59 +00:00
|
|
|
|
show_raw = 1;
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
2007-12-06 15:55:03 +00:00
|
|
|
|
assert (!ctx->outstream);
|
|
|
|
|
ctx->outstream = out;
|
|
|
|
|
ctx->use_html = use_html;
|
|
|
|
|
ctx->indentlevel = 0;
|
2007-12-06 19:02:42 +00:00
|
|
|
|
clear_helptags (ctx);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
|
|
|
|
|
if (use_html)
|
|
|
|
|
es_fputs ("<div class=\"GnuPGAuditLog\">\n", ctx->outstream);
|
|
|
|
|
|
2007-11-19 16:03:50 +00:00
|
|
|
|
if (!ctx->log || !ctx->logused)
|
|
|
|
|
{
|
2007-12-06 15:55:03 +00:00
|
|
|
|
writeout_para (ctx, _("No audit log entries."));
|
2007-11-26 11:00:39 +00:00
|
|
|
|
goto leave;
|
2007-11-19 16:03:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-12-12 10:28:30 +00:00
|
|
|
|
if (show_raw)
|
2007-11-19 16:03:50 +00:00
|
|
|
|
{
|
2007-12-12 10:28:30 +00:00
|
|
|
|
int maxlen;
|
2007-11-19 16:03:50 +00:00
|
|
|
|
|
2007-12-12 10:28:30 +00:00
|
|
|
|
for (idx=0,maxlen=0; idx < DIM (eventstr_msgidx); idx++)
|
2007-12-06 15:55:03 +00:00
|
|
|
|
{
|
2007-12-12 10:28:30 +00:00
|
|
|
|
n = strlen (eventstr_msgstr + eventstr_msgidx[idx]);
|
|
|
|
|
if (n > maxlen)
|
|
|
|
|
maxlen = n;
|
2007-12-06 15:55:03 +00:00
|
|
|
|
}
|
2007-12-12 10:28:30 +00:00
|
|
|
|
|
|
|
|
|
if (use_html)
|
|
|
|
|
es_fputs ("<pre>\n", out);
|
|
|
|
|
for (idx=0; idx < ctx->logused; idx++)
|
2007-12-06 15:55:03 +00:00
|
|
|
|
{
|
2007-12-12 10:28:30 +00:00
|
|
|
|
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);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
}
|
2007-12-12 10:28:30 +00:00
|
|
|
|
if (use_html)
|
|
|
|
|
es_fputs ("</pre>\n", out);
|
|
|
|
|
else
|
|
|
|
|
es_fputs ("\n", out);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-12-12 10:28:30 +00:00
|
|
|
|
enter_li (ctx);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
switch (ctx->type)
|
|
|
|
|
{
|
|
|
|
|
case AUDIT_TYPE_NONE:
|
2007-12-12 10:28:30 +00:00
|
|
|
|
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);
|
2007-12-06 15:55:03 +00:00
|
|
|
|
break;
|
|
|
|
|
case AUDIT_TYPE_VERIFY:
|
|
|
|
|
proc_type_verify (ctx);
|
|
|
|
|
break;
|
2007-11-19 16:03:50 +00:00
|
|
|
|
}
|
2007-12-12 10:28:30 +00:00
|
|
|
|
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);
|
2007-11-26 11:00:39 +00:00
|
|
|
|
|
2007-12-06 19:02:42 +00:00
|
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
2007-11-26 11:00:39 +00:00
|
|
|
|
leave:
|
2007-12-06 15:55:03 +00:00
|
|
|
|
if (use_html)
|
|
|
|
|
es_fputs ("</div>\n", ctx->outstream);
|
|
|
|
|
ctx->outstream = NULL;
|
|
|
|
|
ctx->use_html = 0;
|
2007-12-06 19:02:42 +00:00
|
|
|
|
clear_helptags (ctx);
|
2008-02-13 16:47:14 +00:00
|
|
|
|
i18n_switchback (orig_codeset);
|
2007-11-19 16:03:50 +00:00
|
|
|
|
}
|
|
|
|
|
|