1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package ctl2021import (22 "fmt"23 "os"24 "strings"2526 "github.com/foxcpp/maddy/internal/auth/pass_table"27 maddycli "github.com/foxcpp/maddy/internal/cli"28 clitools2 "github.com/foxcpp/maddy/internal/cli/clitools"29 "github.com/urfave/cli/v2"30 "golang.org/x/crypto/bcrypt"31)3233func init() {34 maddycli.AddSubcommand(35 &cli.Command{36 Name: "hash",37 Usage: "Generate password hashes for use with pass_table",38 Action: hashCommand,39 Flags: []cli.Flag{40 &cli.StringFlag{41 Name: "password",42 Aliases: []string{"p"},43 Usage: "Use `PASSWORD instead of reading password from stdin\n\t\tWARNING: Provided only for debugging convenience. Don't leave your passwords in shell history!",44 },45 &cli.StringFlag{46 Name: "hash",47 Usage: "Use specified hash algorithm",48 Value: "bcrypt",49 },50 &cli.IntFlag{51 Name: "bcrypt-cost",52 Usage: "Specify bcrypt cost value",53 Value: bcrypt.DefaultCost,54 },55 &cli.IntFlag{56 Name: "argon2-time",57 Usage: "Time factor for Argon2id",58 Value: 3,59 },60 &cli.IntFlag{61 Name: "argon2-memory",62 Usage: "Memory in KiB to use for Argon2id",63 Value: 1024,64 },65 &cli.IntFlag{66 Name: "argon2-threads",67 Usage: "Threads to use for Argon2id",68 Value: 1,69 },70 },71 })72}7374func hashCommand(ctx *cli.Context) error {75 hashFunc := ctx.String("hash")76 if hashFunc == "" {77 hashFunc = pass_table.DefaultHash78 }7980 hashCompute := pass_table.HashCompute[hashFunc]81 if hashCompute == nil {82 var funcs []string83 for k := range pass_table.HashCompute {84 funcs = append(funcs, k)85 }8687 return cli.Exit(fmt.Sprintf("Error: Unknown hash function, available: %s", strings.Join(funcs, ", ")), 2)88 }8990 opts := pass_table.HashOpts{91 BcryptCost: bcrypt.DefaultCost,92 Argon2Memory: 1024,93 Argon2Time: 2,94 Argon2Threads: 1,95 }96 if ctx.IsSet("bcrypt-cost") {97 if ctx.Int("bcrypt-cost") > bcrypt.MaxCost {98 return cli.Exit("Error: too big bcrypt cost", 2)99 }100 if ctx.Int("bcrypt-cost") < bcrypt.MinCost {101 return cli.Exit("Error: too small bcrypt cost", 2)102 }103 opts.BcryptCost = ctx.Int("bcrypt-cost")104 }105 if ctx.IsSet("argon2-memory") {106 opts.Argon2Memory = uint32(ctx.Int("argon2-memory"))107 }108 if ctx.IsSet("argon2-time") {109 opts.Argon2Time = uint32(ctx.Int("argon2-time"))110 }111 if ctx.IsSet("argon2-threads") {112 opts.Argon2Threads = uint8(ctx.Int("argon2-threads"))113 }114115 var pass string116 if ctx.IsSet("password") {117 pass = ctx.String("password")118 } else {119 var err error120 pass, err = clitools2.ReadPassword("Password")121 if err != nil {122 return err123 }124 }125126 if pass == "" {127 fmt.Fprintln(os.Stderr, "WARNING: This is the hash of an empty string")128 }129 if strings.TrimSpace(pass) != pass {130 fmt.Fprintln(os.Stderr, "WARNING: There is leading/trailing whitespace in the string")131 }132133 hash, err := hashCompute(opts, pass)134 if err != nil {135 return err136 }137 fmt.Println(hashFunc + ":" + hash)138 return nil139}