pull/12/head
parent
fd31094cb6
commit
68398ef0be
4 changed files with 242 additions and 199 deletions
@ -0,0 +1,63 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"crypto/sha256" |
||||
"crypto/x509" |
||||
"encoding/hex" |
||||
"net" |
||||
"net/url" |
||||
"regexp" |
||||
"time" |
||||
) |
||||
|
||||
func enforceCertificateValidity(clientCerts []*x509.Certificate, conn net.Conn, log *LogEntry) { |
||||
// This will fail if any of multiple certs are invalid
|
||||
// Maybe we should just require one valid?
|
||||
now := time.Now() |
||||
for _, cert := range clientCerts { |
||||
if now.Before(cert.NotBefore) { |
||||
conn.Write([]byte("64 Client certificate not yet valid!\r\n")) |
||||
log.Status = 64 |
||||
return |
||||
} else if now.After(cert.NotAfter) { |
||||
conn.Write([]byte("65 Client certificate has expired!\r\n")) |
||||
log.Status = 65 |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
func handleCertificateZones(URL *url.URL, clientCerts []*x509.Certificate, config Config, conn net.Conn, log *LogEntry) { |
||||
authorised := true |
||||
for zone, allowedFingerprints := range config.CertificateZones { |
||||
matched, err := regexp.Match(zone, []byte(URL.Path)) |
||||
if !matched || err != nil { |
||||
continue |
||||
} |
||||
authorised = false |
||||
for _, clientCert := range clientCerts { |
||||
for _, allowedFingerprint := range allowedFingerprints { |
||||
if getCertFingerprint(clientCert) == allowedFingerprint { |
||||
authorised = true |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
if !authorised { |
||||
if len(clientCerts) > 0 { |
||||
conn.Write([]byte("61 Provided certificate not authorised for this resource\r\n")) |
||||
log.Status = 61 |
||||
} else { |
||||
conn.Write([]byte("60 A pre-authorised certificate is required to access this resource\r\n")) |
||||
log.Status = 60 |
||||
} |
||||
return |
||||
} |
||||
} |
||||
|
||||
func getCertFingerprint(cert *x509.Certificate) string { |
||||
hash := sha256.Sum256(cert.Raw) |
||||
fingerprint := hex.EncodeToString(hash[:]) |
||||
return fingerprint |
||||
} |
@ -0,0 +1,115 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"log" |
||||
"net/url" |
||||
"os" |
||||
"path/filepath" |
||||
"sort" |
||||
"strings" |
||||
) |
||||
|
||||
func generateDirectoryListing(URL *url.URL, path string, config Config) string { |
||||
var listing string |
||||
files, err := ioutil.ReadDir(path) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
listing = "# Directory listing\n\n" |
||||
// Override with .mollyhead file
|
||||
header_path := filepath.Join(path, ".mollyhead") |
||||
_, err = os.Stat(header_path) |
||||
if err == nil { |
||||
header, err := ioutil.ReadFile(header_path) |
||||
if err == nil { |
||||
listing = string(header) |
||||
} |
||||
} |
||||
// Do "up" link first
|
||||
if URL.Path != "/" { |
||||
if strings.HasSuffix(URL.Path, "/") { |
||||
URL.Path = URL.Path[:len(URL.Path)-1] |
||||
} |
||||
up := filepath.Dir(URL.Path) |
||||
listing += fmt.Sprintf("=> %s %s\n", up, "..") |
||||
} |
||||
// Sort files
|
||||
sort.SliceStable(files, func(i, j int) bool { |
||||
if config.DirectoryReverse { |
||||
i, j = j, i |
||||
} |
||||
if config.DirectorySort == "Name" { |
||||
return files[i].Name() < files[j].Name() |
||||
} else if config.DirectorySort == "Size" { |
||||
return files[i].Size() < files[j].Size() |
||||
} else if config.DirectorySort == "Time" { |
||||
return files[i].ModTime().Before(files[j].ModTime()) |
||||
} |
||||
return false // Should not happen
|
||||
}) |
||||
// Format lines
|
||||
for _, file := range files { |
||||
// Skip dotfiles
|
||||
if strings.HasPrefix(file.Name(), ".") { |
||||
continue |
||||
} |
||||
// Only list world readable files
|
||||
if uint64(file.Mode().Perm())&0444 != 0444 { |
||||
continue |
||||
} |
||||
listing += fmt.Sprintf("=> %s %s\n", url.PathEscape(file.Name()), generatePrettyFileLabel(file, path, config)) |
||||
} |
||||
return listing |
||||
} |
||||
|
||||
func generatePrettyFileLabel(info os.FileInfo, path string, config Config) string { |
||||
var size string |
||||
if info.IsDir() { |
||||
size = " " |
||||
} else if info.Size() < 1024 { |
||||
size = fmt.Sprintf("%4d B", info.Size()) |
||||
} else if info.Size() < (1024 << 10) { |
||||
size = fmt.Sprintf("%4d KiB", info.Size()>>10) |
||||
} else if info.Size() < 1024<<20 { |
||||
size = fmt.Sprintf("%4d MiB", info.Size()>>20) |
||||
} else if info.Size() < 1024<<30 { |
||||
size = fmt.Sprintf("%4d GiB", info.Size()>>30) |
||||
} else if info.Size() < 1024<<40 { |
||||
size = fmt.Sprintf("%4d TiB", info.Size()>>40) |
||||
} else { |
||||
size = "GIGANTIC" |
||||
} |
||||
|
||||
name := info.Name() |
||||
if config.DirectoryTitles && filepath.Ext(name) == "."+config.GeminiExt { |
||||
name = readHeading(path, info) |
||||
} |
||||
if len(name) > 40 { |
||||
name = info.Name()[:36] + "..." |
||||
} |
||||
if info.IsDir() { |
||||
name += "/" |
||||
} |
||||
return fmt.Sprintf("%-40s %s %v", name, size, info.ModTime().Format("Jan _2 2006")) |
||||
} |
||||
|
||||
func readHeading(path string, info os.FileInfo) string { |
||||
filePath := filepath.Join(path, info.Name()) |
||||
file, err := os.Open(filePath) |
||||
if err != nil { |
||||
return info.Name() |
||||
} |
||||
defer file.Close() |
||||
|
||||
scanner := bufio.NewScanner(file) |
||||
for scanner.Scan() { |
||||
line := scanner.Text() |
||||
if strings.HasPrefix(line, "# ") { |
||||
return strings.TrimSpace(line[1:]) |
||||
} |
||||
} |
||||
return info.Name() |
||||
} |
Loading…
Reference in new issue