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 @@
+
+
+
+
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