commit 2eb4ffad3db6c937f2cb46855a016a383c358b1b Author: Andreas Neue Date: Sat Nov 22 14:21:30 2014 +0100 init diff --git a/addr.go b/addr.go new file mode 100644 index 0000000..938aed7 --- /dev/null +++ b/addr.go @@ -0,0 +1,32 @@ +package ircd + +import ( + "strings" +) + +func AddrName(addr string) string { + parts := strings.SplitN(addr, "@", 2) + return parts[0] +} + +func AddrHost(addr string) string { + parts := strings.SplitN(addr, "@", 2) + if len(parts) > 1 { + return parts[1] + } + return "" +} + +func AddrSplit(addr string) (string, string) { + parts := strings.SplitN(addr, "@", 2) + if len(parts) > 1 { + return parts[0], parts[1] + } + return parts[0], "" +} + +func AddrJoin(name string, host string) string { + return name + "@" + host +} + +// vi:ts=4:sw=4:et \ No newline at end of file diff --git a/channel.go b/channel.go new file mode 100644 index 0000000..2461765 --- /dev/null +++ b/channel.go @@ -0,0 +1,159 @@ +package ircd + +import ( + "time" +) + +type ChangedByTS struct { + By string + Time int64 +} + +type ChannelModes string + +type Channel struct { + Name string + + server *Server + + topic string + topicChanged ChangedByTS + flags string + keys map[int]string + args map[int]string + bans map[int]string + + receive chan *Message + destroy chan bool + + invites map[string]bool + clients map[string]ChannelModes +} + +func NewChannel(srv *Server, name string) *Channel { + ch := &Channel{Name: name, server: srv, topic: "", flags: ""} + ch.receive = make(chan *Message, 1024) + ch.destroy = make(chan bool) + ch.clients = make(map[string]ChannelModes) + go ch.loop() + return ch +} + +func (ch *Channel) Receive(msg *Message) { + ch.receive <- msg +} + +func (ch *Channel) loop() { + for { + time.Sleep(1e6) + select { + case msg := <-ch.receive: + cmd := msg.Cmd + hook, exists := chCommandHooks[cmd] + if !exists { + ch.server.sendCommand(msg.Src, ERR_UNKNOWNCOMMAND, cmd, + "Unknown command.") + return + } + argc := len(msg.Args) + if argc < hook.MinArgs { + ch.server.sendCommand(msg.Src, ERR_NEEDMOREPARAMS, cmd, + "Not enough parameters.") + return + } + hook.HookFn(ch, msg) + case <-ch.destroy: + break + default: + continue + } + } +} + +func (ch *Channel) recvMsg(msg *Message) { +} + +func (ch *Channel) sendMsg(msg *Message) { + ch.server.sendMsg(msg) +} + +func (ch *Channel) bcMsg(msg *Message, localEcho bool) { + msg.Ctx = ch.Name + for client, _ := range ch.clients { + if client != msg.Src || localEcho { + msg.Dst = client + ch.server.sendMsgToClient(msg) + } + } +} + +func (ch *Channel) AddMode(mode string) { + // +} + +type ChCommandHook struct { + HookFn func(ch *Channel, msg *Message) + MinArgs int + NeedOper bool + NeedAuth bool +} + +var chCommandHooks = map[string]ChCommandHook{ + CMD_QUIT: {chHandleCmdQuit, 0, false, false}, + CMD_JOIN: {chHandleCmdJoin, 0, false, false}, + CMD_PART: {chHandleCmdPart, 0, false, false}, + CMD_MODE: {chHandleCmdMode, 0, false, false}, + CMD_TOPIC: {chHandleCmdTopic, 0, false, false}, + CMD_NAMES: {chHandleCmdNames, 0, false, false}, + CMD_LIST: {chHandleCmdList, 0, false, false}, + CMD_INVITE: {chHandleCmdInvite, 0, false, false}, + CMD_KICK: {chHandleCmdKick, 0, false, false}, + CMD_PRIVMSG: {chHandleCmdPrivmsg, 0, false, false}, + CMD_NOTICE: {chHandleCmdNotice, 0, false, false}, + CMD_USERS: {chHandleCmdUsers, 0, false, false}, +} + +func chHandleCmdQuit(ch *Channel, msg *Message) { + ch.bcMsg(M(msg.Src, "", msg.Ctx, "QUIT", "", ""), true) + delete(ch.clients, msg.Src) +} + +func chHandleCmdJoin(ch *Channel, msg *Message) { + ch.clients[msg.Src] = "" + ch.bcMsg(M(msg.Src, "", msg.Ctx, "JOIN", "", ""), true) +} + +func chHandleCmdPart(ch *Channel, msg *Message) { + ch.bcMsg(M(msg.Src, "", msg.Ctx, "PART", "", ""), true) + delete(ch.clients, msg.Src) +} + +func chHandleCmdMode(ch *Channel, msg *Message) { +} + +func chHandleCmdTopic(ch *Channel, msg *Message) { +} + +func chHandleCmdNames(ch *Channel, msg *Message) { +} + +func chHandleCmdList(ch *Channel, msg *Message) { +} + +func chHandleCmdInvite(ch *Channel, msg *Message) { +} + +func chHandleCmdKick(ch *Channel, msg *Message) { +} + +func chHandleCmdPrivmsg(ch *Channel, msg *Message) { + ch.bcMsg(msg, false) +} + +func chHandleCmdNotice(ch *Channel, msg *Message) { +} + +func chHandleCmdUsers(ch *Channel, msg *Message) { +} + +// vi:ts=4:sw=4:et \ No newline at end of file diff --git a/client.go b/client.go new file mode 100644 index 0000000..231caeb --- /dev/null +++ b/client.go @@ -0,0 +1,323 @@ +package ircd + +import ( + "bufio" + "code.dnix.de/xlog" + "fmt" + "io" + "net" + "strings" + "time" +) + +type Client interface { + Name() string + Send(*Message) + Receive(*Message) + Register() chan bool + AddMode(string) + DelMode(string) + HasMode(string) bool +} + +type RemoteClient struct { + server *Server + + name string + password string + modes string + + isSSL bool + isRegistered bool + isAuthed bool + isClosed bool + + receive chan *Message + register chan bool + + conn net.Conn + writeq chan string + channels map[*Channel]bool +} + +func NewRemoteClient(srv *Server, conn net.Conn) *RemoteClient { + cl := new(RemoteClient) + cl.server = srv + + cl.name = "" + cl.password = "" + cl.modes = "" + + cl.receive = make(chan *Message) + cl.register = make(chan bool) + + cl.isRegistered = false + cl.isAuthed = false + cl.isClosed = false + cl.conn = conn + cl.writeq = make(chan string, 256) + + go cl.connReader() + go cl.connWriter() + go cl.loop() + + xlog.Info("Client connected.") + + return cl +} + +func (cl *RemoteClient) Name() string { + return cl.name +} + +func (cl *RemoteClient) Send(msg *Message) { + cl.server.Receive <- msg +} + +func (cl *RemoteClient) Receive(msg *Message) { + cl.receive <- msg +} + +func (cl *RemoteClient) Register() chan bool { + return cl.register +} + +func (cl *RemoteClient) AddMode(mode string) { + cl.modes = cl.modes + mode +} + +func (cl *RemoteClient) DelMode(mode string) { + cl.modes = strings.Replace(cl.modes, mode, "", -1) +} + +func (cl *RemoteClient) HasMode(mode string) bool { + return strings.IndexRune(cl.modes, rune(mode[0])) != -1 +} + +func (cl *RemoteClient) writeMsg(msg *Message) { + var src, ctx, cmd, args, text string + + src = fmt.Sprintf("%s!%s@%s", msg.Src, msg.Src, cl.server.Host) + ctx = msg.Ctx + cmd = msg.Cmd + text = msg.Text + args = "" + for _, arg := range msg.Args { + args += " " + arg + } + if text != "" { + cl.writeLine(fmt.Sprintf(":%s %s %s%s :%s", src, cmd, ctx, args, text)) + } else { + cl.writeLine(fmt.Sprintf(":%s %s %s%s", src, cmd, ctx, args)) + } +} + +func (cl *RemoteClient) loop() { + for { + time.Sleep(1 * time.Millisecond) + if cl.isClosed { + return + } + select { + case msg := <-cl.receive: + cl.writeMsg(msg) + default: + continue + } + } +} + +func (cl *RemoteClient) destroy(s string) { + if cl.isClosed { + return + } + cl.isClosed = true + close(cl.writeq) + cl.conn.Write([]byte("ERROR :Closing link (" + s + ")")) + cl.conn.Close() + cl.server.DelClient <- cl + if cl.name != "" { + xlog.Info("Client '%s' disconnected: %s", cl.name, s) + } else { + xlog.Info("Client disconnected: %s", s) + } +} + +func (cl *RemoteClient) writeLine(format string, a ...interface{}) { + cl.writeq <- fmt.Sprintf(format, a...) +} + +func (cl *RemoteClient) connReader() { + input := bufio.NewReader(cl.conn) + for { + s, err := input.ReadString('\n') + if err == io.EOF { + cl.destroy("Connection lost.") + return + } + if err != nil { + cl.destroy(fmt.Sprintf("Read error (%s).", err.Error())) + return + } + s = strings.Trim(s, "\r\n") + cl.handleLine(s) + } +} + +func (cl *RemoteClient) connWriter() { + for line := range cl.writeq { + written := 0 + bytes := []byte(line + "\r\n") + for written < len(line) { + n, err := cl.conn.Write(bytes[written:]) + if err == io.EOF { + cl.destroy("Connection lost.") + return + } else if err != nil { + cl.destroy(fmt.Sprintf("Write error (%s).", err.Error())) + return + } + written += n + } + } +} + +var lineFuncs = map[string]func(*RemoteClient, *Message) bool{ + CMD_PASS: handleLinePass, + CMD_NICK: handleLineNick, + CMD_USER: handleLineUser, + // CMD_OPER: handleLineOper, + // CMD_QUIT: handleLineQuit, + CMD_JOIN: handleLineJoin, + CMD_PART: handleLinePart, + CMD_MODE: handleLineMode, + // CMD_TOPIC: handleLineTopic, + // CMD_NAMES: handleLineNames, + // CMD_LIST: handleLineList, + // CMD_INVITE: handleLineInvite, + // CMD_KICK: handleLineKick, + // CMD_VERSION: handleLineVersion, + // CMD_STATS: handleLineStats, + // CMD_TIME: handleLineTime, + // CMD_ADMIN: handleLineAdmin, + // CMD_INFO: handleLineInfo, + CMD_PRIVMSG: handleLinePrivmsg, + // CMD_NOTICE: handleLineNotice, + // CMD_WHO: handleLineWho, + // CMD_WHOIS: handleLineWhois, + // CMD_WHOWAS: handleLineWhowas, + // CMD_KILL: handleLineKill, + CMD_PING: handleLinePing, + // CMD_PONG: handleLinePong, + // CMD_ERROR: handleLineError, + // CMD_AWAY: handleLineAway, + CMD_REHASH: handleLineRehash, + // CMD_RESTART: handleLineRestart, + // CMD_SUMMON: handleLineSummon, + // CMD_USERS: handleLineUsers, + // CMD_USERHOST: handleLineUserhost, + // CMD_ISON: handleLineIson, +} + +func (cl *RemoteClient) handleLine(s string) { + xlog.Debug("Raw: [%s] '%s'.", cl.name, s) + msg := M("", "", "", "", "", "") + args := strings.SplitN(s, " :", 2) + if len(args) > 1 { + msg.Text = args[1] + } + args = strings.Fields(args[0]) + msg.Cmd = strings.ToUpper(args[0]) + if len(args) > 1 { + msg.Args = args[1:] + } + msg.Src = cl.name + msg.Dst = "" + if _, exists := lineFuncs[msg.Cmd]; !exists { + // xlog.Warning("No such command (%s).", msg.Cmd) + return + } + if route := lineFuncs[msg.Cmd](cl, msg); route { + // xlog.Warning("Routing to server (%s).", msg.Cmd) + cl.Send(msg) + } +} + +func checkAuth(cl *RemoteClient) { + if cl.name == "" || cl.password == "" { + return + } +} + +func handleLinePass(cl *RemoteClient, msg *Message) bool { + cl.password = msg.Args[0] + return false +} + +func handleLineNick(cl *RemoteClient, msg *Message) bool { + if cl.name != "" { + // TODO multiple registration not possible + return false + } + /* + if _, exists := cl.Server.Clients[msg.Args[0]]; exists { + cl.destroy("User '" + msg.Args[0] + "' already connected.") + } else { + cl.name = msg.Args[0] + cl.isRegistered = true + cl.Server.AddClient <- cl + xlog.Info("User '%s' registered.", msg.Args[0]) + } + */ + if len(msg.Args) < 1 { + xlog.Warning("Nicksalat!") + return false + } + cl.name = msg.Args[0] + cl.server.AddClient <- cl + if registered := <-cl.register; registered { + cl.isRegistered = true + xlog.Info("User '%s' registered.", msg.Args[0]) + } else { + cl.destroy("User '" + msg.Args[0] + "' already connected.") + } + return false +} + +func handleLineUser(cl *RemoteClient, msg *Message) bool { + return false +} + +func handleLineJoin(cl *RemoteClient, msg *Message) bool { + msg.Dst = msg.Args[0] + msg.Args = make([]string, 0) + return true +} + +func handleLinePart(cl *RemoteClient, msg *Message) bool { + msg.Dst = msg.Args[0] + msg.Args = make([]string, 0) + return true +} + +func handleLineMode(cl *RemoteClient, msg *Message) bool { + msg.Dst = msg.Args[0] + msg.Args = make([]string, 0) + return true +} + +func handleLinePrivmsg(cl *RemoteClient, msg *Message) bool { + msg.Dst = msg.Args[0] + msg.Args = make([]string, 0) + return true +} + +func handleLinePing(cl *RemoteClient, msg *Message) bool { + return true +} + +func handleLineRehash(cl *RemoteClient, msg *Message) bool { + return true +} + +// vim:ts=4:sts=4:sw=4:noet:tw=72 diff --git a/entity.go b/entity.go new file mode 100644 index 0000000..76a6d42 --- /dev/null +++ b/entity.go @@ -0,0 +1,6 @@ +package ircd + +type Entity interface { +} + +// vi:ts=4:sw=4:et \ No newline at end of file diff --git a/message.go b/message.go new file mode 100644 index 0000000..e145984 --- /dev/null +++ b/message.go @@ -0,0 +1,24 @@ +package ircd + +import ( + "strings" +) + +type Message struct { + Src string + Dst string + Ctx string + Cmd string + Args []string + Text string +} + +func M(src, dst, ctx, cmd, args, text string) *Message { + argv := []string{} + if args != "" { + argv = strings.Split(args, " ") + } + return &Message{src, dst, ctx, cmd, argv, text} +} + +// vi:ts=4:sw=4:et \ No newline at end of file diff --git a/protocol.go b/protocol.go new file mode 100644 index 0000000..5ac4cb5 --- /dev/null +++ b/protocol.go @@ -0,0 +1,206 @@ +package ircd + +const ( + // Commands + CMD_ADMIN = "ADMIN" + CMD_AWAY = "AWAY" + CMD_CLEARCHAN = "CLEARCHAN" + CMD_CLEAROPS = "CLEAROPS" + CMD_DIE = "DIE" + CMD_ERROR = "ERROR" + CMD_HELP = "HELP" + CMD_INFO = "INFO" + CMD_INVITE = "INVITE" + CMD_ISON = "ISON" + CMD_JOIN = "JOIN" + CMD_KICK = "KICK" + CMD_KILL = "KILL" + CMD_KLINE = "KLINE" + CMD_LIST = "LIST" + CMD_MODE = "MODE" + CMD_NAMES = "NAMES" + CMD_NICK = "NICK" + CMD_NOTICE = "NOTICE" + CMD_OJOIN = "OJOIN" + CMD_OPER = "OPER" + CMD_OPME = "OPME" + CMD_PART = "PART" + CMD_PASS = "PASS" + CMD_PING = "PING" + CMD_PONG = "PONG" + CMD_PRIVMSG = "PRIVMSG" + CMD_QUIT = "QUIT" + CMD_REHASH = "REHASH" + CMD_RESTART = "RESTART" + CMD_SERVER = "SERVER" + CMD_STATS = "STATS" + CMD_SUMMON = "SUMMON" + CMD_SQUIT = "SQUIT" + CMD_TIME = "TIME" + CMD_TOPIC = "TOPIC" + CMD_USER = "USER" + CMD_USERHOST = "USERHOST" + CMD_USERS = "USERS" + CMD_VERSION = "VERSION" + CMD_WALLOPS = "WALLOPS" + CMD_WHO = "WHO" + CMD_WHOIS = "WHOIS" + CMD_WHOWAS = "WHOWAS" + // Signon responses + RPL_WELCOME = "001" + RPL_YOURHOST = "002" + RPL_CREATED = "003" + RPL_MYINFO = "004" + RPL_ISUPPORT = "005" + // Command replies + RPL_ADMINEMAIL = "259" + RPL_ADMINLOC1 = "257" + RPL_ADMINLOC2 = "258" + RPL_ADMINME = "256" + RPL_AWAY = "301" + RPL_BANLIST = "367" + RPL_CHANNELMODEIS = "324" + RPL_ENDOFBANLIST = "368" + RPL_ENDOFEXCEPTLIST = "349" + RPL_ENDOFINFO = "374" + RPL_ENDOFINVITELIST = "347" + RPL_ENDOFLINKS = "365" + RPL_ENDOFMOTD = "376" + RPL_ENDOFNAMES = "366" + RPL_ENDOFSTATS = "219" + RPL_ENDOFUSERS = "394" + RPL_ENDOFWHO = "315" + RPL_ENDOFWHOIS = "318" + RPL_EXCEPTLIST = "348" + RPL_INFO = "371" + RPL_INVITELIST = "346" + RPL_INVITING = "341" + RPL_ISON = "303" + RPL_LINKS = "364" + RPL_LISTSTART = "321" + RPL_LIST = "322" + RPL_LISTEND = "323" + RPL_LUSERCHANNELS = "254" + RPL_LUSERCLIENT = "251" + RPL_LUSERME = "255" + RPL_LUSEROP = "252" + RPL_LUSERUNKNOWN = "253" + RPL_MOTD = "372" + RPL_MOTDSTART = "375" + RPL_NAMEREPLY = "353" + RPL_NOTOPIC = "331" + RPL_NOUSERS = "395" + RPL_NOWAWAY = "306" + RPL_REHASHING = "382" + RPL_SERVLIST = "234" + RPL_SERVLISTEND = "235" + RPL_STATSCOMMANDS = "212" + RPL_STATSLINKINFO = "211" + RPL_STATSOLINE = "243" + RPL_STATSpLINE = "249" + RPL_STATSPLINE = "220" + RPL_STATSUPTIME = "242" + RPL_SUMMONING = "342" + RPL_TIME = "391" + RPL_TOPIC = "332" + RPL_TOPICWHOTIME = "333" + RPL_TRACECLASS = "209" + RPL_TRACECONNECTING = "201" + RPL_TRACEEND = "262" + RPL_TRACEHANDSHAKE = "202" + RPL_TRACELINK = "200" + RPL_TRACELOG = "261" + RPL_TRACENEWTYPE = "208" + RPL_TRACEOPERATOR = "204" + RPL_TRACERECONNECT = "210" + RPL_TRACESERVER = "206" + RPL_TRACESERVICE = "207" + RPL_TRACEUNKNOWN = "203" + RPL_TRACEUSER = "205" + RPL_TRYAGAIN = "263" + RPL_UMODEIS = "221" + RPL_UNAWAY = "305" + RPL_UNIQOPIS = "325" + RPL_USERHOST = "302" + RPL_USERS = "393" + RPL_USERSSTART = "392" + RPL_VERSION = "351" + RPL_WHOISACCOUNT = "330" + RPL_WHOISCHANNELS = "319" + RPL_WHOISHOST = "378" + RPL_WHOISIDLE = "317" + RPL_WHOISMODES = "379" + RPL_WHOISOPERATOR = "313" + RPL_WHOISSECURE = "671" + RPL_WHOISSERVER = "312" + RPL_WHOISUSER = "311" + RPL_WHOREPLY = "352" + RPL_YOUREOPER = "381" + RPL_YOURESERVICE = "383" + // Error replies + ERR_ALREADYREGISTERED = "462" + ERR_BADCHANMASK = "476" + ERR_BADCHANNELKEY = "475" + ERR_BADMASK = "415" + ERR_BANLISTFULL = "478" + ERR_BANNEDFROMCHAN = "474" + ERR_CANNOTSENDTOCHAN = "404" + ERR_CANTKILLSERVER = "483" + ERR_CHANNELISFULL = "471" + ERR_CHANOPRIVSNEEDED = "482" + ERR_ERRONEOUSNICKNAME = "432" + ERR_FILEERROR = "424" + ERR_INVITEONLYCHAN = "473" + ERR_KEYSET = "467" + ERR_NEEDMOREPARAMS = "461" + ERR_NICKCOLLISION = "436" + ERR_NICKNAMEINUSE = "433" + ERR_NOADMININFO = "423" + ERR_NOCHANMODES = "477" + ERR_NOLOGIN = "444" + ERR_NOMOTD = "422" + ERR_NONICKNAMEGIVEN = "431" + ERR_NOOPERHOST = "491" + ERR_NOORIGIN = "409" + ERR_NOPERMFORHOST = "463" + ERR_NOPRIVILEGES = "481" + ERR_NORECIPIENT = "411" + ERR_NOSUCHCHANNEL = "403" + ERR_NOSUCHNICK = "401" + ERR_NOSUCHSERVER = "402" + ERR_NOSUCHSERVICE = "408" + ERR_NOTEXTTOSEND = "412" + ERR_NOTONCHANNEL = "442" + ERR_NOTOPLEVEL = "413" + ERR_NOTREGISTERED = "451" + ERR_PASSWDMISMATCH = "464" + ERR_RESTRICTED = "484" + ERR_SUMMONDISABLED = "445" + ERR_TOOMANYCHANNELS = "405" + ERR_TOOMANYTARGETS = "407" + ERR_UMODEUNKNOWNFLAG = "501" + ERR_UNAVAILRESOURCE = "437" + ERR_UNIQOPPRIVSNEEDED = "485" + ERR_UNKNOWNCOMMAND = "421" + ERR_UNKNOWNMODE = "472" + ERR_USERNOTINCHANNEL = "441" + ERR_USERONCHANNEL = "443" + ERR_USERSDISABLED = "446" + ERR_USERSDONTMATCH = "502" + ERR_WASNOSUCHNICK = "406" + ERR_WILDTOPLEVEL = "414" + ERR_YOUREBANNEDCREEP = "465" + ERR_YOUWILLBEBANNED = "466" + // Custom numerics + RPL_HELPSTART = "704" // Sent at start of HELP response + RPL_HELPTXT = "705" // Sent for lines of HELP response + RPL_ENDOFHELP = "706" // Sent at end of HELP response + RPL_STATSMEMMEM = "710" // STATS reply for "mem" - Memory statistics + RPL_STATSMEMHEAP = "711" // STATS reply for "mem" - Heap statistics + RPL_STATSMEMRTIME = "712" // STATS reply for "mem" - Runtime statistics + // Custom errors + ERR_BANONCHAN = "435" // Cannot change nickname while banned on channel + ERR_UNKNOWNSTAT = "910" // STATS error for unknown STAT request +) + +// vi:ts=4:sw=4:et \ No newline at end of file diff --git a/server.go b/server.go new file mode 100644 index 0000000..6c3f804 --- /dev/null +++ b/server.go @@ -0,0 +1,353 @@ +package ircd + +import ( + "code.dnix.de/conf" + "fmt" + "net" + "os" + "strconv" + "strings" + "time" + // "io" + "code.dnix.de/xlog" + "runtime" +) + +const ( + CHANLIMIT = 1024 + CHANNELLEN = 200 + TOPICLEN = 1024 +) + +var flagConfig string + +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 ControlMsg struct { + Opcode int + Client *Client +} + +type Server struct { + Config *conf.ConfigFile + + Receive chan *Message + AddClient chan Client + DelClient chan Client + Host string + + info string + software string + version string + created string + motd string + clients map[string]Client + channels map[string]*Channel + ports map[int]bool + configPath string +} + +func init() { +} + +// Create a new server instance. +func NewServer(configPath, software, version string) *Server { + srv := &Server{software: software, version: version, created: "yes"} + + srv.Receive = make(chan *Message, 1024) + srv.AddClient = make(chan Client, 1024) + srv.DelClient = make(chan Client, 1024) + srv.Host = "dnix.de" + + srv.clients = make(map[string]Client) + srv.channels = make(map[string]*Channel) + srv.info = "" + srv.motd = `foo bar baz.` + srv.configPath = configPath + srv.loadConfig() + + loglevel, _ := srv.Config.GetInt("system", "loglevel") + xlog.Init(loglevel) + + return srv +} + +// Open the listening port and start the main server loop. +func (srv *Server) Run() { + xlog.Info("%s/%s", srv.software, srv.version) + srv.Host, _ = srv.Config.GetString("server", "host") + srv.info, _ = srv.Config.GetString("server", "info") + port, _ := srv.Config.GetInt("net", "port") + go srv.listen(port) + srv.loop() +} + +func (srv *Server) loop() { + xlog.Debug("Entering server main loop.") + for { + time.Sleep(1 * time.Millisecond) + select { + case msg := <-srv.Receive: + srv.recvMsg(msg) + case cl := <-srv.AddClient: + if _, exists := srv.clients[cl.Name()]; exists { + xlog.Warning("Client registration failed: '%s'.", cl.Name()) + go func() { cl.Register() <- false }() + continue + } + go func() { cl.Register() <- true }() + srv.clients[cl.Name()] = cl + xlog.Info("Client registered: '%s'.", cl.Name()) + xlog.Info("Server has %d client(s).", len(srv.clients)) + xlog.Debug("Goroutines running: %d.", runtime.NumGoroutine()) + srv.clientLogon(cl) + srv.clientMotd(cl) + case cl := <-srv.DelClient: + delete(srv.clients, cl.Name()) + xlog.Info("Client deleted: '%s'.", cl.Name()) + xlog.Info("Server has %d client(s).", len(srv.clients)) + xlog.Debug("Goroutines running: %d.", runtime.NumGoroutine()) + default: + for name, ch := range srv.channels { + if len(ch.clients) == 0 { + ch.destroy <- true + delete(srv.channels, name) + xlog.Info("Channel destroyed: %s.", name) + } + } + } + } +} + +func (srv *Server) listen(port int) { + if _, exists := srv.ports[port]; exists { + xlog.Warning("Port %i already opened.", port) + } + listen, err := net.Listen("tcp", ":"+strconv.Itoa(port)) + if err != nil { + xlog.Fatal("Cannot listen on port %i (%s). Exiting.", + port, err.Error()) + os.Exit(-1) + } + + xlog.Info("Start listening on port %d.", port) + + for { + time.Sleep(1e6) + conn, err := listen.Accept() + if err != nil { + // return err + } + NewRemoteClient(srv, conn) + } +} + +func (srv *Server) loadConfig() { + conf, err := conf.ReadConfigFile(srv.configPath) + if err != nil { + xlog.Fatal("Can't read config file (%s).", err.Error()) + os.Exit(-1) + } + srv.Config = conf +} + +func (srv *Server) recvMsg(msg *Message) { + if AddrHost(msg.Dst) != "" { + // TODO send remote + return + } + if AddrHost(msg.Src) != "" { + // TODO from remote + return + } + if msg.Dst != "" { + srv.sendMsg(msg) + return + } + cmd := msg.Cmd + hook, exists := srvCommandHooks[cmd] + if !exists { + srv.sendCommand(msg.Src, ERR_UNKNOWNCOMMAND, cmd, "Unknown command.") + return + } + argc := len(msg.Args) + if argc < hook.MinArgs { + srv.sendCommand(msg.Src, ERR_NEEDMOREPARAMS, cmd, + "Not enough parameters.") + return + } + hook.HookFn(srv, msg) +} + +func (srv *Server) sendMsg(msg *Message) { + // Splitting this into two functions is necessary to avoid an + // initialization loop in channel.go when broadcasting channel messages. + if strings.HasPrefix(msg.Dst, "#") { + srv.sendMsgToChannel(msg) + } else { + srv.sendMsgToClient(msg) + } +} + +func (srv *Server) sendMsgToChannel(msg *Message) { + if _, exists := srv.channels[msg.Dst]; !exists { + if msg.Cmd != "JOIN" { + srv.sendCommand(msg.Src, ERR_NOSUCHCHANNEL, msg.Dst, "No such channel.") + return + } + srv.channels[msg.Dst] = NewChannel(srv, msg.Dst) + xlog.Info("Channel created: %s.", msg.Dst) + } + ch := srv.channels[msg.Dst] + ch.Receive(msg) +} + +func (srv *Server) sendMsgToClient(msg *Message) { + if _, exists := srv.clients[msg.Dst]; !exists { + xlog.Error("Client '%s' does not exist.", msg.Dst) + return + } + cl := srv.clients[msg.Dst] + cl.Receive(msg) +} + +func (srv *Server) sendRemote(msg *Message) { +} + +func (srv *Server) sendCommand(dst, cmd, args, text string) { + srv.sendMsg(M(srv.Host, dst, dst, cmd, args, text)) +} + +func (srv *Server) sendClient(cl Client, cmd, args, text string) { + srv.sendMsg(M(srv.Host, cl.Name(), cl.Name(), cmd, args, text)) +} + +func (srv *Server) clientLogon(cl Client) { + srv.sendClient(cl, RPL_WELCOME, "", "Willkommen!") + srv.sendClient(cl, RPL_YOURHOST, "", + fmt.Sprintf("Your host is %s, running on %s/%s.", + srv.Host, srv.software, srv.version)) + srv.sendClient(cl, RPL_CREATED, "", + "This server was created. Yes. Really.") + srv.sendClient(cl, RPL_MYINFO, + fmt.Sprintf(myinfo, srv.Host, srv.software, srv.version), "") + srv.sendClient(cl, RPL_ISUPPORT, isupport, "are supported by this server.") +} + +func (srv *Server) clientMotd(cl Client) { + srv.sendClient(cl, RPL_MOTDSTART, "", + fmt.Sprintf("- %s Message of the day -", srv.Host)) + for _, line := range strings.Split(srv.motd, "\n") { + srv.sendClient(cl, RPL_MOTD, "", fmt.Sprintf("- %s", line)) + } + srv.sendClient(cl, RPL_ENDOFMOTD, "", "End of MOTD command.") +} + +type SrvCommandHook struct { + HookFn func(srv *Server, msg *Message) + MinArgs int + NeedOper bool + NeedAuth bool +} + +var srvCommandHooks = map[string]SrvCommandHook{ + "OPER": {srvHandleCmdOper, 1, false, false}, + "QUIT": {srvHandleCmdQuit, 0, false, false}, + "MODE": {srvHandleCmdMode, 1, false, false}, + "LIST": {srvHandleCmdList, 0, false, false}, + "VERSION": {srvHandleCmdVersion, 0, false, false}, + "STATS": {srvHandleCmdStats, 0, false, false}, + "TIME": {srvHandleCmdTime, 0, false, false}, + "ADMIN": {srvHandleCmdAdmin, 0, false, false}, + "INFO": {srvHandleCmdInfo, 0, false, false}, + "WHO": {srvHandleCmdWho, 0, false, false}, + "WHOIS": {srvHandleCmdWhois, 0, false, false}, + "WHOWAS": {srvHandleCmdWhowas, 0, false, false}, + "KILL": {srvHandleCmdKill, 0, false, false}, + "PING": {srvHandleCmdPing, 0, false, false}, + "PONG": {srvHandleCmdPong, 0, false, false}, + "ERROR": {srvHandleCmdError, 0, false, false}, + "AWAY": {srvHandleCmdAway, 0, false, false}, + "REHASH": {srvHandleCmdRehash, 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}, +} + +func srvHandleCmdOper(srv *Server, msg *Message) { +} + +func srvHandleCmdQuit(srv *Server, msg *Message) { +} + +func srvHandleCmdMode(srv *Server, msg *Message) { +} + +func srvHandleCmdList(srv *Server, msg *Message) { +} + +func srvHandleCmdVersion(srv *Server, msg *Message) { +} + +func srvHandleCmdStats(srv *Server, msg *Message) { +} + +func srvHandleCmdTime(srv *Server, msg *Message) { +} + +func srvHandleCmdAdmin(srv *Server, msg *Message) { +} + +func srvHandleCmdInfo(srv *Server, msg *Message) { +} + +func srvHandleCmdWho(srv *Server, msg *Message) { +} + +func srvHandleCmdWhois(srv *Server, msg *Message) { +} + +func srvHandleCmdWhowas(srv *Server, msg *Message) { +} + +func srvHandleCmdKill(srv *Server, msg *Message) { +} + +func srvHandleCmdPing(srv *Server, msg *Message) { + srv.sendCommand(msg.Src, "PONG", "", msg.Args[0]) +} + +func srvHandleCmdPong(srv *Server, msg *Message) { +} + +func srvHandleCmdError(srv *Server, msg *Message) { +} + +func srvHandleCmdAway(srv *Server, msg *Message) { +} + +func srvHandleCmdRehash(srv *Server, msg *Message) { + srv.loadConfig() + srv.sendCommand(msg.Src, RPL_REHASHING, "", "Rehashing.") + xlog.Info("Rehashing.") +} + +func srvHandleCmdRestart(srv *Server, msg *Message) { +} + +func srvHandleCmdSummon(srv *Server, msg *Message) { +} + +func srvHandleCmdUsers(srv *Server, msg *Message) { +} + +func srvHandleCmdUserhost(srv *Server, msg *Message) { +} + +func srvHandleCmdIson(srv *Server, msg *Message) { +} + +// vim:ts=4:sts=4:sw=4:noet:tw=72 diff --git a/user.go b/user.go new file mode 100644 index 0000000..2db13cd --- /dev/null +++ b/user.go @@ -0,0 +1,35 @@ +package ircd + +import ( +// "dnix/conf" +// "dnix/xlog" +// "encoding/json" +// "os" +// "time" +) + +type UserRecord struct { + Name string + Pass string + FriendsPending []string + Friends []string + LastLogin int64 +} + +/* +func LoadUser(cfg *conf.ConfigFile, name string) { + path, _ := cfg.GetString("server", "user_path") + "name" + file, err := os.Open(path) + if err != nil { + xlog.Error("Can't open user file '%s' (%s).", name, err.Error()) + return + } + buf := make([]byte, 10000) + _, err := file.Read(buf) + if err != nil { + xlog.Error("Can't read user file '%s' (%s).", name, err.Error()) + } +} +*/ + +// vi:ts=4:sw=4:et \ No newline at end of file