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
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
_, 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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
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()
|
||||
|
|
Loading…
Reference in New Issue