#!/usr/bin/perl
sub
get_config {
my
(
$array
) =
@_
;
my
%opts
;
my
$result
=
GetOptionsFromArray(
$array
, \
%opts
,
'output|o=s'
,
'metadata|m=s'
,
'verbose|v'
,
'dry-run|n'
,
'help|h'
, );
pod2usage(1)
if
$opts
{help};
_check_arguments( \
%opts
);
return
\
%opts
;
}
our
$verbose
= 0;
sub
printlog {
if
(
$verbose
) {
print
STDERR
@_
,
"\n"
;
}
}
sub
printfatal {
print
STDERR
@_
,
"\n"
;
}
sub
_check_arguments {
my
(
$opts
) =
@_
;
my
@missing
;
push
@missing
,
"metadata URL (-m)"
if
!
$opts
->{metadata};
push
@missing
,
"output path (-o)"
if
!
$opts
->{output};
pod2usage(
-message
=> (
"Missing "
.
join
(
' and '
,
@missing
) ),
-exitval
=> 2
)
if
@missing
;
}
sub
_check_xml {
my
(
$tmpfile
) =
@_
;
my
$reader
= XML::LibXML::Reader->new(
location
=>
$tmpfile
);
if
( !
$reader
) {
unlink
(
$tmpfile
);
printfatal
"Cannot read $tmpfile: $!"
;
return
0;
}
my
$valid
= 0;
eval
{
while
(
$reader
->
read
) {
if
(
$reader
->depth == 0
and
$reader
->nodeType == XML_READER_TYPE_ELEMENT )
{
if
(
$reader
->localName eq
"EntitiesDescriptor"
and
$reader
->namespaceURI eq
"urn:oasis:names:tc:SAML:2.0:metadata"
)
{
$valid
= 1;
}
}
}
};
if
(
my
$error
= $@ ) {
unlink
(
$tmpfile
);
printfatal
"Cannot parse $tmpfile as XML: $@"
;
return
0;
}
if
(
$valid
== 1 ) {
return
1;
}
else
{
unlink
(
$tmpfile
);
printfatal
"XML does not contain an EntitiesDescriptor"
;
return
0;
}
}
sub
downloadSamlMetadata {
my
(
$opts
) =
@_
;
$verbose
=
$opts
->{verbose};
my
$ua
= LWP::UserAgent->new;
$ua
->timeout(10);
$ua
->show_progress(1)
if
$verbose
;
$ua
->env_proxy;
my
$metadata_file
=
$opts
->{metadata};
my
$output
=
$opts
->{output};
printlog(
"Try to download metadata file at $metadata_file"
);
my
$tmpfile
= mktemp(
"$output.XXXXXXXX"
);
printlog(
"Using temporary file $tmpfile"
);
my
$response
=
$ua
->get(
$metadata_file
,
':content_file'
=>
$tmpfile
);
if
(
$response
->is_success ) {
printlog(
"Metadata successfully downloaded"
);
}
else
{
printlog(
"Removing $tmpfile"
);
unlink
(
$tmpfile
);
printfatal
"Error downloading metadata "
,
$response
->status_line;
return
1;
}
return
1
unless
_check_xml(
$tmpfile
);
if
(
$opts
->{
'dry-run'
} ) {
printlog(
"Removing $tmpfile"
);
unlink
(
$tmpfile
);
}
else
{
if
( !
rename
(
$tmpfile
,
$output
) ) {
unlink
(
$tmpfile
);
printfatal
"Could not atomically replace $output"
;
return
1;
}
}
return
0;
}
sub
main {
my
$config
= get_config( \
@ARGV
);
exit
( downloadSamlMetadata(
$config
) );
}
unless
(
caller
) {
main();
}