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