ircd_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. package ircclient
  2. import (
  3. "bufio"
  4. "crypto/ecdsa"
  5. "crypto/elliptic"
  6. "crypto/rand"
  7. "crypto/tls"
  8. "crypto/x509"
  9. "crypto/x509/pkix"
  10. "encoding/base64"
  11. "encoding/pem"
  12. "fmt"
  13. "log"
  14. "math/big"
  15. rnd "math/rand"
  16. "net"
  17. "strings"
  18. "testing"
  19. "time"
  20. )
  21. // Reuse the KeyPair tls.Certificate, it is valid for 1h
  22. var KeyPair tls.Certificate
  23. var HasKeyPair bool
  24. // Display debug output when running tests.
  25. const TEST_DEBUG_OUTPUT bool = false
  26. // Give Key Pair for server TLS connection
  27. //
  28. // This reuses the KeyPair if already generated in this run.
  29. // Otherwise, it call generateKeyPair to do the actual certificate generation.
  30. func GenerateKeyPair() (keypair tls.Certificate) {
  31. if !HasKeyPair {
  32. KeyPair = generateKeyPair()
  33. HasKeyPair = true
  34. }
  35. return KeyPair
  36. }
  37. // Generate certificate for server TLS connection
  38. func generateKeyPair() (keypair tls.Certificate) {
  39. // generate test certificate
  40. priv, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
  41. durationBefore, _ := time.ParseDuration("-1h")
  42. notBefore := time.Now().Add(durationBefore)
  43. durationAfter, _ := time.ParseDuration("1h")
  44. notAfter := time.Now().Add(durationAfter)
  45. serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 64)
  46. serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
  47. template := x509.Certificate{
  48. SerialNumber: serialNumber,
  49. Subject: pkix.Name{
  50. Organization: []string{"Test Certificate"},
  51. },
  52. NotBefore: notBefore,
  53. NotAfter: notAfter,
  54. KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
  55. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  56. BasicConstraintsValid: true,
  57. IsCA: true,
  58. }
  59. template.IPAddresses = append(template.IPAddresses, net.ParseIP("127.0.0.1"))
  60. template.IPAddresses = append(template.IPAddresses, net.ParseIP("::"))
  61. derBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
  62. c := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
  63. b, _ := x509.MarshalECPrivateKey(priv)
  64. k := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
  65. listenerKeyPair, _ := tls.X509KeyPair(c, k)
  66. return listenerKeyPair
  67. }
  68. // Setup TLS Socket for IRCD connection testing.
  69. func setupTLSSocket() (listen net.Listener, addr string) {
  70. // establish network socket connection to set Comm_handle
  71. var err error
  72. var listener net.Listener
  73. var address string
  74. var tlsconfig tls.Config
  75. var keypair tls.Certificate = GenerateKeyPair()
  76. tlsconfig.Certificates = make([]tls.Certificate, 0)
  77. tlsconfig.Certificates = append(tlsconfig.Certificates, keypair)
  78. listener, err = tls.Listen("tcp", "127.0.0.1:0", &tlsconfig)
  79. if err != nil {
  80. panic(err)
  81. }
  82. // I only need address for making the connection.
  83. // Get address of listening socket
  84. address = listener.Addr().String()
  85. return listener, address
  86. }
  87. // Setup Socket for IRCD connection testing.
  88. func setupSocket() (listen net.Listener, addr string) {
  89. // establish network socket connection to set Comm_handle
  90. var err error
  91. var listener net.Listener
  92. var address string
  93. listener, err = net.Listen("tcp", "127.0.0.1:0")
  94. if err != nil {
  95. panic(err)
  96. }
  97. // I only need address for making the connection.
  98. // Get address of listening socket
  99. address = listener.Addr().String()
  100. return listener, address
  101. }
  102. // Write to IRCD server (testing)
  103. func ircWrite(server net.Conn, output string, t *testing.T) {
  104. if TEST_DEBUG_OUTPUT {
  105. t.Logf(">> %s\n", output)
  106. }
  107. _, err := server.Write([]byte(output + "\r\n"))
  108. if err != nil {
  109. t.Error("server.Write:", err)
  110. }
  111. }
  112. var abortAfter int = 150 // Milliseconds to abort part 3
  113. /*
  114. Mock up a test IRCD server
  115. part 1 : nick, user
  116. part 2 : identify to services (if not SASL/SASL failed)
  117. part 3 : quit after abortAfter ms
  118. Features:
  119. if nick == "bad", return 443 Nick already in use.
  120. JOIN #kick, kicks once from the channel.
  121. SASL authentication testing.
  122. TLS support, we make our own certificates. See generateKeyPair()
  123. Any messages sent to "echo" are reflected back to the client.
  124. Any messages to missing/#missing are returned 404/401.
  125. After abortAfter milliseconds, we close the connection.
  126. */
  127. func ircServer(listener net.Listener, t *testing.T, config *IRCClient) {
  128. var server net.Conn
  129. var err error
  130. server, err = listener.Accept()
  131. if err != nil {
  132. t.Error("Failed to accept connection.")
  133. return
  134. }
  135. listener.Close()
  136. var reader *bufio.Reader = bufio.NewReader(server)
  137. var output, line, expect string
  138. var ping int64 = rnd.Int63()
  139. output = fmt.Sprintf("PING :%X", ping)
  140. ircWrite(server, output, t)
  141. var parts []string
  142. var hasNick, hasUser, hasPing, hasPass bool
  143. var capSASL, successSASL bool
  144. var part1 bool
  145. // part 1 : User, Nick, ServerPass and Ping reply
  146. for !part1 {
  147. line, err = reader.ReadString('\n')
  148. if err == nil {
  149. line = strings.Trim(line, "\r\n")
  150. // process the received line here
  151. parts = strings.Split(line, " ")
  152. if TEST_DEBUG_OUTPUT {
  153. t.Logf("<< %s", line)
  154. }
  155. switch parts[0] {
  156. case "CAP":
  157. if config.UseTLS && config.UseSASL {
  158. if line == "CAP REQ :sasl" {
  159. // Acknowledge we support SASL
  160. ircWrite(server, ":irc.red-green.com CAP * ACK :sasl", t)
  161. capSASL = true
  162. }
  163. if line == "CAP END" {
  164. capSASL = true
  165. if successSASL {
  166. part1 = true
  167. }
  168. }
  169. }
  170. case "AUTHENTICATE":
  171. if capSASL {
  172. if line == "AUTHENTICATE PLAIN" {
  173. ircWrite(server, "AUTHENTICATE +", t)
  174. } else {
  175. // Process SASL auth message
  176. var auth64 string = parts[1]
  177. byteauth, _ := base64.StdEncoding.DecodeString(auth64)
  178. var auth string = string(byteauth)
  179. auth = strings.ReplaceAll(auth, "\x00", " ")
  180. t.Log(auth)
  181. expect = fmt.Sprintf(" %s %s", config.Nick, config.Password)
  182. if expect != auth {
  183. t.Errorf("Got %s, Expected %s", auth, expect)
  184. ircWrite(server, fmt.Sprintf(":irc.red-green.com 904 %s :SASL authentication failed",
  185. config.Nick), t)
  186. } else {
  187. // Success!
  188. ircWrite(server, fmt.Sprintf(":irc.red-green.com 900 %s %s!%[email protected] %s :You are now logged in as %s.",
  189. config.Nick, config.Nick, config.Username, config.Nick, config.Nick), t)
  190. ircWrite(server, fmt.Sprintf(":irc.red-green.com 903 %s :SASL authentication successful",
  191. config.Nick), t)
  192. successSASL = true
  193. }
  194. }
  195. }
  196. case "PASS":
  197. expect = fmt.Sprintf("PASS %s", config.ServerPassword)
  198. if expect != line {
  199. t.Errorf("Got %s, Expected %s", line, expect)
  200. } else {
  201. hasPass = true
  202. }
  203. case "NICK":
  204. expect = fmt.Sprintf("NICK %s", config.MyNick)
  205. if expect != line {
  206. t.Errorf("Got %s, Expected %s", line, expect)
  207. } else {
  208. if config.MyNick == "bad" {
  209. // throw bad nick here
  210. // ircWrite(server, fmt.Sprintf(":irc.red-green.com 433 :Nick already in use."), t)
  211. ircWrite(server, ":irc.red-green.com 433 :Nick already in use.", t)
  212. }
  213. hasNick = true
  214. }
  215. case "USER":
  216. // USER meow-bot 0 * :Meooow! bugz is my owner.
  217. expect = fmt.Sprintf("USER %s 0 * :%s", config.Username, config.Realname)
  218. if expect != line {
  219. t.Errorf("Got %s, Expected %s", line, expect)
  220. } else {
  221. hasUser = true
  222. }
  223. case "PONG":
  224. expect = fmt.Sprintf("PONG %X", ping)
  225. if expect != line {
  226. t.Errorf("Got %s, Expected %s", line, expect)
  227. } else {
  228. hasPing = true
  229. }
  230. }
  231. if !part1 {
  232. if !capSASL && hasNick && hasUser && hasPing && ((config.ServerPassword == "") || hasPass) {
  233. part1 = true
  234. }
  235. }
  236. } else {
  237. t.Error("Read Error:", err)
  238. server.Close()
  239. return
  240. }
  241. }
  242. if !part1 {
  243. t.Error("Expected to pass part1 (user/nick/pong)")
  244. }
  245. // Display MOTD
  246. for _, line = range []string{
  247. ":irc.red-green.com 001 %s :Welcome to the RedGreen IRC Network",
  248. ":irc.red-green.com 002 %s :Your host is irc.red-green.com, running version UnrealIRCd-5.2.0.1",
  249. ":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",
  250. ":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",
  251. ":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",
  252. ":irc.red-green.com 005 %s WATCHOPTS=A WHOX :are supported by this server",
  253. ":irc.red-green.com 375 %s :- irc.red-green.com Message of the Day -",
  254. ":irc.red-green.com 372 %s :- ",
  255. ":irc.red-green.com 376 %s :End of /MOTD command.",
  256. } {
  257. output = fmt.Sprintf(line, config.Nick)
  258. ircWrite(server, output, t)
  259. }
  260. if config.UseSASL {
  261. if !successSASL {
  262. log.Println("Failed SASL Authentication.")
  263. }
  264. }
  265. // part 2: nickserv/register (if not already registered with SASL)
  266. var part2 bool
  267. if successSASL {
  268. ircWrite(server, fmt.Sprintf(":NickServ MODE %s :+r", config.Nick), t)
  269. part2 = true
  270. } else {
  271. if config.Password != "" {
  272. for _, line = range []string{":[email protected] NOTICE %s :This nickname is registered and protected. If it is your",
  273. ":[email protected] NOTICE %s :nick, type \x02/msg NickServ IDENTIFY \x1fpassword\x1f\x02. Otherwise,"} {
  274. output = fmt.Sprintf(line, config.Nick)
  275. ircWrite(server, output, t)
  276. }
  277. } else {
  278. // No password, so we can't register. Skip this part.
  279. part2 = true
  280. }
  281. }
  282. for !part2 {
  283. line, err = reader.ReadString('\n')
  284. if err == nil {
  285. line = strings.Trim(line, "\r\n")
  286. // process the received line here
  287. parts = strings.Split(line, " ")
  288. t.Logf("<< %s", line)
  289. switch parts[0] {
  290. case "NS":
  291. expect = fmt.Sprintf("NS IDENTIFY %s", config.Password)
  292. if expect != line {
  293. t.Errorf("Got %s, Expected %s", line, expect)
  294. }
  295. // ok, mark the user as registered
  296. output = fmt.Sprintf(":[email protected] NOTICE %s :Password accepted - you are now recognized.",
  297. config.Nick)
  298. ircWrite(server, output, t)
  299. output = fmt.Sprintf(":NickServ MODE %s :+r", config.Nick)
  300. ircWrite(server, output, t)
  301. part2 = true
  302. }
  303. } else {
  304. t.Error("Read Error:", err)
  305. server.Close()
  306. return
  307. }
  308. }
  309. if !part2 {
  310. t.Error("Expected to pass part2 (ns identify/+r)")
  311. }
  312. time.AfterFunc(time.Millisecond*time.Duration(abortAfter), func() { server.Close() })
  313. t.Log("Ok, Identified...")
  314. var Kicked bool
  315. for {
  316. line, err = reader.ReadString('\n')
  317. if err == nil {
  318. line = strings.Trim(line, "\r\n")
  319. // process the received line here
  320. parts = strings.Split(line, " ")
  321. if TEST_DEBUG_OUTPUT {
  322. t.Logf("<< %s", line)
  323. }
  324. switch parts[0] {
  325. case "JOIN":
  326. for _, channel := range strings.Split(parts[1], ",") {
  327. output = fmt.Sprintf(":%s JOIN :%s", config.MyNick, channel)
  328. ircWrite(server, output, t)
  329. output = fmt.Sprintf(":irc.server 332 %s %s :Topic for (%s)", config.MyNick, channel, channel)
  330. ircWrite(server, output, t)
  331. output = fmt.Sprintf(":irc.server 333 %s %s user %d", config.MyNick, channel, time.Now().Unix())
  332. ircWrite(server, output, t)
  333. if strings.Contains(channel, "kick") {
  334. if !Kicked {
  335. Kicked = true
  336. output = fmt.Sprintf("user KICK %s %s :Get out", channel, config.MyNick)
  337. ircWrite(server, output, t)
  338. }
  339. }
  340. }
  341. }
  342. switch parts[0] {
  343. case "PRIVMSG", "NOTICE":
  344. if parts[1] == "echo" {
  345. parts[2] = parts[2][1:]
  346. // echo user, return whatever was sent back to them.
  347. output = fmt.Sprintf(":%s %s %s :%s", "echo", parts[0], config.MyNick, strings.Join(parts[2:], " "))
  348. ircWrite(server, output, t)
  349. }
  350. if strings.Contains(parts[1], "missing") {
  351. // Sending to missing user or channel.
  352. var number int
  353. if strings.Contains(parts[1], "#") {
  354. number = 404
  355. } else {
  356. number = 401
  357. }
  358. output = fmt.Sprintf(":irc.red-green.com %d %s %s :No such nick/channel", number, config.GetNick(), parts[1])
  359. ircWrite(server, output, t)
  360. }
  361. case "NICK":
  362. output = fmt.Sprintf(":%s NICK :%s", config.MyNick, parts[1])
  363. ircWrite(server, output, t)
  364. case "QUIT":
  365. output = fmt.Sprintf("ERROR: Closing link:%s", config.MyNick)
  366. ircWrite(server, output, t)
  367. server.Close()
  368. }
  369. } else {
  370. t.Log("Read Error:", err)
  371. return
  372. }
  373. }
  374. }