package server import ( "crypto/x509" "encoding/pem" "errors" "fmt" "io/ioutil" "log" "math/rand" "net" "net/http" "net/rpc" "os" "path/filepath" "strconv" "time" "git.xeserv.us/xena/route/database" "git.xeserv.us/xena/route/lib/elfs" "git.xeserv.us/xena/route/routerpc" "github.com/Xe/uuid" "github.com/Yawning/bulb" "github.com/koding/tunnel" "github.com/sycamoreone/orc/tor" ) // RPC constants const ( RPCPort uint16 = 39453 ) // Server is the main server type type Server struct { cfg *ServerConfig db *database.DB 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 WebPort, DomainSuffix string } // New creates a new Server func New(cfg *ServerConfig) (*Server, error) { db, err := database.New(cfg.RethinkDBHost, 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, db: db, torCon: bc, torCmd: tcmd, torControlPath: torControlPath, rpcS: rpcs, rpcAddr: l.Addr().String(), ts: ts, } rpcs.RegisterName("Urls", &RPCServer{Server: s}) go rpcs.Accept(l) err = s.restore() if err != nil { return nil, err } 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) } func (s *Server) restore() error { rts, err := s.db.GetAllRoutes() if err != nil { return err } for _, rt := range rts { var block *pem.Block block, _ = pem.Decode([]byte(rt.OnionKey)) pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return err } ports := []bulb.OnionPortSpec{ genPortSpec(80, "127.0.0.1:"+s.cfg.WebPort), } _, err = s.torCon.AddOnion(ports, pKey, false) if err != nil { return err } s.ts.AddHost(rt.Hostname, rt.Token) s.ts.AddHost(rt.OnionHostname, rt.Token) log.Printf("added: %s (%s)", rt.Hostname, rt.OnionHostname) } return nil } func genPortSpec(incoming uint16, outgoing string) bulb.OnionPortSpec { return bulb.OnionPortSpec{ VirtPort: incoming, Target: outgoing, } } // RPCServer is a Server wrapped to work inside the context of net/rpc 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:" + rs.cfg.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 } else { resp.Hostname = elfs.MakeName() + rs.cfg.DomainSuffix rs.ts.AddHost(resp.Hostname, token) } rs.Server.ts.AddHost(resp.OnionHostname, token) if oi.PrivateKey != nil { resp.PrivKey = oi.PrivateKey } else { resp.PrivKey = req.PrivKey } err = rs.db.SaveRoute(resp) if err != nil { return err } return nil }