9f398d5ce08082e20c75a7b93a3d0631244f4a28
[gnupg.git] / sm / certdump.c
1 /* certdump.c - Dump a certificate for debugging
2  *      Copyright (C) 2001, 2004 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 2 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, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
19  * USA.
20  */
21
22 #include <config.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <unistd.h> 
28 #include <time.h>
29 #include <assert.h>
30 #ifdef HAVE_LOCALE_H
31 #include <locale.h>
32 #endif
33 #ifdef HAVE_LANGINFO_CODESET
34 #include <langinfo.h>
35 #endif
36
37 #include "gpgsm.h"
38 #include <gcrypt.h>
39 #include <ksba.h>
40
41 #include "keydb.h"
42 #include "i18n.h"
43
44 #ifdef HAVE_FOPENCOOKIE
45 typedef ssize_t my_funopen_hook_ret_t;
46 #else
47 typedef int     my_funopen_hook_ret_t;
48 #endif
49
50
51 struct dn_array_s {
52   char *key;
53   char *value;
54   int   multivalued;
55   int   done;
56 };
57
58
59 /* print the first element of an S-Expression */
60 void
61 gpgsm_print_serial (FILE *fp, ksba_const_sexp_t sn)
62 {
63   const char *p = (const char *)sn;
64   unsigned long n;
65   char *endp;
66
67   if (!p)
68     fputs (_("none"), fp);
69   else if (*p != '(')
70     fputs ("[Internal error - not an S-expression]", fp);
71   else
72     {
73       p++;
74       n = strtoul (p, &endp, 10);
75       p = endp;
76       if (*p!=':')
77         fputs ("[Internal Error - invalid S-expression]", fp);
78       else
79         {
80           for (p++; n; n--, p++)
81             fprintf (fp, "%02X", *(const unsigned char*)p);
82         }
83     }
84 }
85
86
87 /* Dump the serial number or any other simple S-expression. */
88 void
89 gpgsm_dump_serial (ksba_const_sexp_t sn)
90 {
91   const char *p = (const char *)sn;
92   unsigned long n;
93   char *endp;
94
95   if (!p)
96     log_printf ("none");
97   else if (*p != '(')
98     log_printf ("ERROR - not an S-expression");
99   else
100     {
101       p++;
102       n = strtoul (p, &endp, 10);
103       p = endp;
104       if (*p!=':')
105         log_printf ("ERROR - invalid S-expression");
106       else
107         {
108           for (p++; n; n--, p++)
109             log_printf ("%02X", *(const unsigned char *)p);
110         }
111     }
112 }
113
114
115 char *
116 gpgsm_format_serial (ksba_const_sexp_t sn)
117 {
118   const char *p = (const char *)sn;
119   unsigned long n;
120   char *endp;
121   char *buffer;
122   int i;
123
124   if (!p)
125     return NULL;
126
127   if (*p != '(')
128     BUG (); /* Not a valid S-expression. */
129
130   p++;
131   n = strtoul (p, &endp, 10);
132   p = endp;
133   if (*p!=':')
134     BUG (); /* Not a valid S-expression. */
135   p++;
136
137   buffer = xtrymalloc (n*2+1);
138   if (buffer)
139     {
140       for (i=0; n; n--, p++, i+=2)
141         sprintf (buffer+i, "%02X", *(unsigned char *)p);
142       buffer[i] = 0;
143     }
144   return buffer;
145 }
146
147
148
149
150 void
151 gpgsm_print_time (FILE *fp, ksba_isotime_t t)
152 {
153   if (!t || !*t)
154     fputs (_("none"), fp);
155   else
156     fprintf (fp, "%.4s-%.2s-%.2s %.2s:%.2s:%s", t, t+4, t+6, t+9, t+11, t+13);
157 }
158
159 void
160 gpgsm_dump_time (ksba_isotime_t t)
161 {
162   if (!t || !*t)
163     log_printf (_("[none]"));
164   else
165     log_printf ("%.4s-%.2s-%.2s %.2s:%.2s:%s",
166                 t, t+4, t+6, t+9, t+11, t+13);
167 }
168
169
170
171
172 void
173 gpgsm_dump_string (const char *string)
174 {
175
176   if (!string)
177     log_printf ("[error]");
178   else
179     {
180       const unsigned char *s;
181
182       for (s=(const unsigned char*)string; *s; s++)
183         {
184           if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0))
185             break;
186         }
187       if (!*s && *string != '[')
188         log_printf ("%s", string);
189       else
190         {
191           log_printf ( "[ ");
192           log_printhex (NULL, string, strlen (string));
193           log_printf ( " ]");
194         }
195     }
196 }
197
198
199 /* This simple dump function is mainly used for debugging purposes. */
200 void 
201 gpgsm_dump_cert (const char *text, ksba_cert_t cert)
202 {
203   ksba_sexp_t sexp;
204   char *p;
205   char *dn;
206   ksba_isotime_t t;
207
208   log_debug ("BEGIN Certificate `%s':\n", text? text:"");
209   if (cert)
210     {
211       sexp = ksba_cert_get_serial (cert);
212       log_debug ("     serial: ");
213       gpgsm_dump_serial (sexp);
214       ksba_free (sexp);
215       log_printf ("\n");
216
217       ksba_cert_get_validity (cert, 0, t);
218       log_debug ("  notBefore: ");
219       gpgsm_dump_time (t);
220       log_printf ("\n");
221       ksba_cert_get_validity (cert, 1, t);
222       log_debug ("   notAfter: ");
223       gpgsm_dump_time (t);
224       log_printf ("\n");
225
226       dn = ksba_cert_get_issuer (cert, 0);
227       log_debug ("     issuer: ");
228       gpgsm_dump_string (dn);
229       ksba_free (dn);
230       log_printf ("\n");
231     
232       dn = ksba_cert_get_subject (cert, 0);
233       log_debug ("    subject: ");
234       gpgsm_dump_string (dn);
235       ksba_free (dn);
236       log_printf ("\n");
237
238       log_debug ("  hash algo: %s\n", ksba_cert_get_digest_algo (cert));
239
240       p = gpgsm_get_fingerprint_string (cert, 0);
241       log_debug ("  SHA1 Fingerprint: %s\n", p);
242       xfree (p);
243     }
244   log_debug ("END Certificate\n");
245 }
246
247
248 /* Log the certificate's name in "#SN/ISSUERDN" format along with
249    TEXT. */
250 void 
251 gpgsm_cert_log_name (const char *text, ksba_cert_t cert)
252 {
253   log_info ("%s", text? text:"certificate" );
254   if (cert)
255     {
256       ksba_sexp_t sn;
257       char *p;
258
259       p = ksba_cert_get_issuer (cert, 0);
260       sn = ksba_cert_get_serial (cert);
261       if (p && sn)
262         {
263           log_printf (" #");
264           gpgsm_dump_serial (sn);
265           log_printf ("/");
266           gpgsm_dump_string (p);
267         }
268       else
269         log_printf (" [invalid]");
270       ksba_free (sn);
271       xfree (p);
272     }
273   log_printf ("\n");
274 }
275
276
277
278 \f
279 /* helper for the rfc2253 string parser */
280 static const unsigned char *
281 parse_dn_part (struct dn_array_s *array, const unsigned char *string)
282 {
283   static struct {
284     const char *label;
285     const char *oid;
286   } label_map[] = {
287     /* Warning: When adding new labels, make sure that the buffer
288        below we be allocated large enough. */
289     {"EMail",        "1.2.840.113549.1.9.1" },
290     {"T",            "2.5.4.12" },
291     {"GN",           "2.5.4.42" },
292     {"SN",           "2.5.4.4" },
293     {"NameDistinguisher", "0.2.262.1.10.7.20"}, 
294     {"ADDR",         "2.5.4.16" },
295     {"BC",           "2.5.4.15" },
296     {"D",            "2.5.4.13" },
297     {"PostalCode",   "2.5.4.17" },
298     {"Pseudo",       "2.5.4.65" },
299     {"SerialNumber", "2.5.4.5" },
300     {NULL, NULL}
301   };
302   const unsigned char *s, *s1;
303   size_t n;
304   char *p;
305   int i;
306
307   /* Parse attributeType */
308   for (s = string+1; *s && *s != '='; s++)
309     ;
310   if (!*s)
311     return NULL; /* error */
312   n = s - string;
313   if (!n)
314     return NULL; /* empty key */
315
316   /* We need to allocate a few bytes more due to the possible mapping
317      from the shorter OID to the longer label. */
318   array->key = p = xtrymalloc (n+10);
319   if (!array->key)
320     return NULL;
321   memcpy (p, string, n); 
322   p[n] = 0;
323   trim_trailing_spaces (p);
324
325   if (digitp (p))
326     {
327       for (i=0; label_map[i].label; i++ )
328         if ( !strcmp (p, label_map[i].oid) )
329           {
330             strcpy (p, label_map[i].label);
331             break;
332           }
333     }
334   string = s + 1;
335
336   if (*string == '#')
337     { /* hexstring */
338       string++;
339       for (s=string; hexdigitp (s); s++)
340         s++;
341       n = s - string;
342       if (!n || (n & 1))
343         return NULL; /* Empty or odd number of digits. */
344       n /= 2;
345       array->value = p = xtrymalloc (n+1);
346       if (!p)
347         return NULL;
348       for (s1=string; n; s1 += 2, n--, p++)
349         {
350           *(unsigned char *)p = xtoi_2 (s1);
351           if (!*p)
352             *p = 0x01; /* Better print a wrong value than truncating
353                           the string. */
354         }
355       *p = 0;
356    }
357   else
358     { /* regular v3 quoted string */
359       for (n=0, s=string; *s; s++)
360         {
361           if (*s == '\\')
362             { /* pair */
363               s++;
364               if (*s == ',' || *s == '=' || *s == '+'
365                   || *s == '<' || *s == '>' || *s == '#' || *s == ';' 
366                   || *s == '\\' || *s == '\"' || *s == ' ')
367                 n++;
368               else if (hexdigitp (s) && hexdigitp (s+1))
369                 {
370                   s++;
371                   n++;
372                 }
373               else
374                 return NULL; /* invalid escape sequence */
375             }
376           else if (*s == '\"')
377             return NULL; /* invalid encoding */
378           else if (*s == ',' || *s == '=' || *s == '+'
379                    || *s == '<' || *s == '>' || *s == '#' || *s == ';' )
380             break; 
381           else
382             n++;
383         }
384
385       array->value = p = xtrymalloc (n+1);
386       if (!p)
387         return NULL;
388       for (s=string; n; s++, n--)
389         {
390           if (*s == '\\')
391             { 
392               s++;
393               if (hexdigitp (s))
394                 {
395                   *(unsigned char *)p++ = xtoi_2 (s);
396                   s++;
397                 }
398               else
399                 *p++ = *s;
400             }
401           else
402             *p++ = *s;
403         }
404       *p = 0;
405     }
406   return s;
407 }
408
409
410 /* Parse a DN and return an array-ized one.  This is not a validating
411    parser and it does not support any old-stylish syntax; KSBA is
412    expected to return only rfc2253 compatible strings. */
413 static struct dn_array_s *
414 parse_dn (const unsigned char *string)
415 {
416   struct dn_array_s *array;
417   size_t arrayidx, arraysize;
418   int i;
419
420   arraysize = 7; /* C,ST,L,O,OU,CN,email */
421   arrayidx = 0;
422   array = xtrymalloc ((arraysize+1) * sizeof *array);
423   if (!array)
424     return NULL;
425   while (*string)
426     {
427       while (*string == ' ')
428         string++;
429       if (!*string)
430         break; /* ready */
431       if (arrayidx >= arraysize)
432         { 
433           struct dn_array_s *a2;
434
435           arraysize += 5;
436           a2 = xtryrealloc (array, (arraysize+1) * sizeof *array);
437           if (!a2)
438             goto failure;
439           array = a2;
440         }
441       array[arrayidx].key = NULL;
442       array[arrayidx].value = NULL;
443       string = parse_dn_part (array+arrayidx, string);
444       if (!string)
445         goto failure;
446       while (*string == ' ')
447         string++;
448       array[arrayidx].multivalued = (*string == '+');
449       array[arrayidx].done = 0;
450       arrayidx++;
451       if (*string && *string != ',' && *string != ';' && *string != '+')
452         goto failure; /* invalid delimiter */
453       if (*string)
454         string++;
455     }
456   array[arrayidx].key = NULL;
457   array[arrayidx].value = NULL;
458   return array;
459
460  failure:
461   for (i=0; i < arrayidx; i++)
462     {
463       xfree (array[i].key);
464       xfree (array[i].value);
465     }
466   xfree (array);
467   return NULL;
468 }
469
470
471 static void
472 print_dn_part (FILE *fp, struct dn_array_s *dn, const char *key, int translate)
473 {
474   struct dn_array_s *first_dn = dn;
475
476   for (; dn->key; dn++)
477     {
478       if (!dn->done && !strcmp (dn->key, key))
479         {
480           /* Forward to the last multi-valued RDN, so that we can
481              print them all in reverse in the correct order.  Note
482              that this overrides the the standard sequence but that
483              seems to a reasonable thing to do with multi-valued
484              RDNs. */
485           while (dn->multivalued && dn[1].key)
486             dn++;
487         next:
488           if (!dn->done && dn->value && *dn->value)
489             {
490               fprintf (fp, "/%s=", dn->key);
491               if (translate)
492                 print_sanitized_utf8_string (fp, dn->value, '/');
493               else
494                 print_sanitized_string (fp, dn->value, '/');
495             }
496           dn->done = 1;
497           if (dn > first_dn && dn[-1].multivalued)
498             {
499               dn--;
500               goto next;
501             }
502         }
503     }
504 }
505
506 /* Print all parts of a DN in a "standard" sequence.  We first print
507    all the known parts, followed by the uncommon ones */
508 static void
509 print_dn_parts (FILE *fp, struct dn_array_s *dn, int translate)
510 {
511   const char *stdpart[] = {
512     "CN", "OU", "O", "STREET", "L", "ST", "C", "EMail", NULL 
513   };
514   int i;
515   
516   for (i=0; stdpart[i]; i++)
517       print_dn_part (fp, dn, stdpart[i], translate);
518
519   /* Now print the rest without any specific ordering */
520   for (; dn->key; dn++)
521     print_dn_part (fp, dn, dn->key, translate);
522 }
523
524
525 /* Print the S-Expression in BUF, which has a valid length of BUFLEN,
526    as a human readable string in one line to FP. */
527 static void
528 pretty_print_sexp (FILE *fp, const unsigned char *buf, size_t buflen)
529 {
530   size_t len;
531   gcry_sexp_t sexp;
532   char *result, *p;
533
534   if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) )
535     {
536       fputs (_("[Error - invalid encoding]"), fp);
537       return;
538     }
539   len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
540   assert (len);
541   result = xtrymalloc (len);
542   if (!result)
543     {
544       fputs (_("[Error - out of core]"), fp);
545       gcry_sexp_release (sexp);
546       return;
547     }
548   len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
549   assert (len);
550   for (p = result; len; len--, p++)
551     {
552       if (*p == '\n')
553         {
554           if (len > 1) /* Avoid printing the trailing LF. */
555             fputs ("\\n", fp);
556         }
557       else if (*p == '\r')
558         fputs ("\\r", fp);
559       else if (*p == '\v')
560         fputs ("\\v", fp);
561       else if (*p == '\t')
562         fputs ("\\t", fp);
563       else
564         putc (*p, fp);
565     }
566   xfree (result);
567   gcry_sexp_release (sexp);
568 }
569
570
571 void
572 gpgsm_print_name2 (FILE *fp, const char *name, int translate)
573 {
574   const unsigned char *s = (const unsigned char *)name;
575   int i;
576
577   if (!s)
578     {
579       fputs (_("[Error - No name]"), fp);
580     }
581   else if (*s == '<')
582     {
583       const char *s2 = strchr ( (char*)s+1, '>');
584       if (s2)
585         {
586           if (translate)
587             print_sanitized_utf8_buffer (fp, s + 1, s2 - (char*)s - 1, 0);
588           else
589             print_sanitized_buffer (fp, s + 1, s2 - (char*)s - 1, 0);
590         }
591     }
592   else if (*s == '(')
593     {
594       pretty_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL));
595     }
596   else if (!((*s >= '0' && *s < '9')
597              || (*s >= 'A' && *s <= 'Z')
598              || (*s >= 'a' && *s <= 'z')))
599     fputs (_("[Error - invalid encoding]"), fp);
600   else
601     {
602       struct dn_array_s *dn = parse_dn (s);
603       if (!dn)
604         fputs (_("[Error - invalid DN]"), fp);
605       else 
606         {
607           print_dn_parts (fp, dn, translate);          
608           for (i=0; dn[i].key; i++)
609             {
610               xfree (dn[i].key);
611               xfree (dn[i].value);
612             }
613           xfree (dn);
614         }
615     }
616 }
617
618
619 void
620 gpgsm_print_name (FILE *fp, const char *name)
621 {
622   gpgsm_print_name2 (fp, name, 1);
623 }
624
625
626 /* A cookie structure used for the memory stream. */
627 struct format_name_cookie 
628 {
629   char *buffer;         /* Malloced buffer with the data to deliver. */
630   size_t size;          /* Allocated size of this buffer. */
631   size_t len;           /* strlen (buffer). */
632   int error;            /* system error code if any. */
633 };
634
635 /* The writer function for the memory stream. */
636 static my_funopen_hook_ret_t
637 format_name_writer (void *cookie, const char *buffer, size_t size)
638 {
639   struct format_name_cookie *c = cookie;
640   char *p;
641
642   if (c->buffer)
643     p = xtryrealloc (c->buffer, c->size + size + 1);
644   else
645     p = xtrymalloc (size + 1);
646   if (!p)
647     {
648       c->error = errno;
649       xfree (c->buffer);
650       errno = c->error;
651       return -1;
652     }
653   c->buffer = p;
654   memcpy (p + c->len, buffer, size);
655   c->len += size;
656   p[c->len] = 0; /* Terminate string. */ 
657
658   return size;
659 }
660
661 /* Format NAME which is expected to be in rfc2253 format into a better
662    human readable format. Caller must free the returned string.  NULL
663    is returned in case of an error.  With TRANSLATE set to true the
664    name will be translated to the native encoding.  Note that NAME is
665    internally always UTF-8 encoded. */
666 char *
667 gpgsm_format_name2 (const char *name, int translate)
668 {
669 #if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
670   FILE *fp;
671   struct format_name_cookie cookie;
672
673   memset (&cookie, 0, sizeof cookie);
674
675 #ifdef HAVE_FOPENCOOKIE
676   {
677     cookie_io_functions_t io = { NULL };
678     io.write = format_name_writer;
679     
680     fp = fopencookie (&cookie, "w", io);
681   }
682 #else /*!HAVE_FOPENCOOKIE*/
683   {
684     fp = funopen (&cookie, NULL, format_name_writer, NULL, NULL);
685   }
686 #endif /*!HAVE_FOPENCOOKIE*/
687   if (!fp)
688     {
689       int save_errno = errno;
690       log_error ("error creating memory stream: %s\n", strerror (errno));
691       errno = save_errno;
692       return NULL;
693     }
694   gpgsm_print_name2 (fp, name, translate);
695   fclose (fp);
696   if (cookie.error || !cookie.buffer)
697     {
698       xfree (cookie.buffer);
699       errno = cookie.error;
700       return NULL;
701     }
702   return cookie.buffer;
703 #else /* No fun - use the name verbatim. */
704   return xtrystrdup (name);
705 #endif /* No fun. */
706 }
707
708 char *
709 gpgsm_format_name (const char *name)
710 {
711   return gpgsm_format_name2 (name, 1);
712 }
713
714
715 /* Return fingerprint and a percent escaped name in a human readable
716    format suitable for status messages like GOODSIG.  May return NULL
717    on error (out of core). */
718 char *
719 gpgsm_fpr_and_name_for_status (ksba_cert_t cert)
720 {
721   char *fpr, *name, *p;
722   char *buffer;
723
724   fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
725   if (!fpr)
726     return NULL;
727   
728   name = ksba_cert_get_subject (cert, 0);
729   if (!name)
730     {
731       xfree (fpr);
732       return NULL;
733     }
734
735   p = gpgsm_format_name2 (name, 0);
736   ksba_free (name);
737   name = p;
738   if (!name)
739     {
740       xfree (fpr);
741       return NULL;
742     }
743
744   buffer = xtrymalloc (strlen (fpr) + 1 + 3*strlen (name) + 1);
745   if (buffer)
746     {
747       const unsigned char *s;
748
749       p = stpcpy (stpcpy (buffer, fpr), " ");
750       for (s = name; *s; s++)
751         {
752           if (*s < ' ')
753             {
754               sprintf (p, "%%%02X", *s);
755               p += 3;
756             }
757           else
758             *p++ = *s;
759         }
760       *p = 0;
761     }
762   xfree (fpr);
763   xfree (name);
764   return buffer;
765 }
766
767
768 /* Create a key description for the CERT, this may be passed to the
769    pinentry.  The caller must free the returned string. NULL may be
770    returned on error. */
771 char *
772 gpgsm_format_keydesc (ksba_cert_t cert)
773 {
774   int rc;
775   char *name, *subject, *buffer, *p;
776   const char *s;
777   ksba_isotime_t t;
778   char created[20];
779   char *sn;
780   ksba_sexp_t sexp;
781   char *orig_codeset = NULL;
782
783   name = ksba_cert_get_subject (cert, 0);
784   subject = name? gpgsm_format_name2 (name, 0) : NULL;
785   ksba_free (name); name = NULL;
786
787   sexp = ksba_cert_get_serial (cert);
788   sn = sexp? gpgsm_format_serial (sexp) : NULL;
789   ksba_free (sexp);
790
791   ksba_cert_get_validity (cert, 0, t);
792   if (t && *t)
793     sprintf (created, "%.4s-%.2s-%.2s", t, t+4, t+6);
794   else
795     *created = 0;
796
797
798 #ifdef ENABLE_NLS
799   /* The Assuan agent protocol requires us to transmit utf-8 strings */
800   orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL);
801 #ifdef HAVE_LANGINFO_CODESET
802   if (!orig_codeset)
803     orig_codeset = nl_langinfo (CODESET);
804 #endif
805   if (orig_codeset)
806     { /* We only switch when we are able to restore the codeset later.
807          Note that bind_textdomain_codeset does only return on memory
808          errors but not if a codeset is not available.  Thus we don't
809          bother printing a diagnostic here. */
810       orig_codeset = xstrdup (orig_codeset);
811       if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8"))
812         orig_codeset = NULL; 
813     }
814 #endif
815
816
817   rc = asprintf (&name,
818                  _("Please enter the passphrase to unlock the"
819                    " secret key for:\n"
820                    "\"%s\"\n"
821                    "S/N %s, ID %08lX, created %s" ),
822                  subject? subject:"?",
823                  sn? sn: "?",
824                  gpgsm_get_short_fingerprint (cert),
825                  created);
826
827 #ifdef ENABLE_NLS
828   if (orig_codeset)
829     bind_textdomain_codeset (PACKAGE_GT, orig_codeset);
830 #endif
831   xfree (orig_codeset);
832
833   if (rc < 0)
834     {
835       int save_errno = errno;
836       xfree (subject);
837       xfree (sn);
838       errno = save_errno;
839       return NULL;
840     }
841   
842   xfree (subject);
843   xfree (sn);
844
845   buffer = p = xtrymalloc (strlen (name) * 3 + 1);
846   for (s=name; *s; s++)
847     {
848       if (*s < ' ' || *s == '+')
849         {
850           sprintf (p, "%%%02X", *(unsigned char *)s);
851           p += 3;
852         }
853       else if (*s == ' ')
854         *p++ = '+';
855       else
856         *p++ = *s;
857     }
858   *p = 0;
859   free (name); 
860
861   return buffer;
862 }
863