The xml output is dirty and marked as FIXME
[debian/dhcpd-pools.git] / src / getdata.c
1 /*
2 ** Copyright (C) 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 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
53 #include "dhcpd-pools.h"
54 #include "defaults.h"
55
56 /* Parse dhcpd.leases file. All performance boosts for this
57  * function are wellcome */
58 int parse_leases(void)
59 {
60         FILE *dhcpd_leases;
61         char *line, *ipstring, *macstring, *macstring2;
62         struct in_addr inp;
63         struct stat lease_file_stats;
64         unsigned long leasesmallocsize;
65         unsigned long touchesmallocsize;
66         unsigned long backupsmallocsize;
67         int sw_active_lease = 0;
68
69         num_touches = num_leases = num_backups = 0;
70
71         dhcpd_leases = fopen(config.dhcpdlease_file, "r");
72         if (dhcpd_leases == NULL) {
73                 eprintf("parse_leases: %s:", config.dhcpdlease_file);
74                 exit(EXIT_FAILURE);
75         }
76 #ifdef POSIX_FADV_NOREUSE
77         posix_fadvise((long) dhcpd_leases, 0, 0, POSIX_FADV_NOREUSE);
78         if (errno) {
79                 eprintf("parse_leases: fadvise:");
80                 exit(EXIT_FAILURE);
81         }
82 #endif                          /* POSIX_FADV_NOREUSE */
83 #ifdef POSIX_FADV_SEQUENTIAL
84         posix_fadvise((long) dhcpd_leases, 0, 0, POSIX_FADV_SEQUENTIAL);
85         if (errno) {
86                 eprintf("parse_leases: fadvise:");
87                 exit(EXIT_FAILURE);
88         }
89 #endif                          /* POSIX_FADV_SEQUENTIAL */
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
92          * 250. If someone has higher density in lease file I'm
93          * interested to hear about that. */
94         if (stat(config.dhcpdlease_file, &lease_file_stats)) {
95                 eprintf("parse_leases: %s:", config.dhcpdlease_file);
96                 exit(EXIT_FAILURE);
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         macstring = safe_malloc(sizeof(long int) * MAXLEN);
108         macstring2 = safe_malloc(sizeof(long int) * MAXLEN);
109
110         while (!feof(dhcpd_leases)) {
111                 fgets(line, MAXLEN, dhcpd_leases);
112                 /* It's a lease, save IP */
113                 if (strstr(line, "lease") == line) {
114                         strncpy(ipstring, line, (size_t) MAXLEN);
115                         nth_field(2, ipstring, ipstring);
116                         inet_aton(ipstring, &inp);
117                         sw_active_lease = 0;
118                 }
119                 /* Copy IP to correct array */
120                 else if (strstr(line, "binding state active")) {
121                         leases[num_leases] = htonl(inp.s_addr);
122                         num_leases++;
123                         sw_active_lease = 1;
124                 } else if (strstr(line, "  binding state free")) {
125                         touches[num_touches] = htonl(inp.s_addr);
126                         num_touches++;
127                 } else if (strstr(line, "  binding state backup")) {
128                         if (num_backups == 0) {
129                                 backups =
130                                     safe_malloc((size_t) sizeof(long int) *
131                                                 backupsmallocsize);
132                         }
133                         backups[num_backups] = htonl(inp.s_addr);
134                         num_backups++;
135                 }
136
137                 /* FIXME: move to output.c and use FILE *outfile */
138                 if ((config.output_format[0] == 'X')
139                     && (sw_active_lease == 1)
140                     && (strstr(line, "hardware ethernet"))) {
141                         nth_field(3, macstring, line);
142                         macstring[strlen(macstring) - 1] = '\0';
143
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                 if ((leasesmallocsize < num_leases) ||
150                     (touchesmallocsize < num_touches) ||
151                     (backupsmallocsize < num_backups)) {
152                         printf("WARNING: running out of memory\n");
153                         printf("\tlease/touch/backup = %lu/%lu/%lu\n",
154                                leasesmallocsize, touchesmallocsize,
155                                backupsmallocsize);
156                         printf("\tlease/touch/backup = %lu/%lu/%lu\n",
157                                num_leases, num_touches, num_backups);
158                         printf
159                             ("Code should realloc() and init new memory, but no time to write that now!\n");
160                         exit(EXIT_FAILURE);
161                 }
162         }
163
164         return 0;
165 }
166
167 /* Like strcpy but for field which is separated by white spaces.
168  * Number of first field is 1 and not 0 like C programs should
169  * have. Question of semantics, send mail to author if this
170  * annoys. All performance boosts for this function are well
171  * come. */
172 int nth_field(int n, char *dest, const char *src)
173 {
174         int i, j = 0, wordn = 0, len;
175
176         len = strlen(src);
177
178         for (i = 0; i < len; i++) {
179                 if (isspace(src[i])) {
180                         if (!(wordn < n)) {
181                                 dest[j] = '\0';
182                                 break;
183                         }
184                         j = 0;
185                 } else {
186                         if (j == 0) {
187                                 wordn++;
188                         }
189                         if (wordn == n) {
190                                 dest[j] = src[i];
191                         }
192                         j++;
193                 }
194         }
195         return 0;
196 }
197
198 /* dhcpd.conf interesting words */
199 int is_interesting_config_clause(char *s)
200 {
201         if (strstr(s, "shared-network")) {
202                 return 1;
203         } else if (strstr(s, "range")) {
204                 return 3;
205         } else if (strstr(s, "include")) {
206                 return 4;
207         } else {
208                 return 0;
209         }
210 }
211
212 /* TODO: This spagetti monster function need to be rewrote at
213  * least ones. */
214 char *parse_config(int is_include, char *config_file,
215                    char *current_shared_name,
216                    char *next_free_shared_name,
217                    struct shared_network_t *shared_p)
218 {
219         FILE *dhcpd_config;
220         int i = 0, newclause = true, argument = false, comment =
221             false, braces = 0, quote = false;
222         char *word, c;
223         int braces_shared = 1000;
224         struct in_addr inp;
225         struct range_t *range_p;
226
227         char *last_shared_name;
228         last_shared_name = SHARED_NETWORKS_NAMES + shared_net_names;
229
230         word = safe_malloc(sizeof(char) * MAXLEN);
231
232         if (is_include) {
233                 /* Default place holder for ranges "All networks". */
234                 shared_p->name = current_shared_name;
235         }
236
237         /* Open configuration file */
238         dhcpd_config = fopen(config_file, "r");
239         if (dhcpd_config == NULL) {
240                 eprintf("parse_config: %s:", config_file);
241                 exit(EXIT_FAILURE);
242         }
243 #ifdef POSIX_FADV_NOREUSE
244         posix_fadvise((long) dhcpd_config, 0, 0, POSIX_FADV_NOREUSE);
245         if (errno) {
246                 eprintf("parse_config: fadvise:");
247                 exit(EXIT_FAILURE);
248         }
249 #endif                          /* POSIX_FADV_NOREUSE */
250 #ifdef POSIX_FADV_SEQUENTIAL
251         posix_fadvise((long) dhcpd_config, 0, 0, POSIX_FADV_SEQUENTIAL);
252         if (errno) {
253                 eprintf("parse_config: fadvise:");
254                 exit(EXIT_FAILURE);
255         }
256 #endif                          /* POSIX_FADV_SEQUENTIAL */
257         /* Very hairy stuff begins. */
258         while (!feof(dhcpd_config)) {
259                 c = fgetc(dhcpd_config);
260                 /* Certain characters are magical */
261                 switch (c) {
262                         /* Handle comments if they are not quoted */
263                 case '#':
264                         if (quote == false) {
265                                 comment = true;
266                         }
267                         continue;
268                 case '"':
269                         if (comment == false) {
270                                 quote++;
271                                 /* Either one or zero */
272                                 quote = quote % 2;
273                         }
274                         continue;
275                 case '\n':
276                         /* New line resets comment section, but 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 make two
292                                  * 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                                         /* TODO: Using 1000 is lame, but works. */
325                                         braces_shared = 1000;
326                                         shared_p = shared_networks;
327                                 }
328                                 /* Not literally true, but works for this program */
329                                 newclause = true;
330                         }
331                         continue;
332                 default:
333                         break;
334                 }
335
336                 /* Either inside comment or Nth word of clause. */
337                 if (comment == true
338                     || (newclause == false && argument == 0)) {
339                         continue;
340                 }
341                 /* Strip white spaces before new clause word. */
342                 if ((newclause == true || argument != 0) && isspace(c)
343                     && i == 0) {
344                         continue;
345                 }
346                 /* Save to word which clause this is. */
347                 if ((newclause == true || argument != 0)
348                     && (!isspace(c) || quote == true)) {
349                         word[i] = c;
350                         i++;
351                         /* Long word which is almost causing overflow. Not any of words
352                          * this program is looking for are this long. */
353                         if (MAXLEN < i) {
354                                 newclause = false;
355                                 i = 0;
356                                 continue;
357                         }
358                 }
359                 /* See if clause is something that parser is looking for. */
360                 else if (newclause == true) {
361                         /* Insert string end & set state */
362                         word[i] = '\0';
363                         newclause = false;
364                         i = 0;
365
366                         argument = is_interesting_config_clause(word);
367                 }
368                 /* words after range, shared-network or include */
369                 else if (argument != 0) {
370                         word[i] = '\0';
371                         newclause = false;
372                         i = 0;
373
374                         switch (argument) {
375                         case 0:
376                                 /* printf ("nothing interesting: %s\n", word); */
377                                 argument = 0;
378                                 break;
379                         case 1:
380                                 /* printf ("shared-network named: %s\n", word); */
381                                 strcpy(next_free_shared_name, word);
382                                 shared_p =
383                                     shared_networks + num_shared_networks;
384                                 num_shared_networks++;
385                                 shared_p++;
386                                 shared_p->name = next_free_shared_name;
387                                 shared_p->available = 0;
388                                 shared_p->used = 0;
389                                 shared_p->touched = 0;
390                                 shared_p->backups = 0;
391                                 /* Temporary abuse of argument variable */
392                                 argument =
393                                     strlen(next_free_shared_name) + 1;
394                                 if (last_shared_name >
395                                     next_free_shared_name + argument) {
396                                         next_free_shared_name += argument;
397                                 } else {
398                                         /* TODO: make this go away by reallocationg more space. */
399                                         eprintf
400                                             ("parse_config: End of shared-network space, increase SHARED_NETWORKS_NAMES and recompile");
401                                         exit(EXIT_FAILURE);
402                                 }
403                                 argument = 0;
404                                 braces_shared = braces;
405                                 break;
406                         case 2:
407                                 /* printf ("range 2nd ip: %s\n", word); */
408                                 range_p = ranges + num_ranges;
409                                 inet_aton(word, &inp);
410                                 argument = 0;
411                                 range_p->last_ip = htonl(inp.s_addr) + 1;
412                                 range_p->count = 0;
413                                 range_p->touched = 0;
414                                 range_p->backups = 0;
415                                 range_p->shared_net = shared_p;
416                                 num_ranges++;
417                                 if (RANGES < num_ranges) {
418                                         eprintf
419                                             ("parse_config: Range space full! Increase RANGES and recompile.");
420                                         exit(EXIT_FAILURE);
421                                 }
422                                 newclause = true;
423                                 break;
424                         case 3:
425                                 /* printf ("range 1nd ip: %s\n", word); */
426                                 range_p = ranges + num_ranges;
427                                 if (!(inet_aton(word, &inp))) {
428                                         /* word was not ip, try again */
429                                         break;
430                                 }
431                                 range_p->first_ip = htonl(inp.s_addr) - 1;
432                                 argument = 2;
433                                 break;
434                         case 4:
435                                 /* printf ("include file: %s\n", word); */
436                                 argument = 0;
437                                 next_free_shared_name =
438                                     parse_config(false, word,
439                                                  current_shared_name,
440                                                  next_free_shared_name,
441                                                  shared_p);
442                                 newclause = true;
443                                 break;
444                         default:
445                                 eprintf
446                                     ("parse_config: This cannot happen, report a bug!");
447                                 exit(EXIT_FAILURE);
448                         }
449                 }
450         }
451         free(word);
452         return next_free_shared_name;
453 }