diff --git a/client.go b/client.go index 2f4141a..57dd817 100644 --- a/client.go +++ b/client.go @@ -41,9 +41,9 @@ type RemoteClient struct { writeq chan string } -func NewRemoteClient(srv *Server, conn net.Conn) *RemoteClient { +func NewRemoteClient(sv *Server, conn net.Conn) *RemoteClient { cl := new(RemoteClient) - cl.server = srv + cl.server = sv cl.name = "" cl.password = "" @@ -141,7 +141,7 @@ func (cl *RemoteClient) connReader() { return } s = strings.Trim(s, "\r\n") - cl.handleLine(s) + cl.handleCmd(s) } } @@ -174,22 +174,27 @@ func (cl *RemoteClient) writeLine(format string, a ...interface{}) { cl.writeq <- fmt.Sprintf(format, a...) } -var lineFuncs = map[string]func(*RemoteClient, *irc.Message) bool{ - "PASS": handleLinePass, - "NICK": handleLineNick, - "USER": handleLineUser, -} - -func (cl *RemoteClient) handleLine(s string) { - xlog.Debug("handleLine: [%s] '%s'", cl.name, s) +func (cl *RemoteClient) handleCmd(s string) { + xlog.Debug("handleCmd: [%s] '%s'", cl.name, s) msg := irc.Parse(s) - hook, exists := lineFuncs[msg.Cmd] - forward := true - if exists { - forward = hook(cl, msg) - } - if forward { + if cl.name != "" { cl.Send(msg) + return + } + // If client name is not set, intercept cmds until we get one + switch msg.Cmd { + case "PASS": + cl.password = msg.Args[0] + case "NICK": + cl.name = msg.Args[0] + if _, exists := cl.server.clients[cl.name]; exists { + cl.Destroy() + xlog.Debug("User registration failed: '%s'", msg.Args[0]) + return + } + cl.server.AddClient <- cl + xlog.Debug("User '%s' registered", msg.Args[0]) + case "USER": } } @@ -198,27 +203,3 @@ func checkAuth(cl *RemoteClient) { return } } - -func handleLinePass(cl *RemoteClient, msg *irc.Message) bool { - cl.password = msg.Args[0] - return false -} - -func handleLineNick(cl *RemoteClient, msg *irc.Message) bool { - if cl.name != "" { - return false - } - cl.name = msg.Args[0] - if _, exists := cl.server.clients[cl.name]; exists { - cl.Destroy() - xlog.Error("Registration of user '%s' failed", msg.Args[0]) - return false - } - cl.server.AddClient <- cl - xlog.Info("User '%s' registered", msg.Args[0]) - return false -} - -func handleLineUser(cl *RemoteClient, msg *irc.Message) bool { - return false -} diff --git a/monitoring.go b/monitoring.go index 6d356dd..6c7234c 100644 --- a/monitoring.go +++ b/monitoring.go @@ -12,9 +12,10 @@ import ( var ( gaugePacketsTransferred prometheus.Gauge gaugeClientConnections prometheus.Gauge + gaugeQueueLen prometheus.Gauge ) -func monitoringRun(srv *Server) { +func monitoringRun(sv *Server) { gaugePacketsTransferred = prometheus.NewGauge(prometheus.GaugeOpts{ Name: "ircd_packets_transferred", Help: "Packets handled", @@ -23,18 +24,24 @@ func monitoringRun(srv *Server) { Name: "ircd_clients_connected", Help: "Client connections", }) + gaugeQueueLen = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "ircd_queue_len", + Help: "Unhandled msgs in dispatcher queue", + }) prometheus.MustRegister(gaugePacketsTransferred) prometheus.MustRegister(gaugeClientConnections) - go monitoringUpdater(srv) + prometheus.MustRegister(gaugeQueueLen) + go monitoringUpdater(sv) http.Handle("/metrics", prometheus.Handler()) - laddr, _ := srv.config.GetString("net", "listen_prom") + laddr, _ := sv.config.GetString("net", "listen_prom") http.ListenAndServe(laddr, nil) } -func monitoringUpdater(srv *Server) { +func monitoringUpdater(sv *Server) { for { time.Sleep(5 * time.Second) - gaugePacketsTransferred.Set(srv.packetsTransferred) - gaugeClientConnections.Set(srv.clientConnections) + gaugePacketsTransferred.Set(sv.packetsTransferred) + gaugeClientConnections.Set(sv.clientConnections) + gaugeClientConnections.Set(sv.queueLen) } } diff --git a/server.go b/server.go index 5b5b666..11f8d8b 100644 --- a/server.go +++ b/server.go @@ -36,306 +36,313 @@ type Server struct { created string motd string clients map[string]Client - chSubs map[string]map[string]string + chUsers map[string]map[string]string chTopics map[string]string - ports map[int]bool + chModes map[string][]string config *conf.ConfigFile configPath string packetsTransferred float64 clientConnections float64 -} - -func init() { + queueLen float64 } // Create a new server instance. func NewServer(configPath, software, version string) *Server { - srv := &Server{software: software, version: version, created: "yes"} + sv := &Server{software: software, version: version, created: "yes"} - srv.Dispatch = make(chan *irc.Message, 1024) - srv.AddClient = make(chan Client, 1024) - srv.DelClient = make(chan Client, 1024) + sv.Dispatch = make(chan *irc.Message, 1024) + sv.AddClient = make(chan Client, 1024) + sv.DelClient = make(chan Client, 1024) - srv.clients = make(map[string]Client) - srv.chSubs = make(map[string]map[string]string) - srv.chTopics = make(map[string]string) - srv.configPath = configPath - srv.loadConfig() - loglevel, _ := srv.config.GetInt("system", "loglevel") - srv.host, _ = srv.config.GetString("server", "host") - srv.info, _ = srv.config.GetString("server", "info") - srv.motd, _ = srv.config.GetString("server", "motd") + sv.clients = make(map[string]Client) + sv.chUsers = make(map[string]map[string]string) + sv.chTopics = make(map[string]string) + sv.configPath = configPath + sv.loadConfig() + loglevel, _ := sv.config.GetInt("system", "loglevel") + sv.host, _ = sv.config.GetString("server", "host") + sv.info, _ = sv.config.GetString("server", "info") + sv.motd, _ = sv.config.GetString("server", "motd") xlog.Init(loglevel) - srv.packetsTransferred = 0 - srv.clientConnections = 0 + sv.packetsTransferred = 0 + sv.clientConnections = 0 + sv.queueLen = 0 - return srv + return sv } // Open the listening port and start the main server loop. -func (srv *Server) Run() { - xlog.Info("%s/%s", srv.software, srv.version) - go monitoringRun(srv) - laddr, _ := srv.config.GetString("net", "listen_ircd") - go srv.listen(laddr) - srv.dispatch() +func (sv *Server) Run() { + xlog.Info("%s/%s", sv.software, sv.version) + go monitoringRun(sv) + laddr, _ := sv.config.GetString("net", "listen_ircd") + go sv.listen(laddr) + sv.dispatch() } -func (srv *Server) listen(laddr string) { +func (sv *Server) listen(laddr string) { listen, err := net.Listen("tcp", laddr) if err != nil { xlog.Fatal(err.Error()) os.Exit(-1) } - - xlog.Info("Start listening on %s", laddr) - for { time.Sleep(1 * time.Millisecond) conn, err := listen.Accept() if err != nil { xlog.Error(err.Error()) } else { - NewRemoteClient(srv, conn) - srv.clientConnections++ + NewRemoteClient(sv, conn) + sv.clientConnections++ } } } -func (srv *Server) dispatch() { - xlog.Debug("Entering msg dispatcher") +func (sv *Server) dispatch() { for { time.Sleep(1 * time.Millisecond) + sv.queueLen = float64(len(sv.Dispatch)) select { - case msg := <-srv.Dispatch: - srv.recvMsg(msg) - srv.packetsTransferred++ - case cl := <-srv.AddClient: + case msg := <-sv.Dispatch: + sv.recvMsg(msg) + sv.packetsTransferred++ + case cl := <-sv.AddClient: name := cl.Name() - srv.clients[name] = cl - srv.clientLogon(cl) - srv.clientMotd(cl) + sv.clients[name] = cl + sv.clientLogon(cl) xlog.Info("Client registered: '%s'", name) - xlog.Info("Server has %d client(s)", len(srv.clients)) + xlog.Info("Server has %d client(s)", len(sv.clients)) xlog.Debug("Goroutines running: %d", runtime.NumGoroutine()) - case cl := <-srv.DelClient: + case cl := <-sv.DelClient: name := cl.Name() cl.Destroy() - delete(srv.clients, name) + delete(sv.clients, name) xlog.Info("Client deleted: '%s'", name) - xlog.Info("Server has %d client(s)", len(srv.clients)) + xlog.Info("Server has %d client(s)", len(sv.clients)) xlog.Debug("Goroutines running: %d", runtime.NumGoroutine()) default: } } } -func (srv *Server) loadConfig() { - cfg, err := conf.ReadConfigFile(srv.configPath) +func (sv *Server) loadConfig() { + cfg, err := conf.ReadConfigFile(sv.configPath) if err != nil { xlog.Fatal("Can't read config file (%s)", err.Error()) os.Exit(-1) } - srv.config = cfg + sv.config = cfg } -func (srv *Server) recvMsg(msg *irc.Message) { +func (sv *Server) recvMsg(msg *irc.Message) { cmd := msg.Cmd - hook, exists := srvCommandHooks[cmd] + hook, exists := svCommandHooks[cmd] if !exists { - srv.sendReply(msg.Pre, ERR_UNKNOWNCOMMAND, cmd, "Unknown command") + sv.sendReply(msg.Pre, ERR_UNKNOWNCOMMAND, cmd, "Unknown command") return } argc := len(msg.Args) if argc < hook.MinArgs { - srv.sendReply(msg.Pre, ERR_NEEDMOREPARAMS, cmd, "Not enough parameters") + sv.sendReply(msg.Pre, ERR_NEEDMOREPARAMS, cmd, "Not enough parameters") return } if hook.NeedTrail && msg.Trail == "" { - srv.sendReply(msg.Pre, ERR_NEEDMOREPARAMS, cmd, "Not enough parameters") + sv.sendReply(msg.Pre, ERR_NEEDMOREPARAMS, cmd, "Not enough parameters") return } - hook.HookFn(srv, msg) + hook.HookFn(sv, msg) } -func (srv *Server) sendMsg(msg *irc.Message) { +func (sv *Server) sendMsg(msg *irc.Message) { if strings.HasPrefix(msg.Args[0], "#") { - srv.bcChan(msg) - } else { - srv.sendClient(msg) - } -} - -func (srv *Server) sendClient(msg *irc.Message) { - if _, exists := srv.clients[msg.Args[0]]; !exists { - xlog.Debug("sendClient: Client '%s' does not exist", msg.Args[0]) - srv.sendReply(msg.Pre, ERR_NOSUCHNICK, msg.Args[0], "No such nick/channel") - return - } - cl := srv.clients[msg.Args[0]] - cl.Receive(msg) -} - -func (srv *Server) bcChan(msg *irc.Message) { - ch := msg.Args[0] - if _, exists := srv.chSubs[ch]; !exists { - xlog.Error("bcChan: Channel '%s' does not exist", ch) - return - } - for nick, _ := range srv.chSubs[ch] { - if msg.Pre == nick && msg.Cmd == "PRIVMSG" { - continue + ch := msg.Args[0] + if _, exists := sv.chUsers[ch]; !exists { + sv.sendReply(msg.Pre, ERR_NOSUCHNICK, msg.Args[0], "No such nick/channel") + return } - srv.clients[nick].Receive(msg) + for nick, _ := range sv.chUsers[ch] { + if msg.Pre == nick && msg.Cmd == "PRIVMSG" { + continue + } + sv.clients[nick].Receive(msg) + } + } else { + if _, exists := sv.clients[msg.Args[0]]; !exists { + sv.sendReply(msg.Pre, ERR_NOSUCHNICK, msg.Args[0], "No such nick/channel") + return + } + cl := sv.clients[msg.Args[0]] + cl.Receive(msg) } } -func (srv *Server) sendReply(tar, cmd, args, trail string) { - if _, exists := srv.clients[tar]; !exists { - xlog.Error("sendReply: Client '%s' does not exist", tar) +func (sv *Server) sendReply(tar, cmd, args, trail string) { + if _, exists := sv.clients[tar]; !exists { return } - cl := srv.clients[tar] + cl := sv.clients[tar] if args != "" { args = tar + " " + args } else { args = tar } - cl.Receive(irc.M(srv.host, cmd, args, trail)) + cl.Receive(irc.M(sv.host, cmd, args, trail)) } -func (srv *Server) clientLogon(cl Client) { - srv.sendReply(cl.Name(), RPL_WELCOME, "", "Willkommen!") - srv.sendReply(cl.Name(), RPL_YOURHOST, "", +func (sv *Server) clientLogon(cl Client) { + sv.sendReply(cl.Name(), RPL_WELCOME, "", "Willkommen!") + sv.sendReply(cl.Name(), RPL_YOURHOST, "", fmt.Sprintf("Your host is %s, running on %s/%s", - srv.host, srv.software, srv.version)) - srv.sendReply(cl.Name(), RPL_CREATED, "", - fmt.Sprintf("Created: %s", srv.created)) - srv.sendReply(cl.Name(), RPL_MYINFO, "", - fmt.Sprintf(myinfo, srv.host, srv.software, srv.version)) - srv.sendReply(cl.Name(), RPL_ISUPPORT, "", + sv.host, sv.software, sv.version)) + sv.sendReply(cl.Name(), RPL_CREATED, "", + fmt.Sprintf("Created: %s", sv.created)) + sv.sendReply(cl.Name(), RPL_MYINFO, "", + fmt.Sprintf(myinfo, sv.host, sv.software, sv.version)) + sv.sendReply(cl.Name(), RPL_ISUPPORT, "", isupport+" are supported by this server") + sv.sendReply(cl.Name(), RPL_MOTDSTART, "", + fmt.Sprintf("- %s Message of the day -", sv.host)) + for _, line := range strings.Split(sv.motd, "\n") { + sv.sendReply(cl.Name(), RPL_MOTD, "", fmt.Sprintf("- %s", line)) + } + sv.sendReply(cl.Name(), RPL_ENDOFMOTD, "", "End of MOTD command") } -func (srv *Server) clientMotd(cl Client) { - srv.sendReply(cl.Name(), RPL_MOTDSTART, "", - fmt.Sprintf("- %s Message of the day -", srv.host)) - for _, line := range strings.Split(srv.motd, "\n") { - srv.sendReply(cl.Name(), RPL_MOTD, "", fmt.Sprintf("- %s", line)) +func (sv *Server) channelJoin(nick, ch string) { + if _, exists := sv.chUsers[ch]; !exists { + sv.chUsers[ch] = make(map[string]string) + sv.chTopics[ch] = "" } - srv.sendReply(cl.Name(), RPL_ENDOFMOTD, "", "End of MOTD command") -} - -func (srv *Server) channelJoin(nick, ch string) { - if _, exists := srv.chSubs[ch]; !exists { - srv.chSubs[ch] = make(map[string]string) - srv.chTopics[ch] = "" - } - if _, exists := srv.chSubs[ch][nick]; exists { + if _, exists := sv.chUsers[ch][nick]; exists { return } - srv.chSubs[ch][nick] = "" - srv.bcChan(irc.M(nick, "JOIN", ch, "")) - srv.sendReply(nick, RPL_TOPIC, ch, srv.chTopics[ch]) + sv.chUsers[ch][nick] = "" + sv.sendMsg(irc.M(nick, "JOIN", ch, "")) + sv.sendReply(nick, RPL_TOPIC, ch, sv.chTopics[ch]) + sv.channelNames(nick, ch) } -func (srv *Server) channelPart(nick, ch, reason string) { - if _, exists := srv.chSubs[ch]; !exists { +func (sv *Server) channelPart(nick, ch, reason string) { + if _, exists := sv.chUsers[ch]; !exists { return } - if _, exists := srv.chSubs[ch][nick]; !exists { + if _, exists := sv.chUsers[ch][nick]; !exists { return } - srv.bcChan(irc.M(nick, "PART", ch, reason)) - delete(srv.chSubs[ch], nick) + sv.sendMsg(irc.M(nick, "PART", ch, reason)) + delete(sv.chUsers[ch], nick) } -type SrvCommandHook struct { - HookFn func(srv *Server, msg *irc.Message) +func (sv *Server) channelNames(nick, ch string) { + if _, exists := sv.chUsers[ch]; !exists { + return + } + names := "" + for name, mode := range sv.chUsers[ch] { + if names != "" { + names += " " + } + names = names + mode + name + } + sv.sendReply(nick, RPL_NAMEREPLY, "= "+ch, names) + sv.sendReply(nick, RPL_ENDOFNAMES, ch, "End of /NAMES list") +} + +type commandHook struct { + HookFn func(sv *Server, msg *irc.Message) MinArgs int NeedTrail bool NeedOper bool NeedAuth bool } -var srvCommandHooks = map[string]SrvCommandHook{ - "PRIVMSG": {srvHandleCmdPrivmsg, 1, true, false, false}, - "JOIN": {srvHandleCmdJoin, 1, false, false, false}, - "PART": {srvHandleCmdPart, 1, false, false, false}, - "QUIT": {srvHandleCmdQuit, 0, false, false, false}, - "MODE": {srvHandleCmdMode, 1, false, false, false}, - "TOPIC": {srvHandleCmdTopic, 1, false, false, false}, - "WHOIS": {srvHandleCmdWhois, 0, false, false, false}, - "PING": {srvHandleCmdPing, 1, false, false, false}, - "REHASH": {srvHandleCmdRehash, 0, false, false, false}, +var svCommandHooks = map[string]commandHook{ + "PRIVMSG": {handleCmdPrivmsg, 1, true, false, false}, + "JOIN": {handleCmdJoin, 1, false, false, false}, + "PART": {handleCmdPart, 1, false, false, false}, + "QUIT": {handleCmdQuit, 0, false, false, false}, + "MODE": {handleCmdMode, 1, false, false, false}, + "TOPIC": {handleCmdTopic, 1, false, false, false}, + "NAMES": {handleCmdNames, 1, false, false, false}, + "WHOIS": {handleCmdWhois, 0, false, false, false}, + "PING": {handleCmdPing, 1, false, false, false}, + "REHASH": {handleCmdRehash, 0, false, false, false}, /* - "LIST": {srvHandleCmdList, 0, false, false}, - "VERSION": {srvHandleCmdVersion, 0, false, false}, - "STATS": {srvHandleCmdStats, 0, false, false}, - "TIME": {srvHandleCmdTime, 0, false, false}, - "OPER": {srvHandleCmdOper, 1, false, false}, - "ADMIN": {srvHandleCmdAdmin, 0, false, false}, - "INFO": {srvHandleCmdInfo, 0, false, false}, - "WHO": {srvHandleCmdWho, 0, false, false}, - "WHOWAS": {srvHandleCmdWhowas, 0, false, false}, - "KILL": {srvHandleCmdKill, 0, false, false}, - "PONG": {srvHandleCmdPong, 0, false, false}, - "ERROR": {srvHandleCmdError, 0, false, false}, - "AWAY": {srvHandleCmdAway, 0, false, false}, - "RESTART": {srvHandleCmdRestart, 0, false, false}, - "SUMMON": {srvHandleCmdSummon, 0, false, false}, - "USERS": {srvHandleCmdUsers, 0, false, false}, - "USERHOST": {srvHandleCmdUserhost, 0, false, false}, - "ISON": {srvHandleCmdIson, 0, false, false}, + "LIST": {handleCmdList, 0, false, false}, + "VERSION": {handleCmdVersion, 0, false, false}, + "STATS": {handleCmdStats, 0, false, false}, + "TIME": {handleCmdTime, 0, false, false}, + "OPER": {handleCmdOper, 1, false, false}, + "ADMIN": {handleCmdAdmin, 0, false, false}, + "INFO": {handleCmdInfo, 0, false, false}, + "WHO": {handleCmdWho, 0, false, false}, + "WHOWAS": {handleCmdWhowas, 0, false, false}, + "KILL": {handleCmdKill, 0, false, false}, + "PONG": {handleCmdPong, 0, false, false}, + "ERROR": {handleCmdError, 0, false, false}, + "AWAY": {handleCmdAway, 0, false, false}, + "RESTART": {handleCmdRestart, 0, false, false}, + "SUMMON": {handleCmdSummon, 0, false, false}, + "USERS": {handleCmdUsers, 0, false, false}, + "USERHOST": {handleCmdUserhost, 0, false, false}, + "ISON": {handleCmdIson, 0, false, false}, */ } -func srvHandleCmdPrivmsg(srv *Server, msg *irc.Message) { - srv.sendMsg(msg) +func handleCmdPrivmsg(sv *Server, msg *irc.Message) { + sv.sendMsg(msg) } -func srvHandleCmdJoin(srv *Server, msg *irc.Message) { - srv.channelJoin(msg.Pre, msg.Args[0]) +func handleCmdJoin(sv *Server, msg *irc.Message) { + sv.channelJoin(msg.Pre, msg.Args[0]) } -func srvHandleCmdPart(srv *Server, msg *irc.Message) { - srv.channelPart(msg.Pre, msg.Args[0], msg.Trail) +func handleCmdPart(sv *Server, msg *irc.Message) { + sv.channelPart(msg.Pre, msg.Args[0], msg.Trail) } -func srvHandleCmdQuit(srv *Server, msg *irc.Message) { +func handleCmdQuit(sv *Server, msg *irc.Message) { } -func srvHandleCmdMode(srv *Server, msg *irc.Message) { +func handleCmdMode(sv *Server, msg *irc.Message) { } -func srvHandleCmdTopic(srv *Server, msg *irc.Message) { +func handleCmdTopic(sv *Server, msg *irc.Message) { ch := msg.Args[0] - if _, exists := srv.chSubs[ch]; !exists { - srv.sendReply(msg.Pre, ERR_NOSUCHCHANNEL, ch, "No such channel") + if _, exists := sv.chUsers[ch]; !exists { + sv.sendReply(msg.Pre, ERR_NOSUCHCHANNEL, ch, "No such channel") } if msg.Trail == "" { - srv.sendReply(msg.Pre, RPL_TOPIC, ch, srv.chTopics[ch]) + sv.sendReply(msg.Pre, RPL_TOPIC, ch, sv.chTopics[ch]) } else { - srv.chTopics[ch] = msg.Trail - srv.bcChan(msg) - //srv.sendReply(msg.Pre, RPL_TOPIC, ch, msg.Trail) + sv.chTopics[ch] = msg.Trail + sv.sendMsg(msg) + //sv.sendReply(msg.Pre, RPL_TOPIC, ch, msg.Trail) } } -func srvHandleCmdWhois(srv *Server, msg *irc.Message) { - srv.sendReply(msg.Pre, RPL_WHOISUSER, "nick user host *", "real name") +func handleCmdNames(sv *Server, msg *irc.Message) { + ch := msg.Args[0] + if _, exists := sv.chUsers[ch]; !exists { + sv.sendReply(msg.Pre, ERR_NOSUCHCHANNEL, ch, "No such channel") + } + sv.channelNames(msg.Pre, ch) } -func srvHandleCmdPing(srv *Server, msg *irc.Message) { - srv.sendReply(msg.Pre, "PONG", msg.Args[0], "") +func handleCmdWhois(sv *Server, msg *irc.Message) { + sv.sendReply(msg.Pre, RPL_WHOISUSER, "nick user host *", "real name") } -func srvHandleCmdRehash(srv *Server, msg *irc.Message) { - srv.loadConfig() - srv.sendReply(msg.Pre, RPL_REHASHING, "", "Rehashing.") +func handleCmdPing(sv *Server, msg *irc.Message) { + sv.sendReply(msg.Pre, "PONG", msg.Args[0], "") +} + +func handleCmdRehash(sv *Server, msg *irc.Message) { + sv.loadConfig() + sv.sendReply(msg.Pre, RPL_REHASHING, "", "Rehashing.") xlog.Info("Rehashing") }