source: trunk/src/sh_mounts.c@ 19

Last change on this file since 19 was 1, checked in by katerina, 19 years ago

Initial import

File size: 19.0 KB
Line 
1/*
2 * File: sh_mounts.c
3 * Desc: A module for Samhain; checks for mounts present and options on them.
4 * Auth: Cian Synnott <cian.synnott@eircom.net>
5 *
6 */
7/* This program is free software; you can redistribute it */
8/* and/or modify */
9/* it under the terms of the GNU General Public License as */
10/* published by */
11/* the Free Software Foundation; either version 2 of the License, or */
12/* (at your option) any later version. */
13/* */
14/* This program is distributed in the hope that it will be useful, */
15/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
16/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
17/* GNU General Public License for more details. */
18/* */
19/* You should have received a copy of the GNU General Public License */
20/* along with this program; if not, write to the Free Software */
21/* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
22
23#include "config_xor.h"
24
25
26/* Used in the call tracing macros to keep track of where we are in the code */
27#undef FIL__
28#define FIL__ _("sh_mounts.c")
29
30
31#include "samhain.h"
32#include "sh_utils.h"
33#include "sh_error.h"
34#include "sh_modules.h"
35#include "sh_mounts.h"
36
37#ifdef SH_USE_MOUNTS
38#if defined (SH_WITH_CLIENT) || defined (SH_STANDALONE)
39
40/*
41 * #ifdef HAVE_STRING_H
42 * #include <string.h>
43 * #endif
44 */
45
46#ifdef TM_IN_SYS_TIME
47#include <sys/time.h>
48#else
49#include <time.h>
50#endif
51
52/* Prototypes for configuration functions */
53int sh_mounts_config_activate (char * opt);
54int sh_mounts_config_timer (char * opt);
55int sh_mounts_config_mount (char * opt);
56int sh_mounts_config_sevmnt (char * opt);
57int sh_mounts_config_sevopt (char * opt);
58
59/* Prototype for the function to read info on mounted filesystems */
60static struct sh_mounts_mnt *readmounts(void);
61
62/* Table for configuration options, and pointers to the functions that will
63 * configure them. Each function is passed the string resulting from stripping
64 * the option and the "equals" from the config file; e.g. MountCheckActive=1 in
65 * the configuration file will result in the string "1" being passed to
66 * sh_mounts_config_activate() */
67sh_rconf sh_mounts_table[] = {
68 {
69 N_("mountcheckactive"),
70 sh_mounts_config_activate
71 },
72 {
73 N_("mountcheckinterval"),
74 sh_mounts_config_timer
75 },
76 {
77 N_("checkmount"),
78 sh_mounts_config_mount
79 },
80 {
81 N_("severitymountmissing"),
82 sh_mounts_config_sevmnt
83 },
84 {
85 N_("severityoptionmissing"),
86 sh_mounts_config_sevopt
87 },
88 {
89 NULL,
90 NULL
91 },
92};
93
94/* Structures for storing my configuration information, and functions for
95 * manipulating them */
96struct sh_mounts_mnt {
97 char * path;
98 struct sh_mounts_opt * opts;
99 struct sh_mounts_mnt * next;
100};
101
102struct sh_mounts_opt {
103 char * opt;
104 struct sh_mounts_opt * next;
105};
106
107/* Return the mount structure whose path matches 'mnt' or NULL if not found */
108static
109struct sh_mounts_mnt *sh_mounts_mnt_member(struct sh_mounts_mnt *m, char *mnt)
110{
111 struct sh_mounts_mnt *it;
112
113 for (it = m; it != NULL; it = it->next) {
114 if (0 == sl_strcmp(it->path, mnt)) {
115 return it;
116 }
117 }
118 return NULL;
119}
120
121/* Return the opt structure whose option matches 'opt' or NULL if not found */
122static
123struct sh_mounts_opt *sh_mounts_opt_member(struct sh_mounts_opt *o, char *opt)
124{
125 struct sh_mounts_opt *it;
126
127 for (it = o; it != NULL; it = it->next) {
128 /* if (!strcmp(it->opt, opt)) { */
129 if (0 == sl_strcmp(it->opt, opt)) {
130 return it;
131 }
132 }
133 return NULL;
134}
135
136static
137void sh_mounts_opt_free(struct sh_mounts_opt *o) {
138 if (o != NULL) {
139 sh_mounts_opt_free(o->next);
140 SH_FREE(o->opt);
141 SH_FREE(o);
142 }
143}
144
145static
146void sh_mounts_mnt_free(struct sh_mounts_mnt *m) {
147 if (m != NULL) {
148 sh_mounts_mnt_free(m->next);
149 sh_mounts_opt_free(m->opts);
150 SH_FREE(m->path);
151 SH_FREE(m);
152 }
153}
154
155/* Some configuration variables I'll be using */
156static time_t lastcheck = (time_t) 0;
157static int ShMountsActive = S_FALSE;
158static time_t ShMountsInterval = 86400;
159static int ShMountsSevMnt = 7;
160static int ShMountsSevOpt = 7;
161
162static struct sh_mounts_mnt *mountlist = NULL;
163
164/* Module initialisation
165 * This is called once at the start of each samhain run.
166 * Non-configuration setup code should be placed here. */
167int sh_mounts_init ()
168{
169 SL_ENTER(_("sh_mounts_init"));
170
171 /* This is a little odd. Because we've built the configured mount list at
172 * this point, if we've set the module inactive, we need to free the list -
173 * otherwise when we reconf() with it set active, we'll end up with a
174 * duplicated list. Interesting. */
175 if (ShMountsActive == S_FALSE) {
176 sh_mounts_mnt_free(mountlist);
177 mountlist = NULL;
178 SL_RETURN(-1, _("sh_mounts_init"));
179 }
180
181 lastcheck = time(NULL);
182
183 SL_RETURN(0, _("sh_mounts_init"));
184}
185
186/* Module timer
187 * This timer function is called periodically with the current time to see if
188 * it is time to run the module's "check" function. On nonzero return, the
189 * check is run. */
190int sh_mounts_timer (time_t tcurrent)
191{
192 SL_ENTER(_("sh_mounts_timer"));
193
194 if ((time_t) (tcurrent - lastcheck) >= ShMountsInterval) {
195 lastcheck = tcurrent;
196 SL_RETURN(-1, _("sh_mounts_timer"));
197 }
198
199 SL_RETURN(0, _("sh_mounts_timer"));
200}
201
202/* Module check
203 * The business end of things. This is the actual check code for this module.
204 * Everything you want to do periodically should go here. */
205int sh_mounts_check ()
206{
207 struct sh_mounts_mnt *memlist;
208 struct sh_mounts_mnt *cfgmnt, *mnt;
209 struct sh_mounts_opt *cfgopt, *opt;
210
211 SL_ENTER(_("sh_mounts_check"));
212
213 /* Log the check run. For each message type you want, you need to define it
214 * as an enum in sh_cat.h, and then set it up in terms of priority and format
215 * string in sh_cat.c */
216 sh_error_handle(-1, FIL__, __LINE__, 0, MSG_MNT_CHECK);
217
218 /* Read the list of mounts from memory */
219 memlist = readmounts();
220
221 if (memlist == NULL) {
222 sh_error_handle(-1, FIL__, __LINE__, 0, MSG_MNT_MEMLIST);
223 }
224
225 /* For each mount we are configured to check, run through the list of mounted
226 * filesystems and compare the pathnames */
227 for (cfgmnt = mountlist; cfgmnt != NULL; cfgmnt = cfgmnt->next) {
228 mnt = sh_mounts_mnt_member(memlist, cfgmnt->path);
229
230 if (mnt) {
231 for (cfgopt = cfgmnt->opts; cfgopt != NULL; cfgopt = cfgopt->next) {
232 opt = sh_mounts_opt_member(mnt->opts, cfgopt->opt);
233
234 if (!opt) {
235 sh_error_handle(ShMountsSevOpt, FIL__, __LINE__, 0, MSG_MNT_OPTMISS,
236 cfgmnt->path, cfgopt->opt);
237 }
238 }
239 }
240
241 else {
242 sh_error_handle(ShMountsSevMnt, FIL__, __LINE__, 0, MSG_MNT_MNTMISS,
243 cfgmnt->path);
244 }
245 }
246
247 /* Make sure to clean up after ourselves */
248 sh_mounts_mnt_free(memlist);
249
250 SL_RETURN(0, _("sh_mounts_check"));
251}
252
253/* Module cleanup
254 * The end of the tour - when samhain is shutting down, this is run. */
255int sh_mounts_cleanup ()
256{
257 SL_ENTER(_("sh_mounts_cleanup"));
258 sh_mounts_mnt_free(mountlist);
259 mountlist = NULL;
260 SL_RETURN( (0), _("sh_mounts_cleanup"));
261}
262
263/* Module reconfiguration
264 * Run on receipt of a HUP. Right now this is identical to _end(), but it may
265 * not always be. */
266int sh_mounts_reconf()
267{
268 SL_ENTER(_("sh_mounts_null"));
269 sh_mounts_mnt_free(mountlist);
270 mountlist = NULL;
271 SL_RETURN( (0), _("sh_mounts_null"));
272}
273
274/* Module configuration
275 * These functions are called when the configuration file is being parsed. */
276
277/* Configure to check a particular mount */
278int sh_mounts_config_mount (char * opt)
279{
280 struct sh_mounts_mnt *m;
281 struct sh_mounts_opt *o;
282 char *sp, *temp;
283
284 SL_ENTER(_("sh_mounts_config_mount"));
285
286 /* It's probably best to make a copy of opt before messing about with it
287 * via string functions. Good practice and all that. */
288 temp = sh_util_strdup(opt);
289
290 /* Since we're going to "consume" this new buffer, it'll be good to have a
291 * reference to it's allocated memory so we can free it later. Let's use
292 * temp for that, and the now-unused "opt" for consumption */
293 opt = temp;
294
295 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof(struct sh_mounts_mnt));
296
297 /* First, strip out the mount path. */
298 m->path = sh_util_strdup(sh_util_strsep(&opt, " \t"));
299 m->opts = NULL;
300
301 /* Now get all of the mount options - they can be delimited by comma or
302 * whitespace */
303 while (opt != NULL) {
304 sp = sh_util_strsep(&opt, ", \t");
305
306 /* This just catches multiple separators appearing together */
307 if (*sp == '\0') {
308 continue;
309 }
310
311 o = (struct sh_mounts_opt *) SH_ALLOC(sizeof(struct sh_mounts_opt));
312 o->next = m->opts;
313 m->opts = o;
314
315 o->opt = sh_util_strdup(sp);
316 }
317
318 /* Add to the list of configured mounts */
319 m->next = mountlist;
320 mountlist = m;
321
322 /* Free the string buffer we allocated earlier */
323 SH_FREE(temp);
324
325 SL_RETURN(0, _("sh_mounts_config_mount"));
326}
327
328/* Simply sets our boolean as to whether this module is active */
329int sh_mounts_config_activate (char * opt)
330{
331 int i;
332 SL_ENTER(_("sh_mounts_config_activate"));
333 i = sh_util_flagval(opt, &ShMountsActive);
334 SL_RETURN(i, _("sh_mounts_config_activate"));
335}
336
337/* Sets up our timer */
338int sh_mounts_config_timer (char * opt)
339{
340 long val;
341 int retval = 0;
342
343 SL_ENTER(_("sh_mounts_config_timer"));
344 val = strtol (opt, (char **)NULL, 10);
345 if (val <= 0)
346 {
347 sh_error_handle (-1, FIL__, __LINE__, EINVAL, MSG_EINVALS,
348 _("mounts timer"), opt);
349 retval = -1;
350 }
351 val = (val <= 0 ? 86400 : val);
352
353 ShMountsInterval = (time_t) val;
354
355 SL_RETURN(retval, _("sh_mounts_config_timer"));
356}
357
358/* Configure severity for "mount missing" messages */
359int sh_mounts_config_sevmnt (char * opt)
360{
361 int retval = 0;
362 char tmp[32];
363
364
365 SL_ENTER(_("sh_mounts_config_sevmnt"));
366 tmp[0] = '='; tmp[1] = '\0';
367 (void) sl_strlcat (tmp, opt, 32);
368 retval = sh_error_set_level (tmp, &ShMountsSevMnt);
369 SL_RETURN(retval, _("sh_mounts_config_sevmnt"));
370}
371
372int sh_mounts_config_sevopt (char * opt)
373{
374 int retval = 0;
375 char tmp[32];
376
377 SL_ENTER(_("sh_mounts_config_sevopt"));
378 tmp[0] = '='; tmp[1] = '\0';
379 (void) sl_strlcat (tmp, opt, 32);
380 retval = sh_error_set_level (tmp, &ShMountsSevOpt);
381 SL_RETURN(retval, _("sh_mounts_config_sevopt"));
382}
383
384
385/*
386 * Below here we have the code for actually reading options on mounted fs's
387 * I've just got code here to work on FreeBSD, Linux and Solaris. I'm sure
388 * others could be added. Note that some small bits of the OS-specific code
389 * are from mountlist.c in GNU fileutils.
390 */
391
392/* FreeBSD includes */
393#ifdef HOST_IS_FREEBSD
394#include <sys/param.h>
395#include <sys/ucred.h>
396#include <sys/mount.h>
397#endif
398
399/* Linux includes */
400#ifdef HOST_IS_LINUX
401#include <stdio.h>
402#include <mntent.h>
403#endif
404
405/* Solaris includes */
406#ifdef HOST_IS_SOLARIS
407#include <stdio.h>
408#include <sys/mnttab.h>
409#endif
410
411/* HP_UX includes */
412#ifdef HOST_IS_HPUX
413#include <stdio.h>
414#include <mntent.h>
415#endif
416
417/* AIX includes and helper routines (from gnome-vfs-unix-mounts.c */
418#if 0
419#ifdef HOST_IS_AIX
420#include <stdio.h>
421#include <string.h>
422#include <ctype.h>
423
424/* gnome-vfs-unix-mounts.c - read and monitor fstab/mtab
425
426 Copyright (C) 2003 Red Hat, Inc
427
428 The Gnome Library is free software; you can redistribute it and/or
429 modify it under the terms of the GNU Library General Public License as
430 published by the Free Software Foundation; either version 2 of the
431 License, or (at your option) any later version.
432
433 The Gnome Library is distributed in the hope that it will be useful,
434 but WITHOUT ANY WARRANTY; without even the implied warranty of
435 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
436 Library General Public License for more details.
437
438 You should have received a copy of the GNU Library General Public
439 License along with the Gnome Library; see the file COPYING.LIB. If not,
440 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
441 Boston, MA 02111-1307, USA.
442
443 Author: Alexander Larsson <alexl@redhat.com>
444*/
445
446/* read character, ignoring comments (begin with '*', end with '\n' */
447static int aix_fs_getc (FILE *fd)
448{
449 int c;
450
451 while ((c = getc (fd)) == '*') {
452 while (((c = getc (fd)) != '\n') && (c != EOF)) {
453 }
454 }
455}
456
457/* eat all continuous spaces in a file */
458static int aix_fs_ignorespace (FILE *fd)
459{
460 int c;
461
462 while ((c = aix_fs_getc (fd)) != EOF) {
463 if (! (isascii(c) && isspace (c)) ) {
464 ungetc (c,fd);
465 return c;
466 }
467 }
468
469 return EOF;
470}
471
472/* read one word from file */
473static int aix_fs_getword (FILE *fd, char *word, int len)
474{
475 int c;
476 int i = 0;
477
478 --len;
479
480 aix_fs_ignorespace (fd);
481
482 while (((c = aix_fs_getc (fd)) != EOF) && !( isascii(c) && isspace(c) ))
483 {
484 if (c == '"')
485 {
486 while (((c = aix_fs_getc (fd)) != EOF) && (c != '"'))
487 {
488 *word++ = c; ++i;
489 if (i == len)
490 break;
491 }
492 }
493 else
494 {
495 *word++ = c; ++i;
496 }
497 if (i == len)
498 break;
499 }
500 *word = 0;
501
502 return c;
503}
504
505/* PATH_MAX is in sys/limits.h, included via stdio.h
506 */
507typedef struct {
508 char mnt_mount[PATH_MAX];
509 char mnt_special[PATH_MAX];
510 char mnt_fstype[16];
511 char mnt_options[128];
512} AixMountTableEntry;
513
514/* read mount points properties */
515static int aix_fs_get (FILE *fd, AixMountTableEntry *prop)
516{
517 /* Need space for PATH_MAX + ':' (terminating '\0' is in PATH_MAX; SUSv3)
518 */
519 static char word[PATH_MAX+1] = { 0 };
520 char value[PATH_MAX];
521
522 /* reset */
523
524 if (fd == NULL)
525 {
526 word[0] = '\0';
527 return 0;
528 }
529
530 /* read stanza */
531
532 if (word[0] == 0) {
533 if (aix_fs_getword (fd, word, (PATH_MAX+1)) == EOF)
534 return EOF;
535 }
536
537 word[strlen(word) - 1] = 0;
538 sl_strlcpy (prop->mnt_mount, word, PATH_MAX);
539
540 /* read attributes and value */
541
542 while (aix_fs_getword (fd, word, (PATH_MAX+1)) != EOF) {
543 /* test if is attribute or new stanza */
544
545 if (word[strlen(word) - 1] == ':') {
546 return 0;
547 }
548
549 /* read "=" */
550 aix_fs_getword (fd, value, PATH_MAX);
551
552 /* read value */
553 aix_fs_getword (fd, value, PATH_MAX);
554
555 if (strcmp (word, "dev") == 0) {
556 sl_strlcpy (prop->mnt_special, value, PATH_MAX);
557 } else if (strcmp (word, "vfs") == 0) {
558 sl_strlcpy (prop->mnt_fstype, value, 16);
559 } else if (strcmp (word, "options") == 0) {
560 sl_strlcpy(prop->mnt_options, value, 128);
561 }
562 }
563
564 return 0;
565}
566
567/* end AIX helper routines */
568#endif
569#endif
570
571#if defined(HOST_IS_FREEBSD)
572
573/* FreeBSD returns flags instead of strings as mount options, so we'll convert
574 * them here. */
575static
576struct sh_mounts_opt * getoptlist(int flags) {
577 struct sh_mounts_opt *list, *o;
578 int i;
579
580 struct {char *opt; int flag;} table[] = {
581 {"ro", MNT_RDONLY},
582 {"noexec", MNT_NOEXEC},
583 {"nosuid", MNT_NOSUID},
584 {"nodev", MNT_NODEV},
585 {"sync", MNT_SYNCHRONOUS},
586 {"async", MNT_ASYNC},
587 {"local", MNT_LOCAL},
588 {"quota", MNT_QUOTA},
589 {"bound", -1}
590 };
591
592 SL_ENTER(_("getoptlist"));
593
594 list = NULL;
595
596 /* Add any flags found to the list */
597 for (i = 0; table[i].flag != -1; i++) {
598 if (flags & table[i].flag) {
599 o = (struct sh_mounts_opt *) SH_ALLOC(sizeof(struct sh_mounts_opt));
600 o->opt = sh_util_strdup(table[i].opt);
601 o->next = list;
602 list = o;
603 }
604 }
605
606 SL_RETURN(list, _("getoptlist"));
607}
608
609/* Solaris & Linux return identical option string formats */
610#else
611
612/* We just separate the options out by parsing for commas */
613static
614struct sh_mounts_opt * getoptlist(char *opt)
615{
616 struct sh_mounts_opt *list, *o;
617 char *sp, *temp;
618
619 SL_ENTER(_("getoptlist"));
620
621 /* See the comments in sh_mounts_config_mount() above for the reasons for
622 * this arcane little zig-zag */
623 temp = sh_util_strdup(opt);
624 opt = temp;
625
626 list = NULL;
627
628 /* For each option, add to the list */
629 while (opt != NULL) {
630 sp = sh_util_strsep(&opt, ", \t");
631
632 if (*sp == '\0') {
633 continue;
634 }
635
636 o = (struct sh_mounts_opt *) SH_ALLOC(sizeof(struct sh_mounts_opt));
637 o->next = list;
638 list = o;
639
640 o->opt = sh_util_strdup(sp);
641 }
642
643 SH_FREE(temp);
644
645 SL_RETURN(list, _("getoptlist"));
646}
647
648#endif
649
650/* Read the list of mounts from whereever is appropriate to the OS and return
651 * it. Return NULL on error. */
652static struct sh_mounts_mnt * readmounts(void) {
653 struct sh_mounts_mnt *list, *m;
654
655 SL_ENTER(_("readmounts"));
656 m = NULL; /* use it to avoid compiler warning */
657 list = m;
658
659/* The FreeBSD way */
660#ifdef HOST_IS_FREEBSD
661{
662 struct statfs *fsp;
663 int entries;
664
665 entries = getmntinfo(&fsp, MNT_NOWAIT);
666 if (entries < 0) {
667 SL_RETURN((NULL), _("readmounts"));
668 }
669
670 for (; entries-- > 0; fsp++) {
671 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
672 m->path = sh_util_strdup(fsp->f_mntonname);
673 m->opts = getoptlist(fsp->f_flags);
674
675 m->next = list;
676 list = m;
677 }
678}
679#endif
680
681/* The Linux way */
682#ifdef HOST_IS_LINUX
683{
684 struct mntent *mp;
685 FILE *tab = setmntent(_PATH_MOUNTED, "r");
686
687 if (tab == NULL) {
688 SL_RETURN((NULL), _("readmounts"));
689 }
690
691 mp = getmntent(tab);
692 while (mp != NULL) {
693 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
694 m->path = sh_util_strdup(mp->mnt_dir);
695 m->opts = getoptlist(mp->mnt_opts);
696
697 m->next = list;
698 list = m;
699
700 mp = getmntent(tab);
701 }
702
703 (void) endmntent(tab);
704}
705#endif
706
707/* The Solaris way */
708#ifdef HOST_IS_SOLARIS
709{
710 struct mnttab mp;
711 FILE *tab = fopen(MNTTAB, "r");
712
713 if (tab == NULL) {
714 SL_RETURN((NULL), _("readmounts"));
715 }
716
717 while (!getmntent(tab, &mp)) {
718 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
719 m->path = sh_util_strdup(mp.mnt_mountp);
720 m->opts = getoptlist(mp.mnt_mntopts);
721
722 m->next = list;
723 list = m;
724 }
725
726 fclose(tab);
727}
728#endif
729
730
731/* The HP-UX way */
732#ifdef HOST_IS_HPUX
733{
734 struct mntent *mp;
735 FILE *tab = setmntent(MNT_MNTTAB, "r");
736
737 if (tab == NULL) {
738 SL_RETURN((NULL), _("readmounts"));
739 }
740
741 mp = getmntent(tab);
742 while (mp != NULL) {
743 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
744 m->path = sh_util_strdup(mp->mnt_dir);
745 m->opts = getoptlist(mp->mnt_opts);
746
747 m->next = list;
748 list = m;
749
750 mp = getmntent(tab);
751 }
752
753 (void) endmntent(tab);
754}
755#endif
756
757/* The AIX way */
758#if 0
759#ifdef HOST_IS_AIX
760{
761 AixMountTableEntry mntent;
762 FILE *tab = fopen("/etc/filesystems", "r");
763
764 if (tab == NULL) {
765 SL_RETURN((NULL), _("readmounts"));
766 }
767
768 while (!aix_fs_get (tab, &mntent))
769 {
770 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
771 m->path = sh_util_strdup(mntent.mnt_mount);
772 m->opts = getoptlist(mntent.mnt_options);
773
774 m->next = list;
775 list = m;
776
777 mntent.mnt_mount[0] = '\0';
778 mntent.mnt_special[0] = '\0';
779 mntent.mnt_fstype[0] = '\0';
780 mntent.mnt_options[0] = '\0';
781 }
782
783 (void) fclose(tab);
784 aix_fs_get (NULL, NULL); /* reset */
785}
786#endif
787#endif
788
789 SL_RETURN((list), _("readmounts"));
790
791}
792
793
794/* #if defined (SH_WITH_CLIENT) || defined (SH_STANDALONE) */
795#endif
796
797/* #ifdef SH_USE_MOUNTS */
798#endif
799
Note: See TracBrowser for help on using the repository browser.