2020-05-19 21:53:13 +02:00
package main
import (
2021-02-26 08:09:55 +01:00
"crypto/tls"
"flag"
"fmt"
"log"
2023-02-07 19:26:12 +01:00
"net"
2021-02-26 08:09:55 +01:00
"net/http"
"os"
2022-09-04 22:07:14 +02:00
"path/filepath"
"strings"
2021-02-26 08:09:55 +01:00
"time"
2022-09-04 22:07:14 +02:00
2023-04-01 22:55:06 +02:00
"github.com/coreos/go-systemd/v22/activation"
2022-09-04 22:59:21 +02:00
"golang.org/x/crypto/acme"
2022-09-04 22:07:14 +02:00
"golang.org/x/crypto/acme/autocert"
2022-09-06 23:08:06 +02:00
"golang.org/x/crypto/bcrypt"
2020-05-19 21:53:13 +02:00
)
2021-06-09 14:49:44 +02:00
var (
2022-09-04 22:07:14 +02:00
home , _ = os . UserHomeDir ( )
2021-06-09 14:49:44 +02:00
version = "undefined"
)
2020-05-19 21:53:13 +02:00
func perror ( msg string ) {
2021-02-26 08:09:55 +01:00
fmt . Fprintln ( os . Stderr , "" )
fmt . Fprintln ( os . Stderr , msg )
2020-05-19 21:53:13 +02:00
}
func arg_fail ( msg string ) {
2021-02-26 08:09:55 +01:00
perror ( msg )
perror ( "Usage:" )
flag . PrintDefaults ( )
os . Exit ( 2 )
2020-05-19 21:53:13 +02:00
}
2022-09-04 22:07:14 +02:00
type CSVArg [ ] string
func ( a * CSVArg ) Set ( s string ) error {
* a = strings . Split ( s , "," )
return nil
}
func ( a * CSVArg ) String ( ) string {
if a == nil {
return "<nil>"
}
if * a == nil {
return "<empty>"
}
return strings . Join ( * a , "," )
}
2020-05-19 21:53:13 +02:00
type CLIArgs struct {
2021-02-26 08:09:55 +01:00
bind_address string
auth string
verbosity int
timeout time . Duration
cert , key , cafile string
2021-02-26 08:14:05 +01:00
list_ciphers bool
2021-02-26 08:30:18 +01:00
ciphers string
2021-02-26 08:35:17 +01:00
disableHTTP2 bool
2022-09-04 22:07:14 +02:00
showVersion bool
autocert bool
autocertWhitelist CSVArg
autocertDir string
2022-09-04 22:59:21 +02:00
autocertACME string
2022-09-04 23:24:48 +02:00
autocertEmail string
2022-09-04 23:36:49 +02:00
autocertHTTP string
2022-09-06 23:08:06 +02:00
passwd string
passwdCost int
positionalArgs [ ] string
2023-02-07 23:11:15 +01:00
proxy [ ] string
2023-09-06 16:34:07 +02:00
sourceIPHints string
2023-06-26 22:53:08 +02:00
userIPHints bool
2021-02-26 08:09:55 +01:00
}
2020-05-19 21:53:13 +02:00
func parse_args ( ) CLIArgs {
2021-02-26 08:09:55 +01:00
var args CLIArgs
2023-04-01 23:08:35 +02:00
flag . StringVar ( & args . bind_address , "bind-address" , ":8080" , "HTTP proxy listen address. Set empty value to use systemd socket activation." )
2021-02-26 08:09:55 +01:00
flag . StringVar ( & args . auth , "auth" , "none://" , "auth parameters" )
flag . IntVar ( & args . verbosity , "verbosity" , 20 , "logging verbosity " +
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)" )
flag . DurationVar ( & args . timeout , "timeout" , 10 * time . Second , "timeout for network operations" )
flag . StringVar ( & args . cert , "cert" , "" , "enable TLS and use certificate" )
flag . StringVar ( & args . key , "key" , "" , "key for TLS certificate" )
flag . StringVar ( & args . cafile , "cafile" , "" , "CA file to authenticate clients with certificates" )
2021-02-26 08:14:05 +01:00
flag . BoolVar ( & args . list_ciphers , "list-ciphers" , false , "list ciphersuites" )
2021-02-26 08:30:18 +01:00
flag . StringVar ( & args . ciphers , "ciphers" , "" , "colon-separated list of enabled ciphers" )
2021-02-26 08:35:17 +01:00
flag . BoolVar ( & args . disableHTTP2 , "disable-http2" , false , "disable HTTP2" )
2021-06-09 14:49:44 +02:00
flag . BoolVar ( & args . showVersion , "version" , false , "show program version and exit" )
2022-09-04 22:07:14 +02:00
flag . BoolVar ( & args . autocert , "autocert" , false , "issue TLS certificates automatically" )
flag . Var ( & args . autocertWhitelist , "autocert-whitelist" , "restrict autocert domains to this comma-separated list" )
flag . StringVar ( & args . autocertDir , "autocert-dir" , filepath . Join ( home , ".dumbproxy" , "autocert" ) , "path to autocert cache" )
2022-09-04 22:59:21 +02:00
flag . StringVar ( & args . autocertACME , "autocert-acme" , autocert . DefaultACMEDirectory , "custom ACME endpoint" )
2022-09-04 23:24:48 +02:00
flag . StringVar ( & args . autocertEmail , "autocert-email" , "" , "email used for ACME registration" )
2022-09-04 23:36:49 +02:00
flag . StringVar ( & args . autocertHTTP , "autocert-http" , "" , "listen address for HTTP-01 challenges handler of ACME" )
2022-09-06 23:08:06 +02:00
flag . StringVar ( & args . passwd , "passwd" , "" , "update given htpasswd file and add/set password for username. " +
"Username and password can be passed as positional arguments or requested interactively" )
flag . IntVar ( & args . passwdCost , "passwd-cost" , bcrypt . MinCost , "bcrypt password cost (for -passwd mode)" )
2023-02-09 20:51:46 +01:00
flag . Func ( "proxy" , "upstream proxy URL. Can be repeated multiple times to chain proxies. Examples: socks5h://127.0.0.1:9050; https://user:password@example.com:443" , func ( p string ) error {
2023-02-07 23:11:15 +01:00
args . proxy = append ( args . proxy , p )
return nil
} )
2023-09-06 17:21:19 +02:00
flag . StringVar ( & args . sourceIPHints , "ip-hints" , "" , "a comma-separated list of source addresses to use on dial attempts. \"$lAddr\" gets expanded to local address of connection. Example: \"10.0.0.1,fe80::2,$lAddr,0.0.0.0,::\"" )
2023-06-26 22:53:08 +02:00
flag . BoolVar ( & args . userIPHints , "user-ip-hints" , false , "allow IP hints to be specified by user in X-Src-IP-Hints header" )
2021-02-26 08:09:55 +01:00
flag . Parse ( )
2022-09-06 23:08:06 +02:00
args . positionalArgs = flag . Args ( )
2021-02-26 08:09:55 +01:00
return args
2020-05-19 21:53:13 +02:00
}
func run ( ) int {
2021-02-26 08:09:55 +01:00
args := parse_args ( )
2021-06-09 14:49:44 +02:00
if args . showVersion {
fmt . Println ( version )
return 0
}
2021-02-26 08:14:05 +01:00
if args . list_ciphers {
list_ciphers ( )
2021-02-26 08:09:55 +01:00
return 0
}
2020-05-19 21:53:13 +02:00
2022-09-06 23:08:06 +02:00
if args . passwd != "" {
if err := passwd ( args . passwd , args . passwdCost , args . positionalArgs ... ) ; err != nil {
log . Fatalf ( "can't set password: %v" , err )
}
return 0
}
2021-02-26 08:09:55 +01:00
logWriter := NewLogWriter ( os . Stderr )
defer logWriter . Close ( )
2020-05-19 21:53:13 +02:00
2021-02-26 08:09:55 +01:00
mainLogger := NewCondLogger ( log . New ( logWriter , "MAIN : " ,
log . LstdFlags | log . Lshortfile ) ,
args . verbosity )
proxyLogger := NewCondLogger ( log . New ( logWriter , "PROXY : " ,
log . LstdFlags | log . Lshortfile ) ,
args . verbosity )
2022-09-06 18:57:18 +02:00
authLogger := NewCondLogger ( log . New ( logWriter , "AUTH : " ,
log . LstdFlags | log . Lshortfile ) ,
args . verbosity )
2020-05-24 00:18:01 +02:00
2022-09-06 18:57:18 +02:00
auth , err := NewAuth ( args . auth , authLogger )
2021-02-26 08:09:55 +01:00
if err != nil {
mainLogger . Critical ( "Failed to instantiate auth provider: %v" , err )
return 3
}
2022-09-06 20:43:56 +02:00
defer auth . Stop ( )
2020-05-24 00:18:01 +02:00
2023-06-26 21:29:58 +02:00
var dialer Dialer = NewBoundDialer ( new ( net . Dialer ) , args . sourceIPHints )
2023-02-07 23:11:15 +01:00
for _ , proxyURL := range args . proxy {
newDialer , err := proxyDialerFromURL ( proxyURL , dialer )
if err != nil {
mainLogger . Critical ( "Failed to create dialer for proxy %q: %v" , proxyURL , err )
return 3
}
dialer = newDialer
}
2021-02-26 08:09:55 +01:00
server := http . Server {
Addr : args . bind_address ,
2023-06-26 22:53:08 +02:00
Handler : NewProxyHandler ( args . timeout , auth , maybeWrapWithContextDialer ( dialer ) , args . userIPHints , proxyLogger ) ,
2021-02-26 08:09:55 +01:00
ErrorLog : log . New ( logWriter , "HTTPSRV : " , log . LstdFlags | log . Lshortfile ) ,
ReadTimeout : 0 ,
ReadHeaderTimeout : 0 ,
WriteTimeout : 0 ,
IdleTimeout : 0 ,
}
2020-05-24 00:18:01 +02:00
2021-02-26 08:35:17 +01:00
if args . disableHTTP2 {
server . TLSNextProto = make ( map [ string ] func ( * http . Server , * tls . Conn , http . Handler ) )
}
2021-02-26 08:09:55 +01:00
mainLogger . Info ( "Starting proxy server..." )
2023-04-01 22:55:06 +02:00
var listener net . Listener
if args . bind_address == "" {
// socket activation
listeners , err := activation . Listeners ( )
if err != nil {
mainLogger . Critical ( "socket activation failed: %v" , err )
return 3
}
if len ( listeners ) != 1 {
mainLogger . Critical ( "socket activation failed: unexpected number of listeners: %d" ,
len ( listeners ) )
return 3
}
if listeners [ 0 ] == nil {
mainLogger . Critical ( "socket activation failed: nil listener returned" )
return 3
}
listener = listeners [ 0 ]
} else {
newListener , err := net . Listen ( "tcp" , args . bind_address )
if err != nil {
mainLogger . Critical ( "listen failed: %v" , err )
return 3
}
listener = newListener
}
2021-02-26 08:09:55 +01:00
if args . cert != "" {
2023-04-01 22:55:06 +02:00
cfg , err1 := makeServerTLSConfig ( args . cert , args . key , args . cafile , args . ciphers , ! args . disableHTTP2 )
2021-02-26 08:09:55 +01:00
if err1 != nil {
2021-06-14 17:07:19 +02:00
mainLogger . Critical ( "TLS config construction failed: %v" , err1 )
2021-02-26 08:09:55 +01:00
return 3
}
2023-04-01 22:55:06 +02:00
listener = tls . NewListener ( listener , cfg )
2022-09-04 22:07:14 +02:00
} else if args . autocert {
m := & autocert . Manager {
Cache : autocert . DirCache ( args . autocertDir ) ,
Prompt : autocert . AcceptTOS ,
2022-09-04 22:59:21 +02:00
Client : & acme . Client { DirectoryURL : args . autocertACME } ,
2022-09-04 23:24:48 +02:00
Email : args . autocertEmail ,
2022-09-04 22:07:14 +02:00
}
if args . autocertWhitelist != nil {
m . HostPolicy = autocert . HostWhitelist ( [ ] string ( args . autocertWhitelist ) ... )
}
2022-09-04 23:36:49 +02:00
if args . autocertHTTP != "" {
go func ( ) {
log . Fatalf ( "HTTP-01 ACME challenge server stopped: %v" ,
http . ListenAndServe ( args . autocertHTTP , m . HTTPHandler ( nil ) ) )
} ( )
}
2022-09-04 22:07:14 +02:00
cfg := m . TLSConfig ( )
2023-04-01 22:55:06 +02:00
cfg , err = updateServerTLSConfig ( cfg , args . cafile , args . ciphers , ! args . disableHTTP2 )
2022-09-07 09:27:33 +02:00
if err != nil {
mainLogger . Critical ( "TLS config construction failed: %v" , err )
return 3
}
2023-04-01 22:55:06 +02:00
listener = tls . NewListener ( listener , cfg )
2021-02-26 08:09:55 +01:00
}
2023-04-01 22:55:06 +02:00
mainLogger . Info ( "Proxy server started." )
err = server . Serve ( listener )
2021-02-26 08:09:55 +01:00
mainLogger . Critical ( "Server terminated with a reason: %v" , err )
mainLogger . Info ( "Shutting down..." )
return 0
2020-05-19 21:53:13 +02:00
}
func main ( ) {
2021-02-26 08:09:55 +01:00
os . Exit ( run ( ) )
2020-05-19 21:53:13 +02:00
}