diff --git a/main.go b/main.go index 49367bd..96ade66 100644 --- a/main.go +++ b/main.go @@ -1,241 +1,241 @@ /* Copyright 2016-2018 Harald Sitter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "bufio" "bytes" "fmt" "io" "io/ioutil" "log" "net" "os" "path" "path/filepath" "strings" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" "github.com/gin-gonic/gin" "github.com/pkg/sftp" "net/http" _ "net/http/pprof" ) func isHidden(fName string) bool { return strings.HasPrefix(fName, ".") } func getFile(c *gin.Context, sftp *sftp.Client, path string) { fmt.Println("file") file, err := sftp.Open(path) defer file.Close() if err != nil { panic(err) } buffer := bufio.NewReader(file) // For unknown reasons reading from sftp files never EOFs, so // we need to manually keep track of how much we can and have read and abort // once all bytes are read. stat, err := file.Stat() if err != nil { panic(err) } toRead := stat.Size() c.Stream(func(w io.Writer) bool { wrote, err := buffer.WriteTo(w) toRead -= wrote if err != nil || toRead <= 0 { return false } return true }) } func getDir(c *gin.Context, sftp *sftp.Client, path string) { fmt.Println("dir") fileInfos, err := sftp.ReadDir(path) if err != nil { panic(err) } var buffer bytes.Buffer buffer.WriteString("") for _, info := range fileInfos { url := info.Name() if isHidden(url) { continue } buffer.WriteString(fmt.Sprintf("%s
\n", url, url)) } buffer.WriteString("") c.Data(http.StatusOK, "text/html", buffer.Bytes()) } func newSession() (*ssh.Client, *sftp.Client) { // key, err := ioutil.ReadFile(filepath.Join(os.Getenv("HOME"), ".ssh/keys/kde.depot-8192")) key, err := ioutil.ReadFile(filepath.Join(os.Getenv("HOME"), ".ssh/id_rsa")) if err != nil { log.Fatalf("unable to read private key: %v", err) } // Create the Signer for this private key. signer, err := ssh.ParsePrivateKey(key) if err != nil { log.Fatalf("unable to parse private key: %v", err) } hostKeyCallback, err := knownhosts.New(filepath.Join(os.Getenv("HOME"), ".ssh/known_hosts")) if err != nil { log.Fatalf("failed to create known_hosts handler: %v", err) } config := &ssh.ClientConfig{ User: "ftpneon", Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, HostKeyCallback: hostKeyCallback, } - client, err := ssh.Dial("tcp", "milonia.kde.org:22", config) + client, err := ssh.Dial("tcp", "master.kde.org:22", config) if err != nil { log.Fatalf("unable to connect: %v", err) } sftp, err := sftp.NewClient(client) if err != nil { log.Fatal(err) } return client, sftp } func knownIP(c *gin.Context) bool { ip := c.ClientIP() knownAddresses := GetAddresses() if len(knownAddresses) <= 0 { // Allow by default to not impair production services should the IP // listing break at some point. return true } res := false for _, addr := range knownAddresses { if !net.ParseIP(ip).Equal(net.ParseIP(addr)) { continue } res = true break } if !res { // Log the checked IP and all server IPs to allow investigating after the // fact if this should go wrong at some point. fmt.Printf("Stranger danger: %s not in %s\n", ip, knownAddresses) } return res } func allowed(c *gin.Context) bool { path := path.Clean(c.Param("path")) fmt.Println(path) return !strings.HasPrefix(path, "/.") && (strings.HasPrefix(path, "/stable") || strings.HasPrefix(path, "/unstable")) } func get(c *gin.Context) { if path.Clean(c.Param("path")) == "/robots.txt" { c.String(http.StatusOK, "User-agent: *\nDisallow: /") return } if !knownIP(c) { c.String(http.StatusForbidden, "STRANGER DANGER! Only trusted other servers are allowed!") return } if !allowed(c) { c.String(http.StatusForbidden, "not an allowed path") return } path := "/home/ftpneon/" + c.Param("path") ssh, sftp := newSession() defer ssh.Close() defer sftp.Close() fileInfo, err := sftp.Stat(path) if err != nil { c.String(http.StatusNotFound, err.Error()) } if fileInfo.IsDir() { getDir(c, sftp, path) } else { getFile(c, sftp, path) } } func main() { addressesPollTick() router := gin.Default() router.GET("*path", get) port := os.Getenv("PORT") if len(port) <= 0 { port = "8080" } iface := os.Getenv("INTERFACE") if len(iface) > 0 { go func() { router.Run(iface + ":" + port) }() } if iface != "0.0.0.0" { // Only when not already listening on everything. Otherwise we'd have // a bind-race, so either we claim 0.0.0.0 and listen everywhere or only // on the selected interfaces below making it impossible to claim 0.0.0.0. go func() { // Docker interface router.Run("172.17.0.1:" + port) }() go func() { // Debian localhost compat // https://www.debian.org/doc/manuals/debian-reference/ch05.en.html#_the_hostname_resolution // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=719621 router.Run("127.0.1.1:" + port) }() go func() { // Localhost router.Run("127.0.0.1:" + port) }() } select {} // sleep thread forever }