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