3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-23 04:19:25 +01:00

Merge pull request #22 from jlatt/cleanup

cleanup
This commit is contained in:
Jeremy Latt 2014-03-13 17:17:17 -07:00
commit 5df8173df2
16 changed files with 547 additions and 478 deletions

View File

@ -38,7 +38,7 @@ hostname lookups.
```sh ```sh
go get go get
go install go install
ergonomadic -conf ergonomadic.conf -initdb ergonomadic initdb -conf ergonomadic.conf
``` ```
## Configuration ## Configuration
@ -48,16 +48,16 @@ bcrypted byte strings. You can generate them with the `genpasswd`
subcommand. subcommand.
```sh ```sh
ergonomadic -genpasswd 'hunter2!' ergonomadic genpasswd 'hunter2!'
``` ```
## Running the Server ## Running the Server
```sh ```sh
ergonomadic -conf ergonomadic.conf ergonomadic run -conf ergonomadic.conf
``` ```
## Helpful Documentation ## IRC Documentation
- [RFC 1459: Internet Relay Chat Protocol](http://tools.ietf.org/html/rfc1459) - [RFC 1459: Internet Relay Chat Protocol](http://tools.ietf.org/html/rfc1459)
- [RFC 2811: IRC Channel Management](http://tools.ietf.org/html/rfc2811) - [RFC 2811: IRC Channel Management](http://tools.ietf.org/html/rfc2811)

View File

@ -3,14 +3,9 @@ name = "irc.example.com" ; required, usually a hostname
database = "ergonomadic.db" ; path relative to this file database = "ergonomadic.db" ; path relative to this file
listen = "localhost:6667" ; see `net.Listen` for examples listen = "localhost:6667" ; see `net.Listen` for examples
listen = "[::1]:6667" ; multiple `listen`s are allowed. listen = "[::1]:6667" ; multiple `listen`s are allowed.
log = "debug" ; error, warn, info, debug
motd = "motd.txt" ; path relative to this file motd = "motd.txt" ; path relative to this file
password = "JDJhJDA0JHJzVFFlNXdOUXNhLmtkSGRUQVVEVHVYWXRKUmdNQ3FKVTRrczRSMTlSWGRPZHRSMVRzQmtt" ; 'test' password = "JDJhJDA0JHJzVFFlNXdOUXNhLmtkSGRUQVVEVHVYWXRKUmdNQ3FKVTRrczRSMTlSWGRPZHRSMVRzQmtt" ; 'test'
[operator "root"] [operator "root"]
password = "JDJhJDA0JEhkcm10UlNFRkRXb25iOHZuSDVLZXVBWlpyY0xyNkQ4dlBVc1VMWVk1LlFjWFpQbGxZNUtl" ; 'toor' password = "JDJhJDA0JEhkcm10UlNFRkRXb25iOHZuSDVLZXVBWlpyY0xyNkQ4dlBVc1VMWVk1LlFjWFpQbGxZNUtl" ; 'toor'
[debug]
net = true
client = false
channel = false
server = false

View File

@ -9,51 +9,68 @@ import (
"path/filepath" "path/filepath"
) )
func usage() {
fmt.Fprintln(os.Stderr, "ergonomadic <run|genpasswd|initdb|upgradedb> [options]")
fmt.Fprintln(os.Stderr, " run -conf <config> -- run server")
fmt.Fprintln(os.Stderr, " initdb -conf <config> -- initialize database")
fmt.Fprintln(os.Stderr, " upgrade -conf <config> -- upgrade database")
fmt.Fprintln(os.Stderr, " genpasswd <password> -- bcrypt a password")
flag.PrintDefaults()
}
func loadConfig(conf string) *irc.Config {
config, err := irc.LoadConfig(conf)
if err != nil {
log.Fatalln("error loading config:", err)
}
err = os.Chdir(filepath.Dir(conf))
if err != nil {
log.Fatalln("chdir error:", err)
}
return config
}
func genPasswd() {
}
func main() { func main() {
conf := flag.String("conf", "ergonomadic.conf", "ergonomadic config file") var conf string
initdb := flag.Bool("initdb", false, "initialize database") flag.Usage = usage
upgradedb := flag.Bool("upgradedb", false, "update database")
passwd := flag.String("genpasswd", "", "bcrypt a password") runFlags := flag.NewFlagSet("run", flag.ExitOnError)
runFlags.Usage = usage
runFlags.StringVar(&conf, "conf", "ergonomadic.conf", "ergonomadic config file")
flag.Parse() flag.Parse()
if *passwd != "" { switch flag.Arg(0) {
encoded, err := irc.GenerateEncodedPassword(*passwd) case "genpasswd":
encoded, err := irc.GenerateEncodedPassword(flag.Arg(1))
if err != nil { if err != nil {
log.Fatal("encoding error: ", err) log.Fatalln("encoding error:", err)
} }
fmt.Println(encoded) fmt.Println(encoded)
return
}
config, err := irc.LoadConfig(*conf) case "initdb":
if err != nil { runFlags.Parse(flag.Args()[1:])
log.Fatal("error loading config: ", err) config := loadConfig(conf)
}
err = os.Chdir(filepath.Dir(*conf))
if err != nil {
log.Fatal("chdir error: ", err)
}
if *initdb {
irc.InitDB(config.Server.Database) irc.InitDB(config.Server.Database)
log.Println("database initialized: ", config.Server.Database) log.Println("database initialized: ", config.Server.Database)
return
}
if *upgradedb { case "upgradedb":
runFlags.Parse(flag.Args()[1:])
config := loadConfig(conf)
irc.UpgradeDB(config.Server.Database) irc.UpgradeDB(config.Server.Database)
log.Println("database upgraded: ", config.Server.Database) log.Println("database upgraded: ", config.Server.Database)
return
}
// TODO move to data structures
irc.DEBUG_NET = config.Debug.Net
irc.DEBUG_CLIENT = config.Debug.Client
irc.DEBUG_CHANNEL = config.Debug.Channel
irc.DEBUG_SERVER = config.Debug.Server
default:
runFlags.Parse(flag.Args()[1:])
config := loadConfig(conf)
irc.Log.SetLevel(config.Server.Log)
server := irc.NewServer(config) server := irc.NewServer(config)
log.Println(irc.SEM_VER, "running") log.Println(irc.SEM_VER, "running")
defer log.Println(irc.SEM_VER, "exiting") defer log.Println(irc.SEM_VER, "exiting")
server.Run() server.Run()
}
} }

116
irc/capability.go Normal file
View File

@ -0,0 +1,116 @@
package irc
import (
"strings"
)
type CapSubCommand string
const (
CAP_LS CapSubCommand = "LS"
CAP_LIST CapSubCommand = "LIST"
CAP_REQ CapSubCommand = "REQ"
CAP_ACK CapSubCommand = "ACK"
CAP_NAK CapSubCommand = "NAK"
CAP_CLEAR CapSubCommand = "CLEAR"
CAP_END CapSubCommand = "END"
)
// Capabilities are optional features a client may request from a server.
type Capability string
const (
MultiPrefix Capability = "multi-prefix"
SASL Capability = "sasl"
)
var (
SupportedCapabilities = CapabilitySet{
MultiPrefix: true,
}
)
func (capability Capability) String() string {
return string(capability)
}
// CapModifiers are indicators showing the state of a capability after a REQ or
// ACK.
type CapModifier rune
const (
Ack CapModifier = '~'
Disable CapModifier = '-'
Sticky CapModifier = '='
)
func (mod CapModifier) String() string {
return string(mod)
}
type CapState uint
const (
CapNone CapState = iota
CapNegotiating CapState = iota
CapNegotiated CapState = iota
)
type CapabilitySet map[Capability]bool
func (set CapabilitySet) String() string {
strs := make([]string, len(set))
index := 0
for capability := range set {
strs[index] = string(capability)
index += 1
}
return strings.Join(strs, " ")
}
func (set CapabilitySet) DisableString() string {
parts := make([]string, len(set))
index := 0
for capability := range set {
parts[index] = Disable.String() + capability.String()
index += 1
}
return strings.Join(parts, " ")
}
func (msg *CapCommand) HandleRegServer(server *Server) {
client := msg.Client()
switch msg.subCommand {
case CAP_LS:
client.capState = CapNegotiating
client.Reply(RplCap(client, CAP_LS, SupportedCapabilities))
case CAP_LIST:
client.Reply(RplCap(client, CAP_LIST, client.capabilities))
case CAP_REQ:
for capability := range msg.capabilities {
if !SupportedCapabilities[capability] {
client.Reply(RplCap(client, CAP_NAK, msg.capabilities))
return
}
}
for capability := range msg.capabilities {
client.capabilities[capability] = true
}
client.Reply(RplCap(client, CAP_ACK, msg.capabilities))
case CAP_CLEAR:
reply := RplCap(client, CAP_ACK, client.capabilities.DisableString())
client.capabilities = make(CapabilitySet)
client.Reply(reply)
case CAP_END:
client.capState = CapNegotiated
server.tryRegister(client)
default:
client.ErrInvalidCapCmd(msg.subCommand)
}
}

View File

@ -444,8 +444,8 @@ func (channel *Channel) Persist() (err error) {
(name, flags, key, topic, user_limit, ban_list, except_list, (name, flags, key, topic, user_limit, ban_list, except_list,
invite_list) invite_list)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
channel.name, channel.flags.String(), channel.key, channel.topic, channel.name.String(), channel.flags.String(), channel.key.String(),
channel.userLimit, channel.lists[BanMask].String(), channel.topic.String(), channel.userLimit, channel.lists[BanMask].String(),
channel.lists[ExceptMask].String(), channel.lists[InviteMask].String()) channel.lists[ExceptMask].String(), channel.lists[InviteMask].String())
} else { } else {
_, err = channel.server.db.Exec(` _, err = channel.server.db.Exec(`

View File

@ -2,11 +2,16 @@ package irc
import ( import (
"fmt" "fmt"
"log"
"net" "net"
"time" "time"
) )
const (
LOGIN_TIMEOUT = time.Minute / 2 // how long the client has to login
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
)
type Client struct { type Client struct {
atime time.Time atime time.Time
authorized bool authorized bool
@ -14,7 +19,7 @@ type Client struct {
capabilities CapabilitySet capabilities CapabilitySet
capState CapState capState CapState
channels ChannelSet channels ChannelSet
commands chan editableCommand commands chan Command
ctime time.Time ctime time.Time
flags map[UserMode]bool flags map[UserMode]bool
hasQuit bool hasQuit bool
@ -23,9 +28,9 @@ type Client struct {
idleTimer *time.Timer idleTimer *time.Timer
loginTimer *time.Timer loginTimer *time.Timer
nick Name nick Name
phase Phase
quitTimer *time.Timer quitTimer *time.Timer
realname Text realname Text
registered bool
server *Server server *Server
socket *Socket socket *Socket
username Name username Name
@ -39,10 +44,9 @@ func NewClient(server *Server, conn net.Conn) *Client {
capState: CapNone, capState: CapNone,
capabilities: make(CapabilitySet), capabilities: make(CapabilitySet),
channels: make(ChannelSet), channels: make(ChannelSet),
commands: make(chan editableCommand), commands: make(chan Command),
ctime: now, ctime: now,
flags: make(map[UserMode]bool), flags: make(map[UserMode]bool),
phase: Registration,
server: server, server: server,
} }
client.socket = NewSocket(conn, client.commands) client.socket = NewSocket(conn, client.commands)
@ -115,7 +119,10 @@ func (client *Client) Idle() {
} }
func (client *Client) Register() { func (client *Client) Register() {
client.phase = Normal if client.registered {
return
}
client.registered = true
client.loginTimer.Stop() client.loginTimer.Stop()
client.Touch() client.Touch()
} }
@ -145,9 +152,7 @@ func (client *Client) destroy() {
client.socket.Close() client.socket.Close()
if DEBUG_CLIENT { Log.debug.Printf("%s: destroyed", client)
log.Printf("%s: destroyed", client)
}
} }
func (client *Client) IdleTime() time.Duration { func (client *Client) IdleTime() time.Duration {

View File

@ -88,25 +88,19 @@ func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) {
`SELECT nickname FROM client WHERE userhost LIKE ? ESCAPE '\'`, `SELECT nickname FROM client WHERE userhost LIKE ? ESCAPE '\'`,
QuoteLike(userhost)) QuoteLike(userhost))
if err != nil { if err != nil {
if DEBUG_SERVER { Log.error.Println("ClientLookupSet.FindAll.Query:", err)
log.Println("ClientLookupSet.FindAll.Query:", err)
}
return return
} }
for rows.Next() { for rows.Next() {
var nickname Name var nickname Name
err := rows.Scan(&nickname) err := rows.Scan(&nickname)
if err != nil { if err != nil {
if DEBUG_SERVER { Log.error.Println("ClientLookupSet.FindAll.Scan:", err)
log.Println("ClientLookupSet.FindAll.Scan:", err)
}
return return
} }
client := clients.Get(nickname) client := clients.Get(nickname)
if client == nil { if client == nil {
if DEBUG_SERVER { Log.error.Println("ClientLookupSet.FindAll: missing client:", nickname)
log.Println("ClientLookupSet.FindAll: missing client:", nickname)
}
continue continue
} }
set.Add(client) set.Add(client)
@ -122,9 +116,7 @@ func (clients *ClientLookupSet) Find(userhost Name) *Client {
var nickname Name var nickname Name
err := row.Scan(&nickname) err := row.Scan(&nickname)
if err != nil { if err != nil {
if DEBUG_SERVER { Log.error.Println("ClientLookupSet.Find:", err)
log.Println("ClientLookupSet.Find:", err)
}
return nil return nil
} }
return clients.Get(nickname) return clients.Get(nickname)
@ -161,21 +153,17 @@ func NewClientDB() *ClientDB {
func (db *ClientDB) Add(client *Client) { func (db *ClientDB) Add(client *Client) {
_, err := db.db.Exec(`INSERT INTO client (nickname, userhost) VALUES (?, ?)`, _, err := db.db.Exec(`INSERT INTO client (nickname, userhost) VALUES (?, ?)`,
client.Nick(), client.UserHost()) client.Nick().String(), client.UserHost().String())
if err != nil { if err != nil {
if DEBUG_SERVER { Log.error.Println("ClientDB.Add:", err)
log.Println("ClientDB.Add:", err)
}
} }
} }
func (db *ClientDB) Remove(client *Client) { func (db *ClientDB) Remove(client *Client) {
_, err := db.db.Exec(`DELETE FROM client WHERE nickname = ?`, _, err := db.db.Exec(`DELETE FROM client WHERE nickname = ?`,
client.Nick()) client.Nick().String())
if err != nil { if err != nil {
if DEBUG_SERVER { Log.error.Println("ClientDB.Remove:", err)
log.Println("ClientDB.Remove:", err)
}
} }
} }

View File

@ -8,10 +8,11 @@ import (
"strings" "strings"
) )
type editableCommand interface { type Command interface {
Command Client() *Client
SetCode(StringCode) Code() StringCode
SetClient(*Client) SetClient(*Client)
SetCode(StringCode)
} }
type checkPasswordCommand interface { type checkPasswordCommand interface {
@ -19,7 +20,7 @@ type checkPasswordCommand interface {
CheckPassword() CheckPassword()
} }
type parseCommandFunc func([]string) (editableCommand, error) type parseCommandFunc func([]string) (Command, error)
var ( var (
NotEnoughArgsError = errors.New("not enough arguments") NotEnoughArgsError = errors.New("not enough arguments")
@ -78,7 +79,7 @@ func (command *BaseCommand) SetCode(code StringCode) {
command.code = code command.code = code
} }
func ParseCommand(line string) (cmd editableCommand, err error) { func ParseCommand(line string) (cmd Command, err error) {
code, args := ParseLine(line) code, args := ParseLine(line)
constructor := parseCommandFuncs[code] constructor := parseCommandFuncs[code]
if constructor == nil { if constructor == nil {
@ -154,7 +155,7 @@ func (cmd *PingCommand) String() string {
return fmt.Sprintf("PING(server=%s, server2=%s)", cmd.server, cmd.server2) return fmt.Sprintf("PING(server=%s, server2=%s)", cmd.server, cmd.server2)
} }
func NewPingCommand(args []string) (editableCommand, error) { func NewPingCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -179,7 +180,7 @@ func (cmd *PongCommand) String() string {
return fmt.Sprintf("PONG(server1=%s, server2=%s)", cmd.server1, cmd.server2) return fmt.Sprintf("PONG(server1=%s, server2=%s)", cmd.server1, cmd.server2)
} }
func NewPongCommand(args []string) (editableCommand, error) { func NewPongCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -216,7 +217,7 @@ func (cmd *PassCommand) CheckPassword() {
cmd.err = ComparePassword(cmd.hash, cmd.password) cmd.err = ComparePassword(cmd.hash, cmd.password)
} }
func NewPassCommand(args []string) (editableCommand, error) { func NewPassCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -236,7 +237,7 @@ func (m *NickCommand) String() string {
return fmt.Sprintf("NICK(nickname=%s)", m.nickname) return fmt.Sprintf("NICK(nickname=%s)", m.nickname)
} }
func NewNickCommand(args []string) (editableCommand, error) { func NewNickCommand(args []string) (Command, error) {
if len(args) != 1 { if len(args) != 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -286,7 +287,7 @@ func (cmd *RFC2812UserCommand) Flags() []UserMode {
return flags return flags
} }
func NewUserCommand(args []string) (editableCommand, error) { func NewUserCommand(args []string) (Command, error) {
if len(args) != 4 { if len(args) != 4 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -321,7 +322,7 @@ func (cmd *QuitCommand) String() string {
return fmt.Sprintf("QUIT(message=%s)", cmd.message) return fmt.Sprintf("QUIT(message=%s)", cmd.message)
} }
func NewQuitCommand(args []string) (editableCommand, error) { func NewQuitCommand(args []string) (Command, error) {
msg := &QuitCommand{} msg := &QuitCommand{}
if len(args) > 0 { if len(args) > 0 {
msg.message = NewText(args[0]) msg.message = NewText(args[0])
@ -341,7 +342,7 @@ func (cmd *JoinCommand) String() string {
return fmt.Sprintf("JOIN(channels=%s, zero=%t)", cmd.channels, cmd.zero) return fmt.Sprintf("JOIN(channels=%s, zero=%t)", cmd.channels, cmd.zero)
} }
func NewJoinCommand(args []string) (editableCommand, error) { func NewJoinCommand(args []string) (Command, error) {
msg := &JoinCommand{ msg := &JoinCommand{
channels: make(map[Name]Text), channels: make(map[Name]Text),
} }
@ -388,7 +389,7 @@ func (cmd *PartCommand) String() string {
return fmt.Sprintf("PART(channels=%s, message=%s)", cmd.channels, cmd.message) return fmt.Sprintf("PART(channels=%s, message=%s)", cmd.channels, cmd.message)
} }
func NewPartCommand(args []string) (editableCommand, error) { func NewPartCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -413,7 +414,7 @@ func (cmd *PrivMsgCommand) String() string {
return fmt.Sprintf("PRIVMSG(target=%s, message=%s)", cmd.target, cmd.message) return fmt.Sprintf("PRIVMSG(target=%s, message=%s)", cmd.target, cmd.message)
} }
func NewPrivMsgCommand(args []string) (editableCommand, error) { func NewPrivMsgCommand(args []string) (Command, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -436,7 +437,7 @@ func (cmd *TopicCommand) String() string {
return fmt.Sprintf("TOPIC(channel=%s, topic=%s)", cmd.channel, cmd.topic) return fmt.Sprintf("TOPIC(channel=%s, topic=%s)", cmd.channel, cmd.topic)
} }
func NewTopicCommand(args []string) (editableCommand, error) { func NewTopicCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -486,7 +487,7 @@ type ModeCommand struct {
} }
// MODE <nickname> *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) ) // MODE <nickname> *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )
func NewUserModeCommand(nickname Name, args []string) (editableCommand, error) { func NewUserModeCommand(nickname Name, args []string) (Command, error) {
cmd := &ModeCommand{ cmd := &ModeCommand{
nickname: nickname, nickname: nickname,
changes: make(ModeChanges, 0), changes: make(ModeChanges, 0),
@ -563,7 +564,7 @@ type ChannelModeCommand struct {
} }
// MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> ) // MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
func NewChannelModeCommand(channel Name, args []string) (editableCommand, error) { func NewChannelModeCommand(channel Name, args []string) (Command, error) {
cmd := &ChannelModeCommand{ cmd := &ChannelModeCommand{
channel: channel, channel: channel,
changes: make(ChannelModeChanges, 0), changes: make(ChannelModeChanges, 0),
@ -609,7 +610,7 @@ func (msg *ChannelModeCommand) String() string {
return fmt.Sprintf("MODE(channel=%s, changes=%s)", msg.channel, msg.changes) return fmt.Sprintf("MODE(channel=%s, changes=%s)", msg.channel, msg.changes)
} }
func NewModeCommand(args []string) (editableCommand, error) { func NewModeCommand(args []string) (Command, error) {
if len(args) == 0 { if len(args) == 0 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -629,7 +630,7 @@ type WhoisCommand struct {
} }
// WHOIS [ <target> ] <mask> *( "," <mask> ) // WHOIS [ <target> ] <mask> *( "," <mask> )
func NewWhoisCommand(args []string) (editableCommand, error) { func NewWhoisCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -661,7 +662,7 @@ type WhoCommand struct {
} }
// WHO [ <mask> [ "o" ] ] // WHO [ <mask> [ "o" ] ]
func NewWhoCommand(args []string) (editableCommand, error) { func NewWhoCommand(args []string) (Command, error) {
cmd := &WhoCommand{} cmd := &WhoCommand{}
if len(args) > 0 { if len(args) > 0 {
@ -693,7 +694,7 @@ func (msg *OperCommand) LoadPassword(server *Server) {
} }
// OPER <name> <password> // OPER <name> <password>
func NewOperCommand(args []string) (editableCommand, error) { func NewOperCommand(args []string) (Command, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -716,7 +717,7 @@ func (msg *CapCommand) String() string {
msg.subCommand, msg.capabilities) msg.subCommand, msg.capabilities)
} }
func NewCapCommand(args []string) (editableCommand, error) { func NewCapCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -750,7 +751,7 @@ func (msg *ProxyCommand) String() string {
return fmt.Sprintf("PROXY(sourceIP=%s, sourcePort=%s)", msg.sourceIP, msg.sourcePort) return fmt.Sprintf("PROXY(sourceIP=%s, sourcePort=%s)", msg.sourceIP, msg.sourcePort)
} }
func NewProxyCommand(args []string) (editableCommand, error) { func NewProxyCommand(args []string) (Command, error) {
if len(args) < 5 { if len(args) < 5 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -774,7 +775,7 @@ func (msg *AwayCommand) String() string {
return fmt.Sprintf("AWAY(%s)", msg.text) return fmt.Sprintf("AWAY(%s)", msg.text)
} }
func NewAwayCommand(args []string) (editableCommand, error) { func NewAwayCommand(args []string) (Command, error) {
cmd := &AwayCommand{} cmd := &AwayCommand{}
if len(args) > 0 { if len(args) > 0 {
@ -794,7 +795,7 @@ func (msg *IsOnCommand) String() string {
return fmt.Sprintf("ISON(nicks=%s)", msg.nicks) return fmt.Sprintf("ISON(nicks=%s)", msg.nicks)
} }
func NewIsOnCommand(args []string) (editableCommand, error) { func NewIsOnCommand(args []string) (Command, error) {
if len(args) == 0 { if len(args) == 0 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -809,7 +810,7 @@ type MOTDCommand struct {
target Name target Name
} }
func NewMOTDCommand(args []string) (editableCommand, error) { func NewMOTDCommand(args []string) (Command, error) {
cmd := &MOTDCommand{} cmd := &MOTDCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.target = NewName(args[0]) cmd.target = NewName(args[0])
@ -827,7 +828,7 @@ func (cmd *NoticeCommand) String() string {
return fmt.Sprintf("NOTICE(target=%s, message=%s)", cmd.target, cmd.message) return fmt.Sprintf("NOTICE(target=%s, message=%s)", cmd.target, cmd.message)
} }
func NewNoticeCommand(args []string) (editableCommand, error) { func NewNoticeCommand(args []string) (Command, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -850,7 +851,7 @@ func (msg *KickCommand) Comment() Text {
return msg.comment return msg.comment
} }
func NewKickCommand(args []string) (editableCommand, error) { func NewKickCommand(args []string) (Command, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -881,7 +882,7 @@ type ListCommand struct {
target Name target Name
} }
func NewListCommand(args []string) (editableCommand, error) { func NewListCommand(args []string) (Command, error) {
cmd := &ListCommand{} cmd := &ListCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.channels = NewNames(strings.Split(args[0], ",")) cmd.channels = NewNames(strings.Split(args[0], ","))
@ -898,7 +899,7 @@ type NamesCommand struct {
target Name target Name
} }
func NewNamesCommand(args []string) (editableCommand, error) { func NewNamesCommand(args []string) (Command, error) {
cmd := &NamesCommand{} cmd := &NamesCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.channels = NewNames(strings.Split(args[0], ",")) cmd.channels = NewNames(strings.Split(args[0], ","))
@ -914,7 +915,7 @@ type DebugCommand struct {
subCommand Name subCommand Name
} }
func NewDebugCommand(args []string) (editableCommand, error) { func NewDebugCommand(args []string) (Command, error) {
if len(args) == 0 { if len(args) == 0 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -929,7 +930,7 @@ type VersionCommand struct {
target Name target Name
} }
func NewVersionCommand(args []string) (editableCommand, error) { func NewVersionCommand(args []string) (Command, error) {
cmd := &VersionCommand{} cmd := &VersionCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.target = NewName(args[0]) cmd.target = NewName(args[0])
@ -943,7 +944,7 @@ type InviteCommand struct {
channel Name channel Name
} }
func NewInviteCommand(args []string) (editableCommand, error) { func NewInviteCommand(args []string) (Command, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -959,7 +960,7 @@ type TimeCommand struct {
target Name target Name
} }
func NewTimeCommand(args []string) (editableCommand, error) { func NewTimeCommand(args []string) (Command, error) {
cmd := &TimeCommand{} cmd := &TimeCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.target = NewName(args[0]) cmd.target = NewName(args[0])
@ -973,7 +974,7 @@ type KillCommand struct {
comment Text comment Text
} }
func NewKillCommand(args []string) (editableCommand, error) { func NewKillCommand(args []string) (Command, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
@ -990,7 +991,7 @@ type WhoWasCommand struct {
target Name target Name
} }
func NewWhoWasCommand(args []string) (editableCommand, error) { func NewWhoWasCommand(args []string) (Command, error) {
if len(args) < 1 { if len(args) < 1 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }

View File

@ -23,18 +23,12 @@ type Config struct {
PassConfig PassConfig
Database string Database string
Listen []string Listen []string
Log string
MOTD string MOTD string
Name string Name string
} }
Operator map[string]*PassConfig Operator map[string]*PassConfig
Debug struct {
Net bool
Client bool
Channel bool
Server bool
}
} }
func (conf *Config) Operators() map[Name][]byte { func (conf *Config) Operators() map[Name][]byte {

View File

@ -1,30 +1,10 @@
package irc package irc
import (
"errors"
"time"
)
var (
// debugging flags
DEBUG_NET = false
DEBUG_CLIENT = false
DEBUG_CHANNEL = false
DEBUG_SERVER = false
// errors
ErrAlreadyDestroyed = errors.New("already destroyed")
)
const ( const (
SEM_VER = "ergonomadic-1.3.1" SEM_VER = "ergonomadic-1.3.1"
CRLF = "\r\n" CRLF = "\r\n"
MAX_REPLY_LEN = 512 - len(CRLF) MAX_REPLY_LEN = 512 - len(CRLF)
LOGIN_TIMEOUT = time.Minute / 2 // how long the client has to login
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
// string codes // string codes
AWAY StringCode = "AWAY" AWAY StringCode = "AWAY"
CAP StringCode = "CAP" CAP StringCode = "CAP"
@ -195,67 +175,4 @@ const (
ERR_NOOPERHOST NumericCode = 491 ERR_NOOPERHOST NumericCode = 491
ERR_UMODEUNKNOWNFLAG NumericCode = 501 ERR_UMODEUNKNOWNFLAG NumericCode = 501
ERR_USERSDONTMATCH NumericCode = 502 ERR_USERSDONTMATCH NumericCode = 502
CAP_LS CapSubCommand = "LS"
CAP_LIST CapSubCommand = "LIST"
CAP_REQ CapSubCommand = "REQ"
CAP_ACK CapSubCommand = "ACK"
CAP_NAK CapSubCommand = "NAK"
CAP_CLEAR CapSubCommand = "CLEAR"
CAP_END CapSubCommand = "END"
Add ModeOp = '+'
List ModeOp = '='
Remove ModeOp = '-'
Away UserMode = 'a'
Invisible UserMode = 'i'
LocalOperator UserMode = 'O'
Operator UserMode = 'o'
Restricted UserMode = 'r'
ServerNotice UserMode = 's' // deprecated
WallOps UserMode = 'w'
Anonymous ChannelMode = 'a' // flag
BanMask ChannelMode = 'b' // arg
ChannelCreator ChannelMode = 'O' // flag
ChannelOperator ChannelMode = 'o' // arg
ExceptMask ChannelMode = 'e' // arg
InviteMask ChannelMode = 'I' // arg
InviteOnly ChannelMode = 'i' // flag
Key ChannelMode = 'k' // flag arg
Moderated ChannelMode = 'm' // flag
NoOutside ChannelMode = 'n' // flag
OpOnlyTopic ChannelMode = 't' // flag
Persistent ChannelMode = 'P' // flag
Private ChannelMode = 'p' // flag
Quiet ChannelMode = 'q' // flag
ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag, deprecated
UserLimit ChannelMode = 'l' // flag arg
Voice ChannelMode = 'v' // arg
MultiPrefix Capability = "multi-prefix"
SASL Capability = "sasl"
Disable CapModifier = '-'
Ack CapModifier = '~'
Sticky CapModifier = '='
)
var (
SupportedCapabilities = CapabilitySet{
MultiPrefix: true,
}
)
const (
Registration Phase = iota
Normal Phase = iota
)
const (
CapNone CapState = iota
CapNegotiating CapState = iota
CapNegotiated CapState = iota
) )

60
irc/logging.go Normal file
View File

@ -0,0 +1,60 @@
package irc
import (
"io"
"log"
"os"
)
type Logging struct {
debug *log.Logger
info *log.Logger
warn *log.Logger
error *log.Logger
}
var (
levels = map[string]uint8{
"debug": 4,
"info": 3,
"warn": 2,
"error": 1,
}
devNull io.Writer
)
func init() {
var err error
devNull, err = os.Open(os.DevNull)
if err != nil {
log.Fatal(err)
}
}
func NewLogger(on bool) *log.Logger {
return log.New(output(on), "", log.LstdFlags)
}
func output(on bool) io.Writer {
if on {
return os.Stdout
}
return devNull
}
func (logging *Logging) SetLevel(level string) {
logging.debug = NewLogger(levels[level] >= levels["debug"])
logging.info = NewLogger(levels[level] >= levels["info"])
logging.warn = NewLogger(levels[level] >= levels["warn"])
logging.error = NewLogger(levels[level] >= levels["error"])
}
func NewLogging(level string) *Logging {
logging := &Logging{}
logging.SetLevel(level)
return logging
}
var (
Log = NewLogging("warn")
)

162
irc/modes.go Normal file
View File

@ -0,0 +1,162 @@
package irc
import (
"strings"
)
// user mode flags
type UserMode rune
func (mode UserMode) String() string {
return string(mode)
}
type UserModes []UserMode
func (modes UserModes) String() string {
strs := make([]string, len(modes))
for index, mode := range modes {
strs[index] = mode.String()
}
return strings.Join(strs, "")
}
// channel mode flags
type ChannelMode rune
func (mode ChannelMode) String() string {
return string(mode)
}
type ChannelModes []ChannelMode
func (modes ChannelModes) String() string {
strs := make([]string, len(modes))
for index, mode := range modes {
strs[index] = mode.String()
}
return strings.Join(strs, "")
}
type ModeOp rune
func (op ModeOp) String() string {
return string(op)
}
const (
Add ModeOp = '+'
List ModeOp = '='
Remove ModeOp = '-'
)
const (
Away UserMode = 'a'
Invisible UserMode = 'i'
LocalOperator UserMode = 'O'
Operator UserMode = 'o'
Restricted UserMode = 'r'
ServerNotice UserMode = 's' // deprecated
WallOps UserMode = 'w'
)
var (
SupportedUserModes = UserModes{
Away, Invisible, Operator,
}
)
const (
Anonymous ChannelMode = 'a' // flag
BanMask ChannelMode = 'b' // arg
ChannelCreator ChannelMode = 'O' // flag
ChannelOperator ChannelMode = 'o' // arg
ExceptMask ChannelMode = 'e' // arg
InviteMask ChannelMode = 'I' // arg
InviteOnly ChannelMode = 'i' // flag
Key ChannelMode = 'k' // flag arg
Moderated ChannelMode = 'm' // flag
NoOutside ChannelMode = 'n' // flag
OpOnlyTopic ChannelMode = 't' // flag
Persistent ChannelMode = 'P' // flag
Private ChannelMode = 'p' // flag
Quiet ChannelMode = 'q' // flag
ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag, deprecated
UserLimit ChannelMode = 'l' // flag arg
Voice ChannelMode = 'v' // arg
)
var (
SupportedChannelModes = ChannelModes{
BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside,
OpOnlyTopic, Persistent, Private, UserLimit,
}
)
//
// commands
//
func (m *ModeCommand) HandleServer(s *Server) {
client := m.Client()
target := s.clients.Get(m.nickname)
if target == nil {
client.ErrNoSuchNick(m.nickname)
return
}
if client != target && !client.flags[Operator] {
client.ErrUsersDontMatch()
return
}
changes := make(ModeChanges, 0, len(m.changes))
for _, change := range m.changes {
switch change.mode {
case Invisible, ServerNotice, WallOps:
switch change.op {
case Add:
if target.flags[change.mode] {
continue
}
target.flags[change.mode] = true
changes = append(changes, change)
case Remove:
if !target.flags[change.mode] {
continue
}
delete(target.flags, change.mode)
changes = append(changes, change)
}
case Operator, LocalOperator:
if change.op == Remove {
if !target.flags[change.mode] {
continue
}
delete(target.flags, change.mode)
changes = append(changes, change)
}
}
}
// Who should get these replies?
if len(changes) > 0 {
client.Reply(RplMode(client, target, changes))
}
}
func (msg *ChannelModeCommand) HandleServer(server *Server) {
client := msg.Client()
channel := server.channels.Get(msg.channel)
if channel == nil {
client.ErrNoSuchChannel(msg.channel)
return
}
channel.Mode(client, msg.changes)
}

View File

@ -6,7 +6,23 @@ import (
"time" "time"
) )
func NewStringReply(source Identifier, code StringCode, type ReplyCode interface {
String() string
}
type StringCode string
func (code StringCode) String() string {
return string(code)
}
type NumericCode uint
func (code NumericCode) String() string {
return fmt.Sprintf("%03d", code)
}
func NewStringReply(source Identifiable, code StringCode,
format string, args ...interface{}) string { format string, args ...interface{}) string {
var header string var header string
if source == nil { if source == nil {
@ -79,15 +95,15 @@ func (target *Client) MultilineReply(names []string, code NumericCode, format st
// messaging replies // messaging replies
// //
func RplPrivMsg(source Identifier, target Identifier, message Text) string { func RplPrivMsg(source Identifiable, target Identifiable, message Text) string {
return NewStringReply(source, PRIVMSG, "%s :%s", target.Nick(), message) return NewStringReply(source, PRIVMSG, "%s :%s", target.Nick(), message)
} }
func RplNotice(source Identifier, target Identifier, message Text) string { func RplNotice(source Identifiable, target Identifiable, message Text) string {
return NewStringReply(source, NOTICE, "%s :%s", target.Nick(), message) return NewStringReply(source, NOTICE, "%s :%s", target.Nick(), message)
} }
func RplNick(source Identifier, newNick Name) string { func RplNick(source Identifiable, newNick Name) string {
return NewStringReply(source, NICK, newNick.String()) return NewStringReply(source, NICK, newNick.String())
} }
@ -108,11 +124,11 @@ func RplChannelMode(client *Client, channel *Channel,
return NewStringReply(client, MODE, "%s %s", channel, changes) return NewStringReply(client, MODE, "%s %s", channel, changes)
} }
func RplTopicMsg(source Identifier, channel *Channel) string { func RplTopicMsg(source Identifiable, channel *Channel) string {
return NewStringReply(source, TOPIC, "%s :%s", channel, channel.topic) return NewStringReply(source, TOPIC, "%s :%s", channel, channel.topic)
} }
func RplPing(target Identifier) string { func RplPing(target Identifiable) string {
return NewStringReply(nil, PING, ":%s", target.Nick()) return NewStringReply(nil, PING, ":%s", target.Nick())
} }
@ -165,7 +181,8 @@ func (target *Client) RplCreated() {
func (target *Client) RplMyInfo() { func (target *Client) RplMyInfo() {
target.NumericReply(RPL_MYINFO, target.NumericReply(RPL_MYINFO,
"%s %s aiOorsw abeIikmntpqrsl", target.server.name, SEM_VER) "%s %s %s %s",
target.server.name, SEM_VER, SupportedUserModes, SupportedChannelModes)
} }
func (target *Client) RplUModeIs(client *Client) { func (target *Client) RplUModeIs(client *Client) {

View File

@ -16,6 +16,16 @@ import (
"time" "time"
) )
type ServerCommand interface {
Command
HandleServer(*Server)
}
type RegServerCommand interface {
Command
HandleRegServer(*Server)
}
type Server struct { type Server struct {
channels ChannelNameMap channels ChannelNameMap
clients *ClientLookupSet clients *ClientLookupSet
@ -32,19 +42,24 @@ type Server struct {
whoWas *WhoWasList whoWas *WhoWasList
} }
var (
SERVER_SIGNALS = []os.Signal{syscall.SIGINT, syscall.SIGHUP,
syscall.SIGTERM, syscall.SIGQUIT}
)
func NewServer(config *Config) *Server { func NewServer(config *Config) *Server {
server := &Server{ server := &Server{
channels: make(ChannelNameMap), channels: make(ChannelNameMap),
clients: NewClientLookupSet(), clients: NewClientLookupSet(),
commands: make(chan Command, 16), commands: make(chan Command),
ctime: time.Now(), ctime: time.Now(),
db: OpenDB(config.Server.Database), db: OpenDB(config.Server.Database),
idle: make(chan *Client, 16), idle: make(chan *Client),
motdFile: config.Server.MOTD, motdFile: config.Server.MOTD,
name: NewName(config.Server.Name), name: NewName(config.Server.Name),
newConns: make(chan net.Conn, 16), newConns: make(chan net.Conn),
operators: config.Operators(), operators: config.Operators(),
signals: make(chan os.Signal, 1), signals: make(chan os.Signal, len(SERVER_SIGNALS)),
whoWas: NewWhoWasList(100), whoWas: NewWhoWasList(100),
} }
@ -58,8 +73,7 @@ func NewServer(config *Config) *Server {
go server.listen(addr) go server.listen(addr)
} }
signal.Notify(server.signals, syscall.SIGINT, syscall.SIGHUP, signal.Notify(server.signals, SERVER_SIGNALS...)
syscall.SIGTERM, syscall.SIGQUIT)
return server return server
} }
@ -80,9 +94,7 @@ func (server *Server) loadChannels() {
log.Fatal("error loading channels: ", err) log.Fatal("error loading channels: ", err)
} }
for rows.Next() { for rows.Next() {
var name Name var name, flags, key, topic string
var flags string
var key, topic Text
var userLimit uint64 var userLimit uint64
var banList, exceptList, inviteList string var banList, exceptList, inviteList string
err = rows.Scan(&name, &flags, &key, &topic, &userLimit, &banList, err = rows.Scan(&name, &flags, &key, &topic, &userLimit, &banList,
@ -92,12 +104,12 @@ func (server *Server) loadChannels() {
continue continue
} }
channel := NewChannel(server, name) channel := NewChannel(server, NewName(name))
for _, flag := range flags { for _, flag := range flags {
channel.flags[ChannelMode(flag)] = true channel.flags[ChannelMode(flag)] = true
} }
channel.key = key channel.key = NewText(key)
channel.topic = topic channel.topic = NewText(topic)
channel.userLimit = userLimit channel.userLimit = userLimit
loadChannelList(channel, banList, BanMask) loadChannelList(channel, banList, BanMask)
loadChannelList(channel, exceptList, ExceptMask) loadChannelList(channel, exceptList, ExceptMask)
@ -107,20 +119,18 @@ func (server *Server) loadChannels() {
func (server *Server) processCommand(cmd Command) { func (server *Server) processCommand(cmd Command) {
client := cmd.Client() client := cmd.Client()
if DEBUG_SERVER { Log.debug.Printf("%s → %s %s", client, server, cmd)
log.Printf("%s → %s %s", client, server, cmd)
}
switch client.phase { if !client.registered {
case Registration:
regCmd, ok := cmd.(RegServerCommand) regCmd, ok := cmd.(RegServerCommand)
if !ok { if !ok {
client.Quit("unexpected command") client.Quit("unexpected command")
return return
} }
regCmd.HandleRegServer(server) regCmd.HandleRegServer(server)
return
}
case Normal:
srvCmd, ok := cmd.(ServerCommand) srvCmd, ok := cmd.(ServerCommand)
if !ok { if !ok {
client.ErrUnknownCommand(cmd.Code()) client.ErrUnknownCommand(cmd.Code())
@ -138,7 +148,6 @@ func (server *Server) processCommand(cmd Command) {
client.Touch() client.Touch()
} }
srvCmd.HandleServer(server) srvCmd.HandleServer(server)
}
} }
func (server *Server) Shutdown() { func (server *Server) Shutdown() {
@ -178,21 +187,15 @@ func (s *Server) listen(addr string) {
log.Fatal(s, "listen error: ", err) log.Fatal(s, "listen error: ", err)
} }
if DEBUG_SERVER { Log.info.Printf("%s listening on %s", s, addr)
log.Printf("%s listening on %s", s, addr)
}
for { for {
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
if DEBUG_SERVER { Log.error.Printf("%s accept error: %s", s, err)
log.Printf("%s accept error: %s", s, err)
}
continue continue
} }
if DEBUG_SERVER { Log.debug.Printf("%s accept: %s", s, conn.RemoteAddr())
log.Printf("%s accept: %s", s, conn.RemoteAddr())
}
s.newConns <- conn s.newConns <- conn
} }
@ -203,14 +206,17 @@ func (s *Server) listen(addr string) {
// //
func (s *Server) tryRegister(c *Client) { func (s *Server) tryRegister(c *Client) {
if c.HasNick() && c.HasUsername() && (c.capState != CapNegotiating) { if c.registered || !c.HasNick() || !c.HasUsername() ||
(c.capState == CapNegotiating) {
return
}
c.Register() c.Register()
c.RplWelcome() c.RplWelcome()
c.RplYourHost() c.RplYourHost()
c.RplCreated() c.RplCreated()
c.RplMyInfo() c.RplMyInfo()
s.MOTD(c) s.MOTD(c)
}
} }
func (server *Server) MOTD(client *Client) { func (server *Server) MOTD(client *Client) {
@ -281,44 +287,6 @@ func (msg *ProxyCommand) HandleRegServer(server *Server) {
msg.Client().hostname = msg.hostname msg.Client().hostname = msg.hostname
} }
func (msg *CapCommand) HandleRegServer(server *Server) {
client := msg.Client()
switch msg.subCommand {
case CAP_LS:
client.capState = CapNegotiating
client.Reply(RplCap(client, CAP_LS, SupportedCapabilities))
case CAP_LIST:
client.Reply(RplCap(client, CAP_LIST, client.capabilities))
case CAP_REQ:
client.capState = CapNegotiating
for capability := range msg.capabilities {
if !SupportedCapabilities[capability] {
client.Reply(RplCap(client, CAP_NAK, msg.capabilities))
return
}
}
for capability := range msg.capabilities {
client.capabilities[capability] = true
}
client.Reply(RplCap(client, CAP_ACK, msg.capabilities))
case CAP_CLEAR:
reply := RplCap(client, CAP_ACK, client.capabilities.DisableString())
client.capabilities = make(CapabilitySet)
client.Reply(reply)
case CAP_END:
client.capState = CapNegotiated
server.tryRegister(client)
default:
client.ErrInvalidCapCmd(msg.subCommand)
}
}
func (m *NickCommand) HandleRegServer(s *Server) { func (m *NickCommand) HandleRegServer(s *Server) {
client := m.Client() client := m.Client()
if !client.authorized { if !client.authorized {
@ -369,7 +337,7 @@ func (msg *RFC2812UserCommand) HandleRegServer(server *Server) {
} }
flags := msg.Flags() flags := msg.Flags()
if len(flags) > 0 { if len(flags) > 0 {
for _, mode := range msg.Flags() { for _, mode := range flags {
client.flags[mode] = true client.flags[mode] = true
} }
client.RplUModeIs(client) client.RplUModeIs(client)
@ -521,58 +489,6 @@ func (msg *PrivMsgCommand) HandleServer(server *Server) {
} }
} }
func (m *ModeCommand) HandleServer(s *Server) {
client := m.Client()
target := s.clients.Get(m.nickname)
if target == nil {
client.ErrNoSuchNick(m.nickname)
return
}
if client != target && !client.flags[Operator] {
client.ErrUsersDontMatch()
return
}
changes := make(ModeChanges, 0, len(m.changes))
for _, change := range m.changes {
switch change.mode {
case Invisible, ServerNotice, WallOps:
switch change.op {
case Add:
if target.flags[change.mode] {
continue
}
target.flags[change.mode] = true
changes = append(changes, change)
case Remove:
if !target.flags[change.mode] {
continue
}
delete(target.flags, change.mode)
changes = append(changes, change)
}
case Operator, LocalOperator:
if change.op == Remove {
if !target.flags[change.mode] {
continue
}
delete(target.flags, change.mode)
changes = append(changes, change)
}
}
}
// Who should get these replies?
if len(changes) > 0 {
client.Reply(RplMode(client, target, changes))
}
}
func (client *Client) WhoisChannelsNames() []string { func (client *Client) WhoisChannelsNames() []string {
chstrs := make([]string, len(client.channels)) chstrs := make([]string, len(client.channels))
index := 0 index := 0
@ -609,17 +525,6 @@ func (m *WhoisCommand) HandleServer(server *Server) {
} }
} }
func (msg *ChannelModeCommand) HandleServer(server *Server) {
client := msg.Client()
channel := server.channels.Get(msg.channel)
if channel == nil {
client.ErrNoSuchChannel(msg.channel)
return
}
channel.Mode(client, msg.changes)
}
func whoChannel(client *Client, channel *Channel, friends ClientSet) { func whoChannel(client *Client, channel *Channel, friends ClientSet) {
for member := range channel.members { for member := range channel.members {
if !client.flags[Invisible] || friends[client] { if !client.flags[Invisible] || friends[client] {

View File

@ -3,7 +3,6 @@ package irc
import ( import (
"bufio" "bufio"
"io" "io"
"log"
"net" "net"
"strings" "strings"
) )
@ -20,7 +19,7 @@ type Socket struct {
writer *bufio.Writer writer *bufio.Writer
} }
func NewSocket(conn net.Conn, commands chan<- editableCommand) *Socket { func NewSocket(conn net.Conn, commands chan<- Command) *Socket {
socket := &Socket{ socket := &Socket{
conn: conn, conn: conn,
reader: bufio.NewReader(conn), reader: bufio.NewReader(conn),
@ -38,12 +37,10 @@ func (socket *Socket) String() string {
func (socket *Socket) Close() { func (socket *Socket) Close() {
socket.conn.Close() socket.conn.Close()
if DEBUG_NET { Log.debug.Printf("%s closed", socket)
log.Printf("%s closed", socket)
}
} }
func (socket *Socket) readLines(commands chan<- editableCommand) { func (socket *Socket) readLines(commands chan<- Command) {
commands <- &ProxyCommand{ commands <- &ProxyCommand{
hostname: AddrLookupHostname(socket.conn.RemoteAddr()), hostname: AddrLookupHostname(socket.conn.RemoteAddr()),
} }
@ -57,9 +54,7 @@ func (socket *Socket) readLines(commands chan<- editableCommand) {
if len(line) == 0 { if len(line) == 0 {
continue continue
} }
if DEBUG_NET { Log.debug.Printf("%s → %s", socket, line)
log.Printf("%s → %s", socket, line)
}
msg, err := ParseCommand(line) msg, err := ParseCommand(line)
if err != nil { if err != nil {
@ -87,16 +82,14 @@ func (socket *Socket) Write(line string) (err error) {
return return
} }
if DEBUG_NET { Log.debug.Printf("%s ← %s", socket, line)
log.Printf("%s ← %s", socket, line)
}
return return
} }
func (socket *Socket) isError(err error, dir rune) bool { func (socket *Socket) isError(err error, dir rune) bool {
if err != nil { if err != nil {
if DEBUG_NET && (err != io.EOF) { if err != io.EOF {
log.Printf("%s %c error: %s", socket, dir, err) Log.debug.Printf("%s %c error: %s", socket, dir, err)
} }
return true return true
} }

View File

@ -9,83 +9,6 @@ import (
// simple types // simple types
// //
type CapSubCommand string
type Capability string
func (capability Capability) String() string {
return string(capability)
}
type CapModifier rune
func (mod CapModifier) String() string {
return string(mod)
}
type CapState uint
type CapabilitySet map[Capability]bool
func (set CapabilitySet) String() string {
strs := make([]string, len(set))
index := 0
for capability := range set {
strs[index] = string(capability)
index += 1
}
return strings.Join(strs, " ")
}
func (set CapabilitySet) DisableString() string {
parts := make([]string, len(set))
index := 0
for capability := range set {
parts[index] = Disable.String() + capability.String()
index += 1
}
return strings.Join(parts, " ")
}
// add, remove, list modes
type ModeOp rune
func (op ModeOp) String() string {
return string(op)
}
// user mode flags
type UserMode rune
func (mode UserMode) String() string {
return string(mode)
}
type Phase uint
type ReplyCode interface {
String() string
}
type StringCode Name
func (code StringCode) String() string {
return string(code)
}
type NumericCode uint
func (code NumericCode) String() string {
return fmt.Sprintf("%03d", code)
}
// channel mode flags
type ChannelMode rune
func (mode ChannelMode) String() string {
return string(mode)
}
type ChannelNameMap map[Name]*Channel type ChannelNameMap map[Name]*Channel
func (channels ChannelNameMap) Get(name Name) *Channel { func (channels ChannelNameMap) Get(name Name) *Channel {
@ -181,31 +104,7 @@ func (channels ChannelSet) First() *Channel {
// interfaces // interfaces
// //
type Identifier interface { type Identifiable interface {
Id() Name Id() Name
Nick() Name Nick() Name
} }
type Replier interface {
Reply(...string)
}
type Command interface {
Code() StringCode
Client() *Client
}
type ServerCommand interface {
Command
HandleServer(*Server)
}
type AuthServerCommand interface {
Command
HandleAuthServer(*Server)
}
type RegServerCommand interface {
Command
HandleRegServer(*Server)
}