merged multi-auth branch back into trunk
[strongswan.git] / src / pluto / plutomain.c
1 /* Pluto main program
2 * Copyright (C) 1997 Angelos D. Keromytis.
3 * Copyright (C) 1998-2001 D. Hugh Redelmeier.
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 *
15 * RCSID $Id$
16 */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <sys/un.h>
27 #include <fcntl.h>
28 #include <getopt.h>
29 #include <resolv.h>
30 #include <arpa/nameser.h> /* missing from <resolv.h> on old systems */
31 #include <sys/queue.h>
32 #include <sys/prctl.h>
33 #include <pwd.h>
34 #include <grp.h>
35
36 #ifdef CAPABILITIES
37 #include <sys/capability.h>
38 #endif /* CAPABILITIES */
39
40 #include <freeswan.h>
41 #include <settings.h>
42
43 #include <pfkeyv2.h>
44 #include <pfkey.h>
45
46 #include "constants.h"
47 #include "defs.h"
48 #include "id.h"
49 #include "ca.h"
50 #include "certs.h"
51 #include "ac.h"
52 #include "connections.h"
53 #include "foodgroups.h"
54 #include "packet.h"
55 #include "demux.h" /* needs packet.h */
56 #include "server.h"
57 #include "kernel.h"
58 #include "log.h"
59 #include "keys.h"
60 #include "adns.h" /* needs <resolv.h> */
61 #include "dnskey.h" /* needs keys.h and adns.h */
62 #include "rnd.h"
63 #include "state.h"
64 #include "ipsec_doi.h" /* needs demux.h and state.h */
65 #include "ocsp.h"
66 #include "crl.h"
67 #include "fetch.h"
68 #include "xauth.h"
69 #include "sha1.h"
70 #include "md5.h"
71 #include "crypto.h" /* requires sha1.h and md5.h */
72 #include "nat_traversal.h"
73 #include "virtual.h"
74
75 static void
76 usage(const char *mess)
77 {
78 if (mess != NULL && *mess != '\0')
79 fprintf(stderr, "%s\n", mess);
80 fprintf(stderr
81 , "Usage: pluto"
82 " [--help]"
83 " [--version]"
84 " [--optionsfrom <filename>]"
85 " \\\n\t"
86 "[--nofork]"
87 " [--stderrlog]"
88 " [--noklips]"
89 " [--nocrsend]"
90 " \\\n\t"
91 "[--strictcrlpolicy]"
92 " [--crlcheckinterval <interval>]"
93 " [--cachecrls]"
94 " [--uniqueids]"
95 " \\\n\t"
96 "[--interface <ifname>]"
97 " [--ikeport <port-number>]"
98 " \\\n\t"
99 "[--ctlbase <path>]"
100 " \\\n\t"
101 "[--perpeerlogbase <path>] [--perpeerlog]"
102 " \\\n\t"
103 "[--secretsfile <secrets-file>]"
104 " [--policygroupsdir <policygroups-dir>]"
105 " \\\n\t"
106 "[--adns <pathname>]"
107 "[--pkcs11module <path>]"
108 "[--pkcs11keepstate]"
109 "[--pkcs11initargs <string>]"
110 #ifdef DEBUG
111 " \\\n\t"
112 "[--debug-none]"
113 " [--debug-all]"
114 " \\\n\t"
115 "[--debug-raw]"
116 " [--debug-crypt]"
117 " [--debug-parsing]"
118 " [--debug-emitting]"
119 " \\\n\t"
120 "[--debug-control]"
121 " [--debug-lifecycle]"
122 " [--debug-klips]"
123 " [--debug-dns]"
124 " \\\n\t"
125 "[--debug-oppo]"
126 " [--debug-controlmore]"
127 " [--debug-private]"
128 #endif
129 " [ --debug-natt]"
130 " \\\n\t"
131 "[--nat_traversal] [--keep_alive <delay_sec>]"
132 " \\\n\t"
133 "[--force_keepalive] [--disable_port_floating]"
134 " \\\n\t"
135 "[--virtual_private <network_list>]"
136 "\n"
137 "strongSwan %s\n"
138 , ipsec_version_code());
139 exit_pluto(mess == NULL? 0 : 1);
140 }
141
142
143 /* lock file support
144 * - provides convenient way for scripts to find Pluto's pid
145 * - prevents multiple Plutos competing for the same port
146 * - same basename as unix domain control socket
147 * NOTE: will not take account of sharing LOCK_DIR with other systems.
148 */
149
150 static char pluto_lock[sizeof(ctl_addr.sun_path)] = DEFAULT_CTLBASE LOCK_SUFFIX;
151 static bool pluto_lock_created = FALSE;
152
153 /* create lockfile, or die in the attempt */
154 static int
155 create_lock(void)
156 {
157 int fd = open(pluto_lock, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC
158 , S_IRUSR | S_IRGRP | S_IROTH);
159
160 if (fd < 0)
161 {
162 if (errno == EEXIST)
163 {
164 fprintf(stderr, "pluto: lock file \"%s\" already exists\n"
165 , pluto_lock);
166 exit_pluto(10);
167 }
168 else
169 {
170 fprintf(stderr
171 , "pluto: unable to create lock file \"%s\" (%d %s)\n"
172 , pluto_lock, errno, strerror(errno));
173 exit_pluto(1);
174 }
175 }
176 pluto_lock_created = TRUE;
177 return fd;
178 }
179
180 static bool
181 fill_lock(int lockfd, pid_t pid)
182 {
183 char buf[30]; /* holds "<pid>\n" */
184 int len = snprintf(buf, sizeof(buf), "%u\n", (unsigned int) pid);
185 bool ok = len > 0 && write(lockfd, buf, len) == len;
186
187 close(lockfd);
188 return ok;
189 }
190
191 static void
192 delete_lock(void)
193 {
194 if (pluto_lock_created)
195 {
196 delete_ctl_socket();
197 unlink(pluto_lock); /* is noting failure useful? */
198 }
199 }
200
201 /* settings defined by strongswan.conf */
202 settings_t *settings;
203
204 /* by default pluto sends certificate requests to its peers */
205 bool no_cr_send = FALSE;
206
207 /* by default the CRL policy is lenient */
208 bool strict_crl_policy = FALSE;
209
210 /* by default CRLs are cached locally as files */
211 bool cache_crls = FALSE;
212
213 /* by default pluto does not check crls dynamically */
214 long crl_check_interval = 0;
215
216 /* path to the PKCS#11 module */
217 char *pkcs11_module_path = NULL;
218
219 /* by default pluto logs out after every smartcard use */
220 bool pkcs11_keep_state = FALSE;
221
222 /* by default pluto does not allow pkcs11 proxy access via whack */
223 bool pkcs11_proxy = FALSE;
224
225 /* argument string to pass to PKCS#11 module.
226 * Not used for compliant modules, just for NSS softoken
227 */
228 static const char *pkcs11_init_args = NULL;
229
230 int
231 main(int argc, char **argv)
232 {
233 bool fork_desired = TRUE;
234 bool log_to_stderr_desired = FALSE;
235 bool nat_traversal = FALSE;
236 bool nat_t_spf = TRUE; /* support port floating */
237 unsigned int keep_alive = 0;
238 bool force_keepalive = FALSE;
239 char *virtual_private = NULL;
240 int lockfd;
241 #ifdef CAPABILITIES
242 cap_t caps;
243 int keep[] = { CAP_NET_ADMIN, CAP_NET_BIND_SERVICE };
244 #endif /* CAPABILITIES */
245
246 /* getting settings from strongswan.conf */
247 settings = settings_create(STRONGSWAN_CONF);
248
249 /* handle arguments */
250 for (;;)
251 {
252 # define DBG_OFFSET 256
253 static const struct option long_opts[] = {
254 /* name, has_arg, flag, val */
255 { "help", no_argument, NULL, 'h' },
256 { "version", no_argument, NULL, 'v' },
257 { "optionsfrom", required_argument, NULL, '+' },
258 { "nofork", no_argument, NULL, 'd' },
259 { "stderrlog", no_argument, NULL, 'e' },
260 { "noklips", no_argument, NULL, 'n' },
261 { "nocrsend", no_argument, NULL, 'c' },
262 { "strictcrlpolicy", no_argument, NULL, 'r' },
263 { "crlcheckinterval", required_argument, NULL, 'x'},
264 { "cachecrls", no_argument, NULL, 'C' },
265 { "uniqueids", no_argument, NULL, 'u' },
266 { "interface", required_argument, NULL, 'i' },
267 { "ikeport", required_argument, NULL, 'p' },
268 { "ctlbase", required_argument, NULL, 'b' },
269 { "secretsfile", required_argument, NULL, 's' },
270 { "foodgroupsdir", required_argument, NULL, 'f' },
271 { "perpeerlogbase", required_argument, NULL, 'P' },
272 { "perpeerlog", no_argument, NULL, 'l' },
273 { "policygroupsdir", required_argument, NULL, 'f' },
274 #ifdef USE_LWRES
275 { "lwdnsq", required_argument, NULL, 'a' },
276 #else /* !USE_LWRES */
277 { "adns", required_argument, NULL, 'a' },
278 #endif /* !USE_LWRES */
279 { "pkcs11module", required_argument, NULL, 'm' },
280 { "pkcs11keepstate", no_argument, NULL, 'k' },
281 { "pkcs11initargs", required_argument, NULL, 'z' },
282 { "pkcs11proxy", no_argument, NULL, 'y' },
283 { "nat_traversal", no_argument, NULL, '1' },
284 { "keep_alive", required_argument, NULL, '2' },
285 { "force_keepalive", no_argument, NULL, '3' },
286 { "disable_port_floating", no_argument, NULL, '4' },
287 { "debug-natt", no_argument, NULL, '5' },
288 { "virtual_private", required_argument, NULL, '6' },
289 #ifdef DEBUG
290 { "debug-none", no_argument, NULL, 'N' },
291 { "debug-all", no_argument, NULL, 'A' },
292 { "debug-raw", no_argument, NULL, DBG_RAW + DBG_OFFSET },
293 { "debug-crypt", no_argument, NULL, DBG_CRYPT + DBG_OFFSET },
294 { "debug-parsing", no_argument, NULL, DBG_PARSING + DBG_OFFSET },
295 { "debug-emitting", no_argument, NULL, DBG_EMITTING + DBG_OFFSET },
296 { "debug-control", no_argument, NULL, DBG_CONTROL + DBG_OFFSET },
297 { "debug-lifecycle", no_argument, NULL, DBG_LIFECYCLE + DBG_OFFSET },
298 { "debug-klips", no_argument, NULL, DBG_KLIPS + DBG_OFFSET },
299 { "debug-dns", no_argument, NULL, DBG_DNS + DBG_OFFSET },
300 { "debug-oppo", no_argument, NULL, DBG_OPPO + DBG_OFFSET },
301 { "debug-controlmore", no_argument, NULL, DBG_CONTROLMORE + DBG_OFFSET },
302 { "debug-private", no_argument, NULL, DBG_PRIVATE + DBG_OFFSET },
303
304 { "impair-delay-adns-key-answer", no_argument, NULL, IMPAIR_DELAY_ADNS_KEY_ANSWER + DBG_OFFSET },
305 { "impair-delay-adns-txt-answer", no_argument, NULL, IMPAIR_DELAY_ADNS_TXT_ANSWER + DBG_OFFSET },
306 { "impair-bust-mi2", no_argument, NULL, IMPAIR_BUST_MI2 + DBG_OFFSET },
307 { "impair-bust-mr2", no_argument, NULL, IMPAIR_BUST_MR2 + DBG_OFFSET },
308 #endif
309 { 0,0,0,0 }
310 };
311 /* Note: we don't like the way short options get parsed
312 * by getopt_long, so we simply pass an empty string as
313 * the list. It could be "hvdenp:l:s:" "NARXPECK".
314 */
315 int c = getopt_long(argc, argv, "", long_opts, NULL);
316
317 /* Note: "breaking" from case terminates loop */
318 switch (c)
319 {
320 case EOF: /* end of flags */
321 break;
322
323 case 0: /* long option already handled */
324 continue;
325
326 case ':': /* diagnostic already printed by getopt_long */
327 case '?': /* diagnostic already printed by getopt_long */
328 usage("");
329 break; /* not actually reached */
330
331 case 'h': /* --help */
332 usage(NULL);
333 break; /* not actually reached */
334
335 case 'v': /* --version */
336 {
337 const char **sp = ipsec_copyright_notice();
338
339 printf("%s%s\n", ipsec_version_string(),
340 compile_time_interop_options);
341 for (; *sp != NULL; sp++)
342 puts(*sp);
343 }
344 exit_pluto(0);
345 break; /* not actually reached */
346
347 case '+': /* --optionsfrom <filename> */
348 optionsfrom(optarg, &argc, &argv, optind, stderr);
349 /* does not return on error */
350 continue;
351
352 case 'd': /* --nofork*/
353 fork_desired = FALSE;
354 continue;
355
356 case 'e': /* --stderrlog */
357 log_to_stderr_desired = TRUE;
358 continue;
359
360 case 'n': /* --noklips */
361 no_klips = TRUE;
362 continue;
363
364 case 'c': /* --nocrsend */
365 no_cr_send = TRUE;
366 continue;
367
368 case 'r': /* --strictcrlpolicy */
369 strict_crl_policy = TRUE;
370 continue;
371
372 case 'x': /* --crlcheckinterval <time>*/
373 if (optarg == NULL || !isdigit(optarg[0]))
374 usage("missing interval time");
375
376 {
377 char *endptr;
378 long interval = strtol(optarg, &endptr, 0);
379
380 if (*endptr != '\0' || endptr == optarg
381 || interval <= 0)
382 usage("<interval-time> must be a positive number");
383 crl_check_interval = interval;
384 }
385 continue;
386
387 case 'C': /* --cachecrls */
388 cache_crls = TRUE;
389 continue;
390
391 case 'u': /* --uniqueids */
392 uniqueIDs = TRUE;
393 continue;
394
395 case 'i': /* --interface <ifname> */
396 if (!use_interface(optarg))
397 usage("too many --interface specifications");
398 continue;
399
400 case 'p': /* --port <portnumber> */
401 if (optarg == NULL || !isdigit(optarg[0]))
402 usage("missing port number");
403
404 {
405 char *endptr;
406 long port = strtol(optarg, &endptr, 0);
407
408 if (*endptr != '\0' || endptr == optarg
409 || port <= 0 || port > 0x10000)
410 usage("<port-number> must be a number between 1 and 65535");
411 pluto_port = port;
412 }
413 continue;
414
415 case 'b': /* --ctlbase <path> */
416 if (snprintf(ctl_addr.sun_path, sizeof(ctl_addr.sun_path)
417 , "%s%s", optarg, CTL_SUFFIX) == -1)
418 usage("<path>" CTL_SUFFIX " too long for sun_path");
419 if (snprintf(info_addr.sun_path, sizeof(info_addr.sun_path)
420 , "%s%s", optarg, INFO_SUFFIX) == -1)
421 usage("<path>" INFO_SUFFIX " too long for sun_path");
422 if (snprintf(pluto_lock, sizeof(pluto_lock)
423 , "%s%s", optarg, LOCK_SUFFIX) == -1)
424 usage("<path>" LOCK_SUFFIX " must fit");
425 continue;
426
427 case 's': /* --secretsfile <secrets-file> */
428 shared_secrets_file = optarg;
429 continue;
430
431 case 'f': /* --policygroupsdir <policygroups-dir> */
432 policygroups_dir = optarg;
433 continue;
434
435 case 'a': /* --adns <pathname> */
436 pluto_adns_option = optarg;
437 continue;
438
439 case 'm': /* --pkcs11module <pathname> */
440 pkcs11_module_path = optarg;
441 continue;
442
443 case 'k': /* --pkcs11keepstate */
444 pkcs11_keep_state = TRUE;
445 continue;
446
447 case 'y': /* --pkcs11proxy */
448 pkcs11_proxy = TRUE;
449 continue;
450
451 case 'z': /* --pkcs11initargs */
452 pkcs11_init_args = optarg;
453 continue;
454
455 #ifdef DEBUG
456 case 'N': /* --debug-none */
457 base_debugging = DBG_NONE;
458 continue;
459
460 case 'A': /* --debug-all */
461 base_debugging = DBG_ALL;
462 continue;
463 #endif
464
465 case 'P': /* --perpeerlogbase */
466 base_perpeer_logdir = optarg;
467 continue;
468
469 case 'l':
470 log_to_perpeer = TRUE;
471 continue;
472
473 case '1': /* --nat_traversal */
474 nat_traversal = TRUE;
475 continue;
476 case '2': /* --keep_alive */
477 keep_alive = atoi(optarg);
478 continue;
479 case '3': /* --force_keepalive */
480 force_keepalive = TRUE;
481 continue;
482 case '4': /* --disable_port_floating */
483 nat_t_spf = FALSE;
484 continue;
485 case '5': /* --debug-nat_t */
486 base_debugging |= DBG_NATT;
487 continue;
488 case '6': /* --virtual_private */
489 virtual_private = optarg;
490 continue;
491
492 default:
493 #ifdef DEBUG
494 if (c >= DBG_OFFSET)
495 {
496 base_debugging |= c - DBG_OFFSET;
497 continue;
498 }
499 # undef DBG_OFFSET
500 #endif
501 bad_case(c);
502 }
503 break;
504 }
505 if (optind != argc)
506 usage("unexpected argument");
507 reset_debugging();
508 lockfd = create_lock();
509
510 /* select between logging methods */
511
512 if (log_to_stderr_desired)
513 log_to_syslog = FALSE;
514 else
515 log_to_stderr = FALSE;
516
517 /* set the logging function of pfkey debugging */
518 #ifdef DEBUG
519 pfkey_debug_func = DBG_log;
520 #else
521 pfkey_debug_func = NULL;
522 #endif
523
524 /* create control socket.
525 * We must create it before the parent process returns so that
526 * there will be no race condition in using it. The easiest
527 * place to do this is before the daemon fork.
528 */
529 {
530 err_t ugh = init_ctl_socket();
531
532 if (ugh != NULL)
533 {
534 fprintf(stderr, "pluto: %s", ugh);
535 exit_pluto(1);
536 }
537 }
538
539 /* If not suppressed, do daemon fork */
540
541 if (fork_desired)
542 {
543 {
544 pid_t pid = fork();
545
546 if (pid < 0)
547 {
548 int e = errno;
549
550 fprintf(stderr, "pluto: fork failed (%d %s)\n",
551 errno, strerror(e));
552 exit_pluto(1);
553 }
554
555 if (pid != 0)
556 {
557 /* parent: die, after filling PID into lock file.
558 * must not use exit_pluto: lock would be removed!
559 */
560 exit(fill_lock(lockfd, pid)? 0 : 1);
561 }
562 }
563
564 if (setsid() < 0)
565 {
566 int e = errno;
567
568 fprintf(stderr, "setsid() failed in main(). Errno %d: %s\n",
569 errno, strerror(e));
570 exit_pluto(1);
571 }
572 }
573 else
574 {
575 /* no daemon fork: we have to fill in lock file */
576 (void) fill_lock(lockfd, getpid());
577 fprintf(stdout, "Pluto initialized\n");
578 fflush(stdout);
579 }
580
581 /* Close everything but ctl_fd and (if needed) stderr.
582 * There is some danger that a library that we don't know
583 * about is using some fd that we don't know about.
584 * I guess we'll soon find out.
585 */
586 {
587 int i;
588
589 for (i = getdtablesize() - 1; i >= 0; i--) /* Bad hack */
590 {
591 if ((!log_to_stderr || i != 2) && i != ctl_fd)
592 close(i);
593 }
594
595 /* make sure that stdin, stdout, stderr are reserved */
596 if (open("/dev/null", O_RDONLY) != 0)
597 abort();
598 if (dup2(0, 1) != 1)
599 abort();
600 if (!log_to_stderr && dup2(0, 2) != 2)
601 abort();
602 }
603
604 init_constants();
605 init_log("pluto");
606
607 /* Note: some scripts may look for this exact message -- don't change
608 * ipsec barf was one, but it no longer does.
609 */
610 plog("Starting Pluto (strongSwan Version %s%s)"
611 , ipsec_version_code()
612 , compile_time_interop_options);
613
614 init_nat_traversal(nat_traversal, keep_alive, force_keepalive, nat_t_spf);
615 init_virtual_ip(virtual_private);
616 scx_init(pkcs11_module_path, pkcs11_init_args); /* load and initialize PKCS #11 module */
617 xauth_init(); /* load and initialize XAUTH module */
618 init_rnd_pool();
619 init_secret();
620 init_states();
621 init_crypto();
622 init_demux();
623 init_kernel();
624 init_adns();
625 init_id();
626 init_fetch();
627
628 /* drop unneeded capabilities and change UID/GID */
629
630 prctl(PR_SET_KEEPCAPS, 1);
631
632 #ifdef IPSEC_GROUP
633 {
634 struct group group, *grp;
635 char buf[1024];
636
637 if (getgrnam_r(IPSEC_GROUP, &group, buf, sizeof(buf), &grp) != 0 ||
638 grp == NULL || setgid(grp->gr_gid) != 0)
639 {
640 plog("unable to change daemon group");
641 abort();
642 }
643 }
644 #endif
645 #ifdef IPSEC_USER
646 {
647 struct passwd passwd, *pwp;
648 char buf[1024];
649
650 if (getpwnam_r(IPSEC_USER, &passwd, buf, sizeof(buf), &pwp) != 0 ||
651 pwp == NULL || setuid(pwp->pw_uid) != 0)
652 {
653 plog("unable to change daemon user");
654 abort();
655 }
656 }
657 #endif
658
659 #ifdef CAPABILITIES
660 caps = cap_init();
661 cap_set_flag(caps, CAP_EFFECTIVE, 2, keep, CAP_SET);
662 cap_set_flag(caps, CAP_INHERITABLE, 2, keep, CAP_SET);
663 cap_set_flag(caps, CAP_PERMITTED, 2, keep, CAP_SET);
664 if (cap_set_proc(caps) != 0)
665 {
666 plog("unable to drop daemon capabilities");
667 abort();
668 }
669 cap_free(caps);
670 #endif /* CAPABILITIES */
671
672 /* loading X.509 CA certificates */
673 load_authcerts("CA cert", CA_CERT_PATH, AUTH_CA);
674 /* loading X.509 AA certificates */
675 load_authcerts("AA cert", AA_CERT_PATH, AUTH_AA);
676 /* loading X.509 OCSP certificates */
677 load_authcerts("OCSP cert", OCSP_CERT_PATH, AUTH_OCSP);
678 /* loading X.509 CRLs */
679 load_crls();
680 /* loading attribute certificates (experimental) */
681 load_acerts();
682
683 daily_log_event();
684 call_server();
685 return -1; /* Shouldn't ever reach this */
686 }
687
688 /* leave pluto, with status.
689 * Once child is launched, parent must not exit this way because
690 * the lock would be released.
691 *
692 * 0 OK
693 * 1 general discomfort
694 * 10 lock file exists
695 */
696 void
697 exit_pluto(int status)
698 {
699 reset_globals(); /* needed because we may be called in odd state */
700 free_preshared_secrets();
701 free_remembered_public_keys();
702 delete_every_connection();
703 free_crl_fetch(); /* free chain of crl fetch requests */
704 free_ocsp_fetch(); /* free chain of ocsp fetch requests */
705 free_authcerts(); /* free chain of X.509 authority certificates */
706 free_crls(); /* free chain of X.509 CRLs */
707 free_acerts(); /* free chain of X.509 attribute certificates */
708 free_ca_infos(); /* free chain of X.509 CA information records */
709 free_ocsp(); /* free ocsp cache */
710 free_ifaces();
711 scx_finalize(); /* finalize and unload PKCS #11 module */
712 xauth_finalize(); /* finalize and unload XAUTH module */
713 settings->destroy(settings);
714 stop_adns();
715 free_md_pool();
716 delete_lock();
717 #ifdef LEAK_DETECTIVE
718 report_leaks();
719 #endif /* LEAK_DETECTIVE */
720 close_log();
721 exit(status);
722 }
723
724 /*
725 * Local Variables:
726 * c-basic-offset:4
727 * c-style: pluto
728 * End:
729 */