package main import ( "context" "crypto/sha256" "crypto/x509" "encoding/hex" "os" "path/filepath" "sort" "strings" sr "tildegit.org/tjp/sliderule" "tildegit.org/tjp/sliderule/contrib/cgi" "tildegit.org/tjp/sliderule/contrib/fs" "tildegit.org/tjp/sliderule/contrib/tlsauth" "tildegit.org/tjp/sliderule/finger" "tildegit.org/tjp/sliderule/gemini" "tildegit.org/tjp/sliderule/gemini/gemtext/atomconv" "tildegit.org/tjp/sliderule/gopher/gophermap" "tildegit.org/tjp/sliderule/logging" "tildegit.org/tjp/syw" ) func geminiRouter(conf config) sr.Handler { fsys := os.DirFS(conf.geminiRoot) privileged := tlsAuth(conf.privilegedUsers) router := &sr.Router{} router.Route( "/*", gemini.GeminiOnly(true)(sr.FallthroughHandler( fs.TitanUpload(privileged, conf.geminiRoot)(postUploadRedirect), fs.GeminiFileHandler(fsys), fs.GeminiDirectoryDefault(fsys, "index.gmi"), fs.GeminiDirectoryListing(fsys, nil), )), ) router.Route( "/cgi-bin/*", gemini.GeminiOnly(false)(cgi.GeminiCGIDirectory( "/cgi-bin/", strings.Join([]string{".", strings.Trim(conf.geminiRoot, "/"), "cgi-bin"}, "/"), )), ) router.Route( "/cgi-bin/private/*", gemini.GeminiOnly(false)(tlsauth.GeminiAuth(privileged)( cgi.GeminiCGIDirectory("/cgi-bin/private/", strings.Join([]string{ ".", strings.Trim(conf.geminiRoot, "/"), "cgi-bin", "private", }, "/")), )), ) if conf.geminiRepos != "" { router.Mount("/git", syw.GeminiRouter(conf.geminiRepos, nil)) } h := router.Handler() if conf.geminiAutoAtom { h = atomconv.Auto(h) } return h } func gopherRouter(conf config) sr.Handler { settings := gophermap.FileSystemSettings{ ParseExtended: true, Exec: true, ListUsers: false, DirMaps: []string{"gophermap"}, DirTag: "gophertag", } router := &sr.Router{} router.Route( "/*", sr.FallthroughHandler( cgi.ExecGopherMaps("/", conf.gopherRoot, &settings), fs.GopherFileHandler(conf.gopherRoot, &settings), fs.GopherDirectoryDefault(conf.gopherRoot, &settings), fs.GopherDirectoryListing(conf.gopherRoot, &settings), ), ) router.Route( "/cgi-bin/*", cgi.GopherCGIDirectory("/cgi-bin/", filepath.Join(conf.gopherRoot, "cgi-bin"), &settings), ) if conf.gopherRepos != "" { router.Mount("/git", syw.GopherRouter(conf.gopherRepos, nil)) } return router.Handler() } var postUploadRedirect = sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { u := *request.URL u.Path = strings.SplitN(u.Path, ";", 2)[0] u.Scheme = "gemini" return gemini.Redirect(u.String()) }) func tlsAuth(uploaders []string) tlsauth.Approver { sort.Strings(uploaders) return func(cert *x509.Certificate) bool { raw := sha256.Sum256(cert.Raw) user := hex.EncodeToString(raw[:]) _, found := sort.Find(len(uploaders), func(i int) int { switch { case uploaders[i] < user: return 1 case uploaders[i] == user: return 0 default: return -1 } }) return found } } func fingerHandler(conf config) sr.Handler { return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { name := strings.TrimPrefix(request.Path, "/") if name == "" { return finger.Error("listings not permitted") } path, ok := conf.fingerResponses[strings.ToLower(name)] if !ok { return finger.Error("user not found") } file, err := os.Open(path) if err != nil { ctx.Value("errorlog").(logging.Logger).Log( "msg", "finger response file open error", "error", err, ) } return finger.Success(file) }) }