1// Copyright (c) 2014, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15//go:build ignore 16 17package main 18 19import ( 20 "bufio" 21 "errors" 22 "flag" 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "sort" 28 "strconv" 29 "strings" 30) 31 32// ssl.h reserves values 1000 and above for error codes corresponding to 33// alerts. If automatically assigned reason codes exceed this value, this script 34// will error. This must be kept in sync with SSL_AD_REASON_OFFSET in ssl.h. 35const reservedReasonCode = 1000 36 37var resetFlag *bool = flag.Bool("reset", false, "If true, ignore current assignments and reassign from scratch") 38 39type libraryInfo struct { 40 sourceDirs []string 41 headerName string 42} 43 44func getLibraryInfo(lib string) libraryInfo { 45 var info libraryInfo 46 if lib == "ssl" { 47 info.sourceDirs = []string{"ssl"} 48 } else { 49 info.sourceDirs = []string{ 50 filepath.Join("crypto", lib), 51 filepath.Join("crypto", lib+"_extra"), 52 filepath.Join("crypto", "fipsmodule", lib), 53 } 54 } 55 info.headerName = lib + ".h" 56 57 if lib == "evp" { 58 info.headerName = "evp_errors.h" 59 info.sourceDirs = append(info.sourceDirs, filepath.Join("crypto", "hpke")) 60 } 61 62 if lib == "x509v3" { 63 info.headerName = "x509v3_errors.h" 64 info.sourceDirs = append(info.sourceDirs, filepath.Join("crypto", "x509")) 65 } 66 67 return info 68} 69 70func makeErrors(lib string, reset bool) error { 71 topLevelPath, err := findToplevel() 72 if err != nil { 73 return err 74 } 75 76 info := getLibraryInfo(lib) 77 78 headerPath := filepath.Join(topLevelPath, "include", "openssl", info.headerName) 79 errDir := filepath.Join(topLevelPath, "crypto", "err") 80 dataPath := filepath.Join(errDir, lib+".errordata") 81 82 headerFile, err := os.Open(headerPath) 83 if err != nil { 84 if os.IsNotExist(err) { 85 return fmt.Errorf("No header %s. Run in the right directory or touch the file.", headerPath) 86 } 87 88 return err 89 } 90 91 prefix := strings.ToUpper(lib) 92 reasons, err := parseHeader(prefix, headerFile) 93 headerFile.Close() 94 95 if reset { 96 err = nil 97 // Retain any reason codes above reservedReasonCode. 98 newReasons := make(map[string]int) 99 for key, value := range reasons { 100 if value >= reservedReasonCode { 101 newReasons[key] = value 102 } 103 } 104 reasons = newReasons 105 } 106 107 if err != nil { 108 return err 109 } 110 111 for _, sourceDir := range info.sourceDirs { 112 fullPath := filepath.Join(topLevelPath, sourceDir) 113 dir, err := os.Open(fullPath) 114 if err != nil { 115 if os.IsNotExist(err) { 116 // Some directories in the search path may not exist. 117 continue 118 } 119 return err 120 } 121 defer dir.Close() 122 filenames, err := dir.Readdirnames(-1) 123 if err != nil { 124 return err 125 } 126 127 for _, name := range filenames { 128 if !strings.HasSuffix(name, ".c") && !strings.HasSuffix(name, ".cc") { 129 continue 130 } 131 132 if err := addReasons(reasons, filepath.Join(fullPath, name), prefix); err != nil { 133 return err 134 } 135 } 136 } 137 138 assignNewValues(reasons, reservedReasonCode) 139 140 headerFile, err = os.Open(headerPath) 141 if err != nil { 142 return err 143 } 144 defer headerFile.Close() 145 146 newHeaderFile, err := os.OpenFile(headerPath+".tmp", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) 147 if err != nil { 148 return err 149 } 150 defer newHeaderFile.Close() 151 152 if err := writeHeaderFile(newHeaderFile, headerFile, prefix, reasons); err != nil { 153 return err 154 } 155 // Windows forbids renaming an open file. 156 headerFile.Close() 157 newHeaderFile.Close() 158 if err := os.Rename(headerPath+".tmp", headerPath); err != nil { 159 return err 160 } 161 162 dataFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 163 if err != nil { 164 return err 165 } 166 167 outputStrings(dataFile, lib, reasons) 168 dataFile.Close() 169 170 return nil 171} 172 173func findToplevel() (path string, err error) { 174 path = "." 175 buildingPath := filepath.Join(path, "BUILDING.md") 176 177 _, err = os.Stat(buildingPath) 178 for i := 0; i < 2 && err != nil && os.IsNotExist(err); i++ { 179 if i == 0 { 180 path = ".." 181 } else { 182 path = filepath.Join("..", path) 183 } 184 buildingPath = filepath.Join(path, "BUILDING.md") 185 _, err = os.Stat(buildingPath) 186 } 187 if err != nil { 188 return "", errors.New("Cannot find BUILDING.md file at the top-level") 189 } 190 return path, nil 191} 192 193type assignment struct { 194 key string 195 value int 196} 197 198func outputAssignments(w io.Writer, assignments map[string]int) { 199 sorted := make([]assignment, 0, len(assignments)) 200 for key, value := range assignments { 201 sorted = append(sorted, assignment{key, value}) 202 } 203 204 sort.Slice(sorted, func(i, j int) bool { return sorted[i].value < sorted[j].value }) 205 206 for _, assignment := range sorted { 207 fmt.Fprintf(w, "#define %s %d\n", assignment.key, assignment.value) 208 } 209} 210 211func parseDefineLine(line, lib string) (key string, value int, ok bool) { 212 if !strings.HasPrefix(line, "#define ") { 213 return 214 } 215 216 fields := strings.Fields(line) 217 if len(fields) != 3 { 218 return 219 } 220 221 key = fields[1] 222 if !strings.HasPrefix(key, lib+"_R_") { 223 return 224 } 225 226 var err error 227 if value, err = strconv.Atoi(fields[2]); err != nil { 228 return 229 } 230 231 ok = true 232 return 233} 234 235func writeHeaderFile(w io.Writer, headerFile io.Reader, lib string, reasons map[string]int) error { 236 var last []byte 237 var haveLast, sawDefine bool 238 newLine := []byte("\n") 239 240 scanner := bufio.NewScanner(headerFile) 241 for scanner.Scan() { 242 line := scanner.Text() 243 _, _, ok := parseDefineLine(line, lib) 244 if ok { 245 sawDefine = true 246 continue 247 } 248 249 if haveLast { 250 w.Write(last) 251 w.Write(newLine) 252 } 253 254 if len(line) > 0 || !sawDefine { 255 last = []byte(line) 256 haveLast = true 257 } else { 258 haveLast = false 259 } 260 sawDefine = false 261 } 262 263 if err := scanner.Err(); err != nil { 264 return err 265 } 266 267 outputAssignments(w, reasons) 268 w.Write(newLine) 269 270 if haveLast { 271 w.Write(last) 272 w.Write(newLine) 273 } 274 275 return nil 276} 277 278func outputStrings(w io.Writer, lib string, assignments map[string]int) { 279 lib = strings.ToUpper(lib) 280 prefixLen := len(lib + "_R_") 281 282 keys := make([]string, 0, len(assignments)) 283 for key := range assignments { 284 keys = append(keys, key) 285 } 286 sort.Strings(keys) 287 288 for _, key := range keys { 289 fmt.Fprintf(w, "%s,%d,%s\n", lib, assignments[key], key[prefixLen:]) 290 } 291} 292 293func assignNewValues(assignments map[string]int, reserved int) { 294 // Needs to be in sync with the reason limit in 295 // |ERR_reason_error_string|. 296 max := 99 297 298 for _, value := range assignments { 299 if reserved >= 0 && value >= reserved { 300 continue 301 } 302 if value > max { 303 max = value 304 } 305 } 306 307 max++ 308 309 // Sort the keys, so this script is reproducible. 310 keys := make([]string, 0, len(assignments)) 311 for key, value := range assignments { 312 if value == -1 { 313 keys = append(keys, key) 314 } 315 } 316 sort.Strings(keys) 317 318 for _, key := range keys { 319 if reserved >= 0 && max >= reserved { 320 // If this happens, try passing -reset. Otherwise bump 321 // up reservedReasonCode. 322 panic("Automatically-assigned values exceeded limit!") 323 } 324 assignments[key] = max 325 max++ 326 } 327} 328 329func handleDeclareMacro(line, prefix, join, macroName string, m map[string]int) { 330 if i := strings.Index(line, macroName); i >= 0 { 331 contents := line[i+len(macroName):] 332 if i := strings.Index(contents, ")"); i >= 0 { 333 contents = contents[:i] 334 args := strings.Split(contents, ",") 335 for i := range args { 336 args[i] = strings.TrimSpace(args[i]) 337 } 338 if len(args) != 2 { 339 panic("Bad macro line: " + line) 340 } 341 if args[0] == prefix { 342 token := args[0] + join + args[1] 343 if _, ok := m[token]; !ok { 344 m[token] = -1 345 } 346 } 347 } 348 } 349} 350 351func addReasons(reasons map[string]int, filename, prefix string) error { 352 file, err := os.Open(filename) 353 if err != nil { 354 return err 355 } 356 defer file.Close() 357 358 reasonPrefix := prefix + "_R_" 359 360 scanner := bufio.NewScanner(file) 361 for scanner.Scan() { 362 line := scanner.Text() 363 364 handleDeclareMacro(line, prefix, "_R_", "OPENSSL_DECLARE_ERROR_REASON(", reasons) 365 366 for len(line) > 0 { 367 i := strings.Index(line, prefix+"_") 368 if i == -1 { 369 break 370 } 371 372 line = line[i:] 373 end := strings.IndexFunc(line, func(r rune) bool { 374 return !(r == '_' || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')) 375 }) 376 if end == -1 { 377 end = len(line) 378 } 379 380 var token string 381 token, line = line[:end], line[end:] 382 383 switch { 384 case strings.HasPrefix(token, reasonPrefix): 385 if _, ok := reasons[token]; !ok { 386 reasons[token] = -1 387 } 388 } 389 } 390 } 391 392 return scanner.Err() 393} 394 395func parseHeader(lib string, file io.Reader) (reasons map[string]int, err error) { 396 reasons = make(map[string]int) 397 398 scanner := bufio.NewScanner(file) 399 for scanner.Scan() { 400 key, value, ok := parseDefineLine(scanner.Text(), lib) 401 if !ok { 402 continue 403 } 404 405 reasons[key] = value 406 } 407 408 err = scanner.Err() 409 return 410} 411 412func main() { 413 flag.Parse() 414 if flag.NArg() == 0 { 415 fmt.Fprintf(os.Stderr, "Usage: make_errors.go LIB [LIB2...]\n") 416 os.Exit(1) 417 } 418 419 for _, lib := range flag.Args() { 420 if err := makeErrors(lib, *resetFlag); err != nil { 421 fmt.Fprintf(os.Stderr, "Error generating errors for %q: %s\n", lib, err) 422 os.Exit(1) 423 } 424 } 425} 426