aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2017-04-14 02:08:40 -0700
committerChristine Dodrill <me@christine.website>2017-04-14 02:11:12 -0700
commitdfa688ec71053f552386b2bbee6c8d45e01b6f46 (patch)
treefc08ddc2e8f2ccaa1c36c4c4250aca59b3244066
parent249836270b7e1fe145296b5aa0ef644dcd99bb71 (diff)
downloadx-dfa688ec71053f552386b2bbee6c8d45e01b6f46.tar.xz
x-dfa688ec71053f552386b2bbee6c8d45e01b6f46.zip
kcpd
-rw-r--r--irc/kcpd/.gitignore1
-rw-r--r--irc/kcpd/README.md4
-rw-r--r--irc/kcpd/client.go93
-rw-r--r--irc/kcpd/main.go110
-rw-r--r--irc/kcpd/server.go97
-rw-r--r--irc/kcpd/util.go21
6 files changed, 326 insertions, 0 deletions
diff --git a/irc/kcpd/.gitignore b/irc/kcpd/.gitignore
new file mode 100644
index 0000000..31bb52e
--- /dev/null
+++ b/irc/kcpd/.gitignore
@@ -0,0 +1 @@
+cfg \ No newline at end of file
diff --git a/irc/kcpd/README.md b/irc/kcpd/README.md
new file mode 100644
index 0000000..85e39ae
--- /dev/null
+++ b/irc/kcpd/README.md
@@ -0,0 +1,4 @@
+kcpd
+====
+
+A simple relay for multiplexing IRC sessions. Useful for bouncers or proxies.
diff --git a/irc/kcpd/client.go b/irc/kcpd/client.go
new file mode 100644
index 0000000..0f744dc
--- /dev/null
+++ b/irc/kcpd/client.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+ "crypto/tls"
+ "errors"
+ "io"
+ "net"
+
+ kcp "github.com/xtaci/kcp-go"
+ "github.com/xtaci/smux"
+)
+
+// Client opens a TCP listener and forwards traffic to the remote server over KCP.
+type Client struct {
+ cfg *Config
+
+ listener net.Listener
+}
+
+// ErrBadConfig means the configuration is not correctly defined.
+var ErrBadConfig = errors.New("kcpd: bad configuration file")
+
+// NewClient constructs a new client with a given config.
+func NewClient(cfg *Config) (*Client, error) {
+ if cfg.Mode != "client" {
+ return nil, ErrBadConfig
+ }
+
+ if cfg.ClientServerAddress == "" && cfg.ClientUsername == "" && cfg.ClientPassword == "" && cfg.ClientBindaddr == "" {
+ return nil, ErrBadConfig
+ }
+
+ return &Client{cfg: cfg}, nil
+}
+
+// Dial blockingly connects to the remote server and relays TCP traffic.
+func (c *Client) Dial() error {
+ conn, err := kcp.Dial(c.cfg.ClientServerAddress)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ tlsConn := tls.Client(conn, &tls.Config{
+ InsecureSkipVerify: true, // XXX hack please remove
+ })
+ defer tlsConn.Close()
+
+ session, err := smux.Client(tlsConn, smux.DefaultConfig())
+ if err != nil {
+ return err
+ }
+ defer session.Close()
+
+ l, err := net.Listen("tcp", c.cfg.ClientBindaddr)
+ if err != nil {
+ return err
+ }
+ defer l.Close()
+ c.listener = l
+
+ for {
+ cconn, err := l.Accept()
+ if err != nil {
+ break
+ }
+
+ cstream, err := session.OpenStream()
+ if err != nil {
+ break
+ }
+
+ go copyConn(cconn, cstream)
+ }
+
+ return nil
+}
+
+// Close frees resouces acquired in the client.
+func (c *Client) Close() error {
+ return c.listener.Close()
+}
+
+// copyConn copies one connection to another bidirectionally.
+func copyConn(left, right net.Conn) error {
+ defer left.Close()
+ defer right.Close()
+
+ go io.Copy(left, right)
+ io.Copy(right, left)
+
+ return nil
+}
diff --git a/irc/kcpd/main.go b/irc/kcpd/main.go
new file mode 100644
index 0000000..4ec913c
--- /dev/null
+++ b/irc/kcpd/main.go
@@ -0,0 +1,110 @@
+package main
+
+import (
+ "flag"
+ "io/ioutil"
+ "log"
+ "os"
+ "time"
+
+ "github.com/caarlos0/env"
+ _ "github.com/joho/godotenv/autoload"
+ yaml "gopkg.in/yaml.v1"
+)
+
+// Config is the configuration for kcpd
+type Config struct {
+ Mode string `env:"KCPD_MODE,required" envDefault:"server" yaml:"mode"`
+
+ // Client mode config
+
+ // What IP the client should connect to
+ ClientServerAddress string `env:"KCPD_SERVER_ADDRESS" yaml:"server"`
+ // Administrator's NickServ username
+ ClientUsername string `env:"KCPD_ADMIN_USERNAME" yaml:"admin_username"`
+ // Administrator's NickServ password
+ ClientPassword string `env:"KCPD_ADMIN_PASSWORD" yaml:"admin_password"`
+ // Local bindaddr
+ ClientBindaddr string `env:"KCPD_CLIENT_BINDADDR" yaml:"client_bind"`
+
+ // Server mode config
+
+ // What UDP port/address should kcpd bind on?
+ ServerBindAddr string `env:"KCPD_BIND" yaml:"bind"`
+ // Atheme URL for Nickserv authentication of the administrator for setting up KCP sessions
+ ServerAthemeURL string `env:"KCPD_ATHEME_URL" yaml:"atheme_url"`
+ // URL endpoint for allowing/denying users
+ ServerAllowListEndpoint string `env:"KCPD_ALLOWLIST_ENDPOINT" yaml:"allow_list_endpoint"`
+ // local ircd (unsecure) endpoint
+ ServerLocalIRCd string `env:"KCPD_LOCAL_IRCD" yaml:"local_ircd"`
+ // WEBIRC password to use for local sockets
+ ServerWEBIRCPassword string `env:"KCPD_WEBIRC_PASSWORD" yaml:"webirc_password"`
+ // ServerTLSCert is the TLS cert file
+ ServerTLSCert string `env:"KCPD_TLS_CERT" yaml:"tls_cert"`
+ // ServerTLSKey is the TLS key file
+ ServerTLSKey string `env:"KCPD_TLS_KEY" yaml:"tls_key"`
+}
+
+var (
+ configFname = flag.String("config", "", "configuration file to use (if unset config will be pulled from the environment)")
+)
+
+func main() {
+ flag.Parse()
+
+ cfg := &Config{}
+
+ if *configFname != "" {
+ fin, err := os.Open(*configFname)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer fin.Close()
+
+ data, err := ioutil.ReadAll(fin)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = yaml.Unmarshal(data, cfg)
+ if err != nil {
+ log.Fatal(err)
+ }
+ } else {
+ err := env.Parse(cfg)
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ switch cfg.Mode {
+ case "client":
+ c, err := NewClient(cfg)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for {
+ err = c.Dial()
+ if err != nil {
+ log.Println(err)
+ }
+
+ time.Sleep(time.Second)
+ }
+
+ case "server":
+ s, err := NewServer(cfg)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = s.ListenAndServe()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ default:
+ log.Fatal(ErrBadConfig)
+ }
+}
diff --git a/irc/kcpd/server.go b/irc/kcpd/server.go
new file mode 100644
index 0000000..c44f040
--- /dev/null
+++ b/irc/kcpd/server.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+ "crypto/tls"
+ "fmt"
+ "log"
+ "net"
+
+ kcp "github.com/xtaci/kcp-go"
+ "github.com/xtaci/smux"
+)
+
+// Server represents the server side of kcpd. It listens on KCP and emits TCP connections from KCP streams.
+type Server struct {
+ cfg *Config
+}
+
+// NewServer creates a new Server and validates config.
+func NewServer(cfg *Config) (*Server, error) {
+ if cfg.Mode != "server" {
+ return nil, ErrBadConfig
+ }
+
+ if cfg.ServerBindAddr == "" && cfg.ServerAthemeURL == "" && cfg.ServerAllowListEndpoint == "" && cfg.ServerLocalIRCd == "" && cfg.ServerWEBIRCPassword == "" && cfg.ServerTLSCert == "" && cfg.ServerTLSKey == "" {
+ return nil, ErrBadConfig
+ }
+
+ return &Server{cfg: cfg}, nil
+}
+
+// ListenAndServe blockingly listens on the UDP port and relays KCP streams to TCP sockets.
+func (s *Server) ListenAndServe() error {
+ l, err := kcp.Listen(s.cfg.ServerBindAddr)
+ if err != nil {
+ return err
+ }
+ defer l.Close()
+
+ log.Printf("listening on KCP: %v", l.Addr())
+
+ for {
+ conn, err := l.Accept()
+ if err != nil {
+ log.Println(err)
+ continue
+ }
+
+ go s.handleConn(conn)
+ }
+}
+
+func (s *Server) handleConn(conn net.Conn) error {
+ defer conn.Close()
+
+ log.Printf("new client: %v", conn.RemoteAddr())
+
+ cert, err := tls.LoadX509KeyPair(s.cfg.ServerTLSCert, s.cfg.ServerTLSKey)
+ if err != nil {
+ return err
+ }
+
+ tcfg := &tls.Config{
+ InsecureSkipVerify: true, // XXX hack remove
+ Certificates: []tls.Certificate{cert},
+ }
+
+ tlsConn := tls.Server(conn, tcfg)
+ defer tlsConn.Close()
+
+ session, err := smux.Server(tlsConn, smux.DefaultConfig())
+ if err != nil {
+ return err
+ }
+ defer session.Close()
+
+ for {
+ cstream, err := session.AcceptStream()
+ if err != nil {
+ log.Printf("client at %s error: %v", conn.RemoteAddr(), err)
+ return err
+ }
+
+ ircConn, err := net.Dial("tcp", s.cfg.ServerLocalIRCd)
+ if err != nil {
+ log.Printf("client at %s error: %v", conn.RemoteAddr(), err)
+ return err
+ }
+
+ host, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
+
+ fmt.Fprintf(ircConn, "WEBIRC %s %s %s %s\r\n", s.cfg.ServerWEBIRCPassword, RandStringRunes(8), host, host)
+
+ go copyConn(cstream, ircConn)
+ }
+
+ return nil
+}
diff --git a/irc/kcpd/util.go b/irc/kcpd/util.go
new file mode 100644
index 0000000..a2c7b3f
--- /dev/null
+++ b/irc/kcpd/util.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "math/rand"
+ "time"
+)
+
+func init() {
+ rand.Seed(time.Now().UnixNano())
+}
+
+var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+// RandStringRunes creates a new random string of length n.
+func RandStringRunes(n int) string {
+ b := make([]rune, n)
+ for i := range b {
+ b[i] = letterRunes[rand.Intn(len(letterRunes))]
+ }
+ return string(b)
+}