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