source: trunk/src/sh_suidchk.c@ 148

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

Bugfixes and threaded process check.

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