// vim:ts=4:sts=4:sw=4:noet:tw=72 package ircd import ( "crypto/rand" "crypto/tls" "fmt" "net" "os" "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 MODES=1 PREFIX=(ohv)@%+" type Server struct { localq chan *irc.Message remoteq chan *irc.Message addq chan Client delq chan Client host string info string software string version string created string motd string cluster *ClusterConnector opers map[string]string clients map[string]Client clModes map[string]string chUsers map[string]map[string]string chTopics map[string]string chModes map[string]map[string]bool config *conf.ConfigFile configPath string packetsTransferred float64 connectionsCount float64 authCallback func(name, pass string) bool } func NewServer(configPath, software, version string) *Server { sv := &Server{software: software, version: version, created: time.Now().String()} sv.localq = make(chan *irc.Message, 65536) sv.remoteq = make(chan *irc.Message, 65536) sv.addq = make(chan Client, 128) sv.delq = make(chan Client, 128) sv.opers = make(map[string]string) sv.clients = make(map[string]Client) sv.clModes = make(map[string]string) 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") urls, _ := sv.config.GetString("cluster", "urls") sv.cluster = NewClusterConnector(urls, false) nicks, _ := sv.config.GetString("control", "a") for _, nick := range strings.Split(nicks, ",") { sv.opers[nick] = "a" } nicks, _ = sv.config.GetString("control", "o") for _, nick := range strings.Split(nicks, ",") { sv.opers[nick] = "o" } sv.packetsTransferred = 0 sv.connectionsCount = 0 return sv } func (sv *Server) SetAuthCallback(authCB func(name, pass string) bool) { sv.authCallback = authCB } 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) } tls := true pem, err := sv.config.GetString("tls", "pem") if err != nil { tls = false } key, err := sv.config.GetString("tls", "key") if err != nil { tls = false } laddr, err = sv.config.GetString("net", "listen_tls") if err == nil && tls { go sv.listenTls(laddr, pem, key) } for { err = sv.dispatcher() if err != nil { xlog.Error("Dispatcher in panic: %s", err.Error()) } } } func (sv *Server) Dispatch(msg *irc.Message) { sv.localq <- 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(10 * time.Millisecond) conn, err := listen.Accept() if err != nil { xlog.Error(err.Error()) } else { NewRemoteClient(sv, conn) sv.connectionsCount++ } } } func (sv *Server) listenTls(laddr, pem, key string) { cert, err := tls.LoadX509KeyPair(pem, key) if err != nil { xlog.Fatal(err.Error()) } cfg := tls.Config{Certificates: []tls.Certificate{cert}} cfg.Rand = rand.Reader listen, err := tls.Listen("tcp", laddr, &cfg) if err != nil { xlog.Fatal(err.Error()) } for { time.Sleep(10 * time.Millisecond) conn, err := listen.Accept() if err != nil { xlog.Error(err.Error()) } else { NewRemoteClient(sv, conn) sv.connectionsCount++ } } } func (sv *Server) dispatcher() (err error) { defer func() { if r := recover(); r != nil { var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("pkg: %v", r) } } }() for { select { case msg := <-sv.localq: sv.recvMsg(msg) sv.packetsTransferred++ case msg := <-sv.remoteq: if sv.localOrigin(msg) { continue } sv.recvMsg(msg) sv.packetsTransferred++ case cl := <-sv.addq: sv.addClient(cl) case cl := <-sv.delq: sv.delClient(cl) default: time.Sleep(100 * time.Microsecond) } } return } 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) addClient(cl Client) { 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) return } 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) return } sv.clients[clid] = cl sv.sendLogon(cl.Name()) cl.Register(true) sv.cluster.Subscribe(clid, sv.remoteq) xlog.Info("Client registered: '%s'", clid) xlog.Info("Server has %d client(s)", len(sv.clients)) } func (sv *Server) delClient(cl Client) { clid := strings.ToLower(cl.Name()) sv.cluster.Unsubscribe(clid) cl.Destroy() for chid, _ := range sv.chUsers { if _, exists := sv.chUsers[chid][clid]; exists { sv.channelRemoveUser(chid, clid) sv.sendMsg(irc.M(cl.Name(), "PART", chid, "quit")) } } delete(sv.clients, clid) xlog.Info("Client deleted: '%s'", clid) xlog.Info("Server has %d client(s)", len(sv.clients)) } func (sv *Server) recvMsg(msg *irc.Message) { cmd := msg.Cmd clid := strings.ToLower(msg.Pre) hook, exists := svCommandHooks[cmd] if !exists { sv.sendReply(msg.Pre, ERR_UNKNOWNCOMMAND, cmd, "Unknown command") return } nm := hook.NeedMode if nm != "" { if m, isoper := sv.opers[clid]; !isoper { sv.sendReply(msg.Pre, ERR_NOPRIVILEGES, cmd, "Permission denied: You're not a server operator") return } else { if strings.Index(nm, m) == -1 { sv.sendReply(msg.Pre, ERR_NOPRIVILEGES, cmd, "Permission denied: You're not a server admin") 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) { sv.deliverMsg(msg) sv.forwardMsg(msg) } func (sv *Server) deliverMsg(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) forwardMsg(msg *irc.Message) { if sv.localOrigin(msg) { sv.cluster.Publish(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) localOrigin(msg *irc.Message) bool { _, localClient := sv.clients[strings.ToLower(msg.Pre)] localServer := (msg.Pre == sv.host) return localClient || localServer } 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") if m, isoper := sv.opers[strings.ToLower(nick)]; isoper { switch m { case "a": sv.sendReply(nick, RPL_YOUREOPER, "", "You are now a server admin") case "o": sv.sendReply(nick, RPL_YOUREOPER, "", "You are now a server operator") } } } func (sv *Server) channelNames(nick, ch string) { chid := strings.ToLower(ch) if _, exists := sv.chUsers[chid]; !exists { return } names := "" for clid, _ := range sv.chUsers[chid] { name := sv.clients[clid].Name() if names != "" { names += " " } modeFlags := "ohv" modeChars := "@%+" for i, mf := range modeFlags { if _, exists := sv.chModes[chid][string(mf)+" "+clid]; exists { name = string(modeChars[i]) + name break } } names = names + name } chunks := len(strings.Split(names, " "))/8 + 1 nameList := strings.SplitN(names, " ", chunks) for _, nameSlice := range nameList { sv.sendReply(nick, RPL_NAMEREPLY, "= "+ch, nameSlice) } sv.sendReply(nick, RPL_ENDOFNAMES, ch, "End of /NAMES list") } func (sv *Server) channelCheckMode(chid, mode string) bool { if _, exists := sv.chModes[chid]; !exists { return false } if _, exists := sv.chModes[chid][mode]; !exists { return false } return true } func (sv *Server) channelCheckPerm(chid, clid, modes string) bool { for _, m := range modes { if sv.channelCheckMode(chid, string(m)+" "+clid) { return true } } return false } func (sv *Server) channelRemoveUser(chid, clid string) { if _, exists := sv.chUsers[chid]; !exists { return } delete(sv.chUsers[chid], clid) delete(sv.chModes[chid], "o "+clid) delete(sv.chModes[chid], "h "+clid) delete(sv.chModes[chid], "v "+clid) if len(sv.chUsers[chid]) == 0 { sv.cluster.Unsubscribe(chid) delete(sv.chUsers, chid) delete(sv.chTopics, chid) delete(sv.chModes, chid) } }