source: trunk/src/sh_suidchk.c@ 181

Last change on this file since 181 was 170, checked in by katerina, 16 years ago

Plenty of compiler warnings fixed, SQL query length fixed, doc update.

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