HS, circuit, onion, and stream commands

This commit is contained in:
Chad Retz 2018-05-11 01:28:31 -05:00
parent 8e934e2747
commit da1464009f
9 changed files with 314 additions and 19 deletions

View File

@ -98,11 +98,9 @@ func (c *Conn) Authenticate(password string) error {
return err
}
func (c *Conn) sendAuthenticate(byts []byte) (err error) {
func (c *Conn) sendAuthenticate(byts []byte) error {
if len(byts) == 0 {
_, err = c.SendRequest("AUTHENTICATE")
} else {
_, err = c.SendRequest("AUTHENTICATE %v", hex.EncodeToString(byts))
return c.sendRequestIgnoreResponse("AUTHENTICATE")
}
return
return c.sendRequestIgnoreResponse("AUTHENTICATE %v", hex.EncodeToString(byts))
}

35
control/cmd_circuit.go Normal file
View File

@ -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)
}

View File

@ -30,8 +30,7 @@ func (c *Conn) sendSetConf(cmd string, entries []*ConfEntry) error {
cmd += "=" + util.EscapeSimpleQuotedStringIfNeeded(*entry.Value)
}
}
_, err := c.SendRequest(cmd)
return err
return c.sendRequestIgnoreResponse(cmd)
}
func (c *Conn) GetConf(keys ...string) ([]*ConfEntry, error) {
@ -60,6 +59,9 @@ func (c *Conn) SaveConf(force bool) error {
if force {
cmd += " FORCE"
}
_, err := c.SendRequest(cmd)
return err
return c.sendRequestIgnoreResponse(cmd)
}
func (c *Conn) LoadConf(conf string) error {
return c.sendRequestIgnoreResponse("+LOADCONF\r\n%v\r\n.", conf)
}

View File

@ -64,8 +64,7 @@ func (c *Conn) sendSetEvents() error {
cmd += " " + string(event)
}
c.eventListenersLock.RUnlock()
_, err := c.SendRequest(cmd)
return err
return c.sendRequestIgnoreResponse(cmd)
}
// zero on fail

View File

@ -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)
}

View File

@ -1,10 +1,17 @@
package control
import "github.com/cretz/bine/util"
import (
"strings"
"github.com/cretz/bine/util"
)
func (c *Conn) Signal(signal string) error {
_, err := c.SendRequest("SIGNAL %v", signal)
return err
return c.sendRequestIgnoreResponse("SIGNAL %v", signal)
}
func (c *Conn) Quit() error {
return c.sendRequestIgnoreResponse("QUIT")
}
type MappedAddress struct {
@ -34,3 +41,55 @@ func (c *Conn) MapAddresses(addresses []*MappedAddress) ([]*MappedAddress, error
}
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")
}

153
control/cmd_onion.go Normal file
View File

@ -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)
}

28
control/cmd_stream.go Normal file
View File

@ -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)
}

View File

@ -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) {
if c.debugEnabled() {
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
}
func (c *Conn) Quit() error {
_, err := c.SendRequest("QUIT")
return err
}
func (c *Conn) Close() error {
// We'll close all the chans first
c.asyncChansLock.Lock()