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
|
* Optional permanent channel's state saving in plain text files
|
||||||
(so you can reload daemon and all channels topics and keys won't
|
(so you can reload daemon and all channels topics and keys won't
|
||||||
disappear)
|
disappear)
|
||||||
|
* Optional ability to authenticate users by nickname↔password
|
||||||
|
|
||||||
Some remarks and recommendations related to it's simplicity:
|
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
|
SUPPORTED IRC COMMANDS
|
||||||
|
|
||||||
* NICK/USER during registration workflow
|
* PASS/NICK/USER during registration workflow
|
||||||
* PING/PONGs
|
* PING/PONGs
|
||||||
* NOTICE/PRIVMSG
|
* NOTICE/PRIVMSG
|
||||||
* MOTD, LUSERS, WHO, WHOIS, QUIT
|
* 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
|
loaded during startup. If omitted, then states will be
|
||||||
lost after daemon termination
|
lost after daemon termination
|
||||||
* -tls_key/-tls_cert: enable TLS and specify key and certificate file
|
* -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
|
* -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
|
LICENCE
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -37,6 +37,7 @@ type Client struct {
|
|||||||
nickname string
|
nickname string
|
||||||
username string
|
username string
|
||||||
realname string
|
realname string
|
||||||
|
password string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientAlivenessState struct {
|
type ClientAlivenessState struct {
|
||||||
@ -49,7 +50,7 @@ func (client Client) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(hostname string, conn net.Conn) *Client {
|
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,
|
// Client processor blockingly reads everything remote client sends,
|
||||||
@ -64,7 +65,6 @@ func (client *Client) Processor(sink chan<- ClientEvent) {
|
|||||||
bufNet = make([]byte, BufSize)
|
bufNet = make([]byte, BufSize)
|
||||||
_, err := client.conn.Read(bufNet)
|
_, err := client.conn.Read(bufNet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(client, "connection lost", err)
|
|
||||||
sink <- ClientEvent{client, EventDel, ""}
|
sink <- ClientEvent{client, EventDel, ""}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
40
daemon.go
40
daemon.go
@ -25,6 +25,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,6 +39,8 @@ var (
|
|||||||
RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,9}$")
|
RENickname = regexp.MustCompile("^[a-zA-Z0-9-]{1,9}$")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var passwordsRefreshLock sync.Mutex
|
||||||
|
|
||||||
type Daemon struct {
|
type Daemon struct {
|
||||||
Verbose bool
|
Verbose bool
|
||||||
hostname string
|
hostname string
|
||||||
@ -49,6 +52,7 @@ type Daemon struct {
|
|||||||
lastAlivenessCheck time.Time
|
lastAlivenessCheck time.Time
|
||||||
logSink chan<- LogEvent
|
logSink chan<- LogEvent
|
||||||
stateSink chan<- StateEvent
|
stateSink chan<- StateEvent
|
||||||
|
passwords map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDaemon(hostname, motd string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Daemon {
|
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.
|
// When client finishes NICK/USER workflow, then MOTD and LUSERS are send to him.
|
||||||
func (daemon *Daemon) ClientRegister(client *Client, command string, cols []string) {
|
func (daemon *Daemon) ClientRegister(client *Client, command string, cols []string) {
|
||||||
switch command {
|
switch command {
|
||||||
|
case "PASS":
|
||||||
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
|
client.ReplyNotEnoughParameters("PASS")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.password = cols[1]
|
||||||
case "NICK":
|
case "NICK":
|
||||||
if len(cols) == 1 || len(cols[1]) < 1 {
|
if len(cols) == 1 || len(cols[1]) < 1 {
|
||||||
client.ReplyParts("431", "No nickname given")
|
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], ":")
|
client.realname = strings.TrimLeft(args[3], ":")
|
||||||
}
|
}
|
||||||
if client.nickname != "*" && client.username != "" {
|
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.registered = true
|
||||||
client.ReplyNicknamed("001", "Hi, welcome to IRC")
|
client.ReplyNicknamed("001", "Hi, welcome to IRC")
|
||||||
client.ReplyNicknamed("002", "Your host is "+daemon.hostname+", running goircd")
|
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")
|
client.ReplyNicknamed("004", daemon.hostname+" goircd o o")
|
||||||
daemon.SendLusers(client)
|
daemon.SendLusers(client)
|
||||||
daemon.SendMotd(client)
|
daemon.SendMotd(client)
|
||||||
|
log.Println(client, "logged in")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +266,7 @@ func (daemon *Daemon) HandlerJoin(client *Client, cmd string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
roomNew, roomSink := daemon.RoomRegister(room)
|
roomNew, roomSink := daemon.RoomRegister(room)
|
||||||
|
log.Println("Room", roomNew, "created")
|
||||||
if key != "" {
|
if key != "" {
|
||||||
roomNew.key = key
|
roomNew.key = key
|
||||||
roomNew.StateSave()
|
roomNew.StateSave()
|
||||||
@ -302,6 +322,7 @@ func (daemon *Daemon) Processor(events <-chan ClientEvent) {
|
|||||||
log.Println(client, "command", command)
|
log.Println(client, "command", command)
|
||||||
}
|
}
|
||||||
if command == "QUIT" {
|
if command == "QUIT" {
|
||||||
|
log.Println(client, "quit")
|
||||||
delete(daemon.clients, client)
|
delete(daemon.clients, client)
|
||||||
delete(daemon.clientAliveness, client)
|
delete(daemon.clientAliveness, client)
|
||||||
client.conn.Close()
|
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"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hostname = flag.String("hostname", "localhost", "Hostname")
|
hostname = flag.String("hostname", "localhost", "Hostname")
|
||||||
bind = flag.String("bind", ":6667", "Address to bind to")
|
bind = flag.String("bind", ":6667", "Address to bind to")
|
||||||
motd = flag.String("motd", "", "Path to MOTD file")
|
motd = flag.String("motd", "", "Path to MOTD file")
|
||||||
logdir = flag.String("logdir", "", "Absolute path to directory for logs")
|
logdir = flag.String("logdir", "", "Absolute path to directory for logs")
|
||||||
statedir = flag.String("statedir", "", "Absolute path to directory for states")
|
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")
|
tlsKey = flag.String("tls_key", "", "TLS keyfile")
|
||||||
tlsCert = flag.String("tls_cert", "", "TLS certificate")
|
tlsCert = flag.String("tls_cert", "", "TLS certificate")
|
||||||
@ -118,6 +122,18 @@ func Run() {
|
|||||||
}
|
}
|
||||||
log.Println("Listening on", *bind)
|
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)
|
go daemon.Processor(events)
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
|
4
room.go
4
room.go
@ -46,6 +46,10 @@ type Room struct {
|
|||||||
stateSink chan<- StateEvent
|
stateSink chan<- StateEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r Room) String() string {
|
||||||
|
return r.name
|
||||||
|
}
|
||||||
|
|
||||||
func NewRoom(hostname, name string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Room {
|
func NewRoom(hostname, name string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Room {
|
||||||
room := Room{name: name}
|
room := Room{name: name}
|
||||||
room.members = make(map[*Client]bool)
|
room.members = make(map[*Client]bool)
|
||||||
|
Loading…
Reference in New Issue
Block a user