Codebase list alertmanager-irc-relay / a63bfa3
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> Luca Bigliardi 3 years ago
4 changed file(s) with 104 addition(s) and 27 deletion(s). Raw diff Collapse all Expand all
6565
6666 # Set the internal buffer size for alerts received but not yet sent to IRC.
6767 alert_buffer_size: 2048
68
69 # Patterns used to guess whether NickServ is asking us to IDENTIFY
70 # Note: If you need to change this because the bot is not catching a request
71 # from a rather common NickServ, please consider sending a PR to update the
72 # default config instead.
73 nickserv_identify_patterns:
74 - "identify via /msg NickServ identify <password>"
75 - "type /msg NickServ IDENTIFY password"
76 - "authenticate yourself to services with the IDENTIFY command"
6877 ```
6978
7079 Running the bot (assuming *$GOPATH* and *$PATH* are properly setup for go):
4545 MsgOnce bool `yaml:"msg_once_per_alert_group"`
4646 UsePrivmsg bool `yaml:"use_privmsg"`
4747 AlertBufferSize int `yaml:"alert_buffer_size"`
48
49 NickservIdentifyPatterns []string `yaml:nickserv_identify_patterns`
4850 }
4951
5052 func LoadConfig(configFile string) (*Config, error) {
6365 MsgOnce: false,
6466 UsePrivmsg: false,
6567 AlertBufferSize: 2048,
68 NickservIdentifyPatterns: []string{
69 "identify via /msg NickServ identify <password>",
70 "type /msg NickServ IDENTIFY password",
71 "authenticate yourself to services with the IDENTIFY command",
72 },
6673 }
6774
6875 if configFile != "" {
8080 // might change its copy.
8181 Nick string
8282 NickPassword string
83 Client *irc.Conn
84 AlertMsgs chan AlertMsg
83
84 NickservIdentifyPatterns []string
85
86 Client *irc.Conn
87 AlertMsgs chan AlertMsg
8588
8689 // irc.Conn has a Connected() method that can tell us wether the TCP
8790 // connection is up, and thus if we should trigger connect/disconnect.
115118 channelReconciler := NewChannelReconciler(config, client, delayerMaker, timeTeller)
116119
117120 notifier := &IRCNotifier{
118 Nick: config.IRCNick,
119 NickPassword: config.IRCNickPass,
120 Client: client,
121 AlertMsgs: alertMsgs,
122 sessionUpSignal: make(chan bool),
123 sessionDownSignal: make(chan bool),
124 channelReconciler: channelReconciler,
125 UsePrivmsg: config.UsePrivmsg,
126 NickservDelayWait: nickservWaitSecs * time.Second,
127 BackoffCounter: backoffCounter,
128 timeTeller: timeTeller,
121 Nick: config.IRCNick,
122 NickPassword: config.IRCNickPass,
123 NickservIdentifyPatterns: config.NickservIdentifyPatterns,
124 Client: client,
125 AlertMsgs: alertMsgs,
126 sessionUpSignal: make(chan bool),
127 sessionDownSignal: make(chan bool),
128 channelReconciler: channelReconciler,
129 UsePrivmsg: config.UsePrivmsg,
130 NickservDelayWait: nickservWaitSecs * time.Second,
131 BackoffCounter: backoffCounter,
132 timeTeller: timeTeller,
129133 }
130134
131135 notifier.registerHandlers()
146150 n.sessionDownSignal <- false
147151 })
148152
149 for _, event := range []string{irc.NOTICE, "433"} {
153 n.Client.HandleFunc(irc.NOTICE,
154 func(_ *irc.Conn, line *irc.Line) {
155 n.HandleNotice(line.Nick, line.Text())
156 })
157
158 for _, event := range []string{"433"} {
150159 n.Client.HandleFunc(event, loggerHandler)
151160 }
152161 }
153162
154 func (n *IRCNotifier) MaybeIdentifyNick() {
163 func (n *IRCNotifier) HandleNotice(nick string, msg string) {
164 logging.Info("Received NOTICE from %s: %s", nick, msg)
165 if strings.ToLower(nick) == "nickserv" {
166 n.HandleNickservMsg(msg)
167 }
168 }
169
170 func (n *IRCNotifier) HandleNickservMsg(msg string) {
155171 if n.NickPassword == "" {
156 return
157 }
158
159 // Very lazy/optimistic, but this is good enough for my irssi config,
160 // so it should work here as well.
172 logging.Debug("Skip processing NickServ request, no password configured")
173 return
174 }
175
176 // Remove most common formatting options from NickServ messages
177 cleaner := strings.NewReplacer(
178 "\001", "", // bold
179 "\002", "", // faint
180 "\004", "", // underline
181 )
182 cleanedMsg := cleaner.Replace(msg)
183
184 for _, identifyPattern := range n.NickservIdentifyPatterns {
185 logging.Debug("Checking if NickServ message matches identify request '%s'", identifyPattern)
186 if strings.Contains(cleanedMsg, identifyPattern) {
187 logging.Info("Handling NickServ request to IDENTIFY")
188 n.Client.Privmsgf("NickServ", "IDENTIFY %s", n.NickPassword)
189 return
190 }
191 }
192 }
193
194 func (n *IRCNotifier) MaybeGhostNick() {
195 if n.NickPassword == "" {
196 logging.Debug("Skip GHOST check, no password configured")
197 return
198 }
199
161200 currentNick := n.Client.Me().Nick
162201 if currentNick != n.Nick {
163202 logging.Info("My nick is '%s', sending GHOST to NickServ to get '%s'",
168207
169208 logging.Info("Changing nick to '%s'", n.Nick)
170209 n.Client.Nick(n.Nick)
171 }
172 logging.Info("Sending IDENTIFY to NickServ")
173 n.Client.Privmsgf("NickServ", "IDENTIFY %s", n.NickPassword)
210 time.Sleep(n.NickservDelayWait)
211 }
212 }
213
214 func (n *IRCNotifier) MaybeWaitForNickserv() {
215 if n.NickPassword == "" {
216 logging.Debug("Skip NickServ wait, no password configured")
217 return
218 }
219
220 // Very lazy/optimistic, but this is good enough for my irssi config,
221 // so it should work here as well.
222 logging.Info("Waiting for NickServ to notice us and issue an identify request")
174223 time.Sleep(n.NickservDelayWait)
175224 }
176225
260309 case <-n.sessionUpSignal:
261310 n.sessionUp = true
262311 n.sessionWg.Add(1)
263 n.MaybeIdentifyNick()
312 n.MaybeGhostNick()
313 n.MaybeWaitForNickserv()
264314 n.channelReconciler.Start(ctx)
265315 ircConnectedGauge.Set(1)
266316 case <-n.sessionDownSignal:
3838 IRCChannel{Name: "#foo"},
3939 },
4040 UsePrivmsg: false,
41 NickservIdentifyPatterns: []string{
42 "identify yourself ktnxbye",
43 },
4144 }
4245 }
4346
467470
468471 var testStep sync.WaitGroup
469472
473 // Trigger NickServ identify request when we see the NICK command
474 // Note: We also test formatting cleanup with this message
475 nickHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
476 var err error
477 _, err = conn.WriteString(":NickServ!NickServ@services. NOTICE airtest :This nickname is registered. Please choose a different nickname, or \002identify yourself\002 ktnxbye.\n")
478 return err
479 }
480 server.SetHandler("NICK", nickHandler)
481
470482 // Wait until the pre-joined channel is seen (joining happens
471483 // after identification).
472484 joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
499511 }
500512 }
501513
502 func TestGhostAndIdentify(t *testing.T) {
514 func TestGhost(t *testing.T) {
503515 server, port := makeTestServer(t)
504516 config := makeTestIRCConfig(port)
505517 config.IRCNickPass = "nickpassword"
529541 server.SetHandler("NICK", nickHandler)
530542
531543 // Wait until the pre-joined channel is seen (joining happens
532 // after identification).
544 // after ghosting).
533545 joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error {
534546 testStep.Done()
535547 return hJOIN(conn, line)
552564 "NICK foo^",
553565 "PRIVMSG NickServ :GHOST foo nickpassword",
554566 "NICK foo",
555 "PRIVMSG NickServ :IDENTIFY nickpassword",
556567 "PRIVMSG ChanServ :UNBAN #foo",
557568 "JOIN #foo",
558569 "QUIT :see ya",