Browse Source

Merge remote-tracking branch 'origin/master' into user-mask

Conflicts:
	irc/reply.go
	irc/server.go
	irc/types.go
tags/v0.1.0
Jeremy Latt 10 years ago
parent
commit
5d46e7d7fa
7 changed files with 224 additions and 101 deletions
  1. 16
    8
      irc/channel.go
  2. 40
    34
      irc/client.go
  3. 20
    6
      irc/commands.go
  4. 30
    3
      irc/constants.go
  5. 19
    6
      irc/reply.go
  6. 73
    42
      irc/server.go
  7. 26
    2
      irc/types.go

+ 16
- 8
irc/channel.go View File

@@ -54,18 +54,26 @@ func (channel *Channel) ClientIsOperator(client *Client) bool {
54 54
 	return client.flags[Operator] || channel.members.HasMode(client, ChannelOperator)
55 55
 }
56 56
 
57
-func (channel *Channel) Nicks() []string {
57
+func (channel *Channel) Nicks(target *Client) []string {
58
+	isMultiPrefix := (target != nil) && target.capabilities[MultiPrefix]
58 59
 	nicks := make([]string, len(channel.members))
59 60
 	i := 0
60 61
 	for client, modes := range channel.members {
61
-		switch {
62
-		case modes[ChannelOperator]:
63
-			nicks[i] = "@" + client.Nick()
64
-		case modes[Voice]:
65
-			nicks[i] = "+" + client.Nick()
66
-		default:
67
-			nicks[i] = client.Nick()
62
+		if isMultiPrefix {
63
+			if modes[ChannelOperator] {
64
+				nicks[i] += "@"
65
+			}
66
+			if modes[Voice] {
67
+				nicks[i] += "+"
68
+			}
69
+		} else {
70
+			if modes[ChannelOperator] {
71
+				nicks[i] += "@"
72
+			} else if modes[Voice] {
73
+				nicks[i] += "+"
74
+			}
68 75
 		}
76
+		nicks[i] += client.Nick()
69 77
 		i += 1
70 78
 	}
71 79
 	return nicks

+ 40
- 34
irc/client.go View File

@@ -12,36 +12,41 @@ func IsNickname(nick string) bool {
12 12
 }
13 13
 
14 14
 type Client struct {
15
-	atime       time.Time
16
-	awayMessage string
17
-	channels    ChannelSet
18
-	commands    chan editableCommand
19
-	ctime       time.Time
20
-	flags       map[UserMode]bool
21
-	hasQuit     bool
22
-	hops        uint
23
-	hostname    string
24
-	idleTimer   *time.Timer
25
-	loginTimer  *time.Timer
26
-	nick        string
27
-	phase       Phase
28
-	quitTimer   *time.Timer
29
-	realname    string
30
-	server      *Server
31
-	socket      *Socket
32
-	username    string
15
+	atime        time.Time
16
+	authorized   bool
17
+	awayMessage  string
18
+	capabilities CapabilitySet
19
+	capState     CapState
20
+	channels     ChannelSet
21
+	commands     chan editableCommand
22
+	ctime        time.Time
23
+	flags        map[UserMode]bool
24
+	hasQuit      bool
25
+	hops         uint
26
+	hostname     string
27
+	idleTimer    *time.Timer
28
+	loginTimer   *time.Timer
29
+	nick         string
30
+	phase        Phase
31
+	quitTimer    *time.Timer
32
+	realname     string
33
+	server       *Server
34
+	socket       *Socket
35
+	username     string
33 36
 }
34 37
 
35 38
 func NewClient(server *Server, conn net.Conn) *Client {
36 39
 	now := time.Now()
37 40
 	client := &Client{
38
-		atime:    now,
39
-		channels: make(ChannelSet),
40
-		commands: make(chan editableCommand),
41
-		ctime:    now,
42
-		flags:    make(map[UserMode]bool),
43
-		phase:    server.InitPhase(),
44
-		server:   server,
41
+		atime:        now,
42
+		capState:     CapNone,
43
+		capabilities: make(CapabilitySet),
44
+		channels:     make(ChannelSet),
45
+		commands:     make(chan editableCommand),
46
+		ctime:        now,
47
+		flags:        make(map[UserMode]bool),
48
+		phase:        Registration,
49
+		server:       server,
45 50
 	}
46 51
 	client.socket = NewSocket(conn, client.commands)
47 52
 	client.loginTimer = time.AfterFunc(LOGIN_TIMEOUT, client.connectionTimeout)
@@ -68,6 +73,12 @@ func (client *Client) run() {
68 73
 	}
69 74
 }
70 75
 
76
+func (client *Client) connectionTimeout() {
77
+	client.commands <- &QuitCommand{
78
+		message: "connection timeout",
79
+	}
80
+}
81
+
71 82
 //
72 83
 // idle timer goroutine
73 84
 //
@@ -76,14 +87,6 @@ func (client *Client) connectionIdle() {
76 87
 	client.server.idle <- client
77 88
 }
78 89
 
79
-//
80
-// quit timer goroutine
81
-//
82
-
83
-func (client *Client) connectionTimeout() {
84
-	client.server.timeout <- client
85
-}
86
-
87 90
 //
88 91
 // server goroutine
89 92
 //
@@ -233,7 +236,10 @@ func (client *Client) ChangeNickname(nickname string) {
233 236
 	}
234 237
 }
235 238
 
236
-func (client *Client) Reply(reply string) {
239
+func (client *Client) Reply(reply string, args ...interface{}) {
240
+	if len(args) > 0 {
241
+		reply = fmt.Sprintf(reply, args...)
242
+	}
237 243
 	client.socket.Write(reply)
238 244
 }
239 245
 

+ 20
- 6
irc/commands.go View File

@@ -706,20 +706,34 @@ func NewOperCommand(args []string) (editableCommand, error) {
706 706
 	return cmd, nil
707 707
 }
708 708
 
709
-// TODO
710 709
 type CapCommand struct {
711 710
 	BaseCommand
712
-	args []string
711
+	subCommand   CapSubCommand
712
+	capabilities CapabilitySet
713 713
 }
714 714
 
715 715
 func (msg *CapCommand) String() string {
716
-	return fmt.Sprintf("CAP(args=%s)", msg.args)
716
+	return fmt.Sprintf("CAP(subCommand=%s, capabilities=%s)",
717
+		msg.subCommand, msg.capabilities)
717 718
 }
718 719
 
719 720
 func NewCapCommand(args []string) (editableCommand, error) {
720
-	return &CapCommand{
721
-		args: args,
722
-	}, nil
721
+	if len(args) < 1 {
722
+		return nil, NotEnoughArgsError
723
+	}
724
+
725
+	cmd := &CapCommand{
726
+		subCommand:   CapSubCommand(strings.ToUpper(args[0])),
727
+		capabilities: make(CapabilitySet),
728
+	}
729
+
730
+	if len(args) > 1 {
731
+		strs := spacesExpr.Split(args[1], -1)
732
+		for _, str := range strs {
733
+			cmd.capabilities[Capability(str)] = true
734
+		}
735
+	}
736
+	return cmd, nil
723 737
 }
724 738
 
725 739
 // HAPROXY support

+ 30
- 3
irc/constants.go View File

@@ -156,6 +156,7 @@ const (
156 156
 	ERR_TOOMANYTARGETS    NumericCode = 407
157 157
 	ERR_NOSUCHSERVICE     NumericCode = 408
158 158
 	ERR_NOORIGIN          NumericCode = 409
159
+	ERR_INVALIDCAPCMD     NumericCode = 410
159 160
 	ERR_NORECIPIENT       NumericCode = 411
160 161
 	ERR_NOTEXTTOSEND      NumericCode = 412
161 162
 	ERR_NOTOPLEVEL        NumericCode = 413
@@ -201,6 +202,14 @@ const (
201 202
 	ERR_UMODEUNKNOWNFLAG  NumericCode = 501
202 203
 	ERR_USERSDONTMATCH    NumericCode = 502
203 204
 
205
+	CAP_LS    CapSubCommand = "LS"
206
+	CAP_LIST  CapSubCommand = "LIST"
207
+	CAP_REQ   CapSubCommand = "REQ"
208
+	CAP_ACK   CapSubCommand = "ACK"
209
+	CAP_NAK   CapSubCommand = "NAK"
210
+	CAP_CLEAR CapSubCommand = "CLEAR"
211
+	CAP_END   CapSubCommand = "END"
212
+
204 213
 	Add    ModeOp = '+'
205 214
 	List   ModeOp = '='
206 215
 	Remove ModeOp = '-'
@@ -231,10 +240,28 @@ const (
231 240
 	Secret          ChannelMode = 's' // flag, deprecated
232 241
 	UserLimit       ChannelMode = 'l' // flag arg
233 242
 	Voice           ChannelMode = 'v' // arg
243
+
244
+	MultiPrefix Capability = "multi-prefix"
245
+	SASL        Capability = "sasl"
246
+
247
+	Disable CapModifier = '-'
248
+	Ack     CapModifier = '~'
249
+	Sticky  CapModifier = '='
250
+)
251
+
252
+var (
253
+	SupportedCapabilities = CapabilitySet{
254
+		MultiPrefix: true,
255
+	}
256
+)
257
+
258
+const (
259
+	Registration Phase = iota
260
+	Normal       Phase = iota
234 261
 )
235 262
 
236 263
 const (
237
-	Authorization Phase = iota
238
-	Registration  Phase = iota
239
-	Normal        Phase = iota
264
+	CapNone        CapState = iota
265
+	CapNegotiating CapState = iota
266
+	CapNegotiated  CapState = iota
240 267
 )

+ 19
- 6
irc/reply.go View File

@@ -250,11 +250,19 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
250 250
 
251 251
 	if channel != nil {
252 252
 		channelName = channel.name
253
-
254
-		if channel.members[client][ChannelOperator] {
255
-			flags += "@"
256
-		} else if channel.members[client][Voice] {
257
-			flags += "+"
253
+		if target.capabilities[MultiPrefix] {
254
+			if channel.members[client][ChannelOperator] {
255
+				flags += "@"
256
+			}
257
+			if channel.members[client][Voice] {
258
+				flags += "+"
259
+			}
260
+		} else {
261
+			if channel.members[client][ChannelOperator] {
262
+				flags += "@"
263
+			} else if channel.members[client][Voice] {
264
+				flags += "+"
265
+			}
258 266
 		}
259 267
 	}
260 268
 	target.NumericReply(RPL_WHOREPLY,
@@ -370,7 +378,7 @@ func (target *Client) RplListEnd(server *Server) {
370 378
 }
371 379
 
372 380
 func (target *Client) RplNamReply(channel *Channel) {
373
-	target.MultilineReply(channel.Nicks(), RPL_NAMREPLY,
381
+	target.MultilineReply(channel.Nicks(target), RPL_NAMREPLY,
374 382
 		"= %s :%s", channel)
375 383
 }
376 384
 
@@ -528,3 +536,8 @@ func (target *Client) ErrWasNoSuchNick(nickname string) {
528 536
 	target.NumericReply(ERR_WASNOSUCHNICK,
529 537
 		"%s :There was no such nickname", nickname)
530 538
 }
539
+
540
+func (target *Client) ErrInvalidCapCmd(subCommand CapSubCommand) {
541
+	target.NumericReply(ERR_INVALIDCAPCMD,
542
+		"%s :Invalid CAP subcommand", subCommand)
543
+}

+ 73
- 42
irc/server.go View File

@@ -29,7 +29,6 @@ type Server struct {
29 29
 	operators map[string][]byte
30 30
 	password  []byte
31 31
 	signals   chan os.Signal
32
-	timeout   chan *Client
33 32
 	whoWas    *WhoWasList
34 33
 }
35 34
 
@@ -46,7 +45,6 @@ func NewServer(config *Config) *Server {
46 45
 		newConns:  make(chan net.Conn, 16),
47 46
 		operators: config.Operators(),
48 47
 		signals:   make(chan os.Signal, 1),
49
-		timeout:   make(chan *Client, 16),
50 48
 		whoWas:    NewWhoWasList(100),
51 49
 	}
52 50
 
@@ -99,14 +97,6 @@ func (server *Server) processCommand(cmd Command) {
99 97
 	}
100 98
 
101 99
 	switch client.phase {
102
-	case Authorization:
103
-		authCmd, ok := cmd.(AuthServerCommand)
104
-		if !ok {
105
-			client.Quit("unexpected command")
106
-			return
107
-		}
108
-		authCmd.HandleAuthServer(server)
109
-
110 100
 	case Registration:
111 101
 		regCmd, ok := cmd.(RegServerCommand)
112 102
 		if !ok {
@@ -115,7 +105,7 @@ func (server *Server) processCommand(cmd Command) {
115 105
 		}
116 106
 		regCmd.HandleRegServer(server)
117 107
 
118
-	default:
108
+	case Normal:
119 109
 		srvCmd, ok := cmd.(ServerCommand)
120 110
 		if !ok {
121 111
 			client.ErrUnknownCommand(cmd.Code())
@@ -159,20 +149,10 @@ func (server *Server) Run() {
159 149
 
160 150
 		case client := <-server.idle:
161 151
 			client.Idle()
162
-
163
-		case client := <-server.timeout:
164
-			client.Quit("connection timeout")
165 152
 		}
166 153
 	}
167 154
 }
168 155
 
169
-func (server *Server) InitPhase() Phase {
170
-	if server.password == nil {
171
-		return Registration
172
-	}
173
-	return Authorization
174
-}
175
-
176 156
 //
177 157
 // listen goroutine
178 158
 //
@@ -208,7 +188,7 @@ func (s *Server) listen(addr string) {
208 188
 //
209 189
 
210 190
 func (s *Server) tryRegister(c *Client) {
211
-	if c.HasNick() && c.HasUsername() {
191
+	if c.HasNick() && c.HasUsername() && (c.capState != CapNegotiating) {
212 192
 		c.Register()
213 193
 		c.RplWelcome()
214 194
 		c.RplYourHost()
@@ -268,18 +248,10 @@ func (s *Server) Nick() string {
268 248
 }
269 249
 
270 250
 //
271
-// authorization commands
251
+// registration commands
272 252
 //
273 253
 
274
-func (msg *ProxyCommand) HandleAuthServer(server *Server) {
275
-	msg.Client().hostname = msg.hostname
276
-}
277
-
278
-func (msg *CapCommand) HandleAuthServer(server *Server) {
279
-	// TODO
280
-}
281
-
282
-func (msg *PassCommand) HandleAuthServer(server *Server) {
254
+func (msg *PassCommand) HandleRegServer(server *Server) {
283 255
 	client := msg.Client()
284 256
 	if msg.err != nil {
285 257
 		client.ErrPasswdMismatch()
@@ -287,27 +259,70 @@ func (msg *PassCommand) HandleAuthServer(server *Server) {
287 259
 		return
288 260
 	}
289 261
 
290
-	client.phase = Registration
262
+	client.authorized = true
291 263
 }
292 264
 
293
-func (msg *QuitCommand) HandleAuthServer(server *Server) {
294
-	msg.Client().Quit(msg.message)
295
-}
296
-
297
-//
298
-// registration commands
299
-//
300
-
301 265
 func (msg *ProxyCommand) HandleRegServer(server *Server) {
302 266
 	msg.Client().hostname = msg.hostname
303 267
 }
304 268
 
305 269
 func (msg *CapCommand) HandleRegServer(server *Server) {
306
-	// TODO
270
+	client := msg.Client()
271
+
272
+	switch msg.subCommand {
273
+	case CAP_LS:
274
+		client.capState = CapNegotiating
275
+		client.Reply("CAP LS * :%s", SupportedCapabilities)
276
+
277
+	case CAP_LIST:
278
+		client.Reply("CAP LIST * :%s", client.capabilities)
279
+
280
+	case CAP_REQ:
281
+		client.capState = CapNegotiating
282
+		for capability := range msg.capabilities {
283
+			if !SupportedCapabilities[capability] {
284
+				client.Reply("CAP NAK * :%s", msg.capabilities)
285
+				return
286
+			}
287
+		}
288
+		for capability := range msg.capabilities {
289
+			client.capabilities[capability] = true
290
+		}
291
+		client.Reply("CAP ACK * :%s", msg.capabilities)
292
+
293
+	case CAP_CLEAR:
294
+		format := strings.TrimRight(
295
+			strings.Repeat("%s%s ", len(client.capabilities)), " ")
296
+		args := make([]interface{}, len(client.capabilities))
297
+		index := 0
298
+		for capability := range client.capabilities {
299
+			args[index] = Disable
300
+			args[index+1] = capability
301
+			index += 2
302
+			delete(client.capabilities, capability)
303
+		}
304
+		client.Reply("CAP ACK * :"+format, args...)
305
+
306
+	case CAP_END:
307
+		client.capState = CapNegotiated
308
+		server.tryRegister(client)
309
+
310
+	default:
311
+		client.ErrInvalidCapCmd(msg.subCommand)
312
+	}
307 313
 }
308 314
 
309 315
 func (m *NickCommand) HandleRegServer(s *Server) {
310 316
 	client := m.Client()
317
+	if !client.authorized {
318
+		client.ErrPasswdMismatch()
319
+		client.Quit("bad password")
320
+		return
321
+	}
322
+
323
+	if client.capState == CapNegotiating {
324
+		client.capState = CapNegotiated
325
+	}
311 326
 
312 327
 	if m.nickname == "" {
313 328
 		client.ErrNoNicknameGiven()
@@ -329,11 +344,22 @@ func (m *NickCommand) HandleRegServer(s *Server) {
329 344
 }
330 345
 
331 346
 func (msg *RFC1459UserCommand) HandleRegServer(server *Server) {
347
+	client := msg.Client()
348
+	if !client.authorized {
349
+		client.ErrPasswdMismatch()
350
+		client.Quit("bad password")
351
+		return
352
+	}
332 353
 	msg.setUserInfo(server)
333 354
 }
334 355
 
335 356
 func (msg *RFC2812UserCommand) HandleRegServer(server *Server) {
336 357
 	client := msg.Client()
358
+	if !client.authorized {
359
+		client.ErrPasswdMismatch()
360
+		client.Quit("bad password")
361
+		return
362
+	}
337 363
 	flags := msg.Flags()
338 364
 	if len(flags) > 0 {
339 365
 		for _, mode := range msg.Flags() {
@@ -346,9 +372,14 @@ func (msg *RFC2812UserCommand) HandleRegServer(server *Server) {
346 372
 
347 373
 func (msg *UserCommand) setUserInfo(server *Server) {
348 374
 	client := msg.Client()
375
+	if client.capState == CapNegotiating {
376
+		client.capState = CapNegotiated
377
+	}
378
+
349 379
 	server.clients.Remove(client)
350 380
 	client.username, client.realname = msg.username, msg.realname
351 381
 	server.clients.Add(client)
382
+
352 383
 	server.tryRegister(client)
353 384
 }
354 385
 

+ 26
- 2
irc/types.go View File

@@ -11,6 +11,30 @@ import (
11 11
 
12 12
 type UserMaskSet map[string]bool
13 13
 
14
+type CapSubCommand string
15
+
16
+type Capability string
17
+
18
+type CapModifier rune
19
+
20
+func (mod CapModifier) String() string {
21
+	return string(mod)
22
+}
23
+
24
+type CapState uint
25
+
26
+type CapabilitySet map[Capability]bool
27
+
28
+func (set CapabilitySet) String() string {
29
+	strs := make([]string, len(set))
30
+	index := 0
31
+	for capability := range set {
32
+		strs[index] = string(capability)
33
+		index += 1
34
+	}
35
+	return strings.Join(strs, " ")
36
+}
37
+
14 38
 // add, remove, list modes
15 39
 type ModeOp rune
16 40
 
@@ -22,7 +46,7 @@ func (op ModeOp) String() string {
22 46
 type UserMode rune
23 47
 
24 48
 func (mode UserMode) String() string {
25
-	return fmt.Sprintf("%c", mode)
49
+	return string(mode)
26 50
 }
27 51
 
28 52
 type Phase uint
@@ -47,7 +71,7 @@ func (code NumericCode) String() string {
47 71
 type ChannelMode rune
48 72
 
49 73
 func (mode ChannelMode) String() string {
50
-	return fmt.Sprintf("%c", mode)
74
+	return string(mode)
51 75
 }
52 76
 
53 77
 type ChannelNameMap map[string]*Channel

Loading…
Cancel
Save