Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 37 additions & 45 deletions irc/channel.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package irc

import (
"log"
"strconv"
)

Expand All @@ -13,7 +12,7 @@ type Channel struct {
name Name
server *Server
topic Text
userLimit uint64
userLimit uint
}

// NewChannel creates a new channel from a `Server` and a `name`
Expand Down Expand Up @@ -115,15 +114,15 @@ func (channel *Channel) ModeString(client *Client) (str string) {
str += " " + channel.key.String()
}
if showUserLimit {
str += " " + strconv.FormatUint(channel.userLimit, 10)
str += " " + strconv.FormatUint(uint64(channel.userLimit), 10)
}

return
}

func (channel *Channel) IsFull() bool {
return (channel.userLimit > 0) &&
(uint64(len(channel.members)) >= channel.userLimit)
(uint(len(channel.members)) >= channel.userLimit)
}

func (channel *Channel) CheckKey(key Text) bool {
Expand Down Expand Up @@ -193,17 +192,11 @@ func (channel *Channel) GetTopic(client *Client) {
return
}

if channel.topic == "" {
// clients appear not to expect this
//replier.Reply(RplNoTopic(channel))
return
}

client.RplTopic(channel)
}

func (channel *Channel) SetTopic(client *Client, topic Text) {
if !(client.flags[Operator] || channel.members.Has(client)) {
if !channel.members.Has(client) && !channel.ClientIsOperator(client) {
client.ErrNotOnChannel(channel)
return
}
Expand All @@ -220,37 +213,24 @@ func (channel *Channel) SetTopic(client *Client, topic Text) {
member.Reply(reply)
}

if err := channel.Persist(); err != nil {
log.Println("Channel.Persist:", channel, err)
}
channel.Persist()
}

func (channel *Channel) CanSpeak(client *Client) bool {
if client.flags[Operator] {
return true
}

if channel.flags[NoOutside] && !channel.members.Has(client) {
return false
}

if channel.flags[Moderated] && !(channel.members.HasMode(client, Voice) ||
channel.members.HasMode(client, ChannelOperator)) {
return false
}
return true
}

func (channel *Channel) PrivMsg(client *Client, message Text) {
if !channel.CanSpeak(client) {
client.ErrCannotSendToChan(channel)
return
}
reply := RplPrivMsg(client, channel, message)
for member := range channel.members {
if member == client {
continue
}
member.Reply(reply)
}
return true
}

func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode,
Expand Down Expand Up @@ -390,17 +370,19 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
}

case UserLimit:
limit, err := strconv.ParseUint(change.arg, 10, 64)
if err != nil {
if plimit, err := strconv.ParseUint(change.arg, 10, 16); err != nil {
client.ErrNeedMoreParams("MODE")
return false
}
if (limit == 0) || (limit == channel.userLimit) {
return false
}

channel.userLimit = limit
return true
} else {
limit := uint(plimit)
if (limit == 0) || (limit == channel.userLimit) {
return false
}

channel.userLimit = limit
return true
}

case ChannelOperator, Voice:
return channel.applyModeMember(client, change.mode, change.op,
Expand All @@ -409,6 +391,7 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
default:
client.ErrUnknownMode(change.mode, channel)
}

return false
}

Expand All @@ -418,7 +401,7 @@ func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) {
return
}

applied := make(ChannelModeChanges, 0)
applied := make(ChannelModeChanges, 0, len(changes))
for _, change := range changes {
if channel.applyMode(client, change) {
applied = append(applied, change)
Expand All @@ -431,9 +414,7 @@ func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) {
member.Reply(reply)
}

if err := channel.Persist(); err != nil {
log.Println("Channel.Persist:", channel, err)
}
channel.Persist()
}
}

Expand All @@ -451,15 +432,20 @@ func (channel *Channel) Persist() (err error) {
_, err = channel.server.db.Exec(`
DELETE FROM channel WHERE name = ?`, channel.name.String())
}

if err != nil {
Log.error.Println("Channel.Persist:", channel, err)
}
return
}

func (channel *Channel) Notice(client *Client, message Text) {
func (channel *Channel) textReply(client *Client, makeReply RplTextFunc, message Text) {
if !channel.CanSpeak(client) {
client.ErrCannotSendToChan(channel)
return
}
reply := RplNotice(client, channel, message)

reply := makeReply(client, channel, message)
for member := range channel.members {
if member == client {
continue
Expand All @@ -468,6 +454,14 @@ func (channel *Channel) Notice(client *Client, message Text) {
}
}

func (channel *Channel) PrivMsg(client *Client, message Text) {
channel.textReply(client, RplPrivMsg, message)
}

func (channel *Channel) Notice(client *Client, message Text) {
channel.textReply(client, RplNotice, message)
}

func (channel *Channel) Quit(client *Client) {
channel.members.Remove(client)
client.channels.Remove(channel)
Expand Down Expand Up @@ -511,9 +505,7 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) {

if channel.flags[InviteOnly] {
channel.lists[InviteMask].Add(invitee.UserHost())
if err := channel.Persist(); err != nil {
log.Println("Channel.Persist:", channel, err)
}
channel.Persist()
}

inviter.RplInviting(invitee, channel.name)
Expand Down
42 changes: 24 additions & 18 deletions irc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import (
const (
IDLE_TIMEOUT = time.Minute // how long before a client is considered idle
QUIT_TIMEOUT = time.Minute // how long after idle before a client is kicked

CONN_BAD_PASSWORD Text = "bad password"
CONN_CLOSED Text = "connection closed"
CONN_TIMEOUT Text = "connection timeout"
CONN_UNEXPECTED Text = "unexpected command"
)

type Client struct {
Expand All @@ -29,22 +34,20 @@ type Client struct {
realname Text
registered bool
server *Server
socket *Socket
conn *IRCConn
username Name
}

func NewClient(server *Server, conn net.Conn) *Client {
now := time.Now()
client := &Client{
atime: now,
authorized: server.password == nil,
capState: CapNone,
capabilities: make(CapabilitySet),
channels: make(ChannelSet),
ctime: now,
ctime: time.Now(),
flags: make(map[UserMode]bool),
server: server,
socket: NewSocket(conn),
conn: NewIRCConn(conn),
}
client.Touch()
go client.run()
Expand All @@ -64,17 +67,16 @@ func (client *Client) run() {
// Set the hostname for this client. The client may later send a PROXY
// command from stunnel that sets the hostname to something more accurate.
client.send(NewProxyCommand(AddrLookupHostname(
client.socket.conn.RemoteAddr())))
client.conn.conn.RemoteAddr())))

for err == nil {
if line, err = client.socket.Read(); err != nil {
command = NewQuitCommand("connection closed")
if line, err = client.conn.Read(); err != nil {
command = NewQuitCommand(CONN_CLOSED, CONN_CLOSED)

} else if command, err = ParseCommand(line); err != nil {
switch err {
case ErrParseCommand:
client.Reply(RplNotice(client.server, client,
NewText("failed to parse command")))
client.server.Notice(client, "failed to parse command")

case NotEnoughArgsError:
// TODO
Expand Down Expand Up @@ -103,15 +105,19 @@ func (client *Client) send(command Command) {
// quit timer goroutine

func (client *Client) connectionTimeout() {
client.send(NewQuitCommand("connection timeout"))
client.send(NewQuitCommand(CONN_TIMEOUT, CONN_TIMEOUT))
}

//
// idle timer goroutine
//

type IdleCommand struct {
BaseCommand
}

func (client *Client) connectionIdle() {
client.server.idle <- client
client.send(&IdleCommand{})
}

//
Expand Down Expand Up @@ -172,9 +178,9 @@ func (client *Client) destroy() {
client.quitTimer.Stop()
}

client.socket.Close()
client.conn.Close()

Log.debug.Printf("%s: destroyed", client)
Log.debug.Println(client, "destroyed")
}

func (client *Client) IdleTime() time.Duration {
Expand Down Expand Up @@ -245,7 +251,7 @@ func (client *Client) Friends() ClientSet {

func (client *Client) SetNickname(nickname Name) {
if client.HasNick() {
Log.error.Printf("%s nickname already set!", client)
Log.error.Println(client, "nickname already set", nickname)
return
}
client.nick = nickname
Expand All @@ -265,16 +271,16 @@ func (client *Client) ChangeNickname(nickname Name) {
}

func (client *Client) Reply(reply string) error {
return client.socket.Write(reply)
return client.conn.Write(reply)
}

func (client *Client) Quit(message Text) {
func (client *Client) Quit(message Text, reason Text) {
if client.hasQuit {
return
}

client.hasQuit = true
client.Reply(RplError("quit"))
client.Reply(RplError(reason))
client.server.whoWas.Append(client)
friends := client.Friends()
friends.Remove(client)
Expand Down
12 changes: 8 additions & 4 deletions irc/client_lookup_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package irc
import (
"database/sql"
"errors"
"log"
"regexp"
"strings"
)
Expand All @@ -21,10 +20,14 @@ var (
`?`, `_`)
)

// Return true iff the string contains IRC mask wildcards.
func HasWildcards(mask string) bool {
return wildMaskExpr.MatchString(mask)
}

// Take a userhost string that might be just a nick and fill in the wildcards
// for the other parts. To be a full userhost (and easier to parse), add the !*
// and @* to indicate any username and any hostname.
func ExpandUserHost(userhost Name) (expanded Name) {
expanded = userhost
// fill in missing wildcards for nicks
Expand All @@ -37,6 +40,7 @@ func ExpandUserHost(userhost Name) (expanded Name) {
return
}

// Quote a SQL like expression.
func QuoteLike(userhost Name) string {
return likeQuoter.Replace(userhost.String())
}
Expand Down Expand Up @@ -73,10 +77,10 @@ func (clients *ClientLookupSet) Remove(client *Client) error {
if !client.HasNick() {
return ErrNickMissing
}
if clients.Get(client.nick) != client {
if clients.Get(client.Nick()) != client {
return ErrNicknameMismatch
}
delete(clients.byNick, client.nick.ToLower())
delete(clients.byNick, client.Nick().ToLower())
clients.db.Remove(client)
return nil
}
Expand Down Expand Up @@ -146,7 +150,7 @@ func NewClientDB() *ClientDB {
for _, stmt := range stmts {
_, err := db.db.Exec(stmt)
if err != nil {
log.Fatal("NewClientDB: ", stmt, err)
Log.error.Fatalln("NewClientDB:", stmt, err)
}
}
return db
Expand Down
Loading