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