Przeglądaj źródła

Test ISupport/005. Test IRCParse. #3.

Steve Thielemann 2 lat temu
rodzic
commit
c235d12041
3 zmienionych plików z 309 dodań i 130 usunięć
  1. 116 2
      client_test.go
  2. 167 109
      irc-client.go
  3. 26 19
      ircd_test.go

+ 116 - 2
client_test.go

@@ -3,12 +3,91 @@ package ircclient
 import (
 import (
 	"fmt"
 	"fmt"
 	"net"
 	"net"
+	"reflect"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"syscall"
 	"syscall"
 	"testing"
 	"testing"
 )
 )
 
 
+func TestParse(t *testing.T) {
+	var Msg IRCMsg
+	var Expect map[string]IRCMsg = map[string]IRCMsg{
+		"PING :78629A0F5F3F164F": IRCMsg{
+			Cmd: "PING", Msg: "78629A0F5F3F164F", MsgParts: []string{"PING"}},
+		":irc.red-green.com 001 test :Welcome to the RedGreen IRC Network": IRCMsg{
+			From: "irc.red-green.com", Cmd: "001", To: "test",
+			Msg:      "Welcome to the RedGreen IRC Network",
+			MsgParts: []string{":irc.red-green.com", "001", "test"}},
+		":irc.red-green.com 375 test :- irc.red-green.com Message of the Day -": IRCMsg{
+			From: "irc.red-green.com", Cmd: "375", To: "test",
+			Msg:      "- irc.red-green.com Message of the Day -",
+			MsgParts: []string{":irc.red-green.com", "375", "test"}},
+		":irc.red-green.com 433 :Nick already in use": IRCMsg{
+			From: "irc.red-green.com", Cmd: "433",
+			Msg:      "Nick already in use",
+			MsgParts: []string{":irc.red-green.com", "433"}},
+		":[email protected] NOTICE test :Password accepted - you are now recognized.": IRCMsg{
+			From: "NickServ", Cmd: "NOTICE", To: "test",
+			Msg: "Password accepted - you are now recognized.",
+			MsgParts: []string{":[email protected]",
+				"NOTICE", "test"}},
+		":irc.red-green.com 005 meow-bot HCN INVEX KICKLEN=307 KNOCK MAP MAXCHANNELS=10 MAXLIST=b:60,e:60,I:60 MAXNICKLEN=30 MINNICKLEN=0 MODES=12 NAMESX NETWORK=RedGreen :are supported by this server": IRCMsg{
+			From: "irc.red-green.com", Cmd: "005", To: "meow-bot",
+			Msg: "are supported by this server",
+			MsgParts: []string{":irc.red-green.com", "005", "meow-bot", "HCN", "INVEX",
+				"KICKLEN=307", "KNOCK", "MAP", "MAXCHANNELS=10", "MAXLIST=b:60,e:60,I:60",
+				"MAXNICKLEN=30", "MINNICKLEN=0", "MODES=12", "NAMESX", "NETWORK=RedGreen"}},
+		":irc.red-green.com 353 meow-bot = #backstage :meow-bot performer5 performer4 performer3 performer1 performer2 Stage_Manager Apollo ~bugz": IRCMsg{
+			From: "irc.red-green.com", Cmd: "353", To: "meow-bot",
+			Msg:      "meow-bot performer5 performer4 performer3 performer1 performer2 Stage_Manager Apollo ~bugz",
+			MsgParts: []string{":irc.red-green.com", "353", "meow-bot", "=", "#backstage"}},
+	}
+
+	for line, msgout := range Expect {
+		Msg = IRCParse(line)
+		var fields []string = []string{"To", "From", "Cmd", "Msg"}
+		msg_value := reflect.ValueOf(Msg)
+		msgout_value := reflect.ValueOf(msgout)
+
+		for _, field := range fields {
+			var got string
+			var expect string
+			got = msg_value.FieldByName(field).String()
+			expect = msgout_value.FieldByName(field).String()
+			if got != expect {
+				t.Errorf("%s %s: got %s, expected %s", line, field, got, expect)
+			}
+		}
+
+		/*
+			if Msg.To != msgout.To {
+				t.Errorf("%s To: got %s, expected %s", line, Msg.To, msgout.To)
+			}
+			if Msg.From != msgout.From {
+				t.Errorf("%s From: got %s, expected %s", line, Msg.From, msgout.From)
+			}
+			if Msg.Cmd != msgout.Cmd {
+				t.Errorf("%s Cmd: got %s, expected %s", line, Msg.Cmd, msgout.Cmd)
+			}
+			if Msg.Msg != msgout.Msg {
+				t.Errorf("%s Msg: got %s, expected %s", line, Msg.Msg, msgout.Msg)
+			}
+		*/
+		if len(Msg.MsgParts) != len(msgout.MsgParts) {
+			t.Errorf("%s MsgParts got len %d, expected len %d", line, len(Msg.MsgParts), len(msgout.MsgParts))
+			// Length didn't match, don't need to compare each part.
+		} else {
+			// Length matches, compare each part
+			for idx := range Msg.MsgParts {
+				if Msg.MsgParts[idx] != msgout.MsgParts[idx] {
+					t.Errorf("%s MsgParts[%d] %s, expected %s", line, idx, Msg.MsgParts[idx], msgout.MsgParts[idx])
+				}
+			}
+		}
+	}
+}
+
 func TestConnect(t *testing.T) {
 func TestConnect(t *testing.T) {
 	var config IRCConfig = IRCConfig{Nick: "test",
 	var config IRCConfig = IRCConfig{Nick: "test",
 		Username:       "test",
 		Username:       "test",
@@ -33,12 +112,45 @@ func TestConnect(t *testing.T) {
 	defer config.Close()
 	defer config.Close()
 
 
 	var Msg IRCMsg
 	var Msg IRCMsg
-	var motd, identify bool
+	var motd, identify, support bool
+	var isupport map[string]string = map[string]string{
+		"AWAYLEN": "307", "BOT": "B", "CASEMAPPING": "ascii",
+		"CHANLIMIT": "#:10", "CHANMODES": "beI,kLf,lH,psmntirzMQNRTOVKDdGPZSCc",
+		"CHANNELLEN": "32", "CHANTYPES": "#", "CLIENTTAGDENY": "*,-draft/typing,-typing",
+		"DEAF": "d", "ELIST": "MNUCT", "EXCEPTS": "", "EXTBAN": "~,GptmTSOcarnqjf",
+		"HCN": "", "INVEX": "", "KICKLEN": "307", "KNOCK": "", "MAP": "", "MAXCHANNELS": "10",
+		"MAXLIST": "b:60,e:60,I:60", "MAXNICKLEN": "30", "MINNICKLEN": "0", "MODES": "12",
+		"NAMESX": "", "NETWORK": "RedGreen", "NICKLEN": "30", "PREFIX": "(qaohv)~&@%+",
+		"QUITLEN": "307", "SAFELIST": "", "SILENCE": "15", "STATUSMSG": "~&@%+",
+		"TARGMAX":  "DCCALLOW:,ISON:,JOIN:,KICK:4,KILL:,LIST:,NAMES:1,NOTICE:1,PART:,PRIVMSG:4,SAJOIN:,SAPART:,TAGMSG:1,USERHOST:,USERIP:,WATCH:,WHOIS:1,WHOWAS:1",
+		"TOPICLEN": "360", "UHNAMES": "", "USERIP": "", "WALLCHOPS": "", "WATCH": "128",
+		"WATCHOPTS": "A", "WHOX": "",
+	}
+
 	for Msg = range FromIRC {
 	for Msg = range FromIRC {
 		if Msg.Cmd == "EndMOTD" {
 		if Msg.Cmd == "EndMOTD" {
 			t.Log("Got EndMOTD")
 			t.Log("Got EndMOTD")
 			motd = true
 			motd = true
+
+			support = true
+			// Verify we parsed 005 ISupport:
+			for key, value := range isupport {
+				var got string
+				var has bool
+				got, has = config.ISupport[key]
+				if !has {
+					t.Errorf("Missing ISUPPORT[%s] expected (%s)", key, value)
+					support = false
+				} else {
+					if got != value {
+						t.Errorf("ISUPPORT[%s] got %s, expected %s", key, got, value)
+						support = false
+					}
+				}
+			}
+
 		}
 		}
+
 		if Msg.Cmd == "Identified" {
 		if Msg.Cmd == "Identified" {
 			t.Log("Identified")
 			t.Log("Identified")
 			identify = true
 			identify = true
@@ -51,7 +163,9 @@ func TestConnect(t *testing.T) {
 	if !identify {
 	if !identify {
 		t.Error("Missing Identified")
 		t.Error("Missing Identified")
 	}
 	}
-
+	if !support {
+		t.Error("Missing ISupport")
+	}
 	if config.MyNick != config.Nick {
 	if config.MyNick != config.Nick {
 		t.Errorf("Got %s, Expected %s", config.MyNick, config.Nick)
 		t.Errorf("Got %s, Expected %s", config.MyNick, config.Nick)
 	}
 	}

+ 167 - 109
irc-client.go

@@ -38,6 +38,89 @@ type IRCMsg struct {
 	Msg      string
 	Msg      string
 }
 }
 
 
+// Strip out the NICK part of the From message.
+// :[email protected] => NickServ
+func IRCNick(from string) string {
+	if from[0] == ':' {
+		from = from[1:]
+	}
+	var pos int = strings.Index(from, "!")
+	if pos != -1 {
+		from = from[:pos]
+	}
+	return from
+}
+
+/*
+IRCParse - split line into IRCMsg
+
+Everything after " :" is the Msg.
+Everything before " :" is split into MsgParts[].
+
+If >= 3 MsgParts {
+	To = MsgParts[2]
+}
+if >= 2 MsgParts {
+	From = IrcNic(MsgParts[0])
+	Cmd = MsgParts[1]
+} else {
+	Cmd = MsgParts[0]
+}
+
+Messages are of the following:
+
+:irc.red-green.com 001 test :Welcome to IRC
+^From              ^Cmd     ^Msg
+                       ^To
+^0                 ^1  ^2   MsgParts[]
+
+PING :1234567890
+^Cmd ^Msg
+^0 MsgParts[]
+
+*/
+func IRCParse(line string) IRCMsg {
+	var pos int = strings.Index(line, " :")
+	var results IRCMsg
+
+	if pos != -1 {
+		// Message is everything after " :"
+		results.Msg = line[pos+2:]
+		line = line[:pos]
+	}
+
+	results.MsgParts = strings.Split(line, " ")
+
+	if len(results.MsgParts) >= 2 {
+		results.From = IRCNick(results.MsgParts[0])
+		results.Cmd = results.MsgParts[1]
+	} else {
+		results.Cmd = results.MsgParts[0]
+	}
+	if len(results.MsgParts) >= 3 {
+		results.To = results.MsgParts[2]
+	}
+	return results
+}
+
+/*
+func oldIRCParse(line string) []string {
+	var pos int = strings.Index(line, " :")
+	var message string
+
+	if pos != -1 {
+		message = line[pos+2:]
+		line = line[:pos]
+	}
+	var results []string
+	results = strings.Split(line, " ")
+	if message != "" {
+		results = append(results, message)
+	}
+	return results
+}
+*/
+
 type IRCWrite struct {
 type IRCWrite struct {
 	To     string
 	To     string
 	Output string
 	Output string
@@ -326,33 +409,6 @@ func (Config *IRCConfig) Close() {
 	Config.wg.Wait()
 	Config.wg.Wait()
 }
 }
 
 
-func IRCParse(line string) []string {
-	var pos int = strings.Index(line, " :")
-	var message string
-
-	if pos != -1 {
-		message = line[pos+2:]
-		line = line[:pos]
-	}
-	var results []string
-	results = strings.Split(line, " ")
-	if message != "" {
-		results = append(results, message)
-	}
-	return results
-}
-
-func IRCNick(from string) string {
-	if from[0] == ':' {
-		from = from[1:]
-	}
-	var pos int = strings.Index(from, "!")
-	if pos != -1 {
-		from = from[:pos]
-	}
-	return from
-}
-
 func RandomNick(nick string) string {
 func RandomNick(nick string) string {
 	var result string = nick + "-"
 	var result string = nick + "-"
 	result += strconv.Itoa(rand.Intn(1000))
 	result += strconv.Itoa(rand.Intn(1000))
@@ -387,7 +443,7 @@ func (Config *IRCConfig) ReaderRoutine() {
 	for {
 	for {
 		var line string
 		var line string
 		var err error
 		var err error
-		var results []string
+		var results IRCMsg
 
 
 		line, err = Config.Reader.ReadString('\n')
 		line, err = Config.Reader.ReadString('\n')
 		if err == nil {
 		if err == nil {
@@ -398,59 +454,73 @@ func (Config *IRCConfig) ReaderRoutine() {
 
 
 			results = IRCParse(line)
 			results = IRCParse(line)
 
 
-			if results[1] == "433" && !registering {
-				// Nick already in use!
-				var newNick string = RandomNick(Config.Nick)
-				Config.MyNick = newNick
-				Config.PriorityWrite("NICK " + newNick)
-			}
-
-			if results[1] == "PRIVMSG" {
-				// Is this an action?
-				if len(results) >= 3 {
-					if (results[3][0] == '\x01') && (results[3][len(results[3])-1] == '\x01') {
-						// ACTION
-						results[1] = "CTCP"
-						results[3] = results[3][1 : len(results[3])-1]
-						log.Println("CTCP:", results[3])
-
-						// Process CTCP commands
-						if strings.HasPrefix(results[3], "ACTION ") {
-							results[1] = "ACTION"
-							results[3] = results[3][7:]
-						}
+			switch results.Cmd {
+			case "PING":
+				// Ping from Server
+				Config.PriorityWrite("PONG " + results.Msg)
+			case "005":
+				var support string
+				for _, support = range results.MsgParts[3:] {
+					if strings.Contains(support, "=") {
+						var suppart []string = strings.Split(support, "=")
+						Config.ISupport[suppart[0]] = suppart[1]
+					} else {
+						Config.ISupport[support] = ""
+					}
+				}
+			case "433":
+				if !registering {
+					// Nick already in use!
+					var newNick string = RandomNick(Config.Nick)
+					Config.MyNick = newNick
+					Config.PriorityWrite("NICK " + newNick)
+				}
+			case "PRIVMSG":
+				if (results.Msg[0] == '\x01') && (results.Msg[len(results.Msg)-1] == '\x01') {
+					// ACTION
+					results.Cmd = "CTCP"
+					results.Msg = results.Msg[1 : len(results.Msg)-1]
+					if Config.Debug_Output {
+						log.Println("CTCP:", results.Msg)
+					}
 
 
-						if strings.HasPrefix(results[3], "PING ") {
-							Config.WriteTo(IRCNick(results[0]),
-								fmt.Sprintf("NOTICE %s :\x01PING %s\x01",
-									IRCNick(results[0]),
-									results[3][5:]))
-						}
+					// Process CTCP commands
+					if strings.HasPrefix(results.Msg, "ACTION ") {
+						results.Cmd = "ACTION"
+						results.Msg = results.Msg[7:]
+					}
 
 
-						if results[3] == "VERSION" {
-							// Send version reply
-							var version string
-							if Config.Version != "" {
-								version = Config.Version + " " + VERSION
-							} else {
-								version = VERSION
-							}
-							Config.WriteTo(IRCNick(results[0]),
-								fmt.Sprintf("NOTICE %s :\x01VERSION %s\x01",
-									IRCNick(results[0]), version))
-						}
+					if strings.HasPrefix(results.Msg, "PING ") {
+						Config.WriteTo(IRCNick(results.From),
+							fmt.Sprintf("NOTICE %s :\x01PING %s\x01",
+								IRCNick(results.From),
+								results.Msg[5:]))
+					}
 
 
-						if results[3] == "TIME" {
-							// Send time reply
-							var now time.Time = time.Now()
-							Config.WriteTo(IRCNick(results[0]),
-								fmt.Sprintf("NOTICE %s :\x01TIME %s\x01",
-									IRCNick(results[0]),
-									now.Format(time.ANSIC)))
+					if results.Msg == "VERSION" {
+						// Send version reply
+						var version string
+						if Config.Version != "" {
+							version = Config.Version + " " + VERSION
+						} else {
+							version = VERSION
 						}
 						}
+						Config.WriteTo(IRCNick(results.From),
+							fmt.Sprintf("NOTICE %s :\x01VERSION %s\x01",
+								IRCNick(results.From), version))
+					}
 
 
+					if results.Msg == "TIME" {
+						// Send time reply
+						var now time.Time = time.Now()
+						Config.WriteTo(IRCNick(results.From),
+							fmt.Sprintf("NOTICE %s :\x01TIME %s\x01",
+								IRCNick(results.From),
+								now.Format(time.ANSIC)))
 					}
 					}
+
 				}
 				}
+
 			}
 			}
 		} else {
 		} else {
 			// This is likely, 2022/04/05 10:11:41 ReadString: EOF
 			// This is likely, 2022/04/05 10:11:41 ReadString: EOF
@@ -461,17 +531,20 @@ func (Config *IRCConfig) ReaderRoutine() {
 			return
 			return
 		}
 		}
 
 
-		var msg IRCMsg = IRCMsg{MsgParts: results}
-		if len(results) >= 3 {
-			msg.From = IRCNick(results[0])
-			msg.Cmd = results[1]
-			msg.To = results[2]
-			if len(results) >= 4 {
-				msg.Msg = results[3]
+		var msg IRCMsg = results
+		/*
+			IRCMsg{MsgParts: results}
+			if len(results) >= 3 {
+				msg.From = IRCNick(results[0])
+				msg.Cmd = results[1]
+				msg.To = results[2]
+				if len(results) >= 4 {
+					msg.Msg = results[3]
+				}
+			} else {
+				msg.Cmd = results[0]
 			}
 			}
-		} else {
-			msg.Cmd = results[0]
-		}
+		*/
 
 
 		if !Config.Debug_Output {
 		if !Config.Debug_Output {
 			if msg.Cmd == "ERROR" || msg.Cmd[0] == '4' || msg.Cmd[0] == '5' {
 			if msg.Cmd == "ERROR" || msg.Cmd[0] == '4' || msg.Cmd[0] == '5' {
@@ -480,45 +553,28 @@ func (Config *IRCConfig) ReaderRoutine() {
 			}
 			}
 		}
 		}
 
 
-		// Answer PING/PONG immediately.
-		if results[0] == "PING" {
-			Config.PriorityWrite("PONG " + results[1])
-		}
-
-		if (msg.Cmd == "401") || (msg.Cmd == "404") {
+		if msg.Cmd == "401" || msg.Cmd == "404" {
 			// No such nick/channel
 			// No such nick/channel
 			log.Printf("Remove %s from buffer.", msg.MsgParts[3])
 			log.Printf("Remove %s from buffer.", msg.MsgParts[3])
 			Config.DelChannel <- msg.MsgParts[3]
 			Config.DelChannel <- msg.MsgParts[3]
 		}
 		}
 
 
-		if msg.Cmd == "005" {
-			// ISUPPORT msg.MsgParts[3:len(msg.MsgParts)-1]
-			var support string
-			for _, support = range msg.MsgParts[3 : len(msg.MsgParts)-1] {
-				if strings.Contains(support, "=") {
-					var suppart []string = strings.Split(support, "=")
-					Config.ISupport[suppart[0]] = suppart[1]
-				} else {
-					Config.ISupport[support] = ""
-				}
-			}
-		}
-
 		if !Config.Registered {
 		if !Config.Registered {
 			// We're not registered yet
 			// We're not registered yet
 
 
 			// Answer the queries for SASL authentication
 			// Answer the queries for SASL authentication
-			if (msg.Cmd == "CAP") && (msg.Msg == "ACK") {
+			if msg.Cmd == "CAP" && msg.MsgParts[3] == "ACK" {
 				Config.PriorityWrite("AUTHENTICATE PLAIN")
 				Config.PriorityWrite("AUTHENTICATE PLAIN")
 			}
 			}
 
 
-			if msg.Cmd == "CAP" && msg.Msg == "NAK" {
+			if msg.Cmd == "CAP" && msg.MsgParts[3] == "NAK" {
 				// SASL Authentication failed/not available.
 				// SASL Authentication failed/not available.
 				Config.PriorityWrite("CAP END")
 				Config.PriorityWrite("CAP END")
 				Config.UseSASL = false
 				Config.UseSASL = false
 			}
 			}
 
 
-			if (msg.MsgParts[0] == "AUTHENTICATE") && (msg.MsgParts[1] == "+") {
+			// msg.Cmd == "AUTH..."
+			if msg.MsgParts[0] == "AUTHENTICATE" && msg.MsgParts[1] == "+" {
 				var userpass string = fmt.Sprintf("\x00%s\x00%s", Config.Nick, Config.Password)
 				var userpass string = fmt.Sprintf("\x00%s\x00%s", Config.Nick, Config.Password)
 				var b64 string = base64.StdEncoding.EncodeToString([]byte(userpass))
 				var b64 string = base64.StdEncoding.EncodeToString([]byte(userpass))
 				Config.PriorityWrite("AUTHENTICATE " + b64)
 				Config.PriorityWrite("AUTHENTICATE " + b64)
@@ -571,7 +627,7 @@ func (Config *IRCConfig) ReaderRoutine() {
 			// Were we kicked, is channel in AutoJoin?
 			// Were we kicked, is channel in AutoJoin?
 			// 2022/04/13 20:02:52 << :[email protected] KICK #bugz meow-bot :bugz
 			// 2022/04/13 20:02:52 << :[email protected] KICK #bugz meow-bot :bugz
 			// Msg: ircclient.IRCMsg{MsgParts:[]string{":[email protected]", "KICK", "#bugz", "meow-bot", "bugz"}, From:"bugz", To:"#bugz", Cmd:"KICK", Msg:"meow-bot"}
 			// Msg: ircclient.IRCMsg{MsgParts:[]string{":[email protected]", "KICK", "#bugz", "meow-bot", "bugz"}, From:"bugz", To:"#bugz", Cmd:"KICK", Msg:"meow-bot"}
-			if strings.Contains(msg.Msg, Config.MyNick) {
+			if msg.MsgParts[3] == Config.MyNick {
 				if Config.IsAuto(msg.To) {
 				if Config.IsAuto(msg.To) {
 					// Yes, we were kicked from AutoJoin channel
 					// Yes, we were kicked from AutoJoin channel
 					time.AfterFunc(time.Duration(Config.RejoinDelay)*time.Millisecond, func() { Config.WriteTo(msg.To, "JOIN "+msg.To) })
 					time.AfterFunc(time.Duration(Config.RejoinDelay)*time.Millisecond, func() { Config.WriteTo(msg.To, "JOIN "+msg.To) })
@@ -609,8 +665,10 @@ func (Config *IRCConfig) ReaderRoutine() {
 			// :meow NICK :meow-bot
 			// :meow NICK :meow-bot
 
 
 			if msg.From == Config.MyNick {
 			if msg.From == Config.MyNick {
-				Config.SetNick(msg.To)
-				log.Println("Nick is now:", Config.MyNick)
+				Config.SetNick(msg.Msg)
+				if Config.Debug_Output {
+					log.Println("Nick is now:", Config.MyNick)
+				}
 			}
 			}
 		}
 		}
 	}
 	}

+ 26 - 19
ircd_test.go

@@ -20,23 +20,7 @@ import (
 	"time"
 	"time"
 )
 )
 
 
-func setupSocket() (listen net.Listener, addr string) {
-	// establish network socket connection to set Comm_handle
-	var err error
-	var listener net.Listener
-	var address string
-
-	listener, err = net.Listen("tcp", "127.0.0.1:0")
-	if err != nil {
-		panic(err)
-	}
-
-	// I only need address for making the connection.
-	// Get address of listening socket
-	address = listener.Addr().String()
-	return listener, address
-}
-
+// Reuse the KeyPair tls.Certificate, it is valid for 1h
 var KeyPair tls.Certificate
 var KeyPair tls.Certificate
 var HasKeyPair bool
 var HasKeyPair bool
 
 
@@ -105,6 +89,23 @@ func setupTLSSocket() (listen net.Listener, addr string) {
 	return listener, address
 	return listener, address
 }
 }
 
 
+func setupSocket() (listen net.Listener, addr string) {
+	// establish network socket connection to set Comm_handle
+	var err error
+	var listener net.Listener
+	var address string
+
+	listener, err = net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		panic(err)
+	}
+
+	// I only need address for making the connection.
+	// Get address of listening socket
+	address = listener.Addr().String()
+	return listener, address
+}
+
 func ircWrite(server net.Conn, output string, t *testing.T) {
 func ircWrite(server net.Conn, output string, t *testing.T) {
 	t.Logf(">> %s\n", output)
 	t.Logf(">> %s\n", output)
 	server.Write([]byte(output + "\r\n"))
 	server.Write([]byte(output + "\r\n"))
@@ -122,9 +123,10 @@ var abortAfter int = 150 // Milliseconds to abort part 3
 	if nick == "bad", return 443 Nick already in use.
 	if nick == "bad", return 443 Nick already in use.
 	JOIN #kick, kicks once from the channel.
 	JOIN #kick, kicks once from the channel.
 	SASL authentication testing.
 	SASL authentication testing.
-	TLS support, we make our own certificates.
+	TLS support, we make our own certificates. See generateKeyPair()
 	Any messages sent to "echo" are reflected back to the client.
 	Any messages sent to "echo" are reflected back to the client.
 	Any messages to missing/#missing are returned 404/401.
 	Any messages to missing/#missing are returned 404/401.
+	After abortAfter milliseconds, we close the connection.
 */
 */
 func ircServer(listener net.Listener, t *testing.T, config *IRCConfig) {
 func ircServer(listener net.Listener, t *testing.T, config *IRCConfig) {
 	var server net.Conn
 	var server net.Conn
@@ -255,8 +257,13 @@ func ircServer(listener net.Listener, t *testing.T, config *IRCConfig) {
 	}
 	}
 
 
 	// Display MOTD
 	// Display MOTD
-	for _, line = range []string{":irc.red-green.com 001 %s :Welcome to the RedGreen IRC Network",
+	for _, line = range []string{
+		":irc.red-green.com 001 %s :Welcome to the RedGreen IRC Network",
 		":irc.red-green.com 002 %s :Your host is irc.red-green.com, running version UnrealIRCd-5.2.0.1",
 		":irc.red-green.com 002 %s :Your host is irc.red-green.com, running version UnrealIRCd-5.2.0.1",
+		":irc.red-green.com 005 %s AWAYLEN=307 BOT=B CASEMAPPING=ascii CHANLIMIT=#:10 CHANMODES=beI,kLf,lH,psmntirzMQNRTOVKDdGPZSCc CHANNELLEN=32 CHANTYPES=# CLIENTTAGDENY=*,-draft/typing,-typing DEAF=d ELIST=MNUCT EXCEPTS EXTBAN=~,GptmTSOcarnqjf :are supported by this server",
+		":irc.red-green.com 005 %s HCN INVEX KICKLEN=307 KNOCK MAP MAXCHANNELS=10 MAXLIST=b:60,e:60,I:60 MAXNICKLEN=30 MINNICKLEN=0 MODES=12 NAMESX NETWORK=RedGreen :are supported by this server",
+		":irc.red-green.com 005 %s NICKLEN=30 PREFIX=(qaohv)~&@%%+ QUITLEN=307 SAFELIST SILENCE=15 STATUSMSG=~&@%%+ TARGMAX=DCCALLOW:,ISON:,JOIN:,KICK:4,KILL:,LIST:,NAMES:1,NOTICE:1,PART:,PRIVMSG:4,SAJOIN:,SAPART:,TAGMSG:1,USERHOST:,USERIP:,WATCH:,WHOIS:1,WHOWAS:1 TOPICLEN=360 UHNAMES USERIP WALLCHOPS WATCH=128 :are supported by this server",
+		":irc.red-green.com 005 %s WATCHOPTS=A WHOX :are supported by this server",
 		":irc.red-green.com 375 %s :- irc.red-green.com Message of the Day -",
 		":irc.red-green.com 375 %s :- irc.red-green.com Message of the Day -",
 		":irc.red-green.com 372 %s :- ",
 		":irc.red-green.com 372 %s :- ",
 		":irc.red-green.com 376 %s :End of /MOTD command.",
 		":irc.red-green.com 376 %s :End of /MOTD command.",