#!/usr/bin/ruby require 'optparse' require 'yaml' require 'fileutils' require 'pathname' bold = ["\033[0m", "\033[1m"] @options = {} def system!(cmd) system(cmd) or raise "failed to run #{cmd}" end def sanitize(str, where) raise "unsanitary string in #{where}" if (str =~ /[^\w.-]/) str end def sanitize_path(str, where) raise "unsanitary string in #{where}" if (str =~ /[^@\w\/.:+-]/) str end def info(str) puts str if @options[:verbose] end ################################ OptionParser.new do |opts| opts.banner = "Usage: build [options] .yml" opts.on("-v", "--verbose", "be more verbose") do |v| @options[:verbose] = v end @options[:markup] = true opts.on("-m", "--[no-]markup", "markup the output using ANSI escape codes") do |m| @options[:markup] = m end opts.on("-r REL", "--release REL", "release name") do |v| @options[:release] = v end opts.on("-d DEST", "--destination DEST", "directory to place signature in") do |v| @options[:destination] = v end opts.on("-c SIGNER", "--compare-to SIGNER", "compare other manifests to SIGNER's, if not given pick first") do |v| @options[:compareto] = v end end.parse! base_dir = Pathname.new(__FILE__).expand_path.dirname.parent build_desc_file = ARGV.shift or raise "must supply YAML build description file" build_desc = YAML.load_file(build_desc_file) in_sums = [] result_dir = 'result' package_name = build_desc["name"] or raise "must supply name" package_name = sanitize(package_name, "package name") destination = @options[:destination] || File.join(base_dir, "sigs", package_name) release = @options[:release] || "current" release = sanitize(release, "release") verbose = @options[:verbose] bold = ['', ''] unless @options[:markup] release_path = File.join(destination, release) File.exists?(release_path) or raise "#{release_path} does not exist" result_file = "#{package_name}-build.assert" sig_file = "#{result_file}.sig" current_manifest = nil if @options[:compareto] # Load a specific 'golden' manifest to compare to compareto = @options[:compareto] result_path = sanitize_path(File.join(release_path, compareto, result_file), "result path") result = YAML.load_file(result_path) current_manifest = result['out_manifest'] end did_fail = false Dir.foreach(release_path) do |signer_dir| next if signer_dir == "." or signer_dir == ".." signer_path = sanitize_path(File.join(release_path, signer_dir), "signer path") next if !File.directory?(signer_path) result_path = sanitize_path(File.join(signer_path, result_file), "result path") sig_path = sanitize_path(File.join(signer_path, sig_file), "result path") if !File.exist?(result_path) puts "missing result at #{result_path}" next end if !File.exist?(sig_path) puts "missing signature at #{sig_path}" next end result = YAML.load_file(result_path) system("gpg --keyserver pgp.mit.edu --recv-keys `gpg --quiet --batch --verify \"#{File.join(signer_path, 'signature.pgp')}\" \"#{result_path}\" 2>&1 | head -n1 | grep \"key ID\" | awk '{ print $15 }'` > /dev/null 2>&1") out = `gpg --quiet --batch --verify \"#{sig_path}\" \"#{result_path}\" 2>&1` if $? != 0 out.each_line do |line| if line =~ /^gpg: Signature made/ info(line) else puts line end end puts "#{bold[1]}#{signer_dir}: BAD SIGNATURE#{bold[0]}" puts did_fail = true elsif current_manifest and (result['out_manifest'] != current_manifest or result['release'] != release or result['name'] != package_name) out.each_line do |line| if line =~ /^gpg: Signature made/ info(line) elsif line =~ /^gpg: Good signature/ info(line) elsif line =~ /^gpg: aka/ info(line) else puts line end end puts "#{bold[1]}#{signer_dir}: MISMATCH#{bold[0]}" puts if verbose lines1 = current_manifest.each_line lines2 = result['out_manifest'].each_line lines1.zip(lines2) do |line| if line[0] != line[1] puts "- "+line[0] puts "+ "+line[1] end end end did_fail = true else out.each_line do |line| if line =~ /^gpg: Signature made/ info(line) elsif line =~ /^gpg: Good signature/ info(line) elsif line =~ /^gpg: aka/ info(line) else puts line end end puts "#{bold[1]}#{signer_dir}: OK#{bold[0]}" puts end if !current_manifest # take first manifest as 'current' to compare against current_manifest = result['out_manifest'] end end exit 1 if did_fail