sutil holds only the utilities wanted on every machine; it is not a command-line front end for the packages in pkg/. cbk and bip39wordlist remain libraries with no CLI surface. The README states the boundary.
126 lines
3.2 KiB
Go
126 lines
3.2 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"sneak.berlin/go/x/internal/pwgen"
|
|
"sneak.berlin/go/x/internal/randstream"
|
|
)
|
|
|
|
// streamChunkSize is the write size used by `generate random`. Large
|
|
// writes keep the per-syscall overhead negligible.
|
|
const streamChunkSize = 1 << 20
|
|
|
|
// newGenerateCommand returns `sutil generate`, the parent of everything
|
|
// that produces random output.
|
|
func newGenerateCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "generate",
|
|
Aliases: []string{"gen"},
|
|
Short: "Generate random bytes and passwords",
|
|
}
|
|
|
|
cmd.AddCommand(
|
|
newGenerateRandomCommand(),
|
|
newGeneratePasswordCommand(),
|
|
)
|
|
|
|
return cmd
|
|
}
|
|
|
|
// newGenerateRandomCommand returns `sutil generate random [seedstr]`.
|
|
func newGenerateRandomCommand() *cobra.Command {
|
|
var byteCount int64
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "random [seedstr]",
|
|
Short: "Write a fast stream of pseudorandom bytes to stdout",
|
|
Long: `Writes pseudorandom bytes to stdout until stopped, or until --count
|
|
bytes have been written.
|
|
|
|
With seedstr the stream is deterministic: the same seed always produces
|
|
the same bytes. Without it the stream is keyed from the operating
|
|
system's secure random source and is different every run.
|
|
|
|
This exists because /dev/urandom is slow.`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
seed := ""
|
|
if len(args) == 1 {
|
|
seed = args[0]
|
|
}
|
|
|
|
stream, err := randstream.New(randstream.Params{Seed: seed})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return streamBytes(cmd.OutOrStdout(), stream, byteCount)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().Int64VarP(
|
|
&byteCount, "count", "c", 0, "stop after this many bytes (0 = never)",
|
|
)
|
|
|
|
return cmd
|
|
}
|
|
|
|
// newGeneratePasswordCommand returns `sutil generate password`.
|
|
func newGeneratePasswordCommand() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "password",
|
|
Aliases: []string{"pw"},
|
|
Short: "Print a memorable password built from system dictionary words",
|
|
Long: `Prints a password of the form
|
|
|
|
<word><digit><delim><word><digit><delim><word><digit>
|
|
|
|
for example marble7-sprout2-velvet9. The words come from the system word
|
|
list (/usr/share/dict/words), the delimiter is chosen at random from
|
|
|
|
` + pwgen.Delimiters + `
|
|
|
|
and is the same in both positions. The command panics if the system has
|
|
no usable word list.`,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
generator, err := pwgen.New(pwgen.Params{})
|
|
if err != nil {
|
|
panic("sutil: cannot load the system word list: " + err.Error())
|
|
}
|
|
|
|
password, err := generator.Generate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = fmt.Fprintln(cmd.OutOrStdout(), password)
|
|
|
|
return err
|
|
},
|
|
}
|
|
}
|
|
|
|
// streamBytes copies limit bytes (or everything, when limit is zero) from
|
|
// src to dst in streamChunkSize chunks.
|
|
func streamBytes(dst io.Writer, src io.Reader, limit int64) error {
|
|
if limit > 0 {
|
|
src = io.LimitReader(src, limit)
|
|
}
|
|
|
|
// Wrapping dst hides any ReadFrom method it has, so io.CopyBuffer
|
|
// cannot bypass the large buffer.
|
|
_, err := io.CopyBuffer(writerOnly{dst}, src, make([]byte, streamChunkSize))
|
|
|
|
return err
|
|
}
|
|
|
|
// writerOnly exposes only the Write method of the wrapped writer.
|
|
type writerOnly struct {
|
|
io.Writer
|
|
}
|