1 /* dumpsexp.c - Dump S-expressions.
2  * Copyright (C) 2007, 2010 Free Software Foundation, Inc.
3  * Copyright (C) 2010 g10 Code GmbH.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as
7  * published by the Free Software Foundation; either version 2.1 of
8  * the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <assert.h>
24 #include <stdarg.h>
25 #include <errno.h>
26 /* For a native WindowsCE binary we need to include gpg-error.h to
27    provide a replacement for strerror.  */
28 #ifdef __MINGW32CE__
29 # include <gpg-error.h>
30 #endif
31 
32 #define PGM "dumpsexp"
33 #define MYVERSION_LINE PGM " (Libgcrypt) " VERSION
34 #define BUGREPORT_LINE "\nReport bugs to <bug-libgcrypt@gnupg.org>.\n"
35 
36 
37 static int verbose;  /* Verbose mode.  */
38 static int decimal;  /* Print addresses in decimal.  */
39 static int assume_hex;  /* Assume input is hexencoded.  */
40 static int advanced; /* Advanced format output.  */
41 
42 static void
print_version(int with_help)43 print_version (int with_help)
44 {
45   fputs (MYVERSION_LINE "\n"
46          "Copyright (C) 2010 Free Software Foundation, Inc.\n"
47          "License LGPLv2.1+: GNU LGPL version 2.1 or later "
48          "<http://gnu.org/licenses/>\n"
49          "This is free software: you are free to change and redistribute it.\n"
50          "There is NO WARRANTY, to the extent permitted by law.\n",
51          stdout);
52 
53   if (with_help)
54     fputs ("\n"
55            "Usage: " PGM " [OPTIONS] [file]\n"
56            "Debug tool for S-expressions\n"
57            "\n"
58            "  --decimal     Print offsets using decimal notation\n"
59            "  --assume-hex  Assume input is a hex dump\n"
60            "  --advanced    Print file in advanced format\n"
61            "  --verbose     Show what we are doing\n"
62            "  --version     Print version of the program and exit\n"
63            "  --help        Display this help and exit\n"
64            BUGREPORT_LINE, stdout );
65 
66   exit (0);
67 }
68 
69 static int
print_usage(void)70 print_usage (void)
71 {
72   fputs ("usage: " PGM " [OPTIONS] NBYTES\n", stderr);
73   fputs ("       (use --help to display options)\n", stderr);
74   exit (1);
75 }
76 
77 
78 #define space_p(a)    ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
79 #define digit_p(a)    ((a) >= '0' && (a) <= '9')
80 #define octdigit_p(a) ((a) >= '0' && (a) <= '7')
81 #define alpha_p(a)    (   ((a) >= 'A' && (a) <= 'Z')  \
82                        || ((a) >= 'a' && (a) <= 'z'))
83 #define hexdigit_p(a) (digit_p (a)                     \
84                        || ((a) >= 'A' && (a) <= 'F')  \
85                        || ((a) >= 'a' && (a) <= 'f'))
86 #define xtoi_1(a)     ((a) <= '9'? ((a)- '0'): \
87                        (a) <= 'F'? ((a)-'A'+10):((a)-'a'+10))
88 
89 
90 /* Return true if P points to a byte containing a whitespace according
91    to the S-expressions definition. */
92 static inline int
whitespace_p(int c)93 whitespace_p (int c)
94 {
95   switch (c)
96     {
97     case ' ': case '\t': case '\v': case '\f': case '\r': case '\n': return 1;
98     default: return 0;
99     }
100 }
101 
102 static void
logit(const char * format,...)103 logit (const char *format, ...)
104 {
105   va_list arg_ptr;
106 
107   va_start (arg_ptr, format) ;
108   fputs (PGM ": ", stderr);
109   vfprintf (stderr, format, arg_ptr);
110   putc ('\n', stderr);
111   va_end (arg_ptr);
112 }
113 
114 /* The raw data buffer and its current length */
115 static unsigned char databuffer[16];
116 static int databufferlen;
117 /* The number of bytes in databuffer which should be skipped at a flush.  */
118 static int skipdatabufferlen;
119 /* The number of raw bytes printed on the last line.  */
120 static int nbytesprinted;
121 /* The file offset of the current data buffer .  */
122 static unsigned long databufferoffset;
123 
124 
125 
126 static int
my_getc(FILE * fp)127 my_getc (FILE *fp)
128 {
129   int c1, c2;
130 
131   if (!assume_hex)
132     return getc (fp);
133 
134   while ( (c1=getc (fp)) != EOF && space_p (c1) )
135     ;
136   if (c1 == EOF)
137     return EOF;
138 
139   if (!hexdigit_p (c1))
140     {
141       logit ("non hex-digit encountered\n");
142       return EOF;
143     }
144 
145   while ( (c2=getc (fp)) != EOF && space_p (c2) )
146     ;
147   if (c2 == EOF)
148     {
149       logit ("error reading second hex nibble\n");
150       return EOF;
151     }
152   if (!hexdigit_p (c2))
153     {
154       logit ("second hex nibble is not a hex-digit\n");
155       return EOF;
156     }
157   return xtoi_1 (c1) * 16 + xtoi_1 (c2);
158 }
159 
160 
161 
162 
163 
164 /* Flush the raw data buffer.  */
165 static void
flushdatabuffer(void)166 flushdatabuffer (void)
167 {
168   int i;
169 
170   if (!databufferlen)
171     return;
172   nbytesprinted = 0;
173   if (decimal)
174     printf ("%08lu ", databufferoffset);
175   else
176     printf ("%08lx ", databufferoffset);
177   for (i=0; i < databufferlen; i++)
178     {
179       if (i == 8)
180         putchar (' ');
181       if (i < skipdatabufferlen)
182         fputs ("   ", stdout);
183       else
184         {
185           printf (" %02x", databuffer[i]);
186           databufferoffset++;
187         }
188       nbytesprinted++;
189     }
190   for (; i < sizeof (databuffer); i++)
191     {
192       if (i == 8)
193         putchar (' ');
194       fputs ("   ", stdout);
195     }
196   fputs ("  |", stdout);
197   for (i=0; i < databufferlen; i++)
198     {
199       if (i < skipdatabufferlen)
200         putchar (' ');
201       else if (databuffer[i] >= ' ' && databuffer[i] <= '~'
202                && databuffer[i] != '|')
203         putchar (databuffer[i]);
204       else
205         putchar ('.');
206     }
207   putchar ('|');
208   putchar ('\n');
209   databufferlen = 0;
210   skipdatabufferlen = 0;
211 }
212 
213 
214 /* Add C to the raw data buffer and flush as needed.  */
215 static void
addrawdata(int c)216 addrawdata (int c)
217 {
218   if ( databufferlen >= sizeof databuffer )
219     flushdatabuffer ();
220   databuffer[databufferlen++] = c;
221 }
222 
223 
224 static void
printcursor(int both)225 printcursor (int both)
226 {
227   int i;
228 
229   flushdatabuffer ();
230   printf ("%8s ", "");
231   for (i=0; i < sizeof (databuffer); i++)
232     {
233       if (i == 8)
234         putchar (' ');
235       if (i+1 == nbytesprinted)
236         {
237           fputs (" ^ ", stdout);
238           if (!both)
239             break;
240         }
241       else
242         fputs ("   ", stdout);
243     }
244   if (both)
245     {
246       fputs ("   ", stdout);
247       for (i=0; i < nbytesprinted-1; i++)
248         putchar (' ');
249       putchar ('^');
250     }
251   databufferlen = skipdatabufferlen = nbytesprinted;
252 }
253 
254 static void
printerr(const char * text)255 printerr (const char *text)
256 {
257   printcursor (1);
258   printf ("\n          Error: %s\n", text);
259 }
260 
261 static void
printctl(const char * text)262 printctl (const char *text)
263 {
264   if (verbose && !advanced)
265     {
266       printcursor (0);
267       printf ("%s\n", text);
268     }
269 }
270 
271 static void
printchr(int c)272 printchr (int c)
273 {
274   putchar (c);
275 }
276 
277 /* static void */
278 /* printhex (int c) */
279 /* { */
280 /*   printf ("\\x%02x", c); */
281 /* } */
282 
283 
284 #if 0
285 /****************
286  * Print SEXP to buffer using the MODE.  Returns the length of the
287  * SEXP in buffer or 0 if the buffer is too short (We have at least an
288  * empty list consisting of 2 bytes).  If a buffer of NULL is provided,
289  * the required length is returned.
290  */
291 size_t
292 gcry_sexp_sprint (const gcry_sexp_t list,
293                   void *buffer, size_t maxlength )
294 {
295   static unsigned char empty[3] = { ST_OPEN, ST_CLOSE, ST_STOP };
296   const unsigned char *s;
297   char *d;
298   DATALEN n;
299   char numbuf[20];
300   int i, indent = 0;
301 
302   s = list? list->d : empty;
303   d = buffer;
304   while ( *s != ST_STOP )
305     {
306       switch ( *s )
307         {
308         case ST_OPEN:
309           s++;
310           if (indent)
311             putchar ('\n');
312           for (i=0; i < indent; i++)
313             putchar (' ');
314           putchar ('(');
315           indent++;
316           break;
317         case ST_CLOSE:
318           s++;
319           putchar (')');
320           indent--;
321           if (*s != ST_OPEN && *s != ST_STOP)
322             {
323               putchar ('\n');
324               for (i=0; i < indent; i++)
325                 putchar (' ');
326             }
327           break;
328         case ST_DATA:
329           s++;
330           memcpy (&n, s, sizeof n);
331           s += sizeof n;
332           {
333             int type;
334             size_t nn;
335 
336             switch ( (type=suitable_encoding (s, n)))
337               {
338               case 1: nn = convert_to_string (s, n, NULL); break;
339               case 2: nn = convert_to_token (s, n, NULL); break;
340               default: nn = convert_to_hex (s, n, NULL); break;
341               }
342             switch (type)
343               {
344               case 1: convert_to_string (s, n, d); break;
345               case 2: convert_to_token (s, n, d); break;
346               default: convert_to_hex (s, n, d); break;
347               }
348             d += nn;
349             if (s[n] != ST_CLOSE)
350               putchar (' ');
351           }
352           else
353             {
354               snprintf (numbuf, sizeof numbuf,  "%u:", (unsigned int)n );
355               d = stpcpy (d, numbuf);
356               memcpy (d, s, n);
357               d += n;
358             }
359           s += n;
360           break;
361         default:
362           BUG ();
363 	}
364     }
365   putchar ('\n');
366   return len;
367 }
368 #endif
369 
370 
371 /* Prepare for saving a chunk of data.  */
372 static void
init_data(void)373 init_data (void)
374 {
375 
376 }
377 
378 /* Push C on the current data chunk.  */
379 static void
push_data(int c)380 push_data (int c)
381 {
382   (void)c;
383 }
384 
385 /* Flush and thus print the current data chunk.  */
386 static void
flush_data(void)387 flush_data (void)
388 {
389 
390 }
391 
392 
393 /* Returns 0 on success.  */
394 static int
parse_and_print(FILE * fp)395 parse_and_print (FILE *fp)
396 {
397   static const char tokenchars[] =
398     "abcdefghijklmnopqrstuvwxyz"
399     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
400     "0123456789-./_:*+=";
401   int c;
402   int level = 0;
403   int tokenc = 0;
404   int hexcount = 0;
405   int disphint = 0;
406   unsigned long datalen = 0;
407   char quote_buf[10];
408   int quote_idx = 0;
409   enum
410     {
411       INIT_STATE = 0, IN_NUMBER, PRE_DATA, IN_DATA, IN_STRING,
412       IN_ESCAPE, IN_OCT_ESC, IN_HEX_ESC,
413       CR_ESC, LF_ESC, IN_HEXFMT, IN_BASE64
414     }
415   state = INIT_STATE;
416 
417 
418   while ((c = my_getc (fp)) != EOF )
419     {
420       addrawdata (c);
421       switch (state)
422         {
423         case INIT_STATE:
424           if (tokenc)
425             {
426               if (strchr (tokenchars, c))
427                 {
428                   printchr (c);
429                   continue;
430                 }
431               tokenc = 0;
432             }
433         parse_init_state:
434           if (c == '(')
435             {
436               if (disphint)
437                 {
438                   printerr ("unmatched display hint");
439                   disphint = 0;
440                 }
441               printctl ("open");
442               level++;
443             }
444           else if (c == ')')
445             {
446               if (disphint)
447                 {
448                   printerr ("unmatched display hint");
449                   disphint = 0;
450                 }
451               printctl ("close");
452               level--;
453             }
454           else if (c == '\"')
455             {
456               state = IN_STRING;
457               printctl ("beginstring");
458               init_data ();
459             }
460           else if (c == '#')
461             {
462               state = IN_HEXFMT;
463               hexcount = 0;
464               printctl ("beginhex");
465               init_data ();
466             }
467           else if (c == '|')
468             {
469               state = IN_BASE64;
470               printctl ("beginbase64");
471               init_data ();
472             }
473           else if (c == '[')
474             {
475               if (disphint)
476                 printerr ("nested display hint");
477               disphint = c;
478             }
479           else if (c == ']')
480             {
481               if (!disphint)
482                 printerr ("no open display hint");
483               disphint = 0;
484             }
485           else if (c >= '0' && c <= '9')
486             {
487               if (c == '0')
488                 printerr ("zero prefixed length");
489               state = IN_NUMBER;
490               datalen = (c - '0');
491             }
492           else if (strchr (tokenchars, c))
493             {
494               printchr (c);
495               tokenc = c;
496             }
497           else if (whitespace_p (c))
498             ;
499           else if (c == '{')
500             {
501               printerr ("rescanning is not supported");
502             }
503           else if (c == '&' || c == '\\')
504             {
505               printerr ("reserved punctuation detected");
506             }
507           else
508             {
509               printerr ("bad character detected");
510             }
511           break;
512 
513         case IN_NUMBER:
514           if (digit_p (c))
515             {
516               unsigned long tmp = datalen * 10 + (c - '0');
517               if (tmp < datalen)
518                 {
519                   printerr ("overflow in data length");
520                   state = INIT_STATE;
521                   datalen = 0;
522                 }
523               else
524                 datalen = tmp;
525             }
526           else if (c == ':')
527             {
528               if (!datalen)
529                 {
530                   printerr ("no data length");
531                   state = INIT_STATE;
532                 }
533               else
534                 state = PRE_DATA;
535             }
536           else if (c == '\"' || c == '#' || c == '|' )
537             {
538               /* We ignore the optional length and divert to the init
539                  state parser code. */
540               goto parse_init_state;
541             }
542           else
543             printerr ("invalid length specification");
544           break;
545 
546         case PRE_DATA:
547           state = IN_DATA;
548           printctl ("begindata");
549           init_data ();
550 	  /* fall through */
551         case IN_DATA:
552           if (datalen)
553             {
554               push_data (c);
555               datalen--;
556             }
557           if (!datalen)
558             {
559               state = INIT_STATE;
560               printctl ("enddata");
561               flush_data ();
562             }
563           break;
564 
565         case IN_STRING:
566           if (c == '\"')
567             {
568               printctl ("endstring");
569               flush_data ();
570               state = INIT_STATE;
571             }
572           else if (c == '\\')
573             state = IN_ESCAPE;
574           else
575             push_data (c);
576           break;
577 
578         case IN_ESCAPE:
579           switch (c)
580             {
581             case 'b':  push_data ('\b'); state = IN_STRING; break;
582             case 't':  push_data ('\t'); state = IN_STRING; break;
583             case 'v':  push_data ('\v'); state = IN_STRING; break;
584             case 'n':  push_data ('\n'); state = IN_STRING; break;
585             case 'f':  push_data ('\f'); state = IN_STRING; break;
586             case 'r':  push_data ('\r'); state = IN_STRING; break;
587             case '"':  push_data ('"');  state = IN_STRING; break;
588             case '\'': push_data ('\''); state = IN_STRING; break;
589             case '\\': push_data ('\\'); state = IN_STRING; break;
590 
591             case '0': case '1': case '2': case '3': case '4':
592             case '5': case '6': case '7':
593               state = IN_OCT_ESC;
594               quote_idx = 0;
595               quote_buf[quote_idx++] = c;
596               break;
597 
598             case 'x':
599               state = IN_HEX_ESC;
600               quote_idx = 0;
601               break;
602 
603             case '\r':
604               state = CR_ESC;
605               break;
606 
607             case '\n':
608               state = LF_ESC;
609               break;
610 
611             default:
612               printerr ("invalid escape sequence");
613               state = IN_STRING;
614               break;
615             }
616           break;
617 
618         case IN_OCT_ESC:
619           if (quote_idx < 3 && strchr ("01234567", c))
620             {
621               quote_buf[quote_idx++] = c;
622               if (quote_idx == 3)
623                 {
624                   push_data ((unsigned int)quote_buf[0] * 8 * 8
625                              + (unsigned int)quote_buf[1] * 8
626                              + (unsigned int)quote_buf[2]);
627                   state = IN_STRING;
628                 }
629             }
630           else
631             state = IN_STRING;
632           break;
633         case IN_HEX_ESC:
634           if (quote_idx < 2 && strchr ("0123456789abcdefABCDEF", c))
635             {
636               quote_buf[quote_idx++] = c;
637               if (quote_idx == 2)
638                 {
639                   push_data (xtoi_1 (quote_buf[0]) * 16
640                              + xtoi_1 (quote_buf[1]));
641                   state = IN_STRING;
642                 }
643             }
644           else
645             state = IN_STRING;
646           break;
647         case CR_ESC:
648           state = IN_STRING;
649           break;
650         case LF_ESC:
651           state = IN_STRING;
652           break;
653 
654         case IN_HEXFMT:
655           if (hexdigit_p (c))
656             {
657               push_data (c);
658               hexcount++;
659             }
660           else if (c == '#')
661             {
662               if ((hexcount & 1))
663                 printerr ("odd number of hex digits");
664               printctl ("endhex");
665               flush_data ();
666               state = INIT_STATE;
667             }
668           else if (!whitespace_p (c))
669             printerr ("bad hex character");
670           break;
671 
672         case IN_BASE64:
673           if (c == '|')
674             {
675               printctl ("endbase64");
676               flush_data ();
677               state = INIT_STATE;
678             }
679           else
680             push_data (c);
681           break;
682 
683         default:
684           logit ("invalid state %d detected", state);
685           exit (1);
686         }
687     }
688   flushdatabuffer ();
689   if (ferror (fp))
690     {
691       logit ("error reading input: %s\n", strerror (errno));
692       return -1;
693     }
694   return 0;
695 }
696 
697 
698 
699 int
main(int argc,char ** argv)700 main (int argc, char **argv)
701 {
702   int rc;
703 
704   if (argc)
705     {
706       argc--; argv++;
707     }
708   while (argc && **argv == '-' && (*argv)[1] == '-')
709     {
710       if (!(*argv)[2])
711         {
712           argc--; argv++;
713           break;
714         }
715       else if (!strcmp (*argv, "--version"))
716         print_version (0);
717       else if (!strcmp (*argv, "--help"))
718         print_version (1);
719       else if (!strcmp (*argv, "--verbose"))
720         {
721           argc--; argv++;
722           verbose = 1;
723         }
724       else if (!strcmp (*argv, "--decimal"))
725         {
726           argc--; argv++;
727           decimal = 1;
728         }
729       else if (!strcmp (*argv, "--assume-hex"))
730         {
731           argc--; argv++;
732           assume_hex = 1;
733         }
734       else if (!strcmp (*argv, "--advanced"))
735         {
736           argc--; argv++;
737           advanced = 1;
738         }
739       else
740         print_usage ();
741     }
742 
743   if (!argc)
744     {
745       rc = parse_and_print (stdin);
746     }
747   else
748     {
749       rc = 0;
750       for (; argc; argv++, argc--)
751         {
752           FILE *fp = fopen (*argv, "rb");
753           if (!fp)
754             {
755               logit ("can't open `%s': %s\n", *argv, strerror (errno));
756               rc = 1;
757             }
758           else
759             {
760               if (parse_and_print (fp))
761                 rc = 1;
762               fclose (fp);
763             }
764         }
765     }
766 
767   return !!rc;
768 }
769