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