route/server/server.go

279 lines
5.3 KiB
Go
Raw Normal View History

package server
import (
2017-01-20 00:31:22 +00:00
"crypto/rsa"
2017-03-26 19:51:37 +00:00
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"log"
"net"
"net/http"
2017-03-26 19:51:37 +00:00
"net/http/httputil"
"net/rpc"
"os"
"path/filepath"
2017-01-23 16:03:14 +00:00
"strings"
"time"
"git.xeserv.us/xena/route/database"
"git.xeserv.us/xena/route/lib/elfs"
2017-03-26 19:51:37 +00:00
"git.xeserv.us/xena/route/lib/tun2"
"git.xeserv.us/xena/route/routerpc"
2017-01-20 00:31:22 +00:00
"git.xeserv.us/xena/route/utils"
"github.com/Xe/uuid"
"github.com/Yawning/bulb"
2017-01-26 04:22:27 +00:00
"github.com/brandur/simplebox"
2017-03-26 19:51:37 +00:00
"github.com/mtneug/pkg/ulid"
"golang.org/x/crypto/acme/autocert"
)
// RPC constants
const (
RPCPort uint16 = 39453
)
// Server is the main server type
type Server struct {
2017-01-20 00:31:22 +00:00
cfg *Config
2017-01-20 00:31:22 +00:00
db *database.DB
tor *Tor
rpcS *rpc.Server
rpcAddr string
2017-03-26 19:51:37 +00:00
ts *tun2.Server
2017-01-26 03:26:41 +00:00
CertCache *database.CertCache
}
2017-01-20 00:31:22 +00:00
// Config configures Server
type Config struct {
2017-01-28 00:14:05 +00:00
ControlHost, ControlKeyFile string
RethinkDBHost, RethinkDBDatabase string
TorDataDir, TorHashedPassword, TorPassword string
WebPort, DomainSuffix, SSLPort, GRPCClientPort string
2017-03-26 19:51:37 +00:00
BackendPort, KCPPort string
2017-01-28 00:14:05 +00:00
CertKey *[32]byte
}
// New creates a new Server
2017-01-20 00:31:22 +00:00
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
}
2017-01-20 00:31:22 +00:00
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
}
2017-01-20 00:31:22 +00:00
pKey, err := utils.PemToRSAPrivateKey(data)
if err != nil {
return nil, err
}
2017-01-20 00:31:22 +00:00
_, err = t.AddOnion(pKey, RPCPort, l.Addr().String())
if err != nil {
return nil, err
}
rpcs := rpc.NewServer()
s := &Server{
2017-01-20 00:31:22 +00:00
cfg: &cfg,
2017-01-20 00:31:22 +00:00
db: db,
tor: t,
rpcS: rpcs,
rpcAddr: l.Addr().String(),
2017-01-26 03:26:41 +00:00
CertCache: &database.CertCache{
DB: db,
},
}
2017-03-26 19:51:37 +00:00
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
Cache: s.CertCache,
HostPolicy: nil,
Email: "xena@yolo-swag.com",
}
2017-01-26 04:22:27 +00:00
if cfg.CertKey != nil {
s.CertCache.SimpleBox = simplebox.NewFromSecretKey(cfg.CertKey)
}
2017-03-26 19:51:37 +00:00
tcfg := &tun2.ServerConfig{
TCPAddr: cfg.BackendPort,
KCPAddr: cfg.KCPPort,
TLSConfig: &tls.Config{
GetCertificate: m.GetCertificate,
},
Storage: s.db,
}
2017-03-26 19:51:37 +00:00
ts, err := tun2.NewServer(tcfg)
if err != nil {
return nil, err
}
2017-03-26 19:51:37 +00:00
s.ts = ts
2017-03-26 19:51:37 +00:00
rpcs.RegisterName("Urls", &RPCServer{Server: s})
go rpcs.Accept(l)
log.Println("rpc at tcp://" + l.Addr().String())
err = s.restore()
2017-01-28 00:14:05 +00:00
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) {
2017-01-22 17:49:14 +00:00
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)
2017-01-20 01:17:18 +00:00
r.Header.Set("X-Request-Ingress", time.Now().String())
2017-03-26 19:51:37 +00:00
rid := ulid.New().String()
2017-01-23 16:03:14 +00:00
r.Header.Set("X-Request-Id", rid)
w.Header().Set("X-Request-Id", rid)
2017-02-08 02:25:14 +00:00
// http://www.gnuterrypratchett.com/
2017-03-26 19:51:37 +00:00
w.Header().Set("X-Clacks-Overhead", "GNU Ashlynn")
2017-02-08 02:25:14 +00:00
2017-01-23 16:03:14 +00:00
if strings.HasSuffix(r.Host, ".onion") {
w.Header().Add("DNT", "1")
}
if r.RequestURI == rpc.DefaultRPCPath {
s.rpcS.ServeHTTP(w, r)
return
}
2017-03-26 19:51:37 +00:00
rp := &httputil.ReverseProxy{
Transport: s.ts,
FlushInterval: 1 * time.Second,
}
rp.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
}
2017-01-20 00:31:22 +00:00
_, err = s.tor.AddOnion(pKey, 80, "127.0.0.1:"+s.cfg.WebPort)
if err != nil {
return err
}
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
}
2017-01-20 00:31:22 +00:00
// 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()
2017-01-20 00:31:22 +00:00
var pKey *rsa.PrivateKey
if kk, ok := req.PrivKey.(*rsa.PrivateKey); ok {
pKey = kk
2017-01-20 01:17:18 +00:00
} else {
return errors.New("there must be a 1024 bit RSA private key")
2017-01-20 00:31:22 +00:00
}
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
2017-01-20 01:17:18 +00:00
resp.PrivKey = pKey
if req.Hostname != "" {
resp.Hostname = req.Hostname
} else {
resp.Hostname = elfs.MakeName() + rs.cfg.DomainSuffix
}
err = rs.db.SaveRoute(resp)
if err != nil {
return err
}
return nil
}