kernel - mlockall: Return EINVAL for invalid flags combinations.
[dragonfly.git] / sbin / dhclient / clparse.c
1 /*      $OpenBSD: clparse.c,v 1.34 2007/02/14 23:19:26 deraadt Exp $    */
2 /*      $DragonFly: src/sbin/dhclient/clparse.c,v 1.1 2008/08/30 16:07:58 hasso Exp $   */
3
4 /* Parser for dhclient config and lease files... */
5
6 /*
7  * Copyright (c) 1997 The Internet Software Consortium.
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. Neither the name of The Internet Software Consortium nor the names
20  *    of its contributors may be used to endorse or promote products derived
21  *    from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
24  * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
25  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27  * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
28  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
31  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
32  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
34  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  *
37  * This software has been written for the Internet Software Consortium
38  * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
39  * Enterprises.  To learn more about the Internet Software Consortium,
40  * see ``http://www.vix.com/isc''.  To learn more about Vixie
41  * Enterprises, see ``http://www.vix.com''.
42  */
43
44 #include "dhcpd.h"
45 #include "dhctoken.h"
46
47 /*
48  * client-conf-file :== client-declarations EOF
49  * client-declarations :== <nil>
50  *                       | client-declaration
51  *                       | client-declarations client-declaration
52  */
53 int
54 read_client_conf(void)
55 {
56         FILE *cfile;
57         char *val;
58         int token;
59
60         new_parse(path_dhclient_conf);
61
62         /* Set some defaults... */
63         config->link_timeout = 10;
64         config->timeout = 60;
65         config->select_interval = 0;
66         config->reboot_timeout = 10;
67         config->retry_interval = 300;
68         config->backoff_cutoff = 15;
69         config->initial_interval = 3;
70         config->bootp_policy = ACCEPT;
71         config->script_name = _PATH_DHCLIENT_SCRIPT;
72         config->requested_options
73             [config->requested_option_count++] = DHO_SUBNET_MASK;
74         config->requested_options
75             [config->requested_option_count++] = DHO_BROADCAST_ADDRESS;
76         config->requested_options
77             [config->requested_option_count++] = DHO_TIME_OFFSET;
78         config->requested_options
79             [config->requested_option_count++] = DHO_ROUTERS;
80         config->requested_options
81             [config->requested_option_count++] = DHO_DOMAIN_NAME;
82         config->requested_options
83             [config->requested_option_count++] = DHO_DOMAIN_NAME_SERVERS;
84         config->requested_options
85             [config->requested_option_count++] = DHO_HOST_NAME;
86
87         if ((cfile = fopen(path_dhclient_conf, "r")) != NULL) {
88                 do {
89                         token = peek_token(&val, cfile);
90                         if (token == EOF)
91                                 break;
92                         parse_client_statement(cfile);
93                 } while (1);
94                 token = next_token(&val, cfile); /* Clear the peek buffer */
95                 fclose(cfile);
96         }
97
98         return (!warnings_occurred);
99 }
100
101 /*
102  * lease-file :== client-lease-statements EOF
103  * client-lease-statements :== <nil>
104  *                   | client-lease-statements LEASE client-lease-statement
105  */
106 void
107 read_client_leases(void)
108 {
109         FILE    *cfile;
110         char    *val;
111         int      token;
112
113         new_parse(path_dhclient_db);
114
115         /* Open the lease file.   If we can't open it, just return -
116            we can safely trust the server to remember our state. */
117         if ((cfile = fopen(path_dhclient_db, "r")) == NULL)
118                 return;
119         do {
120                 token = next_token(&val, cfile);
121                 if (token == EOF)
122                         break;
123                 if (token != TOK_LEASE) {
124                         warning("Corrupt lease file - possible data loss!");
125                         skip_to_semi(cfile);
126                         break;
127                 } else
128                         parse_client_lease_statement(cfile, 0);
129
130         } while (1);
131         fclose(cfile);
132 }
133
134 /*
135  * client-declaration :==
136  *      TOK_SEND option-decl |
137  *      TOK_DEFAULT option-decl |
138  *      TOK_SUPERSEDE option-decl |
139  *      TOK_APPEND option-decl |
140  *      TOK_PREPEND option-decl |
141  *      TOK_MEDIA string-list |
142  *      hardware-declaration |
143  *      TOK_REQUEST option-list |
144  *      TOK_REQUIRE option-list |
145  *      TOK_TIMEOUT number |
146  *      TOK_RETRY number |
147  *      TOK_SELECT_TIMEOUT number |
148  *      TOK_REBOOT number |
149  *      TOK_BACKOFF_CUTOFF number |
150  *      TOK_INITIAL_INTERVAL number |
151  *      TOK_SCRIPT string |
152  *      interface-declaration |
153  *      TOK_LEASE client-lease-statement |
154  *      TOK_ALIAS client-lease-statement |
155  *      TOK_REJECT reject-statement
156  */
157 void
158 parse_client_statement(FILE *cfile)
159 {
160         char *val;
161         int token, code;
162
163         switch (next_token(&val, cfile)) {
164         case TOK_SEND:
165                 parse_option_decl(cfile, &config->send_options[0]);
166                 return;
167         case TOK_DEFAULT:
168                 code = parse_option_decl(cfile, &config->defaults[0]);
169                 if (code != -1)
170                         config->default_actions[code] = ACTION_DEFAULT;
171                 return;
172         case TOK_SUPERSEDE:
173                 code = parse_option_decl(cfile, &config->defaults[0]);
174                 if (code != -1)
175                         config->default_actions[code] = ACTION_SUPERSEDE;
176                 return;
177         case TOK_APPEND:
178                 code = parse_option_decl(cfile, &config->defaults[0]);
179                 if (code != -1)
180                         config->default_actions[code] = ACTION_APPEND;
181                 return;
182         case TOK_PREPEND:
183                 code = parse_option_decl(cfile, &config->defaults[0]);
184                 if (code != -1)
185                         config->default_actions[code] = ACTION_PREPEND;
186                 return;
187         case TOK_MEDIA:
188                 parse_string_list(cfile, &config->media, 1);
189                 return;
190         case TOK_HARDWARE:
191                 parse_hardware_param(cfile, &ifi->hw_address);
192                 return;
193         case TOK_REQUEST:
194                 config->requested_option_count =
195                         parse_option_list(cfile, config->requested_options);
196                 return;
197         case TOK_REQUIRE:
198                 memset(config->required_options, 0,
199                     sizeof(config->required_options));
200                 parse_option_list(cfile, config->required_options);
201                 return;
202         case TOK_LINK_TIMEOUT:
203                 parse_lease_time(cfile, &config->link_timeout);
204                 return;
205         case TOK_TIMEOUT:
206                 parse_lease_time(cfile, &config->timeout);
207                 return;
208         case TOK_RETRY:
209                 parse_lease_time(cfile, &config->retry_interval);
210                 return;
211         case TOK_SELECT_TIMEOUT:
212                 parse_lease_time(cfile, &config->select_interval);
213                 return;
214         case TOK_REBOOT:
215                 parse_lease_time(cfile, &config->reboot_timeout);
216                 return;
217         case TOK_BACKOFF_CUTOFF:
218                 parse_lease_time(cfile, &config->backoff_cutoff);
219                 return;
220         case TOK_INITIAL_INTERVAL:
221                 parse_lease_time(cfile, &config->initial_interval);
222                 return;
223         case TOK_SCRIPT:
224                 config->script_name = parse_string(cfile);
225                 return;
226         case TOK_INTERFACE:
227                 parse_interface_declaration(cfile);
228                 return;
229         case TOK_LEASE:
230                 parse_client_lease_statement(cfile, 1);
231                 return;
232         case TOK_ALIAS:
233                 parse_client_lease_statement(cfile, 2);
234                 return;
235         case TOK_REJECT:
236                 parse_reject_statement(cfile);
237                 return;
238         default:
239                 parse_warn("expecting a statement.");
240                 skip_to_semi(cfile);
241                 break;
242         }
243         token = next_token(&val, cfile);
244         if (token != ';') {
245                 parse_warn("semicolon expected.");
246                 skip_to_semi(cfile);
247         }
248 }
249
250 int
251 parse_X(FILE *cfile, u_int8_t *buf, int max)
252 {
253         int      token;
254         char    *val;
255         int      len;
256
257         token = peek_token(&val, cfile);
258         if (token == TOK_NUMBER_OR_NAME || token == TOK_NUMBER) {
259                 len = 0;
260                 do {
261                         token = next_token(&val, cfile);
262                         if (token != TOK_NUMBER && token != TOK_NUMBER_OR_NAME) {
263                                 parse_warn("expecting hexadecimal constant.");
264                                 skip_to_semi(cfile);
265                                 return (0);
266                         }
267                         convert_num(&buf[len], val, 16, 8);
268                         if (len++ > max) {
269                                 parse_warn("hexadecimal constant too long.");
270                                 skip_to_semi(cfile);
271                                 return (0);
272                         }
273                         token = peek_token(&val, cfile);
274                         if (token == ':')
275                                 token = next_token(&val, cfile);
276                 } while (token == ':');
277                 val = (char *)buf;
278         } else if (token == TOK_STRING) {
279                 token = next_token(&val, cfile);
280                 len = strlen(val);
281                 if (len + 1 > max) {
282                         parse_warn("string constant too long.");
283                         skip_to_semi(cfile);
284                         return (0);
285                 }
286                 memcpy(buf, val, len + 1);
287         } else {
288                 parse_warn("expecting string or hexadecimal data");
289                 skip_to_semi(cfile);
290                 return (0);
291         }
292         return (len);
293 }
294
295 /*
296  * option-list :== option_name |
297  *                 option_list COMMA option_name
298  */
299 int
300 parse_option_list(FILE *cfile, u_int8_t *list)
301 {
302         int      ix, i;
303         int      token;
304         char    *val;
305
306         ix = 0;
307         do {
308                 token = next_token(&val, cfile);
309                 if (!is_identifier(token)) {
310                         parse_warn("expected option name.");
311                         skip_to_semi(cfile);
312                         return (0);
313                 }
314                 for (i = 0; i < 256; i++)
315                         if (!strcasecmp(dhcp_options[i].name, val))
316                                 break;
317
318                 if (i == 256) {
319                         parse_warn("%s: unexpected option name.", val);
320                         skip_to_semi(cfile);
321                         return (0);
322                 }
323                 list[ix++] = i;
324                 if (ix == 256) {
325                         parse_warn("%s: too many options.", val);
326                         skip_to_semi(cfile);
327                         return (0);
328                 }
329                 token = next_token(&val, cfile);
330         } while (token == ',');
331         if (token != ';') {
332                 parse_warn("expecting semicolon.");
333                 skip_to_semi(cfile);
334                 return (0);
335         }
336         return (ix);
337 }
338
339 /*
340  * interface-declaration :==
341  *      INTERFACE string LBRACE client-declarations RBRACE
342  */
343 void
344 parse_interface_declaration(FILE *cfile)
345 {
346         char *val;
347         int token;
348
349         token = next_token(&val, cfile);
350         if (token != TOK_STRING) {
351                 parse_warn("expecting interface name (in quotes).");
352                 skip_to_semi(cfile);
353                 return;
354         }
355
356         if (strcmp(ifi->name, val) != 0) {
357                 skip_to_semi(cfile);
358                 return;
359         }
360
361         token = next_token(&val, cfile);
362         if (token != '{') {
363                 parse_warn("expecting left brace.");
364                 skip_to_semi(cfile);
365                 return;
366         }
367
368         do {
369                 token = peek_token(&val, cfile);
370                 if (token == EOF) {
371                         parse_warn("unterminated interface declaration.");
372                         return;
373                 }
374                 if (token == '}')
375                         break;
376                 parse_client_statement(cfile);
377         } while (1);
378         token = next_token(&val, cfile);
379 }
380
381 /*
382  * client-lease-statement :==
383  *      RBRACE client-lease-declarations LBRACE
384  *
385  *      client-lease-declarations :==
386  *              <nil> |
387  *              client-lease-declaration |
388  *              client-lease-declarations client-lease-declaration
389  */
390 void
391 parse_client_lease_statement(FILE *cfile, int is_static)
392 {
393         struct client_lease     *lease, *lp, *pl;
394         int                      token;
395         char                    *val;
396
397         token = next_token(&val, cfile);
398         if (token != '{') {
399                 parse_warn("expecting left brace.");
400                 skip_to_semi(cfile);
401                 return;
402         }
403
404         lease = malloc(sizeof(struct client_lease));
405         if (!lease)
406                 error("no memory for lease.");
407         memset(lease, 0, sizeof(*lease));
408         lease->is_static = is_static;
409
410         do {
411                 token = peek_token(&val, cfile);
412                 if (token == EOF) {
413                         parse_warn("unterminated lease declaration.");
414                         return;
415                 }
416                 if (token == '}')
417                         break;
418                 parse_client_lease_declaration(cfile, lease);
419         } while (1);
420         token = next_token(&val, cfile);
421
422         /* If the lease declaration didn't include an interface
423          * declaration that we recognized, it's of no use to us.
424          */
425         if (!ifi) {
426                 free_client_lease(lease);
427                 return;
428         }
429
430         /* If this is an alias lease, it doesn't need to be sorted in. */
431         if (is_static == 2) {
432                 client->alias = lease;
433                 return;
434         }
435
436         /*
437          * The new lease may supersede a lease that's not the active
438          * lease but is still on the lease list, so scan the lease list
439          * looking for a lease with the same address, and if we find it,
440          * toss it.
441          */
442         pl = NULL;
443         for (lp = client->leases; lp; lp = lp->next) {
444                 if (lp->address.len == lease->address.len &&
445                     !memcmp(lp->address.iabuf, lease->address.iabuf,
446                     lease->address.len)) {
447                         if (pl)
448                                 pl->next = lp->next;
449                         else
450                                 client->leases = lp->next;
451                         free_client_lease(lp);
452                         break;
453                 }
454         }
455
456         /*
457          * If this is a preloaded lease, just put it on the list of
458          * recorded leases - don't make it the active lease.
459          */
460         if (is_static) {
461                 lease->next = client->leases;
462                 client->leases = lease;
463                 return;
464         }
465
466         /*
467          * The last lease in the lease file on a particular interface is
468          * the active lease for that interface.    Of course, we don't
469          * know what the last lease in the file is until we've parsed
470          * the whole file, so at this point, we assume that the lease we
471          * just parsed is the active lease for its interface.   If
472          * there's already an active lease for the interface, and this
473          * lease is for the same ip address, then we just toss the old
474          * active lease and replace it with this one.   If this lease is
475          * for a different address, then if the old active lease has
476          * expired, we dump it; if not, we put it on the list of leases
477          * for this interface which are still valid but no longer
478          * active.
479          */
480         if (client->active) {
481                 if (client->active->expiry < cur_time)
482                         free_client_lease(client->active);
483                 else if (client->active->address.len ==
484                     lease->address.len &&
485                     !memcmp(client->active->address.iabuf,
486                     lease->address.iabuf, lease->address.len))
487                         free_client_lease(client->active);
488                 else {
489                         client->active->next = client->leases;
490                         client->leases = client->active;
491                 }
492         }
493         client->active = lease;
494
495         /* Phew. */
496 }
497
498 /*
499  * client-lease-declaration :==
500  *      BOOTP |
501  *      INTERFACE string |
502  *      FIXED_ADDR ip_address |
503  *      FILENAME string |
504  *      SERVER_NAME string |
505  *      OPTION option-decl |
506  *      RENEW time-decl |
507  *      REBIND time-decl |
508  *      EXPIRE time-decl
509  */
510 void
511 parse_client_lease_declaration(FILE *cfile, struct client_lease *lease)
512 {
513         char *val;
514         int token;
515
516         switch (next_token(&val, cfile)) {
517         case TOK_BOOTP:
518                 lease->is_bootp = 1;
519                 break;
520         case TOK_INTERFACE:
521                 token = next_token(&val, cfile);
522                 if (token != TOK_STRING) {
523                         parse_warn("expecting interface name (in quotes).");
524                         skip_to_semi(cfile);
525                         break;
526                 }
527                 if (strcmp(ifi->name, val) != 0) {
528                         parse_warn("wrong interface name. Expecting '%s'.",
529                            ifi->name);
530                         skip_to_semi(cfile);
531                         break;
532                 }
533                 break;
534         case TOK_FIXED_ADDR:
535                 if (!parse_ip_addr(cfile, &lease->address))
536                         return;
537                 break;
538         case TOK_MEDIUM:
539                 parse_string_list(cfile, &lease->medium, 0);
540                 return;
541         case TOK_FILENAME:
542                 lease->filename = parse_string(cfile);
543                 return;
544         case TOK_SERVER_NAME:
545                 lease->server_name = parse_string(cfile);
546                 return;
547         case TOK_RENEW:
548                 lease->renewal = parse_date(cfile);
549                 return;
550         case TOK_REBIND:
551                 lease->rebind = parse_date(cfile);
552                 return;
553         case TOK_EXPIRE:
554                 lease->expiry = parse_date(cfile);
555                 return;
556         case TOK_OPTION:
557                 parse_option_decl(cfile, lease->options);
558                 return;
559         default:
560                 parse_warn("expecting lease declaration.");
561                 skip_to_semi(cfile);
562                 break;
563         }
564         token = next_token(&val, cfile);
565         if (token != ';') {
566                 parse_warn("expecting semicolon.");
567                 skip_to_semi(cfile);
568         }
569 }
570
571 int
572 parse_option_decl(FILE *cfile, struct option_data *options)
573 {
574         char            *val;
575         int              token;
576         u_int8_t         buf[4];
577         u_int8_t         hunkbuf[1024];
578         int              hunkix = 0;
579         char            *fmt;
580         struct iaddr     ip_addr;
581         u_int8_t        *dp;
582         int              len, code;
583         int              nul_term = 0;
584
585         token = next_token(&val, cfile);
586         if (!is_identifier(token)) {
587                 parse_warn("expecting identifier after option keyword.");
588                 if (token != ';')
589                         skip_to_semi(cfile);
590                 return (-1);
591         }
592
593         /* Look up the actual option info. */
594         fmt = NULL;
595         for (code = 0; code < 256; code++)
596                 if (strcmp(dhcp_options[code].name, val) == 0)
597                         break;
598
599         if (code > 255) {
600                 parse_warn("no option named %s", val);
601                 skip_to_semi(cfile);
602                 return (-1);
603         }
604
605         /* Parse the option data... */
606         do {
607                 for (fmt = dhcp_options[code].format; *fmt; fmt++) {
608                         if (*fmt == 'A')
609                                 break;
610                         switch (*fmt) {
611                         case 'X':
612                                 len = parse_X(cfile, &hunkbuf[hunkix],
613                                     sizeof(hunkbuf) - hunkix);
614                                 hunkix += len;
615                                 break;
616                         case 't': /* Text string... */
617                                 token = next_token(&val, cfile);
618                                 if (token != TOK_STRING) {
619                                         parse_warn("expecting string.");
620                                         skip_to_semi(cfile);
621                                         return (-1);
622                                 }
623                                 len = strlen(val);
624                                 if (hunkix + len + 1 > sizeof(hunkbuf)) {
625                                         parse_warn("option data buffer %s",
626                                             "overflow");
627                                         skip_to_semi(cfile);
628                                         return (-1);
629                                 }
630                                 memcpy(&hunkbuf[hunkix], val, len + 1);
631                                 nul_term = 1;
632                                 hunkix += len;
633                                 break;
634                         case 'I': /* IP address. */
635                                 if (!parse_ip_addr(cfile, &ip_addr))
636                                         return (-1);
637                                 len = ip_addr.len;
638                                 dp = ip_addr.iabuf;
639 alloc:
640                                 if (hunkix + len > sizeof(hunkbuf)) {
641                                         parse_warn("option data buffer "
642                                             "overflow");
643                                         skip_to_semi(cfile);
644                                         return (-1);
645                                 }
646                                 memcpy(&hunkbuf[hunkix], dp, len);
647                                 hunkix += len;
648                                 break;
649                         case 'L':       /* Unsigned 32-bit integer... */
650                         case 'l':       /* Signed 32-bit integer... */
651                                 token = next_token(&val, cfile);
652                                 if (token != TOK_NUMBER) {
653 need_number:
654                                         parse_warn("expecting number.");
655                                         if (token != ';')
656                                                 skip_to_semi(cfile);
657                                         return (-1);
658                                 }
659                                 convert_num(buf, val, 0, 32);
660                                 len = 4;
661                                 dp = buf;
662                                 goto alloc;
663                         case 's':       /* Signed 16-bit integer. */
664                         case 'S':       /* Unsigned 16-bit integer. */
665                                 token = next_token(&val, cfile);
666                                 if (token != TOK_NUMBER)
667                                         goto need_number;
668                                 convert_num(buf, val, 0, 16);
669                                 len = 2;
670                                 dp = buf;
671                                 goto alloc;
672                         case 'b':       /* Signed 8-bit integer. */
673                         case 'B':       /* Unsigned 8-bit integer. */
674                                 token = next_token(&val, cfile);
675                                 if (token != TOK_NUMBER)
676                                         goto need_number;
677                                 convert_num(buf, val, 0, 8);
678                                 len = 1;
679                                 dp = buf;
680                                 goto alloc;
681                         case 'f': /* Boolean flag. */
682                                 token = next_token(&val, cfile);
683                                 if (!is_identifier(token)) {
684                                         parse_warn("expecting identifier.");
685 bad_flag:
686                                         if (token != ';')
687                                                 skip_to_semi(cfile);
688                                         return (-1);
689                                 }
690                                 if (!strcasecmp(val, "true") ||
691                                     !strcasecmp(val, "on"))
692                                         buf[0] = 1;
693                                 else if (!strcasecmp(val, "false") ||
694                                     !strcasecmp(val, "off"))
695                                         buf[0] = 0;
696                                 else {
697                                         parse_warn("expecting boolean.");
698                                         goto bad_flag;
699                                 }
700                                 len = 1;
701                                 dp = buf;
702                                 goto alloc;
703                         default:
704                                 warning("Bad format %c in parse_option_param.",
705                                     *fmt);
706                                 skip_to_semi(cfile);
707                                 return (-1);
708                         }
709                 }
710                 token = next_token(&val, cfile);
711         } while (*fmt == 'A' && token == ',');
712
713         if (token != ';') {
714                 parse_warn("semicolon expected.");
715                 skip_to_semi(cfile);
716                 return (-1);
717         }
718
719         options[code].data = malloc(hunkix + nul_term);
720         if (!options[code].data)
721                 error("out of memory allocating option data.");
722         memcpy(options[code].data, hunkbuf, hunkix + nul_term);
723         options[code].len = hunkix;
724         return (code);
725 }
726
727 void
728 parse_string_list(FILE *cfile, struct string_list **lp, int multiple)
729 {
730         int                      token;
731         char                    *val;
732         struct string_list      *cur, *tmp;
733
734         /* Find the last medium in the media list. */
735         if (*lp)
736                 for (cur = *lp; cur->next; cur = cur->next)
737                         ;       /* nothing */
738         else
739                 cur = NULL;
740
741         do {
742                 token = next_token(&val, cfile);
743                 if (token != TOK_STRING) {
744                         parse_warn("Expecting media options.");
745                         skip_to_semi(cfile);
746                         return;
747                 }
748
749                 tmp = malloc(sizeof(struct string_list) + strlen(val));
750                 if (tmp == NULL)
751                         error("no memory for string list entry.");
752                 strlcpy(tmp->string, val, strlen(val) + 1);
753                 tmp->next = NULL;
754
755                 /* Store this medium at the end of the media list. */
756                 if (cur)
757                         cur->next = tmp;
758                 else
759                         *lp = tmp;
760                 cur = tmp;
761
762                 token = next_token(&val, cfile);
763         } while (multiple && token == ',');
764
765         if (token != ';') {
766                 parse_warn("expecting semicolon.");
767                 skip_to_semi(cfile);
768         }
769 }
770
771 void
772 parse_reject_statement(FILE *cfile)
773 {
774         struct iaddrlist *list;
775         struct iaddr addr;
776         char *val;
777         int token;
778
779         do {
780                 if (!parse_ip_addr(cfile, &addr)) {
781                         parse_warn("expecting IP address.");
782                         skip_to_semi(cfile);
783                         return;
784                 }
785
786                 list = malloc(sizeof(struct iaddrlist));
787                 if (!list)
788                         error("no memory for reject list!");
789
790                 list->addr = addr;
791                 list->next = config->reject_list;
792                 config->reject_list = list;
793
794                 token = next_token(&val, cfile);
795         } while (token == ',');
796
797         if (token != ';') {
798                 parse_warn("expecting semicolon.");
799                 skip_to_semi(cfile);
800         }
801 }