irc-client.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. package ircclient
  2. import (
  3. "bufio"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "encoding/base64"
  7. "fmt"
  8. "io"
  9. "log"
  10. "math/rand"
  11. "net"
  12. "os"
  13. "os/signal"
  14. "strconv"
  15. "strings"
  16. "syscall"
  17. "time"
  18. )
  19. func StrInArray(strings []string, str string) bool {
  20. for _, s := range strings {
  21. if s == str {
  22. return true
  23. }
  24. }
  25. return false
  26. }
  27. type IRCMsg struct {
  28. MsgParts []string
  29. From string
  30. To string
  31. Cmd string
  32. Msg string
  33. }
  34. type IRCWrite struct {
  35. To string
  36. Output string
  37. }
  38. type IRCConfig struct {
  39. Port int `json: Port`
  40. Hostname string `json: Hostname`
  41. UseTLS bool `json: UseTLS` // Use TLS Secure connection
  42. UseSASL bool `json: UseSASL` // Authenticate via SASL
  43. Insecure bool `json: Insecure` // Allow self-signed certificates
  44. Nick string `json: Nick`
  45. Username string `json: Username`
  46. Realname string `json: Realname`
  47. Password string `json: Password` // Password for nickserv
  48. ServerPassword string `json: ServerPassword` // Password for server
  49. AutoJoin []string `json: AutoJoin` // Channels to auto-join
  50. RejoinDelay int `json: RejoinDelay` // ms to rejoin
  51. MyNick string
  52. Socket net.Conn
  53. Reader *bufio.Reader
  54. ReadChannel chan IRCMsg
  55. ReadEvents []string
  56. WriteChannel chan IRCWrite
  57. DelChannel chan string
  58. Registered bool
  59. Flood_Num int // Number of lines sent before considered a flood
  60. Flood_Time int // Number of Seconds to track previous messages
  61. Flood_Delay int // Delay between sending when flood protection on (Milliseconds)
  62. OnExit func()
  63. }
  64. // Writer *bufio.Writer
  65. func (Config *IRCConfig) Connect() bool {
  66. var err error
  67. if Config.Flood_Num == 0 {
  68. Config.Flood_Num = 5
  69. }
  70. if Config.Flood_Time == 0 {
  71. Config.Flood_Time = 10
  72. }
  73. if Config.Flood_Delay == 0 {
  74. Config.Flood_Delay = 1000
  75. }
  76. Config.Registered = false
  77. if Config.ReadChannel == nil {
  78. log.Println("Warning: ReadChannel is nil.")
  79. }
  80. if Config.UseTLS {
  81. var tlsConfig tls.Config
  82. if !Config.Insecure {
  83. certPool := x509.NewCertPool()
  84. tlsConfig.ClientCAs = certPool
  85. tlsConfig.InsecureSkipVerify = false
  86. } else {
  87. tlsConfig.InsecureSkipVerify = true
  88. }
  89. Config.Socket, err = tls.Dial("tcp", Config.Hostname+":"+strconv.Itoa(Config.Port), &tlsConfig)
  90. if err != nil {
  91. log.Fatal("tls.Dial:", err)
  92. }
  93. // Config.Writer = bufio.NewWriter(Config.TLSSocket)
  94. Config.Reader = bufio.NewReader(Config.Socket)
  95. } else {
  96. Config.Socket, err = net.Dial("tcp", Config.Hostname+":"+strconv.Itoa(Config.Port))
  97. if err != nil {
  98. log.Fatal("net.Dial:", err)
  99. }
  100. // Config.Writer = bufio.NewWriter(Config.Socket)
  101. Config.Reader = bufio.NewReader(Config.Socket)
  102. }
  103. Config.WriteChannel = make(chan IRCWrite)
  104. Config.DelChannel = make(chan string)
  105. // We are connected.
  106. go Config.WriterRoutine()
  107. // Registration
  108. if Config.UseTLS && Config.UseSASL {
  109. Config.PriorityWrite("CAP REQ :sasl")
  110. }
  111. if Config.ServerPassword != "" {
  112. Config.PriorityWrite(fmt.Sprintf("PASS %s", Config.ServerPassword))
  113. }
  114. // Register nick
  115. Config.MyNick = Config.Nick
  116. Config.PriorityWrite(fmt.Sprintf("NICK %s", Config.Nick))
  117. Config.PriorityWrite(fmt.Sprintf("USER %s 0 * :%s", Config.Username, Config.Realname))
  118. // Config.Writer.Flush()
  119. go Config.ReaderRoutine()
  120. return true
  121. }
  122. // Low level write to [TLS]Socket routine.
  123. func (Config *IRCConfig) write(output string) error {
  124. var err error
  125. log.Println(">>", output)
  126. output += "\r\n"
  127. /*
  128. if Config.UseTLS {
  129. _, err = Config.TLSSocket.Write([]byte(output))
  130. } else {
  131. */
  132. _, err = Config.Socket.Write([]byte(output))
  133. return err
  134. }
  135. func (Config *IRCConfig) WriterRoutine() {
  136. var err error
  137. var throttle ThrottleBuffer
  138. var Flood FloodTrack
  139. throttle.init()
  140. Flood.Init(Config.Flood_Num, Config.Flood_Time)
  141. // signal handler
  142. var sigChannel chan os.Signal = make(chan os.Signal, 1)
  143. signal.Notify(sigChannel, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
  144. // Change this into a select with timeout.
  145. // Timeout, if there's something to be buffered.
  146. for {
  147. if throttle.Life_sucks {
  148. select {
  149. case output := <-Config.WriteChannel:
  150. if output.To == "" {
  151. err = Config.write(output.Output)
  152. if err != nil {
  153. log.Println("Writer:", err)
  154. }
  155. continue
  156. }
  157. throttle.push(output.To, output.Output)
  158. case <-sigChannel:
  159. if Config.OnExit != nil {
  160. Config.OnExit()
  161. }
  162. os.Exit(2)
  163. case remove := <-Config.DelChannel:
  164. log.Printf("Remove: [%s]\n", remove)
  165. throttle.delete(remove)
  166. case <-time.After(time.Millisecond * time.Duration(Config.Flood_Delay)):
  167. // Send from the buffer
  168. // debugging the flood buffer
  169. // log.Printf("targets: %#v\n", targets)
  170. // log.Printf("last: %d\n", last)
  171. // log.Printf("buffer: %#v\n", buffer)
  172. var msg string = throttle.pop()
  173. err = Config.write(msg)
  174. if err != nil {
  175. log.Println("Writer:", err)
  176. }
  177. }
  178. } else {
  179. // Life is good.
  180. select {
  181. case <-sigChannel:
  182. if Config.OnExit != nil {
  183. Config.OnExit()
  184. }
  185. os.Exit(2)
  186. case output := <-Config.WriteChannel:
  187. if output.To == "" {
  188. err = Config.write(output.Output)
  189. if err != nil {
  190. log.Println("Writer:", err)
  191. }
  192. continue
  193. }
  194. if Flood.Full() {
  195. throttle.push(output.To, output.Output)
  196. } else {
  197. // Flood limits not reached
  198. Flood.Save()
  199. err = Config.write(output.Output)
  200. if err != nil {
  201. log.Println("Writer:", err)
  202. }
  203. }
  204. }
  205. }
  206. }
  207. log.Println("~WriterRoutine")
  208. }
  209. func (Config *IRCConfig) Close() {
  210. /*
  211. if Config.UseTLS {
  212. Config.TLSSocket.Close()
  213. } else {
  214. */
  215. Config.Socket.Close()
  216. }
  217. func IRCParse(line string) []string {
  218. var pos int = strings.Index(line, " :")
  219. var message string
  220. if pos != -1 {
  221. message = line[pos+2:]
  222. line = line[:pos]
  223. }
  224. var results []string
  225. results = strings.Split(line, " ")
  226. if message != "" {
  227. results = append(results, message)
  228. }
  229. return results
  230. }
  231. func IRCNick(from string) string {
  232. if from[0] == ':' {
  233. from = from[1:]
  234. }
  235. var pos int = strings.Index(from, "!")
  236. if pos != -1 {
  237. from = from[:pos]
  238. }
  239. return from
  240. }
  241. func RandomNick(nick string) string {
  242. var result string = nick + "-"
  243. result += strconv.Itoa(rand.Intn(1000))
  244. return result
  245. }
  246. func (Config *IRCConfig) PriorityWrite(output string) {
  247. Config.WriteChannel <- IRCWrite{To: "", Output: output}
  248. }
  249. func (Config *IRCConfig) WriteTo(to string, output string) {
  250. Config.WriteChannel <- IRCWrite{To: to, Output: output}
  251. }
  252. func (Config *IRCConfig) Msg(to string, message string) {
  253. Config.WriteTo(to, fmt.Sprintf("PRIVMSG %s :%s", to, message))
  254. }
  255. func (Config *IRCConfig) Notice(to string, message string) {
  256. Config.WriteTo(to, fmt.Sprintf("NOTICE %s :%s", to, message))
  257. }
  258. func (Config *IRCConfig) Action(to string, message string) {
  259. Config.WriteTo(to, fmt.Sprintf("PRIVMSG %s :\x01ACTION %s\x01", to, message))
  260. }
  261. func (Config *IRCConfig) ReaderRoutine() {
  262. for {
  263. var line string
  264. var err error
  265. var results []string
  266. line, err = Config.Reader.ReadString('\n')
  267. if err == nil {
  268. line = strings.Trim(line, "\r\n")
  269. log.Println("<<", line)
  270. results = IRCParse(line)
  271. if results[1] == "433" {
  272. // Nick already in use!
  273. var newNick string = RandomNick(Config.Nick)
  274. Config.MyNick = newNick
  275. Config.PriorityWrite("NICK " + newNick)
  276. }
  277. if results[1] == "PRIVMSG" {
  278. // Is this an action?
  279. if len(results) >= 3 {
  280. if (results[3][0] == '\x01') && (results[3][len(results[3])-1] == '\x01') {
  281. // ACTION
  282. results[1] = "CTCP"
  283. results[3] = results[3][1 : len(results[3])-1]
  284. log.Println("CTCP:", results[3])
  285. // Process CTCP commands
  286. if strings.HasPrefix(results[3], "ACTION ") {
  287. results[1] = "ACTION"
  288. results[3] = results[3][7:]
  289. }
  290. if strings.HasPrefix(results[3], "PING ") {
  291. Config.WriteTo(IRCNick(results[0]),
  292. fmt.Sprintf("NOTICE %s :\x01PING %s\x01",
  293. IRCNick(results[0]),
  294. results[3][5:]))
  295. }
  296. if results[3] == "VERSION" {
  297. // Send version reply
  298. Config.WriteTo(IRCNick(results[0]),
  299. fmt.Sprintf("NOTICE %s :\x01VERSION red-green.com/irc-client 0.1\x01",
  300. IRCNick(results[0])))
  301. }
  302. if results[3] == "TIME" {
  303. // Send time reply
  304. var now time.Time = time.Now()
  305. Config.WriteTo(IRCNick(results[0]),
  306. fmt.Sprintf("NOTICE %s :\x01TIME %s\x01",
  307. IRCNick(results[0]),
  308. now.Format(time.ANSIC)))
  309. }
  310. }
  311. }
  312. }
  313. } else {
  314. // This is likely, 2022/04/05 10:11:41 ReadString: EOF
  315. if err != io.EOF {
  316. log.Println("ReadString:", err)
  317. }
  318. close(Config.ReadChannel)
  319. return
  320. }
  321. var msg IRCMsg = IRCMsg{MsgParts: results}
  322. if len(results) >= 3 {
  323. msg.From = IRCNick(results[0])
  324. msg.Cmd = results[1]
  325. msg.To = results[2]
  326. if len(results) >= 4 {
  327. msg.Msg = results[3]
  328. }
  329. } else {
  330. msg.Cmd = results[0]
  331. }
  332. // Answer PING/PONG immediately.
  333. if results[0] == "PING" {
  334. Config.PriorityWrite("PONG " + results[1])
  335. }
  336. if (msg.Cmd == "401") || (msg.Cmd == "404") {
  337. // No such nick/channel
  338. log.Printf("Remove %s from buffer.", msg.MsgParts[3])
  339. Config.DelChannel <- msg.MsgParts[3]
  340. }
  341. if !Config.Registered {
  342. // We're not registered yet
  343. // Answer the queries for SASL authentication
  344. if (msg.Cmd == "CAP") && (msg.Msg == "ACK") {
  345. Config.PriorityWrite("AUTHENTICATE PLAIN")
  346. }
  347. if (msg.MsgParts[0] == "AUTHENTICATE") && (msg.MsgParts[1] == "+") {
  348. var userpass string = fmt.Sprintf("\x00%s\x00%s", Config.Nick, Config.Password)
  349. var b64 string = base64.StdEncoding.EncodeToString([]byte(userpass))
  350. Config.PriorityWrite("AUTHENTICATE " + b64)
  351. }
  352. if msg.Cmd == "903" {
  353. // Success SASL
  354. Config.PriorityWrite("CAP END")
  355. Config.Registered = true
  356. }
  357. if msg.Cmd == "904" {
  358. // Failed SASL
  359. Config.PriorityWrite("CAP END")
  360. // Should we exit here?
  361. }
  362. /*
  363. 2022/04/06 19:12:11 << :[email protected] NOTICE meow :This nickname is registered and protected. If it is your
  364. 2022/04/06 19:12:11 << :[email protected] NOTICE meow :nick, type /msg NickServ IDENTIFY password. Otherwise,
  365. 2022/04/06 19:12:11 << :[email protected] NOTICE meow :please choose a different nick.
  366. */
  367. if (msg.From == "NickServ") && (msg.Cmd == "NOTICE") {
  368. if strings.Contains(msg.Msg, "IDENTIFY") {
  369. Config.PriorityWrite(fmt.Sprintf("NS IDENTIFY %s", Config.Password))
  370. }
  371. // :[email protected] NOTICE meow :Password accepted - you are now recognized.
  372. }
  373. if !Config.UseSASL && (msg.Cmd == "900") {
  374. Config.Registered = true
  375. }
  376. }
  377. // This is a better way of knowing when we've identified for services
  378. if (msg.Cmd == "MODE") && (msg.To == Config.MyNick) {
  379. // This should probably be look for + and contains "r"
  380. if (msg.Msg[0] == '+') && (strings.Contains(msg.Msg, "r")) {
  381. Config.ReadChannel <- IRCMsg{Cmd: "Identified"}
  382. }
  383. if len(Config.AutoJoin) > 0 {
  384. Config.PriorityWrite("JOIN " + strings.Join(Config.AutoJoin, ","))
  385. }
  386. }
  387. if msg.Cmd == "KICK" {
  388. // Were we kicked, is channel in AutoJoin?
  389. // 2022/04/13 20:02:52 << :[email protected] KICK #bugz meow-bot :bugz
  390. // Msg: ircclient.IRCMsg{MsgParts:[]string{":[email protected]", "KICK", "#bugz", "meow-bot", "bugz"}, From:"bugz", To:"#bugz", Cmd:"KICK", Msg:"meow-bot"}
  391. if strings.Contains(msg.Msg, Config.MyNick) {
  392. if StrInArray(Config.AutoJoin, msg.To) {
  393. // Yes, we were kicked from AutoJoin channel
  394. time.AfterFunc(time.Duration(Config.RejoinDelay)*time.Millisecond, func() { Config.WriteTo(msg.To, "JOIN "+msg.To) })
  395. }
  396. }
  397. }
  398. if Config.ReadChannel != nil {
  399. Config.ReadChannel <- msg
  400. }
  401. if (msg.Cmd == "376") || (msg.Cmd == "422") {
  402. // End MOTD, or MOTD Missing
  403. var reg IRCMsg = IRCMsg{Cmd: "EndMOTD"}
  404. Config.ReadChannel <- reg
  405. }
  406. if msg.Cmd == "NICK" {
  407. // :meow NICK :meow-bot
  408. if msg.From == Config.MyNick {
  409. Config.MyNick = msg.To
  410. log.Println("Nick is now:", Config.MyNick)
  411. }
  412. }
  413. }
  414. }