maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package ctl
 20
 21import (
 22	"fmt"
 23	"os"
 24	"strings"
 25
 26	"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)
 32
 33func 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}
 73
 74func hashCommand(ctx *cli.Context) error {
 75	hashFunc := ctx.String("hash")
 76	if hashFunc == "" {
 77		hashFunc = pass_table.DefaultHash
 78	}
 79
 80	hashCompute := pass_table.HashCompute[hashFunc]
 81	if hashCompute == nil {
 82		var funcs []string
 83		for k := range pass_table.HashCompute {
 84			funcs = append(funcs, k)
 85		}
 86
 87		return cli.Exit(fmt.Sprintf("Error: Unknown hash function, available: %s", strings.Join(funcs, ", ")), 2)
 88	}
 89
 90	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	}
114
115	var pass string
116	if ctx.IsSet("password") {
117		pass = ctx.String("password")
118	} else {
119		var err error
120		pass, err = clitools2.ReadPassword("Password")
121		if err != nil {
122			return err
123		}
124	}
125
126	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	}
132
133	hash, err := hashCompute(opts, pass)
134	if err != nil {
135		return err
136	}
137	fmt.Println(hashFunc + ":" + hash)
138	return nil
139}