From 1bc760aeb784701a702c0a306d464834e96b1f3d Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Thu, 7 Feb 2013 15:01:17 +0100 Subject: Git.pm: allow command_close_bidi_pipe to be called as method The documentation of command_close_bidi_pipe() claims that it can be called as a method, but it does not check whether the first argument is $self or not assuming the latter. Using _maybe_self() fixes this. Signed-off-by: Michal Nazarewicz Signed-off-by: Junio C Hamano --- perl/Git.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/Git.pm b/perl/Git.pm index 931047c51d..bbb753a0ac 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -430,7 +430,7 @@ have more complicated structure. sub command_close_bidi_pipe { local $?; - my ($pid, $in, $out, $ctx) = @_; + my ($self, $pid, $in, $out, $ctx) = _maybe_self(@_); foreach my $fh ($in, $out) { unless (close $fh) { if ($!) { -- cgit v1.2.3 From 8a2cc51b6ff335d8e5abf2d8f7be33412e4b9158 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Thu, 7 Feb 2013 15:01:18 +0100 Subject: Git.pm: fix example in command_close_bidi_pipe documentation File handle goes as the first argument when calling print on it. Signed-off-by: Michal Nazarewicz Signed-off-by: Junio C Hamano --- perl/Git.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/Git.pm b/perl/Git.pm index bbb753a0ac..11f310af1d 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -418,7 +418,7 @@ and it is the fourth value returned by C. The call idiom is: my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check'); - print "000000000\n" $out; + print $out "000000000\n"; while (<$in>) { ... } $r->command_close_bidi_pipe($pid, $in, $out, $ctx); -- cgit v1.2.3 From 1323dba6afad29d3cd07e9da85b947bfc2b912de Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Tue, 12 Feb 2013 15:02:30 +0100 Subject: Git.pm: refactor command_close_bidi_pipe to use _cmd_close The body of the loop in command_close_bidi_pipe sub is identical to what _cmd_close sub does. Instead of duplicating, refactor _cmd_close so that it accepts a list of file handles to be closed, which makes it usable with command_close_bidi_pipe. Signed-off-by: Michal Nazarewicz Signed-off-by: Junio C Hamano --- perl/Git.pm | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/perl/Git.pm b/perl/Git.pm index 11f310af1d..2ccb95dc14 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -267,13 +267,13 @@ sub command { if (not defined wantarray) { # Nothing to pepper the possible exception with. - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } elsif (not wantarray) { local $/; my $text = <$fh>; try { - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } catch Git::Error::Command with { # Pepper with the output: my $E = shift; @@ -286,7 +286,7 @@ sub command { my @lines = <$fh>; defined and chomp for @lines; try { - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } catch Git::Error::Command with { my $E = shift; $E->{'-outputref'} = \@lines; @@ -313,7 +313,7 @@ sub command_oneline { my $line = <$fh>; defined $line and chomp $line; try { - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } catch Git::Error::Command with { # Pepper with the output: my $E = shift; @@ -381,7 +381,7 @@ have more complicated structure. sub command_close_pipe { my ($self, $fh, $ctx) = _maybe_self(@_); $ctx ||= ''; - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } =item command_bidi_pipe ( COMMAND [, ARGUMENTS... ] ) @@ -431,18 +431,8 @@ have more complicated structure. sub command_close_bidi_pipe { local $?; my ($self, $pid, $in, $out, $ctx) = _maybe_self(@_); - foreach my $fh ($in, $out) { - unless (close $fh) { - if ($!) { - carp "error closing pipe: $!"; - } elsif ($? >> 8) { - throw Git::Error::Command($ctx, $? >>8); - } - } - } - + _cmd_close($ctx, $in, $out); waitpid $pid, 0; - if ($? >> 8) { throw Git::Error::Command($ctx, $? >>8); } @@ -1355,9 +1345,11 @@ sub _execv_git_cmd { exec('git', @_); } # Close pipe to a subprocess. sub _cmd_close { - my ($fh, $ctx) = @_; - if (not close $fh) { - if ($!) { + my $ctx = shift @_; + foreach my $fh (@_) { + if (close $fh) { + # nop + } elsif ($!) { # It's just close, no point in fatalities carp "error closing pipe: $!"; } elsif ($? >> 8) { -- cgit v1.2.3 From f4c0035de660f5be4d78b0ba0aa8a7863b89c72f Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Tue, 12 Feb 2013 15:02:31 +0100 Subject: Git.pm: allow pipes to be closed prior to calling command_close_bidi_pipe The command_close_bidi_pipe() function will insist on closing both input and output pipes returned by command_bidi_pipe(). With this change it is possible to close one of the pipes in advance and pass undef as an argument. Signed-off-by: Michal Nazarewicz Signed-off-by: Junio C Hamano --- perl/Git.pm | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/perl/Git.pm b/perl/Git.pm index 2ccb95dc14..620e0f9e51 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -426,12 +426,25 @@ Note that you should not rely on whatever actually is in C; currently it is simply the command name but in future the context might have more complicated structure. +C and C may be C if they have been closed prior to +calling this function. This may be useful in a query-response type of +commands where caller first writes a query and later reads response, eg: + + my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check'); + print $out "000000000\n"; + close $out; + while (<$in>) { ... } + $r->command_close_bidi_pipe($pid, $in, undef, $ctx); + +This idiom may prevent potential dead locks caused by data sent to the output +pipe not being flushed and thus not reaching the executed command. + =cut sub command_close_bidi_pipe { local $?; my ($self, $pid, $in, $out, $ctx) = _maybe_self(@_); - _cmd_close($ctx, $in, $out); + _cmd_close($ctx, (grep { defined } ($in, $out))); waitpid $pid, 0; if ($? >> 8) { throw Git::Error::Command($ctx, $? >>8); -- cgit v1.2.3 From 52dce6d036f1f3859062c475127e498ab3d261b9 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Tue, 12 Feb 2013 15:02:32 +0100 Subject: Git.pm: add interface for git credential command Add a credential() function which is an interface to the git credential command. The code is heavily based on credential_* functions in . Signed-off-by: Michal Nazarewicz Signed-off-by: Junio C Hamano --- perl/Git.pm | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/perl/Git.pm b/perl/Git.pm index 620e0f9e51..377f7bafb7 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -59,6 +59,7 @@ require Exporter; command_bidi_pipe command_close_bidi_pipe version exec_path html_path hash_object git_cmd_try remote_refs prompt + credential credential_read credential_write temp_acquire temp_release temp_reset temp_path); @@ -1003,6 +1004,156 @@ sub _close_cat_blob { } +=item credential_read( FILEHANDLE ) + +Reads credential key-value pairs from C. Reading stops at EOF or +when an empty line is encountered. Each line must be of the form C +with a non-empty key. Function returns hash with all read values. Any white +space (other than new-line character) is preserved. + +=cut + +sub credential_read { + my ($self, $reader) = _maybe_self(@_); + my %credential; + while (<$reader>) { + chomp; + if ($_ eq '') { + last; + } elsif (!/^([^=]+)=(.*)$/) { + throw Error::Simple("unable to parse git credential data:\n$_"); + } + $credential{$1} = $2; + } + return %credential; +} + +=item credential_write( FILEHANDLE, CREDENTIAL_HASHREF ) + +Writes credential key-value pairs from hash referenced by +C to C. Keys and values cannot contain +new-lines or NUL bytes characters, and key cannot contain equal signs nor be +empty (if they do Error::Simple is thrown). Any white space is preserved. If +value for a key is C, it will be skipped. + +If C<'url'> key exists it will be written first. (All the other key-value +pairs are written in sorted order but you should not depend on that). Once +all lines are written, an empty line is printed. + +=cut + +sub credential_write { + my ($self, $writer, $credential) = _maybe_self(@_); + my ($key, $value); + + # Check if $credential is valid prior to writing anything + while (($key, $value) = each %$credential) { + if (!defined $key || !length $key) { + throw Error::Simple("credential key empty or undefined"); + } elsif ($key =~ /[=\n\0]/) { + throw Error::Simple("credential key contains invalid characters: $key"); + } elsif (defined $value && $value =~ /[\n\0]/) { + throw Error::Simple("credential value for key=$key contains invalid characters: $value"); + } + } + + for $key (sort { + # url overwrites other fields, so it must come first + return -1 if $a eq 'url'; + return 1 if $b eq 'url'; + return $a cmp $b; + } keys %$credential) { + if (defined $credential->{$key}) { + print $writer $key, '=', $credential->{$key}, "\n"; + } + } + print $writer "\n"; +} + +sub _credential_run { + my ($self, $credential, $op) = _maybe_self(@_); + my ($pid, $reader, $writer, $ctx) = command_bidi_pipe('credential', $op); + + credential_write $writer, $credential; + close $writer; + + if ($op eq "fill") { + %$credential = credential_read $reader; + } + if (<$reader>) { + throw Error::Simple("unexpected output from git credential $op response:\n$_\n"); + } + + command_close_bidi_pipe($pid, $reader, undef, $ctx); +} + +=item credential( CREDENTIAL_HASHREF [, OPERATION ] ) + +=item credential( CREDENTIAL_HASHREF, CODE ) + +Executes C for a given set of credentials and specified +operation. In both forms C needs to be a reference to +a hash which stores credentials. Under certain conditions the hash can +change. + +In the first form, C can be C<'fill'>, C<'approve'> or C<'reject'>, +and function will execute corresponding C sub-command. If +it's omitted C<'fill'> is assumed. In case of C<'fill'> the values stored in +C will be changed to the ones returned by the C command. The usual usage would look something like: + + my %cred = ( + 'protocol' => 'https', + 'host' => 'example.com', + 'username' => 'bob' + ); + Git::credential \%cred; + if (try_to_authenticate($cred{'username'}, $cred{'password'})) { + Git::credential \%cred, 'approve'; + ... do more stuff ... + } else { + Git::credential \%cred, 'reject'; + } + +In the second form, C needs to be a reference to a subroutine. The +function will execute C to fill the provided credential +hash, then call C with C as the sole argument. If +C's return value is defined, the function will execute C (if return value yields true) or C (if return +value is false). If the return value is undef, nothing at all is executed; +this is useful, for example, if the credential could neither be verified nor +rejected due to an unrelated network error. The return value is the same as +what C returns. With this form, the usage might look as follows: + + if (Git::credential { + 'protocol' => 'https', + 'host' => 'example.com', + 'username' => 'bob' + }, sub { + my $cred = shift; + return !!try_to_authenticate($cred->{'username'}, + $cred->{'password'}); + }) { + ... do more stuff ... + } + +=cut + +sub credential { + my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill'); + + if ('CODE' eq ref $op_or_code) { + _credential_run $credential, 'fill'; + my $ret = $op_or_code->($credential); + if (defined $ret) { + _credential_run $credential, $ret ? 'approve' : 'reject'; + } + return $ret; + } else { + _credential_run $credential, $op_or_code; + } +} + { # %TEMP_* Lexical Context my (%TEMP_FILEMAP, %TEMP_FILES); -- cgit v1.2.3 From 4d31a44a08365b90ff28c5e87e187363e10c2a31 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Tue, 12 Feb 2013 15:02:33 +0100 Subject: git-send-email: use git credential to obtain password If smtp_user is provided but smtp_pass is not, instead of prompting for password, make git-send-email use git credential command instead. Signed-off-by: Michal Nazarewicz Signed-off-by: Junio C Hamano --- Documentation/git-send-email.txt | 4 +-- git-send-email.perl | 71 ++++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 44a1f7c4e8..0cffef8aa5 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -164,8 +164,8 @@ Sending Furthermore, passwords need not be specified in configuration files or on the command line. If a username has been specified (with '--smtp-user' or a 'sendemail.smtpuser'), but no password has been -specified (with '--smtp-pass' or 'sendemail.smtppass'), then the -user is prompted for a password while the input is masked for privacy. +specified (with '--smtp-pass' or 'sendemail.smtppass'), then +a password is obtained using 'git-credential'. --smtp-server=:: If set, specifies the outgoing SMTP server to use (e.g. diff --git a/git-send-email.perl b/git-send-email.perl index be809e5b59..c3501d987e 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -1045,6 +1045,47 @@ sub maildomain { return maildomain_net() || maildomain_mta() || 'localhost.localdomain'; } +sub smtp_host_string { + if (defined $smtp_server_port) { + return "$smtp_server:$smtp_server_port"; + } else { + return $smtp_server; + } +} + +# Returns 1 if authentication succeeded or was not necessary +# (smtp_user was not specified), and 0 otherwise. + +sub smtp_auth_maybe { + if (!defined $smtp_authuser || $auth) { + return 1; + } + + # Workaround AUTH PLAIN/LOGIN interaction defect + # with Authen::SASL::Cyrus + eval { + require Authen::SASL; + Authen::SASL->import(qw(Perl)); + }; + + # TODO: Authentication may fail not because credentials were + # invalid but due to other reasons, in which we should not + # reject credentials. + $auth = Git::credential({ + 'protocol' => 'smtp', + 'host' => smtp_host_string(), + 'username' => $smtp_authuser, + # if there's no password, "git credential fill" will + # give us one, otherwise it'll just pass this one. + 'password' => $smtp_authpass + }, sub { + my $cred = shift; + return !!$smtp->auth($cred->{'username'}, $cred->{'password'}); + }); + + return $auth; +} + # Returns 1 if the message was sent, and 0 otherwise. # In actuality, the whole program dies when there # is an error sending a message. @@ -1155,9 +1196,7 @@ X-Mailer: git-send-email $gitversion else { require Net::SMTP; $smtp_domain ||= maildomain(); - $smtp ||= Net::SMTP->new((defined $smtp_server_port) - ? "$smtp_server:$smtp_server_port" - : $smtp_server, + $smtp ||= Net::SMTP->new(smtp_host_string(), Hello => $smtp_domain, Debug => $debug_net_smtp); if ($smtp_encryption eq 'tls' && $smtp) { @@ -1185,31 +1224,7 @@ X-Mailer: git-send-email $gitversion defined $smtp_server_port ? " port=$smtp_server_port" : ""; } - if (defined $smtp_authuser) { - # Workaround AUTH PLAIN/LOGIN interaction defect - # with Authen::SASL::Cyrus - eval { - require Authen::SASL; - Authen::SASL->import(qw(Perl)); - }; - - if (!defined $smtp_authpass) { - - system "stty -echo"; - - do { - print "Password: "; - $_ = ; - print "\n"; - } while (!defined $_); - - chomp($smtp_authpass = $_); - - system "stty echo"; - } - - $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message; - } + smtp_auth_maybe or die $smtp->message; $smtp->mail( $raw_from ) or die $smtp->message; $smtp->to( @recipients ) or die $smtp->message; -- cgit v1.2.3