Ability to authenticate users by nickname↔password database

This commit is contained in:
Sergey Matveev 2014-08-14 14:01:54 +04:00
parent 5c79c4235e
commit a939b027fd
5 changed files with 83 additions and 8 deletions

17
README
View File

@ -28,6 +28,7 @@ But it has some convincing features:
* Optional permanent channel's state saving in plain text files
(so you can reload daemon and all channels topics and keys won't
disappear)
* Optional ability to authenticate users by nickname↔password
Some remarks and recommendations related to it's simplicity:
@ -36,7 +37,7 @@ Some remarks and recommendations related to it's simplicity:
SUPPORTED IRC COMMANDS
* NICK/USER during registration workflow
* PASS/NICK/USER during registration workflow
* PING/PONGs
* NOTICE/PRIVMSG
* MOTD, LUSERS, WHO, WHOIS, QUIT
@ -56,8 +57,22 @@ Just execute goircd daemon. It has following optional arguments:
loaded during startup. If omitted, then states will be
lost after daemon termination
* -tls_key/-tls_cert: enable TLS and specify key and certificate file
* -passwords: enable client authentication and specify path to
passwords file
* -verbose: increase log messages verbosity
AUTHENTICATION
You can turn on optional client authentication by preparing passwords
file and using the -passwords argument. Format of passwords file is:
login1:password1\n
login2:password2\n
You can force rereading of passwords file without server interruption by
sending HUP signal to him.
LICENCE
This program is free software: you can redistribute it and/or modify

View File

@ -37,6 +37,7 @@ type Client struct {
nickname string
username string
realname string
password string
}
type ClientAlivenessState struct {
@ -49,7 +50,7 @@ func (client Client) String() string {
}
func NewClient(hostname string, conn net.Conn) *Client {
return &Client{hostname: hostname, conn: conn, nickname: "*"}
return &Client{hostname: hostname, conn: conn, nickname: "*", password: ""}
}
// Client processor blockingly reads everything remote client sends,
@ -64,7 +65,6 @@ func (client *Client) Processor(sink chan<- ClientEvent) {
bufNet = make([]byte, BufSize)
_, err := client.conn.Read(bufNet)
if err != nil {
log.Println(client, "connection lost", err)
sink <- ClientEvent{client, EventDel, ""}
break
}

View File

@ -25,6 +25,7 @@ import (
"regexp"
"sort"
"strings"
"sync"
"time"
)
@ -38,6 +39,8 @@ var (
RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,9}$")
)
var passwordsRefreshLock sync.Mutex
type Daemon struct {
Verbose bool
hostname string
@ -49,6 +52,7 @@ type Daemon struct {
lastAlivenessCheck time.Time
logSink chan<- LogEvent
stateSink chan<- StateEvent
passwords map[string]string
}
func NewDaemon(hostname, motd string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Daemon {
@ -154,6 +158,12 @@ func (daemon *Daemon) SendList(client *Client, cols []string) {
// When client finishes NICK/USER workflow, then MOTD and LUSERS are send to him.
func (daemon *Daemon) ClientRegister(client *Client, command string, cols []string) {
switch command {
case "PASS":
if len(cols) == 1 || len(cols[1]) < 1 {
client.ReplyNotEnoughParameters("PASS")
return
}
client.password = cols[1]
case "NICK":
if len(cols) == 1 || len(cols[1]) < 1 {
client.ReplyParts("431", "No nickname given")
@ -185,6 +195,14 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri
client.realname = strings.TrimLeft(args[3], ":")
}
if client.nickname != "*" && client.username != "" {
passwordsRefreshLock.Lock()
if daemon.passwords != nil && (client.password == "" || daemon.passwords[client.nickname] != client.password) {
passwordsRefreshLock.Unlock()
client.ReplyParts("462", "You may not register")
client.conn.Close()
return
}
passwordsRefreshLock.Unlock()
client.registered = true
client.ReplyNicknamed("001", "Hi, welcome to IRC")
client.ReplyNicknamed("002", "Your host is "+daemon.hostname+", running goircd")
@ -192,6 +210,7 @@ func (daemon *Daemon) ClientRegister(client *Client, command string, cols []stri
client.ReplyNicknamed("004", daemon.hostname+" goircd o o")
daemon.SendLusers(client)
daemon.SendMotd(client)
log.Println(client, "logged in")
}
}
@ -247,6 +266,7 @@ func (daemon *Daemon) HandlerJoin(client *Client, cmd string) {
continue
}
roomNew, roomSink := daemon.RoomRegister(room)
log.Println("Room", roomNew, "created")
if key != "" {
roomNew.key = key
roomNew.StateSave()
@ -302,6 +322,7 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
log.Println(client, "command", command)
}
if command == "QUIT" {
log.Println(client, "quit")
delete(daemon.clients, client)
delete(daemon.clientAliveness, client)
client.conn.Close()
@ -447,3 +468,22 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
}
}
}
func (daemon *Daemon) PasswordsRefresh() {
contents, err := ioutil.ReadFile(*passwords)
if err != nil {
log.Fatalf("Can no read passwords file %s: %s", *passwords, err)
return
}
processed := make(map[string]string)
for _, entry := range strings.Split(string(contents), "\n") {
loginAndPassword := strings.Split(entry, ":")
if len(loginAndPassword) == 2 {
processed[loginAndPassword[0]] = loginAndPassword[1]
}
}
log.Printf("Read %d passwords", len(processed))
passwordsRefreshLock.Lock()
daemon.passwords = processed
passwordsRefreshLock.Unlock()
}

View File

@ -23,17 +23,21 @@ import (
"io/ioutil"
"log"
"net"
"os"
"os/signal"
"path"
"path/filepath"
"strings"
"syscall"
)
var (
hostname = flag.String("hostname", "localhost", "Hostname")
bind = flag.String("bind", ":6667", "Address to bind to")
motd = flag.String("motd", "", "Path to MOTD file")
logdir = flag.String("logdir", "", "Absolute path to directory for logs")
statedir = flag.String("statedir", "", "Absolute path to directory for states")
hostname = flag.String("hostname", "localhost", "Hostname")
bind = flag.String("bind", ":6667", "Address to bind to")
motd = flag.String("motd", "", "Path to MOTD file")
logdir = flag.String("logdir", "", "Absolute path to directory for logs")
statedir = flag.String("statedir", "", "Absolute path to directory for states")
passwords = flag.String("passwords", "", "Optional path to passwords file")
tlsKey = flag.String("tls_key", "", "TLS keyfile")
tlsCert = flag.String("tls_cert", "", "TLS certificate")
@ -118,6 +122,18 @@ func Run() {
}
log.Println("Listening on", *bind)
if *passwords != "" {
daemon.PasswordsRefresh()
hups := make(chan os.Signal)
signal.Notify(hups, syscall.SIGHUP)
go func() {
for {
<-hups
daemon.PasswordsRefresh()
}
}()
}
go daemon.Processor(events)
for {
conn, err := listener.Accept()

View File

@ -46,6 +46,10 @@ type Room struct {
stateSink chan<- StateEvent
}
func (r Room) String() string {
return r.name
}
func NewRoom(hostname, name string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Room {
room := Room{name: name}
room.members = make(map[*Client]bool)