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