21 Commits

Author SHA1 Message Date
James Mills
c69d9a1259 New test framework with better reliability 2017-11-28 02:11:40 -08:00
James Mills
e6491c955a Moar tests 2017-11-28 01:03:28 -08:00
James Mills
c0b8342b32 Rename some tests 2017-11-28 00:17:25 -08:00
James Mills
6f16f24a98 Add TestUser_PRIVMSG test 2017-11-28 00:13:21 -08:00
James Mills
b5239acd9b Update vendor 3rd-party deps for missing github.com/stretchr/testify/assert 2017-11-27 23:56:51 -08:00
James Mills
73ace6b829 Add TestConnect_RplWelcome test 2017-11-27 23:43:19 -08:00
James Mills
bf78fbc9f4 Added a basic integration test suite framework 2017-11-27 23:21:48 -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
21 changed files with 438 additions and 51 deletions

6
.gitmodules vendored
View File

@@ -49,3 +49,9 @@
[submodule "vendor/github.com/petermattis/goid"] [submodule "vendor/github.com/petermattis/goid"]
path = vendor/github.com/petermattis/goid path = vendor/github.com/petermattis/goid
url = https://github.com/petermattis/goid url = https://github.com/petermattis/goid
[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

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

@@ -6,7 +6,7 @@ APP=eris
PACKAGE=irc PACKAGE=irc
REPO?=prologic/$(APP) REPO?=prologic/$(APP)
TAG?=latest TAG?=latest
BUILD?=-dev BUILD?=dev
all: dev all: dev

View File

@@ -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
@@ -83,6 +84,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 {
@@ -220,6 +222,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 +241,7 @@ func (client *Client) destroy() {
} }
close(client.replies) close(client.replies)
client.replies = nil
client.socket.Close() client.socket.Close()
@@ -279,11 +288,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 +315,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,7 +358,9 @@ func (client *Client) ChangeNickname(nickname Name) {
} }
func (client *Client) Reply(reply string) { func (client *Client) Reply(reply string) {
client.replies <- reply if client.replies != nil {
client.replies <- reply
}
} }
func (client *Client) Quit(message Text) { func (client *Client) Quit(message Text) {

View File

@@ -105,7 +105,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 +123,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

@@ -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

@@ -70,7 +70,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 +81,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 +90,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,
} }
) )

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] {
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] {
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

@@ -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,6 +73,7 @@ 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),
} }
@@ -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/insecire)
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
} }

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,14 +1,18 @@
package irc package irc
import (
"fmt"
)
var ( var (
//PackageName package name //PackageName package name
Package = "eris" Package = "eris"
// Version release version // Version release version
Version = "1.6.0" Version = "1.6.3"
// Build will be overwritten automatically by the build system // Build will be overwritten automatically by the build system
Build = "-dev" Build = "dev"
// GitCommit will be overwritten automatically by the build system // GitCommit will be overwritten automatically by the build system
GitCommit = "HEAD" GitCommit = "HEAD"
@@ -16,5 +20,5 @@ var (
// 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@%s", Package, Version, Build, GitCommit)
} }

View File

@@ -17,6 +17,7 @@ type WhoWas struct {
nickname Name nickname Name
username Name username Name
hostname Name hostname Name
hostmask Name
realname Text realname Text
} }
@@ -33,6 +34,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)

185
main_test.go Normal file
View File

@@ -0,0 +1,185 @@
package main
import (
"flag"
"log"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/thoj/go-ircevent"
eris "github.com/prologic/eris/irc"
)
var (
done chan bool
server *eris.Server
client *irc.Connection
clients map[string]*irc.Connection
tls = flag.Bool("tls", false, "run tests with TLS")
)
func setupServer() *eris.Server {
config := &eris.Config{}
config.Network.Name = "Test"
config.Server.Name = "test"
config.Server.Description = "Test"
config.Server.Listen = []string{":6667"}
server := eris.NewServer(config)
go server.Run()
return server
}
func newClient(nick, user, name string, start bool) *irc.Connection {
client := irc.IRC(nick, user)
client.RealName = 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()
done = make(chan bool)
server = setupServer()
client = newClient("test", "test", "Test", true)
clients = make(map[string]*irc.Connection)
clients["test1"] = newClient("test1", "test", "Test 1", true)
clients["test2"] = newClient("test2", "test", "Test 2", true)
result := m.Run()
for _, client := range clients {
client.Quit()
}
server.Stop()
os.Exit(result)
}
func TestConnection(t *testing.T) {
assert := assert.New(t)
var (
expected bool
actual chan bool
)
expected = true
actual = make(chan bool)
client := newClient("connect", "connect", "Connect", false)
client.AddCallback("001", func(e *irc.Event) {
defer func() { done <- true }()
actual <- true
})
time.AfterFunc(1*time.Second, func() { done <- true })
defer client.Quit()
go client.Loop()
<-done
assert.Equal(expected, <-actual)
}
func TestRplWelcome(t *testing.T) {
assert := assert.New(t)
var (
expected string
actual chan string
)
expected = "Welcome to the .* Internet Relay Network .*!.*@.*"
actual = make(chan string)
client := newClient("connect", "connect", "Connect", false)
client.AddCallback("001", func(e *irc.Event) {
defer func() { done <- true }()
actual <- e.Message()
})
time.AfterFunc(1*time.Second, func() { done <- true })
defer client.Quit()
go client.Loop()
<-done
assert.Regexp(expected, <-actual)
}
func TestUser_JOIN(t *testing.T) {
assert := assert.New(t)
var (
expected []string
actual chan string
)
expected = []string{"test", "=", "#test", "@test"}
actual = make(chan string)
client.AddCallback("353", func(e *irc.Event) {
defer func() { done <- true }()
for i := range e.Arguments {
actual <- e.Arguments[i]
}
})
time.AfterFunc(1*time.Second, func() { done <- true })
client.Join("#test")
client.SendRaw("NAMES #test")
<-done
for i := range expected {
assert.Equal(expected[i], <-actual)
}
}
func TestUser_PRIVMSG(t *testing.T) {
assert := assert.New(t)
var (
expected string
actual chan string
)
expected = "Hello World!"
actual = make(chan string)
clients["test1"].AddCallback("PRIVMSG", func(e *irc.Event) {
defer func() { done <- true }()
actual <- e.Message()
})
time.AfterFunc(1*time.Second, func() { done <- true })
client.Privmsg("test1", expected)
<-done
assert.Equal(expected, <-actual)
}

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

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