/* 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) } }