diff --git a/cmd_onion.go b/cmd_onion.go
index 2c62417..b616c02 100644
--- a/cmd_onion.go
+++ b/cmd_onion.go
@@ -1,8 +1,8 @@
// cmd_onion.go - various onion service commands: ADD_ONION, DEL_ONION...
//
-// To the extent possible under law, David Stainton waived all copyright
-// and related or neighboring rights to this module of bulb, using the creative
-// commons "cc0" public domain dedication. See LICENSE or
+// To the extent possible under law, David Stainton and Ivan Markin waived
+// all copyright and related or neighboring rights to this module of bulb,
+// using the creative commons "cc0" public domain dedication. See LICENSE or
// for full details.
package bulb
@@ -37,16 +37,27 @@ type OnionPortSpec struct {
Target string
}
-// AddOnion issues an ADD_ONION command and returns the parsed response.
-func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bool) (*OnionInfo, error) {
+// NewOnionConfig is a configuration for NewOnion command.
+type NewOnionConfig struct {
+ PortSpecs []OnionPortSpec
+ PrivateKey crypto.PrivateKey
+ DiscardPK bool
+ Detach bool
+ BasicAuth bool
+ NonAnonymous bool
+}
+
+// NewOnion issues an ADD_ONION command using configuration config and
+// returns the parsed response.
+func (c *Conn) NewOnion(config *NewOnionConfig) (*OnionInfo, error) {
const keyTypeRSA = "RSA1024"
var err error
var portStr string
- if ports == nil {
+ if config.PortSpecs == nil {
return nil, newProtocolError("invalid port specification")
}
- for _, v := range ports {
+ for _, v := range config.PortSpecs {
portStr += fmt.Sprintf(" Port=%d", v.VirtPort)
if v.Target != "" {
portStr += "," + v.Target
@@ -54,10 +65,10 @@ func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bo
}
var hsKeyType, hsKeyStr string
- if key != nil {
- switch t := key.(type) {
+ if config.PrivateKey != nil {
+ switch t := config.PrivateKey.(type) {
case *rsa.PrivateKey:
- rsaPK, _ := key.(*rsa.PrivateKey)
+ rsaPK, _ := config.PrivateKey.(*rsa.PrivateKey)
if rsaPK.N.BitLen() != 1024 {
return nil, newProtocolError("invalid RSA key size")
}
@@ -68,24 +79,38 @@ func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bo
hsKeyType = keyTypeRSA
hsKeyStr = base64.StdEncoding.EncodeToString(pkDER)
case *OnionPrivateKey:
- genericPK, _ := key.(*OnionPrivateKey)
+ genericPK, _ := config.PrivateKey.(*OnionPrivateKey)
hsKeyType = genericPK.KeyType
hsKeyStr = genericPK.Key
default:
return nil, newProtocolError("unsupported private key type: %v", t)
}
+ } else {
+ hsKeyStr = "BEST"
+ hsKeyType = "NEW"
}
- var resp *Response
- if hsKeyStr == "" {
- flags := " Flags=DiscardPK"
- if !oneshot {
- flags = ""
- }
- resp, err = c.Request("ADD_ONION NEW:BEST%s%s", portStr, flags)
- } else {
- resp, err = c.Request("ADD_ONION %s:%s%s", hsKeyType, hsKeyStr, portStr)
+ var flags []string
+ var flagsStr string
+
+ if config.DiscardPK {
+ flags = append(flags, "DiscardPK")
}
+ if config.Detach {
+ flags = append(flags, "Detach")
+ }
+ if config.BasicAuth {
+ flags = append(flags, "BasicAuth")
+ }
+ if config.NonAnonymous {
+ flags = append(flags, "NonAnonymous")
+ }
+ if flags != nil {
+ flagsStr = " Flags="
+ flagsStr += strings.Join(flags, ",")
+ }
+ request := fmt.Sprintf("ADD_ONION %s:%s%s%s", hsKeyType, hsKeyStr, portStr, flagsStr)
+ resp, err := c.Request(request)
if err != nil {
return nil, err
}
@@ -102,7 +127,7 @@ func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bo
if strings.HasPrefix(l, serviceIDPrefix) {
serviceID = strings.TrimPrefix(l, serviceIDPrefix)
} else if strings.HasPrefix(l, privateKeyPrefix) {
- if oneshot || hsKeyStr != "" {
+ if config.DiscardPK || hsKeyStr != "" {
return nil, newProtocolError("received an unexpected private key")
}
hsKeyStr = strings.TrimPrefix(l, privateKeyPrefix)
@@ -142,6 +167,18 @@ func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bo
return oi, nil
}
+// [DEPRECATED] AddOnion issues an ADD_ONION command and
+// returns the parsed response.
+func (c *Conn) AddOnion(ports []OnionPortSpec, key crypto.PrivateKey, oneshot bool) (*OnionInfo, error) {
+ cfg := &NewOnionConfig{}
+ cfg.PortSpecs = ports
+ if key != nil {
+ cfg.PrivateKey = key
+ }
+ cfg.DiscardPK = oneshot
+ return c.NewOnion(cfg)
+}
+
// DeleteOnion issues a DEL_ONION command and returns the parsed response.
func (c *Conn) DeleteOnion(serviceID string) error {
_, err := c.Request("DEL_ONION %s", serviceID)
diff --git a/examples/listener/listener.go b/examples/listener/listener.go
index c7e7f42..36709ea 100644
--- a/examples/listener/listener.go
+++ b/examples/listener/listener.go
@@ -55,7 +55,11 @@ func main() {
}
log.Printf("Expected ID: %v", id)
- l, err := c.Listener(80, pk)
+ cfg := &bulb.NewOnionConfig{
+ DiscardPK: true,
+ PrivateKey: pk,
+ }
+ l, err := c.NewListener(cfg, 80)
if err != nil {
log.Fatalf("Failed to get Listener: %v", err)
}
diff --git a/listener.go b/listener.go
index b7ddfa8..847b50f 100644
--- a/listener.go
+++ b/listener.go
@@ -1,8 +1,8 @@
// listener.go - Tor backed net.Listener.
//
-// To the extent possible under law, Yawning Angel waived all copyright
-// and related or neighboring rights to bulb, using the creative
-// commons "cc0" public domain dedication. See LICENSE or
+// To the extent possible under law, Yawning Angel and Ivan Markin
+// waived all copyright and related or neighboring rights to bulb, using
+// the creative commons "cc0" public domain dedication. See LICENSE or
// for full details.
package bulb
@@ -49,17 +49,23 @@ func (l *onionListener) Addr() net.Addr {
return l.addr
}
-// Listener returns a net.Listener backed by a Onion Service, optionally
-// having Tor generate an ephemeral private key. Regardless of the status of
-// the returned Listener, the Onion Service will be torn down when the control
-// connection is closed.
-//
-// WARNING: Only one port can be listened to per PrivateKey if this interface
-// is used. To bind to more ports, use the AddOnion call directly.
-func (c *Conn) Listener(port uint16, key crypto.PrivateKey) (net.Listener, error) {
- const (
- loopbackAddr = "127.0.0.1:0"
- )
+// NewListener returns a net.Listener backed by an Onion Service using configuration
+// config, optionally having Tor generate an ephemeral private key (config is nil or
+// config.PrivateKey is nil).
+// All of virtual ports specified in vports will be mapped to the port to which
+// the underlying TCP listener binded. PortSpecs in config will be ignored since
+// there is only one mapping for a vports set is possible.
+func (c *Conn) NewListener(config *NewOnionConfig, vports ...uint16) (net.Listener, error) {
+ var cfg NewOnionConfig
+ if config == nil {
+ cfg = NewOnionConfig{
+ DiscardPK: true,
+ }
+ } else {
+ cfg = *config
+ }
+
+ const loopbackAddr = "127.0.0.1:0"
// Listen on the loopback interface.
tcpListener, err := net.Listen("tcp4", loopbackAddr)
@@ -72,16 +78,39 @@ func (c *Conn) Listener(port uint16, key crypto.PrivateKey) (net.Listener, error
return nil, newProtocolError("failed to extract local port")
}
+ if len(vports) < 1 {
+ return nil, newProtocolError("no virual ports specified")
+ }
+ targetPortStr := strconv.FormatUint((uint64)(tAddr.Port), 10)
+ var portSpecs []OnionPortSpec
+ for _, vport := range vports {
+ portSpecs = append(portSpecs, OnionPortSpec{
+ VirtPort: vport,
+ Target: targetPortStr,
+ })
+ }
+ cfg.PortSpecs = portSpecs
// Create the onion.
- ports := []OnionPortSpec{{port, strconv.FormatUint((uint64)(tAddr.Port), 10)}}
- oi, err := c.AddOnion(ports, key, key == nil)
+ oi, err := c.NewOnion(&cfg)
if err != nil {
tcpListener.Close()
return nil, err
}
- oa := &onionAddr{info: oi, port: port}
+ oa := &onionAddr{info: oi, port: vports[0]}
ol := &onionListener{addr: oa, ctrlConn: c, listener: tcpListener}
return ol, nil
}
+
+// [DEPRECATED] Listener returns a net.Listener backed by an Onion Service.
+func (c *Conn) Listener(port uint16, key crypto.PrivateKey) (net.Listener, error) {
+ cfg := &NewOnionConfig{}
+ if key != nil {
+ cfg.PrivateKey = key
+ cfg.DiscardPK = false
+ } else {
+ cfg.DiscardPK = true
+ }
+ return c.NewListener(cfg, port)
+}