1238384Sjkim#!/usr/bin/perl -w
2238384Sjkim# Written by Zoltan Glozik <zglozik@stones.com>.
3238384Sjkim# Copyright (c) 2002 The OpenTSA Project.  All rights reserved.
4238384Sjkim$::version = '$Id: tsget,v 1.1.2.2 2009/09/07 17:57:02 steve Exp $';
5238384Sjkim
6238384Sjkimuse strict;
7238384Sjkimuse IO::Handle;
8238384Sjkimuse Getopt::Std;
9238384Sjkimuse File::Basename;
10238384Sjkimuse WWW::Curl::Easy;
11238384Sjkim
12238384Sjkimuse vars qw(%options);
13238384Sjkim
14238384Sjkim# Callback for reading the body.
15238384Sjkimsub read_body {
16238384Sjkim    my ($maxlength, $state) = @_;
17238384Sjkim    my $return_data = "";
18238384Sjkim    my $data_len = length ${$state->{data}};
19238384Sjkim    if ($state->{bytes} < $data_len) {
20238384Sjkim	$data_len = $data_len - $state->{bytes};
21238384Sjkim	$data_len = $maxlength if $data_len > $maxlength;
22238384Sjkim	$return_data = substr ${$state->{data}}, $state->{bytes}, $data_len;
23238384Sjkim	$state->{bytes} += $data_len;
24238384Sjkim    }
25238384Sjkim    return $return_data;
26238384Sjkim}
27238384Sjkim
28238384Sjkim# Callback for writing the body into a variable.
29238384Sjkimsub write_body {
30238384Sjkim    my ($data, $pointer) = @_;
31238384Sjkim    ${$pointer} .= $data;
32238384Sjkim    return length($data);
33238384Sjkim}
34238384Sjkim
35238384Sjkim# Initialise a new Curl object.
36238384Sjkimsub create_curl {
37238384Sjkim    my $url = shift;
38238384Sjkim
39238384Sjkim    # Create Curl object.
40238384Sjkim    my $curl = WWW::Curl::Easy::new();
41238384Sjkim
42238384Sjkim    # Error-handling related options.
43238384Sjkim    $curl->setopt(CURLOPT_VERBOSE, 1) if $options{d};
44238384Sjkim    $curl->setopt(CURLOPT_FAILONERROR, 1);
45238384Sjkim    $curl->setopt(CURLOPT_USERAGENT, "OpenTSA tsget.pl/" . (split / /, $::version)[2]);
46238384Sjkim
47238384Sjkim    # Options for POST method.
48238384Sjkim    $curl->setopt(CURLOPT_UPLOAD, 1);
49238384Sjkim    $curl->setopt(CURLOPT_CUSTOMREQUEST, "POST");
50238384Sjkim    $curl->setopt(CURLOPT_HTTPHEADER,
51238384Sjkim		["Content-Type: application/timestamp-query",
52238384Sjkim		"Accept: application/timestamp-reply,application/timestamp-response"]);
53238384Sjkim    $curl->setopt(CURLOPT_READFUNCTION, \&read_body);
54238384Sjkim    $curl->setopt(CURLOPT_HEADERFUNCTION, sub { return length($_[0]); });
55238384Sjkim
56238384Sjkim    # Options for getting the result.
57238384Sjkim    $curl->setopt(CURLOPT_WRITEFUNCTION, \&write_body);
58238384Sjkim
59238384Sjkim    # SSL related options.
60238384Sjkim    $curl->setopt(CURLOPT_SSLKEYTYPE, "PEM");
61238384Sjkim    $curl->setopt(CURLOPT_SSL_VERIFYPEER, 1);	# Verify server's certificate.
62238384Sjkim    $curl->setopt(CURLOPT_SSL_VERIFYHOST, 2);	# Check server's CN.
63238384Sjkim    $curl->setopt(CURLOPT_SSLKEY, $options{k}) if defined($options{k});
64238384Sjkim    $curl->setopt(CURLOPT_SSLKEYPASSWD, $options{p}) if defined($options{p});
65238384Sjkim    $curl->setopt(CURLOPT_SSLCERT, $options{c}) if defined($options{c});
66238384Sjkim    $curl->setopt(CURLOPT_CAINFO, $options{C}) if defined($options{C});
67238384Sjkim    $curl->setopt(CURLOPT_CAPATH, $options{P}) if defined($options{P});
68238384Sjkim    $curl->setopt(CURLOPT_RANDOM_FILE, $options{r}) if defined($options{r});
69238384Sjkim    $curl->setopt(CURLOPT_EGDSOCKET, $options{g}) if defined($options{g});
70238384Sjkim
71238384Sjkim    # Setting destination.
72238384Sjkim    $curl->setopt(CURLOPT_URL, $url);
73238384Sjkim
74238384Sjkim    return $curl;
75238384Sjkim}
76238384Sjkim
77238384Sjkim# Send a request and returns the body back.
78238384Sjkimsub get_timestamp {
79238384Sjkim    my $curl = shift;
80238384Sjkim    my $body = shift;
81238384Sjkim    my $ts_body;
82238384Sjkim    local $::error_buf;
83238384Sjkim
84238384Sjkim    # Error-handling related options.
85238384Sjkim    $curl->setopt(CURLOPT_ERRORBUFFER, "::error_buf");
86238384Sjkim
87238384Sjkim    # Options for POST method.
88238384Sjkim    $curl->setopt(CURLOPT_INFILE, {data => $body, bytes => 0});
89238384Sjkim    $curl->setopt(CURLOPT_INFILESIZE, length(${$body}));
90238384Sjkim
91238384Sjkim    # Options for getting the result.
92238384Sjkim    $curl->setopt(CURLOPT_FILE, \$ts_body);
93238384Sjkim
94238384Sjkim    # Send the request...
95238384Sjkim    my $error_code = $curl->perform();
96238384Sjkim    my $error_string;
97238384Sjkim    if ($error_code != 0) {
98238384Sjkim        my $http_code = $curl->getinfo(CURLINFO_HTTP_CODE);
99238384Sjkim	$error_string = "could not get timestamp";
100238384Sjkim	$error_string .= ", http code: $http_code" unless $http_code == 0;
101238384Sjkim	$error_string .= ", curl code: $error_code";
102238384Sjkim	$error_string .= " ($::error_buf)" if defined($::error_buf);
103238384Sjkim    } else {
104238384Sjkim        my $ct = $curl->getinfo(CURLINFO_CONTENT_TYPE);
105238384Sjkim	if (lc($ct) ne "application/timestamp-reply"
106238384Sjkim	    && lc($ct) ne "application/timestamp-response") {
107238384Sjkim	    $error_string = "unexpected content type returned: $ct";
108238384Sjkim        }
109238384Sjkim    }
110238384Sjkim    return ($ts_body, $error_string);
111238384Sjkim
112238384Sjkim}
113238384Sjkim
114238384Sjkim# Print usage information and exists.
115238384Sjkimsub usage {
116238384Sjkim
117238384Sjkim    print STDERR "usage: $0 -h <server_url> [-e <extension>] [-o <output>] ";
118238384Sjkim    print STDERR "[-v] [-d] [-k <private_key.pem>] [-p <key_password>] ";
119238384Sjkim    print STDERR "[-c <client_cert.pem>] [-C <CA_certs.pem>] [-P <CA_path>] ";
120238384Sjkim    print STDERR "[-r <file:file...>] [-g <EGD_socket>] [<request>]...\n";
121238384Sjkim    exit 1;
122238384Sjkim}
123238384Sjkim
124238384Sjkim# ----------------------------------------------------------------------
125238384Sjkim#   Main program
126238384Sjkim# ----------------------------------------------------------------------
127238384Sjkim
128238384Sjkim# Getting command-line options (default comes from TSGET environment variable).
129238384Sjkimmy $getopt_arg =  "h:e:o:vdk:p:c:C:P:r:g:";
130238384Sjkimif (exists $ENV{TSGET}) {
131238384Sjkim    my @old_argv = @ARGV;
132238384Sjkim    @ARGV = split /\s+/, $ENV{TSGET};
133238384Sjkim    getopts($getopt_arg, \%options) or usage;
134238384Sjkim    @ARGV = @old_argv;
135238384Sjkim}
136238384Sjkimgetopts($getopt_arg, \%options) or usage;
137238384Sjkim
138238384Sjkim# Checking argument consistency.
139238384Sjkimif (!exists($options{h}) || (@ARGV == 0 && !exists($options{o}))
140238384Sjkim    || (@ARGV > 1 && exists($options{o}))) {
141238384Sjkim    print STDERR "Inconsistent command line options.\n";
142238384Sjkim    usage;
143238384Sjkim}
144238384Sjkim# Setting defaults.
145238384Sjkim@ARGV = ("-") unless @ARGV != 0;
146238384Sjkim$options{e} = ".tsr" unless defined($options{e});
147238384Sjkim
148238384Sjkim# Processing requests.
149238384Sjkimmy $curl = create_curl $options{h};
150238384Sjkimundef $/;   # For reading whole files.
151238384SjkimREQUEST: foreach (@ARGV) {
152238384Sjkim    my $input = $_;
153238384Sjkim    my ($base, $path) = fileparse($input, '\.[^.]*');
154238384Sjkim    my $output_base = $base . $options{e};
155238384Sjkim    my $output = defined($options{o}) ? $options{o} : $path . $output_base;
156238384Sjkim
157238384Sjkim    STDERR->printflush("$input: ") if $options{v};
158238384Sjkim    # Read request.
159238384Sjkim    my $body;
160238384Sjkim    if ($input eq "-") {
161238384Sjkim	# Read the request from STDIN;
162238384Sjkim	$body = <STDIN>;
163238384Sjkim    } else {
164238384Sjkim	# Read the request from file.
165238384Sjkim        open INPUT, "<" . $input
166238384Sjkim	    or warn("$input: could not open input file: $!\n"), next REQUEST;
167238384Sjkim        $body = <INPUT>;
168238384Sjkim        close INPUT
169238384Sjkim	    or warn("$input: could not close input file: $!\n"), next REQUEST;
170238384Sjkim    }
171238384Sjkim
172238384Sjkim    # Send request.
173238384Sjkim    STDERR->printflush("sending request") if $options{v};
174238384Sjkim
175238384Sjkim    my ($ts_body, $error) = get_timestamp $curl, \$body;
176238384Sjkim    if (defined($error)) {
177238384Sjkim	die "$input: fatal error: $error\n";
178238384Sjkim    }
179238384Sjkim    STDERR->printflush(", reply received") if $options{v};
180238384Sjkim
181238384Sjkim    # Write response.
182238384Sjkim    if ($output eq "-") {
183238384Sjkim	# Write to STDOUT.
184238384Sjkim        print $ts_body;
185238384Sjkim    } else {
186238384Sjkim	# Write to file.
187238384Sjkim        open OUTPUT, ">", $output
188238384Sjkim	    or warn("$output: could not open output file: $!\n"), next REQUEST;
189238384Sjkim        print OUTPUT $ts_body;
190238384Sjkim        close OUTPUT
191238384Sjkim	    or warn("$output: could not close output file: $!\n"), next REQUEST;
192238384Sjkim    }
193238384Sjkim    STDERR->printflush(", $output written.\n") if $options{v};
194238384Sjkim}
195238384Sjkim$curl->cleanup();
196238384SjkimWWW::Curl::Easy::global_cleanup();
197