source: trunk/src/sh_processcheck.c@ 204

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

Handle OpenVZ hidden PIDs when searching for hidden processes within an OpenVZ container.

File size: 34.7 KB
Line 
1/* SAMHAIN file system integrity testing */
2/* Copyright (C) 2006 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/***************************************************************************
21 *
22 * This file provides a module for samhain to check for hidden/faked/missing
23 * processes on the host.
24 *
25 */
26
27#include "config_xor.h"
28
29#define _XOPEN_SOURCE 500
30
31#include <stdio.h>
32#include <stdlib.h>
33#include <ctype.h>
34#include <string.h>
35#include <sys/types.h>
36#include <dirent.h>
37#include <sys/stat.h>
38#include <signal.h>
39#include <unistd.h>
40
41#ifdef _POSIX_PRIORITY_SCHEDULING
42#include <sched.h>
43#endif
44
45#ifdef HAVE_GETPRIORITY
46#include <errno.h>
47#include <sys/resource.h>
48#endif
49
50#ifdef HAVE_SYS_STATVFS_H
51#include <sys/statvfs.h>
52#endif
53
54
55#ifdef HAVE_REGEX_H
56#include <regex.h>
57#endif
58
59#include "samhain.h"
60#include "sh_modules.h"
61#include "sh_processcheck.h"
62#include "sh_utils.h"
63#include "sh_error.h"
64#include "sh_extern.h"
65#include "sh_calls.h"
66#include "sh_pthread.h"
67
68#ifdef SH_USE_PROCESSCHECK
69
70#define FIL__ _("sh_processcheck.c")
71
72#ifdef __linux__
73#define PS_THREADS
74#endif
75
76/* We won't want to build this into yule
77 */
78#if defined (SH_WITH_CLIENT) || defined (SH_STANDALONE)
79
80SH_MUTEX_STATIC(mutex_proc_check, PTHREAD_MUTEX_INITIALIZER);
81
82/* sh_prochk_maxpid is one more than the largest pid
83 */
84static size_t sh_prochk_minpid = 0x0001;
85static size_t sh_prochk_maxpid = 0x8000;
86static size_t sh_prochk_size = 0;
87
88static int ShProchkActive = S_TRUE;
89static short * sh_prochk_res = NULL;
90
91static char * sh_prochk_pspath = NULL;
92static char * sh_prochk_psarg = NULL;
93
94#define SH_PROCHK_INTERVAL 300
95static time_t sh_prochk_interval = SH_PROCHK_INTERVAL;
96static int sh_prochk_severity = SH_ERR_SEVERE;
97static int sh_prochk_openvz = S_FALSE;
98
99static int sh_prochk_set_maxpid (const char * str);
100static int sh_prochk_set_minpid (const char * str);
101static int sh_prochk_set_active (const char *str);
102static int sh_prochk_add_process (const char *str);
103static int sh_prochk_set_pspath (const char *str);
104static int sh_prochk_set_psarg (const char *str);
105static int sh_prochk_set_interval(const char *str);
106static int sh_prochk_set_severity(const char *str);
107static int sh_prochk_set_openvz (const char *str);
108
109sh_rconf sh_prochk_table[] = {
110 {
111 N_("severityprocesscheck"),
112 sh_prochk_set_severity,
113 },
114 {
115 N_("processcheckexists"),
116 sh_prochk_add_process,
117 },
118 {
119 N_("processcheckactive"),
120 sh_prochk_set_active,
121 },
122 {
123 N_("processcheckminpid"),
124 sh_prochk_set_minpid,
125 },
126 {
127 N_("processcheckmaxpid"),
128 sh_prochk_set_maxpid,
129 },
130 {
131 N_("processcheckpspath"),
132 sh_prochk_set_pspath,
133 },
134 {
135 N_("processcheckpsarg"),
136 sh_prochk_set_psarg,
137 },
138 {
139 N_("processcheckinterval"),
140 sh_prochk_set_interval,
141 },
142 {
143 N_("processcheckisopenvz"),
144 sh_prochk_set_openvz,
145 },
146 {
147 NULL,
148 NULL
149 }
150};
151
152#define SH_PROC_MISSING 1
153#define SH_PROC_FAKED 2
154#define SH_PROC_HIDDEN 4
155#define SH_PROC_EXISTS 8
156
157#ifndef HAVE_LSTAT
158#define lstat(x,y) stat(x,y)
159#endif /* HAVE_LSTAT */
160
161#if defined(S_IFLNK) && !defined(S_ISLNK)
162#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK)
163#else
164#if !defined(S_ISLNK)
165#define S_ISLNK(mode) (0)
166#endif
167#endif
168
169static const short SH_PR_PS = 0x0001;
170
171static const short SH_PR_GETSID = 0x0002;
172static const short SH_PR_KILL = 0x0004;
173static const short SH_PR_GETPGID = 0x0008;
174
175static const short SH_PR_LSTAT = 0x0010;
176static const short SH_PR_OPENDIR = 0x0020;
177static const short SH_PR_CHDIR = 0x0040;
178static const short SH_PR_SCHED = 0x0080;
179
180static const short SH_PR_PRIORITY = 0x0100;
181static const short SH_PR_STATVSF = 0x0200;
182
183static const short SH_PR_PS2 = 0x1000;
184static const short SH_PR_PS_ANY = 0x2000;
185static const short SH_PR_ALL = 0x4000;
186static const short SH_PR_ANY = 0x8000;
187
188/* /proc:
189 * linux: /proc/pid/exe
190 * freebsd: /proc/pid/file
191 * solaris10: /proc/pid/path/a.out
192 */
193static char * get_user_and_path (pid_t pid, char * user, size_t usrlen)
194{
195 extern char * sh_unix_getUIDname (int level, uid_t uid, char * out, size_t len);
196
197 char path[128];
198 char * buf;
199 struct stat sbuf;
200 int len;
201 char * tmp;
202
203 sl_snprintf (path, sizeof(path), _("/proc/%ld/exe"), (unsigned long) pid);
204
205 if (0 == retry_lstat(FIL__, __LINE__, path, &sbuf) && S_ISLNK(sbuf.st_mode))
206 {
207 goto linkread;
208 }
209
210 sl_snprintf (path, sizeof(path), _("/proc/%ld/file"), (unsigned long) pid);
211
212 if (0 == retry_lstat(FIL__, __LINE__, path, &sbuf) && S_ISLNK(sbuf.st_mode))
213 {
214 goto linkread;
215 }
216
217 sl_snprintf (path, sizeof(path), _("/proc/%ld/path/a.out"), (unsigned long) pid);
218
219 if (0 == retry_lstat(FIL__, __LINE__, path, &sbuf) && S_ISLNK(sbuf.st_mode))
220 {
221 goto linkread;
222 }
223
224 return NULL;
225
226 linkread:
227
228 buf = SH_ALLOC(PATH_MAX);
229 len = readlink(path, buf, PATH_MAX); /* flawfinder: ignore */
230 len = (len >= PATH_MAX) ? (PATH_MAX-1) : len;
231
232 if (len > 0)
233 {
234 buf[len] = '\0';
235 }
236 else
237 {
238 SH_FREE(buf);
239 return NULL;
240 }
241
242 tmp = sh_unix_getUIDname (SH_ERR_ALL, sbuf.st_uid, user, usrlen);
243
244 if (!tmp)
245 sl_snprintf (user, usrlen, "%ld", (unsigned long) sbuf.st_uid);
246
247 return buf;
248}
249
250
251struct watchlist {
252 char * str;
253 unsigned long pid;
254#ifdef HAVE_REGEX_H
255 regex_t preg;
256#endif
257 int seen;
258
259 struct watchlist *next;
260};
261
262static struct watchlist * process_check = NULL;
263
264static struct watchlist * list_missing = NULL;
265static struct watchlist * list_fake = NULL;
266static struct watchlist * list_hidden = NULL;
267
268/* recursively remove all list entries
269 */
270static void kill_list (struct watchlist * head)
271{
272 if (head->next)
273 kill_list (head->next);
274
275 if (head->str)
276 SH_FREE(head->str);
277 SH_FREE(head);
278
279 return;
280}
281
282
283/* check the list for old entries; clean out old entries; reset others
284 * Return number of non-obsolete entries
285 */
286static size_t clean_list (struct watchlist ** head_ptr)
287{
288 size_t count = 0;
289 struct watchlist * ptr = *head_ptr;
290 struct watchlist * pre = *head_ptr;
291
292 while (ptr)
293 {
294 if (ptr->seen == S_FALSE) /* obsolete entry */
295 {
296 if (ptr == pre) /* at head */
297 {
298 ptr = pre->next;
299 *head_ptr = pre->next;
300 if (pre->str)
301 SH_FREE(pre->str);
302 SH_FREE(pre);
303 pre = ptr;
304 }
305 else
306 {
307 pre->next = ptr->next;
308 if (ptr->str)
309 SH_FREE(ptr->str);
310 SH_FREE(ptr);
311 ptr = pre->next;
312 }
313 }
314 else
315 {
316 ++count;
317 ptr->seen = S_FALSE; /* reset status */
318 pre = ptr;
319 ptr = ptr->next;
320 }
321 }
322 return count;
323}
324
325/* check if process is in list; if not, add it and return false
326 */
327static int is_in_list (struct watchlist ** head_ptr,
328 char * str, unsigned long pid)
329{
330 struct watchlist * ptr = *head_ptr;
331
332 if (str)
333 {
334 while (ptr)
335 {
336 if (ptr->str && (0 == strcmp(str, ptr->str)))
337 {
338 ptr->seen = S_TRUE;
339 return S_TRUE;
340 }
341 ptr = ptr->next;
342 }
343 }
344 else
345 {
346 while (ptr)
347 {
348 if (ptr->pid == pid)
349 {
350 ptr->seen = S_TRUE;
351 return S_TRUE;
352 }
353 ptr = ptr->next;
354 }
355 }
356
357 ptr = SH_ALLOC(sizeof(struct watchlist));
358
359 if (str)
360 {
361 ptr->str = sh_util_strdup(str);
362 }
363 else
364 {
365 ptr->str = NULL;
366 ptr->pid = pid;
367 }
368 ptr->next = *head_ptr;
369 ptr->seen = S_TRUE;
370 *head_ptr = ptr;
371
372 return S_FALSE;
373}
374
375static int is_in_watchlist (const char *str, unsigned long num)
376{
377 struct watchlist * list = process_check;
378
379 while (list)
380 {
381#ifdef HAVE_REGEX_H
382 if (0 == regexec(&(list->preg), str, 0, NULL, 0))
383 {
384 list->seen = S_TRUE;
385 list->pid = num;
386 return S_TRUE;
387 }
388#else
389 if (strstr(str, list->str))
390 {
391 list->seen = S_TRUE;
392 list->pid = num;
393 return S_TRUE;
394 }
395#endif
396 list = list->next;
397 }
398 return S_FALSE;
399}
400
401/* These variables are not used anywhere. They only exist
402 * to assign &userold, &user to them, which keeps gcc from
403 * putting them into a register, and avoids the 'clobbered
404 * by longjmp' warning. And no, 'volatile' proved insufficient.
405 */
406static void * sh_dummy_watchlist = NULL;
407
408static void check_watchlist (short * res)
409{
410 struct watchlist * list = process_check;
411 char * tmp;
412 size_t indx;
413
414 /* Take the address to keep gcc from putting them into registers.
415 * Avoids the 'clobbered by longjmp' warning.
416 */
417 sh_dummy_watchlist = (void*) &list;
418
419 while (list)
420 {
421 if (list->seen == S_FALSE)
422 {
423 /* avoid repetition of messages
424 */
425 if (S_FALSE == is_in_list(&list_missing, list->str, 0))
426 {
427 SH_MUTEX_LOCK(mutex_thread_nolog);
428 tmp = sh_util_safe_name (list->str);
429 sh_error_handle(sh_prochk_severity, FIL__, __LINE__, 0,
430 MSG_PCK_MISS,
431 tmp);
432 SH_MUTEX_UNLOCK(mutex_thread_nolog);
433 }
434 }
435 else
436 {
437 indx = list->pid - sh_prochk_minpid;
438
439 if (list->pid < sh_prochk_maxpid && list->pid >= sh_prochk_minpid &&
440 ((res[indx] & SH_PR_ANY) == 0) && /* not found */
441 ((res[indx] & SH_PR_PS) != 0) && /* seen in first ps */
442 ((res[indx] & SH_PR_PS2) != 0)) /* seen in second ps */
443 {
444 /* fake process, thus considered missing
445 */
446 if (S_FALSE == is_in_list(&list_missing, list->str, 0))
447 {
448 SH_MUTEX_LOCK(mutex_thread_nolog);
449 tmp = sh_util_safe_name (list->str);
450 sh_error_handle(sh_prochk_severity, FIL__, __LINE__, 0,
451 MSG_PCK_MISS,
452 tmp);
453 SH_MUTEX_UNLOCK(mutex_thread_nolog);
454 SH_FREE(tmp);
455 }
456 }
457 list->seen = S_FALSE;
458 }
459 list = list->next;
460 }
461}
462
463/* Add 'str' to the list of watched processes for which
464 * existence should be checked.
465 */
466int sh_prochk_add_process (const char *str)
467{
468 struct watchlist *new;
469 int status;
470 char errbuf[256];
471
472 SL_ENTER(_("sh_prochk_add_process"));
473
474 if( str == NULL )
475 SL_RETURN(-1, _("sh_prochk_add_process") );
476
477 new = SH_ALLOC(sizeof(struct watchlist));
478 new->next = process_check;
479 new->str = sh_util_strdup(str);
480#ifdef HAVE_REGEX_H
481 status = regcomp(&(new->preg), str, REG_NOSUB|REG_EXTENDED);
482 if (status != 0)
483 {
484 regerror(status, &(new->preg), errbuf, sizeof(errbuf));
485 SH_MUTEX_LOCK(mutex_thread_nolog);
486 sh_error_handle((-1), FIL__, __LINE__, status, MSG_E_SUBGEN,
487 errbuf, _("sh_processes_add_process"));
488 SH_MUTEX_UNLOCK(mutex_thread_nolog);
489 SH_FREE(new->str);
490 SH_FREE(new);
491 SL_RETURN(-1, _("sh_prochk_add_process") );
492 }
493#endif
494 new->pid = 0;
495 new->seen = S_FALSE;
496
497 process_check = new;
498 SL_RETURN(0, _("sh_prochk_add_process") );
499}
500
501/* severity
502 */
503int sh_prochk_set_severity (const char * c)
504{
505 char tmp[32];
506 tmp[0] = '='; tmp[1] = '\0';
507 sl_strlcat (tmp, c, 32);
508 return sh_error_set_level (tmp, &sh_prochk_severity);
509}
510
511
512
513/* Path to ps
514 */
515int sh_prochk_set_pspath(const char *str)
516{
517 SL_ENTER(_("sh_prochk_set_pspath"));
518
519 if (!str && ('/' != str[0]))
520 SL_RETURN((-1), _("sh_prochk_set_pspath"));
521 if (sh_prochk_pspath)
522 SH_FREE(sh_prochk_pspath);
523 sh_prochk_pspath = sh_util_strdup (str);
524
525 SL_RETURN((0), _("sh_prochk_set_pspath"));
526
527}
528
529/* argument for ps
530 */
531int sh_prochk_set_psarg(const char *str)
532{
533 SL_ENTER(_("sh_prochk_set_psarg"));
534
535 if (sh_prochk_psarg)
536 SH_FREE(sh_prochk_psarg);
537 sh_prochk_psarg = sh_util_strdup (str);
538
539 SL_RETURN((0), _("sh_prochk_set_psarg"));
540
541}
542
543
544/* Decide if we're active.
545 */
546int sh_prochk_set_active(const char *str)
547{
548 int value;
549
550 SL_ENTER(_("sh_prochk_set_active"));
551
552 value = sh_util_flagval(str, &ShProchkActive);
553
554 SL_RETURN((value), _("sh_prochk_set_active"));
555}
556
557/* Are we on openvz.
558 */
559static int openvz_hidden = 0;
560
561int sh_prochk_set_openvz(const char *str)
562{
563 int value;
564
565 SL_ENTER(_("sh_prochk_set_openvz"));
566
567 value = sh_util_flagval(str, &sh_prochk_openvz);
568
569 if (sh_prochk_openvz != S_FALSE) {
570 openvz_hidden = 1;
571 }
572
573 SL_RETURN((value), _("sh_prochk_set_openvz"));
574}
575
576/* Minimum PID
577 */
578int sh_prochk_set_minpid(const char * str)
579{
580 size_t value;
581 char * foo;
582 int retval = 0;
583
584 SL_ENTER(_("sh_prochk_set_minpid"));
585
586 value = (size_t) strtoul(str, &foo, 0);
587 if (*foo != '\0')
588 retval = -1;
589 else
590 sh_prochk_minpid = value;
591
592 SL_RETURN((retval), _("sh_prochk_set_minpid"));
593}
594
595/* Maximum PID
596 */
597static int userdef_maxpid = 0;
598
599int sh_prochk_set_maxpid(const char * str)
600{
601 size_t value;
602 char * foo;
603 int retval = -1;
604
605 SL_ENTER(_("sh_prochk_set_maxpid"));
606
607 value = (size_t) strtoul(str, &foo, 0);
608
609 if (*foo == '\0' && SL_TRUE == sl_ok_adds(value, 1)) {
610 sh_prochk_maxpid = value + 1;
611 userdef_maxpid = 1;
612 }
613
614 SL_RETURN((retval), _("sh_prochk_set_maxpid"));
615}
616
617int sh_prochk_set_interval (const char * c)
618{
619 int retval = 0;
620 long val;
621
622 SL_ENTER(_("sh_prochk_set_interval"));
623 val = strtol (c, (char **)NULL, 10);
624 if (val <= 0)
625 {
626 SH_MUTEX_LOCK(mutex_thread_nolog);
627 sh_error_handle ((-1), FIL__, __LINE__, EINVAL, MSG_EINVALS,
628 _("process check interval"), c);
629 SH_MUTEX_UNLOCK(mutex_thread_nolog);
630 retval = -1;
631 }
632
633 sh_prochk_interval = (time_t) val;
634 SL_RETURN(0, _("sh_prochk_set_interval"));
635}
636
637
638
639/* Recurse to the end of the list and then free the data as we return
640 * back up towards the start, making sure to free any strdupped strings
641 */
642static void sh_prochk_free_list(struct watchlist *head)
643{
644 if ( head != NULL )
645 {
646 sh_prochk_free_list(head->next);
647 if (head->str)
648 SH_FREE(head->str);
649#ifdef HAVE_REGEX_H
650 regfree(&(head->preg));
651#endif
652 SH_FREE(head);
653 }
654 return;
655}
656
657#if defined(__linux__)
658#define PROC_PID_MAX _("/proc/sys/kernel/pid_max")
659
660static int proc_max_pid (size_t * procpid)
661{
662 char * ret;
663 unsigned long pid;
664 FILE * fd;
665 char str[128];
666 char * ptr;
667
668 SL_ENTER(_("proc_max_pid"));
669
670 if (userdef_maxpid != 0)
671 SL_RETURN((-1), _("proc_max_pid"));
672
673 if (0 == access(PROC_PID_MAX, R_OK)) /* flawfinder: ignore */
674 {
675 if (NULL != (fd = fopen(PROC_PID_MAX, "r")))
676 {
677 str[0] = '\0';
678 ret = fgets(str, 128, fd);
679 if (ret && *str != '\0')
680 {
681 pid = strtoul(str, &ptr, 0);
682 if (*ptr == '\0' || *ptr == '\n')
683 {
684 fclose(fd);
685 *procpid = (size_t) pid;
686 SL_RETURN(0, _("proc_max_pid"));
687 }
688 }
689 fclose(fd);
690 }
691 }
692 SL_RETURN((-1), _("proc_max_pid"));
693}
694#else
695static int proc_max_pid(size_t * dummy)
696{
697 (void) dummy;
698 return -1;
699}
700#endif
701
702static void sh_processes_tlist (char * list, size_t len, short res)
703{
704 if (res & SH_PR_PS) sl_strlcat(list, _(" ps(initial)"), len);
705 if (res & SH_PR_CHDIR) sl_strlcat(list, _(" chdir"), len);
706 if (res & SH_PR_OPENDIR) sl_strlcat(list, _(" opendir"), len);
707 if (res & SH_PR_LSTAT) sl_strlcat(list, _(" lstat"), len);
708 if (res & SH_PR_PRIORITY) sl_strlcat(list, _(" getpriority"), len);
709 if (res & SH_PR_SCHED) sl_strlcat(list, _(" sched_getparam"), len);
710 if (res & SH_PR_GETSID) sl_strlcat(list, _(" getsid"), len);
711 if (res & SH_PR_GETPGID) sl_strlcat(list, _(" getpgid"), len);
712 if (res & SH_PR_KILL) sl_strlcat(list, _(" kill"), len);
713 if (res & SH_PR_STATVSF) sl_strlcat(list, _(" statvfs"), len);
714 if (res & SH_PR_PS2) sl_strlcat(list, _(" ps(final)"), len);
715 return;
716}
717
718
719static short sh_processes_check (pid_t pid, short res)
720{
721 int have_checks = 0;
722 int need_checks = 0;
723#ifdef HAVE_PROCFS
724 char path[128];
725 struct stat buf;
726 DIR * dir;
727 int retval;
728#ifdef HAVE_STATVFS
729 struct statvfs vfsbuf;
730#endif
731#endif
732
733#if !defined(sun) && !defined(__sun) && !defined(__sun__)
734#ifdef _POSIX_PRIORITY_SCHEDULING
735 struct sched_param p;
736#endif
737#endif
738
739 if (0 == kill(pid, 0))
740 {
741 res |= SH_PR_KILL; res |= SH_PR_ANY; ++have_checks;
742 ++need_checks;
743 }
744 else if (errno != EPERM)
745 {
746 ++need_checks;
747 }
748
749
750#ifdef HAVE_GETPGID
751 if ((pid_t)-1 != getpgid(pid))
752 {
753 res |= SH_PR_GETPGID; res |= SH_PR_ANY; ++have_checks;
754 }
755 ++need_checks;
756#endif
757
758#ifdef HAVE_GETSID
759 if ((pid_t)-1 != getsid(pid))
760 {
761 res |= SH_PR_GETSID; res |= SH_PR_ANY; ++have_checks;
762 }
763 ++need_checks;
764#endif
765
766 /* sched_getparam() is broken on solaris 10, may segfault in librt
767 */
768#if !defined(sun) && !defined(__sun) && !defined(__sun__)
769#ifdef _POSIX_PRIORITY_SCHEDULING
770 if (0 == sched_getparam (pid, &p))
771 {
772 res |= SH_PR_SCHED; res |= SH_PR_ANY; ++have_checks;
773 }
774 ++need_checks;
775#endif
776#endif
777
778#ifdef HAVE_GETPRIORITY
779 errno = 0;
780 if (((-1) == getpriority (PRIO_PROCESS, (int) pid)) && (errno == ESRCH));
781 else
782 {
783 res |= SH_PR_PRIORITY; res |= SH_PR_ANY; ++have_checks;
784 }
785 ++need_checks;
786#endif
787
788#ifdef HAVE_PROCFS
789 sl_snprintf (path, sizeof(path), "/proc/%ld", (unsigned long) pid);
790
791 do {
792 retval = lstat (path, &buf);
793 } while (retval < 0 && errno == EINTR);
794
795 if (0 == retval)
796 {
797 res |= SH_PR_LSTAT; res |= SH_PR_ANY; ++have_checks;
798 }
799 ++need_checks;
800
801 if (NULL != (dir = opendir(path)))
802 {
803 res |= SH_PR_OPENDIR; res |= SH_PR_ANY; ++have_checks;
804 closedir(dir);
805 }
806 ++need_checks;
807
808#ifdef HAVE_STATVFS
809 do {
810 retval = statvfs (path, &vfsbuf);
811 } while (retval < 0 && errno == EINTR);
812
813 if (0 == retval)
814 {
815 res |= SH_PR_STATVSF; res |= SH_PR_ANY; ++have_checks;
816 }
817 ++need_checks;
818#endif
819
820#if !defined(SH_PROFILE)
821 if (0 == chdir(path))
822 {
823 res |= SH_PR_CHDIR; res |= SH_PR_ANY; ++have_checks;
824 do {
825 retval = chdir ("/");
826 } while (retval < 0 && errno == EINTR);
827 }
828 ++need_checks;
829#endif
830#endif
831
832 if (have_checks == need_checks)
833 {
834 res |= SH_PR_ALL;
835 }
836 return res;
837}
838
839extern int flag_err_debug;
840
841static int sh_processes_readps (FILE * in, short * res,
842 char * str, size_t len,
843 short flag, pid_t pid)
844{
845 int cc;
846 unsigned int lnum = 0;
847 unsigned long num = 0;
848 char c;
849 unsigned int pos = 0;
850 char tstr[256];
851 enum { SKIP_TO_WS, SKIP_WS, SKIP_TO_WS2, SKIP_WS2, GET_NUM, SKIP_END, GET_NUM2 } line;
852
853 SL_ENTER(_("sh_processes_readps"));
854
855 if (!in) {
856 SL_RETURN((-1), _("sh_processes_readps"));
857 }
858
859 tstr[(sizeof(tstr)-1)] = '\0';
860 tstr[0] = '\0';
861 line = SKIP_END; /* Skip 1st line */
862
863 do
864 {
865 cc = fgetc(in);
866
867 if (EOF == cc)
868 {
869 if (feof(in))
870 {
871 break;
872 }
873 else if (errno == EAGAIN)
874 {
875 clearerr(in);
876 continue;
877 }
878#ifdef HOST_IS_OPENBSD
879 else if (errno == ENODEV)
880 {
881 clearerr(in);
882 continue;
883 }
884#endif
885 else
886 {
887 char errbuf[SH_ERRBUF_SIZE];
888
889 /* SH_MUTEX_LOCK(mutex_thread_nolog) is in caller */
890 sh_error_handle(SH_ERR_ALL, FIL__, __LINE__, errno, MSG_E_SUBGEN,
891 sh_error_message(errno, errbuf, sizeof(errbuf)),
892 _("sh_processes_readps"));
893 break;
894 }
895 }
896
897 c = (char) cc;
898
899 if (pos < (sizeof(tstr)-1))
900 {
901 tstr[pos] = c; ++pos;
902 }
903
904 switch(line)
905 {
906 case SKIP_END:
907 if (c == '\n')
908 {
909 tstr[pos-1] = '\0';
910 if (flag_err_debug == SL_TRUE)
911 {
912 /* SH_MUTEX_LOCK(mutex_thread_nolog) is in caller */
913 sh_error_handle(SH_ERR_ALL, FIL__, __LINE__, num,
914 MSG_E_SUBGEN,
915 tstr,
916 _("sh_processes_readps"));
917 }
918 /* fprintf(stderr, "<%ld> %s\n", num, tstr); */
919 line = SKIP_WS; pos = 0;
920 if (str != NULL && num == (unsigned long) pid)
921 sl_strlcpy(str, tstr, len);
922 if (lnum != 0)
923 is_in_watchlist (tstr, num);
924 ++lnum;
925 }
926 break;
927 case SKIP_TO_WS:
928 if (!isspace(cc))
929 break;
930 line = SKIP_WS;
931 /* fallthrough */
932 case SKIP_WS:
933 if (isspace(cc))
934 break;
935 num = 0;
936 line = GET_NUM;
937 /* fallthrough */
938 case GET_NUM:
939 if (isdigit(cc))
940 {
941 num = num * 10 + (c - '0');
942 break;
943 }
944 else if (isspace(cc))
945 {
946#ifdef PS_THREADS
947 num = 0;
948 line = SKIP_WS2;
949#else
950 if (num < sh_prochk_maxpid && num >= sh_prochk_minpid)
951 {
952 res[num - sh_prochk_minpid] |= flag;
953 }
954 line = SKIP_END;
955#endif
956 break;
957 }
958 else
959 {
960 line = SKIP_TO_WS;
961 break;
962 }
963 case SKIP_TO_WS2:
964 if (!isspace(cc))
965 break;
966 line = SKIP_WS2;
967 /* fallthrough */
968 case SKIP_WS2:
969 if (isspace(cc))
970 break;
971 num = 0;
972 line = GET_NUM2;
973 /* fallthrough */
974 case GET_NUM2:
975 if (isdigit(cc))
976 {
977 num = num * 10 + (c - '0');
978 break;
979 }
980 else if (isspace(cc))
981 {
982 if (num < sh_prochk_maxpid && num >= sh_prochk_minpid)
983 {
984 res[num - sh_prochk_minpid] |= flag;
985 }
986 line = SKIP_END;
987 break;
988 }
989 else
990 {
991 line = SKIP_TO_WS2;
992 break;
993 }
994 default:
995 SL_RETURN ((-1), _("sh_processes_readps"));
996 }
997 } while (1);
998
999 if (ferror(in))
1000 {
1001 SL_RETURN ((-1), _("sh_processes_readps"));
1002 }
1003
1004 SL_RETURN ((0), _("sh_processes_readps"));
1005}
1006
1007static int sh_processes_runps (short * res, char * str, size_t len,
1008 short flag, pid_t pid)
1009{
1010 sh_tas_t task;
1011
1012 int status = 0;
1013 char * p;
1014 struct sigaction new_act;
1015 struct sigaction old_act;
1016 int retval = 0;
1017 char dir[SH_PATHBUF];
1018
1019 SL_ENTER(_("sh_processes_runps"));
1020
1021 sh_ext_tas_init(&task);
1022 p = sh_unix_getUIDdir (SH_ERR_ERR, task.run_user_uid, dir, sizeof(dir));
1023 if (p)
1024 {
1025 (void) sh_ext_tas_add_envv (&task, _("HOME"), p);
1026 }
1027 (void) sh_ext_tas_add_envv (&task, _("SHELL"),
1028 _("/bin/sh"));
1029 (void) sh_ext_tas_add_envv (&task, _("PATH"),
1030 _("/sbin:/usr/sbin:/bin:/usr/bin"));
1031 if (sh.timezone != NULL)
1032 {
1033 (void) sh_ext_tas_add_envv(&task, "TZ", sh.timezone);
1034 }
1035
1036 if (!sh_prochk_pspath)
1037 sh_ext_tas_command(&task, PSPATH);
1038 else
1039 sh_ext_tas_command(&task, sh_prochk_pspath);
1040
1041 (void) sh_ext_tas_add_argv(&task, _("ps"));
1042
1043 if (!sh_prochk_psarg)
1044 {
1045#ifdef PS_THREADS
1046 (void) sh_ext_tas_add_argv(&task, _("-eT"));
1047#else
1048 (void) sh_ext_tas_add_argv(&task, PSARG);
1049#endif
1050 }
1051 else
1052 {
1053 (void) sh_ext_tas_add_argv(&task, sh_prochk_psarg);
1054 }
1055
1056 task.rw = 'r';
1057 task.fork_twice = S_FALSE;
1058
1059 status = sh_ext_popen(&task);
1060 if (status != 0)
1061 {
1062 /* SH_MUTEX_LOCK(mutex_thread_nolog) is in caller */
1063 sh_error_handle(SH_ERR_ALL, FIL__, __LINE__, status, MSG_E_SUBGEN,
1064 _("Could not open pipe"), _("sh_processes_runps"));
1065 SL_RETURN ((-1), _("sh_processes_runps"));
1066 }
1067
1068 /* ignore SIGPIPE (instead get EPIPE if connection is closed)
1069 */
1070 new_act.sa_handler = SIG_IGN;
1071 (void) retry_sigaction (FIL__, __LINE__, SIGPIPE, &new_act, &old_act);
1072
1073 /* read from the open pipe
1074 */
1075 if (task.pipe != NULL)
1076 {
1077 retval = sh_processes_readps (task.pipe, res, str, len, flag, pid);
1078 }
1079
1080 /* restore old signal handler
1081 */
1082 (void) retry_sigaction (FIL__, __LINE__, SIGPIPE, &old_act, NULL);
1083
1084 /* close pipe and return exit status
1085 */
1086 (void) sh_ext_pclose(&task);
1087 sh_ext_tas_free (&task);
1088 SL_RETURN ((retval), _("sh_processes_runps"));
1089}
1090
1091/* Check whether there is a visible process
1092 * with PID = i + 1024
1093 */
1094static int openvz_ok(short * res, size_t i)
1095{
1096 if (sh_prochk_openvz == S_FALSE) {
1097 return 0;
1098 }
1099
1100 i += 1024;
1101
1102 if (i >= sh_prochk_size) {
1103 return 0;
1104 }
1105
1106 if ( ((res[i] & SH_PR_PS) || (res[i] & SH_PR_PS2)) && (res[i] & SH_PR_ANY))
1107 {
1108 /* This is a system process corresponding to a 'virtual'
1109 * process that has a PID offset by 1024
1110 */
1111 return 1;
1112 }
1113
1114 if (openvz_hidden)
1115 {
1116 --openvz_hidden;
1117 return 1;
1118 }
1119
1120 return 0;
1121}
1122
1123static int sh_process_check_int (short * res)
1124{
1125 volatile size_t i;
1126 size_t j;
1127 char tests[512];
1128 volatile int retval;
1129
1130 pid_t this_pid;
1131
1132 SL_ENTER(_("sh_process_check_int"));
1133
1134 this_pid = getpid();
1135
1136 if (!res)
1137 {
1138 SH_MUTEX_LOCK(mutex_thread_nolog);
1139 sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
1140 _("Internal error: NULL argument, switching off"),
1141 _("sh_process_check_int"));
1142 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1143 SL_RETURN ((-1), _("sh_process_check_int"));
1144 }
1145
1146 SH_MUTEX_LOCK(mutex_thread_nolog);
1147 retval = sh_processes_runps (res, NULL, 0, SH_PR_PS, 0);
1148 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1149 for (i = sh_prochk_minpid; i != sh_prochk_maxpid; ++i)
1150 {
1151 j = i - sh_prochk_minpid;
1152 res[j] = sh_processes_check ((pid_t) i, res[j]);
1153 }
1154 SH_MUTEX_LOCK(mutex_thread_nolog);
1155 retval += sh_processes_runps (res, NULL, 0, SH_PR_PS2, 0);
1156 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1157
1158 if (retval != 0)
1159 {
1160 SH_MUTEX_LOCK(mutex_thread_nolog);
1161 sh_error_handle((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
1162 _("Failed to run ps, switching off"),
1163 _("sh_process_check_int"));
1164 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1165 SL_RETURN ((-1), _("sh_process_check_int"));
1166 }
1167
1168 /* Evaluate results
1169 */
1170 for (i = sh_prochk_minpid; i != sh_prochk_maxpid; ++i)
1171 {
1172 /* don't check the current process
1173 */
1174 if (i == (size_t) this_pid)
1175 continue;
1176
1177 j = i - sh_prochk_minpid;
1178
1179 if (((res[j] & SH_PR_PS) != 0) || ((res[j] & SH_PR_PS2) != 0))
1180 {
1181 res[j] |= SH_PR_PS_ANY;
1182 }
1183 else
1184 {
1185 res[j] &= ~SH_PR_PS_ANY;
1186 }
1187
1188 tests[0] = '\0';
1189
1190 if ((res[j] & SH_PR_ANY) || (res[j] & SH_PR_PS_ANY))
1191 {
1192 /* list all tests where the pid was found
1193 */
1194 sh_processes_tlist (tests, sizeof(tests), res[j]);
1195
1196 /*
1197 * case 1: in ps and found
1198 */
1199 if ((res[j] & SH_PR_PS_ANY) && (res[j] & SH_PR_ANY))
1200 {
1201 SH_MUTEX_LOCK(mutex_thread_nolog);
1202 sh_error_handle((-1), FIL__, __LINE__, 0, MSG_PCK_OK,
1203 (unsigned long) i, tests);
1204 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1205 }
1206
1207 /*
1208 * case 2: not in ps and found
1209 */
1210 else if ((res[j] & SH_PR_PS_ANY) == 0)
1211 {
1212 res[j] = sh_processes_check ((pid_t) i, 0);
1213 /*
1214 * if still there, it is real and hidden
1215 */
1216 if ((res[j] & SH_PR_ANY) && !openvz_ok(res, j))
1217 {
1218 if (S_FALSE == is_in_list(&list_hidden, NULL, i))
1219 {
1220 char user[16];
1221 char * aout;
1222 char * safe;
1223
1224 SH_MUTEX_LOCK(mutex_thread_nolog);
1225 aout = get_user_and_path ((pid_t) i, user, sizeof(user));
1226 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1227
1228 if (aout)
1229 {
1230 safe = sh_util_safe_name (aout);
1231 SH_MUTEX_LOCK(mutex_thread_nolog);
1232 sh_error_handle(sh_prochk_severity, FIL__, __LINE__, 0,
1233 MSG_PCK_P_HIDDEN,
1234 (unsigned long) i, tests, safe, user);
1235 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1236 SH_FREE(safe);
1237 SH_FREE(aout);
1238 }
1239 else
1240 {
1241 SH_MUTEX_LOCK(mutex_thread_nolog);
1242 sh_error_handle(sh_prochk_severity, FIL__, __LINE__, 0,
1243 MSG_PCK_HIDDEN,
1244 (unsigned long) i, tests);
1245 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1246 }
1247 }
1248 }
1249 }
1250
1251 /*
1252 * case 3: in ps, but not found
1253 */
1254 else
1255 {
1256 if (((res[j] & SH_PR_PS) != 0) && ((res[j] & SH_PR_PS2) != 0))
1257 {
1258 if (S_FALSE == is_in_list(&list_fake, NULL, i))
1259 {
1260 SH_MUTEX_LOCK(mutex_thread_nolog);
1261 sh_error_handle(sh_prochk_severity, FIL__, __LINE__, 0,
1262 MSG_PCK_FAKE,
1263 (unsigned long) i, tests);
1264 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1265 }
1266 }
1267 }
1268 }
1269 } /* loop end */
1270
1271 check_watchlist (res);
1272
1273 SL_RETURN (0, _("sh_process_check_int"));
1274}
1275
1276/* Initialise.
1277 */
1278static int sh_prochk_init_internal(void)
1279{
1280 SL_ENTER(_("sh_prochk_init"));
1281
1282 (void) proc_max_pid (&sh_prochk_maxpid);
1283
1284 if (sh_prochk_minpid > sh_prochk_maxpid)
1285 ShProchkActive = S_FALSE;
1286
1287 /* We need to free anything allocated by the configuration functions if
1288 * we find that the module is to be left inactive - otherwise _reconf()
1289 * won't quite work.
1290 */
1291 if( ShProchkActive == S_FALSE )
1292 {
1293 sh_prochk_free_list(process_check);
1294 process_check = NULL;
1295 SL_RETURN(-1, _("sh_prochk_init"));
1296 }
1297
1298 sh_prochk_size = sh_prochk_maxpid - sh_prochk_minpid;
1299
1300 sh_prochk_res = SH_ALLOC(sizeof(short) * sh_prochk_size);
1301 memset (sh_prochk_res, 0, sizeof(short) * sh_prochk_size);
1302
1303 SL_RETURN(0, _("sh_prochk_init"));
1304}
1305
1306int sh_prochk_init (struct mod_type * arg)
1307{
1308 if (ShProchkActive == S_FALSE)
1309 return SH_MOD_FAILED;
1310#ifdef HAVE_PTHREAD
1311 if (arg != NULL && arg->initval < 0 &&
1312 (sh.flag.isdaemon == S_TRUE || sh.flag.loop == S_TRUE))
1313 {
1314 if (0 == sh_pthread_create(sh_threaded_module_run, (void *)arg))
1315 return SH_MOD_THREAD;
1316 else
1317 return SH_MOD_FAILED;
1318 }
1319#endif
1320 return sh_prochk_init_internal();
1321}
1322
1323int sh_prochk_timer(time_t tcurrent)
1324{
1325 static time_t lastcheck = 0;
1326
1327 SL_ENTER(_("sh_prochk_timer"));
1328 if ((time_t) (tcurrent - lastcheck) >= sh_prochk_interval)
1329 {
1330 lastcheck = tcurrent;
1331 SL_RETURN((-1), _("sh_prochk_timer"));
1332 }
1333 SL_RETURN(0, _("sh_prochk_timer"));
1334}
1335
1336int sh_prochk_check(void)
1337{
1338 int status;
1339
1340 SL_ENTER(_("sh_prochk_check"));
1341
1342 SH_MUTEX_LOCK(mutex_proc_check);
1343
1344 status = 0;
1345
1346 if( ShProchkActive != S_FALSE )
1347 {
1348 SH_MUTEX_LOCK(mutex_thread_nolog);
1349 sh_error_handle((-1), FIL__, __LINE__, 0, MSG_PCK_CHECK,
1350 (unsigned long) sh_prochk_minpid,
1351 (unsigned long) (sh_prochk_maxpid-1));
1352 SH_MUTEX_UNLOCK(mutex_thread_nolog);
1353
1354 if (sh_prochk_res) {
1355 memset (sh_prochk_res, 0, sizeof(short) * sh_prochk_size);
1356 }
1357 status = sh_process_check_int(sh_prochk_res);
1358
1359 if (status != 0)
1360 ShProchkActive = S_FALSE;
1361
1362 /* clean out old entries which are not marked
1363 * as missing/hidden/fake anymore
1364 */
1365 clean_list (&list_missing);
1366 clean_list (&list_hidden);
1367 clean_list (&list_fake);
1368 }
1369
1370 SH_MUTEX_UNLOCK(mutex_proc_check);
1371
1372 SL_RETURN(status, _("sh_prochk_check"));
1373}
1374
1375/* Free our lists and the associated memory
1376 */
1377int sh_prochk_cleanup(void)
1378{
1379 SL_ENTER(_("sh_prochk_cleanup"));
1380
1381 sh_prochk_reconf();
1382
1383 if (list_missing) {
1384 kill_list(list_missing);
1385 list_missing = NULL;
1386 }
1387 if (list_hidden) {
1388 kill_list(list_hidden);
1389 list_hidden = NULL;
1390 }
1391 if (list_fake) {
1392 kill_list(list_fake);
1393 list_fake = NULL;
1394 }
1395
1396 SL_RETURN(0, _("sh_prochk_cleanup"));
1397}
1398
1399/* Free our lists and the associated memory
1400 */
1401int sh_prochk_reconf(void)
1402{
1403 SL_ENTER(_("sh_prochk_reconf"));
1404
1405 SH_MUTEX_LOCK(mutex_proc_check);
1406 userdef_maxpid = 0;
1407 sh_prochk_maxpid = 0x8000;
1408 sh_prochk_minpid = 0x0001;
1409 sh_prochk_interval = SH_PROCHK_INTERVAL;
1410 sh_prochk_openvz = S_FALSE;
1411
1412 sh_prochk_free_list(process_check);
1413 process_check = NULL;
1414 if (sh_prochk_res != NULL)
1415 SH_FREE(sh_prochk_res);
1416 sh_prochk_res = NULL;
1417
1418 if (sh_prochk_psarg)
1419 SH_FREE(sh_prochk_psarg);
1420 sh_prochk_psarg = NULL;
1421 if (sh_prochk_pspath)
1422 SH_FREE(sh_prochk_pspath);
1423 sh_prochk_pspath = NULL;
1424 SH_MUTEX_UNLOCK(mutex_proc_check);
1425
1426 SL_RETURN(0, _("sh_prochk_reconf"));
1427}
1428
1429/* #if defined (SH_WITH_CLIENT) || defined (SH_STANDALONE) */
1430#endif
1431
1432/* #ifdef SH_USE_PROCESSCHECK */
1433#endif
1434
1435
1436#ifdef SH_CUTEST
1437#include "CuTest.h"
1438
1439void Test_processcheck_watchlist_ok (CuTest *tc) {
1440#if defined(SH_USE_PROCESSCHECK) && (defined(SH_WITH_CLIENT) || defined(SH_STANDALONE))
1441 CuAssertTrue(tc, 0 == sh_prochk_add_process("init"));
1442 CuAssertTrue(tc,
1443 S_TRUE == is_in_watchlist(" 1 ? 00:00:00 init", 0));
1444 CuAssertTrue(tc,
1445 S_FALSE == is_in_watchlist(" 1 ? 00:00:00 flix", 0));
1446 CuAssertTrue(tc,
1447 S_TRUE == is_in_watchlist("25218 ? SNs 0:01 /usr/sbin/init -k start -DSSL", 0));
1448 CuAssertTrue(tc,
1449 S_FALSE == is_in_watchlist("25218 ? SNs 0:01 /usr/sbin/apache2 -k start -DSSL", 0));
1450
1451
1452 sh_prochk_free_list(process_check);
1453 process_check = NULL;
1454 CuAssertTrue(tc, S_FALSE == is_in_watchlist("init", 0));
1455
1456 CuAssertTrue(tc, 0 == sh_prochk_add_process("init"));
1457 CuAssertTrue(tc, 0 == sh_prochk_add_process("ssh"));
1458 CuAssertTrue(tc, 0 == sh_prochk_add_process("syslog"));
1459 CuAssertTrue(tc, S_TRUE == is_in_watchlist("init", 0));
1460 CuAssertTrue(tc, S_TRUE == is_in_watchlist("ssh", 0));
1461 CuAssertTrue(tc, S_TRUE == is_in_watchlist("syslog", 0));
1462
1463 sh_prochk_free_list(process_check);
1464 process_check = NULL;
1465 CuAssertTrue(tc, S_FALSE == is_in_watchlist("init", 0));
1466 CuAssertTrue(tc, S_FALSE == is_in_watchlist("ssh", 0));
1467 CuAssertTrue(tc, S_FALSE == is_in_watchlist("syslog", 0));
1468#else
1469 (void) tc; /* fix compiler warning */
1470#endif
1471 return;
1472}
1473
1474void Test_processcheck_listhandle_ok (CuTest *tc) {
1475#if defined(SH_USE_PROCESSCHECK) && (defined(SH_WITH_CLIENT) || defined(SH_STANDALONE))
1476 CuAssertTrue(tc, S_FALSE == is_in_list(&list_missing, "init", 0));
1477 CuAssertTrue(tc, S_TRUE == is_in_list(&list_missing, "init", 0));
1478 CuAssertTrue(tc, S_FALSE == is_in_list(&list_missing, "foobar", 0));
1479 CuAssertTrue(tc, S_TRUE == is_in_list(&list_missing, "foobar", 0));
1480
1481 if (list_missing)
1482 kill_list(list_missing);
1483 list_missing = NULL;
1484
1485 CuAssertTrue(tc, S_FALSE == is_in_list(&list_missing, "init", 0));
1486 CuAssertTrue(tc, S_TRUE == is_in_list(&list_missing, "init", 0));
1487 CuAssertTrue(tc, S_FALSE == is_in_list(&list_missing, "foobar", 0));
1488 CuAssertTrue(tc, S_TRUE == is_in_list(&list_missing, "foobar", 0));
1489
1490 if (list_missing)
1491 kill_list(list_missing);
1492 list_missing = NULL;
1493
1494 CuAssertTrue(tc, S_FALSE == is_in_list(&list_missing, "init", 0));
1495 CuAssertTrue(tc, S_TRUE == is_in_list(&list_missing, "init", 0));
1496 CuAssertTrue(tc, S_FALSE == is_in_list(&list_missing, "foobar", 0));
1497 CuAssertTrue(tc, S_TRUE == is_in_list(&list_missing, "foobar", 0));
1498
1499 CuAssertTrue(tc, 2 == clean_list(&list_missing));
1500 CuAssertPtrNotNull(tc, list_missing);
1501
1502 CuAssertTrue(tc, S_TRUE == is_in_list(&list_missing, "init", 0));
1503 CuAssertTrue(tc, S_TRUE == is_in_list(&list_missing, "foobar", 0));
1504
1505 CuAssertTrue(tc, 2 == clean_list(&list_missing));
1506 CuAssertPtrNotNull(tc, list_missing);
1507
1508 CuAssertTrue(tc, 0 == clean_list(&list_missing));
1509 CuAssertTrue(tc, NULL == list_missing);
1510#else
1511 (void) tc; /* fix compiler warning */
1512#endif
1513 return;
1514}
1515
1516
1517/* #ifdef SH_CUTEST */
1518#endif
1519
Note: See TracBrowser for help on using the repository browser.