X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/a9a8460b2479dfbe34982e8042288838aa77ba3f..02f8856f7d52ec972d6ab7704f612446013572a5:/mr diff --git a/mr b/mr index e96a3dc..0c34e46 100755 --- a/mr +++ b/mr @@ -20,6 +20,8 @@ B [options] diff B [options] log +B [options] bootstrap url + B [options] register [repository] B [options] config section ["parameter=[value]" ...] @@ -43,6 +45,10 @@ working directory. Or, if you are in a subdirectory of a repository that contains no other registered repositories, it will stay in that directory, and work on only that repository, +B is configured by .mrconfig files, which list the repositories. It +starts by reading the .mrconfig file in your home directory, and this can +in turn chain load .mrconfig files from repositories. + These predefined commands should be fairly familiar to users of any revision control system: @@ -98,6 +104,11 @@ These commands are also available: =over 4 +=item bootstrap url + +Causes mr to download the url, save it to a .mrconfig file in the +current directory, and then check out all repositories listed in it. + =item list (or ls) List the repositories that mr will act on. @@ -179,6 +190,11 @@ the current working directory. Use the specified mrconfig file. The default is B<~/.mrconfig> +=item -p + +Search in the current directory, and its parent directories and use +the first B<.mrconfig> found, instead of the default B<~/.mrconfig>. + =item -v Be verbose. @@ -214,21 +230,12 @@ with no number specified. This can greatly speed up operations such as updates. It is not recommended for interactive operations. Note that running more than 10 jobs at a time is likely to run afoul of -ssh connection limits. Running between 3 and 5 jobs at a time will yeild +ssh connection limits. Running between 3 and 5 jobs at a time will yield a good speedup in updates without loading the machine too much. =back -=head1 FILES - -The ~/.mrlog file contains commands that mr has remembered to run later, -due to being offline. You can delete or edit this file to remove commands, -or even to add other commands for 'mr online' to run. If the file is -present, mr assumes it is in offline mode. - -B is configured by .mrconfig files. It starts by reading the .mrconfig -file in your home directory, and this can in turn chain load .mrconfig files -from repositories. +=head1 "MRCONFIG FILES" Here is an example .mrconfig file: @@ -309,8 +316,7 @@ repository, ordering it to be processed earlier is not recommended. =item chain If the "chain" parameter is set and its command returns true, then B -will try to load a .mrconfig file from the root of the repository. (You -should avoid chaining from repositories with untrusted committers.) +will try to load a .mrconfig file from the root of the repository. =item include @@ -341,9 +347,35 @@ the action that is performed for a given revision control system, you can override these rcs specific actions. To add a new revision control system, you can just add rcs specific actions for it. +The ~/.mrlog file contains commands that mr has remembered to run later, +due to being offline. You can delete or edit this file to remove commands, +or even to add other commands for 'mr online' to run. If the file is +present, mr assumes it is in offline mode. + +=head "UNTRUSTED MRCONFIG FILES" + +Since mrconfig files can contain arbitrary shell commands, they can do +anything. This flexability is good, but it also allows a malicious mrconfig +file to delete your whole home directory. Such a file might be contained +inside a repository that your main ~/.mrconfig checks out. To avoid worries +about a malicious change being committed to such a file, mr has the ability +to read mrconfig files in untrusted mode. Such files are limited to running +only known safe commands (like "git clone"). + +By default, mr trusts all mrconfig files. (This default will change in a +future release!) But if you have a ~/.mrtrust file, mr will only trust +mrconfig files that are listed within it. (One file per line.) All other +files will be treated as untrusted. + +=head1 EXTENSIONS + +mr can be extended to support things such as unison and git-svn. Some +files providing such extensions are available in /usr/share/mr/. See +the documentation in the files for details about using them. + =head1 AUTHOR -Copyright 2007 Joey Hess +Copyright 2007-2009 Joey Hess Licensed under the GNU GPL version 2 or higher. @@ -767,6 +799,114 @@ sub expandenv { return $val; } +my %trusted; +sub is_trusted_config { + my $config=shift; # must be abs_pathed already + + # We always trust ~/.mrconfig. + return 1 if $config eq abs_path("$ENV{HOME}/.mrconfig"); + + my $trustfile=$ENV{HOME}."/.mrtrust"; + + if (! -e $trustfile) { + print "mr: Assuming $config is trusted.\n"; + print "mr: For better security, you are encouraged to create ~/.mrtrust\n"; + print "mr: and list all trusted mrconfig files in it.\n"; + return 1; + } + + if (! %trusted) { + $trusted{"$ENV{HOME}/.mrconfig"}=1; + open (TRUST, "<", $trustfile) || die "$trustfile: $!"; + while () { + chomp; + s/^~\//$ENV{HOME}\//; + $trusted{abs_path($_)}=1; + } + close TRUST; + } + + return $trusted{$config}; +} + + +sub is_trusted_repo { + my $repo=shift; + + # Tightly limit what is allowed in a repo name. + # No ../, no absolute paths, and no unusual filenames + # that might try to escape to the shell. + return $repo =~ /^[-_.+\/A-Za-z0-9]+$/ && + $repo !~ /\.\./ && $repo !~ /^\//; +} + +sub is_trusted_checkout { + my $command=shift; + + # To determine if the command is safe, compare it with the + # *_trusted_checkout config settings. Those settings are + # templates for allowed commands, so make sure that each word + # of the command matches the corresponding word of the template. + + my @words; + foreach my $word (split(' ', $command)) { + # strip quoting + if ($word=~/^'(.*)'$/) { + $word=$1; + } + elsif ($word=~/^"(.*)"$/) { + $word=$1; + } + + push @words, $word; + } + + foreach my $key (grep { /_trusted_checkout$/ } + keys %{$config{''}{DEFAULT}}) { + my @twords=split(' ', $config{''}{DEFAULT}{$key}); + next if @words > @twords; + + my $match=1; + my $url; + for (my $c=0; $c < @twords && $match; $c++) { + if ($twords[$c] eq '$url') { + # Match all the typical characters found in + # urls, plus @ which svn can use. Note + # that the "url" might also be a local + # directory. + $match=( + defined $words[$c] && + $words[$c] =~ /^[-_.+:@\/A-Za-z0-9]+$/ + ); + $url=$words[$c]; + } + elsif ($twords[$c] eq '$repo') { + # If a repo is not specified, assume it + # will be the last path component of the + # url, or something derived from it, and + # check that. + if (! defined $words[$c] && defined $url) { + ($words[$c])=$url=~/\/([^\/]+)\/?$/; + } + + $match=( + defined $words[$c] && + is_trusted_repo($words[$c]) + ); + } + elsif (defined $words[$c] && $twords[$c] eq $words[$c]) { + $match=1; + } + else { + $match=0; + } + } + return 1 if $match; + } + + return 0; +} + my %loaded; sub loadconfig { my $f=shift; @@ -775,9 +915,11 @@ sub loadconfig { my $in; my $dir; + my $trusted; if (ref $f eq 'GLOB') { $dir=""; - $in=$f; + $in=$f; + $trusted=1; } else { if (! -e $f) { @@ -790,6 +932,8 @@ sub loadconfig { } $loaded{$absf}=1; + $trusted=is_trusted_config($absf); + ($dir)=$f=~/^(.*\/)[^\/]+$/; if (! defined $dir) { $dir="."; @@ -827,7 +971,16 @@ sub loadconfig { chomp; next if /^\s*\#/ || /^\s*$/; if (/^\[([^\]]*)\]\s*$/) { - $section=expandenv($1); + $section=$1; + + if (! $trusted) { + if (! is_trusted_repo($section) || + $section eq 'ALIAS' || + $section eq 'DEFAULT') { + die "mr: illegal section \"[$section]\" in untrusted $f line $line\n"; + } + } + $section=expandenv($section) if $trusted; } elsif (/^(\w+)\s*=\s*(.*)/) { my $parameter=$1; @@ -841,6 +994,17 @@ sub loadconfig { chomp $value; } + if (! $trusted) { + # Untrusted files can only contain checkout + # parameters. + if ($parameter ne 'checkout') { + die "mr: illegal setting \"$parameter=$value\" in untrusted $f line $line\n"; + } + if (! is_trusted_checkout($value)) { + die "mr: illegal checkout command \"$value\" in untrusted $f line $line\n"; + } + } + if ($parameter eq "include") { print "mr: including output of \"$value\"\n" if $verbose; unshift @lines, `$value`; @@ -1012,6 +1176,9 @@ sub dispatch { elsif ($action eq 'register') { register(@ARGV); } + elsif ($action eq 'bootstrap') { + bootstrap(); + } elsif ($action eq 'remember' || $action eq 'offline' || $action eq 'online') { @@ -1117,6 +1284,25 @@ sub register { exec($command) || die "exec: $!"; } +sub bootstrap { + my $url=shift @ARGV; + + if (! defined $url || ! length $url) { + die "mr: bootstrap requires url\n"; + } + + if (-e ".mrconfig") { + die "mr: .mrconfig file already exists, not overwriting with $url\n"; + } + + if (system("curl", "-s", $url, "-o", ".mrconfig") != 0) { + die "mr: download of $url failed\n"; + } + + exec("mr $ENV{MR_SWITCHES} -c .mrconfig checkout"); + die "failed to run mr checkout"; +} + # alias expansion and command stemming sub expandaction { my $action=shift; @@ -1141,12 +1327,24 @@ sub expandaction { return $action; } +sub find_nearest_mrconfig { + my $dir=getcwd(); + while (length $dir) { + if (-e "$dir/.mrconfig") { + return "$dir/.mrconfig"; + } + $dir=~s/\/[^\/]*$//; + } + die "no .mrconfig found in path\n"; +} + sub getopts { my @saved=@ARGV; Getopt::Long::Configure("bundling", "no_permute"); my $result=GetOptions( "d|directory=s" => sub { $directory=abs_path($_[1]) }, "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 }, + "p|path" => sub { $ENV{MR_CONFIG}=find_nearest_mrconfig(); $config_overridden=1 }, "v|verbose" => \$verbose, "q|quiet" => \$quiet, "s|stats" => \$stats, @@ -1155,7 +1353,7 @@ sub getopts { "j|jobs:i" => \$jobs, ); if (! $result || @ARGV < 1) { - die("Usage: mr [-d directory] action [params ...]\n". + die("Usage: mr [options] action [params ...]\n". "(Use mr help for man page.)\n"); } @@ -1269,7 +1467,7 @@ git_bare_test = svn_update = svn update "$@" git_update = git pull "$@" -bzr_update = bzr merge "$@" +bzr_update = bzr merge --pull "$@" cvs_update = cvs update "$@" hg_update = hg pull "$@" && hg update "$@" darcs_update = darcs pull -a "$@" @@ -1360,6 +1558,15 @@ git_bare_register = echo "Registering git url: $url in $MR_CONFIG" mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'" +svn_trusted_checkout = svn co $url $repo +svn_alt_trusted_checkout = svn checkout $url $repo +git_trusted_checkout = git clone $url $repo +bzr_trusted_checkout = bzr clone $url $repo +# cvs: too hard +hg_trusted_checkout = hg clone $url $repo +darcs_trusted_checkout = darcs get $url $repo +git_bare_trusted_checkout = git clone --bare $url $repo + help = if [ ! -e "$MR_PATH" ]; then error "cannot find program path" @@ -1370,6 +1577,7 @@ help = man -l "$tmp" || error "man failed" list = true config = +bootstrap = online = if [ -s ~/.mrlog ]; then