Imported Upstream version 1.45
tony mancill
11 years ago
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 | 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 | #!/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 | // 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 | /* | |
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 ¤t, | |
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 ¤t, | |
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 | 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 | // 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 | 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_; |