contrib / persistent-https / client.goon commit Sync with maint (a0a1831)
   1// Copyright 2012 Google Inc. All Rights Reserved.
   2//
   3// Licensed under the Apache License, Version 2.0 (the "License");
   4// you may not use this file except in compliance with the License.
   5// You may obtain a copy of the License at
   6//
   7//     http://www.apache.org/licenses/LICENSE-2.0
   8//
   9// Unless required by applicable law or agreed to in writing, software
  10// distributed under the License is distributed on an "AS IS" BASIS,
  11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12// See the License for the specific language governing permissions and
  13// limitations under the License.
  14
  15package main
  16
  17import (
  18        "bufio"
  19        "errors"
  20        "fmt"
  21        "net"
  22        "net/url"
  23        "os"
  24        "os/exec"
  25        "strings"
  26        "syscall"
  27        "time"
  28)
  29
  30type Client struct {
  31        ProxyBin string
  32        Args     []string
  33
  34        insecure bool
  35}
  36
  37func (c *Client) Run() error {
  38        if err := c.resolveArgs(); err != nil {
  39                return fmt.Errorf("resolveArgs() got error: %v", err)
  40        }
  41
  42        // Connect to the proxy.
  43        uconn, hconn, addr, err := c.connect()
  44        if err != nil {
  45                return fmt.Errorf("connect() got error: %v", err)
  46        }
  47        // Keep the unix socket connection open for the duration of the request.
  48        defer uconn.Close()
  49        // Keep a connection to the HTTP server open, so no other user can
  50        // bind on the same address so long as the process is running.
  51        defer hconn.Close()
  52
  53        // Start the git-remote-http subprocess.
  54        cargs := []string{"-c", fmt.Sprintf("http.proxy=%v", addr), "remote-http"}
  55        cargs = append(cargs, c.Args...)
  56        cmd := exec.Command("git", cargs...)
  57
  58        for _, v := range os.Environ() {
  59                if !strings.HasPrefix(v, "GIT_PERSISTENT_HTTPS_SECURE=") {
  60                        cmd.Env = append(cmd.Env, v)
  61                }
  62        }
  63        // Set the GIT_PERSISTENT_HTTPS_SECURE environment variable when
  64        // the proxy is using a SSL connection.  This allows credential helpers
  65        // to identify secure proxy connections, despite being passed an HTTP
  66        // scheme.
  67        if !c.insecure {
  68                cmd.Env = append(cmd.Env, "GIT_PERSISTENT_HTTPS_SECURE=1")
  69        }
  70
  71        cmd.Stdin = os.Stdin
  72        cmd.Stdout = os.Stdout
  73        cmd.Stderr = os.Stderr
  74        if err := cmd.Run(); err != nil {
  75                if eerr, ok := err.(*exec.ExitError); ok {
  76                        if stat, ok := eerr.ProcessState.Sys().(syscall.WaitStatus); ok && stat.ExitStatus() != 0 {
  77                                os.Exit(stat.ExitStatus())
  78                        }
  79                }
  80                return fmt.Errorf("git-remote-http subprocess got error: %v", err)
  81        }
  82        return nil
  83}
  84
  85func (c *Client) connect() (uconn net.Conn, hconn net.Conn, addr string, err error) {
  86        uconn, err = DefaultSocket.Dial()
  87        if err != nil {
  88                if e, ok := err.(*net.OpError); ok && (os.IsNotExist(e.Err) || e.Err == syscall.ECONNREFUSED) {
  89                        if err = c.startProxy(); err == nil {
  90                                uconn, err = DefaultSocket.Dial()
  91                        }
  92                }
  93                if err != nil {
  94                        return
  95                }
  96        }
  97
  98        if addr, err = c.readAddr(uconn); err != nil {
  99                return
 100        }
 101
 102        // Open a tcp connection to the proxy.
 103        if hconn, err = net.Dial("tcp", addr); err != nil {
 104                return
 105        }
 106
 107        // Verify the address hasn't changed ownership.
 108        var addr2 string
 109        if addr2, err = c.readAddr(uconn); err != nil {
 110                return
 111        } else if addr != addr2 {
 112                err = fmt.Errorf("address changed after connect. got %q, want %q", addr2, addr)
 113                return
 114        }
 115        return
 116}
 117
 118func (c *Client) readAddr(conn net.Conn) (string, error) {
 119        conn.SetDeadline(time.Now().Add(5 * time.Second))
 120        data := make([]byte, 100)
 121        n, err := conn.Read(data)
 122        if err != nil {
 123                return "", fmt.Errorf("error reading unix socket: %v", err)
 124        } else if n == 0 {
 125                return "", errors.New("empty data response")
 126        }
 127        conn.Write([]byte{1}) // Ack
 128
 129        var addr string
 130        if addrs := strings.Split(string(data[:n]), "\n"); len(addrs) != 2 {
 131                return "", fmt.Errorf("got %q, wanted 2 addresses", data[:n])
 132        } else if c.insecure {
 133                addr = addrs[1]
 134        } else {
 135                addr = addrs[0]
 136        }
 137        return addr, nil
 138}
 139
 140func (c *Client) startProxy() error {
 141        cmd := exec.Command(c.ProxyBin)
 142        cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
 143        stdout, err := cmd.StdoutPipe()
 144        if err != nil {
 145                return err
 146        }
 147        defer stdout.Close()
 148        if err := cmd.Start(); err != nil {
 149                return err
 150        }
 151        result := make(chan error)
 152        go func() {
 153                bytes, _, err := bufio.NewReader(stdout).ReadLine()
 154                if line := string(bytes); err == nil && line != "OK" {
 155                        err = fmt.Errorf("proxy returned %q, want \"OK\"", line)
 156                }
 157                result <- err
 158        }()
 159        select {
 160        case err := <-result:
 161                return err
 162        case <-time.After(5 * time.Second):
 163                return errors.New("timeout waiting for proxy to start")
 164        }
 165        panic("not reachable")
 166}
 167
 168func (c *Client) resolveArgs() error {
 169        if nargs := len(c.Args); nargs == 0 {
 170                return errors.New("remote needed")
 171        } else if nargs > 2 {
 172                return fmt.Errorf("want at most 2 args, got %v", c.Args)
 173        }
 174
 175        // Rewrite the url scheme to be http.
 176        idx := len(c.Args) - 1
 177        rawurl := c.Args[idx]
 178        rurl, err := url.Parse(rawurl)
 179        if err != nil {
 180                return fmt.Errorf("invalid remote: %v", err)
 181        }
 182        c.insecure = rurl.Scheme == "persistent-http"
 183        rurl.Scheme = "http"
 184        c.Args[idx] = rurl.String()
 185        if idx != 0 && c.Args[0] == rawurl {
 186                c.Args[0] = c.Args[idx]
 187        }
 188        return nil
 189}