package Cvs::Cvsroot; use strict; use File::Temp; use base qw(Class::Accessor); Cvs::Cvsroot->mk_accessors(qw(cvsroot method password fingerprint passphrase)); sub new { my($proto, $cvsroot, %conf) = @_; return unless defined $cvsroot; my $class = ref $proto || $proto; my $self = {}; bless($self, $class); $self->{cvsroot} = $cvsroot; if($cvsroot =~ /^\//) { $self->{method} = 'filesystem'; } elsif($cvsroot =~ /^\:pserver\:/) { $self->{method} = 'pserver'; $self->{password} = $conf{password}; } elsif($cvsroot =~ /^(?:.*\@)?.+:.*$/) { $conf{'remote-shell'} ||= 'ssh'; if($conf{'remote-shell'} =~ /(ssh|rsh)$/) { $self->{'remote-shell'} = $conf{'remote-shell'}; $self->{method} = $1; if($self->{method} eq 'ssh') { $self->{password} = $conf{password}; $self->{fingerprint} = $conf{fingerprint}; $self->{passphrase} = $conf{passphrase}; } } else { warn 'unknown remote-shell: ' . $conf{'remote-shell'}; return; } } else { warn "not implemented cvsroot method: $cvsroot"; return; } return $self; } sub bind { my($self, $cmd) = @_; my $debug = $cmd->cvs->debug(); if($debug) { print STDERR "Binding CVSROOT handlers\n"; print STDERR "CVSROOT access method is: $self->{method}\n"; } my $init_context = $cmd->initial_context(); if(defined $self->{'remote-shell'}) { $ENV{CVS_RSH} = $self->{'remote-shell'}; } if($self->{method} eq 'pserver') { $init_context->push_handler ( qr/^cvs .*: used empty password; /, sub { if(defined $self->{password}) { if($cmd->cvs->login->success()) { # The former command failed because it wasn't # logged. So we need to relaunch it internally $cmd->restart(); } else { $cmd->err_result('pserver login failure'); return $init_context->finish(); } } else { $cmd->err_result('you have to login.'); return $init_context->finish(); } } ); } elsif($self->{method} eq 'ssh') { # without pty, ssh call the ssh-askpass program to grab needed # informations from user. In batch mode it's not possible, so # we rewrite an ssh-askpass in a shell script stored in a # temporary file and we tell ssh to call it. my($fh, $file) = File::Temp::tmpnam() or die "can't create a temporary file"; print STDERR "Creating askpass script `$file'\n" if $debug; chmod(0700, $file); $fh->print("#!/bin/sh\n"); $fh->print("echo \$1|grep -iq password&&echo $self->{password}&&exit\n"); $fh->print("echo \$1|grep -iq passphrase&&echo $self->{passphrase}&&exit\n"); $fh->print("echo yes\n"); $fh->close(); $cmd->push_cleanup(sub { print STDERR "Deleting askpass script `$file'\n" if $debug; unlink $file }); $ENV{SSH_ASKPASS} = $file; # ssh doesn't tell ssh-askpass until the DISPLAY environment # isn't set, so we have to set it to something (see ssh's # manual for more details). $ENV{DISPLAY} = ''; my $ssh_context = $cmd->new_context(); my $fingerprint; # building a combo pattern for all ssh error starting with the # string "ssh: " my $error_patterns = join ('|', '.*: Name or service not known', 'connect to address [\d.]+ port \d+: Connection refused', ); $init_context->push_handler ( qr/^ssh: (?:$error_patterns)/, sub { $cmd->err_result(shift->[0]); return $init_context->finish(); } ); $init_context->push_handler ( qr/Could not create directory/, sub { # Hint: this can happened where the home directory isn't writable } ); $init_context->push_handler ( qr/^Enter passphrase for key/, sub { $cmd->send($self->{passphrase}); } ); $init_context->push_handler ( # maybe ssh version defendant... qr/'s password:/, sub { $cmd->send("$self->{password}\n"); } ); $init_context->push_handler ( qr/Permission denied/, sub { $cmd->err_result('ssh: authentication failure'); return $init_context->finish(); } ); $init_context->push_handler ( qr/^The authenticity of host .* can't be established\./, sub { return $ssh_context; } ); $ssh_context->push_handler ( qr/key fingerprint is ([a-f\d:]+)\./, sub { $fingerprint = shift->[1]; } ); $ssh_context->push_handler ( qr/^Are you sure you want to continue connecting/, sub { if(defined $fingerprint && defined $self->{fingerprint}) { if($fingerprint eq $self->{fingerprint}) { $cmd->send("yes\n"); } else { $cmd->send("no\n"); } } else { $cmd->send("yes\n"); } } ); $ssh_context->push_handler ( qr/Host key verification failed\./, sub { $cmd->err_result('ssh: '.shift->[0]); return $ssh_context->finish(); } ); $ssh_context->push_handler ( qr/Warning: Permanently added .* to the list of known hosts\./, sub { # fallback to initial context return $init_context; } ); } } 1; =pod =head1 LICENCE This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =head1 COPYRIGHT Copyright (C) 2003 - Olivier Poitrey