package prosody import ( "crypto/hmac" "crypto/sha1" "encoding/hex" "errors" "fmt" "io" "os/exec" "strconv" "github.com/xdg-go/pbkdf2" ) func (p *Prosody) ChangePassword(user string, currentPwd string, newPwd string) error { acc, err := p.loadAccount(user) if err != nil { return fmt.Errorf("p.loadAccount %w", err) } iterationCount, err := strconv.Atoi(acc.IterationCount) if err != nil { return fmt.Errorf("strconv.Atoi %w", err) } storedKey, err := hashPassword(currentPwd, acc.Salt, iterationCount) if err != nil { return fmt.Errorf("hashPassword: %w", err) } // Compare the hashes if storedKey != acc.StoredKey { return errors.New("password is incorrect") } cmd := exec.Command("/usr/bin/prosodyctl", "passwd", user+"@"+p.plainDomain) // Create a pipe to write to the process's standard input. stdin, err := cmd.StdinPipe() if err != nil { return fmt.Errorf("creating stdin pipe: %w", err) } // Start the process. if err := cmd.Start(); err != nil { return fmt.Errorf("starting command: %w", err) } // Write the password to the process's standard input. _, err = io.WriteString(stdin, newPwd+"\n"+newPwd+"\n") if err != nil { return fmt.Errorf("writing to stdin pipe: %w", err) } // Close the pipe to indicate that we're done writing. if err := stdin.Close(); err != nil { return fmt.Errorf("closing stdin pipe: %w", err) } // Wait for the command to finish. if err := cmd.Wait(); err != nil { return fmt.Errorf("waiting for command: %w", err) } return nil } func hashPassword(password, salt string, iterationCount int) (string, error) { // Hash the password using the SCRAM mechanism saltedPassword := pbkdf2.Key([]byte(password), []byte(salt), iterationCount, 20, sha1.New) clientKey := hmacSha1(saltedPassword, []byte("Client Key")) storedKey := sha1.Sum(clientKey) return hex.EncodeToString(storedKey[:]), nil } func hmacSha1(key, data []byte) []byte { mac := hmac.New(sha1.New, key) mac.Write(data) return mac.Sum(nil) }