From f737c34a3e566bd2319e87dea7bec3a3ed3d7b0d Mon Sep 17 00:00:00 2001 From: greysoh Date: Sat, 7 Dec 2024 00:21:33 -0500 Subject: [PATCH] chore: Initial commit. --- .gitignore | 2 + go.mod | 8 ++ go.sum | 4 + main.go | 219 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore index 5b90e79..a5096c7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ go.work.sum # env file .env +# Build artifacts +hostess diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..81e3b9d --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module git.greysoh.dev/imterah/hostess + +go 1.23.3 + +require ( + github.com/gorilla/websocket v1.5.3 // indirect + golang.org/x/net v0.31.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cc64875 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..46c208d --- /dev/null +++ b/main.go @@ -0,0 +1,219 @@ +package main + +import ( + "fmt" + "log" + "net" + "net/http" + "strings" + + "github.com/gorilla/websocket" +) + +var isLoggingEnabled bool + +func logRequest(method, path string, respStatus int) { + if isLoggingEnabled { + log.Printf("%s %s - %d", method, path, respStatus) + } +} + +func main() { + listenAddr := ":8080" + sourceIP := "http://192.168.2.10" + hostHeader := "immich.hofers.cloud" + + isLoggingEnabled = true + + client := &http.Client{} + wsUpgrader := &websocket.Upgrader{} + wsDialer := &websocket.Dialer{ + NetDial: func(network, addr string) (net.Conn, error) { + spoofedAddr := sourceIP[strings.LastIndex(sourceIP, "/")+1:] + + if strings.Index(spoofedAddr, ":") == -1 { + if strings.HasPrefix(sourceIP, "http://") { + spoofedAddr += ":80" + } else if strings.HasPrefix(sourceIP, "https://") { + spoofedAddr += ":443" + } + } + + fmt.Printf("spoofed address (in dialer): %s\n", spoofedAddr) + + conn, err := net.Dial(network, spoofedAddr) + return conn, err + }, + } + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // TODO: Currently incomplete + if websocket.IsWebSocketUpgrade(r) { + fmt.Println("detected WebSocket upgrade request. doing initialization tasks...") + remoteAddr := "" + + if strings.HasPrefix(sourceIP, "http://") { + remoteAddr += "ws://" + } + + if strings.HasPrefix(sourceIP, "https://") { + remoteAddr += "wss://" + } + + remoteAddr += hostHeader + + // I'd like to preserve more headers, but Cookies is the only easy thing to implement... + newClientHeaders := make(http.Header) + + newClientHeaders.Add("Cookie", r.Header.Get("Cookie")) + + fmt.Printf("spoofed address (in handler): %s\n", remoteAddr) + wsClient, resp, err := wsDialer.Dial(remoteAddr, newClientHeaders) + + if err != nil { + log.Printf("failed to initialize client's (remote server) WebSocket for '%s': %s", r.URL.Path, err.Error()) + http.Error(w, "Internal Server Error", 500) + + logRequest(r.Method, r.URL.Path, 500) + return + } + + for key, values := range resp.Header { + for _, value := range values { + w.Header().Add(key, value) + } + } + + wsServer, err := wsUpgrader.Upgrade(w, r, w.Header()) + + if err != nil { + log.Printf("failed to initialize server's (us) WebSocket for '%s': %s", r.URL.Path, err.Error()) + http.Error(w, "Internal Server Error", 500) + + logRequest(r.Method, r.URL.Path, 500) + return + } + + go func() { + for { + messageType, message, err := wsServer.ReadMessage() + + if err != nil || messageType == websocket.CloseMessage { + if err != nil && err.Error() != "EOF" { + log.Printf("failed to read server's (us) WebSocket for '%s': %s", r.URL.Path, err.Error()) + } + + err := wsClient.Close() + + if err != nil { + log.Printf("failed to close client's (remote server) WebSocket for '%s': %s", r.URL.Path, err.Error()) + return + } + + break + } + + wsClient.WriteMessage(messageType, message) + } + }() + + go func() { + for { + messageType, message, err := wsClient.ReadMessage() + + if err != nil || messageType == websocket.CloseMessage { + if err != nil && err.Error() != "EOF" { + log.Printf("failed to read client's (remote server) WebSocket for '%s': %s", r.URL.Path, err.Error()) + } + + err := wsServer.Close() + + if err != nil { + log.Printf("failed to close server's (remote server) WebSocket for '%s': %s", r.URL.Path, err.Error()) + return + } + + break + } + + wsServer.WriteMessage(messageType, message) + } + }() + + logRequest(r.Method, r.URL.Path, 101) + + return + } + + req, err := http.NewRequest(r.Method, sourceIP+r.URL.Path, r.Body) + + if err != nil { + log.Printf("failed to construct request for '%s': %s", r.URL.Path, err.Error()) + http.Error(w, "Internal Server Error", 500) + + logRequest(r.Method, r.URL.Path, r.Response.StatusCode) + return + } + + req.Host = hostHeader + + for key, values := range r.Header { + if key == "Host" { + continue + } + + for _, value := range values { + req.Header.Add(key, value) + } + } + + resp, err := client.Do(req) + + if err != nil { + log.Printf("failed to handle response for '%s': %s", r.URL.Path, err.Error()) + http.Error(w, "Internal Server Error", 500) + + logRequest(r.Method, r.URL.Path, r.Response.StatusCode) + return + } + + for key, values := range resp.Header { + for _, value := range values { + w.Header().Add(key, value) + } + } + + /* + if resp.StatusCode >= 300 && resp.StatusCode <= 399 { + existingLocationHeaderUnparsed := resp.Header.Get("Location") + + if existingLocationHeaderUnparsed != "" { + w.Header().Set("Location", existingLocationHeaderUnparsed) + } + } + */ + + w.WriteHeader(resp.StatusCode) + + totalBytesRead := int64(0) + byteBuffer := make([]byte, 65535) + + for totalBytesRead != resp.ContentLength { + readContents, err := resp.Body.Read(byteBuffer) + totalBytesRead += int64(readContents) + + if err != nil && totalBytesRead != resp.ContentLength { + log.Printf("failed to either read or finish reading response for '%s': %s", r.URL.Path, err.Error()) + logRequest(r.Method, r.URL.Path, resp.StatusCode) + return + } + + w.Write(byteBuffer[:readContents]) + } + + logRequest(r.Method, r.URL.Path, resp.StatusCode) + }) + + log.Printf("Hostess is listening on %s", listenAddr) + log.Fatal(http.ListenAndServe(listenAddr, nil)) +}