Files
fproxy/fproxy/serve.go
2019-05-02 09:21:21 -06:00

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,
}
}