diff --git a/server/config/Makefile b/server/config/Makefile index 49a0b90..38ce507 100644 --- a/server/config/Makefile +++ b/server/config/Makefile @@ -7,11 +7,10 @@ include $(GOROOT)/src/Make.inc TARG=irc/server/config GOFILES=\ config.go\ - port.go\ - oper.go\ - link.go\ - ban.go\ - info.go\ - settings.go\ + parser.go\ include $(GOROOT)/src/Make.pkg + +parser.go: parser.rl + ragel -Z -G2 -o parser.go parser.rl + diff --git a/server/config/ban.go b/server/config/ban.go deleted file mode 100644 index 38eda27..0000000 --- a/server/config/ban.go +++ /dev/null @@ -1,33 +0,0 @@ -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 index e78f93f..56e6c39 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -1,16 +1,14 @@ package config import ( - "io" "os" "fmt" - "strconv" - "scanner" + "net" + "strings" ) type Config struct { fn string - scan *scanner.Scanner // Ports we listen on. Ports map[int]*cPort @@ -31,17 +29,6 @@ type Config struct { 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} @@ -74,103 +61,129 @@ func (conf *Config) Rehash() { } } -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) +/* Port configuration */ +type cPort struct { + Port int + BindIP net.IP // bind to a specific IP for listen port + Class string // "server" or "client" + + // Is port a tls.Listener? Does it support compression (no)? + SSL, Zip bool +} + +func defaultPort() *cPort { + return &cPort{ + BindIP: nil, Class: "client", + SSL: false, Zip: false, + } +} + +func (p *cPort) String() string { + str := []string{fmt.Sprintf("port %d {", p.Port)} + if p.BindIP != nil { + str = append(str, + fmt.Sprintf("\tbind_ip = %s", p.BindIP.String())) + } + str = append(str, + fmt.Sprintf("\tclass = %s", p.Class), + fmt.Sprintf("\tssl = %t", p.SSL), + fmt.Sprintf("\tzip = %t", p.Zip), + "}", + ) + return strings.Join(str, "\n") +} + +/* Oper configuration */ +type cOper struct { + Username, Password string + HostMask []string + + // Permissions for oper + CanKill, CanBan, CanRenick, CanLink bool +} + +func defaultOper() *cOper { + return &cOper{ + HostMask: []string{}, + CanKill: true, CanBan: true, + CanRenick: false, CanLink: false, + } +} + +func (o *cOper) String() string { + str := []string{fmt.Sprintf("oper %s {", o.Username)} + str = append(str, fmt.Sprintf("\tpassword = %s", o.Password)) + if len(o.HostMask) == 0 { + str = append(str, fmt.Sprintf("\thostmask = *@*")) + } else { + for _, h := range o.HostMask { + str = append(str, fmt.Sprintf("\thostmask = %s", h)) } - fmt.Printf("Token: '%s', type %s\n", s.TokenText(), scanner.TokenString(tok)) - tok, text = conf.next() } + str = append(str, + fmt.Sprintf("\tkill = %t", o.CanKill), + fmt.Sprintf("\tban = %t", o.CanBan), + fmt.Sprintf("\trenick = %t", o.CanRenick), + fmt.Sprintf("\tlink = %t", o.CanLink), + "}", + ) + return strings.Join(str, "\n") } -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() - } +/* Link configuration */ +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 } -var booleans = map[string]bool { - "true": true, - "yes": true, - "on": true, - "1": true, - "false": false, - "no": false, - "off": false, - "0": false, +/* Static ban configuration */ +type cBan interface { + Match(string) bool + Reason() string } -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 +// G-Line etc; +type cBanNick struct { + NickMask string // nick!ident@host + Reason string } -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 +// Z-Line +type cBanIP struct { + Address string // ip (or hostname), plus optional CIDR netmask + Reason string + ip string // parsed into these + cidr int } -func (conf *Config) expectString() (string, bool) { - tok, text := conf.next() - if tok != scanner.String && tok != scanner.Ident { - conf.parseError("Expected string, got '%s'", text) - return "", false - } - return text, true +// CTCP version ban +type cBanVersion struct { + VersionRegex string // regex to match against version reply + Reason string } -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 +// Ban server from linking to network +type cBanServer struct { + ServerMask string // matched against name of linked server + Reason string } -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 +/* IRCd settings */ +type cSettings struct { + SSLKey, SSLCert, SSLCACert string + MaxChans, MaxConnsPerIP int + LogFile string } -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...))) +/* IRCd information */ +type cInfo struct { + Name, Network, Info, MOTDFile string + Admins []string + Numeric int } + diff --git a/server/config/info.go b/server/config/info.go deleted file mode 100644 index 92cf53a..0000000 --- a/server/config/info.go +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 5157b76..0000000 --- a/server/config/link.go +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 8a41396..0000000 --- a/server/config/oper.go +++ /dev/null @@ -1,106 +0,0 @@ -package config - -import ( - "fmt" - "strings" - "scanner" -) - -type cOper struct { - Username, Password string - HostMask []string - - // Permissions for oper - CanKill, CanBan, CanRenick, CanLink bool -} - -var operKeywords = keywordMap{ - "password": (*Config).parseOperPassword, - "hostmask": (*Config).parseOperHostMask, - "kill": (*Config).parseOperKill, - "ban": (*Config).parseOperBan, - "renick": (*Config).parseOperRenick, - "link": (*Config).parseOperLink, -} - -func defaultOper() *cOper { - return &cOper{ - HostMask: []string{}, - CanKill: true, CanBan: true, - CanRenick: false, CanLink: false, - } -} - -func (o *cOper) String() string { - str := []string{fmt.Sprintf("oper \"%s\" {", o.Username)} - str = append(str, fmt.Sprintf("\tpassword = \"%s\"", o.Password)) - if len(o.HostMask) == 0 { - str = append(str, fmt.Sprintf("\thostmask = \"*@*\"")) - } else { - for _, h := range o.HostMask { - str = append(str, fmt.Sprintf("\thostmask = \"%s\"", h)) - } - } - str = append(str, - fmt.Sprintf("\tkill = %t", o.CanKill), - fmt.Sprintf("\tban = %t", o.CanBan), - fmt.Sprintf("\trenick = %t", o.CanRenick), - fmt.Sprintf("\tlink = %t", o.CanLink), - "}", - ) - return strings.Join(str, "\n") -} - -func (conf *Config) parseOper() { - oper := defaultOper() - tok, text := conf.next() - if tok != scanner.String && tok != scanner.Ident { - conf.parseError("Invalid username '%s'", text) - return - } - oper.Username = text - conf.parseKwBlock(oper, "oper", operKeywords) - fmt.Println(oper.String()) -} - -func (conf *Config) parseOperPassword(oi interface{}) { - oper := oi.(*cOper) - if pass, ok := conf.expectString(); ok { - oper.Password = pass - } -} - -func (conf *Config) parseOperHostMask(oi interface{}) { - oper := oi.(*cOper) - if mask, ok := conf.expectString(); ok { - oper.HostMask = append(oper.HostMask, mask) - } -} - -func (conf *Config) parseOperKill(oi interface{}) { - oper := oi.(*cOper) - if kill, ok := conf.expectBool(); ok { - oper.CanKill = kill - } -} - -func (conf *Config) parseOperBan(oi interface{}) { - oper := oi.(*cOper) - if ban, ok := conf.expectBool(); ok { - oper.CanBan = ban - } -} - -func (conf *Config) parseOperRenick(oi interface{}) { - oper := oi.(*cOper) - if renick, ok := conf.expectBool(); ok { - oper.CanRenick = renick - } -} - -func (conf *Config) parseOperLink(oi interface{}) { - oper := oi.(*cOper) - if link, ok := conf.expectBool(); ok { - oper.CanLink = link - } -} diff --git a/server/config/parser.rl b/server/config/parser.rl new file mode 100644 index 0000000..3681927 --- /dev/null +++ b/server/config/parser.rl @@ -0,0 +1,136 @@ +package config + +import ( + "io" + "os" + "fmt" + "net" + "strconv" +) + +%% machine config; +%% write data; + +var booleans = map[string]bool { + "true": true, + "yes": true, + "on": true, + "1": true, + "false": false, + "no": false, + "off": false, + "0": false, +} + +%%{ + # mark the current position for acquiring data + action mark { mark = p } + + # acceptable boolean values + true = "yes" | "on" | "1" | "true" ; + false = "no" | "off" | "0" | "false" ; + boolean = true | false ; + + # IP addresses, v4 and v6 + octet = digit # 0-9 + | digit digit # 00-99 + | [01] digit digit # 000-199 + | "2" [0-4] digit # 200-249 + | "2" "5" [0-5] ; # 250-255 + ipv4addr = octet "." octet "." octet "." octet ; + ipv6addr = ( xdigit{1,4} ":" ){7} xdigit{1,4} # all 8 blocks + | xdigit{0,4} "::" # special case + | ( xdigit{1,4} ":" ){1,6} ":" # first 1-6, :: + | ":" ( ":" xdigit{1,4} ){1,6} # ::, final 1-6 + | xdigit{1,4} ":" ( ":" xdigit{1,4} ){1,6} # 1::1-6 + | ( xdigit{1,4} ":" ){2}( ":" xdigit{1,4} ){1,5} # 2::1-5 + | ( xdigit{1,4} ":" ){3}( ":" xdigit{1,4} ){1,4} # 3::1-4 + | ( xdigit{1,4} ":" ){4}( ":" xdigit{1,4} ){1,5} # 4::1-3 + | ( xdigit{1,4} ":" ){5}( ":" xdigit{1,4} ){1,2} # 5::1-2 + | ( xdigit{1,4} ":" ){6}( ":" xdigit{1,4} ) ; # 6::1 + + # Actions to create a cPort and save it into the Config struct + action new_port { cur = defaultPort() } + action save_port { + port := cur.(*cPort) + conf.Ports[port.Port] = port + cur = nil + } + + # parse and save the port number + action set_portnum { + cur.(*cPort).Port, _ = strconv.Atoi(string(data[mark:p])) + } + portnum = digit+ >mark %set_portnum ; + + # parse a bind_ip statement and save the IP + action set_bindip { + cur.(*cPort).BindIP = net.ParseIP(string(data[mark:p])) + } + bindip = (ipv4addr | ipv6addr) >mark %set_bindip ; + portbindip = "bind_ip" " "+ "=" " "+ bindip ; + + # parse a class statement and save it + action set_class { + cur.(*cPort).Class = string(data[mark:p]) + } + portclass = "class" " "+ "=" " "+ ("server" | "client" >mark %set_class) ; + + # parse SSL and Zip booleans + action set_ssl { + cur.(*cPort).SSL = booleans[string(data[mark:p])] + } + portssl = "ssl" " "+ "=" " "+ (boolean >mark %set_ssl) ; + action set_zip { + cur.(*cPort).Zip = booleans[string(data[mark:p])] + } + portzip = "zip" " "+ "=" " "+ (boolean >mark %set_zip) ; + + portstmt = ( portbindip | portclass | portssl | portzip ) ; + portblock = "{" space* ( portstmt | (portstmt " "* "\n" space* )+ ) space* "}" ; + + # a port configuration can either be: + # port \n + # port { portblock } + basicport = "port" >new_port " "+ portnum " "* "\n" %save_port; + portdefn = "port" >new_port " "+ portnum " "+ portblock %save_port ; + portconfig = space* ( basicport | portdefn ) space*; + +# config = portconfig+ +# | operconfig+ +# | linkconfig* +# | infoconfig +# | settings ; + + main := portconfig+; +}%% + +func (conf *Config) Parse(r io.Reader) { + cs, p, mark, pe, eof, buflen := 0, 0, 0, 0, 0, 16384 + done := false + var cur interface{} + data := make([]byte, buflen) + + %% write init; + + for !done { + n, err := r.Read(data) + pe = p + n + if err == os.EOF { + fmt.Println("yeahhhhh.") + done = true + eof = pe + } + + %% write exec; + + } + + if cs < config_first_final { + fmt.Printf("Parse error at %d near '%s'\n", p, data[p:p+10]) + } + + for _, port := range conf.Ports { + fmt.Println(port.String()) + } +} diff --git a/server/config/port.go b/server/config/port.go deleted file mode 100644 index a49a4b3..0000000 --- a/server/config/port.go +++ /dev/null @@ -1,97 +0,0 @@ -package config - -import ( - "net" - "fmt" - "strings" - "scanner" -) - -type cPort struct { - Port int - BindIP net.IP // bind to a specific IP for listen port - Class string // "server" or "client" - - // Is port a tls.Listener? Does it support compression (no)? - SSL, Zip bool -} - -var portKeywords = keywordMap{ - "bind_ip": (*Config).parsePortBindIP, - "class": (*Config).parsePortClass, - "ssl": (*Config).parsePortSSL, - "zip": (*Config).parsePortZip, -} - -func defaultPort() *cPort { - return &cPort{ - BindIP: nil, Class: "client", - SSL: false, Zip: false, - } -} - -func (p *cPort) String() string { - str := []string{fmt.Sprintf("port %d {", p.Port)} - if p.BindIP != nil { - str = append(str, - fmt.Sprintf("\tbind_ip = \"%s\"", p.BindIP.String())) - } - str = append(str, - 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) parsePortBindIP(pi interface{}) { - port := pi.(*cPort) - _, text := conf.next() - if ip := net.ParseIP(text); ip != nil { - port.BindIP = ip - } else { - conf.parseError("'%s' is not a valid IP address", text) - } -} - -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 - } -} - -func (conf *Config) parsePortZip(pi interface{}) { - port := pi.(*cPort) - if zip, ok := conf.expectBool(); ok { - port.Zip = zip - } -} diff --git a/server/config/settings.go b/server/config/settings.go deleted file mode 100644 index 6c488e3..0000000 --- a/server/config/settings.go +++ /dev/null @@ -1,8 +0,0 @@ -package config - -type cSettings struct { - SSLKey, SSLCert, SSLCACert string - MaxChans, MaxConnsPerIP int - LogFile string -} -