Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic auth to SOCKS5 forwarding #11

Merged
merged 1 commit into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- New `--socks` option which allows forwarding requests to a SOCKS5 proxy.
- New `--socks-auth` option which allows adding basic authentication details to forwarded SOCKS5 proxy requests.

## [v1.2.0] - 2024-07-15
### Added
Expand Down
34 changes: 16 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ Code based on the guide here: <https://medium.com/@mlowicki/http-s-proxy-in-gola

## Features

- HTTP and HTTPS.
- Can choose which port to run on.
- Can specify paths to certificate and private key file to use.
- Logs each proxied connection.
- Log options can be supplied using `glog`.
- Can choose the log verbosity with the `-v` flag.
- Can choose to log to a file.
- Basic authentication.
- Can log request headers.
- Can log failed authentication attempt details.
- Printing version number.
- Tunnel HTTP proxy to socks5 proxy
- HTTP and HTTPS
- Can choose which port to run on
- Can specify paths to certificate and private key file to use
- Logs each proxied connection
- Log options can be supplied using `glog`
- Can choose the log verbosity with the `-v` flag
- Can choose to log to a file
- Basic authentication
- Can log request headers
- Can log failed authentication attempt details
- Printing version number
- Tunnelling HTTP proxy to SOCKS5 proxy

## Install

Expand Down Expand Up @@ -49,10 +49,6 @@ You can run the binary directly:

```bash
./simple-proxy

# tunnel the http proxy to socks5 proxy
# client -> localhost:7990 [http proxy] -> 127.0.0.1:7890 [socks5 proxy] -> server
./simple-proxy -port 7990 -socks 127.0.0.1:7890
```

## Windows
Expand Down Expand Up @@ -91,10 +87,12 @@ Usage of simple-proxy:
log to standard error instead of files
-port string
proxy port to listen on (default "8888")
-socks5 string
proxy tunnel the http requests to a socks5 proxy (default "", feature off)
-protocol string
proxy protocol (http or https) (default "http")
-socks5 string
SOCKS5 proxy for tunneling, not used if not provided
-socks5-auth string
basic auth for socks5, format 'username:password', no auth if not provided
-stderrthreshold value
logs at or above this threshold go to stderr
-timeout int
Expand Down
44 changes: 31 additions & 13 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"time"

"github.com/golang/glog"
my_proxy "github.com/jthomperoo/simple-proxy/proxy"
"github.com/jthomperoo/simple-proxy/proxy"
)

var (
Expand All @@ -37,7 +37,9 @@ func main() {
var port string
flag.StringVar(&port, "port", "8888", "proxy port to listen on")
var socks5 string
flag.StringVar(&socks5, "socks5", "", "SOCKS5 proxy for tunneling")
flag.StringVar(&socks5, "socks5", "", "SOCKS5 proxy for tunneling, not used if not provided")
var socks5Auth string
flag.StringVar(&socks5Auth, "socks5-auth", "", "basic auth for socks5, format 'username:password', no auth if not provided")
var certPath string
flag.StringVar(&certPath, "cert", "", "path to cert file")
var keyPath string
Expand Down Expand Up @@ -65,26 +67,42 @@ func main() {
glog.Fatalf("If using HTTPS protocol --cert and --key are required\n")
}

my_proxy.Socks5 = socks5
var socks5Forward *proxy.Socks5Forward
if socks5 != "" {
socks5Forward = &proxy.Socks5Forward{
Address: socks5,
}
if socks5Auth != "" {
parts := strings.Split(socks5Auth, ":")
if len(parts) < 2 {
glog.Fatalf("Invalid socks5 basic auth provided, must be in format 'username:password', auth: %s\n", basicAuth)
}

socks5Forward.Username = &parts[0]
socks5Forward.Password = &parts[1]
}
}

var handler http.Handler
if basicAuth == "" {
handler = &my_proxy.ProxyHandler{
Timeout: time.Duration(timeoutSecs) * time.Second,
LogAuth: logAuth,
LogHeaders: logHeaders,
handler = &proxy.ProxyHandler{
Timeout: time.Duration(timeoutSecs) * time.Second,
LogAuth: logAuth,
LogHeaders: logHeaders,
Socks5Forward: socks5Forward,
}
} else {
parts := strings.Split(basicAuth, ":")
if len(parts) < 2 {
glog.Fatalf("Invalid basic auth provided, must be in format 'username:password', auth: %s\n", basicAuth)
}
handler = &my_proxy.ProxyHandler{
Timeout: time.Duration(timeoutSecs) * time.Second,
Username: &parts[0],
Password: &parts[1],
LogAuth: logAuth,
LogHeaders: logHeaders,
handler = &proxy.ProxyHandler{
Timeout: time.Duration(timeoutSecs) * time.Second,
Username: &parts[0],
Password: &parts[1],
LogAuth: logAuth,
LogHeaders: logHeaders,
Socks5Forward: socks5Forward,
}
}

Expand Down
64 changes: 40 additions & 24 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,28 @@ import (
"time"

"github.com/golang/glog"
net_proxy "golang.org/x/net/proxy"
netProxy "golang.org/x/net/proxy"
)

// Using a socks5 proxy to tunnel the HTTP requests, e.g., 127.0.0.1:7890
var Socks5 = ""

func NewProxyHandler(timeoutSeconds int) *ProxyHandler {
return &ProxyHandler{
Timeout: time.Duration(timeoutSeconds) * time.Second,
}
}

type ProxyHandler struct {
Timeout time.Duration
Username *string
Password *string
LogAuth bool
LogHeaders bool
Timeout time.Duration
Username *string
Password *string
LogAuth bool
LogHeaders bool
Socks5Forward *Socks5Forward
}

type Socks5Forward struct {
Address string
Username *string
Password *string
}

func (p *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Expand All @@ -52,52 +56,65 @@ func (p *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
if r.Method == http.MethodConnect {
handleTunneling(w, r, p.Timeout)
handleTunneling(w, r, p.Timeout, p.Socks5Forward)
} else {
handleHTTP(w, r)
}
}

func handleTunneling(w http.ResponseWriter, r *http.Request, timeout time.Duration) {
var dest_conn net.Conn
func handleTunneling(w http.ResponseWriter, r *http.Request, timeout time.Duration, socks5Forward *Socks5Forward) {
var destConn net.Conn
var err error

// Check if the socks5 proxy is set
// Then tunnel to socks5 proxy
if Socks5 == "" {
dest_conn, err = net.DialTimeout("tcp", r.Host, timeout)
if socks5Forward == nil {
destConn, err = net.DialTimeout("tcp", r.Host, timeout)
} else {
var socks5_dailer net_proxy.Dialer
socks5_dailer, err = net_proxy.SOCKS5("tcp", Socks5, nil, &net.Dialer{
var socks5Auth *netProxy.Auth
if socks5Forward.Username != nil && socks5Forward.Password != nil {
socks5Auth = &netProxy.Auth{
User: *socks5Forward.Username,
Password: *socks5Forward.Password,
}
}

var socks5Dialer netProxy.Dialer
socks5Dialer, err = netProxy.SOCKS5("tcp", socks5Forward.Address, socks5Auth, &net.Dialer{
Timeout: timeout,
KeepAlive: 30 * time.Second,
})

if err != nil {
glog.Errorf("Failed to dail socks5 proxy %s, %s\n", Socks5, err.Error())
glog.Errorf("Failed to dial socks5 proxy %s, %s\n", socks5Forward.Address, err.Error())
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
dest_conn, err = socks5_dailer.Dial("tcp", r.Host)

destConn, err = socks5Dialer.Dial("tcp", r.Host)
}

if err != nil {
glog.Errorf("Failed to dial host, %s\n", err.Error())
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}

w.WriteHeader(http.StatusOK)

hijacker, ok := w.(http.Hijacker)
if !ok {
glog.Errorln("Attempted to hijack connection that does not support it")
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
return
}
client_conn, _, err := hijacker.Hijack()

clientConn, _, err := hijacker.Hijack()
if err != nil {
glog.Errorf("Failed to hijack connection, %s\n", err.Error())
http.Error(w, err.Error(), http.StatusServiceUnavailable)
}
go transfer(dest_conn, client_conn)
go transfer(client_conn, dest_conn)

go transfer(destConn, clientConn)
go transfer(clientConn, destConn)
}

func transfer(destination io.WriteCloser, source io.ReadCloser) {
Expand Down Expand Up @@ -139,7 +156,6 @@ func proxyBasicAuth(r *http.Request) (username, password string, ok bool) {
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
func parseBasicAuth(auth string) (username, password string, ok bool) {
const prefix = "Basic "
// Case insensitive prefix match. See Issue 22736.
if len(auth) < len(prefix) || !equalFold(auth[:len(prefix)], prefix) {
return
}
Expand Down
Loading