/* 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