Line data Source code
1 : /* audit.c - GnuPG's audit subsystem
2 : * Copyright (C) 2007, 2009 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 : /* A list to maintain a list of helptags. */
32 : struct helptag_s
33 : {
34 : struct helptag_s *next;
35 : const char *name;
36 : };
37 : typedef struct helptag_s *helptag_t;
38 :
39 :
40 : /* One log entry. */
41 : struct log_item_s
42 : {
43 : audit_event_t event; /* The event. */
44 : gpg_error_t err; /* The logged error code. */
45 : int intvalue; /* A logged integer value. */
46 : char *string; /* A malloced string or NULL. */
47 : ksba_cert_t cert; /* A certifciate or NULL. */
48 : int have_err:1;
49 : int have_intvalue:1;
50 : };
51 : typedef struct log_item_s *log_item_t;
52 :
53 :
54 :
55 : /* The main audit object. */
56 : struct audit_ctx_s
57 : {
58 : const char *failure; /* If set a description of the internal failure. */
59 : audit_type_t type;
60 :
61 : log_item_t log; /* The table with the log entries. */
62 : size_t logsize; /* The allocated size for LOG. */
63 : size_t logused; /* The used size of LOG. */
64 :
65 : estream_t outstream; /* The current output stream. */
66 : int use_html; /* The output shall be HTML formatted. */
67 : int indentlevel; /* Current level of indentation. */
68 : helptag_t helptags; /* List of help keys. */
69 : };
70 :
71 :
72 :
73 :
74 : static void writeout_para (audit_ctx_t ctx,
75 : const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
76 : static void writeout_li (audit_ctx_t ctx, const char *oktext,
77 : const char *format, ...) GPGRT_ATTR_PRINTF(3,4);
78 : static void writeout_rem (audit_ctx_t ctx,
79 : const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
80 :
81 :
82 : /* Add NAME to the list of help tags. NAME needs to be a const string
83 : an this function merly stores this pointer. */
84 : static void
85 0 : add_helptag (audit_ctx_t ctx, const char *name)
86 : {
87 : helptag_t item;
88 :
89 0 : for (item=ctx->helptags; item; item = item->next)
90 0 : if (!strcmp (item->name, name))
91 0 : return; /* Already in the list. */
92 0 : item = xtrycalloc (1, sizeof *item);
93 0 : if (!item)
94 0 : return; /* Don't care about memory problems. */
95 0 : item->name = name;
96 0 : item->next = ctx->helptags;
97 0 : ctx->helptags = item;
98 : }
99 :
100 :
101 : /* Remove all help tags from the context. */
102 : static void
103 0 : clear_helptags (audit_ctx_t ctx)
104 : {
105 0 : while (ctx->helptags)
106 : {
107 0 : helptag_t tmp = ctx->helptags->next;
108 0 : xfree (ctx->helptags);
109 0 : ctx->helptags = tmp;
110 : }
111 0 : }
112 :
113 :
114 :
115 : static const char *
116 0 : event2str (audit_event_t event)
117 : {
118 : /* We need the cast so that compiler does not complain about an
119 : always true comparison (>= 0) for an unsigned value. */
120 0 : int idx = eventstr_msgidxof ((int)event);
121 0 : if (idx == -1)
122 0 : return "Unknown event";
123 : else
124 0 : return eventstr_msgstr + eventstr_msgidx[idx];
125 : }
126 :
127 :
128 :
129 : /* Create a new audit context. In case of an error NULL is returned
130 : and errno set appropriately. */
131 : audit_ctx_t
132 0 : audit_new (void)
133 : {
134 : audit_ctx_t ctx;
135 :
136 0 : ctx = xtrycalloc (1, sizeof *ctx);
137 :
138 0 : return ctx;
139 : }
140 :
141 :
142 : /* Release an audit context. Passing NULL for CTX is allowed and does
143 : nothing. */
144 : void
145 0 : audit_release (audit_ctx_t ctx)
146 : {
147 : int idx;
148 0 : if (!ctx)
149 0 : return;
150 0 : if (ctx->log)
151 : {
152 0 : for (idx=0; idx < ctx->logused; idx++)
153 : {
154 0 : if (ctx->log[idx].string)
155 0 : xfree (ctx->log[idx].string);
156 0 : if (ctx->log[idx].cert)
157 0 : ksba_cert_release (ctx->log[idx].cert);
158 : }
159 0 : xfree (ctx->log);
160 : }
161 0 : clear_helptags (ctx);
162 0 : xfree (ctx);
163 : }
164 :
165 :
166 : /* Set the type for the audit operation. If CTX is NULL, this is a
167 : dummy function. */
168 : void
169 0 : audit_set_type (audit_ctx_t ctx, audit_type_t type)
170 : {
171 0 : if (!ctx || ctx->failure)
172 0 : return; /* Audit not enabled or an internal error has occurred. */
173 :
174 0 : if (ctx->type && ctx->type != type)
175 : {
176 0 : ctx->failure = "conflict in type initialization";
177 0 : return;
178 : }
179 0 : ctx->type = type;
180 : }
181 :
182 :
183 : /* Create a new log item and put it into the table. Return that log
184 : item on success; return NULL on memory failure and mark that in
185 : CTX. */
186 : static log_item_t
187 0 : create_log_item (audit_ctx_t ctx)
188 : {
189 : log_item_t item, table;
190 : size_t size;
191 :
192 0 : if (!ctx->log)
193 : {
194 0 : size = 10;
195 0 : table = xtrymalloc (size * sizeof *table);
196 0 : if (!table)
197 : {
198 0 : ctx->failure = "Out of memory in create_log_item";
199 0 : return NULL;
200 : }
201 0 : ctx->log = table;
202 0 : ctx->logsize = size;
203 0 : item = ctx->log + 0;
204 0 : ctx->logused = 1;
205 : }
206 0 : else if (ctx->logused >= ctx->logsize)
207 : {
208 0 : size = ctx->logsize + 10;
209 0 : table = xtryrealloc (ctx->log, size * sizeof *table);
210 0 : if (!table)
211 : {
212 0 : ctx->failure = "Out of memory while reallocating in create_log_item";
213 0 : return NULL;
214 : }
215 0 : ctx->log = table;
216 0 : ctx->logsize = size;
217 0 : item = ctx->log + ctx->logused++;
218 : }
219 : else
220 0 : item = ctx->log + ctx->logused++;
221 :
222 0 : item->event = AUDIT_NULL_EVENT;
223 0 : item->err = 0;
224 0 : item->have_err = 0;
225 0 : item->intvalue = 0;
226 0 : item->have_intvalue = 0;
227 0 : item->string = NULL;
228 0 : item->cert = NULL;
229 :
230 0 : return item;
231 :
232 : }
233 :
234 : /* Add a new event to the audit log. If CTX is NULL, this function
235 : does nothing. */
236 : void
237 0 : audit_log (audit_ctx_t ctx, audit_event_t event)
238 : {
239 : log_item_t item;
240 :
241 0 : if (!ctx || ctx->failure)
242 0 : return; /* Audit not enabled or an internal error has occurred. */
243 0 : if (!event)
244 : {
245 0 : ctx->failure = "Invalid event passed to audit_log";
246 0 : return;
247 : }
248 0 : if (!(item = create_log_item (ctx)))
249 0 : return;
250 0 : item->event = event;
251 : }
252 :
253 : /* Add a new event to the audit log. If CTX is NULL, this function
254 : does nothing. This version also adds the result of the operation
255 : to the log. */
256 : void
257 0 : audit_log_ok (audit_ctx_t ctx, audit_event_t event, gpg_error_t err)
258 : {
259 : log_item_t item;
260 :
261 0 : if (!ctx || ctx->failure)
262 0 : return; /* Audit not enabled or an internal error has occurred. */
263 0 : if (!event)
264 : {
265 0 : ctx->failure = "Invalid event passed to audit_log_ok";
266 0 : return;
267 : }
268 0 : if (!(item = create_log_item (ctx)))
269 0 : return;
270 0 : item->event = event;
271 0 : item->err = err;
272 0 : item->have_err = 1;
273 : }
274 :
275 :
276 : /* Add a new event to the audit log. If CTX is NULL, this function
277 : does nothing. This version also add the integer VALUE to the log. */
278 : void
279 0 : audit_log_i (audit_ctx_t ctx, audit_event_t event, int value)
280 : {
281 : log_item_t item;
282 :
283 0 : if (!ctx || ctx->failure)
284 0 : return; /* Audit not enabled or an internal error has occurred. */
285 0 : if (!event)
286 : {
287 0 : ctx->failure = "Invalid event passed to audit_log_i";
288 0 : return;
289 : }
290 0 : if (!(item = create_log_item (ctx)))
291 0 : return;
292 0 : item->event = event;
293 0 : item->intvalue = value;
294 0 : item->have_intvalue = 1;
295 : }
296 :
297 :
298 : /* Add a new event to the audit log. If CTX is NULL, this function
299 : does nothing. This version also add the integer VALUE to the log. */
300 : void
301 0 : audit_log_s (audit_ctx_t ctx, audit_event_t event, const char *value)
302 : {
303 : log_item_t item;
304 : char *tmp;
305 :
306 0 : if (!ctx || ctx->failure)
307 0 : return; /* Audit not enabled or an internal error has occurred. */
308 0 : if (!event)
309 : {
310 0 : ctx->failure = "Invalid event passed to audit_log_s";
311 0 : return;
312 : }
313 0 : tmp = xtrystrdup (value? value : "");
314 0 : if (!tmp)
315 : {
316 0 : ctx->failure = "Out of memory in audit_event";
317 0 : return;
318 : }
319 0 : if (!(item = create_log_item (ctx)))
320 : {
321 0 : xfree (tmp);
322 0 : return;
323 : }
324 0 : item->event = event;
325 0 : item->string = tmp;
326 : }
327 :
328 : /* Add a new event to the audit log. If CTX is NULL, this function
329 : does nothing. This version also adds the certificate CERT and the
330 : result of an operation to the log. */
331 : void
332 0 : audit_log_cert (audit_ctx_t ctx, audit_event_t event,
333 : ksba_cert_t cert, gpg_error_t err)
334 : {
335 : log_item_t item;
336 :
337 0 : if (!ctx || ctx->failure)
338 0 : return; /* Audit not enabled or an internal error has occurred. */
339 0 : if (!event)
340 : {
341 0 : ctx->failure = "Invalid event passed to audit_log_cert";
342 0 : return;
343 : }
344 0 : if (!(item = create_log_item (ctx)))
345 0 : return;
346 0 : item->event = event;
347 0 : item->err = err;
348 0 : item->have_err = 1;
349 0 : if (cert)
350 : {
351 0 : ksba_cert_ref (cert);
352 0 : item->cert = cert;
353 : }
354 : }
355 :
356 :
357 : /* Write TEXT to the outstream. */
358 : static void
359 0 : writeout (audit_ctx_t ctx, const char *text)
360 : {
361 0 : if (ctx->use_html)
362 : {
363 0 : for (; *text; text++)
364 : {
365 0 : if (*text == '<')
366 0 : es_fputs ("<", ctx->outstream);
367 0 : else if (*text == '&')
368 0 : es_fputs ("&", ctx->outstream);
369 : else
370 0 : es_putc (*text, ctx->outstream);
371 : }
372 : }
373 : else
374 0 : es_fputs (text, ctx->outstream);
375 0 : }
376 :
377 :
378 : /* Write TEXT to the outstream using a variable argument list. */
379 : static void
380 0 : writeout_v (audit_ctx_t ctx, const char *format, va_list arg_ptr)
381 : {
382 : char *buf;
383 :
384 0 : gpgrt_vasprintf (&buf, format, arg_ptr);
385 0 : if (buf)
386 : {
387 0 : writeout (ctx, buf);
388 0 : xfree (buf);
389 : }
390 : else
391 0 : writeout (ctx, "[!!Out of core!!]");
392 0 : }
393 :
394 :
395 : /* Write TEXT as a paragraph. */
396 : static void
397 0 : writeout_para (audit_ctx_t ctx, const char *format, ...)
398 : {
399 : va_list arg_ptr;
400 :
401 0 : if (ctx->use_html)
402 0 : es_fputs ("<p>", ctx->outstream);
403 0 : va_start (arg_ptr, format) ;
404 0 : writeout_v (ctx, format, arg_ptr);
405 0 : va_end (arg_ptr);
406 0 : if (ctx->use_html)
407 0 : es_fputs ("</p>\n", ctx->outstream);
408 : else
409 0 : es_fputc ('\n', ctx->outstream);
410 0 : }
411 :
412 :
413 : static void
414 0 : enter_li (audit_ctx_t ctx)
415 : {
416 0 : if (ctx->use_html)
417 : {
418 0 : if (!ctx->indentlevel)
419 : {
420 0 : es_fputs ("<table border=\"0\">\n"
421 : " <colgroup>\n"
422 : " <col width=\"80%\" />\n"
423 : " <col width=\"20%\" />\n"
424 : " </colgroup>\n",
425 : ctx->outstream);
426 : }
427 : }
428 0 : ctx->indentlevel++;
429 0 : }
430 :
431 :
432 : static void
433 0 : leave_li (audit_ctx_t ctx)
434 : {
435 0 : ctx->indentlevel--;
436 0 : if (ctx->use_html)
437 : {
438 0 : if (!ctx->indentlevel)
439 0 : es_fputs ("</table>\n", ctx->outstream);
440 : }
441 0 : }
442 :
443 :
444 : /* Write TEXT as a list element. If OKTEXT is not NULL, append it to
445 : the last line. */
446 : static void
447 0 : writeout_li (audit_ctx_t ctx, const char *oktext, const char *format, ...)
448 : {
449 : va_list arg_ptr;
450 0 : const char *color = NULL;
451 :
452 0 : if (ctx->use_html && format && oktext)
453 : {
454 0 : if (!strcmp (oktext, "Yes")
455 0 : || !strcmp (oktext, "good") )
456 0 : color = "green";
457 0 : else if (!strcmp (oktext, "No")
458 0 : || !strcmp (oktext, "bad") )
459 0 : color = "red";
460 : }
461 :
462 0 : if (format && oktext)
463 : {
464 0 : const char *s = NULL;
465 :
466 0 : if (!strcmp (oktext, "Yes"))
467 0 : oktext = _("Yes");
468 0 : else if (!strcmp (oktext, "No"))
469 0 : oktext = _("No");
470 0 : else if (!strcmp (oktext, "good"))
471 : {
472 : /* TRANSLATORS: Copy the prefix between the vertical bars
473 : verbatim. It will not be printed. */
474 0 : oktext = _("|audit-log-result|Good");
475 : }
476 0 : else if (!strcmp (oktext, "bad"))
477 0 : oktext = _("|audit-log-result|Bad");
478 0 : else if (!strcmp (oktext, "unsupported"))
479 0 : oktext = _("|audit-log-result|Not supported");
480 0 : else if (!strcmp (oktext, "no-cert"))
481 0 : oktext = _("|audit-log-result|No certificate");
482 0 : else if (!strcmp (oktext, "disabled"))
483 0 : oktext = _("|audit-log-result|Not enabled");
484 0 : else if (!strcmp (oktext, "error"))
485 0 : oktext = _("|audit-log-result|Error");
486 0 : else if (!strcmp (oktext, "not-used"))
487 0 : oktext = _("|audit-log-result|Not used");
488 0 : else if (!strcmp (oktext, "okay"))
489 0 : oktext = _("|audit-log-result|Okay");
490 0 : else if (!strcmp (oktext, "skipped"))
491 0 : oktext = _("|audit-log-result|Skipped");
492 0 : else if (!strcmp (oktext, "some"))
493 0 : oktext = _("|audit-log-result|Some");
494 : else
495 0 : s = "";
496 :
497 : /* If we have set a prefix, skip it. */
498 0 : if (!s && *oktext == '|' && (s=strchr (oktext+1,'|')))
499 0 : oktext = s+1;
500 : }
501 :
502 0 : if (ctx->use_html)
503 : {
504 : int i;
505 :
506 0 : es_fputs (" <tr><td><table><tr><td>", ctx->outstream);
507 0 : if (color)
508 0 : es_fprintf (ctx->outstream, "<font color=\"%s\">*</font>", color);
509 : else
510 0 : es_fputs ("*", ctx->outstream);
511 0 : for (i=1; i < ctx->indentlevel; i++)
512 0 : es_fputs (" ", ctx->outstream);
513 0 : es_fputs ("</td><td>", ctx->outstream);
514 : }
515 : else
516 0 : es_fprintf (ctx->outstream, "* %*s", (ctx->indentlevel-1)*2, "");
517 0 : if (format)
518 : {
519 0 : va_start (arg_ptr, format) ;
520 0 : writeout_v (ctx, format, arg_ptr);
521 0 : va_end (arg_ptr);
522 : }
523 0 : if (ctx->use_html)
524 0 : es_fputs ("</td></tr></table>", ctx->outstream);
525 0 : if (format && oktext)
526 : {
527 0 : if (ctx->use_html)
528 : {
529 0 : es_fputs ("</td><td>", ctx->outstream);
530 0 : if (color)
531 0 : es_fprintf (ctx->outstream, "<font color=\"%s\">", color);
532 : }
533 : else
534 0 : writeout (ctx, ": ");
535 0 : writeout (ctx, oktext);
536 0 : if (color)
537 0 : es_fputs ("</font>", ctx->outstream);
538 : }
539 :
540 0 : if (ctx->use_html)
541 0 : es_fputs ("</td></tr>\n", ctx->outstream);
542 : else
543 0 : es_fputc ('\n', ctx->outstream);
544 0 : }
545 :
546 :
547 : /* Write a remark line. */
548 : static void
549 0 : writeout_rem (audit_ctx_t ctx, const char *format, ...)
550 : {
551 : va_list arg_ptr;
552 :
553 0 : if (ctx->use_html)
554 : {
555 : int i;
556 :
557 0 : es_fputs (" <tr><td><table><tr><td>*", ctx->outstream);
558 0 : for (i=1; i < ctx->indentlevel; i++)
559 0 : es_fputs (" ", ctx->outstream);
560 0 : es_fputs (" </td><td> (", ctx->outstream);
561 :
562 : }
563 : else
564 0 : es_fprintf (ctx->outstream, "* %*s (", (ctx->indentlevel-1)*2, "");
565 0 : if (format)
566 : {
567 0 : va_start (arg_ptr, format) ;
568 0 : writeout_v (ctx, format, arg_ptr);
569 0 : va_end (arg_ptr);
570 : }
571 0 : if (ctx->use_html)
572 0 : es_fputs (")</td></tr></table></td></tr>\n", ctx->outstream);
573 : else
574 0 : es_fputs (")\n", ctx->outstream);
575 0 : }
576 :
577 :
578 : /* Return the first log item for EVENT. If STOPEVENT is not 0 never
579 : look behind that event in the log. If STARTITEM is not NULL start
580 : search _after_that item. */
581 : static log_item_t
582 0 : find_next_log_item (audit_ctx_t ctx, log_item_t startitem,
583 : audit_event_t event, audit_event_t stopevent)
584 : {
585 : int idx;
586 :
587 0 : for (idx=0; idx < ctx->logused; idx++)
588 : {
589 0 : if (startitem)
590 : {
591 0 : if (ctx->log + idx == startitem)
592 0 : startitem = NULL;
593 : }
594 0 : else if (stopevent && ctx->log[idx].event == stopevent)
595 : break;
596 0 : else if (ctx->log[idx].event == event)
597 0 : return ctx->log + idx;
598 : }
599 0 : return NULL;
600 : }
601 :
602 :
603 : static log_item_t
604 0 : find_log_item (audit_ctx_t ctx, audit_event_t event, audit_event_t stopevent)
605 : {
606 0 : return find_next_log_item (ctx, NULL, event, stopevent);
607 : }
608 :
609 :
610 : /* Helper to a format a serial number. */
611 : static char *
612 0 : format_serial (ksba_const_sexp_t sn)
613 : {
614 0 : const char *p = (const char *)sn;
615 : unsigned long n;
616 : char *endp;
617 :
618 0 : if (!p)
619 0 : return NULL;
620 0 : if (*p != '(')
621 0 : BUG (); /* Not a valid S-expression. */
622 0 : n = strtoul (p+1, &endp, 10);
623 0 : p = endp;
624 0 : if (*p != ':')
625 0 : BUG (); /* Not a valid S-expression. */
626 0 : return bin2hex (p+1, n, NULL);
627 : }
628 :
629 :
630 : /* Return a malloced string with the serial number and the issuer DN
631 : of the certificate. */
632 : static char *
633 0 : get_cert_name (ksba_cert_t cert)
634 : {
635 : char *result;
636 : ksba_sexp_t sn;
637 : char *issuer, *p;
638 :
639 0 : if (!cert)
640 0 : return xtrystrdup ("[no certificate]");
641 :
642 0 : issuer = ksba_cert_get_issuer (cert, 0);
643 0 : sn = ksba_cert_get_serial (cert);
644 0 : if (issuer && sn)
645 : {
646 0 : p = format_serial (sn);
647 0 : if (!p)
648 0 : result = xtrystrdup ("[invalid S/N]");
649 : else
650 : {
651 0 : result = xtrymalloc (strlen (p) + strlen (issuer) + 2 + 1);
652 0 : if (result)
653 : {
654 0 : *result = '#';
655 0 : strcpy (stpcpy (stpcpy (result+1, p),"/"), issuer);
656 : }
657 0 : xfree (p);
658 : }
659 : }
660 : else
661 0 : result = xtrystrdup ("[missing S/N or issuer]");
662 0 : ksba_free (sn);
663 0 : xfree (issuer);
664 0 : return result;
665 : }
666 :
667 : /* Return a malloced string with the serial number and the issuer DN
668 : of the certificate. */
669 : static char *
670 0 : get_cert_subject (ksba_cert_t cert, int idx)
671 : {
672 : char *result;
673 : char *subject;
674 :
675 0 : if (!cert)
676 0 : return xtrystrdup ("[no certificate]");
677 :
678 0 : subject = ksba_cert_get_subject (cert, idx);
679 0 : if (subject)
680 : {
681 0 : result = xtrymalloc (strlen (subject) + 1 + 1);
682 0 : if (result)
683 : {
684 0 : *result = '/';
685 0 : strcpy (result+1, subject);
686 : }
687 : }
688 : else
689 0 : result = NULL;
690 0 : xfree (subject);
691 0 : return result;
692 : }
693 :
694 :
695 : /* List the given certificiate. If CERT is NULL, this is a NOP. */
696 : static void
697 0 : list_cert (audit_ctx_t ctx, ksba_cert_t cert, int with_subj)
698 : {
699 : char *name;
700 : int idx;
701 :
702 0 : name = get_cert_name (cert);
703 0 : writeout_rem (ctx, "%s", name);
704 0 : xfree (name);
705 0 : if (with_subj)
706 : {
707 0 : enter_li (ctx);
708 0 : for (idx=0; (name = get_cert_subject (cert, idx)); idx++)
709 : {
710 0 : writeout_rem (ctx, "%s", name);
711 0 : xfree (name);
712 : }
713 0 : leave_li (ctx);
714 : }
715 0 : }
716 :
717 :
718 : /* List the chain of certificates from STARTITEM up to STOPEVENT. The
719 : certifcates are written out as comments. */
720 : static void
721 0 : list_certchain (audit_ctx_t ctx, log_item_t startitem, audit_event_t stopevent)
722 : {
723 : log_item_t item;
724 :
725 0 : startitem = find_next_log_item (ctx, startitem, AUDIT_CHAIN_BEGIN,stopevent);
726 0 : writeout_li (ctx, startitem? "Yes":"No", _("Certificate chain available"));
727 0 : if (!startitem)
728 0 : return;
729 :
730 0 : item = find_next_log_item (ctx, startitem,
731 : AUDIT_CHAIN_ROOTCERT, AUDIT_CHAIN_END);
732 0 : if (!item)
733 0 : writeout_rem (ctx, "%s", _("root certificate missing"));
734 : else
735 : {
736 0 : list_cert (ctx, item->cert, 0);
737 : }
738 0 : item = startitem;
739 0 : while ( ((item = find_next_log_item (ctx, item,
740 : AUDIT_CHAIN_CERT, AUDIT_CHAIN_END))))
741 : {
742 0 : list_cert (ctx, item->cert, 1);
743 : }
744 : }
745 :
746 :
747 :
748 : /* Process an encrypt operation's log. */
749 : static void
750 0 : proc_type_encrypt (audit_ctx_t ctx)
751 : {
752 : log_item_t loopitem, item;
753 : int recp_no, idx;
754 : char numbuf[35];
755 : int algo;
756 : char *name;
757 :
758 0 : item = find_log_item (ctx, AUDIT_ENCRYPTION_DONE, 0);
759 0 : writeout_li (ctx, item?"Yes":"No", "%s", _("Data encryption succeeded"));
760 :
761 0 : enter_li (ctx);
762 :
763 0 : item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
764 0 : writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
765 :
766 0 : item = find_log_item (ctx, AUDIT_SESSION_KEY, 0);
767 0 : writeout_li (ctx, item? "Yes":"No", "%s", _("Session key created"));
768 0 : if (item)
769 : {
770 0 : algo = gcry_cipher_map_name (item->string);
771 0 : if (algo)
772 0 : writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
773 0 : else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2"))
774 0 : writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2");
775 0 : else if (item->string)
776 0 : writeout_rem (ctx, _("unsupported algorithm: %s"), item->string);
777 : else
778 0 : writeout_rem (ctx, _("seems to be not encrypted"));
779 : }
780 :
781 0 : item = find_log_item (ctx, AUDIT_GOT_RECIPIENTS, 0);
782 0 : snprintf (numbuf, sizeof numbuf, "%d",
783 0 : item && item->have_intvalue? item->intvalue : 0);
784 0 : writeout_li (ctx, numbuf, "%s", _("Number of recipients"));
785 :
786 : /* Loop over all recipients. */
787 0 : loopitem = NULL;
788 0 : recp_no = 0;
789 0 : while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_ENCRYPTED_TO, 0)))
790 : {
791 0 : recp_no++;
792 0 : writeout_li (ctx, NULL, _("Recipient %d"), recp_no);
793 0 : if (loopitem->cert)
794 : {
795 0 : name = get_cert_name (loopitem->cert);
796 0 : writeout_rem (ctx, "%s", name);
797 0 : xfree (name);
798 0 : enter_li (ctx);
799 0 : for (idx=0; (name = get_cert_subject (loopitem->cert, idx)); idx++)
800 : {
801 0 : writeout_rem (ctx, "%s", name);
802 0 : xfree (name);
803 : }
804 0 : leave_li (ctx);
805 : }
806 : }
807 :
808 0 : leave_li (ctx);
809 0 : }
810 :
811 :
812 :
813 : /* Process a sign operation's log. */
814 : static void
815 0 : proc_type_sign (audit_ctx_t ctx)
816 : {
817 : log_item_t item, loopitem;
818 : int signer, idx;
819 : const char *result;
820 : ksba_cert_t cert;
821 : char *name;
822 : int lastalgo;
823 :
824 0 : item = find_log_item (ctx, AUDIT_SIGNING_DONE, 0);
825 0 : writeout_li (ctx, item?"Yes":"No", "%s", _("Data signing succeeded"));
826 :
827 0 : enter_li (ctx);
828 :
829 0 : item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
830 0 : writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
831 : /* Write remarks with the data hash algorithms. We use a very
832 : simple scheme to avoid some duplicates. */
833 0 : loopitem = NULL;
834 0 : lastalgo = 0;
835 0 : while ((loopitem = find_next_log_item
836 : (ctx, loopitem, AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG)))
837 : {
838 0 : if (loopitem->intvalue && loopitem->intvalue != lastalgo)
839 0 : writeout_rem (ctx, _("data hash algorithm: %s"),
840 : gcry_md_algo_name (loopitem->intvalue));
841 0 : lastalgo = loopitem->intvalue;
842 : }
843 :
844 : /* Loop over all signer. */
845 0 : loopitem = NULL;
846 0 : signer = 0;
847 0 : while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0)))
848 : {
849 0 : signer++;
850 :
851 0 : item = find_next_log_item (ctx, loopitem, AUDIT_SIGNED_BY, AUDIT_NEW_SIG);
852 0 : if (!item)
853 0 : result = "error";
854 0 : else if (!item->err)
855 0 : result = "okay";
856 0 : else if (gpg_err_code (item->err) == GPG_ERR_CANCELED)
857 0 : result = "skipped";
858 : else
859 0 : result = gpg_strerror (item->err);
860 0 : cert = item? item->cert : NULL;
861 :
862 0 : writeout_li (ctx, result, _("Signer %d"), signer);
863 0 : item = find_next_log_item (ctx, loopitem,
864 : AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG);
865 0 : if (item)
866 0 : writeout_rem (ctx, _("attr hash algorithm: %s"),
867 : gcry_md_algo_name (item->intvalue));
868 :
869 0 : if (cert)
870 : {
871 0 : name = get_cert_name (cert);
872 0 : writeout_rem (ctx, "%s", name);
873 0 : xfree (name);
874 0 : enter_li (ctx);
875 0 : for (idx=0; (name = get_cert_subject (cert, idx)); idx++)
876 : {
877 0 : writeout_rem (ctx, "%s", name);
878 0 : xfree (name);
879 : }
880 0 : leave_li (ctx);
881 : }
882 : }
883 :
884 0 : leave_li (ctx);
885 0 : }
886 :
887 :
888 :
889 : /* Process a decrypt operation's log. */
890 : static void
891 0 : proc_type_decrypt (audit_ctx_t ctx)
892 : {
893 : log_item_t loopitem, item;
894 : int algo, recpno;
895 : char *name;
896 : char numbuf[35];
897 : int idx;
898 :
899 0 : item = find_log_item (ctx, AUDIT_DECRYPTION_RESULT, 0);
900 0 : writeout_li (ctx, item && !item->err?"Yes":"No",
901 : "%s", _("Data decryption succeeded"));
902 :
903 0 : enter_li (ctx);
904 :
905 0 : item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
906 0 : writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
907 :
908 0 : item = find_log_item (ctx, AUDIT_DATA_CIPHER_ALGO, 0);
909 0 : algo = item? item->intvalue : 0;
910 0 : writeout_li (ctx, algo?"Yes":"No", "%s", _("Encryption algorithm supported"));
911 0 : if (algo)
912 0 : writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
913 :
914 0 : item = find_log_item (ctx, AUDIT_BAD_DATA_CIPHER_ALGO, 0);
915 0 : if (item && item->string)
916 : {
917 0 : algo = gcry_cipher_map_name (item->string);
918 0 : if (algo)
919 0 : writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
920 0 : else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2"))
921 0 : writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2");
922 0 : else if (item->string)
923 0 : writeout_rem (ctx, _("unsupported algorithm: %s"), item->string);
924 : else
925 0 : writeout_rem (ctx, _("seems to be not encrypted"));
926 : }
927 :
928 :
929 0 : for (recpno = 0, item = NULL;
930 0 : (item = find_next_log_item (ctx, item, AUDIT_NEW_RECP, 0)); recpno++)
931 : ;
932 0 : snprintf (numbuf, sizeof numbuf, "%d", recpno);
933 0 : writeout_li (ctx, numbuf, "%s", _("Number of recipients"));
934 :
935 : /* Loop over all recipients. */
936 0 : loopitem = NULL;
937 0 : while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_RECP, 0)))
938 : {
939 : const char *result;
940 :
941 0 : recpno = loopitem->have_intvalue? loopitem->intvalue : -1;
942 :
943 0 : item = find_next_log_item (ctx, loopitem,
944 : AUDIT_RECP_RESULT, AUDIT_NEW_RECP);
945 0 : if (!item)
946 0 : result = "not-used";
947 0 : else if (!item->err)
948 0 : result = "okay";
949 0 : else if (gpg_err_code (item->err) == GPG_ERR_CANCELED)
950 0 : result = "skipped";
951 : else
952 0 : result = gpg_strerror (item->err);
953 :
954 0 : item = find_next_log_item (ctx, loopitem,
955 : AUDIT_RECP_NAME, AUDIT_NEW_RECP);
956 0 : writeout_li (ctx, result, _("Recipient %d"), recpno);
957 0 : if (item && item->string)
958 0 : writeout_rem (ctx, "%s", item->string);
959 :
960 : /* If we have a certificate write out more infos. */
961 0 : item = find_next_log_item (ctx, loopitem,
962 : AUDIT_SAVE_CERT, AUDIT_NEW_RECP);
963 0 : if (item && item->cert)
964 : {
965 0 : enter_li (ctx);
966 0 : for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
967 : {
968 0 : writeout_rem (ctx, "%s", name);
969 0 : xfree (name);
970 : }
971 0 : leave_li (ctx);
972 : }
973 : }
974 :
975 0 : leave_li (ctx);
976 0 : }
977 :
978 :
979 :
980 : /* Process a verification operation's log. */
981 : static void
982 0 : proc_type_verify (audit_ctx_t ctx)
983 : {
984 : log_item_t loopitem, item;
985 : int signo, count, idx, n_good, n_bad;
986 : char numbuf[35];
987 : const char *result;
988 :
989 : /* If there is at least one signature status we claim that the
990 : verification succeeded. This does not mean that the data has
991 : verified okay. */
992 0 : item = find_log_item (ctx, AUDIT_SIG_STATUS, 0);
993 0 : writeout_li (ctx, item?"Yes":"No", "%s", _("Data verification succeeded"));
994 0 : enter_li (ctx);
995 :
996 0 : item = find_log_item (ctx, AUDIT_GOT_DATA, AUDIT_NEW_SIG);
997 0 : writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
998 0 : if (!item)
999 0 : goto leave;
1000 :
1001 0 : item = find_log_item (ctx, AUDIT_NEW_SIG, 0);
1002 0 : writeout_li (ctx, item? "Yes":"No", "%s", _("Signature available"));
1003 0 : if (!item)
1004 0 : goto leave;
1005 :
1006 : /* Print info about the used data hashing algorithms. */
1007 0 : for (idx=0, n_good=n_bad=0; idx < ctx->logused; idx++)
1008 : {
1009 0 : item = ctx->log + idx;
1010 0 : if (item->event == AUDIT_NEW_SIG)
1011 0 : break;
1012 0 : else if (item->event == AUDIT_DATA_HASH_ALGO)
1013 0 : n_good++;
1014 0 : else if (item->event == AUDIT_BAD_DATA_HASH_ALGO)
1015 0 : n_bad++;
1016 : }
1017 0 : item = find_log_item (ctx, AUDIT_DATA_HASHING, AUDIT_NEW_SIG);
1018 0 : if (!item || item->err || !n_good)
1019 0 : result = "No";
1020 0 : else if (n_good && !n_bad)
1021 0 : result = "Yes";
1022 : else
1023 0 : result = "Some";
1024 0 : writeout_li (ctx, result, "%s", _("Parsing data succeeded"));
1025 0 : if (n_good || n_bad)
1026 : {
1027 0 : for (idx=0; idx < ctx->logused; idx++)
1028 : {
1029 0 : item = ctx->log + idx;
1030 0 : if (item->event == AUDIT_NEW_SIG)
1031 0 : break;
1032 0 : else if (item->event == AUDIT_DATA_HASH_ALGO)
1033 0 : writeout_rem (ctx, _("data hash algorithm: %s"),
1034 : gcry_md_algo_name (item->intvalue));
1035 0 : else if (item->event == AUDIT_BAD_DATA_HASH_ALGO)
1036 0 : writeout_rem (ctx, _("bad data hash algorithm: %s"),
1037 0 : item->string? item->string:"?");
1038 : }
1039 : }
1040 :
1041 :
1042 : /* Loop over all signatures. */
1043 0 : loopitem = find_log_item (ctx, AUDIT_NEW_SIG, 0);
1044 0 : assert (loopitem);
1045 : do
1046 : {
1047 0 : signo = loopitem->have_intvalue? loopitem->intvalue : -1;
1048 :
1049 0 : item = find_next_log_item (ctx, loopitem,
1050 : AUDIT_SIG_STATUS, AUDIT_NEW_SIG);
1051 0 : writeout_li (ctx, item? item->string:"?", _("Signature %d"), signo);
1052 0 : item = find_next_log_item (ctx, loopitem,
1053 : AUDIT_SIG_NAME, AUDIT_NEW_SIG);
1054 0 : if (item)
1055 0 : writeout_rem (ctx, "%s", item->string);
1056 :
1057 0 : item = find_next_log_item (ctx, loopitem,
1058 : AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG);
1059 0 : if (item)
1060 0 : writeout_rem (ctx, _("data hash algorithm: %s"),
1061 : gcry_md_algo_name (item->intvalue));
1062 0 : item = find_next_log_item (ctx, loopitem,
1063 : AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG);
1064 0 : if (item)
1065 0 : writeout_rem (ctx, _("attr hash algorithm: %s"),
1066 : gcry_md_algo_name (item->intvalue));
1067 :
1068 0 : enter_li (ctx);
1069 :
1070 : /* List the certificate chain. */
1071 0 : list_certchain (ctx, loopitem, AUDIT_NEW_SIG);
1072 :
1073 : /* Show the result of the chain validation. */
1074 0 : item = find_next_log_item (ctx, loopitem,
1075 : AUDIT_CHAIN_STATUS, AUDIT_NEW_SIG);
1076 0 : if (item && item->have_err)
1077 : {
1078 0 : writeout_li (ctx, item->err? "No":"Yes",
1079 0 : _("Certificate chain valid"));
1080 0 : if (item->err)
1081 0 : writeout_rem (ctx, "%s", gpg_strerror (item->err));
1082 : }
1083 :
1084 : /* Show whether the root certificate is fine. */
1085 0 : item = find_next_log_item (ctx, loopitem,
1086 : AUDIT_ROOT_TRUSTED, AUDIT_CHAIN_STATUS);
1087 0 : if (item)
1088 : {
1089 0 : writeout_li (ctx, item->err?"No":"Yes", "%s",
1090 : _("Root certificate trustworthy"));
1091 0 : if (item->err)
1092 : {
1093 0 : add_helptag (ctx, "gpgsm.root-cert-not-trusted");
1094 0 : writeout_rem (ctx, "%s", gpg_strerror (item->err));
1095 0 : list_cert (ctx, item->cert, 0);
1096 : }
1097 : }
1098 :
1099 : /* Show result of the CRL/OCSP check. */
1100 0 : item = find_next_log_item (ctx, loopitem,
1101 : AUDIT_CRL_CHECK, AUDIT_NEW_SIG);
1102 0 : if (item)
1103 : {
1104 : const char *ok;
1105 0 : switch (gpg_err_code (item->err))
1106 : {
1107 0 : case 0: ok = "good"; break;
1108 0 : case GPG_ERR_CERT_REVOKED: ok = "bad"; break;
1109 0 : case GPG_ERR_NOT_ENABLED: ok = "disabled"; break;
1110 : case GPG_ERR_NO_CRL_KNOWN:
1111 0 : ok = _("no CRL found for certificate");
1112 0 : break;
1113 : case GPG_ERR_CRL_TOO_OLD:
1114 0 : ok = _("the available CRL is too old");
1115 0 : break;
1116 0 : default: ok = gpg_strerror (item->err); break;
1117 : }
1118 :
1119 0 : writeout_li (ctx, ok, "%s", _("CRL/OCSP check of certificates"));
1120 0 : if (item->err
1121 0 : && gpg_err_code (item->err) != GPG_ERR_CERT_REVOKED
1122 0 : && gpg_err_code (item->err) != GPG_ERR_NOT_ENABLED)
1123 0 : add_helptag (ctx, "gpgsm.crl-problem");
1124 : }
1125 :
1126 0 : leave_li (ctx);
1127 : }
1128 0 : while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0)));
1129 :
1130 :
1131 : leave:
1132 : /* Always list the certificates stored in the signature. */
1133 0 : item = NULL;
1134 0 : count = 0;
1135 0 : while ( ((item = find_next_log_item (ctx, item,
1136 : AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
1137 0 : count++;
1138 0 : snprintf (numbuf, sizeof numbuf, "%d", count);
1139 0 : writeout_li (ctx, numbuf, _("Included certificates"));
1140 0 : item = NULL;
1141 0 : while ( ((item = find_next_log_item (ctx, item,
1142 : AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
1143 : {
1144 0 : char *name = get_cert_name (item->cert);
1145 0 : writeout_rem (ctx, "%s", name);
1146 0 : xfree (name);
1147 0 : enter_li (ctx);
1148 0 : for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
1149 : {
1150 0 : writeout_rem (ctx, "%s", name);
1151 0 : xfree (name);
1152 : }
1153 0 : leave_li (ctx);
1154 : }
1155 0 : leave_li (ctx);
1156 0 : }
1157 :
1158 :
1159 :
1160 :
1161 : /* Print the formatted audit result. THIS IS WORK IN PROGRESS. */
1162 : void
1163 0 : audit_print_result (audit_ctx_t ctx, estream_t out, int use_html)
1164 : {
1165 : int idx;
1166 : size_t n;
1167 : log_item_t item;
1168 : helptag_t helptag;
1169 : const char *s;
1170 0 : int show_raw = 0;
1171 : char *orig_codeset;
1172 :
1173 0 : if (!ctx)
1174 0 : return;
1175 :
1176 0 : orig_codeset = i18n_switchto_utf8 ();
1177 :
1178 : /* We use an environment variable to include some debug info in the
1179 : log. */
1180 0 : if ((s = getenv ("gnupg_debug_audit")))
1181 0 : show_raw = 1;
1182 :
1183 0 : assert (!ctx->outstream);
1184 0 : ctx->outstream = out;
1185 0 : ctx->use_html = use_html;
1186 0 : ctx->indentlevel = 0;
1187 0 : clear_helptags (ctx);
1188 :
1189 0 : if (use_html)
1190 0 : es_fputs ("<div class=\"" GNUPG_NAME "AuditLog\">\n", ctx->outstream);
1191 :
1192 0 : if (!ctx->log || !ctx->logused)
1193 : {
1194 0 : writeout_para (ctx, _("No audit log entries."));
1195 0 : goto leave;
1196 : }
1197 :
1198 0 : if (show_raw)
1199 : {
1200 : int maxlen;
1201 :
1202 0 : for (idx=0,maxlen=0; idx < DIM (eventstr_msgidx); idx++)
1203 : {
1204 0 : n = strlen (eventstr_msgstr + eventstr_msgidx[idx]);
1205 0 : if (n > maxlen)
1206 0 : maxlen = n;
1207 : }
1208 :
1209 0 : if (use_html)
1210 0 : es_fputs ("<pre>\n", out);
1211 0 : for (idx=0; idx < ctx->logused; idx++)
1212 : {
1213 0 : es_fprintf (out, "log: %-*s",
1214 0 : maxlen, event2str (ctx->log[idx].event));
1215 0 : if (ctx->log[idx].have_intvalue)
1216 0 : es_fprintf (out, " i=%d", ctx->log[idx].intvalue);
1217 0 : if (ctx->log[idx].string)
1218 : {
1219 0 : es_fputs (" s='", out);
1220 0 : writeout (ctx, ctx->log[idx].string);
1221 0 : es_fputs ("'", out);
1222 : }
1223 0 : if (ctx->log[idx].cert)
1224 0 : es_fprintf (out, " has_cert");
1225 0 : if (ctx->log[idx].have_err)
1226 : {
1227 0 : es_fputs (" err='", out);
1228 0 : writeout (ctx, gpg_strerror (ctx->log[idx].err));
1229 0 : es_fputs ("'", out);
1230 : }
1231 0 : es_fputs ("\n", out);
1232 : }
1233 0 : if (use_html)
1234 0 : es_fputs ("</pre>\n", out);
1235 : else
1236 0 : es_fputs ("\n", out);
1237 : }
1238 :
1239 0 : enter_li (ctx);
1240 0 : switch (ctx->type)
1241 : {
1242 : case AUDIT_TYPE_NONE:
1243 0 : writeout_li (ctx, NULL, _("Unknown operation"));
1244 0 : break;
1245 : case AUDIT_TYPE_ENCRYPT:
1246 0 : proc_type_encrypt (ctx);
1247 0 : break;
1248 : case AUDIT_TYPE_SIGN:
1249 0 : proc_type_sign (ctx);
1250 0 : break;
1251 : case AUDIT_TYPE_DECRYPT:
1252 0 : proc_type_decrypt (ctx);
1253 0 : break;
1254 : case AUDIT_TYPE_VERIFY:
1255 0 : proc_type_verify (ctx);
1256 0 : break;
1257 : }
1258 0 : item = find_log_item (ctx, AUDIT_AGENT_READY, 0);
1259 0 : if (item && item->have_err)
1260 : {
1261 0 : writeout_li (ctx, item->err? "No":"Yes", "%s", _("Gpg-Agent usable"));
1262 0 : if (item->err)
1263 : {
1264 0 : writeout_rem (ctx, "%s", gpg_strerror (item->err));
1265 0 : add_helptag (ctx, "gnupg.agent-problem");
1266 : }
1267 : }
1268 0 : item = find_log_item (ctx, AUDIT_DIRMNGR_READY, 0);
1269 0 : if (item && item->have_err)
1270 : {
1271 0 : writeout_li (ctx, item->err? "No":"Yes", "%s", _("Dirmngr usable"));
1272 0 : if (item->err)
1273 : {
1274 0 : writeout_rem (ctx, "%s", gpg_strerror (item->err));
1275 0 : add_helptag (ctx, "gnupg.dirmngr-problem");
1276 : }
1277 : }
1278 0 : leave_li (ctx);
1279 :
1280 :
1281 : /* Show the help from the collected help tags. */
1282 0 : if (ctx->helptags)
1283 : {
1284 0 : if (use_html)
1285 : {
1286 0 : es_fputs ("<hr/>\n", ctx->outstream);
1287 0 : if (ctx->helptags->next)
1288 0 : es_fputs ("<ul>\n", ctx->outstream);
1289 : }
1290 : else
1291 0 : es_fputs ("\n\n", ctx->outstream);
1292 : }
1293 0 : for (helptag = ctx->helptags; helptag; helptag = helptag->next)
1294 : {
1295 : char *text;
1296 :
1297 0 : if (use_html && ctx->helptags->next)
1298 0 : es_fputs ("<li>\n", ctx->outstream);
1299 :
1300 0 : text = gnupg_get_help_string (helptag->name, 0);
1301 0 : if (text)
1302 : {
1303 0 : writeout_para (ctx, "%s", text);
1304 0 : xfree (text);
1305 : }
1306 : else
1307 0 : writeout_para (ctx, _("No help available for '%s'."), helptag->name);
1308 0 : if (use_html && ctx->helptags->next)
1309 0 : es_fputs ("</li>\n", ctx->outstream);
1310 0 : if (helptag->next)
1311 0 : es_fputs ("\n", ctx->outstream);
1312 : }
1313 0 : if (use_html && ctx->helptags && ctx->helptags->next)
1314 0 : es_fputs ("</ul>\n", ctx->outstream);
1315 :
1316 : leave:
1317 0 : if (use_html)
1318 0 : es_fputs ("</div>\n", ctx->outstream);
1319 0 : ctx->outstream = NULL;
1320 0 : ctx->use_html = 0;
1321 0 : clear_helptags (ctx);
1322 0 : i18n_switchback (orig_codeset);
1323 : }
|