diff options
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/errors.go | 2 | ||||
| -rw-r--r-- | cmd/format.go | 79 | ||||
| -rw-r--r-- | cmd/fscrypt/commands.go | 200 | ||||
| -rw-r--r-- | cmd/fscrypt/flags.go | 73 | ||||
| -rw-r--r-- | cmd/fscrypt/fscrypt.go | 257 | ||||
| -rw-r--r-- | cmd/fscrypt/keys.go | 29 | ||||
| -rw-r--r-- | cmd/fscrypt/prompt.go | 50 | ||||
| -rw-r--r-- | cmd/fscrypt/setup.go | 4 | ||||
| -rw-r--r-- | cmd/fscrypt/status.go | 12 | ||||
| -rw-r--r-- | cmd/run.go | 34 |
10 files changed, 367 insertions, 373 deletions
diff --git a/cmd/errors.go b/cmd/errors.go index d4aca6d..c67c0f1 100644 --- a/cmd/errors.go +++ b/cmd/errors.go @@ -109,7 +109,7 @@ func (ctx *Context) processError(err error) { // Errors with a help text should print it out. if helpText := ctx.getHelpText(err); helpText != "" { fmt.Fprintln(os.Stderr) - fmt.Fprintln(os.Stderr, WrapText(helpText, 0)) + fmt.Fprintln(os.Stderr, helpText) } os.Exit(FailureCode) return diff --git a/cmd/format.go b/cmd/format.go index 877938c..993b955 100644 --- a/cmd/format.go +++ b/cmd/format.go @@ -20,6 +20,7 @@ package cmd import ( + "bufio" "bytes" "fmt" "io" @@ -45,11 +46,9 @@ var ( // LineLength is the maximum length of any output. If not set, the width // of the terminal be detected and assigned to LineLength. LineLength int - // FallbackLineLength is the LineLength used if detection fails. By - // default we fall back to punch cards. - FallbackLineLength = 80 - // MaxLineLength is the maximum allowed detected value of LineLength. - MaxLineLength = 120 + // DefaultLineLength is the LineLength we use if we cannot detect the + // terminal width. By default we fall back to punch cards. + DefaultLineLength = 80 // Output is the io.Writer all commands should use for their normal // output (errors should just return the appropriate error). If not set, // it is automatically set based on the provided flags. @@ -58,26 +57,34 @@ var ( // We use the width of the terminal unless we cannot get the width. func init() { - if LineLength > 0 { - return + if LineLength == 0 { + var err error + LineLength, _, err = terminal.GetSize(int(os.Stdout.Fd())) + if err != nil { + LineLength = DefaultLineLength + } } - width, _, err := terminal.GetSize(int(os.Stdout.Fd())) - if err != nil { - LineLength = FallbackLineLength - } else { - LineLength = util.MinInt(width, MaxLineLength) +} + +// MaxSubcommandLength returns the length of the longest subcommand (where the +// length of the command is Name + Title). Return 0 if there aren't subcommands. +func (c *Command) MaxNameLength() (max int) { + for _, s := range c.SubCommands { + max = util.MaxInt(max, len(s.Name)) } + return } // WrapText wraps an input string so that each line begins with numTabs tabs // (except the first line) and ends with a newline (except the last line), and // each line has length less than lineLength. If the text contains a word which -// is too long, that word gets its own line. -func WrapText(text string, numTabs int) string { +// is too long, that word gets its own line. The first line's calculated length +// is startSpaces less (to account for strange offsets on the first line). +func WrapText(startSpaces, numTabs int, text string) string { // We use a buffer to format the wrapped text so we get O(n) runtime var buffer bytes.Buffer spaceLeft := 0 - maxTextLen := LineLength - numTabs*TabWidth + maxTextLen := LineLength - startSpaces delimiter := strings.Repeat("\t", numTabs) for i, word := range strings.Fields(text) { wordLen := utf8.RuneCountInString(word) @@ -119,14 +126,23 @@ func Pluralize(count int, word string) string { return fmt.Sprintf("%d %ss", count, word) } +// ReadLine returns a line of input from standard input. An empty string is +// returned if the user didn't insert anything, we're in quiet mode or on error. +// This function should be the only way user input is acquired from an +// application (except for passwords). +func ReadLine() (string, error) { + if QuietFlag.Value { + return "", nil + } + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + return scanner.Text(), scanner.Err() +} + // AskQuestion asks the user a yes or no question. Returning a boolean on a // successful answer and an error if there was not a response from the user. // Returns the defaultChoice on empty input (or in quiet mode). func AskQuestion(question string, defaultChoice bool) (bool, error) { - // If in quiet mode, we just use the default. - if QuietFlag.Value { - return defaultChoice, nil - } // Loop until failure or valid input. for { if defaultChoice { @@ -135,7 +151,7 @@ func AskQuestion(question string, defaultChoice bool) (bool, error) { fmt.Fprintf(Output, "%s %s ", question, defaultNoSuffix) } - input, err := util.ReadLine() + input, err := ReadLine() if err != nil { return false, err } @@ -159,16 +175,8 @@ func AskConfirmation(question, warning string, defaultChoice bool) error { return nil } - // Defaults of "no" require forcing. - if QuietFlag.Value { - if defaultChoice { - return nil - } - return ErrMustForce - } - if warning != "" { - fmt.Fprintln(Output, WrapText("WARNING: "+warning, 0)) + fmt.Fprintln(Output, "WARNING: "+warning) } confirmed, err := AskQuestion(question, defaultChoice) @@ -176,6 +184,10 @@ func AskConfirmation(question, warning string, defaultChoice bool) error { return err } if !confirmed { + // To override a "false" default, use ForceFlag. + if QuietFlag.Value { + return ErrMustForce + } return ErrCanceled } return nil @@ -185,7 +197,14 @@ func AskConfirmation(question, warning string, defaultChoice bool) error { // the provided Context and writer. Panics if text cannot be executed. func ExecuteTemplate(w io.Writer, text string, ctx *Context) { tmpl := template.Must(template.New("").Funcs(template.FuncMap{ - "WrapText": WrapText, + "WrapText": WrapText, + "LineLength": func() int { return LineLength }, + "add": func(nums ...int) (sum int) { + for _, num := range nums { + sum += num + } + return + }, }).Parse(text)) if err := tmpl.Execute(w, ctx); err != nil { panic(err) diff --git a/cmd/fscrypt/commands.go b/cmd/fscrypt/commands.go index bbfcf2b..b29435a 100644 --- a/cmd/fscrypt/commands.go +++ b/cmd/fscrypt/commands.go @@ -408,79 +408,6 @@ func unlockPath(path string) error { // Action: destoryMetadataAction, // } -// func destoryMetadataAction(c *cli.Context) error { -// switch c.NArg() { -// case 0: -// switch { -// case protectorFlag.Value != "": -// // Case (1) - protector destroy -// protector, err := getProtectorFromFlag(protectorFlag.Value, nil) -// if err != nil { -// return newExitError(c, err) -// } - -// prompt := fmt.Sprintf("Destroy protector %s on %q?", -// protector.Descriptor(), protector.Context.Mount.Path) -// warning := "All files protected only with this protector will be lost!!" -// if err := askConfirmation(prompt, false, warning); err != nil { -// return newExitError(c, err) -// } -// if err := protector.Destroy(); err != nil { -// return newExitError(c, err) -// } - -// fmt.Fprintf(c.App.Writer, "Protector %s deleted from filesystem %q.\n", -// protector.Descriptor(), protector.Context.Mount.Path) -// case policyFlag.Value != "": -// // Case (2) - policy destroy -// policy, err := getPolicyFromFlag(policyFlag.Value, nil) -// if err != nil { -// return newExitError(c, err) -// } - -// prompt := fmt.Sprintf("Destroy policy %s on %q?", -// policy.Descriptor(), policy.Context.Mount.Path) -// warning := "All files using this policy will be lost!!" -// if err := askConfirmation(prompt, false, warning); err != nil { -// return newExitError(c, err) -// } -// if err := policy.Destroy(); err != nil { -// return newExitError(c, err) -// } - -// fmt.Fprintf(c.App.Writer, "Policy %s deleted from filesystem %q.\n", -// policy.Descriptor(), policy.Context.Mount.Path) -// default: -// message := fmt.Sprintf("Must specify one of: %s, %s, or %s", -// mountpointArg, -// shortDisplay(protectorFlag), -// shortDisplay(policyFlag)) -// return &usageError{c, message} -// } -// case 1: -// // Case (3) - mountpoint destroy -// path := c.Args().Get(0) -// ctx, err := actions.NewContextFromMountpoint(path, nil) -// if err != nil { -// return newExitError(c, err) -// } - -// prompt := fmt.Sprintf("Destroy all the metadata on %q?", ctx.Mount.Path) -// warning := "All the encrypted files on this filesystem will be lost!!" -// if err := askConfirmation(prompt, false, warning); err != nil { -// return newExitError(c, err) -// } -// if err := ctx.Mount.RemoveAllMetadata(); err != nil { -// return newExitError(c, err) -// } - -// fmt.Fprintf(c.App.Writer, "All metadata on %q deleted.\n", ctx.Mount.Path) -// default: -// return expectedArgsErr(c, 1, true) -// } -// return nil -// } - // var changePassphrase = cli.Command{ // Name: "change-passphrase", // ArgsUsage: shortDisplay(protectorFlag), @@ -492,31 +419,6 @@ func unlockPath(path string) error { // Action: changePassphraseAction, // } -// func changePassphraseAction(c *cli.Context) error { -// if c.NArg() != 0 { -// return expectedArgsErr(c, 0, false) -// } -// if err := checkRequiredFlags(c, []*stringFlag{protectorFlag}); err != nil { -// return err -// } - -// protector, err := getProtectorFromFlag(protectorFlag.Value, nil) -// if err != nil { -// return newExitError(c, err) -// } -// if err := protector.Unlock(oldExistingKeyFn); err != nil { -// return newExitError(c, err) -// } -// defer protector.Lock() -// if err := protector.Rewrap(newCreateKeyFn); err != nil { -// return newExitError(c, err) -// } - -// fmt.Fprintf(c.App.Writer, "Passphrase for protector %s successfully changed.\n", -// protector.Descriptor()) -// return nil -// } - // var addProtectorToPolicy = cli.Command{ // Name: "add-protector-to-policy", // ArgsUsage: fmt.Sprintf("%s %s", shortDisplay(protectorFlag), shortDisplay(policyFlag)), @@ -530,49 +432,6 @@ func unlockPath(path string) error { // Action: addProtectorAction, // } -// func addProtectorAction(c *cli.Context) error { -// if c.NArg() != 0 { -// return expectedArgsErr(c, 0, false) -// } -// if err := checkRequiredFlags(c, []*stringFlag{protectorFlag, policyFlag}); err != nil { -// return err -// } - -// protector, err := getProtectorFromFlag(protectorFlag.Value, nil) -// if err != nil { -// return newExitError(c, err) -// } -// policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser) -// if err != nil { -// return newExitError(c, err) -// } -// // Sanity check before unlocking everything -// if err := policy.AddProtector(protector); errors.Cause(err) != actions.ErrLocked { -// return newExitError(c, err) -// } - -// prompt := fmt.Sprintf("Protect policy %s with protector %s?", -// policy.Descriptor(), protector.Descriptor()) -// warning := "All files using this policy will be accessible with this protector!!" -// if err := askConfirmation(prompt, true, warning); err != nil { -// return newExitError(c, err) -// } - -// if err := protector.Unlock(existingKeyFn); err != nil { -// return newExitError(c, err) -// } -// if err := policy.Unlock(optionFn, existingKeyFn); err != nil { -// return newExitError(c, err) -// } -// if err := policy.AddProtector(protector); err != nil { -// return newExitError(c, err) -// } - -// fmt.Fprintf(c.App.Writer, "Protector %s now protecting policy %s.\n", -// protector.Descriptor(), policy.Descriptor()) -// return nil -// } - // var removeProtectorFromPolicy = cli.Command{ // Name: "remove-protector-from-policy", // ArgsUsage: fmt.Sprintf("%s %s", shortDisplay(protectorFlag), shortDisplay(policyFlag)), @@ -587,40 +446,6 @@ func unlockPath(path string) error { // Action: removeProtectorAction, // } -// func removeProtectorAction(c *cli.Context) error { -// if c.NArg() != 0 { -// return expectedArgsErr(c, 0, false) -// } -// if err := checkRequiredFlags(c, []*stringFlag{protectorFlag, policyFlag}); err != nil { -// return err -// } - -// // We do not need to unlock anything for this operation -// protector, err := getProtectorFromFlag(protectorFlag.Value, nil) -// if err != nil { -// return newExitError(c, err) -// } -// policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser) -// if err != nil { -// return newExitError(c, err) -// } - -// prompt := fmt.Sprintf("Stop protecting policy %s with protector %s?", -// policy.Descriptor(), protector.Descriptor()) -// warning := "All files using this policy will NO LONGER be accessible with this protector!!" -// if err := askConfirmation(prompt, false, warning); err != nil { -// return newExitError(c, err) -// } - -// if err := policy.RemoveProtector(protector); err != nil { -// return newExitError(c, err) -// } - -// fmt.Fprintf(c.App.Writer, "Protector %s no longer protecting policy %s.\n", -// protector.Descriptor(), policy.Descriptor()) -// return nil -// } - // var dumpMetadata = cli.Command{ // Name: "dump", // ArgsUsage: fmt.Sprintf("[%s | %s]", shortDisplay(protectorFlag), shortDisplay(policyFlag)), @@ -635,28 +460,3 @@ func unlockPath(path string) error { // Flags: []cli.Flag{protectorFlag, policyFlag}, // Action: dumpMetadataAction, // } - -// func dumpMetadataAction(c *cli.Context) error { -// switch { -// case protectorFlag.Value != "": -// // Case (1) - protector print -// protector, err := getProtectorFromFlag(protectorFlag.Value, nil) -// if err != nil { -// return newExitError(c, err) -// } -// fmt.Fprintln(c.App.Writer, protector) -// case policyFlag.Value != "": -// // Case (2) - policy print -// policy, err := getPolicyFromFlag(policyFlag.Value, nil) -// if err != nil { -// return newExitError(c, err) -// } -// fmt.Fprintln(c.App.Writer, policy) -// default: -// message := fmt.Sprintf("Must specify one of: %s or %s", -// shortDisplay(protectorFlag), -// shortDisplay(policyFlag)) -// return &usageError{c, message} -// } -// return nil -// } diff --git a/cmd/fscrypt/flags.go b/cmd/fscrypt/flags.go index 5983053..084fa19 100644 --- a/cmd/fscrypt/flags.go +++ b/cmd/fscrypt/flags.go @@ -37,23 +37,18 @@ import ( // Bool flags: used to switch some behavior on or off var ( legacyFlag = &cmd.BoolFlag{ - Name: "legacy", - Usage: `Allow for support of older kernels with ext4 (before - v4.8) and F2FS (before v4.6) filesystems.`, + Name: "legacy", + Usage: `Configure fscrypt to support older kernels.`, Default: true, } skipUnlockFlag = &cmd.BoolFlag{ - Name: "skip-unlock", - Usage: `Leave the directory in a locked state after setup. - "fscrypt unlock" will need to be run in order to use the - directory.`, + Name: "skip-unlock", + Usage: "Leave the directory in a locked state after setup.", } dropCachesFlag = &cmd.BoolFlag{ Name: "drop-caches", Usage: `After purging the keys from the keyring, drop the - associated caches for the purge to take effect. Without - this flag, cached encrypted files may still have their - plaintext visible. Requires root privileges.`, + associated caches for the purge to take effect.`, Default: true, } ) @@ -62,78 +57,62 @@ var ( var ( timeTargetFlag = &cmd.DurationFlag{ Name: "time", - ArgName: "TIME", + ArgName: "time", Usage: `Set the global options so that passphrase hashing takes - TIME long. TIME should be formatted as a sequence of - decimal numbers, each with optional fraction and a unit - suffix, such as "300ms", "1.5s" or "2h45m". Valid time - units are "ms", "s", "m", and "h".`, + <time> long.`, Default: 1 * time.Second, } sourceFlag = &cmd.StringFlag{ Name: "source", - ArgName: "SOURCE", - Usage: fmt.Sprintf(`New protectors will have type SOURCE. SOURCE - can be one of pam_passphrase, custom_passphrase, or - raw_key. If not specified, the user will be prompted for - the source, with a default pulled from %s.`, - actions.ConfigFileLocation), + ArgName: "source", + Usage: `New protectors will have type <source> (one of + pam_passphrase, custom_passphrase, or raw_key).`, } nameFlag = &cmd.StringFlag{ Name: "name", - ArgName: "PROTECTOR_NAME", - Usage: `New custom_passphrase and raw_key protectors will be - named PROTECTOR_NAME. If not specified, the user will be - prompted for a name.`, + ArgName: "name", + Usage: "Use <name> as the name for a new protector.", } keyFileFlag = &cmd.StringFlag{ Name: "key", - ArgName: "FILE", - Usage: `Use the contents of FILE as the wrapping key when - creating or unlocking raw_key protectors. FILE should be - formatted as raw binary and should be exactly 32 bytes - long.`, + ArgName: "path", + Usage: "Use the file at <path> as the protector key.", } userFlag = &cmd.StringFlag{ Name: "user", - ArgName: "USERNAME", - Usage: `Specifiy which user should be used for login passphrases + ArgName: "username", + Usage: `Specify which user should be used for login passphrases or to which user's keyring keys should be provisioned.`, } - mountpointIDArg = "MOUNTPOINT:ID" + mountpointIDArg = usedMountpointArg.ArgName + ":id" protectorFlag = &cmd.StringFlag{ Name: "protector", ArgName: mountpointIDArg, - Usage: `Specify an existing protector on filesystem MOUNTPOINT - with protector descriptor ID which should be used in the - command.`, + Usage: fmt.Sprintf(`An existing protector on %s with hexadecimal + id <id>.`, usedMountpointArg), } unlockWithFlag = &cmd.StringFlag{ Name: "unlock-with", ArgName: mountpointIDArg, - Usage: `Specify an existing protector on filesystem MOUNTPOINT - with protector descriptor ID which should be used to - unlock a policy (usually specified with --policy). This - flag is only useful if a policy is protected with - multiple protectors. If not specified, the user will be - prompted for a protector.`, + Usage: fmt.Sprintf(`The protector that should be used to unlock + the policy specified with %s.`, policyFlag), } policyFlag = &cmd.StringFlag{ Name: "policy", ArgName: mountpointIDArg, - Usage: `Specify an existing policy on filesystem MOUNTPOINT with - key descriptor ID which should be used in the command.`, + Usage: fmt.Sprintf(`An existing policy on %s with hexadecimal id + <id>.`, usedMountpointArg), } ) -// The first group is optional and corresponds to the mountpoint. The second -// group is required and corresponds to the descriptor. +// The first group corresponds to the mountpoint string. The second group +// corresponds to the hexideciamal descriptor. var idFlagRegex = regexp.MustCompile("^([[:print:]]+):([[:alnum:]]+)$") func matchMetadataFlag(flagValue string) (mountpoint, descriptor string, err error) { matches := idFlagRegex.FindStringSubmatch(flagValue) if matches == nil { - return "", "", fmt.Errorf("flag value %q does not have format %s", + return "", "", fmt.Errorf("flag value %q does not have format %q", flagValue, mountpointIDArg) } log.Printf("parsed flag: mountpoint=%q descriptor=%s", matches[1], matches[2]) diff --git a/cmd/fscrypt/fscrypt.go b/cmd/fscrypt/fscrypt.go index 63b2cbc..2054193 100644 --- a/cmd/fscrypt/fscrypt.go +++ b/cmd/fscrypt/fscrypt.go @@ -63,12 +63,12 @@ var baseFlags = []cmd.Flag{cmd.VerboseFlag, cmd.QuietFlag, cmd.HelpFlag} var fscryptCommand = cmd.Command{ Title: "manage linux filesystem encryption", UsageLines: []string{ - fmt.Sprintf("<command> [arguments] [command options] [%s | %s]", + fmt.Sprintf("<command> [arguments] [options] [%s|%s]", cmd.VerboseFlag, cmd.QuietFlag), cmd.VersionUsage, }, SubCommands: []*cmd.Command{setupCommand, encryptCommand, unlockCommand, - purgeCommand, statusCommand, metadataCommand, cmd.VersionCommand}, Flags: baseFlags, + purgeCommand, statusCommand, metadataCommand, cmd.VersionCommand}, Flags: baseFlags, ManPage: &cmd.ManPage{Name: "fscrypt", Section: 8}, } @@ -108,7 +108,7 @@ var encryptCommand = &cmd.Command{ Arguments: []*cmd.Argument{directoryToEncryptArg}, InheritFlags: true, Flags: []cmd.Flag{sourceFlag, nameFlag, protectorFlag, policyFlag, - keyFileFlag, userFlag, skipUnlockFlag}, + unlockWithFlag, keyFileFlag, userFlag, skipUnlockFlag}, ManPage: &cmd.ManPage{Name: "fscrypt-encrypt", Section: 8}, Action: encryptAction, } @@ -140,7 +140,7 @@ var unlockCommand = &cmd.Command{ UsageLines: []string{"???"}, // TODO(joerichey) Arguments: []*cmd.Argument{encryptedPathArg}, InheritFlags: true, - Flags: []cmd.Flag{protectorFlag, policyFlag, keyFileFlag, userFlag}, + Flags: []cmd.Flag{unlockWithFlag, keyFileFlag, userFlag}, ManPage: &cmd.ManPage{Name: "fscrypt-unlock", Section: 8}, Action: unlockAction, } @@ -220,7 +220,7 @@ var statusCommand = &cmd.Command{ Name: "status", Title: "get the status of the system or a path", UsageLines: []string{"", usedMountpointArg.String(), encryptedPathArg.String()}, - Arguments: []*cmd.Argument{usedMountpointArg, encryptedPathArg}, + Arguments: []*cmd.Argument{usedMountpointArg, encryptedPathArg}, Flags: []cmd.Flag{cmd.VerboseFlag, cmd.HelpFlag}, ManPage: &cmd.ManPage{Name: "fscrypt-status", Section: 8}, Action: statusAction, @@ -254,10 +254,10 @@ func statusAction(c *cmd.Context) error { var metadataCommand = &cmd.Command{ Name: "metadata", Title: "manipulate fscrypt metadata directly", - UsageLines: []string{fmt.Sprintf("<command> [command options] [%s] [%s]", + UsageLines: []string{fmt.Sprintf("<command> [options] [%s] [%s]", protectorFlag, policyFlag)}, - SubCommands: []*cmd.Command{createCommand}, // destroyCommand, changeCommand, - // addProtectorCommand, removeProtectorCommand, dumpCommand}, + SubCommands: []*cmd.Command{createCommand, destroyCommand, changeCommand, + addProtectorCommand, removeProtectorCommand, dumpCommand}, InheritFlags: true, Flags: []cmd.Flag{protectorFlag, policyFlag}, ManPage: &cmd.ManPage{Name: "fscrypt-metadata", Section: 8}, @@ -268,7 +268,7 @@ var createCommand = &cmd.Command{ Title: "manually create metadata on a filesystem", UsageLines: []string{ fmt.Sprintf("protector %s", usedMountpointArg), - fmt.Sprintf("policy %s, %s", usedMountpointArg, protectorFlag), + fmt.Sprintf("policy %s %s", usedMountpointArg, protectorFlag), }, SubCommands: []*cmd.Command{createProtectorCommand, createPolicyCommand}, Arguments: []*cmd.Argument{usedMountpointArg}, @@ -363,66 +363,73 @@ func createPolicyAction(c *cmd.Context) error { } var destroyCommand = &cmd.Command{ - Name: "destroy", + Name: "destroy", Title: "directly delete an existing protector or policy", UsageLines: []string{ fmt.Sprintf("%s [%s]", protectorFlag, cmd.ForceFlag), fmt.Sprintf("%s [%s]", policyFlag, cmd.ForceFlag), fmt.Sprintf("%s [%s]", usedMountpointArg, cmd.ForceFlag), }, - Arguments: []*cmd.Argument{usedMountpointArg}, + Arguments: []*cmd.Argument{usedMountpointArg}, InheritFlags: true, - Flags: []cmd.Flag{cmd.ForceFlag}, - Action: destroyAction, + Flags: []cmd.Flag{cmd.ForceFlag}, + Action: destroyAction, } -func destoryAction(c *cmd.Context) error { +func destroyAction(c *cmd.Context) error { + if err := cmd.CheckExpectedArgs(c, 1, true); err != nil { + return err + } + hasProtector := protectorFlag.Value != "" + hasPolicy := policyFlag.Value != "" + hasMount := len(c.Args) == 1 + if (hasProtector && hasPolicy) || (hasPolicy && hasMount) || (hasMount && hasProtector) { + return cmd.UsageError(fmt.Sprintf("Multiple of %s, %s, %s provided", + protectorFlag, policyFlag, usedMountpointArg)) + } + switch { - case protectorFlag.Value != "": - if len(c.Args) != 0 { - break - } + case hasProtector: // Case (1) - protector destroy protector, err := getProtectorFromFlag(protectorFlag.Value, nil) if err != nil { - return newExitError(c, err) + return err } prompt := fmt.Sprintf("Destroy protector %s on %q?", protector.Descriptor(), protector.Context.Mount.Path) warning := "All files protected only with this protector will be lost!!" - if err := askConfirmation(prompt, false, warning); err != nil { - return newExitError(c, err) + if err = cmd.AskConfirmation(prompt, warning, false); err != nil { + return err } - if err := protector.Destroy(); err != nil { - return newExitError(c, err) + if err = protector.Destroy(); err != nil { + return err } - fmt.Fprintf(c.App.Writer, "Protector %s deleted from filesystem %q.\n", + fmt.Fprintf(cmd.Output, "Protector %s deleted from filesystem %q.\n", protector.Descriptor(), protector.Context.Mount.Path) - case policyFlag.Value != "": - if len(c.Args) != 0 { - break - } + return nil + case hasPolicy: // Case (2) - policy destroy policy, err := getPolicyFromFlag(policyFlag.Value, nil) if err != nil { - return newExitError(c, err) + return err } prompt := fmt.Sprintf("Destroy policy %s on %q?", policy.Descriptor(), policy.Context.Mount.Path) - warning := "All files using this policy will be lost!!" - if err := askConfirmation(prompt, false, warning); err != nil { - return newExitError(c, err) + warning := "All files and directories using this policy will be lost!" + if err = cmd.AskConfirmation(prompt, warning, false); err != nil { + return err } - if err := policy.Destroy(); err != nil { - return newExitError(c, err) + if err = policy.Destroy(); err != nil { + return err } - fmt.Fprintf(c.App.Writer, "Policy %s deleted from filesystem %q.\n", + fmt.Fprintf(cmd.Output, "Policy %s deleted from filesystem %q.\n", policy.Descriptor(), policy.Context.Mount.Path) - case len(c.Args) == 1: + return nil + case hasMount: // Case (3) - mountpoint destroy ctx, err := actions.NewContextFromMountpoint(c.Args[0], nil) if err != nil { @@ -440,8 +447,182 @@ func destoryAction(c *cmd.Context) error { fmt.Fprintf(cmd.Output, "All metadata on %q deleted.\n", ctx.Mount.Path) return nil + default: + return cmd.UsageError(fmt.Sprintf("None of %s, %s, %s provided", + protectorFlag, policyFlag, usedMountpointArg)) } - return cmd.UsageError(fmt.Sprintf("Must specify exactly one of: %s, %s, or %s", - usedMountpointArg, protectorFlag, policyFlag)) } +var changeCommand = &cmd.Command{ + Name: "change-passphrase", + Title: "change the passphrase for a protector", + UsageLines: []string{protectorFlag.String()}, + InheritFlags: true, + Flags: []cmd.Flag{protectorFlag}, + Action: changeAction, +} + +func changeAction(c *cmd.Context) error { + if err := cmd.CheckExpectedArgs(c, 0, false); err != nil { + return err + } + if err := cmd.CheckRequiredFlags([]*cmd.StringFlag{protectorFlag}); err != nil { + return err + } + + protector, err := getProtectorFromFlag(protectorFlag.Value, nil) + if err != nil { + return err + } + if err := protector.Unlock(oldExistingKeyFn); err != nil { + return err + } + defer protector.Lock() + if err := protector.Rewrap(newCreateKeyFn); err != nil { + return err + } + + fmt.Fprintf(cmd.Output, "Passphrase for protector %s successfully changed.\n", + protector.Descriptor()) + return nil +} + +var addProtectorCommand = &cmd.Command{ + Name: "add-protector-to-policy", + Title: "start protecting a policy with a protector", + UsageLines: []string{fmt.Sprintf("%s [%s] %s [%s]", protectorFlag, + keyFileFlag, policyFlag, unlockWithFlag)}, + InheritFlags: true, + Flags: []cmd.Flag{keyFileFlag, unlockWithFlag}, + Action: addProtectorAction, +} + +func addProtectorAction(c *cmd.Context) error { + if err := cmd.CheckExpectedArgs(c, 0, false); err != nil { + return err + } + err := cmd.CheckRequiredFlags([]*cmd.StringFlag{protectorFlag, policyFlag}) + if err != nil { + return err + } + + protector, err := getProtectorFromFlag(protectorFlag.Value, nil) + if err != nil { + return err + } + policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser) + if err != nil { + return err + } + // Sanity check before unlocking everything + if err := policy.AddProtector(protector); errors.Cause(err) != actions.ErrLocked { + return err + } + + prompt := fmt.Sprintf("Protect policy %s with protector %s?", + policy.Descriptor(), protector.Descriptor()) + warning := "All files using this policy will be accessible with this protector!" + if err := cmd.AskConfirmation(prompt, warning, true); err != nil { + return err + } + + if err := protector.Unlock(existingKeyFn); err != nil { + return err + } + if err := policy.Unlock(optionFn, existingKeyFn); err != nil { + return err + } + if err := policy.AddProtector(protector); err != nil { + return err + } + + fmt.Fprintf(cmd.Output, "Protector %s now protecting policy %s.\n", + protector.Descriptor(), policy.Descriptor()) + return nil +} + +var removeProtectorCommand = &cmd.Command{ + Name: "remove-protector-from-policy", + Title: "stop protecting a policy with a protector", + UsageLines: []string{fmt.Sprintf("%s %s [%s]", protectorFlag, + policyFlag, cmd.ForceFlag)}, + InheritFlags: true, + Flags: []cmd.Flag{keyFileFlag, unlockWithFlag}, + Action: removeProtectorAction, +} + +func removeProtectorAction(c *cmd.Context) error { + if err := cmd.CheckExpectedArgs(c, 0, false); err != nil { + return err + } + err := cmd.CheckRequiredFlags([]*cmd.StringFlag{protectorFlag, policyFlag}) + if err != nil { + return err + } + + // We do not need to unlock anything for this operation + protector, err := getProtectorFromFlag(protectorFlag.Value, nil) + if err != nil { + return err + } + policy, err := getPolicyFromFlag(policyFlag.Value, protector.Context.TargetUser) + if err != nil { + return err + } + + prompt := fmt.Sprintf("Stop protecting policy %s with protector %s?", + policy.Descriptor(), protector.Descriptor()) + warning := "All files using this policy will NOT BE ACCESSIBLE with this protector!" + if err := cmd.AskConfirmation(prompt, warning, false); err != nil { + return err + } + if err := policy.RemoveProtector(protector); err != nil { + return err + } + + fmt.Fprintf(cmd.Output, "Protector %s no longer protecting policy %s.\n", + protector.Descriptor(), policy.Descriptor()) + return nil +} + +var dumpCommand = &cmd.Command{ + Name: "dump", + Title: "display debug info about protectors and policies", + UsageLines: []string{protectorFlag.String(), policyFlag.String()}, + Flags: []cmd.Flag{protectorFlag, policyFlag, cmd.VerboseFlag, cmd.HelpFlag}, + Action: dumpAction, +} + +func dumpAction(c *cmd.Context) error { + if err := cmd.CheckExpectedArgs(c, 0, false); err != nil { + return err + } + hasProtector := protectorFlag.Value != "" + hasPolicy := policyFlag.Value != "" + if hasProtector && hasPolicy { + return cmd.UsageError(fmt.Sprintf("Multiple of %s, %s provided", + protectorFlag, policyFlag)) + } + + switch { + case hasProtector: + // Case (1) - protector dump + protector, err := getProtectorFromFlag(protectorFlag.Value, nil) + if err != nil { + return err + } + fmt.Fprintln(cmd.Output, protector) + return nil + case hasPolicy: + // Case (2) - policy dump + policy, err := getPolicyFromFlag(policyFlag.Value, nil) + if err != nil { + return err + } + fmt.Fprintln(cmd.Output, policy) + return nil + default: + return cmd.UsageError(fmt.Sprintf("None of %s, %s provided", + protectorFlag, policyFlag)) + } +} diff --git a/cmd/fscrypt/keys.go b/cmd/fscrypt/keys.go index 872ca2a..ec02148 100644 --- a/cmd/fscrypt/keys.go +++ b/cmd/fscrypt/keys.go @@ -31,9 +31,11 @@ import ( "golang.org/x/crypto/ssh/terminal" "github.com/google/fscrypt/actions" + "github.com/google/fscrypt/cmd" "github.com/google/fscrypt/crypto" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/pam" + "github.com/google/fscrypt/util" ) // The file descriptor for standard input @@ -71,7 +73,7 @@ func (p passphraseReader) Read(buf []byte) (int, error) { case '\r', '\n': return position, io.EOF case 3, 4: - return position, ErrCanceled + return position, cmd.ErrCanceled case 8, 127: if position > 0 { position-- @@ -86,9 +88,7 @@ func (p passphraseReader) Read(buf []byte) (int, error) { // passphrase into a key. If we are not reading from a terminal, just read into // the passphrase into the key normally. func getPassphraseKey(prompt string) (*crypto.Key, error) { - if !quietFlag.Value { - fmt.Print(prompt) - } + fmt.Fprint(cmd.Output, prompt) // Only disable echo if stdin is actually a terminal. if terminal.IsTerminal(stdinFd) { @@ -116,7 +116,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun panic("this KeyFunc does not support retrying") } // Don't retry for non-interactive sessions - if quietFlag.Value { + if cmd.QuietFlag.Value { return nil, ErrWrongKey } fmt.Println("Incorrect Passphrase") @@ -124,8 +124,8 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun switch info.Source() { case metadata.SourceType_pam_passphrase: - prompt := fmt.Sprintf("Enter %slogin passphrase for %s: ", - prefix, formatUsername(info.UID())) + username := util.GetUser(int(info.UID())).Username + prompt := fmt.Sprintf("Enter %s%s: ", prefix, formatInfo(info)) key, err := getPassphraseKey(prompt) if err != nil { return nil, err @@ -134,13 +134,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun // To confirm, check that the passphrase is the user's // login passphrase. if shouldConfirm { - username, err := usernameFromID(info.UID()) - if err != nil { - key.Wipe() - return nil, err - } - - err = pam.IsUserLoginToken(username, key, quietFlag.Value) + err = pam.IsUserLoginToken(username, key, cmd.QuietFlag.Value) if err != nil { key.Wipe() return nil, err @@ -149,8 +143,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun return key, nil case metadata.SourceType_custom_passphrase: - prompt := fmt.Sprintf("Enter %scustom passphrase for protector %q: ", - prefix, info.Name()) + prompt := fmt.Sprintf("Enter %s%s: ", prefix, formatInfo(info)) key, err := getPassphraseKey(prompt) if err != nil { return nil, err @@ -158,7 +151,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun // To confirm, make sure the user types the same // passphrase in again. - if shouldConfirm && !quietFlag.Value { + if shouldConfirm && !cmd.QuietFlag.Value { key2, err := getPassphraseKey("Confirm passphrase: ") if err != nil { key.Wipe() @@ -178,7 +171,7 @@ func makeKeyFunc(supportRetry, shouldConfirm bool, prefix string) actions.KeyFun if prefix != "" { return nil, ErrNotPassphrase } - prompt := fmt.Sprintf("Enter key file for protector %q: ", info.Name()) + prompt := fmt.Sprintf("Enter path to %s: ", formatInfo(info)) // Raw keys use a file containing the key data. file, err := promptForKeyFile(prompt) if err != nil { diff --git a/cmd/fscrypt/prompt.go b/cmd/fscrypt/prompt.go index 57d0fc7..c9b3147 100644 --- a/cmd/fscrypt/prompt.go +++ b/cmd/fscrypt/prompt.go @@ -26,6 +26,7 @@ import ( "strconv" "github.com/google/fscrypt/actions" + "github.com/google/fscrypt/cmd" "github.com/google/fscrypt/metadata" "github.com/google/fscrypt/util" ) @@ -41,11 +42,11 @@ var sourceDescriptions = map[metadata.SourceType]string{ func formatInfo(data actions.ProtectorInfo) string { switch data.Source() { case metadata.SourceType_pam_passphrase: - return "login protector for " + formatUsername(data.UID()) + return "login passphrase for " + util.GetUser(int(data.UID())).Username case metadata.SourceType_custom_passphrase: - return fmt.Sprintf("custom protector %q", data.Name()) + return fmt.Sprintf("custom passphrase for protector %q", data.Name()) case metadata.SourceType_raw_key: - return fmt.Sprintf("raw key protector %q", data.Name()) + return fmt.Sprintf("raw key for protector %q", data.Name()) default: panic(ErrInvalidSource) } @@ -59,13 +60,13 @@ func promptForName(ctx *actions.Context) (string, error) { } // Don't ask for a name if we do not need it - if quietFlag.Value || ctx.Config.Source == metadata.SourceType_pam_passphrase { + if cmd.QuietFlag.Value || ctx.Config.Source == metadata.SourceType_pam_passphrase { return "", nil } for { - fmt.Print("Enter a name for the new protector: ") - name, err := util.ReadLine() + fmt.Fprint(cmd.Output, "Enter a name for the new protector: ") + name, err := cmd.ReadLine() if err != nil { return "", err } @@ -88,23 +89,18 @@ func promptForSource(ctx *actions.Context) error { return nil } - // Just use the default in quiet mode - if quietFlag.Value { - return nil - } - // We print all the sources with their number, description, and name. - fmt.Println("Your data can be protected with one of the following sources:") + fmt.Fprintln(cmd.Output, "Your data can be protected with one of the following sources:") for idx := 1; idx < len(metadata.SourceType_value); idx++ { source := metadata.SourceType(idx) description := sourceDescriptions[source] - fmt.Printf("%d - %s (%s)\n", idx, description, source) + fmt.Fprintf(cmd.Output, "%d - %s (%s)\n", idx, description, source) } for { - fmt.Printf("Enter the source number for the new protector [%d - %s]: ", + fmt.Fprintf(cmd.Output, "Enter the source number for the new protector [%d - %s]: ", ctx.Config.Source, ctx.Config.Source) - input, err := util.ReadLine() + input, err := cmd.ReadLine() if err != nil { return err } @@ -114,7 +110,7 @@ func promptForSource(ctx *actions.Context) error { return nil } - // Check for a valid index, reprompt if invalid. + // Check for a valid index, prompt again if invalid. index, err := strconv.Atoi(input) if err == nil && index >= 1 && index < len(metadata.SourceType_value) { ctx.Config.Source = metadata.SourceType(index) @@ -130,14 +126,14 @@ func promptForKeyFile(prompt string) (*os.File, error) { if keyFileFlag.Value != "" { return os.Open(keyFileFlag.Value) } - if quietFlag.Value { + if cmd.QuietFlag.Value { return nil, ErrSpecifyKeyFile } // Prompt for a valid path until we get a file we can open. for { - fmt.Print(prompt) - filename, err := util.ReadLine() + fmt.Fprint(cmd.Output, prompt) + filename, err := cmd.ReadLine() if err != nil { return nil, err } @@ -145,7 +141,7 @@ func promptForKeyFile(prompt string) (*os.File, error) { if err == nil { return file, nil } - fmt.Println(err) + fmt.Fprintln(cmd.Output, err) } } @@ -155,7 +151,7 @@ func promptForKeyFile(prompt string) (*os.File, error) { // from, that protector is automatically selected. func promptForProtector(options []*actions.ProtectorOption) (int, error) { numOptions := len(options) - log.Printf("selecting from %s", pluralize(numOptions, "protector")) + log.Printf("selecting from %s", cmd.Pluralize(numOptions, "protector")) // Get the number of load errors. numLoadErrors := 0 @@ -172,12 +168,12 @@ func promptForProtector(options []*actions.ProtectorOption) (int, error) { if numOptions == 1 { return 0, nil } - if quietFlag.Value { + if cmd.QuietFlag.Value { return 0, ErrSpecifyProtector } // List all of the protector options which did not have a load error. - fmt.Println("The available protectors are: ") + fmt.Fprintln(cmd.Output, "The available protectors are: ") for idx, option := range options { if option.LoadError != nil { continue @@ -187,16 +183,16 @@ func promptForProtector(options []*actions.ProtectorOption) (int, error) { if option.LinkedMount != nil { description += fmt.Sprintf(" (linked protector on %q)", option.LinkedMount.Path) } - fmt.Println(description) + fmt.Fprintln(cmd.Output, description) } if numLoadErrors > 0 { - fmt.Print(wrapText("NOTE: %d of the %d protectors failed to load. "+loadHelpText, 0)) + fmt.Fprintln(cmd.Output, "NOTE: %d of the %d protectors failed to load. "+loadHelpText) } for { - fmt.Print("Enter the number of protector to use: ") - input, err := util.ReadLine() + fmt.Fprint(cmd.Output, "Enter the number of protector to use: ") + input, err := cmd.ReadLine() if err != nil { return 0, err } diff --git a/cmd/fscrypt/setup.go b/cmd/fscrypt/setup.go index 63f0c51..a421783 100644 --- a/cmd/fscrypt/setup.go +++ b/cmd/fscrypt/setup.go @@ -70,8 +70,8 @@ func setupFilesystem(path string) error { return err } - fmt.Fprintf(Output, "Metadata directories created at %q.\n", ctx.Mount.BaseDir()) - fmt.Fprintf(Output, "Filesystem %q (%s) ready for use with %s encryption.\n", + fmt.Fprintf(cmd.Output, "Metadata directories created at %q.\n", ctx.Mount.BaseDir()) + fmt.Fprintf(cmd.Output, "Filesystem %q (%s) ready for use with %s encryption.\n", ctx.Mount.Path, ctx.Mount.Device, ctx.Mount.Filesystem) return nil } diff --git a/cmd/fscrypt/status.go b/cmd/fscrypt/status.go index 87799a8..e9c2d8d 100644 --- a/cmd/fscrypt/status.go +++ b/cmd/fscrypt/status.go @@ -38,7 +38,7 @@ import ( // Creates a writer which correctly aligns tabs with the specified header. // Must call Flush() when done. func makeTableWriter(w io.Writer, header string) *tabwriter.Writer { - tableWriter := tabwriter.NewWriter(w, 0, indentLength, indentLength, ' ', 0) + tableWriter := tabwriter.NewWriter(cmd.Output, 0, cmd.TabWidth, cmd.TabWidth, ' ', 0) fmt.Fprintln(tableWriter, header) return tableWriter } @@ -140,8 +140,10 @@ func writeFilesystemStatus(ctx *actions.Context) error { return err } - fmt.Fprintf(w, "%s filesystem %q has %s and %s\n\n", ctx.Mount.Filesystem, ctx.Mount.Path, - pluralize(len(options), "protector"), pluralize(len(policyDescriptors), "policy")) + fmt.Fprintf(cmd.Output, "%s filesystem %q has %s and %s\n\n", + ctx.Mount.Filesystem, ctx.Mount.Path, + cmd.Pluralize(len(options), "protector"), + cmd.Pluralize(len(policyDescriptors), "policy")) if len(options) > 0 { writeOptions(options) @@ -183,7 +185,7 @@ func writePathStatus(path string) error { fmt.Fprintln(cmd.Output) options := policy.ProtectorOptions() - fmt.Fprintf(cmd.Output, "Protected with %s:\n", pluralize(len(options), "protector")) - writeOptions(cmd.Output, options) + fmt.Fprintf(cmd.Output, "Protected with %s:\n", cmd.Pluralize(len(options), "protector")) + writeOptions(options) return nil } @@ -48,22 +48,46 @@ var TemplateTitle = "{{.FullName}}{{if .Command.Title}} - {{.Command.Title}}{{en var TemplateUsage = `{{with $lines := .Command.UsageLines}}
Usage:
{{- range $lines}}
- {{$.FullName}} {{. -}}
+ {{printf "%s %s" $.FullName . | WrapText 8 2 -}}
{{end}}
{{end -}}
{{with $commands := .Command.SubCommands}}
+{{with $n := $.Command.MaxNameLength -}}
+{{with $fmt := printf "%%-%ds - %%s" $n -}}
Commands:
+{{if le (add 8 $n 3 $.Command.MaxTitleLength) LineLength -}}
+
+{{range $commands -}}
+
+{{end -}}
+
+{{else -}}
+
+{{range $commands}}
+{{end -}}
+
+{{end}}{{end}}{{end}}
+{{end -}}
+
+
{{- range $commands}}
- {{.Name}}{{if .Title}} - {{.Title}}{{end -}}
-{{end}}
+ {{if not .Title -}}
+ {{.Name -}}
+ {{else if le (add 8 $n 3 (len .Title)) LineLength -}}
+ {{printf $fmt .Name .Title -}}
+ {{else -}}
+ {{.Name}}
+ {{WrapText 16 2 .Title -}}
+ {{end -}}
+{{end}}{{end}}{{end}}
{{end -}}
{{with $arguments := .FullArguments}}
Arguments:
{{- range $arguments}}
{{.}}
- {{WrapText .Usage 2 -}}
+ {{WrapText 16 2 .Usage -}}
{{end}}
{{end -}}
@@ -71,7 +95,7 @@ Arguments: Options:
{{- range $flags}}
{{.}}
- {{WrapText .FullUsage 2 -}}
+ {{WrapText 16 2 .FullUsage -}}
{{end}}
{{end -}}
|