summaryrefslogtreecommitdiff
path: root/webircgateway/pkg/proxy
diff options
context:
space:
mode:
Diffstat (limited to 'webircgateway/pkg/proxy')
-rw-r--r--webircgateway/pkg/proxy/proxy.go129
-rw-r--r--webircgateway/pkg/proxy/server.go237
2 files changed, 366 insertions, 0 deletions
diff --git a/webircgateway/pkg/proxy/proxy.go b/webircgateway/pkg/proxy/proxy.go
new file mode 100644
index 0000000..c332f40
--- /dev/null
+++ b/webircgateway/pkg/proxy/proxy.go
@@ -0,0 +1,129 @@
+package proxy
+
+import (
+ "encoding/json"
+ "errors"
+ "io"
+ "net"
+)
+
+type KiwiProxyState int
+
+const KiwiProxyStateClosed KiwiProxyState = 0
+const KiwiProxyStateConnecting KiwiProxyState = 1
+const KiwiProxyStateHandshaking KiwiProxyState = 2
+const KiwiProxyStateConnected KiwiProxyState = 3
+
+type ConnError struct {
+ Msg string
+ Type string
+}
+
+func (err *ConnError) Error() string {
+ return err.Msg
+}
+
+type KiwiProxyConnection struct {
+ Username string
+ ProxyInterface string
+ DestHost string
+ DestPort int
+ DestTLS bool
+ State KiwiProxyState
+ Conn *net.Conn
+}
+
+func MakeKiwiProxyConnection() *KiwiProxyConnection {
+ return &KiwiProxyConnection{
+ State: KiwiProxyStateClosed,
+ }
+}
+
+func (c *KiwiProxyConnection) Close() error {
+ if c.State == KiwiProxyStateClosed {
+ return errors.New("Connection already closed")
+ }
+
+ return (*c.Conn).Close()
+}
+
+func (c *KiwiProxyConnection) Dial(proxyServerAddr string) error {
+ if c.State != KiwiProxyStateClosed {
+ return errors.New("Connection in closed state")
+ }
+
+ c.State = KiwiProxyStateConnecting
+
+ conn, err := net.Dial("tcp", proxyServerAddr)
+ if err != nil {
+ return err
+ }
+
+ c.Conn = &conn
+ c.State = KiwiProxyStateHandshaking
+
+ meta, _ := json.Marshal(map[string]interface{}{
+ "username": c.Username,
+ "interface": c.ProxyInterface,
+ "host": c.DestHost,
+ "port": c.DestPort,
+ "ssl": c.DestTLS,
+ })
+
+ (*c.Conn).Write(append(meta, byte('\n')))
+
+ buf := make([]byte, 1024)
+ bufLen, readErr := (*c.Conn).Read(buf)
+ if readErr != nil {
+ (*c.Conn).Close()
+ c.State = KiwiProxyStateClosed
+ return readErr
+ }
+
+ response := string(buf)
+ if bufLen > 0 && response[0] == '1' {
+ c.State = KiwiProxyStateConnected
+ } else {
+ (*c.Conn).Close()
+ c.State = KiwiProxyStateClosed
+
+ if bufLen == 0 {
+ return errors.New("The proxy could not connect to the destination")
+ }
+
+ switch response[0] {
+ case '0':
+ return errors.New("The proxy could not connect to the destination")
+ case '2':
+ return &ConnError{Msg: "Connection reset", Type: "conn_reset"}
+ case '3':
+ return &ConnError{Msg: "Connection refused", Type: "conn_refused"}
+ case '4':
+ return &ConnError{Msg: "Host not found", Type: "not_found"}
+ case '5':
+ return &ConnError{Msg: "Connection timed out", Type: "conn_timeout"}
+ }
+ }
+
+ return nil
+}
+
+func (c *KiwiProxyConnection) Read(b []byte) (n int, err error) {
+ if c.State == KiwiProxyStateConnecting || c.State == KiwiProxyStateHandshaking {
+ return 0, nil
+ } else if c.State == KiwiProxyStateClosed {
+ return 0, io.EOF
+ } else {
+ return (*c.Conn).Read(b)
+ }
+}
+
+func (c *KiwiProxyConnection) Write(b []byte) (n int, err error) {
+ if c.State == KiwiProxyStateConnecting || c.State == KiwiProxyStateHandshaking {
+ return 0, nil
+ } else if c.State == KiwiProxyStateClosed {
+ return 0, io.EOF
+ } else {
+ return (*c.Conn).Write(b)
+ }
+}
diff --git a/webircgateway/pkg/proxy/server.go b/webircgateway/pkg/proxy/server.go
new file mode 100644
index 0000000..7e3f62f
--- /dev/null
+++ b/webircgateway/pkg/proxy/server.go
@@ -0,0 +1,237 @@
+package proxy
+
+import (
+ "bufio"
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "strconv"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/kiwiirc/webircgateway/pkg/identd"
+)
+
+const (
+ ResponseError = "0"
+ ResponseOK = "1"
+ ResponseReset = "2"
+ ResponseRefused = "3"
+ ResponseUnknownHost = "4"
+ ResponseTimeout = "5"
+)
+
+var identdRpc *identd.RpcClient
+var Server net.Listener
+
+type HandshakeMeta struct {
+ Host string `json:"host"`
+ Port int `json:"port"`
+ TLS bool `json:"ssl"`
+ Username string `json:"username"`
+ Interface string `json:"interface"`
+}
+
+func MakeClient(conn net.Conn) *Client {
+ return &Client{
+ Client: conn,
+ }
+}
+
+type Client struct {
+ Client net.Conn
+ Upstream net.Conn
+ UpstreamAddr *net.TCPAddr
+ Username string
+ BindAddr *net.TCPAddr
+ TLS bool
+}
+
+func (c *Client) Run() {
+ var err error
+
+ err = c.Handshake()
+ if err != nil {
+ log.Println(err.Error())
+ return
+ }
+
+ err = c.ConnectUpstream()
+ if err != nil {
+ log.Println(err.Error())
+ return
+ }
+
+ c.Pipe()
+}
+
+func (c *Client) Handshake() error {
+ // Read the first line - it should be JSON
+ reader := bufio.NewReader(c.Client)
+ line, readErr := reader.ReadBytes('\n')
+ if readErr != nil {
+ return readErr
+ }
+
+ var meta = HandshakeMeta{
+ Username: "user",
+ Port: 6667,
+ Interface: "0.0.0.0",
+ }
+ unmarshalErr := json.Unmarshal(line, &meta)
+ if unmarshalErr != nil {
+ c.Client.Write([]byte(ResponseError))
+ return unmarshalErr
+ }
+
+ if meta.Host == "" || meta.Port == 0 || meta.Username == "" || meta.Interface == "" {
+ c.Client.Write([]byte(ResponseError))
+ return fmt.Errorf("missing args")
+ }
+
+ c.Username = meta.Username
+ c.TLS = meta.TLS
+
+ bindAddr, bindAddrErr := net.ResolveTCPAddr("tcp", meta.Interface+":")
+ if bindAddrErr != nil {
+ c.Client.Write([]byte(ResponseError))
+ return fmt.Errorf("interface: " + bindAddrErr.Error())
+ }
+ c.BindAddr = bindAddr
+
+ hostStr := net.JoinHostPort(meta.Host, strconv.Itoa(meta.Port))
+ addr, addrErr := net.ResolveTCPAddr("tcp", hostStr)
+ if addrErr != nil {
+ c.Client.Write([]byte(ResponseUnknownHost))
+ return fmt.Errorf("remote host: " + addrErr.Error())
+ }
+ c.UpstreamAddr = addr
+
+ return nil
+}
+
+func (c *Client) ConnectUpstream() error {
+ dialer := &net.Dialer{}
+ dialer.LocalAddr = c.BindAddr
+ dialer.Timeout = time.Second * 10
+
+ conn, err := dialer.Dial("tcp", c.UpstreamAddr.String())
+ if err != nil {
+ response := ""
+ errType := typeOfErr(err)
+ switch errType {
+ case "timeout":
+ response = ResponseTimeout
+ case "unknown_host":
+ response = ResponseUnknownHost
+ case "refused":
+ response = ResponseRefused
+ }
+
+ c.Client.Write([]byte(response))
+ return err
+ }
+
+ if identdRpc != nil {
+ lAddr, lPortStr, _ := net.SplitHostPort(conn.LocalAddr().String())
+ lPort, _ := strconv.Atoi(lPortStr)
+ identdRpc.AddIdent(lPort, c.UpstreamAddr.Port, c.Username, lAddr)
+ }
+
+ if c.TLS {
+ tlsConfig := &tls.Config{InsecureSkipVerify: true}
+ tlsConn := tls.Client(conn, tlsConfig)
+ err := tlsConn.Handshake()
+ if err != nil {
+ conn.Close()
+ c.Client.Write([]byte(ResponseReset))
+ return err
+ }
+
+ conn = net.Conn(tlsConn)
+ }
+
+ c.Upstream = conn
+ c.Client.Write([]byte(ResponseOK))
+ return nil
+}
+
+func (c *Client) Pipe() {
+ wg := sync.WaitGroup{}
+ wg.Add(2)
+
+ go func() {
+ io.Copy(c.Client, c.Upstream)
+ c.Client.Close()
+ wg.Done()
+ }()
+
+ go func() {
+ io.Copy(c.Upstream, c.Client)
+ c.Upstream.Close()
+ wg.Done()
+ }()
+
+ wg.Wait()
+
+ if identdRpc != nil {
+ lAddr, lPortStr, _ := net.SplitHostPort(c.Upstream.LocalAddr().String())
+ lPort, _ := strconv.Atoi(lPortStr)
+ identdRpc.RemoveIdent(lPort, c.UpstreamAddr.Port, c.Username, lAddr)
+ }
+}
+
+func Start(laddr string) {
+ srv, err := net.Listen("tcp", laddr)
+ if err != nil {
+ log.Fatal(err.Error())
+ }
+
+ // Expose the server
+ Server = srv
+ log.Printf("Kiwi proxy listening on %s", srv.Addr().String())
+
+ identdRpc = identd.MakeRpcClient("kiwiproxy" + laddr)
+ go identdRpc.ConnectAndReconnect("127.0.0.1:1133")
+
+ for {
+ conn, err := srv.Accept()
+ if err != nil {
+ log.Print(err.Error())
+ break
+ }
+
+ c := MakeClient(conn)
+ go c.Run()
+ }
+}
+
+func typeOfErr(err error) string {
+ if err == nil {
+ return ""
+ }
+
+ if netError, ok := err.(net.Error); ok && netError.Timeout() {
+ return "timeout"
+ }
+
+ switch t := err.(type) {
+ case *net.OpError:
+ if t.Op == "dial" {
+ return "unknown_host"
+ } else if t.Op == "read" {
+ return "refused"
+ }
+
+ case syscall.Errno:
+ if t == syscall.ECONNREFUSED {
+ return "refused"
+ }
+ }
+
+ return ""
+}