Browse Source

Merge pull request #22 from jlatt/cleanup

cleanup
tags/v0.1.0
Jeremy Latt 10 years ago
parent
commit
5df8173df2
16 changed files with 544 additions and 475 deletions
  1. 4
    4
      README.md
  2. 1
    6
      ergonomadic.conf
  3. 51
    34
      ergonomadic.go
  4. 116
    0
      irc/capability.go
  5. 2
    2
      irc/channel.go
  6. 14
    9
      irc/client.go
  7. 8
    20
      irc/client_lookup_set.go
  8. 37
    36
      irc/commands.go
  9. 1
    7
      irc/config.go
  10. 0
    83
      irc/constants.go
  11. 60
    0
      irc/logging.go
  12. 162
    0
      irc/modes.go
  13. 24
    7
      irc/reply.go
  14. 56
    151
      irc/server.go
  15. 7
    14
      irc/socket.go
  16. 1
    102
      irc/types.go

+ 4
- 4
README.md View File

@@ -38,7 +38,7 @@ hostname lookups.
38 38
 ```sh
39 39
 go get
40 40
 go install
41
-ergonomadic -conf ergonomadic.conf -initdb
41
+ergonomadic initdb -conf ergonomadic.conf
42 42
 ```
43 43
 
44 44
 ## Configuration
@@ -48,16 +48,16 @@ bcrypted byte strings. You can generate them with the `genpasswd`
48 48
 subcommand.
49 49
 
50 50
 ```sh
51
-ergonomadic -genpasswd 'hunter2!'
51
+ergonomadic genpasswd 'hunter2!'
52 52
 ```
53 53
 
54 54
 ## Running the Server
55 55
 
56 56
 ```sh
57
-ergonomadic -conf ergonomadic.conf
57
+ergonomadic run -conf ergonomadic.conf
58 58
 ```
59 59
 
60
-## Helpful Documentation
60
+## IRC Documentation
61 61
 
62 62
 - [RFC 1459: Internet Relay Chat Protocol](http://tools.ietf.org/html/rfc1459)
63 63
 - [RFC 2811: IRC Channel Management](http://tools.ietf.org/html/rfc2811)

+ 1
- 6
ergonomadic.conf View File

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

+ 51
- 34
ergonomadic.go View File

@@ -9,51 +9,68 @@ import (
9 9
 	"path/filepath"
10 10
 )
11 11
 
12
+func usage() {
13
+	fmt.Fprintln(os.Stderr, "ergonomadic <run|genpasswd|initdb|upgradedb> [options]")
14
+	fmt.Fprintln(os.Stderr, "  run -conf <config>     -- run server")
15
+	fmt.Fprintln(os.Stderr, "  initdb -conf <config>  -- initialize database")
16
+	fmt.Fprintln(os.Stderr, "  upgrade -conf <config> -- upgrade database")
17
+	fmt.Fprintln(os.Stderr, "  genpasswd <password>   -- bcrypt a password")
18
+	flag.PrintDefaults()
19
+}
20
+
21
+func loadConfig(conf string) *irc.Config {
22
+	config, err := irc.LoadConfig(conf)
23
+	if err != nil {
24
+		log.Fatalln("error loading config:", err)
25
+	}
26
+
27
+	err = os.Chdir(filepath.Dir(conf))
28
+	if err != nil {
29
+		log.Fatalln("chdir error:", err)
30
+	}
31
+	return config
32
+}
33
+
34
+func genPasswd() {
35
+}
36
+
12 37
 func main() {
13
-	conf := flag.String("conf", "ergonomadic.conf", "ergonomadic config file")
14
-	initdb := flag.Bool("initdb", false, "initialize database")
15
-	upgradedb := flag.Bool("upgradedb", false, "update database")
16
-	passwd := flag.String("genpasswd", "", "bcrypt a password")
38
+	var conf string
39
+	flag.Usage = usage
40
+
41
+	runFlags := flag.NewFlagSet("run", flag.ExitOnError)
42
+	runFlags.Usage = usage
43
+	runFlags.StringVar(&conf, "conf", "ergonomadic.conf", "ergonomadic config file")
44
+
17 45
 	flag.Parse()
18 46
 
19
-	if *passwd != "" {
20
-		encoded, err := irc.GenerateEncodedPassword(*passwd)
47
+	switch flag.Arg(0) {
48
+	case "genpasswd":
49
+		encoded, err := irc.GenerateEncodedPassword(flag.Arg(1))
21 50
 		if err != nil {
22
-			log.Fatal("encoding error: ", err)
51
+			log.Fatalln("encoding error:", err)
23 52
 		}
24 53
 		fmt.Println(encoded)
25
-		return
26
-	}
27 54
 
28
-	config, err := irc.LoadConfig(*conf)
29
-	if err != nil {
30
-		log.Fatal("error loading config: ", err)
31
-	}
32
-	err = os.Chdir(filepath.Dir(*conf))
33
-	if err != nil {
34
-		log.Fatal("chdir error: ", err)
35
-	}
36
-
37
-	if *initdb {
55
+	case "initdb":
56
+		runFlags.Parse(flag.Args()[1:])
57
+		config := loadConfig(conf)
38 58
 		irc.InitDB(config.Server.Database)
39 59
 		log.Println("database initialized: ", config.Server.Database)
40
-		return
41
-	}
42 60
 
43
-	if *upgradedb {
61
+	case "upgradedb":
62
+		runFlags.Parse(flag.Args()[1:])
63
+		config := loadConfig(conf)
44 64
 		irc.UpgradeDB(config.Server.Database)
45 65
 		log.Println("database upgraded: ", config.Server.Database)
46
-		return
47
-	}
48 66
 
49
-	// TODO move to data structures
50
-	irc.DEBUG_NET = config.Debug.Net
51
-	irc.DEBUG_CLIENT = config.Debug.Client
52
-	irc.DEBUG_CHANNEL = config.Debug.Channel
53
-	irc.DEBUG_SERVER = config.Debug.Server
54
-
55
-	server := irc.NewServer(config)
56
-	log.Println(irc.SEM_VER, "running")
57
-	defer log.Println(irc.SEM_VER, "exiting")
58
-	server.Run()
67
+	default:
68
+		runFlags.Parse(flag.Args()[1:])
69
+		config := loadConfig(conf)
70
+		irc.Log.SetLevel(config.Server.Log)
71
+		server := irc.NewServer(config)
72
+		log.Println(irc.SEM_VER, "running")
73
+		defer log.Println(irc.SEM_VER, "exiting")
74
+		server.Run()
75
+	}
59 76
 }

+ 116
- 0
irc/capability.go View File

@@ -0,0 +1,116 @@
1
+package irc
2
+
3
+import (
4
+	"strings"
5
+)
6
+
7
+type CapSubCommand string
8
+
9
+const (
10
+	CAP_LS    CapSubCommand = "LS"
11
+	CAP_LIST  CapSubCommand = "LIST"
12
+	CAP_REQ   CapSubCommand = "REQ"
13
+	CAP_ACK   CapSubCommand = "ACK"
14
+	CAP_NAK   CapSubCommand = "NAK"
15
+	CAP_CLEAR CapSubCommand = "CLEAR"
16
+	CAP_END   CapSubCommand = "END"
17
+)
18
+
19
+// Capabilities are optional features a client may request from a server.
20
+type Capability string
21
+
22
+const (
23
+	MultiPrefix Capability = "multi-prefix"
24
+	SASL        Capability = "sasl"
25
+)
26
+
27
+var (
28
+	SupportedCapabilities = CapabilitySet{
29
+		MultiPrefix: true,
30
+	}
31
+)
32
+
33
+func (capability Capability) String() string {
34
+	return string(capability)
35
+}
36
+
37
+// CapModifiers are indicators showing the state of a capability after a REQ or
38
+// ACK.
39
+type CapModifier rune
40
+
41
+const (
42
+	Ack     CapModifier = '~'
43
+	Disable CapModifier = '-'
44
+	Sticky  CapModifier = '='
45
+)
46
+
47
+func (mod CapModifier) String() string {
48
+	return string(mod)
49
+}
50
+
51
+type CapState uint
52
+
53
+const (
54
+	CapNone        CapState = iota
55
+	CapNegotiating CapState = iota
56
+	CapNegotiated  CapState = iota
57
+)
58
+
59
+type CapabilitySet map[Capability]bool
60
+
61
+func (set CapabilitySet) String() string {
62
+	strs := make([]string, len(set))
63
+	index := 0
64
+	for capability := range set {
65
+		strs[index] = string(capability)
66
+		index += 1
67
+	}
68
+	return strings.Join(strs, " ")
69
+}
70
+
71
+func (set CapabilitySet) DisableString() string {
72
+	parts := make([]string, len(set))
73
+	index := 0
74
+	for capability := range set {
75
+		parts[index] = Disable.String() + capability.String()
76
+		index += 1
77
+	}
78
+	return strings.Join(parts, " ")
79
+}
80
+
81
+func (msg *CapCommand) HandleRegServer(server *Server) {
82
+	client := msg.Client()
83
+
84
+	switch msg.subCommand {
85
+	case CAP_LS:
86
+		client.capState = CapNegotiating
87
+		client.Reply(RplCap(client, CAP_LS, SupportedCapabilities))
88
+
89
+	case CAP_LIST:
90
+		client.Reply(RplCap(client, CAP_LIST, client.capabilities))
91
+
92
+	case CAP_REQ:
93
+		for capability := range msg.capabilities {
94
+			if !SupportedCapabilities[capability] {
95
+				client.Reply(RplCap(client, CAP_NAK, msg.capabilities))
96
+				return
97
+			}
98
+		}
99
+		for capability := range msg.capabilities {
100
+			client.capabilities[capability] = true
101
+		}
102
+		client.Reply(RplCap(client, CAP_ACK, msg.capabilities))
103
+
104
+	case CAP_CLEAR:
105
+		reply := RplCap(client, CAP_ACK, client.capabilities.DisableString())
106
+		client.capabilities = make(CapabilitySet)
107
+		client.Reply(reply)
108
+
109
+	case CAP_END:
110
+		client.capState = CapNegotiated
111
+		server.tryRegister(client)
112
+
113
+	default:
114
+		client.ErrInvalidCapCmd(msg.subCommand)
115
+	}
116
+}

+ 2
- 2
irc/channel.go View File

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

+ 14
- 9
irc/client.go View File

@@ -2,11 +2,16 @@ package irc
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"log"
6 5
 	"net"
7 6
 	"time"
8 7
 )
9 8
 
9
+const (
10
+	LOGIN_TIMEOUT = time.Minute / 2 // how long the client has to login
11
+	IDLE_TIMEOUT  = time.Minute     // how long before a client is considered idle
12
+	QUIT_TIMEOUT  = time.Minute     // how long after idle before a client is kicked
13
+)
14
+
10 15
 type Client struct {
11 16
 	atime        time.Time
12 17
 	authorized   bool
@@ -14,7 +19,7 @@ type Client struct {
14 19
 	capabilities CapabilitySet
15 20
 	capState     CapState
16 21
 	channels     ChannelSet
17
-	commands     chan editableCommand
22
+	commands     chan Command
18 23
 	ctime        time.Time
19 24
 	flags        map[UserMode]bool
20 25
 	hasQuit      bool
@@ -23,9 +28,9 @@ type Client struct {
23 28
 	idleTimer    *time.Timer
24 29
 	loginTimer   *time.Timer
25 30
 	nick         Name
26
-	phase        Phase
27 31
 	quitTimer    *time.Timer
28 32
 	realname     Text
33
+	registered   bool
29 34
 	server       *Server
30 35
 	socket       *Socket
31 36
 	username     Name
@@ -39,10 +44,9 @@ func NewClient(server *Server, conn net.Conn) *Client {
39 44
 		capState:     CapNone,
40 45
 		capabilities: make(CapabilitySet),
41 46
 		channels:     make(ChannelSet),
42
-		commands:     make(chan editableCommand),
47
+		commands:     make(chan Command),
43 48
 		ctime:        now,
44 49
 		flags:        make(map[UserMode]bool),
45
-		phase:        Registration,
46 50
 		server:       server,
47 51
 	}
48 52
 	client.socket = NewSocket(conn, client.commands)
@@ -115,7 +119,10 @@ func (client *Client) Idle() {
115 119
 }
116 120
 
117 121
 func (client *Client) Register() {
118
-	client.phase = Normal
122
+	if client.registered {
123
+		return
124
+	}
125
+	client.registered = true
119 126
 	client.loginTimer.Stop()
120 127
 	client.Touch()
121 128
 }
@@ -145,9 +152,7 @@ func (client *Client) destroy() {
145 152
 
146 153
 	client.socket.Close()
147 154
 
148
-	if DEBUG_CLIENT {
149
-		log.Printf("%s: destroyed", client)
150
-	}
155
+	Log.debug.Printf("%s: destroyed", client)
151 156
 }
152 157
 
153 158
 func (client *Client) IdleTime() time.Duration {

+ 8
- 20
irc/client_lookup_set.go View File

@@ -88,25 +88,19 @@ func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) {
88 88
 		`SELECT nickname FROM client WHERE userhost LIKE ? ESCAPE '\'`,
89 89
 		QuoteLike(userhost))
90 90
 	if err != nil {
91
-		if DEBUG_SERVER {
92
-			log.Println("ClientLookupSet.FindAll.Query:", err)
93
-		}
91
+		Log.error.Println("ClientLookupSet.FindAll.Query:", err)
94 92
 		return
95 93
 	}
96 94
 	for rows.Next() {
97 95
 		var nickname Name
98 96
 		err := rows.Scan(&nickname)
99 97
 		if err != nil {
100
-			if DEBUG_SERVER {
101
-				log.Println("ClientLookupSet.FindAll.Scan:", err)
102
-			}
98
+			Log.error.Println("ClientLookupSet.FindAll.Scan:", err)
103 99
 			return
104 100
 		}
105 101
 		client := clients.Get(nickname)
106 102
 		if client == nil {
107
-			if DEBUG_SERVER {
108
-				log.Println("ClientLookupSet.FindAll: missing client:", nickname)
109
-			}
103
+			Log.error.Println("ClientLookupSet.FindAll: missing client:", nickname)
110 104
 			continue
111 105
 		}
112 106
 		set.Add(client)
@@ -122,9 +116,7 @@ func (clients *ClientLookupSet) Find(userhost Name) *Client {
122 116
 	var nickname Name
123 117
 	err := row.Scan(&nickname)
124 118
 	if err != nil {
125
-		if DEBUG_SERVER {
126
-			log.Println("ClientLookupSet.Find:", err)
127
-		}
119
+		Log.error.Println("ClientLookupSet.Find:", err)
128 120
 		return nil
129 121
 	}
130 122
 	return clients.Get(nickname)
@@ -161,21 +153,17 @@ func NewClientDB() *ClientDB {
161 153
 
162 154
 func (db *ClientDB) Add(client *Client) {
163 155
 	_, err := db.db.Exec(`INSERT INTO client (nickname, userhost) VALUES (?, ?)`,
164
-		client.Nick(), client.UserHost())
156
+		client.Nick().String(), client.UserHost().String())
165 157
 	if err != nil {
166
-		if DEBUG_SERVER {
167
-			log.Println("ClientDB.Add:", err)
168
-		}
158
+		Log.error.Println("ClientDB.Add:", err)
169 159
 	}
170 160
 }
171 161
 
172 162
 func (db *ClientDB) Remove(client *Client) {
173 163
 	_, err := db.db.Exec(`DELETE FROM client WHERE nickname = ?`,
174
-		client.Nick())
164
+		client.Nick().String())
175 165
 	if err != nil {
176
-		if DEBUG_SERVER {
177
-			log.Println("ClientDB.Remove:", err)
178
-		}
166
+		Log.error.Println("ClientDB.Remove:", err)
179 167
 	}
180 168
 }
181 169
 

+ 37
- 36
irc/commands.go View File

@@ -8,10 +8,11 @@ import (
8 8
 	"strings"
9 9
 )
10 10
 
11
-type editableCommand interface {
12
-	Command
13
-	SetCode(StringCode)
11
+type Command interface {
12
+	Client() *Client
13
+	Code() StringCode
14 14
 	SetClient(*Client)
15
+	SetCode(StringCode)
15 16
 }
16 17
 
17 18
 type checkPasswordCommand interface {
@@ -19,7 +20,7 @@ type checkPasswordCommand interface {
19 20
 	CheckPassword()
20 21
 }
21 22
 
22
-type parseCommandFunc func([]string) (editableCommand, error)
23
+type parseCommandFunc func([]string) (Command, error)
23 24
 
24 25
 var (
25 26
 	NotEnoughArgsError = errors.New("not enough arguments")
@@ -78,7 +79,7 @@ func (command *BaseCommand) SetCode(code StringCode) {
78 79
 	command.code = code
79 80
 }
80 81
 
81
-func ParseCommand(line string) (cmd editableCommand, err error) {
82
+func ParseCommand(line string) (cmd Command, err error) {
82 83
 	code, args := ParseLine(line)
83 84
 	constructor := parseCommandFuncs[code]
84 85
 	if constructor == nil {
@@ -154,7 +155,7 @@ func (cmd *PingCommand) String() string {
154 155
 	return fmt.Sprintf("PING(server=%s, server2=%s)", cmd.server, cmd.server2)
155 156
 }
156 157
 
157
-func NewPingCommand(args []string) (editableCommand, error) {
158
+func NewPingCommand(args []string) (Command, error) {
158 159
 	if len(args) < 1 {
159 160
 		return nil, NotEnoughArgsError
160 161
 	}
@@ -179,7 +180,7 @@ func (cmd *PongCommand) String() string {
179 180
 	return fmt.Sprintf("PONG(server1=%s, server2=%s)", cmd.server1, cmd.server2)
180 181
 }
181 182
 
182
-func NewPongCommand(args []string) (editableCommand, error) {
183
+func NewPongCommand(args []string) (Command, error) {
183 184
 	if len(args) < 1 {
184 185
 		return nil, NotEnoughArgsError
185 186
 	}
@@ -216,7 +217,7 @@ func (cmd *PassCommand) CheckPassword() {
216 217
 	cmd.err = ComparePassword(cmd.hash, cmd.password)
217 218
 }
218 219
 
219
-func NewPassCommand(args []string) (editableCommand, error) {
220
+func NewPassCommand(args []string) (Command, error) {
220 221
 	if len(args) < 1 {
221 222
 		return nil, NotEnoughArgsError
222 223
 	}
@@ -236,7 +237,7 @@ func (m *NickCommand) String() string {
236 237
 	return fmt.Sprintf("NICK(nickname=%s)", m.nickname)
237 238
 }
238 239
 
239
-func NewNickCommand(args []string) (editableCommand, error) {
240
+func NewNickCommand(args []string) (Command, error) {
240 241
 	if len(args) != 1 {
241 242
 		return nil, NotEnoughArgsError
242 243
 	}
@@ -286,7 +287,7 @@ func (cmd *RFC2812UserCommand) Flags() []UserMode {
286 287
 	return flags
287 288
 }
288 289
 
289
-func NewUserCommand(args []string) (editableCommand, error) {
290
+func NewUserCommand(args []string) (Command, error) {
290 291
 	if len(args) != 4 {
291 292
 		return nil, NotEnoughArgsError
292 293
 	}
@@ -321,7 +322,7 @@ func (cmd *QuitCommand) String() string {
321 322
 	return fmt.Sprintf("QUIT(message=%s)", cmd.message)
322 323
 }
323 324
 
324
-func NewQuitCommand(args []string) (editableCommand, error) {
325
+func NewQuitCommand(args []string) (Command, error) {
325 326
 	msg := &QuitCommand{}
326 327
 	if len(args) > 0 {
327 328
 		msg.message = NewText(args[0])
@@ -341,7 +342,7 @@ func (cmd *JoinCommand) String() string {
341 342
 	return fmt.Sprintf("JOIN(channels=%s, zero=%t)", cmd.channels, cmd.zero)
342 343
 }
343 344
 
344
-func NewJoinCommand(args []string) (editableCommand, error) {
345
+func NewJoinCommand(args []string) (Command, error) {
345 346
 	msg := &JoinCommand{
346 347
 		channels: make(map[Name]Text),
347 348
 	}
@@ -388,7 +389,7 @@ func (cmd *PartCommand) String() string {
388 389
 	return fmt.Sprintf("PART(channels=%s, message=%s)", cmd.channels, cmd.message)
389 390
 }
390 391
 
391
-func NewPartCommand(args []string) (editableCommand, error) {
392
+func NewPartCommand(args []string) (Command, error) {
392 393
 	if len(args) < 1 {
393 394
 		return nil, NotEnoughArgsError
394 395
 	}
@@ -413,7 +414,7 @@ func (cmd *PrivMsgCommand) String() string {
413 414
 	return fmt.Sprintf("PRIVMSG(target=%s, message=%s)", cmd.target, cmd.message)
414 415
 }
415 416
 
416
-func NewPrivMsgCommand(args []string) (editableCommand, error) {
417
+func NewPrivMsgCommand(args []string) (Command, error) {
417 418
 	if len(args) < 2 {
418 419
 		return nil, NotEnoughArgsError
419 420
 	}
@@ -436,7 +437,7 @@ func (cmd *TopicCommand) String() string {
436 437
 	return fmt.Sprintf("TOPIC(channel=%s, topic=%s)", cmd.channel, cmd.topic)
437 438
 }
438 439
 
439
-func NewTopicCommand(args []string) (editableCommand, error) {
440
+func NewTopicCommand(args []string) (Command, error) {
440 441
 	if len(args) < 1 {
441 442
 		return nil, NotEnoughArgsError
442 443
 	}
@@ -486,7 +487,7 @@ type ModeCommand struct {
486 487
 }
487 488
 
488 489
 // MODE <nickname> *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )
489
-func NewUserModeCommand(nickname Name, args []string) (editableCommand, error) {
490
+func NewUserModeCommand(nickname Name, args []string) (Command, error) {
490 491
 	cmd := &ModeCommand{
491 492
 		nickname: nickname,
492 493
 		changes:  make(ModeChanges, 0),
@@ -563,7 +564,7 @@ type ChannelModeCommand struct {
563 564
 }
564 565
 
565 566
 // MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
566
-func NewChannelModeCommand(channel Name, args []string) (editableCommand, error) {
567
+func NewChannelModeCommand(channel Name, args []string) (Command, error) {
567 568
 	cmd := &ChannelModeCommand{
568 569
 		channel: channel,
569 570
 		changes: make(ChannelModeChanges, 0),
@@ -609,7 +610,7 @@ func (msg *ChannelModeCommand) String() string {
609 610
 	return fmt.Sprintf("MODE(channel=%s, changes=%s)", msg.channel, msg.changes)
610 611
 }
611 612
 
612
-func NewModeCommand(args []string) (editableCommand, error) {
613
+func NewModeCommand(args []string) (Command, error) {
613 614
 	if len(args) == 0 {
614 615
 		return nil, NotEnoughArgsError
615 616
 	}
@@ -629,7 +630,7 @@ type WhoisCommand struct {
629 630
 }
630 631
 
631 632
 // WHOIS [ <target> ] <mask> *( "," <mask> )
632
-func NewWhoisCommand(args []string) (editableCommand, error) {
633
+func NewWhoisCommand(args []string) (Command, error) {
633 634
 	if len(args) < 1 {
634 635
 		return nil, NotEnoughArgsError
635 636
 	}
@@ -661,7 +662,7 @@ type WhoCommand struct {
661 662
 }
662 663
 
663 664
 // WHO [ <mask> [ "o" ] ]
664
-func NewWhoCommand(args []string) (editableCommand, error) {
665
+func NewWhoCommand(args []string) (Command, error) {
665 666
 	cmd := &WhoCommand{}
666 667
 
667 668
 	if len(args) > 0 {
@@ -693,7 +694,7 @@ func (msg *OperCommand) LoadPassword(server *Server) {
693 694
 }
694 695
 
695 696
 // OPER <name> <password>
696
-func NewOperCommand(args []string) (editableCommand, error) {
697
+func NewOperCommand(args []string) (Command, error) {
697 698
 	if len(args) < 2 {
698 699
 		return nil, NotEnoughArgsError
699 700
 	}
@@ -716,7 +717,7 @@ func (msg *CapCommand) String() string {
716 717
 		msg.subCommand, msg.capabilities)
717 718
 }
718 719
 
719
-func NewCapCommand(args []string) (editableCommand, error) {
720
+func NewCapCommand(args []string) (Command, error) {
720 721
 	if len(args) < 1 {
721 722
 		return nil, NotEnoughArgsError
722 723
 	}
@@ -750,7 +751,7 @@ func (msg *ProxyCommand) String() string {
750 751
 	return fmt.Sprintf("PROXY(sourceIP=%s, sourcePort=%s)", msg.sourceIP, msg.sourcePort)
751 752
 }
752 753
 
753
-func NewProxyCommand(args []string) (editableCommand, error) {
754
+func NewProxyCommand(args []string) (Command, error) {
754 755
 	if len(args) < 5 {
755 756
 		return nil, NotEnoughArgsError
756 757
 	}
@@ -774,7 +775,7 @@ func (msg *AwayCommand) String() string {
774 775
 	return fmt.Sprintf("AWAY(%s)", msg.text)
775 776
 }
776 777
 
777
-func NewAwayCommand(args []string) (editableCommand, error) {
778
+func NewAwayCommand(args []string) (Command, error) {
778 779
 	cmd := &AwayCommand{}
779 780
 
780 781
 	if len(args) > 0 {
@@ -794,7 +795,7 @@ func (msg *IsOnCommand) String() string {
794 795
 	return fmt.Sprintf("ISON(nicks=%s)", msg.nicks)
795 796
 }
796 797
 
797
-func NewIsOnCommand(args []string) (editableCommand, error) {
798
+func NewIsOnCommand(args []string) (Command, error) {
798 799
 	if len(args) == 0 {
799 800
 		return nil, NotEnoughArgsError
800 801
 	}
@@ -809,7 +810,7 @@ type MOTDCommand struct {
809 810
 	target Name
810 811
 }
811 812
 
812
-func NewMOTDCommand(args []string) (editableCommand, error) {
813
+func NewMOTDCommand(args []string) (Command, error) {
813 814
 	cmd := &MOTDCommand{}
814 815
 	if len(args) > 0 {
815 816
 		cmd.target = NewName(args[0])
@@ -827,7 +828,7 @@ func (cmd *NoticeCommand) String() string {
827 828
 	return fmt.Sprintf("NOTICE(target=%s, message=%s)", cmd.target, cmd.message)
828 829
 }
829 830
 
830
-func NewNoticeCommand(args []string) (editableCommand, error) {
831
+func NewNoticeCommand(args []string) (Command, error) {
831 832
 	if len(args) < 2 {
832 833
 		return nil, NotEnoughArgsError
833 834
 	}
@@ -850,7 +851,7 @@ func (msg *KickCommand) Comment() Text {
850 851
 	return msg.comment
851 852
 }
852 853
 
853
-func NewKickCommand(args []string) (editableCommand, error) {
854
+func NewKickCommand(args []string) (Command, error) {
854 855
 	if len(args) < 2 {
855 856
 		return nil, NotEnoughArgsError
856 857
 	}
@@ -881,7 +882,7 @@ type ListCommand struct {
881 882
 	target   Name
882 883
 }
883 884
 
884
-func NewListCommand(args []string) (editableCommand, error) {
885
+func NewListCommand(args []string) (Command, error) {
885 886
 	cmd := &ListCommand{}
886 887
 	if len(args) > 0 {
887 888
 		cmd.channels = NewNames(strings.Split(args[0], ","))
@@ -898,7 +899,7 @@ type NamesCommand struct {
898 899
 	target   Name
899 900
 }
900 901
 
901
-func NewNamesCommand(args []string) (editableCommand, error) {
902
+func NewNamesCommand(args []string) (Command, error) {
902 903
 	cmd := &NamesCommand{}
903 904
 	if len(args) > 0 {
904 905
 		cmd.channels = NewNames(strings.Split(args[0], ","))
@@ -914,7 +915,7 @@ type DebugCommand struct {
914 915
 	subCommand Name
915 916
 }
916 917
 
917
-func NewDebugCommand(args []string) (editableCommand, error) {
918
+func NewDebugCommand(args []string) (Command, error) {
918 919
 	if len(args) == 0 {
919 920
 		return nil, NotEnoughArgsError
920 921
 	}
@@ -929,7 +930,7 @@ type VersionCommand struct {
929 930
 	target Name
930 931
 }
931 932
 
932
-func NewVersionCommand(args []string) (editableCommand, error) {
933
+func NewVersionCommand(args []string) (Command, error) {
933 934
 	cmd := &VersionCommand{}
934 935
 	if len(args) > 0 {
935 936
 		cmd.target = NewName(args[0])
@@ -943,7 +944,7 @@ type InviteCommand struct {
943 944
 	channel  Name
944 945
 }
945 946
 
946
-func NewInviteCommand(args []string) (editableCommand, error) {
947
+func NewInviteCommand(args []string) (Command, error) {
947 948
 	if len(args) < 2 {
948 949
 		return nil, NotEnoughArgsError
949 950
 	}
@@ -959,7 +960,7 @@ type TimeCommand struct {
959 960
 	target Name
960 961
 }
961 962
 
962
-func NewTimeCommand(args []string) (editableCommand, error) {
963
+func NewTimeCommand(args []string) (Command, error) {
963 964
 	cmd := &TimeCommand{}
964 965
 	if len(args) > 0 {
965 966
 		cmd.target = NewName(args[0])
@@ -973,7 +974,7 @@ type KillCommand struct {
973 974
 	comment  Text
974 975
 }
975 976
 
976
-func NewKillCommand(args []string) (editableCommand, error) {
977
+func NewKillCommand(args []string) (Command, error) {
977 978
 	if len(args) < 2 {
978 979
 		return nil, NotEnoughArgsError
979 980
 	}
@@ -990,7 +991,7 @@ type WhoWasCommand struct {
990 991
 	target    Name
991 992
 }
992 993
 
993
-func NewWhoWasCommand(args []string) (editableCommand, error) {
994
+func NewWhoWasCommand(args []string) (Command, error) {
994 995
 	if len(args) < 1 {
995 996
 		return nil, NotEnoughArgsError
996 997
 	}

+ 1
- 7
irc/config.go View File

@@ -23,18 +23,12 @@ type Config struct {
23 23
 		PassConfig
24 24
 		Database string
25 25
 		Listen   []string
26
+		Log      string
26 27
 		MOTD     string
27 28
 		Name     string
28 29
 	}
29 30
 
30 31
 	Operator map[string]*PassConfig
31
-
32
-	Debug struct {
33
-		Net     bool
34
-		Client  bool
35
-		Channel bool
36
-		Server  bool
37
-	}
38 32
 }
39 33
 
40 34
 func (conf *Config) Operators() map[Name][]byte {

+ 0
- 83
irc/constants.go View File

@@ -1,30 +1,10 @@
1 1
 package irc
2 2
 
3
-import (
4
-	"errors"
5
-	"time"
6
-)
7
-
8
-var (
9
-	// debugging flags
10
-	DEBUG_NET     = false
11
-	DEBUG_CLIENT  = false
12
-	DEBUG_CHANNEL = false
13
-	DEBUG_SERVER  = false
14
-
15
-	// errors
16
-	ErrAlreadyDestroyed = errors.New("already destroyed")
17
-)
18
-
19 3
 const (
20 4
 	SEM_VER       = "ergonomadic-1.3.1"
21 5
 	CRLF          = "\r\n"
22 6
 	MAX_REPLY_LEN = 512 - len(CRLF)
23 7
 
24
-	LOGIN_TIMEOUT = time.Minute / 2 // how long the client has to login
25
-	IDLE_TIMEOUT  = time.Minute     // how long before a client is considered idle
26
-	QUIT_TIMEOUT  = time.Minute     // how long after idle before a client is kicked
27
-
28 8
 	// string codes
29 9
 	AWAY    StringCode = "AWAY"
30 10
 	CAP     StringCode = "CAP"
@@ -195,67 +175,4 @@ const (
195 175
 	ERR_NOOPERHOST        NumericCode = 491
196 176
 	ERR_UMODEUNKNOWNFLAG  NumericCode = 501
197 177
 	ERR_USERSDONTMATCH    NumericCode = 502
198
-
199
-	CAP_LS    CapSubCommand = "LS"
200
-	CAP_LIST  CapSubCommand = "LIST"
201
-	CAP_REQ   CapSubCommand = "REQ"
202
-	CAP_ACK   CapSubCommand = "ACK"
203
-	CAP_NAK   CapSubCommand = "NAK"
204
-	CAP_CLEAR CapSubCommand = "CLEAR"
205
-	CAP_END   CapSubCommand = "END"
206
-
207
-	Add    ModeOp = '+'
208
-	List   ModeOp = '='
209
-	Remove ModeOp = '-'
210
-
211
-	Away          UserMode = 'a'
212
-	Invisible     UserMode = 'i'
213
-	LocalOperator UserMode = 'O'
214
-	Operator      UserMode = 'o'
215
-	Restricted    UserMode = 'r'
216
-	ServerNotice  UserMode = 's' // deprecated
217
-	WallOps       UserMode = 'w'
218
-
219
-	Anonymous       ChannelMode = 'a' // flag
220
-	BanMask         ChannelMode = 'b' // arg
221
-	ChannelCreator  ChannelMode = 'O' // flag
222
-	ChannelOperator ChannelMode = 'o' // arg
223
-	ExceptMask      ChannelMode = 'e' // arg
224
-	InviteMask      ChannelMode = 'I' // arg
225
-	InviteOnly      ChannelMode = 'i' // flag
226
-	Key             ChannelMode = 'k' // flag arg
227
-	Moderated       ChannelMode = 'm' // flag
228
-	NoOutside       ChannelMode = 'n' // flag
229
-	OpOnlyTopic     ChannelMode = 't' // flag
230
-	Persistent      ChannelMode = 'P' // flag
231
-	Private         ChannelMode = 'p' // flag
232
-	Quiet           ChannelMode = 'q' // flag
233
-	ReOp            ChannelMode = 'r' // flag
234
-	Secret          ChannelMode = 's' // flag, deprecated
235
-	UserLimit       ChannelMode = 'l' // flag arg
236
-	Voice           ChannelMode = 'v' // arg
237
-
238
-	MultiPrefix Capability = "multi-prefix"
239
-	SASL        Capability = "sasl"
240
-
241
-	Disable CapModifier = '-'
242
-	Ack     CapModifier = '~'
243
-	Sticky  CapModifier = '='
244
-)
245
-
246
-var (
247
-	SupportedCapabilities = CapabilitySet{
248
-		MultiPrefix: true,
249
-	}
250
-)
251
-
252
-const (
253
-	Registration Phase = iota
254
-	Normal       Phase = iota
255
-)
256
-
257
-const (
258
-	CapNone        CapState = iota
259
-	CapNegotiating CapState = iota
260
-	CapNegotiated  CapState = iota
261 178
 )

+ 60
- 0
irc/logging.go View File

@@ -0,0 +1,60 @@
1
+package irc
2
+
3
+import (
4
+	"io"
5
+	"log"
6
+	"os"
7
+)
8
+
9
+type Logging struct {
10
+	debug *log.Logger
11
+	info  *log.Logger
12
+	warn  *log.Logger
13
+	error *log.Logger
14
+}
15
+
16
+var (
17
+	levels = map[string]uint8{
18
+		"debug": 4,
19
+		"info":  3,
20
+		"warn":  2,
21
+		"error": 1,
22
+	}
23
+	devNull io.Writer
24
+)
25
+
26
+func init() {
27
+	var err error
28
+	devNull, err = os.Open(os.DevNull)
29
+	if err != nil {
30
+		log.Fatal(err)
31
+	}
32
+}
33
+
34
+func NewLogger(on bool) *log.Logger {
35
+	return log.New(output(on), "", log.LstdFlags)
36
+}
37
+
38
+func output(on bool) io.Writer {
39
+	if on {
40
+		return os.Stdout
41
+	}
42
+	return devNull
43
+}
44
+
45
+func (logging *Logging) SetLevel(level string) {
46
+	logging.debug = NewLogger(levels[level] >= levels["debug"])
47
+	logging.info = NewLogger(levels[level] >= levels["info"])
48
+	logging.warn = NewLogger(levels[level] >= levels["warn"])
49
+	logging.error = NewLogger(levels[level] >= levels["error"])
50
+}
51
+
52
+func NewLogging(level string) *Logging {
53
+	logging := &Logging{}
54
+	logging.SetLevel(level)
55
+	return logging
56
+}
57
+
58
+var (
59
+	Log = NewLogging("warn")
60
+)

+ 162
- 0
irc/modes.go View File

@@ -0,0 +1,162 @@
1
+package irc
2
+
3
+import (
4
+	"strings"
5
+)
6
+
7
+// user mode flags
8
+type UserMode rune
9
+
10
+func (mode UserMode) String() string {
11
+	return string(mode)
12
+}
13
+
14
+type UserModes []UserMode
15
+
16
+func (modes UserModes) String() string {
17
+	strs := make([]string, len(modes))
18
+	for index, mode := range modes {
19
+		strs[index] = mode.String()
20
+	}
21
+	return strings.Join(strs, "")
22
+}
23
+
24
+// channel mode flags
25
+type ChannelMode rune
26
+
27
+func (mode ChannelMode) String() string {
28
+	return string(mode)
29
+}
30
+
31
+type ChannelModes []ChannelMode
32
+
33
+func (modes ChannelModes) String() string {
34
+	strs := make([]string, len(modes))
35
+	for index, mode := range modes {
36
+		strs[index] = mode.String()
37
+	}
38
+	return strings.Join(strs, "")
39
+}
40
+
41
+type ModeOp rune
42
+
43
+func (op ModeOp) String() string {
44
+	return string(op)
45
+}
46
+
47
+const (
48
+	Add    ModeOp = '+'
49
+	List   ModeOp = '='
50
+	Remove ModeOp = '-'
51
+)
52
+
53
+const (
54
+	Away          UserMode = 'a'
55
+	Invisible     UserMode = 'i'
56
+	LocalOperator UserMode = 'O'
57
+	Operator      UserMode = 'o'
58
+	Restricted    UserMode = 'r'
59
+	ServerNotice  UserMode = 's' // deprecated
60
+	WallOps       UserMode = 'w'
61
+)
62
+
63
+var (
64
+	SupportedUserModes = UserModes{
65
+		Away, Invisible, Operator,
66
+	}
67
+)
68
+
69
+const (
70
+	Anonymous       ChannelMode = 'a' // flag
71
+	BanMask         ChannelMode = 'b' // arg
72
+	ChannelCreator  ChannelMode = 'O' // flag
73
+	ChannelOperator ChannelMode = 'o' // arg
74
+	ExceptMask      ChannelMode = 'e' // arg
75
+	InviteMask      ChannelMode = 'I' // arg
76
+	InviteOnly      ChannelMode = 'i' // flag
77
+	Key             ChannelMode = 'k' // flag arg
78
+	Moderated       ChannelMode = 'm' // flag
79
+	NoOutside       ChannelMode = 'n' // flag
80
+	OpOnlyTopic     ChannelMode = 't' // flag
81
+	Persistent      ChannelMode = 'P' // flag
82
+	Private         ChannelMode = 'p' // flag
83
+	Quiet           ChannelMode = 'q' // flag
84
+	ReOp            ChannelMode = 'r' // flag
85
+	Secret          ChannelMode = 's' // flag, deprecated
86
+	UserLimit       ChannelMode = 'l' // flag arg
87
+	Voice           ChannelMode = 'v' // arg
88
+)
89
+
90
+var (
91
+	SupportedChannelModes = ChannelModes{
92
+		BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside,
93
+		OpOnlyTopic, Persistent, Private, UserLimit,
94
+	}
95
+)
96
+
97
+//
98
+// commands
99
+//
100
+
101
+func (m *ModeCommand) HandleServer(s *Server) {
102
+	client := m.Client()
103
+	target := s.clients.Get(m.nickname)
104
+
105
+	if target == nil {
106
+		client.ErrNoSuchNick(m.nickname)
107
+		return
108
+	}
109
+
110
+	if client != target && !client.flags[Operator] {
111
+		client.ErrUsersDontMatch()
112
+		return
113
+	}
114
+
115
+	changes := make(ModeChanges, 0, len(m.changes))
116
+
117
+	for _, change := range m.changes {
118
+		switch change.mode {
119
+		case Invisible, ServerNotice, WallOps:
120
+			switch change.op {
121
+			case Add:
122
+				if target.flags[change.mode] {
123
+					continue
124
+				}
125
+				target.flags[change.mode] = true
126
+				changes = append(changes, change)
127
+
128
+			case Remove:
129
+				if !target.flags[change.mode] {
130
+					continue
131
+				}
132
+				delete(target.flags, change.mode)
133
+				changes = append(changes, change)
134
+			}
135
+
136
+		case Operator, LocalOperator:
137
+			if change.op == Remove {
138
+				if !target.flags[change.mode] {
139
+					continue
140
+				}
141
+				delete(target.flags, change.mode)
142
+				changes = append(changes, change)
143
+			}
144
+		}
145
+	}
146
+
147
+	// Who should get these replies?
148
+	if len(changes) > 0 {
149
+		client.Reply(RplMode(client, target, changes))
150
+	}
151
+}
152
+
153
+func (msg *ChannelModeCommand) HandleServer(server *Server) {
154
+	client := msg.Client()
155
+	channel := server.channels.Get(msg.channel)
156
+	if channel == nil {
157
+		client.ErrNoSuchChannel(msg.channel)
158
+		return
159
+	}
160
+
161
+	channel.Mode(client, msg.changes)
162
+}

+ 24
- 7
irc/reply.go View File

@@ -6,7 +6,23 @@ import (
6 6
 	"time"
7 7
 )
8 8
 
9
-func NewStringReply(source Identifier, code StringCode,
9
+type ReplyCode interface {
10
+	String() string
11
+}
12
+
13
+type StringCode string
14
+
15
+func (code StringCode) String() string {
16
+	return string(code)
17
+}
18
+
19
+type NumericCode uint
20
+
21
+func (code NumericCode) String() string {
22
+	return fmt.Sprintf("%03d", code)
23
+}
24
+
25
+func NewStringReply(source Identifiable, code StringCode,
10 26
 	format string, args ...interface{}) string {
11 27
 	var header string
12 28
 	if source == nil {
@@ -79,15 +95,15 @@ func (target *Client) MultilineReply(names []string, code NumericCode, format st
79 95
 // messaging replies
80 96
 //
81 97
 
82
-func RplPrivMsg(source Identifier, target Identifier, message Text) string {
98
+func RplPrivMsg(source Identifiable, target Identifiable, message Text) string {
83 99
 	return NewStringReply(source, PRIVMSG, "%s :%s", target.Nick(), message)
84 100
 }
85 101
 
86
-func RplNotice(source Identifier, target Identifier, message Text) string {
102
+func RplNotice(source Identifiable, target Identifiable, message Text) string {
87 103
 	return NewStringReply(source, NOTICE, "%s :%s", target.Nick(), message)
88 104
 }
89 105
 
90
-func RplNick(source Identifier, newNick Name) string {
106
+func RplNick(source Identifiable, newNick Name) string {
91 107
 	return NewStringReply(source, NICK, newNick.String())
92 108
 }
93 109
 
@@ -108,11 +124,11 @@ func RplChannelMode(client *Client, channel *Channel,
108 124
 	return NewStringReply(client, MODE, "%s %s", channel, changes)
109 125
 }
110 126
 
111
-func RplTopicMsg(source Identifier, channel *Channel) string {
127
+func RplTopicMsg(source Identifiable, channel *Channel) string {
112 128
 	return NewStringReply(source, TOPIC, "%s :%s", channel, channel.topic)
113 129
 }
114 130
 
115
-func RplPing(target Identifier) string {
131
+func RplPing(target Identifiable) string {
116 132
 	return NewStringReply(nil, PING, ":%s", target.Nick())
117 133
 }
118 134
 
@@ -165,7 +181,8 @@ func (target *Client) RplCreated() {
165 181
 
166 182
 func (target *Client) RplMyInfo() {
167 183
 	target.NumericReply(RPL_MYINFO,
168
-		"%s %s aiOorsw abeIikmntpqrsl", target.server.name, SEM_VER)
184
+		"%s %s %s %s",
185
+		target.server.name, SEM_VER, SupportedUserModes, SupportedChannelModes)
169 186
 }
170 187
 
171 188
 func (target *Client) RplUModeIs(client *Client) {

+ 56
- 151
irc/server.go View File

@@ -16,6 +16,16 @@ import (
16 16
 	"time"
17 17
 )
18 18
 
19
+type ServerCommand interface {
20
+	Command
21
+	HandleServer(*Server)
22
+}
23
+
24
+type RegServerCommand interface {
25
+	Command
26
+	HandleRegServer(*Server)
27
+}
28
+
19 29
 type Server struct {
20 30
 	channels  ChannelNameMap
21 31
 	clients   *ClientLookupSet
@@ -32,19 +42,24 @@ type Server struct {
32 42
 	whoWas    *WhoWasList
33 43
 }
34 44
 
45
+var (
46
+	SERVER_SIGNALS = []os.Signal{syscall.SIGINT, syscall.SIGHUP,
47
+		syscall.SIGTERM, syscall.SIGQUIT}
48
+)
49
+
35 50
 func NewServer(config *Config) *Server {
36 51
 	server := &Server{
37 52
 		channels:  make(ChannelNameMap),
38 53
 		clients:   NewClientLookupSet(),
39
-		commands:  make(chan Command, 16),
54
+		commands:  make(chan Command),
40 55
 		ctime:     time.Now(),
41 56
 		db:        OpenDB(config.Server.Database),
42
-		idle:      make(chan *Client, 16),
57
+		idle:      make(chan *Client),
43 58
 		motdFile:  config.Server.MOTD,
44 59
 		name:      NewName(config.Server.Name),
45
-		newConns:  make(chan net.Conn, 16),
60
+		newConns:  make(chan net.Conn),
46 61
 		operators: config.Operators(),
47
-		signals:   make(chan os.Signal, 1),
62
+		signals:   make(chan os.Signal, len(SERVER_SIGNALS)),
48 63
 		whoWas:    NewWhoWasList(100),
49 64
 	}
50 65
 
@@ -58,8 +73,7 @@ func NewServer(config *Config) *Server {
58 73
 		go server.listen(addr)
59 74
 	}
60 75
 
61
-	signal.Notify(server.signals, syscall.SIGINT, syscall.SIGHUP,
62
-		syscall.SIGTERM, syscall.SIGQUIT)
76
+	signal.Notify(server.signals, SERVER_SIGNALS...)
63 77
 
64 78
 	return server
65 79
 }
@@ -80,9 +94,7 @@ func (server *Server) loadChannels() {
80 94
 		log.Fatal("error loading channels: ", err)
81 95
 	}
82 96
 	for rows.Next() {
83
-		var name Name
84
-		var flags string
85
-		var key, topic Text
97
+		var name, flags, key, topic string
86 98
 		var userLimit uint64
87 99
 		var banList, exceptList, inviteList string
88 100
 		err = rows.Scan(&name, &flags, &key, &topic, &userLimit, &banList,
@@ -92,12 +104,12 @@ func (server *Server) loadChannels() {
92 104
 			continue
93 105
 		}
94 106
 
95
-		channel := NewChannel(server, name)
107
+		channel := NewChannel(server, NewName(name))
96 108
 		for _, flag := range flags {
97 109
 			channel.flags[ChannelMode(flag)] = true
98 110
 		}
99
-		channel.key = key
100
-		channel.topic = topic
111
+		channel.key = NewText(key)
112
+		channel.topic = NewText(topic)
101 113
 		channel.userLimit = userLimit
102 114
 		loadChannelList(channel, banList, BanMask)
103 115
 		loadChannelList(channel, exceptList, ExceptMask)
@@ -107,38 +119,35 @@ func (server *Server) loadChannels() {
107 119
 
108 120
 func (server *Server) processCommand(cmd Command) {
109 121
 	client := cmd.Client()
110
-	if DEBUG_SERVER {
111
-		log.Printf("%s → %s %s", client, server, cmd)
112
-	}
122
+	Log.debug.Printf("%s → %s %s", client, server, cmd)
113 123
 
114
-	switch client.phase {
115
-	case Registration:
124
+	if !client.registered {
116 125
 		regCmd, ok := cmd.(RegServerCommand)
117 126
 		if !ok {
118 127
 			client.Quit("unexpected command")
119 128
 			return
120 129
 		}
121 130
 		regCmd.HandleRegServer(server)
131
+		return
132
+	}
122 133
 
123
-	case Normal:
124
-		srvCmd, ok := cmd.(ServerCommand)
125
-		if !ok {
126
-			client.ErrUnknownCommand(cmd.Code())
127
-			return
128
-		}
129
-		switch srvCmd.(type) {
130
-		case *PingCommand, *PongCommand:
131
-			client.Touch()
134
+	srvCmd, ok := cmd.(ServerCommand)
135
+	if !ok {
136
+		client.ErrUnknownCommand(cmd.Code())
137
+		return
138
+	}
139
+	switch srvCmd.(type) {
140
+	case *PingCommand, *PongCommand:
141
+		client.Touch()
132 142
 
133
-		case *QuitCommand:
134
-			// no-op
143
+	case *QuitCommand:
144
+		// no-op
135 145
 
136
-		default:
137
-			client.Active()
138
-			client.Touch()
139
-		}
140
-		srvCmd.HandleServer(server)
146
+	default:
147
+		client.Active()
148
+		client.Touch()
141 149
 	}
150
+	srvCmd.HandleServer(server)
142 151
 }
143 152
 
144 153
 func (server *Server) Shutdown() {
@@ -178,21 +187,15 @@ func (s *Server) listen(addr string) {
178 187
 		log.Fatal(s, "listen error: ", err)
179 188
 	}
180 189
 
181
-	if DEBUG_SERVER {
182
-		log.Printf("%s listening on %s", s, addr)
183
-	}
190
+	Log.info.Printf("%s listening on %s", s, addr)
184 191
 
185 192
 	for {
186 193
 		conn, err := listener.Accept()
187 194
 		if err != nil {
188
-			if DEBUG_SERVER {
189
-				log.Printf("%s accept error: %s", s, err)
190
-			}
195
+			Log.error.Printf("%s accept error: %s", s, err)
191 196
 			continue
192 197
 		}
193
-		if DEBUG_SERVER {
194
-			log.Printf("%s accept: %s", s, conn.RemoteAddr())
195
-		}
198
+		Log.debug.Printf("%s accept: %s", s, conn.RemoteAddr())
196 199
 
197 200
 		s.newConns <- conn
198 201
 	}
@@ -203,14 +206,17 @@ func (s *Server) listen(addr string) {
203 206
 //
204 207
 
205 208
 func (s *Server) tryRegister(c *Client) {
206
-	if c.HasNick() && c.HasUsername() && (c.capState != CapNegotiating) {
207
-		c.Register()
208
-		c.RplWelcome()
209
-		c.RplYourHost()
210
-		c.RplCreated()
211
-		c.RplMyInfo()
212
-		s.MOTD(c)
209
+	if c.registered || !c.HasNick() || !c.HasUsername() ||
210
+		(c.capState == CapNegotiating) {
211
+		return
213 212
 	}
213
+
214
+	c.Register()
215
+	c.RplWelcome()
216
+	c.RplYourHost()
217
+	c.RplCreated()
218
+	c.RplMyInfo()
219
+	s.MOTD(c)
214 220
 }
215 221
 
216 222
 func (server *Server) MOTD(client *Client) {
@@ -281,44 +287,6 @@ func (msg *ProxyCommand) HandleRegServer(server *Server) {
281 287
 	msg.Client().hostname = msg.hostname
282 288
 }
283 289
 
284
-func (msg *CapCommand) HandleRegServer(server *Server) {
285
-	client := msg.Client()
286
-
287
-	switch msg.subCommand {
288
-	case CAP_LS:
289
-		client.capState = CapNegotiating
290
-		client.Reply(RplCap(client, CAP_LS, SupportedCapabilities))
291
-
292
-	case CAP_LIST:
293
-		client.Reply(RplCap(client, CAP_LIST, client.capabilities))
294
-
295
-	case CAP_REQ:
296
-		client.capState = CapNegotiating
297
-		for capability := range msg.capabilities {
298
-			if !SupportedCapabilities[capability] {
299
-				client.Reply(RplCap(client, CAP_NAK, msg.capabilities))
300
-				return
301
-			}
302
-		}
303
-		for capability := range msg.capabilities {
304
-			client.capabilities[capability] = true
305
-		}
306
-		client.Reply(RplCap(client, CAP_ACK, msg.capabilities))
307
-
308
-	case CAP_CLEAR:
309
-		reply := RplCap(client, CAP_ACK, client.capabilities.DisableString())
310
-		client.capabilities = make(CapabilitySet)
311
-		client.Reply(reply)
312
-
313
-	case CAP_END:
314
-		client.capState = CapNegotiated
315
-		server.tryRegister(client)
316
-
317
-	default:
318
-		client.ErrInvalidCapCmd(msg.subCommand)
319
-	}
320
-}
321
-
322 290
 func (m *NickCommand) HandleRegServer(s *Server) {
323 291
 	client := m.Client()
324 292
 	if !client.authorized {
@@ -369,7 +337,7 @@ func (msg *RFC2812UserCommand) HandleRegServer(server *Server) {
369 337
 	}
370 338
 	flags := msg.Flags()
371 339
 	if len(flags) > 0 {
372
-		for _, mode := range msg.Flags() {
340
+		for _, mode := range flags {
373 341
 			client.flags[mode] = true
374 342
 		}
375 343
 		client.RplUModeIs(client)
@@ -521,58 +489,6 @@ func (msg *PrivMsgCommand) HandleServer(server *Server) {
521 489
 	}
522 490
 }
523 491
 
524
-func (m *ModeCommand) HandleServer(s *Server) {
525
-	client := m.Client()
526
-	target := s.clients.Get(m.nickname)
527
-
528
-	if target == nil {
529
-		client.ErrNoSuchNick(m.nickname)
530
-		return
531
-	}
532
-
533
-	if client != target && !client.flags[Operator] {
534
-		client.ErrUsersDontMatch()
535
-		return
536
-	}
537
-
538
-	changes := make(ModeChanges, 0, len(m.changes))
539
-
540
-	for _, change := range m.changes {
541
-		switch change.mode {
542
-		case Invisible, ServerNotice, WallOps:
543
-			switch change.op {
544
-			case Add:
545
-				if target.flags[change.mode] {
546
-					continue
547
-				}
548
-				target.flags[change.mode] = true
549
-				changes = append(changes, change)
550
-
551
-			case Remove:
552
-				if !target.flags[change.mode] {
553
-					continue
554
-				}
555
-				delete(target.flags, change.mode)
556
-				changes = append(changes, change)
557
-			}
558
-
559
-		case Operator, LocalOperator:
560
-			if change.op == Remove {
561
-				if !target.flags[change.mode] {
562
-					continue
563
-				}
564
-				delete(target.flags, change.mode)
565
-				changes = append(changes, change)
566
-			}
567
-		}
568
-	}
569
-
570
-	// Who should get these replies?
571
-	if len(changes) > 0 {
572
-		client.Reply(RplMode(client, target, changes))
573
-	}
574
-}
575
-
576 492
 func (client *Client) WhoisChannelsNames() []string {
577 493
 	chstrs := make([]string, len(client.channels))
578 494
 	index := 0
@@ -609,17 +525,6 @@ func (m *WhoisCommand) HandleServer(server *Server) {
609 525
 	}
610 526
 }
611 527
 
612
-func (msg *ChannelModeCommand) HandleServer(server *Server) {
613
-	client := msg.Client()
614
-	channel := server.channels.Get(msg.channel)
615
-	if channel == nil {
616
-		client.ErrNoSuchChannel(msg.channel)
617
-		return
618
-	}
619
-
620
-	channel.Mode(client, msg.changes)
621
-}
622
-
623 528
 func whoChannel(client *Client, channel *Channel, friends ClientSet) {
624 529
 	for member := range channel.members {
625 530
 		if !client.flags[Invisible] || friends[client] {

+ 7
- 14
irc/socket.go View File

@@ -3,7 +3,6 @@ package irc
3 3
 import (
4 4
 	"bufio"
5 5
 	"io"
6
-	"log"
7 6
 	"net"
8 7
 	"strings"
9 8
 )
@@ -20,7 +19,7 @@ type Socket struct {
20 19
 	writer *bufio.Writer
21 20
 }
22 21
 
23
-func NewSocket(conn net.Conn, commands chan<- editableCommand) *Socket {
22
+func NewSocket(conn net.Conn, commands chan<- Command) *Socket {
24 23
 	socket := &Socket{
25 24
 		conn:   conn,
26 25
 		reader: bufio.NewReader(conn),
@@ -38,12 +37,10 @@ func (socket *Socket) String() string {
38 37
 
39 38
 func (socket *Socket) Close() {
40 39
 	socket.conn.Close()
41
-	if DEBUG_NET {
42
-		log.Printf("%s closed", socket)
43
-	}
40
+	Log.debug.Printf("%s closed", socket)
44 41
 }
45 42
 
46
-func (socket *Socket) readLines(commands chan<- editableCommand) {
43
+func (socket *Socket) readLines(commands chan<- Command) {
47 44
 	commands <- &ProxyCommand{
48 45
 		hostname: AddrLookupHostname(socket.conn.RemoteAddr()),
49 46
 	}
@@ -57,9 +54,7 @@ func (socket *Socket) readLines(commands chan<- editableCommand) {
57 54
 		if len(line) == 0 {
58 55
 			continue
59 56
 		}
60
-		if DEBUG_NET {
61
-			log.Printf("%s → %s", socket, line)
62
-		}
57
+		Log.debug.Printf("%s → %s", socket, line)
63 58
 
64 59
 		msg, err := ParseCommand(line)
65 60
 		if err != nil {
@@ -87,16 +82,14 @@ func (socket *Socket) Write(line string) (err error) {
87 82
 		return
88 83
 	}
89 84
 
90
-	if DEBUG_NET {
91
-		log.Printf("%s ← %s", socket, line)
92
-	}
85
+	Log.debug.Printf("%s ← %s", socket, line)
93 86
 	return
94 87
 }
95 88
 
96 89
 func (socket *Socket) isError(err error, dir rune) bool {
97 90
 	if err != nil {
98
-		if DEBUG_NET && (err != io.EOF) {
99
-			log.Printf("%s %c error: %s", socket, dir, err)
91
+		if err != io.EOF {
92
+			Log.debug.Printf("%s %c error: %s", socket, dir, err)
100 93
 		}
101 94
 		return true
102 95
 	}

+ 1
- 102
irc/types.go View File

@@ -9,83 +9,6 @@ import (
9 9
 // simple types
10 10
 //
11 11
 
12
-type CapSubCommand string
13
-
14
-type Capability string
15
-
16
-func (capability Capability) String() string {
17
-	return string(capability)
18
-}
19
-
20
-type CapModifier rune
21
-
22
-func (mod CapModifier) String() string {
23
-	return string(mod)
24
-}
25
-
26
-type CapState uint
27
-
28
-type CapabilitySet map[Capability]bool
29
-
30
-func (set CapabilitySet) String() string {
31
-	strs := make([]string, len(set))
32
-	index := 0
33
-	for capability := range set {
34
-		strs[index] = string(capability)
35
-		index += 1
36
-	}
37
-	return strings.Join(strs, " ")
38
-}
39
-
40
-func (set CapabilitySet) DisableString() string {
41
-	parts := make([]string, len(set))
42
-	index := 0
43
-	for capability := range set {
44
-		parts[index] = Disable.String() + capability.String()
45
-		index += 1
46
-	}
47
-	return strings.Join(parts, " ")
48
-}
49
-
50
-// add, remove, list modes
51
-type ModeOp rune
52
-
53
-func (op ModeOp) String() string {
54
-	return string(op)
55
-}
56
-
57
-// user mode flags
58
-type UserMode rune
59
-
60
-func (mode UserMode) String() string {
61
-	return string(mode)
62
-}
63
-
64
-type Phase uint
65
-
66
-type ReplyCode interface {
67
-	String() string
68
-}
69
-
70
-type StringCode Name
71
-
72
-func (code StringCode) String() string {
73
-	return string(code)
74
-}
75
-
76
-type NumericCode uint
77
-
78
-func (code NumericCode) String() string {
79
-	return fmt.Sprintf("%03d", code)
80
-}
81
-
82
-// channel mode flags
83
-type ChannelMode rune
84
-
85
-func (mode ChannelMode) String() string {
86
-	return string(mode)
87
-}
88
-
89 12
 type ChannelNameMap map[Name]*Channel
90 13
 
91 14
 func (channels ChannelNameMap) Get(name Name) *Channel {
@@ -181,31 +104,7 @@ func (channels ChannelSet) First() *Channel {
181 104
 // interfaces
182 105
 //
183 106
 
184
-type Identifier interface {
107
+type Identifiable interface {
185 108
 	Id() Name
186 109
 	Nick() Name
187 110
 }
188
-
189
-type Replier interface {
190
-	Reply(...string)
191
-}
192
-
193
-type Command interface {
194
-	Code() StringCode
195
-	Client() *Client
196
-}
197
-
198
-type ServerCommand interface {
199
-	Command
200
-	HandleServer(*Server)
201
-}
202
-
203
-type AuthServerCommand interface {
204
-	Command
205
-	HandleAuthServer(*Server)
206
-}
207
-
208
-type RegServerCommand interface {
209
-	Command
210
-	HandleRegServer(*Server)
211
-}

Loading…
Cancel
Save