2024-11-23 12:30:23 +09:00
|
|
|
package hodu
|
|
|
|
|
2025-02-01 00:06:05 +09:00
|
|
|
import "crypto/rsa"
|
2025-02-10 14:48:18 +09:00
|
|
|
import "encoding/base64"
|
2025-01-19 01:56:31 +09:00
|
|
|
import "fmt"
|
2024-12-23 01:27:47 +09:00
|
|
|
import "net"
|
2024-12-08 00:57:58 +09:00
|
|
|
import "net/http"
|
2024-12-12 21:09:16 +09:00
|
|
|
import "net/netip"
|
2024-12-08 00:57:58 +09:00
|
|
|
import "os"
|
2025-02-01 00:06:05 +09:00
|
|
|
import "path/filepath"
|
2024-12-08 00:57:58 +09:00
|
|
|
import "runtime"
|
2024-12-12 21:09:16 +09:00
|
|
|
import "strings"
|
2024-11-23 12:30:23 +09:00
|
|
|
import "sync"
|
2025-01-07 15:23:05 +09:00
|
|
|
import "time"
|
2024-11-23 12:30:23 +09:00
|
|
|
|
2024-12-07 21:24:06 +09:00
|
|
|
const HODU_RPC_VERSION uint32 = 0x010000
|
2024-11-23 12:30:23 +09:00
|
|
|
|
|
|
|
type LogLevel int
|
2024-12-09 01:51:04 +09:00
|
|
|
type LogMask int
|
2024-11-23 12:30:23 +09:00
|
|
|
|
|
|
|
const (
|
2024-12-09 01:51:04 +09:00
|
|
|
LOG_DEBUG LogLevel = 1 << iota
|
2024-11-23 12:30:23 +09:00
|
|
|
LOG_INFO
|
2024-12-09 01:51:04 +09:00
|
|
|
LOG_WARN
|
|
|
|
LOG_ERROR
|
2024-11-23 12:30:23 +09:00
|
|
|
)
|
|
|
|
|
2024-12-09 01:59:05 +09:00
|
|
|
const LOG_ALL LogMask = LogMask(LOG_DEBUG | LOG_INFO | LOG_WARN | LOG_ERROR)
|
|
|
|
const LOG_NONE LogMask = LogMask(0)
|
|
|
|
|
2024-12-12 21:09:16 +09:00
|
|
|
var IPV4_PREFIX_ZERO = netip.MustParsePrefix("0.0.0.0/0")
|
|
|
|
var IPV6_PREFIX_ZERO = netip.MustParsePrefix("::/0")
|
|
|
|
|
2025-01-28 00:44:02 +09:00
|
|
|
type Named struct {
|
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
2024-11-23 12:30:23 +09:00
|
|
|
type Logger interface {
|
2024-12-08 23:16:43 +09:00
|
|
|
Write(id string, level LogLevel, fmtstr string, args ...interface{})
|
2024-12-15 15:07:35 +09:00
|
|
|
WriteWithCallDepth(id string, level LogLevel, call_depth int, fmtstr string, args ...interface{})
|
|
|
|
Rotate()
|
2025-01-11 11:48:19 +09:00
|
|
|
Close()
|
2024-11-23 12:30:23 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
type Service interface {
|
2024-12-08 23:16:43 +09:00
|
|
|
RunTask(wg *sync.WaitGroup) // blocking. run the actual task loop. it must call wg.Done() upon exit from itself.
|
2024-11-23 12:30:23 +09:00
|
|
|
StartService(data interface{}) // non-blocking. spin up a service. it may be invokded multiple times for multiple instances
|
|
|
|
StopServices() // non-blocking. send stop request to all services spun up
|
2024-12-14 14:04:33 +09:00
|
|
|
FixServices() // do some fixup as needed
|
2024-11-23 12:30:23 +09:00
|
|
|
WaitForTermination() // blocking. must wait until all services are stopped
|
2024-11-23 20:13:07 +09:00
|
|
|
WriteLog(id string, level LogLevel, fmtstr string, args ...interface{})
|
2024-11-23 12:30:23 +09:00
|
|
|
}
|
2024-12-07 22:18:07 +09:00
|
|
|
|
2025-01-31 19:54:28 +09:00
|
|
|
type HttpAccessAction int
|
|
|
|
const (
|
|
|
|
HTTP_ACCESS_ACCEPT HttpAccessAction = iota
|
|
|
|
HTTP_ACCESS_REJECT
|
|
|
|
HTTP_ACCESS_AUTH_REQUIRED
|
|
|
|
)
|
|
|
|
|
|
|
|
type HttpAccessRule struct {
|
|
|
|
Prefix string
|
|
|
|
OrgNets []netip.Prefix
|
|
|
|
Action HttpAccessAction
|
|
|
|
}
|
|
|
|
|
2025-02-01 00:06:05 +09:00
|
|
|
type HttpAuthCredMap map[string]string
|
|
|
|
|
|
|
|
type HttpAuthConfig struct {
|
|
|
|
Enabled bool
|
|
|
|
Realm string
|
|
|
|
Creds HttpAuthCredMap
|
|
|
|
TokenTtl time.Duration
|
|
|
|
TokenRsaKey *rsa.PrivateKey
|
|
|
|
AccessRules []HttpAccessRule
|
|
|
|
}
|
|
|
|
|
2025-01-28 23:50:28 +09:00
|
|
|
type JsonErrmsg struct {
|
|
|
|
Text string `json:"error-text"`
|
|
|
|
}
|
|
|
|
|
2025-02-04 01:30:19 +09:00
|
|
|
type json_in_cred struct {
|
|
|
|
Username string `json:"username"`
|
|
|
|
Password string `json:"password"`
|
|
|
|
}
|
|
|
|
|
2025-02-18 01:02:26 +09:00
|
|
|
type json_in_notice struct {
|
|
|
|
Text string `json:"text"`
|
|
|
|
}
|
|
|
|
|
2025-01-28 00:44:02 +09:00
|
|
|
type json_out_go_stats struct {
|
|
|
|
CPUs int `json:"cpus"`
|
|
|
|
Goroutines int `json:"goroutines"`
|
|
|
|
NumCgoCalls int64 `json:"num-cgo-calls"`
|
|
|
|
NumGCs uint32 `json:"num-gcs"`
|
|
|
|
AllocBytes uint64 `json:"memory-alloc-bytes"`
|
|
|
|
TotalAllocBytes uint64 `json:"memory-total-alloc-bytes"`
|
|
|
|
SysBytes uint64 `json:"memory-sys-bytes"`
|
|
|
|
Lookups uint64 `json:"memory-lookups"`
|
|
|
|
MemAllocs uint64 `json:"memory-num-allocs"`
|
|
|
|
MemFrees uint64 `json:"memory-num-frees"`
|
|
|
|
|
|
|
|
HeapAllocBytes uint64 `json:"memory-heap-alloc-bytes"`
|
|
|
|
HeapSysBytes uint64 `json:"memory-heap-sys-bytes"`
|
|
|
|
HeapIdleBytes uint64 `json:"memory-heap-idle-bytes"`
|
|
|
|
HeapInuseBytes uint64 `json:"memory-heap-inuse-bytes"`
|
|
|
|
HeapReleasedBytes uint64 `json:"memory-heap-released-bytes"`
|
|
|
|
HeapObjects uint64 `json:"memory-heap-objects"`
|
|
|
|
StackInuseBytes uint64 `json:"memory-stack-inuse-bytes"`
|
|
|
|
StackSysBytes uint64 `json:"memory-stack-sys-bytes"`
|
|
|
|
MSpanInuseBytes uint64 `json:"memory-mspan-inuse-bytes"`
|
|
|
|
MSpanSysBytes uint64 `json:"memory-mspan-sys-bytes"`
|
|
|
|
MCacheInuseBytes uint64 `json:"memory-mcache-inuse-bytes"`
|
|
|
|
MCacheSysBytes uint64 `json:"memory-mcache-sys-bytes"`
|
|
|
|
BuckHashSysBytes uint64 `json:"memory-buck-hash-sys-bytes"`
|
|
|
|
GCSysBytes uint64 `json:"memory-gc-sys-bytes"`
|
|
|
|
OtherSysBytes uint64 `json:"memory-other-sys-bytes"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Named) SetName(name string) {
|
|
|
|
n.name = name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Named) Name() string {
|
|
|
|
return n.name
|
|
|
|
}
|
|
|
|
|
2025-01-08 17:32:40 +09:00
|
|
|
func TcpAddrStrClass(addr string) string {
|
2024-12-12 21:09:16 +09:00
|
|
|
// the string is supposed to be addr:port
|
|
|
|
|
2024-12-07 23:03:23 +09:00
|
|
|
if len(addr) > 0 {
|
2024-12-12 21:09:16 +09:00
|
|
|
var ap netip.AddrPort
|
|
|
|
var err error
|
|
|
|
ap, err = netip.ParseAddrPort(addr)
|
|
|
|
if err == nil {
|
|
|
|
if ap.Addr().Is6() { return "tcp6" }
|
2025-01-16 01:26:58 +09:00
|
|
|
if ap.Addr().Is4() || ap.Addr().Is4In6() { return "tcp4" }
|
2024-12-07 23:03:23 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "tcp"
|
|
|
|
}
|
2024-12-08 00:57:58 +09:00
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func TcpAddrClass(addr *net.TCPAddr) string {
|
2025-01-16 01:26:58 +09:00
|
|
|
var netip_addr netip.Addr
|
|
|
|
netip_addr = addr.AddrPort().Addr()
|
|
|
|
if netip_addr.Is4() || netip_addr.Is4In6() {
|
2025-01-15 01:19:50 +09:00
|
|
|
return "tcp4"
|
|
|
|
} else {
|
|
|
|
return "tcp6"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-13 02:25:27 +09:00
|
|
|
func word_to_route_option(word string) RouteOption {
|
2024-12-12 21:09:16 +09:00
|
|
|
switch word {
|
|
|
|
case "tcp4":
|
|
|
|
return RouteOption(ROUTE_OPTION_TCP4)
|
|
|
|
case "tcp6":
|
|
|
|
return RouteOption(ROUTE_OPTION_TCP6)
|
|
|
|
case "tcp":
|
|
|
|
return RouteOption(ROUTE_OPTION_TCP)
|
|
|
|
case "tty":
|
|
|
|
return RouteOption(ROUTE_OPTION_TTY)
|
|
|
|
case "http":
|
|
|
|
return RouteOption(ROUTE_OPTION_HTTP)
|
|
|
|
case "https":
|
|
|
|
return RouteOption(ROUTE_OPTION_HTTPS)
|
2024-12-13 02:25:27 +09:00
|
|
|
case "ssh":
|
|
|
|
return RouteOption(ROUTE_OPTION_SSH)
|
2024-12-12 21:09:16 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
return RouteOption(ROUTE_OPTION_UNSPEC)
|
|
|
|
}
|
|
|
|
|
2025-01-19 01:56:31 +09:00
|
|
|
func StringToRouteOption(desc string) RouteOption {
|
2024-12-12 21:09:16 +09:00
|
|
|
var fld string
|
2024-12-13 02:25:27 +09:00
|
|
|
var option RouteOption
|
2024-12-12 21:09:16 +09:00
|
|
|
var p RouteOption
|
|
|
|
|
2024-12-13 02:25:27 +09:00
|
|
|
option = RouteOption(0)
|
2024-12-12 21:09:16 +09:00
|
|
|
for _, fld = range strings.Fields(desc) {
|
2024-12-13 02:25:27 +09:00
|
|
|
p = word_to_route_option(fld)
|
2024-12-12 21:09:16 +09:00
|
|
|
if p == RouteOption(ROUTE_OPTION_UNSPEC) { return p }
|
2024-12-13 02:25:27 +09:00
|
|
|
option |= p
|
2024-12-12 21:09:16 +09:00
|
|
|
}
|
2024-12-13 02:25:27 +09:00
|
|
|
return option
|
2024-12-12 21:09:16 +09:00
|
|
|
}
|
|
|
|
|
2025-01-16 01:26:58 +09:00
|
|
|
func (option RouteOption) String() string {
|
2024-12-12 21:09:16 +09:00
|
|
|
var str string
|
|
|
|
str = ""
|
2024-12-13 02:25:27 +09:00
|
|
|
if option & RouteOption(ROUTE_OPTION_TCP6) != 0 { str += " tcp6" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_TCP4) != 0 { str += " tcp4" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_TCP) != 0 { str += " tcp" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_TTY) != 0 { str += " tty" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_HTTP) != 0 { str += " http" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_HTTPS) != 0 { str += " https" }
|
|
|
|
if option & RouteOption(ROUTE_OPTION_SSH) != 0 { str += " ssh" }
|
2024-12-12 21:09:16 +09:00
|
|
|
if str == "" { return str }
|
|
|
|
return str[1:] // remove the leading space
|
|
|
|
}
|
|
|
|
|
2024-12-08 00:57:58 +09:00
|
|
|
func dump_call_frame_and_exit(log Logger, req *http.Request, err interface{}) {
|
|
|
|
var buf []byte
|
|
|
|
buf = make([]byte, 65536); buf = buf[:min(65536, runtime.Stack(buf, false))]
|
|
|
|
log.Write("", LOG_ERROR, "[%s] %s %s - %v\n%s", req.RemoteAddr, req.Method, req.URL.String(), err, string(buf))
|
2025-01-11 11:48:19 +09:00
|
|
|
log.Close()
|
2024-12-08 00:57:58 +09:00
|
|
|
os.Exit(99) // fatal error. treat panic() as a fatal runtime error
|
|
|
|
}
|
2024-12-23 01:27:47 +09:00
|
|
|
|
|
|
|
func svc_addr_to_dst_addr (svc_addr *net.TCPAddr) *net.TCPAddr {
|
|
|
|
var addr net.TCPAddr
|
|
|
|
|
|
|
|
addr = *svc_addr
|
|
|
|
if addr.IP.To4() != nil {
|
|
|
|
if addr.IP.IsUnspecified() {
|
|
|
|
addr.IP = net.IPv4(127, 0, 0, 1) // net.IPv4loopback is not defined. so use net.IPv4()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if addr.IP.IsUnspecified() {
|
|
|
|
addr.IP = net.IPv6loopback // net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &addr
|
|
|
|
}
|
2025-01-07 15:23:05 +09:00
|
|
|
|
|
|
|
func is_digit_or_period(r rune) bool {
|
|
|
|
return (r >= '0' && r <= '9') || r == '.'
|
|
|
|
}
|
|
|
|
|
|
|
|
func get_last_rune_of_non_empty_string(s string) rune {
|
|
|
|
var tmp []rune
|
|
|
|
// the string must not be blank for this to work
|
|
|
|
tmp = []rune(s)
|
|
|
|
return tmp[len(tmp) - 1]
|
|
|
|
}
|
|
|
|
|
2025-01-19 01:56:31 +09:00
|
|
|
func ParseDurationString(dur string) (time.Duration, error) {
|
2025-01-07 15:23:05 +09:00
|
|
|
// i want the input to be in seconds with resolution of 9 digits after
|
|
|
|
// the decimal point. For example, 0.05 to mean 500ms.
|
|
|
|
// however, i don't care if a unit is part of the input.
|
|
|
|
var tmp string
|
|
|
|
|
|
|
|
if dur == "" { return 0, nil }
|
|
|
|
|
|
|
|
tmp = dur
|
|
|
|
if is_digit_or_period(get_last_rune_of_non_empty_string(tmp)) { tmp = tmp + "s" }
|
|
|
|
return time.ParseDuration(tmp)
|
|
|
|
}
|
2025-01-13 20:17:49 +09:00
|
|
|
|
2025-01-19 01:56:31 +09:00
|
|
|
func DurationToSecString(d time.Duration) string {
|
|
|
|
return fmt.Sprintf("%.09f", d.Seconds())
|
|
|
|
}
|
|
|
|
|
2025-02-01 00:06:05 +09:00
|
|
|
// ------------------------------------
|
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func WriteJsonRespHeader(w http.ResponseWriter, status_code int) int {
|
2025-01-13 20:17:49 +09:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
|
|
|
|
2025-02-01 00:06:05 +09:00
|
|
|
func WriteJsRespHeader(w http.ResponseWriter, status_code int) int {
|
2025-01-13 20:17:49 +09:00
|
|
|
w.Header().Set("Content-Type", "application/javascript")
|
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func WriteCssRespHeader(w http.ResponseWriter, status_code int) int {
|
2025-01-13 20:17:49 +09:00
|
|
|
w.Header().Set("Content-Type", "text/css")
|
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func WriteHtmlRespHeader(w http.ResponseWriter, status_code int) int {
|
2025-01-13 20:17:49 +09:00
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
|
|
|
|
2025-01-15 01:19:50 +09:00
|
|
|
func WriteEmptyRespHeader(w http.ResponseWriter, status_code int) int {
|
2025-01-13 20:17:49 +09:00
|
|
|
w.WriteHeader(status_code)
|
|
|
|
return status_code
|
|
|
|
}
|
2025-01-14 14:09:52 +09:00
|
|
|
|
2025-02-01 00:06:05 +09:00
|
|
|
// ------------------------------------
|
|
|
|
|
2025-01-14 14:09:52 +09:00
|
|
|
func server_route_to_proxy_info(r *ServerRoute) *ServerRouteProxyInfo {
|
|
|
|
return &ServerRouteProxyInfo{
|
|
|
|
SvcOption: r.SvcOption,
|
|
|
|
PtcName: r.PtcName,
|
|
|
|
PtcAddr: r.PtcAddr,
|
|
|
|
SvcAddr: r.SvcAddr,
|
|
|
|
SvcPermNet: r.SvcPermNet,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func proxy_info_to_server_route(pi *ServerRouteProxyInfo) *ServerRoute {
|
|
|
|
return &ServerRoute{
|
|
|
|
SvcOption: pi.SvcOption,
|
|
|
|
PtcName: pi.PtcName,
|
|
|
|
PtcAddr: pi.PtcAddr,
|
|
|
|
SvcAddr: pi.SvcAddr,
|
|
|
|
SvcPermNet: pi.SvcPermNet,
|
|
|
|
}
|
|
|
|
}
|
2025-01-28 00:44:02 +09:00
|
|
|
|
2025-02-01 00:06:05 +09:00
|
|
|
// ------------------------------------
|
|
|
|
|
2025-01-28 00:44:02 +09:00
|
|
|
func (stats *json_out_go_stats) from_runtime_stats() {
|
|
|
|
var mstat runtime.MemStats
|
|
|
|
|
|
|
|
runtime.ReadMemStats(&mstat)
|
|
|
|
|
|
|
|
stats.CPUs = runtime.NumCPU()
|
|
|
|
stats.Goroutines = runtime.NumGoroutine()
|
|
|
|
stats.NumCgoCalls = runtime.NumCgoCall()
|
|
|
|
stats.NumGCs = mstat.NumGC
|
|
|
|
|
|
|
|
stats.AllocBytes = mstat.Alloc
|
|
|
|
stats.TotalAllocBytes = mstat.TotalAlloc
|
|
|
|
stats.SysBytes = mstat.Sys
|
|
|
|
stats.Lookups = mstat.Lookups
|
|
|
|
stats.MemAllocs = mstat.Mallocs
|
|
|
|
stats.MemFrees = mstat.Frees
|
|
|
|
|
|
|
|
stats.HeapAllocBytes = mstat.HeapAlloc
|
|
|
|
stats.HeapSysBytes = mstat.HeapSys
|
|
|
|
stats.HeapIdleBytes = mstat.HeapIdle
|
|
|
|
stats.HeapInuseBytes = mstat.HeapInuse
|
|
|
|
stats.HeapReleasedBytes = mstat.HeapReleased
|
|
|
|
stats.HeapObjects = mstat.HeapObjects
|
|
|
|
stats.StackInuseBytes = mstat.StackInuse
|
|
|
|
stats.StackSysBytes = mstat.StackSys
|
|
|
|
stats.MSpanInuseBytes = mstat.MSpanInuse
|
|
|
|
stats.MSpanSysBytes = mstat.MSpanSys
|
|
|
|
stats.MCacheInuseBytes = mstat.MCacheInuse
|
|
|
|
stats.MCacheSysBytes = mstat.MCacheSys
|
|
|
|
stats.BuckHashSysBytes = mstat.BuckHashSys
|
|
|
|
stats.GCSysBytes = mstat.GCSys
|
|
|
|
stats.OtherSysBytes = mstat.OtherSys
|
|
|
|
}
|
2025-02-01 00:06:05 +09:00
|
|
|
|
|
|
|
// ------------------------------------
|
|
|
|
|
|
|
|
func (auth *HttpAuthConfig) Authenticate(req *http.Request) (int, string) {
|
|
|
|
var rule HttpAccessRule
|
|
|
|
var raddrport netip.AddrPort
|
|
|
|
var raddr netip.Addr
|
|
|
|
var err error
|
|
|
|
|
|
|
|
raddrport, err = netip.ParseAddrPort(req.RemoteAddr)
|
|
|
|
if err == nil { raddr = raddrport.Addr() }
|
|
|
|
|
|
|
|
for _, rule = range auth.AccessRules {
|
|
|
|
// i don't take into account X-Forwarded-For and similar headers
|
|
|
|
if req.URL.Path == rule.Prefix || strings.HasPrefix(req.URL.Path, filepath.Clean(rule.Prefix + "/")) {
|
|
|
|
var org_net_ok bool
|
|
|
|
|
|
|
|
if len(rule.OrgNets) > 0 && raddr.IsValid() {
|
|
|
|
var netpfx netip.Prefix
|
|
|
|
|
|
|
|
org_net_ok = false
|
|
|
|
for _, netpfx = range rule.OrgNets {
|
|
|
|
if err == nil && netpfx.Contains(raddr) {
|
|
|
|
org_net_ok = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
org_net_ok = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if org_net_ok {
|
|
|
|
if rule.Action == HTTP_ACCESS_ACCEPT {
|
|
|
|
return http.StatusOK, ""
|
|
|
|
} else if rule.Action == HTTP_ACCESS_REJECT {
|
|
|
|
return http.StatusForbidden, ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if auth != nil && auth.Enabled {
|
|
|
|
var auth_hdr string
|
|
|
|
var username string
|
|
|
|
var password string
|
|
|
|
var credpass string
|
|
|
|
var ok bool
|
|
|
|
|
|
|
|
auth_hdr = req.Header.Get("Authorization")
|
2025-02-04 01:30:19 +09:00
|
|
|
if auth_hdr != "" {
|
|
|
|
var auth_parts []string
|
|
|
|
|
|
|
|
auth_parts = strings.Fields(auth_hdr)
|
|
|
|
if len(auth_parts) == 2 && strings.EqualFold(auth_parts[0], "Bearer") && auth.TokenRsaKey != nil {
|
|
|
|
var jwt *JWT[ServerTokenClaim]
|
|
|
|
var claim ServerTokenClaim
|
|
|
|
jwt = NewJWT(auth.TokenRsaKey, &claim)
|
|
|
|
err = jwt.VerifyRS512(strings.TrimSpace(auth_parts[1]))
|
|
|
|
if err == nil {
|
|
|
|
// verification ok. let's check the actual payload
|
|
|
|
var now time.Time
|
|
|
|
now = time.Now()
|
|
|
|
if now.After(time.Unix(claim.IssuedAt, 0)) && now.Before(time.Unix(claim.ExpiresAt, 0)) { return http.StatusOK, "" } // not expired
|
|
|
|
}
|
2025-02-01 00:06:05 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-10 14:48:18 +09:00
|
|
|
// this application wants these two header values to be base64-encoded
|
2025-02-04 01:30:19 +09:00
|
|
|
username = req.Header.Get("X-Auth-Username")
|
|
|
|
password = req.Header.Get("X-Auth-Password")
|
2025-02-10 14:48:18 +09:00
|
|
|
if username != "" {
|
|
|
|
var tmp []byte
|
|
|
|
tmp, err = base64.StdEncoding.DecodeString(username)
|
|
|
|
if err != nil { return http.StatusBadRequest, "" }
|
|
|
|
username = string(tmp)
|
|
|
|
}
|
|
|
|
if password != "" {
|
|
|
|
var tmp []byte
|
|
|
|
tmp, err = base64.StdEncoding.DecodeString(password)
|
|
|
|
if err != nil { return http.StatusBadRequest, "" }
|
|
|
|
password = string(tmp)
|
|
|
|
}
|
2025-02-04 01:30:19 +09:00
|
|
|
|
2025-02-01 00:06:05 +09:00
|
|
|
// fall back to basic authentication
|
2025-02-04 01:30:19 +09:00
|
|
|
if username == "" && password == "" && auth.Realm != "" {
|
|
|
|
username, password, ok = req.BasicAuth()
|
|
|
|
if !ok { return http.StatusUnauthorized, auth.Realm }
|
|
|
|
}
|
2025-02-01 00:06:05 +09:00
|
|
|
|
|
|
|
credpass, ok = auth.Creds[username]
|
2025-02-04 01:30:19 +09:00
|
|
|
if !ok || credpass != password {
|
|
|
|
return http.StatusUnauthorized, auth.Realm
|
|
|
|
}
|
2025-02-01 00:06:05 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
return http.StatusOK, ""
|
|
|
|
}
|