forked from elazarl/goproxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
https.go
118 lines (108 loc) · 3.26 KB
/
https.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package goproxy
import (
"bufio"
"crypto/tls"
"net"
"net/http"
"net/url"
"sync/atomic"
)
type ConnectActionLiteral int
const (
ConnectAccept = iota
ConnectReject
ConnectMitm
)
var (
OkConnect = &ConnectAction{Action: ConnectAccept}
MitmConnect = &ConnectAction{Action: ConnectMitm}
RejectConnect = &ConnectAction{Action: ConnectReject}
)
type ConnectAction struct {
Action ConnectActionLiteral
TlsConfig *tls.Config
}
func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) {
ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
hij, ok := w.(http.Hijacker)
if !ok {
panic("httpserver does not support hijacking")
}
proxyClient, _, e := hij.Hijack()
if e != nil {
panic("Cannot hijack connection " + e.Error())
}
ctx.Logf("Running %d CONNECT handlers", len(proxy.httpsHandlers))
todo, host := OkConnect, r.URL.Host
ctx.Req = r
for _, h := range proxy.httpsHandlers {
todo, host = h.HandleConnect(host, ctx)
ctx.Logf("handler: %v %s", todo, host)
}
switch todo.Action {
case ConnectAccept:
if !hasPort.MatchString(host) {
host += ":80"
}
targetSiteCon, e := net.Dial("tcp", host)
if e != nil {
// trying to mimic the behaviour of the offending website
// don't answer at all
return
}
ctx.Logf("Accepting CONNECT to %s", host)
proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
go proxy.copyAndClose(targetSiteCon, proxyClient)
go proxy.copyAndClose(proxyClient, targetSiteCon)
case ConnectMitm:
proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
ctx.Logf("Assuming CONNECT is TLS, mitm proxying it")
// this goes in a separate goroutine, so that the net/http server won't think we're
// still handling the request even after hijacking the connection. Those HTTP CONNECT
// request can take forever, and the server will be stuck when "closed".
// TODO: Allow Server.Close() mechanism to shut down this connection as nicely as possible
tlsConfig := todo.TlsConfig
if tlsConfig == nil {
tlsConfig = defaultTlsConfig
}
go func() {
//TODO: cache connections to the remote website
rawClientTls := tls.Server(proxyClient, tlsConfig)
if err := rawClientTls.Handshake(); err != nil {
ctx.Warnf("Cannot handshake client %v %v", r.Host, err)
}
defer rawClientTls.Close()
clientTlsReader := bufio.NewReader(rawClientTls)
for !isEof(clientTlsReader) {
req, err := http.ReadRequest(clientTlsReader)
if err != nil {
ctx.Warnf("Cannot read TLS request from mitm'd client %v %v", r.Host, err)
return
}
ctx.Logf("req %v", r.Host)
req, resp := proxy.filterRequest(req, ctx)
if resp == nil {
req.URL, err = url.Parse("https://" + r.Host + req.URL.Path)
if err != nil {
ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path)
return
}
resp, err = proxy.tr.RoundTrip(req)
if err != nil {
ctx.Warnf("Cannot read TLS response from mitm'd server %v", err)
return
}
ctx.Logf("resp %v", resp.Status)
}
resp = proxy.filterResponse(resp, ctx)
if err := resp.Write(rawClientTls); err != nil {
ctx.Warnf("Cannot write TLS response from mitm'd client %v", err)
return
}
}
ctx.Logf("Exiting on EOF")
}()
case ConnectReject:
proxyClient.Close()
}
}