// 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 . package main import ( "bufio" "context" "errors" "log" "net/http" "os" "strings" "sync" ) var ( Passwords map[string]*User = make(map[string]*User) PasswordsM sync.RWMutex ) type CtxUserKeyType struct{} var CtxUserKey CtxUserKeyType type Auther interface { Auth(password string) bool } type User struct { name string ro bool auther Auther } func strToAuther(verifier string) (string, Auther, error) { st := strings.SplitN(verifier, "$", 3) if len(st) != 3 || st[0] != "" { return "", nil, errors.New("invalid verifier structure") } algorithm := st[1] var auther Auther var err error switch algorithm { case "argon2i": auther, err = parseArgon2i(st[2]) case "sha256": auther, err = parseSHA256(st[2]) default: err = errors.New("unknown hashing algorithm") } return algorithm, auther, err } func passwdReader(fd *os.File) bool { isGood := true scanner := bufio.NewScanner(fd) for scanner.Scan() { t := scanner.Text() if len(t) == 0 { continue } splitted := strings.Split(t, ":") if len(splitted) < 2 { log.Println("wrong login:password[:ro] format:", t) isGood = false continue } login := splitted[0] passwd := splitted[1] if passwd == "" { log.Println("deleting login:", login) PasswordsM.Lock() delete(Passwords, login) PasswordsM.Unlock() continue } _, auther, err := strToAuther(passwd) if err != nil { log.Println("login:", login, "invalid password:", err) isGood = false continue } var ro bool if len(splitted) > 2 { switch splitted[2] { case "ro": ro = true default: log.Println("wrong format of optional field:", t) isGood = false continue } } log.Println("adding password for:", login) PasswordsM.Lock() Passwords[login] = &User{name: login, ro: ro, auther: auther} PasswordsM.Unlock() } return isGood } func passwdLister(fd *os.File) { PasswordsM.RLock() logins := make([]string, 0, len(Passwords)) for login := range Passwords { logins = append(logins, login) } PasswordsM.RUnlock() for _, login := range logins { fd.WriteString(login + "\n") } } func checkAuth(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { username, password, gotAuth := r.BasicAuth() var user *User if gotAuth { PasswordsM.RLock() user = Passwords[username] PasswordsM.RUnlock() } var passwordValid bool if gotAuth && user != nil { passwordValid = user.auther.Auth(password) } if (gotAuth && user == nil) || (user != nil && !passwordValid) || (*AuthRequired && !gotAuth) { log.Println(r.RemoteAddr, "unauthenticated", username) http.Error(w, "unauthenticated", http.StatusUnauthorized) return } handler(w, r.WithContext(context.WithValue(r.Context(), CtxUserKey, user))) } }