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