Commits

murarth committed c267494

Fixed several XLine issues

  • Participants
  • Parent commits aa617a2

Comments (0)

Files changed (9)

commands/cmd_opers.go

 
 import (
 	"fmt"
-	"net"
+	"os"
 	"strings"
 	"time"
 )
 	addCommand("STATS", cmdStats, 1, 1, true, true)
 	addCommand("SQUIT", cmdSQuit, 1, 2, true, true)
 	addCommand("WALLOPS", cmdWallops, 1, 1, true, true)
-	addCommand("ZLINE", cmdZLine, 1, 3, true, true)
 }
 
 func cmdChgHost(serv *Server, user *User, tokens Tokens) ClientError {
 	return nil
 }
 
-func sendXLines(serv *Server, user *User, xlines []*XLine) {
+func SendXLines(serv *Server, user *User, xlines []*XLine) {
 	for _, x := range xlines {
 		serv.SendServer(user, "223 %s %s %d %d %s :%s", user.Nick,
 			x.Mask(), x.Creation, x.Duration, x.Setter, x.Reason)
 
 		switch (statChar) {
 			case 'e':
-				sendXLines(serv, user, serv.GetXLines('E'))
-			case 'g':
-				sendXLines(serv, user, serv.GetXLines('G'))
+				// TODO: E-lines
+				SendXLines(serv, user, serv.GetXLines('E'))
 			case 'k':
-				sendXLines(serv, user, serv.GetXLines('K'))
-			case 'q':
-				sendXLines(serv, user, serv.GetXLines('Q'))
-			case 'Z':
-				sendXLines(serv, user, serv.GetXLines('Z'))
+				SendXLines(serv, user, serv.GetXLines('K'))
 			case 'c':
 				//serv.SendLinks(user)
 			case 'l':
 	return nil
 }
 
-func cmdZLine(serv *Server, user *User, tokens Tokens) ClientError {
-	if len(tokens) == 3 {
-		doAddXLine(serv, user, 'Z', ApplyKill, tokens[0], tokens[1], tokens[2])
-	} else {
-		doRemoveXLine(serv, user, 'Z', tokens[0])
-	}
-	return nil
-}
-
 func doAddXLine(serv *Server, user *User, typ byte,
 		apply Applier, mask, duration, reason string) {
 	if dur := ParseDuration(duration); dur == -1 {
 		serv.SendServer(user, "NOTICE %s :*** %c-Line for %s already exists",
 			user.Nick, typ, mask)
 	} else {
-		var xline *XLine
+		var matcher Matcher
+		var err os.Error
 
 		if u := serv.GetUser(mask); u != nil {
-			mask = u.IPString
+			matcher, err = NewMatcherFromUser(typ, u)
+		} else {
+			matcher, err = NewMatcher(typ, mask)
 		}
 
-		if strings.IndexRune(mask, '/') != -1 {
-			if ip, mask, err := net.ParseCIDR(mask); err != nil {
-				serv.SendServer(user, "NOTICE %s :*** Invalid CIDR range %s",
-					user.Nick, mask)
-			} else {
-				xline = NewXLine(time.Seconds(), dur, typ,
-					user.Nick, reason, apply, NewMaskMatcher(ip, mask))
-			}
-		} else {
-			if mask, err := ValidXLinePattern(mask, false); err != nil {
-				serv.SendServer(user, "NOTICE %s :*** %c-Line %s",
-					user.Nick, typ, err)
-			} else {
-				xline = NewXLine(time.Seconds(), dur, typ,
-					user.Nick, reason, apply, NewPatternMatcher(mask))
-			}
+		if err != nil {
+			serv.SendServer(user, "NOTICE %s: Invalid %c-Line mask: %s",
+				user.Nick, typ, err)
+			return
 		}
 
-		if xline != nil {
-			match := serv.XLineMatch(xline)
-			if int(match) > serv.MaxXLine {
-				serv.ServerNotice('a', "%s tried to set " +
-					"a %c-Line on %s, which covers %.2f%% of the network!",
-					user.Nick, typ, xline.Mask(), match)
+		xline := NewXLine(time.Seconds(), dur, typ,
+			user.Nick, reason, apply, matcher)
+
+		match := serv.XLineMatch(xline)
+
+		if int(match) > serv.MaxXLine {
+			serv.ServerNotice('a', "%s tried to set " +
+				"a %c-Line on %s, which covers %.2f%% of the network!",
+				user.Nick, typ, xline.Mask(), match)
+		} else {
+			if xline.Duration == 0 {
+				serv.GlobalNotice('x', "%s added permanent %c-Line for %s: %s",
+					user.Nick, typ, xline.Mask(), xline.Reason)
 			} else {
-				serv.AddXLine(typ, xline)
+				expiry := time.SecondsToLocalTime(time.Seconds() + xline.Duration)
+				serv.GlobalNotice('x', "%s added timed %c-Line for %s, " +
+					"expires on %s: %s", user.Nick, typ, xline.Mask(),
+					expiry.Format("Mon Jan _2 15:04:05 2006"), xline.Reason)
+			}
 
-				if xline.Duration == 0 {
-					serv.GlobalNotice('x', "%s added permanent %c-Line for %s: %s",
-						user.Nick, typ, xline.Mask(), xline.Reason)
-				} else {
-					expiry := time.SecondsToLocalTime(time.Seconds() + xline.Duration)
-					serv.GlobalNotice('x', "%s added timed %c-Line for %s, " +
-						"expires on %s: %s", user.Nick, typ, xline.Mask(),
-						expiry.Format("Mon Jan _2 15:04:05 2006"), xline.Reason)
-				}
+			serv.AddXLine(typ, xline)
 
+			if typ != 'K' {
 				serv.Broadcast(":%s ADDLINE %c %s %s %d %d :%s", user.Id,
 					typ, mask, user.Nick, xline.Creation, xline.Duration, reason)
 			}
 
 func doRemoveXLine(serv *Server, user *User, typ byte, mask string) {
 	if serv.RemoveXLine(typ, mask) {
+		serv.Broadcast(":%s DELLINE %c %s", user.Id, typ, mask)
 		serv.GlobalNotice('x', "%s removed %c-Line on %s", user.Nick, typ, mask)
-		serv.Broadcast(":%s DELLINE %c %s", user.Id, typ, mask)
 	} else {
 		serv.SendServer(user, "NOTICE %s :*** %c-Line %s not found in list",
 			user.Nick, typ, mask)
 	}
 }
 
-func (self *Server) AddXLine(typ byte, x *XLine) bool {
+func (self *Server) AddXLine(typ byte, x *XLine) {
 	xlines, ok := self.XLines[typ]
 
 	if !ok {
 
 	mask := IRCToLower(x.Mask())
 
-	if _, ok := xlines[mask]; ok {
-		return false
+	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)
+		}
 	}
-
-	xlines[mask] = x
-
-	return true
 }
 
+func (self *Server) TestXLineExpire(x *XLine) bool {
+	now := time.Seconds()
+
+	if x.Duration != 0 && x.Creation + x.Duration < now {
+		self.RemoveXLine(x.Type, x.Mask())
+		self.ServerNotice('x',
+			"Removing expired %c-Line %s (set by %s %d seconds ago)",
+			x.Type, x.Mask(), x.Setter, now - x.Creation)
+		return true
+	}
+
+	return false
+}
+
 func (self *Server) MatchesAnyXLine(user *User) *XLine {
-	now := time.Seconds()
-
 	for _, xlines := range self.XLines {
-		for mask, x := range xlines {
-			if x.Duration != 0 && x.Creation + x.Duration < now {
-				xlines[mask] = nil, false
-				self.GlobalNotice('x',
-					"Removing expired %c-Line %s (set by %s %d seconds ago)",
-					x.Type, x.Mask(), x.Setter, now - x.Creation)
-				self.Broadcast(":%s DELLINE %c %s", self.Id, x.Type, x.Mask())
-			} else if x.MatchesUser(user) {
+		for _, x := range xlines {
+			if !self.TestXLineExpire(x) && x.MatchesUser(user) {
 				return x
 			}
 		}
 		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() {
 	if host != "" {
 		if x := self.MatchesAnyXLine(user); x != nil {
 			x.Apply(self, user)
-			if !self.CheckUser(user) {
+			if local.Killed {
 				return
 			}
 		}
 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 !self.CheckUser(user) {
+		if local.Killed {
 			return
 		}
 	}
 
-	self.UnregisteredCount--
-
-	local.registration |= REG_DONE
 	local.Registered()
 	local.IdleTime = time.Seconds()
 
 }
 
 func (self *Server) KillUser(user *User, format string, args... interface{}) {
+	isRegistered := true
 	reason := fmt.Sprintf(format, args...)
 
 	if local := user.Local(); local != nil {
-		if !local.IsRegistered() {
-			self.UnregisteredCount--
-		}
-		local.Killed = true
 		local.Kill(fmt.Sprintf("ERROR :Closing link: (%s@%s) [%s]",
 			user.Ident, user.GetRealHost(), reason))
-		self.GlobalNotice('q', "Client exiting: %s [%s] (%s)",
-			user.FullRealHost(), user.IPString, 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)
+		}
 	}
 
-	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)
+	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)
+
+		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)
+			}
 		}
+
 	}
 
 	mask := self.MaskIP(user.IP)
 // Truncate a UTF8 string to n bytes without splitting any Unicode code points
 func TruncUTF8(s string, n int) string {
 	if len(s) > n {
-		chars := 0
 		for i, c := range s {
-			chars += utf8.RuneLen(c)
-			if chars > n {
+			if i + utf8.RuneLen(c) > n {
 				return s[:i]
 			}
 		}
 
 type UMode_o struct {
 	ULineMode
-	UNoHandle
 
 	NormalPriority
 
 func (*UMode_o) Type() ModeType { return ModeBoolean }
 func (*UMode_o) Mode() byte { return 'o' }
 
+func (*UMode_o) HandlePre(serv *Server, user *User, set bool, param *string) (EventResult, ClientError) {
+	if !set && user.IsOper() {
+		return Allow, nil
+	}
+	return PassThrough, nil
+}
+
 func (*UMode_o) HandlePost(serv *Server, user *User, set bool, param string) {
 	if user.IsLocal() {
 		if set {
 			serv.AddOperator(user)
 		} else {
 			serv.RemoveOperator(user)
-			user.OperType = nil
 		}
 	}
+	if !set {
+		user.OperType = nil
+	}
 }
 
 func (o *UMode_o) Configure(config *Config) os.Error {

modules/m_qline.go

 package main
 
 import (
-	"time"
+	"os"
+	"strings"
 )
 
 func init() {
 	AddModule("qline")
 	addCommand("QLINE", cmdQLine, 1, 3, true, true)
-	addXLine('Q', ApplyNickChange)
+	addXLine('Q', ApplyNickChange, NickMatcherFactory(0))
+
+	q := new(QLineHandler)
+	HandleStatsRequest(q)
+	HandleUserPreNick(q)
 }
 
-type PreventQLinedNick struct {
+type QLineHandler struct {
 	NormalPriority
 }
 
-func (*PreventQLinedNick) UserPreNick(serv *Server, user *User,
+func (*QLineHandler) StatsRequest(serv *Server, user *User, mode byte) {
+	SendXLines(serv, user, serv.GetXLines('Q'))
+}
+
+func (*QLineHandler) UserPreNick(serv *Server, user *User,
 		newNick string) (EventResult, ClientError) {
 	for _, xline := range serv.GetXLines('Q') {
-		if m, ok := xline.Matcher.(*NickMatcher); ok {
-			if m.MatchesNick(newNick) {
-				return Deny, NewError(432, "Invalid nickname: " + xline.Reason).Set(newNick)
+		if !serv.TestXLineExpire(xline) {
+			if m, ok := xline.Matcher.(*NickMatcher); ok {
+				if m.MatchesNick(newNick) {
+					return Deny, NewError(432, "Invalid nickname: " + xline.Reason).Set(newNick)
+				}
 			}
 		}
 	}
 type NickChangeApplier int
 
 func (NickChangeApplier) Apply(serv *Server, user *User, x *XLine) {
-	serv.SetNick(user, user.Id.String())
+	newNick := user.Id.String()
+
+	if local := user.Local(); local != nil && !local.IsRegistered() {
+		serv.Send(user, user, "NICK %s", newNick)
+	}
+
+	serv.SetNick(user, newNick)
 }
 
 var ApplyNickChange = NickChangeApplier(0)
 	return &NickMatcher{p}
 }
 
+type NickMatcherFactory int
+
+var MatchesNickOnly = os.NewError("Matches only nickname")
+
+func (NickMatcherFactory) FromMask(mask string) (Matcher, os.Error) {
+	if strings.IndexRune(mask, '!') != -1 || strings.IndexRune(mask, '@') != -1 {
+		return nil, MatchesNickOnly
+	}
+	return &NickMatcher{mask}, nil
+}
+
+func (NickMatcherFactory) FromUser(user *User) (Matcher, os.Error) {
+	return &NickMatcher{user.Nick}, nil
+}
+
 func (m *NickMatcher) Mask() string {
 	return m.pattern
 }
 }
 
 func cmdQLine(serv *Server, user *User, tokens Tokens) ClientError {
-	pattern := tokens[0]
-
 	if len(tokens) == 3 {
-		if dur := ParseDuration(tokens[1]); dur == -1 {
-			serv.SendServer(user, "NOTICE %s :*** Invalid X-Line duration %s",
-				user.Nick, tokens[1])
-		} else if serv.HasXLine('Q', pattern) {
-			serv.SendServer(user, "NOTICE %s :*** Q-Line for %s already exists",
-				user.Nick, pattern)
-		} else {
-			xline := NewXLine(time.Seconds(), dur, 'Q', user.Nick, tokens[2],
-				ApplyNickChange, NewNickMatcher(pattern))
-
-			serv.AddXLine('Q', xline)
-		}
-	} else if !serv.RemoveXLine('Q', pattern) {
-		serv.SendServer(user, "NOTICE %s :*** Q-Line %s not found in list",
-			user.Nick, pattern)
+		doAddXLine(serv, user, 'Q', ApplyNickChange, tokens[0], tokens[1], tokens[2])
+	} else {
+		doRemoveXLine(serv, user, 'Q', tokens[0])
 	}
 
 	return nil

modules/m_shun.go

 )
 
 func init() {
-	addXLine('S', ApplyShun)
+	addXLine('S', ApplyShun, NewHostMatcher)
 	AddModule("shun")
 	addCommand("SHUN", cmdShun, 1, 3, true, true)
-	shun := new(ShunHandler)
+	shun := new(Shun)
 	HandleConfigure(shun)
 	HandleStatsRequest(shun)
 	HandleUserPreCommand(shun)
 }
 
-type ShunApplier struct {}
+type ShunApplier int
 
-func (*ShunApplier) Apply(*Server, *User, *XLine) {}
+func (ShunApplier) Apply(*Server, *User, *XLine) {}
 
-var ApplyShun = new(ShunApplier)
+var ApplyShun = ShunApplier(0)
 
 func cmdShun(serv *Server, user *User, tokens Tokens) ClientError {
 	if len(tokens) == 3 {
 	return nil
 }
 
-type ShunHandler struct {
+type Shun struct {
 	HighPriority
 
 	Notify bool
 	Allowed map[string] bool
 }
 
-func (s *ShunHandler) Configure(config *Config) os.Error {
+func (s *Shun) Configure(config *Config) os.Error {
 	s.Allowed = make(map[string] bool)
 	s.Notify = config.GetBoolDefault(false, "Shun", "NotifyUser")
 
 	return nil
 }
 
-func (s *ShunHandler) StatsRequest(serv *Server, user *User, statChar byte) {
+func (s *Shun) StatsRequest(serv *Server, user *User, statChar byte) {
 	if statChar == 'S' {
-		sendXLines(serv, user, serv.GetXLines('S'))
+		SendXLines(serv, user, serv.GetXLines('S'))
 	}
 }
 
-func (s *ShunHandler) UserPart(serv *Server, user *User, ch *Channel, reason *string) {
+func (s *Shun) UserPart(serv *Server, user *User, ch *Channel, reason *string) {
 	if serv.MatchesXLine('S', user) != nil {
 		*reason = ""
 	}
 }
 
-func (s *ShunHandler) UserPreCommand(serv *Server, user *User,
+func (s *Shun) UserPreCommand(serv *Server, user *User,
 		tokens Tokens) (EventResult, ClientError) {
 	if local := user.Local(); local != nil {
 		if local.IsRegistered() {
 	return PassThrough, nil
 }
 
-func (s *ShunHandler) UserQuit(serv *Server, user *User, reason *string) {
+func (s *Shun) UserQuit(serv *Server, user *User, reason *string) {
 	if user.IsLocal() && serv.MatchesXLine('S', user) != nil {
 		*reason = ""
 	}
 	}
 
 	for typ, xlines := range self.XLines {
-		for _, x := range xlines {
-			route.Send(":%s ADDLINE %c %s %s %d %d :%s", self.Id, typ,
-				x.Mask(), x.Setter, x.Creation, x.Duration, x.Reason)
+		if typ != 'K' {
+			for _, x := range xlines {
+				route.Send(":%s ADDLINE %c %s %s %d %d :%s", self.Id, typ,
+					x.Mask(), x.Setter, x.Creation, x.Duration, x.Reason)
+			}
 		}
 	}
 
 )
 
 func init() {
-	addServerCommand("ADDLINE", scmdAddLine, 6, 6, true, RequireServer)
+	addServerCommand("ADDLINE", scmdAddLine, 6, 6, true, RequireEither)
 	addServerCommand("BURST", scmdBurst, 1, 1, false, RequireServer)
 	addServerCommand("CAPAB", scmdCapab, 1, 4, false, RequireServer)
-	addServerCommand("DELLINE", scmdDelLine, 2, 2, true, RequireServer)
+	addServerCommand("DELLINE", scmdDelLine, 2, 2, true, RequireEither)
 	addServerCommand("ENDBURST", scmdEndBurst, 0, 0, true, RequireServer)
 	addServerCommand("ERROR", scmdError, 1, 1, false, RequireServer)
 	addServerCommand("LINK", scmdLink, 4, 4, false, RequireServer)
 	duration, _ := strconv.Atoi64(tokens[4])
 	reason := tokens[5]
 
-	var matcher Matcher
-	if strings.IndexRune(mask, '/') != -1 {
-		ip, mask, _ := net.ParseCIDR(mask)
-		matcher = NewMaskMatcher(ip, mask)
-	} else {
-		matcher = NewPatternMatcher(mask)
+	matcher, err := NewMatcher(typ, mask)
+	if err != nil {
+		return err
 	}
 
 	applier := GetXLineApplier(typ)
 	Apply(*Server, *User, *XLine)
 }
 
-var XLineAppliers = map[byte] Applier{
-	'G': ApplyKill,
-	'K': ApplyKill,
-	'Z': ApplyKill,
+var XLineData = map[byte] *XLineDatum {
+	'G': &XLineDatum{ApplyKill, NewHostMatcher},
+	'K': &XLineDatum{ApplyKill, NewHostMatcher},
 }
 
-func addXLine(typ byte, apply Applier) {
-	if _, ok := XLineAppliers[typ]; ok {
+type MatcherFactory interface {
+	FromMask(mask string) (Matcher, os.Error)
+	FromUser(user *User) (Matcher, os.Error)
+}
+
+type XLineDatum struct {
+	Applier Applier
+	MatcherFactory MatcherFactory
+}
+
+func addXLine(typ byte, apply Applier, matchFactory MatcherFactory) {
+	if _, ok := XLineData[typ]; ok {
 		panic("Duplicate XLine " + string(typ))
 	}
-	XLineAppliers[typ] = apply
+	XLineData[typ] = &XLineDatum{apply, matchFactory}
 }
 
 func GetXLineApplier(typ byte) Applier {
-	return XLineAppliers[typ]
+	return XLineData[typ].Applier
+}
+
+var NoMatcher = os.NewError("No registered matcher")
+
+func NewMatcher(typ byte, mask string) (Matcher, os.Error) {
+	if data, ok := XLineData[typ]; ok {
+		return data.MatcherFactory.FromMask(mask)
+	}
+	return nil, NoMatcher
+}
+
+func NewMatcherFromUser(typ byte, user *User) (Matcher, os.Error) {
+	if data, ok := XLineData[typ]; ok {
+		data.MatcherFactory.FromUser(user)
+	}
+	return nil, NoMatcher
 }
 
 type Matcher interface {
 
 var XLineNoNick = os.NewError("cannot operate on nick!user@host masks")
 
-func ValidXLinePattern(p string, withIdent bool) (string, os.Error) {
+func ValidXLinePattern(p string) os.Error {
 	if strings.IndexRune(p, '!') != -1 {
-		return "", XLineNoNick
+		return XLineNoNick
 	}
 
-	return p, nil
+	return nil
 }
 
-func NewPatternMatcher(pattern string) Matcher {
-	return &PatternMatcher{pattern}
+func NewPatternMatcher(pattern string) (Matcher, os.Error) {
+	if err := ValidXLinePattern(pattern); err != nil {
+		return nil, err
+	}
+	return &PatternMatcher{pattern}, nil
 }
 
 func (x *PatternMatcher) Mask() string { return x.pattern }
 	return user.IP.Mask(x.mask).Equal(x.ip)
 }
 
+type HostMatcherFactory int
+
+func (HostMatcherFactory) FromMask(mask string) (Matcher, os.Error) {
+	if strings.IndexRune(mask, '/') != -1 {
+		if ip, mask, err := net.ParseCIDR(mask); err == nil {
+			return NewMaskMatcher(ip, mask), nil
+		} else {
+			return nil, err
+		}
+	}
+	return NewPatternMatcher(mask)
+}
+
+func (f HostMatcherFactory) FromUser(user *User) (Matcher, os.Error) {
+	return f.FromMask(user.IPString)
+}
+
+var NewHostMatcher = HostMatcherFactory(0)
+
 type KillApplier int
 
 func (KillApplier) Apply(serv *Server, user *User, x *XLine) {