diff options
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/youri-submit-proxy.in | 77 | ||||
-rwxr-xr-x | bin/youri-submit-restricted.in | 64 | ||||
-rwxr-xr-x | bin/youri-submit.in | 404 |
3 files changed, 390 insertions, 155 deletions
diff --git a/bin/youri-submit-proxy.in b/bin/youri-submit-proxy.in new file mode 100755 index 0000000..67fed6e --- /dev/null +++ b/bin/youri-submit-proxy.in @@ -0,0 +1,77 @@ +#!/usr/bin/perl + +=head1 NAME + +youri-submit-proxy - proxy wrapper over youri-submit-restricted + +=head1 VERSION + +Version 1.0 + +=head1 SYNOPSIS + +youri-submit-proxy [options] <target> <files> + +=head1 DESCRIPTION + +youri-submit-proxy is a proxy wrapper over youri-submit-restricted, intended to +be used in collaborative work to change uid before calling it through sudo. + +=head1 SEE ALSO + +youri-submit-restricted(1), youri-submit(1) + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2002-2006, YOURI project + +This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. + +=cut + +use strict; +use warnings; +use Fcntl ':mode'; +use File::Basename; + +my ($uid, $gid); +if (-l $0) { + # this is a symlink, get uid and gid from it + ($uid, $gid) = (lstat($0))[4, 5]; +} else { + ($uid, $gid) = (stat($0))[4, 5]; +} +my $user = getpwuid($uid) or die "unknown uid $uid"; +my $prog = '@bindir@/youri-submit-restricted'; + +my %dirs; +my @options; +foreach my $arg (@ARGV) { + if (-f $arg) { + # push parent dir in list + my $parent = dirname($arg); + $dirs{$parent}++; + } + push(@options, $arg); +} + +foreach my $dir (keys %dirs) { + # save original perms and gid + my ($orig_mode, $orig_gid) = (stat($dir))[2,5]; + $dirs{$dir} = { + mode => $orig_mode, + gid => $orig_gid + }; + # ensure correct perms and gid + chown -1, $gid, $dir; + chmod $orig_mode|S_IRGRP|S_IWGRP, $dir; +} + +# call wrapped program +system('sudo', '-H', '-u', $user, $prog, @options); + +foreach my $dir (keys %dirs) { + # restore original perms and gid + chown -1, $dirs{$dir}->{gid}, $dir; + chmod $dirs{$dir}->{mode}, $dir; +} diff --git a/bin/youri-submit-restricted.in b/bin/youri-submit-restricted.in new file mode 100755 index 0000000..d28ba84 --- /dev/null +++ b/bin/youri-submit-restricted.in @@ -0,0 +1,64 @@ +#!/usr/bin/perl -T + +=head1 NAME + +youri-submit-restricted - filtering wrapper over youri-submit + +=head1 VERSION + +Version 1.0 + +=head1 SYNOPSIS + +youri-submit-restricted [options] <target> <files> + +=head1 DESCRIPTION + +youri-submit-restricted is just a filtering wrapper over youri-submit, intended +to be used in collaborative work to sanitize environment and options before +calling it. + +=head1 SEE ALSO + +youri-submit(1) + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2002-2006, YOURI project + +This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. + +=cut + +use strict; +use warnings; + +my $prog = '@bindir@/youri-submit'; +my @prohibited_options = qw/--config --skip-check --skip-action/; +my %prohibited_options = map { $_ => 1 } @prohibited_options; +my @prohibited_envvars = qw/ + ENV BASH_ENV IFS CDPATH + PERLLIB PERL5LIB PERL5OPT PERLIO + PERLIO_DEBUG PERL5DB PERL_ENCODING + PERL_HASH_SEED PERL_SIGNALS PERL_UNICODE +/; + +my @options; +while (my $arg = shift @ARGV) { + if ($prohibited_options{$arg}) { + # drop prohibited options + print STDERR "prohibited option $arg, skipping\n"; + shift @ARGV; + } else { + # untaint everything else + $arg =~ /(.*)/; + push(@options, $1); + } +} + +# secure ENV +$ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin"; +delete $ENV{$_} foreach @prohibited_envvars; + +# call wrapped program +system($prog, @options); diff --git a/bin/youri-submit.in b/bin/youri-submit.in index c232cfb..07118f1 100755 --- a/bin/youri-submit.in +++ b/bin/youri-submit.in @@ -3,7 +3,7 @@ =head1 NAME -youri-upload - package upload agent +youri-submit - package submission tool =head1 VERSION @@ -11,27 +11,30 @@ Version 2.0 =head1 SYNOPSIS -youri-upload [options] <target> <files> +youri-submit [options] <target> <files> +youri-submit --list <category> [target] + +youri-submit --help [category] [item] + Options: --config <file> use file <file> as config file --skip-pre <pre> skip pre <pre> - --skip-action <action> skip action <action> --skip-check <check> skip check <check> + --skip-action <action> skip action <action> --skip-post <post> skip post <post> --skip-reject <reject> skip reject <reject> --define <key>=<value> pass additional values - --list-targets list available targets - --list-checks <target> list configured checks for for <target> - --list-actions <target> list configured actions for for <target> + --clean delete package after success --verbose verbose run --test test run - --help print this help message + --list <category> list items from given category + --help [category] display contextual help =head1 DESCRIPTION -B<youri-upload> allows to upload packages in a repository. +B<youri-submit> allows to submit packages to a repository. All packages given on command lines are passed to a list of check plugins, depending on given upload target. If none of them fails, all packages are @@ -69,6 +72,10 @@ Skip reject action plugin with given identity. Define additional parameters, to be used by plugins. +=item B<--clean> + +Delete submited packages upon successfull submission. + =item B<--verbose> Produce more verbose output (can be used more than once) @@ -77,21 +84,16 @@ Produce more verbose output (can be used more than once) Don't perform any modification. -=item B<--list-targets> - -List available targets - -=item B<--list-checks> I<target> - -List configured checks for given target - -=item B<--list-actions> I<target> - -List configured actions for given target - -=item B<--help> - -Print a brief help message and exits. +=item B<--list> I<category> + +List available items from given category and exits. Category must be either +B<targets>, B<actions> or B<checks>. A target is needed for the two last ones. + +=item B<--help> I<category> + +Display help for given category and exits. Category must be either +B<repository>, B<action> or B<check>. An item is needed for the two last ones. +If no category given, display standard help. =back @@ -103,48 +105,51 @@ Configuration is read from the first file found among: =item * the one specified by B<--config> option on command-line -=item * $HOME/.youri/upload.conf - -=item * @sysconfdir@/youri/upload.conf +=item * $HOME/.youri/submit.conf + +=item * @sysconfdir@/youri/submit.conf =back -All additional configuration files specified by B<includes> directive are then -processed. Then command line options. Any directive overrides prior definition. +The configuration file should be a YAML-format files, with the following +mandatory top-level directives: =over -=item B<includes> I<files> +=item B<repository> -Uses space-separated list I<files> as a list of additional configuration files. +The definition of repository plugin to be used. -=item B<repository> I<id> +=item B<targets> -Declares a repository object with identity I<id>. +The list of available submission targets, each one being composed from the +following keys: -=item B<targets> I<ids> +=over -Declares a list of upload target objects with identity taken in space-separated list I<ids>. +=item B<checks> + +The list of check plugins to use for this target. + +=item B<actions> + +The list of action plugins to use for this target. =back -Each object declared in configuration must be fully defined later, using a -configuration section, starting with bracketed object identity, followed by at -least a class directive, then any number of additional object-specific -directives. +=item B<checks> + +The list of check plugin definitions, indexed by their identity. -Example: +=item B<actions> + +The list of action plugin definitions, indexed by their identity. - objects = foo - - [foo] - class = Foo::Bar - key1 = value1 - key2 = value2 +=back =head1 SEE ALSO -Youri::Config, for configuration file format. +Youri::Config, for additional details about configuration file format. Each used plugin man page, for available options. @@ -164,90 +169,136 @@ use Youri::Utils; use Pod::Usage; my $config = Youri::Config->new( - command_spec => [ - 'config=s', - 'skip-pre=s@', - 'skip-check=s@', - 'skip-action=s@', - 'skip-post=s@', - 'skip-reject=s@', - 'list-targets!', - 'list-checks=s', - 'list-actions=s', - 'define=s%', - 'help|h!', - 'test|t!', - 'server|s', - 'verbose|v!' - ], - file_spec => [ - 'includes=s', - 'targets=s', - 'repository=s', - ], - directories => [ '@sysconfdir@', "$ENV{HOME}/.youri" ], - file_name => 'upload.conf', - caller => $0, + args => { + 'skip-check' => '=s@', + 'skip-action' => '=s@', + 'define' => '=s%', + 'verbose' => '|v!', + 'clean' => '!', + 'test' => '|t!', + 'list' => '|l!' + 'config' => '=s', + 'skip-prei' => '=s@', + 'skip-post' => '=s@', + 'skip-reject' => '=s@', + }, + directories => [ "$ENV{HOME}/.youri", '@sysconfdir@/youri' ], + file => 'submit.conf', ); -# compute available targets first -my %targets = map { $_ => 1 } split(/\s+/, $config->get('targets')); - -if ($config->get('list-targets')) { - print join(' ', keys %targets) . "\n"; - exit 0; -} elsif ($config->get('list-checks')) { - my %target = get_target_config($config->get('list-checks')); - print join(' ', @{$target{checks}}) . "\n"; - exit 0; -} elsif ($config->get('list-actions')) { - my %target = get_target_config($config->get('list-actions')); - print join(' ', @{$target{actions}}) . "\n"; +if ($config->get_arg('list')) { + my $category = $ARGV[0]; + pod2usage(-verbose => 0, -message => "No category specified, aborting\n") + unless $category; + if ($category eq 'targets') { + print join(' ', keys %{$config->get_param('targets')}); + } elsif ($category eq 'checks' || $category eq 'actions') { + my $target = $ARGV[1]; + pod2usage(-verbose => 0, -message => "No target specified, aborting\n") + unless $target; + if ($category eq 'checks') { + my $checks = $config->get_param('targets')->{$target}->{checks}; + print join(' ', @{$checks}) if $checks; + } else { + my $actions = $config->get_param('targets')->{$target}->{actions}; + print join(' ', @{$actions}) if $actions; + } + } else { + pod2usage(-verbose => 0, -message => "Invalid category $category, aborting\n") + } + print "\n"; exit 0; } +if ($config->get_arg('help')) { + my $category = $ARGV[0]; + my ($item, $section); + if ($category eq 'repository') { + $section = $config->get_param('repository'); + pod2usage( + -verbose => 0, + -message => "No repository defined, aborting\n" + ) unless $section; + } elsif ($category eq 'check' || $category eq 'action') { + $item = $ARGV[1]; + pod2usage( + -verbose => 0, + -message => "No item specified, aborting\n" + ) unless $item; + if ($category eq 'check') { + $section = $config->get_param('checks')->{$item}; + pod2usage( + -verbose => 0, + -message => "No such check $item defined, aborting\n" + ) unless $section; + } else { + $section = $config->get_param('actions')->{$item}; + pod2usage( + -verbose => 0, + -message => "No such action $item defined, aborting\n" + ) unless $section; + } + } else { + pod2usage(-verbose => 0, -message => "Invalid category $category, aborting\n") + } + my $file = $section->{class} . '.pm'; + $file =~ s/::/\//g; + pod2usage( + -verbose => 99, + -sections => 'NAME|DESCRIPTION', + -input => $file, + -pathlist => \@INC + ); +} + + pod2usage(-verbose => 0, -message => "No target specified, aborting\n") unless @ARGV > 0; +pod2usage(-verbose => 0, -message => "No packages specified, aborting\n") + unless @ARGV > 1; # convenient global flags -my $test = $config->get('test'); -my $verbose = $config->get('verbose'); +my $test = $config->get_arg('test'); +my $verbose = $config->get_arg('verbose'); # check target my $target = shift @ARGV; -my %target = get_target_config($target); +my $target_conf = $config->get_param('targets')->{$target}; # create repository my $repository; -my $repository_id = $config->get('repository'); -die "No repository declared" unless $repository_id; -print "Creating repository $repository_id\n" if $verbose; +my $repository_conf = $config->get_param('repository'); +die "No repository declared" unless $repository_conf; +print "Creating repository\n" if $verbose; eval { $repository = create_instance( 'Youri::Repository', - test => $test, - verbose => $verbose > 0 ? $verbose - 1 : 0, - targets => [ keys %targets ], - $config->get_section($repository_id) + $repository_conf, + { + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + targets => [ keys %{$config->get_param('targets')} ], + } ); }; -die "Failed to create repository $repository_id: $@\n" if $@; +die "Failed to create repository: $@\n" if $@; # perfrom pre action my %skip_pres = map { $_ => 1 } @{$config->get('skip-pre')}; my $pre_packages = []; my $ok = 1; -foreach my $id (@{$target{pre}}) { +foreach my $id (@{$target_conf->{pre}}) { next if $skip_pres{$id}; print "Creating pre $id\n" if $verbose; my $pre; eval { $pre = create_instance( 'Youri::Upload::Pre', - id => $id, - test => $test, - verbose => $verbose > 0 ? $verbose - 1 : 0, - $config->get_section($id) + { + id => $id, + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + } ); }; if ($@) { @@ -272,29 +323,42 @@ foreach my $group ((map { [ { section => "", file => $_ } ] } @ARGV), @$pre_pac @packages, create_instance( 'Youri::Package', - class => $repository->get_package_class(), - file => $opt->{file}, - %$opt + { + class => $repository->get_package_class(), + }, + { + file => $opt->{file}, + %$opt + }, ) ); } @packages or next; # check all packages pass all tests - my %skip_checks = map { $_ => 1 } @{$config->get('skip-check')}; - my $ok = 1; + my %errors; + my $skip_check = $config->get_arg('skip-check'); + my %skip_check = $skip_check ? map { $_ => 1 } @{$skip_check} : (); my @error; - foreach my $id (@{$target{checks}}) { + foreach my $id (@{$target_conf->{checks}}) { next if $skip_checks{$id}; print "Creating check $id\n" if $verbose; my $check; + my $check_conf = $config->get_param('checks')->{$id}; + + if (!$check_conf) { + print STDERR "No such check $id, skipping\n"; + next; + } eval { $check = create_instance( 'Youri::Upload::Check', - id => $id, - test => $test, - verbose => $verbose > 0 ? $verbose - 1 : 0, - $config->get_section($id) + $check_conf, + { + id => $id, + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + } ); }; if ($@) { @@ -302,30 +366,40 @@ foreach my $group ((map { [ { section => "", file => $_ } ] } @ARGV), @$pre_pac } else { foreach my $package (@packages) { print "running check $id on package $package\n" if $verbose; - unless ($check->run($package, $repository, $target, $config->get('define'))) { - my $err = $check->get_error(); - print STDERR "Error: $err\n"; - push @error, $err; - $ok = 0 - } + my @errors = $check->run( + $package, + $repository, + $target, + $config->get_arg('define') + ); + push(@{$errors{$package}}, @errors) if @errors; + } + } + if (%errors) { + print "Submission errors, aborting:\n"; + foreach my $package (keys %errors) { + print "- $package:\n"; + foreach my $error (@{$errors{$package}}) { + print " - $error\n"; } - } - } - if (!$ok) { # reject the packages - my %skip_rejects = map { $_ => 1 } @{$config->get('skip-reject')}; - foreach my $id (@{$target{rejects}}) { + my $skip_rejects = $config->get_arg('skip-reject'); + my %skip_rejects = $skip_rejects ? map { $_ => 1 } @{$skip_rejects} : (); + foreach my $id (@{$target_conf{rejects}}) { next if $skip_rejects{$id}; print "Creating reject $id\n" if $verbose; my $reject; + my $reject_conf = $config->get_param('rejects')->{$id}; eval { $reject = create_instance( 'Youri::Upload::Reject', - id => $id, - test => $test, - verbose => $verbose > 0 ? $verbose - 1 : 0, - $config->get_section($id) + $reject_conf, + { + id => $id, + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + } ); }; if ($@) { @@ -334,7 +408,7 @@ foreach my $group ((map { [ { section => "", file => $_ } ] } @ARGV), @$pre_pac foreach my $package (@packages) { print "running reject $id on package $package\n" if $verbose; eval { - $reject->run($package, \@error, $repository, $target, $config->get('define')); + $reject->run($package, \%errors, $repository, $target, $config->get('define')); }; if ($@) { print STDERR "Failed to run action $id on package $package: $@\n"; @@ -346,48 +420,70 @@ foreach my $group ((map { [ { section => "", file => $_ } ] } @ARGV), @$pre_pac } # proceed further - my %skip_actions = map { $_ => 1 } @{$config->get('skip-action')}; - foreach my $id (@{$target{actions}}) { + my $skip_action = $config->get_arg('skip-action'); + my %skip_action = $skip_action ? map { $_ => 1 } @{$skip_action} : (); + foreach my $id (@{$target_conf->{actions}}) { next if $skip_actions{$id}; print "Creating action $id\n" if $verbose; my $action; + my $action_conf = $config->get_param('actions')->{$id}; + + if (!$action_conf) { + print STDERR "No such action $id, skipping\n"; + next; + } eval { $action = create_instance( 'Youri::Upload::Action', - id => $id, - test => $test, - verbose => $verbose > 0 ? $verbose - 1 : 0, - $config->get_section($id) + $action_conf, + { + id => $id, + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + } ); }; if ($@) { print STDERR "Failed to create action $id: $@\n"; } else { - foreach my $package (@packages) { - print "running action $id on package $package\n" if $verbose; - eval { - $action->run($package, $repository, $target, $config->get('define')); - }; - if ($@) { - print STDERR "Failed to run action $id on package $package: $@\n"; - } - } - } + foreach my $package (@packages) { + print "running action $id on package $package\n" if $verbose; + eval { + $action->run( + $package, + $repository, + $target, + $config->get_arg('define') + ); + }; + if ($@) { + print STDERR "Failed to run action $id on package $package: $@\n"; + } + } } } # perfrom post action -my %skip_posts = map { $_ => 1 } @{$config->get('skip-post')}; -foreach my $id (@{$target{post}}) { - next if $skip_posts{$id}; +my $skip_post = $config->get_arg('skip-post'); +my %skip_post = $skip_post ? map { $_ => 1 } @{$skip_post} : (); +foreach my $id (@{$target_conf->{post}}) { + next if $skip_post{$id}; print "Creating post $id\n" if $verbose; my $post; + my $post_conf = $config->get_param('posts')->{$id}; + + if (!$post_conf) { + print STDERR "No such action $id, skipping\n"; + next; + } eval { $post = create_instance( 'Youri::Upload::Post', - id => $id, - test => $test, - verbose => $verbose > 0 ? $verbose - 1 : 0, - $config->get_section($id) + $post_conf, + { + id => $id, + test => $test, + verbose => $verbose > 0 ? $verbose - 1 : 0, + } ); }; if ($@) { @@ -400,11 +496,9 @@ foreach my $id (@{$target{post}}) { } } -sub get_target_config { - my ($id) = @_; - die "Unavailable target $target" unless $targets{$id}; - - my %target = $config->get_section($id); - die "Undefined target $id" unless %target; - return %target; +if ($config->get('clean')) { + foreach my $package (@packages) { + print "cleaning file $package\n" if $verbose; + unlink $package->as_file(); + } } |