]>
Commit | Line | Data |
---|---|---|
19fa75a9 A |
1 | /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108 -*- |
2 | * | |
3 | * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. | |
4 | * Copyright (c) 2018 Apple Computer, Inc. All rights reserved. | |
5 | * | |
6 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
7 | * you may not use this file except in compliance with the License. | |
8 | * You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, software | |
13 | * distributed under the License is distributed on an "AS IS" BASIS, | |
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
15 | * See the License for the specific language governing permissions and | |
16 | * limitations under the License. | |
17 | * | |
18 | */ | |
19 | ||
20 | //************************************************************************************************************* | |
21 | // | |
22 | // General purpose stupid little parser, currently used by dnssd-proxy for its configuration file | |
23 | // | |
24 | //************************************************************************************************************* | |
25 | // Headers | |
26 | ||
27 | #include <stdio.h> // For printf() | |
28 | #include <stdlib.h> // For malloc() | |
29 | #include <string.h> // For strrchr(), strcmp() | |
30 | #include <time.h> // For "struct tm" etc. | |
31 | #include <signal.h> // For SIGINT, SIGTERM | |
32 | #include <assert.h> | |
33 | #include <netdb.h> // For gethostbyname() | |
34 | #include <sys/socket.h> // For AF_INET, AF_INET6, etc. | |
35 | #include <net/if.h> // For IF_NAMESIZE | |
36 | #include <netinet/in.h> // For INADDR_NONE | |
37 | #include <netinet/tcp.h> // For SOL_TCP, TCP_NOTSENT_LOWAT | |
38 | #include <arpa/inet.h> // For inet_addr() | |
39 | #include <unistd.h> | |
40 | #include <errno.h> | |
41 | #include <fcntl.h> | |
42 | #include <stdbool.h> | |
43 | ||
44 | #include "config-parse.h" | |
45 | ||
46 | #ifdef STANDALONE | |
47 | #undef LogMsg | |
48 | #define LogMsg(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) | |
49 | #endif // STANDALONE | |
50 | ||
51 | // Parse one line of a config file. | |
52 | // A line consists of a verb followed by one or more hunks of text. | |
53 | // We parse the verb first, then that tells us how many hunks of text to expect. | |
54 | // Each hunk is space-delineated; the last hunk can contain spaces. | |
55 | static bool config_parse_line(void *context, const char *filename, char *line, int lineno, | |
56 | config_file_verb_t *verbs, int num_verbs) | |
57 | { | |
58 | char *sp; | |
59 | #define MAXCFHUNKS 10 | |
60 | char *hunks[MAXCFHUNKS]; | |
61 | int num_hunks = 0; | |
62 | config_file_verb_t *config_file_verb = NULL; | |
63 | int i; | |
64 | ||
65 | sp = line; | |
66 | do { | |
67 | // Skip leading spaces. | |
68 | while (*sp && (*sp == ' ' || *sp == '\t')) | |
69 | sp++; | |
70 | if (num_hunks == 0) { | |
71 | // If this is a blank line with spaces on it or a comment line, we ignore it. | |
72 | if (!*sp || *sp == '#') | |
73 | return true; | |
74 | } | |
75 | hunks[num_hunks++] = sp; | |
76 | // Find EOL or hunk | |
77 | while (*sp && (*sp != ' ' && *sp != '\t')) { | |
78 | sp++; | |
79 | } | |
80 | if (*sp) { | |
81 | *sp++ = 0; | |
82 | } | |
83 | if (num_hunks == 1) { | |
84 | for (i = 0; i < num_verbs; i++) { | |
85 | // If the verb name matches, or the verb name is NULL (meaning whatever doesn't | |
86 | // match a preceding verb), we've found our verb. | |
87 | if (verbs[i].name == NULL || !strcmp(verbs[i].name, hunks[0])) { | |
88 | config_file_verb = &verbs[i]; | |
89 | break; | |
90 | } | |
91 | } | |
92 | if (config_file_verb == NULL) { | |
93 | LogMsg("cfParseLine: unknown verb %s at line %d", hunks[0], lineno); | |
94 | return false; | |
95 | } | |
96 | } | |
97 | } while (*sp && num_hunks < MAXCFHUNKS && config_file_verb->max_hunks > num_hunks); | |
98 | ||
99 | // If we didn't get the hunks we needed, bail. | |
100 | if (config_file_verb->min_hunks > num_hunks) { | |
101 | LogMsg("config_file_parse_line: error: verb %s requires between %d and %d modifiers; %d given at line %d", | |
102 | hunks[0], config_file_verb->min_hunks, config_file_verb->max_hunks, num_hunks, lineno); | |
103 | return false; | |
104 | } | |
105 | ||
106 | return config_file_verb->handler(context, filename, hunks, num_hunks, lineno); | |
107 | } | |
108 | ||
109 | // Parse a configuration file | |
110 | bool config_parse(void *context, const char *filename, config_file_verb_t *verbs, int num_verbs) | |
111 | { | |
112 | int file; | |
113 | char *buf, *line, *eof, *eol, *nextCR, *nextNL; | |
114 | off_t flen, have; | |
115 | ssize_t len; | |
116 | int lineno; | |
117 | bool success = true; | |
118 | ||
119 | file = open(filename, O_RDONLY); | |
120 | if (file < 0) { | |
121 | LogMsg("cfParse: fatal: %s: %s", filename, strerror(errno)); | |
122 | return false; | |
123 | } | |
124 | ||
125 | // Get the length of the file. | |
126 | flen = lseek(file, 0, SEEK_END); | |
127 | lseek(file, 0, SEEK_SET); | |
128 | buf = malloc(flen + 1); | |
129 | if (buf == NULL) { | |
130 | LogMsg("cfParse: fatal: not enough memory for %s", filename); | |
131 | goto outclose; | |
132 | } | |
133 | ||
134 | // Just in case we have a read() syscall that doesn't always read the whole file at once | |
135 | have = 0; | |
136 | while (have < flen) { | |
137 | len = read(file, &buf[have], flen - have); | |
138 | if (len < 0) { | |
139 | LogMsg("cfParse: fatal: read of %s at %lld len %lld: %s", | |
140 | filename, (long long)have, (long long)(flen - have), strerror(errno)); | |
141 | goto outfree; | |
142 | } | |
143 | if (len == 0) { | |
144 | LogMsg("cfParse: fatal: read of %s at %lld len %lld: zero bytes read", | |
145 | filename, (long long)have, (long long)(flen - have)); | |
146 | outfree: | |
147 | free(buf); | |
148 | outclose: | |
149 | close(file); | |
150 | return false; | |
151 | } | |
152 | have += len; | |
153 | } | |
154 | close(file); | |
155 | buf[flen] = 0; // NUL terminate. | |
156 | eof = buf + flen; | |
157 | ||
158 | // Parse through the file line by line. | |
159 | line = buf; | |
160 | lineno = 1; | |
161 | while (line < eof) { // < because NUL at eof could be last eol. | |
162 | nextCR = strchr(line, '\r'); | |
163 | nextNL = strchr(line, '\n'); | |
164 | ||
165 | // Added complexity for CR/LF agnostic line endings. Necessary? | |
166 | if (nextNL != NULL) { | |
167 | if (nextCR != NULL && nextCR < nextNL) | |
168 | eol = nextCR; | |
169 | else | |
170 | eol = nextNL; | |
171 | } else { | |
172 | if (nextCR != NULL) | |
173 | eol = nextCR; | |
174 | else | |
175 | eol = buf + flen; | |
176 | } | |
177 | ||
178 | // If this isn't a blank line or a comment line, parse it. | |
179 | if (eol - line != 1 && line[0] != '#') { | |
180 | *eol = 0; | |
181 | // If we get a bad config line, we're going to return failure later, but continue parsing now. | |
182 | if (!config_parse_line(context, filename, line, lineno, verbs, num_verbs)) | |
183 | success = false; | |
184 | } | |
185 | line = eol + 1; | |
186 | lineno++; | |
187 | } | |
188 | free(buf); | |
189 | return success; | |
190 | } | |
191 |