2016-05-16 16:57:37 +00:00
|
|
|
package httputil
|
2015-08-27 20:38:35 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2015-10-05 19:26:39 +00:00
|
|
|
"encoding/base64"
|
2015-10-07 18:30:53 +00:00
|
|
|
"errors"
|
2015-10-16 15:06:17 +00:00
|
|
|
"fmt"
|
2015-10-05 19:26:39 +00:00
|
|
|
"io"
|
2015-08-27 20:38:35 +00:00
|
|
|
"io/ioutil"
|
2015-10-05 19:26:39 +00:00
|
|
|
"net/http"
|
2015-11-06 20:47:45 +00:00
|
|
|
"os"
|
2015-10-05 19:38:16 +00:00
|
|
|
"strings"
|
2015-11-06 20:47:45 +00:00
|
|
|
"sync/atomic"
|
2015-11-06 17:53:56 +00:00
|
|
|
|
2016-05-24 15:30:06 +00:00
|
|
|
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
|
2016-05-16 16:57:37 +00:00
|
|
|
"github.com/github/git-lfs/auth"
|
2016-05-13 16:38:06 +00:00
|
|
|
"github.com/github/git-lfs/config"
|
2015-08-27 20:38:35 +00:00
|
|
|
)
|
|
|
|
|
2016-05-16 16:57:37 +00:00
|
|
|
func ntlmClientSession(c *config.Configuration, creds auth.Creds) (ntlm.ClientSession, error) {
|
2016-05-13 16:38:06 +00:00
|
|
|
if c.NtlmSession != nil {
|
|
|
|
return c.NtlmSession, nil
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
2015-10-05 19:38:16 +00:00
|
|
|
splits := strings.Split(creds["username"], "\\")
|
2015-10-07 20:54:23 +00:00
|
|
|
|
|
|
|
if len(splits) != 2 {
|
2015-10-16 15:06:17 +00:00
|
|
|
errorMessage := fmt.Sprintf("Your user name must be of the form DOMAIN\\user. It is currently %s", creds["username"], "string")
|
|
|
|
return nil, errors.New(errorMessage)
|
2015-10-07 18:30:53 +00:00
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-10-08 20:18:24 +00:00
|
|
|
session, err := ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
|
2015-10-07 20:54:23 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-10-05 19:55:24 +00:00
|
|
|
session.SetUserInfo(splits[1], creds["password"], strings.ToUpper(splits[0]))
|
2016-05-13 16:38:06 +00:00
|
|
|
c.NtlmSession = session
|
2015-10-07 18:30:53 +00:00
|
|
|
return session, nil
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
|
2016-05-16 16:57:37 +00:00
|
|
|
func doNTLMRequest(request *http.Request, retry bool) (*http.Response, error) {
|
2015-10-07 18:30:53 +00:00
|
|
|
handReq, err := cloneRequest(request)
|
2015-10-07 20:54:23 +00:00
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2016-05-16 16:57:37 +00:00
|
|
|
res, err := NewHttpClient(config.Config, handReq.Host).Do(handReq)
|
2015-10-07 20:54:23 +00:00
|
|
|
if err != nil && res == nil {
|
2015-10-08 20:18:24 +00:00
|
|
|
return nil, err
|
2015-10-07 18:30:53 +00:00
|
|
|
}
|
2015-10-05 19:26:39 +00:00
|
|
|
|
2015-08-27 20:38:35 +00:00
|
|
|
//If the status is 401 then we need to re-authenticate, otherwise it was successful
|
|
|
|
if res.StatusCode == 401 {
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2016-05-16 16:57:37 +00:00
|
|
|
creds, err := auth.GetCreds(request)
|
2015-10-07 20:54:23 +00:00
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-10-07 18:30:53 +00:00
|
|
|
negotiateReq, err := cloneRequest(request)
|
2015-10-07 20:54:23 +00:00
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-06 18:24:17 +00:00
|
|
|
challengeMessage, err := negotiate(negotiateReq, ntlmNegotiateMessage)
|
2015-10-07 20:54:23 +00:00
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-10-07 18:30:53 +00:00
|
|
|
challengeReq, err := cloneRequest(request)
|
2015-10-07 20:54:23 +00:00
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-10-07 18:30:53 +00:00
|
|
|
res, err := challenge(challengeReq, challengeMessage, creds)
|
2015-10-07 20:54:23 +00:00
|
|
|
if err != nil {
|
2015-10-08 20:18:24 +00:00
|
|
|
return nil, err
|
2015-10-07 18:30:53 +00:00
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-09-01 14:28:43 +00:00
|
|
|
//If the status is 401 then we need to re-authenticate
|
|
|
|
if res.StatusCode == 401 && retry == true {
|
2016-05-16 16:57:37 +00:00
|
|
|
return doNTLMRequest(challengeReq, false)
|
2015-09-01 14:28:43 +00:00
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2016-05-16 16:57:37 +00:00
|
|
|
auth.SaveCredentials(creds, res)
|
2015-10-07 20:54:23 +00:00
|
|
|
|
|
|
|
return res, nil
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
2015-10-08 20:18:24 +00:00
|
|
|
return res, nil
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
|
2015-10-07 20:54:23 +00:00
|
|
|
func negotiate(request *http.Request, message string) ([]byte, error) {
|
2015-08-27 20:38:35 +00:00
|
|
|
request.Header.Add("Authorization", message)
|
2016-05-16 16:57:37 +00:00
|
|
|
res, err := NewHttpClient(config.Config, request.Host).Do(request)
|
2015-10-08 20:18:24 +00:00
|
|
|
|
2015-10-16 15:41:28 +00:00
|
|
|
if res == nil && err != nil {
|
2015-10-08 20:18:24 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-11-06 18:20:46 +00:00
|
|
|
io.Copy(ioutil.Discard, res.Body)
|
|
|
|
res.Body.Close()
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-06 18:20:46 +00:00
|
|
|
ret, err := parseChallengeResponse(res)
|
2015-10-07 20:54:23 +00:00
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-10-16 15:41:28 +00:00
|
|
|
return ret, nil
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
|
2016-05-16 16:57:37 +00:00
|
|
|
func challenge(request *http.Request, challengeBytes []byte, creds auth.Creds) (*http.Response, error) {
|
2015-08-27 20:38:35 +00:00
|
|
|
challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
|
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2016-05-13 16:38:06 +00:00
|
|
|
session, err := ntlmClientSession(config.Config, creds)
|
2015-10-07 18:30:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-10-07 18:30:53 +00:00
|
|
|
session.ProcessChallengeMessage(challenge)
|
|
|
|
authenticate, err := session.GenerateAuthenticateMessage()
|
2015-08-27 20:38:35 +00:00
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-06 18:20:27 +00:00
|
|
|
authMsg := base64.StdEncoding.EncodeToString(authenticate.Bytes())
|
|
|
|
request.Header.Add("Authorization", "NTLM "+authMsg)
|
2016-05-16 16:57:37 +00:00
|
|
|
return NewHttpClient(config.Config, request.Host).Do(request)
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
|
2015-10-07 20:54:23 +00:00
|
|
|
func parseChallengeResponse(response *http.Response) ([]byte, error) {
|
2015-10-07 18:30:53 +00:00
|
|
|
header := response.Header.Get("Www-Authenticate")
|
2015-11-06 18:20:35 +00:00
|
|
|
if len(header) < 6 {
|
|
|
|
return nil, fmt.Errorf("Invalid NTLM challenge response: %q", header)
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-10-07 18:30:53 +00:00
|
|
|
//parse out the "NTLM " at the beginning of the response
|
|
|
|
challenge := header[5:]
|
|
|
|
val, err := base64.StdEncoding.DecodeString(challenge)
|
2015-10-07 20:54:23 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2015-10-07 18:30:53 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return []byte(val), nil
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
|
2015-10-07 18:30:53 +00:00
|
|
|
func cloneRequest(request *http.Request) (*http.Request, error) {
|
2015-11-06 18:49:13 +00:00
|
|
|
cloneReqBody, err := cloneRequestBody(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-06 18:49:13 +00:00
|
|
|
clonedReq, err := http.NewRequest(request.Method, request.URL.String(), cloneReqBody)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-06 18:49:13 +00:00
|
|
|
for k, _ := range request.Header {
|
|
|
|
clonedReq.Header.Add(k, request.Header.Get(k))
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-06 19:31:16 +00:00
|
|
|
clonedReq.TransferEncoding = request.TransferEncoding
|
2015-11-06 18:49:13 +00:00
|
|
|
clonedReq.ContentLength = request.ContentLength
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-06 18:49:13 +00:00
|
|
|
return clonedReq, nil
|
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-06 18:49:13 +00:00
|
|
|
func cloneRequestBody(req *http.Request) (io.ReadCloser, error) {
|
|
|
|
if req.Body == nil {
|
|
|
|
return nil, nil
|
2015-08-31 18:34:17 +00:00
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-06 20:47:45 +00:00
|
|
|
var cb *cloneableBody
|
|
|
|
var err error
|
2015-11-16 18:46:28 +00:00
|
|
|
isCloneableBody := true
|
2015-11-06 20:47:45 +00:00
|
|
|
|
|
|
|
// check to see if the request body is already a cloneableBody
|
|
|
|
body := req.Body
|
|
|
|
if existingCb, ok := body.(*cloneableBody); ok {
|
2015-11-16 18:46:28 +00:00
|
|
|
isCloneableBody = false
|
2015-11-06 20:47:45 +00:00
|
|
|
cb, err = existingCb.CloneBody()
|
|
|
|
} else {
|
|
|
|
cb, err = newCloneableBody(req.Body, 0)
|
|
|
|
}
|
|
|
|
|
2015-11-06 18:49:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-08-28 20:40:41 +00:00
|
|
|
}
|
2015-10-07 20:54:23 +00:00
|
|
|
|
2015-11-16 18:46:28 +00:00
|
|
|
if isCloneableBody {
|
2015-11-06 20:47:45 +00:00
|
|
|
cb2, err := cb.CloneBody()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Body = cb2
|
|
|
|
}
|
|
|
|
|
|
|
|
return cb, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type cloneableBody struct {
|
2015-11-16 18:46:28 +00:00
|
|
|
bytes []byte // in-memory buffer of body
|
|
|
|
file *os.File // file buffer of in-memory overflow
|
|
|
|
reader io.Reader // internal reader for Read()
|
2015-11-06 20:47:45 +00:00
|
|
|
closed bool // tracks whether body is closed
|
|
|
|
dup *dupTracker
|
|
|
|
}
|
|
|
|
|
|
|
|
func newCloneableBody(r io.Reader, limit int64) (*cloneableBody, error) {
|
|
|
|
if limit < 1 {
|
|
|
|
limit = 1048576 // default
|
|
|
|
}
|
|
|
|
|
|
|
|
b := &cloneableBody{}
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
w, err := io.CopyN(buf, r, limit)
|
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-11-16 18:46:28 +00:00
|
|
|
b.bytes = buf.Bytes()
|
|
|
|
byReader := bytes.NewBuffer(b.bytes)
|
2015-11-06 20:47:45 +00:00
|
|
|
|
|
|
|
if w >= limit {
|
|
|
|
tmp, err := ioutil.TempFile("", "git-lfs-clone-reader")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = io.Copy(tmp, r)
|
|
|
|
tmp.Close()
|
|
|
|
if err != nil {
|
|
|
|
os.RemoveAll(tmp.Name())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := os.Open(tmp.Name())
|
|
|
|
if err != nil {
|
|
|
|
os.RemoveAll(tmp.Name())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
dups := int32(0)
|
|
|
|
b.dup = &dupTracker{name: f.Name(), dups: &dups}
|
2015-11-16 18:46:28 +00:00
|
|
|
b.file = f
|
|
|
|
b.reader = io.MultiReader(byReader, b.file)
|
2015-11-06 20:47:45 +00:00
|
|
|
} else {
|
|
|
|
// no file, so set the reader to just the in-memory buffer
|
2015-11-16 18:46:28 +00:00
|
|
|
b.reader = byReader
|
2015-11-06 20:47:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *cloneableBody) Read(p []byte) (int, error) {
|
|
|
|
if b.closed {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
2015-11-16 18:46:28 +00:00
|
|
|
return b.reader.Read(p)
|
2015-11-06 20:47:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *cloneableBody) Close() error {
|
|
|
|
if !b.closed {
|
|
|
|
b.closed = true
|
2015-11-16 18:46:28 +00:00
|
|
|
if b.file == nil {
|
2015-11-06 20:47:45 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-16 18:46:28 +00:00
|
|
|
b.file.Close()
|
2015-11-06 20:47:45 +00:00
|
|
|
b.dup.Rm()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *cloneableBody) CloneBody() (*cloneableBody, error) {
|
|
|
|
if b.closed {
|
|
|
|
return &cloneableBody{closed: true}, nil
|
|
|
|
}
|
|
|
|
|
2015-11-16 18:46:28 +00:00
|
|
|
b2 := &cloneableBody{bytes: b.bytes}
|
2015-11-06 20:47:45 +00:00
|
|
|
|
2015-11-16 18:46:28 +00:00
|
|
|
if b.file == nil {
|
|
|
|
b2.reader = bytes.NewBuffer(b.bytes)
|
2015-11-06 20:47:45 +00:00
|
|
|
} else {
|
2015-11-16 18:46:28 +00:00
|
|
|
f, err := os.Open(b.file.Name())
|
2015-11-06 20:47:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-11-16 18:46:28 +00:00
|
|
|
b2.file = f
|
|
|
|
b2.reader = io.MultiReader(bytes.NewBuffer(b.bytes), b2.file)
|
2015-11-06 20:47:45 +00:00
|
|
|
b2.dup = b.dup
|
|
|
|
b.dup.Add()
|
|
|
|
}
|
|
|
|
|
|
|
|
return b2, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type dupTracker struct {
|
|
|
|
name string
|
|
|
|
dups *int32
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *dupTracker) Add() {
|
|
|
|
atomic.AddInt32(t.dups, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *dupTracker) Rm() {
|
|
|
|
newval := atomic.AddInt32(t.dups, -1)
|
|
|
|
if newval < 0 {
|
|
|
|
os.RemoveAll(t.name)
|
|
|
|
}
|
2015-08-28 20:40:41 +00:00
|
|
|
}
|
|
|
|
|
2015-11-06 18:24:17 +00:00
|
|
|
const ntlmNegotiateMessage = "NTLM TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB"
|