// 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 = "ALIAS FRIEND UNFRIEND CASEMAPPING=rfc1459 CHANLIMIT=#:1024 CHANMODES=b,k,l,imnpst CHANNELLEN=200 CHANTYPES=# EXCEPTS=e KICKLEN MAXLIST=b:50,e:50 MODES=1 NETWORK=dnix.de NICKLEN=32 PREFIX=(aohv)&@%%+ SAFELIST STATUSMSG=&@%%+ TOPICLEN" type Server struct { Dispatch chan *irc.Message AddClient chan Client DelClient chan Client host string info string software string version string created string motd string clients map[string]Client chUsers map[string]map[string]string //chModes map[string][]string chTopics map[string]string config *conf.ConfigFile configPath string packetsTransferred float64 connectionsCurrent float64 connectionsCount float64 queueLen float64 } // Create a new server instance. func NewServer(configPath, software, version string) *Server { sv := &Server{software: software, version: version, created: "yes"} sv.Dispatch = make(chan *irc.Message, 1024) sv.AddClient = make(chan Client, 1024) sv.DelClient = make(chan Client, 1024) 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) sv.packetsTransferred = 0 sv.connectionsCurrent = 0 sv.connectionsCount = 0 sv.queueLen = 0 return sv } // 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.dispatch() } 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) dispatch() { for { time.Sleep(1 * time.Microsecond) sv.queueLen = float64(len(sv.Dispatch)) select { case msg := <-sv.Dispatch: sv.recvMsg(msg) sv.packetsTransferred++ case cl := <-sv.AddClient: lnick := strings.ToLower(cl.Name()) if _, exists := sv.clients[lnick]; exists { cl.Register(false) xlog.Info("Client registration failed: '%s'", lnick) } else { sv.clients[lnick] = cl sv.sendLogon(lnick) sv.connectionsCurrent = float64(len(sv.clients)) cl.Register(true) xlog.Info("Client registered: '%s'", lnick) xlog.Info("Server has %d client(s)", len(sv.clients)) xlog.Debug("Goroutines running: %d", runtime.NumGoroutine()) } case cl := <-sv.DelClient: nick := cl.Name() lnick := strings.ToLower(nick) cl.Destroy() for chname, ch := range sv.chUsers { if _, exists := ch[lnick]; exists { delete(ch, lnick) sv.sendMsg(irc.M(nick, "PART", chname, "quit")) } } delete(sv.clients, lnick) sv.connectionsCurrent = float64(len(sv.clients)) xlog.Info("Client deleted: '%s'", lnick) xlog.Info("Server has %d client(s)", len(sv.clients)) xlog.Debug("Goroutines running: %d", runtime.NumGoroutine()) default: } } } 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], "#") { lch := strings.ToLower(msg.Args[0]) if _, exists := sv.chUsers[lch]; !exists { sv.sendReply(msg.Pre, ERR_NOSUCHNICK, msg.Args[0], "No such nick/channel") return } for lnick, _ := range sv.chUsers[lch] { if strings.ToLower(msg.Pre) == lnick && msg.Cmd == "PRIVMSG" { continue } if cl, exists := sv.clients[lnick]; exists { cl.Receive(msg) } } } else { lnick := strings.ToLower(msg.Args[0]) if _, exists := sv.clients[lnick]; !exists { sv.sendReply(msg.Pre, ERR_NOSUCHNICK, msg.Args[0], "No such nick/channel") return } cl := sv.clients[lnick] cl.Receive(msg) } } func (sv *Server) sendReply(tar, cmd, args, trail string) { lnick := strings.ToLower(tar) if _, exists := sv.clients[lnick]; !exists { return } cl := sv.clients[lnick] if args != "" { args = tar + " " + args } else { args = tar } 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("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) channelJoin(nick, ch string) { lnick := strings.ToLower(nick) lch := strings.ToLower(ch) if _, exists := sv.chUsers[lch]; !exists { sv.chUsers[lch] = make(map[string]string) sv.chTopics[lch] = "" } if _, exists := sv.chUsers[lch][lnick]; exists { return } sv.chUsers[lch][lnick] = "" sv.sendMsg(irc.M(nick, "JOIN", ch, "")) sv.sendReply(nick, RPL_TOPIC, ch, sv.chTopics[ch]) sv.channelNames(nick, ch) } func (sv *Server) channelPart(nick, ch, reason string) { lnick := strings.ToLower(nick) lch := strings.ToLower(nick) if _, exists := sv.chUsers[lch]; !exists { return } if _, exists := sv.chUsers[lch][lnick]; !exists { return } sv.sendMsg(irc.M(nick, "PART", ch, reason)) delete(sv.chUsers[lch], lnick) } func (sv *Server) channelNames(nick, ch string) { lch := strings.ToLower(ch) if _, exists := sv.chUsers[lch]; !exists { return } names := "" for lnick, mode := range sv.chUsers[lch] { nick := sv.clients[lnick].Name() if names != "" { names += " " } names = names + mode + nick } 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) { sv.channelJoin(msg.Pre, msg.Args[0]) } func handleCmdPart(sv *Server, msg *irc.Message) { sv.channelPart(msg.Pre, msg.Args[0], msg.Trail) } func handleCmdQuit(sv *Server, msg *irc.Message) { } func handleCmdMode(sv *Server, msg *irc.Message) { } 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") }