From 2eb4ffad3db6c937f2cb46855a016a383c358b1b Mon Sep 17 00:00:00 2001 From: Andreas Neue Date: Sat, 22 Nov 2014 14:21:30 +0100 Subject: [PATCH] init --- addr.go | 32 +++++ channel.go | 159 +++++++++++++++++++++++ client.go | 323 +++++++++++++++++++++++++++++++++++++++++++++++ entity.go | 6 + message.go | 24 ++++ protocol.go | 206 ++++++++++++++++++++++++++++++ server.go | 353 ++++++++++++++++++++++++++++++++++++++++++++++++++++ user.go | 35 ++++++ 8 files changed, 1138 insertions(+) create mode 100644 addr.go create mode 100644 channel.go create mode 100644 client.go create mode 100644 entity.go create mode 100644 message.go create mode 100644 protocol.go create mode 100644 server.go create mode 100644 user.go 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