/* dumpsexp.c - Dump S-expressions. * Copyright (C) 2007 Free Software Foundation, Inc. * * Getrandom is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * Getrandom is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #define PGM "dumpsexp" #define MYVERSION_LINE PGM " (Libgcrypt) " VERSION #define BUGREPORT_LINE "\nReport bugs to .\n" static int verbose; /* Verbose mode. */ static int decimal; /* Print addresses in decimal. */ static int assume_hex; /* Assume input is hexencoded. */ static void print_version (int with_help) { fputs (MYVERSION_LINE "\n" "Copyright (C) 2007 Free Software Foundation, Inc.\n" "License GPLv2+: GNU GPL version 2 or later " "\n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n", stdout); if (with_help) fputs ("\n" "Usage: " PGM " [OPTIONS] [file]\n" "Debug tool for S-expressions\n" "\n" " --decimal Print offsets using decimal notation\n" " --assume-hex Assume input is a hex dump\n" " --verbose Show what we are doing\n" " --version Print version of the program and exit\n" " --help Display this help and exit\n" BUGREPORT_LINE, stdout ); exit (0); } static int print_usage (void) { fputs ("usage: " PGM " [OPTIONS] NBYTES\n", stderr); fputs (" (use --help to display options)\n", stderr); exit (1); } #define space_p(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t') #define digit_p(a) ((a) >= '0' && (a) <= '9') #define octdigit_p(a) ((a) >= '0' && (a) <= '7') #define alpha_p(a) ( ((a) >= 'A' && (a) <= 'Z') \ || ((a) >= 'a' && (a) <= 'z')) #define hexdigit_p(a) (digit_p (a) \ || ((a) >= 'A' && (a) <= 'F') \ || ((a) >= 'a' && (a) <= 'f')) #define xtoi_1(a) ((a) <= '9'? ((a)- '0'): \ (a) <= 'F'? ((a)-'A'+10):((a)-'a'+10)) /* Return true if P points to a byte containing a whitespace according to the S-expressions definition. */ static inline int whitespace_p (int c) { switch (c) { case ' ': case '\t': case '\v': case '\f': case '\r': case '\n': return 1; default: return 0; } } static void logit (const char *format, ...) { va_list arg_ptr; va_start (arg_ptr, format) ; fputs (PGM ": ", stderr); vfprintf (stderr, format, arg_ptr); putc ('\n', stderr); va_end (arg_ptr); } /* The raw data buffer and its current length */ static unsigned char databuffer[16]; static int databufferlen; /* The number of bytes in databuffer which should be skipped at a flush. */ static int skipdatabufferlen; /* The number of raw bytes printed on the last line. */ static int nbytesprinted; /* The file offset of the current data buffer . */ static unsigned long databufferoffset; static int my_getc (FILE *fp) { int c1, c2; if (!assume_hex) return getc (fp); while ( (c1=getc (fp)) != EOF && space_p (c1) ) ; if (c1 == EOF) return EOF; if (!hexdigit_p (c1)) { logit ("non hex-digit encountered\n"); return EOF; } while ( (c2=getc (fp)) != EOF && space_p (c2) ) ; if (c2 == EOF) { logit ("error reading second hex nibble\n"); return EOF; } if (!hexdigit_p (c2)) { logit ("second hex nibble is not a hex-digit\n"); return EOF; } return xtoi_1 (c1) * 16 + xtoi_1 (c2); } /* Flush the raw data buffer. */ static void flushdatabuffer (void) { int i; if (!databufferlen) return; nbytesprinted = 0; if (decimal) printf ("%08lu ", databufferoffset); else printf ("%08lx ", databufferoffset); for (i=0; i < databufferlen; i++) { if (i == 8) putchar (' '); if (i < skipdatabufferlen) fputs (" ", stdout); else { printf (" %02x", databuffer[i]); databufferoffset++; } nbytesprinted++; } for (; i < sizeof (databuffer); i++) { if (i == 8) putchar (' '); fputs (" ", stdout); } fputs (" |", stdout); for (i=0; i < databufferlen; i++) { if (i < skipdatabufferlen) putchar (' '); else if (databuffer[i] >= ' ' && databuffer[i] <= '~' && databuffer[i] != '|') putchar (databuffer[i]); else putchar ('.'); } putchar ('|'); putchar ('\n'); databufferlen = 0; skipdatabufferlen = 0; } /* Add C to the raw data buffer and flush as needed. */ static void addrawdata (int c) { if ( databufferlen >= sizeof databuffer ) flushdatabuffer (); databuffer[databufferlen++] = c; } static void printcursor (int both) { int i; flushdatabuffer (); printf ("%8s ", ""); for (i=0; i < sizeof (databuffer); i++) { if (i == 8) putchar (' '); if (i+1 == nbytesprinted) { fputs (" ^ ", stdout); if (!both) break; } else fputs (" ", stdout); } if (both) { fputs (" ", stdout); for (i=0; i < nbytesprinted-1; i++) putchar (' '); putchar ('^'); } databufferlen = skipdatabufferlen = nbytesprinted; } static void printerr (const char *text) { printcursor (1); printf ("\n Error: %s\n", text); } static void printctl (const char *text) { if (verbose) { printcursor (0); printf ("%s\n", text); } } static void printchr (int c) { (void)c; } static void printhex (int c) { (void)c; } static int parse_and_print (FILE *fp) { static const char tokenchars[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789-./_:*+="; int c; int level = 0; int tokenc = 0; int hexcount = 0; int disphint = 0; unsigned long datalen = 0; char quote_buf[10]; int quote_idx = 0; enum { INIT_STATE = 0, IN_NUMBER, PRE_DATA, IN_DATA, IN_STRING, IN_ESCAPE, IN_OCT_ESC, IN_HEX_ESC, CR_ESC, LF_ESC, IN_HEXFMT, IN_BASE64 } state = INIT_STATE; while ((c = my_getc (fp)) != EOF ) { addrawdata (c); switch (state) { case INIT_STATE: if (tokenc) { if (strchr (tokenchars, c)) { printchr (c); continue; } tokenc = 0; } parse_init_state: if (c == '(') { if (disphint) { printerr ("unmatched display hint"); disphint = 0; } printctl ("open"); level++; } else if (c == ')') { if (disphint) { printerr ("unmatched display hint"); disphint = 0; } printctl ("close"); level--; } else if (c == '\"') { state = IN_STRING; printctl ("beginstring"); } else if (c == '#') { state = IN_HEXFMT; hexcount = 0; printctl ("beginhex"); } else if (c == '|') { state = IN_BASE64; printctl ("beginbase64"); } else if (c == '[') { if (disphint) printerr ("nested display hint"); disphint = c; } else if (c == ']') { if (!disphint) printerr ("no open display hint"); disphint = 0; } else if (c >= '0' && c <= '9') { if (c == '0') printerr ("zero prefixed length"); state = IN_NUMBER; datalen = (c - '0'); } else if (strchr (tokenchars, c)) { printchr (c); tokenc = c; } else if (whitespace_p (c)) ; else if (c == '{') { printerr ("rescanning is not supported"); } else if (c == '&' || c == '\\') { printerr ("reserved punctuation detected"); } else { printerr ("bad character detected"); } break; case IN_NUMBER: if (digit_p (c)) { unsigned long tmp = datalen * 10 + (c - '0'); if (tmp < datalen) { printerr ("overflow in data length"); state = INIT_STATE; datalen = 0; } else datalen = tmp; } else if (c == ':') { if (!datalen) { printerr ("no data length"); state = INIT_STATE; } else state = PRE_DATA; } else if (c == '\"' || c == '#' || c == '|' ) { /* We ignore the optional length and divert to the init state parser code. */ goto parse_init_state; } else printerr ("invalid length specification"); break; case PRE_DATA: state = IN_DATA; printctl ("begindata"); case IN_DATA: if (datalen) { printhex (c); datalen--; } if (!datalen) { state = INIT_STATE; printctl ("enddata"); } break; case IN_STRING: if (c == '\"') { printctl ("endstring"); state = INIT_STATE; } else if (c == '\\') state = IN_ESCAPE; else printchr (c); break; case IN_ESCAPE: switch (c) { case 'b': case 't': case 'v': case 'n': case 'f': case 'r': case '"': case '\'': case '\\': printhex (c); state = IN_STRING; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': state = IN_OCT_ESC; quote_idx = 0; quote_buf[quote_idx++] = c; break; case 'x': state = IN_HEX_ESC; quote_idx = 0; break; case '\r': state = CR_ESC; break; case '\n': state = LF_ESC; break; default: printerr ("invalid escape sequence"); state = IN_STRING; break; } case IN_OCT_ESC: state = IN_STRING; break; case IN_HEX_ESC: state = IN_STRING; break; case CR_ESC: state = IN_STRING; break; case LF_ESC: state = IN_STRING; break; case IN_HEXFMT: if (hexdigit_p (c)) { printchr (c); hexcount++; } else if (c == '#') { if ((hexcount & 1)) printerr ("odd number of hex digits"); printctl ("endhex"); state = INIT_STATE; } else if (!whitespace_p (c)) printerr ("bad hex character"); break; case IN_BASE64: if (c == '|') { printctl ("endbase64"); state = INIT_STATE; } else printchr (c); break; default: logit ("invalid state %d detected", state); exit (1); } } flushdatabuffer (); if (ferror (fp)) { logit ("error reading input: %s\n", strerror (errno)); return -1; } return 0; } int main (int argc, char **argv) { int rc; if (argc) { argc--; argv++; } while (argc && **argv == '-' && (*argv)[1] == '-') { if (!(*argv)[2]) { argc--; argv++; break; } else if (!strcmp (*argv, "--version")) print_version (0); else if (!strcmp (*argv, "--help")) print_version (1); else if (!strcmp (*argv, "--verbose")) { argc--; argv++; verbose = 1; } else if (!strcmp (*argv, "--decimal")) { argc--; argv++; decimal = 1; } else if (!strcmp (*argv, "--assume-hex")) { argc--; argv++; assume_hex = 1; } else print_usage (); } if (!argc) { rc = parse_and_print (stdin); } else { for (; argc; argc--) { FILE *fp = fopen (*argv, "rb"); if (!fp) { logit ("can't open `%s': %s\n", *argv, strerror (errno)); rc = 1; } else { if ( parse_and_print (fp) ) rc = 1; fclose (fp); } } } return !rc; }