source: trunk/src/sh_suidchk.c@ 138

Last change on this file since 138 was 138, checked in by rainer, 17 years ago

More fixes for compile and runtime errors.

File size: 50.9 KB
RevLine 
[1]1/* SAMHAIN file system integrity testing */
2/* Copyright (C) 2001 Rainer Wichmann */
3/* */
4/* This program is free software; you can redistribute it */
5/* and/or modify */
6/* it under the terms of the GNU General Public License as */
7/* published by */
8/* the Free Software Foundation; either version 2 of the License, or */
9/* (at your option) any later version. */
10/* */
11/* This program is distributed in the hope that it will be useful, */
12/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14/* GNU General Public License for more details. */
15/* */
16/* You should have received a copy of the GNU General Public License */
17/* along with this program; if not, write to the Free Software */
18/* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
19
20#include "config_xor.h"
21
22
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <fcntl.h>
29#include <unistd.h>
30#include <errno.h>
31#include <limits.h>
[94]32
[1]33#ifdef HAVE_SCHED_H
34#include <sched.h>
35#endif
36
37#ifdef SH_USE_SUIDCHK
38
39#undef FIL__
40#define FIL__ _("sh_suidchk.c")
41
42#if defined (SH_WITH_CLIENT) || defined (SH_STANDALONE)
43
44#if TIME_WITH_SYS_TIME
45#include <sys/time.h>
46#include <time.h>
47#else
48#if HAVE_SYS_TIME_H
49#include <sys/time.h>
50#else
51#include <time.h>
52#endif
53#endif
54
55#ifdef HAVE_DIRENT_H
56#include <dirent.h>
57#define NAMLEN(dirent) sl_strlen((dirent)->d_name)
58#else
59#define dirent direct
60#define NAMLEN(dirent) (dirent)->d_namlen
61#ifdef HAVE_SYS_NDIR_H
62#include <sys/ndir.h>
63#endif
64#ifdef HAVE_SYS_DIR_H
65#include <sys/dir.h>
66#endif
67#ifdef HAVE_NDIR_H
68#include <ndir.h>
69#endif
70#endif
[137]71#define NEED_ADD_DIRENT
[1]72
73#include "samhain.h"
[131]74#include "sh_pthread.h"
[1]75#include "sh_utils.h"
76#include "sh_error.h"
77#include "sh_modules.h"
78#include "sh_suidchk.h"
79#include "sh_hash.h"
80#include "sh_unix.h"
81#include "sh_files.h"
82#include "sh_schedule.h"
83#include "sh_calls.h"
84
85
86sh_rconf sh_suidchk_table[] = {
87 {
88 N_("severitysuidcheck"),
89 sh_suidchk_set_severity
90 },
91 {
92 N_("suidcheckactive"),
93 sh_suidchk_set_activate
94 },
95 {
96 N_("suidcheckinterval"),
97 sh_suidchk_set_timer
98 },
99 {
100 N_("suidcheckschedule"),
101 sh_suidchk_set_schedule
102 },
103 {
104 N_("suidcheckexclude"),
105 sh_suidchk_set_exclude
106 },
107 {
108 N_("suidcheckfps"),
109 sh_suidchk_set_fps
110 },
111 {
112 N_("suidcheckyield"),
113 sh_suidchk_set_yield
114 },
115 {
[119]116 N_("suidchecknosuid"),
117 sh_suidchk_set_nosuid
118 },
119 {
[1]120 N_("suidcheckquarantinefiles"),
121 sh_suidchk_set_quarantine
122 },
123 {
124 N_("suidcheckquarantinemethod"),
125 sh_suidchk_set_qmethod
126 },
127 {
128 N_("suidcheckquarantinedelete"),
129 sh_suidchk_set_qdelete
130 },
131 {
132 NULL,
133 NULL
134 },
135};
136
137
138static time_t lastcheck = (time_t) 0;
139static int ShSuidchkActive = S_TRUE;
140static time_t ShSuidchkInterval = 7200;
141static long ShSuidchkFps = 0;
[119]142static int ShSuidchkNosuid = S_FALSE;
[1]143static int ShSuidchkYield = S_FALSE;
144static int ShSuidchkQEnable = S_FALSE;
145static int ShSuidchkQMethod = SH_Q_CHANGEPERM;
146static int ShSuidchkQDelete = S_FALSE;
147static int ShSuidchkSeverity = SH_ERR_SEVERE;
148static char * ShSuidchkExclude = NULL;
[68]149static size_t ExcludeLen = 0;
[1]150
151static time_t FileLimNow = 0;
152static time_t FileLimStart = 0;
153static long FileLimNum = 0;
154static long FileLimTotal = 0;
155
156static sh_schedule_t * ShSuidchkSched = NULL;
157
158static char *
159filesystem_type (char * path, char * relpath, struct stat * statp);
160
161#ifndef PATH_MAX
162#define PATH_MAX 1024
163#endif
164
165extern unsigned long sh_files_maskof (int class);
166
167/* Recursively descend into the directory to make sure that
168 * there is no symlink in the path.
169 */
170static int do_truncate_int (char * path, int depth)
171{
172 char * q;
173 struct stat one;
174 struct stat two;
175 int fd;
[132]176 char errbuf[SH_ERRBUF_SIZE];
[1]177
178 if (depth > 99)
179 {
180 sh_error_handle ((-1), FIL__, __LINE__, EINVAL,
181 MSG_SUID_ERROR,
182 _("do_truncate: max depth 99 exceeded"));
183 return -1;
184 }
185 ++depth;
186 if (path[0] != '/')
187 {
188 sh_error_handle ((-1), FIL__, __LINE__, EINVAL,
189 MSG_SUID_ERROR,
190 _("do_truncate: not an absolute path"));
191 return -1;
192 }
193 ++path;
194 q = strchr(path, '/');
195 if (q)
196 {
197 *q = '\0';
198 if (0 != retry_lstat(FIL__, __LINE__, path, &one))
199 {
200 sh_error_handle ((-1), FIL__, __LINE__, errno,
201 MSG_SUID_ERROR,
[132]202 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]203 *q = '/';
204 return -1;
205 }
206 if (/*@-usedef@*/!S_ISDIR(one.st_mode)/*@+usedef@*/)
207
208 {
209 sh_error_handle ((-1), FIL__, __LINE__, EINVAL,
210 MSG_SUID_ERROR,
211 _("Possible race: not a directory"));
212 *q = '/';
213 return -1;
214 }
215
216
217 if (0 != chdir(path))
218 {
219 sh_error_handle ((-1), FIL__, __LINE__, errno,
220 MSG_SUID_ERROR,
[132]221 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]222 *q = '/';
223 return -1;
224 }
225 *q = '/';
226 if (0 != retry_lstat(FIL__, __LINE__, ".", &two))
227 {
228 sh_error_handle ((-1), FIL__, __LINE__, errno,
229 MSG_SUID_ERROR,
[132]230 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]231 return -1;
232 }
233 if (/*@-usedef@*/(one.st_dev != two.st_dev) ||
234 (one.st_ino != two.st_ino) ||
235 (!S_ISDIR(two.st_mode))/*@+usedef@*/)
236 {
237 sh_error_handle ((-1), FIL__, __LINE__, EINVAL,
238 MSG_SUID_ERROR,
239 _("Possible race: lstat(dir) != lstat(.)"));
240 return -1;
241 }
242
243
244 return (do_truncate_int(q, depth));
245 }
246 else
247 {
248 /* no more '/', so this is the file
249 */
250 if (*path == '\0')
251 return -1;
252 if (0 != retry_lstat(FIL__, __LINE__, path, &one))
253 {
254 sh_error_handle ((-1), FIL__, __LINE__, errno,
255 MSG_SUID_ERROR,
[132]256 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]257 return -1;
258 }
259 fd = open(path, O_RDWR);
260 if (-1 == fd)
261 {
262 sh_error_handle ((-1), FIL__, __LINE__, errno,
263 MSG_SUID_ERROR,
[132]264 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]265 return -1;
266 }
267 if (0 != retry_fstat(FIL__, __LINE__, fd, &two))
268 {
269 sh_error_handle ((-1), FIL__, __LINE__, errno,
270 MSG_SUID_ERROR,
[132]271 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]272 (void) close(fd);
273 return -1;
274 }
275 if (/*@-usedef@*/(one.st_dev != two.st_dev) ||
276 (one.st_ino != two.st_ino)/*@+usedef@*/)
277 {
278 sh_error_handle ((-1), FIL__, __LINE__, EINVAL,
279 MSG_SUID_ERROR,
280 _("Possible race: lstat != fstat"));
281 (void) close(fd);
282 return -1;
283 }
284 if (!S_ISREG(two.st_mode))
285 {
286 sh_error_handle ((-1), FIL__, __LINE__, EINVAL,
287 MSG_SUID_ERROR,
288 _("Possible race: not a regular file"));
289 (void) close(fd);
290 return -1;
291 }
292 if ((0 == (two.st_mode & S_ISUID)) && (0 == (two.st_mode & S_ISGID)))
293 {
294 sh_error_handle ((-1), FIL__, __LINE__, EINVAL,
295 MSG_SUID_ERROR,
296 _("Possible race: not a suid/sgid file"));
297 (void) close(fd);
298 return -1;
299 }
300 if (ShSuidchkQDelete == S_FALSE)
301 {
302 if ((two.st_mode & S_ISUID) > 0)
303 two.st_mode -= S_ISUID;
304 if ((two.st_mode & S_ISGID) > 0)
305 two.st_mode -= S_ISGID;
306#ifdef HAVE_FCHMOD
307 if (-1 == /*@-unrecog@*/fchmod(fd, two.st_mode)/*@+unrecog@*/)
308 {
309 sh_error_handle ((-1), FIL__, __LINE__, errno,
310 MSG_SUID_ERROR,
[132]311 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]312 (void) close(fd);
313 return -1;
314 }
315#else
316 sh_error_handle ((-1), FIL__, __LINE__, errno,
317 MSG_SUID_ERROR,
318 _("The fchmod() function is not available"));
319 (void) close(fd);
320 return -1;
321#endif
322 if (two.st_nlink > 1)
323 {
324 sh_error_handle ((-1), FIL__, __LINE__, 0,
325 MSG_SUID_ERROR,
326 _("Not truncated because hardlink count gt 1"));
327 (void) close(fd);
328 return -1;
329 }
330 /* The man page says: 'POSIX has ftruncate'
331 */
332 if (-1 == /*@-unrecog@*/ftruncate(fd, 0)/*@+unrecog@*/)
333 {
334 sh_error_handle ((-1), FIL__, __LINE__, errno,
335 MSG_SUID_ERROR,
[132]336 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]337 (void) close(fd);
338 return -1;
339 }
340 }
341 else
342 {
343 if (-1 == retry_aud_unlink(FIL__, __LINE__, path))
344 {
345 sh_error_handle ((-1), FIL__, __LINE__, errno,
346 MSG_SUID_ERROR,
[132]347 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]348 (void) close(fd);
349 return -1;
350 }
351 }
352 (void) close (fd);
353 return (0);
354 }
355}
356
[94]357static int do_truncate (const char * path_in)
[1]358{
[94]359 int caperr;
360 int result;
361 char * path;
[132]362 char errbuf[SH_ERRBUF_SIZE];
[1]363
364 if (0 != chdir("/"))
365 {
366 sh_error_handle ((-1), FIL__, __LINE__, errno,
367 MSG_SUID_ERROR,
[132]368 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]369 }
370
371 if (0 != (caperr = sl_get_cap_qdel()))
372 {
373 sh_error_handle((-1), FIL__, __LINE__, caperr, MSG_E_SUBGEN,
[132]374 sh_error_message (caperr, errbuf, sizeof(errbuf)),
[1]375 _("sl_get_cap_qdel"));
376 }
377
[94]378 path = sh_util_strdup (path_in);
[1]379 result = do_truncate_int (path, 0);
[94]380 SH_FREE(path);
[1]381
382 if (0 != (caperr = sl_drop_cap_qdel()))
383 {
384 sh_error_handle((-1), FIL__, __LINE__, caperr, MSG_E_SUBGEN,
[132]385 sh_error_message (caperr, errbuf, sizeof(errbuf)),
[1]386 _("sl_drop_cap_qdel"));
387 }
388
389 if (0 != chdir("/"))
390 {
391 sh_error_handle ((-1), FIL__, __LINE__, errno,
392 MSG_SUID_ERROR,
[132]393 sh_error_message(errno, errbuf, sizeof(errbuf)));
[1]394 }
395 return result;
396}
397
[94]398static void sh_q_delete(const char * fullpath)
399{
400 int status;
401 char * msg;
402 char * tmp;
403
404 if (do_truncate (fullpath) == -1)
405 {
406 status = errno;
407 msg = SH_ALLOC(SH_BUFSIZE);
408 tmp = sh_util_safe_name(fullpath);
409
410 (void) sl_snprintf(msg, SH_BUFSIZE,
411 _("Problem quarantining file. File NOT quarantined. errno = %ld"),
412 status);
413 sh_error_handle (ShSuidchkSeverity,
414 FIL__, __LINE__,
415 status,
416 MSG_SUID_QREPORT, msg,
417 tmp );
418 SH_FREE(tmp);
419 SH_FREE(msg);
420 }
421 else
422 {
423 tmp = sh_util_safe_name(fullpath);
424 sh_error_handle (ShSuidchkSeverity,
425 FIL__, __LINE__, 0,
426 MSG_SUID_QREPORT,
427 _("Quarantine method applied"),
428 tmp );
429 SH_FREE(tmp);
430 }
431 return;
432}
433
434static void sh_q_move(const char * fullpath, file_type * theFile,
435 const char * timestrc, const char * timestra,
436 const char * timestrm)
437{
438 int status;
439 int readFile = -1;
440 int writeFile = -1;;
441 struct stat fileInfo;
442 ssize_t count;
443 char * msg;
444 char * tmp;
445 char * basetmp;
446 char * filetmp;
447 char buffer[1024];
448 char * dir = SH_ALLOC(PATH_MAX+1);
449 mode_t umask_old;
450 FILE * filePtr = NULL;
451
452 (void) sl_strlcpy (dir, DEFAULT_QDIR, PATH_MAX+1);
453
454 if (retry_stat (FIL__, __LINE__, dir, &fileInfo) != 0)
455 {
456 /* Quarantine directory does not exist,
457 */
458 status = errno;
459 msg = SH_ALLOC(SH_BUFSIZE);
460 tmp = sh_util_safe_name(fullpath);
461
462 (void) sl_snprintf(msg, SH_BUFSIZE,
463 _("Problem quarantining file. File NOT quarantined. errno = %ld (stat)"),
464 status);
465 sh_error_handle (ShSuidchkSeverity,
466 FIL__, __LINE__,
467 status,
468 MSG_SUID_QREPORT, msg,
469 tmp );
470 SH_FREE(tmp);
471 SH_FREE(msg);
472 }
473 else
474 {
475 if (retry_lstat (FIL__, __LINE__,
476 fullpath, &fileInfo) == -1)
477 {
478 status = errno;
479 msg = SH_ALLOC(SH_BUFSIZE);
480 tmp = sh_util_safe_name(fullpath);
481
482 (void) sl_snprintf(msg, SH_BUFSIZE, _("I/O error. errno = %ld(stat)"), status);
483 sh_error_handle (ShSuidchkSeverity,
484 FIL__, __LINE__,
485 status,
486 MSG_SUID_QREPORT,
487 msg, tmp );
488 SH_FREE(tmp);
489 SH_FREE(msg);
490 }
491 else
492 {
493 basetmp = sh_util_strdup(fullpath);
494 filetmp = SH_ALLOC(PATH_MAX+1);
495 tmp = sh_util_basename(basetmp);
496
497 (void) sl_snprintf(filetmp, PATH_MAX+1, "%s/%s",
498 DEFAULT_QDIR, tmp);
499 SH_FREE(tmp);
500 SH_FREE(basetmp);
501
502 readFile = open (fullpath, O_RDONLY);
503 if (readFile != -1)
504 writeFile = open (filetmp, O_WRONLY|O_CREAT);
505
506 if ((readFile == -1) || (writeFile == -1))
507 {
508 status = errno;
509 msg = SH_ALLOC(SH_BUFSIZE);
510 tmp = sh_util_safe_name(fullpath);
511
512 (void) sl_snprintf(msg, SH_BUFSIZE, _("Problem quarantining file. File NOT quarantined. errno = %ld (open)"), status);
513 sh_error_handle (ShSuidchkSeverity,
514 FIL__, __LINE__, status,
515 MSG_SUID_QREPORT,
516 msg, tmp );
517 SH_FREE(tmp);
518 SH_FREE(msg);
519 }
520 else
521 {
522 /* sizeof(buffer) is 1024
523 */
524 while ((count = (int) read (readFile, buffer, sizeof (buffer))) > 0)
525 {
526 if ((int) write (writeFile, buffer, (size_t) count) != count)
527 {
528 status = errno;
529 msg = SH_ALLOC(SH_BUFSIZE);
530 tmp = sh_util_safe_name(fullpath);
531
532 (void) sl_snprintf(msg, SH_BUFSIZE,
533 _("I/O error. errno = %ld (write)"), status);
534 sh_error_handle (ShSuidchkSeverity,
535 FIL__,
536 __LINE__,
537 status,
538 MSG_SUID_QREPORT,
539 msg, tmp );
540 SH_FREE(tmp);
541 SH_FREE(msg);
542 }
543 }
544 }
545
546 (void) close (readFile);
547 (void) fchmod(writeFile, S_IRUSR | S_IWUSR | S_IXUSR);
548 (void) close (writeFile);
549
550 if (do_truncate (fullpath) == -1)
551 {
552 status = errno;
553 msg = SH_ALLOC(SH_BUFSIZE);
554 tmp = sh_util_safe_name(fullpath);
555
556 (void) sl_snprintf(msg, SH_BUFSIZE,
557 _("Problem quarantining file. File NOT quarantined. errno = %ld"),
558 status);
559 sh_error_handle (ShSuidchkSeverity,
560 FIL__, __LINE__, status,
561 MSG_SUID_QREPORT,
562 msg, tmp );
563 SH_FREE(tmp);
564 SH_FREE(msg);
565 }
566 else
567 {
568 tmp = sh_util_basename(fullpath);
569
570 (void) sl_snprintf(filetmp, PATH_MAX+1, "%s/%s.info",
571 DEFAULT_QDIR,
572 tmp);
573
574 SH_FREE(tmp);
575 /*
576 * avoid chmod by setting umask
577 */
578 umask_old = umask (0077);
579 filePtr = fopen (filetmp, "w+");
580
581 /*@-usedef@*/
582 if (filePtr)
583 {
584 fprintf(filePtr,
585 _("File Info:\n filename=%s\n size=%lu\n owner=%s(%d)\n group=%s(%d)\n ctime=%s\n atime=%s\n mtime=%s\n"),
586 fullpath,
587 (unsigned long) theFile->size,
588 theFile->c_owner, (int) theFile->owner,
589 theFile->c_group, (int) theFile->group,
590 timestrc, timestra, timestrm);
591 (void) fclose (filePtr);
592 }
593 /*@+usedef@*/
594 umask (umask_old);
595
596 tmp = sh_util_safe_name(fullpath);
597 sh_error_handle (ShSuidchkSeverity,
598 FIL__,__LINE__,
599 0, MSG_SUID_QREPORT,
600 _("Quarantine method applied"),
601 tmp );
602 SH_FREE(tmp);
603 }
604 SH_FREE(filetmp);
605 }
606 }
607 SH_FREE(dir);
608 return;
609}
610
611static void sh_q_changeperm(const char * fullpath)
612{
613 int caperr;
614 int status;
615 char * msg;
616 char * tmp;
617 struct stat fileInfo;
618 struct stat fileInfo_F;
619 int cperm_status = 0;
620 int file_d = -1;
[132]621 char errbuf[SH_ERRBUF_SIZE];
[94]622
623 if (retry_lstat(FIL__, __LINE__, fullpath, &fileInfo) == -1)
624 {
625 status = errno;
626 msg = SH_ALLOC(SH_BUFSIZE);
627 tmp = sh_util_safe_name(fullpath);
628
629 (void) sl_snprintf(msg, SH_BUFSIZE, _("I/O error. errno = %ld"), status);
630 sh_error_handle (ShSuidchkSeverity,
631 FIL__, __LINE__,
632 status,
633 MSG_SUID_QREPORT, msg,
634 tmp );
635 SH_FREE(tmp);
636 SH_FREE(msg);
637 cperm_status = -1;
638 }
639
640 if (cperm_status == 0)
641 {
642 if (0 != (caperr = sl_get_cap_qdel()))
643 {
644 sh_error_handle((-1), FIL__, __LINE__,
645 caperr, MSG_E_SUBGEN,
[132]646 sh_error_message (caperr, errbuf, sizeof(errbuf)),
[94]647 _("sl_get_cap_qdel"));
648 cperm_status = -1;
649 }
650 }
651
652 if (cperm_status == 0)
653 {
654 file_d = aud_open (FIL__, __LINE__, SL_YESPRIV,
655 fullpath, O_RDONLY, 0);
656 if (-1 == file_d)
657 {
658 status = errno;
659 msg = SH_ALLOC(SH_BUFSIZE);
660 tmp = sh_util_safe_name(fullpath);
661
662 (void) sl_snprintf(msg, SH_BUFSIZE, _("I/O error. errno = %ld"), status);
663 sh_error_handle (ShSuidchkSeverity,
664 FIL__, __LINE__,
665 status,
666 MSG_SUID_QREPORT, msg,
667 tmp );
668 SH_FREE(tmp);
669 SH_FREE(msg);
670 cperm_status = -1;
671 }
672 }
673
674 if (cperm_status == 0)
675 {
676 if (retry_fstat(FIL__, __LINE__, file_d, &fileInfo_F) == -1)
677 {
678 status = errno;
679 msg = SH_ALLOC(SH_BUFSIZE);
680 tmp = sh_util_safe_name(fullpath);
681
682 (void) sl_snprintf(msg, SH_BUFSIZE,
683 _("I/O error. errno = %ld"), status);
684 sh_error_handle (ShSuidchkSeverity,
685 FIL__, __LINE__,
686 status,
687 MSG_SUID_QREPORT, msg,
688 tmp );
689 SH_FREE(tmp);
690 SH_FREE(msg);
691 cperm_status = -1;
692 }
693 }
694
695 if (cperm_status == 0)
696 {
697 if (fileInfo_F.st_ino != fileInfo.st_ino ||
698 fileInfo_F.st_dev != fileInfo.st_dev ||
699 fileInfo_F.st_mode != fileInfo.st_mode)
700 {
701 status = errno;
702 msg = SH_ALLOC(SH_BUFSIZE);
703 tmp = sh_util_safe_name(fullpath);
704
705 (void) sl_snprintf(msg, SH_BUFSIZE,
706 _("Race detected. errno = %ld"), status);
707 sh_error_handle (ShSuidchkSeverity,
708 FIL__, __LINE__,
709 status,
710 MSG_SUID_QREPORT, msg,
711 tmp );
712 SH_FREE(tmp);
713 SH_FREE(msg);
714 cperm_status = -1;
715 }
716 }
717
718 if ((fileInfo.st_mode & S_ISUID) > 0)
719 fileInfo.st_mode -= S_ISUID;
720 if ((fileInfo.st_mode & S_ISGID) > 0)
721 fileInfo.st_mode -= S_ISGID;
722
723 if (cperm_status == 0)
724 {
725 if (fchmod(file_d, fileInfo.st_mode) == -1)
726 {
727 status = errno;
728 msg = SH_ALLOC(SH_BUFSIZE);
729 tmp = sh_util_safe_name(fullpath);
730
731 (void) sl_snprintf(msg, SH_BUFSIZE,
732 _("Problem quarantining file. File NOT quarantined. errno = %ld"),
733 status);
734 sh_error_handle (ShSuidchkSeverity,
735 FIL__, __LINE__,
736 status,
737 MSG_SUID_QREPORT,
738 msg, tmp );
739 SH_FREE(tmp);
740 SH_FREE(msg);
741 }
742 else
743 {
744 tmp = sh_util_safe_name(fullpath);
745 sh_error_handle (ShSuidchkSeverity,
746 FIL__, __LINE__,
747 0,
748 MSG_SUID_QREPORT,
749 _("Quarantine method applied"),
750 tmp );
751 SH_FREE(tmp);
752 }
753 }
754
755 if (0 != (caperr = sl_drop_cap_qdel()))
756 {
757 sh_error_handle((-1), FIL__, __LINE__,
758 caperr, MSG_E_SUBGEN,
[132]759 sh_error_message (caperr, errbuf, sizeof(errbuf)),
[94]760 _("sl_drop_cap_qdel"));
761 }
762
763 if (file_d != -1)
764 {
765 do {
766 status = close (file_d);
767 } while (status == -1 && errno == EINTR);
768
769 if (-1 == status)
770 {
771 status = errno;
772 msg = SH_ALLOC(SH_BUFSIZE);
773 tmp = sh_util_safe_name(fullpath);
774
775 (void) sl_snprintf(msg, SH_BUFSIZE,
776 _("I/O error. errno = %ld"), status);
777 sh_error_handle (ShSuidchkSeverity,
778 FIL__, __LINE__,
779 status,
780 MSG_SUID_QREPORT, msg,
781 tmp );
782 SH_FREE(tmp);
783 SH_FREE(msg);
784 cperm_status = -1;
785 }
786 }
787 return;
788}
789
790static void report_file (const char * tmpcat, file_type * theFile,
791 char * timestrc, char * timestra, char * timestrm)
792{
793 char * msg = SH_ALLOC(SH_BUFSIZE);
794 char * tmp = sh_util_safe_name(tmpcat);
795
796 msg[0] = '\0';
797 /*@-usedef@*/
[132]798
[94]799#ifdef SH_USE_XML
800 (void) sl_snprintf(msg, SH_BUFSIZE, _("owner_new=\"%s\" iowner_new=\"%ld\" group_new=\"%s\" igroup_new=\"%ld\" size_new=\"%lu\" ctime_new=\"%s\" atime_new=\"%s\" mtime_new=\"%s\""),
801 theFile->c_owner, theFile->owner,
802 theFile->c_group, theFile->group,
803 (unsigned long) theFile->size,
804 timestrc, timestra, timestrm);
805#else
806 (void) sl_snprintf(msg, SH_BUFSIZE, _("owner_new=<%s>, iowner_new=<%ld>, group_new=<%s>, igroup_new=<%ld>, filesize=<%lu>, ctime=<%s>, atime=<%s>, mtime=<%s>"),
807 theFile->c_owner, theFile->owner,
808 theFile->c_group, theFile->group,
809 (unsigned long) theFile->size,
810 timestrc, timestra, timestrm);
811#endif
812 /*@+usedef@*/
813
814 sh_error_handle (ShSuidchkSeverity, FIL__, __LINE__,
815 0, MSG_SUID_POLICY,
816 _("suid/sgid file not in database"),
817 tmp, msg );
818 SH_FREE(tmp);
819 SH_FREE(msg);
820 return;
821}
822
[1]823static
824int sh_suidchk_check_internal (char * iname)
825{
826 DIR * thisDir = NULL;
827 struct dirent * thisEntry;
828 char * tmpcat;
829 char * tmp;
830 char timestrc[32];
831 char timestra[32];
832 char timestrm[32];
833 struct stat buf;
834 int status;
[115]835 int fflags;
[1]836 char * fs;
837 long sl_status = SL_ENONE;
838 file_type theFile;
[19]839 char fileHash[2*(KEY_LEN + 1)];
[1]840
[131]841 struct sh_dirent * dirlist = NULL;
842 struct sh_dirent * dirlist_orig = NULL;
[132]843 char errbuf[SH_ERRBUF_SIZE];
[22]844
[1]845 SL_ENTER(_("sh_suidchk_check_internal"));
846
847 if (iname == NULL)
848 {
849 TPT((0, FIL__, __LINE__ , _("msg=<directory name is NULL>\n")));
850 SL_RETURN( (-1), _("sh_suidchk_check_internal"));
851 }
852
853 if (sig_urgent > 0) {
854 SL_RETURN( (0), _("sh_suidchk_check_internal"));
855 }
856
857 thisDir = opendir (iname);
858
859 if (thisDir == NULL)
860 {
861 status = errno;
862 tmp = sh_util_safe_name(iname);
863 sh_error_handle (ShDFLevel[SH_ERR_T_DIR], FIL__, __LINE__, status,
864 MSG_E_OPENDIR,
[132]865 sh_error_message (status, errbuf, sizeof(errbuf)), tmp);
[1]866 SH_FREE(tmp);
867 SL_RETURN( (-1), _("sh_suidchk_check_internal"));
868 }
869
[94]870 /* Loop over directory entries
871 */
[137]872 SH_MUTEX_LOCK(mutex_readdir);
[131]873
[1]874 do {
875
876 if (sig_urgent > 0) {
877 SL_RETURN( (0), _("sh_suidchk_check_internal"));
878 }
879
880 thisEntry = readdir (thisDir);
881
882 if (thisEntry != NULL) {
883
884 if (sl_strcmp (thisEntry->d_name, ".") == 0)
885 continue;
886
887 if (sl_strcmp (thisEntry->d_name, "..") == 0)
888 continue;
889
[131]890 dirlist = addto_sh_dirlist (thisEntry, dirlist);
891 }
[1]892
[131]893 } while (thisEntry != NULL);
[94]894
[137]895 SH_MUTEX_UNLOCK(mutex_readdir);
[94]896
[131]897 closedir(thisDir);
[1]898
[131]899 dirlist_orig = dirlist;
[1]900
[131]901 do {
902
903 /* If the directory is empty, dirlist = NULL
904 */
905 if (!dirlist)
906 break;
907
908 tmpcat = SH_ALLOC(PATH_MAX);
909 (void) sl_strlcpy(tmpcat, iname, PATH_MAX);
910
911 if ((sl_strlen(tmpcat) != sl_strlen(iname)) || (tmpcat[0] == '\0'))
912 {
913 sl_status = SL_ETRUNC;
914 }
915 else
916 {
917 if (tmpcat[1] != '\0')
918 sl_status = sl_strlcat(tmpcat, "/", PATH_MAX);
919 }
920
921 if (! SL_ISERROR(sl_status))
922 sl_status = sl_strlcat(tmpcat, dirlist->sh_d_name, PATH_MAX);
923
924 if (SL_ISERROR(sl_status))
925 {
926 tmp = sh_util_safe_name(tmpcat);
927 sh_error_handle ((-1), FIL__, __LINE__, (int) sl_status,
928 MSG_E_SUBGPATH,
929 _("path too long"),
930 _("sh_suidchk_check_internal"), tmp );
931 SH_FREE(tmp);
932 dirlist = dirlist->next;
933 continue;
934 }
935
936 ++FileLimNum;
937 ++FileLimTotal;
938
939 /* Rate limit (Fps == Files per second)
940 */
941 if ((ShSuidchkFps > 0 && FileLimNum > ShSuidchkFps && FileLimTotal > 0)&&
942 (ShSuidchkYield == S_FALSE))
943 {
944 FileLimNum = 0;
945 FileLimNow = time(NULL);
946
947 if ( (FileLimNow - FileLimStart) > 0 &&
948 FileLimTotal/(FileLimNow - FileLimStart) > ShSuidchkFps )
949 (void) retry_msleep((int)((FileLimTotal/(FileLimNow-FileLimStart))/
950 ShSuidchkFps) , 0);
951 }
[1]952
[131]953 status = (int) retry_lstat(FIL__, __LINE__, tmpcat, &buf);
[1]954
[131]955 if (status != 0)
956 {
957 status = errno;
958 tmp = sh_util_safe_name(tmpcat);
959 sh_error_handle (SH_ERR_ERR, FIL__, __LINE__, status, MSG_ERR_LSTAT,
[132]960 sh_error_message(status, errbuf, sizeof(errbuf)),
[131]961 tmpcat );
962 SH_FREE(tmp);
963 }
964 else
965 {
966 if (/*@-usedef@*/S_ISDIR(buf.st_mode)/*@+usedef@*/ &&
967 (ShSuidchkExclude == NULL ||
968 0 != strcmp(tmpcat, ShSuidchkExclude)))
969 {
970 /* fs is a STATIC string or NULL
971 */
972 fs = filesystem_type (tmpcat, tmpcat, &buf);
973 if (fs != NULL
[30]974#ifndef SH_SUIDTESTDIR
[131]975 &&
976 0 != strncmp (_("afs"), fs, 3) &&
977 0 != strncmp (_("devfs"), fs, 5) &&
978 0 != strncmp (_("iso9660"), fs, 7) &&
979 0 != strncmp (_("lustre"), fs, 6) &&
980 0 != strncmp (_("mmfs"), fs, 4) &&
981 0 != strncmp (_("msdos"), fs, 5) &&
982 0 != strncmp (_("nfs"), fs, 3) &&
983 0 != strncmp (_("proc"), fs, 4) &&
984 0 != strncmp (_("vfat"), fs, 4)
[30]985#endif
[131]986 )
987 {
988 if ((ShSuidchkNosuid == S_TRUE) ||
989 (0 != strncmp (_("nosuid"), fs, 6)))
990 /* fprintf(stderr, "%s: %s\n", fs, tmpcat); */
991 (void) sh_suidchk_check_internal(tmpcat);
992 }
993 }
994 else if (S_ISREG(buf.st_mode) &&
995 (0 !=(S_ISUID & buf.st_mode) ||
[1]996#if defined(HOST_IS_LINUX)
[131]997 (0 !=(S_ISGID & buf.st_mode) &&
998 0 !=(S_IXGRP & buf.st_mode))
[1]999#else
[131]1000 0 !=(S_ISGID & buf.st_mode)
[1]1001#endif
[131]1002 )
1003 )
1004 {
1005
1006 (void) sl_strlcpy (theFile.fullpath, tmpcat, PATH_MAX);
1007 theFile.check_mask = sh_files_maskof(SH_LEVEL_READONLY);
1008 CLEAR_SH_FFLAG_REPORTED(theFile.file_reported);
1009 theFile.attr_string = NULL;
1010
1011 status = sh_unix_getinfo (ShDFLevel[SH_ERR_T_RO],
[138]1012 dirlist->sh_d_name,
[131]1013 &theFile, fileHash, 0);
1014
1015 tmp = sh_util_safe_name(tmpcat);
1016
1017 if (status != 0)
1018 {
1019 sh_error_handle (ShSuidchkSeverity, FIL__, __LINE__,
1020 0, MSG_E_SUBGPATH,
1021 _("Could not check suid/sgid file"),
1022 _("sh_suidchk_check_internal"),
1023 tmp);
1024 }
1025 else
1026 {
1027
1028 if ( sh.flag.update == S_TRUE &&
1029 (sh.flag.checkSum == SH_CHECK_INIT ||
1030 sh.flag.checkSum == SH_CHECK_CHECK))
1031 {
1032 /* Updating database. Report new files that
1033 * are not in database already. Then compare
1034 * to database and report changes.
1035 */
1036 if (-1 == sh_hash_have_it (tmpcat))
1037 {
1038 sh_error_handle ((-1), FIL__, __LINE__,
1039 0, MSG_SUID_FOUND, tmp );
1040 }
1041 else
1042 {
1043 sh_error_handle (SH_ERR_ALL, FIL__, __LINE__,
1044 0, MSG_SUID_FOUND, tmp );
1045 }
1046
1047 if (0 == sh_hash_compdata (SH_LEVEL_READONLY,
1048 &theFile, fileHash,
1049 _("[SuidCheck]"),
1050 ShSuidchkSeverity))
1051 {
1052 sh_hash_pushdata_memory (&theFile, fileHash);
1053 }
1054
1055 sh_hash_addflag(tmpcat, SH_FFLAG_SUIDCHK);
1056
1057 }
1058
1059 else if (sh.flag.checkSum == SH_CHECK_INIT &&
1060 sh.flag.update == S_FALSE )
1061 {
1062 /* Running init. Report on files detected.
1063 */
1064 sh_hash_pushdata (&theFile, fileHash);
1065 sh_error_handle ((-1), FIL__, __LINE__,
1066 0, MSG_SUID_FOUND, tmp );
1067 }
1068
1069 else if (sh.flag.checkSum == SH_CHECK_CHECK )
1070 {
1071 /* Running file check. Report on new files
1072 * detected, and quarantine them.
1073 */
1074 sh_error_handle (SH_ERR_ALL, FIL__, __LINE__,
1075 0, MSG_SUID_FOUND, tmp );
1076
1077 fflags = sh_hash_getflags(tmpcat);
1078
1079 if ( (-1 == fflags) || (!SH_FFLAG_SUIDCHK_SET(fflags)))
1080 {
1081 if (-1 == fflags)
[132]1082 {
[137]1083 (void) sh_unix_gmttime (theFile.ctime, timestrc, sizeof(timestrc));
1084 (void) sh_unix_gmttime (theFile.atime, timestra, sizeof(timestra));
1085 (void) sh_unix_gmttime (theFile.mtime, timestrm, sizeof(timestrm));
[132]1086
1087 report_file(tmpcat, &theFile, timestrc, timestra, timestrm);
1088 }
[131]1089 /* Quarantine file according to configured method
1090 */
1091 if (ShSuidchkQEnable == S_TRUE)
1092 {
1093 switch (ShSuidchkQMethod)
1094 {
1095 case SH_Q_DELETE:
1096 sh_q_delete(theFile.fullpath);
1097 break;
1098 case SH_Q_CHANGEPERM:
1099 sh_q_changeperm(theFile.fullpath);
1100 break;
1101 case SH_Q_MOVE:
1102 sh_q_move(theFile.fullpath, &theFile, timestrc, timestra, timestrm);
1103 break;
1104 default:
1105 sh_error_handle (ShSuidchkSeverity, FIL__,
1106 __LINE__, 0, MSG_SUID_QREPORT,
1107 _("Bad quarantine method"), tmp);
1108 break;
1109 }
1110 }
1111 else
1112 {
1113 /* 1.8.1 push file to in-memory database
1114 */
1115 (void) sh_hash_compdata (SH_LEVEL_READONLY,
1116 &theFile, fileHash,
1117 _("[SuidCheck]"),
1118 ShSuidchkSeverity);
1119
1120 sh_hash_addflag(tmpcat, SH_FFLAG_SUIDCHK);
1121
1122 }
1123 }
1124 else
1125 {
1126 /* File exists. Check for modifications.
1127 */
1128 (void) sh_hash_compdata (SH_LEVEL_READONLY,
[1]1129 &theFile, fileHash,
[131]1130 _("[SuidCheck]"),
1131 ShSuidchkSeverity);
1132
1133 sh_hash_addflag(tmpcat, SH_FFLAG_SUIDCHK);
1134
1135 }
1136 }
1137 }
1138 SH_FREE(tmp);
1139 if (theFile.attr_string)
1140 SH_FREE(theFile.attr_string);
1141 }
1142 }
1143 SH_FREE(tmpcat);
[115]1144
[131]1145
[1]1146#ifdef HAVE_SCHED_YIELD
1147 if (ShSuidchkYield == S_TRUE)
1148 {
1149 if (sched_yield() == -1)
1150 {
1151 status = errno;
1152 sh_error_handle ((-1), FIL__, __LINE__, status, MSG_E_SUBGEN,
[131]1153 _("Failed to release time slice"),
[1]1154 _("sh_suidchk_check_internal") );
1155 }
1156 }
1157#endif
[131]1158
1159 dirlist = dirlist->next;
[68]1160
[131]1161 } while (dirlist != NULL);
[1]1162
[131]1163
1164 kill_sh_dirlist (dirlist_orig);
1165
[1]1166 SL_RETURN( (0), _("sh_suidchk_check_internal"));
1167}
1168
1169/*************
1170 *
1171 * module init
1172 *
1173 *************/
1174int sh_suidchk_init ()
1175{
1176 if (ShSuidchkActive == S_FALSE)
1177 return (-1);
1178
1179 return (0);
1180}
1181
1182
1183/*************
1184 *
1185 * module cleanup
1186 *
1187 *************/
1188int sh_suidchk_end ()
1189{
1190 return (0);
1191}
1192
1193
1194/*************
1195 *
1196 * module timer
1197 *
1198 *************/
1199int sh_suidchk_timer (time_t tcurrent)
1200{
1201 if (sh.flag.checkSum == SH_CHECK_INIT)
1202 return -1;
1203
1204 /* One-shot (not daemon and not loop forever)
1205 */
1206 if (sh.flag.isdaemon != S_TRUE && sh.flag.loop == S_FALSE)
1207 return -1;
1208
1209 if (ShSuidchkSched != NULL)
1210 {
1211 return test_sched(ShSuidchkSched);
1212 }
1213 if ((time_t) (tcurrent - lastcheck) >= ShSuidchkInterval)
1214 {
1215 lastcheck = tcurrent;
1216 return (-1);
1217 }
1218 return 0;
1219}
1220
1221/*************
1222 *
1223 * module check
1224 *
1225 *************/
1226
1227int sh_suidchk_check ()
1228{
1229 int status;
1230
1231 SL_ENTER(_("sh_suidchk_check"));
1232
1233 sh_error_handle (SH_ERR_NOTICE, FIL__, __LINE__, EINVAL, MSG_E_SUBGEN,
1234 _("Checking for SUID programs"),
1235 _("suidchk_check") );
1236
1237 FileLimNow = time(NULL);
1238 FileLimStart = FileLimNow;
1239 FileLimNum = 0;
1240 FileLimTotal = 0;
1241
[22]1242#ifdef SH_SUIDTESTDIR
1243 status = sh_suidchk_check_internal (SH_SUIDTESTDIR);
1244#else
[1]1245 status = sh_suidchk_check_internal ("/");
[22]1246#endif
[1]1247
1248 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_SUID_SUMMARY,
1249 FileLimTotal,
1250 (long) (time(NULL) - FileLimStart) );
1251
1252 SL_RETURN(status, _("sh_suidchk_check"));
1253}
1254
1255/*************
1256 *
1257 * module setup
1258 *
1259 *************/
1260
[68]1261int sh_suidchk_set_severity (const char * c)
[1]1262{
1263 int retval;
1264 char tmp[32];
1265
1266 SL_ENTER(_("sh_suidchk_set_severity"));
1267 tmp[0] = '='; tmp[1] = '\0';
1268 (void) sl_strlcat (tmp, c, 32);
1269 retval = sh_error_set_level (tmp, &ShSuidchkSeverity);
1270 SL_RETURN(retval, _("sh_suidchk_set_severity"));
1271}
1272
[68]1273int sh_suidchk_set_exclude (const char * c)
[1]1274{
1275 SL_ENTER(_("sh_suidchk_set_exclude"));
[68]1276
[1]1277 if (c == NULL || c[0] == '\0')
1278 {
1279 SL_RETURN(-1, _("sh_suidchk_set_exclude"));
1280 }
1281
1282 if (0 == sl_strncmp(c, _("NULL"), 4))
1283 {
1284 if (ShSuidchkExclude != NULL)
1285 SH_FREE(ShSuidchkExclude);
1286 ShSuidchkExclude = NULL;
1287 SL_RETURN(0, _("sh_suidchk_set_exclude"));
1288 }
1289
1290 if (ShSuidchkExclude != NULL)
1291 SH_FREE(ShSuidchkExclude);
1292
[68]1293 ShSuidchkExclude = sh_util_strdup (c);
1294 ExcludeLen = sl_strlen (ShSuidchkExclude);
1295 if (ShSuidchkExclude[ExcludeLen-1] == '/')
[1]1296 {
[68]1297 ShSuidchkExclude[ExcludeLen-1] = '\0';
[55]1298 ExcludeLen--;
[1]1299 }
1300 SL_RETURN(0, _("sh_suidchk_set_exclude"));
1301}
1302
[68]1303int sh_suidchk_set_timer (const char * c)
[1]1304{
1305 long val;
1306
1307 SL_ENTER(_("sh_suidchk_set_timer"));
1308
1309 val = strtol (c, (char **)NULL, 10);
1310 if (val <= 0)
1311 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_EINVALS,
1312 _("suidchk timer"), c);
1313
1314 val = (val <= 0 ? 7200 : val);
1315
1316 ShSuidchkInterval = (time_t) val;
1317 SL_RETURN( 0, _("sh_suidchk_set_timer"));
1318}
1319
1320
1321int sh_suidchk_free_schedule ()
1322{
1323 sh_schedule_t * current = ShSuidchkSched;
1324 sh_schedule_t * next = NULL;
1325
1326 while (current != NULL)
1327 {
1328 next = current->next;
1329 SH_FREE(current);
1330 current = next;
1331 }
1332 ShSuidchkSched = NULL;
1333 return 0;
1334}
1335
[68]1336int sh_suidchk_set_schedule (const char * str)
[1]1337{
1338 int status;
1339 sh_schedule_t * newSched = NULL;
1340
1341 SL_ENTER(_("sh_suidchk_set_schedule"));
1342
1343 /*
1344 if (ShSuidchkSched != NULL)
1345 {
1346 SH_FREE(ShSuidchkSched);
1347 ShSuidchkSched = NULL;
1348 }
1349 */
1350
1351 if (0 == sl_strncmp(str, _("NULL"), 4))
1352 {
1353 (void) sh_suidchk_free_schedule ();
1354 return 0;
1355 }
1356
1357 newSched = SH_ALLOC(sizeof(sh_schedule_t));
1358 status = create_sched(str, newSched);
1359 if (status != 0)
1360 {
1361 SH_FREE(newSched);
1362 newSched = NULL;
1363 }
1364 else
1365 {
1366 newSched->next = ShSuidchkSched;
1367 ShSuidchkSched = newSched;
1368 }
1369 SL_RETURN( status, _("sh_suidchk_set_schedule"));
1370}
1371
1372
1373
[68]1374int sh_suidchk_set_fps (const char * c)
[1]1375{
1376 long val;
1377
1378 SL_ENTER(_("sh_suidchk_set_fps"));
1379
1380 val = strtol (c, (char **)NULL, 10);
1381 if (val < 0)
1382 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_EINVALS,
1383 _("suidchk fps"), c);
1384
1385 val = (val < 0 ? 0 : val);
1386
1387 ShSuidchkFps = val;
1388 SL_RETURN( 0, _("sh_suidchk_set_fps"));
1389}
1390
[68]1391int sh_suidchk_set_yield (const char * c)
[1]1392{
1393 int i;
1394 SL_ENTER(_("sh_suidchk_set_yield"));
1395#ifdef HAVE_SCHED_YIELD
1396 i = sh_util_flagval(c, &ShSuidchkYield);
1397#else
1398 (void) c; /* cast to void to avoid compiler warning */
1399 i = -1;
1400#endif
1401 SL_RETURN(i, _("sh_suidchk_set_yield"));
1402}
1403
[68]1404int sh_suidchk_set_activate (const char * c)
[1]1405{
1406 int i;
1407 SL_ENTER(_("sh_suidchk_set_activate"));
1408 i = sh_util_flagval(c, &ShSuidchkActive);
1409 SL_RETURN(i, _("sh_suidchk_set_activate"));
1410}
1411
[119]1412int sh_suidchk_set_nosuid (const char * c)
1413{
1414 int i;
1415 SL_ENTER(_("sh_suidchk_set_nosuid"));
1416 i = sh_util_flagval(c, &ShSuidchkNosuid);
1417 SL_RETURN(i, _("sh_suidchk_set_nosuid"));
1418}
1419
[68]1420int sh_suidchk_set_quarantine (const char * c)
[1]1421{
1422 int i;
1423 SL_ENTER(_("sh_suidchk_set_quarantine"));
1424 i = sh_util_flagval(c, &ShSuidchkQEnable);
1425 SL_RETURN(i, _("sh_suidchk_set_quarantine"));
1426}
1427
[68]1428int sh_suidchk_set_qdelete (const char * c)
[1]1429{
1430 int i;
1431 SL_ENTER(_("sh_suidchk_set_qdelete"));
1432 i = sh_util_flagval(c, &ShSuidchkQDelete);
1433 SL_RETURN(i, _("sh_suidchk_set_qdelete"));
1434}
1435
[68]1436int sh_suidchk_set_qmethod (const char * c)
[1]1437{
1438 long val;
1439 int ret = 0;
[22]1440 struct stat buf;
[1]1441
1442 SL_ENTER(_("sh_suidchk_set_qmethod"));
1443
1444 val = strtol (c, (char **)NULL, 10);
1445 if (val < 0)
1446 {
1447 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_EINVALS,
1448 _("suidchk qmethod"), c);
1449 ret = -1;
1450 }
1451 else
1452 {
1453 switch (val)
1454 {
1455 case SH_Q_DELETE:
1456 ShSuidchkQMethod = SH_Q_DELETE;
1457 break;
1458 case SH_Q_CHANGEPERM:
1459 ShSuidchkQMethod = SH_Q_CHANGEPERM;
1460 break;
1461 case SH_Q_MOVE:
[22]1462 if (retry_stat (FIL__, __LINE__, DEFAULT_QDIR, &buf) != 0)
[1]1463 {
1464 if (mkdir (DEFAULT_QDIR, 0750) == -1)
1465 {
1466 sh_error_handle ((-1), FIL__, __LINE__, EINVAL,
1467 MSG_SUID_ERROR,
1468 _("Unable to create quarantine directory"));
1469 }
1470 }
1471 ShSuidchkQMethod = SH_Q_MOVE;
1472 break;
1473 default:
1474 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_EINVALS,
1475 _("suidchk qmethod"), c);
1476 ShSuidchkQMethod = -1;
1477 ret = -1;
1478 break;
1479 }
1480 }
1481
1482 SL_RETURN( ret, _("sh_suidchk_set_qmethod"));
1483}
1484
1485#if defined(FSTYPE_STATFS) || defined(FSTYPE_AIX_STATFS)
1486/* dirname.c -- return all but the last element in a path
1487 Copyright (C) 1990 Free Software Foundation, Inc.
1488
1489 This program is free software; you can redistribute it and/or modify
1490 it under the terms of the GNU General Public License as published by
1491 the Free Software Foundation; either version 2, or (at your option)
1492 any later version.
1493
1494 This program is distributed in the hope that it will be useful,
1495 but WITHOUT ANY WARRANTY; without even the implied warranty of
1496 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1497 GNU General Public License for more details.
1498
1499 You should have received a copy of the GNU General Public License
1500 along with this program; if not, write to the Free Software Foundation,
1501 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
1502
1503/* Return the leading directories part of PATH,
1504 allocated with malloc. If out of memory, return 0.
1505 Assumes that trailing slashes have already been
1506 removed. */
1507
1508char * sh_dirname (const char * path)
1509{
1510 char *newpath;
1511 char *slash;
1512 int length; /* Length of result, not including NUL. */
1513
1514 slash = strrchr (path, '/');
1515 if (slash == NULL)
1516 {
1517 /* File is in the current directory. */
1518 path = ".";
1519 length = 1;
1520 }
1521 else
1522 {
1523 /* Remove any trailing slashes from the result. */
1524 while (slash > path && *slash == '/')
1525 --slash;
1526
1527 length = slash - path + 1;
1528 }
1529 newpath = (char *) SH_ALLOC (length + 1);
1530 if (newpath == NULL)
1531 return NULL;
1532 strncpy (newpath, path, length);
1533 newpath[length] = '\0';
1534 return newpath;
1535}
1536/* #ifdef FSTYPE_STATFS */
1537#endif
1538
1539/* fstype.c -- determine type of filesystems that files are on
1540 Copyright (C) 1990, 91, 92, 93, 94 Free Software Foundation, Inc.
1541
1542 This program is free software; you can redistribute it and/or modify
1543 it under the terms of the GNU General Public License as published by
1544 the Free Software Foundation; either version 2, or (at your option)
1545 any later version.
1546
1547 This program is distributed in the hope that it will be useful,
1548 but WITHOUT ANY WARRANTY; without even the implied warranty of
1549 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1550 GNU General Public License for more details.
1551
1552 You should have received a copy of the GNU General Public License
1553 along with this program; if not, write to the Free Software
1554 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
1555
1556/* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
1557
1558/* Modified by R. Wichmann:
1559 - replaced error() by sh_error_handle()
1560 - replaced xstrdup() by sl_strdup()
1561 - replaced strstr() by sl_strstr()
1562 - some additions to recognize nosuid fs
1563*/
1564
1565/* modetype.h -- file type bits definitions for POSIX systems
1566 Requires sys/types.h sys/stat.h.
1567 Copyright (C) 1990 Free Software Foundation, Inc.
1568
1569 This program is free software; you can redistribute it and/or modify
1570 it under the terms of the GNU General Public License as published by
1571 the Free Software Foundation; either version 2, or (at your option)
1572 any later version.
1573
1574 This program is distributed in the hope that it will be useful,
1575 but WITHOUT ANY WARRANTY; without even the implied warranty of
1576 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1577 GNU General Public License for more details.
1578
1579 You should have received a copy of the GNU General Public License
1580 along with this program; if not, write to the Free Software
1581 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
1582
1583/* POSIX.1 doesn't mention the S_IFMT bits; instead, it uses S_IStype
1584 test macros. To make storing file types more convenient, define
1585 them; the values don't need to correspond to what the kernel uses,
1586 because of the way we use them. */
1587#ifndef S_IFMT /* Doesn't have traditional Unix macros. */
1588#define S_IFBLK 1
1589#define S_IFCHR 2
1590#define S_IFDIR 4
1591#define S_IFREG 8
1592#ifdef S_ISLNK
1593#define S_IFLNK 16
1594#endif
1595#ifdef S_ISFIFO
1596#define S_IFIFO 32
1597#endif
1598#ifdef S_ISSOCK
1599#define S_IFSOCK 64
1600#endif
1601#endif /* !S_IFMT */
1602
1603#ifdef STAT_MACROS_BROKEN
1604#undef S_ISBLK
1605#undef S_ISCHR
1606#undef S_ISDIR
1607#undef S_ISREG
1608#undef S_ISFIFO
1609#undef S_ISLNK
1610#undef S_ISSOCK
1611#undef S_ISMPB
1612#undef S_ISMPC
1613#undef S_ISNWK
1614#endif
1615
1616/* Do the reverse: define the POSIX.1 macros for traditional Unix systems
1617 that don't have them. */
1618#if !defined(S_ISBLK) && defined(S_IFBLK)
1619#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
1620#endif
1621#if !defined(S_ISCHR) && defined(S_IFCHR)
1622#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
1623#endif
1624#if !defined(S_ISDIR) && defined(S_IFDIR)
1625#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
1626#endif
1627#if !defined(S_ISREG) && defined(S_IFREG)
1628#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
1629#endif
1630#if !defined(S_ISFIFO) && defined(S_IFIFO)
1631#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
1632#endif
1633#if !defined(S_ISLNK) && defined(S_IFLNK)
1634#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
1635#endif
1636#if !defined(S_ISSOCK) && defined(S_IFSOCK)
1637#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
1638#endif
1639#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */
1640#define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB)
1641#define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC)
1642#endif
1643#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */
1644#define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK)
1645#endif
1646
1647
1648static char *filesystem_type_uncached (char *path, char *relpath,
1649 struct stat *statp);
1650
1651#ifdef FSTYPE_MNTENT /* 4.3BSD etc. */
1652static int xatoi (char *cp);
1653#endif
1654
1655#ifdef FSTYPE_MNTENT /* 4.3BSD, SunOS, HP-UX, Dynix, Irix. */
1656#include <mntent.h>
1657#if !defined(MOUNTED)
1658# if defined(MNT_MNTTAB) /* HP-UX. */
1659# define MOUNTED MNT_MNTTAB
1660# endif
1661# if defined(MNTTABNAME) /* Dynix. */
1662# define MOUNTED MNTTABNAME
1663# endif
1664#endif
1665#endif
1666
1667#ifdef FSTYPE_GETMNT /* Ultrix. */
1668#include <sys/param.h>
1669#include <sys/mount.h>
1670#include <sys/fs_types.h>
1671#endif
1672
1673#ifdef FSTYPE_USG_STATFS /* SVR3. */
1674#include <sys/statfs.h>
1675#include <sys/fstyp.h>
1676#endif
1677
1678#ifdef FSTYPE_STATVFS /* SVR4. */
1679#include <sys/statvfs.h>
1680#include <sys/fstyp.h>
1681#endif
1682
1683#ifdef FSTYPE_STATFS /* 4.4BSD. */
1684#include <sys/param.h> /* NetBSD needs this. */
1685#include <sys/mount.h>
1686
1687#ifndef MFSNAMELEN /* NetBSD defines this. */
1688static char *
1689fstype_to_string (t)
1690 short t;
1691{
1692#ifdef INITMOUNTNAMES /* Defined in 4.4BSD, not in NET/2. */
1693 static char *mn[] = INITMOUNTNAMES;
1694 if (t >= 0 && t <= MOUNT_MAXTYPE)
1695 return mn[t];
1696 else
1697 return "?";
1698#else /* !INITMOUNTNAMES */
1699 switch (t)
1700 {
1701 case MOUNT_UFS:
1702 return _("ufs");
1703 case MOUNT_NFS:
1704 return _("nfs");
1705#ifdef MOUNT_PC
1706 case MOUNT_PC:
1707 return _("pc");
1708#endif
1709#ifdef MOUNT_MFS
1710 case MOUNT_MFS:
1711 return _("mfs");
1712#endif
1713#ifdef MOUNT_LO
1714 case MOUNT_LO:
1715 return _("lofs");
1716#endif
1717#ifdef MOUNT_TFS
1718 case MOUNT_TFS:
1719 return _("tfs");
1720#endif
1721#ifdef MOUNT_TMP
1722 case MOUNT_TMP:
1723 return _("tmp");
1724#endif
1725#ifdef MOUNT_MSDOS
1726 case MOUNT_MSDOS:
1727 return _("msdos");
1728#endif
1729#ifdef MOUNT_ISO9660
1730 case MOUNT_ISO9660:
1731 return _("iso9660fs");
1732#endif
1733 default:
1734 return "?";
1735 }
1736#endif /* !INITMOUNTNAMES */
1737}
1738#endif /* !MFSNAMELEN */
1739#endif /* FSTYPE_STATFS */
1740
1741#ifdef FSTYPE_AIX_STATFS /* AIX. */
1742#include <sys/vmount.h>
1743#include <sys/statfs.h>
1744
1745#define FSTYPE_STATFS /* Otherwise like 4.4BSD. */
1746#define f_type f_vfstype
1747
1748static char *
1749fstype_to_string (t)
1750 short t;
1751{
1752 switch (t)
1753 {
1754 case MNT_AIX:
1755 return _("aix"); /* AIX 4.3: NFS filesystems are actually MNT_AIX. */
1756#ifdef MNT_NAMEFS
1757 case MNT_NAMEFS:
1758 return _("namefs");
1759#endif
1760 case MNT_NFS:
1761 return _("nfs");
1762 case MNT_JFS:
1763 return _("jfs");
1764 case MNT_CDROM:
1765 return _("cdrom");
1766#ifdef MNT_PROCFS
1767 case MNT_PROCFS:
1768 return _("procfs");
1769#endif
1770#ifdef MNT_SFS
1771 case MNT_SFS:
1772 return _("sfs");
1773#endif
1774#ifdef MNT_CACHEFS
1775 case MNT_CACHEFS:
1776 return _("cachefs");
1777#endif
1778#ifdef MNT_NFS3
1779 case MNT_NFS3:
1780 return _("nfs3");
1781#endif
1782#ifdef MNT_AUTOFS
1783 case MNT_AUTOFS:
1784 return _("autofs");
1785#endif
1786#ifdef MNT_VXFS
1787 case MNT_VXFS:
1788 return _("vxfs");
1789#endif
1790#ifdef MNT_VXODM
1791 case MNT_VXODM:
1792 return _("veritasfs");
1793#endif
1794#ifdef MNT_UDF
1795 case MNT_UDF:
1796 return _("udfs");
1797#endif
1798#ifdef MNT_NFS4
1799 case MNT_NFS4:
1800 return _("nfs4");
1801#endif
1802#ifdef MNT_RFS4
1803 case MNT_RFS4:
1804 return _("nfs4");
1805#endif
1806#ifdef MNT_CIFS
1807 case MNT_CIFS:
1808 return _("cifs");
1809#endif
1810 default:
1811 return "?";
1812 }
1813}
1814#endif /* FSTYPE_AIX_STATFS */
1815
1816#ifdef AFS
1817#include <netinet/in.h>
1818#include <afs/venus.h>
1819#if __STDC__
1820/* On SunOS 4, afs/vice.h defines this to rely on a pre-ANSI cpp. */
1821#undef _VICEIOCTL
1822#define _VICEIOCTL(id) ((unsigned int ) _IOW('V', id, struct ViceIoctl))
1823#endif
1824#ifndef _IOW
1825/* AFS on Solaris 2.3 doesn't get this definition. */
1826#include <sys/ioccom.h>
1827#endif
1828
1829static int
1830in_afs (path)
1831 char *path;
1832{
1833 static char space[2048];
1834 struct ViceIoctl vi;
1835
1836 vi.in_size = 0;
1837 vi.out_size = sizeof (space);
1838 vi.out = space;
1839
1840 if (pioctl (path, VIOC_FILE_CELL_NAME, &vi, 1)
1841 && (errno == EINVAL || errno == ENOENT))
1842 return 0;
1843 return 1;
1844}
1845#endif /* AFS */
1846
1847/* Nonzero if the current filesystem's type is known. */
1848static int fstype_known = 0;
1849
1850/* Return a static string naming the type of filesystem that the file PATH,
1851 described by STATP, is on.
1852 RELPATH is the file name relative to the current directory.
1853 Return "unknown" if its filesystem type is unknown. */
1854
1855static char *
1856filesystem_type (char * path, char * relpath, struct stat * statp)
1857{
1858 static char *current_fstype = NULL;
1859 static dev_t current_dev;
1860
1861 if (current_fstype != NULL)
1862 {
1863 if ((0 != fstype_known) && statp->st_dev == current_dev)
1864 return current_fstype; /* Cached value. */
1865 SH_FREE (current_fstype);
1866 }
1867 current_dev = statp->st_dev;
1868 current_fstype = filesystem_type_uncached (path, relpath, statp);
1869 return current_fstype;
1870}
1871
1872/* Return a newly allocated string naming the type of filesystem that the
1873 file PATH, described by STATP, is on.
1874 RELPATH is the file name relative to the current directory.
1875 Return "unknown" if its filesystem type is unknown. */
1876
1877static char *
1878filesystem_type_uncached (path, relpath, statp)
1879 char *path;
1880 char *relpath;
1881 struct stat *statp;
1882{
1883 char * type = NULL;
1884#ifdef MFSNAMELEN /* NetBSD. */
1885 static char my_tmp_type[64];
1886#endif
1887
1888#ifdef FSTYPE_MNTENT /* 4.3BSD, SunOS, HP-UX, Dynix, Irix. */
1889 char *table = MOUNTED;
1890 FILE *mfp;
1891 struct mntent *mnt;
1892
1893 if (path == NULL || relpath == NULL)
1894 return NULL;
1895
1896 mfp = setmntent (table, "r");
1897 if (mfp == NULL)
1898 {
1899 sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
1900 _("setmntent() failed"),
1901 _("filesystem_type_uncached") );
1902 return NULL;
1903 }
1904
1905 /* Find the entry with the same device number as STATP, and return
1906 that entry's fstype. */
1907 while (type == NULL && (mnt = getmntent (mfp)) != NULL)
1908 {
1909 char *devopt;
1910 dev_t dev;
1911 struct stat disk_stats;
1912
1913#ifdef MNTTYPE_IGNORE
1914 if (0 == strcmp (mnt->mnt_type, MNTTYPE_IGNORE))
1915 continue;
1916#endif
1917
1918 /* Newer systems like SunOS 4.1 keep the dev number in the mtab,
1919 in the options string. For older systems, we need to stat the
1920 directory that the filesystem is mounted on to get it.
1921
1922 Unfortunately, the HPUX 9.x mnttab entries created by automountq
1923 contain a dev= option but the option value does not match the
1924 st_dev value of the file (maybe the lower 16 bits match?). */
1925
1926#if !defined(hpux) && !defined(__hpux__)
1927 devopt = sl_strstr (mnt->mnt_opts, "dev=");
1928 if (devopt)
1929 {
1930 if (devopt[4] == '0' && (devopt[5] == 'x' || devopt[5] == 'X'))
1931 dev = (dev_t) xatoi (devopt + 6);
1932 else
1933 dev = (dev_t) xatoi (devopt + 4);
1934 }
1935 else
1936#endif /* not hpux */
1937 {
1938 if (stat (mnt->mnt_dir, &disk_stats) == -1)
1939 {
1940 sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
1941 _("stat() failed"),
1942 _("filesystem_type_uncached") );
1943 return NULL;
1944 }
1945 dev = disk_stats.st_dev;
1946 }
1947
1948 if (dev == statp->st_dev)
1949 {
1950 /* check for the "nosuid" option
1951 */
[61]1952#ifdef HAVE_HASMNTOPT
[119]1953 if (NULL == hasmntopt(mnt, "nosuid") || (ShSuidchkNosuid == S_TRUE))
[1]1954 type = mnt->mnt_type;
1955 else
[61]1956 type = _("nosuid"); /* hasmntopt (nosuid) */
1957#else
1958 type = mnt->mnt_type;
1959#endif
[1]1960 }
1961 }
1962
1963 if (endmntent (mfp) == 0)
1964 {
1965 sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
1966 _("endmntent() failed"),
1967 _("filesystem_type_uncached") );
1968 }
1969#endif
1970
1971#ifdef FSTYPE_GETMNT /* Ultrix. */
1972 int offset = 0;
1973 struct fs_data fsd;
1974
1975 if (path == NULL || relpath == NULL)
1976 return NULL;
1977
1978 while (type == NULL
1979 && getmnt (&offset, &fsd, sizeof (fsd), NOSTAT_MANY, 0) > 0)
1980 {
1981 if (fsd.fd_req.dev == statp->st_dev)
1982 type = gt_names[fsd.fd_req.fstype];
1983 }
1984#endif
1985
1986#ifdef FSTYPE_USG_STATFS /* SVR3. */
1987 struct statfs fss;
1988 char typebuf[FSTYPSZ];
1989
1990 if (path == NULL || relpath == NULL)
1991 return NULL;
1992
1993 if (statfs (relpath, &fss, sizeof (struct statfs), 0) == -1)
1994 {
1995 /* Don't die if a file was just removed. */
1996 if (errno != ENOENT)
1997 {
1998 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
1999 _("statfs() failed"),
2000 _("filesystem_type_uncached") );
2001 return NULL;
2002 }
2003 }
2004 else if (!sysfs (GETFSTYP, fss.f_fstyp, typebuf))
2005 type = typebuf;
2006#endif
2007
2008#ifdef FSTYPE_STATVFS /* SVR4. */
2009 struct statvfs fss;
2010
2011 if (path == NULL || relpath == NULL)
2012 return NULL;
2013
2014 if (statvfs (relpath, &fss) == -1)
2015 {
2016 /* Don't die if a file was just removed. */
2017 if (errno != ENOENT)
2018 {
2019 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
2020 _("statvfs() failed"),
2021 _("filesystem_type_uncached") );
2022 return NULL;
2023 }
2024 }
2025 else
2026 {
2027 type = fss.f_basetype;
2028
2029 /* patch by Konstantin Khrooschev <nathoo@co.ru>
2030 */
[119]2031 if( (fss.f_flag & ST_NOSUID) && (ShSuidchkNosuid == S_FALSE))
[1]2032 type = _("nosuid");
2033 }
2034 (void) statp; /* fix compiler warning */
2035#endif
2036
2037#ifdef FSTYPE_STATFS /* 4.4BSD. */
2038 struct statfs fss;
2039 char *p;
2040#if defined(MNT_VISFLAGMASK) && defined(HAVE_STRUCT_STATFS_F_FLAGS)
2041 int flags;
2042#endif
2043 /* char * sh_dirname(const char *path); */
2044
2045 if (path == NULL || relpath == NULL)
2046 return NULL;
2047
2048 if (S_ISLNK (statp->st_mode))
2049 p = sh_dirname (relpath);
2050 else
2051 p = relpath;
2052
2053 if (statfs (p, &fss) == -1)
2054 {
2055 /* Don't die if symlink to nonexisting file, or a file that was
2056 just removed. */
2057 if (errno != ENOENT)
2058 {
2059 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
2060 _("statfs() failed"),
2061 _("filesystem_type_uncached") );
2062 return NULL;
2063 }
2064 }
2065 else
2066 {
2067
2068#ifdef MFSNAMELEN /* NetBSD. */
2069 /* MEMORY LEAK !!!
2070 * type = sh_util_strdup (fss.f_fstypename);
2071 */
2072 sl_strlcpy (my_tmp_type, fss.f_fstypename, 64);
2073 type = my_tmp_type;
2074#else
2075 type = fstype_to_string (fss.f_type);
2076#endif
2077
2078#ifdef HAVE_STRUCT_STATFS_F_FLAGS
2079#ifdef MNT_VISFLAGMASK
2080 flags = fss.f_flags & MNT_VISFLAGMASK;
[119]2081 if ((flags & MNT_NOSUID) && (ShSuidchkNosuid == S_FALSE))
[1]2082#else
[119]2083 if ((fss.f_flags & MNT_NOSUID) && (ShSuidchkNosuid == S_FALSE))
[1]2084#endif
2085 type = _("nosuid");
2086#endif
2087 }
2088 if (p != relpath)
2089 SH_FREE (p);
2090#endif
2091
2092#ifdef AFS
2093 if ((!type || !strcmp (type, "xx")) && in_afs (relpath))
2094 type = "afs";
2095#endif
2096
2097 /* An unknown value can be caused by an ENOENT error condition.
2098 Don't cache those values. */
2099 fstype_known = (int)(type != NULL);
2100
2101 return sh_util_strdup (type ? type : "unknown");
2102}
2103
2104#ifdef FSTYPE_MNTENT /* 4.3BSD etc. */
2105/* Return the value of the hexadecimal number represented by CP.
2106 No prefix (like '0x') or suffix (like 'h') is expected to be
2107 part of CP. */
2108
2109static int
2110xatoi (cp)
2111 char *cp;
2112{
2113 int val;
2114
2115 val = 0;
2116 while (*cp != '\0')
2117 {
2118 /*@+charint@*/
2119 if (*cp >= 'a' && *cp <= 'f')
2120 val = val * 16 + *cp - 'a' + 10;
2121 else if (*cp >= 'A' && *cp <= 'F')
2122 val = val * 16 + *cp - 'A' + 10;
2123 else if (*cp >= '0' && *cp <= '9')
2124 val = val * 16 + *cp - '0';
2125 else
2126 break;
2127 /*@-charint@*/
2128 cp++;
2129 }
2130 return val;
2131}
2132#endif
2133
2134
2135
2136#endif
2137
2138
2139/* #ifdef SH_USE_UTMP */
2140#endif
2141
2142
2143
Note: See TracBrowser for help on using the repository browser.