mirror of
https://github.com/google/alertmanager-irc-relay.git
synced 2024-12-25 12:22:44 +01:00
a63bfa3aad
handle nickserv identify requests instead of blindly issuing a message when connected this helps if nickserv's state is wiped and we are being asked to re-identify introduce a `nickserv_identify_patterns` config option. these patterns are used to guess identify requests of the various nickserv implementations Signed-off-by: Luca Bigliardi <shammash@google.com>
613 lines
15 KiB
Go
613 lines
15 KiB
Go
// Copyright 2018 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
irc "github.com/fluffle/goirc/client"
|
|
"github.com/google/alertmanager-irc-relay/logging"
|
|
)
|
|
|
|
func makeTestIRCConfig(IRCPort int) *Config {
|
|
return &Config{
|
|
IRCNick: "foo",
|
|
IRCNickPass: "",
|
|
IRCHost: "127.0.0.1",
|
|
IRCPort: IRCPort,
|
|
IRCUseSSL: false,
|
|
IRCChannels: []IRCChannel{
|
|
IRCChannel{Name: "#foo"},
|
|
},
|
|
UsePrivmsg: false,
|
|
NickservIdentifyPatterns: []string{
|
|
"identify yourself ktnxbye",
|
|
},
|
|
}
|
|
}
|
|
|
|
func makeTestNotifier(t *testing.T, config *Config) (*IRCNotifier, chan AlertMsg, context.Context, context.CancelFunc, *sync.WaitGroup) {
|
|
fakeDelayerMaker := &FakeDelayerMaker{}
|
|
fakeTime := &FakeTime{
|
|
afterChan: make(chan time.Time, 1),
|
|
}
|
|
alertMsgs := make(chan AlertMsg)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
stopWg := sync.WaitGroup{}
|
|
stopWg.Add(1)
|
|
notifier, err := NewIRCNotifier(config, alertMsgs, fakeDelayerMaker, fakeTime)
|
|
if err != nil {
|
|
t.Fatal(fmt.Sprintf("Could not create IRC notifier: %s", err))
|
|
}
|
|
notifier.Client.Config().Flood = true
|
|
|
|
return notifier, alertMsgs, ctx, cancel, &stopWg
|
|
}
|
|
|
|
func TestServerPassword(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
config.IRCHostPass = "hostsecret"
|
|
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
|
|
var testStep sync.WaitGroup
|
|
|
|
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return nil
|
|
}
|
|
server.SetHandler("JOIN", joinHandler)
|
|
|
|
testStep.Add(1)
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
testStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
"PASS hostsecret",
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
"QUIT :see ya",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Did not send IRC server password. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestSendAlertOnPreJoinedChannel(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
notifier, alertMsgs, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
|
|
var testStep sync.WaitGroup
|
|
|
|
testChannel := "#foo"
|
|
testMessage := "test message"
|
|
|
|
// Send the alert after configured channels have joined, to ensure we
|
|
// check for no re-join attempt.
|
|
joinedHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
if line.Args[0] == testChannel {
|
|
testStep.Done()
|
|
}
|
|
return hJOIN(conn, line)
|
|
}
|
|
server.SetHandler("JOIN", joinedHandler)
|
|
|
|
testStep.Add(1)
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
testStep.Wait()
|
|
|
|
server.SetHandler("JOIN", hJOIN)
|
|
|
|
noticeHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return nil
|
|
}
|
|
server.SetHandler("NOTICE", noticeHandler)
|
|
|
|
testStep.Add(1)
|
|
alertMsgs <- AlertMsg{Channel: testChannel, Alert: testMessage}
|
|
|
|
testStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
"NOTICE #foo :test message",
|
|
"QUIT :see ya",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestUsePrivmsgToSendAlertOnPreJoinedChannel(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
config.UsePrivmsg = true
|
|
notifier, alertMsgs, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
|
|
var testStep sync.WaitGroup
|
|
|
|
testChannel := "#foo"
|
|
testMessage := "test message"
|
|
|
|
// Send the alert after configured channels have joined, to ensure we
|
|
// check for no re-join attempt.
|
|
joinedHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
if line.Args[0] == testChannel {
|
|
testStep.Done()
|
|
}
|
|
return hJOIN(conn, line)
|
|
}
|
|
server.SetHandler("JOIN", joinedHandler)
|
|
|
|
testStep.Add(1)
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
testStep.Wait()
|
|
|
|
server.SetHandler("JOIN", hJOIN)
|
|
|
|
privmsgHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return nil
|
|
}
|
|
server.SetHandler("PRIVMSG", privmsgHandler)
|
|
|
|
testStep.Add(1)
|
|
alertMsgs <- AlertMsg{Channel: testChannel, Alert: testMessage}
|
|
|
|
testStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
"PRIVMSG #foo :test message",
|
|
"QUIT :see ya",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestSendAlertAndJoinChannel(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
notifier, alertMsgs, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
|
|
var testStep sync.WaitGroup
|
|
|
|
testChannel := "#foobar"
|
|
testMessage := "test message"
|
|
|
|
// Send the alert after configured channels have joined, to ensure log
|
|
// ordering.
|
|
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return hJOIN(conn, line)
|
|
}
|
|
server.SetHandler("JOIN", joinHandler)
|
|
|
|
testStep.Add(1)
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
testStep.Wait()
|
|
|
|
server.SetHandler("JOIN", hJOIN)
|
|
|
|
noticeHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return nil
|
|
}
|
|
server.SetHandler("NOTICE", noticeHandler)
|
|
|
|
testStep.Add(1)
|
|
alertMsgs <- AlertMsg{Channel: testChannel, Alert: testMessage}
|
|
|
|
testStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
// #foobar joined before sending message
|
|
"PRIVMSG ChanServ :UNBAN #foobar",
|
|
"JOIN #foobar",
|
|
"NOTICE #foobar :test message",
|
|
"QUIT :see ya",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestSendAlertDisconnected(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
notifier, alertMsgs, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
|
|
var testStep, holdUserStep sync.WaitGroup
|
|
|
|
testChannel := "#foo"
|
|
disconnectedTestMessage := "disconnected test message"
|
|
connectedTestMessage := "connected test message"
|
|
|
|
// First send an alert while the session is not established.
|
|
testStep.Add(1)
|
|
holdUserStep.Add(1)
|
|
holdUser := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
logging.Info("=Server= Wait before completing session")
|
|
testStep.Wait()
|
|
logging.Info("=Server= Completing session")
|
|
holdUserStep.Done()
|
|
return hUSER(conn, line)
|
|
}
|
|
server.SetHandler("USER", holdUser)
|
|
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
// Alert channels is not consumed while disconnected
|
|
select {
|
|
case alertMsgs <- AlertMsg{Channel: testChannel, Alert: disconnectedTestMessage}:
|
|
t.Error("Alert consumed while disconnected")
|
|
default:
|
|
}
|
|
|
|
testStep.Done()
|
|
holdUserStep.Wait()
|
|
|
|
// Make sure session is established by checking that pre-joined
|
|
// channel is there.
|
|
testStep.Add(1)
|
|
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return hJOIN(conn, line)
|
|
}
|
|
server.SetHandler("JOIN", joinHandler)
|
|
|
|
testStep.Wait()
|
|
|
|
// Now send and wait until a notice has been received.
|
|
testStep.Add(1)
|
|
noticeHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return nil
|
|
}
|
|
server.SetHandler("NOTICE", noticeHandler)
|
|
|
|
alertMsgs <- AlertMsg{Channel: testChannel, Alert: connectedTestMessage}
|
|
|
|
testStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
// Only message sent while being connected is received.
|
|
"NOTICE #foo :connected test message",
|
|
"QUIT :see ya",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestReconnect(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
|
|
var testStep sync.WaitGroup
|
|
|
|
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return hJOIN(conn, line)
|
|
}
|
|
server.SetHandler("JOIN", joinHandler)
|
|
|
|
testStep.Add(1)
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
// Wait until the pre-joined channel is seen.
|
|
testStep.Wait()
|
|
|
|
// Simulate disconnection.
|
|
testStep.Add(1)
|
|
server.Client.Close()
|
|
|
|
// Wait again until the pre-joined channel is seen.
|
|
testStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
// Commands from first connection
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
// Commands from reconnection
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
"QUIT :see ya",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Reconnection did not happen correctly. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestConnectErrorRetry(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
// Attempt SSL handshake. The server does not support it, resulting in
|
|
// a connection error.
|
|
config.IRCUseSSL = true
|
|
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
// Pilot reconnect attempts via backoff delay to prevent race
|
|
// conditions in the test while we change the components behavior on
|
|
// the fly.
|
|
delayer := notifier.BackoffCounter.(*FakeDelayer)
|
|
delayer.DelayOnChan = true
|
|
|
|
var testStep, joinStep sync.WaitGroup
|
|
|
|
testStep.Add(1)
|
|
earlyHandler := func() {
|
|
testStep.Done()
|
|
}
|
|
|
|
server.SetCloseEarly(earlyHandler)
|
|
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
delayer.StopDelay <- true
|
|
|
|
testStep.Wait()
|
|
|
|
// We have caused a connection failure, now check for a reconnection
|
|
notifier.Client.Config().SSL = false
|
|
joinStep.Add(1)
|
|
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
joinStep.Done()
|
|
return hJOIN(conn, line)
|
|
}
|
|
server.SetHandler("JOIN", joinHandler)
|
|
server.SetCloseEarly(nil)
|
|
|
|
delayer.StopDelay <- true
|
|
|
|
joinStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
"QUIT :see ya",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Reconnection did not happen correctly. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestIdentify(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
config.IRCNickPass = "nickpassword"
|
|
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
notifier.NickservDelayWait = 0 * time.Second
|
|
|
|
var testStep sync.WaitGroup
|
|
|
|
// Trigger NickServ identify request when we see the NICK command
|
|
// Note: We also test formatting cleanup with this message
|
|
nickHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
var err error
|
|
_, err = conn.WriteString(":NickServ!NickServ@services. NOTICE airtest :This nickname is registered. Please choose a different nickname, or \002identify yourself\002 ktnxbye.\n")
|
|
return err
|
|
}
|
|
server.SetHandler("NICK", nickHandler)
|
|
|
|
// Wait until the pre-joined channel is seen (joining happens
|
|
// after identification).
|
|
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return hJOIN(conn, line)
|
|
}
|
|
server.SetHandler("JOIN", joinHandler)
|
|
|
|
testStep.Add(1)
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
testStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"PRIVMSG NickServ :IDENTIFY nickpassword",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
"QUIT :see ya",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Identification did not happen correctly. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestGhost(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
config.IRCNickPass = "nickpassword"
|
|
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
notifier.NickservDelayWait = 0 * time.Second
|
|
|
|
var testStep sync.WaitGroup
|
|
|
|
// Trigger 433 for first nick when we see the USER command
|
|
userHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
var err error
|
|
if line.Args[0] == "foo" {
|
|
_, err = conn.WriteString(":example.com 433 * foo :nick in use\n")
|
|
}
|
|
return err
|
|
}
|
|
server.SetHandler("USER", userHandler)
|
|
|
|
// Trigger 001 when we see NICK foo^
|
|
nickHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
var err error
|
|
if line.Args[0] == "foo^" {
|
|
_, err = conn.WriteString(":example.com 001 foo^ :Welcome\n")
|
|
}
|
|
return err
|
|
}
|
|
server.SetHandler("NICK", nickHandler)
|
|
|
|
// Wait until the pre-joined channel is seen (joining happens
|
|
// after ghosting).
|
|
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
testStep.Done()
|
|
return hJOIN(conn, line)
|
|
}
|
|
server.SetHandler("JOIN", joinHandler)
|
|
|
|
testStep.Add(1)
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
testStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
"NICK foo^",
|
|
"PRIVMSG NickServ :GHOST foo nickpassword",
|
|
"NICK foo",
|
|
"PRIVMSG ChanServ :UNBAN #foo",
|
|
"JOIN #foo",
|
|
"QUIT :see ya",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Ghosting did not happen correctly. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|
|
|
|
func TestStopRunningWhenHalfConnected(t *testing.T) {
|
|
server, port := makeTestServer(t)
|
|
config := makeTestIRCConfig(port)
|
|
notifier, _, ctx, cancel, stopWg := makeTestNotifier(t, config)
|
|
|
|
var testStep sync.WaitGroup
|
|
|
|
// Send a StopRunning request while the client is connected but the
|
|
// session is not up
|
|
testStep.Add(1)
|
|
holdUser := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
|
logging.Info("=Server= NOT completing session")
|
|
testStep.Done()
|
|
return nil
|
|
}
|
|
server.SetHandler("USER", holdUser)
|
|
|
|
go notifier.Run(ctx, stopWg)
|
|
|
|
testStep.Wait()
|
|
|
|
cancel()
|
|
stopWg.Wait()
|
|
|
|
server.Stop()
|
|
|
|
expectedCommands := []string{
|
|
"NICK foo",
|
|
"USER foo 12 * :",
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedCommands, server.Log) {
|
|
t.Error("Alert not sent correctly. Received commands:\n", strings.Join(server.Log, "\n"))
|
|
}
|
|
}
|