client.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. package main
  15. import (
  16. "bufio"
  17. "errors"
  18. "fmt"
  19. "net"
  20. "net/url"
  21. "os"
  22. "os/exec"
  23. "strings"
  24. "syscall"
  25. "time"
  26. )
  27. type Client struct {
  28. ProxyBin string
  29. Args []string
  30. insecure bool
  31. }
  32. func (c *Client) Run() error {
  33. if err := c.resolveArgs(); err != nil {
  34. return fmt.Errorf("resolveArgs() got error: %v", err)
  35. }
  36. // Connect to the proxy.
  37. uconn, hconn, addr, err := c.connect()
  38. if err != nil {
  39. return fmt.Errorf("connect() got error: %v", err)
  40. }
  41. // Keep the unix socket connection open for the duration of the request.
  42. defer uconn.Close()
  43. // Keep a connection to the HTTP server open, so no other user can
  44. // bind on the same address so long as the process is running.
  45. defer hconn.Close()
  46. // Start the git-remote-http subprocess.
  47. cargs := []string{"-c", fmt.Sprintf("http.proxy=%v", addr), "remote-http"}
  48. cargs = append(cargs, c.Args...)
  49. cmd := exec.Command("git", cargs...)
  50. for _, v := range os.Environ() {
  51. if !strings.HasPrefix(v, "GIT_PERSISTENT_HTTPS_SECURE=") {
  52. cmd.Env = append(cmd.Env, v)
  53. }
  54. }
  55. // Set the GIT_PERSISTENT_HTTPS_SECURE environment variable when
  56. // the proxy is using a SSL connection. This allows credential helpers
  57. // to identify secure proxy connections, despite being passed an HTTP
  58. // scheme.
  59. if !c.insecure {
  60. cmd.Env = append(cmd.Env, "GIT_PERSISTENT_HTTPS_SECURE=1")
  61. }
  62. cmd.Stdin = os.Stdin
  63. cmd.Stdout = os.Stdout
  64. cmd.Stderr = os.Stderr
  65. if err := cmd.Run(); err != nil {
  66. if eerr, ok := err.(*exec.ExitError); ok {
  67. if stat, ok := eerr.ProcessState.Sys().(syscall.WaitStatus); ok && stat.ExitStatus() != 0 {
  68. os.Exit(stat.ExitStatus())
  69. }
  70. }
  71. return fmt.Errorf("git-remote-http subprocess got error: %v", err)
  72. }
  73. return nil
  74. }
  75. func (c *Client) connect() (uconn net.Conn, hconn net.Conn, addr string, err error) {
  76. uconn, err = DefaultSocket.Dial()
  77. if err != nil {
  78. if e, ok := err.(*net.OpError); ok && (os.IsNotExist(e.Err) || e.Err == syscall.ECONNREFUSED) {
  79. if err = c.startProxy(); err == nil {
  80. uconn, err = DefaultSocket.Dial()
  81. }
  82. }
  83. if err != nil {
  84. return
  85. }
  86. }
  87. if addr, err = c.readAddr(uconn); err != nil {
  88. return
  89. }
  90. // Open a tcp connection to the proxy.
  91. if hconn, err = net.Dial("tcp", addr); err != nil {
  92. return
  93. }
  94. // Verify the address hasn't changed ownership.
  95. var addr2 string
  96. if addr2, err = c.readAddr(uconn); err != nil {
  97. return
  98. } else if addr != addr2 {
  99. err = fmt.Errorf("address changed after connect. got %q, want %q", addr2, addr)
  100. return
  101. }
  102. return
  103. }
  104. func (c *Client) readAddr(conn net.Conn) (string, error) {
  105. conn.SetDeadline(time.Now().Add(5 * time.Second))
  106. data := make([]byte, 100)
  107. n, err := conn.Read(data)
  108. if err != nil {
  109. return "", fmt.Errorf("error reading unix socket: %v", err)
  110. } else if n == 0 {
  111. return "", errors.New("empty data response")
  112. }
  113. conn.Write([]byte{1}) // Ack
  114. var addr string
  115. if addrs := strings.Split(string(data[:n]), "\n"); len(addrs) != 2 {
  116. return "", fmt.Errorf("got %q, wanted 2 addresses", data[:n])
  117. } else if c.insecure {
  118. addr = addrs[1]
  119. } else {
  120. addr = addrs[0]
  121. }
  122. return addr, nil
  123. }
  124. func (c *Client) startProxy() error {
  125. cmd := exec.Command(c.ProxyBin)
  126. cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
  127. stdout, err := cmd.StdoutPipe()
  128. if err != nil {
  129. return err
  130. }
  131. defer stdout.Close()
  132. if err := cmd.Start(); err != nil {
  133. return err
  134. }
  135. result := make(chan error)
  136. go func() {
  137. bytes, _, err := bufio.NewReader(stdout).ReadLine()
  138. if line := string(bytes); err == nil && line != "OK" {
  139. err = fmt.Errorf("proxy returned %q, want \"OK\"", line)
  140. }
  141. result <- err
  142. }()
  143. select {
  144. case err := <-result:
  145. return err
  146. case <-time.After(5 * time.Second):
  147. return errors.New("timeout waiting for proxy to start")
  148. }
  149. panic("not reachable")
  150. }
  151. func (c *Client) resolveArgs() error {
  152. if nargs := len(c.Args); nargs == 0 {
  153. return errors.New("remote needed")
  154. } else if nargs > 2 {
  155. return fmt.Errorf("want at most 2 args, got %v", c.Args)
  156. }
  157. // Rewrite the url scheme to be http.
  158. idx := len(c.Args) - 1
  159. rawurl := c.Args[idx]
  160. rurl, err := url.Parse(rawurl)
  161. if err != nil {
  162. return fmt.Errorf("invalid remote: %v", err)
  163. }
  164. c.insecure = rurl.Scheme == "persistent-http"
  165. rurl.Scheme = "http"
  166. c.Args[idx] = rurl.String()
  167. if idx != 0 && c.Args[0] == rawurl {
  168. c.Args[0] = c.Args[idx]
  169. }
  170. return nil
  171. }