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