diff options
| author | Christine Dodrill <me@christine.website> | 2017-04-14 02:08:40 -0700 |
|---|---|---|
| committer | Christine Dodrill <me@christine.website> | 2017-04-14 02:11:12 -0700 |
| commit | dfa688ec71053f552386b2bbee6c8d45e01b6f46 (patch) | |
| tree | fc08ddc2e8f2ccaa1c36c4c4250aca59b3244066 | |
| parent | 249836270b7e1fe145296b5aa0ef644dcd99bb71 (diff) | |
| download | x-dfa688ec71053f552386b2bbee6c8d45e01b6f46.tar.xz x-dfa688ec71053f552386b2bbee6c8d45e01b6f46.zip | |
kcpd
| -rw-r--r-- | irc/kcpd/.gitignore | 1 | ||||
| -rw-r--r-- | irc/kcpd/README.md | 4 | ||||
| -rw-r--r-- | irc/kcpd/client.go | 93 | ||||
| -rw-r--r-- | irc/kcpd/main.go | 110 | ||||
| -rw-r--r-- | irc/kcpd/server.go | 97 | ||||
| -rw-r--r-- | irc/kcpd/util.go | 21 |
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) +} |
