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,741 @@
1
+ package webircgateway
2
+
3
+ import (
4
+ "bufio"
5
+ "crypto/tls"
6
+ "errors"
7
+ "fmt"
8
+ "io"
9
+ "net"
10
+ "runtime/debug"
11
+ "strconv"
12
+ "strings"
13
+ "sync"
14
+ "sync/atomic"
15
+ "syscall"
16
+ "time"
17
+
18
+ "golang.org/x/time/rate"
19
+
20
+ "github.com/kiwiirc/webircgateway/pkg/dnsbl"
21
+ "github.com/kiwiirc/webircgateway/pkg/irc"
22
+ "github.com/kiwiirc/webircgateway/pkg/proxy"
23
+ )
24
+
25
+ const (
26
+ // ClientStateIdle - Client connected and just sat there
27
+ ClientStateIdle = "idle"
28
+ // ClientStateConnecting - Connecting upstream
29
+ ClientStateConnecting = "connecting"
30
+ // ClientStateRegistering - Registering to the IRC network
31
+ ClientStateRegistering = "registering"
32
+ // ClientStateConnected - Connected upstream
33
+ ClientStateConnected = "connected"
34
+ // ClientStateEnding - Client is ending its connection
35
+ ClientStateEnding = "ending"
36
+ )
37
+
38
+ type ClientSignal [3]string
39
+
40
+ // Client - Connecting client struct
41
+ type Client struct {
42
+ Gateway *Gateway
43
+ Id uint64
44
+ State string
45
+ EndWG sync.WaitGroup
46
+ shuttingDownLock sync.Mutex
47
+ shuttingDown bool
48
+ SeenQuit bool
49
+ Recv chan string
50
+ ThrottledRecv *ThrottledStringChannel
51
+ upstream io.ReadWriteCloser
52
+ UpstreamRecv chan string
53
+ UpstreamSend chan string
54
+ UpstreamStarted bool
55
+ UpstreamConfig *ConfigUpstream
56
+ RemoteAddr string
57
+ RemoteHostname string
58
+ RemotePort int
59
+ DestHost string
60
+ DestPort int
61
+ DestTLS bool
62
+ IrcState *irc.State
63
+ Encoding string
64
+ // Tags get passed upstream via the WEBIRC command
65
+ Tags map[string]string
66
+ // Captchas may be needed to verify a client
67
+ RequiresVerification bool
68
+ Verified bool
69
+ SentPass bool
70
+ // Signals for the transport to make use of (data, connection state, etc)
71
+ Signals chan ClientSignal
72
+ Features struct {
73
+ Messagetags bool
74
+ Metadata bool
75
+ ExtJwt bool
76
+ }
77
+ // The specific message-tags CAP that the client has requested if we are wrapping it
78
+ RequestedMessageTagsCap string
79
+ // Prefix used by the server when sending its own messages
80
+ ServerMessagePrefix irc.Mask
81
+ }
82
+
83
+ var nextClientID uint64 = 1
84
+
85
+ // NewClient - Makes a new client
86
+ func NewClient(gateway *Gateway) *Client {
87
+ thisID := atomic.AddUint64(&nextClientID, 1)
88
+
89
+ recv := make(chan string, 50)
90
+ c := &Client{
91
+ Gateway: gateway,
92
+ Id: thisID,
93
+ State: ClientStateIdle,
94
+ Recv: recv,
95
+ ThrottledRecv: NewThrottledStringChannel(recv, rate.NewLimiter(rate.Inf, 1)),
96
+ UpstreamSend: make(chan string, 50),
97
+ UpstreamRecv: make(chan string, 50),
98
+ Encoding: "UTF-8",
99
+ Signals: make(chan ClientSignal, 50),
100
+ Tags: make(map[string]string),
101
+ IrcState: irc.NewState(),
102
+ UpstreamConfig: &ConfigUpstream{},
103
+ }
104
+
105
+ // Auto enable some features by default. They may be disabled later on
106
+ c.Features.ExtJwt = true
107
+
108
+ c.RequiresVerification = gateway.Config.RequiresVerification
109
+
110
+ // Handles data to/from the client and upstreams
111
+ go c.clientLineWorker()
112
+
113
+ // This Add(1) will be ended once the client starts shutting down in StartShutdown()
114
+ c.EndWG.Add(1)
115
+
116
+ // Add to the clients maps and wait until everything has been marked
117
+ // as completed (several routines add themselves to EndWG so that we can catch
118
+ // when they are all completed)
119
+ gateway.Clients.Set(strconv.FormatUint(c.Id, 10), c)
120
+ go func() {
121
+ c.EndWG.Wait()
122
+ gateway.Clients.Remove(strconv.FormatUint(c.Id, 10))
123
+
124
+ hook := &HookClientState{
125
+ Client: c,
126
+ Connected: false,
127
+ }
128
+ hook.Dispatch("client.state")
129
+ }()
130
+
131
+ hook := &HookClientState{
132
+ Client: c,
133
+ Connected: true,
134
+ }
135
+ hook.Dispatch("client.state")
136
+
137
+ return c
138
+ }
139
+
140
+ // Log - Log a line of text with context of this client
141
+ func (c *Client) Log(level int, format string, args ...interface{}) {
142
+ prefix := fmt.Sprintf("client:%d ", c.Id)
143
+ c.Gateway.Log(level, prefix+format, args...)
144
+ }
145
+
146
+ // TrafficLog - Log out raw IRC traffic
147
+ func (c *Client) TrafficLog(isUpstream bool, toGateway bool, traffic string) {
148
+ label := ""
149
+ if isUpstream && toGateway {
150
+ label = "Upstream->"
151
+ } else if isUpstream && !toGateway {
152
+ label = "->Upstream"
153
+ } else if !isUpstream && toGateway {
154
+ label = "Client->"
155
+ } else if !isUpstream && !toGateway {
156
+ label = "->Client"
157
+ }
158
+ c.Log(1, "Traffic (%s) %s", label, traffic)
159
+ }
160
+
161
+ func (c *Client) Ready() {
162
+ dnsblAction := c.Gateway.Config.DnsblAction
163
+ validAction := dnsblAction == "verify" || dnsblAction == "deny"
164
+ dnsblTookAction := ""
165
+
166
+ if len(c.Gateway.Config.DnsblServers) > 0 && c.RemoteAddr != "" && !c.Verified && validAction {
167
+ dnsblTookAction = c.checkDnsBl()
168
+ }
169
+
170
+ if dnsblTookAction == "" && c.Gateway.Config.RequiresVerification && !c.Verified {
171
+ c.SendClientSignal("data", "CAPTCHA NEEDED")
172
+ }
173
+ }
174
+
175
+ func (c *Client) checkDnsBl() (tookAction string) {
176
+ dnsResult := dnsbl.Lookup(c.Gateway.Config.DnsblServers, c.RemoteAddr)
177
+ if dnsResult.Listed && c.Gateway.Config.DnsblAction == "deny" {
178
+ c.SendIrcError("Blocked by DNSBL")
179
+ c.SendClientSignal("state", "closed", "dnsbl_listed")
180
+ c.StartShutdown("dnsbl")
181
+ tookAction = "deny"
182
+ } else if dnsResult.Listed && c.Gateway.Config.DnsblAction == "verify" {
183
+ c.RequiresVerification = true
184
+ c.SendClientSignal("data", "CAPTCHA NEEDED")
185
+ tookAction = "verify"
186
+ }
187
+
188
+ return
189
+ }
190
+
191
+ func (c *Client) IsShuttingDown() bool {
192
+ c.shuttingDownLock.Lock()
193
+ defer c.shuttingDownLock.Unlock()
194
+ return c.shuttingDown
195
+ }
196
+
197
+ func (c *Client) StartShutdown(reason string) {
198
+ c.shuttingDownLock.Lock()
199
+ defer c.shuttingDownLock.Unlock()
200
+
201
+ c.Log(1, "StartShutdown(%s) ShuttingDown=%t", reason, c.shuttingDown)
202
+ if !c.shuttingDown {
203
+ c.shuttingDown = true
204
+ c.State = ClientStateEnding
205
+
206
+ switch reason {
207
+ case "upstream_closed":
208
+ c.Log(2, "Upstream closed the connection")
209
+ case "err_connecting_upstream":
210
+ case "err_no_upstream":
211
+ // Error has been logged already
212
+ case "client_closed":
213
+ c.Log(2, "Client disconnected")
214
+ default:
215
+ c.Log(2, "Closed: %s", reason)
216
+ }
217
+
218
+ close(c.Signals)
219
+ c.EndWG.Done()
220
+ }
221
+ }
222
+
223
+ func (c *Client) SendClientSignal(signal string, args ...string) {
224
+ c.shuttingDownLock.Lock()
225
+ defer c.shuttingDownLock.Unlock()
226
+
227
+ if !c.shuttingDown {
228
+ switch len(args) {
229
+ case 0:
230
+ c.Signals <- ClientSignal{signal}
231
+ case 1:
232
+ c.Signals <- ClientSignal{signal, args[0]}
233
+ case 2:
234
+ c.Signals <- ClientSignal{signal, args[0], args[1]}
235
+ }
236
+ }
237
+ }
238
+
239
+ func (c *Client) SendIrcError(message string) {
240
+ c.SendClientSignal("data", "ERROR :"+message)
241
+ }
242
+
243
+ func (c *Client) SendIrcFail(params ...string) {
244
+ failMessage := irc.Message{
245
+ Command: "FAIL",
246
+ Params: params,
247
+ }
248
+ c.SendClientSignal("data", failMessage.ToLine())
249
+ }
250
+
251
+ func (c *Client) connectUpstream() {
252
+ client := c
253
+
254
+ c.UpstreamStarted = true
255
+
256
+ var upstreamConfig ConfigUpstream
257
+
258
+ if client.DestHost == "" {
259
+ client.Log(2, "Using configured upstream")
260
+ var err error
261
+ upstreamConfig, err = c.Gateway.findUpstream()
262
+ if err != nil {
263
+ client.Log(3, "No upstreams available")
264
+ client.SendIrcError("The server has not been configured")
265
+ client.StartShutdown("err_no_upstream")
266
+ return
267
+ }
268
+ } else {
269
+ if !c.Gateway.isIrcAddressAllowed(client.DestHost) {
270
+ client.Log(2, "Server %s is not allowed. Closing connection", client.DestHost)
271
+ client.SendIrcError("Not allowed to connect to " + client.DestHost)
272
+ client.SendClientSignal("state", "closed", "err_forbidden")
273
+ client.StartShutdown("err_no_upstream")
274
+ return
275
+ }
276
+
277
+ client.Log(2, "Using client given upstream")
278
+ upstreamConfig = c.configureUpstream()
279
+ }
280
+
281
+ c.UpstreamConfig = &upstreamConfig
282
+
283
+ hook := &HookIrcConnectionPre{
284
+ Client: client,
285
+ UpstreamConfig: &upstreamConfig,
286
+ }
287
+ hook.Dispatch("irc.connection.pre")
288
+ if hook.Halt {
289
+ client.SendClientSignal("state", "closed", "err_forbidden")
290
+ client.StartShutdown("err_connecting_upstream")
291
+ return
292
+ }
293
+
294
+ client.State = ClientStateConnecting
295
+
296
+ upstream, upstreamErr := client.makeUpstreamConnection()
297
+ if upstreamErr != nil {
298
+ // Error handling was already managed in makeUpstreamConnection()
299
+ return
300
+ }
301
+
302
+ client.State = ClientStateRegistering
303
+
304
+ client.upstream = upstream
305
+ client.readUpstream()
306
+ client.writeWebircLines(upstream)
307
+ client.maybeSendPass(upstream)
308
+ client.SendClientSignal("state", "connected")
309
+ }
310
+
311
+ func (c *Client) makeUpstreamConnection() (io.ReadWriteCloser, error) {
312
+ client := c
313
+ upstreamConfig := c.UpstreamConfig
314
+
315
+ var connection io.ReadWriteCloser
316
+
317
+ if upstreamConfig.Proxy == nil {
318
+ // Connect directly to the IRCd
319
+ dialer := net.Dialer{}
320
+ dialer.Timeout = time.Second * time.Duration(upstreamConfig.Timeout)
321
+
322
+ if upstreamConfig.LocalAddr != "" {
323
+ parsedIP := net.ParseIP(upstreamConfig.LocalAddr)
324
+ if parsedIP != nil {
325
+ dialer.LocalAddr = &net.TCPAddr{
326
+ IP: parsedIP,
327
+ Port: 0,
328
+ }
329
+ } else {
330
+ client.Log(3, "Failed to parse localaddr for upstream connection \"%s\"", upstreamConfig.LocalAddr)
331
+ }
332
+ }
333
+
334
+ var conn net.Conn
335
+ var connErr error
336
+ if upstreamConfig.Protocol == "unix" {
337
+ conn, connErr = dialer.Dial("unix", upstreamConfig.Hostname)
338
+ } else {
339
+ upstreamStr := fmt.Sprintf("%s:%d", upstreamConfig.Hostname, upstreamConfig.Port)
340
+ conn, connErr = dialer.Dial(upstreamConfig.Protocol, upstreamStr)
341
+ }
342
+
343
+ if connErr != nil {
344
+ client.Log(3, "Error connecting to the upstream IRCd. %s", connErr.Error())
345
+ errString := ""
346
+ if errString = typeOfErr(connErr); errString != "" {
347
+ errString = "err_" + errString
348
+ }
349
+ client.SendClientSignal("state", "closed", errString)
350
+ client.StartShutdown("err_connecting_upstream")
351
+ return nil, errors.New("error connecting upstream")
352
+ }
353
+
354
+ // Add the ports into the identd before possible TLS handshaking. If we do it after then
355
+ // there's a good chance the identd lookup will occur before the handshake has finished
356
+ if c.Gateway.Config.Identd {
357
+ // Keep track of the upstreams local and remote port numbers
358
+ _, lPortStr, _ := net.SplitHostPort(conn.LocalAddr().String())
359
+ client.IrcState.LocalPort, _ = strconv.Atoi(lPortStr)
360
+ _, rPortStr, _ := net.SplitHostPort(conn.RemoteAddr().String())
361
+ client.IrcState.RemotePort, _ = strconv.Atoi(rPortStr)
362
+
363
+ c.Gateway.identdServ.AddIdent(client.IrcState.LocalPort, client.IrcState.RemotePort, client.IrcState.Username, "")
364
+ }
365
+
366
+ if upstreamConfig.TLS {
367
+ tlsConfig := &tls.Config{InsecureSkipVerify: true}
368
+ tlsConn := tls.Client(conn, tlsConfig)
369
+ err := tlsConn.Handshake()
370
+ if err != nil {
371
+ client.Log(3, "Error connecting to the upstream IRCd. %s", err.Error())
372
+ client.SendClientSignal("state", "closed", "err_tls")
373
+ client.StartShutdown("err_connecting_upstream")
374
+ return nil, errors.New("error connecting upstream")
375
+ }
376
+
377
+ conn = net.Conn(tlsConn)
378
+ }
379
+
380
+ connection = conn
381
+ }
382
+
383
+ if upstreamConfig.Proxy != nil {
384
+ // Connect to the IRCd via a proxy
385
+ conn := proxy.MakeKiwiProxyConnection()
386
+ conn.DestHost = upstreamConfig.Hostname
387
+ conn.DestPort = upstreamConfig.Port
388
+ conn.DestTLS = upstreamConfig.TLS
389
+ conn.Username = upstreamConfig.Proxy.Username
390
+ conn.ProxyInterface = upstreamConfig.Proxy.Interface
391
+
392
+ dialErr := conn.Dial(fmt.Sprintf(
393
+ "%s:%d",
394
+ upstreamConfig.Proxy.Hostname,
395
+ upstreamConfig.Proxy.Port,
396
+ ))
397
+
398
+ if dialErr != nil {
399
+ errString := ""
400
+ if errString = typeOfErr(dialErr); errString != "" {
401
+ errString = "err_" + errString
402
+ } else {
403
+ errString = "err_proxy"
404
+ }
405
+ client.Log(3,
406
+ "Error connecting to the kiwi proxy, %s:%d. %s",
407
+ upstreamConfig.Proxy.Hostname,
408
+ upstreamConfig.Proxy.Port,
409
+ dialErr.Error(),
410
+ )
411
+
412
+ client.SendClientSignal("state", "closed", errString)
413
+ client.StartShutdown("err_connecting_upstream")
414
+ return nil, errors.New("error connecting upstream")
415
+ }
416
+
417
+ connection = conn
418
+ }
419
+
420
+ return connection, nil
421
+ }
422
+
423
+ func (c *Client) writeWebircLines(upstream io.ReadWriteCloser) {
424
+ // Send any WEBIRC lines
425
+ if c.UpstreamConfig.WebircPassword == "" {
426
+ c.Log(1, "No webirc to send")
427
+ return
428
+ }
429
+
430
+ gatewayName := "webircgateway"
431
+ if c.Gateway.Config.GatewayName != "" {
432
+ gatewayName = c.Gateway.Config.GatewayName
433
+ }
434
+ if c.UpstreamConfig.GatewayName != "" {
435
+ gatewayName = c.UpstreamConfig.GatewayName
436
+ }
437
+
438
+ webircTags := c.buildWebircTags()
439
+ if strings.Contains(webircTags, " ") {
440
+ webircTags = ":" + webircTags
441
+ }
442
+
443
+ clientHostname := c.RemoteHostname
444
+ if c.Gateway.Config.ClientHostname != "" {
445
+ clientHostname = makeClientReplacements(c.Gateway.Config.ClientHostname, c)
446
+ }
447
+
448
+ remoteAddr := c.RemoteAddr
449
+ // Prefix IPv6 addresses that start with a : so they can be sent as an individual IRC
450
+ // parameter. eg. ::1 would not parse correctly as a parameter, while 0::1 will
451
+ if strings.HasPrefix(remoteAddr, ":") {
452
+ remoteAddr = "0" + remoteAddr
453
+ }
454
+
455
+ webircLine := fmt.Sprintf(
456
+ "WEBIRC %s %s %s %s %s\n",
457
+ c.UpstreamConfig.WebircPassword,
458
+ gatewayName,
459
+ clientHostname,
460
+ remoteAddr,
461
+ webircTags,
462
+ )
463
+ c.Log(1, "->upstream: %s", webircLine)
464
+ upstream.Write([]byte(webircLine))
465
+ }
466
+
467
+ func (c *Client) maybeSendPass(upstream io.ReadWriteCloser) {
468
+ if c.UpstreamConfig.ServerPassword == "" {
469
+ return
470
+ }
471
+ c.SentPass = true
472
+ passLine := fmt.Sprintf(
473
+ "PASS %s\n",
474
+ c.UpstreamConfig.ServerPassword,
475
+ )
476
+ c.Log(1, "->upstream: %s", passLine)
477
+ upstream.Write([]byte(passLine))
478
+ }
479
+
480
+ func (c *Client) processLineToUpstream(data string) {
481
+ client := c
482
+ upstreamConfig := c.UpstreamConfig
483
+
484
+ if strings.HasPrefix(data, "PASS ") && c.SentPass {
485
+ // Hijack the PASS command if we already sent a pass command
486
+ return
487
+ } else if strings.HasPrefix(data, "USER ") {
488
+ // Hijack the USER command as we may have some overrides
489
+ data = fmt.Sprintf(
490
+ "USER %s 0 * :%s",
491
+ client.IrcState.Username,
492
+ client.IrcState.RealName,
493
+ )
494
+ } else if strings.HasPrefix(strings.ToUpper(data), "QUIT ") {
495
+ client.SeenQuit = true
496
+ }
497
+
498
+ message, _ := irc.ParseLine(data)
499
+
500
+ hook := &HookIrcLine{
501
+ Client: client,
502
+ UpstreamConfig: upstreamConfig,
503
+ Line: data,
504
+ Message: message,
505
+ ToServer: true,
506
+ }
507
+ hook.Dispatch("irc.line")
508
+ if hook.Halt {
509
+ return
510
+ }
511
+
512
+ // Plugins may have modified the data
513
+ data = hook.Line
514
+
515
+ c.TrafficLog(true, false, data)
516
+ data = utf8ToOther(data, client.Encoding)
517
+ if data == "" {
518
+ client.Log(1, "Failed to encode into '%s'. Dropping data", c.Encoding)
519
+ return
520
+ }
521
+
522
+ if client.upstream != nil {
523
+ client.upstream.Write([]byte(data + "\r\n"))
524
+ } else {
525
+ client.Log(2, "Tried sending data upstream before connected")
526
+ }
527
+ }
528
+
529
+ func (c *Client) handleLineFromUpstream(data string) {
530
+ client := c
531
+ upstreamConfig := c.UpstreamConfig
532
+
533
+ message, _ := irc.ParseLine(data)
534
+
535
+ hook := &HookIrcLine{
536
+ Client: client,
537
+ UpstreamConfig: upstreamConfig,
538
+ Line: data,
539
+ Message: message,
540
+ ToServer: false,
541
+ }
542
+ hook.Dispatch("irc.line")
543
+ if hook.Halt {
544
+ return
545
+ }
546
+
547
+ // Plugins may have modified the data
548
+ data = hook.Line
549
+
550
+ if data == "" {
551
+ return
552
+ }
553
+
554
+ data = ensureUtf8(data, client.Encoding)
555
+ if data == "" {
556
+ client.Log(1, "Failed to decode as 'UTF-8'. Dropping data")
557
+ return
558
+ }
559
+
560
+ data = client.ProcessLineFromUpstream(data)
561
+ if data == "" {
562
+ return
563
+ }
564
+
565
+ client.SendClientSignal("data", data)
566
+ }
567
+
568
+ func typeOfErr(err error) string {
569
+ if err == nil {
570
+ return ""
571
+ }
572
+
573
+ if netError, ok := err.(net.Error); ok && netError.Timeout() {
574
+ return "timeout"
575
+ }
576
+
577
+ switch t := err.(type) {
578
+ case *proxy.ConnError:
579
+ switch t.Type {
580
+ case "conn_reset":
581
+ return ""
582
+ case "conn_refused":
583
+ return "refused"
584
+ case "not_found":
585
+ return "unknown_host"
586
+ case "conn_timeout":
587
+ return "timeout"
588
+ default:
589
+ return ""
590
+ }
591
+
592
+ case *net.OpError:
593
+ if t.Op == "dial" {
594
+ return "unknown_host"
595
+ } else if t.Op == "read" {
596
+ return "refused"
597
+ }
598
+
599
+ case syscall.Errno:
600
+ if t == syscall.ECONNREFUSED {
601
+ return "refused"
602
+ }
603
+ }
604
+
605
+ return ""
606
+ }
607
+
608
+ func (c *Client) readUpstream() {
609
+ client := c
610
+
611
+ // Data from upstream to client
612
+ go func() {
613
+ reader := bufio.NewReader(client.upstream)
614
+ for {
615
+ data, err := reader.ReadString('\n')
616
+ if err != nil {
617
+ break
618
+ }
619
+
620
+ data = strings.Trim(data, "\n\r")
621
+ client.UpstreamRecv <- data
622
+ }
623
+
624
+ close(client.UpstreamRecv)
625
+ client.upstream.Close()
626
+ client.upstream = nil
627
+
628
+ if client.IrcState.RemotePort > 0 {
629
+ c.Gateway.identdServ.RemoveIdent(client.IrcState.LocalPort, client.IrcState.RemotePort, "")
630
+ }
631
+ }()
632
+ }
633
+
634
+ // Handle lines sent from the client
635
+ func (c *Client) clientLineWorker() {
636
+ for {
637
+ shouldQuit, _ := c.handleDataLine()
638
+ if shouldQuit {
639
+ break
640
+ }
641
+
642
+ }
643
+
644
+ c.Log(1, "leaving clientLineWorker")
645
+ }
646
+
647
+ func (c *Client) handleDataLine() (shouldQuit bool, hadErr bool) {
648
+ defer func() {
649
+ if err := recover(); err != nil {
650
+ c.Log(3, fmt.Sprint("Error handling data ", err))
651
+ fmt.Println("Error handling data", err)
652
+ debug.PrintStack()
653
+ shouldQuit = false
654
+ hadErr = true
655
+ }
656
+ }()
657
+
658
+ // We only want to send data upstream if we have an upstream connection
659
+ upstreamSend := c.UpstreamSend
660
+ if c.upstream == nil {
661
+ upstreamSend = nil
662
+ }
663
+
664
+ select {
665
+ case clientData, ok := <-c.ThrottledRecv.Output:
666
+ if !ok {
667
+ c.Log(1, "client.Recv closed")
668
+ if !c.SeenQuit && c.Gateway.Config.SendQuitOnClientClose != "" && c.State == ClientStateEnding {
669
+ c.processLineToUpstream("QUIT :" + c.Gateway.Config.SendQuitOnClientClose)
670
+ }
671
+
672
+ c.StartShutdown("client_closed")
673
+
674
+ if c.upstream != nil {
675
+ c.upstream.Close()
676
+ }
677
+ return true, false
678
+ }
679
+ c.Log(1, "in c.ThrottledRecv.Output")
680
+ c.TrafficLog(false, true, clientData)
681
+
682
+ clientLine, err := c.ProcessLineFromClient(clientData)
683
+ if err == nil && clientLine != "" {
684
+ c.UpstreamSend <- clientLine
685
+ }
686
+
687
+ case line, ok := <-upstreamSend:
688
+ if !ok {
689
+ c.Log(1, "client.UpstreamSend closed")
690
+ return true, false
691
+ }
692
+ c.Log(1, "in .UpstreamSend")
693
+ c.processLineToUpstream(line)
694
+
695
+ case upstreamData, ok := <-c.UpstreamRecv:
696
+ if !ok {
697
+ c.Log(1, "client.UpstreamRecv closed")
698
+ c.SendClientSignal("state", "closed")
699
+ c.StartShutdown("upstream_closed")
700
+ return true, false
701
+ }
702
+ c.Log(1, "in .UpstreamRecv")
703
+ c.TrafficLog(true, true, upstreamData)
704
+
705
+ c.handleLineFromUpstream(upstreamData)
706
+ }
707
+
708
+ return false, false
709
+ }
710
+
711
+ // configureUpstream - Generate an upstream configuration from the information set on the client instance
712
+ func (c *Client) configureUpstream() ConfigUpstream {
713
+ upstreamConfig := ConfigUpstream{}
714
+ upstreamConfig.Hostname = c.DestHost
715
+ upstreamConfig.Port = c.DestPort
716
+ upstreamConfig.TLS = c.DestTLS
717
+ upstreamConfig.Timeout = c.Gateway.Config.GatewayTimeout
718
+ upstreamConfig.Throttle = c.Gateway.Config.GatewayThrottle
719
+ upstreamConfig.WebircPassword = c.Gateway.findWebircPassword(c.DestHost)
720
+ upstreamConfig.Protocol = c.Gateway.Config.GatewayProtocol
721
+ upstreamConfig.LocalAddr = c.Gateway.Config.GatewayLocalAddr
722
+
723
+ return upstreamConfig
724
+ }
725
+
726
+ func (c *Client) buildWebircTags() string {
727
+ str := ""
728
+ for key, val := range c.Tags {
729
+ if str != "" {
730
+ str += " "
731
+ }
732
+
733
+ if val == "" {
734
+ str += key
735
+ } else {
736
+ str += key + "=" + val
737
+ }
738
+ }
739
+
740
+ return str
741
+ }