mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +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.
|
||||
|
||||
## 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?
|
||||
|
||||
I wanted to learn Go.
|
||||
@ -20,16 +31,14 @@ I wanted to learn Go.
|
||||
|
||||
## 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
|
||||
|
||||
```sh
|
||||
go get
|
||||
go install
|
||||
ergonomadic
|
||||
```
|
||||
|
||||
### from local
|
||||
```sh
|
||||
go run ergonomadic.go
|
||||
ergonomadic -conf '/path/to/ergonomadic.json'
|
||||
```
|
||||
|
@ -1,7 +1,6 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.crypto/bcrypt"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@ -16,7 +15,6 @@ type Client struct {
|
||||
atime time.Time
|
||||
awayMessage string
|
||||
channels ChannelSet
|
||||
checkPass chan CheckCommand
|
||||
commands chan editableCommand
|
||||
ctime time.Time
|
||||
flags map[UserMode]bool
|
||||
@ -37,14 +35,13 @@ type Client struct {
|
||||
func NewClient(server *Server, conn net.Conn) *Client {
|
||||
now := time.Now()
|
||||
client := &Client{
|
||||
atime: now,
|
||||
channels: make(ChannelSet),
|
||||
checkPass: make(chan CheckCommand),
|
||||
commands: make(chan editableCommand),
|
||||
ctime: now,
|
||||
flags: make(map[UserMode]bool),
|
||||
phase: server.InitPhase(),
|
||||
server: server,
|
||||
atime: now,
|
||||
channels: make(ChannelSet),
|
||||
commands: make(chan editableCommand),
|
||||
ctime: now,
|
||||
flags: make(map[UserMode]bool),
|
||||
phase: server.InitPhase(),
|
||||
server: server,
|
||||
}
|
||||
client.socket = NewSocket(conn, client.commands)
|
||||
client.loginTimer = time.AfterFunc(LOGIN_TIMEOUT, client.connectionTimeout)
|
||||
@ -57,56 +54,17 @@ func NewClient(server *Server, conn net.Conn) *Client {
|
||||
// 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() {
|
||||
for command := range client.commands {
|
||||
command.SetClient(client)
|
||||
client.server.commands <- command
|
||||
|
||||
switch command.(type) {
|
||||
case *PassCommand, *OperCommand:
|
||||
cmd := <-client.checkPass
|
||||
response := cmd.Response()
|
||||
response.SetClient(client)
|
||||
client.server.commands <- response
|
||||
checkPass, ok := command.(checkPasswordCommand)
|
||||
if ok {
|
||||
checkPass.LoadPassword(client.server)
|
||||
checkPass.CheckPassword()
|
||||
}
|
||||
|
||||
client.server.commands <- command
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +117,6 @@ func (client *Client) Idle() {
|
||||
func (client *Client) Register() {
|
||||
client.phase = Normal
|
||||
client.loginTimer.Stop()
|
||||
client.loginTimer = nil
|
||||
client.Touch()
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.crypto/bcrypt"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
@ -14,6 +15,11 @@ type editableCommand interface {
|
||||
SetClient(*Client)
|
||||
}
|
||||
|
||||
type checkPasswordCommand interface {
|
||||
LoadPassword(*Server)
|
||||
CheckPassword()
|
||||
}
|
||||
|
||||
type parseCommandFunc func([]string) (editableCommand, error)
|
||||
|
||||
var (
|
||||
@ -188,19 +194,34 @@ func NewPongCommand(args []string) (editableCommand, error) {
|
||||
|
||||
type PassCommand struct {
|
||||
BaseCommand
|
||||
password string
|
||||
hash []byte
|
||||
password []byte
|
||||
err error
|
||||
}
|
||||
|
||||
var ErrCannotCheck = errors.New("cannot check password")
|
||||
|
||||
func (cmd *PassCommand) String() string {
|
||||
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) {
|
||||
if len(args) < 1 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
return &PassCommand{
|
||||
password: args[0],
|
||||
password: []byte(args[0]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -652,25 +673,29 @@ func (msg *WhoCommand) String() string {
|
||||
}
|
||||
|
||||
type OperCommand struct {
|
||||
BaseCommand
|
||||
name string
|
||||
password string
|
||||
PassCommand
|
||||
name string
|
||||
}
|
||||
|
||||
func (msg *OperCommand) String() string {
|
||||
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>
|
||||
func NewOperCommand(args []string) (editableCommand, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
|
||||
return &OperCommand{
|
||||
name: args[0],
|
||||
password: args[1],
|
||||
}, nil
|
||||
cmd := &OperCommand{
|
||||
name: args[0],
|
||||
}
|
||||
cmd.password = []byte(args[1])
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
@ -32,6 +32,14 @@ func (conf *Config) PasswordBytes() []byte {
|
||||
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 {
|
||||
Name string
|
||||
Password string
|
||||
|
@ -34,21 +34,17 @@ func NewServer(config *Config) *Server {
|
||||
server := &Server{
|
||||
channels: make(ChannelNameMap),
|
||||
clients: make(ClientNameMap),
|
||||
commands: make(chan Command),
|
||||
commands: make(chan Command, 16),
|
||||
ctime: time.Now(),
|
||||
idle: make(chan *Client),
|
||||
idle: make(chan *Client, 16),
|
||||
motdFile: config.MOTD,
|
||||
name: config.Name,
|
||||
newConns: make(chan net.Conn),
|
||||
operators: make(map[string][]byte),
|
||||
newConns: make(chan net.Conn, 16),
|
||||
operators: config.OperatorsMap(),
|
||||
password: config.PasswordBytes(),
|
||||
timeout: make(chan *Client),
|
||||
}
|
||||
|
||||
for _, opConf := range config.Operators {
|
||||
server.operators[opConf.Name] = opConf.PasswordBytes()
|
||||
}
|
||||
|
||||
for _, listenerConf := range config.Listeners {
|
||||
go server.listen(listenerConf)
|
||||
}
|
||||
@ -266,18 +262,7 @@ func (msg *CapCommand) HandleAuthServer(server *Server) {
|
||||
|
||||
func (msg *PassCommand) HandleAuthServer(server *Server) {
|
||||
client := msg.Client()
|
||||
cmd := &CheckPassCommand{
|
||||
hash: server.password,
|
||||
password: []byte(msg.password),
|
||||
}
|
||||
go func() {
|
||||
client.checkPass <- cmd
|
||||
}()
|
||||
}
|
||||
|
||||
func (msg *CheckedPassCommand) HandleAuthServer(server *Server) {
|
||||
client := msg.Client()
|
||||
if !msg.isRight {
|
||||
if msg.err != nil {
|
||||
client.ErrPasswdMismatch()
|
||||
client.Quit("bad password")
|
||||
return
|
||||
@ -603,21 +588,7 @@ func (msg *WhoCommand) HandleServer(server *Server) {
|
||||
func (msg *OperCommand) HandleServer(server *Server) {
|
||||
client := msg.Client()
|
||||
|
||||
cmd := &CheckOperCommand{}
|
||||
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 {
|
||||
if (msg.hash == nil) || (msg.err != nil) {
|
||||
client.ErrPasswdMismatch()
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user