parse_leases: exit at read error
[debian/dhcpd-pools.git] / src / getdata.c
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 <COPYRIGHT HOLDER> ``AS IS'' AND ANY EXPRESS
20  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
29  * 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 #ifdef HAVE_CONFIG_H
37 #include <config.h>
38 #endif
39
40 #ifdef  HAVE_STDLIB_H
41 #include <stdlib.h>
42 #else                           /* Not STDC_HEADERS */
43 extern char *malloc();
44 #define EXIT_FAILURE    1       /* Failing exit status.  */
45 #define EXIT_SUCCESS    0       /* Successful exit status.  */
46 #endif                          /* STDC_HEADERS */
47
48 #ifdef  HAVE_STRING_H
49 #include <string.h>
50 #else
51 #include <strings.h>
52 #endif
53
54 #include <arpa/inet.h>
55 #include <assert.h>
56 #include <ctype.h>
57 #include <err.h>
58 #include <errno.h>
59 #include <fcntl.h>
60 #include <features.h>
61 #include <netinet/in.h>
62 #include <stddef.h>
63 #include <stdio.h>
64 #include <sys/stat.h>
65 #ifndef _XOPEN_SOURCE
66 #define _XOPEN_SOURCE 600
67 #endif
68
69 #include "dhcpd-pools.h"
70 #include "defaults.h"
71
72 /* Parse dhcpd.leases file. All performance boosts for this function are
73  * wellcome */
74 int parse_leases(void)
75 {
76         FILE *dhcpd_leases;
77         char *line, *ipstring, *macstring = NULL;
78         struct in_addr inp;
79         struct stat lease_file_stats;
80         struct macaddr_t *macaddr_p = NULL;
81         unsigned long leasesmallocsize;
82         unsigned long touchesmallocsize;
83         unsigned long backupsmallocsize;
84         int sw_active_lease = 0;
85
86         num_touches = num_leases = num_backups = 0;
87
88         dhcpd_leases = fopen(config.dhcpdlease_file, "r");
89         if (dhcpd_leases == NULL) {
90                 err(EXIT_FAILURE, "parse_leases: %s",
91                     config.dhcpdlease_file);
92         }
93 #ifdef POSIX_FADV_WILLNEED
94         posix_fadvise((long) dhcpd_leases, 0, 0, POSIX_FADV_WILLNEED);
95         if (errno) {
96                 err(EXIT_FAILURE, "parse_leases: fadvise %s",
97                     config.dhcpdlease_file);
98         }
99 #endif                          /* POSIX_FADV_WILLNEED */
100 #ifdef POSIX_FADV_SEQUENTIAL
101         posix_fadvise((long) dhcpd_leases, 0, 0, POSIX_FADV_SEQUENTIAL);
102         if (errno) {
103                 err(EXIT_FAILURE, "parse_leases: fadvise %s",
104                     config.dhcpdlease_file);
105         }
106 #endif                          /* POSIX_FADV_SEQUENTIAL */
107
108         /* I found out that there's one lease address per 300 bytes in
109          * dhcpd.leases file. Malloc is little bit pessimistic and uses 250.
110          * If someone has higher density in lease file I'm interested to
111          * hear about that. */
112         if (stat(config.dhcpdlease_file, &lease_file_stats)) {
113                 err(EXIT_FAILURE, "parse_leases: %s",
114                     config.dhcpdlease_file);
115         }
116         leasesmallocsize = (lease_file_stats.st_size / 250) + MAXLEN - 2;
117         touchesmallocsize = (lease_file_stats.st_size / 250) + MAXLEN - 2;
118         backupsmallocsize = (lease_file_stats.st_size / 120) + MAXLEN - 2;
119         leases = safe_malloc(sizeof(long int) * leasesmallocsize);
120         touches =
121             safe_malloc((size_t) sizeof(long int) * touchesmallocsize);
122
123         line = safe_malloc(sizeof(long int) * MAXLEN);
124         ipstring = safe_malloc(sizeof(long int) * MAXLEN);
125         if (config.output_format[0] == 'X') {
126                 macstring = safe_malloc(sizeof(char) * 18);
127                 macaddr = safe_malloc(sizeof(struct macaddr_t));
128                 macaddr_p = macaddr;
129                 macaddr_p->next = NULL;
130         }
131
132         while (!feof(dhcpd_leases)) {
133                 if (!fgets(line, MAXLEN, dhcpd_leases) && ferror(dhcpd_leases)) {
134                         err(EXIT_FAILURE, "parse_leases: %s", config.dhcpdlease_file);
135                 }
136                 /* It's a lease, save IP */
137                 if (strstr(line, "lease") == line) {
138                         strncpy(ipstring, line, (size_t) MAXLEN);
139                         nth_field(2, ipstring, ipstring);
140                         inet_aton(ipstring, &inp);
141                         sw_active_lease = 0;
142                 }
143                 /* Copy IP to correct array */
144                 else if (strstr(line, "binding state active")) {
145                         leases[num_leases] = htonl(inp.s_addr);
146                         num_leases++;
147                         assert(!(leasesmallocsize < num_leases));
148                         sw_active_lease = 1;
149                 } else if (strstr(line, "  binding state free")) {
150                         touches[num_touches] = htonl(inp.s_addr);
151                         num_touches++;
152                         assert(!(touchesmallocsize < num_touches));
153                 } else if (strstr(line, "  binding state backup")) {
154                         if (num_backups == 0) {
155                                 backups =
156                                     safe_malloc((size_t) sizeof(long int) *
157                                                 backupsmallocsize);
158                         }
159                         backups[num_backups] = htonl(inp.s_addr);
160                         num_backups++;
161                         assert(!(backupsmallocsize < num_backups));
162                 }
163
164                 if ((macaddr != NULL)
165                     && (sw_active_lease == 1)
166                     && (strstr(line, "hardware ethernet"))) {
167                         nth_field(3, macstring, line);
168                         macstring[17] = '\0';
169                         macaddr_p->ethernet = safe_strdup(macstring);
170                         macaddr_p->ip = safe_strdup(ipstring);
171                         macaddr_p->next =
172                             safe_malloc(sizeof(struct macaddr_t));
173                         macaddr_p = macaddr_p->next;
174                         macaddr_p->next = NULL;
175                 }
176         }
177         free(line);
178         free(ipstring);
179         if (macaddr != NULL) {
180                 free(macstring);
181         }
182         fclose(dhcpd_leases);
183         return 0;
184 }
185
186 /* Like strcpy but for field which is separated by white spaces. Number of
187  * first field is 1 and not 0 like C programs should have. Question of
188  * semantics, send mail to author if this annoys. All performance boosts for
189  * this function are well come. */
190 int nth_field(int n, char *dest, const char *src)
191 {
192         int i, j = 0, wordn = 0, len;
193
194         len = strlen(src);
195
196         for (i = 0; i < len; i++) {
197                 if (isspace(src[i])) {
198                         if (!(wordn < n)) {
199                                 dest[j] = '\0';
200                                 break;
201                         }
202                         j = 0;
203                 } else {
204                         if (j == 0) {
205                                 wordn++;
206                         }
207                         if (wordn == n) {
208                                 dest[j] = src[i];
209                         }
210                         j++;
211                 }
212         }
213         return 0;
214 }
215
216 /* dhcpd.conf interesting words */
217 int is_interesting_config_clause(char *s)
218 {
219         if (strstr(s, "range")) {
220                 return 3;
221         } else if (strstr(s, "shared-network")) {
222                 return 1;
223         } else if (strstr(s, "include")) {
224                 return 4;
225         } else {
226                 return 0;
227         }
228 }
229
230 /* FIXME: This spagetti monster function need to be rewrote at least ones. */
231 void parse_config(int is_include, char *config_file,
232                   struct shared_network_t *shared_p)
233 {
234         FILE *dhcpd_config;
235         int i = 0, newclause = true, argument = false, comment =
236             false, braces = 0, quote = false;
237         char *word, c;
238         int braces_shared = 1000;
239         struct in_addr inp;
240         struct range_t *range_p;
241
242         word = safe_malloc(sizeof(char) * MAXLEN);
243
244         if (is_include) {
245                 /* Default place holder for ranges "All networks". */
246                 shared_p->name = shared_networks->name;
247         }
248
249         /* Open configuration file */
250         dhcpd_config = fopen(config_file, "r");
251         if (dhcpd_config == NULL) {
252                 err(EXIT_FAILURE, "parse_config: %s", config_file);
253         }
254 #ifdef POSIX_FADV_WILLNEED
255         posix_fadvise((long) dhcpd_config, 0, 0, POSIX_FADV_WILLNEED);
256         if (errno) {
257                 err(EXIT_FAILURE, "parse_config: fadvise %s", config_file);
258         }
259 #endif                          /* POSIX_FADV_WILLNEED */
260 #ifdef POSIX_FADV_SEQUENTIAL
261         posix_fadvise((long) dhcpd_config, 0, 0, POSIX_FADV_SEQUENTIAL);
262         if (errno) {
263                 err(EXIT_FAILURE, "parse_config: fadvise %s", config_file);
264         }
265 #endif                          /* POSIX_FADV_SEQUENTIAL */
266
267         /* Very hairy stuff begins. */
268         while (!feof(dhcpd_config)) {
269                 c = fgetc(dhcpd_config);
270                 /* Certain characters are magical */
271                 switch (c) {
272                         /* Handle comments if they are not quoted */
273                 case '#':
274                         if (quote == false) {
275                                 comment = true;
276                         }
277                         continue;
278                 case '"':
279                         if (comment == false) {
280                                 quote++;
281                                 /* Either one or zero */
282                                 quote = quote % 2;
283                         }
284                         continue;
285                 case '\n':
286                         /* New line resets comment section, but
287                          * not if quoted */
288                         if (quote == false) {
289                                 comment = false;
290                         }
291                         break;
292                 case ';':
293                         /* Quoted colon does not mean new clause */
294                         if (quote == true) {
295                                 break;
296                         }
297                         if (comment == false && argument != 2
298                             && argument != 4) {
299                                 newclause = true;
300                                 i = 0;
301                         } else if (argument == 2) {
302                                 /* Range ends to ; and this hair in code
303                                  * make two ranges wrote to gether like...
304                                  *
305                                  * range 10.20.30.40 10.20.30.41;range 10.20.30.42 10.20.30.43;
306                                  *
307                                  * ...to be interpreted correctly. */
308                                 c = ' ';
309                         }
310                         continue;
311                 case '{':
312                         if (quote == true) {
313                                 break;
314                         }
315                         if (comment == false) {
316                                 braces++;
317                         }
318                         /* i == 0 detects word that ends to brace like:
319                          *
320                          * shared-network DSL{ ... */
321                         if (i == 0) {
322                                 newclause = true;
323                         }
324                         continue;
325                 case '}':
326                         if (quote == true) {
327                                 break;
328                         }
329                         if (comment == false) {
330                                 braces--;
331                                 /* End of shared-network */
332                                 if (braces_shared == braces) {
333                                         /* FIXME: Using 1000 is lame, but
334                                          * works. */
335                                         braces_shared = 1000;
336                                         shared_p = shared_networks;
337                                 }
338                                 /* Not literally true, but works for this
339                                  * program */
340                                 newclause = true;
341                         }
342                         continue;
343                 default:
344                         break;
345                 }
346
347                 /* Either inside comment or Nth word of clause. */
348                 if (comment == true
349                     || (newclause == false && argument == 0)) {
350                         continue;
351                 }
352                 /* Strip white spaces before new clause word. */
353                 if ((newclause == true || argument != 0) && isspace(c)
354                     && i == 0) {
355                         continue;
356                 }
357                 /* Save to word which clause this is. */
358                 if ((newclause == true || argument != 0)
359                     && (!isspace(c) || quote == true)) {
360                         word[i] = c;
361                         i++;
362                         /* Long word which is almost causing overflow. None
363                          * of words are this long which the program is
364                          * searching. */
365                         if (MAXLEN < i) {
366                                 newclause = false;
367                                 i = 0;
368                                 continue;
369                         }
370                 }
371                 /* See if clause is something that parser is looking for. */
372                 else if (newclause == true) {
373                         /* Insert string end & set state */
374                         word[i] = '\0';
375                         newclause = false;
376                         i = 0;
377
378                         argument = is_interesting_config_clause(word);
379                 }
380                 /* words after range, shared-network or include */
381                 else if (argument != 0) {
382                         word[i] = '\0';
383                         newclause = false;
384                         i = 0;
385
386                         switch (argument) {
387                         case 2:
388                                 /* printf ("range 2nd ip: %s\n", word); */
389                                 range_p = ranges + num_ranges;
390                                 inet_aton(word, &inp);
391                                 argument = 0;
392                                 range_p->last_ip = htonl(inp.s_addr) + 1;
393                                 range_p->count = 0;
394                                 range_p->touched = 0;
395                                 range_p->backups = 0;
396                                 range_p->shared_net = shared_p;
397                                 num_ranges++;
398                                 if (RANGES < num_ranges + 1) {
399                                         RANGES *= 2;
400                                         ranges =
401                                             safe_realloc(ranges,
402                                                          sizeof(struct
403                                                                 range_t) *
404                                                          RANGES);
405                                         range_p = ranges + num_ranges;
406                                 }
407                                 newclause = true;
408                                 break;
409                         case 3:
410                                 /* printf ("range 1nd ip: %s\n", word); */
411                                 range_p = ranges + num_ranges;
412                                 if (!(inet_aton(word, &inp))) {
413                                         /* word was not ip, try
414                                          * again */
415                                         break;
416                                 }
417                                 range_p->first_ip = htonl(inp.s_addr) - 1;
418                                 argument = 2;
419                                 break;
420                         case 1:
421                                 /* printf ("shared-network named: %s\n", word); */
422                                 num_shared_networks++;
423                                 shared_p =
424                                     shared_networks + num_shared_networks;
425                                 shared_p->name = safe_strdup(word);
426                                 shared_p->available = 0;
427                                 shared_p->used = 0;
428                                 shared_p->touched = 0;
429                                 shared_p->backups = 0;
430                                 if (SHARED_NETWORKS <
431                                     num_shared_networks + 2) {
432                                         /* FIXME: make this
433                                          * away by reallocationg
434                                          * more space. */
435                                         errx(EXIT_FAILURE,
436                                              "parse_config: increase default.h SHARED_NETWORKS and recompile");
437                                 }
438                                 argument = 0;
439                                 braces_shared = braces;
440                                 break;
441                         case 4:
442                                 /* printf ("include file: %s\n", word); */
443                                 argument = 0;
444                                 parse_config(false, word, shared_p);
445                                 newclause = true;
446                                 break;
447                         case 0:
448                                 /* printf ("nothing interesting: %s\n", word); */
449                                 argument = 0;
450                                 break;
451                         default:
452                                 warnx("impossible occurred, report a bug");
453                                 assert(0);
454                         }
455                 }
456         }
457         free(word);
458         fclose(dhcpd_config);
459         return;
460 }