Guest User

Untitled

a guest
Dec 16th, 2017
114
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.84 KB | None | 0 0
  1. import (
  2. "crypto/tls"
  3. "log"
  4. "fmt"
  5. "net"
  6. "time"
  7. "os"
  8. "os/signal"
  9. "strings"
  10. "strconv"
  11. "syscall"
  12.  
  13. "github.com/stardustapp/core/base"
  14. "github.com/stardustapp/core/inmem"
  15. "github.com/stardustapp/core/extras"
  16. "github.com/stardustapp/core/toolbox"
  17.  
  18. irc "gopkg.in/irc.v1"
  19. )
  20.  
  21. // set up a global process-shutdown signal
  22. var shutdownChan chan struct{}
  23. var isShuttingDown bool
  24. func init() {
  25. shutdownChan = make(chan struct{})
  26. c := make(chan os.Signal, 2)
  27. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  28.  
  29. // start waiting for interupt signals
  30. go func() {
  31. <-c
  32. isShuttingDown = true
  33. log.Println("WARN: Received Interrupt - quitting all sockets")
  34. close(shutdownChan)
  35.  
  36. // TODO: waitgroup lol
  37. <-c
  38. log.Println("FATAL: Received Interrupt AGAIN - suiciding")
  39. os.Exit(1)
  40. }()
  41. }
  42.  
  43. func buildArrayFolder(in ...string) base.Folder {
  44. folder := inmem.NewFolder("array")
  45. for idx, str := range in {
  46. folder.Put(strconv.Itoa(idx+1), inmem.NewString("", str))
  47. }
  48. return folder
  49. }
  50.  
  51. // Returns an absolute Skylink URI to the established connection
  52. func (r *Root) DialConnImpl(config *DialConfig) string {
  53.  
  54. // Don't start up if the sky is falling
  55. if isShuttingDown {
  56. log.Println("warn: rejecting dial due to shutdown state")
  57. return "Err: This IRC modem is shutting down"
  58. }
  59.  
  60. // Build the full endpoint
  61. endpoint := fmt.Sprintf("%s:%s", config.Hostname, config.Port)
  62. firstMsg := &Message{
  63. Command: "LOG",
  64. Params: buildArrayFolder("Dialing " + endpoint + " over TCP..."),
  65. Source: "dialer",
  66. Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
  67. }
  68.  
  69. // Create the connection holder
  70. conn := &Connection{
  71. Config: config,
  72. State: toolbox.NewReactiveString("state", "Pending"),
  73.  
  74. History: inmem.NewFolder("history"),
  75. HistoryHorizon: "0",
  76. HistoryLatest: toolbox.NewReactiveString("history-latest", "0"),
  77.  
  78. out: make(chan *Message),
  79. }
  80. conn.History.Put("0", firstMsg)
  81.  
  82. // Helper to store messages
  83. addMsg := func (msg *Message) {
  84. i, _ := strconv.Atoi(conn.HistoryLatest.Get())
  85. nextSeq := strconv.Itoa(i + 1)
  86. conn.History.Put(nextSeq, msg)
  87. conn.HistoryLatest.Set(nextSeq)
  88.  
  89. // Trim old messages
  90. horizon, _ := strconv.Atoi(conn.HistoryHorizon)
  91. maxOld := i - 250
  92. for horizon < maxOld {
  93. conn.History.Put(strconv.Itoa(horizon), nil)
  94. horizon++
  95. conn.HistoryHorizon = strconv.Itoa(horizon)
  96. }
  97. }
  98.  
  99. // Track our info for outbound packets
  100. var currentNick string
  101.  
  102. // Configure IRC library as needed
  103. conf := irc.ClientConfig{
  104. Nick: config.Nickname,
  105. Pass: config.Password,
  106. User: config.Username,
  107. Name: config.FullName,
  108. Handler: irc.HandlerFunc(func(c *irc.Client, m *irc.Message) {
  109.  
  110. // Clean up CTCP stuff so everyone doesn't have to parse it manually.
  111. // TODO: the go-irc library does this but only for PRIVMSG
  112. // TODO: split the ctcp cmd from the ctcp args
  113. if m.Command == "NOTICE" {
  114. lastArg := m.Trailing()
  115. lastIdx := len(lastArg) - 1
  116. if lastIdx > 0 && lastArg[0] == '\x01' && lastArg[lastIdx] == '\x01' {
  117. m.Command = "CTCP_ANSWER"
  118. m.Params[len(m.Params)-1] = lastArg[1:lastIdx]
  119. }
  120. }
  121.  
  122. // Track nickname - TODO: irc-app really should handle this
  123. if m.Command == "001" {
  124. currentNick = m.Params[0]
  125. log.Println("Changed nickname from", currentNick, "to", m.Params[0])
  126. }
  127. if m.Command == "NICK" {
  128. if m.Prefix.Name == currentNick && len(m.Params) > 0 {
  129. currentNick = m.Params[0]
  130. }
  131. }
  132.  
  133. // Add inbound messages to the history
  134. msg := &Message{
  135. Command: m.Command,
  136. Params: buildArrayFolder(m.Params...),
  137. Source: "server",
  138. Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
  139. Tags: inmem.NewFolder("tags"),
  140. }
  141. if m.Prefix != nil {
  142. msg.PrefixName = m.Prefix.Name
  143. msg.PrefixUser = m.Prefix.User
  144. msg.PrefixHost = m.Prefix.Host
  145. }
  146. for key, _ := range m.Tags {
  147. if val, ok := m.GetTag(key); ok {
  148. msg.Tags.Put(key, inmem.NewString(key, val))
  149. }
  150. }
  151. addMsg(msg)
  152. }),
  153. }
  154.  
  155. // Establish the network connection
  156. log.Println("Connecting to TCP server at", endpoint)
  157. rawConn, err := net.Dial("tcp", endpoint)
  158. if err != nil {
  159. log.Println("Failed to dial", endpoint, err)
  160. conn.State.Set("Failed: Dial error")
  161. return "Err! " + err.Error()
  162. }
  163. var netConn net.Conn = rawConn
  164.  
  165. // Record username info in identd server
  166. if config.Ident == "" {
  167. config.Ident = "dialer"
  168. }
  169. identdRPC("add " + config.Ident + " " +
  170. strings.Split(netConn.LocalAddr().String(),":")[1] + " " +
  171. strings.Split(netConn.RemoteAddr().String(),":")[1])
  172.  
  173. // Perform TLS setup if desired
  174. if config.UseTLS == "yes" {
  175. addMsg(&Message{
  176. Command: "LOG",
  177. Params: buildArrayFolder("Performing TLS handshake..."),
  178. Source: "dialer",
  179. Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
  180. })
  181.  
  182. // Extract hostname of endpoint
  183. colonPos := strings.LastIndex(endpoint, ":")
  184. if colonPos == -1 {
  185. colonPos = len(endpoint)
  186. }
  187. hostname := endpoint[:colonPos]
  188.  
  189. // Configure a TLS client
  190. log.Println("Starting TLS handshake with", endpoint)
  191. tlsConn := tls.Client(rawConn, &tls.Config{
  192. ServerName: hostname,
  193. NextProtos: []string{"irc"},
  194. })
  195.  
  196. // Make sure it's legit
  197. if err := tlsConn.Handshake(); err != nil {
  198. log.Println("Failed to perform TLS handshake:", endpoint, err)
  199. conn.State.Set("Failed: TLS error")
  200. return "Err! " + err.Error()
  201. }
  202. netConn = tlsConn
  203. }
  204.  
  205. // Record that the analog transport is configured
  206. addMsg(&Message{
  207. Command: "LOG",
  208. Params: buildArrayFolder("Connection established."),
  209. Source: "dialer",
  210. Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
  211. })
  212. conn.State.Set("Ready")
  213.  
  214. // Create the protocol client
  215. conn.svc = irc.NewClient(netConn, conf)
  216.  
  217. // Fire it up
  218. go func() {
  219. if err := conn.svc.Run(); err != nil {
  220. log.Println("Failed to run client:", err)
  221.  
  222. // We hit this when the client stops running, so record that
  223. addMsg(&Message{
  224. Command: "LOG",
  225. Params: buildArrayFolder("Connection closed: " + err.Error()),
  226. Source: "dialer",
  227. Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
  228. })
  229. }
  230.  
  231. conn.State.Set("Closed")
  232.  
  233. // synchronize to prevent send-message from panicing
  234. conn.sendMutex.Lock()
  235. defer conn.sendMutex.Unlock()
  236. close(conn.out)
  237. }()
  238.  
  239. // Also watch for process shutdown
  240. go func() {
  241. <-shutdownChan
  242. log.Println("Shutting down client", config.Nickname, "on", endpoint)
  243.  
  244. // synchronize to prevent send-message from panicing
  245. conn.sendMutex.Lock()
  246. defer conn.sendMutex.Unlock()
  247.  
  248. // attempt to peacefully disconnect
  249. conn.out <- &Message{
  250. Command: "QUIT",
  251. Params: inmem.NewFolderOf("params", inmem.NewString(
  252. "1", "IRC modem is shutting down")),
  253. }
  254.  
  255. conn.State.Set("Quitting")
  256. }()
  257.  
  258. // Start outbound pump
  259. go func() {
  260. for msg := range conn.out {
  261. msg.PrefixName = currentNick
  262. msg.Source = "client"
  263. msg.Timestamp = time.Now().UTC().Format(time.RFC3339Nano)
  264. addMsg(msg)
  265.  
  266. // pull native params out of param folder
  267. var params []string
  268. if msg.Params != nil {
  269. params = make([]string, len(msg.Params.Children()))
  270. for _, name := range msg.Params.Children() {
  271. id, _ := strconv.Atoi(name)
  272. if ent, ok := msg.Params.Fetch(name); ok && id > 0 && id <= len(params) {
  273. params[id-1] = ent.(base.String).Get()
  274. }
  275. }
  276. }
  277.  
  278. // pull native tags out too
  279. var tags map[string]irc.TagValue
  280. if msg.Tags != nil {
  281. tags = make(map[string]irc.TagValue, len(msg.Tags.Children()))
  282. for _, name := range msg.Tags.Children() {
  283. if ent, ok := msg.Tags.Fetch(name); ok {
  284. tags[name] = irc.TagValue(ent.(base.String).Get())
  285. }
  286. }
  287. }
  288.  
  289. // encode CTCP payloads and answers
  290. command := msg.Command
  291. if command == "CTCP" || command == "CTCP_ANSWER" {
  292. var payload string
  293. if len(params) > 2 {
  294. payload = "\x01" + params[1] + " " + params[2] + "\x01"
  295. } else if len(params) == 2 {
  296. payload = "\x01" + params[1] + "\x01"
  297. }
  298. params = []string{params[0], payload}
  299.  
  300. if command == "CTCP_ANSWER" {
  301. command = "NOTICE"
  302. } else {
  303. command = "PRIVMSG"
  304. }
  305. }
  306.  
  307. err := conn.svc.WriteMessage(&irc.Message{
  308. Command: command,
  309. Params: params,
  310. Tags: tags,
  311. })
  312. if err != nil {
  313. // TODO: do something about these errors
  314. log.Println("Unexpected error writing IRC payload:", err)
  315. }
  316. }
  317. }()
  318.  
  319. // TODO: this should be made already
  320. if r.Sessions == nil {
  321. r.Sessions = inmem.NewFolder("sessions")
  322. }
  323.  
  324. // Store a session reference
  325. sessionId := extras.GenerateId()
  326. if ok := r.Sessions.Put(sessionId, conn); !ok {
  327. log.Println("Session store rejected us :(")
  328. return "Err! Couldn't store session"
  329. }
  330.  
  331. // Return absolute URI to the created session
  332. name, err := os.Hostname()
  333. if err != nil {
  334. log.Println("Oops 1:", err)
  335. return "Err! no ip"
  336. }
  337. addrs, err := net.LookupHost(name)
  338. if err != nil {
  339. log.Println("Oops 2:", err)
  340. return "Err! no host"
  341. }
  342. if len(addrs) < 1 {
  343. log.Println("Oops 2:", err)
  344. return "Err! no host ip"
  345. }
  346. selfIp := addrs[0]
  347.  
  348. return fmt.Sprintf("skylink+ws://%s:9234/pub/sessions/%s", selfIp, sessionId)
  349. }
  350.  
  351. func identdRPC(line string) error {
  352. conn, err := net.Dial("tcp", "identd-rpc:11333")
  353. if err != nil {
  354. log.Println("Failed to dial identd rpc:", err)
  355. return err
  356. }
  357.  
  358. _, err = conn.Write([]byte(line + "\n"))
  359. if err != nil {
  360. log.Println("Write to identd rpc failed:", err)
  361. return err
  362. }
  363.  
  364. conn.Close()
  365. return nil
  366. }
Add Comment
Please, Sign In to add comment