Files
goSam/client.go
2022-11-18 00:04:49 -05:00

287 lines
6.5 KiB
Go

package goSam
import (
"bufio"
"crypto/sha256"
"encoding/base32"
"encoding/base64"
"encoding/binary"
"fmt"
"math"
"math/rand"
"net"
"strings"
"sync"
"time"
"github.com/eyedeekay/i2pkeys"
//samkeys "github.com/eyedeekay/goSam/compat"
)
// A Client represents a single Connection to the SAM bridge
type Client struct {
host string
port string
fromport string
toport string
user string
pass string
SamConn net.Conn // Control socket
SamDGConn net.PacketConn // Datagram socket
rd *bufio.Reader
// d *Client
sigType string
destination string
inLength uint
inVariance int
inQuantity uint
inBackups uint
outLength uint
outVariance int
outQuantity uint
outBackups uint
dontPublishLease bool
encryptLease bool
leaseSetEncType string
reduceIdle bool
reduceIdleTime uint
reduceIdleQuantity uint
closeIdle bool
closeIdleTime uint
compression bool
debug bool
mutex sync.Mutex
//NEVER, EVER modify lastaddr or id yourself. They are used internally only.
id int32
sammin int
sammax int
}
// SAMsigTypes is a slice of the available signature types
var SAMsigTypes = []string{
"SIGNATURE_TYPE=DSA_SHA1",
"SIGNATURE_TYPE=ECDSA_SHA256_P256",
"SIGNATURE_TYPE=ECDSA_SHA384_P384",
"SIGNATURE_TYPE=ECDSA_SHA512_P521",
"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519",
}
var ValidSAMCommands = []string{
"HELLO",
"SESSION",
"STREAM",
}
var (
i2pB64enc *base64.Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")
i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
)
// NewDefaultClient creates a new client, connecting to the default host:port at localhost:7656
func NewDefaultClient() (*Client, error) {
return NewClient("localhost:7656")
}
// NewClient creates a new client, connecting to a specified port
func NewClient(addr string) (*Client, error) {
return NewClientFromOptions(SetAddr(addr))
}
func NewID() int32 {
id := rand.Int31n(math.MaxInt32)
fmt.Printf("Initializing new ID: %d\n", id)
return id
}
// NewID generates a random number to use as an tunnel name
func (c *Client) NewID() int32 {
if c.id == 0 {
c.id = NewID()
}
return c.id
}
// Destination returns the full destination of the local tunnel
func (c *Client) Destination() string {
return c.destination
}
// Base32 returns the base32 of the local tunnel
func (c *Client) Base32() string {
// hash := sha256.New()
b64, err := i2pB64enc.DecodeString(c.Base64())
if err != nil {
return ""
}
//hash.Write([]byte(b64))
var s []byte
for _, e := range sha256.Sum256(b64) {
s = append(s, e)
}
return strings.ToLower(strings.Replace(i2pB32enc.EncodeToString(s), "=", "", -1))
}
func (c *Client) base64() []byte {
if c.destination != "" {
s, _ := i2pB64enc.DecodeString(c.destination)
alen := binary.BigEndian.Uint16(s[385:387])
return s[:387+alen]
}
return []byte("")
}
// Base64 returns the base64 of the local tunnel
func (c *Client) Base64() string {
return i2pB64enc.EncodeToString(c.base64())
}
// NewClientFromOptions creates a new client, connecting to a specified port
func NewClientFromOptions(opts ...func(*Client) error) (*Client, error) {
var c Client
c.host = "127.0.0.1"
c.port = "7656"
c.inLength = 3
c.inVariance = 0
c.inQuantity = 3
c.inBackups = 1
c.outLength = 3
c.outVariance = 0
c.outQuantity = 3
c.outBackups = 1
c.dontPublishLease = true
c.encryptLease = false
c.reduceIdle = false
c.reduceIdleTime = 300000
c.reduceIdleQuantity = 1
c.closeIdle = true
c.closeIdleTime = 600000
c.debug = false
c.sigType = SAMsigTypes[4]
c.id = 0
c.destination = ""
c.leaseSetEncType = "4,0"
c.fromport = ""
c.toport = ""
c.sammin = 0
c.sammax = 1
for _, o := range opts {
if err := o(&c); err != nil {
return nil, err
}
}
c.id = c.NewID()
conn, err := net.DialTimeout("tcp", c.samaddr(), 15*time.Minute)
if err != nil {
return nil, err
}
if c.debug {
conn = WrapConn(conn)
}
c.SamConn = conn
c.rd = bufio.NewReader(conn)
return &c, c.hello()
}
// ID returns a the current ID of the client as a string
func (p *Client) ID() string {
return fmt.Sprintf("%d", p.NewID())
}
// Addr returns the address of the client as a net.Addr
func (p *Client) Addr() net.Addr {
keys := i2pkeys.I2PAddr(p.Destination())
return keys
}
func (p *Client) LocalAddr() net.Addr {
return p.Addr()
}
// LocalKeys returns the local keys of the client as a fully-fledged i2pkeys.I2PKeys
func (p *Client) PrivateAddr() i2pkeys.I2PKeys {
//keys := i2pkeys.I2PAddr(p.Destination())
keys := i2pkeys.NewKeys(i2pkeys.I2PAddr(p.base64()), p.Destination())
return keys
}
// return the combined host:port of the SAM bridge
func (c *Client) samaddr() string {
return fmt.Sprintf("%s:%s", c.host, c.port)
}
// send the initial handshake command and check that the reply is ok
func (c *Client) hello() error {
r, err := c.sendCmd("HELLO VERSION MIN=3.%d MAX=3.%d %s %s\n", c.sammin, c.sammax, c.getUser(), c.getPass())
if err != nil {
return err
}
if r.Topic != "HELLO" {
return fmt.Errorf("Client Hello Unknown Reply: %+v\n", r)
}
if r.Pairs["RESULT"] != "OK" {
return fmt.Errorf("Handshake did not succeed\nReply:%+v\n", r)
}
return nil
}
// helper to send one command and parse the reply by sam
func (c *Client) sendCmd(str string, args ...interface{}) (*Reply, error) {
if _, err := fmt.Fprintf(c.SamConn, str, args...); err != nil {
return nil, err
}
line, err := c.rd.ReadString('\n')
if err != nil {
return nil, err
}
return parseReply(line)
}
// Close the underlying socket to SAM
func (c *Client) Close() error {
c.rd = nil
return c.SamConn.Close()
}
// NewClient generates an exact copy of the client with the same options, but
// re-does all the handshaky business so that Dial can pick up right where it
// left off, should the need arise.
func (c *Client) NewClient(id int32) (*Client, error) {
return NewClientFromOptions(
SetHost(c.host),
SetPort(c.port),
SetDebug(c.debug),
SetInLength(c.inLength),
SetOutLength(c.outLength),
SetInVariance(c.inVariance),
SetOutVariance(c.outVariance),
SetInQuantity(c.inQuantity),
SetOutQuantity(c.outQuantity),
SetInBackups(c.inBackups),
SetOutBackups(c.outBackups),
SetUnpublished(c.dontPublishLease),
SetEncrypt(c.encryptLease),
SetReduceIdle(c.reduceIdle),
SetReduceIdleTime(c.reduceIdleTime),
SetReduceIdleQuantity(c.reduceIdleQuantity),
SetCloseIdle(c.closeIdle),
SetCloseIdleTime(c.closeIdleTime),
SetCompression(c.compression),
setid(id),
)
}