fetch.9/store.9: Adjust for recent changes.
[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_ROUTERS;
78         config->requested_options
79             [config->requested_option_count++] = DHO_DOMAIN_NAME;
80         config->requested_options
81             [config->requested_option_count++] = DHO_DOMAIN_NAME_SERVERS;
82         config->requested_options
83             [config->requested_option_count++] = DHO_HOST_NAME;
84
85         if ((cfile = fopen(path_dhclient_conf, "r")) != NULL) {
86                 do {
87                         token = peek_token(NULL, cfile);
88                         if (token == EOF)
89                                 break;
90                         parse_client_statement(cfile);
91                 } while (1);
92                 token = next_token(NULL, cfile); /* Clear the peek buffer */
93                 fclose(cfile);
94         }
95
96         return (!warnings_occurred);
97 }
98
99 /*
100  * lease-file :== client-lease-statements EOF
101  * client-lease-statements :== <nil>
102  *                   | client-lease-statements LEASE client-lease-statement
103  */
104 void
105 read_client_leases(void)
106 {
107         FILE    *cfile;
108         int      token;
109
110         new_parse(path_dhclient_db);
111
112         /* Open the lease file.   If we can't open it, just return -
113            we can safely trust the server to remember our state. */
114         if ((cfile = fopen(path_dhclient_db, "r")) == NULL)
115                 return;
116         do {
117                 token = next_token(NULL, cfile);
118                 if (token == EOF)
119                         break;
120                 if (token != TOK_LEASE) {
121                         warning("Corrupt lease file - possible data loss!");
122                         skip_to_semi(cfile);
123                         break;
124                 } else
125                         parse_client_lease_statement(cfile, 0);
126
127         } while (1);
128         fclose(cfile);
129 }
130
131 /*
132  * client-declaration :==
133  *      TOK_SEND option-decl |
134  *      TOK_DEFAULT option-decl |
135  *      TOK_SUPERSEDE option-decl |
136  *      TOK_APPEND option-decl |
137  *      TOK_PREPEND option-decl |
138  *      TOK_MEDIA string-list |
139  *      hardware-declaration |
140  *      TOK_REQUEST option-list |
141  *      TOK_REQUIRE option-list |
142  *      TOK_TIMEOUT number |
143  *      TOK_RETRY number |
144  *      TOK_SELECT_TIMEOUT number |
145  *      TOK_REBOOT number |
146  *      TOK_BACKOFF_CUTOFF number |
147  *      TOK_INITIAL_INTERVAL number |
148  *      TOK_SCRIPT string |
149  *      interface-declaration |
150  *      TOK_LEASE client-lease-statement |
151  *      TOK_ALIAS client-lease-statement |
152  *      TOK_REJECT reject-statement
153  */
154 void
155 parse_client_statement(FILE *cfile)
156 {
157         u_int8_t ignorelist[256];
158         int token, code, count, i;
159
160         switch (next_token(NULL, cfile)) {
161         case TOK_SEND:
162                 parse_option_decl(cfile, &config->send_options[0]);
163                 return;
164         case TOK_DEFAULT:
165                 code = parse_option_decl(cfile, &config->defaults[0]);
166                 if (code != -1)
167                         config->default_actions[code] = ACTION_DEFAULT;
168                 return;
169         case TOK_SUPERSEDE:
170                 code = parse_option_decl(cfile, &config->defaults[0]);
171                 if (code != -1)
172                         config->default_actions[code] = ACTION_SUPERSEDE;
173                 return;
174         case TOK_IGNORE:
175                 count = parse_option_list(cfile, ignorelist);
176                 for (i = 0; i < count; i++)
177                         config->default_actions[ignorelist[i]] = ACTION_IGNORE;
178                 return;
179         case TOK_APPEND:
180                 code = parse_option_decl(cfile, &config->defaults[0]);
181                 if (code != -1)
182                         config->default_actions[code] = ACTION_APPEND;
183                 return;
184         case TOK_PREPEND:
185                 code = parse_option_decl(cfile, &config->defaults[0]);
186                 if (code != -1)
187                         config->default_actions[code] = ACTION_PREPEND;
188                 return;
189         case TOK_MEDIA:
190                 skip_to_semi(cfile);
191                 return;
192         case TOK_HARDWARE:
193                 parse_hardware_param(cfile, &ifi->hw_address);
194                 return;
195         case TOK_REQUEST:
196                 config->requested_option_count =
197                         parse_option_list(cfile, config->requested_options);
198                 return;
199         case TOK_REQUIRE:
200                 memset(config->required_options, 0,
201                     sizeof(config->required_options));
202                 parse_option_list(cfile, config->required_options);
203                 return;
204         case TOK_LINK_TIMEOUT:
205                 parse_lease_time(cfile, &config->link_timeout);
206                 return;
207         case TOK_TIMEOUT:
208                 parse_lease_time(cfile, &config->timeout);
209                 return;
210         case TOK_RETRY:
211                 parse_lease_time(cfile, &config->retry_interval);
212                 return;
213         case TOK_SELECT_TIMEOUT:
214                 parse_lease_time(cfile, &config->select_interval);
215                 return;
216         case TOK_REBOOT:
217                 parse_lease_time(cfile, &config->reboot_timeout);
218                 return;
219         case TOK_BACKOFF_CUTOFF:
220                 parse_lease_time(cfile, &config->backoff_cutoff);
221                 return;
222         case TOK_INITIAL_INTERVAL:
223                 parse_lease_time(cfile, &config->initial_interval);
224                 return;
225         case TOK_SCRIPT:
226                 config->script_name = parse_string(cfile);
227                 return;
228         case TOK_INTERFACE:
229                 parse_interface_declaration(cfile);
230                 return;
231         case TOK_LEASE:
232                 parse_client_lease_statement(cfile, 1);
233                 return;
234         case TOK_ALIAS:
235                 skip_to_semi(cfile);
236                 return;
237         case TOK_REJECT:
238                 parse_reject_statement(cfile);
239                 return;
240         default:
241                 parse_warn("expecting a statement.");
242                 skip_to_semi(cfile);
243                 break;
244         }
245         token = next_token(NULL, cfile);
246         if (token != ';') {
247                 parse_warn("semicolon expected.");
248                 skip_to_semi(cfile);
249         }
250 }
251
252 int
253 parse_X(FILE *cfile, u_int8_t *buf, int max)
254 {
255         int      token;
256         char    *val;
257         int      len;
258
259         token = peek_token(&val, cfile);
260         if (token == TOK_NUMBER_OR_NAME || token == TOK_NUMBER) {
261                 len = 0;
262                 do {
263                         token = next_token(&val, cfile);
264                         if (token != TOK_NUMBER && token != TOK_NUMBER_OR_NAME) {
265                                 parse_warn("expecting hexadecimal constant.");
266                                 skip_to_semi(cfile);
267                                 return (0);
268                         }
269                         convert_num(&buf[len], val, 16, 8);
270                         if (len++ > max) {
271                                 parse_warn("hexadecimal constant too long.");
272                                 skip_to_semi(cfile);
273                                 return (0);
274                         }
275                         token = peek_token(&val, cfile);
276                         if (token == ':')
277                                 token = next_token(&val, cfile);
278                 } while (token == ':');
279                 val = (char *)buf;
280         } else if (token == TOK_STRING) {
281                 token = next_token(&val, cfile);
282                 len = strlen(val);
283                 if (len + 1 > max) {
284                         parse_warn("string constant too long.");
285                         skip_to_semi(cfile);
286                         return (0);
287                 }
288                 memcpy(buf, val, len + 1);
289         } else {
290                 parse_warn("expecting string or hexadecimal data");
291                 skip_to_semi(cfile);
292                 return (0);
293         }
294         return (len);
295 }
296
297 /*
298  * option-list :== option_name |
299  *                 option_list COMMA option_name
300  */
301 int
302 parse_option_list(FILE *cfile, u_int8_t *list)
303 {
304         int      ix, i;
305         int      token;
306         char    *val;
307
308         ix = 0;
309         do {
310                 token = next_token(&val, cfile);
311                 if (!is_identifier(token)) {
312                         parse_warn("expected option name.");
313                         skip_to_semi(cfile);
314                         return (0);
315                 }
316                 for (i = 0; i < 256; i++)
317                         if (!strcasecmp(dhcp_options[i].name, val))
318                                 break;
319
320                 if (i == 256) {
321                         parse_warn("%s: unexpected option name.", val);
322                         skip_to_semi(cfile);
323                         return (0);
324                 }
325                 list[ix++] = i;
326                 if (ix == 256) {
327                         parse_warn("%s: too many options.", val);
328                         skip_to_semi(cfile);
329                         return (0);
330                 }
331                 token = next_token(&val, cfile);
332         } while (token == ',');
333         if (token != ';') {
334                 parse_warn("expecting semicolon.");
335                 skip_to_semi(cfile);
336                 return (0);
337         }
338         return (ix);
339 }
340
341 /*
342  * interface-declaration :==
343  *      INTERFACE string LBRACE client-declarations RBRACE
344  */
345 void
346 parse_interface_declaration(FILE *cfile)
347 {
348         char *val;
349         int token;
350
351         token = next_token(&val, cfile);
352         if (token != TOK_STRING) {
353                 parse_warn("expecting interface name (in quotes).");
354                 skip_to_semi(cfile);
355                 return;
356         }
357
358         if (strcmp(ifi->name, val) != 0) {
359                 skip_to_semi(cfile);
360                 return;
361         }
362
363         token = next_token(&val, cfile);
364         if (token != '{') {
365                 parse_warn("expecting left brace.");
366                 skip_to_semi(cfile);
367                 return;
368         }
369
370         do {
371                 token = peek_token(&val, cfile);
372                 if (token == EOF) {
373                         parse_warn("unterminated interface declaration.");
374                         return;
375                 }
376                 if (token == '}')
377                         break;
378                 parse_client_statement(cfile);
379         } while (1);
380         token = next_token(&val, cfile);
381 }
382
383 /*
384  * client-lease-statement :==
385  *      RBRACE client-lease-declarations LBRACE
386  *
387  *      client-lease-declarations :==
388  *              <nil> |
389  *              client-lease-declaration |
390  *              client-lease-declarations client-lease-declaration
391  */
392 void
393 parse_client_lease_statement(FILE *cfile, int is_static)
394 {
395         struct client_lease     *lease, *lp, *pl;
396         int                      token;
397
398         token = next_token(NULL, cfile);
399         if (token != '{') {
400                 parse_warn("expecting left brace.");
401                 skip_to_semi(cfile);
402                 return;
403         }
404
405         lease = malloc(sizeof(struct client_lease));
406         if (!lease)
407                 error("no memory for lease.");
408         memset(lease, 0, sizeof(*lease));
409         lease->is_static = is_static;
410
411         do {
412                 token = peek_token(NULL, cfile);
413                 if (token == EOF) {
414                         parse_warn("unterminated lease declaration.");
415                         return;
416                 }
417                 if (token == '}')
418                         break;
419                 parse_client_lease_declaration(cfile, lease);
420         } while (1);
421         token = next_token(NULL, cfile);
422
423         /* If the lease declaration didn't include an interface
424          * declaration that we recognized, it's of no use to us.
425          */
426         if (!ifi) {
427                 free_client_lease(lease);
428                 return;
429         }
430
431         /*
432          * The new lease may supersede a lease that's not the active
433          * lease but is still on the lease list, so scan the lease list
434          * looking for a lease with the same address, and if we find it,
435          * toss it.
436          */
437         pl = NULL;
438         for (lp = client->leases; lp; lp = lp->next) {
439                 if (addr_eq(lp->address, lease->address)) {
440                         if (pl)
441                                 pl->next = lp->next;
442                         else
443                                 client->leases = lp->next;
444                         free_client_lease(lp);
445                         break;
446                 } else
447                         pl = lp;
448         }
449
450         /*
451          * If this is a preloaded lease, just put it on the list of
452          * recorded leases - don't make it the active lease.
453          */
454         if (is_static) {
455                 lease->next = client->leases;
456                 client->leases = lease;
457                 return;
458         }
459
460         /*
461          * The last lease in the lease file on a particular interface is
462          * the active lease for that interface.    Of course, we don't
463          * know what the last lease in the file is until we've parsed
464          * the whole file, so at this point, we assume that the lease we
465          * just parsed is the active lease for its interface.   If
466          * there's already an active lease for the interface, and this
467          * lease is for the same ip address, then we just toss the old
468          * active lease and replace it with this one.   If this lease is
469          * for a different address, then if the old active lease has
470          * expired, we dump it; if not, we put it on the list of leases
471          * for this interface which are still valid but no longer
472          * active.
473          */
474         if (client->active) {
475                 if (client->active->expiry < time(NULL))
476                         free_client_lease(client->active);
477                 else if (addr_eq(client->active->address, lease->address))
478                         free_client_lease(client->active);
479                 else {
480                         client->active->next = client->leases;
481                         client->leases = client->active;
482                 }
483         }
484         client->active = lease;
485
486         /* Phew. */
487 }
488
489 /*
490  * client-lease-declaration :==
491  *      BOOTP |
492  *      INTERFACE string |
493  *      FIXED_ADDR ip_address |
494  *      FILENAME string |
495  *      SERVER_NAME string |
496  *      OPTION option-decl |
497  *      RENEW time-decl |
498  *      REBIND time-decl |
499  *      EXPIRE time-decl
500  */
501 void
502 parse_client_lease_declaration(FILE *cfile, struct client_lease *lease)
503 {
504         char *val;
505         int token;
506
507         switch (next_token(&val, cfile)) {
508         case TOK_BOOTP:
509                 lease->is_bootp = 1;
510                 break;
511         case TOK_INTERFACE:
512                 token = next_token(&val, cfile);
513                 if (token != TOK_STRING) {
514                         parse_warn("expecting interface name (in quotes).");
515                         skip_to_semi(cfile);
516                         break;
517                 }
518                 if (strcmp(ifi->name, val) != 0) {
519                         parse_warn("wrong interface name. Expecting '%s'.",
520                            ifi->name);
521                         skip_to_semi(cfile);
522                         break;
523                 }
524                 break;
525         case TOK_FIXED_ADDR:
526                 if (!parse_ip_addr(cfile, &lease->address))
527                         return;
528                 break;
529         case TOK_MEDIUM:
530                 skip_to_semi(cfile);
531                 return;
532         case TOK_FILENAME:
533                 lease->filename = parse_string(cfile);
534                 return;
535         case TOK_SERVER_NAME:
536                 lease->server_name = parse_string(cfile);
537                 return;
538         case TOK_RENEW:
539                 lease->renewal = parse_date(cfile);
540                 return;
541         case TOK_REBIND:
542                 lease->rebind = parse_date(cfile);
543                 return;
544         case TOK_EXPIRE:
545                 lease->expiry = parse_date(cfile);
546                 return;
547         case TOK_OPTION:
548                 parse_option_decl(cfile, lease->options);
549                 return;
550         default:
551                 parse_warn("expecting lease declaration.");
552                 skip_to_semi(cfile);
553                 break;
554         }
555         token = next_token(&val, cfile);
556         if (token != ';') {
557                 parse_warn("expecting semicolon.");
558                 skip_to_semi(cfile);
559         }
560 }
561
562 int
563 parse_option_decl(FILE *cfile, struct option_data *options)
564 {
565         char            *val;
566         int              token;
567         u_int8_t         buf[4];
568         u_int8_t         hunkbuf[1024];
569         int              hunkix = 0;
570         char            *fmt;
571         struct iaddr     ip_addr;
572         u_int8_t        *dp;
573         int              len, code;
574         int              nul_term = 0;
575
576         token = next_token(&val, cfile);
577         if (!is_identifier(token)) {
578                 parse_warn("expecting identifier after option keyword.");
579                 if (token != ';')
580                         skip_to_semi(cfile);
581                 return (-1);
582         }
583
584         /* Look up the actual option info. */
585         fmt = NULL;
586         for (code = 0; code < 256; code++)
587                 if (strcmp(dhcp_options[code].name, val) == 0)
588                         break;
589
590         if (code > 255) {
591                 parse_warn("no option named %s", val);
592                 skip_to_semi(cfile);
593                 return (-1);
594         }
595
596         /* Parse the option data... */
597         do {
598                 for (fmt = dhcp_options[code].format; *fmt; fmt++) {
599                         if (*fmt == 'A')
600                                 break;
601                         switch (*fmt) {
602                         case 'X':
603                                 len = parse_X(cfile, &hunkbuf[hunkix],
604                                     sizeof(hunkbuf) - hunkix);
605                                 hunkix += len;
606                                 break;
607                         case 't': /* Text string... */
608                                 token = next_token(&val, cfile);
609                                 if (token != TOK_STRING) {
610                                         parse_warn("expecting string.");
611                                         skip_to_semi(cfile);
612                                         return (-1);
613                                 }
614                                 len = strlen(val);
615                                 if (hunkix + len + 1 > sizeof(hunkbuf)) {
616                                         parse_warn("option data buffer %s",
617                                             "overflow");
618                                         skip_to_semi(cfile);
619                                         return (-1);
620                                 }
621                                 memcpy(&hunkbuf[hunkix], val, len + 1);
622                                 nul_term = 1;
623                                 hunkix += len;
624                                 break;
625                         case 'I': /* IP address. */
626                                 if (!parse_ip_addr(cfile, &ip_addr))
627                                         return (-1);
628                                 len = ip_addr.len;
629                                 dp = ip_addr.iabuf;
630 alloc:
631                                 if (hunkix + len > sizeof(hunkbuf)) {
632                                         parse_warn("option data buffer "
633                                             "overflow");
634                                         skip_to_semi(cfile);
635                                         return (-1);
636                                 }
637                                 memcpy(&hunkbuf[hunkix], dp, len);
638                                 hunkix += len;
639                                 break;
640                         case 'L':       /* Unsigned 32-bit integer... */
641                         case 'l':       /* Signed 32-bit integer... */
642                                 token = next_token(&val, cfile);
643                                 if (token != TOK_NUMBER) {
644 need_number:
645                                         parse_warn("expecting number.");
646                                         if (token != ';')
647                                                 skip_to_semi(cfile);
648                                         return (-1);
649                                 }
650                                 convert_num(buf, val, 0, 32);
651                                 len = 4;
652                                 dp = buf;
653                                 goto alloc;
654                         case 's':       /* Signed 16-bit integer. */
655                         case 'S':       /* Unsigned 16-bit integer. */
656                                 token = next_token(&val, cfile);
657                                 if (token != TOK_NUMBER)
658                                         goto need_number;
659                                 convert_num(buf, val, 0, 16);
660                                 len = 2;
661                                 dp = buf;
662                                 goto alloc;
663                         case 'b':       /* Signed 8-bit integer. */
664                         case 'B':       /* Unsigned 8-bit integer. */
665                                 token = next_token(&val, cfile);
666                                 if (token != TOK_NUMBER)
667                                         goto need_number;
668                                 convert_num(buf, val, 0, 8);
669                                 len = 1;
670                                 dp = buf;
671                                 goto alloc;
672                         case 'f': /* Boolean flag. */
673                                 token = next_token(&val, cfile);
674                                 if (!is_identifier(token)) {
675                                         parse_warn("expecting identifier.");
676 bad_flag:
677                                         if (token != ';')
678                                                 skip_to_semi(cfile);
679                                         return (-1);
680                                 }
681                                 if (!strcasecmp(val, "true") ||
682                                     !strcasecmp(val, "on"))
683                                         buf[0] = 1;
684                                 else if (!strcasecmp(val, "false") ||
685                                     !strcasecmp(val, "off"))
686                                         buf[0] = 0;
687                                 else {
688                                         parse_warn("expecting boolean.");
689                                         goto bad_flag;
690                                 }
691                                 len = 1;
692                                 dp = buf;
693                                 goto alloc;
694                         default:
695                                 warning("Bad format %c in parse_option_param.",
696                                     *fmt);
697                                 skip_to_semi(cfile);
698                                 return (-1);
699                         }
700                 }
701                 token = next_token(&val, cfile);
702         } while (*fmt == 'A' && token == ',');
703
704         if (token != ';') {
705                 parse_warn("semicolon expected.");
706                 skip_to_semi(cfile);
707                 return (-1);
708         }
709
710         options[code].data = malloc(hunkix + nul_term);
711         if (!options[code].data)
712                 error("out of memory allocating option data.");
713         memcpy(options[code].data, hunkbuf, hunkix + nul_term);
714         options[code].len = hunkix;
715         return (code);
716 }
717
718 void
719 parse_reject_statement(FILE *cfile)
720 {
721         struct iaddrlist *list;
722         struct iaddr addr;
723         int token;
724
725         do {
726                 if (!parse_ip_addr(cfile, &addr)) {
727                         parse_warn("expecting IP address.");
728                         skip_to_semi(cfile);
729                         return;
730                 }
731
732                 list = malloc(sizeof(struct iaddrlist));
733                 if (!list)
734                         error("no memory for reject list!");
735
736                 list->addr = addr;
737                 list->next = config->reject_list;
738                 config->reject_list = list;
739
740                 token = next_token(NULL, cfile);
741         } while (token == ',');
742
743         if (token != ';') {
744                 parse_warn("expecting semicolon.");
745                 skip_to_semi(cfile);
746         }
747 }