15 Commits

Author SHA1 Message Date
James Mills
d3285748f9 Remove silly build token in version and add goreleaser config 2017-12-28 00:53:46 -08:00
Kevin Zita
283ef104a4 Aded support for HostMask user mode (+x) (#47) 2017-12-28 00:36:13 -08:00
James Mills
04d907d1e9 Fixed typo in README 2017-12-14 00:13:29 -08:00
James Mills
d74a6780fe Fixed writeloop goroutine leadk (#45) 2017-12-14 00:02:07 -08:00
James Mills
d7e9ef230a Refactored e2e integration tests 2017-12-06 20:59:04 -08:00
James Mills
75f224a7c0 Improves coverage of integreation tests to 40% (#42)
* Added TestChannel_PRIVMSG test

* Refactored integration testing framework for better timtouts and added TestChannel_InviteOnly test

* Try to fix TestChannel_PRIVMSG test

* Fuck it

* Added TestChannel_NoExternal test

* Added TestChannel_BadChannelKey and TestChannel_GoodChannelKey tests

* Bah humbut

* Update vendored 3rd-party deps

* Removed use of deadlock sync package

* Fix all tests :D yay

* Added some topic tests

* Add TestSASL
2017-12-06 02:19:27 -08:00
James Mills
caab002d51 Relase v1.6.4 2017-12-02 18:58:00 -08:00
James Mills
7ff892bba9 Added Drone CI test step 2017-12-02 18:56:53 -08:00
James Mills
233238b709 Adds a basic integration test suite framework (#38)
* Added a basic integration test suite framework

* Add TestConnect_RplWelcome test

* Update vendor 3rd-party deps for missing github.com/stretchr/testify/assert

* Add TestUser_PRIVMSG test

* Rename some tests

* Moar tests

* New test framework with better reliability
2017-11-28 02:15:30 -08:00
James Mills
59e0792db1 Fixed bad label value when bumping metrics for commands processed (#40) 2017-11-27 23:57:42 -08:00
James Mills
962b6645c1 Release v1.6.3 2017-11-27 19:18:58 -08:00
James Mills
cee8bf9957 Added support for multi-layer channel privacy (Public, Private and Secret) (#36) 2017-11-27 19:16:17 -08:00
James Mills
9d93bca179 Added support for measuring secure vs. non-secure registerd clients (#34) 2017-11-26 17:31:11 -08:00
James Mills
ccae795335 Fixed graceful shutdown (#32) 2017-11-26 17:30:53 -08:00
James Mills
862eb429d4 Update README.md 2017-11-26 15:26:12 -08:00
32 changed files with 791 additions and 81 deletions

View File

@@ -6,9 +6,15 @@ pipeline:
build: build:
image: golang image: golang
commands: commands:
- go get -d - go get -d ./...
- go build . - go build .
test:
image: golang
commands:
- go get -d ./...
- go test ./...
docker: docker:
image: plugins/docker image: plugins/docker
repo: r.mills.io/prologic/eris repo: r.mills.io/prologic/eris

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*~* *~*
bin bin
dist
*.db *.db
*.pem *.pem

18
.gitmodules vendored
View File

@@ -43,9 +43,15 @@
[submodule "vendor/github.com/prometheus/procfs"] [submodule "vendor/github.com/prometheus/procfs"]
path = vendor/github.com/prometheus/procfs path = vendor/github.com/prometheus/procfs
url = https://github.com/prometheus/procfs url = https://github.com/prometheus/procfs
[submodule "vendor/github.com/sasha-s/go-deadlock"] [submodule "vendor/github.com/thoj/go-ircevent"]
path = vendor/github.com/sasha-s/go-deadlock path = vendor/github.com/thoj/go-ircevent
url = https://github.com/sasha-s/go-deadlock url = https://github.com/thoj/go-ircevent
[submodule "vendor/github.com/petermattis/goid"] [submodule "vendor/github.com/stretchr/testify"]
path = vendor/github.com/petermattis/goid path = vendor/github.com/stretchr/testify
url = https://github.com/petermattis/goid url = https://github.com/stretchr/testify
[submodule "vendor/github.com/renstrom/shortuuid"]
path = vendor/github.com/renstrom/shortuuid
url = https://github.com/renstrom/shortuuid
[submodule "vendor/github.com/satori/go.uuid"]
path = vendor/github.com/satori/go.uuid
url = https://github.com/satori/go.uuid

31
.goreleaser.yml Normal file
View File

@@ -0,0 +1,31 @@
builds:
- binary: eris
flags: -tags "static_build"
ldflags: -w -X mail.Version={{.Version}} -X main.Commit={{.Commit}}
env:
- CGO_ENABLED=0
goos:
- darwin
- freebsd
- linux
- windows
goarch:
- i386
- amd64
- arm
- amd64
goarm:
- 6
- 7
sign:
artifacts: checksum
archive:
wrap_in_directory: true
format_overrides:
- goos: windows
format: zip
files:
- "*.pem"
- "*.yml"
- "LICENSE"
- "README.md"

View File

@@ -2,7 +2,6 @@
FROM golang:alpine AS build FROM golang:alpine AS build
ARG TAG ARG TAG
ARG BUILD
ENV APP eris ENV APP eris
ENV REPO prologic/$APP ENV REPO prologic/$APP
@@ -12,7 +11,7 @@ RUN apk add --update git make build-base && \
WORKDIR /go/src/github.com/$REPO WORKDIR /go/src/github.com/$REPO
COPY . /go/src/github.com/$REPO COPY . /go/src/github.com/$REPO
RUN make TAG=$TAG BUILD=$BUILD build RUN make TAG=$TAG build
# Runtime # Runtime
FROM alpine FROM alpine

View File

@@ -6,7 +6,6 @@ APP=eris
PACKAGE=irc PACKAGE=irc
REPO?=prologic/$(APP) REPO?=prologic/$(APP)
TAG?=latest TAG?=latest
BUILD?=dev
all: dev all: dev
@@ -17,13 +16,13 @@ deps:
@go get ./... @go get ./...
build: clean deps build: clean deps
@echo " -> Building $(TAG)$(BUILD)" @echo " -> Building $(REPO) v$(TAG)-@$(COMMIT)"
@go build -tags "netgo static_build" -installsuffix netgo \ @go build -tags "netgo static_build" -installsuffix netgo \
-ldflags "-w -X github.com/$(REPO)/${PACKAGE}.GitCommit=$(COMMIT) -X github.com/$(REPO)/${PACKAGE}.Build=$(BUILD)" . -ldflags "-w -X github.com/$(REPO)/${PACKAGE}.GitCommit=$(COMMIT)
@echo "Built $$(./$(APP) -v)" @echo "Built $$(./$(APP) -v)"
image: image:
@docker build --build-arg TAG=$(TAG) --build-arg BUILD=$(BUILD) -t $(REPO):$(TAG) . @docker build --build-arg TAG=$(TAG) -t $(REPO):$(TAG) .
@echo "Image created: $(REPO):$(TAG)" @echo "Image created: $(REPO):$(TAG)"
test: test:

View File

@@ -1,4 +1,4 @@
# eris - IRC Server / Daemon written in Go :set spell eris - IRC Server / Daemon written in Go
[![Build Status](https://travis-ci.org/prologic/eris.svg)](https://travis-ci.org/prologic/eris) [![Build Status](https://travis-ci.org/prologic/eris.svg)](https://travis-ci.org/prologic/eris)
[![Go Report Card](https://goreportcard.com/badge/github.com/prologic/eris)](https://goreportcard.com/report/github.com/prologic/eris) [![Go Report Card](https://goreportcard.com/badge/github.com/prologic/eris)](https://goreportcard.com/report/github.com/prologic/eris)
@@ -25,7 +25,7 @@ The connotation here is that IRC (*Internet Relay Chat*) is a place of chaos,
strife and discord. IRC is a place where you argue and get into arguments for strife and discord. IRC is a place where you argue and get into arguments for
the sake of argument. the sake of argument.
So `eris` is an IRC daemon written from scratch in Go to factiliate discord So `eris` is an IRC daemon written from scratch in Go to facilitate discord
and have arguments for the sake of argument! and have arguments for the sake of argument!
Pull requests and issues are welcome. Pull requests and issues are welcome.
@@ -35,9 +35,9 @@ Discussion at:
* /server irc.mills.io +6697 (*use TLS/SSL*) * /server irc.mills.io +6697 (*use TLS/SSL*)
* /join #lobby * /join #lobby
Or (*not recommended*)P Or (**not recommended**):
* /server irc.mills.io (*default port 6667, non-TLS) * /server irc.mills.io (*default port 6667, non-TLS*)
* /join #lobby * /join #lobby
## Features ## Features
@@ -54,6 +54,7 @@ Or (*not recommended*)P
* Simple IRC operator privileges (*overrides most things*) * Simple IRC operator privileges (*overrides most things*)
* Secure connection tracking (+z) and SecureOnly user mode (+Z) * Secure connection tracking (+z) and SecureOnly user mode (+Z)
* Secure channels (+Z) * Secure channels (+Z)
* Three layers of channel privacy, Public, Private (+p) and Secret (s)
## Quick Start ## Quick Start
@@ -144,6 +145,10 @@ There are a number of supported accompanying services that are being developed a
* [Soter](https://github.com/prologic/soter) -- An IRC Bot that persists channel modes and topics. * [Soter](https://github.com/prologic/soter) -- An IRC Bot that persists channel modes and topics.
* [Cadmus](https://github.com/prologic/cadmus) -- An IRC Bot that logs channels and provides an interface for viewing and searching logs * [Cadmus](https://github.com/prologic/cadmus) -- An IRC Bot that logs channels and provides an interface for viewing and searching logs
## Recommended Mobile clients
* [Palaver (iOS)](https://palaverapp.com/) -- SASL, TLS, Server Password, Push Notifications, IRCv3 (*Also supports custom image upload service(s) for better privacy of shared photos/images over IRC*)
## License ## License
eris is licensed under the MIT License. eris is licensed under the MIT License.

View File

@@ -374,7 +374,7 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
return channel.applyModeMask(client, change.mode, change.op, return channel.applyModeMask(client, change.mode, change.op,
NewName(change.arg)) NewName(change.arg))
case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Private, SecureChan: case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Private, Secret, SecureChan:
return channel.applyModeFlag(client, change.mode, change.op) return channel.applyModeFlag(client, change.mode, change.op)
case Key: case Key:

View File

@@ -72,8 +72,17 @@ func NewClient(server *Server, conn net.Conn) *Client {
// //
func (client *Client) writeloop() { func (client *Client) writeloop() {
for reply := range client.replies { for {
client.socket.Write(reply) select {
case reply, ok := <-client.replies:
if !ok || reply == "" || client.socket == nil {
return
}
client.socket.Write(reply)
}
if client.replies == nil {
break
}
} }
} }
@@ -119,13 +128,6 @@ func (client *Client) readloop() {
} }
func (client *Client) processCommand(cmd Command) { func (client *Client) processCommand(cmd Command) {
client.server.metrics.Counter("client", "commands").Inc()
defer func(t time.Time) {
v := client.server.metrics.SummaryVec("client", "command_duration_seconds")
v.WithLabelValues(cmd.Code().String()).Observe(time.Now().Sub(t).Seconds())
}(time.Now())
cmd.SetClient(client) cmd.SetClient(client)
if !client.registered { if !client.registered {
@@ -144,6 +146,13 @@ func (client *Client) processCommand(cmd Command) {
return return
} }
client.server.metrics.Counter("client", "commands").Inc()
defer func(t time.Time) {
v := client.server.metrics.SummaryVec("client", "command_duration_seconds")
v.WithLabelValues(cmd.Code().String()).Observe(time.Now().Sub(t).Seconds())
}(time.Now())
switch srvCmd.(type) { switch srvCmd.(type) {
case *PingCommand, *PongCommand: case *PingCommand, *PongCommand:
client.Touch() client.Touch()
@@ -209,6 +218,7 @@ func (client *Client) Register() {
return return
} }
client.registered = true client.registered = true
client.flags[HostMask] = true
client.Touch() client.Touch()
} }
@@ -222,6 +232,12 @@ func (client *Client) destroy() {
// clean up server // clean up server
if _, ok := client.socket.conn.(*tls.Conn); ok {
client.server.metrics.GaugeVec("server", "clients").WithLabelValues("secure").Dec()
} else {
client.server.metrics.GaugeVec("server", "clients").WithLabelValues("insecure").Dec()
}
client.server.connections.Dec() client.server.connections.Dec()
client.server.clients.Remove(client) client.server.clients.Remove(client)

View File

@@ -4,9 +4,7 @@ import (
"errors" "errors"
"regexp" "regexp"
"strings" "strings"
//"sync" "sync"
sync "github.com/sasha-s/go-deadlock"
"github.com/DanielOaks/girc-go/ircmatch" "github.com/DanielOaks/girc-go/ircmatch"
) )

View File

@@ -4,9 +4,7 @@ import (
"errors" "errors"
"io/ioutil" "io/ioutil"
"log" "log"
//"sync" "sync"
sync "github.com/sasha-s/go-deadlock"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"

View File

@@ -20,6 +20,7 @@ var DefObjectives = map[float64]float64{
type Metrics struct { type Metrics struct {
namespace string namespace string
metrics map[string]prometheus.Metric metrics map[string]prometheus.Metric
gaugevecs map[string]*prometheus.GaugeVec
sumvecs map[string]*prometheus.SummaryVec sumvecs map[string]*prometheus.SummaryVec
} }
@@ -27,6 +28,7 @@ func NewMetrics(namespace string) *Metrics {
return &Metrics{ return &Metrics{
namespace: namespace, namespace: namespace,
metrics: make(map[string]prometheus.Metric), metrics: make(map[string]prometheus.Metric),
gaugevecs: make(map[string]*prometheus.GaugeVec),
sumvecs: make(map[string]*prometheus.SummaryVec), sumvecs: make(map[string]*prometheus.SummaryVec),
} }
} }
@@ -101,6 +103,24 @@ func (m *Metrics) NewGaugeFunc(subsystem, name, help string, f func() float64) p
return guage return guage
} }
func (m *Metrics) NewGaugeVec(subsystem, name, help string, labels []string) *prometheus.GaugeVec {
gauagevec := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: m.namespace,
Subsystem: subsystem,
Name: name,
Help: help,
},
labels,
)
key := fmt.Sprintf("%s_%s", subsystem, name)
m.gaugevecs[key] = gauagevec
prometheus.MustRegister(gauagevec)
return gauagevec
}
func (m *Metrics) NewSummary(subsystem, name, help string) prometheus.Summary { func (m *Metrics) NewSummary(subsystem, name, help string) prometheus.Summary {
summary := prometheus.NewSummary( summary := prometheus.NewSummary(
prometheus.SummaryOpts{ prometheus.SummaryOpts{
@@ -148,6 +168,11 @@ func (m *Metrics) Gauge(subsystem, name string) prometheus.Gauge {
return m.metrics[key].(prometheus.Gauge) return m.metrics[key].(prometheus.Gauge)
} }
func (m *Metrics) GaugeVec(subsystem, name string) *prometheus.GaugeVec {
key := fmt.Sprintf("%s_%s", subsystem, name)
return m.gaugevecs[key]
}
func (m *Metrics) Summary(subsystem, name string) prometheus.Summary { func (m *Metrics) Summary(subsystem, name string) prometheus.Summary {
key := fmt.Sprintf("%s_%s", subsystem, name) key := fmt.Sprintf("%s_%s", subsystem, name)
return m.metrics[key].(prometheus.Summary) return m.metrics[key].(prometheus.Summary)

View File

@@ -58,11 +58,12 @@ const (
Registered UserMode = 'r' // not a real user mode (flag) Registered UserMode = 'r' // not a real user mode (flag)
SecureConn UserMode = 'z' SecureConn UserMode = 'z'
SecureOnly UserMode = 'Z' SecureOnly UserMode = 'Z'
HostMask UserMode = 'x'
) )
var ( var (
SupportedUserModes = UserModes{ SupportedUserModes = UserModes{
Invisible, Operator, Invisible, Operator, HostMask,
} }
DefaultChannelModes = ChannelModes{ DefaultChannelModes = ChannelModes{
NoOutside, OpOnlyTopic, NoOutside, OpOnlyTopic,
@@ -70,7 +71,6 @@ var (
) )
const ( const (
Anonymous ChannelMode = 'a' // flag
BanMask ChannelMode = 'b' // arg BanMask ChannelMode = 'b' // arg
ChannelCreator ChannelMode = 'O' // flag ChannelCreator ChannelMode = 'O' // flag
ChannelOperator ChannelMode = 'o' // arg ChannelOperator ChannelMode = 'o' // arg
@@ -82,8 +82,6 @@ const (
NoOutside ChannelMode = 'n' // flag NoOutside ChannelMode = 'n' // flag
OpOnlyTopic ChannelMode = 't' // flag OpOnlyTopic ChannelMode = 't' // flag
Private ChannelMode = 'p' // flag Private ChannelMode = 'p' // flag
Quiet ChannelMode = 'q' // flag
ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag, deprecated Secret ChannelMode = 's' // flag, deprecated
UserLimit ChannelMode = 'l' // flag arg UserLimit ChannelMode = 'l' // flag arg
Voice ChannelMode = 'v' // arg Voice ChannelMode = 'v' // arg
@@ -93,7 +91,7 @@ const (
var ( var (
SupportedChannelModes = ChannelModes{ SupportedChannelModes = ChannelModes{
BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside, BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside,
OpOnlyTopic, Private, UserLimit, SecureChan, OpOnlyTopic, Private, UserLimit, Secret, SecureChan,
} }
) )
@@ -119,7 +117,7 @@ func (m *ModeCommand) HandleServer(s *Server) {
for _, change := range m.changes { for _, change := range m.changes {
switch change.mode { switch change.mode {
case Invisible, WallOps, SecureOnly: case Invisible, HostMask, WallOps, SecureOnly:
switch change.op { switch change.op {
case Add: case Add:
if target.flags[change.mode] { if target.flags[change.mode] {

View File

@@ -4,9 +4,8 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
//"sync" "sync"
sync "github.com/sasha-s/go-deadlock"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )

22
irc/privacy.go Normal file
View File

@@ -0,0 +1,22 @@
package irc
func CanSeeChannel(client *Client, channel *Channel) bool {
isPrivate := channel.flags.Has(Private)
isSecret := channel.flags.Has(Secret)
isMember := channel.members.Has(client)
isOperator := client.flags[Operator]
isRegistered := client.flags[Registered]
isSecure := client.flags[SecureConn]
if !(isSecret || isPrivate) {
return true
}
if isSecret && (isMember || isOperator) {
return true
}
if isPrivate && (isMember || isOperator || (isRegistered && isSecure)) {
return true
}
return false
}

View File

@@ -266,7 +266,7 @@ func (target *Client) RplWhois(client *Client) {
func (target *Client) RplWhoisUser(client *Client) { func (target *Client) RplWhoisUser(client *Client) {
var clientHost Name var clientHost Name
if target.flags[Operator] { if target.flags[Operator] || !client.flags[HostMask] {
clientHost = client.hostname clientHost = client.hostname
} else { } else {
clientHost = client.hostmask clientHost = client.hostmask
@@ -342,7 +342,7 @@ func (target *Client) RplChannelModeIs(channel *Channel) {
func (target *Client) RplWhoReply(channel *Channel, client *Client) { func (target *Client) RplWhoReply(channel *Channel, client *Client) {
var clientHost Name var clientHost Name
if target.flags[Operator] { if target.flags[Operator] || !client.flags[HostMask] {
clientHost = client.hostname clientHost = client.hostname
} else { } else {
clientHost = client.hostmask clientHost = client.hostmask
@@ -489,8 +489,13 @@ func (target *Client) RplMOTDEnd() {
} }
func (target *Client) RplList(channel *Channel) { func (target *Client) RplList(channel *Channel) {
target.NumericReply(RPL_LIST, target.NumericReply(
"%s %d :%s", channel, channel.members.Count(), channel.topic) RPL_LIST,
"%s %d :%s",
channel,
channel.members.Count(),
channel.topic,
)
} }
func (target *Client) RplListEnd(server *Server) { func (target *Client) RplListEnd(server *Server) {
@@ -504,8 +509,12 @@ func (target *Client) RplNamReply(channel *Channel) {
} }
func (target *Client) RplWhoisChannels(client *Client) { func (target *Client) RplWhoisChannels(client *Client) {
target.MultilineReply(client.WhoisChannelsNames(), RPL_WHOISCHANNELS, target.MultilineReply(
"%s :%s", client.Nick()) client.WhoisChannelsNames(target),
RPL_WHOISCHANNELS,
"%s :%s",
client.Nick(),
)
} }
func (target *Client) RplVersion() { func (target *Client) RplVersion() {

View File

@@ -2,9 +2,7 @@ package irc
import ( import (
"bytes" "bytes"
//"sync" "sync"
sync "github.com/sasha-s/go-deadlock"
) )
type SaslState struct { type SaslState struct {

View File

@@ -44,13 +44,16 @@ type Server struct {
accounts PasswordStore accounts PasswordStore
password []byte password []byte
signals chan os.Signal signals chan os.Signal
done chan bool
whoWas *WhoWasList whoWas *WhoWasList
ids map[string]*Identity ids map[string]*Identity
} }
var ( var (
SERVER_SIGNALS = []os.Signal{syscall.SIGINT, syscall.SIGHUP, SERVER_SIGNALS = []os.Signal{
syscall.SIGTERM, syscall.SIGQUIT} syscall.SIGINT, syscall.SIGHUP,
syscall.SIGTERM, syscall.SIGQUIT,
}
) )
func NewServer(config *Config) *Server { func NewServer(config *Config) *Server {
@@ -70,13 +73,14 @@ func NewServer(config *Config) *Server {
operators: config.Operators(), operators: config.Operators(),
accounts: NewMemoryPasswordStore(config.Accounts(), PasswordStoreOpts{}), accounts: NewMemoryPasswordStore(config.Accounts(), PasswordStoreOpts{}),
signals: make(chan os.Signal, len(SERVER_SIGNALS)), signals: make(chan os.Signal, len(SERVER_SIGNALS)),
done: make(chan bool),
whoWas: NewWhoWasList(100), whoWas: NewWhoWasList(100),
ids: make(map[string]*Identity), ids: make(map[string]*Identity),
} }
log.Debugf("accounts: %v", config.Accounts()) log.Debugf("accounts: %v", config.Accounts())
// TODO: Make this configurabel? // TODO: Make this configureable?
server.ids["global"] = NewIdentity(config.Server.Name, "global") server.ids["global"] = NewIdentity(config.Server.Name, "global")
if config.Server.Password != "" { if config.Server.Password != "" {
@@ -123,15 +127,22 @@ func NewServer(config *Config) *Server {
}, },
) )
// server clients gauge // server registered (clients) gauge
server.metrics.NewGaugeFunc( server.metrics.NewGaugeFunc(
"server", "clients", "server", "registered",
"Number of registered clients connected", "Number of registered clients connected",
func() float64 { func() float64 {
return float64(server.clients.Count()) return float64(server.clients.Count())
}, },
) )
// server clients gauge (by secure/insecure)
server.metrics.NewGaugeVec(
"server", "clients",
"Number of registered clients connected (by secure/insecure)",
[]string{"secure"},
)
// server channels gauge // server channels gauge
server.metrics.NewGaugeFunc( server.metrics.NewGaugeFunc(
"server", "channels", "server", "channels",
@@ -191,13 +202,22 @@ func (server *Server) Shutdown() {
server.Global("shutting down...") server.Global("shutting down...")
} }
func (server *Server) Stop() {
server.done <- true
}
func (server *Server) Run() { func (server *Server) Run() {
done := false for {
for !done {
select { select {
case <-server.done:
return
case <-server.signals: case <-server.signals:
server.Shutdown() server.Shutdown()
done = true // Give at least 1s for clients to see the shutdown
go func() {
time.Sleep(1 * time.Second)
server.Stop()
}()
case conn := <-server.newConns: case conn := <-server.newConns:
go NewClient(server, conn) go NewClient(server, conn)
@@ -217,6 +237,12 @@ func (s *Server) acceptor(listener net.Listener) {
} }
log.Debugf("%s accept: %s", s, conn.RemoteAddr()) log.Debugf("%s accept: %s", s, conn.RemoteAddr())
if _, ok := conn.(*tls.Conn); ok {
s.metrics.GaugeVec("server", "clients").WithLabelValues("secure").Inc()
} else {
s.metrics.GaugeVec("server", "clients").WithLabelValues("insecure").Inc()
}
s.connections.Inc() s.connections.Inc()
s.newConns <- conn s.newConns <- conn
} }
@@ -603,10 +629,14 @@ func (msg *PrivMsgCommand) HandleServer(server *Server) {
} }
} }
func (client *Client) WhoisChannelsNames() []string { func (client *Client) WhoisChannelsNames(target *Client) []string {
chstrs := make([]string, client.channels.Count()) chstrs := make([]string, client.channels.Count())
index := 0 index := 0
client.channels.Range(func(channel *Channel) bool { client.channels.Range(func(channel *Channel) bool {
if !CanSeeChannel(target, channel) {
return true
}
switch { switch {
case channel.members.Get(client).Has(ChannelOperator): case channel.members.Get(client).Has(ChannelOperator):
chstrs[index] = "@" + channel.name.String() chstrs[index] = "@" + channel.name.String()
@@ -813,7 +843,7 @@ func (msg *ListCommand) HandleServer(server *Server) {
if len(msg.channels) == 0 { if len(msg.channels) == 0 {
server.channels.Range(func(name Name, channel *Channel) bool { server.channels.Range(func(name Name, channel *Channel) bool {
if !client.flags[Operator] && channel.flags.Has(Private) { if !CanSeeChannel(client, channel) {
return true return true
} }
client.RplList(channel) client.RplList(channel)
@@ -822,7 +852,7 @@ func (msg *ListCommand) HandleServer(server *Server) {
} else { } else {
for _, chname := range msg.channels { for _, chname := range msg.channels {
channel := server.channels.Get(chname) channel := server.channels.Get(chname)
if channel == nil || (!client.flags[Operator] && channel.flags.Has(Private)) { if channel == nil || !CanSeeChannel(client, channel) {
client.ErrNoSuchChannel(chname) client.ErrNoSuchChannel(chname)
continue continue
} }

View File

@@ -3,9 +3,7 @@ package irc
import ( import (
"fmt" "fmt"
"strings" "strings"
//"sync" "sync"
sync "github.com/sasha-s/go-deadlock"
) )
// //

View File

@@ -5,20 +5,17 @@ import (
) )
var ( var (
//PackageName package name // Package package name
Package = "eris" Package = "eris"
// Version release version // Version release version
Version = "1.6.2" Version = "1.6.4"
// Build will be overwritten automatically by the build system // Commit will be overwritten automatically by the build system
Build = "dev" Commit = "HEAD"
// GitCommit will be overwritten automatically by the build system
GitCommit = "HEAD"
) )
// FullVersion display the full version and build // FullVersion display the full version and build
func FullVersion() string { func FullVersion() string {
return fmt.Sprintf("%s-%s-%s@%s", Package, Version, Build, GitCommit) return fmt.Sprintf("%s-%s@%s", Package, Version, Commit)
} }

View File

@@ -1,9 +1,7 @@
package irc package irc
import ( import (
//"sync" "sync"
sync "github.com/sasha-s/go-deadlock"
) )
type WhoWasList struct { type WhoWasList struct {

575
main_test.go Normal file
View File

@@ -0,0 +1,575 @@
package main
import (
"crypto/sha256"
"flag"
"fmt"
"os"
"strings"
"testing"
"time"
log "github.com/sirupsen/logrus"
"github.com/renstrom/shortuuid"
"github.com/stretchr/testify/assert"
"github.com/thoj/go-ircevent"
eris "github.com/prologic/eris/irc"
)
const (
TIMEOUT = 3 * time.Second
)
var (
server *eris.Server
debug = flag.Bool("d", false, "enable debug logging")
)
func setupServer() *eris.Server {
config := &eris.Config{}
config.Network.Name = "Test"
config.Server.Name = "test"
config.Server.Description = "Test"
config.Server.Listen = []string{":6667"}
// SASL
config.Account = map[string]*eris.PassConfig{
"admin": &eris.PassConfig{"JDJhJDA0JGtUU1JVc1JOUy9DbEh1WEdvYVlMdGVnclp6YnA3NDBOZGY1WUZhdTZtRzVmb1VKdXQ5ckZD"},
}
server := eris.NewServer(config)
go server.Run()
return server
}
func randomValidName() string {
var name eris.Name
for {
name = eris.NewName(shortuuid.New())
if name.IsNickname() {
break
}
}
return name.String()
}
func newClient(start bool) *irc.Connection {
name := randomValidName()
client := irc.IRC(name, name)
client.RealName = fmt.Sprintf("Test Client: %s", name)
err := client.Connect("localhost:6667")
if err != nil {
log.Fatalf("error setting up test client: %s", err)
}
if start {
go client.Loop()
}
return client
}
func TestMain(m *testing.M) {
flag.Parse()
if *debug {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.WarnLevel)
}
server = setupServer()
result := m.Run()
server.Stop()
os.Exit(result)
}
func TestConnection(t *testing.T) {
assert := assert.New(t)
expected := true
actual := make(chan bool)
client := newClient(false)
client.AddCallback("001", func(e *irc.Event) {
actual <- true
})
defer client.Quit()
go client.Loop()
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestSASL(t *testing.T) {
assert := assert.New(t)
expected := true
actual := make(chan bool)
client := newClient(false)
client.SASLLogin = "admin"
client.SASLPassword = "admin"
client.AddCallback("001", func(e *irc.Event) {
actual <- true
})
defer client.Quit()
go client.Loop()
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestRplWelcome(t *testing.T) {
assert := assert.New(t)
expected := "Welcome to the .* Internet Relay Network .*!.*@.*"
actual := make(chan string)
client := newClient(false)
client.AddCallback("001", func(e *irc.Event) {
actual <- e.Message()
})
defer client.Quit()
go client.Loop()
select {
case res := <-actual:
assert.Regexp(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestUser_JOIN(t *testing.T) {
assert := assert.New(t)
client := newClient(false)
expected := []string{client.GetNick(), "=", "#join", fmt.Sprintf("@%s", client.GetNick())}
actual := make(chan string)
client.AddCallback("353", func(e *irc.Event) {
for i := range e.Arguments {
actual <- e.Arguments[i]
}
})
defer client.Quit()
go client.Loop()
client.Join("#join")
client.SendRaw("NAMES #join")
for i := range expected {
select {
case res := <-actual:
assert.Equal(expected[i], res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
}
func TestChannel_InviteOnly(t *testing.T) {
assert := assert.New(t)
expected := true
actual := make(chan bool)
client1 := newClient(false)
client2 := newClient(false)
client1.AddCallback("324", func(e *irc.Event) {
if strings.Contains(e.Arguments[2], "i") {
client2.Join("#inviteonly")
} else {
client1.Mode("#inviteonly")
}
})
client2.AddCallback("473", func(e *irc.Event) {
actual <- true
})
client2.AddCallback("JOIN", func(e *irc.Event) {
actual <- false
})
defer client1.Quit()
defer client2.Quit()
go client1.Loop()
go client2.Loop()
client1.Join("#inviteonly")
client1.Mode("#inviteonly", "+i")
client1.Mode("#inviteonly")
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestUser_WithHostMask(t *testing.T) {
assert := assert.New(t)
client1 := newClient(false)
client2 := newClient(false)
expected := fmt.Sprintf("%x", sha256.Sum256([]byte("localhost")))
actual := make(chan string)
client1.AddCallback("001", func(e *irc.Event) {
client1.Mode(client1.GetNick(), "+x")
})
client2.AddCallback("001", func(e *irc.Event) {
client2.Whois(client1.GetNick())
})
client2.AddCallback("401", func(e *irc.Event) {
client2.Whois(client1.GetNick())
})
client2.AddCallback("311", func(e *irc.Event) {
actual <- e.Arguments[3]
})
defer client1.Quit()
defer client2.Quit()
go client1.Loop()
go client2.Loop()
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestUser_WithoutHostMask(t *testing.T) {
assert := assert.New(t)
client1 := newClient(false)
client2 := newClient(false)
expected := "localhost"
actual := make(chan string)
client1.AddCallback("001", func(e *irc.Event) {
client1.Mode(client1.GetNick(), "-x")
})
client2.AddCallback("001", func(e *irc.Event) {
client2.Whois(client1.GetNick())
})
client2.AddCallback("401", func(e *irc.Event) {
client2.Whois(client1.GetNick())
})
client2.AddCallback("311", func(e *irc.Event) {
actual <- e.Arguments[3]
})
defer client1.Quit()
defer client2.Quit()
go client1.Loop()
go client2.Loop()
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestUser_PRIVMSG(t *testing.T) {
assert := assert.New(t)
expected := "Hello World!"
actual := make(chan string)
client1 := newClient(false)
client2 := newClient(false)
client1.AddCallback("001", func(e *irc.Event) {
client1.Privmsg(client2.GetNick(), expected)
})
client1.AddCallback("PRIVMSG", func(e *irc.Event) {
actual <- e.Message()
})
client2.AddCallback("001", func(e *irc.Event) {
client2.Privmsg(client1.GetNick(), expected)
})
client2.AddCallback("PRIVMSG", func(e *irc.Event) {
actual <- e.Message()
})
defer client1.Quit()
defer client2.Quit()
go client1.Loop()
go client2.Loop()
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestChannel_PRIVMSG(t *testing.T) {
assert := assert.New(t)
expected := "Hello World!"
actual := make(chan string)
client1 := newClient(false)
client2 := newClient(false)
client1.AddCallback("JOIN", func(e *irc.Event) {
client1.Privmsg(e.Arguments[0], expected)
})
client2.AddCallback("JOIN", func(e *irc.Event) {
client2.Privmsg(e.Arguments[0], expected)
})
client1.AddCallback("PRIVMSG", func(e *irc.Event) {
actual <- e.Message()
})
client2.AddCallback("PRIVMSG", func(e *irc.Event) {
actual <- e.Message()
})
defer client1.Quit()
defer client2.Quit()
go client1.Loop()
go client2.Loop()
client1.Join("#channelprivmsg")
client2.Join("#channelprivmsg")
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestChannel_NoExternal(t *testing.T) {
assert := assert.New(t)
expected := true
actual := make(chan bool)
client1 := newClient(true)
client2 := newClient(true)
client1.AddCallback("JOIN", func(e *irc.Event) {
channel := e.Arguments[0]
if channel == "#noexternal" {
if e.Nick == client1.GetNick() {
client2.Privmsg("#noexternal", "FooBar!")
} else {
assert.Fail(fmt.Sprintf("unexpected user %s joined %s", e.Nick, channel))
}
} else {
assert.Fail(fmt.Sprintf("unexpected channel %s", channel))
}
})
client2.AddCallback("PRIVMSG", func(e *irc.Event) {
if e.Arguments[0] == "#noexternal" {
actual <- false
}
})
client2.AddCallback("404", func(e *irc.Event) {
actual <- true
})
defer client1.Quit()
defer client2.Quit()
go client1.Loop()
go client2.Loop()
client1.Join("#noexternal")
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestChannel_SetTopic_InvalidChannel(t *testing.T) {
assert := assert.New(t)
expected := true
actual := make(chan bool)
client1 := newClient(false)
client1.AddCallback("403", func(e *irc.Event) {
actual <- true
})
defer client1.Quit()
go client1.Loop()
client1.SendRaw("TOPIC #invalidchannel :FooBar")
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestChannel_SetTopic_NotOnChannel(t *testing.T) {
assert := assert.New(t)
expected := true
actual := make(chan bool)
client1 := newClient(false)
client2 := newClient(false)
client1.AddCallback("442", func(e *irc.Event) {
actual <- true
})
client2.AddCallback("JOIN", func(e *irc.Event) {
client1.SendRaw("TOPIC #notonchannel :FooBar")
})
defer client1.Quit()
go client1.Loop()
client2.Join("#notonchannel")
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestChannel_BadChannelKey(t *testing.T) {
assert := assert.New(t)
expected := true
actual := make(chan bool)
client1 := newClient(false)
client2 := newClient(false)
client1.AddCallback("324", func(e *irc.Event) {
if strings.Contains(e.Arguments[2], "k") {
client2.Join(e.Arguments[1])
} else {
client1.Mode("#badchannelkey")
}
})
client2.AddCallback("JOIN", func(e *irc.Event) {
if e.Nick == client2.GetNick() && e.Arguments[0] == "#badchannelkey" {
actual <- false
}
})
client2.AddCallback("475", func(e *irc.Event) {
actual <- true
})
defer client1.Quit()
defer client2.Quit()
go client1.Loop()
go client2.Loop()
client1.Join("#badchannelkey")
client1.Mode("#badchannelkey", "+k", "opensesame")
client1.Mode("#badchannelkey")
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}
func TestChannel_GoodChannelKey(t *testing.T) {
assert := assert.New(t)
expected := true
actual := make(chan bool)
client1 := newClient(true)
client2 := newClient(true)
client1.AddCallback("324", func(e *irc.Event) {
if strings.Contains(e.Arguments[2], "k") {
client2.SendRawf("JOIN %s :opensesame", e.Arguments[1])
} else {
client1.Mode("#goodchannelkey")
}
})
client2.AddCallback("JOIN", func(e *irc.Event) {
if e.Nick == client2.GetNick() && e.Arguments[0] == "#goodchannelkey" {
actual <- true
}
})
client2.AddCallback("475", func(e *irc.Event) {
actual <- false
})
defer client1.Quit()
defer client2.Quit()
go client1.Loop()
go client2.Loop()
client1.Join("#goodchannelkey")
client1.Mode("#goodchannelkey", "+k", "opensesame")
client1.Mode("#goodchannelkey")
select {
case res := <-actual:
assert.Equal(expected, res)
case <-time.After(TIMEOUT):
assert.Fail("timeout")
}
}

1
vendor/github.com/satori/go.uuid generated vendored Submodule

1
vendor/github.com/stretchr/testify generated vendored Submodule

1
vendor/github.com/thoj/go-ircevent generated vendored Submodule