mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-23 04:19:25 +01:00
commit
5df8173df2
@ -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)
|
||||||
|
@ -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
|
|
||||||
|
@ -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
116
irc/capability.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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(`
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
60
irc/logging.go
Normal 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
162
irc/modes.go
Normal 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)
|
||||||
|
}
|
31
irc/reply.go
31
irc/reply.go
@ -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) {
|
||||||
|
169
irc/server.go
169
irc/server.go
@ -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] {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
103
irc/types.go
103
irc/types.go
@ -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)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user