use actual virtual hosts

This commit is contained in:
Steve 2020-02-22 16:17:38 -05:00
parent 694693e93c
commit 58d983891e
5 changed files with 40 additions and 28 deletions

View File

@ -12,6 +12,7 @@ Configuration is in config/yaml in one of the above directories. See the sample
but a standard file looks like such: but a standard file looks like such:
--- ---
port: 1965
active_capsules: active_capsules:
- localhost - localhost
localhost: localhost:
@ -22,6 +23,9 @@ but a standard file looks like such:
KeyFile: "localhost.key" KeyFile: "localhost.key"
CertFile: "localhost.crt" CertFile: "localhost.crt"
Where each "active_capsule" is a virtual Gemini capsule. SecretShop supports virtual Gemini capsules all listening on port 1965
as well as multiple Gopher servers runnning (though not virtual Gopher hosts due to protocol limitations)
Please note that CGIDir currently not used (waiting on spec clarification). Please note that CGIDir currently not used (waiting on spec clarification).
## Building ## Building

View File

@ -1,4 +1,5 @@
--- ---
port: 1965
active_capsules: active_capsules:
- localhost - localhost
- localhost2 - localhost2
@ -6,14 +7,12 @@ active_holes:
- localhost3 - localhost3
localhost: localhost:
Hostname: "localhost" Hostname: "localhost"
Port: "1965"
RootDir: "/var/gemini" RootDir: "/var/gemini"
CGIDir: "/var/gemini/cgi" CGIDir: "/var/gemini/cgi"
KeyFile: "localhost.key" KeyFile: "localhost.key"
CertFile: "localhost.crt" CertFile: "localhost.crt"
localhost2: localhost2:
Hostname: "gemini.foo.bar" Hostname: "gemini.foo.bar"
Port: "1966"
RootDir: "/var/gemini2" RootDir: "/var/gemini2"
CGIDir: "/var/gemini2/cgi" CGIDir: "/var/gemini2/cgi"
KeyFile: "localhost2.key" KeyFile: "localhost2.key"

18
main.go
View File

@ -21,6 +21,7 @@ func main() {
//Load configs //Load configs
active_capsules := viper.GetStringSlice("active_capsules") active_capsules := viper.GetStringSlice("active_capsules")
active_holes := viper.GetStringSlice("active_holes") active_holes := viper.GetStringSlice("active_holes")
port := viper.GetString("port")
capsule_list := make([]GeminiConfig, len(active_capsules)) capsule_list := make([]GeminiConfig, len(active_capsules))
hole_list := make([]GopherConfig, len(active_holes)) hole_list := make([]GopherConfig, len(active_holes))
for i, c := range active_capsules { for i, c := range active_capsules {
@ -38,14 +39,14 @@ func main() {
log.Printf("%v capsules loaded, %v gopherholes loaded", len(capsule_list), len(hole_list)) log.Printf("%v capsules loaded, %v gopherholes loaded", len(capsule_list), len(hole_list))
// Intialize servers // Intialize servers
wg := new(sync.WaitGroup) wg := new(sync.WaitGroup)
wg.Add(len(capsule_list) + len(hole_list)) wg.Add(1 + len(hole_list))
for i, c := range capsule_list {
log.Printf("Starting capsule %v %v", i, c.Hostname) log.Printf("Starting gemini capsule")
go func(c interface{}) { go func(c interface{}) {
log.Fatal(ListenAndServeTLS(c.(GeminiConfig))) log.Fatal(ListenAndServeTLS(port, c.([]GeminiConfig)))
wg.Done() wg.Done()
}(c) }(capsule_list)
}
for i, h := range hole_list { for i, h := range hole_list {
log.Printf("Starting gopherhole %v %v", i, h.Hostname) log.Printf("Starting gopherhole %v %v", i, h.Hostname)
go func(h interface{}) { go func(h interface{}) {
@ -56,6 +57,7 @@ func main() {
wg.Done() wg.Done()
}(h) }(h)
} }
log.Println("Done bringing up capsules and gopherholes") log.Println("Done bringing up capsules and gopherholes")
log.Println("Ho ho! You found me!") log.Println("Ho ho! You found me!")
wg.Wait() wg.Wait()

View File

@ -40,7 +40,6 @@ type Response struct {
type GeminiConfig struct { type GeminiConfig struct {
Hostname string Hostname string
Port string
KeyFile string KeyFile string
CertFile string CertFile string
RootDir string RootDir string
@ -48,5 +47,5 @@ type GeminiConfig struct {
} }
func (c *GeminiConfig) String() string { func (c *GeminiConfig) String() string {
return fmt.Sprintf("Gemini Config: %v:%v Files:%v CGI:%v", c.Hostname, c.Port, c.RootDir, c.CGIDir) return fmt.Sprintf("Gemini Config: %v Files:%v CGI:%v", c.Hostname, c.RootDir, c.CGIDir)
} }

View File

@ -41,8 +41,7 @@ var (
type Server struct { type Server struct {
Addr string // TCP address to listen on, ":gemini" if empty Addr string // TCP address to listen on, ":gemini" if empty
Hostname string // FQDN Hostname to reach this server on HostnameToRoot map[string]string //FQDN hostname to root folder
ServerRoot string //Root folder for gemini files
} }
type conn struct { type conn struct {
@ -59,19 +58,32 @@ func (s *Server) newConn(rwc net.Conn) *conn {
} }
return c return c
} }
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
func ListenAndServeTLS(port string, cps []GeminiConfig) error {
server := &Server{Addr: ":" + port, HostnameToRoot: make(map[string]string)}
for _, c := range cps {
server.HostnameToRoot[c.Hostname] = c.RootDir
}
return server.ListenAndServeTLS(cps)
}
func (s *Server) ListenAndServeTLS(configs []GeminiConfig) error {
addr := s.Addr addr := s.Addr
mime.AddExtensionType(".gmi", "text/gemini") mime.AddExtensionType(".gmi", "text/gemini")
mime.AddExtensionType(".gemini", "text/gemini") mime.AddExtensionType(".gemini", "text/gemini")
if addr == "" { if addr == "" {
addr = ":1965" addr = ":1965"
} }
certs := make([]tls.Certificate, len(configs))
cert, err := tls.LoadX509KeyPair(certFile, keyFile) for i, c := range configs {
if err != nil { cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
log.Fatalf("server: loadkeys: %s", err) if err != nil {
log.Fatalf("Error loading certs: %s", err)
}
certs[i] = cert
} }
config := tls.Config{Certificates: []tls.Certificate{cert}}
config := tls.Config{Certificates: certs}
config.Rand = rand.Reader config.Rand = rand.Reader
ln, err := tls.Listen("tcp", addr, &config) ln, err := tls.Listen("tcp", addr, &config)
@ -81,10 +93,6 @@ func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
return s.Serve(ln) return s.Serve(ln)
} }
func ListenAndServeTLS(cp GeminiConfig) error {
server := &Server{Addr: ":" + cp.Port, ServerRoot: cp.RootDir, Hostname: cp.Hostname}
return server.ListenAndServeTLS(cp.CertFile, cp.KeyFile)
}
func (s *Server) Serve(l net.Listener) error { func (s *Server) Serve(l net.Listener) error {
defer l.Close() defer l.Close()
@ -135,7 +143,7 @@ func (c *conn) serve(ctx context.Context) {
res = c.server.ParseRequest(req) res = c.server.ParseRequest(req)
} }
c.sendResponse(res) c.sendResponse(res)
log.Printf("%v: %v requested %v; responded with %v %v", c.server.Hostname, c.C.RemoteAddr(), req, res.Status, res.Meta) log.Printf("%v requested %v; responded with %v %v", c.C.RemoteAddr(), req, res.Status, res.Meta)
c.C.Close() c.C.Close()
} }
@ -154,14 +162,14 @@ func (s *Server) ParseRequest(req string) Response {
} }
if u.Host == "" { if u.Host == "" {
return Response{STATUS_BAD_REQUEST, "Need to specify a host", ""} return Response{STATUS_BAD_REQUEST, "Need to specify a host", ""}
} else if u.Hostname() != s.Hostname { } else if s.HostnameToRoot[u.Hostname()] == "" {
return Response{STATUS_PROXY_REQUEST_REFUSED, "Proxying by Hostname not currently supported", ""} return Response{STATUS_PROXY_REQUEST_REFUSED, "Proxying by Hostname not currently supported", ""}
} }
if strings.Contains(u.Path, "..") { if strings.Contains(u.Path, "..") {
return Response{STATUS_PERMANENT_FAILURE, "Dots in path, assuming bad faith.", ""} return Response{STATUS_PERMANENT_FAILURE, "Dots in path, assuming bad faith.", ""}
} }
selector := s.ServerRoot + u.Path selector := s.HostnameToRoot[u.Hostname()] + u.Path
fi, err := os.Stat(selector) fi, err := os.Stat(selector)
switch { switch {
case err != nil: case err != nil:
@ -175,7 +183,7 @@ func (s *Server) ParseRequest(req string) Response {
if strings.HasSuffix(u.Path, "/") { if strings.HasSuffix(u.Path, "/") {
return generateDirectory(selector) return generateDirectory(selector)
} else { } else {
return Response{STATUS_REDIRECT_PERMANENT, "gemini://" + s.Hostname + u.Path + "/", ""} return Response{STATUS_REDIRECT_PERMANENT, "gemini://" + u.Hostname() + u.Path + "/", ""}
} }
default: default:
// it's a file // it's a file