diff --git a/.gitignore b/.gitignore index 93233d9..522d572 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /gobot +/ircd-test *.[568] _obj/ _test/ diff --git a/Makeserv b/Makeserv new file mode 100644 index 0000000..563ca24 --- /dev/null +++ b/Makeserv @@ -0,0 +1,12 @@ +# Copyright 2009 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +include $(GOROOT)/src/Make.inc + +TARG=ircd-test +GOFILES=\ + server.go\ + +include $(GOROOT)/src/Make.cmd + diff --git a/server-plan.svg b/server-plan.svg new file mode 100644 index 0000000..fb18756 --- /dev/null +++ b/server-plan.svg @@ -0,0 +1,612 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + daemon + + + + client + + + + chanpriv + + + + nick + + + + chan + + + + node + + + + network + + + + configreader + + + + map + + + + mux + + + + server + + + + handlers + + + + parser + + + + clientmsg + + + + servermsg + + + + + + + + + + + + + + + + + + + + + diff --git a/server.cfg b/server.cfg new file mode 100644 index 0000000..498a6e7 --- /dev/null +++ b/server.cfg @@ -0,0 +1,16 @@ +port 6667 +port 7009 { + class = "server" +} +port 6697 { ssl = true } +port 7011 { + ssl = true + class = "server" +} + +oper fluffle { + pass = "foobar" + hostmask = "*camelid@*" + link = true +} + diff --git a/server.go b/server.go new file mode 100644 index 0000000..cb3e38b --- /dev/null +++ b/server.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "irc/server/config" +) + +func main() { + cfg := config.LoadConfig("server.cfg") + for e, v := range(cfg.Errors) { + fmt.Println(e, v) + } +} diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 0000000..e1db2ec --- /dev/null +++ b/server/Makefile @@ -0,0 +1,11 @@ +# Copyright 2009 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +include $(GOROOT)/src/Make.inc + +TARG=irc/server +GOFILES=\ + config.go\ + +include $(GOROOT)/src/Make.pkg diff --git a/server/config/Makefile b/server/config/Makefile new file mode 100644 index 0000000..49a0b90 --- /dev/null +++ b/server/config/Makefile @@ -0,0 +1,17 @@ +# Copyright 2009 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +include $(GOROOT)/src/Make.inc + +TARG=irc/server/config +GOFILES=\ + config.go\ + port.go\ + oper.go\ + link.go\ + ban.go\ + info.go\ + settings.go\ + +include $(GOROOT)/src/Make.pkg diff --git a/server/config/ban.go b/server/config/ban.go new file mode 100644 index 0000000..38eda27 --- /dev/null +++ b/server/config/ban.go @@ -0,0 +1,33 @@ +package config + +type cBan interface { + Match(string) bool + Reason() string +} + +// G-Line etc; +type cBanNick struct { + NickMask string // nick!ident@host + Reason string +} + +// Z-Line +type cBanIP struct { + Address string // ip (or hostname), plus optional CIDR netmask + Reason string + ip string // parsed into these + cidr int +} + +// CTCP version ban +type cBanVersion struct { + VersionRegex string // regex to match against version reply + Reason string +} + +// Ban server from linking to network +type cBanServer struct { + ServerMask string // matched against name of linked server + Reason string +} + diff --git a/server/config/config.go b/server/config/config.go new file mode 100644 index 0000000..93b0d4a --- /dev/null +++ b/server/config/config.go @@ -0,0 +1,167 @@ +package config + +import ( + "io" + "os" + "fmt" + "strconv" + "scanner" +) + +type Config struct { + fn string + scan *scanner.Scanner + + // Ports we listen on. + Ports map[int]*cPort + // People with teh p0wer. + Opers map[string]*cOper + // Servers we link to on the network. + Links map[string]*cLink + // Servers/nickmasks/IPs that are unwanted. + Bans []*cBan + + // Server info (name, admins, etc.) + Info *cInfo + + // Server settings + Settings *cSettings + + // Parse errors + Errors []os.Error +} + +type configMap map[string]func(*Config) +type keywordMap map[string]func(*Config, interface{}) + +var configKeywords = configMap{ + "port": (*Config).parsePort, +// "oper": (*Config).parseOper, +// "link": (*Config).parseLink, +// "ban": (*Config).parseBan, +// "info": (*Config).parseInfo, +// "set": (*Config).parseSettings, +} + +func LoadConfig(filename string) *Config { + conf := &Config{fn: filename} + conf.initialise() + if fh, err := os.Open(conf.fn, os.O_RDONLY, 0644); err == nil { + conf.Parse(fh) + fh.Close() + } else { + conf.Errors = append(conf.Errors, err) + } + return conf +} + +func (conf *Config) initialise() { + conf.Ports = make(map[int]*cPort) + conf.Opers = make(map[string]*cOper) + conf.Links = make(map[string]*cLink) + conf.Bans = make([]*cBan, 0) + conf.Info = &cInfo{} + conf.Settings = &cSettings{} + conf.Errors = make([]os.Error, 0) +} + +func (conf *Config) Rehash() { + neu := LoadConfig(conf.fn) + if len(neu.Errors) > 0 { + conf.Errors = neu.Errors + } else { + conf = neu + } +} + +func (conf *Config) Parse(io io.Reader) { + s := &scanner.Scanner{} + s.Init(io) + s.Filename = conf.fn + conf.scan = s + tok, text := conf.next() + for tok != scanner.EOF { + // This external loop should only parse Config things + if f, ok := configKeywords[text]; ok { + f(conf) + } else { + conf.parseError("Invalid top-level keyword '%s'", text) + } + fmt.Printf("Token: '%s', type %s\n", s.TokenText(), scanner.TokenString(tok)) + tok, text = conf.next() + } +} + +func (conf *Config) parseKwBlock(dst interface{}, bt string, kw keywordMap) { + if ok := conf.expect("{"); !ok { + conf.parseError("Expected %s configuration block.", bt) + return + } + tok, text := conf.next() + for tok != scanner.EOF { + if f, ok := kw[text]; ok { + if ok = conf.expect("="); ok { + f(conf, dst) + } + } else if text == "}" { + break + } else { + conf.parseError("Invalid %s keyword '%s'", bt, text) + } + tok, text = conf.next() + } +} + +var booleans = map[string]bool { + "true": true, + "yes": true, + "on": true, + "1": true, + "false": false, + "no": false, + "off": false, + "0": false, +} + +func (conf *Config) expectBool() (bool, bool) { + tok, text := conf.next() + if val, ok := booleans[text]; tok == scanner.Ident && ok { + return val, ok + } + conf.parseError("Expected boolean, got '%s'", text) + return false, false +} + +func (conf *Config) expectInt() (int, bool) { + tok, text := conf.next() + num, err := strconv.Atoi(text) + if tok != scanner.Int || err != nil { + conf.parseError("Expected integer, got '%s'", text) + return 0, false + } + return num, true +} + +func (conf *Config) expect(str string) bool { + _, text := conf.next() + if text != str { + conf.parseError("Expected '%s', got '%s'", str, text) + return false + } + return true +} + +func (conf *Config) next() (int, string) { + tok := conf.scan.Scan() + text := conf.scan.TokenText() + if tok == scanner.String { + // drop "quotes" -> quotes + text = text[1:len(text)-1] + } + return tok, text +} + +func (conf *Config) parseError(err string, args ...interface{}) { + err = conf.scan.Pos().String() + ": " + err + conf.Errors = append(conf.Errors, os.NewError(fmt.Sprintf(err, args...))) +} diff --git a/server/config/info.go b/server/config/info.go new file mode 100644 index 0000000..92cf53a --- /dev/null +++ b/server/config/info.go @@ -0,0 +1,8 @@ +package config + +type cInfo struct { + Name, Network, Info, MOTDFile string + Admins []string + Numeric int +} + diff --git a/server/config/link.go b/server/config/link.go new file mode 100644 index 0000000..5157b76 --- /dev/null +++ b/server/config/link.go @@ -0,0 +1,12 @@ +package config + +type cLink struct { + Server string // Server name for link + Address string // {ip,ip6,host}:port + ReceivePass string // Password when server connects to us + ConnectPass string // Password when we connect to server + + // Do we use tls.Dial? or compression (no)? Do we auto-connect on start? + SSL, Zip, Auto bool +} + diff --git a/server/config/oper.go b/server/config/oper.go new file mode 100644 index 0000000..2f009f8 --- /dev/null +++ b/server/config/oper.go @@ -0,0 +1,16 @@ +package config + +type cOper struct { + Username, Password string + HostMask []string + + // Permissions for oper + CanKill, CanBan, CanNick, CanLink bool +} + +var cOperDefaults = &cOper{ + HostMask: []string{"*@*"}, + CanKill: true, CanBan: true, + CanNick: false, CanLink: false, +} + diff --git a/server/config/port.go b/server/config/port.go new file mode 100644 index 0000000..0c110c4 --- /dev/null +++ b/server/config/port.go @@ -0,0 +1,86 @@ +package config + +import ( + "fmt" + "strings" + "scanner" +) + +type cPort struct { + Port int + BindIP, Family, Class string + + // Is port a tls.Listener? Does it support compression (no)? + SSL, Zip bool + + // address == ":" + address string +} + +var portKeywords = keywordMap{ +// "bind_ip": (*Config).parsePortBindIP, +// "family": (*Config).parsePortFamily, + "class": (*Config).parsePortClass, + "ssl": (*Config).parsePortSSL, +// "zip": (*Config).parsePortZip, +} + +var cPortDefaults = cPort{ + BindIP: "", Family: "tcp", Class: "client", + SSL: false, Zip: false, +} + +func defaultPort() *cPort { + p := cPortDefaults + return &p +} + +func (p *cPort) String() string { + str := []string{fmt.Sprintf("port %d {", p.Port)} + if p.BindIP != "" { + str = append(str, "\tbind_ip = " + p.BindIP) + } + str = append(str, + fmt.Sprintf("\tfamily = \"%s\"",p.Family), + fmt.Sprintf("\tclass = \"%s\"", p.Class), + fmt.Sprintf("\tssl = %t", p.SSL), + fmt.Sprintf("\tzip = %t", p.Zip), + "}", + ) + return strings.Join(str, "\n") +} + +func (conf *Config) parsePort() { + port := defaultPort() + portnum, ok := conf.expectInt() + if !ok || portnum > 65535 || portnum < 1024 { + conf.parseError("Invalid port '%s'", portnum) + port = nil + } else { + port.Port = portnum + conf.Ports[portnum] = port + } + if conf.scan.Peek() != '\n' { + conf.parseKwBlock(port, "port", portKeywords) + } + fmt.Println(port.String()) +} + +func (conf *Config) parsePortClass(pi interface{}) { + port := pi.(*cPort) + tok, text := conf.next() + if tok == scanner.String && (text == "server" || text == "client") { + port.Class = text + } else { + conf.parseError( + "Port class must be \"server\" or \"client\", got '%s'", text) + } +} + +func (conf *Config) parsePortSSL(pi interface{}) { + port := pi.(*cPort) + if ssl, ok := conf.expectBool(); ok { + port.SSL = ssl + } +} + diff --git a/server/config/settings.go b/server/config/settings.go new file mode 100644 index 0000000..6c488e3 --- /dev/null +++ b/server/config/settings.go @@ -0,0 +1,8 @@ +package config + +type cSettings struct { + SSLKey, SSLCert, SSLCACert string + MaxChans, MaxConnsPerIP int + LogFile string +} + diff --git a/server/connmux.go b/server/connmux.go new file mode 100644 index 0000000..6f5cf81 --- /dev/null +++ b/server/connmux.go @@ -0,0 +1,58 @@ +package server + +import ( + "bufio" + "net" + "crypto/tls" + "compress/zlib" +) + +type Mux struct { + // Sockets we've created + listening []net.Listener + serving []net.Conn + + // send and recieve channels to sockets + send map[*Node]chan string + recv chan string + + // input/output/error channels to daemon + // Msg interface defined in parser.go + In chan *Msg + Out chan *Msg + Err chan os.Error +} + +func (m *Mux) Serve(addr string, client bool, conf *tls.Config) os.Error { + var l net.Listener, s net.Conn, e os.Error + if conf == nil { + if l, e = net.Listen("tcp", addr); e != nil { + return e + } + } else { + if l, e = tls.Listen("tcp", addr, conf); e != nil { + return e + } + } + append(m.listening, l) + go func() { + for { + if s, e = l.Accept(); e != nil { + m.Err <- e + } + append(m.serving, s) + io := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + if client { + go m.clientSync(io) + } else { + // TODO(abramley): zlib support + go m.serverSync(io) + } + } + } +} + +func (m *Mux) clientSync(in bufio.ReadWriter) { + +} + diff --git a/server/handlers.go b/server/handlers.go new file mode 100644 index 0000000..abb4e43 --- /dev/null +++ b/server/handlers.go @@ -0,0 +1 @@ +package server diff --git a/server/netmap.go b/server/netmap.go new file mode 100644 index 0000000..28327a2 --- /dev/null +++ b/server/netmap.go @@ -0,0 +1,15 @@ +package server + +type NetMap struct { + // To get to a specific Node, go via.. + via map[*Node]*Node + + // Network links, from server's perspective + links *Link +} + +type Link struct { + node *Node + hops int + links []*Link +} diff --git a/server/network.go b/server/network.go new file mode 100644 index 0000000..86e5ecf --- /dev/null +++ b/server/network.go @@ -0,0 +1,9 @@ +package server + +type Network struct { + nodes map[string]*Node + chans map[string]*Channel + nicks map[string]*Nick + tree *NetMap +} + diff --git a/server/nickchan.go b/server/nickchan.go new file mode 100644 index 0000000..abb4e43 --- /dev/null +++ b/server/nickchan.go @@ -0,0 +1 @@ +package server diff --git a/server/node.go b/server/node.go new file mode 100644 index 0000000..4b959fd --- /dev/null +++ b/server/node.go @@ -0,0 +1,6 @@ +package server + +type Node struct { + Name, Host string +} + diff --git a/server/parser.go b/server/parser.go new file mode 100644 index 0000000..abb4e43 --- /dev/null +++ b/server/parser.go @@ -0,0 +1 @@ +package server diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..abb4e43 --- /dev/null +++ b/server/server.go @@ -0,0 +1 @@ +package server