From 83d021fcb7b4f6d7daf8459383b97607016d1a84 Mon Sep 17 00:00:00 2001 From: Jeremy Latt Date: Sat, 1 Mar 2014 14:34:51 -0800 Subject: [PATCH 1/5] switch to gcfg for conf file - add some validation for config file - add comments explaining config - remove TLS listener since most clients can't use it anyway - remove unused nick generation function --- config.json | 39 ------------------ ergonomadic.conf | 16 ++++++++ ergonomadic.go | 12 +++--- irc/config.go | 100 +++++++++++++++++++++-------------------------- irc/server.go | 56 +++++--------------------- 5 files changed, 76 insertions(+), 147 deletions(-) delete mode 100644 config.json create mode 100644 ergonomadic.conf diff --git a/config.json b/config.json deleted file mode 100644 index b607611a..00000000 --- a/config.json +++ /dev/null @@ -1,39 +0,0 @@ -// Ergonomadic IRC Server Config -// ----------------------------- -// Passwords are generated by `ergonomadic -genpasswd "$plaintext"`. -// Comments are not allowed in the actual config file. -{ - // `name` is usually a hostname. - "name": "irc.example.com", - - // The path to the MOTD is relative to this file's directory. - "motd": "motd.txt", - - // PASS command password - "password": "JDJhJDA0JHBBenUyV3Z5UU5iWUpiYmlNMlNLZC5VRDZDM21HUzFVbmxLUUI3NTVTLkZJOERLdUFaUWNt", - - // `listeners` are places to bind and listen for - // connections. http://golang.org/pkg/net/#Dial demonstrates valid - // values for `net` and `address`. `net` is optional and defaults - // to `tcp`. - "listeners": [ { - "address": "localhost:7777" - }, { - "net": "tcp6", - "address": "[::1]:7777" - } ], - - // Operators for the OPER command - "operators": [ { - "name": "root", - "password": "JDJhJDA0JHBBenUyV3Z5UU5iWUpiYmlNMlNLZC5VRDZDM21HUzFVbmxLUUI3NTVTLkZJOERLdUFaUWNt" - } ], - - // Global debug flags. `net` generates a lot of output. - "debug": { - "net": true, - "client": false, - "channel": false, - "server": false - } -} diff --git a/ergonomadic.conf b/ergonomadic.conf new file mode 100644 index 00000000..6181e9a7 --- /dev/null +++ b/ergonomadic.conf @@ -0,0 +1,16 @@ +[server] +name = "irc.example.com" ; required, usually a hostname +database = "ergonomadic.db" ; path relative to this file +listen = "localhost:6667" ; see `net.Listen` for examples +listen = "[::1]:6667" ; multiple `listen`s are allowed. +motd = "motd.txt" ; path relative to this file +password = "JDJhJDA0JHJzVFFlNXdOUXNhLmtkSGRUQVVEVHVYWXRKUmdNQ3FKVTRrczRSMTlSWGRPZHRSMVRzQmtt" + +[operator "root"] +password = "JDJhJDA0JEhkcm10UlNFRkRXb25iOHZuSDVLZXVBWlpyY0xyNkQ4dlBVc1VMWVk1LlFjWFpQbGxZNUtl" + +[debug] +net = true +client = false +channel = false +server = false diff --git a/ergonomadic.go b/ergonomadic.go index e04f9e8a..6537ff0b 100644 --- a/ergonomadic.go +++ b/ergonomadic.go @@ -22,9 +22,9 @@ func genPasswd(passwd string) { } func initDB(config *irc.Config) { - os.Remove(config.Database()) + os.Remove(config.Server.Database) - db, err := sql.Open("sqlite3", config.Database()) + db, err := sql.Open("sqlite3", config.Server.Database) if err != nil { log.Fatal(err) } @@ -64,10 +64,10 @@ func main() { } // TODO move to data structures - irc.DEBUG_NET = config.Debug["net"] - irc.DEBUG_CLIENT = config.Debug["client"] - irc.DEBUG_CHANNEL = config.Debug["channel"] - irc.DEBUG_SERVER = config.Debug["server"] + irc.DEBUG_NET = config.Debug.Net + irc.DEBUG_CLIENT = config.Debug.Client + irc.DEBUG_CHANNEL = config.Debug.Channel + irc.DEBUG_SERVER = config.Debug.Server irc.NewServer(config).Run() } diff --git a/irc/config.go b/irc/config.go index 07518f00..8fea2974 100644 --- a/irc/config.go +++ b/irc/config.go @@ -1,18 +1,22 @@ package irc import ( + "code.google.com/p/gcfg" "encoding/base64" - "encoding/json" + "errors" "log" - "os" "path/filepath" ) -func decodePassword(password string) []byte { - if password == "" { +type PassConfig struct { + Password string +} + +func (conf *PassConfig) PasswordBytes() []byte { + if conf.Password == "" { return nil } - bytes, err := base64.StdEncoding.DecodeString(password) + bytes, err := base64.StdEncoding.DecodeString(conf.Password) if err != nil { log.Fatal(err) } @@ -20,72 +24,56 @@ func decodePassword(password string) []byte { } type Config struct { - Debug map[string]bool - Listeners []ListenerConfig - MOTD string - Name string - Operators []OperatorConfig - Password string - directory string + Server struct { + PassConfig + Database string + Listen []string + MOTD string + Name string + } + + Operator map[string]*PassConfig + + Debug struct { + Net bool + Client bool + Channel bool + Server bool + } } -func (conf *Config) Database() string { - return filepath.Join(conf.directory, "ergonomadic.db") -} - -func (conf *Config) PasswordBytes() []byte { - return decodePassword(conf.Password) -} - -func (conf *Config) OperatorsMap() map[string][]byte { +func (conf *Config) Operators() map[string][]byte { operators := make(map[string][]byte) - for _, opConf := range conf.Operators { - operators[opConf.Name] = opConf.PasswordBytes() + for name, opConf := range conf.Operator { + operators[name] = opConf.PasswordBytes() } return operators } -type OperatorConfig struct { - Name string - Password string -} - -func (conf *OperatorConfig) PasswordBytes() []byte { - return decodePassword(conf.Password) -} - -type ListenerConfig struct { - Net string - Address string - Key string - Certificate string -} - -func (config *ListenerConfig) IsTLS() bool { - return (config.Key != "") && (config.Certificate != "") -} - func LoadConfig(filename string) (config *Config, err error) { config = &Config{} - - file, err := os.Open(filename) + err = gcfg.ReadFileInto(config, filename) if err != nil { return } - defer file.Close() - - decoder := json.NewDecoder(file) - err = decoder.Decode(config) - if err != nil { + if config.Server.Name == "" { + err = errors.New("server.name missing") + return + } + if config.Server.Database == "" { + err = errors.New("server.database missing") + return + } + if len(config.Server.Listen) == 0 { + err = errors.New("server.listen missing") return } - config.directory = filepath.Dir(filename) - config.MOTD = filepath.Join(config.directory, config.MOTD) - for _, lconf := range config.Listeners { - if lconf.Net == "" { - lconf.Net = "tcp" - } + // make + dir := filepath.Dir(filename) + if config.Server.MOTD != "" { + config.Server.MOTD = filepath.Join(dir, config.Server.MOTD) } + config.Server.Database = filepath.Join(dir, config.Server.Database) return } diff --git a/irc/server.go b/irc/server.go index 49c5f4ad..8c9d9ebe 100644 --- a/irc/server.go +++ b/irc/server.go @@ -2,10 +2,7 @@ package irc import ( "bufio" - "crypto/rand" - "crypto/tls" "database/sql" - "encoding/binary" "fmt" _ "github.com/mattn/go-sqlite3" "log" @@ -36,7 +33,7 @@ type Server struct { } func NewServer(config *Config) *Server { - db, err := sql.Open("sqlite3", config.Database()) + db, err := sql.Open("sqlite3", config.Server.Database) if err != nil { log.Fatal(err) } @@ -48,11 +45,11 @@ func NewServer(config *Config) *Server { ctime: time.Now(), db: db, idle: make(chan *Client, 16), - motdFile: config.MOTD, - name: config.Name, + motdFile: config.Server.MOTD, + name: config.Server.Name, newConns: make(chan net.Conn, 16), - operators: config.OperatorsMap(), - password: config.PasswordBytes(), + operators: config.Operators(), + password: config.Server.PasswordBytes(), signals: make(chan os.Signal, 1), timeout: make(chan *Client, 16), } @@ -61,8 +58,8 @@ func NewServer(config *Config) *Server { server.loadChannels() - for _, listenerConf := range config.Listeners { - go server.listen(listenerConf) + for _, addr := range config.Server.Listen { + go server.listen(addr) } return server @@ -169,33 +166,18 @@ func (server *Server) InitPhase() Phase { return Authorization } -func newListener(config ListenerConfig) (net.Listener, error) { - if config.IsTLS() { - certificate, err := tls.LoadX509KeyPair(config.Certificate, config.Key) - if err != nil { - return nil, err - } - return tls.Listen("tcp", config.Address, &tls.Config{ - Certificates: []tls.Certificate{certificate}, - PreferServerCipherSuites: true, - }) - } - - return net.Listen("tcp", config.Address) -} - // // listen goroutine // -func (s *Server) listen(config ListenerConfig) { - listener, err := newListener(config) +func (s *Server) listen(addr string) { + listener, err := net.Listen("tcp", addr) if err != nil { log.Fatal(s, "listen error: ", err) } if DEBUG_SERVER { - log.Printf("%s listening on %s", s, config.Address) + log.Printf("%s listening on %s", s, addr) } for { @@ -214,24 +196,6 @@ func (s *Server) listen(config ListenerConfig) { } } -func (s *Server) GenerateGuestNick() string { - bytes := make([]byte, 8) - for { - _, err := rand.Read(bytes) - if err != nil { - panic(err) - } - randInt, n := binary.Uvarint(bytes) - if n <= 0 { - continue // TODO handle error - } - nick := fmt.Sprintf("guest%d", randInt) - if s.clients.Get(nick) == nil { - return nick - } - } -} - // // server functionality // From 6d194e3d9426a304588314f7e0f6ebc1653d918e Mon Sep 17 00:00:00 2001 From: Jeremy Latt Date: Sat, 1 Mar 2014 14:45:23 -0800 Subject: [PATCH 2/5] update readme --- README.md | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 96ffe620..b69f5470 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,11 @@ and issues are welcome. ## Some Features - follows the RFC where possible -- JSON-based configuration -- server password -- channels with many standard modes -- IRC operators -- TLS support (but better to use stunnel with proxy protocol) -- haproxy PROXY protocol header for hostname setting +- gcfg gitconfig-style configuration +- server password (PASS command) +- channels with most standard modes +- IRC operators (OPER command) +- haproxy [PROXY protocol](http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt) header for hostname setting - passwords stored in bcrypt format - channels that persist between restarts (+P) @@ -23,6 +22,17 @@ I wanted to learn Go. "Ergonomadic" is an anagram of "Go IRC Daemon". +## What about SSL/TLS support? + +Go has a not-yet-verified-as-safe TLS 1.2 implementation. Sadly, many +popular IRC clients will negotiate nothing newer than SSLv2. If you +want to use SSL to protect traffic, I recommend using +[stunnel](https://www.stunnel.org/index.html) version 4.56 with +haproxy's +[PROXY protocol](http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt). This +will allow the server to get the client's original addresses for +hostname lookups. + ## Installation ```sh @@ -33,11 +43,12 @@ ergonomadic -conf '/path/to/config.json' -initdb ## Configuration -See the example `config.json`. Passwords are base64-encoded bcrypted -byte strings. You can generate them with the `genpasswd` subcommand. +See the example `ergonomadic.conf`. Passwords are base64-encoded +bcrypted byte strings. You can generate them with the `genpasswd` +subcommand. ```sh -ergonomadic -genpasswd 'hunter21!' +ergonomadic -genpasswd 'hunter2!' ``` ## Running the Server From 22c1cfdc3e270a339c17378e2d9c24280987a798 Mon Sep 17 00:00:00 2001 From: Jeremy Latt Date: Sat, 1 Mar 2014 21:51:52 -0800 Subject: [PATCH 3/5] chdir instead of relativizing paths in config --- ergonomadic.go | 5 +++++ irc/config.go | 8 -------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ergonomadic.go b/ergonomadic.go index 6537ff0b..41fe26b3 100644 --- a/ergonomadic.go +++ b/ergonomadic.go @@ -10,6 +10,7 @@ import ( _ "github.com/mattn/go-sqlite3" "log" "os" + "path/filepath" ) func genPasswd(passwd string) { @@ -57,6 +58,10 @@ func main() { if err != nil { log.Fatal(err) } + err = os.Chdir(filepath.Dir(*conf)) + if err != nil { + log.Fatal(err) + } if *initdb { initDB(config) diff --git a/irc/config.go b/irc/config.go index 8fea2974..ba065722 100644 --- a/irc/config.go +++ b/irc/config.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "errors" "log" - "path/filepath" ) type PassConfig struct { @@ -68,12 +67,5 @@ func LoadConfig(filename string) (config *Config, err error) { err = errors.New("server.listen missing") return } - - // make - dir := filepath.Dir(filename) - if config.Server.MOTD != "" { - config.Server.MOTD = filepath.Join(dir, config.Server.MOTD) - } - config.Server.Database = filepath.Join(dir, config.Server.Database) return } From 1479dbb92e91dff8beb59a8e362bbdb4f6e1bb2e Mon Sep 17 00:00:00 2001 From: Jeremy Latt Date: Wed, 5 Mar 2014 14:02:57 -0800 Subject: [PATCH 4/5] document example config passwords --- ergonomadic.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ergonomadic.conf b/ergonomadic.conf index 6181e9a7..2f360024 100644 --- a/ergonomadic.conf +++ b/ergonomadic.conf @@ -4,10 +4,10 @@ database = "ergonomadic.db" ; path relative to this file listen = "localhost:6667" ; see `net.Listen` for examples listen = "[::1]:6667" ; multiple `listen`s are allowed. motd = "motd.txt" ; path relative to this file -password = "JDJhJDA0JHJzVFFlNXdOUXNhLmtkSGRUQVVEVHVYWXRKUmdNQ3FKVTRrczRSMTlSWGRPZHRSMVRzQmtt" +password = "JDJhJDA0JHJzVFFlNXdOUXNhLmtkSGRUQVVEVHVYWXRKUmdNQ3FKVTRrczRSMTlSWGRPZHRSMVRzQmtt" ; 'test' [operator "root"] -password = "JDJhJDA0JEhkcm10UlNFRkRXb25iOHZuSDVLZXVBWlpyY0xyNkQ4dlBVc1VMWVk1LlFjWFpQbGxZNUtl" +password = "JDJhJDA0JEhkcm10UlNFRkRXb25iOHZuSDVLZXVBWlpyY0xyNkQ4dlBVc1VMWVk1LlFjWFpQbGxZNUtl" ; 'toor' [debug] net = true From 0d7677e34111dc22cff79da2ffd7956fb6cab55e Mon Sep 17 00:00:00 2001 From: Edmund Huber Date: Wed, 5 Mar 2014 23:40:49 +0100 Subject: [PATCH 5/5] fix up README some more and helpful comment in config --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b69f5470..f88d3754 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ hostname lookups. ```sh go get go install -ergonomadic -conf '/path/to/config.json' -initdb +ergonomadic -conf ergonomadic.conf -initdb ``` ## Configuration @@ -54,7 +54,7 @@ ergonomadic -genpasswd 'hunter2!' ## Running the Server ```sh -ergonomadic -conf '/path/to/config.json' +ergonomadic -conf ergonomadic.conf ``` ## Helpful Documentation