Merge branch 'fl/askpass'
authorJunio C Hamano <gitster@pobox.com>
Sat, 20 Mar 2010 18:29:35 +0000 (11:29 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 20 Mar 2010 18:29:35 +0000 (11:29 -0700)
* fl/askpass:
git-core: Support retrieving passwords with GIT_ASKPASS
git-svn: Support retrieving passwords with GIT_ASKPASS

1  2 
cache.h
connect.c
git-svn.perl
imap-send.c
diff --combined cache.h
index 89f6a40d1a1011bca4d546c5979d66d44b408ece,a25d269a94ed646867e7cc3e0878196e692e0f7f..f62db0bd7e8f79f5d8a31e61c6164344bad10b65
+++ b/cache.h
@@@ -388,15 -388,6 +388,15 @@@ static inline enum object_type object_t
  #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
  #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
  
 +/*
 + * Repository-local GIT_* environment variables
 + * The array is NULL-terminated to simplify its usage in contexts such
 + * environment creation or simple walk of the list.
 + * The number of non-NULL entries is available as a macro.
 + */
 +#define LOCAL_REPO_ENV_SIZE 8
 +extern const char *const local_repo_env[LOCAL_REPO_ENV_SIZE + 1];
 +
  extern int is_bare_repository_cfg;
  extern int is_bare_repository(void);
  extern int is_inside_git_dir(void);
@@@ -650,10 -641,6 +650,10 @@@ int git_mkstemp(char *path, size_t n, c
  
  int git_mkstemps(char *path, size_t n, const char *template, int suffix_len);
  
 +/* set default permissions by passing mode arguments to open(2) */
 +int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
 +int git_mkstemp_mode(char *pattern, int mode);
 +
  /*
   * NOTE NOTE NOTE!!
   *
@@@ -688,7 -675,6 +688,7 @@@ int normalize_path_copy(char *dst, cons
  int longest_ancestor_length(const char *path, const char *prefix_list);
  char *strip_path_suffix(const char *path, const char *suffix);
  int daemon_avoid_alias(const char *path);
 +int offset_1st_component(const char *path);
  
  /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
  extern int sha1_object_info(const unsigned char *, unsigned long *);
@@@ -789,7 -775,7 +789,7 @@@ extern const char *git_committer_info(i
  extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
  extern const char *fmt_name(const char *name, const char *email);
  extern const char *git_editor(void);
 -extern const char *git_pager(void);
 +extern const char *git_pager(int stdout_is_tty);
  
  struct checkout {
        const char *base_dir;
@@@ -891,6 -877,7 +891,7 @@@ struct ref 
  extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
  
  #define CONNECT_VERBOSE       (1u << 0)
+ extern char *git_getpass(const char *prompt);
  extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
  extern int finish_connect(struct child_process *conn);
  extern int path_match(const char *path, int nr, char **match);
diff --combined connect.c
index 323a771b699cd1b35cc93fc408a1bbf6c9119abf,e570aaf2acfb9619fc3a9289ae0da9237cf063bb..9ae991ac42544716599ff8bf3ebaaa376c8119e4
+++ b/connect.c
@@@ -152,28 -152,6 +152,28 @@@ static enum protocol get_protocol(cons
  #define STR_(s)       # s
  #define STR(s)        STR_(s)
  
 +static void get_host_and_port(char **host, const char **port)
 +{
 +      char *colon, *end;
 +
 +      if (*host[0] == '[') {
 +              end = strchr(*host + 1, ']');
 +              if (end) {
 +                      *end = 0;
 +                      end++;
 +                      (*host)++;
 +              } else
 +                      end = *host;
 +      } else
 +              end = *host;
 +      colon = strchr(end, ':');
 +
 +      if (colon) {
 +              *colon = 0;
 +              *port = colon + 1;
 +      }
 +}
 +
  #ifndef NO_IPV6
  
  static const char *ai_name(const struct addrinfo *ai)
  static int git_tcp_connect_sock(char *host, int flags)
  {
        int sockfd = -1, saved_errno = 0;
 -      char *colon, *end;
        const char *port = STR(DEFAULT_GIT_PORT);
        struct addrinfo hints, *ai0, *ai;
        int gai;
        int cnt = 0;
  
 -      if (host[0] == '[') {
 -              end = strchr(host + 1, ']');
 -              if (end) {
 -                      *end = 0;
 -                      end++;
 -                      host++;
 -              } else
 -                      end = host;
 -      } else
 -              end = host;
 -      colon = strchr(end, ':');
 -
 -      if (colon) {
 -              *colon = 0;
 -              port = colon + 1;
 -              if (!*port)
 -                      port = "<none>";
 -      }
 +      get_host_and_port(&host, &port);
 +      if (!*port)
 +              port = "<none>";
  
        memset(&hints, 0, sizeof(hints));
        hints.ai_socktype = SOCK_STREAM;
  static int git_tcp_connect_sock(char *host, int flags)
  {
        int sockfd = -1, saved_errno = 0;
 -      char *colon, *end;
 -      char *port = STR(DEFAULT_GIT_PORT), *ep;
 +      const char *port = STR(DEFAULT_GIT_PORT);
 +      char *ep;
        struct hostent *he;
        struct sockaddr_in sa;
        char **ap;
        unsigned int nport;
        int cnt;
  
 -      if (host[0] == '[') {
 -              end = strchr(host + 1, ']');
 -              if (end) {
 -                      *end = 0;
 -                      end++;
 -                      host++;
 -              } else
 -                      end = host;
 -      } else
 -              end = host;
 -      colon = strchr(end, ':');
 -
 -      if (colon) {
 -              *colon = 0;
 -              port = colon + 1;
 -      }
 +      get_host_and_port(&host, &port);
  
        if (flags & CONNECT_VERBOSE)
                fprintf(stderr, "Looking up %s ... ", host);
@@@ -397,10 -406,26 +397,10 @@@ static int git_use_proxy(const char *ho
  static void git_proxy_connect(int fd[2], char *host)
  {
        const char *port = STR(DEFAULT_GIT_PORT);
 -      char *colon, *end;
        const char *argv[4];
        struct child_process proxy;
  
 -      if (host[0] == '[') {
 -              end = strchr(host + 1, ']');
 -              if (end) {
 -                      *end = 0;
 -                      end++;
 -                      host++;
 -              } else
 -                      end = host;
 -      } else
 -              end = host;
 -      colon = strchr(end, ':');
 -
 -      if (colon) {
 -              *colon = 0;
 -              port = colon + 1;
 -      }
 +      get_host_and_port(&host, &port);
  
        argv[0] = git_proxy_command;
        argv[1] = host;
@@@ -582,8 -607,18 +582,8 @@@ struct child_process *git_connect(int f
                *arg++ = host;
        }
        else {
 -              /* remove these from the environment */
 -              const char *env[] = {
 -                      ALTERNATE_DB_ENVIRONMENT,
 -                      DB_ENVIRONMENT,
 -                      GIT_DIR_ENVIRONMENT,
 -                      GIT_WORK_TREE_ENVIRONMENT,
 -                      GRAFT_ENVIRONMENT,
 -                      INDEX_ENVIRONMENT,
 -                      NO_REPLACE_OBJECTS_ENVIRONMENT,
 -                      NULL
 -              };
 -              conn->env = env;
 +              /* remove repo-local variables from the environment */
 +              conn->env = local_repo_env;
                conn->use_shell = 1;
        }
        *arg++ = cmd.buf;
@@@ -612,3 -647,40 +612,40 @@@ int finish_connect(struct child_proces
        free(conn);
        return code;
  }
+ char *git_getpass(const char *prompt)
+ {
+       char *askpass;
+       struct child_process pass;
+       const char *args[3];
+       static struct strbuf buffer = STRBUF_INIT;
+       askpass = getenv("GIT_ASKPASS");
+       if (!askpass || !(*askpass))
+               return getpass(prompt);
+       args[0] = askpass;
+       args[1] = prompt;
+       args[2] = NULL;
+       memset(&pass, 0, sizeof(pass));
+       pass.argv = args;
+       pass.out = -1;
+       if (start_command(&pass))
+               exit(1);
+       strbuf_reset(&buffer);
+       if (strbuf_read(&buffer, pass.out, 20) < 0)
+               die("failed to read password from %s\n", askpass);
+       close(pass.out);
+       if (finish_command(&pass))
+               exit(1);
+       strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
+       return buffer.buf;
+ }
diff --combined git-svn.perl
index aceeca5a9487e47e281468d02ca10567b74217bf,07e95ad449f066ea18e3009f6abae14519c23530..2c86ea2e384e4b3ecf9f6c2a856c52a0203ce489
@@@ -351,7 -351,6 +351,7 @@@ information
  }
  
  sub version {
 +      ::_req_svn();
        print "git-svn version $VERSION (svn $SVN::Core::VERSION)\n";
        exit 0;
  }
@@@ -370,6 -369,7 +370,6 @@@ sub do_git_init_db 
                command_noisy(@init_db);
                $_repository = Git->repository(Repository => ".git");
        }
 -      command_noisy('config', 'core.autocrlf', 'false');
        my $set;
        my $pfx = "svn-remote.$Git::SVN::default_repo_id";
        foreach my $i (keys %icv) {
@@@ -1102,7 -1102,6 +1102,7 @@@ sub cmd_info 
        if ($@) {
                $result .= "Repository Root: (offline)\n";
        }
 +      ::_req_svn();
        $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" &&
                ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir");
        $result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
@@@ -2998,7 -2997,7 +2998,7 @@@ sub find_extra_svk_parents 
        for my $ticket ( @tickets ) {
                my ($uuid, $path, $rev) = split /:/, $ticket;
                if ( $uuid eq $self->ra_uuid ) {
 -                      my $url = $self->rewrite_root || $self->{url};
 +                      my $url = $self->{url};
                        my $repos_root = $url;
                        my $branch_from = $path;
                        $branch_from =~ s{^/}{};
@@@ -3206,7 -3205,7 +3206,7 @@@ sub find_extra_svn_parents 
        # are now marked as merge, we can add the tip as a parent.
        my @merges = split "\n", $mergeinfo;
        my @merge_tips;
 -      my $url = $self->rewrite_root || $self->{url};
 +      my $url = $self->{url};
        my $uuid = $self->ra_uuid;
        my %ranges;
        for my $merge ( @merges ) {
@@@ -3971,18 -3970,25 +3971,25 @@@ sub username 
  
  sub _read_password {
        my ($prompt, $realm) = @_;
-       print STDERR $prompt;
-       STDERR->flush;
-       require Term::ReadKey;
-       Term::ReadKey::ReadMode('noecho');
        my $password = '';
-       while (defined(my $key = Term::ReadKey::ReadKey(0))) {
-               last if $key =~ /[\012\015]/; # \n\r
-               $password .= $key;
+       if (exists $ENV{GIT_ASKPASS}) {
+               open(PH, "-|", $ENV{GIT_ASKPASS}, $prompt);
+               $password = <PH>;
+               $password =~ s/[\012\015]//; # \n\r
+               close(PH);
+       } else {
+               print STDERR $prompt;
+               STDERR->flush;
+               require Term::ReadKey;
+               Term::ReadKey::ReadMode('noecho');
+               while (defined(my $key = Term::ReadKey::ReadKey(0))) {
+                       last if $key =~ /[\012\015]/; # \n\r
+                       $password .= $key;
+               }
+               Term::ReadKey::ReadMode('restore');
+               print STDERR "\n";
+               STDERR->flush;
        }
-       Term::ReadKey::ReadMode('restore');
-       print STDERR "\n";
-       STDERR->flush;
        $password;
  }
  
@@@ -5466,12 -5472,7 +5473,12 @@@ sub git_svn_log_cmd 
  
  # adapted from pager.c
  sub config_pager {
 -      chomp(my $pager = command_oneline(qw(var GIT_PAGER)));
 +      if (! -t *STDOUT) {
 +              $ENV{GIT_PAGER_IN_USE} = 'false';
 +              $pager = undef;
 +              return;
 +      }
 +      chomp($pager = command_oneline(qw(var GIT_PAGER)));
        if ($pager eq 'cat') {
                $pager = undef;
        }
  }
  
  sub run_pager {
 -      return unless -t *STDOUT && defined $pager;
 +      return unless defined $pager;
        pipe my ($rfd, $wfd) or return;
        defined(my $pid = fork) or ::fatal "Can't fork: $!";
        if (!$pid) {
diff --combined imap-send.c
index fa703838cf3374a52f8a1b6fecf455fb0ca4fef9,5254b2a2785bd41394ff0107fa9476dd189e16e3..aeb2985b831c982f10c3905f5053136520fcc4fe
@@@ -27,9 -27,6 +27,9 @@@
  #include "run-command.h"
  #ifdef NO_OPENSSL
  typedef void *SSL;
 +#else
 +#include <openssl/evp.h>
 +#include <openssl/hmac.h>
  #endif
  
  struct store_conf {
@@@ -142,20 -139,6 +142,20 @@@ struct imap_server_conf 
        int use_ssl;
        int ssl_verify;
        int use_html;
 +      char *auth_method;
 +};
 +
 +static struct imap_server_conf server = {
 +      NULL,   /* name */
 +      NULL,   /* tunnel */
 +      NULL,   /* host */
 +      0,      /* port */
 +      NULL,   /* user */
 +      NULL,   /* pass */
 +      0,      /* use_ssl */
 +      1,      /* ssl_verify */
 +      0,      /* use_html */
 +      NULL,   /* auth_method */
  };
  
  struct imap_store_conf {
@@@ -230,7 -213,6 +230,7 @@@ enum CAPABILITY 
        LITERALPLUS,
        NAMESPACE,
        STARTTLS,
 +      AUTH_CRAM_MD5,
  };
  
  static const char *cap_list[] = {
        "LITERAL+",
        "NAMESPACE",
        "STARTTLS",
 +      "AUTH=CRAM-MD5",
  };
  
  #define RESP_OK    0
@@@ -967,87 -948,6 +967,87 @@@ static void imap_close_store(struct sto
        free(ctx);
  }
  
 +#ifndef NO_OPENSSL
 +
 +/*
 + * hexchar() and cram() functions are based on the code from the isync
 + * project (http://isync.sf.net/).
 + */
 +static char hexchar(unsigned int b)
 +{
 +      return b < 10 ? '0' + b : 'a' + (b - 10);
 +}
 +
 +#define ENCODED_SIZE(n) (4*((n+2)/3))
 +static char *cram(const char *challenge_64, const char *user, const char *pass)
 +{
 +      int i, resp_len, encoded_len, decoded_len;
 +      HMAC_CTX hmac;
 +      unsigned char hash[16];
 +      char hex[33];
 +      char *response, *response_64, *challenge;
 +
 +      /*
 +       * length of challenge_64 (i.e. base-64 encoded string) is a good
 +       * enough upper bound for challenge (decoded result).
 +       */
 +      encoded_len = strlen(challenge_64);
 +      challenge = xmalloc(encoded_len);
 +      decoded_len = EVP_DecodeBlock((unsigned char *)challenge,
 +                                    (unsigned char *)challenge_64, encoded_len);
 +      if (decoded_len < 0)
 +              die("invalid challenge %s", challenge_64);
 +      HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5());
 +      HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len);
 +      HMAC_Final(&hmac, hash, NULL);
 +      HMAC_CTX_cleanup(&hmac);
 +
 +      hex[32] = 0;
 +      for (i = 0; i < 16; i++) {
 +              hex[2 * i] = hexchar((hash[i] >> 4) & 0xf);
 +              hex[2 * i + 1] = hexchar(hash[i] & 0xf);
 +      }
 +
 +      /* response: "<user> <digest in hex>" */
 +      resp_len = strlen(user) + 1 + strlen(hex) + 1;
 +      response = xmalloc(resp_len);
 +      sprintf(response, "%s %s", user, hex);
 +
 +      response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1);
 +      encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
 +                                    (unsigned char *)response, resp_len);
 +      if (encoded_len < 0)
 +              die("EVP_EncodeBlock error");
 +      response_64[encoded_len] = '\0';
 +      return (char *)response_64;
 +}
 +
 +#else
 +
 +static char *cram(const char *challenge_64, const char *user, const char *pass)
 +{
 +      die("If you want to use CRAM-MD5 authenticate method, "
 +          "you have to build git-imap-send with OpenSSL library.");
 +}
 +
 +#endif
 +
 +static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt)
 +{
 +      int ret;
 +      char *response;
 +
 +      response = cram(prompt, server.user, server.pass);
 +
 +      ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
 +      if (ret != strlen(response))
 +              return error("IMAP error: sending response failed\n");
 +
 +      free(response);
 +
 +      return 0;
 +}
 +
  static struct store *imap_open_store(struct imap_server_conf *srvc)
  {
        struct imap_store *ctx;
                if (!srvc->pass) {
                        char prompt[80];
                        sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host);
-                       arg = getpass(prompt);
+                       arg = git_getpass(prompt);
                        if (!arg) {
                                perror("getpass");
                                exit(1);
                if (!imap->buf.sock.ssl)
                        imap_warn("*** IMAP Warning *** Password is being "
                                  "sent in the clear\n");
 -              if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
 -                      fprintf(stderr, "IMAP error: LOGIN failed\n");
 -                      goto bail;
 +
 +              if (srvc->auth_method) {
 +                      struct imap_cmd_cb cb;
 +
 +                      if (!strcmp(srvc->auth_method, "CRAM-MD5")) {
 +                              if (!CAP(AUTH_CRAM_MD5)) {
 +                                      fprintf(stderr, "You specified"
 +                                              "CRAM-MD5 as authentication method, "
 +                                              "but %s doesn't support it.\n", srvc->host);
 +                                      goto bail;
 +                              }
 +                              /* CRAM-MD5 */
 +
 +                              memset(&cb, 0, sizeof(cb));
 +                              cb.cont = auth_cram_md5;
 +                              if (imap_exec(ctx, &cb, "AUTHENTICATE CRAM-MD5") != RESP_OK) {
 +                                      fprintf(stderr, "IMAP error: AUTHENTICATE CRAM-MD5 failed\n");
 +                                      goto bail;
 +                              }
 +                      } else {
 +                              fprintf(stderr, "Unknown authentication method:%s\n", srvc->host);
 +                              goto bail;
 +                      }
 +              } else {
 +                      if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
 +                              fprintf(stderr, "IMAP error: LOGIN failed\n");
 +                              goto bail;
 +                      }
                }
        } /* !preauth */
  
@@@ -1473,6 -1348,18 +1473,6 @@@ static int split_msg(struct msg_data *a
        return 1;
  }
  
 -static struct imap_server_conf server = {
 -      NULL,   /* name */
 -      NULL,   /* tunnel */
 -      NULL,   /* host */
 -      0,      /* port */
 -      NULL,   /* user */
 -      NULL,   /* pass */
 -      0,      /* use_ssl */
 -      1,      /* ssl_verify */
 -      0,      /* use_html */
 -};
 -
  static char *imap_folder;
  
  static int git_imap_config(const char *key, const char *val, void *cb)
                server.port = git_config_int(key, val);
        else if (!strcmp("tunnel", key))
                server.tunnel = xstrdup(val);
 +      else if (!strcmp("authmethod", key))
 +              server.auth_method = xstrdup(val);
 +
        return 0;
  }