Neale Pickett
·
2016-05-18
network.go
1package main
2
3import (
4 "bufio"
5 "crypto/tls"
6 "fmt"
7 "io"
8 "log"
9 "net"
10 "os"
11 "os/user"
12 "os/exec"
13 "path"
14 "strings"
15 "time"
16)
17
18// This gets called every time the data's needed.
19// That makes it so you can change stuff while running.
20
21func ReadLines(fn string) ([]string, error) {
22 lines := make([]string, 0)
23
24 f, err := os.Open(fn)
25 if err != nil {
26 return lines, err
27 }
28 defer f.Close()
29
30 scanner := bufio.NewScanner(f)
31 for scanner.Scan() {
32 line := strings.TrimSpace(scanner.Text())
33 switch {
34 case line == "":
35 default:
36 lines = append(lines, line)
37 }
38 }
39
40 return lines, nil
41}
42
43type Network struct {
44 running bool
45
46 Nick string
47
48 basePath string
49 serverIndex int
50
51 conn io.ReadWriteCloser
52
53 logf *Logfile
54
55 inq chan string
56 outq chan string
57}
58
59func NewNetwork(basePath string) *Network {
60 nw := Network{
61 running: true,
62 basePath: basePath,
63 }
64 nw.logf = NewLogfile(nw.basePath, int(maxlogsize))
65
66 return &nw
67}
68
69func (nw *Network) Close() {
70 nw.running = false
71 if nw.conn != nil {
72 nw.conn.Close()
73 }
74 nw.logf.Close()
75}
76
77func (nw *Network) watchOutqDirectory() {
78 outqDirname := path.Join(nw.basePath, "outq")
79
80 dir, err := os.Open(outqDirname)
81 if err != nil {
82 log.Fatal(err)
83 }
84 defer dir.Close()
85
86 // XXX: Do this with fsnotify
87 for nw.running {
88 entities, _ := dir.Readdirnames(0)
89 for _, fn := range entities {
90 pathname := path.Join(outqDirname, fn)
91 nw.HandleInfile(pathname)
92 }
93 _, _ = dir.Seek(0, 0)
94 time.Sleep(500 * time.Millisecond)
95 }
96}
97
98func (nw *Network) HandleInfile(fn string) {
99 f, err := os.Open(fn)
100 if err != nil {
101 return
102 }
103 defer f.Close()
104
105 // Do this after Open attempt.
106 // If Open fails, the file will stick around.
107 // Hopefully this is helpful for debugging.
108 os.Remove(fn)
109
110 inf := bufio.NewScanner(f)
111 for inf.Scan() {
112 txt := inf.Text()
113 nw.outq <- txt
114 }
115}
116
117func (nw *Network) serverWriteLoop() {
118 for v := range nw.outq {
119 debug("» %s", v)
120 nw.logf.Log(v)
121 fmt.Fprintln(nw.conn, v)
122 time.Sleep(500 * time.Millisecond)
123 }
124}
125
126func (nw *Network) NextNick() {
127 nicks, err := ReadLines(path.Join(nw.basePath, "nick"))
128 if err != nil {
129 log.Print(err)
130 return
131 }
132
133 // Make up some alternates if they weren't provided
134 if len(nicks) == 1 {
135 nicks = append(nicks, nicks[0] + "_")
136 nicks = append(nicks, nicks[0] + "__")
137 nicks = append(nicks, nicks[0] + "___")
138 }
139
140 nextidx := 0
141 for idx, n := range nicks {
142 if n == nw.Nick {
143 nextidx = idx + 1
144 }
145 }
146
147 nw.Nick = nicks[nextidx % len(nicks)]
148 nw.outq <- "NICK " + nw.Nick
149}
150
151func (nw *Network) JoinChannels() {
152 chans, err := ReadLines(path.Join(nw.basePath, "channels"))
153 if err != nil {
154 log.Print(err)
155 return
156 }
157
158 for _, ch := range chans {
159 debug("Joining %s", ch)
160 nw.outq <- "JOIN " + ch
161 }
162}
163
164func (nw *Network) messageDispatchLoop() {
165 for line := range nw.inq {
166 nw.logf.Log(line)
167
168 m, err := NewMessage(line)
169 if err != nil {
170 log.Print(err)
171 continue
172 }
173
174 switch m.Command {
175 case "PING":
176 nw.outq <- "PONG :" + m.Text
177 continue
178 case "001":
179 nw.JoinChannels()
180 case "433":
181 nw.NextNick()
182 case "PRIVMSG":
183 if m.Text == "\001VERSION\001" {
184 //nw.outq <- "NOTICE " + m.Sender + " :\001VERSION Spongy v8294.003.1R6pl58₄SEσ\001"
185 nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION begin 644 version.txt\001"
186 nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION F4W!O;F=Y('9E<G-I;VX@.#(Y-\"XP,#,N,5(V<&PU..*\"A%-%SX,`\001"
187 nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION `\001"
188 nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION end\001"
189 }
190 }
191
192 handlerPath := path.Join(nw.basePath, "handler")
193 cmd := exec.Command(handlerPath, m.Args...)
194 cmd.Env = os.Environ()
195 cmd.Env = append(cmd.Env, "command=" + m.Command)
196 cmd.Env = append(cmd.Env, "fullsender=" + m.FullSender)
197 cmd.Env = append(cmd.Env, "sender=" + m.Sender)
198 cmd.Env = append(cmd.Env, "forum=" + m.Forum)
199 cmd.Env = append(cmd.Env, "text=" + m.Text)
200 cmd.Env = append(cmd.Env, "raw=" + line)
201 cmd.Stderr = os.Stderr
202 out, err := cmd.Output()
203 if err != nil {
204 log.Print(err)
205 continue
206 }
207
208 if len(out) > 0 {
209 outlines := strings.Split(string(out), "\n")
210 for _, line := range outlines {
211 if len(line) > 0 {
212 nw.outq <- line
213 }
214 }
215 }
216 }
217}
218
219func (nw *Network) ConnectToNextServer() bool {
220 servers, err := ReadLines(path.Join(nw.basePath, "server"))
221 if err != nil {
222 log.Printf("Couldn't find any servers to connect to in %s", nw.basePath)
223 return false
224 }
225
226 if nw.serverIndex > len(servers) {
227 nw.serverIndex = 0
228 }
229 server := servers[nw.serverIndex]
230
231 debug("Connecting to %s", server)
232 switch (server[0]) {
233 case '|':
234 parts := strings.Split(server[1:], " ")
235 nw.conn, err = StartStdioProcess(parts[0], parts[1:])
236 case '^':
237 nw.conn, err = net.Dial("tcp", server[1:])
238 default:
239 log.Print("Not validating server certificate!")
240 config := &tls.Config{
241 InsecureSkipVerify: true,
242 }
243 nw.conn, err = tls.Dial("tcp", server, config)
244 }
245
246 if err != nil {
247 log.Print(err)
248 return false
249 }
250 debug("Connected")
251
252 return true
253}
254
255func (nw *Network) login() {
256 var name string
257 var username string
258
259 usernames, err := ReadLines(path.Join(nw.basePath, "username"))
260 if err == nil {
261 username = usernames[0]
262 } else {
263 username = "sponge"
264 }
265
266 passwd, err := ReadLines(path.Join(nw.basePath, "passwd"))
267 if err == nil {
268 nw.outq <- "PASS " + passwd[0]
269 }
270
271
272 names, err := ReadLines(path.Join(nw.basePath, "name"))
273 if err == nil {
274 name = names[0]
275 }
276
277 if name == "" {
278 me, err := user.Current()
279 if err == nil {
280 name = me.Name
281 }
282 }
283
284 if name == "" {
285 // Rogue used "Rodney" if you didn't give it a name.
286 // This one works for the ladies, too.
287 name = "Ronnie"
288 }
289
290 nw.outq <- "USER " + username + " g g :" + name
291 nw.NextNick()
292}
293
294func (nw *Network) keepaliveLoop() {
295 for nw.running {
296 time.Sleep(1 * time.Minute)
297 nw.outq <- "PING :keepalive"
298 }
299}
300
301
302func (nw *Network) Connect() {
303 for nw.running {
304 if ! nw.ConnectToNextServer() {
305 time.Sleep(8 * time.Second)
306 continue
307 }
308
309 nw.inq = make(chan string, 20)
310 nw.outq = make(chan string, 20)
311
312 go nw.serverWriteLoop()
313 go nw.messageDispatchLoop()
314 go nw.watchOutqDirectory()
315 go nw.keepaliveLoop()
316
317 nw.login()
318
319 scanner := bufio.NewScanner(nw.conn)
320 for scanner.Scan() {
321 nw.inq <- scanner.Text()
322 }
323
324 close(nw.inq)
325 close(nw.outq)
326 }
327}
328