diff options
Diffstat (limited to 'deprecated-webircgateway/pkg/webircgateway/transport_kiwiirc.go')
| -rw-r--r-- | deprecated-webircgateway/pkg/webircgateway/transport_kiwiirc.go | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/deprecated-webircgateway/pkg/webircgateway/transport_kiwiirc.go b/deprecated-webircgateway/pkg/webircgateway/transport_kiwiirc.go new file mode 100644 index 0000000..e5247e8 --- /dev/null +++ b/deprecated-webircgateway/pkg/webircgateway/transport_kiwiirc.go @@ -0,0 +1,206 @@ +package webircgateway + +import ( + "fmt" + "log" + "net" + "net/http" + "runtime/debug" + "strings" + "sync" + + "github.com/gorilla/websocket" + "github.com/igm/sockjs-go/v3/sockjs" + cmap "github.com/orcaman/concurrent-map" +) + +type TransportKiwiirc struct { + gateway *Gateway +} + +func (t *TransportKiwiirc) Init(g *Gateway) { + t.gateway = g + sockjsOptions := sockjs.DefaultOptions + sockjsOptions.WebsocketUpgrader = &websocket.Upgrader{ + // Origin is checked within the session handler + CheckOrigin: func(_ *http.Request) bool { return true }, + } + handler := sockjs.NewHandler("/webirc/kiwiirc", sockjsOptions, t.sessionHandler) + t.gateway.HttpRouter.Handle("/webirc/kiwiirc/", handler) +} + +func (t *TransportKiwiirc) makeChannel(chanID string, ws sockjs.Session) *TransportKiwiircChannel { + client := t.gateway.NewClient() + + originHeader := strings.ToLower(ws.Request().Header.Get("Origin")) + if !t.gateway.IsClientOriginAllowed(originHeader) { + client.Log(2, "Origin %s not allowed. Closing connection", originHeader) + ws.Close(0, "Origin not allowed") + return nil + } + + client.RemoteAddr = t.gateway.GetRemoteAddressFromRequest(ws.Request()).String() + + clientHostnames, err := net.LookupAddr(client.RemoteAddr) + if err != nil || len(clientHostnames) == 0 { + client.RemoteHostname = client.RemoteAddr + } else { + // FQDNs include a . at the end. Strip it out + potentialHostname := strings.Trim(clientHostnames[0], ".") + + // Must check that the resolved hostname also resolves back to the users IP + addr, err := net.LookupIP(potentialHostname) + if err == nil && len(addr) == 1 && addr[0].String() == client.RemoteAddr { + client.RemoteHostname = potentialHostname + } else { + client.RemoteHostname = client.RemoteAddr + } + } + + if t.gateway.isRequestSecure(ws.Request()) { + client.Tags["secure"] = "" + } + + // This doesn't make sense to have since the remote port may change between requests. Only + // here for testing purposes for now. + _, remoteAddrPort, _ := net.SplitHostPort(ws.Request().RemoteAddr) + client.Tags["remote-port"] = remoteAddrPort + + client.Log(2, "New kiwiirc channel on %s from %s %s", ws.Request().Host, client.RemoteAddr, client.RemoteHostname) + client.Ready() + + channel := &TransportKiwiircChannel{ + Id: chanID, + Client: client, + Conn: ws, + waitForClose: make(chan bool), + Closed: false, + } + + go channel.listenForSignals() + + return channel +} + +func (t *TransportKiwiirc) sessionHandler(session sockjs.Session) { + // Don't let a single users error kill the entire service for everyone + defer func() { + if r := recover(); r != nil { + log.Printf("[ERROR] Recovered from %s\n%s", r, debug.Stack()) + } + }() + + channels := cmap.New() + + // Read from sockjs + go func() { + for { + msg, err := session.Recv() + if err == nil && len(msg) > 0 { + idEnd := strings.Index(msg, " ") + if idEnd == -1 { + // msg is in the form of ":chanId" + chanID := msg[1:] + + c, channelExists := channels.Get(chanID) + if channelExists { + channel := c.(*TransportKiwiircChannel) + channel.close() + } + + if !channelExists { + channel := t.makeChannel(chanID, session) + if channel == nil { + continue + } + channels.Set(chanID, channel) + + // When the channel closes, remove it from the map again + go func() { + <-channel.waitForClose + channel.Client.Log(2, "Removing channel from connection") + channels.Remove(chanID) + }() + } + + session.Send(":" + chanID) + + } else { + // msg is in the form of ":chanId data" + chanID := msg[1:idEnd] + data := msg[idEnd+1:] + + channel, channelExists := channels.Get(chanID) + if channelExists { + c := channel.(*TransportKiwiircChannel) + c.handleIncomingLine(data) + } + } + } else if err != nil { + t.gateway.Log(1, "kiwi connection closed (%s)", err.Error()) + break + } + } + + for channel := range channels.IterBuffered() { + c := channel.Val.(*TransportKiwiircChannel) + c.Closed = true + c.Client.StartShutdown("client_closed") + } + }() +} + +type TransportKiwiircChannel struct { + Conn sockjs.Session + Client *Client + Id string + waitForClose chan bool + ClosedLock sync.Mutex + Closed bool +} + +func (c *TransportKiwiircChannel) listenForSignals() { + for { + signal, ok := <-c.Client.Signals + if !ok { + break + } + c.Client.Log(1, "signal:%s %s", signal[0], signal[1]) + if signal[0] == "state" { + if signal[1] == "connected" { + c.Conn.Send(fmt.Sprintf(":%s control connected", c.Id)) + } else if signal[1] == "closed" { + c.Conn.Send(fmt.Sprintf(":%s control closed %s", c.Id, signal[2])) + } + } + + if signal[0] == "data" { + toSend := strings.Trim(signal[1], "\r\n") + c.Conn.Send(fmt.Sprintf(":%s %s", c.Id, toSend)) + } + } + + c.ClosedLock.Lock() + + c.Closed = true + close(c.Client.Recv) + close(c.waitForClose) + + c.ClosedLock.Unlock() +} + +func (c *TransportKiwiircChannel) handleIncomingLine(line string) { + c.ClosedLock.Lock() + + if !c.Closed { + c.Client.Recv <- line + } + + c.ClosedLock.Unlock() +} + +func (c *TransportKiwiircChannel) close() { + if c.Client.upstream != nil { + c.Client.upstream.Close() + } +} |
