ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/gvpe/src/conf.C
(Generate patch)

Comparing gvpe/src/conf.C (file contents):
Revision 1.50 by pcg, Mon Mar 23 15:22:00 2009 UTC vs.
Revision 1.57 by root, Sat Dec 17 22:05:34 2011 UTC

1/* 1/*
2 conf.c -- configuration code 2 conf.C -- configuration code
3 Copyright (C) 2003-2008 Marc Lehmann <gvpe@schmorp.de> 3 Copyright (C) 2003-2008,2011 Marc Lehmann <gvpe@schmorp.de>
4 4
5 This file is part of GVPE. 5 This file is part of GVPE.
6 6
7 GVPE is free software; you can redistribute it and/or modify it 7 GVPE is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the 8 under the terms of the GNU General Public License as published by the
57char *thisnode; 57char *thisnode;
58char *identname; 58char *identname;
59 59
60struct configuration conf; 60struct configuration conf;
61 61
62u8
62u8 best_protocol (u8 protset) 63best_protocol (u8 protset)
63{ 64{
65#if 1//D2
66 if (protset & PROT_IPv42 ) return PROT_IPv42;
67#endif
64 if (protset & PROT_IPv4 ) return PROT_IPv4; 68 if (protset & PROT_IPv4 ) return PROT_IPv4;
65 if (protset & PROT_ICMPv4) return PROT_ICMPv4; 69 if (protset & PROT_ICMPv4) return PROT_ICMPv4;
66 if (protset & PROT_UDPv4 ) return PROT_UDPv4; 70 if (protset & PROT_UDPv4 ) return PROT_UDPv4;
67 if (protset & PROT_TCPv4 ) return PROT_TCPv4; 71 if (protset & PROT_TCPv4 ) return PROT_TCPv4;
68 if (protset & PROT_DNSv4 ) return PROT_DNSv4; 72 if (protset & PROT_DNSv4 ) return PROT_DNSv4;
69 73
70 return 0; 74 return 0;
71} 75}
72 76
77const char *
73const char *strprotocol (u8 protocol) 78strprotocol (u8 protocol)
74{ 79{
80#if 1//D2
81 if (protocol & PROT_IPv42 ) return "rawip2";
82#endif
75 if (protocol & PROT_IPv4 ) return "rawip"; 83 if (protocol & PROT_IPv4 ) return "rawip";
76 if (protocol & PROT_ICMPv4) return "icmp"; 84 if (protocol & PROT_ICMPv4) return "icmp";
77 if (protocol & PROT_UDPv4 ) return "udp"; 85 if (protocol & PROT_UDPv4 ) return "udp";
78 if (protocol & PROT_TCPv4 ) return "tcp"; 86 if (protocol & PROT_TCPv4 ) return "tcp";
79 if (protocol & PROT_DNSv4 ) return "dns"; 87 if (protocol & PROT_DNSv4 ) return "dns";
103 return false; 111 return false;
104 112
105 return true; 113 return true;
106} 114}
107 115
108void
109conf_node::print ()
110{
111 printf ("%4d fe:fd:80:00:0%1x:%02x %c %-8.8s %-10.10s %s%s%d\n",
112 id,
113 id >> 8, id & 0xff,
114 compress ? 'Y' : 'N',
115 connectmode == C_ONDEMAND ? "ondemand"
116 : connectmode == C_NEVER ? "never"
117 : connectmode == C_ALWAYS ? "always"
118 : connectmode == C_DISABLED ? "disabled"
119 : "",
120 nodename,
121 hostname ? hostname : "",
122 hostname ? ":" : "",
123 hostname ? udp_port : 0
124 );
125}
126
127conf_node::~conf_node () 116conf_node::~conf_node ()
128{ 117{
129#if 0 118#if 0
130 // does not work, because string pointers etc. are shared 119 // does not work, because string pointers etc. are shared
131 // is not called, however 120 // is not called, however
140 free (dns_hostname); 129 free (dns_hostname);
141#endif 130#endif
142#endif 131#endif
143} 132}
144 133
134void
145void configuration::init () 135configuration::init ()
146{ 136{
147 memset (this, 0, sizeof (*this)); 137 memset (this, 0, sizeof (*this));
148 138
149 mtu = DEFAULT_MTU; 139 mtu = DEFAULT_MTU;
150 nfmark = 0; 140 nfmark = 0;
151 rekey = DEFAULT_REKEY; 141 rekey = DEFAULT_REKEY;
152 keepalive = DEFAULT_KEEPALIVE; 142 keepalive = DEFAULT_KEEPALIVE;
153 llevel = L_INFO; 143 llevel = L_INFO;
154 ip_proto = IPPROTO_GRE; 144 ip_proto = IPPROTO_GRE;
145#if 1 //D2
146 ip2_proto = 7;
147#endif
155#if ENABLE_ICMP 148#if ENABLE_ICMP
156 icmp_type = ICMP_ECHOREPLY; 149 icmp_type = ICMP_ECHOREPLY;
157#endif 150#endif
158 151
159 default_node.udp_port = DEFAULT_UDPPORT; 152 default_node.udp_port = DEFAULT_UDPPORT;
167 default_node.if_up_data = strdup (""); 160 default_node.if_up_data = strdup ("");
168 161
169#if ENABLE_DNS 162#if ENABLE_DNS
170 default_node.dns_port = 0; // default is 0 == client 163 default_node.dns_port = 0; // default is 0 == client
171 164
165 dns_case_preserving = true;
172 dns_forw_host = strdup ("127.0.0.1"); 166 dns_forw_host = strdup ("127.0.0.1");
173 dns_forw_port = 53; 167 dns_forw_port = 53;
174 dns_timeout_factor = DEFAULT_DNS_TIMEOUT_FACTOR; 168 dns_timeout_factor = DEFAULT_DNS_TIMEOUT_FACTOR;
175 dns_send_interval = DEFAULT_DNS_SEND_INTERVAL; 169 dns_send_interval = DEFAULT_DNS_SEND_INTERVAL;
176 dns_overlap_factor = DEFAULT_DNS_OVERLAP_FACTOR; 170 dns_overlap_factor = DEFAULT_DNS_OVERLAP_FACTOR;
178#endif 172#endif
179 173
180 conf.pidfilename = strdup (LOCALSTATEDIR "/run/gvpe.pid"); 174 conf.pidfilename = strdup (LOCALSTATEDIR "/run/gvpe.pid");
181} 175}
182 176
177void
183void configuration::cleanup() 178configuration::cleanup ()
184{ 179{
185 if (rsa_key) 180 if (rsa_key)
186 RSA_free (rsa_key); 181 RSA_free (rsa_key);
187 182
188 rsa_key = 0; 183 rsa_key = 0;
189 184
190 free (pidfilename); pidfilename = 0; 185 free (pidfilename); pidfilename = 0;
191 free (ifname); ifname = 0; 186 free (ifname); ifname = 0;
192#if ENABLE_HTTP_PROXY 187#if ENABLE_HTTP_PROXY
193 free (proxy_host); proxy_host = 0; 188 free (proxy_host); proxy_host = 0;
194 free (proxy_auth); proxy_auth = 0; 189 free (proxy_auth); proxy_auth = 0;
195#endif 190#endif
196#if ENABLE_DNS 191#if ENABLE_DNS
197 free (dns_forw_host); dns_forw_host = 0; 192 free (dns_forw_host); dns_forw_host = 0;
198#endif 193#endif
194 free (script_if_up); script_if_up = 0;
195 free (script_node_up); script_node_up = 0;
196 free (script_node_change); script_node_change = 0;
197 free (script_node_down); script_node_down = 0;
199} 198}
200 199
201void 200void
202configuration::clear () 201configuration::clear ()
203{ 202{
207 nodes.clear (); 206 nodes.clear ();
208 207
209 cleanup (); 208 cleanup ();
210 init (); 209 init ();
211} 210}
211
212//static bool
213//is_true (const char *name)
214//{
215 //re
216//}
212 217
213#define parse_bool(target,name,trueval,falseval) do { \ 218#define parse_bool(target,name,trueval,falseval) do { \
214 if (!strcmp (val, "yes")) target = trueval; \ 219 if (!strcmp (val, "yes")) target = trueval; \
215 else if (!strcmp (val, "no")) target = falseval; \ 220 else if (!strcmp (val, "no")) target = falseval; \
216 else if (!strcmp (val, "true")) target = trueval; \ 221 else if (!strcmp (val, "true")) target = trueval; \
217 else if (!strcmp (val, "false")) target = falseval; \ 222 else if (!strcmp (val, "false")) target = falseval; \
218 else if (!strcmp (val, "on")) target = trueval; \ 223 else if (!strcmp (val, "on")) target = trueval; \
219 else if (!strcmp (val, "off")) target = falseval; \ 224 else if (!strcmp (val, "off")) target = falseval; \
220 else \ 225 else \
221 return _("illegal boolean value, only 'yes|true|on' or 'no|false|off' allowed. (ignored)"); \ 226 return _("illegal boolean value, only 'yes|true|on' or 'no|false|off' allowed, ignored"); \
222} while (0) 227} while (0)
223 228
224const char * 229const char *
225configuration_parser::parse_line (char *line) 230configuration_parser::parse_line (char *line)
226{ 231{
244 return 0; /* comment: ignore */ 249 return 0; /* comment: ignore */
245 250
246 char *val = strtok (NULL, "\t\n\r ="); 251 char *val = strtok (NULL, "\t\n\r =");
247 252
248 if (!val || val[0] == '#') 253 if (!val || val[0] == '#')
249 return _("no value given for variable. (ignored)"); 254 return _("no value given for variable, ignored");
250 255
251 if (!strcmp (var, "on")) 256 else if (!strcmp (var, "on"))
252 { 257 {
253 if (!::thisnode 258 if (::thisnode
254 || (val[0] == '!' && strcmp (val + 1, ::thisnode)) 259 && ((val[0] == '!' && strcmp (val + 1, ::thisnode))
255 || !strcmp (val, ::thisnode)) 260 || !strcmp (val, ::thisnode)))
256 return parse_line (strtok (NULL, "\n\r")); 261 return parse_line (strtok (NULL, "\n\r"));
257 else 262 }
258 return 0; 263
264 else if (!strcmp (var, "include"))
265 {
266 char *fname = conf.config_filename (val);
267 parse_file (fname);
268 free (fname);
259 } 269 }
260 270
261 // truly global 271 // truly global
262 if (!strcmp (var, "loglevel")) 272 else if (!strcmp (var, "loglevel"))
263 { 273 {
264 loglevel l = string_to_loglevel (val); 274 loglevel l = string_to_loglevel (val);
265 275
266 if (l == L_NONE) 276 if (l == L_NONE)
267 return _("unknown loglevel. (skipping)"); 277 return _("unknown loglevel, ignored");
268 } 278 }
269 else if (!strcmp (var, "ip-proto")) 279 else if (!strcmp (var, "ip-proto"))
270 conf.ip_proto = atoi (val); 280 conf.ip_proto = atoi (val);
281#if 1 //D2
282 else if (!strcmp (var, "ip2-proto"))
283 conf.ip2_proto = atoi (val);
284#endif
271 else if (!strcmp (var, "icmp-type")) 285 else if (!strcmp (var, "icmp-type"))
272 { 286 {
273#if ENABLE_ICMP 287#if ENABLE_ICMP
274 conf.icmp_type = atoi (val); 288 conf.icmp_type = atoi (val);
275#endif 289#endif
337 conf.nfmark = atoi (val); 351 conf.nfmark = atoi (val);
338 else if (!strcmp (var, "if-up")) 352 else if (!strcmp (var, "if-up"))
339 free (conf.script_if_up), conf.script_if_up = strdup (val); 353 free (conf.script_if_up), conf.script_if_up = strdup (val);
340 else if (!strcmp (var, "node-up")) 354 else if (!strcmp (var, "node-up"))
341 free (conf.script_node_up), conf.script_node_up = strdup (val); 355 free (conf.script_node_up), conf.script_node_up = strdup (val);
356 else if (!strcmp (var, "node-change"))
357 free (conf.script_node_change), conf.script_node_change = strdup (val);
342 else if (!strcmp (var, "node-down")) 358 else if (!strcmp (var, "node-down"))
343 free (conf.script_node_down), conf.script_node_down = strdup (val); 359 free (conf.script_node_down), conf.script_node_down = strdup (val);
344 else if (!strcmp (var, "pid-file")) 360 else if (!strcmp (var, "pid-file"))
345 free (conf.pidfilename), conf.pidfilename = strdup (val); 361 free (conf.pidfilename), conf.pidfilename = strdup (val);
346 else if (!strcmp (var, "dns-forw-host")) 362 else if (!strcmp (var, "dns-forw-host"))
375 } 391 }
376 else if (!strcmp (var, "dns-max-outstanding")) 392 else if (!strcmp (var, "dns-max-outstanding"))
377 { 393 {
378#if ENABLE_DNS 394#if ENABLE_DNS
379 conf.dns_max_outstanding = atoi (val); 395 conf.dns_max_outstanding = atoi (val);
396#endif
397 }
398 else if (!strcmp (var, "dns-case-preserving"))
399 {
400#if ENABLE_DNS
401 parse_bool (conf.dns_case_preserving, "dns-case-preserving", true, false);
380#endif 402#endif
381 } 403 }
382 else if (!strcmp (var, "http-proxy-host")) 404 else if (!strcmp (var, "http-proxy-host"))
383 { 405 {
384#if ENABLE_HTTP_PROXY 406#if ENABLE_HTTP_PROXY
440 else if (!strcmp (val, "always")) 462 else if (!strcmp (val, "always"))
441 node->connectmode = conf_node::C_ALWAYS; 463 node->connectmode = conf_node::C_ALWAYS;
442 else if (!strcmp (val, "disabled")) 464 else if (!strcmp (val, "disabled"))
443 node->connectmode = conf_node::C_DISABLED; 465 node->connectmode = conf_node::C_DISABLED;
444 else 466 else
445 return _("illegal value for 'connectmode', use one of 'ondemand', 'never', 'always' or 'disabled'. (ignored)"); 467 return _("illegal value for 'connectmode', use one of 'ondemand', 'never', 'always' or 'disabled', ignored");
446 } 468 }
447 else if (!strcmp (var, "inherit-tos")) 469 else if (!strcmp (var, "inherit-tos"))
448 parse_bool (node->inherit_tos, "inherit-tos", true, false); 470 parse_bool (node->inherit_tos, "inherit-tos", true, false);
449 else if (!strcmp (var, "compress")) 471 else if (!strcmp (var, "compress"))
450 parse_bool (node->compress, "compress", true, false); 472 parse_bool (node->compress, "compress", true, false);
473 } 495 }
474 else if (!strcmp (var, "enable-rawip")) 496 else if (!strcmp (var, "enable-rawip"))
475 { 497 {
476 u8 v; parse_bool (v, "enable-rawip", PROT_IPv4, 0); node->protocols = (node->protocols & ~PROT_IPv4 ) | v; 498 u8 v; parse_bool (v, "enable-rawip", PROT_IPv4, 0); node->protocols = (node->protocols & ~PROT_IPv4 ) | v;
477 } 499 }
500#if 1//D2
501 else if (!strcmp (var, "enable-rawip2"))
502 {
503 u8 v; parse_bool (v, "enable-rawip2", PROT_IPv42, 0); node->protocols = (node->protocols & ~PROT_IPv42 ) | v;
504 }
505#endif
478 else if (!strcmp (var, "allow-direct")) 506 else if (!strcmp (var, "allow-direct"))
479 node->allow_direct.push_back (strdup (val)); 507 node->allow_direct.push_back (strdup (val));
480 else if (!strcmp (var, "deny-direct")) 508 else if (!strcmp (var, "deny-direct"))
481 node->deny_direct.push_back (strdup (val)); 509 node->deny_direct.push_back (strdup (val));
482 else if (!strcmp (var, "max-ttl")) 510 else if (!strcmp (var, "max-ttl"))
484 else if (!strcmp (var, "max-queue")) 512 else if (!strcmp (var, "max-queue"))
485 node->max_queue = atoi (val); 513 node->max_queue = atoi (val);
486 514
487 // unknown or misplaced 515 // unknown or misplaced
488 else 516 else
489 return _("unknown configuration directive. (ignored)"); 517 return _("unknown configuration directive - ignored");
490 518
491 return 0; 519 return 0;
492} 520}
493 521
522void
494void conf_node::finalise () 523conf_node::finalise ()
495{ 524{
496 if (max_queue < 1) 525 if (max_queue < 1)
497 { 526 {
498 slog (L_WARN, _("%s: max-queue value invalid, setting it to 1."), nodename); 527 slog (L_WARN, _("%s: max-queue value invalid, setting it to 1."), nodename);
499 max_queue = 1; 528 max_queue = 1;
504 //slog (L_WARN, _("%s: has non-zero router-priority but either 'never' or 'ondemand' as connectmode, setting it to 'always'."), nodename); 533 //slog (L_WARN, _("%s: has non-zero router-priority but either 'never' or 'ondemand' as connectmode, setting it to 'always'."), nodename);
505 connectmode = C_ALWAYS; 534 connectmode = C_ALWAYS;
506 } 535 }
507} 536}
508 537
538void
509void configuration_parser::parse_argv () 539configuration_parser::parse_argv ()
510{ 540{
511 for (int i = 0; i < argc; ++i) 541 for (int i = 0; i < argc; ++i)
512 { 542 {
513 char *v = argv [i]; 543 char *v = argv [i];
514 544
535 if (warn) 565 if (warn)
536 slog (L_WARN, _("%s, while parsing command line option '%s'."), warn, v); 566 slog (L_WARN, _("%s, while parsing command line option '%s'."), warn, v);
537 567
538 *v = 0; 568 *v = 0;
539 } 569 }
570 }
571}
572
573void
574configuration_parser::parse_file (const char *fname)
575{
576 if (FILE *f = fopen (fname, "r"))
577 {
578 char line [2048];
579 int lineno = 0;
580
581 while (fgets (line, sizeof (line), f))
582 {
583 lineno++;
584
585 const char *warn = parse_line (line);
586
587 if (warn)
588 slog (L_WARN, _("%s, at '%s', line %d."), warn, fname, lineno);
589 }
590
591 fclose (f);
592
593 parse_argv ();
594 }
595 else
596 {
597 slog (L_ERR, _("unable to read config file '%s': %s"), fname, strerror (errno));
598 exit (EXIT_FAILURE);
540 } 599 }
541} 600}
542 601
543configuration_parser::configuration_parser (configuration &conf, 602configuration_parser::configuration_parser (configuration &conf,
544 bool need_keys, 603 bool need_keys,
545 int argc, 604 int argc,
546 char **argv) 605 char **argv)
547: conf (conf),need_keys (need_keys), argc (argc), argv (argv) 606: conf (conf),need_keys (need_keys), argc (argc), argv (argv)
548{ 607{
549 char *fname; 608 char *fname;
550 FILE *f;
551 609
552 conf.clear (); 610 conf.clear ();
611 node = &conf.default_node;
553 612
554 asprintf (&fname, "%s/gvpe.conf", confbase); 613 asprintf (&fname, "%s/gvpe.conf", confbase);
555 f = fopen (fname, "r"); 614 parse_file (fname);
556
557 if (f)
558 {
559 char line[16384];
560 int lineno = 0;
561 node = &conf.default_node;
562
563 while (fgets (line, sizeof (line), f))
564 {
565 lineno++;
566
567 const char *warn = parse_line (line);
568
569 if (warn)
570 slog (L_WARN, _("%s, at '%s', line %d."), warn, fname, lineno);
571 }
572
573 fclose (f);
574
575 parse_argv ();
576 }
577 else
578 {
579 slog (L_ERR, _("unable to read config file '%s': %s"), fname, strerror (errno));
580 exit (EXIT_FAILURE);
581 }
582
583 free (fname); 615 free (fname);
584 616
585 fname = conf.config_filename (conf.prikeyfile, "hostkey"); 617 fname = conf.config_filename (conf.prikeyfile, "hostkey");
586 618
587 f = fopen (fname, "r"); 619 if (FILE *f = fopen (fname, "r"))
588 if (f)
589 { 620 {
590 conf.rsa_key = RSA_new (); 621 conf.rsa_key = RSA_new ();
591 622
592 if (!PEM_read_RSAPrivateKey (f, &conf.rsa_key, NULL, NULL)) 623 if (!PEM_read_RSAPrivateKey (f, &conf.rsa_key, NULL, NULL))
593 { 624 {
605 slog (need_keys ? L_ERR : L_NOTICE, _("unable to open private rsa key file '%s': %s"), fname, strerror (errno)); 636 slog (need_keys ? L_ERR : L_NOTICE, _("unable to open private rsa key file '%s': %s"), fname, strerror (errno));
606 637
607 if (need_keys) 638 if (need_keys)
608 exit (EXIT_FAILURE); 639 exit (EXIT_FAILURE);
609 } 640 }
641
642 free (fname);
610 643
611 if (need_keys && ::thisnode 644 if (need_keys && ::thisnode
612 && conf.rsa_key && conf.thisnode && conf.thisnode->rsa_key) 645 && conf.rsa_key && conf.thisnode && conf.thisnode->rsa_key)
613 if (BN_cmp (conf.rsa_key->n, conf.thisnode->rsa_key->n) != 0 646 if (BN_cmp (conf.rsa_key->n, conf.thisnode->rsa_key->n) != 0
614 || BN_cmp (conf.rsa_key->e, conf.thisnode->rsa_key->e) != 0) 647 || BN_cmp (conf.rsa_key->e, conf.thisnode->rsa_key->e) != 0)
615 { 648 {
616 slog (L_NOTICE, _("private hostkey and public node key mismatch: is '%s' the correct node?"), ::thisnode); 649 slog (L_NOTICE, _("private hostkey and public node key mismatch: is '%s' the correct node?"), ::thisnode);
617 exit (EXIT_FAILURE); 650 exit (EXIT_FAILURE);
618 } 651 }
619 652
620 free (fname);
621
622 for (configuration::node_vector::iterator i = conf.nodes.begin(); i != conf.nodes.end(); ++i) 653 for (configuration::node_vector::iterator i = conf.nodes.begin(); i != conf.nodes.end(); ++i)
623 (*i)->finalise (); 654 (*i)->finalise ();
624} 655}
625 656
657char *
626char *configuration::config_filename (const char *name, const char *dflt) 658configuration::config_filename (const char *name, const char *dflt)
627{ 659{
628 char *fname; 660 char *fname;
629 661
630 asprintf (&fname, name ? name : dflt, ::thisnode); 662 asprintf (&fname, name ? name : dflt, ::thisnode);
631 663
635 asprintf (&fname, "%s/%s", confbase, rname); 667 asprintf (&fname, "%s/%s", confbase, rname);
636 free (rname); 668 free (rname);
637 } 669 }
638 670
639 return fname; 671 return fname;
672}
673
674void
675conf_node::print ()
676{
677 printf ("%4d fe:fd:80:00:0%1x:%02x %c %-8.8s %-10.10s %02x %s%s%d\n",
678 id,
679 id >> 8, id & 0xff,
680 compress ? 'Y' : 'N',
681 connectmode == C_ONDEMAND ? "ondemand"
682 : connectmode == C_NEVER ? "never"
683 : connectmode == C_ALWAYS ? "always"
684 : connectmode == C_DISABLED ? "disabled"
685 : "",
686 nodename,
687 protocols,
688 hostname ? hostname : "",
689 hostname ? ":" : "",
690 hostname ? udp_port : 0
691 );
640} 692}
641 693
642void 694void
643configuration::print () 695configuration::print ()
644{ 696{
651 printf (_("interface: %s\n"), ifname); 703 printf (_("interface: %s\n"), ifname);
652 printf (_("primary rsa key: %s\n"), prikeyfile ? prikeyfile : "<default>"); 704 printf (_("primary rsa key: %s\n"), prikeyfile ? prikeyfile : "<default>");
653 printf (_("rsa key size: %d\n"), rsa_key ? RSA_size (rsa_key) * 8 : -1); 705 printf (_("rsa key size: %d\n"), rsa_key ? RSA_size (rsa_key) * 8 : -1);
654 printf ("\n"); 706 printf ("\n");
655 707
656 printf ("%4s %-17s %s %-8.8s %-10.10s %s\n", 708 printf ("%4s %-17s %s %-8.8s %-10.10s %04s %s\n",
657 _("ID#"), _("MAC"), _("Com"), _("Conmode"), _("Node"), _("Host:Port")); 709 _("ID#"), _("MAC"), _("Com"), _("Conmode"), _("Node"), _("Prot"), _("Host:Port"));
658 710
659 for (node_vector::iterator i = nodes.begin (); i != nodes.end (); ++i) 711 for (node_vector::iterator i = nodes.begin (); i != nodes.end (); ++i)
660 (*i)->print (); 712 (*i)->print ();
661 713
662 printf ("\n"); 714 printf ("\n");
672configuration::~configuration () 724configuration::~configuration ()
673{ 725{
674 cleanup (); 726 cleanup ();
675} 727}
676 728
677

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines