source: trunk/src/sh_suidchk.c@ 167

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

First parts of changes for MODI_TXT

File size: 51.7 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, S_IRUSR|S_IWUSR|S_IXUSR);
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 theFile.link_path = NULL;
1035
1036 status = sh_unix_getinfo (ShDFLevel[SH_ERR_T_RO],
1037 dirlist->sh_d_name,
1038 &theFile, fileHash, 0);
1039
1040 tmp = sh_util_safe_name(tmpcat);
1041
1042 if (status != 0)
1043 {
1044 sh_error_handle (ShSuidchkSeverity, FIL__, __LINE__,
1045 0, MSG_E_SUBGPATH,
1046 _("Could not check suid/sgid file"),
1047 _("sh_suidchk_check_internal"),
1048 tmp);
1049 }
1050 else
1051 {
1052
1053 if ( sh.flag.update == S_TRUE &&
1054 (sh.flag.checkSum == SH_CHECK_INIT ||
1055 sh.flag.checkSum == SH_CHECK_CHECK))
1056 {
1057 /* Updating database. Report new files that
1058 * are not in database already. Then compare
1059 * to database and report changes.
1060 */
1061 if (-1 == sh_hash_have_it (tmpcat))
1062 {
1063 sh_error_handle ((-1), FIL__, __LINE__,
1064 0, MSG_SUID_FOUND, tmp );
1065 }
1066 else
1067 {
1068 sh_error_handle (SH_ERR_ALL, FIL__, __LINE__,
1069 0, MSG_SUID_FOUND, tmp );
1070 }
1071
1072 if (0 == sh_hash_compdata (SH_LEVEL_READONLY,
1073 &theFile, fileHash,
1074 _("[SuidCheck]"),
1075 ShSuidchkSeverity))
1076 {
1077 sh_hash_pushdata_memory (&theFile, fileHash);
1078 }
1079
1080 sh_hash_addflag(tmpcat, SH_FFLAG_SUIDCHK);
1081
1082 }
1083
1084 else if (sh.flag.checkSum == SH_CHECK_INIT &&
1085 sh.flag.update == S_FALSE )
1086 {
1087 /* Running init. Report on files detected.
1088 */
1089 sh_hash_pushdata (&theFile, fileHash);
1090 sh_error_handle ((-1), FIL__, __LINE__,
1091 0, MSG_SUID_FOUND, tmp );
1092 }
1093
1094 else if (sh.flag.checkSum == SH_CHECK_CHECK )
1095 {
1096 /* Running file check. Report on new files
1097 * detected, and quarantine them.
1098 */
1099 sh_error_handle (SH_ERR_ALL, FIL__, __LINE__,
1100 0, MSG_SUID_FOUND, tmp );
1101
1102 fflags = sh_hash_getflags(tmpcat);
1103
1104 if ( (-1 == fflags) || (!SH_FFLAG_SUIDCHK_SET(fflags)))
1105 {
1106 if (-1 == fflags)
1107 {
1108 (void) sh_unix_gmttime (theFile.ctime, timestrc, sizeof(timestrc));
1109 (void) sh_unix_gmttime (theFile.atime, timestra, sizeof(timestra));
1110 (void) sh_unix_gmttime (theFile.mtime, timestrm, sizeof(timestrm));
1111
1112 report_file(tmpcat, &theFile, timestrc, timestra, timestrm);
1113 }
1114 /* Quarantine file according to configured method
1115 */
1116 if (ShSuidchkQEnable == S_TRUE)
1117 {
1118 switch (ShSuidchkQMethod)
1119 {
1120 case SH_Q_DELETE:
1121 sh_q_delete(theFile.fullpath);
1122 break;
1123 case SH_Q_CHANGEPERM:
1124 sh_q_changeperm(theFile.fullpath);
1125 break;
1126 case SH_Q_MOVE:
1127 sh_q_move(theFile.fullpath, &theFile, timestrc, timestra, timestrm);
1128 break;
1129 default:
1130 sh_error_handle (ShSuidchkSeverity, FIL__,
1131 __LINE__, 0, MSG_SUID_QREPORT,
1132 _("Bad quarantine method"), tmp);
1133 break;
1134 }
1135 }
1136 else
1137 {
1138 /* 1.8.1 push file to in-memory database
1139 */
1140 (void) sh_hash_compdata (SH_LEVEL_READONLY,
1141 &theFile, fileHash,
1142 _("[SuidCheck]"),
1143 ShSuidchkSeverity);
1144
1145 sh_hash_addflag(tmpcat, SH_FFLAG_SUIDCHK);
1146
1147 }
1148 }
1149 else
1150 {
1151 /* File exists. Check for modifications.
1152 */
1153 (void) sh_hash_compdata (SH_LEVEL_READONLY,
1154 &theFile, fileHash,
1155 _("[SuidCheck]"),
1156 ShSuidchkSeverity);
1157
1158 sh_hash_addflag(tmpcat, SH_FFLAG_SUIDCHK);
1159
1160 }
1161 }
1162 }
1163 SH_FREE(tmp);
1164 if (theFile.attr_string) SH_FREE(theFile.attr_string);
1165 if (theFile.link_path) SH_FREE(theFile.link_path);
1166 }
1167 }
1168 SH_FREE(tmpcat);
1169
1170
1171#ifdef HAVE_SCHED_YIELD
1172 if (ShSuidchkYield == S_TRUE)
1173 {
1174 if (sched_yield() == -1)
1175 {
1176 status = errno;
1177 sh_error_handle ((-1), FIL__, __LINE__, status, MSG_E_SUBGEN,
1178 _("Failed to release time slice"),
1179 _("sh_suidchk_check_internal") );
1180 }
1181 }
1182#endif
1183
1184 dirlist = dirlist->next;
1185
1186 } while (dirlist != NULL);
1187
1188
1189 kill_sh_dirlist (dirlist_orig);
1190
1191 SL_RETURN( (0), _("sh_suidchk_check_internal"));
1192}
1193
1194/*************
1195 *
1196 * module init
1197 *
1198 *************/
1199int sh_suidchk_init (struct mod_type * arg)
1200{
1201 (void) arg;
1202 if (ShSuidchkActive == S_FALSE)
1203 return (-1);
1204
1205 return (0);
1206}
1207
1208
1209/*************
1210 *
1211 * module cleanup
1212 *
1213 *************/
1214int sh_suidchk_end ()
1215{
1216 return (0);
1217}
1218
1219
1220/*************
1221 *
1222 * module timer
1223 *
1224 *************/
1225int sh_suidchk_timer (time_t tcurrent)
1226{
1227 if (sh.flag.checkSum == SH_CHECK_INIT)
1228 return -1;
1229
1230 /* One-shot (not daemon and not loop forever)
1231 */
1232 if (sh.flag.isdaemon != S_TRUE && sh.flag.loop == S_FALSE)
1233 return -1;
1234
1235 if (ShSuidchkSched != NULL)
1236 {
1237 return test_sched(ShSuidchkSched);
1238 }
1239 if ((time_t) (tcurrent - lastcheck) >= ShSuidchkInterval)
1240 {
1241 lastcheck = tcurrent;
1242 return (-1);
1243 }
1244 return 0;
1245}
1246
1247/*************
1248 *
1249 * module check
1250 *
1251 *************/
1252
1253int sh_suidchk_check ()
1254{
1255 int status;
1256
1257 SL_ENTER(_("sh_suidchk_check"));
1258
1259 sh_error_handle (SH_ERR_NOTICE, FIL__, __LINE__, EINVAL, MSG_E_SUBGEN,
1260 _("Checking for SUID programs"),
1261 _("suidchk_check") );
1262
1263 FileLimNow = time(NULL);
1264 FileLimStart = FileLimNow;
1265 FileLimNum = 0;
1266 FileLimTotal = 0;
1267
1268#ifdef SH_SUIDTESTDIR
1269 status = sh_suidchk_check_internal (SH_SUIDTESTDIR);
1270#else
1271 status = sh_suidchk_check_internal ("/");
1272#endif
1273
1274 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_SUID_SUMMARY,
1275 FileLimTotal,
1276 (long) (time(NULL) - FileLimStart) );
1277
1278 SL_RETURN(status, _("sh_suidchk_check"));
1279}
1280
1281/*************
1282 *
1283 * module setup
1284 *
1285 *************/
1286
1287int sh_suidchk_set_severity (const char * c)
1288{
1289 int retval;
1290 char tmp[32];
1291
1292 SL_ENTER(_("sh_suidchk_set_severity"));
1293 tmp[0] = '='; tmp[1] = '\0';
1294 (void) sl_strlcat (tmp, c, 32);
1295 retval = sh_error_set_level (tmp, &ShSuidchkSeverity);
1296 SL_RETURN(retval, _("sh_suidchk_set_severity"));
1297}
1298
1299int sh_suidchk_set_exclude (const char * c)
1300{
1301 SL_ENTER(_("sh_suidchk_set_exclude"));
1302
1303 if (c == NULL || c[0] == '\0')
1304 {
1305 SL_RETURN(-1, _("sh_suidchk_set_exclude"));
1306 }
1307
1308 if (0 == sl_strncmp(c, _("NULL"), 4))
1309 {
1310 if (ShSuidchkExclude != NULL)
1311 SH_FREE(ShSuidchkExclude);
1312 ShSuidchkExclude = NULL;
1313 SL_RETURN(0, _("sh_suidchk_set_exclude"));
1314 }
1315
1316 if (ShSuidchkExclude != NULL)
1317 SH_FREE(ShSuidchkExclude);
1318
1319 ShSuidchkExclude = sh_util_strdup (c);
1320 ExcludeLen = sl_strlen (ShSuidchkExclude);
1321 if (ShSuidchkExclude[ExcludeLen-1] == '/')
1322 {
1323 ShSuidchkExclude[ExcludeLen-1] = '\0';
1324 ExcludeLen--;
1325 }
1326 SL_RETURN(0, _("sh_suidchk_set_exclude"));
1327}
1328
1329int sh_suidchk_set_timer (const char * c)
1330{
1331 long val;
1332
1333 SL_ENTER(_("sh_suidchk_set_timer"));
1334
1335 val = strtol (c, (char **)NULL, 10);
1336 if (val <= 0)
1337 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_EINVALS,
1338 _("suidchk timer"), c);
1339
1340 val = (val <= 0 ? 7200 : val);
1341
1342 ShSuidchkInterval = (time_t) val;
1343 SL_RETURN( 0, _("sh_suidchk_set_timer"));
1344}
1345
1346
1347static void sh_suidchk_free_schedule ()
1348{
1349 sh_schedule_t * current = ShSuidchkSched;
1350 sh_schedule_t * next = NULL;
1351
1352 while (current != NULL)
1353 {
1354 next = current->next;
1355 SH_FREE(current);
1356 current = next;
1357 }
1358 ShSuidchkSched = NULL;
1359 return;
1360}
1361
1362int sh_suidchk_reconf ()
1363{
1364 sh_suidchk_free_schedule();
1365 set_defaults();
1366 return 0;
1367}
1368
1369int sh_suidchk_set_schedule (const char * str)
1370{
1371 int status;
1372 sh_schedule_t * newSched = NULL;
1373
1374 SL_ENTER(_("sh_suidchk_set_schedule"));
1375
1376 /*
1377 if (ShSuidchkSched != NULL)
1378 {
1379 SH_FREE(ShSuidchkSched);
1380 ShSuidchkSched = NULL;
1381 }
1382 */
1383
1384 if (0 == sl_strncmp(str, _("NULL"), 4))
1385 {
1386 (void) sh_suidchk_free_schedule ();
1387 return 0;
1388 }
1389
1390 newSched = SH_ALLOC(sizeof(sh_schedule_t));
1391 status = create_sched(str, newSched);
1392 if (status != 0)
1393 {
1394 SH_FREE(newSched);
1395 newSched = NULL;
1396 }
1397 else
1398 {
1399 newSched->next = ShSuidchkSched;
1400 ShSuidchkSched = newSched;
1401 }
1402 SL_RETURN( status, _("sh_suidchk_set_schedule"));
1403}
1404
1405
1406
1407int sh_suidchk_set_fps (const char * c)
1408{
1409 long val;
1410
1411 SL_ENTER(_("sh_suidchk_set_fps"));
1412
1413 val = strtol (c, (char **)NULL, 10);
1414 if (val < 0)
1415 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_EINVALS,
1416 _("suidchk fps"), c);
1417
1418 val = (val < 0 ? 0 : val);
1419
1420 ShSuidchkFps = val;
1421 SL_RETURN( 0, _("sh_suidchk_set_fps"));
1422}
1423
1424int sh_suidchk_set_yield (const char * c)
1425{
1426 int i;
1427 SL_ENTER(_("sh_suidchk_set_yield"));
1428#ifdef HAVE_SCHED_YIELD
1429 i = sh_util_flagval(c, &ShSuidchkYield);
1430#else
1431 (void) c; /* cast to void to avoid compiler warning */
1432 i = -1;
1433#endif
1434 SL_RETURN(i, _("sh_suidchk_set_yield"));
1435}
1436
1437int sh_suidchk_set_activate (const char * c)
1438{
1439 int i;
1440 SL_ENTER(_("sh_suidchk_set_activate"));
1441 i = sh_util_flagval(c, &ShSuidchkActive);
1442 SL_RETURN(i, _("sh_suidchk_set_activate"));
1443}
1444
1445int sh_suidchk_set_nosuid (const char * c)
1446{
1447 int i;
1448 SL_ENTER(_("sh_suidchk_set_nosuid"));
1449 i = sh_util_flagval(c, &ShSuidchkNosuid);
1450 SL_RETURN(i, _("sh_suidchk_set_nosuid"));
1451}
1452
1453int sh_suidchk_set_quarantine (const char * c)
1454{
1455 int i;
1456 SL_ENTER(_("sh_suidchk_set_quarantine"));
1457 i = sh_util_flagval(c, &ShSuidchkQEnable);
1458 SL_RETURN(i, _("sh_suidchk_set_quarantine"));
1459}
1460
1461int sh_suidchk_set_qdelete (const char * c)
1462{
1463 int i;
1464 SL_ENTER(_("sh_suidchk_set_qdelete"));
1465 i = sh_util_flagval(c, &ShSuidchkQDelete);
1466 SL_RETURN(i, _("sh_suidchk_set_qdelete"));
1467}
1468
1469int sh_suidchk_set_qmethod (const char * c)
1470{
1471 long val;
1472 int ret = 0;
1473 struct stat buf;
1474
1475 SL_ENTER(_("sh_suidchk_set_qmethod"));
1476
1477 val = strtol (c, (char **)NULL, 10);
1478 if (val < 0)
1479 {
1480 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_EINVALS,
1481 _("suidchk qmethod"), c);
1482 ret = -1;
1483 }
1484 else
1485 {
1486 switch (val)
1487 {
1488 case SH_Q_DELETE:
1489 ShSuidchkQMethod = SH_Q_DELETE;
1490 break;
1491 case SH_Q_CHANGEPERM:
1492 ShSuidchkQMethod = SH_Q_CHANGEPERM;
1493 break;
1494 case SH_Q_MOVE:
1495 if (retry_stat (FIL__, __LINE__, DEFAULT_QDIR, &buf) != 0)
1496 {
1497 if (mkdir (DEFAULT_QDIR, 0750) == -1)
1498 {
1499 sh_error_handle ((-1), FIL__, __LINE__, EINVAL,
1500 MSG_SUID_ERROR,
1501 _("Unable to create quarantine directory"));
1502 }
1503 }
1504 ShSuidchkQMethod = SH_Q_MOVE;
1505 break;
1506 default:
1507 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_EINVALS,
1508 _("suidchk qmethod"), c);
1509 ShSuidchkQMethod = -1;
1510 ret = -1;
1511 break;
1512 }
1513 }
1514
1515 SL_RETURN( ret, _("sh_suidchk_set_qmethod"));
1516}
1517
1518#if defined(FSTYPE_STATFS) || defined(FSTYPE_AIX_STATFS)
1519/* dirname.c -- return all but the last element in a path
1520 Copyright (C) 1990 Free Software Foundation, Inc.
1521
1522 This program is free software; you can redistribute it and/or modify
1523 it under the terms of the GNU General Public License as published by
1524 the Free Software Foundation; either version 2, or (at your option)
1525 any later version.
1526
1527 This program is distributed in the hope that it will be useful,
1528 but WITHOUT ANY WARRANTY; without even the implied warranty of
1529 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1530 GNU General Public License for more details.
1531
1532 You should have received a copy of the GNU General Public License
1533 along with this program; if not, write to the Free Software Foundation,
1534 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
1535
1536/* Return the leading directories part of PATH,
1537 allocated with malloc. If out of memory, return 0.
1538 Assumes that trailing slashes have already been
1539 removed. */
1540
1541char * sh_dirname (const char * path)
1542{
1543 char *newpath;
1544 char *slash;
1545 int length; /* Length of result, not including NUL. */
1546
1547 slash = strrchr (path, '/');
1548 if (slash == NULL)
1549 {
1550 /* File is in the current directory. */
1551 path = ".";
1552 length = 1;
1553 }
1554 else
1555 {
1556 /* Remove any trailing slashes from the result. */
1557 while (slash > path && *slash == '/')
1558 --slash;
1559
1560 length = slash - path + 1;
1561 }
1562 newpath = (char *) SH_ALLOC (length + 1);
1563 if (newpath == NULL)
1564 return NULL;
1565 strncpy (newpath, path, length);
1566 newpath[length] = '\0';
1567 return newpath;
1568}
1569/* #ifdef FSTYPE_STATFS */
1570#endif
1571
1572/* fstype.c -- determine type of filesystems that files are on
1573 Copyright (C) 1990, 91, 92, 93, 94 Free Software Foundation, Inc.
1574
1575 This program is free software; you can redistribute it and/or modify
1576 it under the terms of the GNU General Public License as published by
1577 the Free Software Foundation; either version 2, or (at your option)
1578 any later version.
1579
1580 This program is distributed in the hope that it will be useful,
1581 but WITHOUT ANY WARRANTY; without even the implied warranty of
1582 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1583 GNU General Public License for more details.
1584
1585 You should have received a copy of the GNU General Public License
1586 along with this program; if not, write to the Free Software
1587 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
1588
1589/* Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
1590
1591/* Modified by R. Wichmann:
1592 - replaced error() by sh_error_handle()
1593 - replaced xstrdup() by sl_strdup()
1594 - replaced strstr() by sl_strstr()
1595 - some additions to recognize nosuid fs
1596*/
1597
1598/* modetype.h -- file type bits definitions for POSIX systems
1599 Requires sys/types.h sys/stat.h.
1600 Copyright (C) 1990 Free Software Foundation, Inc.
1601
1602 This program is free software; you can redistribute it and/or modify
1603 it under the terms of the GNU General Public License as published by
1604 the Free Software Foundation; either version 2, or (at your option)
1605 any later version.
1606
1607 This program is distributed in the hope that it will be useful,
1608 but WITHOUT ANY WARRANTY; without even the implied warranty of
1609 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1610 GNU General Public License for more details.
1611
1612 You should have received a copy of the GNU General Public License
1613 along with this program; if not, write to the Free Software
1614 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
1615
1616/* POSIX.1 doesn't mention the S_IFMT bits; instead, it uses S_IStype
1617 test macros. To make storing file types more convenient, define
1618 them; the values don't need to correspond to what the kernel uses,
1619 because of the way we use them. */
1620#ifndef S_IFMT /* Doesn't have traditional Unix macros. */
1621#define S_IFBLK 1
1622#define S_IFCHR 2
1623#define S_IFDIR 4
1624#define S_IFREG 8
1625#ifdef S_ISLNK
1626#define S_IFLNK 16
1627#endif
1628#ifdef S_ISFIFO
1629#define S_IFIFO 32
1630#endif
1631#ifdef S_ISSOCK
1632#define S_IFSOCK 64
1633#endif
1634#endif /* !S_IFMT */
1635
1636#ifdef STAT_MACROS_BROKEN
1637#undef S_ISBLK
1638#undef S_ISCHR
1639#undef S_ISDIR
1640#undef S_ISREG
1641#undef S_ISFIFO
1642#undef S_ISLNK
1643#undef S_ISSOCK
1644#undef S_ISMPB
1645#undef S_ISMPC
1646#undef S_ISNWK
1647#endif
1648
1649/* Do the reverse: define the POSIX.1 macros for traditional Unix systems
1650 that don't have them. */
1651#if !defined(S_ISBLK) && defined(S_IFBLK)
1652#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
1653#endif
1654#if !defined(S_ISCHR) && defined(S_IFCHR)
1655#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
1656#endif
1657#if !defined(S_ISDIR) && defined(S_IFDIR)
1658#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
1659#endif
1660#if !defined(S_ISREG) && defined(S_IFREG)
1661#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
1662#endif
1663#if !defined(S_ISFIFO) && defined(S_IFIFO)
1664#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
1665#endif
1666#if !defined(S_ISLNK) && defined(S_IFLNK)
1667#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
1668#endif
1669#if !defined(S_ISSOCK) && defined(S_IFSOCK)
1670#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
1671#endif
1672#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */
1673#define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB)
1674#define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC)
1675#endif
1676#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */
1677#define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK)
1678#endif
1679
1680
1681static char *filesystem_type_uncached (char *path, char *relpath,
1682 struct stat *statp);
1683
1684#ifdef FSTYPE_MNTENT /* 4.3BSD etc. */
1685static int xatoi (char *cp);
1686#endif
1687
1688#ifdef FSTYPE_MNTENT /* 4.3BSD, SunOS, HP-UX, Dynix, Irix. */
1689#include <mntent.h>
1690#if !defined(MOUNTED)
1691# if defined(MNT_MNTTAB) /* HP-UX. */
1692# define MOUNTED MNT_MNTTAB
1693# endif
1694# if defined(MNTTABNAME) /* Dynix. */
1695# define MOUNTED MNTTABNAME
1696# endif
1697#endif
1698#endif
1699
1700#ifdef FSTYPE_GETMNT /* Ultrix. */
1701#include <sys/param.h>
1702#include <sys/mount.h>
1703#include <sys/fs_types.h>
1704#endif
1705
1706#ifdef FSTYPE_USG_STATFS /* SVR3. */
1707#include <sys/statfs.h>
1708#include <sys/fstyp.h>
1709#endif
1710
1711#ifdef FSTYPE_STATVFS /* SVR4. */
1712#include <sys/statvfs.h>
1713#include <sys/fstyp.h>
1714#endif
1715
1716#ifdef FSTYPE_STATFS /* 4.4BSD. */
1717#include <sys/param.h> /* NetBSD needs this. */
1718#include <sys/mount.h>
1719
1720#ifndef MFSNAMELEN /* NetBSD defines this. */
1721static char *
1722fstype_to_string (t)
1723 short t;
1724{
1725#ifdef INITMOUNTNAMES /* Defined in 4.4BSD, not in NET/2. */
1726 static char *mn[] = INITMOUNTNAMES;
1727 if (t >= 0 && t <= MOUNT_MAXTYPE)
1728 return mn[t];
1729 else
1730 return "?";
1731#else /* !INITMOUNTNAMES */
1732 switch (t)
1733 {
1734 case MOUNT_UFS:
1735 return _("ufs");
1736 case MOUNT_NFS:
1737 return _("nfs");
1738#ifdef MOUNT_PC
1739 case MOUNT_PC:
1740 return _("pc");
1741#endif
1742#ifdef MOUNT_MFS
1743 case MOUNT_MFS:
1744 return _("mfs");
1745#endif
1746#ifdef MOUNT_LO
1747 case MOUNT_LO:
1748 return _("lofs");
1749#endif
1750#ifdef MOUNT_TFS
1751 case MOUNT_TFS:
1752 return _("tfs");
1753#endif
1754#ifdef MOUNT_TMP
1755 case MOUNT_TMP:
1756 return _("tmp");
1757#endif
1758#ifdef MOUNT_MSDOS
1759 case MOUNT_MSDOS:
1760 return _("msdos");
1761#endif
1762#ifdef MOUNT_ISO9660
1763 case MOUNT_ISO9660:
1764 return _("iso9660fs");
1765#endif
1766 default:
1767 return "?";
1768 }
1769#endif /* !INITMOUNTNAMES */
1770}
1771#endif /* !MFSNAMELEN */
1772#endif /* FSTYPE_STATFS */
1773
1774#ifdef FSTYPE_AIX_STATFS /* AIX. */
1775#include <sys/vmount.h>
1776#include <sys/statfs.h>
1777
1778#define FSTYPE_STATFS /* Otherwise like 4.4BSD. */
1779#define f_type f_vfstype
1780
1781static char *
1782fstype_to_string (t)
1783 short t;
1784{
1785 switch (t)
1786 {
1787 case MNT_AIX:
1788 return _("aix"); /* AIX 4.3: NFS filesystems are actually MNT_AIX. */
1789#ifdef MNT_NAMEFS
1790 case MNT_NAMEFS:
1791 return _("namefs");
1792#endif
1793 case MNT_NFS:
1794 return _("nfs");
1795 case MNT_JFS:
1796 return _("jfs");
1797 case MNT_CDROM:
1798 return _("cdrom");
1799#ifdef MNT_PROCFS
1800 case MNT_PROCFS:
1801 return _("procfs");
1802#endif
1803#ifdef MNT_SFS
1804 case MNT_SFS:
1805 return _("sfs");
1806#endif
1807#ifdef MNT_CACHEFS
1808 case MNT_CACHEFS:
1809 return _("cachefs");
1810#endif
1811#ifdef MNT_NFS3
1812 case MNT_NFS3:
1813 return _("nfs3");
1814#endif
1815#ifdef MNT_AUTOFS
1816 case MNT_AUTOFS:
1817 return _("autofs");
1818#endif
1819#ifdef MNT_VXFS
1820 case MNT_VXFS:
1821 return _("vxfs");
1822#endif
1823#ifdef MNT_VXODM
1824 case MNT_VXODM:
1825 return _("veritasfs");
1826#endif
1827#ifdef MNT_UDF
1828 case MNT_UDF:
1829 return _("udfs");
1830#endif
1831#ifdef MNT_NFS4
1832 case MNT_NFS4:
1833 return _("nfs4");
1834#endif
1835#ifdef MNT_RFS4
1836 case MNT_RFS4:
1837 return _("nfs4");
1838#endif
1839#ifdef MNT_CIFS
1840 case MNT_CIFS:
1841 return _("cifs");
1842#endif
1843 default:
1844 return "?";
1845 }
1846}
1847#endif /* FSTYPE_AIX_STATFS */
1848
1849#ifdef AFS
1850#include <netinet/in.h>
1851#include <afs/venus.h>
1852#if __STDC__
1853/* On SunOS 4, afs/vice.h defines this to rely on a pre-ANSI cpp. */
1854#undef _VICEIOCTL
1855#define _VICEIOCTL(id) ((unsigned int ) _IOW('V', id, struct ViceIoctl))
1856#endif
1857#ifndef _IOW
1858/* AFS on Solaris 2.3 doesn't get this definition. */
1859#include <sys/ioccom.h>
1860#endif
1861
1862static int
1863in_afs (path)
1864 char *path;
1865{
1866 static char space[2048];
1867 struct ViceIoctl vi;
1868
1869 vi.in_size = 0;
1870 vi.out_size = sizeof (space);
1871 vi.out = space;
1872
1873 if (pioctl (path, VIOC_FILE_CELL_NAME, &vi, 1)
1874 && (errno == EINVAL || errno == ENOENT))
1875 return 0;
1876 return 1;
1877}
1878#endif /* AFS */
1879
1880/* Nonzero if the current filesystem's type is known. */
1881static int fstype_known = 0;
1882
1883/* Return a static string naming the type of filesystem that the file PATH,
1884 described by STATP, is on.
1885 RELPATH is the file name relative to the current directory.
1886 Return "unknown" if its filesystem type is unknown. */
1887
1888static char *
1889filesystem_type (char * path, char * relpath, struct stat * statp)
1890{
1891 static char *current_fstype = NULL;
1892 static dev_t current_dev;
1893
1894 if (current_fstype != NULL)
1895 {
1896 if ((0 != fstype_known) && statp->st_dev == current_dev)
1897 return current_fstype; /* Cached value. */
1898 SH_FREE (current_fstype);
1899 }
1900 current_dev = statp->st_dev;
1901 current_fstype = filesystem_type_uncached (path, relpath, statp);
1902 return current_fstype;
1903}
1904
1905/* Return a newly allocated string naming the type of filesystem that the
1906 file PATH, described by STATP, is on.
1907 RELPATH is the file name relative to the current directory.
1908 Return "unknown" if its filesystem type is unknown. */
1909
1910static char *
1911filesystem_type_uncached (path, relpath, statp)
1912 char *path;
1913 char *relpath;
1914 struct stat *statp;
1915{
1916 char * type = NULL;
1917#ifdef MFSNAMELEN /* NetBSD. */
1918 static char my_tmp_type[64];
1919#endif
1920
1921#ifdef FSTYPE_MNTENT /* 4.3BSD, SunOS, HP-UX, Dynix, Irix. */
1922 char *table = MOUNTED;
1923 FILE *mfp;
1924 struct mntent *mnt;
1925
1926 if (path == NULL || relpath == NULL)
1927 return NULL;
1928
1929 mfp = setmntent (table, "r");
1930 if (mfp == NULL)
1931 {
1932 sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
1933 _("setmntent() failed"),
1934 _("filesystem_type_uncached") );
1935 return NULL;
1936 }
1937
1938 /* Find the entry with the same device number as STATP, and return
1939 that entry's fstype. */
1940 while (type == NULL && (mnt = getmntent (mfp)) != NULL)
1941 {
1942 char *devopt;
1943 dev_t dev;
1944 struct stat disk_stats;
1945
1946#ifdef MNTTYPE_IGNORE
1947 if (0 == strcmp (mnt->mnt_type, MNTTYPE_IGNORE))
1948 continue;
1949#endif
1950
1951 /* Newer systems like SunOS 4.1 keep the dev number in the mtab,
1952 in the options string. For older systems, we need to stat the
1953 directory that the filesystem is mounted on to get it.
1954
1955 Unfortunately, the HPUX 9.x mnttab entries created by automountq
1956 contain a dev= option but the option value does not match the
1957 st_dev value of the file (maybe the lower 16 bits match?). */
1958
1959#if !defined(hpux) && !defined(__hpux__)
1960 devopt = sl_strstr (mnt->mnt_opts, "dev=");
1961 if (devopt)
1962 {
1963 if (devopt[4] == '0' && (devopt[5] == 'x' || devopt[5] == 'X'))
1964 dev = (dev_t) xatoi (devopt + 6);
1965 else
1966 dev = (dev_t) xatoi (devopt + 4);
1967 }
1968 else
1969#endif /* not hpux */
1970 {
1971 if (stat (mnt->mnt_dir, &disk_stats) == -1)
1972 {
1973 sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
1974 _("stat() failed"),
1975 _("filesystem_type_uncached") );
1976 return NULL;
1977 }
1978 dev = disk_stats.st_dev;
1979 }
1980
1981 if (dev == statp->st_dev)
1982 {
1983 /* check for the "nosuid" option
1984 */
1985#ifdef HAVE_HASMNTOPT
1986 if (NULL == hasmntopt(mnt, "nosuid") || (ShSuidchkNosuid == S_TRUE))
1987 type = mnt->mnt_type;
1988 else
1989 type = _("nosuid"); /* hasmntopt (nosuid) */
1990#else
1991 type = mnt->mnt_type;
1992#endif
1993 }
1994 }
1995
1996 if (endmntent (mfp) == 0)
1997 {
1998 sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
1999 _("endmntent() failed"),
2000 _("filesystem_type_uncached") );
2001 }
2002#endif
2003
2004#ifdef FSTYPE_GETMNT /* Ultrix. */
2005 int offset = 0;
2006 struct fs_data fsd;
2007
2008 if (path == NULL || relpath == NULL)
2009 return NULL;
2010
2011 while (type == NULL
2012 && getmnt (&offset, &fsd, sizeof (fsd), NOSTAT_MANY, 0) > 0)
2013 {
2014 if (fsd.fd_req.dev == statp->st_dev)
2015 type = gt_names[fsd.fd_req.fstype];
2016 }
2017#endif
2018
2019#ifdef FSTYPE_USG_STATFS /* SVR3. */
2020 struct statfs fss;
2021 char typebuf[FSTYPSZ];
2022
2023 if (path == NULL || relpath == NULL)
2024 return NULL;
2025
2026 if (statfs (relpath, &fss, sizeof (struct statfs), 0) == -1)
2027 {
2028 /* Don't die if a file was just removed. */
2029 if (errno != ENOENT)
2030 {
2031 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
2032 _("statfs() failed"),
2033 _("filesystem_type_uncached") );
2034 return NULL;
2035 }
2036 }
2037 else if (!sysfs (GETFSTYP, fss.f_fstyp, typebuf))
2038 type = typebuf;
2039#endif
2040
2041#ifdef FSTYPE_STATVFS /* SVR4. */
2042 struct statvfs fss;
2043
2044 if (path == NULL || relpath == NULL)
2045 return NULL;
2046
2047 if (statvfs (relpath, &fss) == -1)
2048 {
2049 /* Don't die if a file was just removed. */
2050 if (errno != ENOENT)
2051 {
2052 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
2053 _("statvfs() failed"),
2054 _("filesystem_type_uncached") );
2055 return NULL;
2056 }
2057 }
2058 else
2059 {
2060 type = fss.f_basetype;
2061
2062 /* patch by Konstantin Khrooschev <nathoo@co.ru>
2063 */
2064 if( (fss.f_flag & ST_NOSUID) && (ShSuidchkNosuid == S_FALSE))
2065 type = _("nosuid");
2066 }
2067 (void) statp; /* fix compiler warning */
2068#endif
2069
2070#ifdef FSTYPE_STATFS /* 4.4BSD. */
2071 struct statfs fss;
2072 char *p;
2073#if defined(MNT_VISFLAGMASK) && defined(HAVE_STRUCT_STATFS_F_FLAGS)
2074 int flags;
2075#endif
2076 /* char * sh_dirname(const char *path); */
2077
2078 if (path == NULL || relpath == NULL)
2079 return NULL;
2080
2081 if (S_ISLNK (statp->st_mode))
2082 p = sh_dirname (relpath);
2083 else
2084 p = relpath;
2085
2086 if (statfs (p, &fss) == -1)
2087 {
2088 /* Don't die if symlink to nonexisting file, or a file that was
2089 just removed. */
2090 if (errno != ENOENT)
2091 {
2092 sh_error_handle ((-1), FIL__, __LINE__, errno, MSG_E_SUBGEN,
2093 _("statfs() failed"),
2094 _("filesystem_type_uncached") );
2095 return NULL;
2096 }
2097 }
2098 else
2099 {
2100
2101#ifdef MFSNAMELEN /* NetBSD. */
2102 /* MEMORY LEAK !!!
2103 * type = sh_util_strdup (fss.f_fstypename);
2104 */
2105 sl_strlcpy (my_tmp_type, fss.f_fstypename, 64);
2106 type = my_tmp_type;
2107#else
2108 type = fstype_to_string (fss.f_type);
2109#endif
2110
2111#ifdef HAVE_STRUCT_STATFS_F_FLAGS
2112#ifdef MNT_VISFLAGMASK
2113 flags = fss.f_flags & MNT_VISFLAGMASK;
2114 if ((flags & MNT_NOSUID) && (ShSuidchkNosuid == S_FALSE))
2115#else
2116 if ((fss.f_flags & MNT_NOSUID) && (ShSuidchkNosuid == S_FALSE))
2117#endif
2118 type = _("nosuid");
2119#endif
2120 }
2121 if (p != relpath)
2122 SH_FREE (p);
2123#endif
2124
2125#ifdef AFS
2126 if ((!type || !strcmp (type, "xx")) && in_afs (relpath))
2127 type = "afs";
2128#endif
2129
2130 /* An unknown value can be caused by an ENOENT error condition.
2131 Don't cache those values. */
2132 fstype_known = (int)(type != NULL);
2133
2134 return sh_util_strdup (type ? type : "unknown");
2135}
2136
2137#ifdef FSTYPE_MNTENT /* 4.3BSD etc. */
2138/* Return the value of the hexadecimal number represented by CP.
2139 No prefix (like '0x') or suffix (like 'h') is expected to be
2140 part of CP. */
2141
2142static int
2143xatoi (cp)
2144 char *cp;
2145{
2146 int val;
2147
2148 val = 0;
2149 while (*cp != '\0')
2150 {
2151 /*@+charint@*/
2152 if (*cp >= 'a' && *cp <= 'f')
2153 val = val * 16 + *cp - 'a' + 10;
2154 else if (*cp >= 'A' && *cp <= 'F')
2155 val = val * 16 + *cp - 'A' + 10;
2156 else if (*cp >= '0' && *cp <= '9')
2157 val = val * 16 + *cp - '0';
2158 else
2159 break;
2160 /*@-charint@*/
2161 cp++;
2162 }
2163 return val;
2164}
2165#endif
2166
2167
2168
2169#endif
2170
2171
2172/* #ifdef SH_USE_UTMP */
2173#endif
2174
2175
2176
Note: See TracBrowser for help on using the repository browser.