2014-05-11 16:18:55 +00:00
|
|
|
/*
|
|
|
|
goircd -- minimalistic simple Internet Relay Chat (IRC) server
|
2021-01-05 17:48:29 +00:00
|
|
|
Copyright (C) 2014-2021 Sergey Matveev <stargrave@stargrave.org>
|
2014-05-11 16:18:55 +00:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
2019-09-27 08:32:53 +00:00
|
|
|
the Free Software Foundation, version 3 of the License.
|
2014-05-11 16:18:55 +00:00
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2015-05-09 15:27:06 +00:00
|
|
|
|
2014-05-11 16:18:55 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"regexp"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
2016-03-26 14:10:04 +00:00
|
|
|
"sync"
|
2014-05-11 16:18:55 +00:00
|
|
|
)
|
|
|
|
|
2014-05-13 19:59:09 +00:00
|
|
|
var (
|
2014-08-09 12:42:19 +00:00
|
|
|
RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
|
2014-05-13 19:59:09 +00:00
|
|
|
)
|
|
|
|
|
2014-05-11 16:18:55 +00:00
|
|
|
// Sanitize room's name. It can consist of 1 to 50 ASCII symbols
|
|
|
|
// with some exclusions. All room names will have "#" prefix.
|
2014-05-13 19:59:09 +00:00
|
|
|
func RoomNameValid(name string) bool {
|
2014-08-09 12:42:19 +00:00
|
|
|
return RERoom.MatchString(name)
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Room struct {
|
2020-11-06 17:10:06 +00:00
|
|
|
name string
|
|
|
|
topic string
|
|
|
|
key string
|
2015-10-11 08:12:55 +00:00
|
|
|
members map[*Client]struct{}
|
2020-11-06 17:10:06 +00:00
|
|
|
events chan ClientEvent
|
2016-03-26 14:10:04 +00:00
|
|
|
sync.RWMutex
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 17:10:06 +00:00
|
|
|
var (
|
2021-12-29 15:19:22 +00:00
|
|
|
RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
|
2020-11-06 17:10:06 +00:00
|
|
|
rooms map[string]*Room = make(map[string]*Room)
|
|
|
|
roomsLock sync.RWMutex
|
|
|
|
roomsWG sync.WaitGroup
|
|
|
|
)
|
|
|
|
|
2016-03-26 14:10:04 +00:00
|
|
|
func (room *Room) String() (name string) {
|
|
|
|
room.RLock()
|
|
|
|
name = *room.name
|
|
|
|
room.RUnlock()
|
|
|
|
return
|
2014-08-14 10:01:54 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 17:10:06 +00:00
|
|
|
func (r *Room) SendTopic(c *Client) {
|
|
|
|
t := r.topic
|
|
|
|
if t == "" {
|
|
|
|
c.ReplyNicknamed("331", r.name, "No topic is set")
|
|
|
|
} else {
|
|
|
|
c.ReplyNicknamed("332", r.name, t)
|
2015-10-11 08:12:55 +00:00
|
|
|
}
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 17:10:06 +00:00
|
|
|
func (r *Room) SendNames(c *Client) {
|
|
|
|
allowed := false
|
|
|
|
if r.key == "" {
|
|
|
|
allowed = true
|
|
|
|
} else if _, isMember := r.members[c]; isMember {
|
|
|
|
allowed = true
|
|
|
|
}
|
|
|
|
if !allowed {
|
|
|
|
c.ReplyNicknamed("475", r.name, "Cannot join channel (+k)")
|
|
|
|
return
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
2020-11-06 17:10:06 +00:00
|
|
|
r.RLock()
|
|
|
|
nicknames := make([]string, 0, len(r.members))
|
|
|
|
for member := range r.members {
|
|
|
|
nicknames = append(nicknames, member.nickname)
|
|
|
|
}
|
|
|
|
r.RUnlock()
|
|
|
|
sort.Strings(nicknames)
|
|
|
|
maxLen := 512 - len(*hostname) - 2 - 2
|
|
|
|
|
|
|
|
MoreNicknames:
|
|
|
|
lenAll := 0
|
|
|
|
lenName := 0
|
|
|
|
for i, n := range nicknames {
|
|
|
|
lenName = len(n) + 1
|
|
|
|
if lenAll+lenName >= maxLen {
|
|
|
|
c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames[:i-1], " "))
|
|
|
|
nicknames = nicknames[i:]
|
|
|
|
goto MoreNicknames
|
|
|
|
}
|
|
|
|
lenAll += lenName
|
|
|
|
}
|
|
|
|
if len(nicknames) > 0 {
|
|
|
|
c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames, " "))
|
|
|
|
}
|
|
|
|
c.ReplyNicknamed("366", r.name, "End of NAMES list")
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
|
|
|
|
2018-03-04 17:20:25 +00:00
|
|
|
func (room *Room) Match(other string) bool {
|
|
|
|
return strings.ToLower(*room.name) == strings.ToLower(other)
|
|
|
|
}
|
|
|
|
|
2014-05-11 16:18:55 +00:00
|
|
|
func (room *Room) SendTopic(client *Client) {
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RLock()
|
2015-10-11 08:12:55 +00:00
|
|
|
if *room.topic == "" {
|
2016-03-26 14:10:04 +00:00
|
|
|
client.ReplyNicknamed("331", room.String(), "No topic is set")
|
2014-05-11 16:18:55 +00:00
|
|
|
} else {
|
2016-03-26 14:10:04 +00:00
|
|
|
client.ReplyNicknamed("332", room.String(), *room.topic)
|
2020-11-06 17:10:06 +00:00
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
|
|
|
|
2015-10-11 08:12:55 +00:00
|
|
|
// Send message to all room's subscribers, possibly excluding someone.
|
2014-08-09 12:42:19 +00:00
|
|
|
func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) {
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RLock()
|
2014-05-11 16:18:55 +00:00
|
|
|
for member := range room.members {
|
2014-08-09 12:42:19 +00:00
|
|
|
if (len(clientToIgnore) > 0) && member == clientToIgnore[0] {
|
2014-05-11 16:18:55 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
member.Msg(msg)
|
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (room *Room) StateSave() {
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RLock()
|
|
|
|
stateSink <- StateEvent{room.String(), *room.topic, *room.key}
|
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 17:10:06 +00:00
|
|
|
func (r *Room) Processor(events <-chan ClientEvent) {
|
|
|
|
for e := range events {
|
|
|
|
c := e.client
|
|
|
|
switch e.eventType {
|
2015-10-11 08:12:55 +00:00
|
|
|
case EventTerm:
|
2020-11-06 17:10:06 +00:00
|
|
|
roomsWG.Done()
|
2015-10-11 08:12:55 +00:00
|
|
|
return
|
2014-08-09 12:42:19 +00:00
|
|
|
case EventNew:
|
2020-11-06 17:10:06 +00:00
|
|
|
r.Lock()
|
|
|
|
r.members[c] = struct{}{}
|
|
|
|
r.Unlock()
|
2015-10-11 08:12:55 +00:00
|
|
|
if *verbose {
|
2020-11-06 17:10:06 +00:00
|
|
|
log.Println(c, "joined", r.name)
|
2014-06-08 00:49:27 +00:00
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
room.Unlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
room.SendTopic(client)
|
2016-03-26 14:10:04 +00:00
|
|
|
room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, room.String()))
|
|
|
|
logSink <- LogEvent{room.String(), *client.nickname, "joined", true}
|
2015-10-11 08:12:55 +00:00
|
|
|
nicknames := make([]string, 0)
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RLock()
|
2014-05-11 16:18:55 +00:00
|
|
|
for member := range room.members {
|
2015-10-11 08:12:55 +00:00
|
|
|
nicknames = append(nicknames, *member.nickname)
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
sort.Strings(nicknames)
|
2016-03-26 14:10:04 +00:00
|
|
|
client.ReplyNicknamed("353", "=", room.String(), strings.Join(nicknames, " "))
|
|
|
|
client.ReplyNicknamed("366", room.String(), "End of NAMES list")
|
2014-08-09 12:42:19 +00:00
|
|
|
case EventDel:
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RLock()
|
2014-05-11 16:18:55 +00:00
|
|
|
if _, subscribed := room.members[client]; !subscribed {
|
2016-03-26 14:10:04 +00:00
|
|
|
client.ReplyNicknamed("442", room.String(), "You are not on that channel")
|
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
continue
|
|
|
|
}
|
2018-03-04 17:20:25 +00:00
|
|
|
msg := fmt.Sprintf(":%s PART %s :%s", client, room.String(), event.text)
|
|
|
|
room.Broadcast(msg)
|
|
|
|
logSink <- LogEvent{room.String(), *client.nickname, "left", true}
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RUnlock()
|
|
|
|
room.Lock()
|
2014-05-11 16:18:55 +00:00
|
|
|
delete(room.members, client)
|
2016-03-26 14:10:04 +00:00
|
|
|
room.Unlock()
|
2014-08-09 12:42:19 +00:00
|
|
|
case EventTopic:
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RLock()
|
2014-05-11 16:18:55 +00:00
|
|
|
if _, subscribed := room.members[client]; !subscribed {
|
2016-03-26 14:10:04 +00:00
|
|
|
client.ReplyParts("442", room.String(), "You are not on that channel")
|
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if event.text == "" {
|
2015-10-11 08:12:55 +00:00
|
|
|
room.SendTopic(client)
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
continue
|
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RUnlock()
|
2015-10-11 08:12:55 +00:00
|
|
|
topic := strings.TrimLeft(event.text, ":")
|
2016-03-26 14:10:04 +00:00
|
|
|
room.Lock()
|
2015-10-11 08:12:55 +00:00
|
|
|
room.topic = &topic
|
2016-03-26 14:10:04 +00:00
|
|
|
room.Unlock()
|
|
|
|
room.RLock()
|
|
|
|
msg := fmt.Sprintf(":%s TOPIC %s :%s", client, room.String(), *room.topic)
|
2015-10-11 08:12:55 +00:00
|
|
|
room.Broadcast(msg)
|
|
|
|
logSink <- LogEvent{
|
2016-03-26 14:10:04 +00:00
|
|
|
room.String(),
|
2015-10-11 08:12:55 +00:00
|
|
|
*client.nickname,
|
|
|
|
"set topic to " + *room.topic,
|
2015-10-05 20:57:31 +00:00
|
|
|
true,
|
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
room.StateSave()
|
2014-08-09 12:42:19 +00:00
|
|
|
case EventWho:
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RLock()
|
2014-05-11 16:18:55 +00:00
|
|
|
for m := range room.members {
|
2015-10-05 20:57:31 +00:00
|
|
|
client.ReplyNicknamed(
|
|
|
|
"352",
|
2016-03-26 14:10:04 +00:00
|
|
|
room.String(),
|
2015-10-11 08:12:55 +00:00
|
|
|
*m.username,
|
2015-10-18 20:07:57 +00:00
|
|
|
m.Host(),
|
2015-10-11 08:12:55 +00:00
|
|
|
*hostname,
|
2020-11-06 17:10:06 +00:00
|
|
|
m.nickname,
|
2015-10-05 20:57:31 +00:00
|
|
|
"H",
|
2020-11-06 17:10:06 +00:00
|
|
|
"0 "+m.realname,
|
2015-10-05 20:57:31 +00:00
|
|
|
)
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
client.ReplyNicknamed("315", room.String(), "End of /WHO list")
|
|
|
|
room.RUnlock()
|
2014-08-09 12:42:19 +00:00
|
|
|
case EventMode:
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RLock()
|
2014-05-11 16:18:55 +00:00
|
|
|
if event.text == "" {
|
|
|
|
mode := "+"
|
2015-10-13 06:37:03 +00:00
|
|
|
if *room.key != "" {
|
2014-05-11 16:18:55 +00:00
|
|
|
mode = mode + "k"
|
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
client.Msg(fmt.Sprintf("324 %s %s %s", *client.nickname, room.String(), mode))
|
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
continue
|
|
|
|
}
|
2014-08-14 13:41:57 +00:00
|
|
|
if strings.HasPrefix(event.text, "b") {
|
2016-03-26 14:10:04 +00:00
|
|
|
client.ReplyNicknamed("368", room.String(), "End of channel ban list")
|
|
|
|
room.RUnlock()
|
2014-08-14 13:41:57 +00:00
|
|
|
continue
|
|
|
|
}
|
2014-05-11 16:18:55 +00:00
|
|
|
if strings.HasPrefix(event.text, "-k") || strings.HasPrefix(event.text, "+k") {
|
|
|
|
if _, subscribed := room.members[client]; !subscribed {
|
2016-03-26 14:10:04 +00:00
|
|
|
client.ReplyParts("442", room.String(), "You are not on that channel")
|
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
client.ReplyNicknamed("472", event.text, "Unknown MODE flag")
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
continue
|
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
room.RUnlock()
|
2014-05-11 16:18:55 +00:00
|
|
|
var msg string
|
2014-08-09 12:42:19 +00:00
|
|
|
var msgLog string
|
2020-11-06 17:10:06 +00:00
|
|
|
if strings.HasPrefix(e.text, "+k") {
|
|
|
|
cols := strings.Split(e.text, " ")
|
2014-05-11 16:18:55 +00:00
|
|
|
if len(cols) == 1 {
|
2020-11-06 17:10:06 +00:00
|
|
|
c.ReplyNotEnoughParameters("MODE")
|
2014-05-11 16:18:55 +00:00
|
|
|
continue
|
|
|
|
}
|
2016-03-26 14:10:04 +00:00
|
|
|
room.Lock()
|
2015-10-11 08:12:55 +00:00
|
|
|
room.key = &cols[1]
|
|
|
|
msg = fmt.Sprintf(":%s MODE %s +k %s", client, *room.name, *room.key)
|
|
|
|
msgLog = "set channel key to " + *room.key
|
2016-03-26 14:10:04 +00:00
|
|
|
room.Unlock()
|
2015-10-11 08:12:55 +00:00
|
|
|
} else {
|
2015-10-13 06:37:03 +00:00
|
|
|
key := ""
|
2016-03-26 14:10:04 +00:00
|
|
|
room.Lock()
|
2015-10-13 06:37:03 +00:00
|
|
|
room.key = &key
|
2015-10-11 08:12:55 +00:00
|
|
|
msg = fmt.Sprintf(":%s MODE %s -k", client, *room.name)
|
2016-03-26 14:10:04 +00:00
|
|
|
room.Unlock()
|
2014-08-09 12:42:19 +00:00
|
|
|
msgLog = "removed channel key"
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
2015-10-11 08:12:55 +00:00
|
|
|
room.Broadcast(msg)
|
2016-03-26 14:10:04 +00:00
|
|
|
logSink <- LogEvent{room.String(), *client.nickname, msgLog, true}
|
2014-05-11 16:18:55 +00:00
|
|
|
room.StateSave()
|
2014-08-09 12:42:19 +00:00
|
|
|
case EventMsg:
|
2014-05-11 16:18:55 +00:00
|
|
|
sep := strings.Index(event.text, " ")
|
2015-10-05 20:57:31 +00:00
|
|
|
room.Broadcast(fmt.Sprintf(
|
|
|
|
":%s %s %s :%s",
|
|
|
|
client,
|
|
|
|
event.text[:sep],
|
2016-03-26 14:10:04 +00:00
|
|
|
room.String(),
|
2015-10-05 20:57:31 +00:00
|
|
|
event.text[sep+1:]),
|
|
|
|
client,
|
|
|
|
)
|
2015-10-11 08:12:55 +00:00
|
|
|
logSink <- LogEvent{
|
2016-03-26 14:10:04 +00:00
|
|
|
room.String(),
|
2015-10-11 08:12:55 +00:00
|
|
|
*client.nickname,
|
2015-10-05 20:57:31 +00:00
|
|
|
event.text[sep+1:],
|
|
|
|
false,
|
|
|
|
}
|
2014-05-11 16:18:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-06 17:10:06 +00:00
|
|
|
|
|
|
|
func RoomRegister(name string) *Room {
|
|
|
|
r := &Room{
|
|
|
|
name: name,
|
|
|
|
members: make(map[*Client]struct{}),
|
|
|
|
events: make(chan ClientEvent),
|
|
|
|
}
|
|
|
|
roomsLock.Lock()
|
|
|
|
roomsWG.Add(1)
|
|
|
|
rooms[name] = r
|
|
|
|
roomsLock.Unlock()
|
|
|
|
go r.Processor(r.events)
|
|
|
|
return r
|
|
|
|
}
|