package main import ( "crypto/x509" "encoding/pem" "errors" "flag" "fmt" "io/ioutil" "log" "math/rand" "net" "net/http" "net/rpc" "os" "path/filepath" "strconv" "time" "git.xeserv.us/xena/route/routerpc" r "github.com/GoRethink/gorethink" "github.com/Xe/uuid" "github.com/Yawning/bulb" "github.com/facebookgo/flagenv" _ "github.com/joho/godotenv/autoload" "github.com/koding/tunnel" "github.com/sycamoreone/orc/tor" ) var ( rethinkDBHost = flag.String("rethink-host", "", "RethinkDB host") rethinkDBDatabase = flag.String("rethink-database", "", "RethinkDB database") controlKeyFile = flag.String("control-key-file", "", "Control host keyfile") controlHost = flag.String("control-host", "", "Control host onion hash") torDataDir = flag.String("tor-data-dir", "./var", "Tor data directory") torHashedPassword = flag.String("tor-hashed-password", "", "Tor hashed password") torPassword = flag.String("tor-password", "hunter2", "Tor clear password") webPort = flag.String("web-port", "9234", "HTTP ingress port for backends and users") ) // RPC constants const ( RPCPort uint16 = 39453 ) func main() { flag.Parse() flagenv.Parse() rand.Seed(time.Now().Unix()) s, err := create(&ServerConfig{ ControlHost: *controlHost, ControlKeyFile: *controlKeyFile, RethinkDBHost: *rethinkDBHost, RethinkDBDatabase: *rethinkDBDatabase, TorDataDir: *torDataDir, TorHashedPassword: *torHashedPassword, TorPassword: *torPassword, }) if err != nil { log.Fatal(err) } l, err := net.Listen("tcp", "127.0.0.1:"+*webPort) if err != nil { log.Fatal(err) } defer l.Close() hs := &http.Server{ Handler: s, Addr: "127.0.0.1:" + *webPort, } hs.Serve(l) } // Server is the main server type type Server struct { cfg *ServerConfig sess *r.Session torCon *bulb.Conn torCmd *tor.Cmd torControlPath string rpcS *rpc.Server rpcAddr string ts *tunnel.Server } // ServerConfig configures Server type ServerConfig struct { ControlHost, ControlKeyFile string RethinkDBHost, RethinkDBDatabase string TorDataDir, TorHashedPassword, TorPassword string } func create(cfg *ServerConfig) (*Server, error) { session, err := r.Connect(r.ConnectOpts{ Address: cfg.RethinkDBHost, Database: cfg.RethinkDBDatabase, }) if err != nil { return nil, err } l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, err } torControlPath := filepath.Join(cfg.TorDataDir, fmt.Sprintf("%d.sock", rand.Int63())) tc := tor.NewConfig() tc.Set("DataDirectory", cfg.TorDataDir) tc.Set("HashedControlPassword", cfg.TorHashedPassword) tc.Set("SocksPort", "0") cp := rand.Intn(64512) // 64k - 1k tc.Set("ControlPort", cp) tc.Timeout = 30 * time.Second log.Println(tc.ToCmdLineFormat()) tcmd, err := tor.NewCmd(tc) if err != nil { return nil, err } err = tcmd.Start() if err != nil { return nil, err } time.Sleep(5 * time.Second) bc, err := bulb.Dial("tcp", "127.0.0.1:"+strconv.Itoa(cp)) if err != nil { return nil, err } err = bc.Authenticate(cfg.TorPassword) if err != nil { return nil, err } fin, err := os.Open(cfg.ControlKeyFile) if err != nil { return nil, err } defer fin.Close() data, err := ioutil.ReadAll(fin) if err != nil { return nil, err } var block *pem.Block block, _ = pem.Decode([]byte(data)) pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } _, err = bc.AddOnion([]bulb.OnionPortSpec{ bulb.OnionPortSpec{ VirtPort: RPCPort, Target: l.Addr().String(), }, }, pKey, false) if err != nil { return nil, err } rpcs := rpc.NewServer() ts, err := tunnel.NewServer(&tunnel.ServerConfig{}) if err != nil { return nil, err } s := &Server{ cfg: cfg, sess: session, torCon: bc, torCmd: tcmd, torControlPath: torControlPath, rpcS: rpcs, rpcAddr: l.Addr().String(), ts: ts, } rpcs.RegisterName("Urls", &RPCServer{Server: s}) go rpcs.Accept(l) return s, nil } func (s *Server) onionPath(name string) string { return filepath.Join(s.cfg.TorDataDir, name) } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.RequestURI == rpc.DefaultRPCPath { s.rpcS.ServeHTTP(w, r) return } s.ts.ServeHTTP(w, r) } type RPCServer struct { *Server } func (rs *RPCServer) AddHost(req routerpc.AddHostRequest, resp *routerpc.AddHostResponse) error { if req.APIKey != "hunter2" { return errors.New("invalid api key") } token := uuid.New() oi, err := rs.torCon.AddOnion([]bulb.OnionPortSpec{ bulb.OnionPortSpec{ VirtPort: 80, Target: "127.0.0.1:" + *webPort, }, }, req.PrivKey, false) if err != nil { return err } resp.OnionHostname = oi.OnionID + ".onion" resp.Token = token if req.Hostname != "" { rs.Server.ts.AddHost(req.Hostname, token) resp.Hostname = req.Hostname } rs.Server.ts.AddHost(resp.OnionHostname, token) if oi.PrivateKey != nil { resp.PrivKey = oi.PrivateKey } else { resp.PrivKey = req.PrivKey } return nil }