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 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 }