249 lines
4.6 KiB
Go
249 lines
4.6 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"errors"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"net/rpc"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"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
|
|
|
|
CertCache *database.CertCache
|
|
}
|
|
|
|
// Config configures Server
|
|
type Config struct {
|
|
ControlHost, ControlKeyFile string
|
|
RethinkDBHost, RethinkDBDatabase string
|
|
TorDataDir, TorHashedPassword, TorPassword string
|
|
WebPort, DomainSuffix, SSLPort 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,
|
|
|
|
CertCache: &database.CertCache{
|
|
DB: db,
|
|
},
|
|
}
|
|
|
|
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())
|
|
|
|
rid := uuid.New()
|
|
r.Header.Set("X-Request-Id", rid)
|
|
w.Header().Set("X-Request-Id", rid)
|
|
|
|
if strings.HasSuffix(r.Host, ".onion") {
|
|
w.Header().Add("DNT", "1")
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
_, 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
|
|
}
|