mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
commit
5df8173df2
@ -38,7 +38,7 @@ hostname lookups.
|
||||
```sh
|
||||
go get
|
||||
go install
|
||||
ergonomadic -conf ergonomadic.conf -initdb
|
||||
ergonomadic initdb -conf ergonomadic.conf
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@ -48,16 +48,16 @@ bcrypted byte strings. You can generate them with the `genpasswd`
|
||||
subcommand.
|
||||
|
||||
```sh
|
||||
ergonomadic -genpasswd 'hunter2!'
|
||||
ergonomadic genpasswd 'hunter2!'
|
||||
```
|
||||
|
||||
## Running the Server
|
||||
|
||||
```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 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
|
||||
listen = "localhost:6667" ; see `net.Listen` for examples
|
||||
listen = "[::1]:6667" ; multiple `listen`s are allowed.
|
||||
log = "debug" ; error, warn, info, debug
|
||||
motd = "motd.txt" ; path relative to this file
|
||||
password = "JDJhJDA0JHJzVFFlNXdOUXNhLmtkSGRUQVVEVHVYWXRKUmdNQ3FKVTRrczRSMTlSWGRPZHRSMVRzQmtt" ; 'test'
|
||||
|
||||
[operator "root"]
|
||||
password = "JDJhJDA0JEhkcm10UlNFRkRXb25iOHZuSDVLZXVBWlpyY0xyNkQ4dlBVc1VMWVk1LlFjWFpQbGxZNUtl" ; 'toor'
|
||||
|
||||
[debug]
|
||||
net = true
|
||||
client = false
|
||||
channel = false
|
||||
server = false
|
||||
|
@ -9,51 +9,68 @@ import (
|
||||
"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() {
|
||||
conf := flag.String("conf", "ergonomadic.conf", "ergonomadic config file")
|
||||
initdb := flag.Bool("initdb", false, "initialize database")
|
||||
upgradedb := flag.Bool("upgradedb", false, "update database")
|
||||
passwd := flag.String("genpasswd", "", "bcrypt a password")
|
||||
var conf string
|
||||
flag.Usage = usage
|
||||
|
||||
runFlags := flag.NewFlagSet("run", flag.ExitOnError)
|
||||
runFlags.Usage = usage
|
||||
runFlags.StringVar(&conf, "conf", "ergonomadic.conf", "ergonomadic config file")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *passwd != "" {
|
||||
encoded, err := irc.GenerateEncodedPassword(*passwd)
|
||||
switch flag.Arg(0) {
|
||||
case "genpasswd":
|
||||
encoded, err := irc.GenerateEncodedPassword(flag.Arg(1))
|
||||
if err != nil {
|
||||
log.Fatal("encoding error: ", err)
|
||||
log.Fatalln("encoding error:", err)
|
||||
}
|
||||
fmt.Println(encoded)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := irc.LoadConfig(*conf)
|
||||
if err != nil {
|
||||
log.Fatal("error loading config: ", err)
|
||||
}
|
||||
err = os.Chdir(filepath.Dir(*conf))
|
||||
if err != nil {
|
||||
log.Fatal("chdir error: ", err)
|
||||
}
|
||||
|
||||
if *initdb {
|
||||
case "initdb":
|
||||
runFlags.Parse(flag.Args()[1:])
|
||||
config := loadConfig(conf)
|
||||
irc.InitDB(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)
|
||||
log.Println("database upgraded: ", config.Server.Database)
|
||||
return
|
||||
|
||||
default:
|
||||
runFlags.Parse(flag.Args()[1:])
|
||||
config := loadConfig(conf)
|
||||
irc.Log.SetLevel(config.Server.Log)
|
||||
server := irc.NewServer(config)
|
||||
log.Println(irc.SEM_VER, "running")
|
||||
defer log.Println(irc.SEM_VER, "exiting")
|
||||
server.Run()
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
server := irc.NewServer(config)
|
||||
log.Println(irc.SEM_VER, "running")
|
||||
defer log.Println(irc.SEM_VER, "exiting")
|
||||
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,
|
||||
invite_list)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
channel.name, channel.flags.String(), channel.key, channel.topic,
|
||||
channel.userLimit, channel.lists[BanMask].String(),
|
||||
channel.name.String(), channel.flags.String(), channel.key.String(),
|
||||
channel.topic.String(), channel.userLimit, channel.lists[BanMask].String(),
|
||||
channel.lists[ExceptMask].String(), channel.lists[InviteMask].String())
|
||||
} else {
|
||||
_, err = channel.server.db.Exec(`
|
||||
|
@ -2,11 +2,16 @@ package irc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"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 {
|
||||
atime time.Time
|
||||
authorized bool
|
||||
@ -14,7 +19,7 @@ type Client struct {
|
||||
capabilities CapabilitySet
|
||||
capState CapState
|
||||
channels ChannelSet
|
||||
commands chan editableCommand
|
||||
commands chan Command
|
||||
ctime time.Time
|
||||
flags map[UserMode]bool
|
||||
hasQuit bool
|
||||
@ -23,9 +28,9 @@ type Client struct {
|
||||
idleTimer *time.Timer
|
||||
loginTimer *time.Timer
|
||||
nick Name
|
||||
phase Phase
|
||||
quitTimer *time.Timer
|
||||
realname Text
|
||||
registered bool
|
||||
server *Server
|
||||
socket *Socket
|
||||
username Name
|
||||
@ -39,10 +44,9 @@ func NewClient(server *Server, conn net.Conn) *Client {
|
||||
capState: CapNone,
|
||||
capabilities: make(CapabilitySet),
|
||||
channels: make(ChannelSet),
|
||||
commands: make(chan editableCommand),
|
||||
commands: make(chan Command),
|
||||
ctime: now,
|
||||
flags: make(map[UserMode]bool),
|
||||
phase: Registration,
|
||||
server: server,
|
||||
}
|
||||
client.socket = NewSocket(conn, client.commands)
|
||||
@ -115,7 +119,10 @@ func (client *Client) Idle() {
|
||||
}
|
||||
|
||||
func (client *Client) Register() {
|
||||
client.phase = Normal
|
||||
if client.registered {
|
||||
return
|
||||
}
|
||||
client.registered = true
|
||||
client.loginTimer.Stop()
|
||||
client.Touch()
|
||||
}
|
||||
@ -145,9 +152,7 @@ func (client *Client) destroy() {
|
||||
|
||||
client.socket.Close()
|
||||
|
||||
if DEBUG_CLIENT {
|
||||
log.Printf("%s: destroyed", client)
|
||||
}
|
||||
Log.debug.Printf("%s: destroyed", client)
|
||||
}
|
||||
|
||||
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 '\'`,
|
||||
QuoteLike(userhost))
|
||||
if err != nil {
|
||||
if DEBUG_SERVER {
|
||||
log.Println("ClientLookupSet.FindAll.Query:", err)
|
||||
}
|
||||
Log.error.Println("ClientLookupSet.FindAll.Query:", err)
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
var nickname Name
|
||||
err := rows.Scan(&nickname)
|
||||
if err != nil {
|
||||
if DEBUG_SERVER {
|
||||
log.Println("ClientLookupSet.FindAll.Scan:", err)
|
||||
}
|
||||
Log.error.Println("ClientLookupSet.FindAll.Scan:", err)
|
||||
return
|
||||
}
|
||||
client := clients.Get(nickname)
|
||||
if client == nil {
|
||||
if DEBUG_SERVER {
|
||||
log.Println("ClientLookupSet.FindAll: missing client:", nickname)
|
||||
}
|
||||
Log.error.Println("ClientLookupSet.FindAll: missing client:", nickname)
|
||||
continue
|
||||
}
|
||||
set.Add(client)
|
||||
@ -122,9 +116,7 @@ func (clients *ClientLookupSet) Find(userhost Name) *Client {
|
||||
var nickname Name
|
||||
err := row.Scan(&nickname)
|
||||
if err != nil {
|
||||
if DEBUG_SERVER {
|
||||
log.Println("ClientLookupSet.Find:", err)
|
||||
}
|
||||
Log.error.Println("ClientLookupSet.Find:", err)
|
||||
return nil
|
||||
}
|
||||
return clients.Get(nickname)
|
||||
@ -161,21 +153,17 @@ func NewClientDB() *ClientDB {
|
||||
|
||||
func (db *ClientDB) Add(client *Client) {
|
||||
_, err := db.db.Exec(`INSERT INTO client (nickname, userhost) VALUES (?, ?)`,
|
||||
client.Nick(), client.UserHost())
|
||||
client.Nick().String(), client.UserHost().String())
|
||||
if err != nil {
|
||||
if DEBUG_SERVER {
|
||||
log.Println("ClientDB.Add:", err)
|
||||
}
|
||||
Log.error.Println("ClientDB.Add:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *ClientDB) Remove(client *Client) {
|
||||
_, err := db.db.Exec(`DELETE FROM client WHERE nickname = ?`,
|
||||
client.Nick())
|
||||
client.Nick().String())
|
||||
if err != nil {
|
||||
if DEBUG_SERVER {
|
||||
log.Println("ClientDB.Remove:", err)
|
||||
}
|
||||
Log.error.Println("ClientDB.Remove:", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type editableCommand interface {
|
||||
Command
|
||||
SetCode(StringCode)
|
||||
type Command interface {
|
||||
Client() *Client
|
||||
Code() StringCode
|
||||
SetClient(*Client)
|
||||
SetCode(StringCode)
|
||||
}
|
||||
|
||||
type checkPasswordCommand interface {
|
||||
@ -19,7 +20,7 @@ type checkPasswordCommand interface {
|
||||
CheckPassword()
|
||||
}
|
||||
|
||||
type parseCommandFunc func([]string) (editableCommand, error)
|
||||
type parseCommandFunc func([]string) (Command, error)
|
||||
|
||||
var (
|
||||
NotEnoughArgsError = errors.New("not enough arguments")
|
||||
@ -78,7 +79,7 @@ func (command *BaseCommand) SetCode(code StringCode) {
|
||||
command.code = code
|
||||
}
|
||||
|
||||
func ParseCommand(line string) (cmd editableCommand, err error) {
|
||||
func ParseCommand(line string) (cmd Command, err error) {
|
||||
code, args := ParseLine(line)
|
||||
constructor := parseCommandFuncs[code]
|
||||
if constructor == nil {
|
||||
@ -154,7 +155,7 @@ func (cmd *PingCommand) String() string {
|
||||
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 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -179,7 +180,7 @@ func (cmd *PongCommand) String() string {
|
||||
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 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -216,7 +217,7 @@ func (cmd *PassCommand) CheckPassword() {
|
||||
cmd.err = ComparePassword(cmd.hash, cmd.password)
|
||||
}
|
||||
|
||||
func NewPassCommand(args []string) (editableCommand, error) {
|
||||
func NewPassCommand(args []string) (Command, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -236,7 +237,7 @@ func (m *NickCommand) String() string {
|
||||
return fmt.Sprintf("NICK(nickname=%s)", m.nickname)
|
||||
}
|
||||
|
||||
func NewNickCommand(args []string) (editableCommand, error) {
|
||||
func NewNickCommand(args []string) (Command, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -286,7 +287,7 @@ func (cmd *RFC2812UserCommand) Flags() []UserMode {
|
||||
return flags
|
||||
}
|
||||
|
||||
func NewUserCommand(args []string) (editableCommand, error) {
|
||||
func NewUserCommand(args []string) (Command, error) {
|
||||
if len(args) != 4 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -321,7 +322,7 @@ func (cmd *QuitCommand) String() string {
|
||||
return fmt.Sprintf("QUIT(message=%s)", cmd.message)
|
||||
}
|
||||
|
||||
func NewQuitCommand(args []string) (editableCommand, error) {
|
||||
func NewQuitCommand(args []string) (Command, error) {
|
||||
msg := &QuitCommand{}
|
||||
if len(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)
|
||||
}
|
||||
|
||||
func NewJoinCommand(args []string) (editableCommand, error) {
|
||||
func NewJoinCommand(args []string) (Command, error) {
|
||||
msg := &JoinCommand{
|
||||
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)
|
||||
}
|
||||
|
||||
func NewPartCommand(args []string) (editableCommand, error) {
|
||||
func NewPartCommand(args []string) (Command, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -413,7 +414,7 @@ func (cmd *PrivMsgCommand) String() string {
|
||||
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 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -436,7 +437,7 @@ func (cmd *TopicCommand) String() string {
|
||||
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 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -486,7 +487,7 @@ type ModeCommand struct {
|
||||
}
|
||||
|
||||
// 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{
|
||||
nickname: nickname,
|
||||
changes: make(ModeChanges, 0),
|
||||
@ -563,7 +564,7 @@ type ChannelModeCommand struct {
|
||||
}
|
||||
|
||||
// MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
|
||||
func NewChannelModeCommand(channel Name, args []string) (editableCommand, error) {
|
||||
func NewChannelModeCommand(channel Name, args []string) (Command, error) {
|
||||
cmd := &ChannelModeCommand{
|
||||
channel: channel,
|
||||
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)
|
||||
}
|
||||
|
||||
func NewModeCommand(args []string) (editableCommand, error) {
|
||||
func NewModeCommand(args []string) (Command, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -629,7 +630,7 @@ type WhoisCommand struct {
|
||||
}
|
||||
|
||||
// WHOIS [ <target> ] <mask> *( "," <mask> )
|
||||
func NewWhoisCommand(args []string) (editableCommand, error) {
|
||||
func NewWhoisCommand(args []string) (Command, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -661,7 +662,7 @@ type WhoCommand struct {
|
||||
}
|
||||
|
||||
// WHO [ <mask> [ "o" ] ]
|
||||
func NewWhoCommand(args []string) (editableCommand, error) {
|
||||
func NewWhoCommand(args []string) (Command, error) {
|
||||
cmd := &WhoCommand{}
|
||||
|
||||
if len(args) > 0 {
|
||||
@ -693,7 +694,7 @@ func (msg *OperCommand) LoadPassword(server *Server) {
|
||||
}
|
||||
|
||||
// OPER <name> <password>
|
||||
func NewOperCommand(args []string) (editableCommand, error) {
|
||||
func NewOperCommand(args []string) (Command, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -716,7 +717,7 @@ func (msg *CapCommand) String() string {
|
||||
msg.subCommand, msg.capabilities)
|
||||
}
|
||||
|
||||
func NewCapCommand(args []string) (editableCommand, error) {
|
||||
func NewCapCommand(args []string) (Command, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -750,7 +751,7 @@ func (msg *ProxyCommand) String() string {
|
||||
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 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -774,7 +775,7 @@ func (msg *AwayCommand) String() string {
|
||||
return fmt.Sprintf("AWAY(%s)", msg.text)
|
||||
}
|
||||
|
||||
func NewAwayCommand(args []string) (editableCommand, error) {
|
||||
func NewAwayCommand(args []string) (Command, error) {
|
||||
cmd := &AwayCommand{}
|
||||
|
||||
if len(args) > 0 {
|
||||
@ -794,7 +795,7 @@ func (msg *IsOnCommand) String() string {
|
||||
return fmt.Sprintf("ISON(nicks=%s)", msg.nicks)
|
||||
}
|
||||
|
||||
func NewIsOnCommand(args []string) (editableCommand, error) {
|
||||
func NewIsOnCommand(args []string) (Command, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -809,7 +810,7 @@ type MOTDCommand struct {
|
||||
target Name
|
||||
}
|
||||
|
||||
func NewMOTDCommand(args []string) (editableCommand, error) {
|
||||
func NewMOTDCommand(args []string) (Command, error) {
|
||||
cmd := &MOTDCommand{}
|
||||
if len(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)
|
||||
}
|
||||
|
||||
func NewNoticeCommand(args []string) (editableCommand, error) {
|
||||
func NewNoticeCommand(args []string) (Command, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -850,7 +851,7 @@ func (msg *KickCommand) Comment() Text {
|
||||
return msg.comment
|
||||
}
|
||||
|
||||
func NewKickCommand(args []string) (editableCommand, error) {
|
||||
func NewKickCommand(args []string) (Command, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -881,7 +882,7 @@ type ListCommand struct {
|
||||
target Name
|
||||
}
|
||||
|
||||
func NewListCommand(args []string) (editableCommand, error) {
|
||||
func NewListCommand(args []string) (Command, error) {
|
||||
cmd := &ListCommand{}
|
||||
if len(args) > 0 {
|
||||
cmd.channels = NewNames(strings.Split(args[0], ","))
|
||||
@ -898,7 +899,7 @@ type NamesCommand struct {
|
||||
target Name
|
||||
}
|
||||
|
||||
func NewNamesCommand(args []string) (editableCommand, error) {
|
||||
func NewNamesCommand(args []string) (Command, error) {
|
||||
cmd := &NamesCommand{}
|
||||
if len(args) > 0 {
|
||||
cmd.channels = NewNames(strings.Split(args[0], ","))
|
||||
@ -914,7 +915,7 @@ type DebugCommand struct {
|
||||
subCommand Name
|
||||
}
|
||||
|
||||
func NewDebugCommand(args []string) (editableCommand, error) {
|
||||
func NewDebugCommand(args []string) (Command, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -929,7 +930,7 @@ type VersionCommand struct {
|
||||
target Name
|
||||
}
|
||||
|
||||
func NewVersionCommand(args []string) (editableCommand, error) {
|
||||
func NewVersionCommand(args []string) (Command, error) {
|
||||
cmd := &VersionCommand{}
|
||||
if len(args) > 0 {
|
||||
cmd.target = NewName(args[0])
|
||||
@ -943,7 +944,7 @@ type InviteCommand struct {
|
||||
channel Name
|
||||
}
|
||||
|
||||
func NewInviteCommand(args []string) (editableCommand, error) {
|
||||
func NewInviteCommand(args []string) (Command, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -959,7 +960,7 @@ type TimeCommand struct {
|
||||
target Name
|
||||
}
|
||||
|
||||
func NewTimeCommand(args []string) (editableCommand, error) {
|
||||
func NewTimeCommand(args []string) (Command, error) {
|
||||
cmd := &TimeCommand{}
|
||||
if len(args) > 0 {
|
||||
cmd.target = NewName(args[0])
|
||||
@ -973,7 +974,7 @@ type KillCommand struct {
|
||||
comment Text
|
||||
}
|
||||
|
||||
func NewKillCommand(args []string) (editableCommand, error) {
|
||||
func NewKillCommand(args []string) (Command, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
@ -990,7 +991,7 @@ type WhoWasCommand struct {
|
||||
target Name
|
||||
}
|
||||
|
||||
func NewWhoWasCommand(args []string) (editableCommand, error) {
|
||||
func NewWhoWasCommand(args []string) (Command, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, NotEnoughArgsError
|
||||
}
|
||||
|
@ -23,18 +23,12 @@ type Config struct {
|
||||
PassConfig
|
||||
Database string
|
||||
Listen []string
|
||||
Log string
|
||||
MOTD string
|
||||
Name string
|
||||
}
|
||||
|
||||
Operator map[string]*PassConfig
|
||||
|
||||
Debug struct {
|
||||
Net bool
|
||||
Client bool
|
||||
Channel bool
|
||||
Server bool
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *Config) Operators() map[Name][]byte {
|
||||
|
@ -1,30 +1,10 @@
|
||||
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 (
|
||||
SEM_VER = "ergonomadic-1.3.1"
|
||||
CRLF = "\r\n"
|
||||
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
|
||||
AWAY StringCode = "AWAY"
|
||||
CAP StringCode = "CAP"
|
||||
@ -195,67 +175,4 @@ const (
|
||||
ERR_NOOPERHOST NumericCode = 491
|
||||
ERR_UMODEUNKNOWNFLAG NumericCode = 501
|
||||
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"
|
||||
)
|
||||
|
||||
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 {
|
||||
var header string
|
||||
if source == nil {
|
||||
@ -79,15 +95,15 @@ func (target *Client) MultilineReply(names []string, code NumericCode, format st
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func RplNick(source Identifier, newNick Name) string {
|
||||
func RplNick(source Identifiable, newNick Name) 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)
|
||||
}
|
||||
|
||||
func RplTopicMsg(source Identifier, channel *Channel) string {
|
||||
func RplTopicMsg(source Identifiable, channel *Channel) string {
|
||||
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())
|
||||
}
|
||||
|
||||
@ -165,7 +181,8 @@ func (target *Client) RplCreated() {
|
||||
|
||||
func (target *Client) RplMyInfo() {
|
||||
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) {
|
||||
|
213
irc/server.go
213
irc/server.go
@ -16,6 +16,16 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ServerCommand interface {
|
||||
Command
|
||||
HandleServer(*Server)
|
||||
}
|
||||
|
||||
type RegServerCommand interface {
|
||||
Command
|
||||
HandleRegServer(*Server)
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
channels ChannelNameMap
|
||||
clients *ClientLookupSet
|
||||
@ -32,19 +42,24 @@ type Server struct {
|
||||
whoWas *WhoWasList
|
||||
}
|
||||
|
||||
var (
|
||||
SERVER_SIGNALS = []os.Signal{syscall.SIGINT, syscall.SIGHUP,
|
||||
syscall.SIGTERM, syscall.SIGQUIT}
|
||||
)
|
||||
|
||||
func NewServer(config *Config) *Server {
|
||||
server := &Server{
|
||||
channels: make(ChannelNameMap),
|
||||
clients: NewClientLookupSet(),
|
||||
commands: make(chan Command, 16),
|
||||
commands: make(chan Command),
|
||||
ctime: time.Now(),
|
||||
db: OpenDB(config.Server.Database),
|
||||
idle: make(chan *Client, 16),
|
||||
idle: make(chan *Client),
|
||||
motdFile: config.Server.MOTD,
|
||||
name: NewName(config.Server.Name),
|
||||
newConns: make(chan net.Conn, 16),
|
||||
newConns: make(chan net.Conn),
|
||||
operators: config.Operators(),
|
||||
signals: make(chan os.Signal, 1),
|
||||
signals: make(chan os.Signal, len(SERVER_SIGNALS)),
|
||||
whoWas: NewWhoWasList(100),
|
||||
}
|
||||
|
||||
@ -58,8 +73,7 @@ func NewServer(config *Config) *Server {
|
||||
go server.listen(addr)
|
||||
}
|
||||
|
||||
signal.Notify(server.signals, syscall.SIGINT, syscall.SIGHUP,
|
||||
syscall.SIGTERM, syscall.SIGQUIT)
|
||||
signal.Notify(server.signals, SERVER_SIGNALS...)
|
||||
|
||||
return server
|
||||
}
|
||||
@ -80,9 +94,7 @@ func (server *Server) loadChannels() {
|
||||
log.Fatal("error loading channels: ", err)
|
||||
}
|
||||
for rows.Next() {
|
||||
var name Name
|
||||
var flags string
|
||||
var key, topic Text
|
||||
var name, flags, key, topic string
|
||||
var userLimit uint64
|
||||
var banList, exceptList, inviteList string
|
||||
err = rows.Scan(&name, &flags, &key, &topic, &userLimit, &banList,
|
||||
@ -92,12 +104,12 @@ func (server *Server) loadChannels() {
|
||||
continue
|
||||
}
|
||||
|
||||
channel := NewChannel(server, name)
|
||||
channel := NewChannel(server, NewName(name))
|
||||
for _, flag := range flags {
|
||||
channel.flags[ChannelMode(flag)] = true
|
||||
}
|
||||
channel.key = key
|
||||
channel.topic = topic
|
||||
channel.key = NewText(key)
|
||||
channel.topic = NewText(topic)
|
||||
channel.userLimit = userLimit
|
||||
loadChannelList(channel, banList, BanMask)
|
||||
loadChannelList(channel, exceptList, ExceptMask)
|
||||
@ -107,38 +119,35 @@ func (server *Server) loadChannels() {
|
||||
|
||||
func (server *Server) processCommand(cmd Command) {
|
||||
client := cmd.Client()
|
||||
if DEBUG_SERVER {
|
||||
log.Printf("%s → %s %s", client, server, cmd)
|
||||
}
|
||||
Log.debug.Printf("%s → %s %s", client, server, cmd)
|
||||
|
||||
switch client.phase {
|
||||
case Registration:
|
||||
if !client.registered {
|
||||
regCmd, ok := cmd.(RegServerCommand)
|
||||
if !ok {
|
||||
client.Quit("unexpected command")
|
||||
return
|
||||
}
|
||||
regCmd.HandleRegServer(server)
|
||||
|
||||
case Normal:
|
||||
srvCmd, ok := cmd.(ServerCommand)
|
||||
if !ok {
|
||||
client.ErrUnknownCommand(cmd.Code())
|
||||
return
|
||||
}
|
||||
switch srvCmd.(type) {
|
||||
case *PingCommand, *PongCommand:
|
||||
client.Touch()
|
||||
|
||||
case *QuitCommand:
|
||||
// no-op
|
||||
|
||||
default:
|
||||
client.Active()
|
||||
client.Touch()
|
||||
}
|
||||
srvCmd.HandleServer(server)
|
||||
return
|
||||
}
|
||||
|
||||
srvCmd, ok := cmd.(ServerCommand)
|
||||
if !ok {
|
||||
client.ErrUnknownCommand(cmd.Code())
|
||||
return
|
||||
}
|
||||
switch srvCmd.(type) {
|
||||
case *PingCommand, *PongCommand:
|
||||
client.Touch()
|
||||
|
||||
case *QuitCommand:
|
||||
// no-op
|
||||
|
||||
default:
|
||||
client.Active()
|
||||
client.Touch()
|
||||
}
|
||||
srvCmd.HandleServer(server)
|
||||
}
|
||||
|
||||
func (server *Server) Shutdown() {
|
||||
@ -178,21 +187,15 @@ func (s *Server) listen(addr string) {
|
||||
log.Fatal(s, "listen error: ", err)
|
||||
}
|
||||
|
||||
if DEBUG_SERVER {
|
||||
log.Printf("%s listening on %s", s, addr)
|
||||
}
|
||||
Log.info.Printf("%s listening on %s", s, addr)
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
if DEBUG_SERVER {
|
||||
log.Printf("%s accept error: %s", s, err)
|
||||
}
|
||||
Log.error.Printf("%s accept error: %s", s, err)
|
||||
continue
|
||||
}
|
||||
if DEBUG_SERVER {
|
||||
log.Printf("%s accept: %s", s, conn.RemoteAddr())
|
||||
}
|
||||
Log.debug.Printf("%s accept: %s", s, conn.RemoteAddr())
|
||||
|
||||
s.newConns <- conn
|
||||
}
|
||||
@ -203,14 +206,17 @@ func (s *Server) listen(addr string) {
|
||||
//
|
||||
|
||||
func (s *Server) tryRegister(c *Client) {
|
||||
if c.HasNick() && c.HasUsername() && (c.capState != CapNegotiating) {
|
||||
c.Register()
|
||||
c.RplWelcome()
|
||||
c.RplYourHost()
|
||||
c.RplCreated()
|
||||
c.RplMyInfo()
|
||||
s.MOTD(c)
|
||||
if c.registered || !c.HasNick() || !c.HasUsername() ||
|
||||
(c.capState == CapNegotiating) {
|
||||
return
|
||||
}
|
||||
|
||||
c.Register()
|
||||
c.RplWelcome()
|
||||
c.RplYourHost()
|
||||
c.RplCreated()
|
||||
c.RplMyInfo()
|
||||
s.MOTD(c)
|
||||
}
|
||||
|
||||
func (server *Server) MOTD(client *Client) {
|
||||
@ -281,44 +287,6 @@ func (msg *ProxyCommand) HandleRegServer(server *Server) {
|
||||
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) {
|
||||
client := m.Client()
|
||||
if !client.authorized {
|
||||
@ -369,7 +337,7 @@ func (msg *RFC2812UserCommand) HandleRegServer(server *Server) {
|
||||
}
|
||||
flags := msg.Flags()
|
||||
if len(flags) > 0 {
|
||||
for _, mode := range msg.Flags() {
|
||||
for _, mode := range flags {
|
||||
client.flags[mode] = true
|
||||
}
|
||||
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 {
|
||||
chstrs := make([]string, len(client.channels))
|
||||
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) {
|
||||
for member := range channel.members {
|
||||
if !client.flags[Invisible] || friends[client] {
|
||||
|
@ -3,7 +3,6 @@ package irc
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
@ -20,7 +19,7 @@ type Socket struct {
|
||||
writer *bufio.Writer
|
||||
}
|
||||
|
||||
func NewSocket(conn net.Conn, commands chan<- editableCommand) *Socket {
|
||||
func NewSocket(conn net.Conn, commands chan<- Command) *Socket {
|
||||
socket := &Socket{
|
||||
conn: conn,
|
||||
reader: bufio.NewReader(conn),
|
||||
@ -38,12 +37,10 @@ func (socket *Socket) String() string {
|
||||
|
||||
func (socket *Socket) Close() {
|
||||
socket.conn.Close()
|
||||
if DEBUG_NET {
|
||||
log.Printf("%s closed", socket)
|
||||
}
|
||||
Log.debug.Printf("%s closed", socket)
|
||||
}
|
||||
|
||||
func (socket *Socket) readLines(commands chan<- editableCommand) {
|
||||
func (socket *Socket) readLines(commands chan<- Command) {
|
||||
commands <- &ProxyCommand{
|
||||
hostname: AddrLookupHostname(socket.conn.RemoteAddr()),
|
||||
}
|
||||
@ -57,9 +54,7 @@ func (socket *Socket) readLines(commands chan<- editableCommand) {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
if DEBUG_NET {
|
||||
log.Printf("%s → %s", socket, line)
|
||||
}
|
||||
Log.debug.Printf("%s → %s", socket, line)
|
||||
|
||||
msg, err := ParseCommand(line)
|
||||
if err != nil {
|
||||
@ -87,16 +82,14 @@ func (socket *Socket) Write(line string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if DEBUG_NET {
|
||||
log.Printf("%s ← %s", socket, line)
|
||||
}
|
||||
Log.debug.Printf("%s ← %s", socket, line)
|
||||
return
|
||||
}
|
||||
|
||||
func (socket *Socket) isError(err error, dir rune) bool {
|
||||
if err != nil {
|
||||
if DEBUG_NET && (err != io.EOF) {
|
||||
log.Printf("%s %c error: %s", socket, dir, err)
|
||||
if err != io.EOF {
|
||||
Log.debug.Printf("%s %c error: %s", socket, dir, err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
103
irc/types.go
103
irc/types.go
@ -9,83 +9,6 @@ import (
|
||||
// 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
|
||||
|
||||
func (channels ChannelNameMap) Get(name Name) *Channel {
|
||||
@ -181,31 +104,7 @@ func (channels ChannelSet) First() *Channel {
|
||||
// interfaces
|
||||
//
|
||||
|
||||
type Identifier interface {
|
||||
type Identifiable interface {
|
||||
Id() 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