mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 10:42:52 +01:00
many changes
- load config sub files relative to config file dir - load config file by name - expect bcrypt for passwords - -genpasswd for generating config-file-safe passwords - block client thread while checking passwords (PASS and OPER)
This commit is contained in:
parent
be089e7f5f
commit
72726a39b8
23
README.md
23
README.md
@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
Ergonomadic is an IRC daemon written from scratch in Go.
|
Ergonomadic is an IRC daemon written from scratch in Go.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- passwords stored in bcrypt format
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
I wanted to learn Go.
|
I wanted to learn Go.
|
||||||
@ -20,16 +31,14 @@ I wanted to learn Go.
|
|||||||
|
|
||||||
## Running the Server
|
## Running the Server
|
||||||
|
|
||||||
You must create an `ergonomadic.json` config file in the current directory.
|
See the example `config.json`. Passwords are base64-encoded bcrypted
|
||||||
|
byte strings. You can generate them with e.g. `ergonomadic -genpasswd
|
||||||
|
'hunter21!'`.
|
||||||
|
|
||||||
### from your GOPATH
|
### from your GOPATH
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
go get
|
||||||
go install
|
go install
|
||||||
ergonomadic
|
ergonomadic -conf '/path/to/ergonomadic.json'
|
||||||
```
|
|
||||||
|
|
||||||
### from local
|
|
||||||
```sh
|
|
||||||
go run ergonomadic.go
|
|
||||||
```
|
```
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.google.com/p/go.crypto/bcrypt"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@ -16,7 +15,6 @@ type Client struct {
|
|||||||
atime time.Time
|
atime time.Time
|
||||||
awayMessage string
|
awayMessage string
|
||||||
channels ChannelSet
|
channels ChannelSet
|
||||||
checkPass chan CheckCommand
|
|
||||||
commands chan editableCommand
|
commands chan editableCommand
|
||||||
ctime time.Time
|
ctime time.Time
|
||||||
flags map[UserMode]bool
|
flags map[UserMode]bool
|
||||||
@ -37,14 +35,13 @@ type Client struct {
|
|||||||
func NewClient(server *Server, conn net.Conn) *Client {
|
func NewClient(server *Server, conn net.Conn) *Client {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
client := &Client{
|
client := &Client{
|
||||||
atime: now,
|
atime: now,
|
||||||
channels: make(ChannelSet),
|
channels: make(ChannelSet),
|
||||||
checkPass: make(chan CheckCommand),
|
commands: make(chan editableCommand),
|
||||||
commands: make(chan editableCommand),
|
ctime: now,
|
||||||
ctime: now,
|
flags: make(map[UserMode]bool),
|
||||||
flags: make(map[UserMode]bool),
|
phase: server.InitPhase(),
|
||||||
phase: server.InitPhase(),
|
server: server,
|
||||||
server: server,
|
|
||||||
}
|
}
|
||||||
client.socket = NewSocket(conn, client.commands)
|
client.socket = NewSocket(conn, client.commands)
|
||||||
client.loginTimer = time.AfterFunc(LOGIN_TIMEOUT, client.connectionTimeout)
|
client.loginTimer = time.AfterFunc(LOGIN_TIMEOUT, client.connectionTimeout)
|
||||||
@ -57,56 +54,17 @@ func NewClient(server *Server, conn net.Conn) *Client {
|
|||||||
// command goroutine
|
// command goroutine
|
||||||
//
|
//
|
||||||
|
|
||||||
type CheckCommand interface {
|
|
||||||
Response() editableCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckedPassCommand struct {
|
|
||||||
BaseCommand
|
|
||||||
isRight bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckPassCommand struct {
|
|
||||||
hash []byte
|
|
||||||
password []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *CheckPassCommand) CheckPassword() bool {
|
|
||||||
return bcrypt.CompareHashAndPassword(cmd.hash, cmd.password) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *CheckPassCommand) Response() editableCommand {
|
|
||||||
return &CheckedPassCommand{
|
|
||||||
isRight: cmd.CheckPassword(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckOperCommand struct {
|
|
||||||
CheckPassCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckedOperCommand struct {
|
|
||||||
CheckedPassCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *CheckOperCommand) Response() editableCommand {
|
|
||||||
response := &CheckedOperCommand{}
|
|
||||||
response.isRight = cmd.CheckPassword()
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) run() {
|
func (client *Client) run() {
|
||||||
for command := range client.commands {
|
for command := range client.commands {
|
||||||
command.SetClient(client)
|
command.SetClient(client)
|
||||||
client.server.commands <- command
|
|
||||||
|
|
||||||
switch command.(type) {
|
checkPass, ok := command.(checkPasswordCommand)
|
||||||
case *PassCommand, *OperCommand:
|
if ok {
|
||||||
cmd := <-client.checkPass
|
checkPass.LoadPassword(client.server)
|
||||||
response := cmd.Response()
|
checkPass.CheckPassword()
|
||||||
response.SetClient(client)
|
|
||||||
client.server.commands <- response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.server.commands <- command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +117,6 @@ func (client *Client) Idle() {
|
|||||||
func (client *Client) Register() {
|
func (client *Client) Register() {
|
||||||
client.phase = Normal
|
client.phase = Normal
|
||||||
client.loginTimer.Stop()
|
client.loginTimer.Stop()
|
||||||
client.loginTimer = nil
|
|
||||||
client.Touch()
|
client.Touch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"code.google.com/p/go.crypto/bcrypt"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -14,6 +15,11 @@ type editableCommand interface {
|
|||||||
SetClient(*Client)
|
SetClient(*Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type checkPasswordCommand interface {
|
||||||
|
LoadPassword(*Server)
|
||||||
|
CheckPassword()
|
||||||
|
}
|
||||||
|
|
||||||
type parseCommandFunc func([]string) (editableCommand, error)
|
type parseCommandFunc func([]string) (editableCommand, error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -188,19 +194,34 @@ func NewPongCommand(args []string) (editableCommand, error) {
|
|||||||
|
|
||||||
type PassCommand struct {
|
type PassCommand struct {
|
||||||
BaseCommand
|
BaseCommand
|
||||||
password string
|
hash []byte
|
||||||
|
password []byte
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrCannotCheck = errors.New("cannot check password")
|
||||||
|
|
||||||
func (cmd *PassCommand) String() string {
|
func (cmd *PassCommand) String() string {
|
||||||
return fmt.Sprintf("PASS(password=%s)", cmd.password)
|
return fmt.Sprintf("PASS(password=%s)", cmd.password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cmd *PassCommand) LoadPassword(server *Server) {
|
||||||
|
cmd.hash = server.password
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *PassCommand) CheckPassword() {
|
||||||
|
if cmd.hash == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd.err = bcrypt.CompareHashAndPassword(cmd.hash, cmd.password)
|
||||||
|
}
|
||||||
|
|
||||||
func NewPassCommand(args []string) (editableCommand, error) {
|
func NewPassCommand(args []string) (editableCommand, error) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return nil, NotEnoughArgsError
|
return nil, NotEnoughArgsError
|
||||||
}
|
}
|
||||||
return &PassCommand{
|
return &PassCommand{
|
||||||
password: args[0],
|
password: []byte(args[0]),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -652,25 +673,29 @@ func (msg *WhoCommand) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OperCommand struct {
|
type OperCommand struct {
|
||||||
BaseCommand
|
PassCommand
|
||||||
name string
|
name string
|
||||||
password string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *OperCommand) String() string {
|
func (msg *OperCommand) String() string {
|
||||||
return fmt.Sprintf("OPER(name=%s, password=%s)", msg.name, msg.password)
|
return fmt.Sprintf("OPER(name=%s, password=%s)", msg.name, msg.password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (msg *OperCommand) LoadPassword(server *Server) {
|
||||||
|
msg.hash = server.operators[msg.name]
|
||||||
|
}
|
||||||
|
|
||||||
// OPER <name> <password>
|
// OPER <name> <password>
|
||||||
func NewOperCommand(args []string) (editableCommand, error) {
|
func NewOperCommand(args []string) (editableCommand, error) {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return nil, NotEnoughArgsError
|
return nil, NotEnoughArgsError
|
||||||
}
|
}
|
||||||
|
|
||||||
return &OperCommand{
|
cmd := &OperCommand{
|
||||||
name: args[0],
|
name: args[0],
|
||||||
password: args[1],
|
}
|
||||||
}, nil
|
cmd.password = []byte(args[1])
|
||||||
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -32,6 +32,14 @@ func (conf *Config) PasswordBytes() []byte {
|
|||||||
return decodePassword(conf.Password)
|
return decodePassword(conf.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (conf *Config) OperatorsMap() map[string][]byte {
|
||||||
|
operators := make(map[string][]byte)
|
||||||
|
for _, opConf := range conf.Operators {
|
||||||
|
operators[opConf.Name] = opConf.PasswordBytes()
|
||||||
|
}
|
||||||
|
return operators
|
||||||
|
}
|
||||||
|
|
||||||
type OperatorConfig struct {
|
type OperatorConfig struct {
|
||||||
Name string
|
Name string
|
||||||
Password string
|
Password string
|
||||||
|
@ -34,21 +34,17 @@ func NewServer(config *Config) *Server {
|
|||||||
server := &Server{
|
server := &Server{
|
||||||
channels: make(ChannelNameMap),
|
channels: make(ChannelNameMap),
|
||||||
clients: make(ClientNameMap),
|
clients: make(ClientNameMap),
|
||||||
commands: make(chan Command),
|
commands: make(chan Command, 16),
|
||||||
ctime: time.Now(),
|
ctime: time.Now(),
|
||||||
idle: make(chan *Client),
|
idle: make(chan *Client, 16),
|
||||||
motdFile: config.MOTD,
|
motdFile: config.MOTD,
|
||||||
name: config.Name,
|
name: config.Name,
|
||||||
newConns: make(chan net.Conn),
|
newConns: make(chan net.Conn, 16),
|
||||||
operators: make(map[string][]byte),
|
operators: config.OperatorsMap(),
|
||||||
password: config.PasswordBytes(),
|
password: config.PasswordBytes(),
|
||||||
timeout: make(chan *Client),
|
timeout: make(chan *Client),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opConf := range config.Operators {
|
|
||||||
server.operators[opConf.Name] = opConf.PasswordBytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, listenerConf := range config.Listeners {
|
for _, listenerConf := range config.Listeners {
|
||||||
go server.listen(listenerConf)
|
go server.listen(listenerConf)
|
||||||
}
|
}
|
||||||
@ -266,18 +262,7 @@ func (msg *CapCommand) HandleAuthServer(server *Server) {
|
|||||||
|
|
||||||
func (msg *PassCommand) HandleAuthServer(server *Server) {
|
func (msg *PassCommand) HandleAuthServer(server *Server) {
|
||||||
client := msg.Client()
|
client := msg.Client()
|
||||||
cmd := &CheckPassCommand{
|
if msg.err != nil {
|
||||||
hash: server.password,
|
|
||||||
password: []byte(msg.password),
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
client.checkPass <- cmd
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *CheckedPassCommand) HandleAuthServer(server *Server) {
|
|
||||||
client := msg.Client()
|
|
||||||
if !msg.isRight {
|
|
||||||
client.ErrPasswdMismatch()
|
client.ErrPasswdMismatch()
|
||||||
client.Quit("bad password")
|
client.Quit("bad password")
|
||||||
return
|
return
|
||||||
@ -603,21 +588,7 @@ func (msg *WhoCommand) HandleServer(server *Server) {
|
|||||||
func (msg *OperCommand) HandleServer(server *Server) {
|
func (msg *OperCommand) HandleServer(server *Server) {
|
||||||
client := msg.Client()
|
client := msg.Client()
|
||||||
|
|
||||||
cmd := &CheckOperCommand{}
|
if (msg.hash == nil) || (msg.err != nil) {
|
||||||
cmd.hash = server.operators[msg.name]
|
|
||||||
if cmd.hash == nil {
|
|
||||||
client.ErrPasswdMismatch()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cmd.password = []byte(msg.password)
|
|
||||||
go func() {
|
|
||||||
client.checkPass <- cmd
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *CheckedOperCommand) HandleServer(server *Server) {
|
|
||||||
client := msg.Client()
|
|
||||||
if !msg.isRight {
|
|
||||||
client.ErrPasswdMismatch()
|
client.ErrPasswdMismatch()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user