1 #!/usr/perl5/bin/perl
   2 #
   3 # CDDL HEADER START
   4 #
   5 # The contents of this file are subject to the terms of the
   6 # Common Development and Distribution License (the "License").
   7 # You may not use this file except in compliance with the License.
   8 #
   9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10 # or http://www.opensolaris.org/os/licensing.
  11 # See the License for the specific language governing permissions
  12 # and limitations under the License.
  13 #
  14 # When distributing Covered Code, include this CDDL HEADER in each
  15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16 # If applicable, add the following below this CDDL HEADER, with the
  17 # fields enclosed by brackets "[]" replaced with your own identifying
  18 # information: Portions Copyright [yyyy] [name of copyright owner]
  19 #
  20 # CDDL HEADER END
  21 #
  22 #
  23 # ident "%Z%%M% %I%     %E% SMI"
  24 #
  25 # Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  26 # Use is subject to license terms.
  27 #
  28 
  29 # signit [-q] [-i dir][-o dir] [-l user]
  30 #
  31 # Client program for use with code signing server.
  32 # Reads a list of signing credential names and file pathnames
  33 # from standard input. Each file is read from the input directory,
  34 # sent to the signing server, signed with the specified credential, 
  35 # and written to the output directory.
  36 #
  37 # Options:
  38 #       -q      quiet operation: avoid printing files successfully signed
  39 #       -i dir  input directory (defaults to current dir)
  40 #       -o dir  output directory (defautls to input dir)
  41 #       -l user user account on signing server (defaults to current user)
  42 #
  43 # The CODESIGN_SERVER environment variable can be used to
  44 # specify the hostname or IP address of the signing server
  45 # (defaults to quill.sfbay).
  46 
  47 use strict;
  48 use Cwd;
  49 use File::Temp 'tempdir';
  50 use Getopt::Std;
  51 use IPC::Open2;
  52 
  53 #
  54 # Global variables
  55 #
  56 my ($Indir, $Outdir);   # Input and output directories (may be the same)
  57 my $Server;             # Signing server hostname
  58 my $Quiet;              # Suppress printing each file successfully signed
  59 my ($pid);              # Process id for ssh client
  60 my @cred_rules;         # Array of path prefixes and credentials to use
  61 my $Tmpdir = tempdir(CLEANUP => 1);  # Temporary directory
  62 my $Warnings = 0;       # Count of warnings returned
  63 
  64 
  65 #
  66 # Main program
  67 #
  68 
  69 $Server = $ENV{CODESIGN_SERVER} || "quill.sfbay";
  70 
  71 # Get command-line arguments
  72 our($opt_c, $opt_i, $opt_o, $opt_l, $opt_q);
  73 if (!getopts("i:o:c:l:q")) {
  74         die "Usage: $0 [-i dir] [-o dir] [-l user]\n";
  75 }
  76 $Quiet = $opt_q;
  77 
  78 # Get input/output directories
  79 $Indir = $opt_i || getcwd();    # default to current dir
  80 $Outdir = $opt_o || $Indir;     # default to input dir
  81 $Indir = getcwd() . "/$Indir" if (substr($Indir, 0, 1) ne "/");
  82 $Outdir = getcwd() . "/$Outdir" if (substr($Outdir, 0, 1) ne "/");
  83 
  84 # Ignore SIGPIPE to allow proper error messages
  85 $SIG{PIPE} = 'IGNORE';
  86 
  87 # Create ssh connection to server
  88 my(@args);
  89 if (defined($opt_l)) {
  90         push @args, "-l", $opt_l;
  91 }
  92 push @args, "-s", $Server, "codesign";
  93 $pid = open2(*SRV_OUT, *SRV_IN, "/usr/bin/ssh", @args) or 
  94         die "ERROR Connection to server $Server failed\n";
  95 select(SRV_IN); $| = 1; select(STDOUT); # unbuffered writes
  96 
  97 # Sign each file with the specified credential
  98 chdir($Indir);
  99 while (<>) {
 100         my ($cred, $path) = split;
 101 
 102         sign_file($cred, $path);
 103 }
 104 exit($Warnings > 0);
 105 
 106 #
 107 # END()
 108 #
 109 # Clean up after normal or abnormal exit.
 110 #
 111 sub END {
 112         my $old_status = $?;
 113 
 114         $? = 0;
 115         close(SRV_IN);
 116         close(SRV_OUT);
 117         waitpid($pid, 0) if ($pid);
 118         if ($?) {
 119                 print STDERR "ERROR Connection to server $Server failed\n";
 120                 $? = 1;
 121         }
 122         $? = $old_status if ($? == 0);
 123 }
 124 
 125 #
 126 # debug(msg)
 127 #
 128 # Print debug message to standard error.
 129 #
 130 sub debug {
 131         print STDERR "### @_";
 132 }
 133 
 134 #
 135 # check_response(str)
 136 #
 137 # Validate response from server. Print messages for warnings or errors,
 138 # and exit in the case of an error. If the response indicates a successful
 139 # signing operation, return the size of the output data.
 140 #
 141 sub check_response {
 142         my ($str) = @_;
 143 
 144         if ($str =~ /^OK SIGN (\d+)/) {
 145                 return ($1);
 146         }
 147         elsif ($str =~ /^OK/) {
 148                 return (0);
 149         }
 150         elsif ($str =~ /^WARNING/) {
 151                 print STDERR $str;
 152                 $Warnings++;
 153                 return (-1);
 154         }
 155         elsif ($str =~ /^ERROR/) {
 156                 print STDERR $str;
 157                 exit(1);
 158         }
 159         else {
 160                 printf STDERR "ERROR Protocol failure (%d)\n", length($str);
 161                 exit(1);
 162         }
 163 }
 164 
 165 #
 166 # sign_file(credential, filename)
 167 #
 168 # Send the file to the server for signing. Package the file into a
 169 # ZIP archive, send to the server, and extract the ZIP archive that
 170 # is returned. The input ZIP archive always contains a single file,
 171 # but the returned archive may contain one or more files.
 172 #
 173 sub sign_file {
 174         my ($cred, $path) = @_;
 175         my ($res, $size);
 176 
 177         $path =~ s:^\./::g; # remove leading "./"
 178         unlink("$Tmpdir/in.zip");
 179         system("cd $Indir; /usr/bin/zip -q $Tmpdir/in.zip $path");
 180 
 181         sendfile("$Tmpdir/in.zip", "$cred $path") || return;
 182 
 183         $res = <SRV_OUT>;
 184         $size = check_response($res);
 185         if ($size > 0) {
 186                 recvfile("$Tmpdir/out.zip", $size) || return;
 187                 
 188                 if (system("cd $Outdir; /usr/bin/unzip -qo $Tmpdir/out.zip")) {
 189                         $Warnings++;
 190                 } else {
 191                         print "$cred\t$path\n" unless $Quiet;
 192                 }
 193         }
 194 }
 195 
 196 #
 197 # sendfile(file, args)
 198 #
 199 # Send a ZIP archive file to the signing server. This involves
 200 # sending a SIGN command with the given arguments, followed by
 201 # the contents of the archive itself.
 202 #
 203 sub sendfile {
 204         my ($file, $args) = @_;
 205         my ($size, $bytes);
 206 
 207         $size = -s $file;
 208         print SRV_IN "SIGN $size $args\n";
 209         if (!open(F, "<$file")) {
 210                 print STDERR "$file: $!\n";
 211                 return (0);
 212         }
 213         read(F, $bytes, $size);
 214         close(F);
 215         if (!syswrite(SRV_IN, $bytes, $size)) {
 216                 print STDERR "Can't send to server: $!\n";
 217                 return (0);
 218         }
 219         return (1);
 220 }
 221 
 222 #
 223 # recvfile(file, size)
 224 #
 225 # Receive a ZIP archive from the signing server. The caller
 226 # provides the size argument previously obtained from the 
 227 # server response.
 228 #
 229 sub recvfile {
 230         my ($file, $size) = @_;
 231         my $bytes;
 232         
 233         if (!read(SRV_OUT, $bytes, $size)) {
 234                 print STDERR "Can't read from server: $!\n";
 235                 return (0);
 236         }
 237         if (!open(F, ">$file")) {
 238                 print STDERR "$file: $!\n";
 239                 return (0);
 240         }
 241         syswrite(F, $bytes, $size);
 242         close(F);
 243         return (1);
 244 }