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