spongy/spongyd/network.go

329 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"log"
"net"
"os"
"os/user"
"os/exec"
"path"
"strings"
"time"
)
// This gets called every time the data's needed.
// That makes it so you can change stuff while running.
func ReadLines(fn string) ([]string, error) {
lines := make([]string, 0)
f, err := os.Open(fn)
if err != nil {
return lines, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
switch {
case line == "":
default:
lines = append(lines, line)
}
}
return lines, nil
}
type Network struct {
running bool
Nick string
basePath string
serverIndex int
conn io.ReadWriteCloser
logf *Logfile
inq chan string
outq chan string
}
func NewNetwork(basePath string) *Network {
nw := Network{
running: true,
basePath: basePath,
}
nw.logf = NewLogfile(nw.basePath, int(maxlogsize))
return &nw
}
func (nw *Network) Close() {
nw.running = false
if nw.conn != nil {
nw.conn.Close()
}
nw.logf.Close()
}
func (nw *Network) watchOutqDirectory() {
outqDirname := path.Join(nw.basePath, "outq")
dir, err := os.Open(outqDirname)
if err != nil {
log.Fatal(err)
}
defer dir.Close()
// XXX: Do this with fsnotify
for nw.running {
entities, _ := dir.Readdirnames(0)
for _, fn := range entities {
pathname := path.Join(outqDirname, fn)
nw.HandleInfile(pathname)
}
_, _ = dir.Seek(0, 0)
time.Sleep(500 * time.Millisecond)
}
}
func (nw *Network) HandleInfile(fn string) {
f, err := os.Open(fn)
if err != nil {
return
}
defer f.Close()
// Do this after Open attempt.
// If Open fails, the file will stick around.
// Hopefully this is helpful for debugging.
os.Remove(fn)
inf := bufio.NewScanner(f)
for inf.Scan() {
txt := inf.Text()
nw.outq <- txt
}
}
func (nw *Network) serverWriteLoop() {
for v := range nw.outq {
debug("» %s", v)
nw.logf.Log(v)
fmt.Fprintln(nw.conn, v)
time.Sleep(500 * time.Millisecond)
}
}
func (nw *Network) NextNick() {
nicks, err := ReadLines(path.Join(nw.basePath, "nick"))
if err != nil {
log.Print(err)
return
}
// Make up some alternates if they weren't provided
if len(nicks) == 1 {
nicks = append(nicks, nicks[0] + "_")
nicks = append(nicks, nicks[0] + "__")
nicks = append(nicks, nicks[0] + "___")
}
nextidx := 0
for idx, n := range nicks {
if n == nw.Nick {
nextidx = idx + 1
}
}
nw.Nick = nicks[nextidx % len(nicks)]
nw.outq <- "NICK " + nw.Nick
}
func (nw *Network) JoinChannels() {
chans, err := ReadLines(path.Join(nw.basePath, "channels"))
if err != nil {
log.Print(err)
return
}
for _, ch := range chans {
debug("Joining %s", ch)
nw.outq <- "JOIN " + ch
}
}
func (nw *Network) messageDispatchLoop() {
for line := range nw.inq {
nw.logf.Log(line)
m, err := NewMessage(line)
if err != nil {
log.Print(err)
continue
}
switch m.Command {
case "PING":
nw.outq <- "PONG :" + m.Text
continue
case "001":
nw.JoinChannels()
case "433":
nw.NextNick()
case "PRIVMSG":
if m.Text == "\001VERSION\001" {
//nw.outq <- "NOTICE " + m.Sender + " :\001VERSION Spongy v8294.003.1R6pl58₄SEσ\001"
nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION begin 644 version.txt\001"
nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION F4W!O;F=Y('9E<G-I;VX@.#(Y-\"XP,#,N,5(V<&PU..*\"A%-%SX,`\001"
nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION `\001"
nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION end\001"
}
}
handlerPath := path.Join(nw.basePath, "handler")
cmd := exec.Command(handlerPath, m.Args...)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "command=" + m.Command)
cmd.Env = append(cmd.Env, "fullsender=" + m.FullSender)
cmd.Env = append(cmd.Env, "sender=" + m.Sender)
cmd.Env = append(cmd.Env, "forum=" + m.Forum)
cmd.Env = append(cmd.Env, "text=" + m.Text)
cmd.Env = append(cmd.Env, "raw=" + line)
cmd.Stderr = os.Stderr
out, err := cmd.Output()
if err != nil {
log.Print(err)
continue
}
if len(out) > 0 {
outlines := strings.Split(string(out), "\n")
for _, line := range outlines {
if len(line) > 0 {
nw.outq <- line
}
}
}
}
}
func (nw *Network) ConnectToNextServer() bool {
servers, err := ReadLines(path.Join(nw.basePath, "server"))
if err != nil {
log.Printf("Couldn't find any servers to connect to in %s", nw.basePath)
return false
}
if nw.serverIndex > len(servers) {
nw.serverIndex = 0
}
server := servers[nw.serverIndex]
debug("Connecting to %s", server)
switch (server[0]) {
case '|':
parts := strings.Split(server[1:], " ")
nw.conn, err = StartStdioProcess(parts[0], parts[1:])
case '^':
nw.conn, err = net.Dial("tcp", server[1:])
default:
log.Print("Not validating server certificate!")
config := &tls.Config{
InsecureSkipVerify: true,
}
nw.conn, err = tls.Dial("tcp", server, config)
}
if err != nil {
log.Print(err)
return false
}
debug("Connected")
return true
}
func (nw *Network) login() {
var name string
var username string
usernames, err := ReadLines(path.Join(nw.basePath, "username"))
if err == nil {
username = usernames[0]
} else {
username = "sponge"
}
passwd, err := ReadLines(path.Join(nw.basePath, "passwd"))
if err == nil {
nw.outq <- "PASS " + passwd[0]
}
names, err := ReadLines(path.Join(nw.basePath, "name"))
if err == nil {
name = names[0]
}
if name == "" {
me, err := user.Current()
if err == nil {
name = me.Name
}
}
if name == "" {
// Rogue used "Rodney" if you didn't give it a name.
// This one works for the ladies, too.
name = "Ronnie"
}
nw.outq <- "USER " + username + " g g :" + name
nw.NextNick()
}
func (nw *Network) keepaliveLoop() {
for nw.running {
time.Sleep(1 * time.Minute)
nw.outq <- "PING :keepalive"
}
}
func (nw *Network) Connect() {
for nw.running {
if ! nw.ConnectToNextServer() {
time.Sleep(8 * time.Second)
continue
}
nw.inq = make(chan string, 20)
nw.outq = make(chan string, 20)
go nw.serverWriteLoop()
go nw.messageDispatchLoop()
go nw.watchOutqDirectory()
go nw.keepaliveLoop()
nw.login()
scanner := bufio.NewScanner(nw.conn)
for scanner.Scan() {
nw.inq <- scanner.Text()
}
close(nw.inq)
close(nw.outq)
}
}