]>
git.sthu.org Git - pgp-tools.git/blob - gpgwrap/src/gpgwrap.c
1 /****************************************************************************
2 ****************************************************************************
6 ****************************************************************************
7 ****************************************************************************/
22 #include <sys/types.h>
29 #define PROGRAM_NAME "gpgwrap"
30 #define VERSION_STRING PROGRAM_NAME " " VERSION "-" VERSION_DATE
31 #define EXEC_ARGV_SIZE 1024
32 #define PASSPHRASE_BUFFER_SIZE 0x10000
33 #define LIST_BUFFER_SIZE 0x10000
34 #define CMDLINE_MAX_FILES 1024
35 #define GPGWRAP_MODE_DEFAULT 0
36 #define GPGWRAP_MODE_VERSION 1
37 #define GPGWRAP_MODE_FILE 2
38 #define GPGWRAP_MODE_PRINT 3
42 static char program_name
[] = PROGRAM_NAME
;
43 static char environ_name
[] = "GPGWRAP_PASSPHRASE";
44 static int mode
= GPGWRAP_MODE_DEFAULT
;
45 static int verbose
= 0;
46 static int interactive
= 0;
47 static int ask_twice
= 0;
48 static int check_exit_code
= 0;
49 static char *calling_path
= NULL
;
50 static char *environ_var
= NULL
;
51 static char *passphrase_file
= NULL
;
52 static char *option_name
= "--passphrase-fd";
53 static char *files
[CMDLINE_MAX_FILES
];
54 static int nfiles
= 0;
55 static char **gpg_cmd
= NULL
;
59 /****************************************************************************
61 ****************************************************************************/
73 /****************************************************************************
75 ****************************************************************************/
76 #define do_error(args...) \
79 fprintf(stderr, "%s: ", program_name); \
80 fprintf(stderr, args); \
81 fprintf(stderr, "\n"); \
88 /****************************************************************************
90 ****************************************************************************/
91 #define do_warning(args...) \
94 fprintf(stderr, "%s: ", program_name); \
95 fprintf(stderr, args); \
96 fprintf(stderr, "\n"); \
102 /****************************************************************************
104 ****************************************************************************/
110 do_error("could not allocate memory");
115 /****************************************************************************
117 ****************************************************************************/
123 do_error("passphrase too long");
128 /****************************************************************************
130 ****************************************************************************/
131 #define do_verbose(level, args...) \
134 if (verbose < level) break; \
135 fprintf(stderr, "%s[%d]: ", program_name, getpid()); \
136 fprintf(stderr, args); \
137 fprintf(stderr, "\n"); \
143 /****************************************************************************
145 ****************************************************************************/
146 #define do_verbose_start(level, args...) \
149 if (verbose < level) break; \
150 fprintf(stderr, "%s[%d] ", program_name, getpid()); \
151 fprintf(stderr, args); \
157 /****************************************************************************
159 ****************************************************************************/
160 #define do_verbose_append(level, args...) \
163 if (verbose < level) break; \
164 fprintf(stderr, args); \
170 /****************************************************************************
172 ****************************************************************************/
173 #define do_snprintf(string, max, args...) do_snprintf2(snprintf(string, max, args), max)
177 /****************************************************************************
179 ****************************************************************************/
186 if ((len
== -1) || (len
>= max
)) do_error("do_snprintf() size exceeded");
192 /****************************************************************************
194 ****************************************************************************/
207 * look for "unusual" characters and convert them to
208 * backslash escaped octal numbers
211 for (i
= j
= 0, msize
--; i
< size
; i
++)
214 if (j
>= msize
) goto error
;
215 if ((c
< '+') || ((c
> ';') && (c
< 'A')) ||
216 ((c
> 'Z') && (c
!= '_') && (c
< 'a')) ||
217 ((c
> 'z') && (c
!= '~')))
219 c1
= (unsigned char) c
;
220 if (j
>= msize
- 4) goto error
;
222 mbuffer
[j
++] = '0' + (c1
>> 6);
223 mbuffer
[j
++] = '0' + ((c1
>> 3) & 7);
224 mbuffer
[j
++] = '0' + (c1
& 7);
226 else mbuffer
[j
++] = c
;
231 do_error("could not mangle passphrase");
236 /****************************************************************************
237 * unmangle_passphrase
238 ****************************************************************************/
246 int i
, j
, c1
, c2
, c3
;
248 /* replace backslash escaped octal numbers */
250 for (i
= j
= 0; j
< size
; i
++)
255 if (j
> size
- 3) goto error
;
259 if ((c1
< '0') || (c1
> '3') || (c2
< '0') || (c2
> '7') ||
260 (c3
< '0') || (c3
> '7')) goto error
;
264 c
= (char) (((c1
<< 6) | (c2
<< 3) | c3
) & 0xff);
270 do_error("could not unmangle passphrase");
275 /****************************************************************************
277 ****************************************************************************/
286 do_verbose(2, "reading passphrase from file '%s'", passphrase_file
);
287 if (strcmp(passphrase_file
, "-") == 0) fd
= STDIN_FILENO
;
288 else fd
= open(passphrase_file
, O_RDONLY
);
289 if (fd
== -1) do_perror();
290 for (len
= 0; (i
= read(fd
, buffer
, size
)) > 0; len
+= i
)
294 if (size
== 0) do_error_too_long();
296 if (i
== -1) do_perror();
297 if (close(fd
) == -1) do_perror();
303 /****************************************************************************
305 ****************************************************************************/
314 struct termios t
, tt
;
315 char tty
[] = "/dev/tty";
316 char pp
[] = "Passphrase: ";
317 char pp2
[] = "\nPassphrase (again): ";
321 * don't touch stdin, just open the controlling tty and ask for the
325 do_verbose(2, "opening '%s' to prompt for passphrase", tty
);
326 fd
= open(tty
, O_RDWR
);
327 if (fd
== -1) do_perror();
328 write(fd
, pp
, strlen(pp
));
332 tcsetattr(fd
, TCSAFLUSH
, &tt
);
333 len
= read(fd
, buffer
, size
);
334 if (len
== -1) do_perror();
335 if ((ask_twice
) && (len
< size
))
337 buffer2
= (char *) alloca(sizeof (char) * size
);
338 if (buffer2
== NULL
) do_error_oom();
339 write(fd
, pp2
, strlen(pp2
));
340 len2
= read(fd
, buffer2
, size
);
341 if (len2
== -1) do_perror();
343 tcsetattr(fd
, TCSAFLUSH
, &t
);
344 if ((len
!= len2
) || (memcmp(buffer
, buffer2
, len
) != 0)) do_error("passphrases are not the same");
349 tcsetattr(fd
, TCSAFLUSH
, &t
);
352 * if the above read() returns with len == size, we don't
353 * know if there are more bytes, so we assume passphrase is
357 if (len
>= size
) do_error_too_long();
359 if (close(fd
) == -1) do_perror();
361 /* ignore trailing \012 */
368 /****************************************************************************
370 ****************************************************************************/
380 env
= getenv(environ_name
);
381 if ((env
!= NULL
) && (! interactive
))
383 do_verbose(2, "got passphrase from environment variable: %s=%s", environ_name
, env
);
386 * first unmangle the content of the environment
387 * variable inplace, then clear the memory
391 len
= unmangle_passphrase(env
, len2
);
392 if (len
> size
) do_error_too_long();
393 memcpy(buffer
, env
, len
);
394 memset(env
, 0, len2
);
396 else len
= prompt_passphrase(buffer
, size
);
402 /****************************************************************************
404 ****************************************************************************/
410 int status
, value
= 1;
412 do_verbose(2, "waiting for child");
414 if (! check_exit_code
) return;
415 do_verbose(2, "checking child exit code");
416 if (! WIFEXITED(status
)) goto out
;
417 value
= WEXITSTATUS(status
);
418 if (value
== 0) return;
419 do_verbose(2, "child process terminated abnormal, exiting");
426 /****************************************************************************
428 ****************************************************************************/
438 * parent will write passphrase to the opened pipe, child will
442 if (pipe(fds
) == -1) do_perror();
443 do_verbose(2, "forking");
451 if (close(fds
[1]) == -1) do_perror();
459 signal(SIGPIPE
, SIG_IGN
);
460 if (close(fds
[0]) == -1) do_perror();
463 i
= write(fds
[1], buffer
, size
);
464 if ((i
== -1) && (errno
== EPIPE
)) break;
465 if (i
== -1) do_perror();
469 if (size
> 0) do_warning("only partial passphrase written");
470 if (close(fds
[1]) == -1) do_perror();
477 /****************************************************************************
479 ****************************************************************************/
486 char buffer
[PASSPHRASE_BUFFER_SIZE
];
488 if ((passphrase_file
== NULL
) || (interactive
))
490 len
= environ_or_prompt(buffer
, sizeof (buffer
));
491 fd
= do_fork(buffer
, len
);
493 else if (strcmp(passphrase_file
, "-") == 0)
495 len
= read_passphrase(buffer
, sizeof (buffer
));
496 fd
= do_fork(buffer
, len
);
500 do_verbose(2, "opening file '%s' to pass fd", passphrase_file
);
501 fd
= open(passphrase_file
, O_RDONLY
);
502 if (fd
== -1) do_perror();
509 /****************************************************************************
511 ****************************************************************************/
520 if ((passphrase_file
== NULL
) || (interactive
)) len
= environ_or_prompt(buffer
, size
);
521 else len
= read_passphrase(buffer
, size
);
527 /****************************************************************************
529 ****************************************************************************/
540 * putenv() only stores the given pointer in **environ, so we have
544 size
= strlen(environ_name
) + (4 * len
) + 2;
545 old_var
= environ_var
;
546 environ_var
= (char *) malloc(sizeof (char) * size
);
547 if (environ_var
== NULL
) do_error_oom();
548 len2
= do_snprintf(environ_var
, size
, "%s=", environ_name
);
549 if ((buffer
!= NULL
) && (len
> 0)) mangle_passphrase(buffer
, len
, &environ_var
[len2
], size
- len2
);
550 do_verbose(2, "setting environment variable: %s", environ_var
);
551 if (putenv(environ_var
) == -1) do_perror();
552 if (old_var
!= NULL
) free(old_var
);
557 /****************************************************************************
559 ****************************************************************************/
566 if (clear
) do_putenv(NULL
, 0);
571 do_verbose_start(1, "executing:");
572 for (i
= 0; argv
[i
] != NULL
; i
++) do_verbose_append(1, " %s", argv
[i
]);
573 do_verbose_append(1, "\n");
575 execvp(argv
[0], argv
);
577 /* only reached if execvp fails */
584 /****************************************************************************
586 ****************************************************************************/
595 char *argv
[EXEC_ARGV_SIZE
];
596 char homedir_eq
[] = "--homedir=";
597 char options_eq
[] = "--options=";
600 * get fd to read passphrase from, parent will return with fd == -1
604 fd
= get_passphrase_fd();
605 if (fd
== -1) return;
607 /* create argv for execvp */
609 do_snprintf(fd_num
, sizeof (fd_num
), "%d", fd
);
610 for (i
= 0, j
= 0, k
= 1; gpg_cmd
[i
] != NULL
; i
++, k
--)
614 * check if there is enough space to store option_name
618 if (i
>= (EXEC_ARGV_SIZE
- 4)) do_error("too many gpg arguments specified");
619 if (strcmp(gpg_cmd
[i
], option_name
) == 0) do_error("gpg command already has a '%s' option", option_name
);
622 if ((strncmp(gpg_cmd
[i
], homedir_eq
, sizeof (homedir_eq
) - 1) == 0) || (strncmp(gpg_cmd
[i
], options_eq
, sizeof (options_eq
) - 1) == 0)) k
= 1;
623 else if ((strcmp(gpg_cmd
[i
], "--homedir") == 0) || (strcmp(gpg_cmd
[i
], "--options") == 0)) k
= 2;
626 argv
[j
++] = option_name
;
630 argv
[j
++] = gpg_cmd
[i
];
634 argv
[j
++] = option_name
;
643 /****************************************************************************
645 ****************************************************************************/
651 char shell_cmd
[LIST_BUFFER_SIZE
];
652 char verbose_string
[128] = "";
653 char *argv
[] = { "sh", "-c", NULL
, NULL
};
656 /* fork a child and disallow it to read stdin from parent */
658 if (pipe(fds
) == -1) do_perror();
659 do_verbose(1, "forking");
669 if (close(fds
[0]) == -1) do_perror();
670 if (close(fds
[1]) == -1) do_perror();
677 if (close(fds
[1]) == -1) do_perror();
678 if (fds
[0] != STDIN_FILENO
) dup2(fds
[0], STDIN_FILENO
);
680 /* create argv for execvp */
682 for (i
= 0; i
< verbose
; i
++)
684 if (strlen(verbose_string
) >= sizeof (verbose_string
) - 4) break;
685 strcat(verbose_string
, " -v");
687 do_snprintf(shell_cmd
, sizeof (shell_cmd
), "exec %s%s -o %s -- %s",
688 calling_path
, verbose_string
, option_name
, line
);
695 /****************************************************************************
697 ****************************************************************************/
706 char lbuffer
[LIST_BUFFER_SIZE
];
707 int inuse
, start
, free
, nread
, llen
;
708 char *line
, *next_line
;
712 do_verbose(1, "reading gpg commands from file: '%s'", path
);
713 if (strcmp(path
, "-") == 0) fd
= STDIN_FILENO
;
714 else fd
= open(path
, O_RDONLY
);
715 if (fd
== -1) do_perror();
717 /* export passphrase to environment */
719 do_putenv(buffer
, len
);
721 /* read gpg commands */
723 for (inuse
= 0, free
= LIST_BUFFER_SIZE
; (nread
= read(fd
, &lbuffer
[inuse
], free
)) > 0; )
726 for (line
= lbuffer
; (next_line
= memchr(line
, '\n', inuse
)) != NULL
; )
729 llen
= (int) (next_line
- line
) + 1;
730 if (llen
!= strlen(line
) + 1) do_error("line contains \\0 character");
733 line
= next_line
+ 1;
735 start
= (int) (line
- lbuffer
);
736 if ((start
== 0) && (inuse
== LIST_BUFFER_SIZE
)) do_error("line too long");
737 if ((start
> 0) && (inuse
> 0)) memmove(lbuffer
, &lbuffer
[start
], inuse
);
738 free
= LIST_BUFFER_SIZE
- inuse
;
741 /* check for error while read() */
743 if (nread
== -1) do_perror();
744 if (close(fd
) == -1) do_perror();
746 /* check if there are bytes left */
748 if (inuse
> 0) do_error("last line incomplete");
753 /****************************************************************************
755 ****************************************************************************/
761 while (*s
!= '\0') *s
++ = ' ';
766 /****************************************************************************
768 ****************************************************************************/
774 char space1
[] = VERSION_STRING
;
775 char space2
[] = PROGRAM_NAME
;
777 cmdline_fill_space(space1
);
778 cmdline_fill_space(space2
);
779 printf(VERSION_STRING
" | written by Karsten Scheibler\n"
780 "%s | http://unusedino.de/gpgwrap/\n"
781 "%s | gpgwrap@unusedino.de\n\n"
783 "or: %s -P [-v] [-i] [-a] [-p <file>]\n"
784 "or: %s -F [-v] [-i] [-a] [-c] [-p <file>] [-o <name>]\n"
785 " %s [--] <file> [<file> ... ]\n"
786 "or: %s [-v] [-i] [-a] [-p <file>] [-o <name>]\n"
787 " %s [--] gpg [gpg options]\n\n"
788 " -V print out version\n"
789 " -P get the passphrase and print it mangled to stdout\n"
790 " -F read gpg commands from file\n"
791 " -v be more verbose\n"
792 " -i be interactive, always prompt for passphrase\n"
793 " -a ask twice if prompting for passphrase\n"
794 " -c check exit code of child processes\n"
795 " -p <file> read passphrase from <file>\n"
796 " -o <name> specify name of \"--passphrase-fd\" option\n"
798 space1
, space1
, program_name
, program_name
, program_name
,
799 space2
, program_name
, space2
);
805 /****************************************************************************
807 ****************************************************************************/
814 if (file
== NULL
) do_error("%s expects a file name", msg
);
820 /****************************************************************************
821 * cmdline_check_stdin
822 ****************************************************************************/
829 static int stdin_count
= 0;
831 cmdline_check_arg(msg
, file
);
832 if (strcmp(file
, "-") == 0) stdin_count
++;
833 if (stdin_count
> 1) do_error("%s used stdin although already used before", msg
);
839 /****************************************************************************
841 ****************************************************************************/
852 calling_path
= argv
[0];
853 for (args
= 0, argv
++; (arg
= *argv
++) != NULL
; args
++)
855 if ((arg
[0] != '-') || (ignore
))
857 if (mode
== GPGWRAP_MODE_FILE
) goto get_file
;
861 else if ((strcmp(arg
, "-") == 0) && (mode
== GPGWRAP_MODE_FILE
))
864 if (nfiles
>= CMDLINE_MAX_FILES
) do_error("too many files specified");
865 files
[nfiles
++] = cmdline_check_stdin("-F/--file", arg
);
867 else if (strcmp(arg
, "--") == 0)
871 else if ((strcmp(arg
, "-h") == 0) || (strcmp(arg
, "--help") == 0))
875 else if (((strcmp(arg
, "-V") == 0) || (strcmp(arg
, "--version") == 0)) && (args
== 0))
877 mode
= GPGWRAP_MODE_VERSION
;
879 else if (((strcmp(arg
, "-F") == 0) || (strcmp(arg
, "--file") == 0)) && (args
== 0))
881 mode
= GPGWRAP_MODE_FILE
;
883 else if (((strcmp(arg
, "-P") == 0) || (strcmp(arg
, "--print") == 0)) && (args
== 0))
885 mode
= GPGWRAP_MODE_PRINT
;
887 else if (mode
== GPGWRAP_MODE_VERSION
)
891 else if ((strcmp(arg
, "-v") == 0) || (strcmp(arg
, "--verbose") == 0))
895 else if ((strcmp(arg
, "-i") == 0) || (strcmp(arg
, "--interactive") == 0))
899 else if ((strcmp(arg
, "-a") == 0) || (strcmp(arg
, "--ask-twice") == 0))
903 else if ((strcmp(arg
, "-p") == 0) || (strcmp(arg
, "--passphrase-file") == 0))
905 if (passphrase_file
!= NULL
) do_error("-p/--passphrase-file specified more than once");
906 passphrase_file
= cmdline_check_stdin("-p/--passphrase-file", *argv
++);
908 else if (mode
== GPGWRAP_MODE_PRINT
)
912 else if ((strcmp(arg
, "-o") == 0) || (strcmp(arg
, "--option-name") == 0))
914 option_name
= cmdline_check_arg("-o/--option-name", *argv
++);
916 else if (mode
!= GPGWRAP_MODE_FILE
)
920 else if ((strcmp(arg
, "-c") == 0) || (strcmp(arg
, "--check-exit-code") == 0))
927 do_error("unrecognized option '%s'", arg
);
930 if ((mode
== GPGWRAP_MODE_DEFAULT
) && (nfiles
== 0) && (gpg_cmd
== NULL
)) do_error("no gpg command specified");
931 if ((mode
== GPGWRAP_MODE_FILE
) && (nfiles
== 0)) do_error("no files to process");
932 if ((mode
== GPGWRAP_MODE_PRINT
) && (nfiles
> 0)) do_error("no additional arguments allowed");
933 if (mode
!= GPGWRAP_MODE_FILE
) check_exit_code
= 1;
938 /****************************************************************************
940 ****************************************************************************/
949 * we need setlinebuf(), because otherwise do_verbose() output of
950 * parent and child processes may get mixed in some cases
957 cmdline_parse(argc
, argv
);
961 if (mode
== GPGWRAP_MODE_VERSION
)
963 printf(VERSION_STRING
"\n");
965 else if (mode
== GPGWRAP_MODE_FILE
)
968 char buffer
[PASSPHRASE_BUFFER_SIZE
];
970 len
= get_passphrase(buffer
, sizeof (buffer
));
971 for (i
= 0; i
< nfiles
; i
++) exec_list(files
[i
], buffer
, len
);
973 else if (mode
== GPGWRAP_MODE_PRINT
)
975 char buffer
[PASSPHRASE_BUFFER_SIZE
];
976 char mbuffer
[PASSPHRASE_BUFFER_SIZE
];
979 len
= get_passphrase(buffer
, sizeof (buffer
));
980 mangle_passphrase(buffer
, len
, mbuffer
, sizeof (mbuffer
));
981 printf("%s\n", mbuffer
);
989 /******************************************************** Karsten Scheibler */