func New(b bot.Bot, isupportPlugin *isupport.Plugin, modePlugin *mode.Plugin) *Plugin { plugin := &Plugin{ bot: b, tbmgr: map[string]*TemporaryBanManager{}, isupport: isupportPlugin, mode: modePlugin, OldHostmasks: []string{}, } modePlugin.HandleFunc("-b", func(e *mode.ModeChangeEvent) { if ok, _, _ := isupportPlugin.IsChannel(e.Target); !ok { return // not a channel } hostmask := e.Argument tbmgr := plugin.ensureTemporaryBanManager(e.Target) if ban, ok := tbmgr.Remove(hostmask); ok { logging.Debug("%v: %v removed the temporary ban for %v", e.Target, e.Nick, ban.Hostmask) plugin.syncBans(e.Target) } }) b.HandleFunc("join", func(conn *client.Conn, line *client.Line) { if line.Nick != conn.Me().Nick { return } plugin.loadBans(line.Args[0]) go plugin.dumpBans(line.Args[0]) }) return plugin }
func (p *Plugin) syncBans(target string) { // Synchronize bans to file logging.Debug("Synchronizing temporary bans for %v to disk...", target) fn := p.getTempbansFilename(target) f, err := os.Create(fn) if err != nil { logging.Warn("Could not save temporary bans for %v: %v", fn, target, err.Error()) } defer f.Close() // Load temporary bans from this file if err := p.ensureTemporaryBanManager(target).Export(f); err != nil { logging.Warn("Could not save temporary bans: %v", err.Error()) } }
// write writes a \r\n terminated line of output to the connected server, // using Hybrid's algorithm to rate limit if conn.cfg.Flood is false. func (conn *Conn) write(line string) error { if !conn.cfg.Flood { if t := conn.rateLimit(len(line)); t != 0 { // sleep for the current line's time value before sending it logging.Info("irc.rateLimit(): Flood! Sleeping for %.2f secs.", t.Seconds()) <-time.After(t) } } if _, err := conn.io.WriteString(line + "\r\n"); err != nil { return err } if err := conn.io.Flush(); err != nil { return err } logging.Debug("-> %s", line) return nil }
func Migrate() error { ms.Lock() defer ms.Unlock() ms.db.Init(Bolt, COLLECTION, nil) failed := []string{} for coll, m := range ms.migrators { if m.migrated { continue } logging.Debug("Migrating %q.", coll) if err := m.Migrate(); err != nil { logging.Error("Migrating %q failed: %v", coll, err) failed = append(failed, coll) continue } if differ, ok := m.Migrator.(Differ); ok { before, after, err := differ.Diff() if err != nil { logging.Error("Diffing %q failed: %v", coll, err) failed = append(failed, coll) continue } sort.Strings(before) sort.Strings(after) unified, err := diff.Unified(before, after) if err != nil { logging.Error("Migration diff: %v\n%s", err, strings.Join(unified, "\n")) failed = append(failed, coll) continue } } if err := ms.db.Put(K{{"collection", coll}}, &done{true}); err != nil { logging.Warn("Setting migrated status for %q: %v", coll, err) } m.migrated = true } if len(failed) > 0 { return fmt.Errorf("migration failed for: \"%s\"", strings.Join(failed, "\", \"")) } return nil }
func (p *Plugin) loadBans(target string) { logging.Debug("Loading temporary bans for %v from disk...", target) // Check if file exists fn := p.getTempbansFilename(target) f, err := os.Open(fn) switch { case os.IsNotExist(err): return case err == nil: default: logging.Warn("Could not load temporary bans for %v: %v", fn, target, err.Error()) } defer f.Close() // Load temporary bans from this file if err := p.ensureTemporaryBanManager(target).Import(f); err != nil { logging.Warn("Could not load temporary bans: %v", err.Error()) } }
// receive one \r\n terminated line from peer, parse and dispatch it func (conn *Conn) recv() { for { s, err := conn.io.ReadString('\n') if err != nil { if err != io.EOF { logging.Error("irc.recv(): %s", err.Error()) } // We can't defer this, because shutdown() waits for it. conn.wg.Done() conn.shutdown() return } s = strings.Trim(s, "\r\n") logging.Debug("<- %s", s) if line := ParseLine(s); line != nil { line.Time = time.Now() conn.in <- line } else { logging.Warn("irc.recv(): problems parsing line:\n %s", s) } } }
func (plugin *Plugin) OnJoin(conn *client.Conn, line *client.Line) { logging.Info("vpnbot.Plugin: %v joined %v", line.Src, line.Target()) if lastCheck, ok := plugin.lastCheckNicks[line.Nick]; ok && time.Now().Sub(lastCheck) < 15*time.Minute { // There is a check in progress or another one has been done earlier logging.Debug("vpnbot.Plugin: Not checking %v, last check was %v", line.Nick, humanize.Time(plugin.lastCheckNicks[line.Nick])) return } logging.Debug("vpnbot.Plugin: Checking %v...", line.Nick) plugin.lastCheckNicks[line.Nick] = time.Now() // Is this me? if line.Nick == conn.Me().Nick { logging.Debug("vpnbot.Plugin: %v is actually me, skipping.", line.Nick) return } // Nickname == Ident? (9 chars cut) if !strings.HasPrefix(nonWordCharsRegex.ReplaceAllString(line.Nick, ""), strings.TrimLeft(line.Ident, "~")) { logging.Debug("vpnbot.Plugin: %v's nick doesn't match the ident, skipping.", line.Nick) return } // Hostname is masked RDNS vhost/IP? // TODO - Use regex to avoid banning due to similar vhosts if !maskedAddrRegex.MatchString(line.Host) { // Detected custom vHost logging.Debug("vpnbot.Plugin: %v has a custom vhost, skipping.", line.Nick) return } go func() { botNick := line.Nick nobotActivityChan := make(chan string) defer plugin.bot.HandleFunc("privmsg", func(conn *client.Conn, line *client.Line) { if line.Nick == botNick { nobotActivityChan <- "User sent a message" } }).Remove() defer plugin.bot.HandleFunc("noticed", func(conn *client.Conn, line *client.Line) { if line.Nick == botNick { nobotActivityChan <- "User sent a notice" } }).Remove() defer plugin.bot.HandleFunc("part", func(conn *client.Conn, line *client.Line) { if line.Nick == botNick { nobotActivityChan <- "User left" } }).Remove() defer plugin.bot.HandleFunc("part", func(conn *client.Conn, line *client.Line) { if line.Nick == botNick { } if len(line.Args) > 0 { switch line.Args[0] { case "Excess flood": // If this was an excess flood, consider it spam that should // be good to ban anyways nobotActivityChan <- "Excess flood, banning early" banmask := fmt.Sprintf("%v!%v@%v", "*", "*", line.Host) // TODO - Ramp up/down the duration with increasing/decreasing activity of the bots plugin.banGlobal(plugin.generateBan(line.Nick, banmask, "Instant excess flood", 2*24*time.Hour)) default: nobotActivityChan <- "User quit normally" } } }).Remove() // Give nobotActivityChan some time to prove this is not a bot select { case reason := <-nobotActivityChan: logging.Info("vpnbot.Plugin: %v, skipping.", reason) return case <-time.After(1 * time.Second): } // Get WHOIS info info, err := plugin.whois.WhoIs(line.Nick) if err != nil && err != whois.ErrNoSuchNick { logging.Warn("vpnbot.Plugin: Can't get WHOIS info for %v, skipping: %v", line.Nick, err.Error()) return } // Not an oper? if info.IsOperator { logging.Debug("vpnbot.Plugin: %v is operator, skipping.", line.Nick) return } // Not away if info.IsAway { logging.Debug("vpnbot.Plugin: %v is away, skipping.", line.Nick) return } // Realname == Nickname? if info.Realname != line.Nick { logging.Debug( "vpnbot.Plugin: %v's nick doesn't match the realname, skipping.", line.Nick) return } // Count of channels at least 48 if len(info.Channels) < 48 { logging.Debug( "vpnbot.Plugin: %v is not in a high amount of channels, skipping.", line.Nick) return } // Not halfop, op, aop or owner in any channel for _, prefix := range info.Channels { if prefix == '%' || prefix == '@' || prefix == '&' || prefix == '~' { logging.Debug( "vpnbot.Plugin: %v is opped in a channel, skipping.", line.Nick) return } } // Give nobotActivityChan some time to prove this is not a bot select { case reason := <-nobotActivityChan: logging.Info("vpnbot.Plugin: %v, skipping.", reason) return case <-time.After(250 * time.Millisecond): } // More expensive tests below... // Make sure we deal with an unregistered client status := plugin.nickserv.Status(line.Nick)[line.Nick] if status.Error != nil { logging.Warn("vpnbot.Plugin: Can't get auth status for %v, skipping: %v", line.Nick, status.Error) return } if status.Level >= nickserv.Status_IdentifiedViaPassword { logging.Debug("vpnbot.Plugin: %v is identified, skipping.", line.Nick) return } // Give nobotActivityChan some time to prove this is not a bot select { case reason := <-nobotActivityChan: logging.Info("vpnbot.Plugin: %v, skipping.", reason) return case <-time.After(250 * time.Millisecond): } // This is a bot we need to ban! banmask := fmt.Sprintf("%v!%v@%v", "*", "*", line.Host) // TODO - Ramp up/down the duration with increasing/decreasing activity of the bots plugin.banGlobal(plugin.generateBan(line.Nick, banmask, "Known pattern of ban-evading/logging bot", 2*24*time.Hour)) }() }
func (m *RWMutex) RUnlock() { m.init() _, f, l, _ := runtime.Caller(1) logging.Debug("%v: Read-unlocked at %v:%v", m, f, l) m.m.RUnlock() }
func (m *RWMutex) Lock() { m.init() _, f, l, _ := runtime.Caller(1) logging.Debug("%v: Locked at %v:%v", m, f, l) m.m.Lock() }