gpg: Take care to use pubring.kbx if it has ever been used.
[gnupg.git] / kbx / keybox-dump.c
1 /* keybox-dump.c - Debug helpers
2  *      Copyright (C) 2001, 2003 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 <stdio.h>
23 #include <string.h>
24 #include <errno.h>
25
26 #include "keybox-defs.h"
27 #include <gcrypt.h>
28
29 /* Argg, we can't include ../common/util.h */
30 char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf);
31
32
33 static ulong
34 get32 (const byte *buffer)
35 {
36   ulong a;
37   a =  *buffer << 24;
38   a |= buffer[1] << 16;
39   a |= buffer[2] << 8;
40   a |= buffer[3];
41   return a;
42 }
43
44 static ulong
45 get16 (const byte *buffer)
46 {
47   ulong a;
48   a =  *buffer << 8;
49   a |= buffer[1];
50   return a;
51 }
52
53 void
54 print_string (FILE *fp, const byte *p, size_t n, int delim)
55 {
56   for ( ; n; n--, p++ )
57     {
58       if (*p < 0x20 || (*p >= 0x7f && *p < 0xa0) || *p == delim)
59         {
60           putc('\\', fp);
61           if( *p == '\n' )
62             putc('n', fp);
63           else if( *p == '\r' )
64             putc('r', fp);
65           else if( *p == '\f' )
66             putc('f', fp);
67           else if( *p == '\v' )
68             putc('v', fp);
69           else if( *p == '\b' )
70             putc('b', fp);
71           else if( !*p )
72             putc('0', fp);
73           else
74             fprintf(fp, "x%02x", *p );
75         }
76       else
77         putc(*p, fp);
78     }
79 }
80
81
82 static int
83 print_checksum (const byte *buffer, size_t length, size_t unhashed, FILE *fp)
84 {
85   const byte *p;
86   int i;
87   int hashlen;
88   unsigned char digest[20];
89
90   fprintf (fp, "Checksum: ");
91   if (unhashed && unhashed < 20)
92     {
93       fputs ("[specified unhashed sized too short]\n", fp);
94       return 0;
95     }
96   if (!unhashed)
97     {
98       unhashed = 16;
99       hashlen = 16;
100     }
101   else
102     hashlen = 20;
103   if (length < 5+unhashed)
104     {
105       fputs ("[blob too short for a checksum]\n", fp);
106       return 0;
107     }
108
109   p = buffer + length - hashlen;
110   for (i=0; i < hashlen; p++, i++)
111     fprintf (fp, "%02x", *p);
112
113   if (hashlen == 16) /* Compatibility method.  */
114     {
115       gcry_md_hash_buffer (GCRY_MD_MD5, digest, buffer, length - 16);
116       if (!memcmp (buffer + length - 16, digest, 16))
117         fputs (" [valid]\n", fp);
118       else
119         fputs (" [bad]\n", fp);
120     }
121   else
122     {
123       gcry_md_hash_buffer (GCRY_MD_SHA1, digest, buffer, length - unhashed);
124       if (!memcmp (buffer + length - hashlen, digest, hashlen))
125         fputs (" [valid]\n", fp);
126       else
127         fputs (" [bad]\n", fp);
128     }
129   return 0;
130 }
131
132
133 static int
134 dump_header_blob (const byte *buffer, size_t length, FILE *fp)
135 {
136   unsigned long n;
137
138   if (length < 32)
139     {
140       fprintf (fp, "[blob too short]\n");
141       return -1;
142     }
143   fprintf (fp, "Version: %d\n", buffer[5]);
144
145   n = get16 (buffer + 6);
146   fprintf( fp, "Flags:   %04lX", n);
147   if (n)
148     {
149       int any = 0;
150
151       fputs (" (", fp);
152       if ((n & 2))
153         {
154           if (any)
155             putc (',', fp);
156           fputs ("openpgp", fp);
157           any++;
158         }
159       putc (')', fp);
160     }
161   putc ('\n', fp);
162
163   if ( memcmp (buffer+8, "KBXf", 4))
164     fprintf (fp, "[Error: invalid magic number]\n");
165
166   n = get32 (buffer+16);
167   fprintf( fp, "created-at: %lu\n", n );
168   n = get32 (buffer+20);
169   fprintf( fp, "last-maint: %lu\n", n );
170
171   return 0;
172 }
173
174 \f
175 /* Dump one block to FP */
176 int
177 _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp)
178 {
179   const byte *buffer;
180   size_t length;
181   int type, i;
182   ulong n, nkeys, keyinfolen;
183   ulong nuids, uidinfolen;
184   ulong nsigs, siginfolen;
185   ulong rawdata_off, rawdata_len;
186   ulong nserial;
187   ulong unhashed;
188   const byte *p;
189
190   buffer = _keybox_get_blob_image (blob, &length);
191
192   if (length < 32)
193     {
194       fprintf (fp, "[blob too short]\n");
195       return -1;
196     }
197
198   n = get32( buffer );
199   if (n > length)
200     fprintf (fp, "[blob larger than length - output truncated]\n");
201   else
202     length = n;  /* ignore the rest */
203
204   fprintf (fp, "Length: %lu\n", n );
205   type = buffer[4];
206   switch (type)
207     {
208     case BLOBTYPE_EMPTY:
209       fprintf (fp, "Type:   Empty\n");
210       return 0;
211
212     case BLOBTYPE_HEADER:
213       fprintf (fp, "Type:   Header\n");
214       return dump_header_blob (buffer, length, fp);
215     case BLOBTYPE_PGP:
216       fprintf (fp, "Type:   OpenPGP\n");
217       break;
218     case BLOBTYPE_X509:
219       fprintf (fp, "Type:   X.509\n");
220       break;
221     default:
222       fprintf (fp, "Type:   %d\n", type);
223       fprintf (fp, "[can't dump this blob type]\n");
224       return 0;
225     }
226   fprintf (fp, "Version: %d\n", buffer[5]);
227
228   if (length < 40)
229     {
230       fprintf (fp, "[blob too short]\n");
231       return -1;
232     }
233
234   n = get16 (buffer + 6);
235   fprintf( fp, "Blob-Flags: %04lX", n);
236   if (n)
237     {
238       int any = 0;
239
240       fputs (" (", fp);
241       if ((n & 1))
242         {
243           fputs ("secret", fp);
244           any++;
245         }
246       if ((n & 2))
247         {
248           if (any)
249             putc (',', fp);
250           fputs ("ephemeral", fp);
251           any++;
252         }
253       putc (')', fp);
254     }
255   putc ('\n', fp);
256
257   rawdata_off = get32 (buffer + 8);
258   rawdata_len = get32 (buffer + 12);
259
260   fprintf( fp, "Data-Offset: %lu\n", rawdata_off );
261   fprintf( fp, "Data-Length: %lu\n", rawdata_len );
262   if (rawdata_off > length || rawdata_len > length
263       || rawdata_off+rawdata_len > length
264       || rawdata_len + 4 > length
265       || rawdata_off+rawdata_len + 4 > length)
266     fprintf (fp, "[Error: raw data larger than blob]\n");
267   unhashed = length - rawdata_off - rawdata_len;
268   fprintf (fp, "Unhashed: %lu\n", unhashed);
269
270   nkeys = get16 (buffer + 16);
271   fprintf (fp, "Key-Count: %lu\n", nkeys );
272   if (!nkeys)
273     fprintf (fp, "[Error: no keys]\n");
274   if (nkeys > 1 && type == BLOBTYPE_X509)
275     fprintf (fp, "[Error: only one key allowed for X509]\n");
276
277   keyinfolen = get16 (buffer + 18 );
278   fprintf (fp, "Key-Info-Length: %lu\n", keyinfolen);
279   /* fixme: check bounds */
280   p = buffer + 20;
281   for (n=0; n < nkeys; n++, p += keyinfolen)
282     {
283       ulong kidoff, kflags;
284
285       fprintf (fp, "Key-Fpr[%lu]: ", n );
286       for (i=0; i < 20; i++ )
287         fprintf (fp, "%02X", p[i]);
288       kidoff = get32 (p + 20);
289       fprintf (fp, "\nKey-Kid-Off[%lu]: %lu\n", n, kidoff );
290       fprintf (fp, "Key-Kid[%lu]: ", n );
291       /* fixme: check bounds */
292       for (i=0; i < 8; i++ )
293         fprintf (fp, "%02X", buffer[kidoff+i] );
294       kflags = get16 (p + 24 );
295       fprintf( fp, "\nKey-Flags[%lu]: %04lX\n", n, kflags);
296     }
297
298   /* serial number */
299   fputs ("Serial-No: ", fp);
300   nserial = get16 (p);
301   p += 2;
302   if (!nserial)
303     fputs ("none", fp);
304   else
305     {
306       for (; nserial; nserial--, p++)
307         fprintf (fp, "%02X", *p);
308     }
309   putc ('\n', fp);
310
311   /* user IDs */
312   nuids = get16 (p);
313   fprintf (fp, "Uid-Count: %lu\n", nuids );
314   uidinfolen = get16  (p + 2);
315   fprintf (fp, "Uid-Info-Length: %lu\n", uidinfolen);
316   /* fixme: check bounds */
317   p += 4;
318   for (n=0; n < nuids; n++, p += uidinfolen)
319     {
320       ulong uidoff, uidlen, uflags;
321
322       uidoff = get32( p );
323       uidlen = get32( p+4 );
324       if (type == BLOBTYPE_X509 && !n)
325         {
326           fprintf (fp, "Issuer-Off: %lu\n", uidoff );
327           fprintf (fp, "Issuer-Len: %lu\n", uidlen );
328           fprintf (fp, "Issuer: \"");
329         }
330       else if (type == BLOBTYPE_X509 && n == 1)
331         {
332           fprintf (fp, "Subject-Off: %lu\n", uidoff );
333           fprintf (fp, "Subject-Len: %lu\n", uidlen );
334           fprintf (fp, "Subject: \"");
335         }
336       else
337         {
338           fprintf (fp, "Uid-Off[%lu]: %lu\n", n, uidoff );
339           fprintf (fp, "Uid-Len[%lu]: %lu\n", n, uidlen );
340           fprintf (fp, "Uid[%lu]: \"", n );
341         }
342       print_string (fp, buffer+uidoff, uidlen, '\"');
343       fputs ("\"\n", fp);
344       uflags = get16 (p + 8);
345       if (type == BLOBTYPE_X509 && !n)
346         {
347           fprintf (fp, "Issuer-Flags: %04lX\n", uflags );
348           fprintf (fp, "Issuer-Validity: %d\n", p[10] );
349         }
350       else if (type == BLOBTYPE_X509 && n == 1)
351         {
352           fprintf (fp, "Subject-Flags: %04lX\n", uflags );
353           fprintf (fp, "Subject-Validity: %d\n", p[10] );
354         }
355       else
356         {
357           fprintf (fp, "Uid-Flags[%lu]: %04lX\n", n, uflags );
358           fprintf (fp, "Uid-Validity[%lu]: %d\n", n, p[10] );
359         }
360     }
361
362   nsigs = get16 (p);
363   fprintf (fp, "Sig-Count: %lu\n", nsigs );
364   siginfolen = get16 (p + 2);
365   fprintf (fp, "Sig-Info-Length: %lu\n", siginfolen );
366   /* fixme: check bounds  */
367   p += 4;
368   {
369     int in_range = 0;
370     ulong first = 0;
371
372     for (n=0; n < nsigs; n++, p += siginfolen)
373       {
374         ulong sflags;
375
376         sflags = get32 (p);
377         if (!in_range && !sflags)
378           {
379             in_range = 1;
380             first = n;
381             continue;
382           }
383         if (in_range && !sflags)
384           continue;
385         if (in_range)
386           {
387             fprintf (fp, "Sig-Expire[%lu-%lu]: [not checked]\n", first, n-1);
388             in_range = 0;
389           }
390
391         fprintf (fp, "Sig-Expire[%lu]: ", n );
392         if (!sflags)
393           fputs ("[not checked]", fp);
394         else if (sflags == 1 )
395           fputs ("[missing key]", fp);
396         else if (sflags == 2 )
397           fputs ("[bad signature]", fp);
398         else if (sflags < 0x10000000)
399           fprintf (fp, "[bad flag %0lx]", sflags);
400         else if (sflags == (ulong)(-1))
401           fputs ("[good - does not expire]", fp );
402         else
403           fprintf (fp, "[good - expires at %lu]", sflags);
404         putc ('\n', fp );
405       }
406     if (in_range)
407       {
408         fprintf (fp, "Sig-Expire[%lu-%lu]: [not checked]\n", first, n-1);
409         in_range = 0;
410       }
411   }
412   fprintf (fp, "Ownertrust: %d\n", p[0] );
413   fprintf (fp, "All-Validity: %d\n", p[1] );
414   p += 4;
415   n = get32 (p); p += 4;
416   fprintf (fp, "Recheck-After: %lu\n", n );
417   n = get32 (p ); p += 4;
418   fprintf( fp, "Latest-Timestamp: %lu\n", n );
419   n = get32 (p ); p += 4;
420   fprintf (fp, "Created-At: %lu\n", n );
421   n = get32 (p ); p += 4;
422   fprintf (fp, "Reserved-Space: %lu\n", n );
423
424   if (n >= 4 && unhashed >= 24)
425     {
426       n = get32 ( buffer + length - unhashed);
427       fprintf (fp, "Storage-Flags: %08lx\n", n );
428     }
429   print_checksum (buffer, length, unhashed, fp);
430   return 0;
431 }
432
433
434 /* Compute the SHA-1 checksum of the rawdata in BLOB and put it into
435    DIGEST. */
436 static int
437 hash_blob_rawdata (KEYBOXBLOB blob, unsigned char *digest)
438 {
439   const unsigned char *buffer;
440   size_t n, length;
441   int type;
442   ulong rawdata_off, rawdata_len;
443
444   buffer = _keybox_get_blob_image (blob, &length);
445
446   if (length < 32)
447     return -1;
448   n = get32 (buffer);
449   if (n < length)
450     length = n;  /* Blob larger than length in header - ignore the rest. */
451
452   type = buffer[4];
453   switch (type)
454     {
455     case BLOBTYPE_PGP:
456     case BLOBTYPE_X509:
457       break;
458
459     case BLOBTYPE_EMPTY:
460     case BLOBTYPE_HEADER:
461     default:
462       memset (digest, 0, 20);
463       return 0;
464     }
465
466   if (length < 40)
467     return -1;
468
469   rawdata_off = get32 (buffer + 8);
470   rawdata_len = get32 (buffer + 12);
471
472   if (rawdata_off > length || rawdata_len > length
473       || rawdata_off+rawdata_off > length)
474     return -1; /* Out of bounds.  */
475
476   gcry_md_hash_buffer (GCRY_MD_SHA1, digest, buffer+rawdata_off, rawdata_len);
477   return 0;
478 }
479
480
481 struct file_stats_s
482 {
483   unsigned long too_short_blobs;
484   unsigned long too_large_blobs;
485   unsigned long total_blob_count;
486   unsigned long empty_blob_count;
487   unsigned long header_blob_count;
488   unsigned long pgp_blob_count;
489   unsigned long x509_blob_count;
490   unsigned long unknown_blob_count;
491   unsigned long non_flagged;
492   unsigned long secret_flagged;
493   unsigned long ephemeral_flagged;
494 };
495
496 static int
497 update_stats (KEYBOXBLOB blob, struct file_stats_s *s)
498 {
499   const unsigned char *buffer;
500   size_t length;
501   int type;
502   unsigned long n;
503
504   buffer = _keybox_get_blob_image (blob, &length);
505   if (length < 32)
506     {
507       s->too_short_blobs++;
508       return -1;
509     }
510
511   n = get32( buffer );
512   if (n > length)
513     s->too_large_blobs++;
514   else
515     length = n;  /* ignore the rest */
516
517   s->total_blob_count++;
518   type = buffer[4];
519   switch (type)
520     {
521     case BLOBTYPE_EMPTY:
522       s->empty_blob_count++;
523       return 0;
524     case BLOBTYPE_HEADER:
525       s->header_blob_count++;
526       return 0;
527     case BLOBTYPE_PGP:
528       s->pgp_blob_count++;
529       break;
530     case BLOBTYPE_X509:
531       s->x509_blob_count++;
532       break;
533     default:
534       s->unknown_blob_count++;
535       return 0;
536     }
537
538   if (length < 40)
539     {
540       s->too_short_blobs++;
541       return -1;
542     }
543
544   n = get16 (buffer + 6);
545   if (n)
546     {
547       if ((n & 1))
548         s->secret_flagged++;
549       if ((n & 2))
550         s->ephemeral_flagged++;
551     }
552   else
553     s->non_flagged++;
554
555   return 0;
556 }
557
558
559 \f
560 static FILE *
561 open_file (const char **filename, FILE *outfp)
562 {
563   FILE *fp;
564
565   if (!*filename)
566     {
567       *filename = "-";
568       fp = stdin;
569     }
570   else
571     fp = fopen (*filename, "rb");
572   if (!fp)
573     {
574       int save_errno = errno;
575       fprintf (outfp, "can't open '%s': %s\n", *filename, strerror(errno));
576       gpg_err_set_errno (save_errno);
577     }
578   return fp;
579 }
580
581
582
583 int
584 _keybox_dump_file (const char *filename, int stats_only, FILE *outfp)
585 {
586   FILE *fp;
587   KEYBOXBLOB blob;
588   int rc;
589   unsigned long count = 0;
590   struct file_stats_s stats;
591
592   memset (&stats, 0, sizeof stats);
593
594   if (!(fp = open_file (&filename, outfp)))
595     return gpg_error_from_syserror ();
596
597   while ( !(rc = _keybox_read_blob (&blob, fp)) )
598     {
599       if (stats_only)
600         {
601           update_stats (blob, &stats);
602         }
603       else
604         {
605           fprintf (outfp, "BEGIN-RECORD: %lu\n", count );
606           _keybox_dump_blob (blob, outfp);
607           fprintf (outfp, "END-RECORD\n");
608         }
609       _keybox_release_blob (blob);
610       count++;
611     }
612   if (rc == -1)
613     rc = 0;
614   if (rc)
615     fprintf (outfp, "error reading '%s': %s\n", filename, gpg_strerror (rc));
616
617   if (fp != stdin)
618     fclose (fp);
619
620   if (stats_only)
621     {
622       fprintf (outfp,
623                "Total number of blobs: %8lu\n"
624                "               header: %8lu\n"
625                "                empty: %8lu\n"
626                "              openpgp: %8lu\n"
627                "                 x509: %8lu\n"
628                "          non flagged: %8lu\n"
629                "       secret flagged: %8lu\n"
630                "    ephemeral flagged: %8lu\n",
631                stats.total_blob_count,
632                stats.header_blob_count,
633                stats.empty_blob_count,
634                stats.pgp_blob_count,
635                stats.x509_blob_count,
636                stats.non_flagged,
637                stats.secret_flagged,
638                stats.ephemeral_flagged);
639         if (stats.unknown_blob_count)
640           fprintf (outfp, "   unknown blob types: %8lu\n",
641                    stats.unknown_blob_count);
642         if (stats.too_short_blobs)
643           fprintf (outfp, "      too short blobs: %8lu\n",
644                    stats.too_short_blobs);
645         if (stats.too_large_blobs)
646           fprintf (outfp, "      too large blobs: %8lu\n",
647                    stats.too_large_blobs);
648     }
649
650   return rc;
651 }
652
653
654 \f
655 struct dupitem_s
656 {
657   unsigned long recno;
658   unsigned char digest[20];
659 };
660
661
662 static int
663 cmp_dupitems (const void *arg_a, const void *arg_b)
664 {
665   struct dupitem_s *a = (struct dupitem_s *)arg_a;
666   struct dupitem_s *b = (struct dupitem_s *)arg_b;
667
668   return memcmp (a->digest, b->digest, 20);
669 }
670
671
672 int
673 _keybox_dump_find_dups (const char *filename, int print_them, FILE *outfp)
674 {
675   FILE *fp;
676   KEYBOXBLOB blob;
677   int rc;
678   unsigned long recno = 0;
679   unsigned char zerodigest[20];
680   struct dupitem_s *dupitems;
681   size_t dupitems_size, dupitems_count, lastn, n;
682   char fprbuf[3*20+1];
683
684   (void)print_them;
685
686   memset (zerodigest, 0, sizeof zerodigest);
687
688   if (!(fp = open_file (&filename, outfp)))
689     return gpg_error_from_syserror ();
690
691   dupitems_size = 1000;
692   dupitems = malloc (dupitems_size * sizeof *dupitems);
693   if (!dupitems)
694     {
695       gpg_error_t tmperr = gpg_error_from_syserror ();
696       fprintf (outfp, "error allocating array for '%s': %s\n",
697                filename, strerror(errno));
698       return tmperr;
699     }
700   dupitems_count = 0;
701
702   while ( !(rc = _keybox_read_blob (&blob, fp)) )
703     {
704       unsigned char digest[20];
705
706       if (hash_blob_rawdata (blob, digest))
707         fprintf (outfp, "error in blob %ld of '%s'\n", recno, filename);
708       else if (memcmp (digest, zerodigest, 20))
709         {
710           if (dupitems_count >= dupitems_size)
711             {
712               struct dupitem_s *tmp;
713
714               dupitems_size += 1000;
715               tmp = realloc (dupitems, dupitems_size * sizeof *dupitems);
716               if (!tmp)
717                 {
718                   gpg_error_t tmperr = gpg_error_from_syserror ();
719                   fprintf (outfp, "error reallocating array for '%s': %s\n",
720                            filename, strerror(errno));
721                   free (dupitems);
722                   return tmperr;
723                 }
724               dupitems = tmp;
725             }
726           dupitems[dupitems_count].recno = recno;
727           memcpy (dupitems[dupitems_count].digest, digest, 20);
728           dupitems_count++;
729         }
730       _keybox_release_blob (blob);
731       recno++;
732     }
733   if (rc == -1)
734     rc = 0;
735   if (rc)
736     fprintf (outfp, "error reading '%s': %s\n", filename, gpg_strerror (rc));
737   if (fp != stdin)
738     fclose (fp);
739
740   qsort (dupitems, dupitems_count, sizeof *dupitems, cmp_dupitems);
741
742   for (lastn=0, n=1; n < dupitems_count; lastn=n, n++)
743     {
744       if (!memcmp (dupitems[lastn].digest, dupitems[n].digest, 20))
745         {
746           bin2hexcolon (dupitems[lastn].digest, 20, fprbuf);
747           fprintf (outfp, "fpr=%s recno=%lu", fprbuf, dupitems[lastn].recno);
748           do
749             fprintf (outfp, " %lu", dupitems[n].recno);
750           while (++n < dupitems_count
751                  && !memcmp (dupitems[lastn].digest, dupitems[n].digest, 20));
752           putc ('\n', outfp);
753           n--;
754         }
755     }
756
757   free (dupitems);
758
759   return rc;
760 }
761
762
763 /* Print records with record numbers FROM to TO to OUTFP.  */
764 int
765 _keybox_dump_cut_records (const char *filename, unsigned long from,
766                           unsigned long to, FILE *outfp)
767 {
768   FILE *fp;
769   KEYBOXBLOB blob;
770   int rc;
771   unsigned long recno = 0;
772
773   if (!(fp = open_file (&filename, stderr)))
774     return gpg_error_from_syserror ();
775
776   while ( !(rc = _keybox_read_blob (&blob, fp)) )
777     {
778       if (recno > to)
779         break; /* Ready.  */
780       if (recno >= from)
781         {
782           if ((rc = _keybox_write_blob (blob, outfp)))
783             {
784               fprintf (stderr, "error writing output: %s\n",
785                        gpg_strerror (rc));
786               goto leave;
787             }
788         }
789       _keybox_release_blob (blob);
790       recno++;
791     }
792   if (rc == -1)
793     rc = 0;
794   if (rc)
795     fprintf (stderr, "error reading '%s': %s\n", filename, gpg_strerror (rc));
796  leave:
797   if (fp != stdin)
798     fclose (fp);
799   return rc;
800 }