diff options
Diffstat (limited to 'webircgateway/pkg/proxy')
| -rw-r--r-- | webircgateway/pkg/proxy/proxy.go | 129 | ||||
| -rw-r--r-- | webircgateway/pkg/proxy/server.go | 237 |
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 "" +} |
