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