mirror of
https://github.com/google/alertmanager-irc-relay.git
synced 2024-11-05 11:19:21 +01:00
explicitly handle nickserv identify request
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>
This commit is contained in:
parent
9748c8bfbf
commit
a63bfa3aad
@ -66,6 +66,15 @@ msg_template: "Alert {{ .Labels.alertname }} on {{ .Labels.instance }} is {{ .St
|
|||||||
|
|
||||||
# Set the internal buffer size for alerts received but not yet sent to IRC.
|
# Set the internal buffer size for alerts received but not yet sent to IRC.
|
||||||
alert_buffer_size: 2048
|
alert_buffer_size: 2048
|
||||||
|
|
||||||
|
# Patterns used to guess whether NickServ is asking us to IDENTIFY
|
||||||
|
# Note: If you need to change this because the bot is not catching a request
|
||||||
|
# from a rather common NickServ, please consider sending a PR to update the
|
||||||
|
# default config instead.
|
||||||
|
nickserv_identify_patterns:
|
||||||
|
- "identify via /msg NickServ identify <password>"
|
||||||
|
- "type /msg NickServ IDENTIFY password"
|
||||||
|
- "authenticate yourself to services with the IDENTIFY command"
|
||||||
```
|
```
|
||||||
|
|
||||||
Running the bot (assuming *$GOPATH* and *$PATH* are properly setup for go):
|
Running the bot (assuming *$GOPATH* and *$PATH* are properly setup for go):
|
||||||
|
@ -46,6 +46,8 @@ type Config struct {
|
|||||||
MsgOnce bool `yaml:"msg_once_per_alert_group"`
|
MsgOnce bool `yaml:"msg_once_per_alert_group"`
|
||||||
UsePrivmsg bool `yaml:"use_privmsg"`
|
UsePrivmsg bool `yaml:"use_privmsg"`
|
||||||
AlertBufferSize int `yaml:"alert_buffer_size"`
|
AlertBufferSize int `yaml:"alert_buffer_size"`
|
||||||
|
|
||||||
|
NickservIdentifyPatterns []string `yaml:nickserv_identify_patterns`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig(configFile string) (*Config, error) {
|
func LoadConfig(configFile string) (*Config, error) {
|
||||||
@ -64,6 +66,11 @@ func LoadConfig(configFile string) (*Config, error) {
|
|||||||
MsgOnce: false,
|
MsgOnce: false,
|
||||||
UsePrivmsg: false,
|
UsePrivmsg: false,
|
||||||
AlertBufferSize: 2048,
|
AlertBufferSize: 2048,
|
||||||
|
NickservIdentifyPatterns: []string{
|
||||||
|
"identify via /msg NickServ identify <password>",
|
||||||
|
"type /msg NickServ IDENTIFY password",
|
||||||
|
"authenticate yourself to services with the IDENTIFY command",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if configFile != "" {
|
if configFile != "" {
|
||||||
|
90
irc.go
90
irc.go
@ -81,8 +81,11 @@ type IRCNotifier struct {
|
|||||||
// might change its copy.
|
// might change its copy.
|
||||||
Nick string
|
Nick string
|
||||||
NickPassword string
|
NickPassword string
|
||||||
Client *irc.Conn
|
|
||||||
AlertMsgs chan AlertMsg
|
NickservIdentifyPatterns []string
|
||||||
|
|
||||||
|
Client *irc.Conn
|
||||||
|
AlertMsgs chan AlertMsg
|
||||||
|
|
||||||
// irc.Conn has a Connected() method that can tell us wether the TCP
|
// irc.Conn has a Connected() method that can tell us wether the TCP
|
||||||
// connection is up, and thus if we should trigger connect/disconnect.
|
// connection is up, and thus if we should trigger connect/disconnect.
|
||||||
@ -116,17 +119,18 @@ func NewIRCNotifier(config *Config, alertMsgs chan AlertMsg, delayerMaker Delaye
|
|||||||
channelReconciler := NewChannelReconciler(config, client, delayerMaker, timeTeller)
|
channelReconciler := NewChannelReconciler(config, client, delayerMaker, timeTeller)
|
||||||
|
|
||||||
notifier := &IRCNotifier{
|
notifier := &IRCNotifier{
|
||||||
Nick: config.IRCNick,
|
Nick: config.IRCNick,
|
||||||
NickPassword: config.IRCNickPass,
|
NickPassword: config.IRCNickPass,
|
||||||
Client: client,
|
NickservIdentifyPatterns: config.NickservIdentifyPatterns,
|
||||||
AlertMsgs: alertMsgs,
|
Client: client,
|
||||||
sessionUpSignal: make(chan bool),
|
AlertMsgs: alertMsgs,
|
||||||
sessionDownSignal: make(chan bool),
|
sessionUpSignal: make(chan bool),
|
||||||
channelReconciler: channelReconciler,
|
sessionDownSignal: make(chan bool),
|
||||||
UsePrivmsg: config.UsePrivmsg,
|
channelReconciler: channelReconciler,
|
||||||
NickservDelayWait: nickservWaitSecs * time.Second,
|
UsePrivmsg: config.UsePrivmsg,
|
||||||
BackoffCounter: backoffCounter,
|
NickservDelayWait: nickservWaitSecs * time.Second,
|
||||||
timeTeller: timeTeller,
|
BackoffCounter: backoffCounter,
|
||||||
|
timeTeller: timeTeller,
|
||||||
}
|
}
|
||||||
|
|
||||||
notifier.registerHandlers()
|
notifier.registerHandlers()
|
||||||
@ -147,18 +151,53 @@ func (n *IRCNotifier) registerHandlers() {
|
|||||||
n.sessionDownSignal <- false
|
n.sessionDownSignal <- false
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, event := range []string{irc.NOTICE, "433"} {
|
n.Client.HandleFunc(irc.NOTICE,
|
||||||
|
func(_ *irc.Conn, line *irc.Line) {
|
||||||
|
n.HandleNotice(line.Nick, line.Text())
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, event := range []string{"433"} {
|
||||||
n.Client.HandleFunc(event, loggerHandler)
|
n.Client.HandleFunc(event, loggerHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *IRCNotifier) MaybeIdentifyNick() {
|
func (n *IRCNotifier) HandleNotice(nick string, msg string) {
|
||||||
|
logging.Info("Received NOTICE from %s: %s", nick, msg)
|
||||||
|
if strings.ToLower(nick) == "nickserv" {
|
||||||
|
n.HandleNickservMsg(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *IRCNotifier) HandleNickservMsg(msg string) {
|
||||||
if n.NickPassword == "" {
|
if n.NickPassword == "" {
|
||||||
|
logging.Debug("Skip processing NickServ request, no password configured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove most common formatting options from NickServ messages
|
||||||
|
cleaner := strings.NewReplacer(
|
||||||
|
"\001", "", // bold
|
||||||
|
"\002", "", // faint
|
||||||
|
"\004", "", // underline
|
||||||
|
)
|
||||||
|
cleanedMsg := cleaner.Replace(msg)
|
||||||
|
|
||||||
|
for _, identifyPattern := range n.NickservIdentifyPatterns {
|
||||||
|
logging.Debug("Checking if NickServ message matches identify request '%s'", identifyPattern)
|
||||||
|
if strings.Contains(cleanedMsg, identifyPattern) {
|
||||||
|
logging.Info("Handling NickServ request to IDENTIFY")
|
||||||
|
n.Client.Privmsgf("NickServ", "IDENTIFY %s", n.NickPassword)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *IRCNotifier) MaybeGhostNick() {
|
||||||
|
if n.NickPassword == "" {
|
||||||
|
logging.Debug("Skip GHOST check, no password configured")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Very lazy/optimistic, but this is good enough for my irssi config,
|
|
||||||
// so it should work here as well.
|
|
||||||
currentNick := n.Client.Me().Nick
|
currentNick := n.Client.Me().Nick
|
||||||
if currentNick != n.Nick {
|
if currentNick != n.Nick {
|
||||||
logging.Info("My nick is '%s', sending GHOST to NickServ to get '%s'",
|
logging.Info("My nick is '%s', sending GHOST to NickServ to get '%s'",
|
||||||
@ -169,9 +208,19 @@ func (n *IRCNotifier) MaybeIdentifyNick() {
|
|||||||
|
|
||||||
logging.Info("Changing nick to '%s'", n.Nick)
|
logging.Info("Changing nick to '%s'", n.Nick)
|
||||||
n.Client.Nick(n.Nick)
|
n.Client.Nick(n.Nick)
|
||||||
|
time.Sleep(n.NickservDelayWait)
|
||||||
}
|
}
|
||||||
logging.Info("Sending IDENTIFY to NickServ")
|
}
|
||||||
n.Client.Privmsgf("NickServ", "IDENTIFY %s", n.NickPassword)
|
|
||||||
|
func (n *IRCNotifier) MaybeWaitForNickserv() {
|
||||||
|
if n.NickPassword == "" {
|
||||||
|
logging.Debug("Skip NickServ wait, no password configured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very lazy/optimistic, but this is good enough for my irssi config,
|
||||||
|
// so it should work here as well.
|
||||||
|
logging.Info("Waiting for NickServ to notice us and issue an identify request")
|
||||||
time.Sleep(n.NickservDelayWait)
|
time.Sleep(n.NickservDelayWait)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +310,8 @@ func (n *IRCNotifier) SetupPhase(ctx context.Context) {
|
|||||||
case <-n.sessionUpSignal:
|
case <-n.sessionUpSignal:
|
||||||
n.sessionUp = true
|
n.sessionUp = true
|
||||||
n.sessionWg.Add(1)
|
n.sessionWg.Add(1)
|
||||||
n.MaybeIdentifyNick()
|
n.MaybeGhostNick()
|
||||||
|
n.MaybeWaitForNickserv()
|
||||||
n.channelReconciler.Start(ctx)
|
n.channelReconciler.Start(ctx)
|
||||||
ircConnectedGauge.Set(1)
|
ircConnectedGauge.Set(1)
|
||||||
case <-n.sessionDownSignal:
|
case <-n.sessionDownSignal:
|
||||||
|
17
irc_test.go
17
irc_test.go
@ -39,6 +39,9 @@ func makeTestIRCConfig(IRCPort int) *Config {
|
|||||||
IRCChannel{Name: "#foo"},
|
IRCChannel{Name: "#foo"},
|
||||||
},
|
},
|
||||||
UsePrivmsg: false,
|
UsePrivmsg: false,
|
||||||
|
NickservIdentifyPatterns: []string{
|
||||||
|
"identify yourself ktnxbye",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,6 +471,15 @@ func TestIdentify(t *testing.T) {
|
|||||||
|
|
||||||
var testStep sync.WaitGroup
|
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
|
// Wait until the pre-joined channel is seen (joining happens
|
||||||
// after identification).
|
// after identification).
|
||||||
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
||||||
@ -500,7 +512,7 @@ func TestIdentify(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGhostAndIdentify(t *testing.T) {
|
func TestGhost(t *testing.T) {
|
||||||
server, port := makeTestServer(t)
|
server, port := makeTestServer(t)
|
||||||
config := makeTestIRCConfig(port)
|
config := makeTestIRCConfig(port)
|
||||||
config.IRCNickPass = "nickpassword"
|
config.IRCNickPass = "nickpassword"
|
||||||
@ -530,7 +542,7 @@ func TestGhostAndIdentify(t *testing.T) {
|
|||||||
server.SetHandler("NICK", nickHandler)
|
server.SetHandler("NICK", nickHandler)
|
||||||
|
|
||||||
// Wait until the pre-joined channel is seen (joining happens
|
// Wait until the pre-joined channel is seen (joining happens
|
||||||
// after identification).
|
// after ghosting).
|
||||||
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
|
||||||
testStep.Done()
|
testStep.Done()
|
||||||
return hJOIN(conn, line)
|
return hJOIN(conn, line)
|
||||||
@ -553,7 +565,6 @@ func TestGhostAndIdentify(t *testing.T) {
|
|||||||
"NICK foo^",
|
"NICK foo^",
|
||||||
"PRIVMSG NickServ :GHOST foo nickpassword",
|
"PRIVMSG NickServ :GHOST foo nickpassword",
|
||||||
"NICK foo",
|
"NICK foo",
|
||||||
"PRIVMSG NickServ :IDENTIFY nickpassword",
|
|
||||||
"PRIVMSG ChanServ :UNBAN #foo",
|
"PRIVMSG ChanServ :UNBAN #foo",
|
||||||
"JOIN #foo",
|
"JOIN #foo",
|
||||||
"QUIT :see ya",
|
"QUIT :see ya",
|
||||||
|
Loading…
Reference in New Issue
Block a user