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