serverless-ircd 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/.github/workflows/ci.yml +96 -2
  2. package/.github/workflows/deploy-aws.yml +129 -0
  3. package/.github/workflows/deploy-cf.yml +0 -2
  4. package/.gitmodules +3 -0
  5. package/AGENTS.md +5 -0
  6. package/CHANGELOG.md +352 -0
  7. package/MOTD.txt +3 -0
  8. package/PLAN-FIXES.md +358 -0
  9. package/PLAN.md +420 -0
  10. package/README.md +115 -81
  11. package/apps/aws-stack/README.md +73 -0
  12. package/apps/aws-stack/bin/aws.ts +49 -0
  13. package/apps/aws-stack/cdk.json +10 -0
  14. package/apps/aws-stack/package.json +41 -0
  15. package/apps/aws-stack/scripts/smoke-helpers.d.mts +22 -0
  16. package/apps/aws-stack/scripts/smoke-helpers.mjs +89 -0
  17. package/apps/aws-stack/scripts/smoke.mjs +142 -0
  18. package/apps/aws-stack/src/aws-stack.ts +263 -0
  19. package/apps/aws-stack/src/tables.ts +10 -0
  20. package/apps/aws-stack/tests/localstack.test.ts +46 -0
  21. package/apps/aws-stack/tests/smoke-helpers.test.ts +98 -0
  22. package/apps/aws-stack/tests/stack.test.ts +464 -0
  23. package/apps/aws-stack/tsconfig.build.json +11 -0
  24. package/apps/aws-stack/tsconfig.test.json +8 -0
  25. package/apps/aws-stack/vitest.config.ts +20 -0
  26. package/apps/cf-worker/package.json +1 -1
  27. package/apps/cf-worker/wrangler.toml +25 -0
  28. package/apps/local-cli/package.json +1 -1
  29. package/apps/local-cli/src/config-loader.ts +56 -0
  30. package/apps/local-cli/src/server.ts +241 -32
  31. package/apps/local-cli/tests/config-loader.test.ts +107 -0
  32. package/apps/local-cli/tests/e2e.test.ts +74 -0
  33. package/apps/local-cli/tests/security-e2e.test.ts +239 -0
  34. package/biome.json +3 -1
  35. package/dashboards/cloudwatch-irc.json +139 -0
  36. package/docs/AWS-Adapter-Architecture.md +494 -0
  37. package/docs/AWS-Deployment.md +1107 -0
  38. package/docs/Cloudflare-Deployment-Guide.md +660 -0
  39. package/docs/Home.md +1 -0
  40. package/docs/Observability.md +87 -0
  41. package/docs/PlanIRCv3Websocket.md +489 -0
  42. package/docs/PlanWebClient.md +451 -0
  43. package/docs/Release-Process.md +440 -0
  44. package/package.json +8 -2
  45. package/packages/aws-adapter/README.md +35 -0
  46. package/packages/aws-adapter/package.json +52 -0
  47. package/packages/aws-adapter/src/aws-runtime.ts +783 -0
  48. package/packages/aws-adapter/src/cdk-table-defs.ts +69 -0
  49. package/packages/aws-adapter/src/config-loader.ts +96 -0
  50. package/packages/aws-adapter/src/dynamo.ts +61 -0
  51. package/packages/aws-adapter/src/handlers/connect.ts +44 -0
  52. package/packages/aws-adapter/src/handlers/default.ts +322 -0
  53. package/packages/aws-adapter/src/handlers/disconnect.ts +29 -0
  54. package/packages/aws-adapter/src/handlers/index.ts +248 -0
  55. package/packages/aws-adapter/src/handlers/ping-checker.ts +217 -0
  56. package/packages/aws-adapter/src/handlers/state.ts +13 -0
  57. package/packages/aws-adapter/src/handlers/sweeper.ts +84 -0
  58. package/packages/aws-adapter/src/index.ts +67 -0
  59. package/packages/aws-adapter/src/message-store.ts +34 -0
  60. package/packages/aws-adapter/src/serialize.ts +283 -0
  61. package/packages/aws-adapter/src/tables.ts +53 -0
  62. package/packages/aws-adapter/tests/aws-harness.ts +408 -0
  63. package/packages/aws-adapter/tests/aws-integration.test.ts +17 -0
  64. package/packages/aws-adapter/tests/aws-runtime.test.ts +654 -0
  65. package/packages/aws-adapter/tests/config-loader.test.ts +158 -0
  66. package/packages/aws-adapter/tests/dynamo.test.ts +57 -0
  67. package/packages/aws-adapter/tests/global-setup.ts +301 -0
  68. package/packages/aws-adapter/tests/gone-exception.test.ts +271 -0
  69. package/packages/aws-adapter/tests/handlers.test.ts +427 -0
  70. package/packages/aws-adapter/tests/message-store.test.ts +55 -0
  71. package/packages/aws-adapter/tests/ping-checker.test.ts +571 -0
  72. package/packages/aws-adapter/tests/serialize.test.ts +249 -0
  73. package/packages/aws-adapter/tests/smoke.test.ts +48 -0
  74. package/packages/aws-adapter/tests/sweeper.test.ts +420 -0
  75. package/packages/aws-adapter/tests/tables.test.ts +74 -0
  76. package/packages/aws-adapter/tests/transactions.test.ts +446 -0
  77. package/packages/aws-adapter/tsconfig.build.json +16 -0
  78. package/packages/aws-adapter/tsconfig.test.json +14 -0
  79. package/packages/aws-adapter/vitest.config.ts +23 -0
  80. package/packages/cf-adapter/package.json +2 -1
  81. package/packages/cf-adapter/src/config-loader.ts +106 -0
  82. package/packages/cf-adapter/src/connection-do.ts +34 -0
  83. package/packages/cf-adapter/tests/cf-harness.ts +2 -2
  84. package/packages/cf-adapter/tests/cf-integration.test.ts +2 -2
  85. package/packages/cf-adapter/tests/config-loader.test.ts +149 -0
  86. package/packages/cf-adapter/tests/connection-do.test.ts +42 -0
  87. package/packages/in-memory-runtime/package.json +1 -1
  88. package/packages/in-memory-runtime/src/in-memory-runtime.ts +108 -3
  89. package/packages/in-memory-runtime/src/index.ts +1 -1
  90. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +115 -0
  91. package/packages/irc-core/package.json +12 -2
  92. package/packages/irc-core/src/admission.ts +216 -0
  93. package/packages/irc-core/src/caps/capabilities.ts +1 -0
  94. package/packages/irc-core/src/cloak.ts +81 -0
  95. package/packages/irc-core/src/commands/cap.ts +1 -2
  96. package/packages/irc-core/src/commands/chathistory.ts +305 -0
  97. package/packages/irc-core/src/commands/index.ts +7 -0
  98. package/packages/irc-core/src/commands/join.ts +59 -7
  99. package/packages/irc-core/src/commands/privmsg.ts +24 -0
  100. package/packages/irc-core/src/commands/registration.ts +71 -14
  101. package/packages/irc-core/src/commands/sasl.ts +251 -0
  102. package/packages/irc-core/src/config.ts +247 -0
  103. package/packages/irc-core/src/flood-control.ts +175 -0
  104. package/packages/irc-core/src/index.ts +5 -0
  105. package/packages/irc-core/src/ports.ts +655 -0
  106. package/packages/irc-core/src/protocol/base64.ts +16 -0
  107. package/packages/irc-core/src/protocol/index.ts +2 -1
  108. package/packages/irc-core/src/protocol/numerics.ts +11 -0
  109. package/packages/irc-core/src/protocol/parser.ts +27 -2
  110. package/packages/irc-core/src/state/connection.ts +41 -0
  111. package/packages/irc-core/src/types.ts +66 -2
  112. package/packages/irc-core/stryker.commands.conf.json +41 -0
  113. package/packages/irc-core/stryker.protocol.conf.json +26 -0
  114. package/packages/irc-core/tests/admission.test.ts +229 -0
  115. package/packages/irc-core/tests/caps/capabilities.test.ts +22 -0
  116. package/packages/irc-core/tests/cloak.test.ts +78 -0
  117. package/packages/irc-core/tests/commands/chathistory.test.ts +996 -0
  118. package/packages/irc-core/tests/commands/join.test.ts +246 -2
  119. package/packages/irc-core/tests/commands/privmsg.test.ts +146 -2
  120. package/packages/irc-core/tests/commands/registration.test.ts +277 -9
  121. package/packages/irc-core/tests/commands/sasl.test.ts +536 -0
  122. package/packages/irc-core/tests/config.test.ts +646 -0
  123. package/packages/irc-core/tests/flood-control.test.ts +394 -0
  124. package/packages/irc-core/tests/message-store.test.ts +530 -0
  125. package/packages/irc-core/tests/parser.test.ts +44 -1
  126. package/packages/irc-core/tests/ports.test.ts +263 -0
  127. package/packages/irc-server/package.json +1 -1
  128. package/packages/irc-server/src/actor.ts +162 -44
  129. package/packages/irc-server/src/dispatch.ts +89 -5
  130. package/packages/irc-server/src/index.ts +2 -0
  131. package/packages/irc-server/src/routing.ts +151 -0
  132. package/packages/irc-server/tests/actor.test.ts +470 -14
  133. package/packages/irc-server/tests/dispatch.test.ts +84 -0
  134. package/packages/irc-server/tests/routing.test.ts +201 -0
  135. package/packages/irc-test-support/README.md +32 -0
  136. package/packages/irc-test-support/package.json +37 -0
  137. package/packages/{cf-adapter/tests/integration → irc-test-support/src}/in-memory-harness.ts +1 -1
  138. package/packages/irc-test-support/src/index.ts +24 -0
  139. package/packages/{cf-adapter/tests/integration/harness.test.ts → irc-test-support/tests/in-memory-harness.test.ts} +1 -1
  140. package/packages/{cf-adapter/tests/integration/scenarios.test.ts → irc-test-support/tests/in-memory-scenarios.test.ts} +1 -2
  141. package/packages/irc-test-support/tests/smoke.test.ts +11 -0
  142. package/packages/irc-test-support/tsconfig.build.json +16 -0
  143. package/packages/irc-test-support/tsconfig.test.json +14 -0
  144. package/packages/irc-test-support/vitest.config.ts +20 -0
  145. package/progress.md +107 -0
  146. package/tickets.md +2485 -0
  147. package/tools/ci-hardening/package.json +30 -0
  148. package/tools/ci-hardening/src/index.ts +6 -0
  149. package/tools/ci-hardening/src/validate.ts +103 -0
  150. package/tools/ci-hardening/tests/__no_thresholds__.txt +11 -0
  151. package/tools/ci-hardening/tests/__partial_thresholds__.txt +16 -0
  152. package/tools/ci-hardening/tests/__stryker_bad__.json +4 -0
  153. package/tools/ci-hardening/tests/validate.test.ts +177 -0
  154. package/tools/ci-hardening/tsconfig.build.json +12 -0
  155. package/tools/ci-hardening/tsconfig.test.json +10 -0
  156. package/tools/ci-hardening/vitest.config.ts +21 -0
  157. package/tools/tcp-ws-forwarder/package.json +1 -1
  158. package/tools/tcp-ws-forwarder/src/forwarder.ts +34 -23
  159. package/tools/tcp-ws-forwarder/src/logger.ts +155 -0
  160. package/tools/tcp-ws-forwarder/src/main.ts +33 -14
  161. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +86 -0
  162. package/tools/tcp-ws-forwarder/tests/logger.test.ts +136 -0
  163. package/webircgateway/LICENSE +201 -0
  164. package/webircgateway/Makefile +44 -0
  165. package/webircgateway/README.md +134 -0
  166. package/webircgateway/config.conf.example +135 -0
  167. package/webircgateway/go.mod +16 -0
  168. package/webircgateway/go.sum +89 -0
  169. package/webircgateway/main.go +118 -0
  170. package/webircgateway/pkg/dnsbl/dnsbl.go +121 -0
  171. package/webircgateway/pkg/identd/identd.go +86 -0
  172. package/webircgateway/pkg/identd/rpcclient.go +59 -0
  173. package/webircgateway/pkg/irc/isupport.go +56 -0
  174. package/webircgateway/pkg/irc/message.go +217 -0
  175. package/webircgateway/pkg/irc/state.go +79 -0
  176. package/webircgateway/pkg/proxy/proxy.go +129 -0
  177. package/webircgateway/pkg/proxy/server.go +237 -0
  178. package/webircgateway/pkg/recaptcha/recaptcha.go +59 -0
  179. package/webircgateway/pkg/webircgateway/client.go +741 -0
  180. package/webircgateway/pkg/webircgateway/client_command_handlers.go +495 -0
  181. package/webircgateway/pkg/webircgateway/config.go +385 -0
  182. package/webircgateway/pkg/webircgateway/gateway.go +278 -0
  183. package/webircgateway/pkg/webircgateway/gateway_utils.go +133 -0
  184. package/webircgateway/pkg/webircgateway/hooks.go +152 -0
  185. package/webircgateway/pkg/webircgateway/letsencrypt.go +41 -0
  186. package/webircgateway/pkg/webircgateway/messagetags.go +103 -0
  187. package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +206 -0
  188. package/webircgateway/pkg/webircgateway/transport_sockjs.go +107 -0
  189. package/webircgateway/pkg/webircgateway/transport_tcp.go +113 -0
  190. package/webircgateway/pkg/webircgateway/transport_websocket.go +126 -0
  191. package/webircgateway/pkg/webircgateway/utils.go +147 -0
  192. package/webircgateway/plugins/example/plugin.go +11 -0
  193. package/webircgateway/plugins/stats/plugin.go +52 -0
  194. package/webircgateway/staticcheck.conf +1 -0
  195. package/webircgateway/webircgateway.svg +3 -0
  196. package/packages/cf-adapter/tests/integration/index.ts +0 -17
  197. /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/harness.ts +0 -0
  198. /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/scenarios.ts +0 -0
@@ -0,0 +1,133 @@
1
+ package webircgateway
2
+
3
+ import (
4
+ "errors"
5
+ "math/rand"
6
+ "net"
7
+ "net/http"
8
+ "strings"
9
+ )
10
+
11
+ var v4LoopbackAddr = net.ParseIP("127.0.0.1")
12
+
13
+ func (s *Gateway) NewClient() *Client {
14
+ return NewClient(s)
15
+ }
16
+
17
+ func (s *Gateway) IsClientOriginAllowed(originHeader string) bool {
18
+ // Empty list of origins = all origins allowed
19
+ if len(s.Config.RemoteOrigins) == 0 {
20
+ return true
21
+ }
22
+
23
+ // No origin header = running on the same page
24
+ if originHeader == "" {
25
+ return true
26
+ }
27
+
28
+ foundMatch := false
29
+
30
+ for _, originMatch := range s.Config.RemoteOrigins {
31
+ if originMatch.Match(originHeader) {
32
+ foundMatch = true
33
+ break
34
+ }
35
+ }
36
+
37
+ return foundMatch
38
+ }
39
+
40
+ func (s *Gateway) isIrcAddressAllowed(addr string) bool {
41
+ // Empty whitelist = all destinations allowed
42
+ if len(s.Config.GatewayWhitelist) == 0 {
43
+ return true
44
+ }
45
+
46
+ foundMatch := false
47
+
48
+ for _, addrMatch := range s.Config.GatewayWhitelist {
49
+ if addrMatch.Match(addr) {
50
+ foundMatch = true
51
+ break
52
+ }
53
+ }
54
+
55
+ return foundMatch
56
+ }
57
+
58
+ func (s *Gateway) findUpstream() (ConfigUpstream, error) {
59
+ var ret ConfigUpstream
60
+
61
+ if len(s.Config.Upstreams) == 0 {
62
+ return ret, errors.New("No upstreams available")
63
+ }
64
+
65
+ randIdx := rand.Intn(len(s.Config.Upstreams))
66
+ ret = s.Config.Upstreams[randIdx]
67
+
68
+ return ret, nil
69
+ }
70
+
71
+ func (s *Gateway) findWebircPassword(ircHost string) string {
72
+ pass, exists := s.Config.GatewayWebircPassword[strings.ToLower(ircHost)]
73
+ if !exists {
74
+ pass = ""
75
+ }
76
+
77
+ return pass
78
+ }
79
+
80
+ func (s *Gateway) GetRemoteAddressFromRequest(req *http.Request) net.IP {
81
+ remoteIP := remoteIPFromRequest(req)
82
+
83
+ // If the remoteIP is not in a whitelisted reverse proxy range, don't trust
84
+ // the headers and use the remoteIP as the users IP
85
+ if !s.isTrustedProxy(remoteIP) {
86
+ return remoteIP
87
+ }
88
+
89
+ headerVal := req.Header.Get("x-forwarded-for")
90
+ ips := strings.Split(headerVal, ",")
91
+ ipStr := strings.Trim(ips[0], " ")
92
+ if ipStr != "" {
93
+ ip := net.ParseIP(ipStr)
94
+ if ip != nil {
95
+ remoteIP = ip
96
+ }
97
+ }
98
+
99
+ return remoteIP
100
+
101
+ }
102
+
103
+ func (s *Gateway) isRequestSecure(req *http.Request) bool {
104
+ remoteIP := remoteIPFromRequest(req)
105
+
106
+ // If the remoteIP is not in a whitelisted reverse proxy range, don't trust
107
+ // the headers and check the request directly
108
+ if !s.isTrustedProxy(remoteIP) {
109
+ return req.TLS != nil
110
+ }
111
+
112
+ fwdProto := req.Header.Get("x-forwarded-proto")
113
+ return strings.EqualFold(fwdProto, "https")
114
+ }
115
+
116
+ func (s *Gateway) isTrustedProxy(remoteIP net.IP) bool {
117
+ for _, cidrRange := range s.Config.ReverseProxies {
118
+ if cidrRange.Contains(remoteIP) {
119
+ return true
120
+ }
121
+ }
122
+ return false
123
+ }
124
+
125
+ func remoteIPFromRequest(req *http.Request) net.IP {
126
+ if req.RemoteAddr == "@" {
127
+ // remote address is unix socket, treat it as loopback interface
128
+ return v4LoopbackAddr
129
+ }
130
+
131
+ remoteAddr, _, _ := net.SplitHostPort(req.RemoteAddr)
132
+ return net.ParseIP(remoteAddr)
133
+ }
@@ -0,0 +1,152 @@
1
+ package webircgateway
2
+
3
+ import "github.com/kiwiirc/webircgateway/pkg/irc"
4
+
5
+ var hooksRegistered map[string][]interface{}
6
+
7
+ func init() {
8
+ hooksRegistered = make(map[string][]interface{})
9
+ }
10
+
11
+ func HookRegister(hookName string, p interface{}) {
12
+ _, exists := hooksRegistered[hookName]
13
+ if !exists {
14
+ hooksRegistered[hookName] = make([]interface{}, 0)
15
+ }
16
+
17
+ hooksRegistered[hookName] = append(hooksRegistered[hookName], p)
18
+ }
19
+
20
+ type Hook struct {
21
+ ID string
22
+ Halt bool
23
+ }
24
+
25
+ func (h *Hook) getCallbacks(eventType string) []interface{} {
26
+ var f []interface{}
27
+ f = make([]interface{}, 0)
28
+
29
+ callbacks, exists := hooksRegistered[eventType]
30
+ if exists {
31
+ f = callbacks
32
+ }
33
+
34
+ return f
35
+ }
36
+
37
+ /**
38
+ * HookIrcConnectionPre
39
+ * Dispatched just before an IRC connection is attempted
40
+ * Types: irc.connection.pre
41
+ */
42
+ type HookIrcConnectionPre struct {
43
+ Hook
44
+ Client *Client
45
+ UpstreamConfig *ConfigUpstream
46
+ }
47
+
48
+ func (h *HookIrcConnectionPre) Dispatch(eventType string) {
49
+ for _, p := range h.getCallbacks(eventType) {
50
+ if f, ok := p.(func(*HookIrcConnectionPre)); ok {
51
+ f(h)
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * HookIrcLine
58
+ * Dispatched when either:
59
+ * * A line arrives from the IRCd, before sending to the client
60
+ * * A line arrives from the client, before sending to the IRCd
61
+ * Types: irc.line
62
+ */
63
+ type HookIrcLine struct {
64
+ Hook
65
+ Client *Client
66
+ UpstreamConfig *ConfigUpstream
67
+ Line string
68
+ Message *irc.Message
69
+ ToServer bool
70
+ }
71
+
72
+ func (h *HookIrcLine) Dispatch(eventType string) {
73
+ for _, p := range h.getCallbacks(eventType) {
74
+ if f, ok := p.(func(*HookIrcLine)); ok {
75
+ f(h)
76
+ }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * HookClientState
82
+ * Dispatched after a client connects or disconnects
83
+ * Types: client.state
84
+ */
85
+ type HookClientState struct {
86
+ Hook
87
+ Client *Client
88
+ Connected bool
89
+ }
90
+
91
+ func (h *HookClientState) Dispatch(eventType string) {
92
+ for _, p := range h.getCallbacks(eventType) {
93
+ if f, ok := p.(func(*HookClientState)); ok {
94
+ f(h)
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * HookClientInit
101
+ * Dispatched directly after a new Client instance has been created
102
+ * Types: client.init
103
+ */
104
+ type HookClientInit struct {
105
+ Hook
106
+ Client *Client
107
+ Connected bool
108
+ }
109
+
110
+ func (h *HookClientInit) Dispatch(eventType string) {
111
+ for _, p := range h.getCallbacks(eventType) {
112
+ if f, ok := p.(func(*HookClientInit)); ok {
113
+ f(h)
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * HookStatus
120
+ * Dispatched for each line output of the _status HTTP request
121
+ * Types: status.client
122
+ */
123
+ type HookStatus struct {
124
+ Hook
125
+ Client *Client
126
+ Line string
127
+ }
128
+
129
+ func (h *HookStatus) Dispatch(eventType string) {
130
+ for _, p := range h.getCallbacks(eventType) {
131
+ if f, ok := p.(func(*HookStatus)); ok {
132
+ f(h)
133
+ }
134
+ }
135
+ }
136
+
137
+ /**
138
+ * HookGatewayClosing
139
+ * Dispatched when the gateway has been told to shutdown
140
+ * Types: gateway.closing
141
+ */
142
+ type HookGatewayClosing struct {
143
+ Hook
144
+ }
145
+
146
+ func (h *HookGatewayClosing) Dispatch(eventType string) {
147
+ for _, p := range h.getCallbacks(eventType) {
148
+ if f, ok := p.(func(*HookGatewayClosing)); ok {
149
+ f(h)
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,41 @@
1
+ package webircgateway
2
+
3
+ import (
4
+ "context"
5
+ "strings"
6
+ "sync"
7
+
8
+ "golang.org/x/crypto/acme/autocert"
9
+ )
10
+
11
+ type LEManager struct {
12
+ // ensure only one instance of the manager and handler is running
13
+ // while allowing multiple listeners to use it
14
+ Mutex sync.Mutex
15
+ Manager *autocert.Manager
16
+ gateway *Gateway
17
+ }
18
+
19
+ func NewLetsEncryptManager(gateway *Gateway) *LEManager {
20
+ return &LEManager{gateway: gateway}
21
+ }
22
+
23
+ func (le *LEManager) Get(certCacheDir string) *autocert.Manager {
24
+ le.Mutex.Lock()
25
+ defer le.Mutex.Unlock()
26
+
27
+ // Create it if it doesn't already exist
28
+ if le.Manager == nil {
29
+ le.Manager = &autocert.Manager{
30
+ Prompt: autocert.AcceptTOS,
31
+ Cache: autocert.DirCache(strings.TrimRight(certCacheDir, "/")),
32
+ HostPolicy: func(ctx context.Context, host string) error {
33
+ le.gateway.Log(2, "Automatically requesting a HTTPS certificate for %s", host)
34
+ return nil
35
+ },
36
+ }
37
+ le.gateway.HttpRouter.Handle("/.well-known/", le.Manager.HTTPHandler(nil))
38
+ }
39
+
40
+ return le.Manager
41
+ }
@@ -0,0 +1,103 @@
1
+ package webircgateway
2
+
3
+ import (
4
+ "strings"
5
+ "sync"
6
+ "time"
7
+
8
+ "github.com/OneOfOne/xxhash"
9
+ "github.com/kiwiirc/webircgateway/pkg/irc"
10
+ )
11
+
12
+ type MessageTagManager struct {
13
+ Mutex sync.Mutex
14
+ knownTags map[uint64]MessageTags
15
+ gcTimes map[uint64]time.Time
16
+ }
17
+ type MessageTags struct {
18
+ Tags map[string]string
19
+ }
20
+
21
+ func NewMessageTagManager() *MessageTagManager {
22
+ tm := &MessageTagManager{
23
+ knownTags: make(map[uint64]MessageTags),
24
+ gcTimes: make(map[uint64]time.Time),
25
+ }
26
+
27
+ go tm.RunGarbageCollectionLoop()
28
+ return tm
29
+ }
30
+
31
+ func (tags *MessageTagManager) CanMessageContainClientTags(msg *irc.Message) bool {
32
+ return stringInSlice(msg.Command, []string{
33
+ "PRIVMSG",
34
+ "NOTICE",
35
+ "TAGMSG",
36
+ })
37
+ }
38
+
39
+ func (tags *MessageTagManager) RunGarbageCollectionLoop() {
40
+ for {
41
+ tags.Mutex.Lock()
42
+ for messageHash, timeCreated := range tags.gcTimes {
43
+ if timeCreated.Add(time.Second * 30).After(time.Now()) {
44
+ delete(tags.knownTags, messageHash)
45
+ }
46
+
47
+ }
48
+ tags.Mutex.Unlock()
49
+
50
+ time.Sleep(time.Second * 30)
51
+ }
52
+ }
53
+
54
+ func (tags *MessageTagManager) AddTagsFromMessage(client *Client, fromNick string, msg *irc.Message) {
55
+ if !tags.CanMessageContainClientTags(msg) {
56
+ return
57
+ }
58
+
59
+ clientTags := MessageTags{
60
+ Tags: make(map[string]string),
61
+ }
62
+ for tagName, tagVal := range msg.Tags {
63
+ if len(tagName) > 0 && tagName[0] == '+' {
64
+ clientTags.Tags[tagName] = tagVal
65
+ }
66
+ }
67
+
68
+ if len(clientTags.Tags) > 0 {
69
+ tags.Mutex.Lock()
70
+ msgHash := tags.messageHash(client, fromNick, msg)
71
+ tags.knownTags[msgHash] = clientTags
72
+ tags.gcTimes[msgHash] = time.Now()
73
+ tags.Mutex.Unlock()
74
+ }
75
+ }
76
+
77
+ func (tags *MessageTagManager) GetTagsFromMessage(client *Client, fromNick string, msg *irc.Message) (MessageTags, bool) {
78
+ if !tags.CanMessageContainClientTags(msg) {
79
+ return MessageTags{}, false
80
+ }
81
+
82
+ msgHash := tags.messageHash(client, fromNick, msg)
83
+
84
+ tags.Mutex.Lock()
85
+ defer tags.Mutex.Unlock()
86
+
87
+ clientTags, tagsExist := tags.knownTags[msgHash]
88
+ if !tagsExist {
89
+ return clientTags, false
90
+ }
91
+
92
+ return clientTags, true
93
+ }
94
+
95
+ func (tags *MessageTagManager) messageHash(client *Client, fromNick string, msg *irc.Message) uint64 {
96
+ h := xxhash.New64()
97
+ h.WriteString(strings.ToLower(client.UpstreamConfig.Hostname))
98
+ h.WriteString(strings.ToLower(fromNick))
99
+ // make target case insensitive
100
+ h.WriteString(strings.ToLower(msg.GetParam(0, "")))
101
+ h.WriteString(msg.GetParam(1, ""))
102
+ return h.Sum64()
103
+ }
@@ -0,0 +1,206 @@
1
+ package webircgateway
2
+
3
+ import (
4
+ "fmt"
5
+ "log"
6
+ "net"
7
+ "net/http"
8
+ "runtime/debug"
9
+ "strings"
10
+ "sync"
11
+
12
+ "github.com/gorilla/websocket"
13
+ "github.com/igm/sockjs-go/v3/sockjs"
14
+ cmap "github.com/orcaman/concurrent-map"
15
+ )
16
+
17
+ type TransportKiwiirc struct {
18
+ gateway *Gateway
19
+ }
20
+
21
+ func (t *TransportKiwiirc) Init(g *Gateway) {
22
+ t.gateway = g
23
+ sockjsOptions := sockjs.DefaultOptions
24
+ sockjsOptions.WebsocketUpgrader = &websocket.Upgrader{
25
+ // Origin is checked within the session handler
26
+ CheckOrigin: func(_ *http.Request) bool { return true },
27
+ }
28
+ handler := sockjs.NewHandler("/webirc/kiwiirc", sockjsOptions, t.sessionHandler)
29
+ t.gateway.HttpRouter.Handle("/webirc/kiwiirc/", handler)
30
+ }
31
+
32
+ func (t *TransportKiwiirc) makeChannel(chanID string, ws sockjs.Session) *TransportKiwiircChannel {
33
+ client := t.gateway.NewClient()
34
+
35
+ originHeader := strings.ToLower(ws.Request().Header.Get("Origin"))
36
+ if !t.gateway.IsClientOriginAllowed(originHeader) {
37
+ client.Log(2, "Origin %s not allowed. Closing connection", originHeader)
38
+ ws.Close(0, "Origin not allowed")
39
+ return nil
40
+ }
41
+
42
+ client.RemoteAddr = t.gateway.GetRemoteAddressFromRequest(ws.Request()).String()
43
+
44
+ clientHostnames, err := net.LookupAddr(client.RemoteAddr)
45
+ if err != nil || len(clientHostnames) == 0 {
46
+ client.RemoteHostname = client.RemoteAddr
47
+ } else {
48
+ // FQDNs include a . at the end. Strip it out
49
+ potentialHostname := strings.Trim(clientHostnames[0], ".")
50
+
51
+ // Must check that the resolved hostname also resolves back to the users IP
52
+ addr, err := net.LookupIP(potentialHostname)
53
+ if err == nil && len(addr) == 1 && addr[0].String() == client.RemoteAddr {
54
+ client.RemoteHostname = potentialHostname
55
+ } else {
56
+ client.RemoteHostname = client.RemoteAddr
57
+ }
58
+ }
59
+
60
+ if t.gateway.isRequestSecure(ws.Request()) {
61
+ client.Tags["secure"] = ""
62
+ }
63
+
64
+ // This doesn't make sense to have since the remote port may change between requests. Only
65
+ // here for testing purposes for now.
66
+ _, remoteAddrPort, _ := net.SplitHostPort(ws.Request().RemoteAddr)
67
+ client.Tags["remote-port"] = remoteAddrPort
68
+
69
+ client.Log(2, "New kiwiirc channel on %s from %s %s", ws.Request().Host, client.RemoteAddr, client.RemoteHostname)
70
+ client.Ready()
71
+
72
+ channel := &TransportKiwiircChannel{
73
+ Id: chanID,
74
+ Client: client,
75
+ Conn: ws,
76
+ waitForClose: make(chan bool),
77
+ Closed: false,
78
+ }
79
+
80
+ go channel.listenForSignals()
81
+
82
+ return channel
83
+ }
84
+
85
+ func (t *TransportKiwiirc) sessionHandler(session sockjs.Session) {
86
+ // Don't let a single users error kill the entire service for everyone
87
+ defer func() {
88
+ if r := recover(); r != nil {
89
+ log.Printf("[ERROR] Recovered from %s\n%s", r, debug.Stack())
90
+ }
91
+ }()
92
+
93
+ channels := cmap.New()
94
+
95
+ // Read from sockjs
96
+ go func() {
97
+ for {
98
+ msg, err := session.Recv()
99
+ if err == nil && len(msg) > 0 {
100
+ idEnd := strings.Index(msg, " ")
101
+ if idEnd == -1 {
102
+ // msg is in the form of ":chanId"
103
+ chanID := msg[1:]
104
+
105
+ c, channelExists := channels.Get(chanID)
106
+ if channelExists {
107
+ channel := c.(*TransportKiwiircChannel)
108
+ channel.close()
109
+ }
110
+
111
+ if !channelExists {
112
+ channel := t.makeChannel(chanID, session)
113
+ if channel == nil {
114
+ continue
115
+ }
116
+ channels.Set(chanID, channel)
117
+
118
+ // When the channel closes, remove it from the map again
119
+ go func() {
120
+ <-channel.waitForClose
121
+ channel.Client.Log(2, "Removing channel from connection")
122
+ channels.Remove(chanID)
123
+ }()
124
+ }
125
+
126
+ session.Send(":" + chanID)
127
+
128
+ } else {
129
+ // msg is in the form of ":chanId data"
130
+ chanID := msg[1:idEnd]
131
+ data := msg[idEnd+1:]
132
+
133
+ channel, channelExists := channels.Get(chanID)
134
+ if channelExists {
135
+ c := channel.(*TransportKiwiircChannel)
136
+ c.handleIncomingLine(data)
137
+ }
138
+ }
139
+ } else if err != nil {
140
+ t.gateway.Log(1, "kiwi connection closed (%s)", err.Error())
141
+ break
142
+ }
143
+ }
144
+
145
+ for channel := range channels.IterBuffered() {
146
+ c := channel.Val.(*TransportKiwiircChannel)
147
+ c.Closed = true
148
+ c.Client.StartShutdown("client_closed")
149
+ }
150
+ }()
151
+ }
152
+
153
+ type TransportKiwiircChannel struct {
154
+ Conn sockjs.Session
155
+ Client *Client
156
+ Id string
157
+ waitForClose chan bool
158
+ ClosedLock sync.Mutex
159
+ Closed bool
160
+ }
161
+
162
+ func (c *TransportKiwiircChannel) listenForSignals() {
163
+ for {
164
+ signal, ok := <-c.Client.Signals
165
+ if !ok {
166
+ break
167
+ }
168
+ c.Client.Log(1, "signal:%s %s", signal[0], signal[1])
169
+ if signal[0] == "state" {
170
+ if signal[1] == "connected" {
171
+ c.Conn.Send(fmt.Sprintf(":%s control connected", c.Id))
172
+ } else if signal[1] == "closed" {
173
+ c.Conn.Send(fmt.Sprintf(":%s control closed %s", c.Id, signal[2]))
174
+ }
175
+ }
176
+
177
+ if signal[0] == "data" {
178
+ toSend := strings.Trim(signal[1], "\r\n")
179
+ c.Conn.Send(fmt.Sprintf(":%s %s", c.Id, toSend))
180
+ }
181
+ }
182
+
183
+ c.ClosedLock.Lock()
184
+
185
+ c.Closed = true
186
+ close(c.Client.Recv)
187
+ close(c.waitForClose)
188
+
189
+ c.ClosedLock.Unlock()
190
+ }
191
+
192
+ func (c *TransportKiwiircChannel) handleIncomingLine(line string) {
193
+ c.ClosedLock.Lock()
194
+
195
+ if !c.Closed {
196
+ c.Client.Recv <- line
197
+ }
198
+
199
+ c.ClosedLock.Unlock()
200
+ }
201
+
202
+ func (c *TransportKiwiircChannel) close() {
203
+ if c.Client.upstream != nil {
204
+ c.Client.upstream.Close()
205
+ }
206
+ }