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