/* pfs-signatures.c:
*
****************************************************************
* Copyright (C) 2003 Tom Lord
*
* See the file "COPYING" for further information about
* the copyright and warranty status of this work.
*/
#include "config-include/config-options.h"
#include "po/gettext.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/sys/types.h"
#include "hackerlab/os/sys/wait.h"
#include "hackerlab/os/signal.h"
#include "hackerlab/char/str.h"
#include "hackerlab/hash/md5-utils.h"
#include "hackerlab/hash/sha1-utils.h"
#include "hackerlab/rx-posix/regex.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/fs/file-names.h"
#include "libfsutils/file-contents.h"
#include "libfsutils/tmp-files.h"
#include "libawk/associative.h"
#include "libawk/trim.h"
#include "libarch/debug.h"
#include "libarch/my.h"
#include "libarch/namespace.h"
#include "libarch/archives.h"
#include "libarch/cached-archive.h"
#include <gpgme/gpgme.h>
#include "libarch/pfs-signatures.h"
/* __STDC__ prototypes for static functions */
static void exec_shell_command (t_uchar * command);
static t_uchar * archive_signing_rule_file (t_uchar * archive, int no_default);
static t_uchar * archive_signature_checking_rule_file (t_uchar * archive, int no_default, int signed_archive);
static int arch_pfs_memoize_checksum_file (struct arch_archive *archive, t_uchar const * revision, t_uchar * file_contents);
static int old_pfs_sign_for_archive (struct arch_archive * archive, t_uchar * revision, t_uchar * sigfile, int in_fd, int out_fd);
static int gpgme_check_sig(struct arch_archive * archive, t_uchar ** signed_message);
static t_uchar * arch_pfs_revision_key (struct arch_pfs_archive * arch, t_uchar const * revision);
static int arch_pfs_check_checksum_data (struct arch_pfs_archive * arch, t_uchar const * revision);
static int arch_pfs_check_one_checksum_file (struct arch_pfs_archive *arch, t_uchar const * revision, t_uchar const * revision_dir, t_uchar const *check_file_name, int checksum_expected);
static int arch_pfs_old_check_signature_for_archive (struct arch_archive * archive, t_uchar * signed_message);
static int
run_shell (t_uchar const *shell, int in_fd, int out_fd)
{
/* Generate the signature anew */
int pid = fork ();
if (pid == -1)
{
safe_printfmt (2, "unable to fork to run %s\n", shell);
return -1;
}
if (pid)
{
int status;
int wait_pid;
wait_pid = waitpid (pid, &status, 0);
if (wait_pid < 0)
{
panic_msg ("error waiting for signature generation subprocess");
kill (0, SIGKILL);
panic ("error waiting for subprocess");
}
if (WIFSIGNALED (status))
{
safe_printfmt (2, "\n");
safe_printfmt (2, "signature subprocess killed by signal %d\n", WTERMSIG (status));
safe_printfmt (2, "\n");
return -1;
}
else if (!WIFEXITED (status))
{
panic_msg ("waitpid returned for a non-exited process");
kill (0, SIGKILL);
panic ("waitpid returned for a non-exited process");
}
else
{
int exit_status;
exit_status = WEXITSTATUS (status);
if (exit_status)
{
safe_printfmt (2, ("signature command exited with non-0 status (%d)\n"
"\n"
"command: %s\n"
"\n"),
exit_status,
shell);
return -1;
}
else
return 0;
}
}
else
{
safe_move_fd (in_fd, 0);
safe_move_fd (out_fd, 1);
exec_shell_command ((t_uchar *)shell);
panic ("not reached");
}
panic ("not reached");
kill (0, SIGKILL);
return -1;
}
static int
copy_signature(struct arch_archive *from, struct arch_archive *to, t_uchar const * sigfile, t_uchar const * revision, int out_fd)
{
/* Copy signature from another archive. */
struct arch_pfs_archive * pfs_sig_from;
t_uchar * revdir_from = 0;
t_uchar * sigpath = 0;
t_uchar * signed_checksum = 0;
revdir_from = arch_fs_archive_revision_path (from, 0, revision);
sigpath = file_name_in_vicinity (0, revdir_from, sigfile);
/* EVIL. GARH
* RBC 20050309 this evil occurs because copying the signatures
* is an abstraction violation: signing code is not (instrinsically)
* pfs specific, making it archive level will fix this bogon.
*/
if (!str_cmp (from->vtable->type, "pfs"))
pfs_sig_from = (struct arch_pfs_archive *)from;
else if (arch_archive_is_cached_connection (from))
pfs_sig_from = (struct arch_pfs_archive *)(arch_cached_archive_as_pfs(from));
else
{
safe_printfmt (2, _("\n"
"\n"
"********************************\n"
"\n"
"ARCH DOES NOT KNOW HOW TO COPY\n"
"CHECKSUMS FROM\n"
"%s(%s)\n"
"TO\n"
"%s(%s)\n"
"THIS IS A BUG, PLEASE REPORT IT\n"
"HOW TO COPY FROM THAT ARCHIVE.\n"
"\n"
"********************************\n"
"\n"
"\n"),
from->location,from->vtable->type,
to->location, to->vtable->type);
exit (2);
}
signed_checksum = arch_pfs_file_contents (pfs_sig_from->pfs, sigpath, 1);
if (!signed_checksum || arch_pfs_memoize_checksum_file (to, revision, signed_checksum))
{
safe_printfmt (2, ("\n"
"\n"
"********************************\n"
"\n"
"ERROR READING CHECKSUM FILE FROM \n"
"%s(%s)\n"
"\n"
"********************************\n"
"\n"
"\n"),
from->location,from->vtable->type);
exit (2);
}
safe_write_retry (out_fd, signed_checksum, str_length (signed_checksum));
lim_free (0, signed_checksum);
lim_free (0, revdir_from);
lim_free (0, sigpath);
return 0;
}
int
arch_pfs_has_signing_rule (t_uchar * archive)
{
t_uchar * rule_file = 0;
int answer = 0;
rule_file = archive_signing_rule_file (archive, 1);
answer = !!rule_file;
lim_free (0, rule_file);
return answer;
}
/* TODO tidy this up into appropriate source files */
#include "libinifile/inifile.h"
static t_uchar *
get_gpg_command (struct arch_archive * archive)
{
inifile_t inifile;
t_uchar *result;
t_uchar *options;
arch_archives_get_archive_ini (archive->official_name, &inifile);
result = inifile_get_single_string (&inifile, "", "gpg_command", "gpg");
if (!str_length(result))
{
lim_free (0, result);
result = str_save (0, "gpg");
}
options = inifile_get_single_string (&inifile, "", "gpg_options", NULL);
if (options)
{
result = str_replace (result, str_alloc_cat_many (0, result, " ", options, str_end));
lim_free (0, options);
}
inifile_finalise (&inifile);
return result;
}
/**
* \brief generate a signed checksum file for an archive
*
* \param archive the archive the checksum will be going in
* \param revision the revision the checksum is for
* \param sigfile the name of the old sigfile if we are copying from another archive
* \param in_fd stdin for the signing process
* \param out_fd where to write the signature file to (stdout for external processes)
* \param from_archive the archive handle we can copy the old signature from
* \return 0 on success
*/
int
arch_pfs_sign_for_archive (struct arch_archive * archive, t_uchar * revision, t_uchar * sigfile, int in_fd, int out_fd, struct arch_archive *from_archive)
{
int answer = 0;
if (!archive->in_registry)
return old_pfs_sign_for_archive (archive, revision, sigfile, in_fd, out_fd);
/* determine need for mirror vs signing here */
if (from_archive)
answer = copy_signature(from_archive, archive, sigfile, revision, out_fd);
else
{
t_uchar * gpg_command = get_gpg_command (archive);
t_uchar * rule = str_alloc_cat (0, gpg_command, " --clearsign");
answer = run_shell (rule, in_fd, out_fd);
lim_free (0, rule);
lim_free (0, gpg_command);
}
if (!answer)
return 0;
safe_printfmt (2, _("\n"
"(You may also have to use baz lock-revision -b before\n"
" retrying this transaction. See baz lock-revision -H)\n"
"\n"));
return -1;
}
int
old_pfs_sign_for_archive (struct arch_archive * archive, t_uchar * revision, t_uchar * sigfile, int in_fd, int out_fd)
{
t_uchar * rule_file = 0;
t_uchar * rule = 0;
int answer = 0;
/* This can go completely when we stop supporting old style signing rules
* i.e. baz 1.4
* RBC 20050309
*/
rule_file = archive_signing_rule_file (archive->registered_name, 0);
if (!rule_file)
{
safe_printfmt (2, ("\n"
"\n"
"********************************\n"
"SIGNATURE DEMANDED FOR ARCHIVE\n"
" %s\n"
"BUT NO RULE PROVIDED\n"
"\n"
"Consider creating ~/.arch-params/signing/%s\n"
" or ~/.arch-params/signing/=default\n"
"\n"
"********************************\n"
"\n"
"\n"),
archive->registered_name, archive->registered_name);
generic_error_exit:
safe_printfmt (2, ("\n"
"(You may also have to use baz lock-revision -b before\n"
" retrying this transaction. See baz lock-revision -H)\n"
"\n"));
answer = -1;
generic_return:
lim_free (0, rule_file);
lim_free (0, rule);
return answer;
}
rule = file_contents (rule_file);
rule = trim_surrounding_ws (rule);
if (arch_valid_archive_name (rule))
{
/* Copy signature from another archive. */
/* FIXME-REMOVENAME nuke this legacy gpg rules
* DO NOT turn this into _connect_branch
*/
struct arch_archive * sig_from = arch_archive_connect_ext (rule, NULL, 0);
answer = copy_signature(sig_from, archive, sigfile, revision, out_fd);
arch_archive_close (sig_from);
return 0;
}
else
if (run_shell (rule, in_fd, out_fd))
{
goto generic_error_exit;
}
else
{
goto generic_return;
}
}
int
arch_pfs_check_signature_for_archive (struct arch_archive * archive, t_uchar ** signed_message)
{
if (!archive->in_registry)
return arch_pfs_old_check_signature_for_archive (archive, *signed_message);
if (archive->signed_archive)
return gpgme_check_sig(archive, signed_message);
return 0;
}
int
arch_pfs_old_check_signature_for_archive (struct arch_archive * archive, t_uchar * signed_message)
{
t_uchar * rule_file = 0;
t_uchar * rule = 0;
t_uchar * tmp_path = 0;
int tmp_fd = -1;
int pid;
int answer = 0;
rule_file = archive_signature_checking_rule_file (archive->official_name, 0, archive->signed_archive);
if (!rule_file)
{
generic_return:
lim_free (0, rule_file);
lim_free (0, rule);
lim_free (0, tmp_path);
if (tmp_fd > 0)
safe_close (tmp_fd);
return answer;
}
tmp_path = tmp_file_name_in_tmp ("checksum-contents");
tmp_fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0);
safe_unlink (tmp_path);
if (signed_message)
safe_write_retry (tmp_fd, signed_message, str_length (signed_message));
safe_lseek (tmp_fd, (off_t)0, SEEK_SET);
rule = file_contents (rule_file);
pid = fork ();
if (pid == -1)
{
safe_printfmt (2, "unable to fork to run %s\n", rule);
answer = -1;
goto generic_return;
}
if (pid)
{
int status;
int wait_pid;
wait_pid = waitpid (pid, &status, 0);
if (wait_pid < 0)
{
panic_msg ("error waiting for signature checking subprocess");
kill (0, SIGKILL);
panic ("error waiting for subprocess");
}
if (WIFSIGNALED (status))
{
safe_printfmt (2, "\n");
safe_printfmt (2, "signature checking subprocess killed by signal %d\n", WTERMSIG (status));
safe_printfmt (2, "\n");
answer = -1;
goto generic_return;
}
else if (!WIFEXITED (status))
{
panic_msg ("waitpid returned for a non-exited process");
kill (0, SIGKILL);
panic ("waitpid returned for a non-exited process");
}
else
{
int exit_status;
exit_status = WEXITSTATUS (status);
if (exit_status)
answer = -1;
goto generic_return;
}
}
else
{
safe_move_fd (tmp_fd, 0);
exec_shell_command (rule);
panic ("not reached");
}
panic ("not reached");
kill (0, SIGKILL);
return -1;
}
static void
exec_shell_command (t_uchar * command)
{
char * argv[5];
argv[0] = "sh";
argv[1] = "-e";
argv[2] = "-c";
argv[3] = (char *)command;
argv[4] = 0;
execv (cfg__posix_shell, argv);
panic ("not reached");
}
static t_uchar *
archive_signing_rule_file (t_uchar * archive, int no_default)
{
t_uchar * home;
t_uchar * rules_dir = 0;
t_uchar * answer = 0;
home = getenv ("HOME");
invariant (!!home);
rules_dir = file_name_in_vicinity (0, arch_my_arch_params(), "signing");
answer = arch_archives_signature_signing_rule_file (archive);
if (safe_access (answer, F_OK))
{
lim_free (0, answer);
answer = 0;
if (!no_default)
{
answer = file_name_in_vicinity (0, rules_dir, "=default");
if (safe_access (answer, F_OK))
{
lim_free (0, answer);
answer = 0;
}
}
}
lim_free (0, rules_dir);
return answer;
}
static t_uchar *
archive_signature_checking_rule_file (t_uchar * archive, int no_default, int signed_archive)
{
static assoc_table already_checked = 0;
static assoc_table already_know = 0;
static t_uchar exact[] = "exact";
static t_uchar yes[] = "yes";
t_uchar * check_rule_name = 0;
t_uchar * home;
t_uchar * rules_dir = 0;
t_uchar * answer = 0;
t_uchar * checked = 0;
int exact_find = 0;
checked = assoc_ref (already_checked, archive);
answer = assoc_ref (already_know, archive);
if (answer)
{
if (no_default && (checked != exact))
{
answer = 0;
}
else
{
answer = str_save (0, answer);
}
}
else if (!checked)
{
if (signed_archive)
check_rule_name = str_alloc_cat (0, archive, ".check");
else
check_rule_name = str_alloc_cat (0, archive, ".unsigned-check");
home = getenv ("HOME");
invariant (!!home);
rules_dir = file_name_in_vicinity (0, arch_my_arch_params(), "signing");
answer = file_name_in_vicinity (0, rules_dir, check_rule_name);
if (!safe_access (answer, F_OK))
{
exact_find = 1;
}
else
{
lim_free (0, answer);
if (no_default)
answer = 0;
else
{
if (signed_archive)
answer = file_name_in_vicinity (0, rules_dir, "=default.check");
else
answer = file_name_in_vicinity (0, rules_dir, "=default.unsigned-check");
if (safe_access (answer, F_OK))
{
lim_free (0, answer);
answer = 0;
}
}
}
if (answer || !no_default)
{
assoc_set (&already_checked, archive, exact_find ? exact : yes);
if (answer)
{
assoc_set (&already_know, archive, answer);
}
else if (signed_archive)
{
safe_printfmt (2, ("\n"
"WARNING: no rule found for checking signatures from %s\n"
"\n"
" Consider creating ~/.arch-params/signing/%s.check\n"
" or ~/.arch-params/signing/=default.check\n"
"\n"),
archive, archive);
}
}
}
lim_free (0, check_rule_name);
lim_free (0, rules_dir);
return answer;
}
static assoc_table remembered_md5s = 0;
/*
* keys are
* $url $revision $file
*
* values are (ascii, hex) md5 checksums.
*/
static assoc_table remembered_sha1s = 0;
/*
* keys are
* $url $revision $file
*
* values are (ascii, hex) SHA1 checksums.
*/
static assoc_table checked_revisions = 0;
/*
* keys are
* $url $revision
*
* values are "yes" meaning that the checksum
* file for that revision has been read.
*/
static assoc_table checksummed_revisions = 0;
/*
* keys are
* $url $revision
*
* values are "yes" meaning that a checksum
* file for that revision was found.
*/
void
arch_pfs_invalidate_checksum_data (struct arch_pfs_archive * arch, t_uchar * revision)
{
t_uchar *revision_key = arch_pfs_revision_key (arch, revision);
if (assoc_ref (checked_revisions, revision_key))
assoc_del (checked_revisions, revision_key);
lim_free (0, revision_key);
}
t_uchar *
arch_pfs_revision_key (struct arch_pfs_archive * arch, t_uchar const * revision)
{
/* FIXME-REMOVENAME registered_name -> location here */
return str_alloc_cat_many (0, arch->arch.registered_name, " ", revision, str_end);
}
int
arch_pfs_ensure_checksum_data (struct arch_pfs_archive * arch, t_uchar const * revision)
{
int answer = 0;
t_uchar * revision_key = arch_pfs_revision_key (arch, revision);
if (!assoc_ref (checked_revisions, revision_key))
answer = arch_pfs_check_checksum_data (arch, revision);
lim_free (0, revision_key);
return answer;
}
int
arch_pfs_check_checksum_data (struct arch_pfs_archive * arch, t_uchar const * revision)
{
t_uchar * revision_key = arch_pfs_revision_key (arch, revision);
t_uchar * revision_dir = 0;
# define N_CHECKSUM_FILES 3
static char * checksum_file_name[N_CHECKSUM_FILES] = { "checksum", "checksum.cacherev", "ancestry.gz.checksum" };
int x;
int answer = 0;
if (!arch->arch.in_registry)
{
/* If the user thinks he wants to signature-check this archive,
* but the archive doesn't think it's signed, then freak out.
* FIXME: 20030512 RBC - remove this whole block
* when we remove support for registered named in baz 1.4
*/
t_uchar * check_rule = archive_signature_checking_rule_file (arch->arch.registered_name, 1, 1);
if (check_rule && !arch->arch.signed_archive)
{
safe_printfmt (2, _("\n"
"\n"
"********************************\n"
"\n"
"ARCHIVE NOT SIGNED BUT YOU HAVE A RULE\n"
"FOR SIGNATURE CHECKING THIS ARCHIVE.\n"
"\n"
" archive: %s\n"
" revision %s\n"
"\n"
" your rule file: %s\n"
"\n"
"********************************\n"
"\n"
"\n"),
arch->arch.registered_name, revision, check_rule);
answer = -1;
}
lim_free (0, check_rule);
}
else
{
if (arch_archive_check_signed_status (&arch->arch, -1))
answer = -1;
}
revision_dir = arch_fs_archive_revision_path (&arch->arch, 0, revision);
for (x = 0; x < N_CHECKSUM_FILES; ++x)
answer = answer | arch_pfs_check_one_checksum_file (arch, revision, revision_dir, checksum_file_name[x], !x);
assoc_set (&checked_revisions, revision_key, "yes");
lim_free (0, revision_dir);
lim_free (0, revision_key);
return answer;
}
int
arch_pfs_check_one_checksum_file (struct arch_pfs_archive *arch, t_uchar const * revision, t_uchar const * revision_dir, t_uchar const *checksum_file_name, int checksum_expected)
{
int answer = 0;
t_uchar * file_path = 0;
t_uchar * file_contents = 0;
t_uchar * revision_key = arch_pfs_revision_key (arch, revision);
file_path = file_name_in_vicinity (0, revision_dir, checksum_file_name);
file_contents = arch_pfs_file_contents (arch->pfs, file_path, 1);
if (file_contents)
{
assoc_set (&checksummed_revisions, revision_key, "yes");
}
else if (checksum_expected)
{
if (arch->arch.signed_archive)
{
safe_printfmt (2, ("\n"
"\n"
"********************************\n"
"\n"
"NO CHECKSUM %s FOUND FOR REVISION!\n"
" IN ARCHIVE THAT'S SUPPOSED TO\n"
" BE SIGNED!\n"
"\n"
" archive: %s\n"
" revision %s\n"
"\n"
"********************************\n"
"\n"
"\n"),
checksum_file_name, arch->arch.registered_name, revision);
answer = -1;
}
else if (arch->arch.type != arch_archive_tla)
{
/* vanilla tla is the last format that did not have mandatory checksums */
safe_printfmt (2, ("\n"
"\n"
"********************************\n"
"\n"
"NO CHECKSUM %s FOUND FOR REVISION!\n"
"\n"
" revision: %s/%s\n"
" URL: %s\n"
"\n"
"********************************\n"
"\n"
"\n"),
checksum_file_name, arch->arch.official_name, revision, arch->arch.location);
answer = -1;
}
else
{
safe_printfmt (2, ("\n"
"********************************\n"
"NO CHECKSUM %s FOUND FOR REVISION\n"
" (unsigned archive, continuing anyway)\n"
"\n"
" archive: %s\n"
" revision %s\n"
"\n"
"********************************\n"
"\n"),
checksum_file_name, arch->arch.registered_name, revision);
}
}
/* if we have file data, or its an unsigned archive ???
* and the check fails.?
*/
debug (dbg_archive_pfs, 3, "checking signature for %s on revision %s/%s\n", checksum_file_name, arch->arch.official_name, revision);
if ((file_contents || !arch->arch.signed_archive) && arch_pfs_check_signature_for_archive (&arch->arch, &file_contents))
{
safe_printfmt (2, ("\n"
"\n"
"********************************\n"
"\n"
"INVALID SIGNATURE ON REVISION!\n"
" archive: %s\n"
" revision %s\n"
" checksum file: %s\n"
"\n"
"********************************\n"
"\n"
"\n"),
arch->arch.registered_name, revision, checksum_file_name);
answer = -1;
}
else if (file_contents && arch_pfs_memoize_checksum_file (&arch->arch, revision, file_contents))
{
safe_printfmt (2, ("\n"
"\n"
"********************************\n"
"\n"
"INVALID CHECKSUM FILE SYNTAX FOR REVISION!\n"
" archive: %s\n"
" revision %s\n"
" checksum file: %s\n"
"\n"
"********************************\n"
"\n"
"\n"),
arch->arch.registered_name, revision, checksum_file_name);
answer = -1;
}
lim_free (0, revision_key);
lim_free (0, file_path);
lim_free (0, file_contents);
return answer;
}
static void
arch_pfs_insert_key (t_uchar *line, t_uchar *key_prefix, regmatch_t *file, regmatch_t *hash, assoc_table *table)
{
t_uchar * file_name = 0;
t_uchar * assoc_key = 0;
t_uchar * val = 0;
file_name = str_save_n (0, line + file->rm_so, file->rm_eo - file->rm_so);
assoc_key = str_alloc_cat (0, key_prefix, file_name);
val = str_save_n (0, line + hash->rm_so, hash->rm_eo - hash->rm_so);
assoc_set (table, assoc_key, val);
lim_free (0, file_name);
lim_free (0, assoc_key);
lim_free (0, val);
}
static int
arch_pfs_memoize_checksum_file (struct arch_archive *archive, t_uchar const * revision, t_uchar * file_contents)
{
static char file_head_pattern[] = "^Signature-for: ([^/]+)/(.+)$";
static char md5_pattern[] = "^md5 ([^\n ]+) ([a-f0-9]+)";
static char sha1_pattern[] = "^sha1 ([^\n ]+) ([a-f0-9]+)";
static int is_compiled = 0;
static regex_t file_head_preg;
static regex_t md5_preg;
static regex_t sha1_preg;
regmatch_t pmatch[3];
if (!is_compiled)
{
if (regcomp (&file_head_preg, file_head_pattern, REG_EXTENDED | REG_NEWLINE))
panic ("unable to compile checksum-file head regexp");
if (regcomp (&md5_preg, md5_pattern, REG_EXTENDED))
panic ("unable to compile checksum-file MD5 regexp");
if (regcomp (&sha1_preg, sha1_pattern, REG_EXTENDED))
panic ("unable to compile checksum-file SHA1 regexp");
is_compiled = 1;
}
if (regexec (&file_head_preg, file_contents, 3, pmatch, 0))
return -1;
if (str_cmp_n (archive->official_name, str_length (archive->official_name),
file_contents + pmatch[1].rm_so, (pmatch[1].rm_eo - pmatch[1].rm_so)))
return -1;
if (str_cmp_n (revision, str_length (revision),
file_contents + pmatch[2].rm_so, (pmatch[2].rm_eo - pmatch[2].rm_so)))
return -1;
{
t_uchar * key_prefix = 0;
/* so this is the URL to the revision, for all intents and purposes
* so it should be location not archive-name
*/
invariant (archive->location != NULL);
key_prefix = str_alloc_cat_many (0, archive->location, " ", revision, " ", str_end);
file_contents = str_chr_index (file_contents, '\n');
if (file_contents)
file_contents++;
while (file_contents && *file_contents)
{
if (!regexec (&md5_preg, file_contents, 3, pmatch, 0))
arch_pfs_insert_key (file_contents, key_prefix, &pmatch[1], &pmatch[2], &remembered_md5s);
else if (!regexec (&sha1_preg, file_contents, 3, pmatch, 0))
arch_pfs_insert_key (file_contents, key_prefix, &pmatch[1], &pmatch[2], &remembered_sha1s);
else
; /*
* Ignore this line for now; it may be another hash or other metadata
* that was added by a future tla version.
*/
file_contents = str_chr_index (file_contents, '\n');
if (file_contents)
file_contents++;
}
lim_free (0, key_prefix);
}
return 0;
}
int
arch_pfs_checksum_anticipates_file (struct arch_archive * archive, t_uchar const * revision, t_uchar * file)
{
t_uchar * key = 0;
int answer;
key = str_alloc_cat_many (0, archive->location, " ", revision, " ", file, str_end);
/*
* Only MD5 is required for now. Perhaps at a later point we will
* also require SHA1.
*/
answer = !!assoc_ref (remembered_md5s, key);
lim_free (0, key);
return answer;
}
int
arch_pfs_checksum_governs (struct arch_pfs_archive * archive, t_uchar const * revision)
{
t_uchar * key = 0;
int answer;
#undef FIXME
/* Under what circumstance should it be an error that a revision
* lacks a checksum file? (*_ensure_* above should handle this)
*/
key = arch_pfs_revision_key (archive, revision);
answer = !!assoc_ref (checksummed_revisions, key);
lim_free (0, key);
return answer;
}
int
arch_pfs_checksum_governs_strictly (struct arch_pfs_archive * arch)
{
return arch->arch.signed_archive;
}
static void
arch_pfs_validate_checksums (struct arch_archive *arch, t_uchar *revision, t_uchar *tail, t_uchar *key, t_uchar *contents_md5, t_uchar *contents_sha1)
{
t_uchar * remembered_md5 = 0;
t_uchar * remembered_sha1 = 0;
remembered_md5 = assoc_ref (remembered_md5s, key);
remembered_sha1 = assoc_ref (remembered_sha1s, key);
if ((remembered_md5 && str_cmp (remembered_md5, contents_md5))
|| (remembered_sha1 && str_cmp (remembered_sha1, contents_sha1)))
{
safe_printfmt (2, ("\n"
"********************************\n"
" MISMATCHED ARCHIVE CHECKSUM\n"
"\n"
" archive: %s\n"
" revision: %s\n"
" url: %s\n"
" file: %s\n"
"\n"
" expected:\n"), arch->official_name, revision, arch->location, tail);
if (remembered_md5)
safe_printfmt (2, (" MD5: %s\n"), remembered_md5);
if (remembered_sha1)
safe_printfmt (2, (" SHA1: %s\n"), remembered_sha1);
safe_printfmt (2, (" got:\n"));
if (remembered_md5)
safe_printfmt (2, (" MD5: %s\n"), contents_md5);
if (remembered_sha1)
safe_printfmt (2, (" SHA1: %s\n"), contents_sha1);
safe_printfmt (2, ("\n"
"********************************\n"
"\n"));
exit (2);
}
}
t_uchar *
arch_pfs_checked_file_contents (struct arch_pfs_archive * arch, t_uchar * revision, t_uchar * path)
{
if (!arch_pfs_checksum_governs (arch, revision))
return arch_pfs_file_contents (arch->pfs, path, 0);
else
{
t_uchar * tail = 0;
t_uchar * key = 0;
t_uchar * contents = 0;
t_uchar * contents_md5 = 0;
t_uchar * contents_sha1 = 0;
tail = file_name_tail (0, path);
key = arch_pfs_revision_key (arch, revision);
contents = arch_pfs_file_contents (arch->pfs, path, 0);
contents_md5 = md5_ascii_for_str (0, contents);
contents_sha1 = sha1_ascii_for_str (0, contents);
arch_pfs_validate_checksums (&arch->arch, revision, tail, key, contents_md5, contents_sha1);
lim_free (0, tail);
lim_free (0, key);
lim_free (0, contents_md5);
lim_free (0, contents_sha1);
return contents;
}
}
void
arch_pfs_checked_get_file (struct arch_pfs_archive * arch, t_uchar * revision, int out_fd, t_uchar * path)
{
if (!arch_pfs_checksum_governs (arch, revision))
arch_pfs_get_file (arch->pfs, out_fd, path, 0);
else
{
t_uchar * tail = 0;
t_uchar * key = 0;
t_uchar * tmp_path = 0;
int tmp_fd = -1;
t_uchar buf[4096];
md5_context_t md5c = 0;
sha1_context_t sha1c = 0;
t_uchar computed_md5_raw[16];
t_uchar computed_sha1_raw[20];
t_uchar * computed_md5 = 0;
t_uchar * computed_sha1 = 0;
tail = file_name_tail (0, path);
key = arch_pfs_revision_key (arch, revision);
tmp_path = tmp_file_name_in_tmp ("arch-file");
tmp_fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0);
safe_unlink (tmp_path);
arch_pfs_get_file (arch->pfs, tmp_fd, path, 0);
safe_lseek (tmp_fd, (off_t)0, SEEK_SET);
md5c = make_md5_context (0);
sha1c = make_sha1_context (0);
while (1)
{
ssize_t amt;
amt = safe_read_retry (tmp_fd, buf, sizeof (buf));
if (!amt)
break;
else
{
md5_scan (md5c, buf, (size_t)amt);
sha1_scan (sha1c, buf, (size_t)amt);
safe_write_retry (out_fd, buf, (size_t)amt);
}
}
md5_final (computed_md5_raw, md5c);
sha1_final (computed_sha1_raw, sha1c);
computed_md5 = md5_alloc_ascii (0, computed_md5_raw);
computed_sha1 = sha1_alloc_ascii (0, computed_sha1_raw);
arch_pfs_validate_checksums (&arch->arch, revision, tail, key, computed_md5, computed_sha1);
lim_free (0, tail);
lim_free (0, key);
lim_free (0, tmp_path);
safe_close (tmp_fd);
free_md5_context (0, md5c);
lim_free (0, computed_md5);
}
}
static rel_table
inifile_get_ws_list (inifile_t *inifile, t_uchar const * section, t_uchar const *key)
{
int index;
rel_table answer = NULL;
rel_table temp = inifile_get_key_values (inifile, section, key);
rel_for_each (temp, index)
{
if (!str_length (temp[index][0]))
{
rel_free_table (answer);
answer = NULL;
}
else
{
rel_table next_add = rel_ws_split (temp[index][0]);
rel_append_x (&answer, next_add);
rel_free_table (next_add);
}
}
rel_free_table (temp);
return answer;
}
/**
* \brief check a signed message with the policy for archive
* \param archive the archive the message came from
* \param signed_message the clearsigned message, is replaced with the signed content.
* \return 0 on success;
*/
int
gpgme_check_sig(struct arch_archive * archive, t_uchar ** signed_message)
{
int result;
static assoc_table missing_keys = 0;
gpgme_error_t error;
gpgme_ctx_t context;
gpgme_data_t signature_data;
gpgme_data_t signed_content;
invariant (GPG_ERR_NO_ERROR == gpgme_new (&context));
invariant (GPG_ERR_NO_ERROR == gpgme_data_new_from_mem (&signature_data, *signed_message, str_length (*signed_message), 0));
invariant (GPG_ERR_NO_ERROR == gpgme_data_new (&signed_content));
error = gpgme_op_verify (context, signature_data, NULL, signed_content);
if (error == GPG_ERR_NO_DATA)
{
/* not signed at all */
safe_printfmt (2, "Not a signed checksum.\n");
goto error_exit;
}
else if (error == GPG_ERR_INV_VALUE)
{
/* didn't get good data to pass in */
safe_printfmt (2, "No valid data to check.\n");
goto error_exit;
}
else if (error == GPG_ERR_NO_ERROR)
{
size_t len;
gpgme_verify_result_t verify;
gpgme_signature_t sig;
inifile_t inifile;
rel_table fingerprints = NULL;
rel_table ids = NULL;
result = 0;
arch_archives_get_archive_ini (archive->official_name, &inifile);
fingerprints = inifile_get_ws_list (&inifile, "", "allowed_fingerprints");
ids = inifile_get_ws_list (&inifile, "", "allowed_ids");
inifile_finalise (&inifile);
/* ok verified the signature */
verify = gpgme_op_verify_result (context);
sig = verify->signatures;
while (sig)
{
gpgme_key_t key;
int found = 0;
gpgme_get_key (context, sig->fpr, &key, 0);
if (!key && assoc_ref(missing_keys, sig->fpr) == NULL)
{
assoc_set (&missing_keys, sig->fpr, sig->fpr);
safe_printfmt (2, _("Key for signature missing - cannot verify signature (%s)\n"), sig->fpr);
}
if (!key || key->revoked || key->expired || key->disabled || key->invalid)
{
sig = sig->next;
continue;
}
//safe_printfmt (2, "%p %d %s %s %d %d\n", sig, gpg_err_code (sig->status), gpg_strerror (gpg_err_code (sig->status)), sig->fpr, sig->wrong_key_usage, sig->summary);
// validity ?
/* policy on ids, and fingerprints */
/* is the fingerprint in the allowed list */
if (rel_n_records (fingerprints))
{
int index;
rel_for_each (fingerprints, index)
if (!str_casecmp (sig->fpr, fingerprints[index][0]))
found = -1;
}
/* is the id or uids requested available ? */
#if 0
safe_printfmt (2, "key: rev %d exp: %d dis %d inv %d pro %d\n", key->revoked, key->expired, key->disabled, key->invalid, key->protocol);
#endif
if (rel_n_records (ids))
{
int index;
gpgme_user_id_t uid = key->uids;
gpgme_subkey_t subkey;
while (uid)
{
if (uid->validity == GPGME_VALIDITY_MARGINAL ||
uid->validity == GPGME_VALIDITY_FULL ||
uid->validity == GPGME_VALIDITY_ULTIMATE)
{
//safe_printfmt (2, "valid: %s\n", uid->email);
rel_for_each (ids, index)
if (!str_casecmp (uid->email, ids[index][0]))
found = -1;
}
#if 0
else
safe_printfmt (2, "not valid: %d:%s\n", uid->validity, uid->email);
#endif
uid = uid->next;
}
subkey = key->subkeys;
while (subkey)
{
if (subkey->revoked || subkey->expired || subkey->disabled || subkey->invalid)
{
subkey = subkey->next;
continue;
}
rel_for_each (ids, index)
if (!str_casecmp (subkey->keyid, ids[index][0]))
found = -1;
subkey = subkey->next;
}
}
if (!found && (rel_n_records(ids) || rel_n_records (fingerprints)))
{
safe_printfmt (2, _("No ID or fingerprint found in the allowed list for this checksum.\n"));
result = -1;
}
sig = sig->next;
}
rel_free_table (fingerprints);
rel_free_table (ids);
if (result)
goto error_exit;
/* policy on archive name can be checked now */
/* now we need to get a list of the trustable uids that the creator and archive
* can match against
*/
*signed_message = str_replace (*signed_message, gpgme_data_release_and_get_mem (signed_content, &len));
*signed_message = str_replace (*signed_message, str_save_n (0, *signed_message, len));
goto cleanup_exit;
}
else
{
/* crypto error result */
safe_printfmt (2, "Failed to verify signature data: error: %d (%s)\n", error, gpgme_strerror (error));
safe_printfmt (2, "checksum we attempted to verify:\n=====\n%s\n=====\n", *signed_message);
goto error_exit;
}
error_exit:
result = -1;
gpgme_data_release (signed_content);
goto cleanup_exit;
cleanup_exit:
gpgme_release (context);
gpgme_data_release (signature_data);
return result;
}
/* tag: Tom Lord Wed Dec 24 19:41:23 2003 (pfs-signatures.c)
*/
syntax highlighted by Code2HTML, v. 0.9.1