From 8a9d567022791fc58c2142701516a6c6f48e42d8 Mon Sep 17 00:00:00 2001 From: Florent Villard Date: Tue, 17 Oct 2006 13:53:27 +0000 Subject: merge with upstream --- bin/youri-submit-proxy.in | 77 ++++++++ bin/youri-submit-restricted.in | 64 +++++++ bin/youri-submit.in | 404 +++++++++++++++++++++++++---------------- 3 files changed, 390 insertions(+), 155 deletions(-) create mode 100755 bin/youri-submit-proxy.in create mode 100755 bin/youri-submit-restricted.in (limited to 'bin') 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] + +=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] + +=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] +youri-submit [options] +youri-submit --list [target] + +youri-submit --help [category] [item] + Options: --config use file as config file --skip-pre
       skip pre 
-    --skip-action  skip action 
     --skip-check    skip check 
+    --skip-action  skip action 
     --skip-post      skip post 
     --skip-reject  skip reject 
     --define = pass additional values
-    --list-targets         list available targets
-    --list-checks  list configured checks for for 
-    --list-actions  list configured actions for for 
+    --clean                delete package after success
     --verbose              verbose run
     --test                 test run
-    --help                 print this help message
+    --list       list items from given category
+    --help [category]      display contextual help
 
 =head1 DESCRIPTION
 
-B allows to upload packages in a repository.
+B 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
-
-List configured checks for given target 
-
-=item B<--list-actions> I
-
-List configured actions for given target 
-
-=item B<--help>
-
-Print a brief help message and exits.
+=item B<--list> I
+ 
+List available items from given category and exits. Category must be either
+B, B or B. A target is needed for the two last ones.
+ 
+=item B<--help> I
+ 
+Display help for given category and exits. Category must be either
+B, B or B. 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 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 I
+=item B
 
-Uses space-separated list I as a list of additional configuration files.
+The definition of repository plugin to be used.
 
-=item B I
+=item B
 
-Declares a repository object with identity I.
+The list of available submission targets, each one being composed from the
+following keys:
 
-=item B I
+=over
 
-Declares a list of upload target objects with identity taken in space-separated list I.
+=item B
+
+The list of check plugins to use for this target.
+
+=item B
+
+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
+
+The list of check plugin definitions, indexed by their identity.
 
-Example:
+=item B
+ 
+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();
+    }
 }
-- 
cgit v1.2.1