X-Git-Url: http://www.git.cypherpunks.ru/?p=gocheese.git;a=blobdiff_plain;f=main.go;h=08c75535c1491feb8ac230f754cb37b63086e4c2;hp=76f4b8a9908936e14114e45fcf92a2d611eda1f3;hb=HEAD;hpb=60834a0713d5dcc6a9911511cb8618ce7358c824 diff --git a/main.go b/main.go index 76f4b8a..cdab06e 100644 --- a/main.go +++ b/main.go @@ -1,20 +1,18 @@ -/* -GoCheese -- Python private package repository and caching proxy -Copyright (C) 2019-2021 Sergey Matveev - 2019-2021 Elena Balakhonova - -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, 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 . -*/ +// GoCheese -- Python private package repository and caching proxy +// Copyright (C) 2019-2024 Sergey Matveev +// 2019-2024 Elena Balakhonova +// +// 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, 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 . // Python private package repository and caching proxy package main @@ -44,57 +42,45 @@ import ( ) const ( - Version = "3.0.0" + Version = "4.2.0" UserAgent = "GoCheese/" + Version - - Warranty = `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, 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 .` ) var ( - Root = flag.String("root", "./packages", "Path to packages directory") - Bind = flag.String("bind", "[::]:8080", "Address to bind to") - MaxClients = flag.Int("maxclients", 128, "Maximal amount of simultaneous clients") - DoUCSPI = flag.Bool("ucspi", false, "Work as UCSPI-TCP service") + Root string + Bind = flag.String("bind", DefaultBind, "") + MaxClients = flag.Int("maxclients", DefaultMaxClients, "") + DoUCSPI = flag.Bool("ucspi", false, "") - TLSCert = flag.String("tls-cert", "", "Path to TLS X.509 certificate") - TLSKey = flag.String("tls-key", "", "Path to TLS X.509 private key") + TLSCert = flag.String("tls-cert", "", "") + TLSKey = flag.String("tls-key", "", "") - NoRefreshURLPath = flag.String("norefresh", "/norefresh/", "Non-refreshing URL path") - RefreshURLPath = flag.String("refresh", "/simple/", "Auto-refreshing URL path") - GPGUpdateURLPath = flag.String("gpgupdate", "/gpgupdate/", "GPG forceful refreshing URL path") - JSONURLPath = flag.String("json", "/pypi/", "JSON API URL path") + NoRefreshURLPath = flag.String("norefresh", DefaultNoRefreshURLPath, "") + RefreshURLPath = flag.String("refresh", DefaultRefreshURLPath, "") + JSONURLPath = flag.String("json", DefaultJSONURLPath, "") - PyPIURL = flag.String("pypi", "https://pypi.org/simple/", "Upstream (PyPI) URL") - PyPICertHash = flag.String("pypi-cert-hash", "", "Authenticate upstream by its X.509 certificate's SPKI SHA256 hash") - JSONURL = flag.String("pypi-json", "https://pypi.org/pypi/", "Enable and use specified JSON API upstream URL") + PyPIURL = flag.String("pypi", DefaultPyPIURL, "") + JSONURL = flag.String("pypi-json", DefaultJSONURL, "") + PyPICertHash = flag.String("pypi-cert-hash", "", "") - PasswdPath = flag.String("passwd", "", "Path to FIFO for upload authentication") - PasswdListPath = flag.String("passwd-list", "", "Path to FIFO for login listing") - PasswdCheck = flag.Bool("passwd-check", false, "Run password checker") + PasswdPath = flag.String("passwd", "", "") + PasswdListPath = flag.String("passwd-list", "", "") + PasswdCheck = flag.Bool("passwd-check", false, "") + AuthRequired = flag.Bool("auth-required", false, "") - LogTimestamped = flag.Bool("log-timestamped", false, "Prepend timestmap to log messages") - FSCK = flag.Bool("fsck", false, "Check integrity of all packages (errors are in stderr)") - DoVersion = flag.Bool("version", false, "Print version information") - DoWarranty = flag.Bool("warranty", false, "Print warranty information") + LogTimestamped = flag.Bool("log-timestamped", false, "") + FSCK = flag.Bool("fsck", false, "") + DoVersion = flag.Bool("version", false, "") + DoWarranty = flag.Bool("warranty", false, "") Killed bool ) func servePkg(w http.ResponseWriter, r *http.Request, pkgName, filename string) { log.Println(r.RemoteAddr, "get", filename) - path := filepath.Join(*Root, pkgName, filename) + path := filepath.Join(Root, pkgName, filename) if _, err := os.Stat(path); os.IsNotExist(err) { - if !refreshDir(w, r, pkgName, filename, false) { + if !refreshDir(w, r, pkgName, filename) { return } } @@ -107,16 +93,11 @@ func handler(w http.ResponseWriter, r *http.Request) { case "GET": var path string var autorefresh bool - var gpgUpdate bool if strings.HasPrefix(r.URL.Path, *NoRefreshURLPath) { path = strings.TrimPrefix(r.URL.Path, *NoRefreshURLPath) } else if strings.HasPrefix(r.URL.Path, *RefreshURLPath) { path = strings.TrimPrefix(r.URL.Path, *RefreshURLPath) autorefresh = true - } else if strings.HasPrefix(r.URL.Path, *GPGUpdateURLPath) { - path = strings.TrimPrefix(r.URL.Path, *GPGUpdateURLPath) - autorefresh = true - gpgUpdate = true } else { http.Error(w, "unknown action", http.StatusBadRequest) return @@ -130,7 +111,7 @@ func handler(w http.ResponseWriter, r *http.Request) { if parts[0] == "" { listRoot(w, r) } else { - serveListDir(w, r, parts[0], autorefresh, gpgUpdate) + serveListDir(w, r, parts[0], autorefresh) } } else { servePkg(w, r, parts[0], parts[1]) @@ -143,6 +124,7 @@ func handler(w http.ResponseWriter, r *http.Request) { } func main() { + flag.Usage = usage flag.Parse() if *DoWarranty { fmt.Println(Warranty) @@ -162,6 +144,15 @@ func main() { log.SetOutput(os.Stdout) } + if len(flag.Args()) != 1 { + usage() + os.Exit(1) + } + Root = flag.Args()[0] + if _, err := os.Stat(Root); err != nil { + log.Fatal(err) + } + if *FSCK { if !goodIntegrity() { os.Exit(1) @@ -186,7 +177,7 @@ func main() { os.FileMode(0666), ) if err != nil { - log.Fatalln(err) + log.Fatal(err) } passwdReader(fd) fd.Close() @@ -202,7 +193,7 @@ func main() { os.FileMode(0666), ) if err != nil { - log.Fatalln(err) + log.Fatal(err) } passwdLister(fd) fd.Close() @@ -211,13 +202,16 @@ func main() { } if (*TLSCert != "" && *TLSKey == "") || (*TLSCert == "" && *TLSKey != "") { - log.Fatalln("Both -tls-cert and -tls-key are required") + log.Fatal("Both -tls-cert and -tls-key are required") } + UmaskCur = syscall.Umask(0) + syscall.Umask(UmaskCur) + var err error PyPIURLParsed, err = url.Parse(*PyPIURL) if err != nil { - log.Fatalln(err) + log.Fatal(err) } tlsConfig := tls.Config{ ClientSessionCache: tls.NewLRUClientSessionCache(16), @@ -230,12 +224,12 @@ func main() { if *PyPICertHash != "" { ourDgst, err := hex.DecodeString(*PyPICertHash) if err != nil { - log.Fatalln(err) + log.Fatal(err) } tlsConfig.VerifyConnection = func(s tls.ConnectionState) error { spki := s.VerifiedChains[0][0].RawSubjectPublicKeyInfo theirDgst := sha256.Sum256(spki) - if bytes.Compare(ourDgst, theirDgst[:]) != 0 { + if !bytes.Equal(ourDgst, theirDgst[:]) { return errors.New("certificate's SPKI digest mismatch") } return nil @@ -246,14 +240,11 @@ func main() { ReadTimeout: time.Minute, WriteTimeout: time.Minute, } - http.HandleFunc("/", serveHRRoot) - http.HandleFunc("/hr/", serveHRPkg) - http.HandleFunc(*JSONURLPath, serveJSON) - http.HandleFunc(*NoRefreshURLPath, handler) - http.HandleFunc(*RefreshURLPath, handler) - if *GPGUpdateURLPath != "" { - http.HandleFunc(*GPGUpdateURLPath, handler) - } + http.HandleFunc("/", checkAuth(serveHRRoot)) + http.HandleFunc("/hr/", checkAuth(serveHRPkg)) + http.HandleFunc(*JSONURLPath, checkAuth(serveJSON)) + http.HandleFunc(*NoRefreshURLPath, checkAuth(handler)) + http.HandleFunc(*RefreshURLPath, checkAuth(handler)) if *DoUCSPI { server.SetKeepAlivesEnabled(false) @@ -261,7 +252,7 @@ func main() { server.ConnState = connStater err := server.Serve(ln) if _, ok := err.(UCSPIAlreadyAccepted); !ok { - log.Fatalln(err) + log.Fatal(err) } UCSPIJob.Wait() return @@ -273,8 +264,8 @@ func main() { } ln = netutil.LimitListener(ln, *MaxClients) - needsShutdown := make(chan os.Signal, 0) - exitErr := make(chan error, 0) + needsShutdown := make(chan os.Signal, 1) + exitErr := make(chan error) signal.Notify(needsShutdown, syscall.SIGTERM, syscall.SIGINT) go func(s *http.Server) { <-needsShutdown @@ -287,11 +278,10 @@ func main() { log.Println( UserAgent, "ready:", - "root:", *Root, + "root:", Root, "bind:", *Bind, "pypi:", *PyPIURL, "json:", *JSONURL, - "hr: /", ) if *TLSCert == "" { err = server.Serve(ln)