2015-08-27 20:38:35 +00:00
|
|
|
package lfs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
//"bufio"
|
|
|
|
//"net"
|
|
|
|
"net/http"
|
|
|
|
//"encoding/xml"
|
|
|
|
"encoding/base64"
|
|
|
|
"bytes"
|
|
|
|
"io/ioutil"
|
2015-08-28 20:40:41 +00:00
|
|
|
"fmt"
|
2015-08-27 20:38:35 +00:00
|
|
|
//"os"
|
|
|
|
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
|
|
|
|
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
|
|
|
|
)
|
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
|
|
|
|
type myReader struct {
|
|
|
|
*bytes.Buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
// So that it implements the io.ReadCloser interface
|
|
|
|
func (m myReader) Close() error { return nil }
|
|
|
|
|
2015-08-27 20:38:35 +00:00
|
|
|
func (c *Configuration) NTLMSession() ntlm.ClientSession {
|
|
|
|
|
|
|
|
if c.ntlmSession != nil {
|
|
|
|
return c.ntlmSession
|
|
|
|
}
|
|
|
|
|
|
|
|
var session, _ = ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
|
2015-08-31 18:34:17 +00:00
|
|
|
session.SetUserInfo("user","pass","domain")
|
2015-08-27 20:38:35 +00:00
|
|
|
|
|
|
|
c.ntlmSession = session
|
|
|
|
|
|
|
|
return session
|
|
|
|
}
|
|
|
|
|
2015-09-01 14:28:43 +00:00
|
|
|
func DoNTLMRequest(request *http.Request, retry bool) (*http.Response, error) {
|
|
|
|
|
2015-08-27 20:38:35 +00:00
|
|
|
if !Config.NTLM() {
|
2015-08-28 20:40:41 +00:00
|
|
|
return nil, Error(fmt.Errorf("NTLM is not enabled"))
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
handReq := cloneRequest(request)
|
|
|
|
res, nil := InitHandShake(handReq)
|
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-08-28 20:40:41 +00:00
|
|
|
negotiateReq := cloneRequest(request)
|
|
|
|
challengeMessage := Negotiate(negotiateReq, getNegotiateMessage())
|
2015-08-27 20:38:35 +00:00
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
challengeReq := cloneRequest(request)
|
|
|
|
res, nil := Challenge(challengeReq, challengeMessage)
|
|
|
|
|
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 {
|
|
|
|
return DoNTLMRequest(challengeReq, false)
|
|
|
|
}
|
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
return res, nil
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
return res, nil
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
func InitHandShake(request *http.Request) (*http.Response, error){
|
2015-08-27 20:38:35 +00:00
|
|
|
|
|
|
|
var response, err = Config.HttpClient().Do(request)
|
2015-08-31 18:34:17 +00:00
|
|
|
|
2015-08-27 20:38:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func Negotiate(request *http.Request, message string) []byte{
|
|
|
|
|
|
|
|
request.Header.Add("Authorization", message)
|
|
|
|
var response, err = Config.HttpClient().Do(request)
|
|
|
|
|
|
|
|
if err != nil{
|
2015-08-28 20:40:41 +00:00
|
|
|
tracerx.Printf("ntlm: Negotiate Error %s", err.Error())
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret := ParseChallengeMessage(response)
|
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
//Always close negotiate to keep the connection alive
|
|
|
|
//We never return the response from negotiate so we
|
|
|
|
//can't trust decodeApiResponse to decode it
|
2015-08-27 20:38:35 +00:00
|
|
|
io.Copy(ioutil.Discard, response.Body)
|
|
|
|
response.Body.Close()
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
func Challenge(request *http.Request, challengeBytes []byte) (*http.Response, error){
|
2015-08-27 20:38:35 +00:00
|
|
|
|
|
|
|
challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
Config.NTLMSession().ProcessChallengeMessage(challenge)
|
|
|
|
authenticate, err := Config.NTLMSession().GenerateAuthenticateMessage()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error(err)
|
|
|
|
}
|
|
|
|
|
2015-09-01 14:28:43 +00:00
|
|
|
authenticateMessage := ConcatS("NTLM ", base64.StdEncoding.EncodeToString(authenticate.Bytes()))
|
2015-08-27 20:38:35 +00:00
|
|
|
|
|
|
|
request.Header.Add("Authorization", authenticateMessage)
|
|
|
|
response, err := Config.HttpClient().Do(request)
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseChallengeMessage(response *http.Response) []byte{
|
|
|
|
|
|
|
|
if headers, ok := response.Header["Www-Authenticate"]; ok{
|
|
|
|
|
2015-09-01 14:28:43 +00:00
|
|
|
//parse out the "NTLM " at the beginning of the response
|
2015-08-27 20:38:35 +00:00
|
|
|
challenge := headers[0][5:]
|
|
|
|
val, err := base64.StdEncoding.DecodeString(challenge)
|
|
|
|
|
|
|
|
if err != nil{
|
2015-09-01 14:28:43 +00:00
|
|
|
panic(err.Error())
|
2015-08-27 20:38:35 +00:00
|
|
|
}
|
|
|
|
return []byte(val)
|
|
|
|
}
|
|
|
|
|
|
|
|
panic("www-Authenticate header is not present")
|
|
|
|
}
|
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
func cloneRequest(request *http.Request) *http.Request {
|
|
|
|
|
2015-08-31 18:34:17 +00:00
|
|
|
var rdr1, rdr2 myReader
|
|
|
|
var clonedReq *http.Request
|
|
|
|
|
|
|
|
if request.Body != nil {
|
2015-09-01 14:28:43 +00:00
|
|
|
//If we have a body (POST/PUT etc.)
|
2015-08-31 18:34:17 +00:00
|
|
|
//We need to do some magic to copy the request without closing the body stream
|
|
|
|
|
|
|
|
buf, _ := ioutil.ReadAll(request.Body)
|
|
|
|
rdr1 = myReader{bytes.NewBuffer(buf)}
|
|
|
|
rdr2 = myReader{bytes.NewBuffer(buf)}
|
|
|
|
request.Body = rdr2 // OK since rdr2 implements the io.ReadCloser interface
|
|
|
|
clonedReq, _ = http.NewRequest(request.Method, request.URL.String(), rdr1)
|
|
|
|
}else{
|
|
|
|
clonedReq, _ = http.NewRequest(request.Method, request.URL.String(), nil)
|
|
|
|
}
|
2015-08-28 20:40:41 +00:00
|
|
|
|
|
|
|
for k, v := range request.Header {
|
|
|
|
clonedReq.Header.Add(k,v[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
clonedReq.ContentLength = request.ContentLength
|
|
|
|
|
|
|
|
return clonedReq
|
|
|
|
}
|
|
|
|
|
2015-08-27 20:38:35 +00:00
|
|
|
//Get Type 1 message
|
|
|
|
func getNegotiateMessage() string{
|
|
|
|
|
|
|
|
//var negotiate, _ = session.GenerateNegotiateMessage()
|
|
|
|
//return negotiate.Bytes
|
|
|
|
|
|
|
|
return "NTLM TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB"
|
|
|
|
}
|
|
|
|
|
2015-08-28 20:40:41 +00:00
|
|
|
func ConcatS(ar ...string) string {
|
|
|
|
|
|
|
|
var buffer bytes.Buffer
|
|
|
|
|
|
|
|
for _, s := range ar{
|
|
|
|
buffer.WriteString(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
return buffer.String()
|
|
|
|
}
|
|
|
|
|
2015-08-27 20:38:35 +00:00
|
|
|
func Concat(ar ...[]byte) []byte {
|
|
|
|
return bytes.Join(ar, nil)
|
|
|
|
}
|