#!/usr/bin/perl -w
# #
# Software subject to following license(s):
#   Apache 2 License (http://www.opensource.org/licenses/apache2.0)
#   Copyright (c) Responsible Organization
#

# #
# Current developer(s):
#   Luis Fernando Muñoz Mejías <Luis.Munoz@UGent.be>
#

# 
# #
# ccm, 15.4.0-rc11, rc11_1, 2015-06-02T10:19:22Z
#

package ccm_fetch;

# required for CAF (Common Application Framework)
BEGIN {
    unshift(@INC, '/opt/edg/lib/perl', '/usr/lib/perl');
}

use CAF::Application;
use CAF::Reporter;
use LC::Exception qw(SUCCESS throw_error);
use EDG::WP4::CCM::Fetch qw(NOQUATTOR_FORCE);
use strict;
use warnings;
our @ISA = qw(CAF::Application CAF::Reporter);

# needed for method overriding
no warnings 'redefine';

#
# application specific options:
#
sub app_options {

    # local lexicals
    my @options;

    # build options
    push (@options,

          { NAME    => 'cfgfile=s',
            DEFAULT => '/etc/ccm.conf',
            HELP    => 'configuration file for CCM' },

          { NAME    => 'profile|p=s',
            HELP    => 'URL of profile to fetch' },

          { NAME    => 'profile_failover=s',
            HELP    => 'URL of profile to fetch when --profile is not available' },

          { NAME    => 'profile-time=s',
            HELP    => 'Unix timestamp as modification time of profile' },

          { NAME    => 'context|c=s',
            HELP    => 'URL of context to fetch' },

          { NAME    => 'context_time=s',
            HELP    => 'Unix timestamp as modification time of context' },

          { NAME    => 'preprocessor=s',
            HELP    => 'Path of executable to be used to preprocess a profile with a context' },

          { NAME    => 'foreign',
            HELP    => 'whether a foreign profile or local profile' },

          { NAME    => 'cache_root=s',
            DEFAULT => '/var/lib/ccm',
            HELP    => 'Path of executable to be used to preprocess a profile with a context' },

          { NAME    => 'get_timeout=i',
            DEFAULT => '30',
            HELP    => 'Timeout in seconds for HTTP GET operation' },

          { NAME    => 'world_readable=i',
            DEFAULT => '1',
            HELP    => 'World readable profile flag 1/0' },

          { NAME    => 'force|f',
            HELP    => 'Fetch regardless of modification times' },

          { NAME    => 'dbformat=s',
            HELP    => 'Format to use for storing profile' },

          { NAME    => NOQUATTOR_FORCE,
            DEFAULT => 0,
            HELP    => 'Fetch even if CCM updates are globally disabled' },

          { NAME    => 'retrieve_retries=i',
            DEFAULT => 3,
            HELP    => 'Number of times fetch will attempt to retrieve a profile' },

          { NAME    => 'lock_retries=i',
            DEFAULT => 3,
            HELP    => 'Number of times fetch will attempt to get the fetch lock'},

          { NAME    => 'retrieve_wait=i',
            DEFAULT => 30,
            HELP    => 'Number of seconds that fetch will wait between retrieve attempts' },
          { NAME    => 'lock_wait=i',
            DEFAULT => 30,
            HELP    =>  'Number of seconds that fetch will wait between lock attempts' },

          { NAME    => 'key_file=s',
            HELP    => 'Absolute file name for key file to use with HTTPS.'},

          { NAME    => 'cert_file=s',
            HELP    => 'Absolute file name for certificate file to use with HTTPS.' },

          { NAME    => 'ca_file=s',
            HELP    => 'File containing a bundle of trusted CA certificates for use with HTTPS.' },

          { NAME   => 'ca_dir=s',
            HELP   => 'Directory containing trusted CA certificates for use with HTTPS' },

          { NAME   => 'trust=s',
            HELP   => 'Kerberos principal to trust if using encrypted profiles' },

          { NAME   => 'keep_old=i',
            HELP   => 'Number of old profiles to keep before purging' },

          { NAME   => 'purge_time=i',
            HELP   => 'Number of seconds before purging inactive profiles' },

          { NAME   => 'json_typed',
            HELP   => 'Extract typed data from JSON profiles' },

          { NAME    => 'debug|d=i',
            DEFAULT => '0',
            HELP    => 'Turn on dubugging messages' });

    return \@options;
}

#
# initialise
#
sub _initialize {

    # local lexicals
    my $self = shift;

    # version and usage
    $self->{'VERSION'} = "15.4.0-rc11";
    $self->{'USAGE'}   = sprintf("Usage: %s [OPTIONS...]", $0);

    # initialise
    unless ($self->SUPER::_initialize(@_)) {
	return undef;
    }

    return SUCCESS;
}


#-- Main ---------------------------------------------------------------------------------------------#

package main;

# modules
use English;
use LC::Exception qw(SUCCESS throw_error);
use File::Basename;
use EDG::WP4::CCM::Fetch qw(NOQUATTOR NOQUATTOR_EXITCODE NOQUATTOR_FORCE);
use EDG::WP4::CCM::CacheManager;
use LC::Exception;

$ENV{PATH} = join(":", qw(/bin /usr/bin /sbin /usr/sbin));

use strict;
use warnings;
#use diagnostics;

our ($this_app);

# local lexicals
my $fetch   = undef;
my $profile = undef;
my $foreign = undef;
my $config  = undef;


# unbuffer STDOUT & STDERR
autoflush STDOUT 1;
autoflush STDERR 1;

# initialise main class
unless ($this_app = ccm_fetch->new($0, @ARGV)) {
    throw_error("Cannot start application");
    exit (1);
}

#my $ec = LC::Exception::Context->new->will_store_all;

# get Config path
my $opts = {};
if (defined $this_app->option("cfgfile")) {
    $opts->{CONFIG} = $this_app->option("cfgfile");
}
if (defined $this_app->option("profile")) {
    $opts->{PROFILE} = $this_app->option("profile");
}
if (defined $this_app->option("foreign")) {
    $opts->{FOREIGN} = $this_app->option("foreign");
}
if (defined $this_app->option("dbformat")) {
    $opts->{DBFORMAT} = $this_app->option("dbformat");
}

# Disable all updates if the flag is present
if (-f NOQUATTOR && ! $this_app->option(NOQUATTOR_FORCE)) {
    $this_app->warn("CCM updates disabled globally (", NOQUATTOR, " present)");
    my $fh = CAF::FileEditor->new(NOQUATTOR);
    $this_app->warn("$fh") if $fh;
    $fh->close();
    exit(NOQUATTOR_EXITCODE);
}

# initialize Fetch object
$fetch = EDG::WP4::CCM::Fetch->new($opts);
if (!$fetch) {
    throw_error("Initialization failed");
    exit (1);
}


# set debug
$fetch->setup_reporter($this_app->option("debug"), $this_app->option("quiet"),
		       $this_app->option("verbose"));

# set timeout
$fetch->setTimeout($this_app->option("get_timeout"))
		if defined $this_app->option("get_timeout");

# set force
$fetch->setForce($this_app->option("force"))
		if defined $this_app->option("force");

# set failover profile url
$fetch->setProfileFailover($this_app->option("profile_failover"))
		if defined $this_app->option("profile_failover");

# set preprocessor
$fetch->setPreprocessor($this_app->option("preprocessor"))
		if defined $this_app->option("preprocessor");

# set cache root
$fetch->setCacheRoot($this_app->option("cache_root"))
		if defined $this_app->option("cache_root");

# set world readable flag
$fetch->setWorldReadable($this_app->option("world_readable"))
		if defined $this_app->option("world_readable");

# we no longer set profile notification time
# since we always want to get something later than what we
# have on disk

# set context notification time
$fetch->setContextnTime($this_app->option("context_time"))
    if defined $this_app->option("context_time");

# set context URL
$fetch->setContext($this_app->option("context"))
    if defined $this_app->option("context");

#fetch rpofile
my $errno = $fetch->fetchProfile();

if (!defined($errno)) {
    $this_app->error("Network error detected");
    exit(2);
} elsif ($errno == -1){
    $this_app->error("Unable to fetch profile");
    exit(1);
}

exit (0);


=head1 NAME

ccm-fetch - fetch profiles and store in local cache

=head1 DESCRIPTION

This program retrieves profiles and contexts from specified URLs,
subject to modification time constraints, validates them, and stores the
contents in the local configuration cache.

=head1 SYNOPSIS

ccm-fetch-new [I<OPTIONS>]

=head1 OPTIONS

=over 4

=item B<-d>, B<--debug>

Turn on debugging messages.

=item B<-f>, B<--force>

Force fetch to retrieve profiles/contexts, regardless of their
modification times.

=item B<--force-quattor>

Execute even if CCM updates are globally disabled. This option exists
to allow administrators to make one-off updates in a controlled manner.

=item B<--config>=I<file>

Use configuration file I<file>.  Default location:
/etc/ccm.conf.  See below for the parameters that may be
specified in this file.

=item B<--profile>=I<URL>

URL of profile to fetch. You can use either HTTP or HTTPS protocols.

=item B<--profile_failover>=I<URL>

URL of profile to fetch in case the URL in --profile is not available
You can use either HTTP or HTTPS protocols. This option is useful in
case of broken SSL/HTTPS setups (e.g. expired host certificates); a
reconfiguration of the CCM URL is not possible if a new profile cannot
be accessed, as the CCM URL is stored in the profile itself! A
failover URL can be pointing to an auxiliarly URL (e.g. plain HTTP
based) which can be enabled for emergency situations.

=item B<--profile-time>=I<time>

UNIX timestamp to expect as the modification time of the new profile.
This will normally be provided by the notification service. This
argument is supported for compatability reasons, but has no effect.


=item B<--context>=I<URL>

URL of context to fetch.

=item B<--context-time>=I<time>

UNIX timestamp to expect as the modification time of the new context.
This will normally be provided by the notification service.

=item B<--preprocessor>=I<program>

The executable to be called when preprocessing a profile with
a context file.

=item B<--foreign>=I<1/0>

The profile being fetched is a foreign profile and will be stored at
different location (cache_root/foreign)

=back

=head1 CONFIGURATION FILE

The configuration file may specify any of the following parameters.
Where these have the same name as a command line option, the description
is the same.

=over 4

=item debug

=item force

=item profile

=item context

=item cache_root

Location of the root of the cache directory hierarchy.  Defaults to
/var/lib/ccm.

=item get_timeout

Timeout in seconds for the HTTP GET operation, when retrieving profiles
or contexts.

=item lock_retries

Number of times fetch will attempt to get the single-threaded fetch lock
before giving up.

=item lock_wait

Number of seconds that fetch will wait between lock attempts.

=item retrieve_retries

Number of times fetch will attempt to retrieve a profile or context, when
it has been given a valid accompanying notification time.

=item retrieve_wait

Number of seconds that fetch will wait between retrieve attempts.

=item cert_file

Absolute file name for certificate file to use with HTTPS.

=item key_file

Absolute file name for key file to use with HTTPS.

=item ca_file

File containing a bundle of trusted CA certificates for use with HTTPS.

=item ca_dir

Directory containing trusted CA certificates for use with HTTPS. Hash
symlinks are needed.

=item world_readable

World readable profiles flag (1/0). If true profiles will be
stored on local disk with 755 permissions, if false with 700.

=back

=head1 EXIT STATUSES

This program exits with:

=over

=item 0 on success

=item 3 if updates have been disabled

=item 2 on a network error

=item 1 on a different error

=back

=cut
