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