HS, circuit, onion, and stream commands
This commit is contained in:
parent
8e934e2747
commit
da1464009f
|
@ -98,11 +98,9 @@ func (c *Conn) Authenticate(password string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) sendAuthenticate(byts []byte) (err error) {
|
func (c *Conn) sendAuthenticate(byts []byte) error {
|
||||||
if len(byts) == 0 {
|
if len(byts) == 0 {
|
||||||
_, err = c.SendRequest("AUTHENTICATE")
|
return c.sendRequestIgnoreResponse("AUTHENTICATE")
|
||||||
} else {
|
|
||||||
_, err = c.SendRequest("AUTHENTICATE %v", hex.EncodeToString(byts))
|
|
||||||
}
|
}
|
||||||
return
|
return c.sendRequestIgnoreResponse("AUTHENTICATE %v", hex.EncodeToString(byts))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package control
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Conn) ExtendCircuit(circuitID string, path []string, purpose string) (string, error) {
|
||||||
|
if circuitID == "" {
|
||||||
|
circuitID = "0"
|
||||||
|
}
|
||||||
|
cmd := "EXTENDCIRCUIT " + circuitID
|
||||||
|
if len(path) > 0 {
|
||||||
|
cmd += " " + strings.Join(path, ",")
|
||||||
|
}
|
||||||
|
if purpose != "" {
|
||||||
|
cmd += " purpose=" + purpose
|
||||||
|
}
|
||||||
|
resp, err := c.SendRequest(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return resp.Reply[strings.LastIndexByte(resp.Reply, ' ')+1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) SetCircuitPurpose(circuitID string, purpose string) error {
|
||||||
|
return c.sendRequestIgnoreResponse("SETCIRCUITPURPOSE %v purpose=%v", circuitID, purpose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) CloseCircuit(circuitID string, flags []string) error {
|
||||||
|
cmd := "CLOSECIRCUIT " + circuitID
|
||||||
|
for _, flag := range flags {
|
||||||
|
cmd += " " + flag
|
||||||
|
}
|
||||||
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
|
}
|
|
@ -30,8 +30,7 @@ func (c *Conn) sendSetConf(cmd string, entries []*ConfEntry) error {
|
||||||
cmd += "=" + util.EscapeSimpleQuotedStringIfNeeded(*entry.Value)
|
cmd += "=" + util.EscapeSimpleQuotedStringIfNeeded(*entry.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := c.SendRequest(cmd)
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) GetConf(keys ...string) ([]*ConfEntry, error) {
|
func (c *Conn) GetConf(keys ...string) ([]*ConfEntry, error) {
|
||||||
|
@ -60,6 +59,9 @@ func (c *Conn) SaveConf(force bool) error {
|
||||||
if force {
|
if force {
|
||||||
cmd += " FORCE"
|
cmd += " FORCE"
|
||||||
}
|
}
|
||||||
_, err := c.SendRequest(cmd)
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
func (c *Conn) LoadConf(conf string) error {
|
||||||
|
return c.sendRequestIgnoreResponse("+LOADCONF\r\n%v\r\n.", conf)
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,7 @@ func (c *Conn) sendSetEvents() error {
|
||||||
cmd += " " + string(event)
|
cmd += " " + string(event)
|
||||||
}
|
}
|
||||||
c.eventListenersLock.RUnlock()
|
c.eventListenersLock.RUnlock()
|
||||||
_, err := c.SendRequest(cmd)
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// zero on fail
|
// zero on fail
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package control
|
||||||
|
|
||||||
|
func (c *Conn) GetHiddenServiceDescriptorAsync(address string, server string) error {
|
||||||
|
cmd := "HSFETCH " + address
|
||||||
|
if server != "" {
|
||||||
|
cmd += " SERVER=" + server
|
||||||
|
}
|
||||||
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) PostHiddenServiceDescriptorAsync(desc string, servers []string, address string) error {
|
||||||
|
cmd := "+HSPOST"
|
||||||
|
for _, server := range servers {
|
||||||
|
cmd += " SERVER=" + server
|
||||||
|
}
|
||||||
|
if address != "" {
|
||||||
|
cmd += "HSADDRESS=" + address
|
||||||
|
}
|
||||||
|
cmd += "\r\n" + desc + "\r\n."
|
||||||
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
|
}
|
|
@ -1,10 +1,17 @@
|
||||||
package control
|
package control
|
||||||
|
|
||||||
import "github.com/cretz/bine/util"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cretz/bine/util"
|
||||||
|
)
|
||||||
|
|
||||||
func (c *Conn) Signal(signal string) error {
|
func (c *Conn) Signal(signal string) error {
|
||||||
_, err := c.SendRequest("SIGNAL %v", signal)
|
return c.sendRequestIgnoreResponse("SIGNAL %v", signal)
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Quit() error {
|
||||||
|
return c.sendRequestIgnoreResponse("QUIT")
|
||||||
}
|
}
|
||||||
|
|
||||||
type MappedAddress struct {
|
type MappedAddress struct {
|
||||||
|
@ -34,3 +41,55 @@ func (c *Conn) MapAddresses(addresses []*MappedAddress) ([]*MappedAddress, error
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InfoValue struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) GetInfo(keys ...string) ([]*InfoValue, error) {
|
||||||
|
resp, err := c.SendRequest("GETCONF %v", strings.Join(keys, " "))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret := make([]*InfoValue, 0, len(resp.Data))
|
||||||
|
for _, val := range resp.Data {
|
||||||
|
infoVal := &InfoValue{}
|
||||||
|
infoVal.Key, infoVal.Value, _ = util.PartitionString(val, '=')
|
||||||
|
ret = append(ret, infoVal)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) PostDescriptor(descriptor string, purpose string, cache string) error {
|
||||||
|
cmd := "+POSTDESCRIPTOR"
|
||||||
|
if purpose != "" {
|
||||||
|
cmd += " purpose=" + purpose
|
||||||
|
}
|
||||||
|
if cache != "" {
|
||||||
|
cmd += " cache=" + cache
|
||||||
|
}
|
||||||
|
cmd += "\r\n" + descriptor + "\r\n."
|
||||||
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) UseFeatures(features ...string) error {
|
||||||
|
return c.sendRequestIgnoreResponse("USEFEATURE " + strings.Join(features, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: can this take multiple
|
||||||
|
func (c *Conn) ResolveAsync(address string, reverse bool) error {
|
||||||
|
cmd := "RESOLVE "
|
||||||
|
if reverse {
|
||||||
|
cmd += "mode=reverse "
|
||||||
|
}
|
||||||
|
return c.sendRequestIgnoreResponse(cmd + address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) TakeOwnership() error {
|
||||||
|
return c.sendRequestIgnoreResponse("TAKEOWNERSHIP")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) DropGuards() error {
|
||||||
|
return c.sendRequestIgnoreResponse("DROPGUARDS")
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
package control
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cretz/bine/util"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyTypeNew KeyType = "NEW"
|
||||||
|
KeyTypeRSA1024 KeyType = "RSA1024"
|
||||||
|
KeyTypeED25519V3 KeyType = "ED25519-V3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyAlgo string
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyAlgoBest KeyAlgo = "BEST"
|
||||||
|
KeyAlgoRSA1024 KeyAlgo = "RSA1024"
|
||||||
|
KeyAlgoED25519V3 KeyAlgo = "ED25519-V3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Key interface {
|
||||||
|
Type() KeyType
|
||||||
|
Blob() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyFromString(str string) (Key, error) {
|
||||||
|
typ, blob, _ := util.PartitionString(str, ':')
|
||||||
|
switch KeyType(typ) {
|
||||||
|
case KeyTypeNew:
|
||||||
|
return GenKeyFromBlob(blob), nil
|
||||||
|
case KeyTypeRSA1024:
|
||||||
|
return RSA1024KeyFromBlob(blob)
|
||||||
|
case KeyTypeED25519V3:
|
||||||
|
return ED25519KeyFromBlob(blob)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unrecognized key type: %v", typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenKey KeyAlgo
|
||||||
|
|
||||||
|
func GenKeyFromBlob(blob string) GenKey { return GenKey(KeyAlgo(blob)) }
|
||||||
|
func (GenKey) Type() KeyType { return KeyTypeNew }
|
||||||
|
func (g GenKey) Blob() string { return string(g) }
|
||||||
|
|
||||||
|
type RSAKey struct{ *rsa.PrivateKey }
|
||||||
|
|
||||||
|
func RSA1024KeyFromBlob(blob string) (*RSAKey, error) {
|
||||||
|
byts, err := base64.StdEncoding.DecodeString(blob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rsaKey, err := x509.ParsePKCS1PrivateKey(byts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &RSAKey{rsaKey}, nil
|
||||||
|
}
|
||||||
|
func (*RSAKey) Type() KeyType { return KeyTypeRSA1024 }
|
||||||
|
func (r *RSAKey) Blob() string {
|
||||||
|
return base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PrivateKey(r.PrivateKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ED25519Key ed25519.PrivateKey
|
||||||
|
|
||||||
|
func ED25519KeyFromBlob(blob string) (ED25519Key, error) {
|
||||||
|
byts, err := base64.StdEncoding.DecodeString(blob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ED25519Key(ed25519.PrivateKey(byts)), nil
|
||||||
|
}
|
||||||
|
func (ED25519Key) Type() KeyType { return KeyTypeED25519V3 }
|
||||||
|
func (e ED25519Key) Blob() string { return base64.StdEncoding.EncodeToString(e) }
|
||||||
|
|
||||||
|
type AddOnionRequest struct {
|
||||||
|
Key Key
|
||||||
|
Flags []string
|
||||||
|
MaxStreams int
|
||||||
|
Ports map[string]string
|
||||||
|
ClientAuths map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddOnionResponse struct {
|
||||||
|
ServiceID string
|
||||||
|
Key Key
|
||||||
|
ClientAuths map[string]string
|
||||||
|
RawResponse *Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) AddOnion(req *AddOnionRequest) (*AddOnionResponse, error) {
|
||||||
|
// Build command
|
||||||
|
if req.Key == nil {
|
||||||
|
return nil, c.protoErr("Key required")
|
||||||
|
}
|
||||||
|
cmd := "ADDONION " + string(req.Key.Type()) + ":" + req.Key.Blob()
|
||||||
|
if len(req.Flags) > 0 {
|
||||||
|
cmd += " Flags=" + strings.Join(req.Flags, ",")
|
||||||
|
}
|
||||||
|
if req.MaxStreams > 0 {
|
||||||
|
cmd += " MaxStreams=" + strconv.Itoa(req.MaxStreams)
|
||||||
|
}
|
||||||
|
for virt, target := range req.Ports {
|
||||||
|
cmd += " Port=" + virt
|
||||||
|
if target != "" {
|
||||||
|
cmd += "," + target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name, blob := range req.ClientAuths {
|
||||||
|
cmd += " ClientAuth=" + name
|
||||||
|
if blob != "" {
|
||||||
|
cmd += ":" + blob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Invoke and read response
|
||||||
|
resp, err := c.SendRequest(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret := &AddOnionResponse{RawResponse: resp}
|
||||||
|
for _, data := range resp.Data {
|
||||||
|
key, val, _ := util.PartitionString(data, '=')
|
||||||
|
switch key {
|
||||||
|
case "ServiceID":
|
||||||
|
ret.ServiceID = val
|
||||||
|
case "PrivateKey":
|
||||||
|
if ret.Key, err = KeyFromString(val); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "ClientAuth":
|
||||||
|
name, pass, _ := util.PartitionString(val, ':')
|
||||||
|
if ret.ClientAuths == nil {
|
||||||
|
ret.ClientAuths = map[string]string{}
|
||||||
|
}
|
||||||
|
ret.ClientAuths[name] = pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) DelOnion(serviceID string) error {
|
||||||
|
return c.sendRequestIgnoreResponse("DELONION %v", serviceID)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package control
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Conn) AttachStream(streamID string, circuitID string, hopNum int) error {
|
||||||
|
if circuitID == "" {
|
||||||
|
circuitID = "0"
|
||||||
|
}
|
||||||
|
cmd := "ATTACHSTREAM " + streamID + " " + circuitID
|
||||||
|
if hopNum > 0 {
|
||||||
|
cmd += " HOP=" + strconv.Itoa(hopNum)
|
||||||
|
}
|
||||||
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) RedirectStream(streamID string, address string, port int) error {
|
||||||
|
cmd := "REDIRECTSTREAM " + streamID + " " + address
|
||||||
|
if port > 0 {
|
||||||
|
cmd += " " + strconv.Itoa(port)
|
||||||
|
}
|
||||||
|
return c.sendRequestIgnoreResponse(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) CloseStream(streamID string, reason string) error {
|
||||||
|
return c.sendRequestIgnoreResponse("CLOSESTREAM %v %v", streamID, reason)
|
||||||
|
}
|
|
@ -34,6 +34,11 @@ func NewConn(conn *textproto.Conn) *Conn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Conn) sendRequestIgnoreResponse(format string, args ...interface{}) error {
|
||||||
|
_, err := c.SendRequest(format, args...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Conn) SendRequest(format string, args ...interface{}) (*Response, error) {
|
func (c *Conn) SendRequest(format string, args ...interface{}) (*Response, error) {
|
||||||
if c.debugEnabled() {
|
if c.debugEnabled() {
|
||||||
c.debugf("Write line: %v", fmt.Sprintf(format, args...))
|
c.debugf("Write line: %v", fmt.Sprintf(format, args...))
|
||||||
|
@ -58,11 +63,6 @@ func (c *Conn) SendRequest(format string, args ...interface{}) (*Response, error
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Quit() error {
|
|
||||||
_, err := c.SendRequest("QUIT")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Close() error {
|
func (c *Conn) Close() error {
|
||||||
// We'll close all the chans first
|
// We'll close all the chans first
|
||||||
c.asyncChansLock.Lock()
|
c.asyncChansLock.Lock()
|
||||||
|
|
Loading…
Reference in New Issue