mirror of
https://github.com/bolucat/Archive.git
synced 2025-09-27 04:30:12 +08:00
234 lines
5.6 KiB
Go
234 lines
5.6 KiB
Go
package forwardproxy
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
|
|
caddy "github.com/caddyserver/caddy/v2"
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
|
)
|
|
|
|
func init() {
|
|
httpcaddyfile.RegisterHandlerDirective("forward_proxy", parseCaddyfile)
|
|
httpcaddyfile.RegisterDirectiveOrder("forward_proxy", httpcaddyfile.After, "file_server")
|
|
}
|
|
|
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
|
var fp Handler
|
|
err := fp.UnmarshalCaddyfile(h.Dispenser)
|
|
return &fp, err
|
|
}
|
|
|
|
// EncodeAuthCredentials base64-encode credentials
|
|
func EncodeAuthCredentials(user, pass string) (result []byte) {
|
|
raw := []byte(user + ":" + pass)
|
|
result = make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
|
|
base64.StdEncoding.Encode(result, raw)
|
|
return
|
|
}
|
|
|
|
// UnmarshalCaddyfile unmarshals Caddyfile tokens into h.
|
|
func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|
d.Next() // consume directive name
|
|
|
|
args := d.RemainingArgs()
|
|
if len(args) > 0 {
|
|
return d.ArgErr()
|
|
}
|
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
|
switch d.Val() {
|
|
case "basic_auth":
|
|
args := d.RemainingArgs()
|
|
if len(args) != 2 {
|
|
return d.ArgErr()
|
|
}
|
|
if len(args[0]) == 0 {
|
|
return d.Err("empty usernames are not allowed")
|
|
}
|
|
// TODO: Evaluate policy of allowing empty passwords.
|
|
if strings.Contains(args[0], ":") {
|
|
return d.Err("character ':' in usernames is not allowed")
|
|
}
|
|
if h.AuthCredentials == nil {
|
|
h.AuthCredentials = [][]byte{}
|
|
}
|
|
h.AuthCredentials = append(h.AuthCredentials, EncodeAuthCredentials(args[0], args[1]))
|
|
|
|
case "hosts":
|
|
args := d.RemainingArgs()
|
|
if len(args) == 0 {
|
|
return d.ArgErr()
|
|
}
|
|
if len(h.Hosts) != 0 {
|
|
return d.Err("hosts subdirective specified twice")
|
|
}
|
|
h.Hosts = caddyhttp.MatchHost(args)
|
|
|
|
case "ports":
|
|
args := d.RemainingArgs()
|
|
if len(args) == 0 {
|
|
return d.ArgErr()
|
|
}
|
|
if len(h.AllowedPorts) != 0 {
|
|
return d.Err("ports subdirective specified twice")
|
|
}
|
|
h.AllowedPorts = make([]int, len(args))
|
|
for i, p := range args {
|
|
intPort, err := strconv.Atoi(p)
|
|
if intPort <= 0 || intPort > 65535 || err != nil {
|
|
return d.Errf("ports are expected to be space-separated and in 0-65535 range, but got: %s", p)
|
|
}
|
|
h.AllowedPorts[i] = intPort
|
|
}
|
|
|
|
case "hide_ip":
|
|
args := d.RemainingArgs()
|
|
if len(args) != 0 {
|
|
return d.ArgErr()
|
|
}
|
|
h.HideIP = true
|
|
|
|
case "hide_via":
|
|
args := d.RemainingArgs()
|
|
if len(args) != 0 {
|
|
return d.ArgErr()
|
|
}
|
|
h.HideVia = true
|
|
|
|
case "disable_insecure_upstreams_check":
|
|
args := d.RemainingArgs()
|
|
if len(args) != 0 {
|
|
return d.ArgErr()
|
|
}
|
|
h.DisableInsecureUpstreamsCheck = true
|
|
|
|
case "probe_resistance":
|
|
args := d.RemainingArgs()
|
|
if len(args) > 1 {
|
|
return d.ArgErr()
|
|
}
|
|
if len(args) == 1 {
|
|
lowercaseArg := strings.ToLower(args[0])
|
|
if lowercaseArg != args[0] {
|
|
log.Println("[WARNING] Secret domain appears to have uppercase letters in it, which are not visitable")
|
|
}
|
|
h.ProbeResistance = &ProbeResistance{Domain: args[0]}
|
|
} else {
|
|
h.ProbeResistance = &ProbeResistance{}
|
|
}
|
|
|
|
case "serve_pac":
|
|
args := d.RemainingArgs()
|
|
if len(args) > 1 {
|
|
return d.ArgErr()
|
|
}
|
|
if len(h.PACPath) != 0 {
|
|
return d.Err("serve_pac subdirective specified twice")
|
|
}
|
|
if len(args) == 1 {
|
|
h.PACPath = args[0]
|
|
if !strings.HasPrefix(h.PACPath, "/") {
|
|
h.PACPath = "/" + h.PACPath
|
|
}
|
|
} else {
|
|
h.PACPath = "/proxy.pac"
|
|
}
|
|
|
|
case "dial_timeout":
|
|
args := d.RemainingArgs()
|
|
if len(args) != 1 {
|
|
return d.ArgErr()
|
|
}
|
|
timeout, err := caddy.ParseDuration(args[0])
|
|
if err != nil {
|
|
return d.ArgErr()
|
|
}
|
|
if timeout < 0 {
|
|
return d.Err("dial_timeout cannot be negative.")
|
|
}
|
|
h.DialTimeout = caddy.Duration(timeout)
|
|
|
|
case "max_idle_conns":
|
|
args := d.RemainingArgs()
|
|
if len(args) != 1 {
|
|
return d.ArgErr()
|
|
}
|
|
val, err := strconv.Atoi(args[0])
|
|
if err != nil {
|
|
return d.ArgErr()
|
|
}
|
|
h.MaxIdleConns = val
|
|
|
|
case "max_idle_conns_per_host":
|
|
args := d.RemainingArgs()
|
|
if len(args) != 1 {
|
|
return d.ArgErr()
|
|
}
|
|
val, err := strconv.Atoi(args[0])
|
|
if err != nil {
|
|
return d.ArgErr()
|
|
}
|
|
h.MaxIdleConnsPerHost = val
|
|
|
|
case "upstream":
|
|
args := d.RemainingArgs()
|
|
if len(args) != 1 {
|
|
return d.ArgErr()
|
|
}
|
|
if h.Upstream != "" {
|
|
return d.Err("upstream directive specified more than once")
|
|
}
|
|
h.Upstream = args[0]
|
|
|
|
case "acl":
|
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
|
aclDirective := d.Val()
|
|
args := d.RemainingArgs()
|
|
if len(args) == 0 {
|
|
return d.ArgErr()
|
|
}
|
|
var ruleSubjects []string
|
|
var err error
|
|
aclAllow := false
|
|
switch aclDirective {
|
|
case "allow":
|
|
ruleSubjects = args
|
|
aclAllow = true
|
|
case "allow_file":
|
|
if len(args) != 1 {
|
|
return d.Err("allowfile accepts a single filename argument")
|
|
}
|
|
ruleSubjects, err = readLinesFromFile(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aclAllow = true
|
|
case "deny":
|
|
ruleSubjects = args
|
|
case "deny_file":
|
|
if len(args) != 1 {
|
|
return d.Err("denyfile accepts a single filename argument")
|
|
}
|
|
ruleSubjects, err = readLinesFromFile(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return d.Err("expected acl directive: allow/allowfile/deny/denyfile." +
|
|
"got: " + aclDirective)
|
|
}
|
|
ar := ACLRule{Subjects: ruleSubjects, Allow: aclAllow}
|
|
h.ACL = append(h.ACL, ar)
|
|
}
|
|
|
|
default:
|
|
return d.ArgErr()
|
|
}
|
|
}
|
|
return nil
|
|
}
|