Beispiel #1
0
// JoinMUC joins to a conference with nick & optional password
// init & add Conference to the s.conferences map
func (s *Session) JoinMUC(confJID, nick, password string) error {
	bareJID := xmpp.RemoveResourceFromJid(confJID)
	nick = strings.TrimSpace(nick)
	if len(nick) == 0 {
		nick = BOTNAME
	}

	for _, c := range s.conferences {
		if c.JID == bareJID {
			msg := fmt.Sprintf("I'm already in %q with nick %q", c.JID, c.Parser.OwnNick)
			return errors.New(msg)
		}
	}

	if s.conferences == nil {
		s.conferences = make(map[string]Conference)
	}
	// FIXME: fetch settings from database. Separate for each conference.
	parser := GluxiParser{
		OwnNick:      nick,
		NickSuffixes: s.config.c.Section("muc").Key("nick_suffixes").String(),
		Prefix:       s.config.c.Section("muc").Key("prefix").String(),
	}
	conf := Conference{
		JID:      bareJID,
		Password: password,
		Parser:   parser,
	}
	s.conferences[bareJID] = conf
	msg := fmt.Sprintf("Conference %q with nick %q added",
		conf.JID, parser.OwnNick)
	if len(conf.Password) > 0 {
		msg += " and password: "******"account").Key("lang").String(),
		To:   conf.JID + "/" + parser.OwnNick,
		Caps: &xmpp.ClientCaps{
			Hash: "sha-1",
			Node: NODE,
			Ver:  ver,
		},
		X: []*xmpp.X{&xmpp.X{
			XMLName:  xml.Name{Space: "http://jabber.org/protocol/muc", Local: "x"},
			Password: conf.Password,
			History:  &xmpp.History{MaxChars: "0"}}},
	}
	// fmt.Println("DEBUG: JoinMUC: {")
	// PrintXML(st)
	// fmt.Println("\n}")
	return s.conn.SendStanza(st)
}
Beispiel #2
0
// ConfDel deletes conference
func (s *Session) ConfDel(stanza *xmpp.MUCPresence) (deleted Conference, err error) {
	bareJID := xmpp.RemoveResourceFromJid(stanza.From)
	if conf, ok := s.conferences[bareJID]; ok {
		deleted = conf
		delete(s.conferences, conf.JID)
		log.Printf("Conference %q deleted!", conf.JID)
		return
	}
	return Conference{}, errors.New("No such conference! " + bareJID)
}
Beispiel #3
0
func main() {
	s := Session{
		config: &Config{},
	}

	err := s.config.ReadFile(cfgfile)
	if err != nil {
		log.Fatal(err)
	}
	// FIXME: need we respect XDG_CONFIG_DIRS?
	//if err := s.config.ReadFile(cfgfile); err != nil {
	//	log.Fatal(err)
	//}
	account := s.config.c.Section("account")
	conn, err := xmpp.Dial(
		account.Key("server").String()+":"+account.Key("port").String(),
		account.Key("user").String(),
		account.Key("server").String(),
		account.Key("password").String(),
		s.config.xmppConfig)
	if err != nil {
		fmt.Printf("cant connect! %v", err)
		return
	}
	s.conn = conn
	// s.conn.SignalPresence("")
	ver, _ := s.GetInfoReply().VerificationString()

	s.conn.SendStanza(
		xmpp.ClientPresence{
			Lang: account.Key("lang").String(),
			Caps: &xmpp.ClientCaps{
				Hash: "sha-1",
				Node: NODE,
				Ver:  ver,
			},
		},
	)

	_, _, err = s.conn.RequestRoster()
	muc := s.config.c.Section("muc")
	parser := GluxiParser{
		Prefix:       muc.Key("prefix").String(),
		NickSuffixes: muc.Key("nick_suffixes").String(),
		OwnNick:      muc.Key("nick").String(),
	}
	stanzaChan := make(chan xmpp.Stanza)
	go s.readMessages(stanzaChan)

	s.timeouts = make(map[xmpp.Cookie]time.Time)
	s.conferences = make(map[string]Conference)

	ticker := time.NewTicker(1 * time.Second)
	pingticker := time.NewTicker(time.Duration(account.Key("keepalive").MustInt()) * time.Second)

	fmt.Println(muc.Key("autojoin").Strings("\n"))
	for _, joinTo := range muc.Key("autojoin").Strings("\n") {
		confJID := joinTo
		pass := ""
		if tmp := strings.SplitN(joinTo, ",", -1); len(tmp) == 2 {
			confJID = strings.TrimSpace(tmp[0])
			pass = strings.TrimSpace(tmp[1])
		}
		bareJID, nick := SplitJID(confJID)
		if len(nick) == 0 {
			nick = parser.OwnNick
		}
		if err := s.JoinMUC(bareJID, nick, pass); err != nil {
			for _, j := range s.config.c.Section("access").Key("owners").Strings("\n") {
				if IsValidJID(j) { // FIXME: temorary code.
					s.conn.Send(j, "autojoin: "+err.Error())
				}
			}
		}
	}

	plugins := s.config.c.Section("plugins").KeysHash(true)
	filters := s.config.c.Section("filters")
	filterGetTitle, _ := filters.Key("gettitle").Bool()
	filterURLUnescape, _ := filters.Key("url_unescape").Bool()
	filterTurn, _ := filters.Key("turn").Bool()

	for {
		select {
		case now := <-ticker.C:
			haveExpired := false
			for _, expiry := range s.timeouts {
				if now.After(expiry) {
					haveExpired = true
					break
				}
			}
			if !haveExpired {
				continue
			}

			newTimeouts := make(map[xmpp.Cookie]time.Time)
			for cookie, expiry := range s.timeouts {
				if now.After(expiry) {
					s.conn.Cancel(cookie)
				} else {
					newTimeouts[cookie] = expiry
				}
			}
			s.timeouts = newTimeouts

		case tick := <-pingticker.C:
			s.conn.KeepAlive()
			_ = tick
		case stanza, ok := <-stanzaChan:
			if !ok {
				fmt.Printf("Error: %v", err)
				return // Bail out. We're disconnected.
			}

			switch st := stanza.Value.(type) {
			case *xmpp.ClientMessage:
				if st.IsDelayed() || len(st.Subject) > 0 { // ignore history (delayed) and topic messages
					continue
				}
				msg := st.Body
				if filterGetTitle && (!strings.HasSuffix(st.From, parser.OwnNick) && IsContainsURL(msg)) {
					message := strings.Replace(msg, "\n", " ", -1)
					for _, word := range strings.Split(message, " ") {
						if len(word) > 4 && word[0:4] == "http" {
							go s.RunPlugin(stanza, "gettitle", false, word)
							break
						}
					}
				}

				if st.Type == "groupchat" && !strings.HasSuffix(st.From, parser.OwnNick) {
					conf, ok := s.conferences[xmpp.RemoveResourceFromJid(st.From)]
					msg := conf.ChompNick(msg) // chomp "nick:" first
					if !ok {
						continue
					}
					if filterURLUnescape && IsContainsURL(msg) {
						if link, err := url.QueryUnescape(msg); err == nil {
							if strings.Count(msg, "%") > 3 {
								s.conn.SendMUC(conf.JID, "groupchat", link)
							}
						}
					} else if filterTurn && IsWrongLayout(msg) {
						// fmt.Printf("MSG WRONG LAYOUT: %q\n%#v", msg, st)
						s.conn.SendMUC(conf.JID, "groupchat", Turn(msg))
					}
				}
				if parser.Init(st); parser.IsForMe {
					if len(parser.Tokens) <= 1 {
						continue
					}
					CMD := strings.ToUpper(parser.Tokens[1]) // FIXME:
					var toNick, toJID, fromNick, confJID string
					toJID = st.From
					_ = fromNick
					_ = confJID
					bareJID, resource := SplitJID(st.From)
					conf, ok := s.conferences[bareJID]
					if ok {
						confJID = bareJID
						if len(parser.Tokens) >= 3 {
							param := parser.Tokens[2]
							if _, ok := conf.NickIndex(param); ok {
								toJID = conf.JID + "/" + param
							} else if strings.Contains(param, ".") { // requesting from domain
								toJID = param
							}
						} else { // w/o params. User requesting himself
							toJID = st.From
							fromNick = resource
						}
					}
					// if st.Type == "chat" { // actual st stanza received from MUC
					// 	toJID = st.From
					// }
					switch CMD {
					// Run external commands (CLI)
					default:
						if cli, ok := plugins[CMD]; ok {
							plugin := strings.Split(cli, " ")
							params := parser.Tokens[2:]
							if len(plugin) > 1 {
								params = append(plugin[1:], parser.Tokens[2:]...)
							}
							go s.RunPlugin(stanza, plugin[0], true, params...)
						}

					case "JOIN":
						conf := parser.Tokens[2]
						parts := strings.Split(conf, "/")
						var password string

						if len(parts) == 2 {
							conf = parts[0]
							parser.OwnNick = parts[1]
						}
						if len(parser.Tokens) == 4 { // user specify a password
							password = parser.Tokens[3]
						}
						if err := s.JoinMUC(conf, parser.OwnNick, password); err != nil {
							for _, j := range s.config.c.Section("access").Key("owners").Strings("\n") {
								if IsValidJID(j) { // FIXME: temorary code.
									s.conn.Send(j, err.Error())
								}
							}
						}

					case "LEAVE", "EXIT", "QUIT", "PART":
						conf, ok := s.conferences[xmpp.RemoveResourceFromJid(st.From)]
						param1, err := parser.Token(2)
						param1 = xmpp.RemoveResourceFromJid(param1)
						status := "I'm quit!"
						if param2, err := parser.Token(3); err == nil {
							status = param2
						}
						if !ok {
							if err != nil {
								s.Respond(stanza, "I'm not in conference!", false)
								continue
							}
							conf, ok = s.conferences[param1]
							if !ok {
								s.Respond(stanza, "I'm not in "+param1, false)
								continue
							}
						}
						if err == nil {
							tmp, ok := s.conferences[param1]
							if !ok {
								s.Respond(stanza, "I'm not in "+param1, false)
								continue
							}
							conf = tmp
						}

						if err := s.conn.LeaveMUC(conf.JID+"/"+conf.Parser.OwnNick, status); err != nil {
							s.Respond(stanza, err.Error(), false)
							continue
						}
						s.Respond(stanza, "I'm quit from "+conf.JID, false)

					case "INVITE": // FIXME: need to check XEP-0249 support first
						if s.config.c.Section("internal").Key("invite").MustBool() {
							continue
						}
						conf, ok := s.conferences[xmpp.RemoveResourceFromJid(st.From)]
						if !ok {
							s.conn.Send(st.From, "Can't be used in a roster!")
							continue
						}
						var to, reason string
						if len(parser.Tokens) >= 3 {
							to = parser.Tokens[2]
							if len(parser.Tokens) >= 4 {
								reason = parser.Tokens[3]
							}
						}
						// FIXME: resolve jid from nick (db stuff)
						// fmt.Printf("INVITE: to %q\nconf: %q\npass: %q\nreason: %q\n", to, conf.JID, conf.Password, reason)
						s.conn.DirectInviteMUC(to, conf.JID, conf.Password, reason)

					case "VERSION":
						if s.config.c.Section("internal").Key("version").MustBool() {
							continue
						}
						fmt.Printf("TONICK: %q\n", toNick)
						replyChan, cookie, err := s.conn.SendIQ(toJID, "get", xmpp.VersionQuery{})
						if err != nil {
							fmt.Printf("Error sending iq:version request %v %d\n", err, cookie)
						}
						//fmt.Println(st)
						// FIXME: move it to the s.Conferences[jid].timeouts[cookie]
						s.timeouts[cookie] = time.Now().Add(5 * time.Second)
						go s.awaitVersionReply(replyChan, stanza)

					case "TIME":
						if s.config.c.Section("internal").Key("time").MustBool() {
							continue
						}
						replyChan, cookie, err := s.conn.SendIQ(toJID, "get", xmpp.TimeQuery{})
						if err != nil {
							fmt.Printf("Error sending urn:xmpp:time request %#v\n, %d\n", err, cookie)
						}
						s.timeouts[cookie] = time.Now().Add(5 * time.Minute)
						go s.awaitTimeReply(replyChan, stanza)

					case "PING":
						if s.config.c.Section("internal").Key("ping").MustBool() {
							continue
						}
						replyChan, cookie, err := s.conn.SendIQ(toJID, "get", xmpp.PingQuery{})
						if err != nil {
							fmt.Printf("Error sending urn:xmpp:ping request %v %d\n", err, cookie)
						}
						s.timeouts[cookie] = time.Now().Add(10 * time.Minute)
						go func(cookie xmpp.Cookie) {
							r := <-replyChan
							t := s.timeouts[cookie]
							var msg string
							elapsed := time.Now().Sub(t) + 10*time.Minute
							switch iq := r.Value.(type) {
							case *xmpp.ClientIQ:
								switch iq.Type {
								case "error": // FIXME: human readable error
									buf := bytes.NewBuffer(iq.Query)
									msg = fmt.Sprintf("IQ-reply:\n%s\n", buf)
								case "result":
									from := iq.From
									if st.Type == "groupchat" && strings.HasPrefix(iq.From, conf.JID) {
										from = strings.SplitN(iq.From, "/", 2)[1]
									}
									msg = fmt.Sprintf("pong from %q after %.3f seconds.", from, elapsed.Seconds())

								}
								s.Respond(stanza, msg, false)
							}
						}(cookie)
					}
				}
			case *xmpp.ClientIQ:
				if st.Type != "get" && st.Type != "set" {
					continue
				}
				reply := s.processIQ(st)
				if reply == nil {
					reply = xmpp.ErrorReply{
						Type:  "cancel",
						Error: xmpp.ErrorBadRequest{},
					}
				}
				if err := s.conn.SendIQReply(st.From, "result", st.Id, reply); err != nil {
					msg := fmt.Sprintf("Failed to send IQ message: %#v", err)
					s.Respond(stanza, msg, false)
				}
			case *xmpp.MUCPresence:
				s.processPresence(st)
			}
		default:
		}
		time.Sleep(150 * time.Millisecond) // release CPU
	}

}
Beispiel #4
0
// processPresence handle incoming presences
// it also handles MUC (XEP-0045) "onJoin" presences.
func (s *Session) processPresence(stanza *xmpp.MUCPresence) {
	//	if out, err := xml.Marshal(stanza); err != nil {
	//		log.SetPrefix("!!!ERROR!!! ")
	//		log.Printf("PRESENCE: %#v\n", err)
	//	} else {
	//		log.SetPrefix("PRESENCE ")
	//		log.Printf("%#v\n%s\n-- \n", stanza, out)
	//	}
	confJID := xmpp.RemoveResourceFromJid(stanza.From)
	switch stanza.Type {
	case "unavailable":
		if conf, ok := s.conferences[confJID]; ok {
			occupant, err := conf.OccupantDel(stanza)
			if err != nil {
				log.Println(err)
			}
			s.conferences[confJID] = conf
			// We has left conference
			if occupant.Nick == conf.Parser.OwnNick {
				conf, err := s.ConfDel(stanza)
				if err != nil {
					fmt.Println(err)
				}
				fmt.Printf("We're %q has quit %q!\n", occupant.Nick, conf.JID)
			}
		}
	case "": // empty <presence>
		if len(stanza.X) <= 0 {
			return
		}
		x := stanza.X[0]
		if len(stanza.X) > 1 {
			fmt.Printf("SECOND X: %#v\n\n", stanza.X[1])
		}
		fmt.Printf("%#v", stanza.X)
		switch x.XMLName.Space + " " + x.XMLName.Local {
		case "http://jabber.org/protocol/muc#user x":
			// fromJid := xmpp.RemoveResourceFromJid(stanza.From)
			// fromJid = strings.ToLower(fromJid)
			// FIXME: need for joined occupant nick catching
			fromJid, _ := SplitJID(stanza.From)

			for jid, conf := range s.conferences {
				if !conf.Joined && conf.JID == fromJid {
					conf.Joined = true
					s.conferences[jid] = conf
					msg := fmt.Sprintf("I have joined to %#v", conf)
					fmt.Println(msg) // FIXME: send it to requester
				}
			}
			if conf, ok := s.conferences[fromJid]; ok {
				conf.OccupantAdd(stanza)
				s.conferences[fromJid] = conf
			}
		default:
			fmt.Println("WTF?: ", stanza)
		}
	case "subscribe": // http://xmpp.org/rfcs/rfc6121.html#sub-request-gen
		jid := xmpp.RemoveResourceFromJid(stanza.From)
		if err := s.conn.SendPresence(jid, "subscribed", ""); err != nil {
			s.conn.Send(stanza.From, "Subscription error")
		}
		s.conn.SendPresence(jid, "subscribe", "")
	case "subscribed":
		s.conn.Send(stanza.From, "Hi!")
	case "unsubscribe":
		s.conn.Send(stanza.From, "F**k you, then!")
	case "error":
		var msg string
		conf, err := s.ConfDel(stanza)
		if err != nil {
			fmt.Println(err)
		}

		bareJid, nick := SplitJID(stanza.From)
		switch stanza.Error.Any.Space + " " + stanza.Error.Any.Local {
		case "urn:ietf:params:xml:ns:xmpp-stanzas conflict":
			msg = fmt.Sprintf("Can't join %q with nick %q. Error %s: Nickname conflict!",
				bareJid, nick, stanza.Error.Code)
		case "urn:ietf:params:xml:ns:xmpp-stanzas not-authorized":
			msg = fmt.Sprintf("I can't join %q: %#v", conf.JID, stanza.Error)
		case "urn:ietf:params:xml:ns:xmpp-stanzas forbidden":
			msg = fmt.Sprintf("Can't join %q with nick %q. Error %s: I'm banned in this conference!",
				bareJid, nick, stanza.Error.Code)
		default:
			msg = fmt.Sprintf("We got error presence: type: %q, code %q: %#v",
				stanza.Error.Type, stanza.Error.Code, stanza.Error)
		}

		fmt.Println(msg)
		for _, j := range s.config.c.Section("access").Key("owners").Strings("\n") {
			if IsValidJID(j) {
				s.conn.Send(j, msg)
			}
		}
	default:
		log.SetPrefix("WARNING: ")
		if out, err := xml.Marshal(stanza); err == nil {
			log.Printf("Unknown presence stanza:\n %s\n", out) // FIXME: send it to requester
		}
	}
}