Ability to authenticate users by nickname↔password database
This commit is contained in:
parent
5c79c4235e
commit
a939b027fd
17
README
17
README
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
40
daemon.go
40
daemon.go
@ -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()
|
||||
}
|
||||
|
26
goircd.go
26
goircd.go
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user