// Associates an already known nick with an already known channel. func (st *stateTracker) Associate(c, n string) *ChanPrivs { st.mu.Lock() defer st.mu.Unlock() nk, nok := st.nicks[n] ch, c*k := st.chans[c] if !c*k { // As we can implicitly delete both nicks and channels from being // tracked by dissociating one from the other, we should verify that // we're not being passed an old Nick or Channel. logging.Error("Tracker.Associate(): channel %s not found in "+ "internal state.", c) return nil } else if !nok { logging.Error("Tracker.Associate(): nick %s not found in "+ "internal state.", n) return nil } else if _, ok := nk.isOn(ch); ok { logging.Warn("Tracker.Associate(): %s already on %s.", nk, ch) return nil } cp := new(ChanPrivs) ch.addNick(nk, cp) nk.addChannel(ch, cp) return cp.Copy() }
// Dissociates an already known nick from an already known channel. // Does some tidying up to stop tracking nicks we're no longer on // any common channels with, and channels we're no longer on. func (st *stateTracker) Dissociate(ch *Channel, nk *Nick) { if ch == nil || nk == nil { logging.Error("Tracker.Dissociate(): passed nil values :-(") } else if _ch, ok := st.chans[ch.Name]; !ok || ch != _ch { // As we can implicitly delete both nicks and channels from being // tracked by dissociating one from the other, we should verify that // we're not being passed an old Nick or Channel. logging.Error("Tracker.Dissociate(): channel %s not found in "+ "(or differs from) internal state.", ch.Name) } else if _nk, ok := st.nicks[nk.Nick]; !ok || nk != _nk { logging.Error("Tracker.Dissociate(): nick %s not found in "+ "(or differs from) internal state.", nk.Nick) } else if _, ok := nk.IsOn(ch); !ok { logging.Warn("Tracker.Dissociate(): %s not on %s.", nk.Nick, ch.Name) } else if nk == st.me { // I'm leaving the channel for some reason, so it won't be tracked. st.delChannel(ch) } else { // Remove the nick from the channel and the channel from the nick. ch.delNick(nk) nk.delChannel(ch) if len(nk.chans) == 0 { // We're no longer in any channels with this nick. st.delNick(nk) } } }
// Associates an already known nick with an already known channel. func (st *stateTracker) Associate(ch *Channel, nk *Nick) *ChanPrivs { if ch == nil || nk == nil { logging.Error("Tracker.Associate(): passed nil values :-(") return nil } else if _ch, ok := st.chans[ch.Name]; !ok || ch != _ch { // As we can implicitly delete both nicks and channels from being // tracked by dissociating one from the other, we should verify that // we're not being passed an old Nick or Channel. logging.Error("Tracker.Associate(): channel %s not found in "+ "(or differs from) internal state.", ch.Name) return nil } else if _nk, ok := st.nicks[nk.Nick]; !ok || nk != _nk { logging.Error("Tracker.Associate(): nick %s not found in "+ "(or differs from) internal state.", nk.Nick) return nil } else if _, ok := nk.IsOn(ch); ok { logging.Warn("Tracker.Associate(): %s already on %s.", nk.Nick, ch.Name) return nil } cp := new(ChanPrivs) ch.addNick(nk, cp) nk.addChannel(ch, cp) return cp }
// Dissociates an already known nick from an already known channel. // Does some tidying up to stop tracking nicks we're no longer on // any common channels with, and channels we're no longer on. func (st *stateTracker) Dissociate(c, n string) { st.mu.Lock() defer st.mu.Unlock() nk, nok := st.nicks[n] ch, c*k := st.chans[c] if !c*k { // As we can implicitly delete both nicks and channels from being // tracked by dissociating one from the other, we should verify that // we're not being passed an old Nick or Channel. logging.Error("Tracker.Dissociate(): channel %s not found in "+ "internal state.", c) } else if !nok { logging.Error("Tracker.Dissociate(): nick %s not found in "+ "internal state.", n) } else if _, ok := nk.isOn(ch); !ok { logging.Warn("Tracker.Dissociate(): %s not on %s.", nk.nick, ch.name) } else if nk == st.me { // I'm leaving the channel for some reason, so it won't be tracked. st.delChannel(ch) } else { // Remove the nick from the channel and the channel from the nick. ch.delNick(nk) nk.delChannel(ch) if len(nk.chans) == 0 { // We're no longer in any channels with this nick. st.delNick(nk) } } }
func (hs *hSet) remove(hn *hNode) { hs.Lock() defer hs.Unlock() l, ok := hs.set[hn.event] if !ok { logging.Error("Removing node for unknown event '%s'", hn.event) return } if hn.next == nil { l.end = hn.prev } else { hn.next.prev = hn.prev } if hn.prev == nil { l.start = hn.next } else { hn.prev.next = hn.next } hn.next = nil hn.prev = nil hn.set = nil if l.start == nil || l.end == nil { delete(hs.set, hn.event) } }
func pushAuthHTTP(rw http.ResponseWriter, req *http.Request) { if err := req.ParseForm(); err != nil { http.Redirect(rw, req, pushFailureURL("parse"), 302) return } if req.FormValue("error") != "" { http.Redirect(rw, req, pushFailureURL("denied"), 302) return } id := req.FormValue("state") s := pc.GetByB64(id) if id == "" || s == nil { http.Redirect(rw, req, pushFailureURL("nostate"), 302) return } code := req.FormValue("code") if code == "" { http.Redirect(rw, req, pushFailureURL("notoken"), 302) return } tok, err := push.Exchange(code) if err != nil { logging.Error("Failed to get access token for %s: %v", s.Nick, err) http.Redirect(rw, req, pushFailureURL("exchange"), 302) return } s.Token = tok pc.SetState(s) http.Redirect(rw, req, pushDeviceURL(id), 302) }
func (p *Plugin) dumpBans(target string) { num := 0 // Fetch ban list banlist, err := p.mode.Bans(target) if err != nil { logging.Warn("Could not fetch ban list, old bans won't get handled") return } tbmgr := p.ensureTemporaryBanManager(target) // Save only bans from us for _, ban := range banlist { if ban.Nick != p.bot.Me().Nick { // Not a ban from us (going by the nickname at least) isOldHostmask := false for _, hostmask := range p.OldHostmasks { // TODO - Test this implementation hostmask = regexp.QuoteMeta(hostmask) hostmask = strings.Replace(hostmask, "\\*", ".*", -1) hostmask = strings.Replace(hostmask, "\\?", ".?", -1) if matched, err := regexp.MatchString(hostmask, ban.Src); matched { isOldHostmask = true break } else if err != nil { logging.Error("vpnbot.Plugin: dumpBans regular expression failed: %v", err) break } } if !isOldHostmask { // Not a ban from an old hostmask either continue } } if _, ok := tbmgr.Get(ban.Hostmask); ok { // We already have this ban saved continue } if err := tbmgr.Add(NewTemporaryBan( ban.Nick, ban.Hostmask, ban.Src, "Migrated old ban", 48*time.Hour+ban.Timestamp.Sub(time.Now()))); err != nil { logging.Warn("Could not migrate ban on %v: %v", ban.Hostmask, err) } num++ } if num > 0 { p.syncBans(target) logging.Info("Migrated %v bans", num) } }
func (ns *namespace) get(key string) interface{} { var e Entry if err := ns.Get(ns.K(key), &e); err != nil && err != mgo.ErrNotFound && err != boltdb.ErrTxNotWritable { logging.Error("Couldn't get config entry for ns=%q key=%q: %v", ns.ns, key, err) return nil } return e.Value }
func (st *stateTracker) delNick(nk *Nick) { if nk == st.me { // Shouldn't get here => internal state tracking code is fubar. logging.Error("Tracker.DelNick(): TRYING TO DELETE ME :-(") return } delete(st.nicks, nk.Nick) for ch, _ := range nk.chans { nk.delChannel(ch) ch.delNick(nk) if len(ch.nicks) == 0 { // Deleting a nick from tracking shouldn't empty any channels as // *we* should be on the channel with them to be tracking them. logging.Error("Tracker.delNick(): deleting nick %s emptied "+ "channel %s, this shouldn't happen!", nk.Nick, ch.Name) } } }
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 pushDeviceHTTP(rw http.ResponseWriter, req *http.Request) { if err := req.ParseForm(); err != nil { http.Redirect(rw, req, pushFailureURL("parse"), 302) return } id := req.FormValue("state") s := pc.GetByB64(id) if id == "" || s == nil { http.Redirect(rw, req, pushFailureURL("nostate"), 302) return } if req.Method == "POST" { if s.Iden = req.FormValue("iden"); s.Iden == "" { http.Redirect(rw, req, pushFailureURL("noiden"), 302) return } s.Pin = fmt.Sprintf("%06x", rand.Intn(1e6)) if err := push.Confirm(s); err != nil { logging.Error("Failed to send confirmation push for %s: %v", s.Nick, err) http.Redirect(rw, req, pushFailureURL("push"), 302) return } pc.SetState(s) http.Redirect(rw, req, pushSuccessURL(), 302) return } // get device list and print a form devs, err := push.GetDevices(s) if err != nil { logging.Error("Failed to get devices for %s: %v", s.Nick, err) http.Redirect(rw, req, pushFailureURL("device"), 302) return } if len(devs) == 0 { strings.NewReader(pushNoDeviceHTML).WriteTo(rw) return } if err = pushDeviceTmpl.Execute(rw, &pushDevice{id, devs}); err != nil { logging.Error("Template execution failed: %v", err) // assuming here that failure occurred because we couldn't write return } }
func Init() *Collection { pc := &Collection{db.Init().C(COLLECTION)} if err := pc.EnsureIndex(mgo.Index{ Key: []string{"nick"}, Unique: true, }); err != nil { logging.Error("Couldn't create an index on push: %s", err) } return pc }
// Write 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) { 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 { logging.Error("irc.send(): %s", err.Error()) conn.shutdown() return } if err := conn.io.Flush(); err != nil { logging.Error("irc.send(): %s", err.Error()) conn.shutdown() return } logging.Debug("-> %s", line) }
func (plugin *Plugin) isAdmin(mask string) bool { for _, adminmask := range plugin.Admins { // TODO - Test this implementation adminmask = regexp.QuoteMeta(adminmask) adminmask = strings.Replace(adminmask, "\\*", ".*", -1) adminmask = strings.Replace(adminmask, "\\?", ".?", -1) if matched, err := regexp.MatchString(adminmask, mask); matched { return true } else if err != nil { logging.Error("vpnbot.Plugin: isAdmin regular expression failed: %v", err) break } } return false }
// send is started as a goroutine after a connection is established. // It shuttles data from the output channel to write(), and is killed // when Conn.die is closed. func (conn *Conn) send() { for { select { case line := <-conn.out: if err := conn.write(line); err != nil { logging.Error("irc.send(): %s", err.Error()) // We can't defer this, because shutdown() waits for it. conn.wg.Done() conn.shutdown() return } case <-conn.die: // control channel closed, bail out conn.wg.Done() return } } }
func Client(cfg *Config) *Conn { if cfg == nil { cfg = NewConfig("__idiot__") } if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" { cfg.Me = state.NewNick("__idiot__") cfg.Me.Ident = "goirc" cfg.Me.Name = "Powered by GoIRC" } dialer := new(net.Dialer) if cfg.LocalAddr != "" { if !hasPort(cfg.LocalAddr) { cfg.LocalAddr += ":0" } local, err := net.ResolveTCPAddr("tcp", cfg.LocalAddr) if err == nil { dialer.LocalAddr = local } else { logging.Error("irc.Client(): Cannot resolve local address %s: %s", cfg.LocalAddr, err) } } conn := &Conn{ cfg: cfg, dialer: dialer, in: make(chan *Line, 32), out: make(chan string, 32), intHandlers: handlerSet(), fgHandlers: handlerSet(), bgHandlers: handlerSet(), stRemovers: make([]Remover, 0, len(stHandlers)), lastsent: time.Now(), } conn.addIntHandlers() conn.initialise() return conn }
// 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 mongoIndexes(c db.Collection) { err := c.Mongo().EnsureIndex(mgo.Index{Key: []string{"ns", "key"}, Unique: true}) if err != nil { logging.Error("Couldn't create index on sp0rkle.conf: %s", err) } }
func (conn *Conn) LogPanic(line *Line) { if err := recover(); err != nil { _, f, l, _ := runtime.Caller(2) logging.Error("%s:%d: panic: %v", f, l, err) } }
// The main program logic. func main() { // Load configuration path from flags flag.Parse() // Initialize the logger logger = glogging.GLogger{} logging.SetLogger(logger) // Check if we're supposed to generate a default config if *generateDefault { logger.Debug("Saving default configuration...") if err := defaultConfiguration.Save(*configPath); err != nil { logger.Error("Failed at saving default configuration: %v", err) os.Exit(1) } logger.Info("Saved default configuration.") os.Exit(0) } // Check if we're supposed to migrate an old config if *migratePath != "" { logger.Debug("Migrating old configuration...") if c, err := LoadV1Config(*migratePath); err != nil { logger.Error("Failed to load old configuration: %v", err) os.Exit(1) } else { newC := c.Migrate() if err := newC.Save(*configPath); err != nil { logger.Error("Migration failed: %v", err) os.Exit(1) } if err := newC.Validate(); err != nil { logger.Warn("Migration successful but found errors while "+ "validating the new configuration, you should fix this before "+ "running the bot: %v", err) os.Exit(2) } } logger.Info("Migration successful.") os.Exit(0) } // Load configuration from configuration path if c, err := Load(*configPath); err != nil { logger.Error("Can't load configuration from %v: %v\n", *configPath, err) os.Exit(1) } else { loadedConfiguration = c } logger.Debug("Loaded configuration will be printed below.") logger.Debug("%#v", loadedConfiguration) // Validate configuration if err := loadedConfiguration.Validate(); err != nil { logger.Error("The configuration is invalid: %v\n", err) os.Exit(2) } // Now initialize the bot logger.Info("Initializing vpnbot %v...", version) b := bot.NewBot( loadedConfiguration.Server.Address, loadedConfiguration.Server.SSL, loadedConfiguration.Nick, loadedConfiguration.Ident, []string{}) b.Conn().Config().Version = fmt.Sprintf("vpnbot/%v", version) b.Conn().Config().Recover = func(conn *client.Conn, line *client.Line) { if err := recover(); err != nil { logging.Error("An internal error occurred: %v\n%v", err, string(debug.Stack())) } } b.Conn().Config().Pass = loadedConfiguration.Server.Password if loadedConfiguration.Name != "" { b.Conn().Config().Me.Name = loadedConfiguration.Name } // Load plugins // TODO - Move this into its own little intelligent loader struct, maybe. isupportPlugin := isupport.Register(b) modePlugin := mode.Register(b, isupportPlugin) nickservPlugin := nickserv.Register(b, modePlugin) nickservPlugin.Username = loadedConfiguration.NickServ.Username nickservPlugin.Password = loadedConfiguration.NickServ.Password nickservPlugin.Channels = loadedConfiguration.Channels switch { case *makeTempBans: // Run in tempban dumping mode // Prepare channels to let us know about dumped bans doneChan := make(map[string]chan interface{}) for _, channel := range loadedConfiguration.Channels { doneChan[strings.ToLower(channel)] = make(chan interface{}, 1) } // Load the tempban dumping plugin dumptempbanPlugin := dumptempban.Register(b, isupportPlugin, modePlugin) dumptempbanPlugin.DumpedBansFunc = func(target string, num int, err error) { if err != nil { logging.Error("Failed to dump bans for %v: %v", target, err) } else { logging.Info("Dumped %v bans for %v successfully.", num, target) } if done, ok := doneChan[strings.ToLower(target)]; ok { done <- nil } } // Start up the bot asynchronously go b.Run() // Wait for all channels to be done for _, done := range doneChan { <-done } b.Quit("Ban dumping done.") default: // Run normally // Load plugins autojoin.Register(b) adminplugin.Register(b, loadedConfiguration.Admins) bots.Register(b, isupportPlugin) tempbanPlugin := tempban.Register(b, isupportPlugin, modePlugin) tempbanPlugin.OldHostmasks = loadedConfiguration.OldHostmasks whoisPlugin := whois.Register(b, isupportPlugin) vpnbotPlugin := vpnbot.Register(b, whoisPlugin, isupportPlugin, tempbanPlugin, nickservPlugin) vpnbotPlugin.Admins = loadedConfiguration.Admins // This is to update the configuration when the bot joins channels b.HandleFunc("join", func(c *client.Conn, line *client.Line) { // Arguments: [ <channel> ] // Make sure this is about us if line.Nick != c.Me().Nick { return } // I don't think method calls are a good idea in a loop channel := line.Target() // See if we already had this channel saved for _, savedChannel := range loadedConfiguration.Channels { if strings.EqualFold(savedChannel, channel) { return // Channel already saved } } // Store this channel logger.Info("Adding %v to configured channels", channel) loadedConfiguration.Channels = append( loadedConfiguration.Channels, channel) // And save to configuration file! loadedConfiguration.Save(*configPath) }) b.HandleFunc("kick", func(c *client.Conn, line *client.Line) { // Arguments: [ <channel>, <nick>, <reason> ] // Make sure this is about us if line.Args[1] != c.Me().Nick { return } // I don't think method calls are a good idea in a loop channel := line.Target() for index, savedChannel := range loadedConfiguration.Channels { if strings.EqualFold(savedChannel, channel) { // Delete the channel logger.Info("Removing %v from configured channels", savedChannel) loadedConfiguration.Channels = append( loadedConfiguration.Channels[0:index], loadedConfiguration.Channels[index+1:]...) // And save to configuration file! loadedConfiguration.Save(*configPath) return } } }) b.HandleFunc("part", func(c *client.Conn, line *client.Line) { // Arguments: [ <channel> (, <reason>) ] // Make sure this is about us if line.Nick != c.Me().Nick { return } // I don't think method calls are a good idea in a loop channel := line.Target() for index, savedChannel := range loadedConfiguration.Channels { if strings.EqualFold(savedChannel, channel) { // Delete the channel logger.Info("Removing %v from configured channels", savedChannel) loadedConfiguration.Channels = append( loadedConfiguration.Channels[0:index], loadedConfiguration.Channels[index+1:]...) // And save to configuration file! loadedConfiguration.Save(*configPath) return } } }) // Run the bot b.Run() } }
func (ns *namespace) set(key string, value interface{}) { e := Entry{Ns: ns.ns, Key: key, Value: value} if err := ns.Put(ns.K(key), &e); err != nil { logging.Error("Couldn't set config entry %q: %v", e, err) } }