source: branches/samhain-2_2-branch/src/sh_mounts.c@ 427

Last change on this file since 427 was 34, checked in by rainer, 19 years ago

Code cleanup and minor fixes

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)) {} /* do nothing */
453 }
454}
455
456/* eat all continuous spaces in a file */
457static int aix_fs_ignorespace (FILE *fd)
458{
459 int c;
460
461 while ((c = aix_fs_getc (fd)) != EOF) {
462 if (! (isascii(c) && isspace (c)) ) {
463 ungetc (c,fd);
464 return c;
465 }
466 }
467
468 return EOF;
469}
470
471/* read one word from file */
472static int aix_fs_getword (FILE *fd, char *word, int len)
473{
474 int c;
475 int i = 0;
476
477 --len;
478
479 aix_fs_ignorespace (fd);
480
481 while (((c = aix_fs_getc (fd)) != EOF) && !( isascii(c) && isspace(c) ))
482 {
483 if (c == '"')
484 {
485 while (((c = aix_fs_getc (fd)) != EOF) && (c != '"'))
486 {
487 *word++ = c; ++i;
488 if (i == len)
489 break;
490 }
491 }
492 else
493 {
494 *word++ = c; ++i;
495 }
496 if (i == len)
497 break;
498 }
499 *word = 0;
500
501 return c;
502}
503
504/* PATH_MAX is in sys/limits.h, included via stdio.h
505 */
506typedef struct {
507 char mnt_mount[PATH_MAX];
508 char mnt_special[PATH_MAX];
509 char mnt_fstype[16];
510 char mnt_options[128];
511} AixMountTableEntry;
512
513/* read mount points properties */
514static int aix_fs_get (FILE *fd, AixMountTableEntry *prop)
515{
516 /* Need space for PATH_MAX + ':' (terminating '\0' is in PATH_MAX; SUSv3)
517 */
518 static char word[PATH_MAX+1] = { 0 };
519 char value[PATH_MAX];
520
521 /* reset */
522
523 if (fd == NULL)
524 {
525 word[0] = '\0';
526 return 0;
527 }
528
529 /* read stanza */
530
531 if (word[0] == 0) {
532 if (aix_fs_getword (fd, word, (PATH_MAX+1)) == EOF)
533 return EOF;
534 }
535
536 word[strlen(word) - 1] = 0;
537 sl_strlcpy (prop->mnt_mount, word, PATH_MAX);
538
539 /* read attributes and value */
540
541 while (aix_fs_getword (fd, word, (PATH_MAX+1)) != EOF) {
542 /* test if is attribute or new stanza */
543
544 if (word[strlen(word) - 1] == ':') {
545 return 0;
546 }
547
548 /* read "=" */
549 aix_fs_getword (fd, value, PATH_MAX);
550
551 /* read value */
552 aix_fs_getword (fd, value, PATH_MAX);
553
554 if (strcmp (word, "dev") == 0) {
555 sl_strlcpy (prop->mnt_special, value, PATH_MAX);
556 } else if (strcmp (word, "vfs") == 0) {
557 sl_strlcpy (prop->mnt_fstype, value, 16);
558 } else if (strcmp (word, "options") == 0) {
559 sl_strlcpy(prop->mnt_options, value, 128);
560 }
561 }
562
563 return 0;
564}
565
566/* end AIX helper routines */
567#endif
568#endif
569
570#if defined(HOST_IS_FREEBSD)
571
572/* FreeBSD returns flags instead of strings as mount options, so we'll convert
573 * them here. */
574static
575struct sh_mounts_opt * getoptlist(int flags) {
576 struct sh_mounts_opt *list, *o;
577 int i;
578
579 struct {char *opt; int flag;} table[] = {
580 {"ro", MNT_RDONLY},
581 {"noexec", MNT_NOEXEC},
582 {"nosuid", MNT_NOSUID},
583 {"nodev", MNT_NODEV},
584 {"sync", MNT_SYNCHRONOUS},
585 {"async", MNT_ASYNC},
586 {"local", MNT_LOCAL},
587 {"quota", MNT_QUOTA},
588 {"bound", -1}
589 };
590
591 SL_ENTER(_("getoptlist"));
592
593 list = NULL;
594
595 /* Add any flags found to the list */
596 for (i = 0; table[i].flag != -1; i++) {
597 if (flags & table[i].flag) {
598 o = (struct sh_mounts_opt *) SH_ALLOC(sizeof(struct sh_mounts_opt));
599 o->opt = sh_util_strdup(table[i].opt);
600 o->next = list;
601 list = o;
602 }
603 }
604
605 SL_RETURN(list, _("getoptlist"));
606}
607
608/* Solaris & Linux return identical option string formats */
609#else
610
611/* We just separate the options out by parsing for commas */
612static
613struct sh_mounts_opt * getoptlist(char *opt)
614{
615 struct sh_mounts_opt *list, *o;
616 char *sp, *temp;
617
618 SL_ENTER(_("getoptlist"));
619
620 /* See the comments in sh_mounts_config_mount() above for the reasons for
621 * this arcane little zig-zag */
622 temp = sh_util_strdup(opt);
623 opt = temp;
624
625 list = NULL;
626
627 /* For each option, add to the list */
628 while (opt != NULL) {
629 sp = sh_util_strsep(&opt, ", \t");
630
631 if (*sp == '\0') {
632 continue;
633 }
634
635 o = (struct sh_mounts_opt *) SH_ALLOC(sizeof(struct sh_mounts_opt));
636 o->next = list;
637 list = o;
638
639 o->opt = sh_util_strdup(sp);
640 }
641
642 SH_FREE(temp);
643
644 SL_RETURN(list, _("getoptlist"));
645}
646
647#endif
648
649/* Read the list of mounts from whereever is appropriate to the OS and return
650 * it. Return NULL on error. */
651static struct sh_mounts_mnt * readmounts(void) {
652 struct sh_mounts_mnt *list, *m;
653
654 SL_ENTER(_("readmounts"));
655 m = NULL; /* use it to avoid compiler warning */
656 list = m;
657
658/* The FreeBSD way */
659#ifdef HOST_IS_FREEBSD
660{
661 struct statfs *fsp;
662 int entries;
663
664 entries = getmntinfo(&fsp, MNT_NOWAIT);
665 if (entries < 0) {
666 SL_RETURN((NULL), _("readmounts"));
667 }
668
669 for (; entries-- > 0; fsp++) {
670 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
671 m->path = sh_util_strdup(fsp->f_mntonname);
672 m->opts = getoptlist(fsp->f_flags);
673
674 m->next = list;
675 list = m;
676 }
677}
678#endif
679
680/* The Linux way */
681#ifdef HOST_IS_LINUX
682{
683 struct mntent *mp;
684 FILE *tab = setmntent(_PATH_MOUNTED, "r");
685
686 if (tab == NULL) {
687 SL_RETURN((NULL), _("readmounts"));
688 }
689
690 mp = getmntent(tab);
691 while (mp != NULL) {
692 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
693 m->path = sh_util_strdup(mp->mnt_dir);
694 m->opts = getoptlist(mp->mnt_opts);
695
696 m->next = list;
697 list = m;
698
699 mp = getmntent(tab);
700 }
701
702 (void) endmntent(tab);
703}
704#endif
705
706/* The Solaris way */
707#ifdef HOST_IS_SOLARIS
708{
709 struct mnttab mp;
710 FILE *tab = fopen(MNTTAB, "r");
711
712 if (tab == NULL) {
713 SL_RETURN((NULL), _("readmounts"));
714 }
715
716 while (!getmntent(tab, &mp)) {
717 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
718 m->path = sh_util_strdup(mp.mnt_mountp);
719 m->opts = getoptlist(mp.mnt_mntopts);
720
721 m->next = list;
722 list = m;
723 }
724
725 fclose(tab);
726}
727#endif
728
729
730/* The HP-UX way */
731#ifdef HOST_IS_HPUX
732{
733 struct mntent *mp;
734 FILE *tab = setmntent(MNT_MNTTAB, "r");
735
736 if (tab == NULL) {
737 SL_RETURN((NULL), _("readmounts"));
738 }
739
740 mp = getmntent(tab);
741 while (mp != NULL) {
742 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
743 m->path = sh_util_strdup(mp->mnt_dir);
744 m->opts = getoptlist(mp->mnt_opts);
745
746 m->next = list;
747 list = m;
748
749 mp = getmntent(tab);
750 }
751
752 (void) endmntent(tab);
753}
754#endif
755
756/* The AIX way */
757#if 0
758#ifdef HOST_IS_AIX
759{
760 AixMountTableEntry mntent;
761 FILE *tab = fopen("/etc/filesystems", "r");
762
763 if (tab == NULL) {
764 SL_RETURN((NULL), _("readmounts"));
765 }
766
767 while (!aix_fs_get (tab, &mntent))
768 {
769 m = (struct sh_mounts_mnt *) SH_ALLOC(sizeof (struct sh_mounts_mnt));
770 m->path = sh_util_strdup(mntent.mnt_mount);
771 m->opts = getoptlist(mntent.mnt_options);
772
773 m->next = list;
774 list = m;
775
776 mntent.mnt_mount[0] = '\0';
777 mntent.mnt_special[0] = '\0';
778 mntent.mnt_fstype[0] = '\0';
779 mntent.mnt_options[0] = '\0';
780 }
781
782 (void) fclose(tab);
783 aix_fs_get (NULL, NULL); /* reset */
784}
785#endif
786#endif
787
788 SL_RETURN((list), _("readmounts"));
789
790}
791
792
793/* #if defined (SH_WITH_CLIENT) || defined (SH_STANDALONE) */
794#endif
795
796/* #ifdef SH_USE_MOUNTS */
797#endif
798
Note: See TracBrowser for help on using the repository browser.