├── .gitignore
├── LICENSE
├── auth.go
├── client.go
├── dial_common.go
├── dial_linux.go
├── encrypt.go
├── encrypt_test.go
├── go.mod
├── go.sum
├── http.go
├── main.go
├── mitm
├── cache.go
├── cert.go
├── listener.go
├── mitm.go
└── websocket.go
├── proxy.go
├── regexp.go
└── release
├── PKGBUILD
├── config.json
└── jlu-http-proxy.service
/.gitignore:
--------------------------------------------------------------------------------
1 | # idea
2 | .idea
3 |
4 | # Personal config
5 | config.json
6 | !release/config.json
7 |
8 | # release file
9 | jlu-http-proxy
10 |
11 | # build
12 | build
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Yesterday17
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/auth.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io/ioutil"
5 | "regexp"
6 | "strings"
7 | )
8 |
9 | func (p *Proxy) Login() error {
10 | // Get Cookies first
11 | resp, err := LoginClient.Get("https://vpns.jlu.edu.cn/login")
12 | if err != nil {
13 | return err
14 | }
15 | p.Cookies = resp.Header.Get("Set-Cookie")
16 | p.Cookies = strings.Split(p.Cookies, ";")[0]
17 |
18 | // Auth
19 | resp, err = p.SimpleFetchLogin("POST", "/do-login?local_login=true", map[string][]string{
20 | "auth_type": {"local"},
21 | "username": {p.Username},
22 | "password": {p.Password},
23 | "sms_code": {""},
24 | "remember_cookie": {"on"},
25 | })
26 | if err != nil {
27 | return err
28 | }
29 |
30 | body, _ := ioutil.ReadAll(resp.Body)
31 | r := regexp.MustCompile("logoutOtherToken = '([0-9a-f]+)'")
32 | if r.Match(body) {
33 | // Split current active token from html
34 | p.Cookies = "wengine_vpn_ticket=" + string(r.FindSubmatch(body)[1])
35 | }
36 |
37 | // TODO: Check whether logon successfully
38 | return nil
39 | }
40 |
--------------------------------------------------------------------------------
/client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "golang.org/x/net/http2"
6 | "net/http"
7 | "net/url"
8 | "strings"
9 | "time"
10 | )
11 |
12 | var (
13 | DefaultClient *http.Client
14 | LoginClient *http.Client
15 | tlsConfig = &tls.Config{InsecureSkipVerify: true}
16 | )
17 |
18 | func InitClient() {
19 | DefaultClient = &http.Client{
20 | Transport: Transport(),
21 | Timeout: time.Second * 60,
22 | CheckRedirect: func(req *http.Request, via []*http.Request) error {
23 | return http.ErrUseLastResponse
24 | },
25 | }
26 | LoginClient = &http.Client{
27 | Transport: Transport(),
28 | Timeout: time.Second * 60,
29 | }
30 | }
31 |
32 | func Transport() http.RoundTripper {
33 | var proxyUrl *url.URL
34 | var err error
35 | if proxy.Proxy != "" {
36 | proxyUrl, err = url.Parse(DefaultProxy.Proxy)
37 | if err != nil {
38 | panic(err)
39 | }
40 | }
41 |
42 | if proxy.Http2 {
43 | return &http2.Transport{
44 | TLSClientConfig: tlsConfig,
45 | DialTLS: tlsDialOptWithCfg,
46 | }
47 | } else {
48 | return &http.Transport{
49 | TLSClientConfig: tlsConfig,
50 | DialTLS: tlsDialOptWithoutCfg,
51 | Proxy: http.ProxyURL(proxyUrl),
52 | }
53 | }
54 | }
55 |
56 | func (p *Proxy) SimpleFetchClient(method, path string, header url.Values, client *http.Client) (*http.Response, error) {
57 | req, err := http.NewRequest(method, "https://vpns.jlu.edu.cn"+path, strings.NewReader(header.Encode()))
58 | if err != nil {
59 | return nil, err
60 | }
61 | if header != nil {
62 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
63 | }
64 | req.Header.Set("Cookie", p.Cookies)
65 | return client.Do(req)
66 | }
67 |
68 | func (p *Proxy) SimpleFetch(method, path string, header url.Values) (*http.Response, error) {
69 | return p.SimpleFetchClient(method, path, header, DefaultClient)
70 | }
71 |
72 | func (p *Proxy) SimpleFetchLogin(method, path string, header url.Values) (*http.Response, error) {
73 | return p.SimpleFetchClient(method, path, header, LoginClient)
74 | }
75 |
--------------------------------------------------------------------------------
/dial_common.go:
--------------------------------------------------------------------------------
1 | // +build !linux
2 |
3 | package main
4 |
5 | import (
6 | "crypto/tls"
7 | "net"
8 | )
9 |
10 | func tlsDialOptWithoutCfg(network, addr string) (net.Conn, error) {
11 | return tlsDialOptWithCfg(network, addr, tlsConfig)
12 | }
13 |
14 | func tlsDialOptWithCfg(network, addr string, cfg *tls.Config) (net.Conn, error) {
15 | return tls.Dial(network, addr, cfg)
16 | }
17 |
--------------------------------------------------------------------------------
/dial_linux.go:
--------------------------------------------------------------------------------
1 | // +build linux
2 |
3 | package main
4 |
5 | import (
6 | "crypto/tls"
7 | "log"
8 | "net"
9 | "syscall"
10 | )
11 |
12 | func tlsDialOptWithoutCfg(network, addr string) (net.Conn, error) {
13 | return tlsDialOptWithCfg(network, addr, tlsConfig)
14 | }
15 |
16 | func tlsDialOptWithCfg(network, addr string, cfg *tls.Config) (net.Conn, error) {
17 | if DefaultProxy.Mark == 0 {
18 | return tls.Dial(network, addr, cfg)
19 | }
20 |
21 | return tls.DialWithDialer(&net.Dialer{
22 | Control: func(network, address string, c syscall.RawConn) error {
23 | return c.Control(func(fd uintptr) {
24 | err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, DefaultProxy.Mark)
25 | if err != nil {
26 | if err.(syscall.Errno) == 1 {
27 | Panic("Operation not permitted, make sure you're running in root privilege!")
28 | } else {
29 | log.Printf("control: %s", err)
30 | }
31 | return
32 | }
33 | })
34 | },
35 | }, network, addr, cfg)
36 | }
37 |
--------------------------------------------------------------------------------
/encrypt.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/aes"
5 | "crypto/cipher"
6 | "encoding/hex"
7 | )
8 |
9 | const (
10 | AESKey = "wrdvpnisthebest!"
11 | AESIV = "wrdvpnisthebest!"
12 | )
13 |
14 | func Encrypt(text string) string {
15 | result := make([]byte, len(text))
16 |
17 | block, err := aes.NewCipher([]byte(AESKey))
18 | if err != nil {
19 | panic(err)
20 | }
21 |
22 | enc := cipher.NewCFBEncrypter(block, []byte(AESIV))
23 | enc.XORKeyStream(result, []byte(text))
24 | return hex.EncodeToString([]byte(AESIV)) + hex.EncodeToString(result)
25 | }
26 |
27 | func Decrypt(text string) string {
28 | iv, _ := hex.DecodeString(text[0:32])
29 | textB, _ := hex.DecodeString(text[32:])
30 | result := make([]byte, len(textB))
31 |
32 | block, err := aes.NewCipher([]byte(AESKey))
33 | if err != nil {
34 | panic(err)
35 | }
36 |
37 | dec := cipher.NewCFBDecrypter(block, iv)
38 | dec.XORKeyStream(result, textB)
39 | return string(result)
40 | }
41 |
--------------------------------------------------------------------------------
/encrypt_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestEncrypt(t *testing.T) {
8 | enc := Encrypt("uims.jlu.edu.cn")
9 | if enc != "77726476706e69737468656265737421e5fe4c8f693a6445300d8db9d6562d" {
10 | t.Errorf("Wrong encrypt result: %s\n", enc)
11 | }
12 | }
13 |
14 | func TestDecrypt(t *testing.T) {
15 | dec := Decrypt("77726476706e69737468656265737421e5fe4c8f693a6445300d8db9d6562d")
16 | if dec != "uims.jlu.edu.cn" {
17 | t.Errorf("Wrong decrypt result: %s\n", dec)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/Yesterday17/jlu-http-proxy
2 |
3 | go 1.13
4 |
5 | require golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa
6 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
2 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
3 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
4 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
5 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
6 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
7 |
--------------------------------------------------------------------------------
/http.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "encoding/base64"
7 | "html"
8 | "io"
9 | "io/ioutil"
10 | "log"
11 | "net/http"
12 | "regexp"
13 | "strings"
14 | )
15 |
16 | func (p *Proxy) HandleRequest(w http.ResponseWriter, r *http.Request) {
17 | var protocol = "https"
18 | var host = "vpns.jlu.edu.cn"
19 | var path = ""
20 |
21 | r.URL.Host = r.Host
22 |
23 | if r.URL.Host != "vpns.jlu.edu.cn" {
24 | if strings.Contains(r.URL.Path, "wengine-vpn") {
25 | // wdnmd
26 | w.WriteHeader(200)
27 | return
28 | }
29 | } else if strings.HasPrefix(r.URL.Path, "/jlu-http-proxy") {
30 | w.Header().Set("Access-Control-Allow-Origin", "*")
31 | path = r.URL.Path[15:]
32 | switch path {
33 | case "/redirect":
34 | link, err := base64.StdEncoding.DecodeString(r.URL.RawQuery[4:])
35 | if err != nil {
36 | w.WriteHeader(403)
37 | return
38 | }
39 |
40 | w.Header().Set("Location", string(link))
41 | w.WriteHeader(302)
42 | }
43 | return
44 | }
45 |
46 | var toRequest string
47 | if PathMatchRegex.MatchString(r.URL.Path) {
48 | // Keep vpns
49 | if r.URL.Host == "vpns.jlu.edu.cn" {
50 | // Save information before encode
51 | path = r.URL.Path
52 | } else {
53 | ret := PathMatchRegex.FindStringSubmatch(r.URL.Path)
54 | protocol = ret[1]
55 | host = ret[2]
56 | path = ret[3]
57 |
58 | w.Header().Set("Location", protocol+"://"+Decrypt(host)+path)
59 | w.WriteHeader(302)
60 | return
61 | // r.URL.Host = "vpns.jlu.edu.cn"
62 | // r.URL.Path = "/" + protocol + "-" + r.URL.Port() + "/" + host + path
63 | }
64 | } else if r.URL.Host != "vpns.jlu.edu.cn" {
65 | protocol = r.URL.Scheme
66 | host = r.URL.Hostname()
67 |
68 | // Without VPN
69 | r.URL.Path = "/" + protocol + "-" + r.URL.Port() + "/" + Encrypt(host) + r.URL.Path
70 | r.URL.Scheme = protocol
71 | r.URL.Host = "vpns.jlu.edu.cn"
72 | }
73 | r.URL.Scheme = "https"
74 |
75 | // Construct new request
76 | toRequest = r.URL.String()
77 | req, err := http.NewRequest(r.Method, toRequest, r.Body)
78 | if err != nil {
79 | log.Println(err)
80 | return
81 | }
82 |
83 | // Headers
84 | req.Header = r.Header
85 | req.Header.Del("Proxy-Connection")
86 | req.Header.Set("Referer", toRequest)
87 |
88 | // Set headers
89 | cookies := req.Header.Get("Cookie")
90 | // if cookies != "" {
91 | // _, err = p.SimpleFetch("POST", "/wengine-vpn/cookie?method=set&host="+host+"&scheme="+protocol+"&path="+path+"&ck_data="+url.QueryEscape(cookies), nil)
92 | // if err != nil {
93 | // log.Println(err)
94 | // }
95 | // }
96 | if cookies == "" {
97 | cookies = p.Cookies
98 | } else {
99 | reg := regexp.MustCompile("wengine_vpn_ticket=[0-9a-f]+")
100 | if reg.MatchString(cookies) {
101 | cookies = reg.ReplaceAllString(cookies, p.Cookies)
102 | } else {
103 | cookies += "; " + p.Cookies
104 | }
105 | }
106 | req.Header.Set("Cookie", cookies)
107 |
108 | // Do request
109 | if proxy.Http2 {
110 | req.Proto = "HTTP/2"
111 | } else {
112 | req.Proto = "HTTP/1.1"
113 | }
114 | resp, err := DefaultClient.Do(req)
115 | if err != nil {
116 | log.Println(err)
117 | w.WriteHeader(403)
118 | _, _ = w.Write([]byte(err.Error()))
119 | return
120 | }
121 |
122 | // Handle redirect
123 | if resp.StatusCode == 301 || resp.StatusCode == 302 {
124 | location := resp.Header.Get("Location")
125 | if location == "/login" {
126 | // disable and replace vpn login redirect
127 | // reauth and 302 to self
128 | _ = p.Login()
129 | w.Header().Set("Location", "http://vpns.jlu.edu.cn/jlu-http-proxy/redirect?url="+base64.StdEncoding.EncodeToString([]byte(toRequest)))
130 | w.WriteHeader(302)
131 | } else {
132 | location = RedirectLink.ReplaceAllStringFunc(location, func(s string) string {
133 | ret := RedirectLink.FindStringSubmatch(s)
134 | return ret[1] + "://" + Decrypt(ret[2]) + ret[3]
135 | })
136 | w.Header().Set("Location", location)
137 | w.WriteHeader(resp.StatusCode)
138 | }
139 | return
140 | }
141 |
142 | for k, v := range resp.Header {
143 | if k == "Content-Length" {
144 | continue
145 | }
146 | for _, vv := range v {
147 | w.Header().Add(k, vv)
148 | }
149 | }
150 |
151 | if r.Header.Get("Origin") != "" {
152 | w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
153 | } else {
154 | w.Header().Set("Access-Control-Allow-Origin", "*")
155 | }
156 |
157 | w.Header().Set("Access-Control-Allow-Credentials", "true")
158 |
159 | if r.Header.Get("Access-Control-Request-Headers") != "" {
160 | w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
161 | }
162 |
163 | for _, c := range resp.Cookies() {
164 | w.Header().Add("Set-Cookie", c.Raw)
165 | }
166 |
167 | w.WriteHeader(resp.StatusCode)
168 |
169 | body := resp.Body
170 | // Gzip
171 | if resp.Header.Get("Content-Encoding") == "gzip" {
172 | body, err = gzip.NewReader(resp.Body)
173 | if err != nil {
174 | log.Println(err)
175 | return
176 | }
177 | }
178 | defer body.Close()
179 |
180 | result, err := ioutil.ReadAll(body)
181 | if err != nil && err != io.EOF {
182 | log.Println(err)
183 | }
184 |
185 | // Restore vpns toRequest to original
186 | result = VPNsLinkMatch.ReplaceAllFunc(result, func(bytes []byte) []byte {
187 | var postfix string
188 | ret := VPNsLinkMatch.FindSubmatch(bytes)
189 | if len(ret) == 4 && string(ret[3]) != "" {
190 | postfix = "/" + string(ret[3])
191 | }
192 |
193 | link := string(ret[1]) + "://" + Decrypt(string(ret[2])) + postfix
194 | return []byte(link)
195 | })
196 |
197 | // Fix URL Escape in links
198 | result = LinkUnescape.ReplaceAllFunc(result, func(bytes []byte) []byte {
199 | ret := LinkUnescape.FindSubmatch(bytes)
200 | return []byte(string(ret[1]) + "=\"" + html.UnescapeString(string(ret[2])) + "\"")
201 | })
202 |
203 | // Un VPN ify
204 | result = VPNEvalPrefix.ReplaceAll(result, []byte{})
205 | result = VPNEvalPostfix.ReplaceAll(result, []byte{})
206 | result = VPNRewritePrefix.ReplaceAll(result, []byte{})
207 | result = VPNRewritePostfix.ReplaceAll(result, []byte{})
208 | result = VPNInjectPrefix.ReplaceAll(result, []byte{})
209 | result = VPNInjectPostfix.ReplaceAll(result, []byte{})
210 | result = VPNParamRemoveFirst.ReplaceAll(result, []byte{})
211 | result = VPNParamRemoveOther.ReplaceAll(result, []byte{})
212 |
213 | // Remove added tags
214 | result = VPNScriptInfo.ReplaceAll(result, []byte{})
215 | result = VPNScriptWEngine.ReplaceAll(result, []byte(""))
216 |
217 | // Trim
218 | result = bytes.Trim(result, "\r\n")
219 |
220 | // Gzip
221 | if resp.Header.Get("Content-Encoding") == "gzip" {
222 | var buf bytes.Buffer
223 | wr := gzip.NewWriter(&buf)
224 | _, _ = wr.Write(result)
225 | _ = wr.Close()
226 | result = buf.Bytes()
227 | }
228 |
229 | if _, err = w.Write(result); err != nil {
230 | log.Println(err)
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "fmt"
7 | "github.com/Yesterday17/jlu-http-proxy/mitm"
8 | "io/ioutil"
9 | "log"
10 | "net/http"
11 | "os"
12 | "path"
13 | )
14 |
15 | var (
16 | hostname, _ = os.Hostname()
17 | dir, _ = os.Getwd()
18 | proxy *Proxy
19 | )
20 |
21 | func main() {
22 | cfgPath := "config.json"
23 | if len(os.Args) > 1 {
24 | cfgPath = os.Args[1]
25 | }
26 | proxy = LoadConfig(cfgPath)
27 | InitClient()
28 |
29 | // Directory
30 | if proxy.Directory != "" {
31 | dir = proxy.Directory
32 | }
33 | ca := loadCA(dir)
34 |
35 | // Login
36 | if err := proxy.Login(); err != nil {
37 | panic(err)
38 | }
39 |
40 | // Proxy
41 | p := &mitm.Proxy{
42 | Handle: func(https bool) func(w http.ResponseWriter, r *http.Request) {
43 | return func(w http.ResponseWriter, r *http.Request) {
44 | r.URL.Scheme = "http"
45 | if https {
46 | r.URL.Scheme += "s"
47 | }
48 | proxy.HandleRequest(w, r)
49 | }
50 | },
51 | CA: &ca,
52 | TLSServerConfig: &tls.Config{
53 | MinVersion: tls.VersionTLS12,
54 | InsecureSkipVerify: true,
55 | },
56 | TLSClientConfig: &tls.Config{
57 | // VPNS is insecure
58 | InsecureSkipVerify: true,
59 | },
60 | }
61 |
62 | fmt.Println("Start server on port " + proxy.Port)
63 |
64 | // Listen
65 | if err := http.ListenAndServe(":"+proxy.Port, p); err != nil {
66 | panic(err)
67 | }
68 | }
69 |
70 | func loadCA(dir string) tls.Certificate {
71 | cert, err := tls.LoadX509KeyPair(path.Join(dir, "ca-cert.pem"), path.Join(dir, "ca-key.pem"))
72 | if err != nil {
73 | if os.IsNotExist(err) {
74 | cert, err = genCA(dir)
75 | if err != nil {
76 | Panic(err)
77 | }
78 | } else {
79 | Panic(err)
80 | }
81 | }
82 | cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
83 | if err != nil {
84 | Panic(err)
85 | }
86 | return cert
87 | }
88 |
89 | func genCA(dir string) (cert tls.Certificate, err error) {
90 | err = os.MkdirAll(dir, 0700)
91 | if err != nil {
92 | return
93 | }
94 | certPEM, keyPEM, err := mitm.GenerateCA(hostname)
95 | if err != nil {
96 | return
97 | }
98 | cert, _ = tls.X509KeyPair(certPEM, keyPEM)
99 | err = ioutil.WriteFile(path.Join(dir, "ca-cert.pem"), certPEM, 0400)
100 | if err == nil {
101 | err = ioutil.WriteFile(path.Join(dir, "ca-key.pem"), keyPEM, 0400)
102 | }
103 | return cert, err
104 | }
105 |
106 | func Panic(v ...interface{}) {
107 | log.Print(v...)
108 | os.Exit(23)
109 | }
110 |
--------------------------------------------------------------------------------
/mitm/cache.go:
--------------------------------------------------------------------------------
1 | // Code edited from https://github.com/rainforestapp/mitm
2 | /**
3 | Copyright (c) 2015 Keith Rarick
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | */
23 |
24 | package mitm
25 |
26 | import (
27 | "crypto/tls"
28 | "sync"
29 | "time"
30 | )
31 |
32 | // Certificates are cached locally to avoid unnecessary regeneration
33 | const certCacheMaxSize = 1000
34 |
35 | var (
36 | certCache = make(map[*tls.Certificate]map[string]*tls.Certificate)
37 | certCacheMutex sync.RWMutex
38 | )
39 |
40 | func getCert(ca *tls.Certificate, host string) (*tls.Certificate, error) {
41 | if c := getCachedCert(ca, host); c != nil {
42 | return c, nil
43 | }
44 | cert, err := GenerateCert(ca, host)
45 | if err != nil {
46 | return nil, err
47 | }
48 | cacheCert(ca, host, cert)
49 | return cert, nil
50 | }
51 |
52 | func getCachedCert(ca *tls.Certificate, host string) *tls.Certificate {
53 | certCacheMutex.RLock()
54 | defer certCacheMutex.RUnlock()
55 |
56 | if certCache[ca] == nil {
57 | return nil
58 | }
59 | cert := certCache[ca][host]
60 | if cert == nil || cert.Leaf.NotAfter.Before(time.Now()) {
61 | return nil
62 | }
63 | return cert
64 | }
65 |
66 | func cacheCert(ca *tls.Certificate, host string, cert *tls.Certificate) {
67 | certCacheMutex.Lock()
68 | defer certCacheMutex.Unlock()
69 |
70 | if certCache[ca] == nil || len(certCache[ca]) > certCacheMaxSize {
71 | certCache[ca] = make(map[string]*tls.Certificate)
72 | }
73 | certCache[ca][host] = cert
74 | }
75 |
--------------------------------------------------------------------------------
/mitm/cert.go:
--------------------------------------------------------------------------------
1 | // Code edited from https://github.com/rainforestapp/mitm
2 | /**
3 | Copyright (c) 2015 Keith Rarick
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | */
23 |
24 | package mitm
25 |
26 | import (
27 | "crypto/rand"
28 | "crypto/rsa"
29 | "crypto/tls"
30 | "crypto/x509"
31 | "crypto/x509/pkix"
32 | "encoding/pem"
33 | "errors"
34 | "fmt"
35 | "math/big"
36 | "net"
37 | "time"
38 | )
39 |
40 | const (
41 | caMaxAge = 5 * 365 * 24 * time.Hour
42 | leafMaxAge = 24 * time.Hour
43 | caUsage = x509.KeyUsageDigitalSignature |
44 | x509.KeyUsageContentCommitment |
45 | x509.KeyUsageKeyEncipherment |
46 | x509.KeyUsageDataEncipherment |
47 | x509.KeyUsageKeyAgreement |
48 | x509.KeyUsageCertSign |
49 | x509.KeyUsageCRLSign
50 | leafUsage = x509.KeyUsageDigitalSignature
51 | )
52 |
53 | // GenerateCA generates a CA cert and key pair.
54 | func GenerateCA(name string) (certPEM, keyPEM []byte, err error) {
55 | now := time.Now().UTC()
56 | ca := &x509.Certificate{
57 | SerialNumber: big.NewInt(1),
58 | Subject: pkix.Name{CommonName: name},
59 | NotBefore: now,
60 | NotAfter: now.Add(caMaxAge),
61 | KeyUsage: caUsage,
62 | BasicConstraintsValid: true,
63 | IsCA: true,
64 | MaxPathLen: 2,
65 | SignatureAlgorithm: x509.SHA256WithRSA,
66 | }
67 |
68 | // Get KeyPair
69 | key, err := genKeyPair()
70 | if err != nil {
71 | return
72 | }
73 |
74 | certBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, key.Public(), key)
75 | if err != nil {
76 | return
77 | }
78 |
79 | certPEM = pem.EncodeToMemory(&pem.Block{
80 | Type: "CERTIFICATE",
81 | Bytes: certBytes,
82 | })
83 | keyPEM = pem.EncodeToMemory(&pem.Block{
84 | Type: "RSA PRIVATE KEY",
85 | Bytes: x509.MarshalPKCS1PrivateKey(key),
86 | })
87 |
88 | return
89 | }
90 |
91 | // GenerateCert generates a leaf cert from ca.
92 | func GenerateCert(ca *tls.Certificate, hosts ...string) (*tls.Certificate, error) {
93 | now := time.Now().Add(-1 * time.Hour).UTC()
94 | if !ca.Leaf.IsCA {
95 | return nil, errors.New("CA cert is not a CA")
96 | }
97 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
98 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
99 | if err != nil {
100 | return nil, fmt.Errorf("failed to generate serial number: %s", err)
101 | }
102 | template := &x509.Certificate{
103 | SerialNumber: serialNumber,
104 | Subject: pkix.Name{CommonName: hosts[0]},
105 | NotBefore: now,
106 | NotAfter: now.Add(leafMaxAge),
107 | KeyUsage: leafUsage,
108 | BasicConstraintsValid: true,
109 | }
110 |
111 | for _, h := range hosts {
112 | if ip := net.ParseIP(h); ip != nil {
113 | template.IPAddresses = append(template.IPAddresses, ip)
114 | } else {
115 | template.DNSNames = append(template.DNSNames, h)
116 | }
117 | }
118 |
119 | key, err := genKeyPair()
120 | if err != nil {
121 | return nil, err
122 | }
123 | x, err := x509.CreateCertificate(rand.Reader, template, ca.Leaf, key.Public(), ca.PrivateKey)
124 | if err != nil {
125 | return nil, err
126 | }
127 |
128 | cert := new(tls.Certificate)
129 | cert.Certificate = append(cert.Certificate, x)
130 | cert.PrivateKey = key
131 | cert.Leaf, _ = x509.ParseCertificate(x)
132 | return cert, nil
133 | }
134 |
135 | func genKeyPair() (*rsa.PrivateKey, error) {
136 | return rsa.GenerateKey(rand.Reader, 4096)
137 | }
138 |
--------------------------------------------------------------------------------
/mitm/listener.go:
--------------------------------------------------------------------------------
1 | // Code edited from https://github.com/rainforestapp/mitm
2 | /**
3 | Copyright (c) 2015 Keith Rarick
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | */
23 |
24 | package mitm
25 |
26 | import (
27 | "crypto/tls"
28 | "net"
29 | )
30 |
31 | type listener struct {
32 | net.Listener
33 | ca *tls.Certificate
34 | conf *tls.Config
35 | }
36 |
37 | // NewListener returns a net.Listener that generates a new cert from ca for
38 | // each new Accept. It uses SNI to generate the cert, and therefore only
39 | // works with clients that send SNI headers.
40 | //
41 | // This is useful for building transparent MITM proxies.
42 | func NewListener(inner net.Listener, ca *tls.Certificate, conf *tls.Config) net.Listener {
43 | return &listener{inner, ca, conf}
44 | }
45 |
46 | func (l *listener) Accept() (net.Conn, error) {
47 | cn, err := l.Listener.Accept()
48 | if err != nil {
49 | return nil, err
50 | }
51 | sc := Server(cn, ServerParam{
52 | CA: l.ca,
53 | TLSConfig: l.conf,
54 | })
55 | return sc, nil
56 | }
57 |
--------------------------------------------------------------------------------
/mitm/mitm.go:
--------------------------------------------------------------------------------
1 | // Code edited from https://github.com/rainforestapp/mitm
2 | /**
3 | Copyright (c) 2015 Keith Rarick
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | */
23 |
24 | package mitm
25 |
26 | import (
27 | "crypto/tls"
28 | "errors"
29 | "io"
30 | "log"
31 | "net"
32 | "net/http"
33 | "time"
34 | )
35 |
36 | // ServerParam struct
37 | type ServerParam struct {
38 | CA *tls.Certificate // the Root CA for generatng on the fly MITM certificates
39 | TLSConfig *tls.Config // a template TLS config for the server.
40 | }
41 |
42 | // A ServerConn is a net.Conn that holds its clients SNI header in ServerName
43 | // after the handshake.
44 | type ServerConn struct {
45 | *tls.Conn
46 |
47 | // ServerName is set during Conn's handshake to the client's requested
48 | // server name set in the SNI header. It is not safe to access across
49 | // multiple goroutines while Conn is performing the handshake.
50 | ServerName string
51 | }
52 |
53 | // Server wraps cn with a ServerConn configured with p so that during its
54 | // Handshake, it will generate a new certificate using p.CA. After a successful
55 | // Handshake, its ServerName field will be set to the clients requested
56 | // ServerName in the SNI header.
57 | func Server(cn net.Conn, p ServerParam) *ServerConn {
58 | conf := new(tls.Config)
59 | if p.TLSConfig != nil {
60 | *conf = *p.TLSConfig
61 | }
62 | sc := new(ServerConn)
63 | conf.GetCertificate = func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
64 | sc.ServerName = hello.ServerName
65 | return getCert(p.CA, hello.ServerName)
66 | }
67 | sc.Conn = tls.Server(cn, conf)
68 | return sc
69 | }
70 |
71 | // Proxy is a forward proxy that substitutes its own certificate
72 | // for incoming TLS connections in place of the upstream server's
73 | // certificate.
74 | type Proxy struct {
75 | // Handle specifies a function for handling the decrypted HTTP request and response.
76 | Handle func(https bool) func(w http.ResponseWriter, r *http.Request)
77 |
78 | // CA specifies the root CA for generating leaf certs for each incoming
79 | // TLS request.
80 | CA *tls.Certificate
81 |
82 | // TLSServerConfig specifies the tls.Config to use when generating leaf
83 | // cert using CA.
84 | TLSServerConfig *tls.Config
85 |
86 | // TLSClientConfig specifies the tls.Config to use when establishing
87 | // an upstream connection for proxying.
88 | TLSClientConfig *tls.Config
89 |
90 | // Director is function which modifies the request into a new
91 | // request to be sent using Transport. See the documentation for
92 | // httputil.ReverseProxy for more details. For mitm proxies, the
93 | // director defaults to HTTPDirector, but for transparent TLS
94 | // proxies it should be set to HTTPSDirector.
95 | Director func(*http.Request)
96 |
97 | // SkipRequest is a function used to skip some requests from being
98 | // proxied. If it returns true, the request passes by without being
99 | // wrapped. If false, it's wrapped and proxied. By default we use
100 | // SkipNone, which doesn't skip any request
101 | SkipRequest func(*http.Request) bool
102 | }
103 |
104 | var okHeader = "HTTP/1.1 200 OK\r\n\r\n"
105 | var establishedHeader = "HTTP/1.1 200 Connection Established\r\n\r\n"
106 |
107 | func (p *Proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
108 | if p.SkipRequest == nil {
109 | p.SkipRequest = SkipNone
110 | }
111 | if p.Director == nil {
112 | p.Director = HTTPDirector
113 | }
114 |
115 | // Skip some requests
116 | if p.SkipRequest(req) || isWebSocket(req) {
117 | p.forwardRequest(w, req)
118 | return
119 | }
120 |
121 | // http
122 | if req.Method != "CONNECT" {
123 | p.Handle(false)(w, req)
124 | return
125 | }
126 |
127 | // https
128 | cn, _, err := w.(http.Hijacker).Hijack()
129 | if err != nil {
130 | log.Println("Hijack:", err)
131 | http.Error(w, "No Upstream", 503)
132 | return
133 | }
134 | defer cn.Close()
135 |
136 | _, err = io.WriteString(cn, establishedHeader)
137 | if err != nil {
138 | log.Println("Write:", err)
139 | return
140 | }
141 |
142 | var conn = cn
143 | var https = false
144 | if req.URL.Port() == "443" {
145 | https = true
146 | } else if req.URL.Port() != "80" {
147 | if c, err := tls.DialWithDialer(&net.Dialer{Timeout: time.Second * 5}, "tcp", req.URL.Host, nil); err == nil {
148 | _ = c.Close()
149 | https = true
150 | }
151 | }
152 |
153 | if https {
154 | serverConn, ok := cn.(*ServerConn)
155 | if !ok {
156 | serverConn = Server(cn, ServerParam{
157 | CA: p.CA,
158 | TLSConfig: p.TLSServerConfig,
159 | })
160 | if err := serverConn.Handshake(); err != nil {
161 | log.Println("Server Handshake:", err)
162 | return
163 | }
164 | }
165 | conn = serverConn
166 | }
167 |
168 | p.proxyMITM(conn, https)
169 | }
170 |
171 | // SkipNone doesn't skip any request and proxy all of them.
172 | func SkipNone(req *http.Request) bool {
173 | return false
174 | }
175 |
176 | func (p *Proxy) proxyMITM(upstream net.Conn, https bool) {
177 | ch := make(chan int)
178 | wc := &onCloseConn{upstream, func() { ch <- 1 }}
179 | _ = http.Serve(&oneShotListener{wc}, http.HandlerFunc(p.Handle(https)))
180 | <-ch
181 | }
182 |
183 | // HTTPDirector is director designed for use in Proxy for http
184 | // proxies.
185 | func HTTPDirector(r *http.Request) {
186 | r.URL.Host = r.Host
187 | r.URL.Scheme = "http"
188 | }
189 |
190 | // A oneShotListener implements net.Listener whos Accept only returns a
191 | // net.Conn as specified by c followed by an error for each subsequent Accept.
192 | type oneShotListener struct {
193 | c net.Conn
194 | }
195 |
196 | func (l *oneShotListener) Accept() (net.Conn, error) {
197 | if l.c == nil {
198 | return nil, errors.New("closed")
199 | }
200 | c := l.c
201 | l.c = nil
202 | return c, nil
203 | }
204 |
205 | func (l *oneShotListener) Close() error {
206 | return nil
207 | }
208 |
209 | func (l *oneShotListener) Addr() net.Addr {
210 | return l.c.LocalAddr()
211 | }
212 |
213 | // A onCloseConn implements net.Conn and calls its f on Close.
214 | type onCloseConn struct {
215 | net.Conn
216 | f func()
217 | }
218 |
219 | func (c *onCloseConn) Close() error {
220 | if c.f != nil {
221 | c.f()
222 | c.f = nil
223 | }
224 | return c.Conn.Close()
225 | }
226 |
--------------------------------------------------------------------------------
/mitm/websocket.go:
--------------------------------------------------------------------------------
1 | // Code edited from https://github.com/rainforestapp/mitm
2 | /**
3 | Copyright (c) 2015 Keith Rarick
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | */
23 |
24 | package mitm
25 |
26 | import (
27 | "crypto/tls"
28 | "io"
29 | "log"
30 | "net"
31 | "net/http"
32 | "strings"
33 | )
34 |
35 | func isWebSocket(req *http.Request) bool {
36 | return strings.Contains(strings.ToLower(req.Header.Get("Upgrade")), "websocket") &&
37 | strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade")
38 | }
39 |
40 | // fixWebsocketHeaders "un-canonicalizes" websocket headers for a
41 | // request. According to https://tools.ietf.org/html/rfc6455 the correct form is
42 | // Sec-WebSocket-*, which header canonicalization breaks (some servers care).
43 | func fixWebsocketHeaders(req *http.Request) {
44 | for header, _ := range req.Header {
45 | if strings.Contains(header, "Sec-Websocket") {
46 | val := req.Header.Get(header)
47 | correctHeader := strings.Replace(header, "Sec-Websocket", "Sec-WebSocket", 1)
48 | req.Header[correctHeader] = []string{val}
49 | delete(req.Header, header)
50 | }
51 | }
52 | }
53 |
54 | // forwardRequest forwards a WebSocket connection directly to the
55 | // source, skipping the request wrapper. Code shamelessly stolen from
56 | // https://groups.google.com/forum/#!topic/golang-nuts/KBx9pDlvFOc
57 | func (p *Proxy) forwardRequest(w http.ResponseWriter, req *http.Request) {
58 | p.Director(req)
59 | if isWebSocket(req) {
60 | fixWebsocketHeaders(req)
61 | }
62 | host, port, err := net.SplitHostPort(req.URL.Host)
63 | if err != nil {
64 | // Assume there is no port and use default
65 | host = req.URL.Host
66 | if req.URL.Scheme == "https" {
67 | port = "443"
68 | } else {
69 | port = "80"
70 | }
71 | }
72 | address := net.JoinHostPort(host, port)
73 | var d io.ReadWriteCloser
74 | if req.URL.Scheme == "https" {
75 | d, err = tls.Dial("tcp", address, p.TLSClientConfig)
76 | } else {
77 | d, err = net.Dial("tcp", address)
78 | }
79 | if err != nil {
80 | log.Printf("forwardRequest: error dialing websocket backend %s: %v", address, err)
81 | http.Error(w, "No Upstream", 503)
82 | return
83 | }
84 | defer d.Close()
85 |
86 | nc, _, err := w.(http.Hijacker).Hijack()
87 | if err != nil {
88 | log.Printf("forwardRequest: hijack error: %v", err)
89 | http.Error(w, "No Upstream", 503)
90 | return
91 | }
92 | defer nc.Close()
93 |
94 | err = req.Write(d)
95 | if err != nil {
96 | log.Printf("forwardRequest: error copying request to target: %v", err)
97 | return
98 | }
99 |
100 | errc := make(chan error, 2)
101 | cp := func(dst io.Writer, src io.Reader) {
102 | _, err := io.Copy(dst, src)
103 | errc <- err
104 | }
105 | go cp(d, nc)
106 | go cp(nc, d)
107 | <-errc
108 | }
109 |
--------------------------------------------------------------------------------
/proxy.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | )
7 |
8 | var DefaultProxy *Proxy
9 |
10 | type Proxy struct {
11 | Username string `json:"username"`
12 | Password string `json:"password"`
13 | Port string `json:"port"`
14 |
15 | Directory string `json:"directory"`
16 | Mark int `json:"mark"`
17 | Http2 bool `json:"http2"`
18 |
19 | Proxy string `json:"proxy"`
20 |
21 | Cookies string
22 | }
23 |
24 | func LoadConfig(file string) *Proxy {
25 | var p Proxy
26 | content, err := ioutil.ReadFile(file)
27 | if err != nil {
28 | Panic(err)
29 | }
30 | err = json.Unmarshal(content, &p)
31 | if err != nil {
32 | Panic(err)
33 | }
34 |
35 | if p.Username == "" || p.Password == "" || p.Directory == "" || p.Port == "" {
36 | Panic("Empty entries found in config file!")
37 | }
38 |
39 | DefaultProxy = &p
40 | return DefaultProxy
41 | }
42 |
--------------------------------------------------------------------------------
/regexp.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "regexp"
4 |
5 | var PathMatchRegex = regexp.MustCompile("/(https?)(?:-\\d*)?/([0-9a-f]{32,})(.+)")
6 |
7 | var RedirectLink = regexp.MustCompile("https://vpns.jlu.edu.cn/(https?)(?:-[0-9]*)?/([0-9a-f]{32,})(.*)")
8 |
9 | var VPNsLinkMatch = regexp.MustCompile("(?:https:)?(?://)?(?:vpns.jlu.edu.cn)?/(https?)(?:-[0-9]*)?/([0-9a-f]{32,})(?:/([^\")]+))?")
10 |
11 | var LinkUnescape = regexp.MustCompile("(href|link|src)=\"([^\"]+)\"")
12 |
13 | var VPNEvalPrefix = regexp.MustCompile("vpn_eval\\(\\(function\\(\\){\r?\n")
14 |
15 | var VPNEvalPostfix = regexp.MustCompile("}\r?\n\\).toString\\(\\).slice\\(12, -2\\),\"\"\\);")
16 |
17 | var VPNRewritePrefix = regexp.MustCompile("var vpn_return;eval\\(vpn_rewrite_js\\(\\(function \\(\\) { ")
18 |
19 | var VPNRewritePostfix = regexp.MustCompile(" }\\).toString\\(\\)\\.slice\\(14, -2\\), 2\\)\\);return vpn_return;")
20 |
21 | var VPNInjectPrefix = regexp.MustCompile("this.top.vpn_inject_scripts_window\\(this\\);vpn_eval\\(\\(function \\(\\) { ")
22 |
23 | var VPNInjectPostfix = regexp.MustCompile(" }\\)\\.toString\\(\\).slice\\(14, -2\\)\\)")
24 |
25 | var VPNParamRemoveFirst = regexp.MustCompile("vpn-\\d+&")
26 |
27 | var VPNParamRemoveOther = regexp.MustCompile("\\?vpn-\\d+")
28 |
29 | var VPNScriptInfo = regexp.MustCompile("")
30 |
31 | var VPNScriptWEngine = regexp.MustCompile("")
32 |
--------------------------------------------------------------------------------
/release/PKGBUILD:
--------------------------------------------------------------------------------
1 | # Maintainer: Yesterday17
2 | pkgname=jlu-http-proxy-git
3 | _pkgname=jlu-http-proxy
4 | pkgver=1.0.4
5 | pkgrel=5
6 | pkgdesc="HTTP Forward proxy for vpns.jlu.edu.cn"
7 | arch=('i686' 'x86_64')
8 | url="https://github.com/Yesterday17/jlu-http-proxy"
9 | license=('MIT')
10 | makedepends=(
11 | 'go'
12 | 'git'
13 | )
14 | backup=(etc/jlu-http-config/config.json)
15 |
16 | source=(
17 | "$_pkgname::git+https://github.com/Yesterday17/jlu-http-proxy.git"
18 | )
19 |
20 | md5sums=('SKIP')
21 |
22 | build() {
23 | export GO111MODULE=on
24 |
25 | if [ -L "$srcdir/$_pkgname" ]; then
26 | rm "$srcdir/$_pkgname" -rf
27 | mv "$srcdir/go/src/$_pkgname/" "$srcdir/$_pkgname"
28 | fi
29 |
30 | rm -rf "go/src"
31 | mkdir -p "go/src"
32 | mv "$_pkgname" "go/src/"
33 |
34 | ln -sf "go/src/$_pkgname/" "$_pkgname"
35 | cd "go/src/${_pkgname}/"
36 |
37 | echo ":: Building binary"
38 | go build
39 | }
40 |
41 | package() {
42 | cd "${_pkgname}"
43 | install -Dm755 ${_pkgname} "${pkgdir}/usr/bin/${_pkgname}"
44 | install -Dm644 release/config.json -t "${pkgdir}/etc/jlu-http-proxy/"
45 | install -Dm644 release/jlu-http-proxy.service -t "${pkgdir}/usr/lib/systemd/system"
46 | }
47 |
--------------------------------------------------------------------------------
/release/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "username": "",
3 | "password": "",
4 | "port": "8848",
5 | "directory": "",
6 | "mark": 0,
7 | "http2": false,
8 | "auto_reauth": false
9 | }
10 |
--------------------------------------------------------------------------------
/release/jlu-http-proxy.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=jlu-http-proxy Service
3 | After=network-online.target
4 | Wants=network-online.target
5 |
6 | [Service]
7 | Type=simple
8 | ExecStart=/usr/bin/jlu-http-proxy /etc/jlu-http-proxy/config.json
9 | Restart=on-failure
10 | # Don't restart in the case of configuration error
11 | RestartPreventExitStatus=23
12 |
13 | [Install]
14 | WantedBy=multi-user.target
15 |
--------------------------------------------------------------------------------