336 lines
8.0 KiB
Go
336 lines
8.0 KiB
Go
package fproxy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"local/encoder"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
socks5 "github.com/armon/go-socks5"
|
|
"golang.org/x/net/proxy"
|
|
)
|
|
|
|
func deny(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
fmt.Fprintln(w, "You shouldn't be here")
|
|
}
|
|
|
|
func (fp *FProxy) Start() {
|
|
if fp.fromcrt != "" {
|
|
go func() {
|
|
conf := &socks5.Config{
|
|
Credentials: fp,
|
|
Rules: fp,
|
|
}
|
|
server, err := socks5.New(conf)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := server.ListenAndServe("tcp", fp.tlsport); err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
}
|
|
go func() {
|
|
if fp.fromcrt != "" {
|
|
if err := fp.sserver.ListenAndServeTLS(fp.mycrt, fp.mykey); err != http.ErrServerClosed {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
if err := fp.iserver.ListenAndServe(); err != http.ErrServerClosed {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (fp *FProxy) Valid(user, pass string) bool {
|
|
enc, err := encoder.New(nil, &encoder.Salter{}, &encoder.AES{Key: []byte(fp.sockssecret)})
|
|
if err != nil {
|
|
return false
|
|
}
|
|
userb, err := enc.Decode([]byte(user))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
passb, err := enc.Decode([]byte(pass))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
should, err := ioutil.ReadFile(fp.fromcrt)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if string(userb) != string(should[:30]) || string(passb) != string(should[len(should)-30:]) {
|
|
return false
|
|
}
|
|
log.Print(fp.httpport, "directly connecting socks")
|
|
return true
|
|
}
|
|
|
|
func (fp *FProxy) Allow(ctx context.Context, r *socks5.Request) (context.Context, bool) {
|
|
if !fp.fromLocal(r.RemoteAddr.IP.String()) {
|
|
log.Print(fp.httpport, "denying socks from non-local", r.RemoteAddr.IP.String())
|
|
return ctx, false
|
|
}
|
|
if len(fp.whitelist) > 0 && !contains(fp.whitelist, baseHost(r.DestAddr.FQDN)) && !contains(fp.whitelist, baseHost(r.DestAddr.IP.String())) {
|
|
log.Print(fp.httpport, "denying socks to non-whitelist", r.DestAddr.FQDN)
|
|
return ctx, false
|
|
}
|
|
return ctx, true
|
|
}
|
|
|
|
func (fp *FProxy) Close() error {
|
|
fp.iserver.Shutdown(context.TODO())
|
|
return fp.sserver.Shutdown(context.TODO())
|
|
}
|
|
|
|
func (fp *FProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if !fp.fromLocal(r.RemoteAddr) {
|
|
log.Print(fp.httpport, "denying non-local", r.RemoteAddr)
|
|
deny(w, r)
|
|
return
|
|
}
|
|
if !fp.toWhitelist(r) {
|
|
log.Print(fp.httpport, "denying non-whitelist", r.URL)
|
|
deny(w, r)
|
|
return
|
|
}
|
|
if contains(fp.bypass, baseHost(r.URL.Host)) {
|
|
log.Print(fp.httpport, "passing through", r.URL)
|
|
fp.Passthrough(w, r)
|
|
return
|
|
}
|
|
if r.Method == http.MethodConnect && fp.transport.Proxy == nil {
|
|
log.Print(fp.httpport, "directly connecting", r.Host)
|
|
fp.Connect(w, r)
|
|
return
|
|
}
|
|
if r.Method == http.MethodConnect && fp.transport.Proxy != nil {
|
|
log.Print(fp.httpport, "connecting", r.Host, "via", fmt.Sprint(fp.transport.Proxy(r)))
|
|
fp.Connect(w, r)
|
|
return
|
|
}
|
|
if fp.transport.Proxy != nil {
|
|
log.Print(fp.httpport, "proxying", r.Host, "via", fmt.Sprint(fp.transport.Proxy(r)))
|
|
} else {
|
|
log.Print(fp.httpport, "proxying directly to", r.Host)
|
|
}
|
|
fp.ProxyRequest(w, r)
|
|
}
|
|
|
|
func (fp *FProxy) Connect(w http.ResponseWriter, r *http.Request) {
|
|
if fp.transport.Proxy == nil {
|
|
fp.Passthrough(w, r)
|
|
return
|
|
}
|
|
u, err := fp.transport.Proxy(r)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
uHost, uPort, err := net.SplitHostPort(u.Host)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
switch uPort[0] {
|
|
case '6':
|
|
uPort = "1" + uPort[1:]
|
|
default:
|
|
uPort = string(uPort[0]+byte(1)) + uPort[1:]
|
|
}
|
|
host := net.JoinHostPort(uHost, uPort)
|
|
|
|
enc, err := encoder.New(nil, &encoder.Salter{}, &encoder.AES{Key: []byte(fp.sockssecret)})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
should, err := ioutil.ReadFile(fp.mycrt)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
user, err := enc.Encode(should[:30])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
pass, err := enc.Encode(should[len(should)-30:])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
dialer, err := proxy.SOCKS5("tcp", host, &proxy.Auth{
|
|
User: string(user),
|
|
Password: string(pass),
|
|
}, proxy.Direct)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
dest_conn, err := dialer.Dial("tcp", r.Host)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
hijacker, ok := w.(http.Hijacker)
|
|
if !ok {
|
|
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
client_conn, _, err := hijacker.Hijack()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
}
|
|
|
|
transfer := func(destination io.WriteCloser, source io.ReadCloser) {
|
|
defer destination.Close()
|
|
defer source.Close()
|
|
io.Copy(destination, source)
|
|
}
|
|
go transfer(dest_conn, client_conn)
|
|
go transfer(client_conn, dest_conn)
|
|
}
|
|
|
|
func (fp *FProxy) ProxyRequest(w http.ResponseWriter, r *http.Request) {
|
|
copyHeader := func(dst, src http.Header) {
|
|
for k, vv := range src {
|
|
for _, v := range vv {
|
|
dst.Add(k, v)
|
|
}
|
|
}
|
|
}
|
|
transport := fp.transport
|
|
resp, err := transport.RoundTrip(r)
|
|
if err != nil {
|
|
log.Print("ProxyRequest ERR", err)
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
copyHeader(w.Header(), resp.Header)
|
|
w.WriteHeader(resp.StatusCode)
|
|
io.Copy(w, resp.Body)
|
|
}
|
|
|
|
func (fp *FProxy) old_Proxy_Request(w http.ResponseWriter, r *http.Request) {
|
|
fp.setScheme(r)
|
|
proxy := httputil.NewSingleHostReverseProxy(withoutPath(r.URL))
|
|
v := "?"
|
|
if fp.transport.Proxy != nil {
|
|
proxy.Transport = fp.transport
|
|
v = strings.Split(fmt.Sprintln(fp.transport.Proxy(r)), " ")[0]
|
|
}
|
|
log.Printf("%s: proxying %q %q -> %q", fp.sserver.Addr, r.Method, withoutPath(r.URL).String(), v)
|
|
proxy.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
func (fp *FProxy) Passthrough(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Scheme == "http" {
|
|
proxy := httputil.NewSingleHostReverseProxy(withoutPath(r.URL))
|
|
proxy.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
dest_conn, err := net.DialTimeout("tcp", r.Host, 30*time.Second)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
hijacker, ok := w.(http.Hijacker)
|
|
if !ok {
|
|
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
client_conn, _, err := hijacker.Hijack()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
}
|
|
|
|
transfer := func(destination io.WriteCloser, source io.ReadCloser) {
|
|
defer destination.Close()
|
|
defer source.Close()
|
|
io.Copy(destination, source)
|
|
}
|
|
|
|
go transfer(dest_conn, client_conn)
|
|
go transfer(client_conn, dest_conn)
|
|
}
|
|
|
|
func (fp *FProxy) setScheme(r *http.Request) {
|
|
if r.URL.Scheme == "" {
|
|
r.URL.Scheme = "http"
|
|
}
|
|
if strings.HasSuffix(r.URL.Host, ":443") {
|
|
r.URL.Scheme = "https"
|
|
//r.URL.Host = r.URL.Host[:len(r.URL.Host)-len(":443")]
|
|
}
|
|
if contains(fp.secure, baseHost(r.URL.Host)) {
|
|
r.URL.Scheme = "https"
|
|
}
|
|
}
|
|
|
|
func (fp *FProxy) toWhitelist(r *http.Request) bool {
|
|
return len(fp.whitelist) == 0 || contains(fp.whitelist, baseHost(r.URL.Host))
|
|
}
|
|
|
|
func (fp *FProxy) fromLocal(remote string) bool {
|
|
if fp.fromcrt != "" {
|
|
return true
|
|
}
|
|
for _, h := range fp.localhost {
|
|
if strings.HasPrefix(remote, h) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func contains(list []string, element string) bool {
|
|
ind := sort.SearchStrings(list, element)
|
|
if ind < 0 || ind >= len(list) {
|
|
return false
|
|
}
|
|
return list[ind] == element
|
|
}
|
|
|
|
func baseHost(host string) string {
|
|
host = strings.Replace(host, "www.", "", -1)
|
|
host = strings.Replace(host, "http://", "", -1)
|
|
host = strings.Replace(host, "https://", "", -1)
|
|
host = strings.Split(host, ":")[0]
|
|
if ok, err := regexp.MatchString("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+", host); err != nil {
|
|
panic(err)
|
|
} else if !ok {
|
|
hosts := strings.Split(host, ".")
|
|
if len(hosts) > 1 {
|
|
host = fmt.Sprintf("%s.%s", hosts[len(hosts)-2], hosts[len(hosts)-1])
|
|
}
|
|
}
|
|
return host
|
|
}
|
|
|
|
func withoutPath(u *url.URL) *url.URL {
|
|
return &url.URL{
|
|
Scheme: u.Scheme,
|
|
Opaque: u.Opaque,
|
|
User: u.User,
|
|
Host: u.Host,
|
|
Path: "",
|
|
RawPath: "",
|
|
ForceQuery: u.ForceQuery,
|
|
RawQuery: u.RawQuery,
|
|
Fragment: u.Fragment,
|
|
}
|
|
}
|