/goircd.go
Go | 2343 lines | 1809 code | 477 blank | 57 comment | 490 complexity | 54f532d4401f8fa59e34b6271f16b443 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- package main
- import (
- "bufio"
- "bytes"
- "crypto/tls"
- "errors"
- "fmt"
- "io"
- "net"
- "os"
- "os/signal"
- "path"
- "runtime"
- "sort"
- "strings"
- "syscall"
- "time"
- )
- type ServerMessage struct {
- Node *ServerNode
- Tokens Tokens
- Line string
- Error error
- }
- type UserMessage struct {
- User *User
- Tokens Tokens
- Error error
- }
- type AdminData struct {
- AdminName string
- AdminNick string
- AdminEmail string
- }
- // Return name, relative to base unless name is absolute
- func RelDir(name, base string) string {
- if path.IsAbs(name) {
- return name
- }
- return path.Join(base, name)
- }
- func (adm *AdminData) Configure(config *Block) error {
- name, err := config.GetString("Name")
- if err != nil {
- return err
- }
- nick, err := config.GetString("Nick")
- if err != nil {
- return err
- }
- email, err := config.GetString("Email")
- if err != nil {
- return err
- }
- adm.AdminName = name
- adm.AdminNick = nick
- adm.AdminEmail = email
- return nil
- }
- type Limits struct {
- MaxNick int
- MaxIdent int
- MaxRealName int
- MaxVHost int
- MaxAway int
- MaxPartReason int
- MaxQuitReason int
- MaxChannelName int
- MaxChannelList int
- MaxTopic int
- MaxKickReason int
- MaxChannels int
- MaxOperChannels int
- MaxTargets int
- MaxModes int
- MaxXLine int
- MaxWho int
- }
- func (lim *Limits) Configure(config *Block) {
- lim.MaxNick = config.GetIntDefault(31, "Nickname")
- lim.MaxIdent = config.GetIntDefault(11, "Ident")
- lim.MaxRealName = config.GetIntDefault(128, "RealName")
- lim.MaxVHost = config.GetIntDefault(64, "VHost")
- lim.MaxAway = config.GetIntDefault(200, "Away")
- lim.MaxPartReason = config.GetIntDefault(200, "PartReason")
- lim.MaxQuitReason = config.GetIntDefault(255, "QuitReason")
- lim.MaxChannelName = config.GetIntDefault(32, "ChannelName")
- lim.MaxChannelList = config.GetIntDefault(60, "ChannelLists")
- lim.MaxTopic = config.GetIntDefault(307, "Topic")
- lim.MaxKickReason = config.GetIntDefault(255, "KickReason")
- lim.MaxChannels = config.GetIntDefault(10, "MaxChannels")
- lim.MaxOperChannels = config.GetIntDefault(20, "MaxOperChannels")
- lim.MaxTargets = config.GetIntDefault(20, "MaxTargets")
- lim.MaxModes = config.GetIntDefault(20, "MaxModes")
- lim.MaxXLine = config.GetIntDefault(50, "XLineLimit")
- lim.MaxWho = config.GetIntDefault(4096, "MaxWho")
- }
- type Options struct {
- DefaultModes string
- HostInTopic bool
- PartPrefix string
- PartSuffix string
- QuitPrefix string
- QuitSuffix string
- DefaultQuit string
- IPv4Mask net.IPMask
- IPv6Mask net.IPMask
- }
- func MakeCIDR(n int, ipv6 bool) net.IPMask {
- var maskLen int
- if ipv6 {
- maskLen = net.IPv6len
- } else {
- maskLen = net.IPv4len
- }
- if n > maskLen*8 {
- n = maskLen * 8
- }
- res := make(net.IPMask, maskLen)
- i := 0
- for n > 0 {
- if n >= 8 {
- res[i] = 0xff
- } else {
- res[i] = byte(0xff << uint(8-n))
- break
- }
- i++
- n -= 8
- }
- if !ipv6 {
- return net.IPv4Mask(res[0], res[1], res[2], res[3])
- }
- return res
- }
- func (opt *Options) Configure(config *Block) {
- opt.DefaultModes = config.GetStringDefault("", "DefaultModes")
- opt.HostInTopic = config.GetBoolDefault(false, "HostInTopic")
- opt.PartPrefix = config.GetStringDefault("", "PartPrefix")
- opt.PartSuffix = config.GetStringDefault("", "PartSuffix")
- opt.QuitPrefix = config.GetStringDefault("Quit: ", "QuitPrefix")
- opt.QuitSuffix = config.GetStringDefault("", "QuitSuffix")
- opt.DefaultQuit = config.GetStringDefault("Client exited")
- ipv4range := config.GetIntDefault(32, "IPv4Range")
- ipv6range := config.GetIntDefault(128, "IPv6Range")
- opt.IPv4Mask = MakeCIDR(ipv4range, false)
- opt.IPv6Mask = MakeCIDR(ipv6range, true)
- }
- type Security struct {
- FlatMap bool
- HideULines bool
- DiePassword Password
- RestartPassword Password
- }
- func (sec *Security) Configure(config *Block) error {
- sec.FlatMap = config.GetBoolDefault(true, "FlatMap")
- sec.HideULines = config.GetBoolDefault(false, "HideULines")
- dieHash := config.GetStringDefault("", "DieHash")
- diePass, err := config.GetString("DiePassword")
- if err != nil {
- return err
- }
- diePw, err := LoadPassword(dieHash, diePass)
- if err != nil {
- return err
- }
- resHash := config.GetStringDefault("", "RestartHash")
- resPass, err := config.GetString("RestartPassword")
- if err != nil {
- return err
- }
- resPw, err := LoadPassword(resHash, resPass)
- if err != nil {
- return err
- }
- sec.DiePassword = diePw
- sec.RestartPassword = resPw
- return nil
- }
- type Listener struct {
- net.Listener
- Bind *Bind
- }
- type Server struct {
- channels map[string]*Channel
- UserNicks map[string]*User
- LocalUsers map[UID]*User
- MaxLocalUsers int
- MaxGlobalUsers int
- UnregisteredCount int
- nextUserId UID
- Id SID
- ServerName string
- NetworkName string
- Description string
- AdminData
- Limits
- Options
- Security
- Logger
- StartTime time.Time
- MOTD []string
- Rules []string
- FakeUser *User
- ServerTree
- Operators map[*User]bool
- LocalClones map[string]int
- GlobalClones map[string]int
- XLines map[byte]map[string]*XLine
- OperTypes map[string]*OperType
- OperBlocks map[string]*Oper
- Connect []*Connect
- Links map[string]*Link
- ULines map[string]*ULine
- config *Config
- BaseDir string
- ConfigDir string
- ConfigFile string
- listeners map[string]*Listener
- userMessages chan *UserMessage
- serverMessages chan *ServerMessage
- pendingClients chan *User
- pendingConnects chan *Link
- pendingServers chan *ServerNode
- killswitch chan int
- doRestart bool
- routines chan int
- routineCount int
- channelModes string
- channelModesTypes string
- prefixModes string
- statusMsg string
- extBans string
- userModes string
- ISupport []string
- }
- func NewServer(baseDir, configFile string) *Server {
- serv := new(Server)
- serv.channels = make(map[string]*Channel)
- serv.UserNicks = make(map[string]*User)
- serv.LocalUsers = make(map[UID]*User)
- serv.StartTime = time.Now()
- serv.listeners = make(map[string]*Listener)
- serv.userMessages = make(chan *UserMessage, 10240)
- serv.serverMessages = make(chan *ServerMessage, 10240)
- serv.pendingClients = make(chan *User, 64)
- serv.pendingConnects = make(chan *Link, 64)
- serv.pendingServers = make(chan *ServerNode, 16)
- serv.killswitch = make(chan int, 1)
- serv.routines = make(chan int, 4096)
- serv.MOTD = make([]string, 0)
- serv.Rules = make([]string, 0)
- serv.Operators = make(map[*User]bool)
- serv.LocalClones = make(map[string]int)
- serv.GlobalClones = make(map[string]int)
- serv.XLines = make(map[byte]map[string]*XLine)
- serv.nextUserId = 1
- serv.Logger.Init()
- if !path.IsAbs(configFile) {
- configFile = path.Join(baseDir, configFile)
- }
- serv.BaseDir = baseDir
- configDir, _ := path.Split(configFile)
- serv.ConfigDir = configDir
- serv.ConfigFile = configFile
- serv.config = NewConfig()
- serv.Configure()
- serv.ServerTree.Init(serv)
- serv.FakeUser = NewServerUser(serv.Root)
- serv.ModeStrings()
- serv.BuildISupport()
- return serv
- }
- func (self *Server) AddOperator(user *User) {
- self.Operators[user] = true
- }
- func (self *Server) RemoveOperator(user *User) {
- delete(self.Operators, user)
- // Remove Oper only modes
- var i byte = 'A'
- for {
- p := ""
- if m := GetUserMode(i); m != nil && m.CanSet() == ModeOpers {
- if user.HasMode(i) {
- user.RemoveMode(i)
- m.HandlePre(self, user, false, &p)
- m.HandlePost(self, user, false, p)
- }
- }
- if i == 'Z' {
- i = 'a'
- } else if i == 'z' {
- break
- } else {
- i++
- }
- }
- }
- func (self *Server) AddXLine(typ byte, x *XLine) {
- xlines, ok := self.XLines[typ]
- if !ok {
- xlines = make(map[string]*XLine)
- self.XLines[typ] = xlines
- }
- mask := IRCToLower(x.Mask())
- xlines[mask] = x
- self.EnforceXLine(x)
- }
- func (self *Server) EnforceXLine(xline *XLine) {
- for _, u := range self.LocalUsers {
- if xline.MatchesUser(u) {
- xline.Apply(self, u)
- }
- }
- }
- // TestXLineExpire tests whether the given XLine is out of date and, if so,
- // expires it, returning whether the XLine was expired.
- func (self *Server) TestXLineExpire(x *XLine) bool {
- now := time.Now()
- if x.Duration != 0 && x.Creation.Add(x.Duration).Before(now) {
- self.RemoveXLine(x.Type, x.Mask())
- self.ServerNotice('x',
- "Removing expired %c-Line %s (set by %s %d ago)",
- x.Type, x.Mask(), x.Setter, now.Sub(x.Creation))
- return true
- }
- return false
- }
- func (self *Server) MatchesAnyXLine(user *User) *XLine {
- for _, xlines := range self.XLines {
- for _, x := range xlines {
- if !self.TestXLineExpire(x) && x.MatchesUser(user) {
- return x
- }
- }
- }
- return nil
- }
- func (self *Server) MatchesXLine(typ byte, user *User) *XLine {
- xlines := self.XLines[typ]
- for _, x := range xlines {
- if !self.TestXLineExpire(x) && x.MatchesUser(user) {
- return x
- }
- }
- return nil
- }
- // Returns the percentage of current users affected by an XLine
- func (self *Server) XLineMatch(x *XLine) float32 {
- if numUsers := len(self.Users); numUsers != 0 {
- matches := 0
- for _, u := range self.Users {
- if x.MatchesUser(u) {
- matches++
- }
- }
- return float32(matches) * 100.0 / float32(len(self.Users))
- }
- return 0
- }
- func (self *Server) HasXLine(typ byte, mask string) bool {
- xlines, ok := self.XLines[typ]
- mask = IRCToLower(mask)
- if ok {
- _, ok := xlines[mask]
- return ok
- }
- return false
- }
- func (self *Server) RemoveXLine(typ byte, mask string) bool {
- if xlines, ok := self.XLines[typ]; ok {
- mask = IRCToLower(mask)
- if _, ok := xlines[mask]; ok {
- delete(xlines, mask)
- return true
- }
- }
- return false
- }
- func (self *Server) HasULine(name string) bool {
- _, ok := self.ULines[name]
- return ok
- }
- func (self *Server) CheckUser(user *User) bool {
- return self.LocalUsers[user.Id] == user
- }
- func (self *Server) FindConnect(user *LocalUser) *Connect {
- for _, conn := range self.Connect {
- if conn.Matches(user) {
- return conn
- }
- }
- return nil
- }
- func (self *Server) RoutineCount() int {
- pollLoop:
- for {
- select {
- case count := <-self.routines:
- self.routineCount += count
- default:
- break pollLoop
- }
- }
- return self.routineCount
- }
- func (self *Server) RoutineStart() {
- self.routines <- 1
- }
- func (self *Server) RoutineExit() {
- self.routines <- -1
- }
- // Send a message to a User
- func (self *Server) SendLine(user *User, format string, args ...interface{}) {
- if local := user.Local(); local != nil {
- line := fmt.Sprintf(format, args...)
- if err := local.QueueOutgoing(line); err != nil {
- self.KillUser(user, "%s", err)
- }
- } else if user.IsRemote() {
- self.SendNode(user.Server, ":%s PUSH %s :%s",
- self.Id, user.Id, fmt.Sprintf(format, args...))
- }
- }
- // Send a message to a User, sourced by another User
- func (self *Server) Send(source *User, target *User, format string, args ...interface{}) {
- var src string
- if source == nil {
- src = target.Server.Name
- } else {
- src = source.FullHost()
- }
- self.SendLine(target, ":%s %s", src, fmt.Sprintf(format, args...))
- }
- // Send a message to a User with the server as source
- func (self *Server) SendServer(user *User, format string, args ...interface{}) {
- self.Send(nil, user, format, args...)
- }
- // Send a message to all local Users
- func (self *Server) SendAll(source *User, format string, args ...interface{}) {
- for _, user := range self.LocalUsers {
- self.Send(source, user, format, args...)
- }
- }
- func (self *Server) SendChannel(source *User, ch *Channel, targets TargetUsers,
- format string, args ...interface{}) {
- var send RemoteSend
- if source == nil {
- source = self.FakeUser
- }
- for u := range ch.Users {
- if targets.TargetUser(u) {
- if u.IsLocal() {
- self.Send(source, u, format, args...)
- } else {
- send.AddUser(u)
- }
- }
- }
- send.Send(self, ":%s %s", source.Id, fmt.Sprintf(format, args...))
- }
- func (self *Server) SendNumeric(user *User, num uint, format string, args ...interface{}) {
- self.SendServer(user, "%03d %s :%s", num, user.Nick,
- fmt.Sprintf(format, args...))
- }
- func (self *Server) GlobalNotice(mode byte, format string, args ...interface{}) {
- self.ServerNotice(mode, format, args...)
- self.Broadcast(":%s SNOTE %c :%s",
- self.Id, byte(IRCRuneToUpper(rune(mode))), fmt.Sprintf(format, args...))
- }
- func (self *Server) ServerNotice(mode byte, format string, args ...interface{}) {
- modeLower := byte(IRCRuneToLower(rune(mode)))
- if name, ok := snoNames[modeLower]; ok {
- for u := range self.Operators {
- if u.ServerNotices.HasMode(mode) {
- self.SendServer(u, "NOTICE %s :*** %s: %s",
- u.Nick, name, fmt.Sprintf(format, args...))
- }
- }
- }
- }
- func (self *Server) SendError(user *User, err ClientError) {
- self.SendServer(user, "%03d %s %s", err.Numeric(), user.Nick, err.Message())
- }
- func (self *Server) SendBanList(user *User, ch *Channel) {
- for _, ban := range ch.Bans {
- self.SendServer(user, "367 %s %s %s %s %d", user.Nick, ch.Name,
- ban.Mask, ban.SetBy, ban.Creation)
- }
- self.SendServer(user, "368 %s %s :End of channel ban list",
- user.Nick, ch.Name)
- }
- func (self *Server) SendExceptionList(user *User, ch *Channel) {
- for _, exc := range ch.Exceptions {
- self.SendServer(user, "348 %s %s %s %s %d", user.Nick, ch.Name,
- exc.Mask, exc.SetBy, exc.Creation)
- }
- self.SendServer(user, "349 %s %s :End of channel exception list",
- user.Nick, ch.Name)
- }
- func (self *Server) SendInvexList(user *User, ch *Channel) {
- for _, invex := range ch.Invexes {
- self.SendServer(user, "346 %s %s %s %s %d", user.Nick, ch.Name,
- invex.Mask, invex.SetBy, invex.Creation)
- }
- self.SendServer(user, "347 %s %s :End of channel invite exception list",
- user.Nick, ch.Name)
- }
- func (self *Server) SendLusers(user *User) {
- invisible := 0
- for _, u := range self.UserNicks {
- if u.HasMode('i') {
- invisible++
- }
- }
- self.SendNumeric(user, 251, "There are %d users and %d invisible on %d servers",
- len(self.UserNicks)-invisible, invisible, len(self.Servers))
- opers := len(self.Operators)
- if opers != 0 {
- self.Send(nil, user, "252 %s %d :operator(s) online", user.Nick, opers)
- }
- if self.UnregisteredCount != 0 {
- self.Send(nil, user, "253 %s %d :unknown connection(s)",
- user.Nick, self.UnregisteredCount)
- }
- numLocalUsers := len(self.LocalUsers)
- numUsers := len(self.UserNicks)
- numChannels := len(self.channels)
- if numChannels != 0 {
- self.Send(nil, user, "254 %s %d :channel(s) formed",
- user.Nick, numChannels)
- }
- self.SendNumeric(user, 255, "I have %d clients and %d servers",
- numUsers, len(self.Root.Nodes))
- self.SendNumeric(user, 265, "Current Local Users: %d Max: %d",
- numLocalUsers, self.MaxLocalUsers)
- self.SendNumeric(user, 266, "Current Global Users: %d Max: %d",
- numUsers, self.MaxGlobalUsers)
- }
- func (self *Server) SendDisplayedHost(user *User) {
- self.SendServer(user, "396 %s %s :is now your displayed host",
- user.Nick, user.GetHost())
- }
- func (self *Server) SendMOTD(user *User) {
- self.SendNumeric(user, 375, "%s message of the day", self.ServerName)
- for _, line := range self.MOTD {
- self.SendNumeric(user, 372, "%s", line)
- }
- self.SendNumeric(user, 376, "End of message of the day")
- }
- func (self *Server) SendRules(user *User) {
- for _, line := range self.Rules {
- self.SendNumeric(user, 308, "%s", line)
- }
- self.SendNumeric(user, 309, "End of /RULES")
- }
- func (self *Server) SendModes(user *User, ch *Channel) {
- self.SendServer(user, "324 %s %s %s",
- user.Nick, ch.Name, ch.ModeString(!(ch.HasUser(user) || user.IsOper())))
- self.SendServer(user, "329 %s %s %d", user.Nick, ch.Name, ch.Creation)
- }
- func (self *Server) SendNames(user *User, ch *Channel) {
- var users []*User
- if ch.HasUser(user) {
- users = make([]*User, 0, len(ch.Users))
- for u := range ch.Users {
- users = append(users, u)
- }
- } else {
- users = make([]*User, 0, len(ch.Users))
- for u := range ch.Users {
- if user.CanSee(u) {
- users = append(users, u)
- }
- }
- }
- if len(users) != 0 {
- names := make([]string, 0, len(users))
- namesLoop:
- for _, u := range users {
- name := ch.FormatNick(u)
- for _, handler := range NamesItem {
- res := handler.(NamesItemHandler).NamesItem(self, user, ch, u, &name)
- if res == Deny {
- continue namesLoop
- }
- }
- names = append(names, name)
- }
- visChar := '='
- if ch.HasMode('p') {
- visChar = '*'
- } else if ch.HasMode('s') {
- visChar = '@'
- }
- sp := Spool(names, 200, 1)
- for sp.More() {
- self.SendServer(user, "353 %s %c %s :%s",
- user.Nick, visChar, ch.Name, strings.Join(sp.Next(), " "))
- }
- }
- self.SendServer(user, "366 %s %s :End of /NAMES list", user.Nick, ch.Name)
- }
- func (self *Server) SendTopic(user *User, ch *Channel) {
- if ch.Topic == "" {
- self.SendServer(user, "331 %s %s :No topic is set", user.Nick, ch.Name)
- } else {
- self.SendServer(user, "332 %s %s :%s", user.Nick, ch.Name, ch.Topic)
- self.SendServer(user, "333 %s %s %s %d", user.Nick,
- ch.Name, ch.TopicSetter, ch.TopicCreation)
- }
- }
- func (self *Server) SendUserInvites(user *User) {
- if local := user.Local(); local != nil {
- for ch := range local.Invites {
- self.SendNumeric(user, 346, "%s", ch.Name)
- }
- self.SendNumeric(user, 347, "End of /INVITE list")
- }
- }
- func (self *Server) BuildISupport() {
- support := make([]string, 0, 40)
- support = append(support,
- fmt.Sprint("AWAYLEN=", self.MaxAway),
- "CASEMAPPING=rfc1459",
- "CHANMODES="+self.channelModesTypes,
- "CHANTYPES=#",
- "CHARSET=ascii",
- "ELIST=M",
- "EXCEPTS=e",
- "EXTBAN=,"+self.extBans,
- "FNC",
- "INVEX=I",
- fmt.Sprint("KICKLEN=", self.MaxKickReason),
- "MAP",
- fmt.Sprint("MAXBANS=", self.MaxChannelList),
- fmt.Sprint("MAXCHANNELS=", self.MaxChannels),
- fmt.Sprint("MAXPARA=", self.MaxTargets),
- fmt.Sprint("MAXTARGETS=", self.MaxTargets),
- fmt.Sprint("MODES=", self.MaxTargets),
- "NETWORK="+self.NetworkName,
- fmt.Sprint("NICKLEN=", self.MaxNick),
- "PREFIX="+self.prefixModes,
- "STATUSMSG="+self.statusMsg,
- fmt.Sprint("TOPICLEN=", self.MaxTopic),
- "USERIP",
- "VBANLIST",
- "WALLCHOPS",
- "WALLVOICES")
- add005 := func(s string) { support = append(support, s) }
- for _, handler := range Numeric005 {
- handler.(Numeric005Handler).Numeric005(self, add005)
- }
- self.ISupport = support
- }
- func (self *Server) SendVersion(user *User, extra bool) {
- if extra {
- self.SendNumeric(user, 351, "%s %s", VERSION, self.ServerName)
- }
- spool := Spool(self.ISupport, 200, 1)
- for spool.More() {
- self.SendServer(user, "005 %s %s :are supported by this server",
- user.Nick, strings.Join(spool.Next(), " "))
- }
- }
- func (self *Server) SendWallops(user *User, message string) {
- for _, u := range self.UserNicks {
- if u.IsLocal() && u.HasMode('w') {
- self.Send(user, u, "WALLOPS :%s", message)
- }
- }
- self.Propagate(user.Server, ":%s WALLOPS :%s", user.Id, message)
- }
- func (self *Server) MaskIP(ip net.IP) string {
- if ip.To4() == nil {
- return ip.Mask(self.IPv6Mask).String()
- }
- return ip.Mask(self.IPv4Mask).String()
- }
- func (self *Server) NextUserId() UID {
- var id UID
- for {
- id = self.Id.NewUID(self.nextUserId)
- self.nextUserId++
- if self.nextUserId > MaxUID {
- self.nextUserId = 0
- }
- if _, ok := self.LocalUsers[id]; !ok {
- break
- }
- }
- return id
- }
- func (self *Server) AddClone(user *User) {
- mask := self.MaskIP(user.IP)
- fmt.Println("Add clone", mask, user.Nick)
- self.GlobalClones[mask] += 1
- if local := user.Local(); local != nil {
- self.LocalClones[mask] += 1
- class := local.Connect
- if self.LocalClones[mask] > class.MaxLocal {
- self.KillUser(user, "No more connections allowed locally from this host")
- } else if self.GlobalClones[mask] > class.MaxGlobal {
- self.KillUser(user, "No more connections allowed globally from this host")
- }
- }
- }
- func (self *Server) RemoveClone(user *User) {
- mask := self.MaskIP(user.IP)
- fmt.Println("Remove clone", mask, user.Nick)
- self.GlobalClones[mask] -= 1
- if self.GlobalClones[mask] == 0 {
- delete(self.GlobalClones, mask)
- }
- if user.IsLocal() {
- self.LocalClones[mask] -= 1
- if self.LocalClones[mask] == 0 {
- delete(self.LocalClones, mask)
- }
- }
- }
- func (self *Server) AcceptClient(user *User) {
- self.UnregisteredCount++
- local := user.LocalUser
- local.Id = self.NextUserId()
- user.Nick = local.Id.String()
- if class := self.FindConnect(local); class != nil {
- local.Connect = class
- local.UseFakeLag = class.FakeLag
- local.FakeLag.Threshold = class.Threshold
- self.AddClone(user)
- } else {
- self.KillUser(user, "Access denied by configuration")
- }
- self.LocalUsers[user.Id] = user
- if numUsers := len(self.LocalUsers); numUsers > self.MaxLocalUsers {
- self.MaxLocalUsers = numUsers
- }
- if x := self.MatchesAnyXLine(user); x != nil {
- x.Apply(self, user)
- }
- /* TODO: This isn't working. Don't know why.
- if usingTls {
- tlsConn := c.(*tls.Conn)
- state := tlsConn.ConnectionState()
- if len(state.PeerCertificates) != 0 {
- sig := state.PeerCertificates[0].Signature
- fmt.Println("Got signature:", hex.EncodeToString(sig))
- }
- }
- */
- self.RoutineStart()
- go self.ClientLoop(user)
- }
- func (self *Server) AcceptServer(node *ServerNode) {
- self.UnknownServers[node] = true
- self.RoutineStart()
- go self.ServerLoop(node)
- }
- func (self *Server) ConnectServer(link *Link) {
- self.pendingConnects <- link
- }
- func (self *Server) ClientDNS(user *User) {
- self.SendServer(user, "NOTICE Auth :*** Looking up your hostname...")
- names, err := net.LookupAddr(user.IPString)
- if err != nil {
- self.SetHost(user, "")
- } else {
- name := names[0]
- if name[len(name)-1] == '.' {
- name = name[:len(name)-1]
- }
- self.SetHost(user, name)
- }
- self.RoutineExit()
- }
- var (
- PingTimeout = errors.New("Ping timeout")
- RegistrationTimeout = errors.New("Registration timeout")
- )
- func (self *Server) ClientLoop(user *User) {
- self.Log(Debug, "Goroutine spawned: Client loop for %s", user.Id)
- defer func() {
- self.Log(Debug, "Goroutine exit: Client loop for %s", user.Id)
- self.RoutineExit()
- }()
- now := time.Now().UnixNano()
- // Time of next PING, followed by a KILL
- nextPing := now + 10 * 1000000000
- // Whether the User will be killed on nextPing; if false, send a PING
- pingTimeout := true
- local := user.LocalUser
- // The User may have been killed by an XLine.
- // In which case, we're only running this loop to deliver the kill message
- // and there is no need to run DNS lookup or read the client's messages.
- if !local.Killed {
- // TODO: DNS cache
- self.RoutineStart()
- go self.ClientDNS(user)
- self.RoutineStart()
- go self.userRead(local)
- }
- self.RoutineStart()
- go self.userWrite(local)
- if local.Connect != nil {
- nextPing = now + local.Connect.RegistrationTimeout*1000000000
- }
- for {
- select {
- case msg := <-local.messages:
- switch msg {
- case Killed:
- return
- case Registered:
- nextPing = now + local.Connect.PingFreq*1000000000
- pingTimeout = false
- }
- case line := <-local.Incoming:
- if line != "" {
- tok := Tokenize(line, true, self.MaxTargets)
- if len(tok) == 0 {
- panic("Zero length tokens after parsing: " + line)
- }
- self.RecvMessage(user, tok)
- if local.IsRegistered() {
- pingTimeout = false
- nextPing = now + local.Connect.PingFreq*1000000000
- }
- }
- case err := <-local.Errors:
- self.ReportError(user, err)
- case <-time.After(time.Duration(nextPing - now)):
- if pingTimeout {
- if local.IsRegistered() {
- self.ReportError(user, PingTimeout)
- } else {
- self.ReportError(user, RegistrationTimeout)
- }
- } else {
- self.SendLine(user, "PING :%s", self.ServerName)
- nextPing += local.Connect.PingFreq * 1000000000
- pingTimeout = true
- }
- }
- now = time.Now().UnixNano()
- }
- }
- var ExcessFlood = errors.New("Excess flood")
- func (self *Server) userRead(user *LocalUser) {
- defer self.RoutineExit()
- bufSize := user.Connect.RecvQ
- buf := make([]byte, bufSize)
- off := 0
- for {
- select {
- case newSize := <-user.ChangeRecvQ:
- if newSize != bufSize {
- if len(buf) <= newSize {
- user.Errors <- RecvQExceeded
- return
- }
- newBuf := make([]byte, newSize)
- copy(newBuf, buf[:off])
- buf = newBuf
- bufSize = newSize
- }
- default:
- }
- n, err := user.Connection.Read(buf[off:])
- off += n
- if err != nil {
- user.Errors <- err
- break
- }
- if off == bufSize {
- user.Errors <- RecvQExceeded
- return
- }
- for {
- end := bytes.IndexByte(buf[:off], '\n')
- if end == -1 {
- break
- }
- next := end + 1
- if end > 510 { // 512, minus CR-LF
- end = 510
- }
- j := 0
- for i := 0; i < end; i++ {
- c := buf[i]
- // Replace NUL bytes with spaces
- if c == 0 {
- buf[j] = ' '
- // Remove carriage returns
- } else if c == '\r' {
- continue
- }
- if i != j {
- buf[j] = c
- }
- j++
- }
- end = j
- line := string(buf[:end])
- copy(buf, buf[next:])
- off -= next
- user.FakeLag.AddLag(1)
- lag := <-user.FakeLag.Output
- if !user.UseFakeLag && lag > user.FakeLag.Threshold {
- user.Errors <- ExcessFlood
- return
- }
- user.Incoming <- line
- }
- }
- }
- func (self *Server) userWrite(user *LocalUser) {
- defer self.RoutineExit()
- var bufSize int
- if user.Connect == nil {
- bufSize = 512
- } else {
- bufSize = user.Connect.SendQ
- }
- buf := make([]byte, bufSize)
- off := 0
- writeTimeout := time.Duration(0.25e9)
- for {
- var newSize int
- var line string
- if off == 0 {
- // If the SendQ is empty, wait indefinitely on channels
- select {
- case newSize = <-user.ChangeSendQ:
- goto handleChange
- case line = <-user.Outgoing:
- goto handleLine
- }
- } else {
- deadline := time.Now()
- deadline.Add(writeTimeout)
- user.Connection.SetWriteDeadline(deadline)
- // Otherwise, only poll the channels.
- // Then, continue attempting to flush the queue.
- n, err := user.Connection.Write(buf[:off])
- if n != 0 {
- copy(buf, buf[n:off])
- off -= n
- }
- if err != nil {
- if nErr, ok := err.(net.Error); ok {
- if nErr.Timeout() {
- continue
- }
- }
- user.Errors <- err
- return
- }
- select {
- case newSize = <-user.ChangeSendQ:
- goto handleChange
- case line = <-user.Outgoing:
- goto handleLine
- default:
- }
- }
- continue
- handleChange:
- if newSize != bufSize {
- if len(buf) < newSize {
- user.Errors <- SendQExceeded
- return
- }
- newBuf := make([]byte, newSize)
- copy(newBuf, buf[:off])
- buf = newBuf
- bufSize = newSize
- }
- continue
- handleLine:
- if line == "" {
- user.Errors <- io.EOF
- user.Connection.Close()
- return
- }
- if len(line) > 510 {
- line = line[:510]
- }
- line += "\r\n"
- copy(buf[off:], []byte(line))
- off += len(line)
- if off >= bufSize {
- user.Errors <- SendQExceeded
- return
- }
- }
- }
- func (self *Server) ReportError(user *User, error error) {
- if opErr, ok := error.(*net.OpError); ok {
- error = opErr.Err
- }
- self.userMessages <- &UserMessage{User: user, Error: error}
- }
- func (self *Server) RecvMessage(user *User, tokens Tokens) {
- self.userMessages <- &UserMessage{User: user, Tokens: tokens}
- }
- // Create a crypto/tls.Config object from loaded configuration
- // Return an error if any necessary directives are missing or invalid
- func (self *Server) TlsConfig() (config *tls.Config, err error) {
- block := self.config.GetBlock("TLS")
- if block == nil {
- return nil, errors.New("Missing TLS block")
- }
- certFile, err := block.GetString("CertFile")
- if err != nil {
- return nil, err
- }
- keyFile, err := block.GetString("KeyFile")
- if err != nil {
- return nil, err
- }
- if !path.IsAbs(certFile) {
- certFile = path.Join(self.BaseDir, certFile)
- }
- if !path.IsAbs(keyFile) {
- keyFile = path.Join(self.BaseDir, keyFile)
- }
- cert, err := tls.LoadX509KeyPair(certFile, keyFile)
- if err != nil {
- return nil, err
- }
- cfg := new(tls.Config)
- cfg.Certificates = []tls.Certificate{cert}
- cfg.ServerName = self.ServerName
- //cfg.AuthenticateClient = true
- return cfg, nil
- }
- func (self *Server) StartListener(bind *Bind, port int) error {
- var l net.Listener
- var err error
- addr := bind.Address(port)
- if _, ok := self.listeners[addr]; ok {
- return errors.New("Already listening on address " + addr)
- }
- if bind.TLS {
- if tlsCfg, e := self.TlsConfig(); e != nil {
- err = e
- } else {
- l, err = tls.Listen("tcp", addr, tlsCfg)
- }
- } else {
- l, err = net.Listen("tcp", addr)
- }
- if err != nil {
- return err
- }
- if bind.Type == BindClients {
- self.RoutineStart()
- go self.ClientListener(l)
- } else {
- self.RoutineStart()
- go self.ServerListener(l)
- }
- self.listeners[addr] = &Listener{l, bind}
- return nil
- }
- func (self *Server) Go() int {
- for _, handler := range Ready {
- handler.(ReadyHandler).Ready(self)
- }
- sigIncoming := make(chan os.Signal, 5)
- signal.Notify(sigIncoming, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
- for {
- select {
- case cl := <-self.pendingClients:
- self.AcceptClient(cl)
- case link := <-self.pendingConnects:
- if _, ok := self.ServerNames[link.Name]; !ok {
- node := self.NewOutgoingServer(link)
- self.AcceptServer(node)
- }
- case node := <-self.pendingServers:
- self.AcceptServer(node)
- case count := <-self.routines:
- self.routineCount += count
- case msg := <-self.userMessages:
- self.HandleMessage(msg.User, msg.Tokens, msg.Error)
- case msg := <-self.serverMessages:
- self.HandleServerMessage(msg.Node, msg.Tokens, msg.Line, msg.Error)
- case sig := <-sigIncoming:
- if unixSig, ok := sig.(syscall.Signal); ok {
- switch unixSig {
- case syscall.SIGHUP:
- self.ServerNotice('a', "Received SIGHUP, rehashing...")
- self.Rehash()
- case syscall.SIGINT, syscall.SIGTERM:
- self.Shutdown(int(unixSig) | 0x80)
- }
- }
- case ret := <-self.killswitch:
- self.doShutdown()
- return ret
- }
- }
- return 0
- }
- func (self *Server) HandleMessage(user *User, tokens Tokens, uErr error) {
- // The User may have been killed after more messages were queued
- if !self.CheckUser(user) {
- return
- }
- if uErr != nil {
- if uErr == io.EOF {
- self.KillUser(user, self.DefaultQuit)
- } else {
- self.KillUser(user, uErr.Error())
- }
- return
- }
- command := IRCToUpper(tokens[0])
- tokens[0] = command
- var err ClientError
- for _, handler := range UserPreCommand {
- res, e := handler.(UserPreCommandHandler).UserPreCommand(
- self, user, tokens)
- if res == Allow {
- break
- } else if res == Deny {
- err = e
- goto commandError
- }
- }
- if cmd := GetCommand(command); cmd != nil {
- err = cmd.Execute(self, user, tokens[1:])
- } else {
- err = UnknownCommand.Set(command)
- }
- commandError:
- if err != nil {
- self.SendError(user, err)
- }
- }
- // Signal a shutdown
- func (self *Server) Shutdown(code int) {
- select {
- case self.killswitch <- code:
- default:
- }
- }
- func (self *Server) Restart() {
- self.doRestart = true
- select {
- case self.killswitch <- 0:
- default:
- }
- }
- // Internal method which performs the shutdown,
- // should only be called from Server.Go
- func (self *Server) doShutdown() {
- self.ServerNotice('a', "Server is going down")
- self.Log(Info, "Server is going down")
- for _, handler := range Shutdown {
- handler.(ShutdownHandler).Shutdown(self)
- }
- // Close all listeners
- for _, listener := range self.listeners {
- listener.Close()
- }
- // Split all linked servers
- self.SplitAllNodes("Server shutdown")
- // Kill all clients
- self.KillAllClients("Server shutdown")
- // Wait for user routines to terminate, but not too long
- var wait int64 = 5 * 1000000000
- var interval int64 = 250000000
- allEnded := false
- for wait > 0 {
- if self.RoutineCount() == 0 {
- allEnded = true
- break
- }
- <-time.After(time.Duration(interval))
- wait -= interval
- }
- if !allEnded {
- self.Log(Info, "Goroutines not terminating timely, exiting regardless")
- }
- // Close all pending connections
- closeLoop:
- for {
- select {
- case user := <-self.pendingClients:
- self.KillUser(user, "Server shutdown")
- case node := <-self.pendingServers:
- self.SplitNode(node, "Server shutdown")
- default:
- break closeLoop
- }
- }
- if self.doRestart {
- if err := syscall.Exec(os.Args[0], os.Args, os.Environ()); err != nil {
- self.Fatal("Failed to exec process: %s", err)
- }
- }
- }
- func (self *Server) ConfigureServer(config *Config) error {
- serv := config.GetBlock("Server")
- if serv == nil {
- return errors.New("Missing required Server block")
- }
- var err error
- name, err := serv.GetString("Name")
- if err != nil {
- return err
- }
- idStr := serv.GetStringDefault("", "ServerId")
- network, err := serv.GetString("Network")
- if err != nil {
- return err
- }
- if strings.IndexRune(network, ' ') != -1 {
- return errors.New(fmt.Sprintf(
- "Server Network cannot contain space (0x20) at %s",
- serv.GetValue("Network").Pos()))
- }
- desc, err := serv.GetString("Description")
- if err != nil {
- return err
- }
- var id SID
- if idStr == "" {
- id = GenerateSID(name, desc)
- } else {
- id = ParseSID(idStr)
- }
- if id == 0 || id > MaxSID {
- return errors.New(fmt.Sprintf("Invalid Id %s at %s",
- idStr, serv.GetValue("ServerId").Pos()))
- }
- self.Id = id
- self.ServerName = name
- self.NetworkName = network
- self.Description = desc
- return nil
- }
- func LoadLines(name string) ([]string, error) {
- file, err := os.Open(name)
- if err != nil {
- return nil, err
- }
- buf := bufio.NewReader(file)
- res := make([]string, 0)
- var line string
- for {
- bytes, isPrefix, err := buf.ReadLine()
- if err == io.EOF {
- break
- } else if err != nil {
- return nil, err
- }
- line += string(bytes)
- if !isPrefix {
- res = append(res, line)
- line = ""
- }
- }
- return res, nil
- }
- func (self *Server) ConfigureMembers(config *Config) error {
- if maxProcs := config.GetIntDefault(0, "System", "MaxProcessors"); maxProcs > 0 {
- runtime.GOMAXPROCS(maxProcs)
- }
- if admin := config.GetBlock("Admin"); admin == nil {
- return errors.New("Missing required Admin block")
- } else if err := self.AdminData.Configure(admin); err != nil {
- return err
- }
- if lim := config.GetBlock("Limits"); lim == nil {
- self.Limits.Configure(new(Block))
- } else {
- self.Limits.Configure(lim)
- }
- if opt := config.GetBlock("Options"); opt == nil {
- self.Options.Configure(new(Block))
- } else {
- self.Options.Configure(opt)
- }
- if sec := config.GetBlock("Security"); sec == nil {
- return errors.New("Missing Security configuration block")
- } else {
- if err := self.Security.Configure(sec); err != nil {
- return err
- }
- }
- operTypes, opers, err := LoadOperBlocks(config)
- if err != nil {
- return err
- }
- self.OperTypes = operTypes
- self.OperBlocks = opers
- connect, err := LoadConnectBlocks(config)
- if err != nil {
- return err
- }
- self.Connect = connect
- binds, err := LoadBinds(config)
- if err != nil {
- return err
- }
- links, err := LoadLinks(config)
- if err != nil {
- return err
- }
- self.Links = links
- self.ULines, err = LoadULines(config)
- motdName := config.GetStringDefault("", "Files", "MOTD")
- if motdName == "" {
- self.MOTD = make([]string, 0)
- } else {
- motd, err := LoadLines(RelDir(motdName, self.ConfigDir))
- if err != nil {
- return err
- }
- self.MOTD = motd
- }
- rulesName := config.GetStringDefault("", "Files", "Rules")
- if rulesName == "" {
- self.Rules = make([]string, 0)
- } else {
- rules, err := LoadLines(RelDir(rulesName, self.ConfigDir))
- if err != nil {
- return err
- }
- self.Rules = rules
- }
- if log := config.GetBlock("Log"); log == nil {
- self.Logger.Configure(new(Block), self.BaseDir)
- } else {
- self.Logger.Configure(log, self.BaseDir)
- }
- for _, bind := range binds {
- for _, port := range bind.Ports {
- addr := bind.Address(port)
- doBind := true
- if l, ok := self.listeners[addr]; ok {
- if bind.Equal(l.Bind) {
- doBind = false
- } else {
- l.Close()
- }
- }
- if doBind {
- if err := self.StartListener(bind, port); err != nil {
- self.Log(Error, "Failed to open listener: %s", err)
- }
- }
- }
- }
- if len(self.listeners) == 0 {
- self.Fatal("No ports successfully opened, exiting")
- }
- return nil
- }
- func (self *Server) Configure() {
- var err error
- config := self.config
- if err = config.Parse(self.ConfigFile); err != nil {
- self.Log(Error, "Failed to parse configuration file: %s", err)
- os.Exit(2)
- }
- if err = self.ConfigureServer(config); err != nil {
- goto confError
- }
- if err = self.ConfigureMembers(config); err != nil {
- goto confError
- }
- for _, handler := range Configure {
- if err = handler.(ConfigureHandler).Configure(config); err != nil {
- goto confError
- }
- }
- return
- confError:
- self.Log(Error, "Failed to configure: %s", err)
- os.Exit(3)
- }
- func (self *Server) Rehash() {
- self.Log(Info, "Rehashing server")
- config := NewConfig()
- if err := config.Parse(self.ConfigFile); err != nil {
- self.Log(Error, "Failed rehash: %s", err)
- self.ServerNotice('a', "Failed to rehash %s on %s: %s",
- path.Base(self.ConfigFile), self.ServerName, err)
- } else {
- var err error
- if err := self.ConfigureMembers(config); err != nil {
- goto rehashError
- }
- for _, handler := range Configure {
- if err := handler.(ConfigureHandler).Configure(config); err != nil {
- // We can't revert it now, so just report the error and carry on
- self.Log(Error, "Failed event rehash %s: %s", handler, err)
- self.ServerNotice('a', "Error while rehashing %s on %s: %s",
- path.Base(self.ConfigFile), self.ServerName, err)
- }
- }
- self.config = config
- self.Log(Info, "Completed rehash")
- self.ServerNotice('a', "Successfully rehashed %s", self.ServerName)
- return
- rehashError:
- self.Log(Error, "Failed rehash: %s", err)
- self.ServerNotice('a', "Error while rehashing %s on %s: %s",
- path.Base(self.ConfigFile), self.ServerName, err)
- self.ServerNotice('a', "Rehash failed")
- }
- }
- func (self *Server) AddChannel(ch *Channel) {
- self.channels[IRCToLower(ch.Name)] = ch
- }
- func (self *Server) GetChannel(name string) *Channel {
- return self.channels[IRCToLower(name)]
- }
- func (self *Server) GetOper(name string) *Oper {
- return self.OperBlocks[name]
- }
- func (self *Server) GetUser(nick string) *User {
- return self.UserNicks[IRCToLower(nick)]
- }
- // GetUptime returns server uptime duration
- func (self *Server) GetUptime() time.Duration {
- return time.Now().Sub(self.StartTime)
- }
- func (self *Server) GetXLines(typ byte) []*XLine {
- xlines := self.XLines[typ]
- if xlines != nil {
- res := make([]*XLine, 0, len(xlines))
- for _, x := range xlines {
- res = append(res, x)
- }
- return res
- }
- return nil
- }
- func (self *Server) KickUser(user *User, ch *Channel, format string, args ...interface{}) {
- reason := fmt.Sprintf(format, args...)
- self.SendChannel(nil, ch, TargetAll, "KICK %s %s :%s", ch.Name, user.Nick, reason)
- ch.RemoveUser(user)
- for _, handler := range UserKick {
- handler.(UserKickHandler).UserKick(self, self.FakeUser, ch, user, reason)
- }
- ch.RemoveUser(user)
- if len(ch.Users) == 0 {
- self.DestroyChannel(ch)
- }
- }
- func (self *Server) SetMode(ch *Channel, set bool, mode byte, param string) {
- if hdlr := GetChannelMode(mode); hdlr != nil {
- if hdlr.Type() == ModeBoolean && set == ch.HasMode(mode) {
- return
- }
- // We ignore the result, but still want its possible side effects
- hdlr.HandlePre(self, self.FakeUser, ch, set, ¶m)
- hdlr.HandlePost(self, self.FakeUser, ch, set, param)
- switch hdlr.Type() {
- case ModeString, ModeStringSet:
- if set {
- ch.SetModeParameter(mode, param)
- } else {
- ch.RemoveModeParameter(mode)
- }
- case ModeBoolean:
- if set {
- ch.SetMode(mode)
- } else {
- ch.RemoveMode(mode)
- }
- case ModePrefix:
- pfx := hdlr.(PrefixModeHandler)
- u := self.GetUser(param)
- if u != nil && ch.HasUser(u) {
- param = u.Nick
- if set {
- ch.AddPrefix(u, pfx.Level())
- } else {
- ch.RemovePrefix(u, pfx.Level())
- }
- } else {
- return
- }
- }
- emit := NewEmitModes()
- emit.Add(set, mode, param)
- self.SendChannel(nil, ch, TargetAll, "MODE %s %s", ch.Name, emit)
- }
- }
- func (self *Server) SetUMode(user *User, set bool, mode byte, param string) {
- if hdlr := GetUserMode(mode); hdlr != nil {
- if hdlr.Type() == ModeBoolean && set == user.HasMode(mode) {
- return
- }
- // We ignore the result, but still want its possible side effects
- hdlr.HandlePre(self, user, set, ¶m)
- hdlr.HandlePost(self, user, set, param)
- if hdlr.Type() < ModeMany {
- if set {
- user.SetMode(mode)
- } else {
- user.RemoveMode(mode)
- }
- }
- emit := NewEmitModes()
- emit.Add(set, mode, param)
- self.SendServer(user, "MODE %s %s", user.Nick, emit)
- }
- }
- func (self *Server) SetHost(user *User, host string) {
- local := user.LocalUser
- user.Host = host
- // Check host-based XLines
- if host != "" {
- if x := self.MatchesAnyXLine(user); x != nil {
- x.Apply(self, user)
- if local.Killed {
- return
- }
- }
- }
- if class := self.FindConnect(local); class != nil {
- local.Connect = class
- local.ChangeRecvQ <- class.RecvQ
- local.ChangeSendQ <- class.SendQ
- } else {
- self.KillUser(user, "Access denied by configuration")
- return
- }
- if local.registration & REG_HOST == 0 {
- local.registration |= REG_HOST
- if local.registration == REG_READY {
- self.Register(user)
- }
- }
- }
- func (self *Server) SetVirtualHost(user *User, host string) {
- user.VirtualHost = host
- if user.IsLocal() {
- self.SendDisplayedHost(user)
- }
- }
- func (self *Server) SetNick(user *User, nick string) {
- if user.Nick != nick {
- nickLow := IRCToLower(nick)
- oldNickLow := IRCToLower(user.Nick)
- if nickLow != oldNickLow {
- if self.UserNicks[oldNickLow] == user {
- delete(self.UserNicks, oldNickLow)
- }
- self.UserNicks[nickLow] = user
- }
- if local := user.Local(); local != nil {
- if local.IsRegistered() {
- self.Send(user, user, "NICK %s", nick)
- for _, u := range user.GetLocalNeighbors() {
- self.Send(user, u, "NICK %s", nick)
- }
- user.Nick = nick
- } else {
- user.Nick = nick
- local.registration |= REG_NICK
- if local.registration == REG_READY {
- self.Register(user)
- }
- }
- } else {
- user.Nick = nick
- }
- }
- }
- func (self *Server) SetIdent(user *User, ident string) {
- user.Ident = TruncUTF8(ident, self.MaxIdent)
- if local := user.Local(); local != nil {
- local.registration |= REG_IDENT
- if local.registration == REG_READY {
- self.Register(user)
- }
- }
- }
- func (self *Server) SetRealName(user *User, name string) {
- user.RealName = TruncUTF8(name, self.MaxRealName)
- }
- func (self *Server) SetTopic(user *User, ch *Channel, when int64, topic string) {
- setter := user.Nick
- if self.HostInTopic {
- setter = user.FullHost()
- }
- topic = TruncUTF8(topic, self.MaxTopic)
- ch.SetTopic(topic, setter, when)
- self.SendChannel(user, ch, TargetLocal, "TOPIC %s :%s", ch.Name, topic)
- }
- func (self *Server) ModeStrings() {
- self.extBans = ""
- self.userModes = ""
- self.channelModes = ""
- var cmodeTypes [4]string
- var i byte = 'A'
- for {
- off := ModeOffset(i)
- char := string(i)
- if mode := chanModes[off]; mode != nil {
- self.channelModes += char
- switch mode.Type() {
- case ModeList:
- cmodeTypes[0] += char
- case ModeString:
- cmodeTypes[1] += char
- case ModeStringSet:
- cmodeTypes[2] += char
- case ModeBoolean:
- cmodeTypes[3] += char
- }
- }
- if mode := userModes[off]; mode != nil {
- self.userModes += char
- }
- if extBans[off] {
- self.extBans += char
- }
- if i == 'Z' {
- i = 'a'
- } else if i == 'z' {
- break
- } else {
- i++
- }
- }
- self.prefixModes, self.statusMsg = PrefixModes()
- self.channelModesTypes += strings.Join(cmodeTypes[:], ",")
- }
- type SortedPrefixes []PrefixModeHandler
- func (s SortedPrefixes) Len() int { return len(s) }
- func (s SortedPrefixes) Less(i, j int) bool {
- // Sort highest first
- return s[i].Level() > s[j].Level()
- }
- func (s SortedPrefixes) Swap(i, j int) {
- s[i], s[j] = s[j], s[i]
- }
- func PrefixModes() (prefix string, statusMsg string) {
- prefixes := make(SortedPrefixes, 0, len(prefixesByChar))
- for _, pfx := range prefixesByChar {
- prefixes = append(prefixes, pfx)
- }
- sort.Sort(prefixes)
- numPrefixes := len(prefixes)
- modesStr := make([]byte, 0, numPrefixes)
- charsStr := make([]byte, 0, numPrefixes)
- for _, pfx := range prefixes {
- modesStr = append(modesStr, pfx.Mode())
- charsStr = append(charsStr, pfx.Prefix())
- }
- return fmt.Sprintf("(%s)%s", modesStr, charsStr), string(charsStr)
- }
- func (self *Server) Register(user *User) {
- local := user.LocalUser
- self.UnregisteredCount--
- local.registration |= REG_DONE
- // Check ident-based XLines
- if x := self.MatchesAnyXLine(user); x != nil {
- x.Apply(self, user)
- if local.Killed {
- return
- }
- }
- local.Registered()
- local.IdleTime = time.Now()
- self.Users[user.Id] = user
- if numUsers := len(self.Users); numUsers > self.MaxGlobalUsers {
- self.MaxGlobalUsers = numUsers
- }
- if user.Host == user.IPString {
- self.SendServer(user, "NOTICE Auth :*** Could not resolve " +
- "your hostname; using your IP address (%s) instead.", user.Host)
- } else {
- self.SendServer(user, "NOTICE Auth :*** Found your hostname (%s)", user.Host)
- }
- self.SendServer(user, "NOTICE Auth :Welcome to \002%s\002", self.NetworkName)
- self.SendNumeric(user, 1, "Welcome to the %s IRC Network %s",
- self.NetworkName, user.FullHost())
- self.SendNumeric(user, 2, "Your host is %s, running version %s",
- self.ServerName, VERSION)
- self.SendNumeric(user, 3, "This server was created %s", COMPILE_TIME)
- self.SendNumeric(user, 4, "%s %s %s %s", self.ServerName, VERSION,
- self.userModes, self.channelModes)
- self.SendVersion(user, false)
- self.SendMOTD(user)
- self.SendLusers(user)
- for _, handler := range UserPreBroadcast {
- handler.(UserPreBroadcastHandler).UserPreBroadcast(self, user)
- }
- self.BroadcastUser(user)
- for _, handler := range UserPostRegister {
- handler.(UserPostRegisterHandler).UserPostRegister(self, user)
- }
- self.GlobalNotice('c', "Client connecting on port %d: %s [%s] (%s)",
- local.GetServerPort(), user.FullRealHost(), user.IPString, user.RealName)
- }
- func (self *Server) UserInvited(user, invitee *User, ch *Channel) {
- self.Send(user, invitee, "INVITE %s :%s", invitee.Nick, ch.Name)
- self.SendNumeric(user, 341, "%s %s", invitee.Nick, ch.Name)
- }
- func (self *Server) UserUninvited(user, uninvitee *User, ch *Channel) {
- self.Send(nil, user, "%s 494 %s %s :Was uninvited",
- user.Nick, uninvitee.Nick, ch.Name)
- self.SendNumeric(uninvitee, 493, "You were uninvited from %s by %s",
- ch.Name, user.Nick)
- self.SendChannel(nil, ch, TargetAll, "NOTICE %s :*** %s uninvited %s",
- ch.Name, user.Nick, uninvitee.Nick)
- }
- func (self *Server) UserJoined(user *User, ch *Channel, targets TargetUsers) {
- self.SendChannel(user, ch, targets, "JOIN %s", ch.Name)
- if user.IsLocal() {
- if ch.Topic != "" {
- self.SendTopic(user, ch)
- }
- self.SendNames(user, ch)
- }
- }
- func (self *Server) UserParted(user *User, ch *Channel, reason string) {
- except := make(ExceptUsers)
- for _, handler := range UserPart {
- handler.(UserPartHandler).UserPart(self, user, ch, &reason, except)
- }
- reason = TruncUTF8(reason, self.MaxPartReason)
- target := &TargetAnd{TargetLocal, except}
- if reason == "" {
- if user.IsLocal() {
- reason = self.PartPrefix + reason + self.PartSuffix
- }
- self.SendChannel(user, ch, target, "PART %s", ch.Name)
- } else {
- self.SendChannel(user, ch, target, "PART %s :%s", ch.Name, reason)
- }
- self.Propagate(user.Server, ":%s PART %s :%s", user.Id, ch.Name, reason)
- ch.RemoveUser(user)
- if len(ch.Users) == 0 {
- self.DestroyChannel(ch)
- }
- }
- func (self *Server) UserQuit(user *User, reason string) {
- reason = TruncUTF8(reason, self.MaxQuitReason)
- if reason == "" {
- self.KillUser(user, "%s", self.DefaultQuit)
- } else {
- self.KillUser(user, "%s%s%s", self.QuitPrefix, reason, self.QuitSuffix)
- }
- }
- // KillAllClients will terminate every local client, issuing to each User
- // a notification only of their own disconnection and leaving the server in a
- // somewhat unclean state. All client connections will be closed, but their
- // User structures and all related User and Channel data will be left in an
- // undefined state.
- //
- // Generally only useful in performing a complete shutdown of the server.
- func (self *Server) KillAllClients(reason string) {
- for _, user := range self.LocalUsers {
- local := user.LocalUser
- local.Killed = true
- local.Kill(fmt.Sprintf("ERROR :Closing link: (%s@%s) [%s]",
- user.Ident, user.GetRealHost(), reason))
- }
- }
- func (self *Server) KillUser(user *User, format string, args ...interface{}) {
- isRegistered := true
- reason := fmt.Sprintf(format, args...)
- if local := user.Local(); local != nil {
- local.Kill(fmt.Sprintf("ERROR :Closing link: (%s@%s) [%s]",
- user.Ident, user.GetRealHost(), reason))
- local.Killed = true
- if !local.IsRegistered() {
- isRegistered = false
- self.UnregisteredCount--
- } else {
- self.GlobalNotice('q', "Client exiting: %s [%s] (%s)",
- user.FullRealHost(), user.IPString, reason)
- }
- }
- if isRegistered {
- except := make(ExceptUsers)
- for _, handler := range UserQuit {
- handler.(UserQuitHandler).UserQuit(self, user, &reason, except)
- }
- for _, u := range user.GetNeighbors() {
- if except.TargetUser(u) {
- if u.IsLocal() {
- self.Send(user, u, "QUIT :%s", reason)
- }
- }
- }
- self.Propagate(user.Server, ":%s QUIT :%s", user.Id, reason)
- for ch := range user.Channels {
- ch.RemoveUser(user)
- if len(ch.Users) == 0 {
- self.DestroyChannel(ch)
- }
- }
- }
- delete(self.UserNicks, IRCToLower(user.Nick))
- delete(self.Users, user.Id)
- delete(user.Server.Users, user.Id)
- self.RemoveClone(user)
- if user.IsLocal() && user.HasMode('o') {
- delete(self.Operators, user)
- }
- }
- func (self *Server) DestroyChannel(ch *Channel) {
- if len(ch.Users) != 0 {
- panic("Attempt to destroy non-empty Channel " + ch.Name)
- }
- for u := range ch.Invitees {
- delete(u.Invites, ch)
- }
- delete(self.channels, IRCToLower(ch.Name))
- }
- func (self *Server) ClientListener(lst net.Listener) {
- self.Log(Debug, "Goroutine spawned: Client listener for %s", lst.Addr())
- defer func() {
- self.Log(Debug, "Goroutine exit: Client listener for %s", lst.Addr())
- self.RoutineExit()
- }()
- for {
- conn, err := lst.Accept()
- if err == nil {
- user := NewLocalUser(self.Root, conn)
- self.pendingClients <- user
- } else {
- if ne, ok := err.(net.Error); ok && ne.Temporary() {
- <-time.After(10 * time.Second)
- } else {
- self.Log(Error, "Client listener encountered error: %s", err)
- return
- }
- }
- }
- }
- func (s…
Large files files are truncated, but you can click here to view the full file