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