26 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
James Mills
9e075dde67 Fixed send on closed channel bug (#29) 2017-11-26 13:25:21 -08:00
James Mills
20be29bcef Fixed bug with RPL_ENDOFWHOIS (/WHOIS) response missing nick component (#27) 2017-11-26 10:42:14 -08:00
James Mills
34c3be0a88 Update README.md 2017-11-26 10:10:18 -08:00
Mike Taylor
be246a3bc4 minor typo fixes (#25) 2017-11-25 20:21:54 -08:00
James Mills
4fb452b2c0 Release v1.6.2 2017-11-25 20:19:05 -08:00
James Mills
d707382a78 Added support for user hostmask(s) / Hostname/IP Cloacks (#24) 2017-11-25 19:36:38 -08:00
James Mills
7620a3c282 Update README.md 2017-11-25 18:50:42 -08:00
James Mills
18a3e2f2c3 Update README.md 2017-11-25 18:47:15 -08:00
James Mills
d046a9863f Fixed /VERSION response (#22) 2017-11-25 17:57:09 -08:00
James Mills
a1450a81d6 Updated vendor 3rd-party packages (#20) 2017-11-25 16:42:35 -08:00
James Mills
d594386658 Fixed scripts/release.sh to correctly produce linux binaries for both amd64 and arm64 (#18) 2017-11-25 16:04:09 -08:00
36 changed files with 917 additions and 99 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

12
.gitmodules vendored
View File

@@ -43,3 +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/thoj/go-ircevent"]
path = vendor/github.com/thoj/go-ircevent
url = https://github.com/thoj/go-ircevent
[submodule "vendor/github.com/stretchr/testify"]
path = vendor/github.com/stretchr/testify
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

@@ -46,4 +46,4 @@ $ git push -u origin my-feature
When describing your bug report; please be concise and as detailed as you can When describing your bug report; please be concise and as detailed as you can
so we can easily work out what the problem is. It's also very helpful if you so we can easily work out what the problem is. It's also very helpful if you
are able to provide a test case that repeatedly demonstrates the bug at hand: are able to provide a test case that repeatedly demonstrates the bug at hand.

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,41 @@ 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
```#!bash
$ go get github.com/prologic/eris
$ cat > ircd.yml <<EOF
network:
name: Test
server:
name: Test
listen:
- ":6667"
EOF
$ eris
```
If you want TLS (**recommended**) then:
```#!bash
$ go get github.com/prologic/mksslcert
$ mksslcert
```
This generates a self-signed cert `cert.pem` and `key.pem` into the `$PWD`.
Then add a `tlslisten` block to your config:
```#!yaml
server:
tlslisten:
":6697":
key: key.pem
cert: cert.pem
```
## Installation ## Installation
@@ -73,6 +108,14 @@ $ go install github.com/prologic/mkpasswd
$ mkpasswd $ mkpasswd
``` ```
Self-signed certificates can also be generated using the `mksslcert` tool
from [prologic/mksslcert](https://github.com/prologic/mksslcert):
```#!bash
$ go install github.com/prologic/mksslcert
$ mksslcert
```
## Deployment ## Deployment
To run simply run the `eris` binary (*assuming a `ircd.yml` in the current directory*): To run simply run the `eris` binary (*assuming a `ircd.yml` in the current directory*):
@@ -93,15 +136,19 @@ You may want to customize the configuration however and create your own image ba
$ docker stack deploy -c docker-compose.yml eris $ docker stack deploy -c docker-compose.yml eris
``` ```
Which assumes a `ircd.yml` coniguration fiel int he current directory which Docker will use to distribute as the configuration. The `docker-compose.yml` (*Docker Stackfile*) is available at the root of this repository. Which assumes a `ircd.yml` coniguration file in the current directory which Docker will use to distribute as the configuration. The `docker-compose.yml` (*Docker Stackfile*) is available at the root of this repository.
## Related Proejcts ## Related Projects
There are a number of supported accompanying services that are being developed alongside Eris: There are a number of supported accompanying services that are being developed alongside Eris:
* [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

@@ -155,16 +155,16 @@ func (channel *Channel) Join(client *Client, key Text) {
return return
} }
isInvited := channel.lists[InviteMask].Match(client.UserHost()) isInvited := channel.lists[InviteMask].Match(client.UserHost(false))
if !isOperator && channel.flags.Has(InviteOnly) && !isInvited { if !isOperator && channel.flags.Has(InviteOnly) && !isInvited {
client.ErrInviteOnlyChan(channel) client.ErrInviteOnlyChan(channel)
return return
} }
if channel.lists[BanMask].Match(client.UserHost()) && if channel.lists[BanMask].Match(client.UserHost(false)) &&
!isInvited && !isInvited &&
!isOperator && !isOperator &&
!channel.lists[ExceptMask].Match(client.UserHost()) { !channel.lists[ExceptMask].Match(client.UserHost(false)) {
client.ErrBannedFromChan(channel) client.ErrBannedFromChan(channel)
return return
} }
@@ -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:
@@ -508,7 +508,7 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) {
} }
if channel.flags.Has(InviteOnly) { if channel.flags.Has(InviteOnly) {
channel.lists[InviteMask].Add(invitee.UserHost()) channel.lists[InviteMask].Add(invitee.UserHost(false))
} }
inviter.RplInviting(invitee, channel.name) inviter.RplInviting(invitee, channel.name)

View File

@@ -26,6 +26,7 @@ type Client struct {
hasQuit bool hasQuit bool
hops uint hops uint
hostname Name hostname Name
hostmask Name // Cloacked hostname (SHA256)
pingTime time.Time pingTime time.Time
idleTimer *time.Timer idleTimer *time.Timer
nick Name nick Name
@@ -71,9 +72,18 @@ func NewClient(server *Server, conn net.Conn) *Client {
// //
func (client *Client) writeloop() { func (client *Client) writeloop() {
for reply := range client.replies { for {
select {
case reply, ok := <-client.replies:
if !ok || reply == "" || client.socket == nil {
return
}
client.socket.Write(reply) client.socket.Write(reply)
} }
if client.replies == nil {
break
}
}
} }
func (client *Client) readloop() { func (client *Client) readloop() {
@@ -83,6 +93,7 @@ func (client *Client) readloop() {
// Set the hostname for this client. // Set the hostname for this client.
client.hostname = AddrLookupHostname(client.socket.conn.RemoteAddr()) client.hostname = AddrLookupHostname(client.socket.conn.RemoteAddr())
client.hostmask = NewName(SHA256(client.hostname.String()))
for err == nil { for err == nil {
if line, err = client.socket.Read(); err != nil { if line, err = client.socket.Read(); err != nil {
@@ -117,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 {
@@ -142,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()
@@ -207,6 +218,7 @@ func (client *Client) Register() {
return return
} }
client.registered = true client.registered = true
client.flags[HostMask] = true
client.Touch() client.Touch()
} }
@@ -220,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)
@@ -233,6 +251,7 @@ func (client *Client) destroy() {
} }
close(client.replies) close(client.replies)
client.replies = nil
client.socket.Close() client.socket.Close()
@@ -279,11 +298,14 @@ func (c *Client) ModeString() (str string) {
return return
} }
func (c *Client) UserHost() Name { func (c *Client) UserHost(cloacked bool) Name {
username := "*" username := "*"
if c.HasUsername() { if c.HasUsername() {
username = c.username.String() username = c.username.String()
} }
if cloacked {
return Name(fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostmask))
}
return Name(fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostname)) return Name(fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostname))
} }
@@ -303,7 +325,7 @@ func (c *Client) Nick() Name {
} }
func (c *Client) Id() Name { func (c *Client) Id() Name {
return c.UserHost() return c.UserHost(true)
} }
func (c *Client) String() string { func (c *Client) String() string {
@@ -346,8 +368,10 @@ func (client *Client) ChangeNickname(nickname Name) {
} }
func (client *Client) Reply(reply string) { func (client *Client) Reply(reply string) {
if client.replies != nil {
client.replies <- reply client.replies <- reply
} }
}
func (client *Client) Quit(message Text) { func (client *Client) Quit(message Text) {
if client.hasQuit { if client.hasQuit {

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"
) )
@@ -105,7 +103,7 @@ func (clients *ClientLookupSet) FindAll(userhost Name) *ClientSet {
var casemappedNickMask string var casemappedNickMask string
for _, client := range clients.nicks { for _, client := range clients.nicks {
casemappedNickMask = client.UserHost().String() casemappedNickMask = client.UserHost(false).String()
if matcher.Match(casemappedNickMask) { if matcher.Match(casemappedNickMask) {
set.Add(client) set.Add(client)
} }
@@ -123,7 +121,7 @@ func (clients *ClientLookupSet) Find(userhost Name) *Client {
var casemappedNickMask string var casemappedNickMask string
for _, client := range clients.nicks { for _, client := range clients.nicks {
casemappedNickMask = client.UserHost().String() casemappedNickMask = client.UserHost(false).String()
if matcher.Match(casemappedNickMask) { if matcher.Match(casemappedNickMask) {
return client return client
} }

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"
) )
@@ -68,7 +67,7 @@ func (store *MemoryPasswordStore) Verify(username, password string) error {
hash, ok := store.Get(username) hash, ok := store.Get(username)
if !ok { if !ok {
log.Debugf("username %s not found", username) log.Debugf("username %s not found", username)
return fmt.Errorf("account not found: %S", username) return fmt.Errorf("account not found: %s", username)
} }
return store.hasher.Compare(hash, []byte(password)) return store.hasher.Compare(hash, []byte(password))

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

@@ -260,16 +260,16 @@ func (target *Client) RplWhois(client *Client) {
} }
target.RplWhoisServer(client) target.RplWhoisServer(client)
target.RplWhoisLoggedIn(client) target.RplWhoisLoggedIn(client)
target.RplEndOfWhois() target.RplEndOfWhois(client)
} }
func (target *Client) RplWhoisUser(client *Client) { func (target *Client) RplWhoisUser(client *Client) {
var clientHost Name var clientHost Name
if client.flags[SecureConn] { if target.flags[Operator] || !client.flags[HostMask] {
clientHost = client.hostname clientHost = client.hostname
} else { } else {
clientHost = NewName("SECURED") clientHost = client.hostmask
} }
target.NumericReply( target.NumericReply(
@@ -324,9 +324,12 @@ func (target *Client) RplWhoisServer(client *Client) {
) )
} }
func (target *Client) RplEndOfWhois() { func (target *Client) RplEndOfWhois(client *Client) {
target.NumericReply(RPL_ENDOFWHOIS, target.NumericReply(
":End of WHOIS list") RPL_ENDOFWHOIS,
"%s :End of WHOIS list",
client.Nick(),
)
} }
func (target *Client) RplChannelModeIs(channel *Channel) { func (target *Client) RplChannelModeIs(channel *Channel) {
@@ -337,6 +340,14 @@ func (target *Client) RplChannelModeIs(channel *Channel) {
// <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ] // <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
// :<hopcount> <real name> // :<hopcount> <real name>
func (target *Client) RplWhoReply(channel *Channel, client *Client) { func (target *Client) RplWhoReply(channel *Channel, client *Client) {
var clientHost Name
if target.flags[Operator] || !client.flags[HostMask] {
clientHost = client.hostname
} else {
clientHost = client.hostmask
}
channelName := "*" channelName := "*"
flags := "" flags := ""
@@ -366,9 +377,18 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
} }
} }
} }
target.NumericReply(RPL_WHOREPLY, target.NumericReply(
"%s %s %s %s %s %s :%d %s", channelName, client.username, client.hostname, RPL_WHOREPLY,
client.server.name, client.Nick(), flags, client.hops, client.realname) "%s %s %s %s %s %s :%d %s",
channelName,
client.username,
clientHost,
client.server.name,
client.Nick(),
flags,
client.hops,
client.realname,
)
} }
// <name> :End of WHO list // <name> :End of WHO list
@@ -469,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) {
@@ -484,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() {
@@ -579,9 +608,22 @@ func (target *Client) RplLUserMe() {
} }
func (target *Client) RplWhoWasUser(whoWas *WhoWas) { func (target *Client) RplWhoWasUser(whoWas *WhoWas) {
target.NumericReply(RPL_WHOWASUSER, var whoWasHost Name
if target.flags[Operator] {
whoWasHost = whoWas.hostname
} else {
whoWasHost = whoWas.hostmask
}
target.NumericReply(
RPL_WHOWASUSER,
"%s %s %s * :%s", "%s %s %s * :%s",
whoWas.nickname, whoWas.username, whoWas.hostname, whoWas.realname) whoWas.nickname,
whoWas.username,
whoWasHost,
whoWas.realname,
)
} }
func (target *Client) RplEndOfWhoWas(nickname Name) { func (target *Client) RplEndOfWhoWas(nickname Name) {

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"
) )
// //

11
irc/utils.go Normal file
View File

@@ -0,0 +1,11 @@
package irc
import (
"crypto/sha256"
"fmt"
)
func SHA256(data string) string {
hash := sha256.Sum256([]byte(data))
return fmt.Sprintf("%x", hash)
}

View File

@@ -1,20 +1,21 @@
package irc package irc
import (
"fmt"
)
var ( var (
//PackageName package name // Package package name
Package = "eris" Package = "eris"
// Version release version // Version release version
Version = "1.6.0" 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 Package + " v" + 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 {
@@ -17,6 +15,7 @@ type WhoWas struct {
nickname Name nickname Name
username Name username Name
hostname Name hostname Name
hostmask Name
realname Text realname Text
} }
@@ -33,6 +32,7 @@ func (list *WhoWasList) Append(client *Client) {
nickname: client.Nick(), nickname: client.Nick(),
username: client.username, username: client.username,
hostname: client.hostname, hostname: client.hostname,
hostmask: client.hostmask,
realname: client.realname, realname: client.realname,
} }
list.end = (list.end + 1) % len(list.buffer) list.end = (list.end + 1) % len(list.buffer)

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")
}
}

View File

@@ -21,7 +21,7 @@ fi
echo -n "Building binaries ... " echo -n "Building binaries ... "
GOOS=linux GOARCH=amd64 go build -o ./bin/eris-Linux-x86_64 . GOOS=linux GOARCH=amd64 go build -o ./bin/eris-Linux-x86_64 .
GOOS=linux GOARCH=arm64 go build -o ./bin/eris-Linux-x86_64 . GOOS=linux GOARCH=arm64 go build -o ./bin/eris-Linux-arm_64 .
GOOS=darwin GOARCH=amd64 go build -o ./bin/eris-Darwin-x86_64 . GOOS=darwin GOARCH=amd64 go build -o ./bin/eris-Darwin-x86_64 .
GOOS=windows GOARCH=amd64 go build -o ./bin/eris-Windows-x86_64.exe . GOOS=windows GOARCH=amd64 go build -o ./bin/eris-Windows-x86_64.exe .

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