| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 | 
							- package ircclient
 
- import (
 
- 	"bufio"
 
- 	"crypto/ecdsa"
 
- 	"crypto/elliptic"
 
- 	"crypto/rand"
 
- 	"crypto/tls"
 
- 	"crypto/x509"
 
- 	"crypto/x509/pkix"
 
- 	"encoding/base64"
 
- 	"encoding/pem"
 
- 	"fmt"
 
- 	"log"
 
- 	"math/big"
 
- 	rnd "math/rand"
 
- 	"net"
 
- 	"strings"
 
- 	"testing"
 
- 	"time"
 
- )
 
- // Reuse the KeyPair tls.Certificate, it is valid for 1h
 
- var KeyPair tls.Certificate
 
- var HasKeyPair bool
 
- func GenerateKeyPair() (keypair tls.Certificate) {
 
- 	if !HasKeyPair {
 
- 		KeyPair = generateKeyPair()
 
- 		HasKeyPair = true
 
- 	}
 
- 	return KeyPair
 
- }
 
- func generateKeyPair() (keypair tls.Certificate) {
 
- 	// generate test certificate
 
- 	priv, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
 
- 	durationBefore, _ := time.ParseDuration("-1h")
 
- 	notBefore := time.Now().Add(durationBefore)
 
- 	durationAfter, _ := time.ParseDuration("1h")
 
- 	notAfter := time.Now().Add(durationAfter)
 
- 	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 64)
 
- 	serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
 
- 	template := x509.Certificate{
 
- 		SerialNumber: serialNumber,
 
- 		Subject: pkix.Name{
 
- 			Organization: []string{"Test Certificate"},
 
- 		},
 
- 		NotBefore:             notBefore,
 
- 		NotAfter:              notAfter,
 
- 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
 
- 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
 
- 		BasicConstraintsValid: true,
 
- 		IsCA:                  true,
 
- 	}
 
- 	template.IPAddresses = append(template.IPAddresses, net.ParseIP("127.0.0.1"))
 
- 	template.IPAddresses = append(template.IPAddresses, net.ParseIP("::"))
 
- 	derBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
 
- 	c := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
 
- 	b, _ := x509.MarshalECPrivateKey(priv)
 
- 	k := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
 
- 	listenerKeyPair, _ := tls.X509KeyPair(c, k)
 
- 	return listenerKeyPair
 
- }
 
- func setupTLSSocket() (listen net.Listener, addr string) {
 
- 	// establish network socket connection to set Comm_handle
 
- 	var err error
 
- 	var listener net.Listener
 
- 	var address string
 
- 	var tlsconfig tls.Config
 
- 	var keypair tls.Certificate = GenerateKeyPair()
 
- 	tlsconfig.Certificates = make([]tls.Certificate, 0)
 
- 	tlsconfig.Certificates = append(tlsconfig.Certificates, keypair)
 
- 	listener, err = tls.Listen("tcp", "127.0.0.1:0", &tlsconfig)
 
- 	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 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) {
 
- 	t.Logf(">> %s\n", output)
 
- 	_, err := server.Write([]byte(output + "\r\n"))
 
- 	if err != nil {
 
- 		t.Error("server.Write:", err)
 
- 	}
 
- }
 
- var abortAfter int = 150 // Milliseconds to abort part 3
 
- /*
 
- 	mock up an irc server
 
- 	part 1 : nick, user
 
- 	part 2 : identify to services (if not SASL/SASL failed)
 
- 	part 3 : quit after abortAfter ms
 
- 	Features:
 
- 	if nick == "bad", return 443 Nick already in use.
 
- 	JOIN #kick, kicks once from the channel.
 
- 	SASL authentication testing.
 
- 	TLS support, we make our own certificates. See generateKeyPair()
 
- 	Any messages sent to "echo" are reflected back to the client.
 
- 	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 *IRCClient) {
 
- 	var server net.Conn
 
- 	var err error
 
- 	server, err = listener.Accept()
 
- 	if err != nil {
 
- 		t.Error("Failed to accept connection.")
 
- 		return
 
- 	}
 
- 	listener.Close()
 
- 	var reader *bufio.Reader = bufio.NewReader(server)
 
- 	var output, line, expect string
 
- 	var ping int64 = rnd.Int63()
 
- 	output = fmt.Sprintf("PING :%X", ping)
 
- 	ircWrite(server, output, t)
 
- 	var parts []string
 
- 	var hasNick, hasUser, hasPing, hasPass bool
 
- 	var capSASL, successSASL bool
 
- 	var part1 bool
 
- 	// part 1 :  User, Nick, ServerPass and Ping reply
 
- 	for !part1 {
 
- 		line, err = reader.ReadString('\n')
 
- 		if err == nil {
 
- 			line = strings.Trim(line, "\r\n")
 
- 			// process the received line here
 
- 			parts = strings.Split(line, " ")
 
- 			t.Logf("<< %s", line)
 
- 			switch parts[0] {
 
- 			case "CAP":
 
- 				if config.UseTLS && config.UseSASL {
 
- 					if line == "CAP REQ :sasl" {
 
- 						// Acknowledge we support SASL
 
- 						ircWrite(server, ":irc.red-green.com CAP * ACK :sasl", t)
 
- 						capSASL = true
 
- 					}
 
- 					if line == "CAP END" {
 
- 						capSASL = true
 
- 						if successSASL {
 
- 							part1 = true
 
- 						}
 
- 					}
 
- 				}
 
- 			case "AUTHENTICATE":
 
- 				if capSASL {
 
- 					if line == "AUTHENTICATE PLAIN" {
 
- 						ircWrite(server, "AUTHENTICATE +", t)
 
- 					} else {
 
- 						// Process SASL auth message
 
- 						var auth64 string = parts[1]
 
- 						byteauth, _ := base64.StdEncoding.DecodeString(auth64)
 
- 						var auth string = string(byteauth)
 
- 						auth = strings.ReplaceAll(auth, "\x00", " ")
 
- 						t.Log(auth)
 
- 						expect = fmt.Sprintf(" %s %s", config.Nick, config.Password)
 
- 						if expect != auth {
 
- 							t.Errorf("Got %s, Expected %s", auth, expect)
 
- 							ircWrite(server, fmt.Sprintf(":irc.red-green.com 904 %s :SASL authentication failed",
 
- 								config.Nick), t)
 
- 						} else {
 
- 							// Success!
 
- 							ircWrite(server, fmt.Sprintf(":irc.red-green.com 900 %s %s!%[email protected] %s :You are now logged in as %s.",
 
- 								config.Nick, config.Nick, config.Username, config.Nick, config.Nick), t)
 
- 							ircWrite(server, fmt.Sprintf(":irc.red-green.com 903 %s :SASL authentication successful",
 
- 								config.Nick), t)
 
- 							successSASL = true
 
- 						}
 
- 					}
 
- 				}
 
- 			case "PASS":
 
- 				expect = fmt.Sprintf("PASS %s", config.ServerPassword)
 
- 				if expect != line {
 
- 					t.Errorf("Got %s, Expected %s", line, expect)
 
- 				} else {
 
- 					hasPass = true
 
- 				}
 
- 			case "NICK":
 
- 				expect = fmt.Sprintf("NICK %s", config.MyNick)
 
- 				if expect != line {
 
- 					t.Errorf("Got %s, Expected %s", line, expect)
 
- 				} else {
 
- 					if config.MyNick == "bad" {
 
- 						// throw bad nick here
 
- 						// ircWrite(server, fmt.Sprintf(":irc.red-green.com 433 :Nick already in use."), t)
 
- 						ircWrite(server, ":irc.red-green.com 433 :Nick already in use.", t)
 
- 					}
 
- 					hasNick = true
 
- 				}
 
- 			case "USER":
 
- 				// USER meow-bot 0 * :Meooow!  bugz is my owner.
 
- 				expect = fmt.Sprintf("USER %s 0 * :%s", config.Username, config.Realname)
 
- 				if expect != line {
 
- 					t.Errorf("Got %s, Expected %s", line, expect)
 
- 				} else {
 
- 					hasUser = true
 
- 				}
 
- 			case "PONG":
 
- 				expect = fmt.Sprintf("PONG %X", ping)
 
- 				if expect != line {
 
- 					t.Errorf("Got %s, Expected %s", line, expect)
 
- 				} else {
 
- 					hasPing = true
 
- 				}
 
- 			}
 
- 			if !part1 {
 
- 				if !capSASL && hasNick && hasUser && hasPing && ((config.ServerPassword == "") || hasPass) {
 
- 					part1 = true
 
- 				}
 
- 			}
 
- 		} else {
 
- 			t.Error("Read Error:", err)
 
- 			server.Close()
 
- 			return
 
- 		}
 
- 	}
 
- 	if !part1 {
 
- 		t.Error("Expected to pass part1 (user/nick/pong)")
 
- 	}
 
- 	// Display MOTD
 
- 	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 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 372 %s :- ",
 
- 		":irc.red-green.com 376 %s :End of /MOTD command.",
 
- 	} {
 
- 		output = fmt.Sprintf(line, config.Nick)
 
- 		ircWrite(server, output, t)
 
- 	}
 
- 	if config.UseSASL {
 
- 		if !successSASL {
 
- 			log.Println("Failed SASL Authentication.")
 
- 		}
 
- 	}
 
- 	// part 2: nickserv/register  (if not already registered with SASL)
 
- 	var part2 bool
 
- 	if successSASL {
 
- 		ircWrite(server, fmt.Sprintf(":NickServ MODE %s :+r", config.Nick), t)
 
- 		part2 = true
 
- 	} else {
 
- 		if config.Password != "" {
 
- 			for _, line = range []string{":[email protected] NOTICE %s :This nickname is registered and protected.  If it is your",
 
- 				":[email protected] NOTICE %s :nick, type \x02/msg NickServ IDENTIFY \x1fpassword\x1f\x02.  Otherwise,"} {
 
- 				output = fmt.Sprintf(line, config.Nick)
 
- 				ircWrite(server, output, t)
 
- 			}
 
- 		} else {
 
- 			// No password, so we can't register.  Skip this part.
 
- 			part2 = true
 
- 		}
 
- 	}
 
- 	for !part2 {
 
- 		line, err = reader.ReadString('\n')
 
- 		if err == nil {
 
- 			line = strings.Trim(line, "\r\n")
 
- 			// process the received line here
 
- 			parts = strings.Split(line, " ")
 
- 			t.Logf("<< %s", line)
 
- 			switch parts[0] {
 
- 			case "NS":
 
- 				expect = fmt.Sprintf("NS IDENTIFY %s", config.Password)
 
- 				if expect != line {
 
- 					t.Errorf("Got %s, Expected %s", line, expect)
 
- 				}
 
- 				// ok, mark the user as registered
 
- 				output = fmt.Sprintf(":[email protected] NOTICE %s :Password accepted - you are now recognized.",
 
- 					config.Nick)
 
- 				ircWrite(server, output, t)
 
- 				output = fmt.Sprintf(":NickServ MODE %s :+r", config.Nick)
 
- 				ircWrite(server, output, t)
 
- 				part2 = true
 
- 			}
 
- 		} else {
 
- 			t.Error("Read Error:", err)
 
- 			server.Close()
 
- 			return
 
- 		}
 
- 	}
 
- 	if !part2 {
 
- 		t.Error("Expected to pass part2 (ns identify/+r)")
 
- 	}
 
- 	time.AfterFunc(time.Millisecond*time.Duration(abortAfter), func() { server.Close() })
 
- 	t.Log("Ok, Identified...")
 
- 	var Kicked bool
 
- 	for {
 
- 		line, err = reader.ReadString('\n')
 
- 		if err == nil {
 
- 			line = strings.Trim(line, "\r\n")
 
- 			// process the received line here
 
- 			parts = strings.Split(line, " ")
 
- 			t.Logf("<< %s", line)
 
- 			switch parts[0] {
 
- 			case "JOIN":
 
- 				for _, channel := range strings.Split(parts[1], ",") {
 
- 					output = fmt.Sprintf(":%s JOIN :%s", config.MyNick, channel)
 
- 					ircWrite(server, output, t)
 
- 					output = fmt.Sprintf(":irc.server 332 %s %s :Topic for (%s)", config.MyNick, channel, channel)
 
- 					ircWrite(server, output, t)
 
- 					output = fmt.Sprintf(":irc.server 333 %s %s user %d", config.MyNick, channel, time.Now().Unix())
 
- 					ircWrite(server, output, t)
 
- 					if strings.Contains(channel, "kick") {
 
- 						if !Kicked {
 
- 							Kicked = true
 
- 							output = fmt.Sprintf("user KICK %s %s :Get out", channel, config.MyNick)
 
- 							ircWrite(server, output, t)
 
- 						}
 
- 					}
 
- 				}
 
- 			}
 
- 			switch parts[0] {
 
- 			case "PRIVMSG", "NOTICE":
 
- 				if parts[1] == "echo" {
 
- 					parts[2] = parts[2][1:]
 
- 					// echo user, return whatever was sent back to them.
 
- 					output = fmt.Sprintf(":%s %s %s :%s", "echo", parts[0], config.MyNick, strings.Join(parts[2:], " "))
 
- 					ircWrite(server, output, t)
 
- 				}
 
- 				if strings.Contains(parts[1], "missing") {
 
- 					// Sending to missing user or channel.
 
- 					var number int
 
- 					if strings.Contains(parts[1], "#") {
 
- 						number = 404
 
- 					} else {
 
- 						number = 401
 
- 					}
 
- 					output = fmt.Sprintf(":irc.red-green.com %d %s %s :No such nick/channel", number, config.GetNick(), parts[1])
 
- 					ircWrite(server, output, t)
 
- 				}
 
- 			case "NICK":
 
- 				output = fmt.Sprintf(":%s NICK :%s", config.MyNick, parts[1])
 
- 				ircWrite(server, output, t)
 
- 			case "QUIT":
 
- 				output = fmt.Sprintf("ERROR: Closing link:%s", config.MyNick)
 
- 				ircWrite(server, output, t)
 
- 				server.Close()
 
- 			}
 
- 		} else {
 
- 			t.Log("Read Error:", err)
 
- 			return
 
- 		}
 
- 	}
 
- }
 
 
  |