// vim:ts=4:sts=4:sw=4:noet:tw=72 package ircd import ( "fmt" "net" "os" "runtime" "strings" "time" "code.dnix.de/an/conf" "code.dnix.de/an/irc" "code.dnix.de/an/xlog" ) const ( CHANLIMIT = 1024 CHANNELLEN = 200 TOPICLEN = 1024 ) var myinfo string = "%s %s/%s * *" var isupport string = "CASEMAPPING=rfc1459 CHANTYPES=# NICKLEN=32 PREFIX=(aohv)&@%%+" type Server struct { queue chan *irc.Message addq chan Client delq chan Client host string info string software string version string created string motd string clients map[string]Client chUsers map[string]map[string]string chTopics map[string]string chModes map[string]map[string]bool config *conf.ConfigFile configPath string packetsTransferred float64 connectionsCurrent float64 connectionsCount float64 queueLen float64 authCallback func(name, pass string) bool } // Create a new server instance. func NewServer(configPath, software, version string) *Server { sv := &Server{software: software, version: version, created: time.Now().String()} sv.queue = make(chan *irc.Message, 1024) sv.addq = make(chan Client, 128) sv.delq = make(chan Client, 128) sv.clients = make(map[string]Client) sv.chUsers = make(map[string]map[string]string) sv.chTopics = make(map[string]string) sv.chModes = make(map[string]map[string]bool) sv.configPath = configPath sv.loadConfig() loglevel, _ := sv.config.GetInt("system", "loglevel") xlog.Init(loglevel) sv.host, _ = sv.config.GetString("server", "host") sv.info, _ = sv.config.GetString("server", "info") sv.motd, _ = sv.config.GetString("server", "motd") sv.packetsTransferred = 0 sv.connectionsCurrent = 0 sv.connectionsCount = 0 sv.queueLen = 0 return sv } func (sv *Server) SetAuthCallback(authCB func(name, pass string) bool) { sv.authCallback = authCB } // Open the listening port and start the main server loop. func (sv *Server) Run() { xlog.Info("%s/%s", sv.software, sv.version) go monitoringRun(sv) laddr, err := sv.config.GetString("net", "listen") if err == nil { go sv.listen(laddr) } laddr, err = sv.config.GetString("net", "listen_tls") if err == nil { go sv.listenTls(laddr) } sv.dispatcher() } func (sv *Server) Dispatch(msg *irc.Message) { sv.queue <- msg } func (sv *Server) AddClient(cl Client) { sv.addq <- cl } func (sv *Server) DelClient(cl Client) { sv.delq <- cl } func (sv *Server) listen(laddr string) { listen, err := net.Listen("tcp", laddr) if err != nil { xlog.Fatal(err.Error()) os.Exit(-1) } for { time.Sleep(1 * time.Millisecond) conn, err := listen.Accept() if err != nil { xlog.Error(err.Error()) } else { NewRemoteClient(sv, conn) sv.connectionsCount++ } } } func (sv *Server) listenTls(laddr string) { } func (sv *Server) dispatcher() { for { sv.queueLen = float64(len(sv.queue)) select { case msg := <-sv.queue: sv.recvMsg(msg) sv.packetsTransferred++ case cl := <-sv.addq: clid := strings.ToLower(cl.Name()) if _, exists := sv.clients[clid]; exists { cl.Receive(irc.M(sv.host, ERR_NICKNAMEINUSE, cl.Name(), "Nickname is already in use")) go func() { time.Sleep(5 * time.Second) cl.Register(false) }() xlog.Info("Client registration failed: '%s' (client exists)", clid) continue } if !sv.authCallback(cl.Name(), cl.Password()) { cl.Receive(irc.M(sv.host, ERR_PASSWDMISMATCH, "", "Password incorrect")) go func() { time.Sleep(5 * time.Second) cl.Register(false) }() xlog.Info("Client registration failed: '%s' (wrong password)", clid) continue } sv.clients[clid] = cl sv.clients[clid] = cl sv.sendLogon(cl.Name()) sv.connectionsCurrent = float64(len(sv.clients)) cl.Register(true) xlog.Info("Client registered: '%s'", clid) xlog.Info("Server has %d client(s)", len(sv.clients)) xlog.Debug("Goroutines running: %d", runtime.NumGoroutine()) case cl := <-sv.delq: clid := strings.ToLower(cl.Name()) cl.Destroy() for chname, ch := range sv.chUsers { if _, exists := ch[clid]; exists { delete(ch, clid) sv.sendMsg(irc.M(cl.Name(), "PART", chname, "quit")) } } delete(sv.clients, clid) sv.connectionsCurrent = float64(len(sv.clients)) xlog.Info("Client deleted: '%s'", clid) xlog.Info("Server has %d client(s)", len(sv.clients)) xlog.Debug("Goroutines running: %d", runtime.NumGoroutine()) default: time.Sleep(100 * time.Microsecond) } } } 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) } sv.config = cfg } func (sv *Server) recvMsg(msg *irc.Message) { cmd := msg.Cmd hook, exists := svCommandHooks[cmd] if !exists { sv.sendReply(msg.Pre, ERR_UNKNOWNCOMMAND, cmd, "Unknown command") return } argc := len(msg.Args) if argc < hook.MinArgs { sv.sendReply(msg.Pre, ERR_NEEDMOREPARAMS, cmd, "Not enough parameters") return } if hook.NeedTrail && msg.Trail == "" { sv.sendReply(msg.Pre, ERR_NEEDMOREPARAMS, cmd, "Not enough parameters") return } hook.HookFn(sv, msg) } func (sv *Server) sendMsg(msg *irc.Message) { if strings.HasPrefix(msg.Args[0], "#") { chid := strings.ToLower(msg.Args[0]) if _, exists := sv.chUsers[chid]; !exists { sv.sendReply(msg.Pre, ERR_NOSUCHNICK, msg.Args[0], "No such nick/channel") return } for clid, _ := range sv.chUsers[chid] { if strings.ToLower(msg.Pre) == clid && msg.Cmd == "PRIVMSG" { continue } if cl, exists := sv.clients[clid]; exists { cl.Receive(msg) } } } else { clid := strings.ToLower(msg.Args[0]) if _, exists := sv.clients[clid]; !exists { sv.sendReply(msg.Pre, ERR_NOSUCHNICK, msg.Args[0], "No such nick/channel") return } cl := sv.clients[clid] cl.Receive(msg) } } func (sv *Server) sendReply(nick, cmd, args, trail string) { clid := strings.ToLower(nick) if _, exists := sv.clients[clid]; !exists { return } cl := sv.clients[clid] if args != "" { args = nick + " " + args } else { args = nick } cl.Receive(irc.M(sv.host, cmd, args, trail)) } func (sv *Server) sendLogon(nick string) { sv.sendReply(nick, RPL_WELCOME, "", "Willkommen!") sv.sendReply(nick, RPL_YOURHOST, "", fmt.Sprintf("Your host is %s, running on %s/%s", sv.host, sv.software, sv.version)) sv.sendReply(nick, RPL_CREATED, "", fmt.Sprintf("This server was created %s", sv.created)) sv.sendReply(nick, RPL_MYINFO, "", fmt.Sprintf(myinfo, sv.host, sv.software, sv.version)) sv.sendReply(nick, RPL_ISUPPORT, "", isupport+" are supported by this server") sv.sendReply(nick, RPL_MOTDSTART, "", fmt.Sprintf("- %s Message of the day -", sv.host)) for _, line := range strings.Split(sv.motd, "\n") { sv.sendReply(nick, RPL_MOTD, "", fmt.Sprintf("- %s", line)) } sv.sendReply(nick, RPL_ENDOFMOTD, "", "End of MOTD command") } func (sv *Server) channelNames(nick, ch string) { chid := strings.ToLower(ch) if _, exists := sv.chUsers[chid]; !exists { return } names := "" for clid, mode := range sv.chUsers[chid] { name := sv.clients[clid].Name() 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 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": {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 handleCmdPrivmsg(sv *Server, msg *irc.Message) { sv.sendMsg(msg) } func handleCmdJoin(sv *Server, msg *irc.Message) { clid := strings.ToLower(msg.Pre) chid := strings.ToLower(msg.Args[0]) if _, exists := sv.chUsers[chid]; !exists { sv.chUsers[chid] = make(map[string]string) sv.chTopics[chid] = "" sv.chModes[chid] = make(map[string]bool) } if _, exists := sv.chUsers[chid][clid]; exists { return } sv.chUsers[chid][clid] = "" sv.sendMsg(msg) sv.sendReply(msg.Pre, RPL_TOPIC, msg.Args[0], sv.chTopics[msg.Args[0]]) sv.channelNames(msg.Pre, msg.Args[0]) } func handleCmdPart(sv *Server, msg *irc.Message) { clid := strings.ToLower(msg.Pre) chid := strings.ToLower(msg.Args[0]) if _, exists := sv.chUsers[chid]; !exists { return } if _, exists := sv.chUsers[chid][clid]; !exists { return } sv.sendMsg(msg) delete(sv.chUsers[chid], clid) } func handleCmdQuit(sv *Server, msg *irc.Message) { } func handleCmdMode(sv *Server, msg *irc.Message) { if strings.HasPrefix(msg.Args[0], "#") { chid := strings.ToLower(msg.Args[0]) if _, exists := sv.chUsers[chid]; !exists { return } if len(msg.Args) < 2 { return } modeFlag := strings.ToLower(msg.Args[1]) if len(msg.Args) < 3 { //modeTar := "" } else { //modeTar := strings.ToLower(msg.Args[2]) } switch modeFlag { case "+o": case "-o": case "+h": case "-h": case "+b": case "-b": case "+v": case "-v": } sv.sendMsg(msg) } } func handleCmdTopic(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") } if msg.Trail == "" { sv.sendReply(msg.Pre, RPL_TOPIC, ch, sv.chTopics[ch]) } else { sv.chTopics[ch] = msg.Trail sv.sendMsg(msg) //sv.sendReply(msg.Pre, RPL_TOPIC, ch, msg.Trail) } } 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 handleCmdWhois(sv *Server, msg *irc.Message) { sv.sendReply(msg.Pre, RPL_WHOISUSER, "nick user host *", "real name") } 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") }