source: trunk/src/sh_suidchk.c@ 149

Last change on this file since 149 was 149, checked in by katerina, 17 years ago

Make sh_hash.c thread-safe, remove plenty of tiny allocations, improve sh_mem_dump, modify port check to run as thread, and fix unsetting of sh_thread_pause_flag (was too early).

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