1 /*
2 * The dhcpd-pools has BSD 2-clause license which also known as "Simplified
3 * BSD License" or "FreeBSD License".
4 *
5 * Copyright 2006- Sami Kerola. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the
17 * distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 * The views and conclusions contained in the software and documentation are
32 * those of the authors and should not be interpreted as representing
33 * official policies, either expressed or implied, of Sami Kerola.
34 */
35
36 /*! \file getdata.c
37 * \brief Functions to read data from dhcpd.conf and dhcdp.leases files.
38 */
39
40 #include <config.h>
41
42 #include <arpa/inet.h>
43 #include <ctype.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <limits.h>
47 #include <netinet/in.h>
48 #include <stddef.h>
49 #include <stdint.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <sys/stat.h>
53
54 #include "error.h"
55 #include "xalloc.h"
56
57 #include "dhcpd-pools.h"
58
59 /*! \enum dhcpd_magic_numbers
60 * \brief MAXLEN is maximum expected line length in dhcpd.conf and
61 * dhcpd.leases.
62 */
63 enum dhcpd_magic_numbers {
64 MAXLEN = 1024
65 };
66
67 /*! \enum isc_conf_parser
68 * \brief Configuration file parsing state flags. The
69 * is_interesting_config_clause() will return one of these to parse_config().
70 */
71 enum isc_conf_parser {
72 ITS_NOTHING_INTERESTING,
73 ITS_A_RANGE_FIRST_IP,
74 ITS_A_RANGE_SECOND_IP,
75 ITS_A_SHAREDNET,
76 ITS_AN_INCLUDE,
77 ITS_A_SUBNET,
78 ITS_A_NETMASK
79 };
80
81 /*! \brief Lease file parser. The parser can only read ISC DHCPD
82 * dhcpd.leases file format. */
parse_leases(struct conf_t * state)83 int parse_leases(struct conf_t *state)
84 {
85 FILE *dhcpd_leases;
86 char *line, *ipstring, macstring[20], *stop;
87 union ipaddr_t addr;
88 struct leases_t *lease;
89
90 dhcpd_leases = fopen(state->dhcpdlease_file, "r");
91 if (dhcpd_leases == NULL)
92 error(EXIT_FAILURE, errno, "parse_leases: %s", state->dhcpdlease_file);
93 #ifdef HAVE_POSIX_FADVISE
94 # ifdef POSIX_FADV_SEQUENTIAL
95 if (posix_fadvise(fileno(dhcpd_leases), 0, 0, POSIX_FADV_SEQUENTIAL) != 0)
96 error(EXIT_FAILURE, errno, "parse_leases: fadvise %s", state->dhcpdlease_file);
97 # endif /* POSIX_FADV_SEQUENTIAL */
98 #endif /* HAVE_POSIX_FADVISE */
99 line = xmalloc(sizeof(char) * MAXLEN);
100 line[0] = '\0';
101 ipstring = xmalloc(sizeof(char) * MAXLEN);
102 ipstring[0] = '\0';
103 while (!feof(dhcpd_leases)) {
104 if (!fgets(line, MAXLEN, dhcpd_leases) && ferror(dhcpd_leases))
105 error(EXIT_FAILURE, errno, "parse_leases: %s", state->dhcpdlease_file);
106 switch (xstrstr(state, line)) {
107 /* It's a lease, save IP */
108 case PREFIX_LEASE:
109 stop =
110 memccpy(ipstring,
111 line + (state->ip_version ==
112 IPv4 ? 6 : 9), ' ', strlen(line));
113 if (stop != NULL) {
114 --stop;
115 *stop = '\0';
116 }
117 parse_ipaddr(state, ipstring, &addr);
118 break;
119 case PREFIX_BINDING_STATE_FREE:
120 case PREFIX_BINDING_STATE_ABANDONED:
121 case PREFIX_BINDING_STATE_EXPIRED:
122 case PREFIX_BINDING_STATE_RELEASED:
123 if ((lease = find_lease(state, &addr)) != NULL)
124 delete_lease(state, lease);
125 add_lease(state, &addr, FREE);
126 break;
127 case PREFIX_BINDING_STATE_ACTIVE:
128 /* remove old entry, if exists */
129 if ((lease = find_lease(state, &addr)) != NULL)
130 delete_lease(state, lease);
131 add_lease(state, &addr, ACTIVE);
132 break;
133 case PREFIX_BINDING_STATE_BACKUP:
134 /* remove old entry, if exists */
135 if ((lease = find_lease(state, &addr)) != NULL)
136 delete_lease(state, lease);
137 add_lease(state, &addr, BACKUP);
138 state->backups_found = 1;
139 break;
140 case PREFIX_HARDWARE_ETHERNET:
141 if (state->print_mac_addreses == 0)
142 break;
143 memcpy(macstring, line + 20, 17);
144 macstring[17] = '\0';
145 if ((lease = find_lease(state, &addr)) != NULL)
146 lease->ethernet = xstrdup(macstring);
147 break;
148 default:
149 /* do nothing */ ;
150 }
151 }
152 #undef HAS_PREFIX
153 free(line);
154 free(ipstring);
155 fclose(dhcpd_leases);
156 return 0;
157 }
158
159 /*! \brief Keyword search in dhcpd.conf file.
160 * \param s A line from the dhcpd.conf file.
161 * \return Indicator what configuration was found. */
is_interesting_config_clause(struct conf_t * state,char const * restrict s)162 static int is_interesting_config_clause(struct conf_t *state, char const *restrict s)
163 {
164 if (strstr(s, "range"))
165 return ITS_A_RANGE_FIRST_IP;
166 if (strstr(s, "shared-network"))
167 return ITS_A_SHAREDNET;
168 if (state->all_as_shared) {
169 if (strstr(s, "subnet"))
170 return ITS_A_SUBNET;
171 if (strstr(s, "netmask"))
172 return ITS_A_NETMASK;
173 }
174 if (strstr(s, "include"))
175 return ITS_AN_INCLUDE;
176 return ITS_NOTHING_INTERESTING;
177 }
178
179 /*! \brief Flip first and last IP in range if they are in unusual order.
180 */
reorder_last_first(struct range_t * range_p)181 static void reorder_last_first(struct range_t *range_p)
182 {
183 if (ipcomp(&range_p->first_ip, &range_p->last_ip) > 0) {
184 union ipaddr_t tmp;
185
186 tmp = range_p->first_ip;
187 range_p->first_ip = range_p->last_ip;
188 range_p->last_ip = tmp;
189 }
190 }
191
192 /*! \brief The dhcpd.conf file parser.
193 * FIXME: This spaghetti monster function needs to be rewrote at least
194 * ones more.
195 */
parse_config(struct conf_t * state,const int is_include,const char * restrict config_file,struct shared_network_t * restrict shared_p)196 void parse_config(struct conf_t *state, const int is_include, const char *restrict config_file,
197 struct shared_network_t *restrict shared_p)
198 {
199 FILE *dhcpd_config;
200 int newclause = 1, comment = 0, one_ip_range = 0; /* booleans */
201 int quote = 0, braces = 0, argument = ITS_NOTHING_INTERESTING;
202 size_t i = 0;
203 char *word;
204 int braces_shared = 1000;
205 union ipaddr_t addr;
206 struct range_t *range_p = NULL;
207
208 word = xmalloc(sizeof(char) * MAXLEN);
209 if (is_include)
210 /* Default place holder for ranges "All networks". */
211 shared_p->name = state->shared_net_root->name;
212 /* Open configuration file */
213 dhcpd_config = fopen(config_file, "r");
214 if (dhcpd_config == NULL)
215 error(EXIT_FAILURE, errno, "parse_config: %s", config_file);
216 #ifdef HAVE_POSIX_FADVISE
217 # ifdef POSIX_FADV_SEQUENTIAL
218 if (posix_fadvise(fileno(dhcpd_config), 0, 0, POSIX_FADV_SEQUENTIAL) != 0)
219 error(EXIT_FAILURE, errno, "parse_config: fadvise %s", config_file);
220 # endif /* POSIX_FADV_SEQUENTIAL */
221 #endif /* HAVE_POSIX_FADVISE */
222 /* Very hairy stuff begins. */
223 while (unlikely(!feof(dhcpd_config))) {
224 int c;
225
226 c = fgetc(dhcpd_config);
227 if (CHAR_MAX < c)
228 continue;
229 /* Certain characters are magical */
230 switch (c) {
231 /* Handle comments if they are not quoted */
232 case '#':
233 if (quote == 0)
234 comment = 1;
235 continue;
236 case '"':
237 if (comment == 0) {
238 quote++;
239 /* Either one or zero */
240 quote = quote % 2;
241 }
242 continue;
243 case '\n':
244 /* New line resets comment section, but
245 * not if quoted */
246 if (quote == 0)
247 comment = 0;
248 break;
249 case ';':
250 /* Quoted colon does not mean new clause */
251 if (0 < quote)
252 break;
253 if (comment == 0
254 && argument != ITS_A_RANGE_FIRST_IP
255 && argument != ITS_A_RANGE_SECOND_IP && argument != ITS_AN_INCLUDE) {
256 newclause = 1;
257 i = 0;
258 } else if (argument == ITS_A_RANGE_FIRST_IP && one_ip_range == 1) {
259 argument = ITS_A_RANGE_SECOND_IP;
260 c = ' ';
261 } else if (argument == ITS_A_RANGE_SECOND_IP && 0 < i) {
262 /* Range ends to ; and this hair in code
263 * make two ranges wrote together like...
264 *
265 * range 10.20.30.40 10.20.30.41;range 10.20.30.42 10.20.30.43;
266 *
267 * ...to be interpreted correctly. */
268 c = ' ';
269 break;
270 } else if (argument == ITS_A_RANGE_SECOND_IP && i == 0) {
271 if (!range_p) {
272 long int pos;
273 pos = ftell(dhcpd_config);
274 error(EXIT_FAILURE, 0, "parse_config: parsing failed at position: %ld", pos);
275 }
276 range_p->last_ip = range_p->first_ip;
277 goto newrange;
278 }
279 continue;
280 case '{':
281 if (0 < quote)
282 break;
283 if (comment == 0)
284 braces++;
285 /* i == 0 detects word that ends to brace like:
286 *
287 * shared-network DSL{ ... */
288 if (i == 0) {
289 newclause = 1;
290 continue;
291 }
292 break;
293 case '}':
294 if (0 < quote)
295 break;
296 if (comment == 0) {
297 braces--;
298 /* End of shared-network */
299 if (braces_shared == braces) {
300 /* FIXME: Using 1000 is lame, but
301 * works. */
302 braces_shared = 1000;
303 shared_p = state->shared_net_root;
304 }
305 /* Not literally 1, but works for this
306 * program */
307 newclause = 1;
308 }
309 continue;
310 default:
311 break;
312 }
313 /* Either inside comment or Nth word of clause. */
314 if (comment == 1 || (newclause == 0 && argument == ITS_NOTHING_INTERESTING))
315 continue;
316 /* Strip white spaces before new clause word. */
317 if ((newclause == 1 || argument != ITS_NOTHING_INTERESTING)
318 && isspace(c) && i == 0 && one_ip_range == 0)
319 continue;
320 /* Save to word which clause this is. */
321 if ((newclause == 1 || argument != ITS_NOTHING_INTERESTING)
322 && (!isspace(c) || 0 < quote)) {
323 word[i] = (char) c;
324 i++;
325 /* Long word which is almost causing overflow. None
326 * of words are this long which the program is
327 * searching. */
328 if (MAXLEN == i) {
329 newclause = 0;
330 i = 0;
331 continue;
332 }
333 }
334 /* See if clause is something that parser is looking for. */
335 else if (newclause == 1) {
336 /* Insert string end & set state */
337 word[i] = '\0';
338 if (word[i - 1] != '{')
339 newclause = 0;
340 i = 0;
341 argument = is_interesting_config_clause(state, word);
342 if (argument == ITS_A_RANGE_FIRST_IP)
343 one_ip_range = 1;
344 }
345 /* words after range, shared-network or include */
346 else if (argument != ITS_NOTHING_INTERESTING) {
347 word[i] = '\0';
348 newclause = 0;
349 i = 0;
350
351 switch (argument) {
352 case ITS_A_RANGE_SECOND_IP:
353 /* printf ("range 2nd ip: %s\n", word); */
354 range_p = state->ranges + state->num_ranges;
355 argument = ITS_NOTHING_INTERESTING;
356 if (strchr(word, '/')) {
357 parse_cidr(state, range_p, word);
358 one_ip_range = 0;
359 } else {
360 /* not cidr */
361 parse_ipaddr(state, word, &addr);
362 if (one_ip_range == 1) {
363 one_ip_range = 0;
364 copy_ipaddr(&range_p->first_ip, &addr);
365 }
366 copy_ipaddr(&range_p->last_ip, &addr);
367 reorder_last_first(range_p);
368 }
369 newrange:
370 range_p->count = 0;
371 range_p->touched = 0;
372 range_p->backups = 0;
373 range_p->shared_net = shared_p;
374 state->num_ranges++;
375 if (state->ranges_size <= state->num_ranges) {
376 state->ranges_size *= 2;
377 state->ranges = xrealloc(state->ranges, sizeof(struct range_t) * state->ranges_size);
378 range_p = state->ranges + state->num_ranges;
379 }
380 newclause = 1;
381 break;
382 case ITS_A_RANGE_FIRST_IP:
383 /* printf ("range 1st ip: %s\n", word); */
384 range_p = state->ranges + state->num_ranges;
385 if (!(parse_ipaddr(state, word, &addr)))
386 /* word was not ip, try again */
387 break;
388 copy_ipaddr(&range_p->first_ip, &addr);
389 one_ip_range = 0;
390 argument = ITS_A_RANGE_SECOND_IP;
391 break;
392 case ITS_A_SHAREDNET:
393 case ITS_A_SUBNET:
394 /* ignore subnets inside a shared-network */
395 if (argument == ITS_A_SUBNET && shared_p != state->shared_net_root) {
396 argument = ITS_NOTHING_INTERESTING;
397 break;
398 }
399 state->shared_net_head->next = xcalloc(sizeof(struct shared_network_t), 1);
400 state->shared_net_head = state->shared_net_head->next;
401 shared_p = state->shared_net_head;
402 shared_p->name = xstrdup(word);
403 shared_p->netmask = (argument == ITS_A_SUBNET ? -1 : 0); /* do not fill in netmask */
404 /* record network's mask too */
405 if (argument == ITS_A_SUBNET)
406 newclause = 1;
407 argument = ITS_NOTHING_INTERESTING;
408 braces_shared = braces;
409 break;
410 case ITS_A_NETMASK:
411 /* fill in only when requested to do so */
412 if (shared_p->netmask) {
413 if (!(parse_ipaddr(state, word, &addr)))
414 break;
415 shared_p->netmask = 32;
416 while ((addr.v4 & 0x01) == 0) {
417 addr.v4 >>= 1;
418 shared_p->netmask--;
419 }
420 snprintf(word, MAXLEN-1, "%s/%d", shared_p->name, shared_p->netmask);
421 if (shared_p->name)
422 free(shared_p->name);
423 shared_p->name = xstrdup(word);
424 }
425 argument = ITS_NOTHING_INTERESTING;
426 braces_shared = braces;
427 break;
428 case ITS_AN_INCLUDE:
429 /* printf ("include file: %s\n", word); */
430 argument = ITS_NOTHING_INTERESTING;
431 parse_config(state, 0, word, shared_p);
432 newclause = 1;
433 break;
434 case ITS_NOTHING_INTERESTING:
435 /* printf ("nothing interesting: %s\n", word); */
436 argument = ITS_NOTHING_INTERESTING;
437 break;
438 default:
439 puts("impossible occurred, report a bug");
440 abort();
441 }
442 }
443 }
444 free(word);
445 fclose(dhcpd_config);
446 return;
447 }
448