Возникла у меня тут необходимость запускать gpg в пакетном режиме, без взаимодействия с пользователем. Точнее, пользователь должен один раз ввести пароль от ключа, и больше не вмешиваться.
Нашел модуль GnuPG, но оказалось, что он полагается на shared memory, а в нынешнем gpg2 эта фича не поддерживается. Теперь надо а gpg все дополнительные данные скармливать через файловые дескрипторы. Пробую запустить из комнадной строки:
/usr/bin/gpg --decrypt --batch --passphrase-fd 3 encrypted.file 3<password.file
Работает. Ну ладно, будем запускать то же самое из перла, только без файла, а напрямую скармливая пароль через пайп.
В целом вроде бы тоже работает, gpg запускается, зашифрованный файл находит, даже показывает, каким ключом он зашифрован, но… расшифровать не может, поскольку не видит пароля:
can't connect to `/home/dil/.gnupg/S.gpg-agent': No such file or directory
gpg: encrypted with 2048-bit RSA key, ID 673056FF, created 2012-02-17
"юзер <почтовый адрес>"
gpg: public key decryption failed: Bad passphrase
gpg: decryption failed: No secret key
Пробую напечатать пароль непосредственно в перле из дочернего процесса, прочитав его из пайпа. Нормально читается и печатается. А gpg из того же пайпа этот пароль в упор не видит.
Вопрос для продвинутых программистов и сисадминов: где засада?
Подсказка: проблема решилась после добавления в скрипт пары строчек. Угадайте, каких.
Исходник на перле под катом.
#!/usr/bin/perl -w
my ($pwread, $pwwrite);
pipe($pwread, $pwwrite);
my $dataread;
my $pid = open $dataread, "-|";
die "Cannot fork: $!\n" unless defined($pid); # fork error
if($pid) { # parent process
close($pwread);
print $pwwrite 'password';
close($pwwrite);
$DATA = <$dataread>;
unless(defined($DATA) && length($DATA) > 0) {
die "Failed to read data\n";
}
chomp $DATA;
print "data='$DATA'\n";
close($dataread);
waitpid($pid, 0);
exit 0;
}
# child
close($pwwrite);
my $pwfd = fileno($pwread); # узнаём номер файлового дескриптора
print STDERR "pwfd=$pwfd\n";
# my $d = <$pwread>; # вот если читать пароль тут, то он нормально читается
# print STDERR "d='$d'\n"; # и пишется
# print STDOUT "some fake data here\n"; # потом можно подсунуть родительскому процессу данные, якобы от расшифрованного файла, и он их тоже нормально получает
# exit 0;
# а вот если запустить gpg, то пароль из указанного дексриптора почему-то не виден
exec('/usr/bin/gpg',
'--decrypt',
'--batch',
'--passphrase-fd', $pwfd,
'encrypted.file',
) or die "Cannot exec gpg: $!\n";
exit 0; # child exits anyhow;
Оригинал этой записи в личном блоге.
Любые материалы из этого блога запрещается использовать на сайте livejournal.ru в любой форме и любом объёме.
no subject
my $d = <$pwread>
echo $d | gpg --passphrase-fd 0
no subject
no subject
ну и "FD_CLOEXEC, the close-on-exec flag. If the FD_CLOEXEC bit is 0, the file descriptor will remain open across an execve(2), otherwise it will be closed", так что не удивлюсь если оно тупо делает fcloseall при exec.
no subject
А вот вторая половина и есть почти правильный ответ, который для меня оказался совершенно не очевидным. fcloseall() там, конечно, никто не делает, но дескрипторы, номер которых больше $^F (по умолчанию 2) автоматически получают флаг FD_CLOEXEC, если его потом вручную не сбросить.
Хотя в документации это поведение вполне описано, но туда я, как обычно, заглянул в последнюю очередь.
no subject
no subject
И кстати, exec не порождает процесс, он заменяет выполняемую программу.
man 2 execve:
* By default, file descriptors remain open across an execve(). File descriptors that are marked close-on-exec are closed; see the
description of FD_CLOEXEC in fcntl(2). (If a file descriptor is closed, this will cause the release of all record locks
obtained on the underlying file by this process. See fcntl(2) for details.)
no subject
да, забыл про особенности exec. давно ничего такого не писал:(
no subject
Вот такой скриптик успешно запускает /bin/echo, но тот вместо foo в stdout говорит "/bin/echo: write error: Bad file descriptor" в stderr, потому что stdout у него нет.
#!/usr/bin/perl -wclose(STDIN) || print STDERR "Error closing STDIN: $!\n";
close(STDOUT) || print STDERR "Error closing STDOUT: $!\n";
exec("/bin/echo", "foo") || print STDERR "exec failed: $!\n";
А если ещё и stderr закрыть, то echo вообще ничего не говорит, потому что некуда.
no subject
no subject
no subject
no subject
no subject