package server import ( "crypto/rsa" "crypto/x509" "encoding/pem" "errors" "io/ioutil" "log" "net" "net/http" "net/rpc" "os" "path/filepath" "time" "git.xeserv.us/xena/route/database" "git.xeserv.us/xena/route/lib/elfs" "git.xeserv.us/xena/route/lib/tunnel" "git.xeserv.us/xena/route/routerpc" "git.xeserv.us/xena/route/utils" "github.com/Xe/uuid" "github.com/Yawning/bulb" ) // RPC constants const ( RPCPort uint16 = 39453 ) // Server is the main server type type Server struct { cfg *Config db *database.DB tor *Tor rpcS *rpc.Server rpcAddr string ts *tunnel.Server } // Config configures Server type Config struct { ControlHost, ControlKeyFile string RethinkDBHost, RethinkDBDatabase string TorDataDir, TorHashedPassword, TorPassword string WebPort, DomainSuffix string } // New creates a new Server func New(cfg Config) (*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 } t, err := StartTor(TorConfig{ DataDir: cfg.TorDataDir, HashedControlPassword: cfg.TorHashedPassword, ClearPassword: cfg.TorPassword, Timeout: 30 * time.Second, }) 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 } pKey, err := utils.PemToRSAPrivateKey(data) if err != nil { return nil, err } _, err = t.AddOnion(pKey, RPCPort, l.Addr().String()) 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, tor: t, 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) { r.Header.Del("X-Forwarded-For") if r.Header.Get("X-Tor2web") != "" { http.Error(w, "tor2web proxy use is not allowed", 400) return } host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { http.Error(w, err.Error(), 500) return } r.Header.Set("X-Forwarded-For", host) r.Header.Set("X-Remote-IP", host) r.Header.Set("X-Request-Ingress", time.Now().String()) if r.RequestURI == rpc.DefaultRPCPath && r.Host == "127.0.0.1:"+s.cfg.WebPort { 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 } _, err = s.tor.AddOnion(pKey, 80, "127.0.0.1:"+s.cfg.WebPort) 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 } // AddHost adds a host to the 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() var pKey *rsa.PrivateKey if kk, ok := req.PrivKey.(*rsa.PrivateKey); ok { pKey = kk } else { return errors.New("there must be a 1024 bit RSA private key") } oi, err := rs.tor.AddOnion(pKey, 80, "127.0.0.1:"+rs.cfg.WebPort) if err != nil { return err } resp.OnionHostname = oi.OnionID + ".onion" resp.Token = token resp.PrivKey = pKey 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) err = rs.db.SaveRoute(resp) if err != nil { return err } return nil }