github.com/StackExchange/blackbox/v2@v2.0.1-0.20220331193400-d84e904973ab/pkg/box/boxutils.go (about) 1 package box 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "os/user" 8 "path/filepath" 9 "runtime" 10 "strconv" 11 "strings" 12 13 "github.com/StackExchange/blackbox/v2/pkg/makesafe" 14 ) 15 16 // FileStatus returns the status of a file. 17 func FileStatus(name string) (string, error) { 18 /* 19 DECRYPTED: File is decrypted and ready to edit (unknown if it has been edited). 20 ENCRYPTED: GPG file is newer than plaintext. Indicates recented edited then encrypted. 21 SHREDDED: Plaintext is missing. 22 GPGMISSING: The .gpg file is missing. Oops? 23 PLAINERROR: Can't access the plaintext file to determine status. 24 GPGERROR: Can't access .gpg file to determine status. 25 */ 26 27 p := name 28 e := p + ".gpg" 29 ps, perr := os.Stat(p) 30 es, eerr := os.Stat(e) 31 if perr == nil && eerr == nil { 32 if ps.ModTime().Before(es.ModTime()) { 33 return "ENCRYPTED", nil 34 } 35 return "DECRYPTED", nil 36 } 37 38 if os.IsNotExist(perr) && os.IsNotExist(eerr) { 39 return "BOTHMISSING", nil 40 } 41 42 if eerr != nil { 43 if os.IsNotExist(eerr) { 44 return "GPGMISSING", nil 45 } 46 return "GPGERROR", eerr 47 } 48 49 if perr != nil { 50 if os.IsNotExist(perr) { 51 return "SHREDDED", nil 52 } 53 } 54 return "PLAINERROR", perr 55 } 56 57 func anyGpg(names []string) error { 58 for _, name := range names { 59 if strings.HasSuffix(name, ".gpg") { 60 return fmt.Errorf( 61 "no not specify .gpg files. Specify %q not %q", 62 strings.TrimSuffix(name, ".gpg"), name) 63 } 64 } 65 return nil 66 } 67 68 // func isChanged(pname string) (bool, error) { 69 // // if .gpg exists but not plainfile: unchanged 70 // // if plaintext exists but not .gpg: changed 71 // // if plainfile < .gpg: unchanged 72 // // if plainfile > .gpg: don't know, need to try diff 73 74 // // Gather info about the files: 75 76 // pstat, perr := os.Stat(pname) 77 // if perr != nil && (!os.IsNotExist(perr)) { 78 // return false, fmt.Errorf("isChanged(%q) returned error: %w", pname, perr) 79 // } 80 // gname := pname + ".gpg" 81 // gstat, gerr := os.Stat(gname) 82 // if gerr != nil && (!os.IsNotExist(perr)) { 83 // return false, fmt.Errorf("isChanged(%q) returned error: %w", gname, gerr) 84 // } 85 86 // pexists := perr == nil 87 // gexists := gerr == nil 88 89 // // Use the above rules: 90 91 // // if .gpg exists but not plainfile: unchanged 92 // if gexists && !pexists { 93 // return false, nil 94 // } 95 96 // // if plaintext exists but not .gpg: changed 97 // if pexists && !gexists { 98 // return true, nil 99 // } 100 101 // // At this point we can conclude that both p and g exist. 102 // // Can't hurt to test that assertion. 103 // if (!pexists) && (!gexists) { 104 // return false, fmt.Errorf("Assertion failed. p and g should exist: pn=%q", pname) 105 // } 106 107 // pmodtime := pstat.ModTime() 108 // gmodtime := gstat.ModTime() 109 // // if plainfile < .gpg: unchanged 110 // if pmodtime.Before(gmodtime) { 111 // return false, nil 112 // } 113 // // if plainfile > .gpg: don't know, need to try diff 114 // return false, fmt.Errorf("Can not know for sure. Try git diff?") 115 // } 116 117 func parseGroup(userinput string) (int, error) { 118 if userinput == "" { 119 return -1, fmt.Errorf("group spec is empty string") 120 } 121 122 // If it is a valid number, use it. 123 i, err := strconv.Atoi(userinput) 124 if err == nil { 125 return i, nil 126 } 127 128 // If not a number, look it up by name. 129 g, err := user.LookupGroup(userinput) 130 if err == nil { 131 i, err = strconv.Atoi(g.Gid) 132 return i, nil 133 } 134 135 // Give up. 136 return -1, err 137 } 138 139 // FindConfigDir tests various places until it finds the config dir. 140 // If we can't determine the relative path, "" is returned. 141 func FindConfigDir(reporoot, team string) (string, error) { 142 143 candidates := []string{} 144 if team != "" { 145 candidates = append(candidates, ".blackbox-"+team) 146 } 147 candidates = append(candidates, ".blackbox") 148 candidates = append(candidates, "keyrings/live") 149 logDebug.Printf("DEBUG: candidates = %q\n", candidates) 150 151 maxDirLevels := 30 // Prevent an infinite loop 152 relpath := "." 153 for i := 0; i < maxDirLevels; i++ { 154 // Does relpath contain any of our directory names? 155 for _, c := range candidates { 156 t := filepath.Join(relpath, c) 157 logDebug.Printf("Trying %q\n", t) 158 fi, err := os.Stat(t) 159 if err == nil && fi.IsDir() { 160 return t, nil 161 } 162 if err == nil { 163 return "", fmt.Errorf("path %q is not a directory: %w", t, err) 164 } 165 if !os.IsNotExist(err) { 166 return "", fmt.Errorf("dirExists access error: %w", err) 167 } 168 } 169 170 // If we are at the root, stop. 171 if abs, _ := filepath.Abs(relpath); abs == "/" { 172 break 173 } 174 // Try one directory up 175 relpath = filepath.Join("..", relpath) 176 } 177 178 return "", fmt.Errorf("No .blackbox (or equiv) directory found") 179 } 180 181 func gpgAgentNotice() { 182 // Is gpg-agent configured? 183 if os.Getenv("GPG_AGENT_INFO") != "" { 184 return 185 } 186 // Are we on macOS? 187 if runtime.GOOS == "darwin" { 188 // We assume the use of https://gpgtools.org, which 189 // uses the keychain. 190 return 191 } 192 193 // TODO(tlim): v1 verifies that "gpg-agent --version" outputs a version 194 // string that is 2.1.0 or higher. It seems that 1.x is incompatible. 195 196 fmt.Println("WARNING: You probably want to run gpg-agent as") 197 fmt.Println("you will be asked for your passphrase many times.") 198 fmt.Println("Example: $ eval $(gpg-agent --daemon)") 199 fmt.Print("Press CTRL-C now to stop. ENTER to continue: ") 200 input := bufio.NewScanner(os.Stdin) 201 input.Scan() 202 } 203 204 func shouldWeOverwrite() { 205 fmt.Println() 206 fmt.Println("WARNING: This will overwrite any unencrypted files laying about.") 207 fmt.Print("Press CTRL-C now to stop. ENTER to continue: ") 208 input := bufio.NewScanner(os.Stdin) 209 input.Scan() 210 } 211 212 // PrettyCommitMessage generates a pretty commit message. 213 func PrettyCommitMessage(verb string, files []string) string { 214 if len(files) == 0 { 215 // This use-case should probably be an error. 216 return verb + " (no files)" 217 } 218 rfiles := makesafe.RedactMany(files) 219 m, truncated := makesafe.FirstFewFlag(rfiles) 220 if truncated { 221 return verb + ": " + m 222 } 223 return verb + ": " + m 224 }