1 /* audit.c - GnuPG's audit subsystem
2 * Copyright (C) 2007 Free Software Foundation, Inc.
4 * This file is part of GnuPG.
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.
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.
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/>.
29 #include "audit-events.h"
31 /* A list to maintain a list of helptags. */
34 struct helptag_s
*next
;
37 typedef struct helptag_s
*helptag_t
;
43 audit_event_t event
; /* The event. */
44 gpg_error_t err
; /* The logged error code. */
45 int intvalue
; /* A logged interger value. */
46 char *string
; /* A malloced string or NULL. */
47 ksba_cert_t cert
; /* A certifciate or NULL. */
51 typedef struct log_item_s
*log_item_t
;
55 /* The main audit object. */
58 const char *failure
; /* If set a description of the internal failure. */
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. */
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. */
74 static void writeout_para (audit_ctx_t ctx
,
75 const char *format
, ...) JNLIB_GCC_A_PRINTF(2,3);
76 static void writeout_li (audit_ctx_t ctx
, const char *oktext
,
77 const char *format
, ...) JNLIB_GCC_A_PRINTF(3,4);
78 static void writeout_rem (audit_ctx_t ctx
,
79 const char *format
, ...) JNLIB_GCC_A_PRINTF(2,3);
82 /* Add NAME to the list of help tags. NAME needs to be a const string
83 an this function merly stores this pointer. */
85 add_helptag (audit_ctx_t ctx
, const char *name
)
89 for (item
=ctx
->helptags
; item
; item
= item
->next
)
90 if (!strcmp (item
->name
, name
))
91 return; /* Already in the list. */
92 item
= xtrycalloc (1, sizeof *item
);
94 return; /* Don't care about memory problems. */
96 item
->next
= ctx
->helptags
;
101 /* Remove all help tags from the context. */
103 clear_helptags (audit_ctx_t ctx
)
105 while (ctx
->helptags
)
107 helptag_t tmp
= ctx
->helptags
->next
;
108 xfree (ctx
->helptags
);
116 event2str (audit_event_t event
)
118 int idx
= eventstr_msgidxof (event
);
120 return "Unknown event";
122 return eventstr_msgstr
+ eventstr_msgidx
[idx
];
127 /* Create a new audit context. In case of an error NULL is returned
128 and errno set appropriately. */
134 ctx
= xtrycalloc (1, sizeof *ctx
);
140 /* Release an audit context. Passing NULL for CTX is allowed and does
143 audit_release (audit_ctx_t ctx
)
150 for (idx
=0; idx
< ctx
->logused
; idx
++)
152 if (ctx
->log
[idx
].string
)
153 xfree (ctx
->log
[idx
].string
);
154 if (ctx
->log
[idx
].cert
)
155 ksba_cert_release (ctx
->log
[idx
].cert
);
159 clear_helptags (ctx
);
164 /* Set the type for the audit operation. If CTX is NULL, this is a
167 audit_set_type (audit_ctx_t ctx
, audit_type_t type
)
169 if (!ctx
|| ctx
->failure
)
170 return; /* Audit not enabled or an internal error has occurred. */
172 if (ctx
->type
&& ctx
->type
!= type
)
174 ctx
->failure
= "conflict in type initialization";
181 /* Create a new log item and put it into the table. Return that log
182 item on success; return NULL on memory failure and mark that in
185 create_log_item (audit_ctx_t ctx
)
187 log_item_t item
, table
;
193 table
= xtrymalloc (size
* sizeof *table
);
196 ctx
->failure
= "Out of memory in create_log_item";
204 else if (ctx
->logused
>= ctx
->logsize
)
206 size
= ctx
->logsize
+ 10;
207 table
= xtryrealloc (ctx
->log
, size
* sizeof *table
);
210 ctx
->failure
= "Out of memory while reallocating in create_log_item";
215 item
= ctx
->log
+ ctx
->logused
++;
218 item
= ctx
->log
+ ctx
->logused
++;
220 item
->event
= AUDIT_NULL_EVENT
;
224 item
->have_intvalue
= 0;
232 /* Add a new event to the audit log. If CTX is NULL, this function
235 audit_log (audit_ctx_t ctx
, audit_event_t event
)
239 if (!ctx
|| ctx
->failure
)
240 return; /* Audit not enabled or an internal error has occurred. */
243 ctx
->failure
= "Invalid event passed to audit_log";
246 if (!(item
= create_log_item (ctx
)))
251 /* Add a new event to the audit log. If CTX is NULL, this function
252 does nothing. This version also adds the result of the oepration
255 audit_log_ok (audit_ctx_t ctx
, audit_event_t event
, gpg_error_t err
)
259 if (!ctx
|| ctx
->failure
)
260 return; /* Audit not enabled or an internal error has occurred. */
263 ctx
->failure
= "Invalid event passed to audit_log_ok";
266 if (!(item
= create_log_item (ctx
)))
274 /* Add a new event to the audit log. If CTX is NULL, this function
275 does nothing. This version also add the integer VALUE to the log. */
277 audit_log_i (audit_ctx_t ctx
, audit_event_t event
, int value
)
281 if (!ctx
|| ctx
->failure
)
282 return; /* Audit not enabled or an internal error has occurred. */
285 ctx
->failure
= "Invalid event passed to audit_log_i";
288 if (!(item
= create_log_item (ctx
)))
291 item
->intvalue
= value
;
292 item
->have_intvalue
= 1;
296 /* Add a new event to the audit log. If CTX is NULL, this function
297 does nothing. This version also add the integer VALUE to the log. */
299 audit_log_s (audit_ctx_t ctx
, audit_event_t event
, const char *value
)
304 if (!ctx
|| ctx
->failure
)
305 return; /* Audit not enabled or an internal error has occurred. */
308 ctx
->failure
= "Invalid event passed to audit_log_s";
311 tmp
= xtrystrdup (value
? value
: "");
314 ctx
->failure
= "Out of memory in audit_event";
317 if (!(item
= create_log_item (ctx
)))
326 /* Add a new event to the audit log. If CTX is NULL, this function
327 does nothing. This version also adds the certificate CERT and the
328 result of an operation to the log. */
330 audit_log_cert (audit_ctx_t ctx
, audit_event_t event
,
331 ksba_cert_t cert
, gpg_error_t err
)
335 if (!ctx
|| ctx
->failure
)
336 return; /* Audit not enabled or an internal error has occurred. */
339 ctx
->failure
= "Invalid event passed to audit_log_cert";
342 if (!(item
= create_log_item (ctx
)))
349 ksba_cert_ref (cert
);
355 /* Write TEXT to the outstream. */
357 writeout (audit_ctx_t ctx
, const char *text
)
361 for (; *text
; text
++)
364 es_fputs ("<", ctx
->outstream
);
365 else if (*text
== '&')
366 es_fputs ("&", ctx
->outstream
);
368 es_putc (*text
, ctx
->outstream
);
372 es_fputs (text
, ctx
->outstream
);
376 /* Write TEXT to the outstream using a variable argument list. */
378 writeout_v (audit_ctx_t ctx
, const char *format
, va_list arg_ptr
)
382 estream_vasprintf (&buf
, format
, arg_ptr
);
389 writeout (ctx
, "[!!Out of core!!]");
393 /* Write TEXT as a paragraph. */
395 writeout_para (audit_ctx_t ctx
, const char *format
, ...)
400 es_fputs ("<p>", ctx
->outstream
);
401 va_start (arg_ptr
, format
) ;
402 writeout_v (ctx
, format
, arg_ptr
);
405 es_fputs ("</p>\n", ctx
->outstream
);
407 es_fputc ('\n', ctx
->outstream
);
412 enter_li (audit_ctx_t ctx
)
416 if (!ctx
->indentlevel
)
418 es_fputs ("<table border=\"0\">\n"
420 " <col width=\"80%\" />\n"
421 " <col width=\"20%\" />\n"
431 leave_li (audit_ctx_t ctx
)
436 if (!ctx
->indentlevel
)
437 es_fputs ("</table>\n", ctx
->outstream
);
442 /* Write TEXT as a list element. If OKTEXT is not NULL, append it to
445 writeout_li (audit_ctx_t ctx
, const char *oktext
, const char *format
, ...)
448 const char *color
= NULL
;
450 if (ctx
->use_html
&& format
&& oktext
)
452 if (!strcmp (oktext
, "Yes"))
454 else if (!strcmp (oktext
, "No"))
462 es_fputs (" <tr><td><table><tr><td>", ctx
->outstream
);
464 es_fprintf (ctx
->outstream
, "<font color=\"%s\">*</font>", color
);
466 es_fputs ("*", ctx
->outstream
);
467 for (i
=1; i
< ctx
->indentlevel
; i
++)
468 es_fputs (" ", ctx
->outstream
);
469 es_fputs ("</td><td>", ctx
->outstream
);
472 es_fprintf (ctx
->outstream
, "* %*s", (ctx
->indentlevel
-1)*2, "");
475 va_start (arg_ptr
, format
) ;
476 writeout_v (ctx
, format
, arg_ptr
);
480 es_fputs ("</td></tr></table>", ctx
->outstream
);
481 if (format
&& oktext
)
485 es_fputs ("</td><td>", ctx
->outstream
);
487 es_fprintf (ctx
->outstream
, "<font color=\"%s\">", color
);
490 writeout (ctx
, ": ");
491 writeout (ctx
, oktext
);
493 es_fputs ("</font>", ctx
->outstream
);
497 es_fputs ("</td></tr>\n", ctx
->outstream
);
499 es_fputc ('\n', ctx
->outstream
);
503 /* Write a remark line. */
505 writeout_rem (audit_ctx_t ctx
, const char *format
, ...)
513 es_fputs (" <tr><td><table><tr><td>*", ctx
->outstream
);
514 for (i
=1; i
< ctx
->indentlevel
; i
++)
515 es_fputs (" ", ctx
->outstream
);
516 es_fputs (" </td><td> (", ctx
->outstream
);
520 es_fprintf (ctx
->outstream
, "* %*s (", (ctx
->indentlevel
-1)*2, "");
523 va_start (arg_ptr
, format
) ;
524 writeout_v (ctx
, format
, arg_ptr
);
528 es_fputs (")</td></tr></table></td></tr>\n", ctx
->outstream
);
530 es_fputs (")\n", ctx
->outstream
);
534 /* Return the first log item for EVENT. If STOPEVENT is not 0 never
535 look behind that event in the log. If STARTITEM is not NULL start
536 search _after_that item. */
538 find_next_log_item (audit_ctx_t ctx
, log_item_t startitem
,
539 audit_event_t event
, audit_event_t stopevent
)
543 for (idx
=0; idx
< ctx
->logused
; idx
++)
547 if (ctx
->log
+ idx
== startitem
)
550 else if (stopevent
&& ctx
->log
[idx
].event
== stopevent
)
552 else if (ctx
->log
[idx
].event
== event
)
553 return ctx
->log
+ idx
;
560 find_log_item (audit_ctx_t ctx
, audit_event_t event
, audit_event_t stopevent
)
562 return find_next_log_item (ctx
, NULL
, event
, stopevent
);
566 /* Helper to a format a serial number. */
568 format_serial (ksba_const_sexp_t sn
)
570 const char *p
= (const char *)sn
;
577 BUG (); /* Not a valid S-expression. */
578 n
= strtoul (p
+1, &endp
, 10);
581 BUG (); /* Not a valid S-expression. */
582 return bin2hex (p
+1, n
, NULL
);
586 /* Return a malloced string with the serial number and the issuer DN
587 of the certificate. */
589 get_cert_name (ksba_cert_t cert
)
596 return xtrystrdup ("[no certificate]");
598 issuer
= ksba_cert_get_issuer (cert
, 0);
599 sn
= ksba_cert_get_serial (cert
);
602 p
= format_serial (sn
);
604 result
= xtrystrdup ("[invalid S/N]");
607 result
= xtrymalloc (strlen (p
) + strlen (issuer
) + 2 + 1);
611 strcpy (stpcpy (stpcpy (result
+1, p
),"/"), issuer
);
617 result
= xtrystrdup ("[missing S/N or issuer]");
623 /* Return a malloced string with the serial number and the issuer DN
624 of the certificate. */
626 get_cert_subject (ksba_cert_t cert
, int idx
)
632 return xtrystrdup ("[no certificate]");
634 subject
= ksba_cert_get_subject (cert
, idx
);
637 result
= xtrymalloc (strlen (subject
) + 1 + 1);
641 strcpy (result
+1, subject
);
651 /* List the given certificiate. If CERT is NULL, this is a NOP. */
653 list_cert (audit_ctx_t ctx
, ksba_cert_t cert
, int with_subj
)
658 name
= get_cert_name (cert
);
659 writeout_rem (ctx
, "%s", name
);
664 for (idx
=0; (name
= get_cert_subject (cert
, idx
)); idx
++)
666 writeout_rem (ctx
, "%s", name
);
674 /* List the chain of certificates from STARTITEM up to STOPEVENT. The
675 certifcates are written out as comments. */
677 list_certchain (audit_ctx_t ctx
, log_item_t startitem
, audit_event_t stopevent
)
681 startitem
= find_next_log_item (ctx
, startitem
, AUDIT_CHAIN_BEGIN
,stopevent
);
682 writeout_li (ctx
, startitem
? "Yes":"No", _("Certificate chain available"));
686 item
= find_next_log_item (ctx
, startitem
,
687 AUDIT_CHAIN_ROOTCERT
, AUDIT_CHAIN_END
);
689 writeout_rem (ctx
, "%s", _("root certificate missing"));
692 list_cert (ctx
, item
->cert
, 0);
695 while ( ((item
= find_next_log_item (ctx
, item
,
696 AUDIT_CHAIN_CERT
, AUDIT_CHAIN_END
))))
698 list_cert (ctx
, item
->cert
, 1);
704 /* Process an encrypt operation's log. */
706 proc_type_encrypt (audit_ctx_t ctx
)
708 log_item_t loopitem
, item
;
714 item
= find_log_item (ctx
, AUDIT_ENCRYPTION_DONE
, 0);
715 writeout_li (ctx
, item
?"Yes":"No", "%s", _("Data encryption succeeded"));
719 item
= find_log_item (ctx
, AUDIT_GOT_DATA
, 0);
720 writeout_li (ctx
, item
? "Yes":"No", "%s", _("Data available"));
722 item
= find_log_item (ctx
, AUDIT_SESSION_KEY
, 0);
723 writeout_li (ctx
, item
? "Yes":"No", "%s", _("Session key created"));
726 algo
= gcry_cipher_map_name (item
->string
);
728 writeout_rem (ctx
, _("algorithm: %s"), gcry_cipher_algo_name (algo
));
729 else if (item
->string
&& !strcmp (item
->string
, "1.2.840.113549.3.2"))
730 writeout_rem (ctx
, _("unsupported algorithm: %s"), "RC2");
731 else if (item
->string
)
732 writeout_rem (ctx
, _("unsupported algorithm: %s"), item
->string
);
734 writeout_rem (ctx
, _("seems to be not encrypted"));
737 item
= find_log_item (ctx
, AUDIT_GOT_RECIPIENTS
, 0);
738 snprintf (numbuf
, sizeof numbuf
, "%d",
739 item
&& item
->have_intvalue
? item
->intvalue
: 0);
740 writeout_li (ctx
, numbuf
, "%s", _("Number of recipients"));
742 /* Loop over all recipients. */
745 while ((loopitem
=find_next_log_item (ctx
, loopitem
, AUDIT_ENCRYPTED_TO
, 0)))
748 writeout_li (ctx
, NULL
, _("Recipient %d"), recp_no
);
751 name
= get_cert_name (loopitem
->cert
);
752 writeout_rem (ctx
, "%s", name
);
755 for (idx
=0; (name
= get_cert_subject (loopitem
->cert
, idx
)); idx
++)
757 writeout_rem (ctx
, "%s", name
);
769 /* Process a sign operation's log. */
771 proc_type_sign (audit_ctx_t ctx
)
776 writeout_li (ctx
, item
?"Yes":"No", "%s", _("Data signing succeeded"));
780 item
= find_log_item (ctx
, AUDIT_GOT_DATA
, 0);
781 writeout_li (ctx
, item
? "Yes":"No", "%s", _("Data available"));
789 /* Process a decrypt operation's log. */
791 proc_type_decrypt (audit_ctx_t ctx
)
796 writeout_li (ctx
, item
?"Yes":"No", "%s", _("Data decryption succeeded"));
800 item
= find_log_item (ctx
, AUDIT_GOT_DATA
, 0);
801 writeout_li (ctx
, item
? "Yes":"No", "%s", _("Data available"));
809 /* Process a verification operation's log. */
811 proc_type_verify (audit_ctx_t ctx
)
813 log_item_t loopitem
, item
;
814 int signo
, count
, idx
;
817 /* If there is at least one signature status we claim that the
818 verifciation succeeded. This does not mean that the data has
820 item
= find_log_item (ctx
, AUDIT_SIG_STATUS
, 0);
821 writeout_li (ctx
, item
?"Yes":"No", "%s", _("Data verification succeeded"));
824 item
= find_log_item (ctx
, AUDIT_GOT_DATA
, AUDIT_NEW_SIG
);
825 writeout_li (ctx
, item
? "Yes":"No", "%s", _("Data available"));
829 item
= find_log_item (ctx
, AUDIT_NEW_SIG
, 0);
830 writeout_li (ctx
, item
? "Yes":"No", "%s", _("Signature available"));
834 item
= find_log_item (ctx
, AUDIT_DATA_HASH_ALGO
, AUDIT_NEW_SIG
);
835 writeout_li (ctx
, item
?"Yes":"No", "%s", _("Parsing signature succeeded"));
838 item
= find_log_item (ctx
, AUDIT_BAD_DATA_HASH_ALGO
, AUDIT_NEW_SIG
);
840 writeout_rem (ctx
, _("Bad hash algorithm: %s"),
841 item
->string
? item
->string
:"?");
846 /* Loop over all signatures. */
847 loopitem
= find_log_item (ctx
, AUDIT_NEW_SIG
, 0);
851 signo
= loopitem
->have_intvalue
? loopitem
->intvalue
: -1;
853 item
= find_next_log_item (ctx
, loopitem
,
854 AUDIT_SIG_STATUS
, AUDIT_NEW_SIG
);
855 writeout_li (ctx
, item
? item
->string
:"?", _("Signature %d"), signo
);
856 item
= find_next_log_item (ctx
, loopitem
,
857 AUDIT_SIG_NAME
, AUDIT_NEW_SIG
);
859 writeout_rem (ctx
, "%s", item
->string
);
862 /* List the certificate chain. */
863 list_certchain (ctx
, loopitem
, AUDIT_NEW_SIG
);
865 /* Show the result of the chain validation. */
866 item
= find_next_log_item (ctx
, loopitem
,
867 AUDIT_CHAIN_STATUS
, AUDIT_NEW_SIG
);
868 if (item
&& item
->have_err
)
870 writeout_li (ctx
, item
->err
? "No":"Yes",
871 _("Certificate chain valid"));
873 writeout_rem (ctx
, "%s", gpg_strerror (item
->err
));
876 /* Show whether the root certificate is fine. */
877 item
= find_next_log_item (ctx
, loopitem
,
878 AUDIT_ROOT_TRUSTED
, AUDIT_CHAIN_STATUS
);
881 writeout_li (ctx
, item
->err
?"No":"Yes", "%s",
882 _("Root certificate trustworthy"));
885 add_helptag (ctx
, "gpgsm.root-cert-not-trusted");
886 writeout_rem (ctx
, "%s", gpg_strerror (item
->err
));
887 list_cert (ctx
, item
->cert
, 0);
891 /* Show result of the CRL/OCSP check. */
892 writeout_li (ctx
, "-", "%s", _("CRL/OCSP check of certificates"));
893 /* add_helptag (ctx, "gpgsm.ocsp-problem"); */
898 while ((loopitem
= find_next_log_item (ctx
, loopitem
, AUDIT_NEW_SIG
, 0)));
902 /* Always list the certificates stored in the signature. */
905 while ( ((item
= find_next_log_item (ctx
, item
,
906 AUDIT_SAVE_CERT
, AUDIT_NEW_SIG
))))
908 snprintf (numbuf
, sizeof numbuf
, "%d", count
);
909 writeout_li (ctx
, numbuf
, _("Included certificates"));
911 while ( ((item
= find_next_log_item (ctx
, item
,
912 AUDIT_SAVE_CERT
, AUDIT_NEW_SIG
))))
914 char *name
= get_cert_name (item
->cert
);
915 writeout_rem (ctx
, "%s", name
);
918 for (idx
=0; (name
= get_cert_subject (item
->cert
, idx
)); idx
++)
920 writeout_rem (ctx
, "%s", name
);
931 /* Print the formatted audit result. THIS IS WORK IN PROGRESS. */
933 audit_print_result (audit_ctx_t ctx
, estream_t out
, int use_html
)
945 /* We use an environment variable to include some debug info in the
947 if ((s
= getenv ("gnupg_debug_audit")))
950 if (!strcmp (s
, "html"))
954 assert (!ctx
->outstream
);
955 ctx
->outstream
= out
;
956 ctx
->use_html
= use_html
;
957 ctx
->indentlevel
= 0;
958 clear_helptags (ctx
);
961 es_fputs ("<div class=\"GnuPGAuditLog\">\n", ctx
->outstream
);
963 if (!ctx
->log
|| !ctx
->logused
)
965 writeout_para (ctx
, _("No audit log entries."));
973 for (idx
=0,maxlen
=0; idx
< DIM (eventstr_msgidx
); idx
++)
975 n
= strlen (eventstr_msgstr
+ eventstr_msgidx
[idx
]);
981 es_fputs ("<pre>\n", out
);
982 for (idx
=0; idx
< ctx
->logused
; idx
++)
984 es_fprintf (out
, "log: %-*s",
985 maxlen
, event2str (ctx
->log
[idx
].event
));
986 if (ctx
->log
[idx
].have_intvalue
)
987 es_fprintf (out
, " i=%d", ctx
->log
[idx
].intvalue
);
988 if (ctx
->log
[idx
].string
)
990 es_fputs (" s=`", out
);
991 writeout (ctx
, ctx
->log
[idx
].string
);
994 if (ctx
->log
[idx
].cert
)
995 es_fprintf (out
, " has_cert");
996 if (ctx
->log
[idx
].have_err
)
998 es_fputs (" err=`", out
);
999 writeout (ctx
, gpg_strerror (ctx
->log
[idx
].err
));
1000 es_fputs ("'", out
);
1002 es_fputs ("\n", out
);
1005 es_fputs ("</pre>\n", out
);
1007 es_fputs ("\n", out
);
1013 case AUDIT_TYPE_NONE
:
1014 writeout_li (ctx
, NULL
, _("Unknown operation"));
1016 case AUDIT_TYPE_ENCRYPT
:
1017 proc_type_encrypt (ctx
);
1019 case AUDIT_TYPE_SIGN
:
1020 proc_type_sign (ctx
);
1022 case AUDIT_TYPE_DECRYPT
:
1023 proc_type_decrypt (ctx
);
1025 case AUDIT_TYPE_VERIFY
:
1026 proc_type_verify (ctx
);
1029 item
= find_log_item (ctx
, AUDIT_AGENT_READY
, 0);
1030 if (item
&& item
->have_err
)
1032 writeout_li (ctx
, item
->err
? "No":"Yes", "%s", _("Gpg-Agent usable"));
1035 writeout_rem (ctx
, "%s", gpg_strerror (item
->err
));
1036 add_helptag (ctx
, "gnupg.agent-problem");
1039 item
= find_log_item (ctx
, AUDIT_DIRMNGR_READY
, 0);
1040 if (item
&& item
->have_err
)
1042 writeout_li (ctx
, item
->err
? "No":"Yes", "%s", _("Dirmngr usable"));
1045 writeout_rem (ctx
, "%s", gpg_strerror (item
->err
));
1046 add_helptag (ctx
, "gnupg.dirmngr-problem");
1052 /* Show the help from the collected help tags. */
1057 es_fputs ("<hr/>\n", ctx
->outstream
);
1058 if (ctx
->helptags
->next
)
1059 es_fputs ("<ul>\n", ctx
->outstream
);
1062 es_fputs ("\n\n", ctx
->outstream
);
1064 for (helptag
= ctx
->helptags
; helptag
; helptag
= helptag
->next
)
1068 if (use_html
&& ctx
->helptags
->next
)
1069 es_fputs ("<li>\n", ctx
->outstream
);
1071 text
= gnupg_get_help_string (helptag
->name
, 0);
1074 writeout_para (ctx
, "%s", text
);
1078 writeout_para (ctx
, _("No help available for `%s'."), helptag
->name
);
1079 if (use_html
&& ctx
->helptags
->next
)
1080 es_fputs ("</li>\n", ctx
->outstream
);
1082 es_fputs ("\n", ctx
->outstream
);
1084 if (use_html
&& ctx
->helptags
&& ctx
->helptags
->next
)
1085 es_fputs ("</ul>\n", ctx
->outstream
);
1089 es_fputs ("</div>\n", ctx
->outstream
);
1090 ctx
->outstream
= NULL
;
1092 clear_helptags (ctx
);