git » 5medias » commit 8e4209b

Initial commit

author Alberto Bertogli
2018-05-28 23:21:23 UTC
committer Alberto Bertogli
2018-06-09 07:38:48 UTC

Initial commit

.gitignore +7 -0
5medias.go +271 -0
LICENSE +27 -0

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d6d39bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+
+# Binary.
+5medias
+
+# Temporary and config files.
+.*
+
diff --git a/5medias.go b/5medias.go
new file mode 100644
index 0000000..541a962
--- /dev/null
+++ b/5medias.go
@@ -0,0 +1,271 @@
+// 5medias is a simple SOCKS5 proxy.
+//
+// https://www.ietf.org/rfc/rfc1928.txt
+//
+// It only supports TCP CONNECT commands, and static username/password
+// authentication.
+//
+// This authentication is only useful to prevent the most basic unauthorized
+// access, but it is not really secure and it's extremely easy to sniff. Don't
+// rely on it for real security.
+package main
+
+import (
+	"bytes"
+	"encoding/binary"
+	"flag"
+	"fmt"
+	"io"
+	"net"
+	"time"
+
+	"blitiri.com.ar/go/log"
+)
+
+// Command-line flags.
+var (
+	addr     = flag.String("addr", ":1080", "address to listen on")
+	username = flag.String("username", "", "username to expect")
+	password = flag.String("password", "", "password to expect")
+)
+
+func main() {
+	flag.Parse()
+	log.Init()
+
+	ln, err := net.Listen("tcp", *addr)
+	if err != nil {
+		log.Fatalf("error listening: %v", err)
+	}
+
+	log.Infof("listening on %s", *addr)
+
+	for {
+		conn, err := ln.Accept()
+		if err != nil {
+			log.Errorf("error accepting: %v", err)
+			continue
+		}
+
+		c := &Conn{conn: conn}
+		go c.Handle()
+	}
+}
+
+type Conn struct {
+	conn net.Conn
+}
+
+func (c *Conn) Logf(f string, a ...interface{}) {
+	log.Log(log.Info, 1, "%v: %s",
+		c.conn.RemoteAddr(), fmt.Sprintf(f, a...))
+}
+
+func (c *Conn) Handle() {
+	defer c.conn.Close()
+
+	c.Logf("connected")
+	if err := c.handshake(); err != nil {
+		c.Logf("handshake error: %v", err)
+		return
+	}
+
+	dstAddr, err := c.getRequest()
+	if err != nil {
+		c.Logf("request error: %v", err)
+		return
+	}
+
+	c.Logf("dial %q", dstAddr)
+	dstConn, err := net.DialTimeout("tcp", dstAddr, 10*time.Second)
+	if err != nil {
+		c.Logf("outgoing connection error: %v", err)
+		c.reply(5) // Connection refused.
+		return
+	}
+	defer dstConn.Close()
+
+	if dstConn.LocalAddr().(*net.TCPAddr).IP.IsLoopback() {
+		c.Logf("loopback connection denied")
+		c.reply(2) // Connection not allowed by ruleset.
+		return
+	}
+
+	c.reply(0) // Success.
+
+	c.Logf("proxying begin")
+	bidirCopy(c.conn, dstConn)
+	c.Logf("proxying end")
+}
+
+func (c *Conn) handshake() error {
+	hdr := struct {
+		Version  byte
+		NMethods byte
+	}{}
+	if err := binary.Read(c.conn, binary.BigEndian, &hdr); err != nil {
+		return err
+	}
+
+	if hdr.Version != 5 {
+		return fmt.Errorf("invalid version")
+	}
+
+	methods := make([]byte, hdr.NMethods)
+	if err := binary.Read(c.conn, binary.BigEndian, methods); err != nil {
+		return err
+	}
+
+	if *username == "" {
+		// Reply saying that we are ok with no authentication (#0).
+		c.conn.Write([]byte{5, 0})
+	} else {
+		// Check if username/password (method 2) was offered.
+		if !bytes.Contains(methods, []byte{2}) {
+			c.conn.Write([]byte{5, 0xff})
+			return fmt.Errorf("username/password method not offered")
+		}
+
+		// Reply saying that we accept user/password method (#2).
+		c.conn.Write([]byte{5, 2})
+
+		if err := c.auth(); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (c *Conn) auth() error {
+	ver, err := c.readByte()
+	if err != nil {
+		return err
+	}
+	if ver != 1 {
+		return fmt.Errorf("unknown username/password version")
+	}
+
+	cUser, err := c.readBuf()
+	if err != nil {
+		return err
+	}
+
+	cPassword, err := c.readBuf()
+	if err != nil {
+		return err
+	}
+
+	if string(cUser) != *username || string(cPassword) != *password {
+		// Introduce a small delay to mitigate blatant brute force attempts.
+		time.Sleep(200 * time.Millisecond)
+		c.conn.Write([]byte{1, 0xff})
+		return fmt.Errorf("invalid username/password")
+	}
+
+	c.conn.Write([]byte{1, 0})
+
+	return nil
+}
+
+func (c *Conn) getRequest() (string, error) {
+	hdr := struct {
+		Version  byte
+		Command  byte
+		Reserved byte
+		AddrType byte
+	}{}
+	if err := binary.Read(c.conn, binary.BigEndian, &hdr); err != nil {
+		return "", err
+	}
+
+	if hdr.Version != 5 {
+		return "", fmt.Errorf("invalid version")
+	}
+	if hdr.Command != 1 {
+		return "", fmt.Errorf("unsupported command %v", hdr.Command)
+	}
+
+	addrLen := 0
+	switch hdr.AddrType {
+	case 1: // ipv4
+		addrLen = net.IPv4len
+	case 3: // domain name
+		addrLen = 0
+	case 4: // ipv6
+		addrLen = net.IPv6len
+	default:
+		return "", fmt.Errorf("unsupported address type %v", hdr.AddrType)
+	}
+
+	addr := ""
+	if addrLen == 0 {
+		domain, err := c.readBuf()
+		if err != nil {
+			return "", err
+		}
+		addr = string(domain)
+	} else {
+		raw := make([]byte, addrLen)
+		if _, err := c.conn.Read(raw); err != nil {
+			return "", err
+		}
+		ip := net.IP(raw)
+
+		addr = ip.String()
+	}
+
+	port := uint16(0)
+	if err := binary.Read(c.conn, binary.BigEndian, &port); err != nil {
+		return "", err
+	}
+
+	return net.JoinHostPort(addr, fmt.Sprintf("%v", port)), nil
+}
+
+func (c *Conn) reply(result byte) {
+	c.conn.Write([]byte{
+		5, // version
+		result,
+		0,          // reserved
+		1,          // addr type (ipv4, for convenience)
+		0, 0, 0, 0, // ipv4 (0.0.0.0)
+		0, 0, // port 0 (2 bytes)
+	})
+}
+
+func (c *Conn) readByte() (byte, error) {
+	b := make([]byte, 1)
+	_, err := c.conn.Read(b)
+	return b[0], err
+}
+
+func (c *Conn) readBuf() ([]byte, error) {
+	bufLen, err := c.readByte()
+	if err != nil {
+		return nil, err
+	}
+
+	buf := make([]byte, bufLen)
+	_, err = c.conn.Read(buf)
+	return buf, err
+}
+
+func bidirCopy(src, dst io.ReadWriter) {
+	done := make(chan bool, 2)
+
+	go func() {
+		io.Copy(src, dst)
+		done <- true
+	}()
+
+	go func() {
+		io.Copy(dst, src)
+		done <- true
+	}()
+
+	// Return when one of the two completes.
+	// The other goroutine will remain alive, it is up to the caller to create
+	// the conditions to complete it (e.g. by closing one of the sides).
+	<-done
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7106832
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+
+Licensed under the MIT licence, which is reproduced below (from
+https://opensource.org/licenses/MIT).
+
+-----
+
+Copyright (c) 2018
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+