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