2014-02-09 18:40:51 +01:00
|
|
|
package goSam
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2019-04-25 23:45:24 -04:00
|
|
|
"crypto/sha256"
|
2019-04-20 23:27:47 -04:00
|
|
|
"encoding/base32"
|
|
|
|
"encoding/base64"
|
2019-04-25 23:45:24 -04:00
|
|
|
"encoding/binary"
|
2014-02-09 18:40:51 +01:00
|
|
|
"fmt"
|
2019-02-27 20:25:36 -05:00
|
|
|
"math"
|
|
|
|
"math/rand"
|
2014-02-10 19:27:24 +01:00
|
|
|
"net"
|
2019-04-25 23:45:24 -04:00
|
|
|
"strings"
|
2021-04-15 19:16:11 -04:00
|
|
|
"sync"
|
2020-11-29 16:09:55 -05:00
|
|
|
"time"
|
2022-03-10 00:54:43 -05:00
|
|
|
|
|
|
|
"github.com/eyedeekay/i2pkeys"
|
2022-03-09 17:46:44 -05:00
|
|
|
//samkeys "github.com/eyedeekay/goSam/compat"
|
2014-02-09 18:40:51 +01:00
|
|
|
)
|
|
|
|
|
2014-02-11 14:48:13 +01:00
|
|
|
// A Client represents a single Connection to the SAM bridge
|
2014-02-09 18:40:51 +01:00
|
|
|
type Client struct {
|
2019-04-25 23:45:24 -04:00
|
|
|
host string
|
|
|
|
port string
|
|
|
|
fromport string
|
|
|
|
toport string
|
2022-04-28 11:23:54 -04:00
|
|
|
user string
|
|
|
|
pass string
|
2018-07-16 02:49:03 -04:00
|
|
|
|
2022-02-01 23:27:28 -05:00
|
|
|
SamConn net.Conn // Control socket
|
|
|
|
SamDGConn DatagramConn // Datagram socket
|
2021-02-24 23:04:55 -05:00
|
|
|
rd *bufio.Reader
|
2022-02-02 00:40:01 -05:00
|
|
|
// d *Client
|
2018-07-16 02:49:03 -04:00
|
|
|
|
2019-02-27 20:25:36 -05:00
|
|
|
sigType string
|
|
|
|
destination string
|
2019-02-24 23:08:01 -05:00
|
|
|
|
2018-07-16 02:49:03 -04:00
|
|
|
inLength uint
|
|
|
|
inVariance int
|
|
|
|
inQuantity uint
|
|
|
|
inBackups uint
|
|
|
|
|
|
|
|
outLength uint
|
|
|
|
outVariance int
|
|
|
|
outQuantity uint
|
|
|
|
outBackups uint
|
|
|
|
|
|
|
|
dontPublishLease bool
|
|
|
|
encryptLease bool
|
2020-09-03 16:19:12 -04:00
|
|
|
leaseSetEncType string
|
2018-07-16 02:49:03 -04:00
|
|
|
|
2018-08-05 00:53:28 -04:00
|
|
|
reduceIdle bool
|
|
|
|
reduceIdleTime uint
|
|
|
|
reduceIdleQuantity uint
|
|
|
|
|
|
|
|
closeIdle bool
|
|
|
|
closeIdleTime uint
|
|
|
|
|
2019-02-24 23:08:01 -05:00
|
|
|
compression bool
|
2019-02-21 17:38:11 -05:00
|
|
|
|
2018-07-16 02:49:03 -04:00
|
|
|
debug bool
|
2021-04-15 19:16:11 -04:00
|
|
|
mutex sync.Mutex
|
2019-02-27 20:46:18 -05:00
|
|
|
//NEVER, EVER modify lastaddr or id yourself. They are used internally only.
|
2020-12-07 00:51:51 -05:00
|
|
|
id int32
|
|
|
|
sammin int
|
|
|
|
sammax int
|
2014-02-09 18:40:51 +01:00
|
|
|
}
|
|
|
|
|
2022-02-01 21:50:35 -05:00
|
|
|
// SAMsigTypes is a slice of the available signature types
|
2019-02-24 23:08:01 -05:00
|
|
|
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",
|
|
|
|
}
|
|
|
|
|
2020-12-05 16:36:19 -05:00
|
|
|
var ValidSAMCommands = []string{
|
|
|
|
"HELLO",
|
|
|
|
"SESSION",
|
|
|
|
"STREAM",
|
|
|
|
}
|
|
|
|
|
2019-04-20 23:27:47 -04:00
|
|
|
var (
|
|
|
|
i2pB64enc *base64.Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")
|
|
|
|
i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
|
|
|
|
)
|
|
|
|
|
2014-02-14 12:05:13 +01:00
|
|
|
// NewDefaultClient creates a new client, connecting to the default host:port at localhost:7656
|
2014-02-10 19:27:24 +01:00
|
|
|
func NewDefaultClient() (*Client, error) {
|
|
|
|
return NewClient("localhost:7656")
|
|
|
|
}
|
|
|
|
|
2014-02-14 12:05:13 +01:00
|
|
|
// NewClient creates a new client, connecting to a specified port
|
2014-02-10 19:27:24 +01:00
|
|
|
func NewClient(addr string) (*Client, error) {
|
2018-07-16 02:49:03 -04:00
|
|
|
return NewClientFromOptions(SetAddr(addr))
|
|
|
|
}
|
|
|
|
|
2022-02-02 00:40:01 -05:00
|
|
|
func NewID() int32 {
|
|
|
|
id := rand.Int31n(math.MaxInt32)
|
|
|
|
fmt.Printf("Initializing new ID: %d\n", id)
|
|
|
|
return id
|
|
|
|
}
|
|
|
|
|
2019-02-27 20:46:18 -05:00
|
|
|
// NewID generates a random number to use as an tunnel name
|
2019-02-27 20:25:36 -05:00
|
|
|
func (c *Client) NewID() int32 {
|
2020-11-29 16:09:55 -05:00
|
|
|
if c.id == 0 {
|
2022-02-02 00:40:01 -05:00
|
|
|
c.id = NewID()
|
2020-11-29 16:09:55 -05:00
|
|
|
}
|
|
|
|
return c.id
|
2019-02-27 20:25:36 -05:00
|
|
|
}
|
|
|
|
|
2019-04-20 23:27:47 -04:00
|
|
|
// 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 {
|
2020-09-03 21:49:16 -04:00
|
|
|
// hash := sha256.New()
|
2019-05-16 18:56:59 -04:00
|
|
|
b64, err := i2pB64enc.DecodeString(c.Base64())
|
2019-05-16 18:56:33 -04:00
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
2020-09-03 21:49:16 -04:00
|
|
|
//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))
|
2019-04-20 23:27:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) base64() []byte {
|
2019-07-03 22:09:29 -04:00
|
|
|
if c.destination != "" {
|
|
|
|
s, _ := i2pB64enc.DecodeString(c.destination)
|
|
|
|
alen := binary.BigEndian.Uint16(s[385:387])
|
|
|
|
return s[:387+alen]
|
|
|
|
}
|
|
|
|
return []byte("")
|
2019-04-20 23:27:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Base64 returns the base64 of the local tunnel
|
|
|
|
func (c *Client) Base64() string {
|
|
|
|
return i2pB64enc.EncodeToString(c.base64())
|
|
|
|
}
|
|
|
|
|
2018-07-16 02:49:03 -04:00
|
|
|
// 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
|
2020-11-29 16:09:55 -05:00
|
|
|
c.inQuantity = 3
|
2019-02-27 20:25:36 -05:00
|
|
|
c.inBackups = 1
|
2018-07-16 02:49:03 -04:00
|
|
|
c.outLength = 3
|
|
|
|
c.outVariance = 0
|
2020-11-29 16:09:55 -05:00
|
|
|
c.outQuantity = 3
|
2019-02-27 20:25:36 -05:00
|
|
|
c.outBackups = 1
|
2018-07-16 02:49:03 -04:00
|
|
|
c.dontPublishLease = true
|
|
|
|
c.encryptLease = false
|
2018-08-05 00:53:28 -04:00
|
|
|
c.reduceIdle = false
|
|
|
|
c.reduceIdleTime = 300000
|
2019-02-27 20:25:36 -05:00
|
|
|
c.reduceIdleQuantity = 1
|
2018-08-05 00:53:28 -04:00
|
|
|
c.closeIdle = true
|
2020-12-05 16:36:19 -05:00
|
|
|
c.closeIdleTime = 600000
|
2020-11-29 16:12:35 -05:00
|
|
|
c.debug = false
|
2019-02-27 20:25:36 -05:00
|
|
|
c.sigType = SAMsigTypes[4]
|
|
|
|
c.id = 0
|
2019-03-14 22:46:17 -04:00
|
|
|
c.destination = ""
|
2020-09-03 16:19:12 -04:00
|
|
|
c.leaseSetEncType = "4,0"
|
2020-09-13 01:32:22 -04:00
|
|
|
c.fromport = ""
|
|
|
|
c.toport = ""
|
2020-12-07 00:51:51 -05:00
|
|
|
c.sammin = 0
|
|
|
|
c.sammax = 1
|
2018-07-16 02:49:03 -04:00
|
|
|
for _, o := range opts {
|
|
|
|
if err := o(&c); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2020-12-05 16:36:19 -05:00
|
|
|
c.id = c.NewID()
|
2020-12-07 00:51:51 -05:00
|
|
|
conn, err := net.DialTimeout("tcp", c.samaddr(), 15*time.Minute)
|
2014-02-10 19:27:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-16 02:49:03 -04:00
|
|
|
if c.debug {
|
2019-12-08 16:57:51 -05:00
|
|
|
conn = WrapConn(conn)
|
2015-03-25 21:37:41 +01:00
|
|
|
}
|
2018-07-16 02:49:03 -04:00
|
|
|
c.SamConn = conn
|
|
|
|
c.rd = bufio.NewReader(conn)
|
|
|
|
return &c, c.hello()
|
|
|
|
}
|
|
|
|
|
2022-02-01 21:50:35 -05:00
|
|
|
// ID returns a the current ID of the client as a string
|
2019-04-25 23:45:24 -04:00
|
|
|
func (p *Client) ID() string {
|
2021-04-15 17:21:41 -04:00
|
|
|
return fmt.Sprintf("%d", p.NewID())
|
2019-04-25 23:45:24 -04:00
|
|
|
}
|
|
|
|
|
2022-02-01 21:50:35 -05:00
|
|
|
// Addr returns the address of the client as a net.Addr
|
2019-07-05 03:10:01 -04:00
|
|
|
func (p *Client) Addr() net.Addr {
|
2022-03-10 00:54:43 -05:00
|
|
|
keys := i2pkeys.I2PAddr(p.Destination())
|
2022-03-09 17:46:44 -05:00
|
|
|
return keys
|
2022-02-01 21:50:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Client) LocalAddr() net.Addr {
|
|
|
|
return p.Addr()
|
2019-07-05 03:10:01 -04:00
|
|
|
}
|
|
|
|
|
2018-07-16 02:49:03 -04:00
|
|
|
//return the combined host:port of the SAM bridge
|
|
|
|
func (c *Client) samaddr() string {
|
|
|
|
return fmt.Sprintf("%s:%s", c.host, c.port)
|
2014-02-09 18:40:51 +01:00
|
|
|
}
|
|
|
|
|
2014-02-11 13:10:24 +01:00
|
|
|
// send the initial handshake command and check that the reply is ok
|
2015-03-25 22:03:05 +01:00
|
|
|
func (c *Client) hello() error {
|
2022-04-28 11:23:54 -04:00
|
|
|
|
|
|
|
r, err := c.sendCmd("HELLO VERSION MIN=3.%d MAX=3.%d %s %s\n", c.sammin, c.sammax, c.getUser(), c.getPass())
|
2014-02-11 13:10:24 +01:00
|
|
|
if err != nil {
|
2014-02-09 18:40:51 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-02-11 13:10:24 +01:00
|
|
|
if r.Topic != "HELLO" {
|
2020-09-13 01:32:22 -04:00
|
|
|
return fmt.Errorf("Client Hello Unknown Reply: %+v\n", r)
|
2014-02-11 13:10:24 +01:00
|
|
|
}
|
2014-02-09 18:40:51 +01:00
|
|
|
|
2019-02-26 23:10:10 -05:00
|
|
|
if r.Pairs["RESULT"] != "OK" {
|
2014-02-11 13:10:24 +01:00
|
|
|
return fmt.Errorf("Handshake did not succeed\nReply:%+v\n", r)
|
2014-02-09 18:40:51 +01:00
|
|
|
}
|
2014-02-11 13:10:24 +01:00
|
|
|
|
2014-02-09 18:40:51 +01:00
|
|
|
return nil
|
2014-02-11 13:11:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// helper to send one command and parse the reply by sam
|
2015-03-25 22:03:05 +01:00
|
|
|
func (c *Client) sendCmd(str string, args ...interface{}) (*Reply, error) {
|
|
|
|
if _, err := fmt.Fprintf(c.SamConn, str, args...); err != nil {
|
|
|
|
return nil, err
|
2014-02-11 13:11:26 +01:00
|
|
|
}
|
|
|
|
|
2015-03-25 21:37:41 +01:00
|
|
|
line, err := c.rd.ReadString('\n')
|
2014-02-11 13:11:26 +01:00
|
|
|
if err != nil {
|
2015-03-25 22:03:05 +01:00
|
|
|
return nil, err
|
2014-02-11 13:11:26 +01:00
|
|
|
}
|
2014-02-11 13:10:24 +01:00
|
|
|
|
2015-03-25 21:37:41 +01:00
|
|
|
return parseReply(line)
|
2014-02-09 18:40:51 +01:00
|
|
|
}
|
|
|
|
|
2014-02-11 14:48:13 +01:00
|
|
|
// Close the underlying socket to SAM
|
2014-02-09 18:40:51 +01:00
|
|
|
func (c *Client) Close() error {
|
2015-03-25 22:03:05 +01:00
|
|
|
c.rd = nil
|
2014-02-11 14:08:52 +01:00
|
|
|
return c.SamConn.Close()
|
2014-02-09 18:40:51 +01:00
|
|
|
}
|
2019-02-27 20:25:36 -05:00
|
|
|
|
2020-12-05 16:36:19 -05:00
|
|
|
// 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) {
|
2019-02-27 20:25:36 -05:00
|
|
|
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),
|
2020-12-05 16:36:19 -05:00
|
|
|
setid(id),
|
2019-02-27 20:25:36 -05:00
|
|
|
)
|
|
|
|
}
|