├── .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 | --------------------------------------------------------------------------------