Files
hbot/cmd/ircmirror/main.go
Jason A. Donenfeld b52ec14d2d Initial commit of utilities
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-06-03 18:38:26 +02:00

159 lines
3.8 KiB
Go

/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2021 Jason A. Donenfeld. All Rights Reserved.
*/
package main
import (
"flag"
"fmt"
"log"
"math/rand"
"net"
"os"
"regexp"
"strings"
"sync"
"syscall"
"time"
"golang.org/x/sys/unix"
"golang.zx2c4.com/irc/hbot"
)
func raiseFileLimit() error {
var lim unix.Rlimit
err := unix.Getrlimit(syscall.RLIMIT_NOFILE, &lim)
if err != nil {
return err
}
if lim.Cur == lim.Max {
return nil
}
log.Printf("Raising file limit from %d to %d\n", lim.Cur, lim.Max)
lim.Cur = lim.Max
return unix.Setrlimit(syscall.RLIMIT_NOFILE, &lim)
}
func main() {
accountIdArg := flag.String("account-id", "", "account ID number")
srcChannelArg := flag.String("src-channel", "", "source channel")
dstChannelArg := flag.String("dst-channel", "", "destination channel")
srcServerArg := flag.String("src-server", "", "source server")
dstServerArg := flag.String("dst-server", "", "destination server")
flag.Parse()
if matched, _ := regexp.MatchString(`^[0-9]+$`, *accountIdArg); !matched {
fmt.Fprintln(os.Stderr, "Invalid account ID")
flag.Usage()
os.Exit(1)
}
if matched, _ := regexp.MatchString(`^#[a-zA-Z0-9_-]+$`, *srcChannelArg); !matched {
fmt.Fprintln(os.Stderr, "Invalid source channel")
flag.Usage()
os.Exit(1)
}
if matched, _ := regexp.MatchString(`^#[a-zA-Z0-9_-]+$`, *dstChannelArg); !matched {
fmt.Fprintln(os.Stderr, "Invalid destination channel")
flag.Usage()
os.Exit(1)
}
if _, _, err := net.SplitHostPort(*srcServerArg); err != nil {
fmt.Fprintln(os.Stderr, "Invalid source server")
flag.Usage()
os.Exit(1)
}
if _, _, err := net.SplitHostPort(*dstServerArg); err != nil {
fmt.Fprintln(os.Stderr, "Invalid destination server")
flag.Usage()
os.Exit(1)
}
privateKey, err := loadOrGeneratePrivateKey()
if err != nil {
log.Fatalln(err)
}
endpoints, err := getVpnEndpoints()
if err != nil {
log.Fatalln(err)
}
vpnConf, err := registerVpnConf(privateKey, *accountIdArg)
if err != nil {
log.Fatalln(err)
}
rand.Seed(time.Now().UnixNano())
err = raiseFileLimit()
if err != nil {
log.Fatalln(err)
}
dialers, err := makeDialers(vpnConf, endpoints)
if err != nil {
log.Fatalln(err)
}
if len(dialers) < 2 {
log.Fatalln("Not enough dialers returned")
}
writers := newIrcWriterGroup(*dstServerArg, *dstChannelArg)
const writersPerIP = 10
type nextDialer struct {
sync.Mutex
*dialer
}
dialNext := func(nd *nextDialer, v4 bool) {
var last *dialer
for {
nd.Lock()
if nd.dialer == last {
nd.dialer = &dialers[rand.Intn(len(dialers))]
}
last = nd.dialer
nd.Unlock()
name := last.name
dial := last.dial
if v4 {
name += "-v4"
dial, _ = last.splitByAf()
} else {
name += "-v6"
_, dial = last.splitByAf()
}
writers.runWriter(name, dial)
}
}
for i := 0; i < vpnProviderMaxEndpointsInParallel/2; i++ {
nd := new(nextDialer)
for i := 0; i < writersPerIP; i++ {
go dialNext(nd, true)
go dialNext(nd, false)
}
}
srcDial := dialers[rand.Intn(len(dialers))]
logf := func(format string, args ...interface{}) {
log.Printf("[SRC %s] "+format, append([]interface{}{srcDial.name}, args...)...)
}
bot := hbot.NewBot(&hbot.Config{
Host: *srcServerArg,
Nick: randomNick(),
Channels: []string{*srcChannelArg},
Dial: srcDial.dial,
Logger: hbot.Logger{Verbosef: logf, Errorf: logf},
})
bot.AddTrigger(hbot.Trigger{
Condition: func(b *hbot.Bot, m *hbot.Message) bool {
return m.Param(0) == *srcChannelArg && m.Command == "PRIVMSG" &&
!strings.HasPrefix(m.Prefix.User, hbot.CommonBotUserPrefix) &&
!strings.HasPrefix(m.Prefix.User, "~"+hbot.CommonBotUserPrefix)
},
Action: func(b *hbot.Bot, m *hbot.Message) {
writers.queueMessage(m.Prefix.Name, m.Trailing())
},
})
for {
bot.Run()
time.Sleep(time.Second * 5)
}
}