Codebase list stealth / ca540ed
Imported Upstream version 1.45 tony mancill 11 years ago
140 changed file(s) with 6333 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 The following persons have been very helpful in developing and testing
1 STEALTH:
2
3 Hans Gankema <j.a.gankema@rc.rug.nl>,
4 For formulating the initial idea behind `stealth'
5
6 Kees Visser <k.visser@rc.rug.nl>
7 For formulating the initial idea behind `stealth'
8
9 Hopko Meijering <h.meijering@rc.rug.nl>
10 For testing stealth on various computers, and for proofreading
11 the documentation
12
13
14 Thanks!
15
16 Frank.
17
18
0 1.31
1
2 Added options --suppress and --resume to allow logfile rotations
3 --resume implies a --rerun
4 --terminate can be used following --suppress
5
6 Removed ::exit() and atexit() calls, using exceptions instead.
7
8 Added `Monitor', containing all process-control functions previously
9 provided by Scanner itself. Scanner now only performs the scanning
10 functions.
11
12 1.20.2 (Debian) and beyond:
13 ===========================
14
15 See the debian changelog file for all changes as of version 1.20.2
16
17 1.20
18 ====
19
20 Long options added (1.11), `LOG =' may be specified with CHECK commands.
21
22 Starting version 1.20, a Debian package is provided in parallel to the source
23 package. The Debian package contains the full documentation, as well as the
24 binary version of the stealth program.
25
26 1.10
27 ====
28
29 -i <time in seconds or minutes> option added: stealth will start its scan
30 somwhere in the random interval between now and the time specified after -i.
31 By default the time in seconds is expected. If at least an m is appended to
32 the time specificiation, the time specification is interpreted as time in
33 minutes (e.g., -i 5min)
34
35 1.01
36 ====
37
38 DEFINE symbols accept other DEFINE symbols in their definitions. Direct or
39 indirect circular DEFINE definitions should be avoided by the policy's author.
40 The demo.pol policy file, the manual page and the manual was rewritten to
41 reflect this change.
42 The stealthmail script contained a stealth-version reference, which was
43 removed.
44
45 1.00
46 ====
47
48 Official release party (featuring coffee and pastries) at the computing center
49 of the university of Groningen, November 12, 2002. No changes in the software
50 or the docs, just a little celebration. Thanks, Hopko!
51
52 0.90
53 ====
54
55 old reports get 4-digit years
56 DEFINE directives defined in the config file
57 several USE defines were changed
58 Manual added to the documentation
59 Scripts added
60 C++ Classes used by stealth reorganized/redefined.
61
62 0.21: initial release
63 =====================
0 util
1 configsorter
2 reporter
3 scanner
4 monitor
0 See the KICKSTART chapter in the manual:
1
2 doc/manual/index.html, click the kickstart chapter.
3
4
5 ===========================================================================
6 To install stealth by hand instead of using the binary distribution perform
7 the following steps:
8
9 0. The previously used scripts below make/ are obsolete and were removed
10 from this and future distributions. Icmake should be used instead, for
11 which a top-level script (build) and support scripts in the ./icmake/
12 directory are available. Icmake is available on a great many
13 architectures. See the file INSTALL (and INSTALL.im, replacing the
14 previously used INSTALL.cf) for further details.
15
16 1. Inspect the values of the variables in the file INSTALL.im Modify these
17 when necessary.
18
19 2. Make sure the bobcat library has been installed.
20
21 (If you compile the bobcat library yourself, note that stealth does
22 not use the classes Milter and Xpointer; they may --as far as stealth
23 is concerned-- be left out of the library)
24
25 3. Run
26 ./build program
27 to compile stealth
28
29 4. Run (probably as root)
30 ./build install
31 to install. Optionally add an additional argument as a base directory
32 below which the software should be installed.
33
34 Following the installation nothing in this directory tree is required
35 for the proper functioning of stealth, so consider removing it.
36
37
38
0 string BASE;
1
2 // BASE=is the directory below which ALL bisonc++ files will be stored.
3
4 // For an operational non-Debian installation, you probably must be
5 // `root', and BASE "/usr" or BASE "/usr/local" is suggested (see
6 // below). `BASE' itself is not used outside of this file, so feel free to
7 // define BIN, SKEL, MAN and DOC (below) in any which way you like.
8
9 string BIN;
10 // the directory in which bisonc++ will be stored
11
12 string MAN;
13 // MAN is the directory in which the manual page will be stored
14
15 string DOC;
16 // DOC is the directory in which all other documentation will be stored
17
18 string COMPILER;
19 // COMPILER specifies the compiler to use. stealth is coined as
20 // belonging to the Debian `unstable' distribution, which may use a
21 // different version of the compiler you currently have in your
22 // system. E.g., in july 2006 the Debian `testing' version of the compiler
23 // was 4.0.4, but the `unstable' version's compiler was 4.1.2. By defining
24 // COMPILER with a specific version (e.g., COMPILER=g++-4.1) that
25 // particular version can be used. The distributed definition uses the
26 // `default' compiler version.
27
28 void setLocations()
29 {
30 BASE = "/usr";
31
32 BIN = BASE + "/sbin";
33 MAN = BASE + "/share/man/man1";
34 DOC = BASE + "/share/doc/stealth";
35
36 COMPILER = "g++";
37 }
38
0
1 STEALTH
2 SSH-based Trust Enforcement Acquired through a Locally Trusted Host
3
4 Frank B. Brokken
5 f.b.brokken@rug.nl
6
7
8 * The manual is in the usr/share/doc/stealth/html subdirectory.
9 Point your browser to its index.html or stealth.html file to start
10 reading the manual.
11 The manual's sources are in the .../stealth/Yodl directory.
12 These are Yodl sources. Yodl is another Debian package.
13
14 * The man-page stealth.1 is in usr/share/man/man1.
15 The file .../stealth/manpage/stealth.yo is the (Yodl) source file
16 which was used to construct the manpage.
17
18 * The standard way to compile stealth is using the provided `build'
19 script, which is an `icmake' script (see the next item).
20
21 * To compile from scratch, run
22 ./build program
23 from this directory. The file INSTALL.im in this directory contains the
24 definitions of variables defining the locations of the directories in
25 which the binary, the man-page and the manual will be stored. Changing
26 these definitions will install files in other locations.
27
28 * To install `by hand', you should probably be `root'. Running (as root)
29 ./buidl install /
30 installs the files ready for system-wide usage.
31
32 * Remark, comments please to my email addres. The software is GPL, the
33 copyright rests with me. This is to make sure that the most recent
34 version is always with me. All suggestions for improvements
35 will be seriously considered, and if possible, incorporated in the
36 next version of the program.
37
38 * At the same location where you found this archive, you should have found
39 a detached signature file. Use my public key to verify the authenticity
40 of the key. My public key was signed by the CA of the University of
41 Groningen. Here are a PGP key server, and my PGP public key fingerprint:
42
43 Public PGP key: http://pgp.surfnet.nl:11371/
44 Key Fingerprint: 8E36 9FC4 1DAA FCDF 1A0D B19F DAC4 BE50 38C6 6170
45
46 Contact me if you have any doubts about the correctness of the provided
47 signature or the given fingerprint.
48
49 Frank
50
51
52
0 stealth: uses: bobcat
1 IOFork
2 ConfigSorter
3 Scanner
4 Monitor
5
6 Classes:
7 ========
8
9 ConfigSorter: uses: bobcat
10
11 IOFork: uses: bobcat
12
13 Reporter: uses: bobcat
14
15 Scanner: uses: bobcat
16 ConfigSorter
17 IOFork
18 Reporter
19
20 Monitor: uses: bobcat
21 ConfigSorter
22 IOFork
23 Reporter
24 Scanner
25
0 VERSION=1.45
1
2 YEARS=2001-2006
0 #!/usr/bin/icmake -qt/tmp/stealth.ib
1
2 #include "INSTALL.im"
3
4 #include "icmake/run"
5 #include "icmake/md"
6 #include "icmake/getenv"
7 #include "icmake/clean"
8 #include "icmake/special"
9 #include "icmake/manpage"
10 #include "icmake/manual"
11 #include "icmake/program"
12 #include "icmake/install"
13
14 void main(int argc, list argv, list envp)
15 {
16 string option;
17
18 g_env = envp;
19
20 setLocations(); // from INSTALL.im
21
22 getenv("DRYRUN");
23 g_dryrun = g_envvar;
24
25 option = element(1, argv);
26
27 if (option == "clean")
28 clean(0);
29
30 if (option == "distclean")
31 clean(1);
32
33 if (option == "install")
34 install(element(2, argv));
35
36 if (option == "man")
37 manpage();
38
39 if (option == "manual")
40 manual();
41
42 if (option == "program")
43 program();
44
45 printf("Usage: build what\n"
46 "Where `what' is one of:\n"
47 " clean - clean up remnants of previous compilations\n"
48 " distclean - clean + fully remove tmp/\n"
49 " man - build the man-page (requires Yodl)\n"
50 " manual - build the manual (requires Yodl)\n"
51 " program - build stealth\n"
52 " install <base> - to install the software in the locations\n"
53 " defined in the INSTALL.im file, optionally\n"
54 " below <base>\n"
55 "If the environment variable DRYRUN is defined, no commands are\n"
56 "actually executed\n"
57 );
58 exit(1);
59 }
0 stealth (1.45)
1
2 * The previously used scripts below make/ are obsolete and were removed from
3 this and future distributions. Icmake should be used instead, for which a
4 top-level script (build) and support scripts in the ./icmake/ directory
5 are available. Icmake is available on a great many architectures. See the
6 file INSTALL (and INSTALL.im, replacing the previously used INSTALL.cf)
7 for further details.
8
9 All plain `unsigned' variables were changed to `size_t'
10
11 stealth (1.44) unstable; urgency=low
12
13 * License changed to the GNU GENERAL PUBLIC LICENSE. See the file
14 `copyright'.
15
16 Introduced George Danchev <danchev@spnet.net> as uploader
17
18 From now on this file will contain the `upstream' changes. The Debian
19 related changes are in changelog.Debian.gz
20
21 -- Frank B. Brokken <f.b.brokken@rug.nl> Wed, 19 Jul 2006 12:57:17 +0200
22
23 stealth (1.43) unstable; urgency=low
24
25 * Following suggestions made by George Danchev, this version was compiled by
26 the unstable's g++ compiler (version >= 4.1), which unveiled several flaws
27 in the library's class header files. These flaws were removed (i.e.,
28 repaired).
29
30 In order to facilitate compiler selection, the compiler to use is defined
31 in the INSTALL.cf file.
32
33 The debian control-files (i.e., all files under the debian subdirectory)
34 were removed from the source distribution, which is now also named in
35 accordance with the Debian policy. A diff.gz file was added.
36
37 -- Frank B. Brokken <f.b.brokken@rug.nl> Thu, 6 Jul 2006 12:24:58 +0200
38
39 stealth (1.42) unstable; urgency=low
40
41 * When a (remote) CHECK command failed to return 0, Stealth didn't properly
42 terminate. This was repaired by changing the return value of
43 Reporter::relax() to type bool, returning d_continue. This return value is
44 now checked in Monitor::control(). If not true, the Monitor::control()
45 loop terminates, thus terminating the program with exit value 1.
46
47 make/install script now defines PREFIX=/ if called without argument.
48
49 -- Frank B. Brokken <f.b.brokken@rug.nl> Mon, 26 Jun 2006 09:27:34 +0200
50
51 stealth (1.41c) unstable; urgency=low
52
53 * Stealth was `lintianized' and `lindanized'. The info in debian's control
54 file was adapted. As the bobcat libraries are now in libbobcat1* packages,
55 stealth's dependencies were adapted accordingly.
56
57 -- Frank B. Brokken <f.b.broken@rug.nl> Sun, 28 May 2006 12:39:15 +0200
58
59 stealth (1.41b) unstable; urgency=low
60
61 * Recompilation because of changes in the bobcat library.
62 This version of Stealth depends on bobcat 1.7.0.
63 No changes to Stealth itself. The compilation dependency for the
64 g++ compiler has been restored.
65
66 -- Frank B. Brokken <f.b.brokken@rug.nl> Tue, 2 May 2006 21:37:31 +0200
67
68 stealth (1.41a) unstable; urgency=low
69
70 * Minor changes to the make/library script, adapted the program's release
71 years. Dependency check in debian/control for g++ removed since it fails
72 for unkown reasons. The version should be >= 4.0.2
73
74 -- Frank B. Brokken <f.b.brokken@rug.nl> Wed, 1 Feb 2006 12:46:01 +0100
75
76 stealth (1.41) unstable; urgency=low
77
78 * Library requirement up-to-date: bobcat 1.6.0
79
80 -- Frank B. Brokken <f.b.brokken@rug.nl> Mon, 26 Dec 2005 19:17:24 +0100
81
82 stealth (1.40) unstable; urgency=low
83
84 * all local Pattern objects are now static data members
85
86 removed: superfluous stealth.doc-base; debugmacro;
87 classes arg, configfile, errno fdout fork ifdnbuf pattern pipe selector
88 (now in bobcat)
89
90 Renamed all .h2 headers to my standard .ih names:
91 util, configsorter, reporter scanner
92
93 Reporter::reset() now calls Reporter::rewind()
94
95 Reporter uses d_hasMail data member instead of d_sizeBeyondHeader:
96 d_hasMail can simply be set to false following the writing of the header,
97 and then to true at each sync() command.
98
99 added: Reporter::exit(), first inserting the message into the reporter,
100 then to cerr, and exiting.
101
102 fatal error messages are no longer suppressed with -q
103
104 man(ual) pages adapted accordingly.
105
106 -- Frank B. Brokken <f.b.brokken@rug.nl> Mon, 26 Dec 2005 18:23:25 +0100
107
108 stealth (1.35) unstable; urgency=low
109
110 * Recompilation using g++-4.0. Requires bobcat >= 1.4.0
111
112 -- Frank B. Brokken <f.b.brokken@rug.nl> Sat, 19 Nov 2005 16:47:28 +0100
113
114 stealth (1.34) unstable; urgency=low
115
116 * Removed dependencies on `icmake'. See the file `INSTALL' for details about
117 compiling and installing `stealth' from the source package, rather than
118 from the binary (.deb) package.
119 Stealth's functionality has not been altered.
120
121 -- Frank B. Brokken <f.b.brokken@rug.nl> Sun, 4 Sep 2005 15:11:46 +0200
122
123 stealth (1.33) unstable; urgency=low
124
125 * With the advent of the bobcat library (Brokken's Own Base Classes And
126 Templates) various classes were removed from stealth's distribution: Arg,
127 Configfile, Errno, Fork, Hashclasses, Ifdstreambuf, Ofdbuf, Pattern, and
128 Pipe. Also, the manual pages were adapted to reflect the fact that I'm
129 distributing Debian (source and binary) packages, rather than pure source
130 packages. No further change in functionality was implemented. To compile
131 stealth bobcat-dev is required, to run the binary bobcat itself. See
132 http://bobcat.sourceforge.net and http://sourceforge.net/projects/bobcat
133 for further information about bobcat.
134
135 -- Frank B. Brokken <f.b.brokken@rug.nl> Sat, 20 Aug 2005 15:35:32 +0200
136
137 stealth (1.32) unstable; urgency=low
138
139 * Version 1.31 was not distributed. Version 1.32 offers identical user
140 options as V 1.31, but has some minor internal improvements in its code
141 over 1.31. In particular, a running stealth process will signal its
142 suppressor that it's ready. This simplifies the construction of, e.g.,
143 logrotate scripts.
144
145 Note btw that the date and timestamps in this file are CET (+ DST when
146 active)
147
148 -- Frank B. Brokken <f.b.brokken@rug.nl> Mon, 1 Aug 2005 10:49:36 +0200
149
150 stealth (1.31) unstable; urgency=low
151
152 * Added --suspend and --resume options allowing logfile rotations on a
153 keepalive running stealth process. Changed the manual page using standard
154 manpage*() macros instead of SUBST()s
155
156 Internally, a Monitor class was added, exercising and taking over much of
157 the control functionality of the Scanner class
158
159 -- Frank B. Brokken <f.b.brokken@rug.nl> Sat, 30 Jul 2005 00:13:14 +0200
160
161 stealth (1.30-2a) unstable; urgency=low
162
163 * Stupid: forgot to update the program's version itself :-(
164 Now it's 1.30-2a
165
166 -- Frank B. Brokken <f.b.brokken@rug.nl> Tue, 27 Apr 2004 17:56:54 +0200
167
168 stealth (1.30-2) unstable; urgency=low
169
170 * Repaired bug in Scanner::Scanner():
171 The process-id's of the SH and SSH programs were assigned before
172 IOFORK::fork() was executed, so they received undefined values. This was
173 repaired by assigning the d_shPid and d_sshPid assignments to the
174 Scanner::preamble() function.
175
176 Also, the call to killChildren() at the end of stealth's main() function
177 (in stealth.cc) was superfluous, as the atexit() call in preamble already
178 ensures that the childprocesses are called.
179
180 Finally, the Yodl manual files are adapted to Yodl V. >= 2.00. The
181 /usr/local/share/yodl/macros.yo file isn't required anymore, and the
182 XXsloppyhfuzz undefinition was changed into a call of nosloppyhfuzz().
183
184 -- Frank B. Brokken <f.b.brokken@rug.nl> Tue, 27 Apr 2004 17:39:17 +0200
185
186 stealth (1.30-1) unstable; urgency=low
187
188 * --keep-alive, --terminate and --rerun require the name of a file in which
189 the process id of the running stealth process is stored. This file will be
190 writen when the --keep-alive flag is used, read by the other two and
191 removed by the corresponding stealth process when it terminates. Manpages
192 and docs updated accordingly.
193
194 -- Frank B. Brokken <f.b.brokken@rug.nl> Wed, 17 Dec 2003 09:21:11 +0100
195
196 stealth (1.30) unstable; urgency=low
197
198 * --terminate, --rerun, --repeat and --keep-alive flags were added to allow
199 stealth to keep an existing connection for longer periods of
200 time. Manpages and docs updated accordingly
201
202 -- Frank B. Brokken <f.b.brokken@rug.nl> Fri, 12 Dec 2003 12:45:45 +0100
203
204 stealth (1.22-0) unstable; urgency=low
205
206 * Added GET and PUT. Put allows stealth to put files to the client using
207 the existing ssh connection.
208
209 -- Frank B. Brokken <f.b.brokken@rug.nl> Wed, 26 Nov 2003 21:25:02 +0100
210
211 stealth (1.21-0) unstable; urgency=low
212
213 * Added the GET command, allowing stealth to retrieve files from the client
214 for, e.g., local inspection, without requiring an additional ssh
215 connection.
216
217 -- Frank B. Brokken <f.b.brokken@rug.nl> Sat, 22 Nov 2003 13:55:40 +0100
218
219 stealth (1.20-2) unstable; urgency=low
220
221 * New buildscripts added for man(ual) pages. This file will take over from
222 CHANGELOG which logged the original, non-Debian distribution.
223
224 -- Frank B. Brokken <f.b.brokken@rc.rug.nl> Fri, 20 Jun 2003 17:21:42 +0200
225
226 stealth (1.20-1) unstable; urgency=low
227
228 * Initial Release.
229
230 -- Frank B. Brokken <f.b.brokken@rc.rug.nl> Wed, 18 Jun 2003 12:13:41 +0200
231
0 #include "configsorter.ih"
1
2 ConfigSorter::ConfigSorter(ConfigFile &configfile)
3 :
4 d_configfile(configfile),
5 d_use(&s_defaultKeyword[0], &s_defaultKeyword[s_nDefaultKeywords])
6 {
7 fetchCommands();
8
9 string base = d_use["BASE"] + "/.";
10
11 char const *cp = base.c_str();
12
13 if (!Util::mkdir(cp) || chdir(cp))
14 Util::exit("Can't chdir to `%s'", cp);
15 }
0 #ifndef _ConfigSorter_H_
1 #define _ConfigSorter_H_
2
3 #include <string>
4 #include <vector>
5 #include <bobcat/hash>
6
7 namespace FBB
8 {
9 class Arg;
10 class ConfigFile;
11 class Pattern;
12
13 class ConfigSorter
14 {
15 ConfigFile &d_configfile;
16 std::vector<std::string> d_command;
17 HashString<std::string> d_use;
18 HashString<std::string> d_define;
19
20 static std::pair<std::string, std::string> const s_defaultKeyword[];
21 static size_t s_nDefaultKeywords;
22 static Pattern s_firstWord;
23 static Pattern s_comment;
24 static Pattern s_define; // [0]: all text,
25 // [1]: all ${NAME} text
26 // [2]: NAME itself
27 public:
28 ConfigSorter(ConfigFile &configfile);
29
30 std::vector<std::string>::const_iterator firstCmd()
31 {
32 return d_command.begin();
33 }
34
35 std::vector<std::string>::const_iterator beyondCmd()
36 {
37 return d_command.end();
38 }
39
40 std::string const &operator[](std::string const &key)
41 {
42 return d_use[key];
43 }
44
45 private:
46 std::string const &getDEFINE(std::string const &key)
47 {
48 return d_define[key];
49 }
50
51 bool hasDEFINE(std::string const &key)
52 {
53 return d_define.count(key);
54 }
55
56 void fetchCwd(); // determine current working directory
57 void fetchCommands();
58 // replaces the DEFINE's in text
59 void replaceDefines(std::string &text);
60 void insert(HashString<std::string> &hash, Pattern &pattern,
61 std::string const &line);
62 };
63
64 }
65
66 #endif
0 #include "configsorter.h"
1
2 #include <bobcat/arg>
3 #include <bobcat/configfile>
4 #include <bobcat/pattern>
5
6 #include "../util/util.h"
7
8
9 #include <iostream>
10 #include <unistd.h>
11 #include <algorithm>
12
13 using namespace std;
14 using namespace FBB;
0 #include "configsorter.ih"
1
2 pair<string, string> const
3 ConfigSorter::s_defaultKeyword[] =
4 {
5 pair<string,string>("BASE", "."),
6 pair<string,string>("DD", "/bin/dd"),
7 pair<string,string>("DIFF", "/usr/bin/diff"),
8 pair<string,string>("EMAIL", "root"),
9 pair<string,string>("MAILER", "/usr/bin/mail"),
10 pair<string,string>("REPORT", "report"),
11 pair<string,string>("SH", "/bin/sh"),
12 pair<string,string>("MAILARGS", "-s \"STEALTH scan report\""),
13 };
14
15 size_t ConfigSorter::s_nDefaultKeywords =
16 sizeof(s_defaultKeyword) / sizeof(pair<string, string>);
17
18 Pattern ConfigSorter::s_firstWord("^\\s*(\\w+)\\s+(.*)");
19 Pattern ConfigSorter::s_comment("^\\s*[#]?");
20 Pattern ConfigSorter::s_define("(\\$\\{([a-zA-Z0-9_]+)\\})");
21 // [0]: all text,
22 // [1]: all ${NAME} text
23 // [2]: NAME itself
24
0 #include "configsorter.ih"
1
2 // called from ConfigSorter()
3
4 void ConfigSorter::fetchCommands()
5 {
6 for (int idx = 0, n = d_configfile.size(); idx < n; ++idx)
7 {
8 string line = d_configfile[idx];
9
10 if (!(s_firstWord << line)) // can't match a first word
11 {
12 if (!(s_comment << line))
13 Util::debug() << "No match for `" << line << "'" << endl;
14 continue;
15 }
16
17 if (s_firstWord[1] == "USE")
18 insert(d_use, s_firstWord, line);
19 else if (s_firstWord[1] == "DEFINE")
20 insert(d_define, s_firstWord, line);
21 else
22 {
23 Util::debug() << "Regular command: `" << line << "'" << endl;
24 d_command.push_back(line);
25 }
26 }
27
28 bool ok = d_use.count("SSH");
29
30 for
31 (
32 HashString<string>::iterator it = d_define.begin();
33 it != d_define.end();
34 it++
35 )
36 replaceDefines(it->second);
37
38 for
39 (
40 HashString<string>::iterator it = d_use.begin();
41 it != d_use.end();
42 it++
43 )
44 replaceDefines(it->second);
45
46 for
47 (
48 vector<string>::iterator it = d_command.begin();
49 it != d_command.end();
50 it++
51 )
52 replaceDefines(*it);
53
54 if (Arg::instance().option("dc"))
55 {
56 for
57 (
58 HashString<string>::iterator
59 begin = d_use.begin(), end = d_use.end();
60 begin != end;
61 begin++
62 )
63 cout << "USE " << begin->first << ": " << begin->second << endl;
64
65 for (int idx = 0; idx < static_cast<int>(d_command.size()); idx++)
66 cout << (idx + 1) << ": " << d_command[idx] << endl;
67
68 if (Arg::instance().option('c'))
69 Util::exit("ConfigSorter file processed");
70 }
71
72 if (!ok)
73 Util::exit("USE SSH ... entry missing in the configuration file");
74 }
0 #include "configsorter.ih"
1
2 void ConfigSorter::insert(HashString<string> &hash, Pattern &firstWord,
3 string const &line)
4 {
5 if (firstWord << firstWord[2]) // fetch 'KEY definition'
6 {
7 string type = firstWord[1];
8
9 Util::debug() << type << " line: " << line << endl;
10 hash[firstWord[1]] = firstWord[2]; // store key and value
11 Util::debug() << type << " key: " << firstWord[1] <<
12 ", value: " << hash[firstWord[1]] << endl;
13 return;
14 }
15 // error on failure
16 cerr << "Config line `" << line << "' invalid" << endl;
17 }
18
0 #include "configsorter.ih"
1
2 void ConfigSorter::replaceDefines(string &text)
3 {
4 // Pattern define("(\\$\\{([a-zA-Z0-9_]+)\\})"); // [0]: all text,
5 // // [1]: all ${NAME} text
6 // // [2]: NAME itself
7
8 Util::debug() << "ConfigSorter::replaceDefines in " << text << endl;
9
10 string out;
11
12 while (s_define << text) // Got a ${NAME}
13 {
14 out += s_define.before(); // Get all before ${NAME}
15
16 out += // Add:
17 hasDEFINE(s_define[2]) ? // if NAME is DEFINEd
18 getDEFINE(s_define[2]) // then its definition
19 : // otherwise
20 s_define.matched(); // ${NAME} (unmodified)
21
22 Util::debug() << " step: " << out << endl;
23
24 text = s_define.beyond(); // remove all matched
25 } // text from `text'
26
27 Util::debug() << "ConfigSorter::replaceDefines -> " << text << endl;
28
29 text = out + text; // redefine `text'
30 }
31
0 ################################################
1 # this is a demo policy file for stealth >= 1.01
2 ################################################
3
4 # create several DEFINEs
5 # user on controller running stealth
6 DEFINE HOME /home/stealth
7
8 # host being scanned: TARGET
9 DEFINE TARGET target
10
11 # A useful DEFINE for the find command.
12 DEFINE SETUID -xdev -type f -perm +u+s,g+s \( -user root -or -group root \)
13
14 # set up the non-default USE variables
15 # where will stealth store its results?
16 USE BASE ${HOME}/${TARGET}
17
18 # who will get the overview of the results?
19 USE EMAIL root@${TARGET}
20
21 # who will do the mailing ? (note that stealthmail doesn't mail, but
22 # rather, writes /tmp/stealth.mail
23 USE MAILER ${HOME}/bin/stealthmail
24
25 # what arguments will the mailer receive?
26 USE MAILARGS "${TARGET} STEALTH report"
27
28 # arguments used when executing a scp command reaching TARGET
29 # using a special ssh-key:
30 #DEFINE CLIENT -i ${HOME}/.ssh/stealthkey root@${TARGET}
31 # using the standard ssh-key
32 DEFINE CLIENT root@${TARGET}
33
34 # how is ssh used to connect to the client?
35 USE SSH /usr/bin/ssh root@${TARGET} -q
36
37 ##############################################
38 # First, retrieve md5sum and its libraries
39 # from the client, to be tested locally.
40 ##############################################
41
42 LOCAL mkdir -p tmp # local working directory
43 LOCAL scp ${CLIENT}:/usr/bin/md5sum tmp # copy md5sum to here
44 LOCAL scp ${CLIENT}:/lib/libc.so.6 tmp # and its libraries
45 LOCAL scp ${CLIENT}:/lib/ld-linux.so.2 tmp
46
47 #####################################################
48 # Now check the integrity of the downloaded files
49 # When ok, we use md5sum remotely
50 #####################################################
51
52 LABEL \nLocal check of remote md5sum program and its libraries
53 LOCAL CHECK local/md5sum /usr/bin/md5sum tmp/*
54 LOCAL rm -rf tmp # remove locally copied files
55
56 ########################################################
57 # Now we're ready to run remotely. Check all executables
58 # in the remote computer
59 ########################################################
60
61 LABEL \nsuid/sgid/executable files uid or gid root on the / partition
62 CHECK remote/print.root \
63 /usr/bin/find / -xdev -perm +6111 \( -user root -or -group root \) \
64 -type f -printf "%m %n %s %p\n"
65 CHECK remote/md5.root \
66 /usr/bin/find / -xdev -perm +6111 \( -user root -or -group root \) \
67 -type f -exec /usr/bin/md5sum {} \;
68
69 ########################################################
70 # Check all non-executable files in the ldconfig -v
71 # directories
72 ########################################################
73
74 ##########################################
75 LABEL \nlibraries under /lib
76 CHECK remote/print.lib \
77 /usr/bin/find /lib -type f -not -perm +6111 -printf "%m %n %u %g %s %p\n"
78 CHECK remote/md5.lib \
79 /usr/bin/find /lib -type f -not -perm +6111 -exec /usr/bin/md5sum {} \;
80 ##########################################
81
82
83 ##########################################
84 LABEL \nlibraries under /usr/lib
85 CHECK remote/print.usr.lib \
86 /usr/bin/find /usr/lib -type f -not -perm +6111 \
87 -printf "%m %n %u %g %s %p\n"
88
89 CHECK remote/md5.usr.lib \
90 /usr/bin/find /usr/lib -type f -not -perm +6111 -exec /usr/bin/md5sum {} \;
91
92 ##########################################
93 LABEL \nlibraries under /usr/X11R6/lib
94 CHECK remote/print.usr.X11R6.lib \
95 /usr/bin/find /usr/X11R6/lib -type f \
96 -not -perm +6111 -printf "%m %n %u %g %s %p\n"
97 CHECK remote/md5.usr.X11R6.lib \
98 /usr/bin/find /usr/X11R6/lib -type f \
99 -not -perm +6111 -exec /usr/bin/md5sum {} \;
100
101 ########################################################
102 # Check all non-executable files in the /etc directory
103 ########################################################
104
105 ##########################################
106 LABEL \nfiles in /etc
107 CHECK remote/print.etc \
108 /usr/bin/find /etc -type f -not -perm +6111 \
109 -not -regex "/etc/\(adjtime\|mtab\|hosts\.deny\)" \
110 -printf "%m %n %u %g %s %p\n"
111 CHECK remote/md5.etc \
112 /usr/bin/find /etc -type f -not -perm +6111 \
113 -not -regex "/etc/\(adjtime\|mtab\|hosts\.deny\)" \
114 -exec /usr/bin/md5sum {} \;
115
116 ########################################################
117 # Check other special files
118 ########################################################
119
120 ##########################################
121 LABEL \nother special files
122 CHECK remote/print.other \
123 /usr/bin/find /var/spool/cron/crontabs/root -printf "%m %n %u %g %s %p\n"
124 CHECK remote/md5.other \
125 /usr/bin/md5sum /var/spool/cron/crontabs/root
126
127
128
129
0 # create several DEFINEs
1 DEFINE CLIENT localhost
2 DEFINE SETUID -xdev -type f -perm +u+s,g+s \( -user root -or -group root \)
3
4 # set up the non-default USE variables
5 USE BASE /root/stealth/${CLIENT}
6
7 USE EMAIL root@${CLIENT}
8 # USE MAILER /usr/local/sbin/stealthmail
9 USE MAILER /usr/bin/mail
10 USE MAILARGS "${CLIENT} STEALTH report"
11
12 USE SSH /usr/bin/ssh rootbash@${CLIENT} -q
13
14
15 LABEL \nroot setuid/setgid files
16 CHECK LOG = remote/setuid \
17 /usr/bin/find / ${SETUID} -exec /usr/bin/md5sum {} \;
18
19
20 LABEL \nfiles in /usr/local/etc
21 CHECK remote/etcmd5 \
22 /usr/bin/find /usr/local/etc -type f -xdev -exec /usr/bin/md5sum {} \;
0 # this policy will scan the files in /bin, writing the results in /tmp.
1 # the base directory is SIMPLE
2 # The user running stealth should be able to make an ssh connection to itself.
3 # Use this file with, e.g., ./stealth simple.pol
4
5 # set up the non-default USE variables
6 USE BASE SIMPLE
7 USE MAILER ../stealthmail
8 USE MAILARGS "simple STEALTH report"
9 USE SSH /usr/bin/ssh localhost -q
10
11 CHECK bin /usr/bin/find /bin -type f -xdev -exec /usr/bin/md5sum {} \;
0 string remove1;
1 string remove2;
2 string remove3;
3
4 void setRemovals()
5 {
6 // always:
7 remove1 =
8 "debian/stealth.substvars debian/stealth build-stamp configure-stamp";
9
10 // unless `minimal':
11 remove2 =
12 "tmp/bin tmp/o o */o libstealth.a manual-*-stamp "
13 "release.yo release.h";
14 }
15
16 void clean(int dist)
17 {
18 setRemovals();
19
20 run("rm -rf " + remove1);
21
22 if (!dist && getenv("STEALTH") == "minimal")
23 {
24 printf("\n"
25 "WARNING: PERFORMED MINIMAL CLEANUP\n");
26 exit(0);
27 }
28
29 run("rm -rf " + remove2);
30
31 if (dist)
32 run("rm -rf tmp");
33
34 exit(0);
35 }
36
37
38
39
0 list g_env; // set to env in main()
1 int g_envvar; // set to the env var if defined
2
3 string getenv(string var)
4 {
5 int idx;
6
7 for (idx = sizeof(g_env); idx; )
8 {
9 idx -= 2;
10 if (element(idx, g_env) == var)
11 {
12 g_envvar = 1;
13 return element(idx + 1, g_env);
14 }
15 }
16 g_envvar = 0;
17 return "";
18 }
0 void rungzip9(string src, string dest)
1 {
2 int idx;
3 string file;
4 list man;
5
6 chdir(src);
7 man = makelist("*");
8 chdir(g_cwd);
9 for (idx = sizeof(man); idx--; )
10 {
11 file = element(idx, man);
12 run("gzip -9 < " + src + file + " > " + dest + file + ".gz");
13 }
14 }
15
16 void reqzip(string dest, string srcPath, string src)
17 {
18 list files;
19 int idx;
20 string file;
21
22 files = strtok(src, " ");
23 for (idx = sizeof(files); idx--; )
24 {
25 file = element(idx, files);
26 run("gzip -9 < " + srcPath + file + " > " + dest + file + ".gz");
27 }
28 }
29
30 void install(string where)
31 {
32 printf(" installing the executable\n");
33 md(where + BIN);
34 run("cp tmp/bin/* " + where + BIN);
35
36 printf(" installing the manual page stealth.1\n");
37 md(where + MAN);
38 reqzip(where + MAN + "/", "tmp/man/", "stealth.1");
39
40 printf(" installing the manual page stealthman.html\n");
41 md(where + DOC + "/man");
42 reqzip(where + DOC + "/man/", "tmp/manhtml/", "stealthman.html");
43
44 printf(" installing the information directly in and under $DOC\n");
45 reqzip(where + DOC + "/", "./", "changelog README ACKNOWLEDGEMENTS");
46
47 printf(" installing scripts\n");
48 md(where + DOC + "/scripts");
49 reqzip(where + DOC + "/scripts/", "./", "stealthmail");
50
51 printf(" installing examples\n");
52 md(where + DOC + "/examples");
53 rungzip9("example-policies/", where + DOC + "/examples/");
54
55 printf(" installing manual\n");
56 run("cp -r tmp/manual " + where + DOC);
57
58 printf(" Installation completed\n");
59
60 exit(0);
61 }
62
63
64
65
0 #define MANPAGE "../tmp/man/stealth.1"
1 #define MANHTML "../tmp/manhtml/stealthman.html"
2
3 void manpage()
4 {
5 md("tmp/man tmp/manhtml");
6
7 special();
8
9 chdir("manpage");
10
11 if ("stealth.yo" younger MANPAGE || "release.yo" younger MANPAGE)
12 {
13 run("yodl2man -o " MANPAGE " stealth");
14 run("yodl2html -o " MANHTML " stealth");
15 }
16 exit(0);
17 }
0 #define YOSTAMP "../../manual-yo-stamp"
1 #define LATEXSTAMP "manual-latex-stamp"
2 #define HTMLDEST "../../tmp/manual/html"
3 #define LATEXDEST "../../tmp/manual/LaTeX/stealth.latex"
4 #define TXTMANUAL "../../tmp/manual/text/stealth.txt"
5
6 void manual()
7 {
8 list files;
9 string file;
10 string cwd;
11 int idx;
12
13 md("tmp/manual/LaTeX tmp/manual/html tmp/manual/pdf tmp/manual/ps "
14 "tmp/manual/text");
15
16 special();
17
18 cwd = chdir(".");
19
20 chdir("manual/Yodl");
21
22 files = makelist("*.yo") + makelist("*/*.yo");
23 for (idx = sizeof(files); idx--; )
24 {
25 file = element(idx, files);
26 if (file younger YOSTAMP)
27 {
28 run("yodl2html stealth.yo");
29 run("touch " YOSTAMP);
30 run("mv *.html " HTMLDEST);
31 break;
32 }
33 }
34
35 for (idx = sizeof(files); idx--; )
36 {
37 file = element(idx, files);
38 if (file younger TXTMANUAL)
39 {
40 run("yodl2txt -o " TXTMANUAL " stealth.yo");
41 break;
42 }
43 }
44
45 chdir(cwd);
46
47 if (! exists(LATEXSTAMP))
48 {
49 chdir("manual/Yodl");
50 run("yodl2latex -o " LATEXDEST " stealth.yo");
51
52 chdir(cwd + "/tmp/manual/LaTeX");
53 runP(P_NOCHECK, "latex stealth.latex");
54 runP(P_NOCHECK, "latex stealth.latex");
55 run("latex stealth.latex");
56 chdir(cwd);
57 run("touch " LATEXSTAMP);
58 }
59
60 chdir(cwd + "/tmp/manual/LaTeX");
61 if ("stealth.dvi" newer "../ps/stealth.ps")
62 run("dvips -o ../ps/stealth.ps stealth.dvi");
63
64 chdir(cwd + "/tmp/manual/ps");
65 if ("stealth.ps" newer "../pdf/stealth.pdf")
66 run("ps2pdf stealth.ps ../pdf/stealth.pdf");
67
68 exit(0);
69 }
70
71
0 // md: target should be a series of blank-delimited directories to be created
1 // If an element is a whildcard, the directory will always be created,
2 // using mkdir -p.
3 //
4 // uses: run()
5
6 void md(string target)
7 {
8 int idx;
9 list paths;
10 string dir;
11
12 paths = strtok(target, " ");
13
14 for (idx = sizeof(paths); idx--; )
15 {
16 dir = element(idx, paths);
17 if (!exists(dir))
18 run("mkdir -p " + dir);
19 }
20 }
0
1 #define COPT "-Wall -O3"
2
3 #define ECHO_REQUEST 1
4
5 #define LIBS "bobcat"
6 #define LIBPATH ""
7
8
9 string // contain options for
10 g_cwd, // current WD
11 libs, // extra libs, e.g., "-lrss -licce"
12 libpath, // extra lib-paths, eg, "-L../rss"
13 g_sources, // sources to be used
14 g_binary; // the name of the program to create
15 int
16 g_nClasses; // number of classes/subdirectories
17 list
18 g_classes; // list of classes/directories
19
20 void setClasses()
21 {
22 list class;
23
24 while (sizeof(class = fgets("CLASSES", (int)element(1, class))))
25 g_classes += (list)element(0, strtok(element(0, class), " \t\n"));
26
27 g_nClasses = sizeof(g_classes);
28 }
29
30 void static_lib(string ofiles, string library)
31 {
32 if (sizeof(makelist(ofiles)))
33 {
34 run("ar cru " + library + " " + ofiles);
35 run("ranlib " + library);
36 run("rm " + ofiles);
37 }
38 }
39
40 void static_library(string library)
41 {
42 static_lib("*/o/*.o", library);
43 static_lib("o/*.o", library);
44 }
45
46 void initialize()
47 {
48 echo(ECHO_REQUEST);
49 g_sources = "*.cc";
50
51 g_binary = "tmp/bin/stealth";
52
53 g_cwd = chdir(".");
54
55 setClasses(); // remaining classes
56 }
57
58 void link(string library)
59 {
60 exec(COMPILER, "-o", g_binary,
61 "-l" + library, libs, "-L.", libpath , "-s");
62 }
63
64
65 list inspect(int prefix, list srcList, string library)
66 {
67 int idx;
68 string ofile;
69 string oprefix;
70 string file;
71
72 oprefix = "./o/" + (string)prefix;
73
74 for (idx = sizeof(srcList); idx--; )
75 {
76 file = element(idx, srcList);
77 ofile = oprefix + change_ext(file, "o"); // make o-filename
78
79 // A file s must be recompiled if it's newer than its object
80 // file o or newer than its target library l, or if neither o nor l
81 // exist.
82 // Since `a newer b' is true if a is newer than b, or if a exists and
83 // b doesn't exist s must be compiled if s newer o and s newer l.
84 // So, it doesn't have to be compiled if s older o or s older l.
85 // redo if file has changed
86 if (file older ofile || file older library)
87 srcList -= (list)file;
88 }
89 return srcList;
90 }
91
92
93 void c_compile(int prefix, string srcDir, list cfiles)
94 {
95 int idx;
96 string compiler;
97 string file;
98
99 compiler = COMPILER + " -c -o " + srcDir + "/o/" + (string)prefix;
100 md(srcDir + "/o");
101
102 for (idx = sizeof(cfiles); idx--; )
103 {
104 file = element(idx, cfiles);
105
106 run(compiler + change_ext(file, "o") + " " +
107 COPT + " " + srcDir + "/" + file);
108 }
109 }
110
111 void std_cpp(int prefix, string srcDir, string library)
112 {
113 list files;
114
115 chdir(srcDir);
116 // make list of all files
117 files = inspect(prefix, makelist(g_sources), library);
118 chdir(g_cwd);
119
120 if (sizeof(files))
121 c_compile(prefix, srcDir, files); // compile files
122 }
123
124
125 void cpp_make(string mainfile, string library)
126 {
127 int idx;
128 string class;
129 string fullLibname;
130
131 fullLibname = "lib" + library + ".a";
132
133 for (idx = g_nClasses; idx--; )
134 std_cpp(idx, element(idx, g_classes), "../" + fullLibname);
135
136 std_cpp(g_nClasses, ".", fullLibname); // compile all files in current dir
137
138 static_library(fullLibname); // make the library
139
140 link(library);
141 printf("\n"
142 "ok: ", g_binary, "\n");
143 }
144
145 void setlibs()
146 {
147 int
148 n,
149 index;
150 list
151 cut;
152
153 cut = strtok(LIBS, " "); // cut op libraries
154 n = sizeof(cut);
155 for (index = 0; index < n; index++)
156 libs += " -l" + element(index, cut);
157
158 cut = strtok(LIBPATH, " "); // cut up the paths
159 n = sizeof(cut);
160 for (index = 0; index < n; index++)
161 libpath += " -L" + element(index, cut);
162 }
163
164 void program()
165 {
166 initialize();
167 setlibs();
168
169 md("tmp/bin tmp/o");
170
171 special();
172
173 cpp_make
174 (
175 "stealth.cc", // program source
176 "stealth" // static program library base name
177 );
178
179 exit(0);
180 }
0 int g_dryrun; // set to 1 if envvar DRYRUN was set in main()
1
2
3 void runP(int testValue, string cmd)
4 {
5 if (g_dryrun)
6 printf(cmd, "\n");
7 else
8 system(testValue, cmd);
9 }
10
11 void run(string cmd)
12 {
13 runP(0, cmd);
14 }
15
0 void special()
1 {
2 list cut;
3 list line;
4 int refresh;
5 string years;
6 string version;
7
8 refresh = "VERSION" newer "release.yo";
9
10 if (refresh)
11 run("rm -f release.yo");
12
13 while (sizeof(line = fgets("VERSION", (int)element(1, line))))
14 {
15 cut = strtok(element(0, line), "= \t\n");
16
17 if (element(0, cut) == "VERSION")
18 {
19 version = element(1, cut);
20
21 if (refresh)
22 {
23 fprintf("release.yo", "SUBST(_CurVers_)(", version, ")\n");
24 fprintf("release.h", "#define _CurVers_ \"", version, "\"\n");
25 }
26 }
27 else if (refresh && element(0, cut) == "YEARS")
28 {
29 years = element(1, cut);
30 fprintf("release.yo", "SUBST(_CurYrs_)(", years, ")\n");
31 fprintf("release.h", "#define _CurYrs_ \"", years, "\"\n");
32 }
33 }
34 }
0 includefile(../release.yo)
1
2 htmlbodyopt(text)(#27408B)
3 htmlbodyopt(bgcolor)(#FFFAF0)
4
5 gagmacrowarning(stealth Stealth)
6 mailto(f.b.brokken@rug.nl)
7
8 DEFINEMACRO(s)(0)(bf(stealth))
9 DEFINEMACRO(S)(0)(bf(Stealth))
10
11 manpage(stealth)(1)(_CurYrs_)(stealth__CurVers_.tar.gz)(Security Enhancement)
12
13 manpagename(stealth)(Stealthy File Integrity Scanner)
14
15 manpagesynopsis()
16 s() -dcnoq -i <interval> -r <nr> nl()
17 [--keep-alive pidfile [--repeat <seconds> ] ] policy nl()
18 s() [--rerun | --resume | --suppress | --terminate] pidfile
19
20 manpagedescription()
21
22 The name of the s() program is an acronym of:
23 quote(
24 bf(SSH-based Trust Enforcement Acquired through a Locally Trusted Host.)
25 )
26 s() is based on an idea by em(Hans Gankema) and em(Kees Visser), both
27 at the Computing Center of the University of Groningen. em(Hopko Meijering)
28 provided valuable suggestions for improvement.
29
30 s()'s main task is to perform file integrity tests. However, the
31 testing itself will leave no sediments on the tested computer. Therefore,
32 s() has em(stealthy) characteristics. This is considered an
33 important security improving feature.
34
35 On the other hand, one should realize that s() intends to be just
36 another security tool: other security measures like firewalls, portscanners,
37 intrusion detection systems, abolishment of unencrypted protocols, etc. are
38 usually required to improve or promote the security of a group of computers
39 that are connected to the Internet.
40
41 s() uses a policy file to determine the actions to perform. Each
42 policy file is uniquely associated with a host to be tested. This remote host
43 (called the em(client) below) trusts the computer on which s() runs
44 (hence: a em(Locally Trusted Host)), called the em(controller). The controller
45 performs tasks (normally file integrity tests) that em(Enforce) the em(Trust)
46 we have in the client computer. Since almost all integrity tests can be run
47 on the client, one controller can control many clients, even if the
48 controller itself uses aged hardware components.
49
50 As the controller and the client normally are different computers, the
51 controller must communicate with the client in a secure fashion. This is
52 realized using SSH. So, there's another element of `local trust' involved
53 here: the client should permit the controller to set up a secure SSH
54 connection allowing the controller to access sensitive files and private parts
55 of the client's file system.
56
57 bf(It is important to ensure that there is no public access to the
58 controller. All inbound services should be denied. The only access to
59 the controller should be via its console and the controller should be placed
60 in a physically secure location. Sensitive information of clients are stored
61 in the controller, and passwordless access to clients can be obtained from the
62 controller by anyone who gains (root)-access).
63
64 The controller itself only needs two kinds of outgoing services:
65 bf(SSH) to reach its clients, and some mail transport agent (e.g.,
66 bf(sendmail)(1)) to forward its outgoing mail to some mail-hub.
67
68 Here is what happens when s() is run using the first synopsis:
69 itemization(
70 it() First, the em(policy) file is read. This determines the actions to be
71 performed, and the values of several variables that are used by s().
72 it() If the command-line option tt(--keep-alive pidfile) is specified,
73 s() will run as a backgrond process, writing its process id in the file
74 tt(pifile). With tt(--repeat <seconds>) the scan will be rerun every
75 tt(<seconds>) seconds. The number of seconds until the next rerun will be at
76 least 60. However, using the tt(--rerun pidfile) option a background s()
77 process may always be forced into its next scan. When tt(--keep-alive) is
78 specified the scan will be performed just once, whereafter bf(PROGRAM) will
79 wait until it is reactivated by another run of s(), called using the
80 tt(--rerun pidfile) command-line option (note that integrity scans are
81 suppressed between a tt(--suppress) and a tt(--resume) command, see below).
82 Consider specifying tt(--quiet) (see below) when tt(--keep-alive) is used.
83 it() Then, the controller opens a command shell on the client using
84 bf(ssh)(1), and a command shell on the controller itself using bf(sh)(1).
85 it() Next, commands defined in the policy file are executed in their order
86 of appearance. Examples are given below. Normally, return values of the
87 programs are tested. Non-zero return values will terminate s()
88 prematurely. In this case, a message about the reason why s() terminated is
89 written to the report file (and into the mail message sent by s()). In some
90 cases (e.g., when the report file could not be written), the message is
91 written to the standard error stream.
92 it() In most cases, integrity tests can be controlled by the bf(find)(1)
93 program, calling programs like bf(ls)(1), bf(md5sum)(1) or its own
94 tt(-printf) method to produce file-integrity related statistics. Most of these
95 programs write file names at the end of generated lines. This characteristic
96 is used by an internal routine of s() to detect changes in the generated
97 output, which could indicate some harmful intent, like an installed
98 em(root-kit).
99 it() When changes are detected, they are logged on a em(report file), to
100 which information is always appended. s() never reduces or rewrites the
101 report file. Whenever information is added to the report file (exceeding a
102 plain time stamp) the appended information is emailed to a configurable email
103 address for further (human) processing. Usually this will be the systems
104 manager of the tested client. s() follows the `dark cockpit' approach in
105 that no mail is sent when no changes were detected.
106 it() When the tt(--repeat) or tt(--rerun) options are issued, the report
107 file should not be rotated by, e.g., a log-rotating process, but the report
108 file may safely be rotated between a pair of tt(--suppress) and tt(--resume)
109 commands.
110 )
111
112 manpagesection(REPORT FILE ROTATION)
113 Since s() only appends information to the report file, it will
114 eventually grow to a large file, and log-rotation may be desirable. It is of
115 course possible to issue a tt(--terminate) command, rotate the logfiles, and
116 restart s(), but s() also offers a facility to temporarily suppress
117 further scans:
118 itemization(
119 it() Starting s() using the option tt(--suppress pidfile) will
120 suppress a currently active s() process. If s() is
121 actually performing a series of integrity scans when
122 tt(--suppress) is issued, the currently executing command is first
123 completed before the tt(--suppress) command completes. Once
124 tt(--suppress) is active, all scheduled scans
125 are skipped and tt(--rerun) is ignored. However, the
126 tt(--resume) and tt(--terminate) options are still handled.
127 it() Once `s() tt(--suppress pidfile)' has returned, the report file
128 may safely be rotated (using, e.g., bf(logrotate)(1)), and a new
129 (empty) report file may optionally be created by the logrotation
130 process.
131 it() Finally, when the log-rotation has been completed, the log-rotation
132 process should issue the command `s() tt(--resume
133 pidfile)'. This will resume a suppressed s() process,
134 immediately performing the next integrity scan (thus implying
135 tt(--rerun)), whereafter s() will be back in its normal
136 integrity scanning mode (so, resuming repeated scans if
137 originally requested so).
138 )
139
140 manpagesection(RERUN AND TERMINATE)
141
142 Here is what happens when s() is run using other synopses:
143 itemization(
144 it() When started using the tt(--rerun pidfile) command-line option, the
145 s() process associated with process id file tt(pidfile) will perform
146 another scan. This command has no effect following a tt(--suppress) command.
147 it() When started using the tt(--terminate pidfile) command-line option,
148 the s() process associated with process id file tt(pidfile) is terminated.
149 )
150
151 manpagesection(OPEN SSH LINK TO CLIENTS)
152
153 The tt(--keep-alive, --repeat, --rerun, --resume) and tt(--suppress)
154 options were implemented in such a way that the bf(ssh) link to the client
155 remains open, thus minimizing the number of bf(sshd) entries caused by
156 bf(PROGRAM) in the client's log files.
157
158 nsect(THE POLICY FILE)
159
160 The policy file consists of two sets of data: em(use directives) (starting
161 with the keyword bf(USE)) and em(commands). Blank lines and information beyond
162 hash-marks (#) are ignored, while lines following lines terminating in
163 backslashes (\) will be concatenated (em(en passant) removing the
164 backslashes). Initial white space on lines of the policy file is ignored.
165
166 nsect(DEFINE DIRECTIVES)
167
168 bf(DEFINE) directives may be used to associate longer strings of text with
169 certain symbols. E.g., after
170 tt(DEFINE FINDARGS -xdev -type f -exec /usr/bin/md5sum {} \;)
171 the text tt(${FINDARGS}) may be used in bf(USE DIRECTIVES) and
172 bf(commands) (see below) to use the text associated with the bf(FINDARGS)
173 symbol.
174
175 Note that bf(DEFINE) symbols may be used in the definition of
176 other bf(DEFINE) symbols as well. Direct or indirect circular definitions
177 should be avoided, as they are either not or incompletely expanded.
178
179 nsect(USE DIRECTIVES)
180
181 The following bf(USE) directives may be specified (directives are written
182 in capitals, and should appear exactly as written below: letter casing is
183 preserved). Specifications in angular brackets (like tt(<this>)) represent
184 specifications to be given by users of s():
185 itemization(
186 it() bf(USE BASE) tt(<basedirectory>)nl()
187 bf(BASE) defines the directory from where s() operates. All
188 relative path specifications are interpreted relative to bf(BASE). em(By
189 default) this is the directory where s() was started. nl()
190 bf(BASE) is the em(only) directory that em(must) exist when s() is
191 started. All other non-existing paths are created automatically by
192 s().nl()
193 Example:nl()
194 tt(USE BASE /root/client)
195
196 it() bf(USE DD) tt(<dd>)nl()
197 The bf(DD) specification uses tt(/bin/dd) as default, and defines the
198 location of the bf(dd)(1) program, both on the server and on the client. The
199 bf(bin)(1) program is used to copy files between the client and the controller
200 without opening separate ssh-connections. The program specified here is only
201 used by s() for the tt(PUT) and tt(GET) commands, described below.nl()
202 Example showing the default:nl()
203 tt(USE DD /bin/dd)
204
205 it() bf(USE DIFF) tt(<diff>)nl()
206 The bf(DIFF) specification uses tt(/usr/bin/diff) as default,
207 and defines the location of the bf(diff)(1) program on the controller. The
208 bf(diff)(1) program is used to compare a formerly created logfile of an
209 integrity check with a newly created logfile.nl()
210 Example showing the default:nl()
211 tt(USE DIFF /usr/bin/diff)
212
213 it() bf(USE EMAIL) tt(<address>)nl()
214 The bf(EMAIL) specification defines the email-address to receive the
215 report of the integrity scan of the client. The `dark cockpit' philosophy is
216 followed here: mail is only sent when a modification is detected.nl()
217 Example showing the default (apparently an email address on the
218 controller):nl()
219 tt(USE EMAIL root)
220
221 it() bf(USE MAILER) tt(<mailer>)nl()
222 The bf(MAILER) specification defines the program that is used to send
223 the mail to the bf(EMAIL)-address. Contrary to bf(DIFF) and bf(DD) and (see
224 below) bf(SH) and bf(SSH), bf(MAILER) is run as a tt(/bin/sh) command, to
225 allow shell-scripts to process the mail too. By default bf(MAILER) is defined
226 as bf(/usr/bin/mail)(1). bf(MAILER) is called with the following
227 arguments:nl()
228 ----------------------------------------------------------nl()
229 bf(MAILARGS), see below;nl()
230 bf(EMAIL), the addressee of the mail.nl()
231 ----------------------------------------------------------nl()
232 Example showing the default:nl()
233 tt(USE MAILER /usr/bin/mail)
234
235 it() bf(USE MAILARGS) tt(<args>)nl()
236 The bf(MAILARGS) specification defines the arguments that are passed
237 to tt(MAILER), followed by the specification of tt(EMAIL).
238 Example showing the default:nl()
239 tt(USE MAILARGS -s "STEALTH scan report")nl()
240 Note that blanks may be used in the subject specification: use double or
241 single quotes to define elements containing blanks. Use tt(\") to use a double
242 quote in a string that is itself delimted by double quotes, use tt(\') to use
243 a single quote in a string that is itself delimted by single quotes.
244
245 it() bf(USE REPORT) tt(<reportfile>)nl()
246 bf(REPORT) defines the name of the reportfile. Information is always
247 appended to this file. For each run of s() a em(time marker line) is
248 written to the report file. Only when (in addition to the marker line)
249 additional information is appended to the report file the added contents of
250 the report file are mailed to the mail address specified in the bf(USE EMAIL)
251 specification.nl()
252 Example showing the default:nl()
253 tt(USE REPORT report)
254
255 it() bf(USE SH) tt(<sh>)nl()
256 The bf(SH) specification uses tt(/bin/sh) as default, and defines the
257 command shell used by the controller to execute commands on itself.nl()
258 Example showing the default:nl()
259 tt(USE SH /bin/sh)
260
261 it() bf(USE SSH) tt(<user>)nl()
262 bf(The SSH specification has no default), and em(must) be
263 specified. Assuming the client em(trusts) the controller (which is, after all,
264 what this program is all about; so this should not be a very strong
265 assumption), preferably the public ssh-identity key of the controller should
266 be placed in the client's root tt(.ssh/authorized_keys) file, granting the
267 controller root access to the client. Root access is normally needed to gain
268 access to all directories and files of the client's file system.
269
270 In practice, connecting to a account using the bf(sh)(1) shell is
271 preferred. When another shell is already used by that account, one should make
272 sure that that shell doesn't setup its own redirections for standard input and
273 standard output. One way to accomplish that is for force the execution of
274 tt(/bin/sh) in the bf(USE SSH) specification.
275 Examples:
276 verb(
277 # root's shell is /bin/sh:
278 USE SSH root@client -T -q
279 # root uses another shell
280 USE SSH root@client -T -q exec /bin/bash
281 # an alternative:
282 USE SSH root@client -T -q exec /bin/bash --noprofile
283 )
284 )
285
286 nsect(COMMANDS)
287
288 Following the bf(USE) specifications, em(commands) can be specified. The
289 commands are executed in their order of appearance in the policy
290 file. Processing continues until the last command has been processed or until
291 a tested command (see below) returns a non-zero return value.
292
293 nsect(LABEL COMMANDS)
294
295 The following bf(LABEL) commands are available:
296 itemization(
297 it() bf(LABEL) tt(<text>)nl()
298 This defines a text-label which is written to the bf(REPORT) file,
299 in front of the output generated by the next bf(CHECK)-command. If the next
300 bf(CHECK)-command generates no output, the text-label is not written to the
301 bf(REPORT)-file. Once a bf(LABEL) has been defined, it is used until it is
302 redefined by the next bf(LABEL). Use an empty bf(LABEL) specification to
303 suppress the printing of labels.
304
305 The text may contain tt(\n) characters (two characters) which are
306 transformed to a newline character.
307
308 Example:nl()
309 tt(LABEL Inspecting files in /etc\nIncluding subdirectories)nl()
310 tt(LABEL) nl()
311 (The former bf(LABEL) specification clears the latter label text).
312 )
313
314 nsect(LOCAL COMMANDS)
315
316 The following bf(LOCAL) commands are available to be executed on the
317 controller:
318 itemization(
319 it() bf(LOCAL) tt(<command>)nl()
320 Execute tt(command) on the controller, using the bf(SH) command
321 shell. The command must succeed (i.e., must return a zero exit value). nl()
322 Example:nl()
323 tt(LOCAL scp rootsh@client:/usr/bin/md5sum /tmp)nl()
324 This command will copy the client's bf(md5sum)(1) program to the
325 controller.
326
327 it() bf(LOCAL NOTEST) tt(<command>)nl()
328 Execute tt(command) on the controller, using the bf(SH) command
329 shell. The command may or may not succeed.nl()
330 Example:nl()
331 tt(LOCAL NOTEST mkdir /tmp/subdir)nl()
332 This command will create tt(/tmp/subdir) on the controller. The command
333 will fail if the directory cannot be created, but this will not terminate
334 s().
335
336 it() bf(LOCAL CHECK) [bf(LOG =)] tt(<logfile> <command>)nl()
337 Execute tt(command) on the controller, using the bf(SH) command
338 shell. The command must succeed. The output of this command is compared to the
339 output of this command generated during the previous run of s(). The
340 phrase bf(LOG =) is optional. Any differences are written to bf(REPORT). If
341 differences were found, the existing tt(logfile) name is renamed to
342 tt(logfile.YYMMDD-HHMMSS), with tt(YYMMDD-HHMMSS) the datetime-stamp at the
343 time s() was run.
344
345 Note that eventually many tt(logfile.YYMMDD-HHMMSS) files could be
346 created: It is up to the controller's systems manager to decide what to do
347 with old datetime-stamped logfiles.
348
349 The tt(logfile) specifications may use relative and absolute paths. When
350 relative paths are used, these paths are relative to bf(BASE). When the
351 directories implied by the tt(logfile) specifications do not yet exist, they
352 are created first.
353
354 Example:nl()
355 tt(LOCAL CHECK LOG = local/md5sum md5sum /tmp/md5sum)nl()
356 This command will check the MD5 sum of the tt(/tmp/md5sum) program. The
357 resulting output is saved at bf(BASE)tt(/local/md5sum). The program must
358 succeed (i.e., tt(md5sum) must return a zero exit-value).
359
360 it() bf(LOCAL NOTEST CHECK) tt(<logfile> <command>)nl()
361 Execute tt(command) on the controller, using the bf(SH) command
362 shell. The command may or may not succeed. Otherwise, the program acts
363 identically as the bf(LOCAL CHECK ...) command, discussed previously.
364
365 Example:nl()
366 tt(LOCAL NOTEST CHECK LOG=local/md5sum md5sum /tmp/md5sum)nl()
367 This command will check the MD5 sum of the tt(/tmp/md5sum) program. The
368 resulting output is saved at bf(BASE)tt(/local/md5sum). The program must
369 succeed (i.e., tt(md5sum) must return a zero exit-value).
370 )
371
372 Note that the bf(scp)(1) command can be used to copy files between the
373 client and the controller, using a local command. This, however, is
374 discouraged, as a separate bf(ssh)(1)-connection is required for each separate
375 bf(scp)(1) command. This subtlety was brought to the author's attention by
376 Hopko Meijerink (tt(h.meijering@rc.rug.nl)). Using bf(scp)(1) results in
377 several additional entries showing bf(sshd)(1) connections in the client's
378 logfiles, which in turn may provide hints to a hacker that the client is
379 intensively monitored. In order to copy files between the client and the
380 controller, the tt(GET) and tt(PUT) commands (described below) may be used,
381 which use the existing bf(ssh)(1) connection. In general, tt(LOCAL) commands
382 should not be used to establish additional bf(ssh)(1) connections to a client.
383
384 nsect(REMOTE COMMANDS)
385
386 Remote commands are commands executed on the client using the bf(SSH)
387 shell. These commands are executed using the standard tt(PATH) set for the
388 bf(SSH) shell. However, it is advised to specify the full pathname to the
389 programs to be executed, to prevent ``trojan approaches'' where a trojan horse
390 is installed in an `earlier' directory of the tt(PATH)-specification than the
391 intended program.
392
393 Two special remote commands are tt(GET) and tt(PUT), which can be used to
394 copy files between the client and the controller. Internally, tt(GET) and
395 tt(PUT) use the tt(DD) use-specification. If a non-default specification is
396 used, one should ensure that the alternate program accepts bf(dd)(1)'s tt(if=,
397 of=, bs=) and tt(count=) options. With tt(GET) the options tt(bs=, count=) and
398 tt(of=) are used, with tt(PUT) the options tt(bs=, count=) and tt(if=) are
399 used. Normally there should be no need to alter the default tt(DD)
400 specification.
401
402 The tt(GET) command may be used as follows:
403 itemization(
404 it() bf(GET) tt(<client-path> <local-path>)nl()
405 Copy the file indicated by tt(client-path) at the client to tt(local-path)
406 at the controller. tt(client-path) must be the full path of an existing file
407 on the client, tt(local-path) may either be a local directory, in which case
408 the client's file name is used, or another file name may be specified, in
409 which case the client's file is copied to the specified local filename. If the
410 local file already exists, it is overwritten by the copy-procedure.
411
412 Example:nl()
413 tt(GET /usr/bin/md5sum /tmp)nl()
414 The program tt(/usr/bin/md5sum), available at the client, is copied to the
415 controller's tt(/tmp) directory. If the copying fails for some reason,
416 any subsequent commands are skipped, and bf(stealth) terminates.
417
418 it() bf(GET NOTEST) tt(<client-path> <local-path>)nl()
419 Copy the file indicated by tt(client-path) at the client to tt(local-path)
420 at the controller. tt(client-path) must be the full path of an existing file
421 on the client, tt(local-path) may either be a local directory, in which case
422 the client's file name is used, or another file name may be specified, in
423 which case the client's file is copied to the specified local filename. If the
424 local file already exists, it is overwritten by the copy-procedure.
425
426 Example:nl()
427 tt(GET NOTEST /usr/bin/md5sum /tmp)nl()
428 The program tt(/usr/bin/md5sum), available at the client, is copied to the
429 controller's tt(/tmp) directory. Remaining commands in the policy file are
430 executed, even if the copying process wasn't successful.
431 )
432
433 The tt(PUT) command may be used as follows:
434 itemization(
435 it() bf(PUT) tt(<local-path> <remote-path>)nl()
436 Copy the file indicated by tt(local-path) at the controller to
437 tt(remote-path) at the client. The argument tt(local-path) must be the
438 full path of an existing file on the controller. The argument tt(remote-path)
439 must be the full path to a file on the client. If the remote file already
440 exists, it is overwritten by tt(PUT).
441
442 Example:nl()
443 tt(PUT /tmp/md5sum /usr/bin/md5sum)nl()
444 The program tt(/tmp/md5sum), available at the controller, is copied to the
445 client as tt(usr/bin/md5sum). If the copying fails for some reason,
446 any subsequent commands are skipped, and bf(stealth) terminates.
447
448 it() bf(PUT NOTEST) tt(<local-path> <remote-path>)nl()
449 Copy the file indicated by tt(local-path) at the controller to
450 tt(remote-path) at the client. The argument tt(local-path) must be the
451 full path of an existing file on the controller. The argument tt(remote-path)
452 must be the full path to a file on the client. If the remote file already
453 exists, it is overwritten by tt(PUT).
454
455 Example:nl()
456 tt(PUT NOTEST /tmp/md5sum /usr/bin/md5sum)nl()
457 Copy the file indicated by tt(local-path) at the controller to
458 tt(remote-path) at the client. The argument tt(local-path) must be the full
459 path of an existing file on the controller. The argument tt(remote-path) must
460 be the full path to a file on the client. If the remote file already exists,
461 it is overwritten by tt(PUT). Remaining commands in the policy file are
462 executed, even if the copying process wasn't successful.
463 )
464
465 Plain commands can be executed on the client computer by merely specifying
466 them. Of course, this means that programs on the client called, e.g.,
467 tt(LABEL), tt(LOCAL) or tt(USE), cannot be executed, since these names are
468 interpreted otherwise by s(). I don't think that represents much of a
469 problem, though....
470
471 The following commands are available to be executed on the client:
472 itemization(
473 it() tt(<command>)nl()
474 Execute tt(command) on the client, using the bf(SSH) command
475 shell. The command must succeed (i.e., must return a zero exit
476 value). However, any output generated by the the command is ignored. nl()
477 Example:nl()
478 tt(/usr/bin/find /tmp -type f -exec /bin/rm {} \;)nl()
479 This command will remove all ordinary files in and below the client's
480 tt(/tmp) directory.
481
482 it() bf(NOTEST) tt(<command>)nl()
483 Execute tt(command) on the client, using the bf(SSH) command
484 shell. The command may or may not succeed.nl()
485 Example:nl()
486 tt(NOTEST /usr/bin/find /tmp -type f -exec /bin/rm {} \;)nl()
487 Same as the previous command, but this time the exit value of
488 tt(/usr/bin/find) is not interpreted.
489
490 it() bf(CHECK) [bf(LOG =)] tt(<logfile> <command>)nl()
491 Execute tt(command) on the client, using the bf(SSH) command
492 shell. The phrase bf(LOG = ) is optional. The command must succeed. The output
493 of this command is compared to the output of this command generated during the
494 previous run of s(). Any differences are written to bf(REPORT). If
495 differences were found, the existing tt(logfile) name is renamed to
496 tt(logfile.YYMMDD-HHMMSS), with tt(YYMMDD-HHMMSS) the datetime-stamp at the
497 time s() was run.
498
499 Note that the command is executed on the client, but the logfile is kept
500 on the controller. This command represents the core of the method implemented
501 by s(): there will be no residues of the actions performed by s() on
502 the client computers.
503
504 Several examples (note the use of the backslash as line continuation
505 characters):
506
507 tt(CHECK LOG = remote/ls.root \)nl()
508 tt( /usr/bin/find / \)nl()
509 tt( -xdev -perm +6111 -type f -exec /bin/ls -l {} \;)
510
511 All suid/gid/executable files on the same device as the root-directory (/)
512 on the client computer are listed with their permissions, owner and size
513 information. The resulting listing is written on the file
514 bf(BASE)tt(/remote/ls.root).
515
516
517 tt(CHECK remote/md5.root \)nl()
518 tt( /usr/bin/find / \)nl()
519 tt( -xdev -perm +6111 -type f -exec /usr/bin/md5sum {} \;)
520
521 The MD5 checksums of all suid/gid/executable files on the same device as
522 the root-directory (/) on the client computer are determined. The resulting
523 listing is written on the file bf(BASE)tt(/remote/md5.root).
524
525 it() bf(NOTEST CHECK) [bf(LOG =)] tt(<logfile> <command>)nl()
526 Execute tt(command) on the client, using the bf(SSH) command
527 shell. The phrase bf(LOG =) is optional. The command may or may not
528 succeed. Otherwise, the program acts identically as the bf(CHECK ...) command,
529 discussed previously.
530
531 Example:nl()
532 tt(NOTEST CHECK LOG = remote/md5.root \)nl()
533 tt( /usr/bin/find / \)nl()
534 tt( -xdev -perm +6111 -type f -exec /usr/bin/md5sum {} \;)
535
536
537 The MD5 checksums of all suid/gid/executable files on the same device as
538 the root-directory (/) on the client computer are determined. The resulting
539 listing is written on the file bf(BASE)tt(/remote/md5.root). s() will not
540 terminate if the tt(/usr/bin/find) program returns a non-zero exit value.
541 )
542
543 manpagesection(OPTIONS)
544
545 Long options are given immediately following the short-option
546 equivalents, if available. Either can be used.
547
548 itemization(
549 it() tt(-d --debug): Write debug messages to std error;
550 it() tt(-c --parse-config-file):
551 Process the config file, no further action,nl()
552 report the results to std output;
553 it() tt(-e --echo-commands):
554 echo commands to std error when they are processed (implied by
555 tt(-d));
556 it() tt(-i --random-interval <interval>[m]>):
557 start the scan between now and nl()
558 a random interval of interval seconds, or nl()
559 minutes if an `m' is appended immediately after nl()
560 the specified interval.
561 it() tt(-n --no-child-processes): No child processes are executed:
562 child actions+nl()
563 are faked to be OK.
564 it() tt(-o --only-stdout): Scan report is written to stdout.
565 No mail is sent.nl()
566 (implied by -d);
567 it() tt(-q --quiet): Suppress progress messages written to stderr;
568 it() tt(-r --run-command <nr>): Only run command <nr> (natural number).
569 Command numbers are shown by s() tt(-c);
570 it() tt(-v --version): Display version information and exit;
571 it() tt(--keep-alive pidfile): Keep running as a daemon,
572 wake up at interrupts.
573 it() tt(--repeat <seconds>): keep running as a daemon, wake up at
574 interrupts or after <seconds> seconds.
575 it() tt(--rerun pidfile): restart the scan of a currently active s()
576 process;
577 it() tt(--resume pidfile): resume a suppressed s()
578 process, implying tt(--rerun);
579 it() tt(--suppress pidfile): suppress a currently active s()
580 process. All scheduled scans following tt(--suppress) are skipped,
581 tt(--rerun) is ignored, but tt(--resume) and tt(--terminate)
582 (see below) may be issued;
583 it() tt(--terminate pidfile): terminate a currently active s()
584 process;
585 it() tt(--usage): Display help information and exit;
586 it() tt(--help): Display help information and exit;
587 it() tt(pidfile): file containing the process id of a s() process;
588 it() tt(policy): path to the policyfile;
589 )
590
591 manpagesection(DEPLOYMENT SUMMARY)
592 The following summarizes the advised steps to perform when installing
593 stealth. All these steps are elaborated upon in s()'s em(User Guide)
594 (chapter em(Running `stealth')):
595 itemization(
596 it() Install s() (e.g., use bf(dpkg)(1) to install the bf(.deb) file);
597 it() Construct one or more policy files;
598 it() Automate running s() using bf(cron)(1) (possibly calling
599 bf(stealthcron));
600 it() Set up automated log-file rotation, using, e.g., bf(stealthcleanup)
601 and bf(logrotate)(1), defining one or more
602 tt(/etc/logrotate.d/stealth...) configuration files.
603 )
604
605 manpagefiles()
606
607 tt(/usr/share/doc/stealth/);nl()
608 the tt(policy) file;nl()
609 files under the bf(BASE) directory as defined in the tt(policy) file;nl()
610 the report file as defined by the policy's bf(USE REPORT) directive.
611
612 manpageseealso()
613 bf(cron)(1), bf(dd)(1), bf(diff)(1), bf(dpkg)(1), bf(find)(1),
614 bf(logrotate)(1), bf(ls)(1), bf(mail)(1), bf(md5sum)(1), bf(passwd)(5),
615 bf(sendmail)(1), bf(sh)(1), bf(ssh)(1)
616
617 manpagesection(DIAGNOSTICS)
618 By default, the executed commands are echoed to stderr. Use bf(-q) to
619 suppress this echoing.
620
621 manpagebugs()
622 None reported
623
624 manpagesection(COPYRIGHT)
625 This is free software, distributed under the terms of the `GNU General
626 Public License'. Copyright remains with the author. S() is found at
627 tt(http://stealth.sourceforge.net/).
628
629 manpagesection(ORGANIZATION)
630 Computing Center, University of Groningen.
631
632 manpageauthor()
633 Frank B. Brokken (bf(f.b.brokken@rug.nl)).
0 tt(/bin/sh: no output from /usr/bin/diff ...)
1 quote(the actual program names appearing here could change due to your
2 local configuration. The defaults are shown. This indicates that the
3 tt(/usr/bin/diff) program could not be activated on the controller. Check the
4 correctness of both the shell program (tt(/bin/sh)) and the diff program
5 (tt(/usr/bin/diff)): Do they exist? Have their paths been specfied well?
6 Note that filenames passed to tt(diff) might not exist anymore when the
7 program terminates. This should not be the cause of the error.
8 )
9
10 tt(Can't chdir to `path')
11 quote(the directory tt(path) could not be
12 created/used. This may be a permission problem. Check the permissions of
13 tt(path) if tt(path) does actually exist. The problem may be in a path
14 component, not necessarily in the last element of the path.
15 )
16
17 tt(Can't open /dev/null)
18 quote(This message may be generated by a
19 child-process: tt(sh), tt(ssh) or tt(diff). It is generated when the child
20 process could not redirect its standard error messages to the standard error
21 stream. If it appears then there is probably something incorrect in your
22 tt(/dev/) directory: check the availability of tt(/dev/null), check if you can
23 copy a file to tt(/dev/null).
24 )
25
26 tt(Can't open ... to write)
27 quote( This message may be generated when the
28 mentioned log-file could not be written to. Check the permissions of the file,
29 check if the path to the file exists. The problem may be in a path
30 component, not necessarily in the last element of the path or in the file
31 itself.
32 )
33
34 tt(Can't read ...)
35 quote(the mentioned file could not be read. Check if
36 the file exists, and if you have read permissions for it.
37 )
38
39 tt(Config line `...' invalid)
40 quote(The mentioned line of the specified policy file was
41 ill-formed. Check the line's contents against the description of the policy
42 file.
43 )
44
45 tt(ConfigSorter file processed)
46 quote(In this case, the tt(-c) option has been given. When tt(-c) was
47 provided, bf(stealth) stops after having processed the configuration file.
48 )
49
50 tt(Corrupt line in policy file: ...)
51 quote( The apparently corrupted line
52 is shown. The line is corrupted if the line could not be split into an initial
53 word and its remainder. Normally this should not happen. As the line is
54 mentioned, the message itself should assist you in your repairs.
55 )
56
57 tt(Inserting command `...' failed.)
58 quote(the mentioned command could not
59 be sent to a child-process (tt(sh) or tt(ssh)). Check the availability of the
60 tt(ssh) connection to the client. The command itself might also be
61 unacceptable.
62 )
63
64 tt(Invalid interval for -i.)
65 quote(The -i flag was given an invalid (too large or negative) argument.
66 )
67
68 tt(Non-zero exit value for `...')
69 quote(A local command (not using the tt(CHECK) keyword), returned with a
70 non-zero exit. This will terminate further processing of the policy
71 file. Inspect and/or rerun the command `by hand' to find indications about
72 what went wrong. The report file or the standard error stream may also contain
73 additional information about the reason of the failure.
74 )
75
76 tt(Unable to create the logfile `...')
77 quote(the mentioned log file could not be created. Check the permissions of
78 the file, check if the path to the file exists. The problem may be in a path
79 component, not necessarily in the last element of the path or in the file
80 itself.
81 )
82
83 tt(USE SSH ... entry missing in the configuration file)
84 quote(there is no
85 default for the tt(USE SSH) specification in the policy file. The
86 specification could not be found. Provide a specification like:
87 verb(USE SSH ssh -q root@localhost)
88 )
0 After downloading the bf(stealth) archive, it should be unpacked. The name of
1 the archive is of the form tt(stealth-_CurVers_.tar.gz), where tt(_CurVers_)
2 is a version number. Below, tt(_CurVers_) should be altered into the version
3 of the archive that is actually used.
4
5 itemization(
6 it() First, determine a directory under which the archive's file should be
7 stored. E.g., if the files in the archive should be stored under tt(/tmp), and
8 the archive itself is stored in tt(/tmp) as well, do:
9 verb(
10 cd /tmp
11 tar xzf stealth-_CurVers_.tar.gz
12 )
13 This creates a subdirectory tt(stealth) in which the sources are
14 found
15 it() Next, tt(chdir) to that directory:
16 verb(
17 chdir stealth
18 )
19 it() Check the contents of the file tt(make/parameters). It should need no
20 modifications. Among other entries, it contains the entry tt(GCC=g++),
21 indicating the compiler to use. The compiler should be the tt(GNU g++)
22 compiler version 4.0.2 or above. Also note tt(-lbobcat) in the entry
23 verb(
24 LOPTS="-lbobcat -lstealth -L. -s"
25 )
26 When compiling bf(stealth), the
27 url(bobcat)(http://bobcat.sourceforge.net/) library must be available. If you
28 haven't installed bf(bobcat) yet, download it from
29 lurl(http://sourceforge.net/projects/bobcat/), and follow its installation
30 instructions. Make sure to install both the run-time (bf(bobcat_...))
31 and the development (bf(bobcat-dev_...)) versions.
32 it() Execute the command
33 verb(
34 make/program
35 )
36 This command (note that it is bf(not) em(make program)!) will create the
37 program bf(./tmp/bin/stealth), which may then be installed in, e.g.,
38 tt(/usr/sbin).
39 )
0 This chapter describes bf(stealth)'s compilation and installation.
1
2 sect(Compiling and installing Stealth)
3 includefile(install/compile)
0 Welcome to bf(stealth). The program bf(stealth) implements a file integrity
1 scanner. The acronym bf(stealth) can be expanded to
2
3 center(
4 nsect(SSH-based Trust Enforcement Acquired through a Locally Trusted Host.)
5 )
6
7 This expansion contains the following key terms:
8
9 itemization(
10 it() tt(SSH-based): The file integrity scan is (usually) performed over an
11 ssh-connection. Usually the computer being scanned (called the em(client)) and
12 the computer initiating the scan (called the tt(controller)) are different
13 computers.
14 it() The client should accept incoming ssh-connections
15 from the initiating computer. The controller doesn't have to (and shouldn't,
16 probably).
17 it() tt(Trust Enforcement): following the scan, `trust' is enforced in the
18 client, due to the integrity of its files.
19 it() tt(Locally Trusted Host): the client apparently trusts the controller
20 to use an ssh-connection to perform commands on it. The client therefore
21 em(locally trusts) the controller. Hence, em(locally trusted host).
22 )
23
24 bf(stealth) is based on an idea by em(Hans Gankema) and em(Kees Visser),
25 both at the Computing Center of the University of Groningen.
26
27 bf(stealth)'s main task is to perform file integrity tests. However, the
28 testing will leave no sediments on the tested computer. Therefore, bf(stealth)
29 has em(stealthy) characteristics. I consider this an important security
30 improving feature of bf(stealth).
31
32 The controller itself only needs two kinds of outgoing services:
33 bf(ssh)(1) to reach its clients, and some mail transport agent (e.g.,
34 bf(sendmail)(1)) to forward its outgoing mail to some mail-hub.
35
36 Here is what happens when bf(stealth) is run:
37 itemization(
38 it() First, a em(policy) file is read. This determines actions to be
39 performed, and values of several variables used by bf(stealth).
40 it() If the command-line option tt(--keep-alive) or tt(--repeat <seconds>)
41 is given, bf(stealth) will run as a backgrond process, displaying the process
42 ID of the background process. With tt(--repeat <seconds>) the scan will be
43 rerun every tt(<seconds>) seconds. The number of seconds until the next rerun
44 will be at least 60. However, using the tt(--rerun) option a background
45 bf(stealth) process may always be goated into its next scan. When
46 tt(--keep-alive) is specified the scan will be performed just once, whereafter
47 bf(stealth) will wait until it is reactivated by another run of bf(stealth),
48 called using the tt(--rerun <pid>) command-line option.
49 it() Then, the controller opens a command shell on the client using
50 bf(ssh)(1), and a command shell on the controller itself using bf(sh)(1).
51 it() Next, commands defined in the policy file are executed in their order
52 of appearance. Examples are given below. Normally, return values of the
53 programs are tested. Non-zero return values will terminate bf(stealth)
54 prematurely.
55 it() In most cases, integrity tests can be controlled by the bf(find)(1)
56 program, calling programs like bf(ls)(1), bf(md5sum)(1) or its own tt(-printf)
57 method to produce file-integrity related statistics. Most of these programs
58 write file names at the end of generated lines. This characteristic is used by
59 an internal routine of bf(stealth) to detect changes in the generated output,
60 which could indicate some harmful intent, like an installed em(root-kit).
61 it() When changes are detected, they are logged on a em(report file), to
62 which information is always appended. bf(stealth) never reduces or rewrites
63 the report file. When information is added to the report file the newly
64 written information is emailed to a configurable email address for further
65 (human) processing. Usually this will be the systems manager of the tested
66 client. bf(stealth) follows the `dark cockpit' approach in that no mail is
67 sent when no changes were detected.
68 )
69
70 Alternatively, the command-line options tt(--rerun) and tt(--terminate)
71 may be provided to communicate with a bf(stealth) process started earlier
72 using either the tt(--keep-alive) or tt(--repeat) option. In this case,
73 itemization(
74 it() When started using the tt(--terminate <pid>) command-line option, the
75 stealth process running at process-ID tt(<pid>) is terminated. Note that no
76 check is performed as to whether the process associated with tt(<pid>) is
77 truly a bf(stealth) process. It is the responsibility of the user to make sure
78 that the process-ID of the intended process is specified.
79 it() When started using the tt(--rerun <pid>) command-line option, the
80 stealth process running at process-ID tt(<pid>) will perform another
81 scan. Again, no check is performed as to whether the process associated with
82 tt(<pid>) is truly a bf(stealth) process. It is the responsibility of the user
83 to make sure that the process-ID of the intended process is specified.
84 )
85
86 The options tt(--suppress) and tt(--rerun) (see section ref(ROTATE)) were
87 implemented to allow safe rotations of bf(stealth)'s report file.
88
89 subsect(The integrity of the stealth distribution)
90
91 The integrity of the archive tt(stealth-_CurVers_.tar.gz) can be verified as
92 follows:
93 itemization(
94 it() At the location where you found this archive, you should also find a
95 file named tt(stealth-_CurVers_.dsc). This file contains a PGP signed
96 bf(md5sum)(1) signature of the tt(tar.gz) archive. The PGP sigature was
97 provided by me using bf(gpg)(1) (bf(pgp)(1)).
98 it() Compute the MD5 checksum of the tt(stealth-_CurVers_.tar.gz)
99 archive. Its value should match the MD5 checksum that is mentioned in the
100 tt(stealth-_CurVers_.dsc) file. If not, the tt(stealth-_CurVers_.tar.gz)
101 archive has been compromised, and should em(not) be used.
102 it() In order to verify the validity of the electronic signature, do as
103 follows:
104 itemization(
105 it() Obtain my public key from a public PGP keyserver, e.g.
106 verb(
107 http://pgp.surfnet.nl:11371/
108 )
109 it() Make sure you have the right key. Its fingerprint is
110 verb(
111 8E36 9FC4 1DAA FCDF 1A0D B19F DAC4 BE50 38C6 6170
112 )
113 and it has been electronically signed by, e.g., the University of
114 Groningen's PGP-certificate authority. If in doubt, contact me to verify you
115 have the right key.
116 it() Once you're sufficiently satisfied that you indeed have obtained
117 my public PGP key, verify the validity of the signature used for signing
118 tt(stealth-_CurVers_.dsc). With bf(gpg)(1) this can be done by the command
119 verb(
120 gpg --verify stealth-_CurVers_.dsc
121 )
122 )
123 )
124 This should produce output comparable to:
125 verb(
126 gpg: Signature made Mon Aug 1 10:57:41 2005 CEST using DSA key ID 38C66170
127 gpg: Good signature from "Frank B. Brokken <f.b.brokken@rug.nl>"
128 gpg: aka "Frank B. Brokken <f.b.brokken@rc.rug.nl>"
129 )
130
131
0
1 Here are the steps to take to kick-start bf(stealth)
2 itemization(
3 it() Install the stealth Debian package tt(stealth__CurVers__i386.deb) and
4 thus accept the provided binary program (skipping the next three steps) or do
5 not accept the provided binary, and compile bf(stealth) yourself, as per the
6 following steps:
7 it() Unpack tt(stealth__CurVers_.tar.gz):
8 tt(tar xzvf tealth__CurVers_.tar.gz)
9 it() tt(cd stealth)
10 it() Inspect the values of the variables in the file tt(INSTALL.cf) Modify
11 these values when necessary.
12 it() Make sure the bobcat library has been installed.
13 (lurl(http://bobcat.sourceforge.net))
14 it() Run `tt(./make/program)' to compile bf(stealth). Note: it's em(not)
15 `tt(make program)'
16 it() Run (probably as root) `tt(./make/install)' to install. Note: it's
17 em(not) `tt(make install)'
18 )
19 Following the installation nothing in the tt(stealth) directory tree is
20 required for bf(stealth)'s proper functioning, so consider removing it.
21
22 Compiling bf(stealth) assumes that tt(g++) version 3.3 (or higher) is
23 available. If not: install it first.
24
25 Next, do:
26 itemization(
27 it() tt(cp stealthmail /usr/local/sbin)
28 it() tt(mkdir /root/stealth)
29 it() tt(cp local.pol /root/stealth)
30 )
31
32 tt(ssh) and tt(sh) should be available. tt(root@localhost) should be able
33 to login at tt(localhost) using tt(ssh root@localhost), using the
34 tt(/bin/bash) or tt(/bin/sh) shell. Check (as `root') at least
35 verb(
36 ssh root@localhost
37 )
38 as this might ask you for a confirmation that you've got the correct
39 host.
40 Now, run
41 verb(
42 stealth /root/stealth/localhost.pol
43 )
44 to initialize the stealth-report files for tt(localhost). This will
45 initialize the report for:
46 itemization(
47 it() all root setuid/setgid executable files on tt(localhost),
48 it() and for all files under tt(/etc/) on tt(localhost).
49 )
50
51 The mail-report is written on tt(/tmp/stealth-_CurVers_.mail)
52
53 Now change or add or remove one of these files, and rerun bf(stealth). The
54 file tt(/tmp/stealth-_CurVers_.mail) should reflect these changes.
0 Access is granted via the tt(ssh) protocol.
1
2 The client must allow the controller to connect using tt(ssh). Since normally
3 no username and password can be given, the client must allow the controller to
4 connect without specifying a password.
5
6 This is realized using em(public key) technology, assuming tt(open-SSH) is
7 available on both computers, with the client running an tt(sshd) daemon.
8
9 subsect(The controller's user: creating an ssh-key)
10
11 The user on the controller who will call bf(stealth) to scan the client,
12 now generates an tt(ssh-keypair):
13 verb(
14 ssh-keygen -t rsh
15 )
16 This will generate a public/private ssh key pair in tt(.ssh) in the user's
17 home directory. The program asks for a em(passphrase) which should, for the
18 purpose of bf(stealth) be bf(empty): just pressing tt(Enter) as a response to
19 the question
20 verb(
21 Enter passphrase (empty for no passphrase):
22 )
23 will do the trick (a confirmation is requested: press tt(Enter) again).
24 The program returns a key fingerprint, e.g.,
25 verb(
26 03:96:49:63:8a:64:33:45:79:ab:ca:de:c8:c8:4f:e9 user@controller
27 )
28 which may be saved and used for future reference.
29
30 In the directory user's tt(.ssh) directory the files tt(id_rsa) and
31 tt(id_rsa.pub) are now created.
32
33 This completes the actions on the controller.
34
35 subsect(The client's account: accepting ssh from the controller's user)
36
37 Next, the account on the client where the tt(ssh) command connects to
38 (using a specification in the policy file like
39 verb(
40 USE SSH /usr/bin/ssh -q account@client
41 )
42 must now grant access to the controller's user. In order to do so, the file
43 tt(id_rsa.pub) of the user at the controller is added to the file
44 tt(authorized_keys) in the tt(.ssh) directory of the account on the client:
45 verb(
46 # transfer user@controller's file id_rsa.pub to the client's /tmp
47 # directory. Then do:
48
49 cat /tmp/id_rsa.pub >> /home/account/.ssh/authorized_keys
50 )
51
52 Now user@controller may login at acount@client without specifying a
53 password.
54
55 subsect(Logging into the account@client account)
56
57 When user@controller now issues the command
58 verb(
59 ssh account@controller
60 )
61 tt(Ssh) responds as follows:
62 verb(
63 The authenticity of host 'controller (xxx.yyy.aaa.bbb)' can't be
64 established.
65 RSA key fingerprint is c4:52:d6:a3:d4:65:0d:5e:2e:66:d8:ab:de:ad:12:be.
66 Are you sure you want to continue connecting (yes/no)?
67 )
68 Answering tt(yes) results in the message:
69 verb(
70 Warning: Permanently added 'controller,xxx.yyy.aaa.bbb' (RSA) to the
71 list of known hosts.
72 )
73
74 The next time a login is attempted, the authenticity question isn't asked
75 anyore. However, the proper value of the host's RSA key fingerprint (i.e., the
76 key fingerprint of the em(client) computer) should
77 em(always) be verified to prevent em(man in the middle) attacks. The proper
78 value may be obtained at the client computer by issuing the command
79 verb(
80 ssh-keygen -l -f /etc/ssh/ssh_host_rsa_key.pub
81 )
82 This should result in the same value as the fingerprint shown when the
83 first tt(ssh) connection was made. E.g.,
84 verb(
85 1024 c4:52:d6:a3:d4:65:0d:5e:2e:66:d8:ab:de:ad:12:be ssh_host_rsa_key.pub
86 )
87
88 subsect(Using the proper shell)
89
90 On order to minimize the amount of clutter and possible complications when
91 only a simple command-shell is required for executing commands, it is
92 suggested to use a tt(bash) or tt(sh) shell when logging into the
93 tt(account@client)'s account.
94
95 When another shell is already used for tt(account@client), then an extra
96 account (optionally using the same tt(UID) as the original account, but
97 using bf(sh)(1) as the shell), could be used.
98
99 In the bf(passwd)(5) file this could be realized for em(root) as
100 em(rootsh) as follows:nl()
101 verb(
102 rootsh:x:0:0:root:/root:/bin/sh
103 )
104 If shadow passwording is used, an appropriate entry in the tt(/etc/shadow)
105 file is required as well.
0 lsect(COMMANDS)(Commands)
1
2 Following the bf(USE) specifications, em(commands) can be specified. The
3 commands are executed in their order of appearance in the policy
4 file. Processing continues until the last command has been processed or until
5 a tested command (see below) returns a non-zero return value.
6
7 subsect(LABEL commands)
8
9 The following bf(LABEL) commands are available:
10 itemization(
11 it() bf(LABEL) tt(text)
12
13 This defines a text-label which is written to the bf(REPORT) file,
14 just before the output generated by the next bf(CHECK)-command. If the next
15 bf(CHECK)-command generates no output, the label is not written to the
16 bf(REPORT)-file. Once a bf(LABEL) has been defined, it is used until it is
17 redefined by the next bf(LABEL) command. Use an empty bf(LABEL) command to
18 suppress the printing of labels.
19
20 The text may contain tt(\n) characters (two characters) which are
21 transformed to a newline character.
22 it() bf(LABEL)
23
24 As noted, this clears a previously defined tt(LABEL) command.
25 )
26
27 Examples:
28 verb(
29 LABEL Inspecting files in /etc\nIncluding subdirectories
30 LABEL
31 )
32 The second bf(LABEL) command clears the first label.
33
34 subsect(LOCAL commands)
35
36 bf(LOCAL) commands can be used to specify commands that are
37 executed on the controller itself. The following bf(LOCAL) commands are
38 available:
39 itemization(
40 it() bf(LOCAL) tt(command)
41
42 Execute tt(command) on the controller, using the bf(SH) command
43 shell. The command must succeed (i.e., must return a zero exit value).
44 Example:
45 verb(
46 LOCAL mkdir /tmp/client
47 )
48 This command will create the directory tt(/tmp/client) on the controller.
49
50 it() bf(LOCAL NOTEST) tt(command)
51
52 Execute tt(command) on the controller, using the bf(SH) command
53 shell. The command may or may not succeed.
54 Example:
55 verb(
56 LOCAL NOTEST mkdir /tmp/subdir
57 )
58 This command will create tt(/tmp/subdir) on the controller. The command
59 will fail if the directory cannot be created, but this will not terminate
60 bf(stealth).
61
62 it() bf(LOCAL CHECK) [bf(LOG =)] tt(logfile command)
63
64 Execute tt(command) on the controller, using the bf(SH) command shell.
65 The phrase `bf(LOG =)' is optional.
66 If
67 the command does not succeed a em(warning) message is written to the report
68 file. The warning message informs the reader that `remaining results might be
69 forged:
70 verb(
71 *** BE CAREFUL *** REMAINING RESULTS MAY BE FORGED
72 )
73 This situation may occur, e.g., if an essential program (like tt(md5sum))
74 was transferred to the controller, and it was apparently modified since the
75 previous check. Processing continues, but remaining checks performed at the
76 client computer should be interpreted with em(extreme) caution.
77
78 The output of this command is compared to the output of this command
79 generated during the previous run of bf(stealth). Any differences are written
80 to bf(REPORT).
81
82 If differences were found, the existing tt(logfile) name is renamed to
83 tt(logfile.YYYYMMDD-HHMMSS), with tt(YYYYMMDD-HHMMSS) the datetime-stamp at
84 the time bf(stealth) was run.
85
86 Over time, many tt(logfile.YYMMDD-HHMMSS) files could be accumulated.
87 It is up to the controller's systems manager to decide what to do
88 with old datetime-stamped logfiles. For instance, the following script
89 will remove all bf(stealth) reports below the current directory that are
90 older than 30 days:
91 verb(
92 #/bin/sh
93 FILES=`find ./ -path '*[0-9]' -mtime +30 -type f`
94
95 if [ "$FILES" != "" ] ; then
96 rm -f $FILES
97 fi
98 )
99
100 The tt(logfile) specifications may use relative and absolute paths. When
101 relative paths are used, these paths are relative to bf(BASE). When the
102 directories implied by the tt(logfile) specifications do not yet exist, they
103 are created first.
104
105 Example:
106 verb(
107 LOCAL CHECK LOG = local/md5sum md5sum /tmp/md5sum
108 )
109 This command will check the MD5 sum of the tt(/tmp/md5sum) program. The
110 resulting output is saved at bf(BASE)tt(/local/md5sum). The program must
111 succeed (i.e., tt(md5sum) must return a zero exit-value).
112
113 it() bf(LOCAL NOTEST CHECK) [bf(LOG =)] tt(logfile command)
114
115 Execute tt(command) on the controller, using the bf(SH) command
116 shell. The phrase `bf(LOG =)' is optional.
117 The command may or may not succeed. Otherwise, the program acts
118 identically as the bf(LOCAL CHECK ...) command, discussed previously.
119
120 Example:
121 verb(
122 LOCAL NOTEST CHECK LOG=local/md5sum md5sum /tmp/md5sum
123 )
124 This command will check the MD5 sum of the tt(/tmp/md5sum) program. The
125 resulting output is saved at bf(BASE)tt(/local/md5sum). The program may or may
126 not succeed (i.e., tt(md5sum) may or may not return a zero exit-value).
127 )
128
129 subsect(REMOTE commands)
130
131 Plain commands can be executed on the client computer by merely
132 specifying them. Of course, this means that programs called
133 tt(LABEL), tt(LOCAL) tt(USE) or tt(DEFINE), cannot be executed, since
134 these names are interpreted otherwise by bf(stealth). It's unlikely that this
135 will cause problems. Remote commands must succeed (i.e., their return
136 codes must be 0).
137
138 Remote commands are commands executed on the client using the bf(SSH)
139 shell. These commands are executed using the standard tt(PATH) set for the
140 bf(SSH) shell. However, it is advised to specify the full pathname to the
141 programs to be executed, to prevent ``trojan approaches'' where a trojan horse
142 is installed in an `earlier' directory of the tt(PATH)-specification than the
143 intended program.
144
145 Two special remote commands are tt(GET) and tt(PUT), which can be used to
146 copy files between the client and the controller. Internally, tt(GET) and
147 tt(PUT) use the tt(DD) use-specification. If a non-default specification is
148 used, one should ensure that the alternate program accepts bf(dd)(1)'s tt(if=,
149 of=, bs=) and tt(count=) options. With tt(GET) the options tt(bs=, count=) and
150 tt(of=) are used, with tt(PUT) the options tt(bs=, count=) and tt(if=) are
151 used. Normally there should be no need to alter the default tt(DD)
152 specification.
153
154 The tt(GET) command may be used as follows:
155 startit()
156 it() bf(GET) tt(<client-path> <local-path>)nl()
157 Copy the file indicated by tt(client-path) at the client to tt(local-path)
158 at the controller. tt(client-path) must be the full path of an existing file
159 on the client, tt(local-path) may either be a local directory, in which case
160 the client's file name is used, or another file name may be specified, in
161 which case the client's file is copied to the specified local filename. If the
162 local file already exists, it is overwritten by the copy-procedure.
163
164 Example:nl()
165 tt(GET /usr/bin/md5sum /tmp)nl()
166 The program tt(/usr/bin/md5sum), available at the client, is copied to the
167 controller's tt(/tmp) directory. If the copying fails for some reason,
168 any subsequent commands are skipped, and bf(stealth) terminates.
169
170 it() bf(GET NOTEST) tt(<client-path> <local-path>)nl()
171 Copy the file indicated by tt(client-path) at the client to tt(local-path)
172 at the controller. tt(client-path) must be the full path of an existing file
173 on the client, tt(local-path) may either be a local directory, in which case
174 the client's file name is used, or another file name may be specified, in
175 which case the client's file is copied to the specified local filename. If the
176 local file already exists, it is overwritten by the copy-procedure.
177
178 Example:nl()
179 tt(GET NOTEST /usr/bin/md5sum /tmp)nl()
180 The program tt(/usr/bin/md5sum), available at the client, is copied to the
181 controller's tt(/tmp) directory. Remaining commands in the policy file are
182 executed, even if the copying process wasn't successful.
183 endit()
184
185 The tt(PUT) command may be used as follows:
186 startit()
187 it() bf(PUT) tt(<local-path> <remote-path>)nl()
188 Copy the file indicated by tt(local-path) at the controller to
189 tt(remote-path) at the client. The argument tt(local-path) must be the
190 full path of an existing file on the controller. The argument tt(remote-path)
191 must be the full path to a file on the client. If the remote file already
192 exists, it is overwritten by tt(PUT).
193
194 Example:nl()
195 tt(PUT /tmp/md5sum /usr/bin/md5sum)nl()
196 The program tt(/tmp/md5sum), available at the controller, is copied to the
197 client as tt(usr/bin/md5sum). If the copying fails for some reason,
198 any subsequent commands are skipped, and bf(stealth) terminates.
199
200 it() bf(PUT NOTEST) tt(<local-path> <remote-path>)nl()
201 Copy the file indicated by tt(local-path) at the controller to
202 tt(remote-path) at the client. The argument tt(local-path) must be the
203 full path of an existing file on the controller. The argument tt(remote-path)
204 must be the full path to a file on the client. If the remote file already
205 exists, it is overwritten by tt(PUT).
206
207 Example:nl()
208 tt(PUT NOTEST /tmp/md5sum /usr/bin/md5sum)nl()
209 Copy the file indicated by tt(local-path) at the controller to
210 tt(remote-path) at the client. The argument tt(local-path) must be the full
211 path of an existing file on the controller. The argument tt(remote-path) must
212 be the full path to a file on the client. If the remote file already exists,
213 it is overwritten by tt(PUT). Remaining commands in the policy file are
214 executed, even if the copying process wasn't successful.
215 endit()
216
217 Other commands to be executed on the client can be specified as follows:
218
219 itemization(
220 it() tt(command)
221
222 Execute `tt(command)' on the client, using the bf(SSH) command
223 shell. The command must succeed (i.e., must return a zero exit
224 value). However, any output generated by the command is ignored.
225 Example:
226 verb(
227 /usr/bin/find /tmp -type f -exec /bin/rm {} \;
228 )
229 This command will remove all ordinary files at and below the client's
230 tt(/tmp) directory.
231
232 it() bf(NOTEST) tt(command)
233
234 Execute tt(command) on the client, using the bf(SSH) command
235 shell. The command may or may not succeed.
236
237 Example:
238 verb(
239 NOTEST /usr/bin/find /tmp -type f -exec /bin/rm {} \;
240 )
241 Same as the previous command, but this time the exit value of
242 tt(/usr/bin/find) is not interpreted.
243
244 it() bf(CHECK) [bf(LOG =)] tt(logfile command)
245
246 Execute tt(command) on the client, using the bf(SSH) command
247 shell. The phrase `bf(LOG =)' is optional.
248 The command must succeed. The output of this command is compared to the
249 output of this command generated during the previous run of bf(stealth). Any
250 differences are written to bf(REPORT). If differences were found, the existing
251 tt(logfile) name is renamed to tt(logfile.YYYYMMDD-HHMMSS), with
252 tt(YYYYMMDD-HHMMSS) the datetime-stamp at the time bf(stealth) was run.
253
254 Note that the command is executed on the client, but the logfile is kept
255 on the controller. This command represents the core of the method implemented
256 by bf(stealth): there will be no residues of the actions performed by
257 bf(stealth) on the client computers.
258
259 Several examples (note the use of the backslash as line continuation
260 characters):
261 COMMENT(CAREFUL: EXTRA BLANK REQUIRD IN THE YODL FILE BEHIND \ )
262 verb(
263 CHECK LOG = remote/ls.root /usr/bin/find / \
264 -xdev -perm +6111 -type f -exec /bin/ls -l {} \;
265 )
266 All suid/gid/executable files on the same device as the root-directory (/)
267 on the client computer are listed with their permissions, owner and size
268 information. The resulting listing is written on the file
269 bf(BASE)tt(/remote/ls.root).
270
271 This long command could be formulated shorter using a tt(DEFINE):
272 verb(
273 DEFINE LSFIND -xdev -perm +6111 -type f -exec /bin/ls -l {} \;
274 CHECK remote/ls.root /usr/bin/find / ${LSFIND}
275 )
276
277 Another example:
278 verb(
279 DEFINE MD5SUM -xdev -perm +6111 -type f -exec /usr/bin/md5sum {} \;
280 CHECK remote/md5.root /usr/bin/find / ${MD5SUM}
281 )
282 The MD5 checksums of all suid/gid/executable files on the same device as
283 the root-directory (/) on the client computer are determined. The resulting
284 listing is written on the file bf(BASE)tt(/remote/md5.root).
285
286 it() bf(NOTEST CHECK) [bf(LOG =)] tt(logfile command)
287
288 Execute tt(command) on the client, using the bf(SSH) command
289 shell. The phrase `bf(LOG =)' is optional.
290 The command may or may not succeed. Otherwise, the program acts
291 identically as the bf(CHECK ...) command, discussed previously.
292 Example (using the same tt(${MD5SUM}))definition:
293 verb(
294 NOTEST CHECK LOG = remote/md5.root /usr/bin/find / ${MD5SUM}
295 )
296 The MD5 checksums of all suid/gid/executable files on the same device as
297 the root-directory (/) on the client computer are determined. The resulting
298 listing is written on the file bf(BASE)tt(/remote/md5.root). bf(stealth) will
299 not terminate if the tt(/usr/bin/find) program returns a non-zero exit value.
300 )
301
0 sect(DEFINE directives)
1
2 tt(DEFINE) directives can be used to define symbols for longer strings.
3 A tt(DEFINE) directive is constructed as follows:
4 verb(
5 DEFINE name that what is defined by `name'
6 )
7 Here,
8 itemization(
9 it() the tt(name) following tt(DEFINE) is the symbol that may be used in
10 tt(USE) directives (see below) and tt(commands) (see below).
11 it() tt(DEFINE) symbols can be used in other tt(DEFINE) symbols. However,
12 it is the responsibility of the author of the policy file to make sure that
13 (indirect) circular definitions are avoided. E.g., after:
14 verb(
15 DEFINE A ${B}
16 DEFINE B ${A}
17 DEFINE C ${C}
18
19 USE MAILARGS ${A} ${B} ${C}
20 )
21 tt(MAILARGS) will be expanded to
22 verb(
23 ${A} ${A} ${C}
24 )
25 it() The text following tt(DEFINE name) is then inserted literally into
26 the tt(USE) directive or tt(command).
27
28 Example:
29 verb(
30 DEFINE SSH /usr/bin/ssh frankbash@localhost -q
31 DEFINE EXECMD5 -xdev -perm +111 -type f -exec /usr/bin/md5sum {} \;
32 )
33 The symbols defined by tt(DEFINE) directives may consist of
34 letters, digits and the underscore character (tt(_)).
35 In the definition of the symbol any character can be used. The
36 definition is, however, trimmed of initial or trailing blanks.
37
38 To insert a definition into a tt(USE) directive or tt(command) use the
39 verb(
40 ${name}
41 )
42 form. E.g., tt(${EXECMD5}). Concrete examples will be given below.
43 )
0 bf(stealth) reads a policy file defining the actions that must be
1 performed. Each policy file is uniquely associated with a host to be
2 tested. There may be multiple policy files for a host, though. In that case,
3 each policy file will define a certain set of checks to be performed.
4
5 Below, the term em(controller) is used for the computer where bf(stealth)
6 is started, while the term em(client) is used for the computer that is scanned
7 by bf(stealth). The controller and the client could be the same computer, but
8 normally they are different.
9
10 The policy file consists of three sets of data: em(define directives)
11 (starting with the keyword bf(DEFINE)), em(use directives) (starting with the
12 keyword bf(USE)) and em(commands).
13
14 Directives are written in capitals, and should appear exactly as written
15 below: letter casing is preserved.
16
17 Blank lines and information beyond hash-marks (#) are ignored, while lines
18 following lines terminating in backslashes (\ ) will be concatenated (em(en
19 passant) removing the backslashes). Initial white space on lines of the policy
20 file is ignored.
21
0 sect(USE directives)
1
2 tt(USE) directives provide bf(stealth) with arguments which
3 may be conditional to a certain installation. The following bf(USE) directives
4 may be specified:
5 itemization(
6 it() bf(USE BASE) tt(basedirectory)
7
8 bf(BASE) defines the directory from where bf(stealth) operates. All
9 relative path specifications are interpreted relative to bf(BASE). em(By
10 default) this is the directory where bf(stealth) was started.
11
12 bf(BASE) and all other directories that are used below tt(BASE)
13 are created by bf(stealth) if not yet existing.
14
15 Example:
16 verb(
17 USE BASE /root/client
18 )
19 All information generated by bf(stealth) is written in or below the
20 directory tt(/root/client).
21
22 it() bf(USE DD) tt(<dd>)nl()
23 The bf(DD) specification uses tt(/bin/dd) as default, and defines the
24 location of the bf(dd)(1) program, both on the server and on the client. The
25 bf(bin)(1) program is used to copy files between the client and the controller
26 without opening separate ssh-connections. The program specified here is only
27 used by stealth for the tt(PUT) and tt(GET) commands, described below.
28
29 Example showing the default:
30 verb(
31 USE DD /bin/dd
32 )
33
34 it() bf(USE DIFF) tt(path-to-diff)
35
36 The bf(DIFF) specification uses tt(/usr/bin/diff) as default,
37 and defines the location of the bf(diff)(1) program. The
38 bf(diff)(1) program is used to compare a formerly created logfile of an
39 integrity check to a newly created logfile.
40
41 Example showing the default:
42 verb(
43 USE DIFF /usr/bin/diff
44 )
45
46 it() bf(USE EMAIL) tt(address)
47
48 The bf(EMAIL) specification defines the email-address to e-mail the
49 client's integrity scan report to. Mail is only sent when information has
50 changed.
51
52 Example showing the default:
53 verb(
54 USE EMAIL root
55 )
56
57 it() bf(USE MAILER) tt(mailer)
58
59 The bf(MAILER) specification defines the program that is used to send
60 the mail to the bf(EMAIL)-address. By default this is bf(/usr/bin/mail)(1).
61 The bf(MAILER) program is called as follows:
62 verb(
63 MAILER MAILARGS EMAIL
64 )
65 (tt(MAILARGS): see below). The information to be mailed is read from
66 tt(MAILER)'s standard input stream.
67
68 Example showing the default:
69 verb(
70 USE MAILER /usr/bin/mail
71 )
72
73 it() bf(USE MAILARGS) tt(arguments)
74 The bf(MAILARGS) specification defines the arguments to be
75 to be passed to the tt(MAILER) program. By default this is
76 verb(
77 USE MAILARGS -s "STEALTH scan report"
78 )
79 Note that blanks may be used in the subject specification: use double or
80 single quotes to define elements containing blanks. Use tt(\") to use a double
81 quote in a string that is itself delimited by double quotes, use tt(\') to use
82 a single quote in a string that is itself delimited by single quotes.
83
84 Subtle note: in a construction like
85 verb(
86 USE MAILARGS " 't was brillig " and 't went well
87 )
88 the following arguments are passed to tt(MAILER):
89 itemization(
90 it() tt(" 't was brillig ")
91 it() tt(and)
92 it() tt('t)
93 it() tt(went)
94 it() tt(well)
95 )
96 So, when single- and double-quoted strings overlap, the first string is
97 taken as a string, and the information beyond the first string is thereupon
98 interpreted.
99
100 it() bf(USE REPORT) tt(reportfile)
101
102 bf(REPORT) defines the name of the reportfile. Information is always
103 appended to this file. For each run of bf(stealth) a em(time marker line) is
104 written to the report file. Such a marker line looks like this:
105 verb(
106 STEALTH (1.11) started at Mon Jun 16 12:57:26 2003
107 )
108 Only when (in addition to the marker line)
109 additional information was appended to the report file, the added contents of
110 the report file are mailed to the mail address specified in the bf(USE EMAIL)
111 specification.
112
113 Example showing the default:
114 verb(
115 USE REPORT report
116 )
117
118 it() bf(USE ROTATE) tt(interval: number interval-name)[,]
119 [tt(count: number)][,]
120 [tt(, zip: number [zip-program-path])]
121
122 bf(ROTATE) defines the parameters bf(stealth) will use to rotate its
123 report file. This bf(USE) specification supports three elements, the first of
124 which is obligatory when bf(USE ROTATE) is specified. Note that the square
125 brackets are not used in the specification, and indicate optional elements,
126 which may or may not be specified:
127 itemization(
128 it() tt(interval: number interval-name) defines the time interval
129 until the report file is rotated. Rotation can be specified using an integral,
130 positive number, followed by tt(hour) or tt(hours) for hours, tt(day) or
131 tt(days) for days, tt(week) or tt(weeks) for weeks and tt(month) or tt(months)
132 for months. By default no rotation takes place. If rotation is requested, the
133 current report file is moved to the file tt(reportfile)bf(.1), while existing
134 numbered reportfiles are moved to higher ordered numbers first (so, before
135 moving the current reportfile to tt(reportfile)bf(.1), an existing
136 tt(reportfile)bf(.1) is first moved to tt(reportfile)bf(.2), etc.).
137 it() tt(count: number) defines the number of report files bf(stealth)
138 will eventually use. By default, if bf(USE ROTATE) is specified, there is no
139 practical limit to the number of report files bf(stealth) will create (in
140 these cases, another program supposedly controls the number of report files
141 that will eventually be used). External programs may freely manipulate all
142 report files that have been rotated by bf(stealth), but they should not modify
143 the active report file (specified using the bf(USE REPORT) specification).
144 it() tt(zip: number zip-program-path) defines the first of the rotated
145 files that should be compressed, using tt(zip-program-path) to compress the
146 report files. By default, no compression is used, but if tt(zip:) is
147 specified, the default program that will be used to compress a report file is
148 bf(/bin/gzip). If another program is used, it should expect a filename as its
149 first argument, which will then be zipped to a new file receiving the
150 extension bf(.gz), appended to the name that was provided as its first
151 argument. The original file is removed during the zipping-process.
152
153 Example showing a report interval of one week, using a total of 12 report
154 files, compressing all report files but the actual report file and its
155 predecessor (having filename tt(reportfile)bf(.1)):
156 verb(
157 USE ROTATE interval: 1 week, count: 12, zip: 2 /bin/gzip
158 )
159 )
160 it() bf(USE SH) tt(sh-specification)
161
162 The bf(SH) specification uses tt(/bin/sh) as default, and defines the
163 command shell used by the controller to execute local commands.
164
165 Example showing the default:
166
167 verb(
168 USE SH /bin/sh
169 )
170
171 it() bf(USE SSH) tt(ssh-specification)
172
173 bf(The SSH specification has no default), and em(must) be
174 specified. Assuming the client em(trusts) the controller (which is, after all,
175 what this program is all about; so this should not be a very strong
176 assumption), preferably the public ssh-identity key of the controller should
177 be placed in the client's root tt(.ssh/authorized_keys) file, granting the
178 controller root access to the client. Root access is normally needed to gain
179 access to all directories and files of the client's file system.
180
181 In practice, connecting to a account using the bf(sh)(1) shell is
182 preferred. When another shell is already used by that account, one should make
183 sure that that shell doesn't setup its own redirections for standard input and
184 standard output. One way to accomplish that is for force the execution of
185 tt(/bin/sh) in the bf(USE SSH) specification.
186
187 An example of an tt(SSH) specification to scan a localhost is:
188 verb(
189 USE SSH root@localhost -T -q # root's shell is /bin/sh
190 )
191
192 The same, now explicitly using tt(/bin/bash):
193 verb(
194 USE SSH root@localhost -T -q exec /bin/bash # root uses another shell
195 )
196
197 Alternatively, tt(--profile) can be specified to prevent any
198 profile-initialization:
199 verb(
200 USE SSH root@localhost -T -q exec /bin/bash --noprofile
201 )
202 )
0 In order to automate the execution of bf(stealth), a file
1 tt(/etc/cron.d/stealth) could be created, containing a line like
2 (assuming bf(stealth) lives in tt(/usr/sbin)):
3 verb(
4 2,17,32,47 * * * * root test -x /usr/sbin/stealth && \
5 /usr/sbin/stealth -q /root/stealth/client.pol
6 )
7 This will start bf(stealth) 2 minutes after every hour. Alternate schemes
8 are left to the reader to design.
9
10 In general, randomizing events makes it harder to notice them.
11 bf(stealth) may start its tasks at a random point in time if its
12 tt(-i) flag (for em(random interval)) is used. This flag expects an argument
13 in seconds (or in minutes, if at least an tt(m) is appended to the interval
14 specification). Somewhere between the time bf(stealth) starts and the
15 specified interval the scan will commence. For example, the following two
16 commands have identical effects: the scan is started somewhere between the
17 moment bf(stealth) was started and 5 minutes:
18 verb(
19 stealth -i 5min -q /root/stealth/client.pol
20 stealth -i 300 -q /root/stealth/client.pol
21 )
22 When the tt(-d) flag is given, the tt(-i) flag has no effect.
23
24 As another alternative, bf(stealth) my be started specifying the
25 tt(--keep-alive pidfile) option. Here, tt(pidfile) is the name of a file that
26 will contain the process id of the stealth process running in the background.
27 For example:
28 verb(
29 stealth --keep-alive /var/run/stealth -i 300 -q /root/stealth/client.pol
30 )
31 Now, bf(cron)(1) may be used to restart this process at indicated times:
32 verb(
33 2,17,32,47 * * * * root test -x /usr/sbin/stealth && \
34 /usr/sbin/stealth --rerun /var/run/stealth
35 )
36
37 As yet another alternative, the cron-job may activate a script performing
38 bf(stealth)'s rerun, starting another bf(stealth) run if necessary. The
39 advantage of such an approach is that bf(stealth) is automatically started
40 after, e.g., a reboot. The following script expects two arguments (both of
41 which must be absolute paths). The first argument is the path to the
42 em(pidfile) to use, the second argument is the path to the policy file to
43 use. The script is found in the distribution package as
44 tt(/usr/share/doc/stealth/usr/sbin/stealthcron):
45 verbinclude(../../share/usr/sbin/stealthcron)
46 The script could be called from tt(/etc/cron.d/stealth) using a line like
47 verb(
48 22 8 * * * root test -x /usr/sbin/stealthcron && /usr/sbin/stealthcron
49 /var/run/stealth.target /usr/share/stealth/target.pol
50 )
51 Note that the command should be on a single line. It was spread out here
52 over two lines to enhance readability.
0 When bf(stealth) is now run, it will create its initial report files under
1 tt(root/stealth/client).
2
3 The first time bf(stealth) is run, it is usually run `by hand':
4
5 verb(
6 stealth policy
7 )
8 this will show all executed commands on the standard output, and will
9 initialize the reports. Running bf(stealth) this way for the just constructed
10 tt(policy) file results in the following output (lines were wrapped to improve
11 readability):
12 verb(
13 GET /usr/bin/md5sum /root/tmp
14 LABEL \nCheck the client's md5sum program
15 LOCAL CHECK LOG = local/md5 /usr/bin/md5sum /root/tmp/md5sum
16 LABEL \nchecking the client's /usr/bin/find program
17 CHECK LOG = remote/binfind /usr/bin/md5sum /usr/bin/find
18 LABEL \nsuid/sgid/executable files uid or gid root on the / partition
19 CHECK LOG = remote/setuidgid /usr/bin/find / -xdev -perm +u+s,g+s
20 \( -user root -or -group root \) -type f
21 -exec /usr/bin/md5sum {} \;
22 LABEL \nconfiguration files under /etc
23 CHECK LOG = remote/etcfiles /usr/bin/find /etc
24 -type f -not -perm +6111 -not -regex "/etc/\(adjtime\|mtab\)"
25 -exec /usr/bin/md5sum {} \;
26 LOCAL /usr/bin/scp -q root@client:/usr/bin/md5sum /root/tmp
27 LABEL \nCheck the client's md5sum program
28 LOCAL CHECK LOG = local/md5 /usr/bin/md5sum /root/tmp/md5sum
29 LABEL \nchecking the client's /usr/bin/find program
30 CHECK LOG = remote/binfind /usr/bin/md5sum /usr/bin/find
31 LABEL \nsuid/sgid/executable files uid or gid root on the / partition
32 CHECK LOG = remote/setuidgid /usr/bin/find / -xdev -perm +u+s,g+s
33 \( -user root -or -group root \) -type f
34 -exec /usr/bin/md5sum {} \;
35 LABEL \nconfiguration files under /etc
36 CHECK LOG = remote/etcfiles /usr/bin/find /etc
37 -type f -not -perm +6111 -not -regex "/etc/\(adjtime\|mtab\)"
38 -exec /usr/bin/md5sum {} \;
39 )
40
41 This all produces the following output:
42
43 subsect(The mailed report)
44
45 The tt(/root/bin/stealthmail) is called with the following arguments:
46 verb(
47 "Client STEALTH report" admin@elswhere
48 )
49
50 The contents of the mailed report now is (the date will of course change, the
51 next time bf(stealth) is run):
52 verb(
53 STEALTH (1.21) started at Mon Nov 24 10:50:30 2003
54
55 Check the client's md5sum program
56 Initialized report on local/md5
57
58 checking the client's /usr/bin/find program
59 Initialized report on remote/binfind
60
61 suid/sgid/executable files uid or gid root on the / partition
62 Initialized report on remote/setuidgid
63
64 configuration files under /etc
65 Initialized report on remote/etcfiles
66 )
67
68 subsect(Files under /root/stealth/client)
69
70 Under tt(/root/stealth/client) the following entries are now available:
71 itemization(
72
73 it() tt(local): below this directory the reports of the locally performed
74 checks are found. Using our demo tt(policy) file, only one logfile is found
75 here: tt(md5), containing the client's MD5 checksum of its tt(/usr/bin/md5sum)
76 program:
77 verb(
78 45251e259bfaf1951658a7b66c328c52 /root/tmp/md5sum
79 )
80 it() tt(remote): at this directory the reports of the remotely performed
81 checks are found. Using our demo tt(policy) file, three files were created:
82
83 The file tt(binfind), containing the checksum of the client's
84 tt(/usr/bin/find) program:
85 verb(
86 fc62fc774999584f1e29e0f94279a652 /usr/bin/find
87 )
88
89 The file tt(etcfiles), containing the checksums of the client's
90 configuration files under tt(/etc) (shown only partially):
91 verb(
92 ced739ecb2c43a20053a9f0eb308b2b0 /etc/modutils/aliases
93 a2322d7e2f95317b2ddf3543eb4c74c0 /etc/modutils/paths
94 f9e3eac60200d41dd5569eeabb4eddff /etc/modutils/arch/i386
95 f07da2ebf00c6ed6649bae5501b84c4f /etc/modutils/arch/m68k.amiga
96 2893201cc7f7556160fa9cd1fb5ba56a /etc/modutils/arch/m68k.atari
97 ...
98 bf73b4e76066381cd3caf80369ce1d0e /etc/deluser.conf
99 4cd70d9aee333307a09caa4ef003501d /etc/adduser.conf.dpkg-save
100 8c749353c5027d0065359562d4383b8d /etc/gimp/1.2/gtkrc_user
101 3ec404ec597ef5460600cccf0192f4d6 /etc/gimp/1.2/unitrc
102 8c740345b891179228e3d1066291167b /etc/gimp/1.2/gtkrc
103 )
104
105 The file tt(setuidgid), containing the checksums of the client's
106 setuid/setgid root files (shown only partially):
107 verb(
108 030f3f84ec76a8181cca087c4ba655ea /bin/login
109 b6c0209547d88928f391d2bf88af34aa /bin/ping
110 5d324ad212b2ff8f767637ac1a8071ec /bin/su
111 344dbedc398d5114966914419ef53fcc /usr/bin/wall
112 27b045bd7306001f9ea31bc18712d8b7 /usr/bin/rxvt-xpm
113 ...
114 3567b18ffc39c2dc6ec0c0d0fc483f4f /usr/lib/ssh-keysign
115 3383a7955ac2406311e9aa51c6ac9c2c /usr/X11R6/bin/X
116 3c99ea0425c6e0278039e16478d2fb57 /usr/X11R6/bin/xterm
117 d590f7f5b4d6ae61680692a52235d342 /usr/local/bin/setuidcall
118 4c17203d7d91ec4946dea2f0ae365d5b /sbin/unix_chkpwd
119 )
120
121 Of course, the checksums and the filenames shown are only for
122 documentation purposes. At other systems this will show different files and/or
123 checksums, no doubt.
124
125 it() The file tt(/root/client/report) bf(New lines are always appended to
126 the tt(/root/client/report) file. It will never shorten, unless shorten by
127 the systems administrator at `controller').
128
129 This file contains the following:
130 verb(
131 STEALTH (1.21) started at Mon Nov 24 10:50:30 2003
132
133 Check the client's md5sum program
134 Initialized report on local/md5
135
136 checking the client's /usr/bin/find program
137 Initialized report on remote/binfind
138
139 suid/sgid/executable files uid or gid root on the / partition
140 Initialized report on remote/setuidgid
141
142 configuration files under /etc
143 Initialized report on remote/etcfiles
144 )
145 )
146
147 This completes the information created by bf(stealth) during its first run.
0 As bf(stealth) is mainly a system administrator's tool, it could be
1 installed in tt(/usr/local/sbin). In that case, do (as em(root)) from the
2 directory where bf(stealth) was compiled/unpacked:
3 verb(
4 install stealth /usr/local/sbin
5 )
6 options given to bf(install)(1) may restrict further use of bf(stealth).
7
0 Now that bf(stealth) has been compiled, the construction of a policy file has
1 been covered, and a service-account on the client has been defined, what must
2 be done to run bf(stealth) in practice?
3
4 Here's what remains to be done:
5 itemization(
6 it() Install bf(stealth) at a proper location
7 it() Construct one or more policy files
8 it() Learn to interpret bf(stealth)'s output.
9 it() Optionally, automate the removal of old log-files.
10 it() Determine a schedule for running stealth automatically, e.g. using
11 bf(cron)(1)
12 )
13 In this chapter, these topics will be discussed.
14
0 A program like bf(logrotate)(1) allows its users to specify a command or
1 script immediately following log-rotation, and `bf(stealth) tt(--resume
2 pidfile)' could be specified nicely in such a post-rotation section.
3
4 Here is an example of a specification that can be used with
5 bf(logrotate)(1). Logrotate (on Debian systems) keeps its configuration files
6 in tt(/etc/logrotate.d), and assuming there is a host tt(target), whose report
7 file is tt(/var/stealth/target/report), the required bf(logrotate)(1)
8 specification file (e.g., tt(/etc/logrotate.d/target) could be:
9 verbinclude(../../share/etc/logrotate.d/target)
10 Using this specification file, bf(logrotate)(1) will
11 itemization(
12 it() Perform weekly rotations of the report file;
13 it() Keep up to 12 rotated files, compressing them using bf(gzip)(1);
14 it() Before rotating the report file, bf(stealth)'s actions are
15 suppressed;
16 it() Following the rotation, bf(stealth)'s actions are resumed
17 )
18 Note thet tt(stealth --resume xxx) will always start with another file
19 integrity scan.
0 Here we assume that bf(stealth) is run by em(root), and that root wants to
1 store information about the host tt(client) under the subdirectory
2 tt(/root/stealth/client).
3
4 Stealth reports should be sent to the user tt(admin@elsewhere), who is only
5 interested in a short notice of changes, as the full report can always be read
6 elsewhere. So, a support-script is developed to further filter the report
7 generated by bf(stealth).
8
9 As the tt(md5sum) program on the client may be hacked, it is a good idea to
10 transfer the client's tt(md5sum) program to the controller first, in order to
11 check that program locally, before trusting it to compute the md5sums of the
12 client's files. The same holds true for any libraries and support programs
13 (like tt(find)) that are used intensively during integrity scans
14
15 Md5sum checks should be performed on all setuid and setgid files on the
16 tt(client), and in order to be able reach all files on tt(client),
17 tt(root@controller) is allowed to login to the tt(root@client) account using a
18 password-less tt(ssh) connection.
19
20 Furthermore, md5sum checks should be performed on all configuration files,
21 living under tt(/etc) and on the file tt(/usr/bin/find) which is used
22 intensively to perform the checks.
23
24
25 The required tt(policy) file is constructed as follows, per section:
26
27 subsect(the DEFINE directives)
28
29 verb(
30 DEFINE SSHCMD /usr/bin/ssh root@client -T -q exec /bin/bash --noprofile
31 DEFINE EXECMD5 -xdev -perm +u+s,g+s \( -user root -or -group root \) \
32 -type f -exec /usr/bin/md5sum {} \;
33 )
34 The first tt(DEFINE) defines the tt(ssh) command to use: an ssh-connection
35 will be made to the root account at the client.
36
37 The second tt(DEFINE) shows the arguments for bf(find)(1) when looking for
38 all root setuid or setgid normal files. For all these files the bf(md5sum)(1)
39 program should be run.
40
41 subsect(the USE directives)
42
43 verb(
44 USE BASE /root/stealth/client
45 USE EMAIL admin@elswhere
46 USE MAILER /root/bin/stealthmail
47 USE MAILARGS "Client STEALTH report"
48 USE SSH ${SSHCMD}
49 )
50 itemization(
51 it() All output will be written under the tt(/root/stealth/client)
52 directory.
53 it() Mail will be sent to the user tt(admin@elsewhere).
54 it() The mail program will be a script (tt(stealthmail)), living in
55 tt(/root/bin).
56 it() The script handles its own argument. As it can be used for other
57 stealth-scans as well, it is given an argument which can be used as the
58 subject when sending mail, identifying the computer that has been scanned.
59 it() The ssh-command is defined by the tt(SSH-DEFINE).
60 it() the default values of all remaining tt(USE) directives can be used,
61 and were therefore not specified. They are:
62 verb(
63 USE DD /bin/dd
64 USE DIFF /usr/bin/diff
65 USE PIDFILE /var/run/stealth-
66 USE REPORT report
67 USE SH /bin/sh
68 )
69 )
70
71 subsect(the commands)
72
73 First, we'll copy the client's md5sum program to the controller. In practice,
74 this should also include the shared object libraries that are used by md5sum,
75 as they might have become corrupted as well.
76
77 subsubsect(Obtain the client's md5sum program)
78
79 First, the tt(md5sum) program is copied to a local directory
80 verb(
81 GET /usr/bin/md5sum /root/tmp
82 )
83 This command must succeed.
84
85 subsubsect(Check the integrity of the client's md5sum program)
86
87 Next, we'll check the received tt(md5sum) program, using our own:
88 verb(
89 LABEL \nCheck the client's md5sum program
90 LOCAL CHECK LOG = local/md5 /usr/bin/md5sum /root/tmp/md5sum
91 )
92 The tt(LABEL) command will write the label to the report file just before
93 the output of the md5sum program is generated.
94
95 The tt(LOCAL) command will check the md5sum of the program copied from the
96 client. The report is written on the file
97 tt(/root/stealth/client/local/md5). If this fails, the program will not
98 continue, but will alert tt(admin@elsewhere) that the check failed. This is of
99 course rather serious, as it indicates that either the controller's tt(md5sum)
100 is behaving unexpectedly or that the client's tt(md5sum) program has changed.
101
102 The tt(md5sum) program em(may) have changed due to a normal upgrade. If
103 so, tt(admin@elsewhere) will know this, and can (probably) ignore the
104 warning. The next time bf(stealth) is run, the (now updated) MD5 value is
105 used, and it should again match the obtained tt(MD5) value from the copied
106 tt(md5sum) program.
107
108 subsubsect(Check the client's /usr/bin/find command)
109
110 The client will use it's tt(find) command intensively: tt(find) is a great
111 tool for producing files having almost any conceivable combination of
112 characteristics. Of course, the client's tt(find) command itself must be ok,
113 as well as the client's tt(md5sum) program. Now that we know that the client's
114 tt(md5sum) program is ok, we can use it to check the client's tt(/usr/bin/find)
115 program.
116
117 Note that the controller itself will not suffer any processing load here: only
118 the client itself is taxed for checking the intergrity of its own files:
119 verb(
120 LABEL \nchecking the client's /usr/bin/find program
121 CHECK LOG = remote/binfind /usr/bin/md5sum /usr/bin/find
122 )
123
124 subsubsect(Check the client's setuid/setgid files)
125
126 Having checked the client's tt(md5sum) and tt(find) programs, md5 checksum
127 checks should be performed on all setuid and setgid files on the
128 client. For this we activate the tt(md5sum) program on the client. In
129 order to check the setuid/setgid files, the following command is added to the
130 policy file:
131 verb(
132 LABEL \nsuid/sgid/executable files uid or gid root on the / partition
133 CHECK LOG = remote/setuidgid /usr/bin/find / ${EXECMD5}
134 )
135
136 subsubsect(Check the configuration files in the client's /etc/ directory)
137
138 Finally, the client's configuration files are checked. Some of these files
139 change so frequently that we don't want them to be checked. E.g.,
140 tt(/etc/adjtime, /etc/mtab). To check the configuration file, do:
141 verb(
142 LABEL \nconfiguration files under /etc
143 CHECK LOG = remote/etcfiles \
144 /usr/bin/find /etc -type f -not -perm +6111 \
145 -not -regex "/etc/\(adjtime\|mtab\)" \
146 -exec /usr/bin/md5sum {} \;
147 )
148
149 subsect(The complete `policy' file)
150
151 Here is the complete policy file that we've constructed so far:
152
153 verbinclude(running/policy.demo)
154
0 Basically, three kinds of modifications are possible: additions,
1 modifications, and removals. Here we'll show the effect all these changes have
2 on bf(stealth)'s output.
3
4 For the example, the following changes were made to the tt(client)'s
5 files:
6 itemization(
7 it() tt(/etc/motd) was changed
8 it() the file tt(timezone~) was removed
9 it() the file tt(/etc/motd.org) was created
10 )
11
12 Next, bf(stealth) was once again run, producing the following output:
13 itemization(
14 it()
15 The following new info is now added to file tt(/root/client/report):
16 verb(
17 STEALTH (1.21) started at Mon Nov 24 10:54:35 2003
18
19 configuration files under /etc
20 ADDED: /etc/motd.org
21 < 945d0b8208e9861b8f9f2de155e619f9 /etc/motd.org
22 MODIFIED: /etc/motd
23 < 7f96195d5f051375fe7b523d29e379c1 /etc/motd
24 > 945d0b8208e9861b8f9f2de155e619f9 /etc/motd
25 REMOVED: /etc/timezone~
26 > 6322bc8cb3ec53f5eea33201b434b74b /etc/timezone~
27 )
28 Note that all changes were properly detected and logged in the file
29 tt(/root/client/report).
30
31 it() Furthermore, a matching report was sent by em(mail):
32 verb(
33 STEALTH (0.90) started at Mon Oct 28 11:28:43 2002
34
35
36 configuration files under /etc
37 ADDED: /etc/motd.org
38 < 945d0b8208e9861b8f9f2de155e619f9 /etc/motd.org
39 MODIFIED: /etc/motd
40 < 7f96195d5f051375fe7b523d29e379c1 /etc/motd
41 > 945d0b8208e9861b8f9f2de155e619f9 /etc/motd
42 REMOVED: /etc/timezone~
43 > 6322bc8cb3ec53f5eea33201b434b74b /etc/timezone~
44 )
45 Note that the report em(only) shows the info that was added to the
46 em(/root/client/report) file.
47
48 The report itself could be beautified further. I myself use the following
49 script to mail the report to the addressee:
50 verb(
51 #!/bin/bash
52
53 NAME=`basename $0`
54
55 tee /root/stealth/lastreport/$NAME | egrep -v \
56 '^([[:space:]]|[[:space:]]*$)' |
57 sort | uniq | mail -s $1 $2
58 )
59 For the tt(client) computer, this little script will write the mailed
60 report on a file tt(/root/stealth/lastreport/client), overwriting its previous
61 contents, will remove all lines beginning with blanks (thus trimming away the
62 tt(diff)-generated lines), and will mail the tt(sort)ed and tt(uniq)ed lines
63 using tt(mail). The addressee (tt(admin@elsewhere)) will receive the following
64 information:
65 verb(
66 ADDED: /etc/motd.org
67 MODIFIED: /etc/motd
68 REMOVED: /etc/timezone~
69 STEALTH (0.90) started at Mon Oct 28 11:28:43 2002
70 configuration files under /etc
71 )
72 In practice this suffices to have me take action if something out of the
73 ordinary has happened.
74
75 it() Finally, the file
76 verb(
77 /root/stealth/client/remote/etcfiles
78 )
79 was recreated, saving the old file as
80 verb(
81 /root/stealth/client/remote/etcfiles.20021028-112851
82 )
83 As remarked earlier (see section ref(COMMANDS)), many
84 tt(logfile.YYMMDD-HHMMSS) files could eventually accumulate. As discussed in
85 section ref(COMMANDS), it might be considered to remove old log files every
86 now and then.
87 )
88
89 sect(Failing LOCAL commands)
90
91 If the client's tt(md5sum) program itself is altered, a serious situation
92 has developed. In that case, further actions by bf(stealth) would be suspect,
93 as their results might easily be currupted. Checks em(will) proceed, but a
94 warning is generated on the tt(report) file (and in the mail sent to
95 tt(admin@elsewhere):
96 verb(
97 STEALTH (1.21) started at Mon Nov 24 10:54:35 2003
98
99 Check the client's md5sum program
100 MODIFIED: /root/tmp/md5sum
101 < fc62fc774999584f1e29e0f94279a652 /root/tmp/md5sum
102 > 45251e259bfaf1951658a7b66c328c52 /root/tmp/md5sum
103
104 *** BE CAREFUL *** REMAINING RESULTS MAY BE FORGED
105
106 configuration files under /etc
107 REMOVED: /etc/motd.org
108 > 945d0b8208e9861b8f9f2de155e619f9 /etc/motd.org
109 MODIFIED: /etc/motd
110 < 945d0b8208e9861b8f9f2de155e619f9 /etc/motd
111 > 7f96195d5f051375fe7b523d29e379c1 /etc/motd
112 )
113 (The report shows the removal of the previously added file tt(motd.org),
114 and the modification of tt(motd). These are real, as the original tt(motd)
115 file, modified earlier, was restored at this point).
116
0 When bf(stealth) is run again, it will update
1 its report files under tt(root/stealth/client). If nothing has changed,
2 the log-files will remain unaltered. The new run will, however, produce some
3 new info on the file tt(/root/client/report):
4 verb(
5 STEALTH (1.21) started at Mon Nov 24 10:50:30 2003
6
7 Check the client's md5sum program
8 Initialized report on local/md5
9
10 checking the client's /usr/bin/find program
11 Initialized report on remote/binfind
12
13 suid/sgid/executable files uid or gid root on the / partition
14 Initialized report on remote/setuidgid
15
16 configuration files under /etc
17 Initialized report on remote/etcfiles
18
19
20 STEALTH (1.21) started at Mon Nov 24 10:54:35 2003
21 )
22 Note that just one extra line was added: a timestamp showing the date/time
23 of the last run. The systems administrator may reduce/remove the report file
24 every once in a while to reclaim some disk space.
0 DEFINE SSHCMD /usr/bin/ssh root@client -T -q exec /bin/bash --noprofile
1 DEFINE EXECMD5 -xdev -perm +u+s,g+s \( -user root -or -group root \) \
2 -type f -exec /usr/bin/md5sum {} \;
3
4 USE BASE /root/stealth/client
5 USE EMAIL admin@elswhere
6 USE MAILER /root/bin/stealthmail
7 USE MAILARGS "Client STEALTH report"
8 USE SSH ${SSHCMD}
9
10 USE DD /bin/dd
11 USE DIFF /usr/bin/diff
12 USE PIDFILE /var/run/stealth-
13 USE REPORT report
14 USE SH /bin/sh
15
16 GET /usr/bin/md5sum /root/tmp
17
18 LABEL \nCheck the client's md5sum program
19 LOCAL CHECK LOG = local/md5 /usr/bin/md5sum /root/tmp/md5sum
20
21 LABEL \nchecking the client's /usr/bin/find program
22 CHECK LOG = remote/binfind /usr/bin/md5sum /usr/bin/find
23
24 LABEL \nsuid/sgid/executable files uid or gid root on the / partition
25 CHECK LOG = remote/setuidgid /usr/bin/find / ${EXECMD5}
26
27 LABEL \nconfiguration files under /etc
28 CHECK LOG = remote/etcfiles \
29 /usr/bin/find /etc -type f -not -perm +6111 \
30 -not -regex "/etc/\(adjtime\|mtab\)" \
31 -exec /usr/bin/md5sum {} \;
32
33
34
35
0 When bf(stealth) performs integrity scans it will append information to the
1 report file. This file will therefore eventually grow to a large size, and the
2 systems manager controlling bf(stealth) might want to em(rotate) the report
3 file every once in a while (e.g., using a program like bf(logrotate)(1), also
4 see the upcoming section ref(LOGROTATE)). In
5 order to ensure that no log-rotation takes place while bf(stealth) is busy
6 performing integrity scans (thus modifying the report file) the options
7 bf(--suppress) and tt(--resume) were implemented. Both options require the
8 process-ID file of currently active bf(stealth) process as their argument.
9
10 For example, if a bf(stealth) process was once started using the command
11 COMMENT(KEEP A BLANK FOLLOWING THE BACKSLASH)
12 verb(
13 stealth -q --keep-alive /var/run/stealth.small --repeat 900 \
14 /var/stealth/policies/small.pol
15 )
16 then the tt(--suppress) and tt(--resume) commands for this process should
17 be formulated as:
18 verb(
19 stealth --suppress /var/run/stealth.small
20 stealth --resume /var/run/stealth.small
21 )
22 The bf(stealth) process identified in the files provided as arguments to
23 the tt(--suppress) and tt(--resume) options is called the em(targeted stealth
24 process) below.
25
26 The tt(--suppress) option has the following effect:
27 itemization(
28 it() If the targeted bf(stealth) process is currently processing its
29 policy file, performing a (new) integrity scan, then the currently executing
30 policy file command is completed, whereafter further commands are ignored,
31 except for tt(--resume) (see below) and tt(--terminate).
32 it() Any scheduled integrity scans following the tt(--suppress) command
33 are ignored for the targeted bf(stealth) process;
34 it() The targeted bf(stealth) process will write a message that it is
35 being suppressed to the report file and will then process the report file as
36 usual;
37 it() The targeted bf(stealth) process relinquishes its control over the
38 report file;
39 it() The command `bf(stealth) tt(--suppress pidfile)' terminates.
40 )
41 Now that the report file will no longer be affected by the targeted
42 bf(stealth) process, log-rotation may take place. E.g., a program like
43 bf(logrotate)(1) allows its users to specify a command or script just before
44 log-rotation takes place, and `bf(stealth) tt(--suppress pidfile)' could be
45 specified nicely in such a pre-rotation section.
46
47 The tt(--resume) option has the following effect:
48 itemization(
49 it() The targeted bf(stealth) process resumes its activities by performing
50 another integrity scan. Thus, tt(--resume) implies tt(--rerun).
51 it() Any scheduled integrity scans following the tt(--resume) command are
52 again honored by the targeted bf(stealth) process, following the completion of
53 the tt(--resume) command.
54 it() The command `bf(stealth) tt(--resume pidfile)' terminates.
55 )
56 Note that, once tt(--suppress) has been issued, all commands except
57 tt(--resume) and tt(--terminate) are ignored by the targeted bf(stealth)
58 process. While suppressed, the tt(--terminate) command is acknowledged as a
59 `emergency exit' which may or may not interfere with, e.g., an ongoing
60 log-rotation process. The targeted bf(stealth) process should not normally be
61 terminated while it is in its suppressed mode. The normal way to terminate a
62 stealth process running in the background is:
63 itemization(
64 it() Wait for the targeted bf(stealth) process to complete a series of
65 integrity scans;
66 it() Issue the `bf(stealth) tt(--terminate pidfile)' command.
67 )
68
0 Whenever bf(stealth) is run and it encounters a modified situation the
1 already existing status file that is used to summarize that particular
2 situation is saved and a new status file is created. Eventually, this will
3 result in many status files. While report files can be rotated, it is
4 pointless to rotate old status files, since they never are modified. Instead
5 status files exceeding a certain age could be removed and more recent files
6 might be zipped to conserve space. In bf(stealth)'s binary distribution the
7 file tt(/usr/share/doc/stealth/usr/sbin/stealthcleanup) is provided which can
8 be used to perform this cleanup. The script expects one argument: a resource
9 file defining the following shell variables:
10 itemization(
11 it() tt(directories): the directories below which the status files are
12 found;
13 it() tt(gzdays): the number of days a status file must exist before it is
14 compressed using bf(gzip)(1);
15 it() tt(rmdays): the maximum age (in days) of compressed status
16 files. Files exceeding this age are removed using bf(rm)(1).
17 )
18 Here is the tt(stealthcleanup) script as it is found in the binary distribution's
19 tt(/usr/share/doc/stealth/usr/sbin) directory:
20 verbinclude(../../share/usr/sbin/stealthcleanup)
21 Assuming that the status files are written in
22 tt(/var/stealth/target/local) and tt(/var/stealth/target/remote); that status
23 file should be compressed when older than 2 days and removed after 30 days,
24 the resource file is:
25 verbinclude(../../share/etc/stealth/cleanup.rc)
26 Furthermore assuming that the resourcefile is installed in
27 tt(/etc/stealth/cleanup.rc) and the tt(stealthcleanup) script itself in
28 tt(/usr/sbin/stealthcleanup), the tt(stealthcleanup) script could be called
29 as follows:
30 verb(
31 /usr/sbin/stealthcleanup /etc/stealth/cleanup.rc
32 )
33 Note that tt(stealthcleanup) may be called whether or not there are active
34 bf(stealth) processes, as bf(stealth) does not use status files anymore once
35 they have been written.
0
1 COMMENT( Starts a report. The top-level sectioning command is chapter. )
2
3 mailto(f.b.brokken@rug.nl)
4
5 includefile(../../release.yo)
6
7 COMMENT(htmlbodyopt(background)(rcbackground.jpg))
8 htmlbodyopt(text)(#27408B)
9 htmlbodyopt(bgcolor)(#FFFAF0)
10
11 COMMENT(
12 Not required for Yodl >= V2.00
13 includefile(/usr/local/share/yodl/macros)
14 includefile(/usr/local/share/yodl/options)
15 )
16
17 COMMENT( include(abstract) )
18
19 latexoptions(a4paper)
20 latexpackage()(epsf)
21
22 latexcommand(
23 \hfuzz=70pt
24 \addtolength{\textheight}{2cm}
25 \addtolength{\textwidth}{4cm}
26 \addtolength{\hoffset}{-2cm})
27
28 nosloppyhfuzz()
29
30
31 IFDEF(html)
32 (
33 affiliation(center(Computing Center, University of Groningen))
34 report(center(Stealth V. _CurVers_))
35 (center(Frank B. Brokken))(center(_CurYrs_))
36 )
37 (
38 affiliation(Computing Center, University of Groningen)
39 report(Stealth V._CurVers_)(Frank B. Brokken)(_CurYrs_)
40 )
41
42 chapter(Introduction)
43 sect(What's new in Stealth V._CurVers_)
44 includefile(whatsnew)
45
46 sect(Stealth)
47 includefile(intro)
48
49 chapter(Installation)
50 includefile(install/intro)
51
52 chapter(The `policy' file)
53 includefile(policy/intro)
54 includefile(policy/defines)
55 includefile(policy/use)
56 includefile(policy/commands)
57
58 chapter(Granting access)
59 includefile(policy/access)
60
61 chapter(Running `stealth')
62 includefile(running/intro)
63
64 sect(Installing `stealth')
65 includefile(running/installing)
66
67 sect(Construct one or more policy files)
68 includefile(running/makepolicy)
69
70 sect(Running `stealth' for the first time)
71 includefile(running/firstrun)
72
73 sect(Running `stealth' again: all files unaltered)
74 includefile(running/newrunsame)
75
76 sect(Running `stealth' again: modifications have occurred)
77 includefile(running/newrundelta)
78
79 sect(Automating `stealth' runs using `cron')
80 includefile(running/cron)
81
82 lsect(ROTATE)(Report File Rotation)
83 includefile(running/rotate)
84
85 lsubsect(STATUS)(Status file cleanup)
86 includefile(running/status.yo)
87
88 lsubsect(LOGROTATE)
89 (Using `logrotate' to control report- and status files)
90 includefile(running/logrotate.yo)
91
92 chapter(Kick starting `stealth')
93 includefile(kickstart)
94
95 lchapter(USAGE)(Usage info)
96 includefile(usage)
97
98 chapter(Errormessages)
99 includefile(errors)
0 When bf(stealth) is started without arguments, it provides some help about how
1 to start it. A message like the following is produced:
2 verb(
3 stealth by Frank B. Brokken (f.b.brokken@rug.nl)
4
5 stealth V1.40
6 SSH-based Trust Enhancement Acquired through a Locally Trusted Host
7 Copyright (c) GPL 2001-2005
8
9 Usage 1:
10 stealth options policy
11 Where:
12 options: (long options between parentheses) select from:
13 -c: (--parse-config-file) process the config file,
14 no further action, report the results to std output.
15 -d: (--debug) write debug messages to std error
16 -e: (--echo-commands) echo commands to std error when they
17 are processed (implied by -d)
18 -i <interval>[m]: (--random-interval) start the scan between now and
19 a random interval of interval seconds, or minutes
20 if an `m' is appended immediately after the specified interval.
21 -n: (--no-child-processes) no child processes are
22 executed: child actions are faked to be OK.
23 -o: (--only-stdout) scan report is written to stdout. No mail is sent.
24 -q: (--quiet) suppress progress messages to stderr.
25 -r <nr>: (--run-command) only run command <nr> (natural number).
26 -v: (--version): display version information (and exit).
27 --keep-alive pidfile: keep running as a daemon, wake up at interrupts.
28 --repeat <seconds>: keep running as a daemon, wake up at
29 interrupts. or after <seconds> seconds.
30 Requires --keep-alive.
31 --usage: provide this help (and exit)
32 --help: provide this help (and exit)
33 policy: path to the policyfile
34
35 Usage 2:
36 stealth [--rerun|--resume|--suppress|--terminate] pidfile
37 Where:
38 --rerun: restart a stealth integrity scan
39 --resume: resume stealth following --suppress
40 --suppress: suppress stealth activities
41 --terminate: terminate stealth
42 pidfile: file containing the pid of the stealth process to rerun or
43 terminate.
44 )
45 Note that with the second type of usage the policy file is not required:
46 here only the tt(pidfile) must be specified.
0 COMMENT(
1 With 1.40:
2 END COMMENT)
3
4 itemization(
5 it() The tt(-e) (tt(--echo-commands)) option was added to echo commands to
6 std error when they are processed (this option is implied by tt(-d));
7 it() When a command fails (except for commands for which tt(NOTEST) was
8 specified), the reason why the command failed is written to the report file or
9 to the standard error stream;
10 it() The debugging facility is now always available, and does not require
11 recompilation of bf(stealth) anymore.
12 )
13
14 COMMENT(
15 With 1.35:
16
17 Two new options were added to facilitate report-file rotations:
18 itemization(
19 it() tt(--resume pidfile): resume a suppressed bf(stealth)
20 process (implying tt(--rerun));
21 it() tt(--suppress pidfile): suppress a currently active bf(stealth)
22 process. All scheduled scans following tt(--suppress) are skipped,
23 tt(--rerun) is ignored, but tt(--resume) and tt(--terminate)
24 can be issued;
25 )
26 The report file should not be modified while integrity scans take
27 place. The new options were added to make sure this requirement is met when
28 the report file must be rotated. The bf(ssh) connections to clients remain
29 open between pairs of tt(--suppress) and tt(--resume) commands. See section
30 ref(ROTATE) for details about these two options.
31
32 Issues related to suppressing bf(stealth) runs are:
33 itemization(
34 it() cleaning up obsolete status files (section ref(STATUS));
35 it() automating report- and status file rotation using external
36 programs. In section ref(LOGROTATE) a setup is described that can be used with
37 the familiar bf(logrotate)(1) program.
38 it() When upgrading bf(stealth), make sure that all bf(stealth) processes
39 using earlier versions are terminated first.
40 )
41 END COMMENT)
0 #include "monitor.ih"
1
2 // Called by main() once all preliminary actions have been completed. This
3 // function controls the processing of the configuration file.
4
5 void Monitor::control()
6 {
7 while (true)
8 {
9 Util::debug() << "CONTROL: s_mode == " << s_mode << endl;
10
11 d_reporter.standby(); // locks the runfile, opens the report
12 // file
13 processMode();
14 mailReport();
15
16 if (!d_reporter.relax()) // close the report file, unlock the run
17 throw Util::ERROR; // file. If the reporter has set
18 // d_continue to false, then terminate.
19 // This happens when a (remote)
20 // command returns a non-zero exit value.
21 if (s_mode == TERMINATED || s_mode == ONCE)
22 break;
23
24 if (s_mode == SUPPRESSED)
25 {
26 Util::debug() << "Supressed. Now signal the suppressor" << endl;
27
28 ::sleep(1); // This delay is necessary to allow the
29 // suppressor to start waiting once it has
30 // signalled this process. See
31 // Util::signalStealth().
32
33 // let the process that issued
34 // `--suppress' know we're done.
35 Util::sendSignal(SIGUSR1, "SIGUSR1", Util::suppressorPid());
36 Util::debug() << "Wait for --resume..." << endl;
37 }
38
39 do
40 {
41 setDelay();
42 Util::wait();
43 }
44 while (s_mode == SUPPRESSED);
45 }
46 }
0 #include "monitor.ih"
1
2 Monitor::Mode Monitor::s_mode = ONCE;
3 bool Monitor::s_quit = false;
0 #include "monitor.ih"
1
2 // activated by
3 void Monitor::handleProcessSignals(int signum)
4 {
5 switch (signum)
6 {
7 case SIGTERM: // TERMINATE
8 if (s_mode != TERMINATED)
9 {
10 s_quit = true;
11 s_mode = TERMINATE;
12 }
13 break;
14
15 case SIGHUP: // RERUN
16 if (s_mode != KEEP_ALIVE) // wakeup if mode is KEEP_ALIVE
17 return;
18 break;
19
20 case SIGUSR1: // SUPPRESS
21 if (s_mode == KEEP_ALIVE)
22 s_mode = SUPPRESS; // changed to SUPPRESSED in
23 // processMode()
24 break;
25
26 case SIGUSR2: // RESUME
27 if (s_mode == SUPPRESS || s_mode == SUPPRESSED)
28 s_mode = KEEP_ALIVE;
29 break;
30 }
31
32 Util::wakeup();
33 signal(signum, handleProcessSignals);
34 }
0 #include "monitor.ih"
1
2 // Called from control()
3
4 void Monitor::mailReport()
5 {
6 Util::debug() << "Monitor::mailReport() starts" << endl;
7
8 if (!d_reporter.hasMail())
9 {
10 Util::debug() << "no report to mail" << endl;
11 return;
12 }
13
14 d_reporter.rewind(); // resets the `hasmail' variable
15
16 if (Arg::instance().option("o")) // mail the report to stdout
17 {
18 Util::debug() << "Monitor::mailReport() mails report to stdout" <<
19 endl;
20 cout << d_reporter.rdbuf() << endl;
21 return;
22 }
23
24 Util::debug() << "mailing report using: " << d_sorter["MAILER"] <<
25 " " << d_sorter["MAILARGS"] << " " << d_sorter["EMAIL"] << endl;
26
27 // mailcommand subject and email are called as separate arguments
28 // If subject contains blanks, they will be interpreted as separate
29 // arguments by the `mail' IOFork. Ususally d_sorter["MAILER"] will
30 // call a script.
31
32 Process mail(5, d_sorter["MAILER"] + " " + d_sorter["MAILARGS"] +
33 " " + d_sorter["EMAIL"], Process::CIN |
34 Process::IGNORE_COUT |
35 Process::IGNORE_CERR);
36
37 mail.start(Process::USE_SHELL);
38
39 for (string s; getline(d_reporter.in(), s); )
40 {
41 Util::debug() << "Monitor::mailReport() contains: " << s << endl;
42 mail << s << endl;
43 }
44
45 Util::debug() << "Mailing report" << endl;
46 }
0 #include "monitor.ih"
1
2 /*
3 Since the Monitor's destruction is also the termination of the program, no
4 explicit destruction of the newly created objects is necessary. A pointer is
5 used to prevent the construction of a constant object. As the constructor
6 itself would create a constant object, the construction *new...
7 is used.
8
9 */
10
11 Monitor::Monitor(ConfigSorter &sorter, Reporter &reporter, Scanner &scanner)
12 :
13 d_scanner(scanner),
14 d_sorter(sorter),
15 d_reporter(reporter)
16 {
17 if (Util::keepAlive())
18 s_mode = KEEP_ALIVE;
19
20 d_scanner.preamble();
21
22 signal(SIGHUP, Monitor::handleProcessSignals);
23 signal(SIGTERM, Monitor::handleProcessSignals);
24 signal(SIGUSR1, Monitor::handleProcessSignals);
25 signal(SIGUSR2, Monitor::handleProcessSignals);
26 }
27
0 #ifndef _INCLUDED_MONITOR_H_
1 #define _INCLUDED_MONITOR_H_
2
3 namespace FBB
4 {
5 class Reporter;
6 class Scanner;
7 class ConfigSorter;
8
9
10 class Monitor
11 {
12 enum Mode
13 {
14 ONCE, // 0 single run
15 KEEP_ALIVE, // 1 multiple runs
16 TERMINATE, // 2 through SIGTERM
17 TERMINATED, // 3 automatically following TERMINATE
18 SUPPRESS, // 4 through SIGUSR1 (SIGUSR2: back to normal)
19 SUPPRESSED, // 5 automatically following SUPPRESS
20 };
21
22 static Mode s_mode;
23 static bool s_quit; // passed to Scanner::run() for
24 // inspection
25
26 Scanner &d_scanner;
27 ConfigSorter &d_sorter;
28 Reporter &d_reporter;
29
30
31 public:
32 Monitor(ConfigSorter &sorter, Reporter &reporter,
33 Scanner &scanner);
34
35 void control(); // control the scanning process
36 void mailReport(); // mail the report to the responsible
37 // person
38
39 static void handleProcessSignals(int signum);
40
41 private:
42
43 void processMode(); // process the current mode
44
45 void setDelay(); // set delay interval matching the
46 // current mode.
47 };
48
49 }
50 #endif
0 #include "monitor.h"
1
2 #include <signal.h>
3 #include <bobcat/arg>
4 #include <bobcat/process>
5
6 #include "../util/util.h"
7 //#include "../iofork/iofork.h"
8 #include "../configsorter/configsorter.h"
9 #include "../reporter/reporter.h"
10 #include "../scanner/scanner.h"
11
12 using namespace FBB;
13 using namespace std;
14
15
0 #include "monitor.ih"
1
2 void Monitor::processMode()
3 {
4 while (true)
5 {
6 switch (s_mode)
7 {
8 case TERMINATE:
9 d_reporter <<
10 "STEALTH was terminated after " << d_scanner.nScans() <<
11 " scans at " << Util::date << endl;
12 s_mode = TERMINATED;
13 return;
14
15 case SUPPRESS:
16 d_reporter <<
17 "STEALTH was suppressed after " << d_scanner.nScans() <<
18 " scans at " << Util::date << endl;
19 s_mode = SUPPRESSED;
20 return;
21
22 case TERMINATED:
23 case SUPPRESSED:
24 return;
25
26 default:
27 d_scanner.run(&s_quit);
28
29 if (s_mode == TERMINATE || s_mode == SUPPRESS)
30 continue;
31 return;
32 }
33 }
34 }
0 #include "monitor.ih"
1
2 void Monitor::setDelay()
3 {
4 switch (s_mode)
5 {
6 case KEEP_ALIVE:
7 Util::setAlarm();
8 break;
9
10 case TERMINATE:
11 Util::wakeup();
12 break;
13
14 case SUPPRESS:
15 case SUPPRESSED:
16 Util::sleep();
17 break;
18
19 default:
20 break;
21 }
22 }
0 #include "reporter.ih"
1
2 string Reporter::s_msg;
0 /*
1 demo.cc
2
3 g++ demo.cc -L../.. -lstealth | & less
4 */
5
6 #include "demo.h"
7
8 int main(int argc, char **argv, char **envp)
9 {
10 try
11 {
12 Reporter rep("report");
13
14 rep << Util::date << ": Hello world\n";
15
16 rep.reset();
17
18 string s;
19
20 cout << "========= 0 ===========\n";
21
22 while (getline(rep, s))
23 cout << "Added: " << s << endl;
24
25 cout << "========= 1 ===========\n";
26
27 sleep(5);
28
29 rep.reinit(); // make sure we can add new info
30 // as a new run
31
32 // insert info
33 rep << Util::date << ": Hello world (2nd time)\n";
34
35 rep.reset(); // reset the stream to read it again
36
37 while (getline(rep, s))
38 cout << "Added: " << s << endl;
39
40 cout << "========= 2 ===========\n";
41
42 return 0;
43 }
44 catch(Errno &e)
45 {
46 cerr << "Exception " << e.why() << endl;
47 return 1;
48 }
49 }
0 // demo.h
1
2 #ifndef _H_demo_
3 #define _H_demo_
4
5 /*
6 $Id: demo.h,v 1.3 2003-11-30 14:21:29 frank Exp $
7
8 $Log: demo.h,v $
9 Revision 1.3 2003-11-30 14:21:29 frank
10 Adding facilities to reuse the Reporter: reinit() re-initializes the reporter
11 but note that reset() must still be used. See demo/demo.cc for an example
12
13 Revision 1.2 2003/06/20 18:58:14 frank
14 Changes are recorded in stealth/debian/changelog
15
16 */
17
18 //#include <iosfwd>
19 #include <iostream>
20 //#include <fstream>
21 #include <string>
22 //#include <sstream>
23
24 #include "../reporter.h"
25 #include "../../util/util.h"
26 #include "../../errno/errno.h"
27
28 using namespace FBB;
29 using namespace std;
30
31 #endif
0 #include "reporter.ih"
1
2 ostream &Reporter::exit()
3 {
4 setOnce();
5 d_continue = false;
6 return *this;
7 }
8
9
10
11
12
0 #include "reporter.ih"
1
2 // This function is called from standby(), and (re)inits the report file. It
3 // remembers its initial size, writes the header and sets `d_hasMail' to
4 // false. New entries inserted into the report file will automatically set
5 // `d_hasMail' to true.
6
7 void Reporter::reinit()
8 {
9 Util::debug() << "Reinit the reporter" << endl;
10
11 d_out.clear();
12
13 struct stat statbuf;
14
15 if (stat(d_name.c_str(), &statbuf))
16 throw Errno("Can't stat ") << insertable << d_name << throwable;
17
18 d_sizeAtConstruction = statbuf.st_size;
19
20 Util::debug() << "Reinit next report starts at " <<
21 d_sizeAtConstruction << endl;
22
23 *this << "\n"
24 "STEALTH (" << Util::getVersion() << ") started at " <<
25 Util::date << "\n" << endl;
26
27 d_hasMail = false;
28 }
29
30
0 #include "reporter.ih"
1
2 bool Reporter::relax()
3 {
4 flush();
5 d_out.close();
6 Util::unlockRunFile(); // release the lock on an existing
7 // run file.
8 return d_continue; // inform the monitor about the
9 // need to continue
10 }
11
12
13
14
15
0 #include "reporter.ih"
1
2 Reporter::Reporter(string const &name)
3 :
4 MultiStreambuf(cerr, RESET),
5 ostream(this), // initialize the ostream with the MultiStreambuf
6 d_name(name),
7 d_continue(true),
8 d_hasMail(false)
9 {
10 insert(d_out); // insertions go to the report
11
12 // no further initialization of the Reporter is required here. In
13 // particular, the logfile is not yet opened. I wait until Stealth
14 // runs (maybe) in the background. The Monitor will then lock the runfile
15 // and start logging.
16 }
17
18
19
20
0 #ifndef _REPORTER_H_
1 #define _REPORTER_H_
2
3 #include <fstream>
4 #include <bobcat/multistreambuf>
5
6 namespace FBB
7 {
8 class Reporter: private MultiStreambuf, public std::ostream
9 {
10 static std::string s_msg;
11
12 unsigned long d_sizeAtConstruction;
13 std::string d_name;
14 bool d_continue;
15 bool d_hasMail;
16
17 std::fstream d_out;
18
19 public:
20 Reporter(std::string const &name);
21
22 void rewind(); // rewind to the position when
23 // Reporter was constructed or at
24 // the last reinit(). Information inserted
25 // after calling this member will be
26 // extracted
27
28 std::istream &in()
29 {
30 return d_out;
31 }
32
33 bool hasMail() const
34 {
35 return d_hasMail;
36 }
37
38 bool relax(); // close the report file, release a
39 // runfile lock, returns d_continue
40
41 void standby(); // obtain a runfile lock, open the report
42 // file
43
44 std::ostream &exit(); // inserts a message and prepares for
45 // exit. The error message is also written
46 // to stderr. Once `sync()' is called,
47 // ERROR is thrown.
48 private:
49 virtual int sync();
50
51 void reinit();
52
53 Reporter(Reporter const &other); // NI
54 Reporter &operator=(Reporter const &other); // NI
55 };
56 }
57 #endif
0 #include "reporter.h"
1
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <unistd.h>
5 #include <bobcat/errno>
6
7 #include "../util/util.h"
8
9 using namespace FBB;
10 using namespace std;
0 #include "reporter.ih"
1
2 void Reporter::rewind()
3 {
4 unsigned long currentSize = d_out.tellg();
5 d_out.seekg(d_sizeAtConstruction, ios::beg);
6
7 d_sizeAtConstruction = currentSize;
8 d_hasMail = false;
9 }
10
0 #include "reporter.ih"
1
2 // This function is called from Monitor::control() at the beginning of the
3 // configuration processing loop. It (re)opens the report file and prepares it
4 // for the next run.
5
6 void Reporter::standby()
7 {
8 if (!Util::lockRunFile(Util::NONBLOCKING)) // wait for the lock on an
9 return; // existing run file.
10 // No run file: no lock
11
12 d_out.open(d_name.c_str(), ios::out | ios::ate | ios::in);
13 if (!d_out.is_open())
14 {
15 d_out.clear();
16 d_out.open(d_name.c_str(), ios::out | ios::in | ios::trunc);
17 // open if construction
18 } // fails: new file
19
20 if (!d_out.is_open())
21 throw Errno("Can't open ") << insertable << d_name << throwable;
22
23 reinit();
24 }
25
26
27
28
29
0 #include "reporter.ih"
1
2 int Reporter::sync()
3 {
4 d_hasMail = true;
5 return MultiStreambuf::sync();
6 }
0 #include "scanner.ih"
1
2 // SEE ALSO THE MEMBER waitForSentinel()
3
4 void Scanner::copy(std::istream &src, string const &fname)
5 {
6 ofstream currentReport(fname.c_str());
7
8 if (!currentReport)
9 d_reporter.exit() << "Can't open `" << fname << "' to write" << endl;
10
11 string s;
12
13 Util::debug() << "Scanner::copy(): about to read child input " << endl;
14
15 while (getline(src, s))
16 {
17 Util::debug() << "copy SAW: `" << s << "'" << endl;
18
19 if (s.find(d_sentinel) == 0)
20 {
21 Util::debug() << "GOT Sentinel" << endl;
22 break;
23 }
24 currentReport << s << endl;
25 }
26 testExitValue(s);
27 }
28
29
30
31
32
0 #include "scanner.ih"
1
2 Pattern Scanner::s_split("(\\S+)\\s*$");
3 Pattern Scanner::s_firstWord("(\\S+)(\\s+(.*))?");
0 #include "scanner.ih"
1
2 bool Scanner::doCHECKcommand(Process &child)
3 {
4 removeLOG(); // remove optional 'LOG ='
5
6 string logfile = s_firstWord[1]; // CHECK keywords are followed by
7 // the name of a logfile
8
9 s_firstWord.match(s_firstWord[3]); // redefine s_firstWord: 1st word
10 // removed
11
12 Util::debug() << "running checked command: `" << s_firstWord[0] << "'"
13 << endl;
14
15 if (Arg::instance().option('n')) // -n (no go) option?
16 return true; // then indicate by implication that
17 // the command was processed without
18 // differing from the previous run
19
20 nextCommand(child, // otherwise run the command
21 s_firstWord[0]);
22
23
24 // and return whether there are any
25 // differences.
26 return sameOutput(logfile, child);
27 }
0 #include "scanner.ih"
1
2 void Scanner::doPlainCommand(Process &child)
3 {
4 Util::debug() << "running unchecked command: `" << s_firstWord[0] << "'"
5 << endl;
6
7 if (!Arg::instance().option('n')) // unless -n (no execute commands)
8 {
9 nextCommand(child, // start the next command
10 s_firstWord[0]);
11
12 waitForSentinel(child); // read its output
13 }
14 }
0 #include "scanner.ih"
1
2 // receives the next command to execute
3 void Scanner::execute(string const &cmd)
4 {
5 if (!(s_firstWord << cmd)) // determine first word and the rest
6 d_reporter.exit() << "Corrupt line in policy file: " << cmd << endl;
7
8 if (Arg::instance().option("de"))// echo the command with -d, -e
9 cerr << *d_cmdIterator << endl;
10
11 if (s_firstWord[1] == "LABEL") // set a label
12 {
13 d_label = s_firstWord[3]; // the text beyond the LABEL keyword
14 Util::replace(d_label, // change \\n into newlines
15 "\\n", "\n");
16 }
17 else if (s_firstWord[1] == "LOCAL") // run a local command
18 local(s_firstWord[3]);
19 else if (s_firstWord[1] == "GET") // get a file from the client
20 get(cmd);
21 else if (s_firstWord[1] == "PUT") // put a file to the client
22 put(cmd);
23 else // or run a remote command
24 remote(cmd);
25 }
26
0 #include "scanner.ih"
1
2 // Command forms:
3 // GET remote-file local-file
4 // GET NOTEST remote-file local-file
5
6 void Scanner::get(string const &cmd)
7 {
8 Util::debug() << "Scanner::get(): " << cmd << endl;
9
10 removeFirstWord("GET"); // strip off `GET'
11
12 d_testExitValue = !removeFirstWord("NOTEST"); // [NOTEST] ...
13
14 // at this point we have the remote-file and the local-file in the
15 // command. d_firstword[1] contains the remote filename,
16 // d_firstword[3] contains the rest
17
18
19 string source = s_firstWord[1]; // get the (remote) source
20
21 if (!source.length())
22 d_reporter.exit() << "GET command requires source and destination" <<
23 endl;
24
25
26 s_firstWord.match(s_firstWord[3]); // strip off source
27 string destination = s_firstWord[1]; // get the local dest.
28
29 if (!destination.length())
30 d_reporter.exit() <<
31 "At `GET " << source << " <destination>': destination missing" <<
32 endl;
33
34 if (Util::isDirectory(destination)) // is the dest. a dir. ?
35 destination += "/" + Util::fileName(source);
36
37
38 Util::debug() << "Scanner::get(): scp <client>:" << source << " " <<
39 destination << endl;
40
41 if (Arg::instance().option('n')) // no run if -n
42 return;
43
44 nextCommand(d_sshFork, // start the next command
45 d_sorter["DD"] + " if=" + source);
46
47 read(d_sshFork, destination); // read its output, tests exit value
48
49 Util::debug() << "Scanner::get(): " << cmd << " DONE" << endl;
50 }
51
52
0 #include "scanner.ih"
1
2 void Scanner::killChildren()
3 {
4 d_sshFork.stop();
5 d_shFork.stop();
6 }
0 #include "scanner.ih"
1
2 /*
3 At this point: LOCAL was seen. Beyond that, we must see:
4
5 NOTEST CHECK ...
6 CHECK ...
7 NOTEST ...
8 ...
9
10 NOTEST means the return value of the command is not tested
11 CHECK means that the output is compared with a former log
12 */
13
14 void Scanner::local(string const &s_firstWord2)
15 {
16 Util::debug() << "Command Run At The Controller\n";
17
18 s_firstWord.match(s_firstWord2); // what's beyond `LOCAL' ?
19
20 // set d_testExitValue
21 d_testExitValue = !removeFirstWord("NOTEST"); // according to !NOTEST
22
23 if (removeFirstWord("CHECK")) // ... CHECK ...
24 {
25 if (!doCHECKcommand(d_shFork)) // so, do the command
26 d_reporter << endl // and check the result
27 << "*** BE CAREFUL *** REMAINING RESULTS MAY BE FORGED" << endl
28 << endl;
29 }
30 else
31 doPlainCommand(d_shFork); // do unchecked command
32 }
33
34
0 #include "scanner.ih"
1
2 void Scanner::nextCommand(ostream &out, string const &command)
3 {
4 Util::debug() << "Scanner::nextCommand(): inserting\n" << command << \
5 "\nand: echo " << d_sentinel << " $?" << endl;
6
7 // run the command, then
8 // echo the sentinel and returnvalue
9 out << command << endl <<
10 "/bin/echo \"" << d_sentinel << " $?\"" << endl;
11
12 if (!out)
13 d_reporter.exit() <<
14 "Inserting command `" << s_firstWord[0] << "' failed." << endl;
15 }
0 #include "scanner.ih"
1
2 /*
3 Example of diff-output:
4
5 33c33
6 < 90d8b506d249634c4ff80b9018644567 out
7 ---
8 > b88d0b77db74cc4a742d7bc26cdd2a1e out
9
10 */
11
12 bool Scanner::noDifferences(std::string const &current,
13 std::string const &logfile)
14 {
15 Util::debug() << "Scanner::noDifferences(): started " <<
16 d_sorter["DIFF"] << " " << current << " " << logfile << endl;
17
18 d_shFork << d_sorter["DIFF"] << " " << current << " " << logfile <<
19 endl <<
20 "/bin/echo \"" << d_sentinel << "\"" << endl;
21
22 HashString< pair<string, vector<string> > > status;
23
24 Util::debug() << "Scanner::noDifferences(): " << "/bin/echo " <<
25 d_sentinel << endl;
26
27 // key is string, case sensitive.
28 //
29 // The last element of the lines produced by diff is used as the
30 // key. For the current function to operate sensibly, this should be
31 // a filename or path/file.
32 //
33 // If the first character of the line is a < or >, then a modification is
34 // detected: for these lines the following happens:
35 // 1. If the key already existed its .first element is set to
36 // "modified". If the key didn't exist yet, it is set to
37 // "added" at at '<', and to "removed" at a '>'
38 // 2. The line itself is pushed back to the .second (vector) element
39 // of the pair.
40 //
41 // At the end, if the hashtable has any elements, the table is inserted
42 // into the d_reporter and `false' is returned. If the hashtable contains
43 // no elements, 'true' is returned.
44
45 string s;
46 // Pattern split("(\\S+)\\s*$");
47
48 Util::debug() << "Scanner::noDifferences(): starting to read lines" <<
49 endl;
50
51 // if (!d_shFork.available())
52 // d_reporter.exit() << "`" << d_sorter["SH"] << "': no output from " <<
53 // d_sorter["DIFF"] << endl;
54
55 while (getline(d_shFork, s))
56 {
57 Util::debug() << "Scanner::noDifferences(): got: `" << s <<
58 "'\n" <<
59 "Scanner::noDifferences(): sentinel: `" << d_sentinel <<
60 "'" << endl;
61 if (s == d_sentinel)
62 break;
63
64 if (!(s_split << s))
65 continue; // no match at empty lines ?
66
67 string key = s_split[1]; // get the key
68 bool exists = status.count(key);
69
70 if (s[0] == '>') // removal, e.g., > b88d0b.... out
71 status[key].first = exists ? "MODIFIED" : "REMOVED";
72 else if (s[0] == '<')
73 status[key].first = exists ? "MODIFIED" : "ADDED";
74 else
75 continue;
76
77 status[key].second.push_back(s);
78 }
79
80 if (!status.size()) // no elements ?
81 {
82 Util::debug() << "no differences were observed" << endl;
83
84 rename(current.c_str(), logfile.c_str()); // install `logfile'
85 return true; // nothing to report
86 }
87
88 if (d_label.length())
89 d_reporter << d_label << endl;
90
91 for
92 (
93 HashString< pair<string, vector<string> > >::iterator
94 begin = status.begin(), end = status.end();
95 begin != end;
96 begin++
97 )
98 {
99 d_reporter << begin->second.first << ": " << begin->first << endl;
100
101 for
102 (
103 vector<string>::iterator
104 sbegin = begin->second.second.begin(),
105 send = begin->second.second.end();
106 sbegin != send;
107 sbegin++
108 )
109 d_reporter << " " << *sbegin << endl;
110 }
111
112 string
113 datetime = logfile + "." + Util::datetime();
114
115 rename(logfile.c_str(), datetime.c_str());
116 rename(current.c_str(), logfile.c_str()); // install `logfile'
117
118 Util::debug() << "differences were observed: see `" <<
119 d_sorter["REPORT"] << "' and `" << logfile << "'" << endl;
120
121 return false;
122 }
123
124
0 #include "scanner.ih"
1
2 void Scanner::preamble()
3 {
4 d_sshFork.start(); // start the ssh connection
5 d_shFork.start(); // start the sh-connection to the localhost
6
7 // try to echo a sentinel by having
8 // the ssh connection echo it
9 Util::debug() << "Inserting " << d_sentinel << " into " <<
10 d_sorter["SSH"] << endl;
11
12 d_sshFork << "/bin/echo \"" << d_sentinel << "\"" << endl;
13
14 Util::debug() << "Waiting for " << d_sentinel << " from " <<
15 d_sorter["SSH"] << endl;
16
17 d_testExitValue = false;
18 waitForSentinel(d_sshFork); // continue after reading
19
20 Util::debug() << d_sorter["SSH"] << " appears to be functioning well\n";
21 }
0 #include "scanner.ih"
1
2 // Command forms:
3 // PUT local-file remote-file
4 // PUT NOTEST local-file remote-file
5
6 void Scanner::put(string const &cmd)
7 {
8 Util::debug() << "Scanner::put(): " << cmd << endl;
9
10 removeFirstWord("PUT"); // strip off `PUT'
11
12 d_testExitValue = !removeFirstWord("NOTEST"); // [NOTEST] ...
13
14 // at this point we have the remote-file and the local-file in the
15 // command. d_firstword[1] contains the remote filename,
16 // d_firstword[3] contains the rest
17
18
19 string source = s_firstWord[1]; // get the (remote) source
20 if (!source.length())
21 d_reporter.exit() << "PUT command requires source and destination" <<
22 endl;
23
24 s_firstWord.match(s_firstWord[3]); // strip off source
25
26 string destination = s_firstWord[1]; // get the local dest.
27 if (!destination.length())
28 d_reporter.exit() << "At `PUT " << source <<
29 " <destination>': destination missing" << endl;
30
31 if (Util::isDirectory(destination)) // is the dest. a dir. ?
32 destination += "/" + Util::fileName(source);// then append sourcename
33
34
35 Util::debug() << "Scanner::put(): scp <client>:" << source << " " <<
36 destination << endl;
37
38 string command = putCommand(source, destination);
39
40 if (Arg::instance().option('n')) // no run if -n
41 return;
42
43 d_sshFork << command << endl;
44
45 write(source); // write the file using dd
46
47 d_sshFork << "/bin/echo \"" << d_sentinel << " $?\"" << endl;
48
49 waitForSentinel(d_sshFork);
50
51 Util::debug() << "Scanner::put(): " << cmd << " DONE" << endl;
52 }
53
54
55
56
57
58
59
60
0 #include "scanner.ih"
1
2 string Scanner::putCommand(string const &source,
3 string const &destination) const
4 {
5 struct stat statbuf;
6
7 if (stat(source.c_str(), &statbuf))
8 d_reporter.exit() << "PUT " << source << ": can't stat it" << endl;
9
10 ostringstream command;
11
12 command << d_sorter["DD"] << " of=" << destination <<
13 " bs=1 count=" << statbuf.st_size;
14
15 return command.str();
16 }
17
18
19
20
21
22
23
24
0 #include "scanner.ih"
1
2 // SEE ALSO THE MEMBER waitForSentinel()
3
4 void Scanner::read(std::istream &src, string const &fname)
5 {
6 ofstream target(fname.c_str());
7
8 if (!target)
9 d_reporter.exit() << "Can't open `" << fname << "' to write" << endl;
10
11 Util::debug() << "Scanner::read(): about to read child input" << endl;
12
13 char c;
14 string partialSentinel;
15
16 size_t length = 0;
17 while (true)
18 {
19 src.read(&c, 1); // read char by char
20
21 if (c == d_sentinel[length]) // got next sentinel char
22 {
23 length++;
24
25 if (length == d_sentinel.length()) // matched the sentinel
26 {
27 Util::debug() << "GOT Sentinel" << endl;
28
29 string tail; // get the end-chars as well
30 getline(src, tail);
31 partialSentinel += tail;
32
33 break; // so we're done.
34 }
35 partialSentinel += c; // append c to the partial Sentinel
36 }
37 else
38 {
39 if (length)
40 {
41 target.write(partialSentinel.c_str(), length);
42 partialSentinel.erase();
43 length = 0;
44 }
45 target.write(&c, 1);
46 }
47 }
48 testExitValue(partialSentinel);
49 }
50
51
0 #include "scanner.ih"
1
2 void Scanner::remote(string const &cmd)
3 {
4 Util::debug() << "REMOTE: Command Run At The Client" << endl;
5
6 d_testExitValue = !removeFirstWord("NOTEST"); // [NOTEST] ...
7
8 if (removeFirstWord("CHECK")) // ... CHECK ...
9 doCHECKcommand(d_sshFork);
10 else
11 doPlainCommand(d_sshFork);
12
13 Util::debug() << "Scanner::remote(): " << cmd << " DONE" << endl;
14 }
0 #include "scanner.ih"
1
2 bool Scanner::removeFirstWord(char const *word)
3 {
4 if (s_firstWord[1] != word)
5 return false;
6
7 s_firstWord.match(s_firstWord[3]); // make sure firstword[1] now contains
8 // the next word (of d_firstword[3])
9 return true;
10 }
0 #include "scanner.ih"
1
2 void Scanner::removeLOG()
3 {
4 string matched = s_firstWord[0]; // complete matched text
5
6 if (matched.find("LOG") == 0) // LOG at the beginning
7 {
8 size_t pos = matched.find_first_not_of(" \t", 3);
9
10 // got '=', so we got 'LOG ='
11 // remove 'LOG =' and proceed.
12 if (pos != string::npos && matched[pos] == '=')
13 {
14 Util::debug() << "removed `LOG =', kept `" <<
15 matched.substr(pos + 1) << "'" << endl;
16 s_firstWord.match(matched.substr(pos + 1));
17 }
18 else
19 Util::debug() << "LOG is (partial) logname in `" <<
20 matched << "'" << endl;
21 }
22 else
23 Util::debug() << "No `LOG =' in CHECK command `" << matched << "'" <<
24 endl;
25 }
26
0 #include "scanner.ih"
1
2 void Scanner::run(volatile bool const *quit)
3 {
4 ++d_nScans;
5
6 setSentinel(); // determine d_sentinel
7
8 d_cmdIterator = d_sorter.firstCmd(); // d_cmdIterator is set to
9 // the first command. It's a
10 // true iterator, so we can
11 // add values to it, below.
12 string cmdNr;
13
14 if (Arg::instance().option(&cmdNr, 'r')) // is there a command number?
15 {
16 // if so, add its number to
17 // d_cmdIterator
18 d_cmdIterator += atoi(cmdNr.c_str()) - 1;
19 execute(*d_cmdIterator); // and execute that command
20 }
21 else // no number: process all
22 { // commands
23 for
24 (
25 vector<string>::const_iterator beyond = d_sorter.beyondCmd();
26 d_cmdIterator != beyond;
27 d_cmdIterator++
28 )
29 {
30 if (*quit)
31 break;
32 execute(*d_cmdIterator);
33 }
34 }
35
36 if (Util::debug())
37 cerr << "Stealth: policy file processed\n";
38 }
39
40
0 #include "scanner.ih"
1
2 bool Scanner::sameOutput(string const &logfile, istream &extractor)
3 {
4 string current = logfile + ".cur"; // create current logfile
5
6 if (!Util::mkdir(current)) // make sure directory exists
7 d_reporter.exit() << "Unable to create the logfile `" <<
8 current << "'" << endl;
9
10 Util::debug() << "Scanner::sameOutput(): logs to " << current << endl;
11
12 copy(extractor, current); // copy the info in extractor
13 // to the current logfile
14
15 if (access(logfile.c_str(), R_OK)) // no old report yet
16 {
17 Util::debug() << "writing new report: " << logfile << endl;
18
19 rename(current.c_str(), logfile.c_str()); // install `logfile'
20
21 if (d_label.length())
22 {
23 Util::debug() << "Scanner::sameOutput(): writing label: " <<
24 d_label << endl;
25 d_reporter << d_label << endl;
26 }
27
28 d_reporter << "Initialized report on " << logfile << endl;
29 Util::debug() << "Scanner::sameOutput(): Initialized report on " <<
30 logfile << endl;
31 return true;
32 }
33
34 Util::debug() << "comparing new integrity scan results to: `" <<
35 logfile << "'" << endl;
36
37 return noDifferences(current, logfile); // return true if there aren't any
38 // differences.
39 }
40
41
42
43
0 #include "scanner.ih"
1
2 /*
3 Since the Scanner's destruction is also the termination of the program, no
4 explicit destruction of the newly created objects is necessary. A pointer is
5 used to prevent the construction of a constant object. As the constructor
6 itself would create a constant object, the construction *new...
7 is used.
8
9 */
10
11 Scanner::Scanner(ConfigSorter &sorter, Reporter &reporter)
12 :
13 d_sorter(sorter),
14 d_reporter(reporter), // ostream
15 d_firstWord(*new Pattern("(\\S+)(\\s+(.*))?")),// firstword ([1]) and
16 // the rest ([3]) of a text
17 d_sshFork
18 (
19 d_sorter["SSH"],
20 Process::CIN | Process::COUT | Process::IGNORE_CERR
21 ), // child: ignores stderr, reads
22 d_shFork
23 (
24 d_sorter["SH"],
25 Process::CIN | Process::COUT | Process::IGNORE_CERR
26 ), // from stdin/stdout
27 // parent process communicates
28 // via the Fork object's
29 // stream interface.
30 d_nScans(0)
31 {
32 setSentinel();
33 }
34
0 #ifndef _INCLUDED_SCANNER_H_
1 #define _INCLUDED_SCANNER_H_
2
3 #include <string>
4 #include <vector>
5 #include <iosfwd>
6 #include <bobcat/process>
7 // #include "../iofork/iofork.h"
8
9 namespace FBB
10 {
11 class ConfigSorter;
12 class Reporter;
13 class Pattern;
14
15 class Scanner
16 {
17 ConfigSorter &d_sorter;
18 Reporter &d_reporter;
19 Pattern &d_firstWord;
20 Process d_sshFork;
21 Process d_shFork;
22 std::string d_sentinel;
23 std::string d_label;
24 std::vector<std::string>::const_iterator d_cmdIterator;
25 bool d_testExitValue;
26 size_t d_nScans;
27
28 static Pattern s_split;
29 static Pattern s_firstWord;
30
31 public:
32 Scanner(ConfigSorter &sorter, Reporter &reporter);
33 size_t nScans() const
34 {
35 return d_nScans;
36 }
37 void preamble();
38 // run one series of tests
39 void run(volatile bool const *done);
40
41
42 void killChildren();
43
44 private:
45 // copy a textfile
46 void copy(std::istream &src, std::string const &fname);
47
48 // executes a command, and compares
49 // its output to previously
50 // generated output. Returns true if
51 // the outputs are identical
52 bool doCHECKcommand(Process &child);
53
54 // executes a command, without
55 // comparing its output to previously
56 // generated output
57 void doPlainCommand(Process &child);
58
59 // execute the command from d_sorter
60 void execute(std::string const &command);
61
62 // get a remote file
63 void get(std::string const &command);
64
65 // execute a local command
66 void local(std::string const &command);
67
68 // start the nextCommand, including
69 // echo $? to obtain the resultcode
70 void nextCommand(std::ostream &inserter,
71 std::string const &command);
72
73 // returns true if the contents of the
74 // `current' logfile and `logfile'
75 // don't differ.
76 bool noDifferences(std::string const &current,
77 std::string const &logfile);
78
79 // put a local file to the client
80 void put(std::string const &command);
81
82 // construct put-dd command
83 std::string putCommand(std::string const &source,
84 std::string const &destination) const;
85
86 // copy any file
87 void read(std::istream &src, std::string const &fname);
88
89 // execute a remote command
90 void remote(std::string const &command);
91
92 // return `true' if `word' was the
93 // first word. In that case, remove
94 // `word', and have d_firstWord match
95 // what's beyond.
96 // Return false otherwise.
97 bool removeFirstWord(char const *word);
98
99 // see if there are any differences
100 // between the output of the current
101 // command and the output from the
102 // previously run command
103 bool sameOutput(std::string const &logfile,
104 std::istream &extractor);
105
106 // define the sentinel. Redefined
107 // at each new run()
108 void setSentinel();
109
110 // see if the exit value is 0
111 void testExitValue(std::string const &s);
112
113 // wait for the sentinel and exitvalue
114 void waitForSentinel(std::istream &extractor);
115
116 // write any file to the client
117 void write(std::string const &fname);
118
119 void removeLOG(); // remove LOG = from current command
120 };
121
122 }
123 #endif
0 #include "scanner.h"
1
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <unistd.h>
5 #include <sstream>
6 #include <unistd.h>
7 #include <signal.h>
8
9 #include <bobcat/arg>
10 #include <bobcat/pattern>
11 #include <bobcat/hash>
12
13 #include "../util/util.h"
14 #include "../configsorter/configsorter.h"
15 #include "../reporter/reporter.h"
16
17 using namespace FBB;
18 using namespace std;
19
20
0 #include "scanner.ih"
1
2 void Scanner::setSentinel()
3 {
4 ostringstream oss;
5
6 oss << "EOC " << Util::date;
7 d_sentinel = oss.str();
8 }
0 #include "scanner.ih"
1
2 void Scanner::testExitValue(std::string const &s)
3 {
4 if (d_testExitValue && s.substr(s.find_last_not_of(" ")) != "0")
5 d_reporter.exit() <<
6 "Program terminated due to non-zero exit value for\n" <<
7 *d_cmdIterator << " (" << s << ")" << endl;
8 }
9
10
0 #include "scanner.ih"
1
2 // SEE ALSO THE MEMBER copy()
3
4 void Scanner::waitForSentinel(istream &extractor)
5 {
6 string s;
7
8 while (getline(extractor, s))
9 {
10 Util::debug() << "Read line `" << s << "'" << endl;
11
12 if (s.find(d_sentinel) == 0)
13 {
14 Util::debug() << "GOT Sentinel" << endl;
15 break;
16 }
17 }
18
19 testExitValue(s);
20 }
0 #include "scanner.ih"
1
2 // SEE ALSO THE MEMBER waitForSentinel()
3
4 void Scanner::write(string const &fname)
5 {
6 ifstream source(fname.c_str());
7
8 if (!source)
9 d_reporter.exit() << "Can't open `" << fname << "' to read" << endl;
10
11 Util::debug() << "Scanner::write(): about to read local `" << fname <<
12 "'" << endl;
13 while (true)
14 {
15 size_t const SIZEOF_BUF = 1000;
16 char buffer[SIZEOF_BUF];
17
18 size_t nRead = source.read(buffer, SIZEOF_BUF).gcount();
19
20 if (!nRead)
21 break;
22
23 if (!d_sshFork.write(buffer, nRead))
24 d_reporter.exit() << "PUT failed." << endl;
25 }
26
27 d_sshFork.flush();
28 }
29
30
0 /var/stealth/target/report {
1 weekly
2 rotate 12
3 compress
4 missingok
5 prerotate
6 /usr/sbin/stealth --suppress /var/run/stealth.target
7 endscript
8 postrotate
9 /usr/sbin/stealth --resume /var/run/stealth.target
10 endscript
11 }
0 directories="
1 /var/stealth/target/local
2 /var/stealth/target/remote
3 "
4
5 rmdays=30
6 gzdays=3
0 #!/bin/bash
1
2 usage()
3 {
4 echo "
5 Usage: $0 rc-file
6 Where:
7 rc-file: resource file defining:
8 \`directories' - one or more directories containing status files
9 \`gzdays' - number of days status files may exist before they
10 are compressed
11 \`rmdays' - number of days gzipped status files may exist
12 before they are removed.
13 "
14 exit 1
15 }
16
17
18 error()
19 {
20 echo "$*" >&2
21 exit 1
22 }
23
24 [ $# == 1 ] || usage
25
26 # now source the configuration file
27 . $1
28
29 for x in $directories
30 do
31 cd $x || error "\`$x' must be a directory"
32
33 /usr/bin/find ./ -mtime +$rmdays -type f -regex '.*[0-9]+-[0-9]+\.gz' \
34 -exec /bin/rm {} \;
35
36 /usr/bin/find ./ -mtime +$gzdays -type f -regex '.*[0-9]+-[0-9]+' \
37 -exec /bin/gzip {} \;
38 done
39
40 exit 0
41
42
43
44
0 #!/bin/bash
1
2 PROG=`basename $0`
3 STEALTH=/usr/sbin/stealth
4
5 testAbsolute()
6 {
7 echo $1 | grep "^/" > /dev/null 2>&1 && return
8
9 echo "\`$1' must be absolute path"
10 exit 1
11 }
12
13 case $# in
14 (2)
15 testAbsolute $1
16 testAbsolute $2
17
18 if [ -x ${STEALTH} ] ; then
19 ${STEALTH} --rerun $1
20 [ $? -eq 0 ] || ${STEALTH} --keep-alive $1 -q $2
21 fi
22 ;;
23
24 (*)
25 echo "
26 $PROG by Frank B. Brokken (f.b.brokken@rug.nl)
27 Usage: $PROG [sleep] pidfile configfile
28 where:
29 pidfile: absolute path to pidfile to be used by ${STEALTH}
30 configfile: absolute path to configuration file to be used by ${STEALTH}
31
32 calls $STEALTH} --rerun pidfile.
33 If that fails, ${STEALTH} --keep-alive pidfile -q configfile is started.
34 "
35 exit 1
36 ;;
37 esac
0 /*
1 stealth.cc
2
3 */
4
5 #include "stealth.h"
6
7 static Arg::LongOption longOpt_begin[] =
8 {
9 Arg::LongOption("debug", 'd'),
10 Arg::LongOption("echo-commands", 'e'),
11 Arg::LongOption("no-child-processes", 'n'),
12 Arg::LongOption("only-stdout", 'o'),
13 Arg::LongOption("parse-config-file", 'c'),
14 Arg::LongOption("quiet", 'q'),
15 Arg::LongOption("random-interval", 'i'),
16 Arg::LongOption("run-command", 'r'),
17 Arg::LongOption("version", 'v'),
18
19 Arg::LongOption("keep-alive", Arg::Required), // runfilename
20 Arg::LongOption("suppress", Arg::Required), // runfilename
21 Arg::LongOption("repeat", Arg::Required),
22 Arg::LongOption("rerun", Arg::Required),
23 Arg::LongOption("terminate", Arg::Required), // runfilename
24 Arg::LongOption("resume", Arg::Required), // runfilename
25
26 Arg::LongOption("usage"),
27 Arg::LongOption("help"),
28 };
29
30 static Arg::LongOption const * const longOpt_end =
31 longOpt_begin + sizeof(longOpt_begin) / sizeof(Arg::LongOption);
32
33 int main(int argc, char **argv)
34 try
35 {
36 try
37 {
38 // construct Arg object to process
39 Arg &arg = Arg::initialize("cdei:noqr:v",
40 longOpt_begin, longOpt_end,
41 argc, argv);
42
43 bool debug = arg.option('d');
44 Util::setDebug(debug);
45
46 // handle process control options
47 Util::processControlOptions();
48
49 Util::maybeBackground(); // maybe run Stealth in the background
50
51
52 ConfigFile configfile; // ConfigFile object reads
53 // configuration file
54
55 try
56 {
57 configfile.open(arg[0]);
58 }
59 catch (...) // No configfile, then show message
60 {
61 Util::exit("Can't read configuration file `%s'", arg[0]);
62 }
63 // ConfigSorter sorts the
64 // configuration file. Separates
65 // USEs, DEFINEs and commands.
66 ConfigSorter sorter(configfile);
67
68 Reporter reporter(sorter["REPORT"]);
69
70 Scanner scanner(sorter, reporter); // Construct the integrityscanner
71
72 // Contruct the process monitor
73 Monitor monitor(sorter, reporter, scanner);
74
75 monitor.control(); // control the scanning process,
76 // run all the Scanner's tests
77 }
78 catch (Errno const &err)
79 {
80 cerr << err.what() << ": " << err.which() << endl;
81 throw Util::ERROR; // return 1;
82 }
83
84 Util::unlinkRunfile();
85 return 0;
86 }
87 catch (Util::Terminate terminate) // may also be OK
88 {
89 Util::mainProcess();
90 return terminate;
91 }
92
0 #ifndef _INCLUDED_STEALTH_H_
1 #define _INCLUDED_STEALTH_H_
2
3 #include <iostream>
4 #include <signal.h>
5
6 #include <bobcat/arg>
7 #include <bobcat/configfile>
8 #include <bobcat/errno>
9 #include <bobcat/selector>
10 #include <bobcat/process>
11
12 #include "util/util.h"
13 // #include "iofork/iofork.h"
14 #include "configsorter/configsorter.h"
15 #include "reporter/reporter.h"
16 #include "scanner/scanner.h"
17 #include "monitor/monitor.h"
18
19 using namespace std;
20 using namespace FBB;
21
22 #endif
0 #!/bin/bash
1
2 echo "$0 started with arguments:
3 =============================================" > /tmp/stealth.mail
4
5 while [ "$1" != "" ]
6 do
7 echo "$1" >> /tmp/stealth.mail
8 shift
9 done
10
11 echo "== contents begin ===========================" >> /tmp/stealth.mail
12
13 cat >> /tmp/stealth.mail
14
15 echo "== contents end =============================" >> /tmp/stealth.mail
0 #include "util.ih"
1
2 bool Util::s_keepAlive = false;
3 size_t Util::s_repeatInterval = 0;
4 size_t Util::s_delayInterval = 0;
5 Selector Util::s_selector;
6 string Util::s_runFilename;
7 FILE *Util::s_runFILE = 0;
8 bool Util::s_mainProcess = false;
9 ostream Util::s_cnul(0);
10 ostream *Util::s_debug;
0 #include "util.ih"
1
2 ostream & Util::date(std::ostream &str)
3 {
4 time_t curtime = time (NULL);
5
6 char *cp = asctime(localtime(&curtime));
7
8 return str.write(cp, strlen(cp) - 1);
9 }
10
0 #include "util.ih"
1
2 string Util::datetime()
3 {
4 time_t curtime = time(0);
5
6 char buffer[80];
7 strftime(buffer, 80, "%Y%m%d-%H%M%S", localtime(&curtime));
8
9 return buffer;
10 }
11
0 #include "util.ih"
1
2 void Util::exit(char const *fmt, ...)
3 {
4 va_list list;
5 va_start(list, fmt);
6
7 vfprintf(stderr, fmt, list);
8 cerr << endl;
9 va_end(list);
10
11 throw ERROR; // ::exit(1);
12 }
0 #include "util.ih"
1
2 string Util::fileName(string const &name)
3 {
4 string::size_type pos;
5
6 pos = name.rfind("/"); // name contains dir-separator ?
7
8 return pos == string::npos ? name : name.substr(pos + 1);
9 }
0 #include "util.ih"
1
2 // getPid() obtains the process-id from an existing lock-file. The file must
3 // exist and the pid stored in the lock-file must be the process-id of an
4 // existing Stealth program. This is verified by getPid()'s callers, when they
5 // send a signal to the corresponding process. by sending a fake SIGUSR1 signal
6 // to the obtained process-ID. If this fails, the lock-file is apparently
7 // stale. It is removed and an error message is issued.
8
9 size_t Util::getPid(string const &runFilename)
10 {
11 char const *runfile = runFilename.c_str();
12
13 ifstream in(runfile);
14 pid_t pid;
15
16 if (!(in >> pid))
17 exit("Can't read `%s'", runfile);
18
19 in.close();
20
21 return pid;
22 }
23
24
0 #include "util.ih"
1
2 char const *Util::getVersion()
3 {
4 return version;
5 }
6
0 #include "util.ih"
1
2 void Util::handleReplySignal(int signum)
3 {
4 wakeup();
5 }
0 #include "util.ih"
1
2 bool Util::isDirectory(string const &name)
3 {
4 struct stat buffer;
5
6 return !stat(name.c_str(), &buffer) && S_ISDIR(buffer.st_mode);
7 }
0 #include "util.ih"
1
2 bool Util::keepAlive()
3 {
4 return s_keepAlive;
5 }
0 #include "util.ih"
1
2 // Called by stealth --lock ...
3 //
4 void Util::lock(string const &runfile)
5 {
6 size_t pid;
7
8 pid = getPid(runfile);
9 debug() << "Trying to lock " << runfile << " of process " << pid <<
10 endl;
11
12 s_runFilename = runfile;
13 lockRunFile(BLOCKING); // Obtain the lock on the runfile
14
15 signalStealth(SIGUSR1, "SIGUSR1", runfile); // exits, and releases the
16 // lock.
17 }
0 #include "util.ih"
1
2 // In time: make a CFIle object allowing us to open a file, determine its
3 // file descriptor, and have it closed by its destructor.
4
5 bool Util::lockRunFile(LockType type)
6 try
7 {
8 if (s_runFILE)
9 exit("Internal error: runfile already locked");
10
11 debug() << "locking " << s_runFilename << endl;
12
13 if (s_runFilename.empty()) // no runfilename, no lock.
14 return true;
15
16 debug() << "open to read " << s_runFilename << endl;
17
18 s_runFILE = fopen(s_runFilename.c_str(), "r");
19
20 if (s_runFILE == 0)
21 exit("Can't open run-file `%s'", s_runFilename.c_str());
22
23 if (type == BLOCKING)
24 {
25 debug() << "attempting blocking mode lock" << endl;
26 if (flock(fileno(s_runFILE), LOCK_EX) == 0)
27 throw true;
28 debug() << "blocking mode lock FAILED" << endl;
29 }
30 else
31 {
32 debug() << "attempting non-blocking mode lock on FD " <<
33 fileno(s_runFILE) << endl;
34 for (size_t idx = 0; idx < s_maxBlockAttempts; ++idx)
35 {
36 if (flock(fileno(s_runFILE), LOCK_EX | LOCK_NB) == 0)
37 throw true;
38 debug() << "." << flush;
39 ::sleep(1);
40 debug() << "\nNon-blocking mode lock FAILED" << endl;
41 }
42 }
43 throw false;
44 }
45 catch (bool ret)
46 {
47 debug() << "locked (and return): " << ret << endl;
48
49 if (!ret)
50 exit("Failed to lock run-file `%s'", s_runFilename.c_str());
51
52 return true;
53 }
0 #include "util.ih"
1
2 bool Util::mainProcess()
3 {
4 if (s_mainProcess)
5 unlinkRunfile();
6 return s_mainProcess;
7 }
0 #include "util.ih"
1
2 void Util::maybeBackground()
3 {
4 if (keepAlive())
5 {
6 char const *runfile = s_runFilename.c_str();
7
8 ofstream out(runfile);
9 if (!out)
10 exit("Can't write `%s'", runfile);
11
12 int pid = fork();
13 if (pid < 0)
14 exit("--keepalive failed due to failing fork() system call.");
15
16 if (pid > 0) // parent process (gets child pid)
17 {
18 out << pid << endl;
19 throw OK; // ::exit(0);
20 }
21 s_mainProcess = true;
22 }
23 }
24
25
0 #include "util.ih"
1
2 bool Util::mkdir(string const &path)
3 {
4 char buffer[path.length() + 1];
5
6 buffer[path.copy(buffer, string::npos)] = 0; // copy the path
7 char const *dir = dirname(buffer); // construct the dirname
8
9 return
10 (
11 !::mkdir(dir, S_IRWXU) // constructing dir ok,
12 || // (only drwx------)
13 errno == EEXIST // or dir already existed
14 );
15 }
0 #include "util.ih"
1
2 void Util::processControlOptions()
3 {
4 Arg &arg = Arg::instance();
5
6 string value;
7 // options for other stealth processes
8 if (arg.option(&value, "rerun"))
9 signalStealth(SIGHUP, "SIGHUP", value); // exits
10
11 if (arg.option(&value, "terminate"))
12 signalStealth(SIGTERM, "SIGTERM", value); // exits
13
14 if (arg.option(&value, "suppress"))
15 lock(value); // lock locally, let the
16 // integrity wait, exits
17 if (arg.option(&value, "resume"))
18 signalStealth(SIGUSR2, "SIGUSR2", value); // exits
19
20 if (arg.option('v'))
21 showVersion(); // exits
22
23 if
24 (
25 !arg.nArgs() // provide usage if no arguments
26 || // were received
27 arg.option(0, "usage")
28 ||
29 arg.option(0, "help")
30 )
31 usage(); // exits
32
33
34 // options for this process:
35 s_keepAlive = arg.option(&value, "keep-alive");
36
37 if (s_keepAlive)
38 {
39 s_repeatInterval = INT_MAX;
40 s_runFilename = value;
41 }
42
43 if (arg.option(&value, "repeat"))
44 {
45 if (!s_keepAlive)
46 exit("--repeat requires --keep-interval");
47
48 s_keepAlive = true;
49 istringstream in(value);
50
51 if (!(in >> s_repeatInterval)) // value 0: wait indefinite
52 exit("--repeat requires <seconds> until next run");
53
54 if (s_repeatInterval < s_shortestRepeatInterval)
55 {
56 cerr << "`--repeat " << s_repeatInterval <<
57 "' changed to: `--repeat " << s_shortestRepeatInterval <<
58 "'\n";
59 s_repeatInterval = s_shortestRepeatInterval;
60 }
61 else if (s_repeatInterval > INT_MAX)
62 s_repeatInterval = INT_MAX;
63 }
64 randomDelay(); // determine any random delay
65 }
66
67
0 #include "util.ih"
1
2 void Util::randomDelay()
3 {
4 string delay;
5
6 if (!Arg::instance().option(&delay, 'i'))
7 return;
8
9 delay += "\n"; // to make sure the istr doesn't fail
10 // if only a number is read: no separating
11 // ws at the end would cause istr.peek()
12 // to fail.
13
14 istringstream istr(delay.c_str());
15
16 istr >> s_delayInterval;
17
18 if (istr.peek() == 'm')
19 s_delayInterval *= 60;
20
21 if (!istr || s_delayInterval < 0)
22 throw Errno(-1, "Invalid interval for -i");
23
24 srandom(time(0)); // seed the random time generator
25 }
26
27
28
29
30
0 #include "util.ih"
1
2 void Util::replace(std::string &str, char const *org, char const *replacement)
3 {
4 size_t orglen = strlen(org);
5
6 while (true)
7 {
8 string::size_type idx = str.find(org);
9
10 if (idx == string::npos)
11 break;
12
13 str.replace(idx, orglen, replacement);
14 }
15 }
0 #include "util.ih"
1
2 void Util::sendSignal(int signum, char const *signame, pid_t pid)
3 {
4 if (kill(pid, signum))
5 {
6 unlink(s_runFilename.c_str());
7
8 exit("Can't send %s to process `%u',\n"
9 "removing stale run-file `%s'.",
10 signame,
11 pid,
12 s_runFilename.c_str());
13 }
14
15 debug() << signame << " sent" << endl;
16 }
17
0 #include "util.ih"
1
2 void Util::setAlarm()
3 {
4 size_t random_wait =
5 s_delayInterval ?
6 static_cast<size_t>(random() % s_delayInterval)
7 :
8 0;
9
10 if (Arg::instance().option('d'))
11 {
12 cerr << "Would have waited " << random_wait << " seconds\n"
13 << "Randomly selected from " << s_delayInterval <<
14 " seconds\n";
15 random_wait = 0;
16 }
17
18 s_selector.setAlarm(s_repeatInterval + random_wait);
19 }
0 #include "util.ih"
1
2 void Util::showVersion()
3 {
4 cerr << Arg::instance().basename() << " version " << version <<
5 " (Frank B. Brokken, f.b.brokken@rug.nl, " << year << ")\n";
6 throw ERROR; // ::exit(1);
7 }
8
0 #include "util.ih"
1
2 // getPid() obtains the process-id from an existing lock-file. The file must
3 // exist and the pid stored in the lock-file must be the process-id of an
4 // existing Stealth program. This is verified by sending a signal to the
5 // corresponding process. If this fails, the lock-file is apparently stale. It
6 // is removed and an error message is issued.
7
8 // The following signals are used (and processed by Scanner::processSignal())
9 // SIGTERM: terminate stealth
10 // SIGHUP: rerun stealth
11 // SIGUSR1: suppress stealth from starting a new run
12 // SIGUSR2: resume normal actions.
13
14 void Util::signalStealth(int signum, char const *signame,
15 string const &filename)
16 {
17 size_t pid = getPid(filename); // get the pid of the process to
18 // signal
19
20 debug() << "Sending " << signame << " to process " << pid << endl;
21
22 // When suppressing (SIGUSR1) we must add this process' ID to the runfile
23 // so the suppressed stealth process can signal back that it has completed
24 // its suppression tasks. Note that this process still has the lock, which
25 // must be removed first before the suppressed stealth process may
26 // continue.
27 if (signum == SIGUSR1) // --suppress
28 {
29 pid_t myPid = getpid(); // add this process's id to the runfile
30 ofstream runFile(filename.c_str()); // rewrite the runfile
31
32 runFile << pid << "\n" <<
33 myPid << endl;
34 runFile.close(); // done. The runfile now contains the
35 // signalled process ID and the current
36 // process ID
37
38 // install the reply handler.
39 signal(SIGUSR1, handleReplySignal);
40 }
41
42 sendSignal(signum, signame, pid); // signal the running stealth, but we
43 // still have the lock. It will disappear
44 // when this process terminates, so below
45 // it must be explicitly removed when
46 // we're suppressing, and are waiting for
47 // the reply signal
48
49
50 if (signum == SIGUSR1) // when suppressing (SIGUSR1)
51 {
52 debug() << "Suppressing process " << pid << endl;
53
54 sleep(); // Prepare to go to sleep, by setting
55 // s_selector
56
57 unlockRunFile(); // Remove the lock, allow the
58 // suppressed process to continue
59 // The suppressed process will wait
60 // for a second allowing this process
61 // to start its waiting cycle.
62 debug() << "Waiting for the suppressed process to finish its tasks" <<
63 endl;
64
65 try // see Util::wait() for the try {...
66 {
67 s_selector.wait(); // no need to use Util::wait() here,
68 } // because its additional sleep second
69 catch(...) // is irrelevant here.
70 {}
71
72 debug() << "It has. Now terminate this process" << endl;
73 }
74
75 throw OK; // ::exit(0); // done
76 }
77
0 #include "util.ih"
1
2 void Util::sleep()
3 {
4 s_selector.setAlarm(INT_MAX);
5 }
0 #include "util.ih"
1
2 int Util::suppressorPid()
3 {
4 ifstream runFile(s_runFilename.c_str());
5
6 int pid;
7 runFile >> pid >> pid;
8
9 return pid;
10 }
0 #include "util.ih"
1
2 void Util::unlinkRunfile()
3 {
4 unlink(s_runFilename.c_str()); // s_runFilename may be empty
5 }
6
7
0 #include "util.ih"
1
2 void Util::unlockRunFile()
3 {
4 if (s_runFILE)
5 {
6 flock(fileno(s_runFILE), LOCK_UN);
7 fclose(s_runFILE); // closing removes the lock
8 }
9 s_runFILE = 0;
10 }
0 #include "util.ih"
1
2 void Util::usage()
3 {
4 string stealth = Arg::instance().basename();
5
6 cerr <<
7 stealth << " by Frank B. Brokken (f.b.brokken@rug.nl)\n"
8 "\n" <<
9 stealth << " V" << version << "\n"
10 "SSH-based Trust Enhancement Acquired through a Locally "
11 "Trusted Host\n"
12 "Copyright (c) GPL " << year << "\n"
13 "\n"
14 "Usage 1:\n"
15 " " << stealth << " options policy\n"
16 "Where:\n"
17 " options: (long options between parentheses) select from:\n"
18 " -c: (--parse-config-file) process the config file,\n"
19 " no further action, report the results to std output.\n"
20 " -d: (--debug) write debug messages to std error\n"
21 " -e: (--echo-commands) echo commands to std error when they\n"
22 " are processed (implied by -d)\n"
23 " -i <interval>[m]: (--random-interval) start the "
24 "scan between now and \n"
25 " a random interval of interval seconds, or minutes\n"
26 " if an `m' is appended immediately after"
27 " the specified interval.\n"
28 " -n: (--no-child-processes) no child processes are\n"
29 " executed: child actions are faked to be OK.\n"
30 " -o: (--only-stdout) scan report is written to "
31 "stdout. No mail is sent.\n"
32 " -q: (--quiet) suppress progress messages to stderr.\n"
33 " -r <nr>: (--run-command) only run command <nr> "
34 "(natural number).\n"
35 " -v: (--version): display version information (and exit).\n"
36 " --keep-alive pidfile: keep running as a daemon, wake up"
37 " at interrupts.\n"
38 " --repeat <seconds>: keep running as a daemon, wake up at\n"
39 " interrupts. or after <seconds> seconds.\n"
40 " Requires --keep-alive.\n"
41 " --usage: provide this help (and exit)\n"
42 " --help: provide this help (and exit)\n"
43 " policy: path to the policyfile\n"
44 "\n"
45 "Usage 2:\n"
46 " " << stealth << " [--rerun|--resume|--suppress|--terminate] "
47 "pidfile\n"
48 "Where:\n"
49 " --rerun: restart a " << stealth << " integrity scan\n"
50 " --resume: resume " << stealth << " following --suppress\n"
51 " --suppress: suppress " << stealth << " activities\n"
52 " --terminate: terminate "<< stealth << "\n"
53 " pidfile: file containing the pid of the stealth process to "
54 "rerun or\n"
55 " terminate.\n" <<
56 endl;
57
58 throw ERROR; // ::exit(1);
59 }
0 #ifndef _INCLUDED_UTIL_H_
1 #define _INCLUDED_UTIL_H_
2
3 #include <iosfwd>
4 #include <string>
5 #include <sys/types.h>
6 #include <bobcat/selector>
7
8 namespace FBB
9 {
10 class Util
11 {
12 static size_t const s_maxBlockAttempts = 10; // # seconds & tries
13 // recompile lockrunfile.cc when
14 // modifying this value
15
16 // recompile processcontroloptions.cc after changing
17 // the next size_t const value:
18 static size_t const s_shortestRepeatInterval = 60;
19
20 static FILE *s_runFILE; // pointer used for locking
21 static Selector s_selector;
22 static bool s_keepAlive;
23 static bool s_mainProcess;
24 static char version[];
25 static char year[];
26 static std::string s_runFilename;
27 static size_t s_delayInterval; // for the random delay
28 static size_t s_repeatInterval;
29 static std::ostream *s_debug;
30 static std::ostream s_cnul;
31
32 public:
33 enum Terminate
34 {
35 OK = 0,
36 ERROR = 1,
37 };
38 enum LockType
39 {
40 NONBLOCKING,
41 BLOCKING,
42 };
43
44 static std::ostream &debug()
45 {
46 return *s_debug;
47 }
48 static std::string fileName(std::string const &name);
49 // exit() itself includes `endl'
50 static void handleReplySignal(int signum);
51 static bool isDirectory(std::string const &name);
52 static bool keepAlive();
53 static void sendSignal(int sig, char const *signame, pid_t pid);
54 static bool mkdir(std::string const &path); // pathname to a file
55 static std::ostream &date(std::ostream &str);
56 static std::string datetime();
57 static size_t getPid(std::string const &runFilename);
58 static void exit(char const *fmt, ...);
59
60 static bool lockRunFile(LockType lockType);
61
62 static bool mainProcess();
63 static void maybeBackground();
64 static void processControlOptions();
65 static void randomDelay();
66 static void setAlarm();
67 static void setDebug(bool value)
68 {
69 s_debug = value ? &std::cerr : &s_cnul;
70 }
71
72 static void sleep(); // sleep until wakeup
73
74 static int suppressorPid();
75 static void replace(std::string &str, char const *org,
76 char const *replacement);
77 static void signalStealth(int signum, char const *signame,
78 std::string const &filename);
79 static void unlockRunFile();
80
81 static char const *getVersion();
82
83 static void showVersion();
84 static void unlinkRunfile();
85 static void usage();
86 static void wait();
87 static void wakeup();
88
89 private:
90 static void lock(std::string const &runfile);
91
92 };
93 }
94
95
96 #endif
0 #include "util.h"
1
2 #include <iostream>
3 #include <fstream>
4 #include <libgen.h>
5 #include <stdarg.h>
6 #include <cstdlib>
7 #include <cstdio>
8 #include <ctime>
9 #include <sys/stat.h>
10 #include <sys/file.h>
11 #include <sstream>
12 #include <unistd.h>
13 #include <signal.h>
14 #include <limits.h>
15 #include <bobcat/errno>
16 #include <bobcat/arg>
17
18 #include "../configsorter/configsorter.h"
19
20 using namespace std;
21 using namespace FBB;
0 #include "util.ih"
1
2 #include "../release.h"
3
4 char Util::version[] = _CurVers_;
5 char Util::year[] = _CurYrs_;
0 #include "util.ih"
1
2 void Util::wait()
3 {
4 try
5 {
6 s_selector.wait(); // bobcat's selector throws an
7 // Errno on negative returns
8 }
9 catch(...)
10 {}
11
12 ::sleep(1); // to allow s_keepAlive to change its
13 // value
14 }
0 #include "util.ih"
1
2 void Util::wakeup()
3 {
4 s_selector.setAlarm(0);
5 }