hivexsh: Change some exit(1) -> exit(EXIT_FAILURE)
[libguestfs.git] / hivex / hivexsh.c
1 /* hivexsh - Hive shell.
2  * Copyright (C) 2009 Red Hat 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 by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdint.h>
25 #include <inttypes.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <assert.h>
29 #include <errno.h>
30
31 #ifdef HAVE_LIBREADLINE
32 #include <readline/readline.h>
33 #include <readline/history.h>
34 #endif
35
36 #ifdef HAVE_GETTEXT
37 #include "gettext.h"
38 #define _(str) dgettext(PACKAGE, (str))
39 //#define N_(str) dgettext(PACKAGE, (str))
40 #else
41 #define _(str) str
42 //#define N_(str) str
43 #endif
44
45 #define STREQ(a,b) (strcmp((a),(b)) == 0)
46 #define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
47 #define STRNEQ(a,b) (strcmp((a),(b)) != 0)
48 //#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0)
49 //#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0)
50 //#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0)
51 //#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
52 //#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
53 //#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
54
55 #include "c-ctype.h"
56
57 #include "hivex.h"
58
59 static int quit = 0;
60 static int is_tty;
61 static hive_h *h = NULL;
62 static char *prompt_string = NULL; /* Normal prompt string. */
63 static char *loaded = NULL;     /* Basename of loaded file, if any. */
64 static hive_node_h cwd;         /* Current node. */
65 static int open_flags = 0;      /* Flags used when loading a hive file. */
66
67 static void usage (void) __attribute__((noreturn));
68 static void print_node_path (hive_node_h, FILE *);
69 static void set_prompt_string (void);
70 static void initialize_readline (void);
71 static void cleanup_readline (void);
72 static void add_history_line (const char *);
73 static char *rl_gets (const char *prompt_string);
74 static void sort_strings (char **strings, int len);
75 static int dispatch (char *cmd, char *args);
76 static int cmd_cd (char *path);
77 static int cmd_close (char *path);
78 static int cmd_help (char *args);
79 static int cmd_load (char *hivefile);
80 static int cmd_ls (char *args);
81 static int cmd_lsval (char *args);
82
83 static void
84 usage (void)
85 {
86   fprintf (stderr, "hivexsh [-df] [hivefile]\n");
87   exit (EXIT_FAILURE);
88 }
89
90 int
91 main (int argc, char *argv[])
92 {
93   setlocale (LC_ALL, "");
94   bindtextdomain (PACKAGE, LOCALEBASEDIR);
95   textdomain (PACKAGE);
96
97   int c;
98   const char *filename = NULL;
99
100   set_prompt_string ();
101
102   while ((c = getopt (argc, argv, "df")) != EOF) {
103     switch (c) {
104     case 'd':
105       open_flags |= HIVEX_OPEN_DEBUG;
106       break;
107     case 'f':
108       filename = optarg;
109       break;
110     default:
111       usage ();
112     }
113   }
114
115   if (optind < argc) {
116     if (optind + 1 != argc)
117       usage ();
118     if (cmd_load (argv[optind]) == -1)
119       exit (EXIT_FAILURE);
120   }
121
122   /* -f filename parameter */
123   if (filename) {
124     close (0);
125     if (open (filename, O_RDONLY) == -1) {
126       perror (filename);
127       exit (EXIT_FAILURE);
128     }
129   }
130
131   /* Main loop. */
132   is_tty = isatty (0);
133   initialize_readline ();
134
135   if (is_tty)
136     printf (_(
137 "\n"
138 "Welcome to hivexsh, the hivex interactive shell for examining\n"
139 "Windows Registry binary hive files.\n"
140 "\n"
141 "Type: 'help' for help summary\n"
142 "      'quit' to quit the shell\n"
143 "\n"));
144
145   while (!quit) {
146     char *buf = rl_gets (prompt_string);
147     if (!buf) {
148       quit = 1;
149       if (is_tty)
150         printf ("\n");
151       break;
152     }
153
154     while (*buf && c_isspace (*buf))
155       buf++;
156
157     /* Ignore blank line. */
158     if (!*buf) continue;
159
160     /* If the next character is '#' then this is a comment. */
161     if (*buf == '#') continue;
162
163     /* Parsing is very simple - much simpler than guestfish.  This is
164      * because Registry keys often contain spaces, and we don't want
165      * to bother with quoting.  Therefore here we just split at the
166      * first whitespace into "cmd<whitespace>arg(s)".  We let the
167      * command decide how to deal with arg(s), if at all.
168      */
169     size_t len = strcspn (buf, " \t");
170
171     if (len == 0) continue;
172
173     char *cmd = buf;
174     char *args;
175     size_t i = 0;
176
177     if (buf[len] == '\0') {
178       /* This is mostly safe.  Although the cmd_* functions do sometimes
179        * modify args, then shouldn't do so when args is "".
180        */
181       args = (char *) "";
182       goto got_command;
183     }
184
185     buf[len] = '\0';
186     args = buf + len + 1 + strspn (&buf[len+1], " \t");
187
188     len = strlen (args);
189     while (len > 0 && c_isspace (args[len-1])) {
190       args[len-1] = '\0';
191       len--;
192     }
193
194   got_command:
195     /*printf ("command: '%s'  args: '%s'\n", cmd, args)*/;
196     int r = dispatch (cmd, args);
197     if (!is_tty && r == -1)
198       exit (EXIT_FAILURE);
199   }
200
201   cleanup_readline ();
202   free (prompt_string);
203   free (loaded);
204   if (h) hivex_close (h);
205   exit (0);
206 }
207
208 /* Set the prompt string.  This is called whenever it could change, eg.
209  * after loading a file or changing directory.
210  */
211 static void
212 set_prompt_string (void)
213 {
214   free (prompt_string);
215   prompt_string = NULL;
216
217   FILE *fp;
218   char *ptr;
219   size_t size;
220   fp = open_memstream (&ptr, &size);
221   if (fp == NULL) {
222     perror ("open_memstream");
223     exit (EXIT_FAILURE);
224   }
225
226   if (h) {
227     assert (loaded != NULL);
228     assert (cwd != 0);
229
230     fputs (loaded, fp);
231     print_node_path (cwd, fp);
232   }
233
234   fprintf (fp, "> ");
235   fclose (fp);
236   prompt_string = ptr;
237 }
238
239 /* Print the \full\path of a node. */
240 static void
241 print_node_path (hive_node_h node, FILE *fp)
242 {
243   hive_node_h root = hivex_root (h);
244
245   if (node == root) {
246     fputc ('\\', fp);
247     return;
248   }
249
250   hive_node_h parent = hivex_node_parent (h, node);
251   if (parent == 0) {
252     fprintf (stderr, _("hivexsh: error getting parent of node %zu\n"), node);
253     return;
254   }
255   print_node_path (parent, fp);
256
257   if (parent != root)
258     fputc ('\\', fp);
259
260   char *name = hivex_node_name (h, node);
261   if (name == NULL) {
262     fprintf (stderr, _("hivexsh: error getting node name of node %zx\n"), node);
263     return;
264   }
265
266   fputs (name, fp);
267   free (name);
268 }
269
270 static char *line_read = NULL;
271
272 static char *
273 rl_gets (const char *prompt_string)
274 {
275 #ifdef HAVE_LIBREADLINE
276
277   if (is_tty) {
278     if (line_read) {
279       free (line_read);
280       line_read = NULL;
281     }
282
283     line_read = readline (prompt_string);
284
285     if (line_read && *line_read)
286       add_history_line (line_read);
287
288     return line_read;
289   }
290
291 #endif /* HAVE_LIBREADLINE */
292
293   static char buf[8192];
294   int len;
295
296   if (is_tty)
297     printf ("%s", prompt_string);
298   line_read = fgets (buf, sizeof buf, stdin);
299
300   if (line_read) {
301     len = strlen (line_read);
302     if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
303   }
304
305   return line_read;
306 }
307
308 #ifdef HAVE_LIBREADLINE
309 static char histfile[1024];
310 static int nr_history_lines = 0;
311 #endif
312
313 static void
314 initialize_readline (void)
315 {
316 #ifdef HAVE_LIBREADLINE
317   const char *home;
318
319   home = getenv ("HOME");
320   if (home) {
321     snprintf (histfile, sizeof histfile, "%s/.hivexsh", home);
322     using_history ();
323     (void) read_history (histfile);
324   }
325
326   rl_readline_name = "hivexsh";
327 #endif
328 }
329
330 static void
331 cleanup_readline (void)
332 {
333 #ifdef HAVE_LIBREADLINE
334   int fd;
335
336   if (histfile[0] != '\0') {
337     fd = open (histfile, O_WRONLY|O_CREAT, 0644);
338     if (fd == -1) {
339       perror (histfile);
340       return;
341     }
342     close (fd);
343
344     (void) append_history (nr_history_lines, histfile);
345   }
346 #endif
347 }
348
349 static void
350 add_history_line (const char *line)
351 {
352 #ifdef HAVE_LIBREADLINE
353   add_history (line);
354   nr_history_lines++;
355 #endif
356 }
357
358 static int
359 compare (const void *vp1, const void *vp2)
360 {
361   char * const *p1 = (char * const *) vp1;
362   char * const *p2 = (char * const *) vp2;
363   return strcasecmp (*p1, *p2);
364 }
365
366 static void
367 sort_strings (char **strings, int len)
368 {
369   qsort (strings, len, sizeof (char *), compare);
370 }
371
372 static int
373 dispatch (char *cmd, char *args)
374 {
375   if (STRCASEEQ (cmd, "help"))
376     return cmd_help (args);
377   else if (STRCASEEQ (cmd, "load"))
378     return cmd_load (args);
379   else if (STRCASEEQ (cmd, "exit") ||
380            STRCASEEQ (cmd, "q") ||
381            STRCASEEQ (cmd, "quit")) {
382     quit = 1;
383     return 0;
384   }
385
386   /* If no hive file is loaded (!h) then only the small selection of
387    * commands above will work.
388    */
389   if (!h) {
390     fprintf (stderr, _("hivexsh: you must load a hive file first using 'load hivefile'\n"));
391     return -1;
392   }
393
394   if (STRCASEEQ (cmd, "cd"))
395     return cmd_cd (args);
396   else if (STRCASEEQ (cmd, "close") || STRCASEEQ (cmd, "unload"))
397     return cmd_close (args);
398   else if (STRCASEEQ (cmd, "ls"))
399     return cmd_ls (args);
400   else if (STRCASEEQ (cmd, "lsval"))
401     return cmd_lsval (args);
402   else {
403     fprintf (stderr, _("hivexsh: unknown command '%s', use 'help' for help summary\n"),
404              cmd);
405     return -1;
406   }
407 }
408
409 static int
410 cmd_load (char *hivefile)
411 {
412   if (STREQ (hivefile, "")) {
413     fprintf (stderr, _("hivexsh: load: no hive file name given to load\n"));
414     return -1;
415   }
416
417   if (h) hivex_close (h);
418   h = NULL;
419
420   free (loaded);
421   loaded = NULL;
422
423   cwd = 0;
424
425   h = hivex_open (hivefile, open_flags);
426   if (h == NULL) {
427     fprintf (stderr,
428              _(
429 "hivexsh: failed to open hive file: %s: %m\n"
430 "\n"
431 "If you think this file is a valid Windows binary hive file (_not_\n"
432 "a regedit *.reg file) then please run this command again using the\n"
433 "hivexsh option '-d' and attach the complete output _and_ the hive file\n"
434 "which fails into a bug report at https://bugzilla.redhat.com/\n"
435 "\n"),
436              hivefile);
437     return -1;
438   }
439
440   /* Get the basename of the file for the prompt. */
441   char *p = strrchr (hivefile, '/');
442   if (p)
443     loaded = strdup (p+1);
444   else
445     loaded = strdup (hivefile);
446   if (!loaded) {
447     perror ("strdup");
448     exit (EXIT_FAILURE);
449   }
450
451   cwd = hivex_root (h);
452
453   set_prompt_string ();
454
455   return 0;
456 }
457
458 static int
459 cmd_close (char *args)
460 {
461   if (STRNEQ (args, "")) {
462     fprintf (stderr, _("hivexsh: '%s' command should not be given arguments\n"),
463              "close");
464     return -1;
465   }
466
467   if (h) hivex_close (h);
468   h = NULL;
469
470   free (loaded);
471   loaded = NULL;
472
473   cwd = 0;
474
475   set_prompt_string ();
476
477   return 0;
478 }
479
480 static int
481 cmd_cd (char *path)
482 {
483   if (STREQ (path, "")) {
484     print_node_path (cwd, stdout);
485     fputc ('\n', stdout);
486     return 0;
487   }
488
489   if (path[0] == '\\' && path[1] == '\\') {
490     fprintf (stderr, _("%s: %s: \\ characters in path are doubled - are you escaping the path parameter correctly?\n"), "hivexsh", path);
491     return -1;
492   }
493
494   hive_node_h new_cwd = cwd;
495   hive_node_h root = hivex_root (h);
496
497   if (path[0] == '\\') {
498     new_cwd = root;
499     path++;
500   }
501
502   while (path[0]) {
503     size_t len = strcspn (path, "\\");
504     if (len == 0) {
505       path++;
506       continue;
507     }
508
509     char *elem = path;
510     path = path[len] == '\0' ? &path[len] : &path[len+1];
511     elem[len] = '\0';
512
513     if (len == 1 && STREQ (elem, "."))
514       continue;
515
516     if (len == 2 && STREQ (elem, "..")) {
517       if (new_cwd != root)
518         new_cwd = hivex_node_parent (h, new_cwd);
519       continue;
520     }
521
522     new_cwd = hivex_node_get_child (h, new_cwd, elem);
523     if (new_cwd == 0) {
524       fprintf (stderr, _("hivexsh: cd: subkey '%s' not found\n"),
525                elem);
526       return -1;
527     }
528   }
529
530   if (new_cwd != cwd) {
531     cwd = new_cwd;
532     set_prompt_string ();
533   }
534
535   return 0;
536 }
537
538 static int
539 cmd_help (char *args)
540 {
541   printf (_(
542 "Navigate through the hive's keys using the 'cd' command, as if it\n"
543 "contained a filesystem, and use 'ls' to list the subkeys of the\n"
544 "current key.  Full documentation is in the hivexsh(1) manual page.\n"));
545
546   return 0;
547 }
548
549 static int
550 cmd_ls (char *args)
551 {
552   if (STRNEQ (args, "")) {
553     fprintf (stderr, _("hivexsh: '%s' command should not be given arguments\n"),
554              "ls");
555     return -1;
556   }
557
558   /* Get the subkeys. */
559   hive_node_h *children = hivex_node_children (h, cwd);
560   if (children == NULL) {
561     perror ("ls");
562     return -1;
563   }
564
565   /* Get names for each subkey. */
566   size_t len;
567   for (len = 0; children[len] != 0; ++len)
568     ;
569
570   char **names = calloc (len, sizeof (char *));
571   if (names == NULL) {
572     perror ("malloc");
573     exit (EXIT_FAILURE);
574   }
575
576   int ret = -1;
577   size_t i;
578   for (i = 0; i < len; ++i) {
579     names[i] = hivex_node_name (h, children[i]);
580     if (names[i] == NULL) {
581       perror ("hivex_node_name");
582       goto error;
583     }
584   }
585
586   /* Sort the names. */
587   sort_strings (names, len);
588
589   for (i = 0; i < len; ++i)
590     printf ("%s\n", names[i]);
591
592   ret = 0;
593  error:
594   free (children);
595   for (i = 0; i < len; ++i)
596     free (names[i]);
597   free (names);
598   return ret;
599 }
600
601 static int
602 cmd_lsval (char *key)
603 {
604   if (STRNEQ (key, "")) {
605     hive_value_h value;
606
607     errno = 0;
608     if (STREQ (key, "@"))       /* default key written as "@" */
609       value = hivex_node_get_value (h, cwd, "");
610     else
611       value = hivex_node_get_value (h, cwd, key);
612
613     if (value == 0) {
614       if (errno)
615         goto error;
616       /* else key not found */
617       fprintf (stderr, _("%s: %s: key not found\n"), "hivexsh", key);
618       return -1;
619     }
620
621     /* Print the value. */
622     hive_type t;
623     size_t len;
624     if (hivex_value_type (h, value, &t, &len) == -1)
625       goto error;
626
627     switch (t) {
628     case hive_t_string:
629     case hive_t_expand_string:
630     case hive_t_link: {
631       char *str = hivex_value_string (h, value);
632       if (!str)
633         goto error;
634
635       puts (str); /* note: this adds a single \n character */
636       free (str);
637       break;
638     }
639
640     case hive_t_dword:
641     case hive_t_dword_be: {
642       int32_t j = hivex_value_dword (h, value);
643       printf ("%" PRIi32 "\n", j);
644       break;
645     }
646
647     case hive_t_qword: {
648       int64_t j = hivex_value_qword (h, value);
649       printf ("%" PRIi64 "\n", j);
650       break;
651     }
652
653     case hive_t_multiple_strings: {
654       char **strs = hivex_value_multiple_strings (h, value);
655       if (!strs)
656         goto error;
657       size_t j;
658       for (j = 0; strs[j] != NULL; ++j) {
659         puts (strs[j]);
660         free (strs[j]);
661       }
662       free (strs);
663       break;
664     }
665
666     case hive_t_none:
667     case hive_t_binary:
668     case hive_t_resource_list:
669     case hive_t_full_resource_description:
670     case hive_t_resource_requirements_list:
671     default: {
672       char *data = hivex_value_value (h, value, &t, &len);
673       if (!data)
674         goto error;
675
676       if (fwrite (data, 1, len, stdout) != len)
677         goto error;
678
679       free (data);
680       break;
681     }
682     } /* switch */
683   } else {
684     /* No key specified, so print all keys in this node.  We do this
685      * in a format which looks like the output of regedit, although
686      * this isn't a particularly useful format.
687      */
688     hive_value_h *values;
689
690     values = hivex_node_values (h, cwd);
691     if (values == NULL)
692       goto error;
693
694     size_t i;
695     for (i = 0; values[i] != 0; ++i) {
696       char *key = hivex_value_key (h, values[i]);
697       if (!key) goto error;
698
699       if (*key) {
700         putchar ('"');
701         size_t j;
702         for (j = 0; key[j] != 0; ++j) {
703           if (key[j] == '"' || key[j] == '\\')
704             putchar ('\\');
705           putchar (key[j]);
706         }
707         putchar ('"');
708       } else
709         printf ("\"@\"");       /* default key in regedit files */
710       putchar ('=');
711       free (key);
712
713       hive_type t;
714       size_t len;
715       if (hivex_value_type (h, values[i], &t, &len) == -1)
716         goto error;
717
718       switch (t) {
719       case hive_t_string:
720       case hive_t_expand_string:
721       case hive_t_link: {
722         char *str = hivex_value_string (h, values[i]);
723         if (!str)
724           goto error;
725
726         if (t != hive_t_string)
727           printf ("str(%d):", t);
728         putchar ('"');
729         size_t j;
730         for (j = 0; str[j] != 0; ++j) {
731           if (str[j] == '"' || str[j] == '\\')
732             putchar ('\\');
733           putchar (str[j]);
734         }
735         putchar ('"');
736         free (str);
737         break;
738       }
739
740       case hive_t_dword:
741       case hive_t_dword_be: {
742         int32_t j = hivex_value_dword (h, values[i]);
743         printf ("dword:%08" PRIx32 "\"", j);
744         break;
745       }
746
747       case hive_t_qword: /* sic */
748       case hive_t_none:
749       case hive_t_binary:
750       case hive_t_multiple_strings:
751       case hive_t_resource_list:
752       case hive_t_full_resource_description:
753       case hive_t_resource_requirements_list:
754       default: {
755         char *data = hivex_value_value (h, values[i], &t, &len);
756         if (!data)
757           goto error;
758
759         printf ("hex(%d):", t);
760         size_t j;
761         for (j = 0; j < len; ++j) {
762           if (j > 0)
763             putchar (',');
764           printf ("%02x", data[j]);
765         }
766         break;
767       }
768       } /* switch */
769
770       putchar ('\n');
771     } /* for */
772
773     free (values);
774   }
775
776   return 0;
777
778  error:
779   perror ("hivexsh: lsval");
780   return -1;
781 }