132 lines
4.1 KiB
Go
132 lines
4.1 KiB
Go
package tor
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
type TorLogLevel string
|
|
|
|
const TorLogLevelDebug TorLogLevel = "debug"
|
|
const TorLogLevelNotice TorLogLevel = "notice"
|
|
const TorLogLevelInfo TorLogLevel = "info"
|
|
const TorLogLevelWarn TorLogLevel = "warn"
|
|
const TorLogLevelErr TorLogLevel = "err"
|
|
|
|
// TorrcBuilder is a a helper for building torrc files
|
|
type TorrcBuilder struct {
|
|
lines []string
|
|
}
|
|
|
|
// NewTorrc creates a new torrc builder
|
|
func NewTorrc() *TorrcBuilder {
|
|
return &TorrcBuilder{
|
|
lines: []string{},
|
|
}
|
|
}
|
|
|
|
// WithSocksPort sets the SOCKS port of the tor process
|
|
func (tb *TorrcBuilder) WithSocksPort(port int) *TorrcBuilder {
|
|
tb.lines = append(tb.lines, fmt.Sprintf("SocksPort %v", port))
|
|
return tb
|
|
}
|
|
|
|
// WithControlPort sets the control port of the tor process
|
|
func (tb *TorrcBuilder) WithControlPort(port int) *TorrcBuilder {
|
|
tb.lines = append(tb.lines, fmt.Sprintf("ControlPort %v", port))
|
|
return tb
|
|
}
|
|
|
|
// WithLog sets the Log to file directive to the specified file with the specified log level
|
|
func (tb *TorrcBuilder) WithLog(logfile string, level TorLogLevel) *TorrcBuilder {
|
|
tb.lines = append(tb.lines, fmt.Sprintf("Log %v file %v", level, logfile))
|
|
return tb
|
|
}
|
|
|
|
// WithSocksTimeout adjusts how long before a timeout error is generated trying to connect to the SOCKS port
|
|
func (tb *TorrcBuilder) WithSocksTimeout(timeOutSecs int) *TorrcBuilder {
|
|
tb.lines = append(tb.lines, fmt.Sprintf("SocksTimeout %v", timeOutSecs))
|
|
return tb
|
|
}
|
|
|
|
// WithCustom appends to the torrc builder and allows the client to set any option they want, while benefiting
|
|
// from other configuration options.
|
|
func (tb *TorrcBuilder) WithCustom(lines []string) *TorrcBuilder {
|
|
tb.lines = append(tb.lines, lines...)
|
|
return tb
|
|
}
|
|
|
|
// UseCustom clobbers the torrc builder and allows the client to set any option they want, while benefiting
|
|
// from other configuration options.
|
|
func (tb *TorrcBuilder) UseCustom(lines []string) *TorrcBuilder {
|
|
tb.lines = lines
|
|
return tb
|
|
}
|
|
|
|
// WithOnionTrafficOnly ensures that the tor process only routes tor onion traffic.
|
|
func (tb *TorrcBuilder) WithOnionTrafficOnly() *TorrcBuilder {
|
|
for i, line := range tb.lines {
|
|
if strings.HasPrefix(line, "SocksPort") {
|
|
tb.lines[i] = fmt.Sprintf("%v OnionTrafficOnly", line)
|
|
return tb
|
|
}
|
|
}
|
|
return tb
|
|
}
|
|
|
|
// WithOwningPid adds a __OwningControllerProcess line to the config that will attempt to have tor monitor parent PID health and die when parent dies
|
|
func (tb *TorrcBuilder) WithOwningPid(pid int) *TorrcBuilder {
|
|
tb.lines = append(tb.lines, fmt.Sprintf("__OwningControllerProcess %v", pid))
|
|
return tb
|
|
}
|
|
|
|
// WithHashedPassword sets a password for the control port.
|
|
func (tb *TorrcBuilder) WithHashedPassword(password string) *TorrcBuilder {
|
|
var salt [8]byte
|
|
if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil {
|
|
panic("no randomness")
|
|
}
|
|
hashedpassword := generateHashedPassword(salt, password)
|
|
tb.lines = append(tb.lines, fmt.Sprintf("HashedControlPassword %s", hashedpassword))
|
|
return tb
|
|
}
|
|
|
|
// Build finalizes the torrc contents and write a file
|
|
func (tb *TorrcBuilder) Build(path string) error {
|
|
return os.WriteFile(path, []byte(strings.Join(tb.lines, "\n")), 0600)
|
|
}
|
|
|
|
// Preview provides a string representation of the torrc file without writing it to a file location.
|
|
func (tb *TorrcBuilder) Preview() string {
|
|
return strings.Join(tb.lines, "\n")
|
|
}
|
|
|
|
// GenerateHashedPassword calculates a hash in the same way tha tor --hash-password does
|
|
// this function takes a salt as input which is not great from an api-misuse perspective, but
|
|
// we make it private.
|
|
func generateHashedPassword(salt [8]byte, password string) string {
|
|
c := 96
|
|
count := (16 + (c & 15)) << ((c >> 4) + 6)
|
|
tmp := append(salt[:], []byte(password)...)
|
|
slen := len(tmp)
|
|
d := sha1.New()
|
|
for count != 0 {
|
|
if count > slen {
|
|
d.Write(tmp)
|
|
count -= slen
|
|
} else {
|
|
d.Write(tmp[:count])
|
|
count = 0
|
|
}
|
|
}
|
|
hashed := d.Sum([]byte{})
|
|
return fmt.Sprintf("16:%s%s%s", strings.ToUpper(hex.EncodeToString(salt[:])),
|
|
strings.ToUpper(hex.EncodeToString([]byte{byte(c)})),
|
|
strings.ToUpper(hex.EncodeToString(hashed)))
|
|
}
|