Browse Source

Add regexes for commands.

tags/v0.1.0
Jeremy Latt 12 years ago
parent
commit
faece3e7f8
5 changed files with 201 additions and 74 deletions
  1. 2
    2
      goircd.go
  2. 31
    12
      src/irc/client.go
  3. 18
    19
      src/irc/net.go
  4. 96
    32
      src/irc/protocol.go
  5. 54
    9
      src/irc/server.go

+ 2
- 2
goircd.go View File

@@ -1,5 +1,5 @@
1 1
 package main
2
-// http://tools.ietf.org/html/rfc1459
2
+// http://tools.ietf.org/html/rfc2812
3 3
 
4 4
 import (
5 5
 	"irc"
@@ -7,5 +7,5 @@ import (
7 7
 
8 8
 func main() {
9 9
 	server := irc.NewServer()
10
-	server.Listen(":6697")
10
+	server.Listen(":6667")
11 11
 }

+ 31
- 12
src/irc/client.go View File

@@ -1,28 +1,47 @@
1 1
 package irc
2 2
 
3 3
 import (
4
+	"log"
4 5
 	"net"
6
+	"strings"
5 7
 )
6 8
 
9
+type Message struct {
10
+	command string
11
+	args    string
12
+	client  *Client
13
+}
7 14
 
8 15
 type Client struct {
9
-	conn net.Conn
10
-	ch chan Message
16
+	addr       net.Addr
17
+	send       chan string
18
+	recv       chan string
19
+	username   string
20
+	realname   string
21
+	nick       string
11 22
 }
12 23
 
13 24
 func NewClient(conn net.Conn) *Client {
14
-	return &Client{conn, NewMessageChan(NewStringChan(conn))}
25
+	client := new(Client)
26
+	client.addr = conn.RemoteAddr()
27
+	client.send = StringWriteChan(conn)
28
+	client.recv = StringReadChan(conn)
29
+	return client
15 30
 }
16 31
 
17
-// Write messages from the client to the server.
18
-func (c *Client) Communicate(server chan Message) {
19
-	for message := range c.ch {
20
-		message.client = c
21
-		server <- message
22
-	}
23
-	c.Close()
32
+// Adapt `chan string` to a `chan Message`.
33
+func (c *Client) Communicate(server *Server) {
34
+	go func() {
35
+		for str := range c.recv {
36
+			parts := strings.SplitN(str, " ", 2)
37
+			server.Send(Message{parts[0], parts[1], c})
38
+		}
39
+	}()
24 40
 }
25 41
 
26
-func (c *Client) Close() {
27
-	c.conn.Close()
42
+func (c *Client) Send(lines ...string) {
43
+	for _, line := range lines {
44
+		log.Printf("C <- S: %s", line)
45
+		c.send <- line
46
+	}
28 47
 }

+ 18
- 19
src/irc/net.go View File

@@ -3,43 +3,42 @@ package irc
3 3
 import (
4 4
 	"bufio"
5 5
 	"log"
6
+	"strings"
6 7
 	"net"
7 8
 )
8 9
 
9 10
 // Adapt `net.Conn` to a `chan string`.
10
-func NewStringChan(conn net.Conn) chan string {
11
+func StringReadChan(conn net.Conn) chan string {
11 12
 	ch := make(chan string)
12
-	rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
13
-
14
-	done := make(chan bool)
15
-	go func() {
16
-		<- done
17
-		close(ch)
18
-	}()
19
-
20
-	// conn -> ch
13
+	reader := bufio.NewReader(conn)
21 14
 	go func() {
22 15
 		for {
23
-			line, err := rw.ReadString('\n')
16
+			line, err := reader.ReadString('\n')
17
+			if (line != "") {
18
+				ch <- strings.TrimSpace(line)
19
+			}
24 20
 			if err != nil {
25
-				log.Print("StringChan[read]: %v", err)
21
+				log.Print("StringReadChan[read]: ", err)
26 22
 				break
27 23
 			}
28
-			ch <- line
29 24
 		}
30
-		done <- true
25
+		close(ch)
31 26
 	}()
27
+	return ch
28
+}
32 29
 
33
-	// ch -> conn
30
+func StringWriteChan(conn net.Conn) chan string {
31
+	ch := make(chan string)
32
+	writer := bufio.NewWriter(conn)
34 33
 	go func() {
35 34
 		for str := range ch {
36
-			if _, err := rw.WriteString(str + "\r\n"); err != nil {
37
-				log.Print("StringChan[write]: %v", err)
35
+			if _, err := writer.WriteString(str + "\r\n"); err != nil {
36
+				log.Print("StringWriteChan[write]: ", err)
38 37
 				break
39 38
 			}
40
-			rw.Flush()
39
+			writer.Flush()
41 40
 		}
42
-		done <- true
41
+		close(ch)
43 42
 	}()
44 43
 
45 44
 	return ch

+ 96
- 32
src/irc/protocol.go View File

@@ -1,43 +1,107 @@
1 1
 package irc
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 )
5 6
 
6
-type Message struct {
7
-	line string
8
-	client *Client
7
+const (
8
+	VERSION = "goircd-1"
9
+)
10
+
11
+const (
12
+	RPL_WELCOME  = "001"
13
+	RPL_YOURHOST = "002"
14
+	RPL_CREATED  = "003"
15
+	RPL_MYINFO   = "004"
16
+	RPL_NONE     = "300"
17
+)
18
+
19
+func ReplyWelcome(nick string, user string, host string) string {
20
+	return fmt.Sprintf("%s %s Welcome to the Internet Relay Network %s!%s@%s", RPL_WELCOME, nick, nick, user, host)
9 21
 }
10 22
 
11
-func (m *Message) Encode() string {
12
-	return m.line
23
+func ReplyYourHost(nick string, server string) string {
24
+	return fmt.Sprintf("%s %s Your host is %s, running version %s", RPL_YOURHOST, nick, server, VERSION)
13 25
 }
14 26
 
27
+func ReplyCreated(nick string, created string) string {
28
+	return fmt.Sprintf("%s %s This server was created %s", RPL_CREATED, nick, created)
29
+}
30
+
31
+func ReplyMyInfo(nick string, servername string) string {
32
+	return fmt.Sprintf("%s %s %s %s <user modes> <channel modes>", RPL_MYINFO, nick, servername, VERSION)
33
+}
34
+
35
+const (
36
+	ERR_NOSUCHNICK       = "401"
37
+	ERR_NOSUCHSERVER     = "402"
38
+	ERR_NOSUCHCHANNEL    = "403"
39
+	ERR_UNKNOWNCOMMAND   = "421"
40
+	ERR_NICKNAMEINUSE    = "433"
41
+	ERR_NEEDMOREPARAMS   = "461"
42
+	ERR_ALREADYREGISTRED = "462"
43
+	ERR_USERSDONTMATCH   = "502"
44
+)
15 45
 
16
-// Adapt `chan string` to a `chan Message`.
17
-func NewMessageChan(strch chan string) chan Message {
18
-	msgch := make(chan Message)
19
-
20
-	done := make(chan bool)
21
-	go func() {
22
-		<- done
23
-		close(msgch)
24
-	}()
25
-
26
-	// str -> msg
27
-	go func() {
28
-		for str := range strch {
29
-			msgch <- Message{str, nil}
30
-		}
31
-		done <- true
32
-	}()
33
-
34
-	// msg -> str
35
-	go func() {
36
-		for message := range msgch {
37
-			strch <- message.Encode()
38
-		}
39
-		done <- true
40
-	}()
41
-
42
-	return msgch
46
+func ErrAlreadyRegistered(nick string) string {
47
+	return fmt.Sprintf("%s %s :You may not reregister", ERR_ALREADYREGISTRED, nick)
43 48
 }
49
+
50
+func ErrNickNameInUse(nick string) string {
51
+	return fmt.Sprintf("%s %s :Nickname is already in use", ERR_NICKNAMEINUSE, nick)
52
+}
53
+
54
+func ErrUnknownCommand(nick string, command string) string {
55
+	return fmt.Sprintf("%s %s %s :Unknown command", ERR_UNKNOWNCOMMAND, nick, command)
56
+}
57
+
58
+
59
+const (
60
+	RE_PASS     = "(?P<password>\\S+)"
61
+	RE_NICK     = "(?P<nickname>\\S+)"
62
+	RE_USER     = "(?P<user>\\S+) (?P<mode>\\d) (?:\\S+) :(?P<realname>.+)"
63
+	RE_OPER     = "(?P<name>\\S+) (?P<password>\\S+)"
64
+	RE_MODE     = "(?P<nickname>\\S+)(?: (?P<mode>[-+][iwroOs]+))*"
65
+	RE_SERVICE  = "(?P<nickname>\\S+) (?P<reserved1>\\S+) (?P<distribution>\\S+) (?P<type>\\S+) (?P<reserved2>\\S+) :(?P<info>.+)"
66
+	RE_QUIT     = "(?P<message>.*)"
67
+	RE_SQUIT    = "(?P<server>\\S+) :(?P<comment>.+)"
68
+	RE_JOIN     = "0|(?:(?P<channels>\\S+(?:,\\S+)*)(?: (?P<keys>\\S+(?:,\\S+)*))?)"
69
+	RE_PART     = "(?P<channels>\\S+(?:,\\S+)*)(?: :(?P<message>.+))?"
70
+	RE_MODE_CH  = "(?P<channel>\\S+)(?: (?P<mode>[-+][iwroOs]+))*" // XXX incomplete
71
+	RE_TOPIC    = "(?P<channel>\\S+)(?: :(?P<topic>.+))?"
72
+	RE_NAMES    = "(?:(?P<channels>\\S+(?:,\\S+)*)(?: (?P<target>\\S+))?)?"
73
+	RE_LIST     = "(?:(?P<channels>\\S+(?:,\\S+)*)(?: (?P<target>\\S+))?)?"
74
+	RE_INVITE   = "(?P<nickname>\\S+) (?P<channel>\\S+)"
75
+	RE_KICK     = "(?P<channels>\\S+(?:,\\S+)*) (?P<users>\\S+(?:,\\S+))(?: :(?P<comment>.+))?"
76
+	RE_PRIVMSG  = "(?P<target>\\S+) :(?P<text>.+)"
77
+	RE_NOTICE   = "(?P<target>\\S+) :(?P<text>.+)"
78
+	RE_MOTD     = "(?P<target>\\S+)?"
79
+	RE_LUSERS   = "(?:(?P<mask>\\S+)(?: (?P<target>\\S+))?)?"
80
+	RE_VERSION  = "(?P<target>\\S+)?"
81
+	RE_STATS    = "(?:(?P<query>\\S+)(?: (?P<target>\\S+))?)?"
82
+	RE_LINKS    = "(?:(?P<remote>\\S+) )?(?P<mask>\\S+)"
83
+	RE_TIME     = "(?P<target>\\S+)?"
84
+	RE_CONNECT  = "(?P<target>\\S+) (?P<port>\\d+)(?: (?P<remote>\\S+))?"
85
+	RE_TRACE    = "(?P<target>\\S+)?"
86
+	RE_ADMIN    = "(?P<target>\\S+)?"
87
+	RE_INFO     = "(?P<target>\\S+)?"
88
+	RE_SERVLIST = "" // XXX
89
+	RE_SQUERY   = "" // XXX
90
+	RE_WHO      = "" // XXX
91
+	RE_WHOIS    = "" // XXX
92
+	RE_WHOWAS   = "" // XXX
93
+	RE_KILL     = "(?P<nickname>\\S+) :(?P<comment>.+)"
94
+	RE_PING     = "(?P<server1>\\S+)(?: (?P<server2>\\S+))?"
95
+	RE_PONG     = "(?P<server1>\\S+)(?: (?P<server2>\\S+))?"
96
+	RE_ERROR    = ":(?P<error>.+)"
97
+	RE_AWAY     = ":(?P<text>.+)"
98
+	RE_REHASH   = ""
99
+	RE_DIE      = ""
100
+	RE_RESTART  = ""
101
+	RE_SUMMON   = "(?P<user>\\S+)(?: (?P<target>\\S+)(?: (?P<channel>\\S+))?)?"
102
+	RE_USERS    = "(?P<target>\\S+)?"
103
+	RE_WALLOPS  = ":(?P<text>.+)"
104
+	RE_USERHOST = "(?P<nicknames>\\S+(?: \\S+)*)"
105
+	RE_ISON     = "(?P<nicknames>\\S+(?: \\S+)*)"
106
+)
107
+

+ 54
- 9
src/irc/server.go View File

@@ -3,14 +3,17 @@ package irc
3 3
 import (
4 4
 	"log"
5 5
 	"net"
6
+	"strings"
6 7
 )
7 8
 
8 9
 type Server struct {
9 10
 	ch chan Message
11
+	users map[string]*Client
12
+	nicks map[string]*Client
10 13
 }
11 14
 
12 15
 func NewServer() *Server {
13
-	server := Server{make(chan Message)}
16
+	server := Server{make(chan Message), make(map[string]*Client), make(map[string]*Client)}
14 17
 	go server.Receive()
15 18
 	return &server
16 19
 }
@@ -18,27 +21,69 @@ func NewServer() *Server {
18 21
 func (s *Server) Listen(addr string) {
19 22
 	listener, err := net.Listen("tcp", addr)
20 23
 	if err != nil {
21
-		log.Fatal("Server.Listen: %v", err)
24
+		log.Fatal("Server.Listen: ", err)
22 25
 	}
23 26
 
24 27
 	for {
25 28
 		conn, err := listener.Accept()
26 29
 		if err != nil {
27
-			log.Print("Server.Listen: %v", err)
30
+			log.Print("Server.Listen: ", err)
28 31
 			continue
29 32
 		}
30
-		client := NewClient(conn)
31
-		go client.Communicate(s.ch)
33
+		go NewClient(conn).Communicate(s)
32 34
 	}
33 35
 }
34 36
 
35 37
 func (s *Server) Receive() {
36 38
 	for message := range s.ch {
37
-		log.Print("Server.Receive: %v", message.line)
38
-		message.client.ch <- Message{"pong: " + message.line, nil}
39
+		log.Printf("C -> S: %s %s", message.command, message.args)
40
+		switch message.command {
41
+		case "PING":
42
+			message.client.Send("PONG")
43
+		case "PASS":
44
+			s.PassCommand(message.client, message.args)
45
+		case "USER":
46
+			s.UserCommand(message.client, message.args)
47
+		case "NICK":
48
+			s.NickCommand(message.client, message.args)
49
+		default:
50
+			message.client.Send(ErrUnknownCommand(message.client.nick, message.command))
51
+		}
39 52
 	}
40 53
 }
41 54
 
42
-func (s *Server) Close() {
43
-	close(s.ch)
55
+func (s *Server) Send(m Message) {
56
+	s.ch <- m
57
+}
58
+
59
+// commands
60
+
61
+func (s *Server) PassCommand(c *Client, args string) {
62
+}
63
+
64
+func (s *Server) UserCommand(c *Client, args string) {
65
+	parts := strings.SplitN(args, " ", 4)
66
+	username, _, _, realname := parts[0], parts[1], parts[2], parts[3]
67
+	if s.users[username] != nil {
68
+		c.Send(ErrAlreadyRegistered(c.nick))
69
+		return
70
+	}
71
+	c.username, c.realname = username, realname
72
+	s.users[username] = c
73
+	if c.nick != "" {
74
+		c.Send(
75
+			ReplyWelcome(c.nick, c.username, "localhost"),
76
+			ReplyYourHost(c.nick, "irc.jlatt.com"),
77
+			ReplyCreated(c.nick, "2012/04/07"),
78
+			ReplyMyInfo(c.nick, "irc.jlatt.com"))
79
+	}
80
+}
81
+
82
+func (s *Server) NickCommand(c *Client, nick string) {
83
+	if s.nicks[nick] != nil {
84
+		c.Send(ErrNickNameInUse(nick))
85
+		return
86
+	}
87
+	c.nick = nick
88
+	s.nicks[nick] = c
44 89
 }

Loading…
Cancel
Save