Imported Upstream version 2.10.00
tony mancill
11 years ago
41 | 41 | Groningen. Here are a PGP key server, and my PGP public key fingerprint: |
42 | 42 | |
43 | 43 | Public PGP key: http://pgp.surfnet.nl:11371/ |
44 | Key Fingerprint: 8E36 9FC4 1DAA FCDF 1A0D B19F DAC4 BE50 38C6 6170 | |
44 | Key Fingerprint: DF32 13DE B156 7732 E65E 3B4D 7DB2 A8BE EAE4 D8AA | |
45 | 45 | |
46 | 46 | Contact me if you have any doubts about the correctness of the provided |
47 | 47 | signature or the given fingerprint. |
0 | Stealth has several modes of operation, defined in monitor/monitor.h, and | |
1 | (partially) controlled by command-line options: | |
2 | ||
3 | option mode meaning/action | |
4 | ||
5 | default ONCE, single run in the foreground | |
6 | keep-alive KEEP_ALIVE, multiple runs: Stealth becomes a daemon process | |
7 | ||
8 | terminate TERMINATE, terminate a running Stealth daemon process | |
9 | (through SIGTERM) | |
10 | TERMINATED, automatically, following TERMINATE | |
11 | suppress SUPPRESS, suppress a running Stealth daemon process, | |
12 | (through SIGUSR1) | |
13 | SUPPRESSED, automatically following SUPPRESS | |
14 | resume resumes a SUPPRESSed Stealth daemon process | |
15 | (through SIGUSR2) | |
16 | reload RELOAD, a running Stealth daemon process reloads its | |
17 | config files (through SIGCHLD) | |
18 | ||
19 | All actions are controlled by Monitor::control, started by main(). This is the | |
20 | only real action main performs. | |
21 | ||
22 | Monitor: | |
23 | control(): performs one infinite loop, in which the following actions | |
24 | occur: | |
25 | ||
26 | processMode() is called. It performs the actions associated with a | |
27 | particular mode. | |
28 | following this the report is mailed by mailReport(). | |
29 | ||
30 | Actions are s_mode-dependent. | |
31 | The variable s_mode is global, and may be modified by daemon | |
32 | process signal handlers: | |
33 | ||
34 | s_mode = | |
35 | ONCE: the foreground process ends. | |
36 | TERMINATED: the daemon process ends. | |
37 | ||
38 | SUPPRESSED: the daemon process is now in its suppressed state. | |
39 | it sends a SUGUSR1 signal to the suppressing | |
40 | process, and goes into suspend mode, from which it | |
41 | awakes (at control()'s wait() call) when receiving | |
42 | a resume (SIGUSR2) signal. | |
43 | ||
44 | processMode() performs its own infinite loop, during which s_mode may | |
45 | change. By default it calls Scanner::run() to perform a stealth-run. |
0 | ISN: stealth (2.10.00) | |
1 | ||
2 | * --reload pidfile reloads the configuration files | |
3 | ||
4 | * When specifying a directory with --skipfiles all entries below that | |
5 | directory are skipped. | |
6 | ||
0 | 7 | stealth (2.04.00) |
1 | 8 | |
2 | 9 | * Added detection of invalid child process command texts: commands not |
0 | 0 | #include "configsorter.ih" |
1 | 1 | |
2 | ConfigSorter::ConfigSorter(char const *confFile) | |
2 | ConfigSorter::ConfigSorter(string const &confPath) | |
3 | 3 | : |
4 | d_configfile(confFile), | |
5 | d_use(&s_defaultKeyword[0], &s_defaultKeyword[s_nDefaultKeywords]) | |
4 | d_confPath(confPath) | |
6 | 5 | { |
7 | fetchCommands(); | |
8 | ||
9 | string &base = d_use["BASE"]; | |
10 | ||
11 | base += "/."; // the . is required by mkdir | |
12 | ||
13 | char const *cp = base.c_str(); | |
14 | ||
15 | if (!Util::mkdir(cp) || chdir(cp)) | |
16 | fmsg << "Can't chdir to `" << cp << '\'' << endl; | |
17 | ||
18 | base.resize(base.length() - 1); // cut off the . again | |
6 | reload(); | |
19 | 7 | } |
20 | 8 | |
21 | 9 |
12 | 12 | |
13 | 13 | class ConfigSorter |
14 | 14 | { |
15 | std::string d_confPath; | |
15 | 16 | FBB::ConfigFile d_configfile; |
16 | 17 | std::vector<std::string> d_command; |
17 | 18 | FBB::HashString<std::string> d_use; |
25 | 26 | // [1]: all ${NAME} text |
26 | 27 | // [2]: NAME itself |
27 | 28 | public: |
28 | ConfigSorter(char const *confFname); | |
29 | ConfigSorter(std::string const &confPath); | |
30 | ||
31 | void reload(); | |
29 | 32 | |
30 | 33 | std::vector<std::string>::const_iterator firstCmd() const; |
31 | 34 | std::vector<std::string>::const_iterator beyondCmd() const; |
0 | #include "configsorter.ih" | |
1 | ||
2 | void ConfigSorter::reload() | |
3 | { | |
4 | d_configfile.open(d_confPath); | |
5 | ||
6 | d_use = HashString<std::string> | |
7 | (&s_defaultKeyword[0], &s_defaultKeyword[s_nDefaultKeywords]); | |
8 | ||
9 | d_define.clear(); | |
10 | d_command.clear(); | |
11 | ||
12 | fetchCommands(); | |
13 | ||
14 | string &base = d_use["BASE"]; | |
15 | ||
16 | base += "/."; // the . is required by mkdir | |
17 | ||
18 | char const *cp = base.c_str(); | |
19 | ||
20 | if (!Util::mkdir(cp) || chdir(cp)) | |
21 | fmsg << "Can't chdir to `" << cp << '\'' << endl; | |
22 | ||
23 | base.resize(base.length() - 1); // cut off the . again | |
24 | } | |
25 | ||
26 | ||
27 |
587 | 587 | it() tt(-q --quiet): Suppress progress messages written to stderr; |
588 | 588 | it() tt(-r --run-command <nr>): Only run command <nr> (natural number). |
589 | 589 | Command numbers are shown by s() tt(-c); |
590 | it() tt(-s --skip-files <skippath>): All files mentioned in tt(skippath) | |
591 | (using an absolute path) are skipped. Their integrity is not monitored. If a | |
592 | file is already present in a log file then s() will once generate an | |
593 | tt(IGNORING) message in the mail sent to the address specified at tt(EMAIL) in | |
594 | the policy file. Each file mentioned in tt(filepath) must be on a line of | |
595 | its own and must be specified using absolute file paths. Initial and trailing | |
596 | blanks, empty lines and lines having a tt(#) as their 1st non blank character | |
597 | are ignored. | |
590 | it() tt(-s --skip-files <skippath>): All entries in tt(skippath) | |
591 | (specified using an absolute path) are skipped. Their integrity is not | |
592 | monitored. If an entry is already present in a log file then s() will once | |
593 | generate an tt(IGNORING) message in the mail sent to the address specified at | |
594 | tt(EMAIL) in the policy file. Each entry mentioned in tt(filepath) must be on a | |
595 | line of its own and must be specified using absolute paths. Entries ending in | |
596 | a slash are assumed to be directories whose contents must be skipped. Other | |
597 | entries are interpreted as the path names of files to skip. Initial and | |
598 | trailing blanks, empty lines and lines having a tt(#) as their 1st non blank | |
599 | character are ignored. If the | |
598 | 600 | it() tt(-v --version): Display version information and exit; |
599 | 601 | it() tt(--keep-alive pidfile): Keep running as a daemon, |
600 | 602 | wake up at interrupts. |
606 | 608 | interrupts or after <seconds> seconds. The interval will be at |
607 | 609 | least 60 seconds. To this interval a random delay may be added |
608 | 610 | (see tt(--random-interval)). |
611 | it() tt(--reload pidfile): reloads the configuration and skip-files and | |
612 | restarts the scan of a currently active s() process. | |
609 | 613 | it() tt(--rerun pidfile): restart the scan of a currently active s() |
610 | 614 | process; |
611 | 615 | it() tt(--resume pidfile): resume a suppressed s() |
67 | 67 | sent when no changes were detected. |
68 | 68 | ) |
69 | 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, | |
70 | Alternatively, the command-line options tt(--reload, --rerun, --suppress, | |
71 | --resume) and tt(--terminate) may be provided to communicate with a running | |
72 | bf(stealth) process started earlier using either the tt(--keep-alive) or | |
73 | tt(--repeat) option. For these options one argument must be provided: the | |
74 | pathname to a pid-file of a running s(). | |
73 | 75 | itemization( |
74 | 76 | 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. | |
77 | stealth process running at process-ID tt(<pid>) is terminated. | |
78 | it() When started using the tt(--reload <pid>) command-line option, the | |
79 | stealth process running at process-ID tt(<pid>) will reload its configuration | |
80 | and skip-files, which is then immediately followed by another stealth | |
81 | scan. | |
79 | 82 | it() When started using the tt(--rerun <pid>) command-line option, the |
80 | 83 | 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 | scan. | |
84 | 85 | ) |
85 | 86 | |
86 | 87 | The options tt(--suppress) and tt(--rerun) (see section ref(ROTATE)) were |
108 | 109 | ) |
109 | 110 | it() Make sure you have the right key. Its fingerprint is |
110 | 111 | verb( |
111 | 8E36 9FC4 1DAA FCDF 1A0D B19F DAC4 BE50 38C6 6170 | |
112 | DF32 13DE B156 7732 E65E 3B4D 7DB2 A8BE EAE4 D8AA | |
112 | 113 | ) |
113 | 114 | and it has been electronically signed by, e.g., the University of |
114 | 115 | Groningen's PGP-certificate authority. If in doubt, contact me to verify you |
123 | 124 | ) |
124 | 125 | This should produce output comparable to: |
125 | 126 | verb( |
126 | gpg: Signature made Mon Aug 1 10:57:41 2005 CEST using DSA key ID 38C66170 | |
127 | gpg: Signature made Fri Jun 1 10:57:41 2012 CEST using DSA key ID EAE4D8AA | |
127 | 128 | 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 | 129 | ) |
130 | 130 | |
131 | 131 | |
132 | ||
133 |
0 | Some files may not require integrity checks. Automated processes may modify | |
1 | files which are not threatening the proper functioning of running programs or | |
2 | processes. In those cases a file can be prepared holding the absolute paths of | |
3 | files that can be skipped. Each file should appear on a line of its own | |
4 | without any additional information. | |
0 | Some files or directories may not require integrity checks. Automated | |
1 | processes may modify files which are not threatening the proper functioning of | |
2 | running programs or processes. In those cases a file can be prepared holding | |
3 | the absolute paths of entries to be skipped. Each entry should appear on a | |
4 | line of its own without any additional information. | |
5 | 5 | |
6 | 6 | bf(Stealth) can be informed about this file using the tt(-s skippath) or |
7 | tt(--skip-files skippath) option. The file holding the paths of the files to | |
8 | be skipped should be specified using absolute paths, one file per | |
7 | tt(--skip-files skippath) option. The file holding the paths of the entries to | |
8 | be skipped should be specified using absolute paths, using one entry per | |
9 | 9 | line. Initial and trailing blanks, empty lines and lines having a tt(#) as |
10 | 10 | their first non blank character are ignored. |
11 | 11 | |
14 | 14 | stealth -e --skip-files /root/stealth/remote/skipping remote.pol |
15 | 15 | ) |
16 | 16 | |
17 | If a file tt(/etc/skipme) appears in the current logs which is thereafter | |
17 | If an entry tt(/etc/skipme) appears in the current logs which is thereafter | |
18 | 18 | added to the tt(skippath) file then the mail generated by bf(stealth) will |
19 | 19 | once contain a line like the following: |
20 | 20 | verb( |
23 | 23 | ) |
24 | 24 | The shown hash-value is the hash-value at the time of the stealth-run |
25 | 25 | reporting the tt(SKIPPING) message. |
26 | ||
27 | Entries ending in a slash are assumed to be directories whose contents must be | |
28 | skipped. |
1 | 1 | |
2 | 2 | void Monitor::contactOtherStealth() |
3 | 3 | { |
4 | Arg &arg = Arg::instance(); | |
4 | if (d_arg.option(0, "reload")) | |
5 | signalStealth(SIGPIPE, "SIGPIPE", d_arg[0]);// signalStealth calls | |
6 | // end the current process | |
7 | if (d_arg.option(0, "rerun")) | |
8 | signalStealth(SIGHUP, "SIGHUP", d_arg[0]); | |
5 | 9 | |
6 | if (arg.option(0, "rerun")) | |
7 | signalStealth(SIGHUP, "SIGHUP", arg[0]); // all signalStealth calls | |
8 | // end the current process | |
10 | if (d_arg.option(0, "terminate")) | |
11 | signalStealth(SIGTERM, "SIGTERM", d_arg[0]); | |
9 | 12 | |
10 | if (arg.option(0, "terminate")) | |
11 | signalStealth(SIGTERM, "SIGTERM", arg[0]); | |
12 | ||
13 | if (arg.option(0, "suppress")) | |
14 | lock(arg[0]); // lock locally, let the | |
13 | if (d_arg.option(0, "suppress")) | |
14 | lock(d_arg[0]); // lock locally, let the | |
15 | 15 | // integrity wait, exits |
16 | if (arg.option(0, "resume")) | |
17 | signalStealth(SIGUSR2, "SIGUSR2", arg[0]); | |
16 | if (d_arg.option(0, "resume")) | |
17 | signalStealth(SIGUSR2, "SIGUSR2", d_arg[0]); | |
18 | 18 | } |
19 | 19 | |
20 | 20 |
2 | 2 | // Called by main() once all preliminary actions have been completed. This |
3 | 3 | // function controls the processing of the configuration file. |
4 | 4 | |
5 | // Signals are sent by sendSignal. Signals are caught by handleProcessSignals | |
6 | // contactOtherStealth sends the signals depending on command-line options | |
7 | ||
5 | 8 | void Monitor::control() |
6 | 9 | { |
7 | 10 | while (true) |
8 | 11 | { |
9 | imsg << "CONTROL: s_mode == " << s_mode << endl; | |
12 | imsg << "CONTROL: s_mode == " << s_modeID[s_mode] << endl; | |
10 | 13 | |
11 | 14 | d_reporter->standby(); // locks the runfile, opens the report |
12 | 15 | // file |
20 | 23 | // command returns a non-zero exit value. |
21 | 24 | if (s_mode == TERMINATED || s_mode == ONCE) |
22 | 25 | break; |
26 | ||
27 | if (s_mode == RELOAD) | |
28 | { | |
29 | s_mode = KEEP_ALIVE; | |
30 | continue; | |
31 | } | |
23 | 32 | |
24 | 33 | if (s_mode == SUPPRESSED) |
25 | 34 | { |
50 | 59 | while (s_mode == SUPPRESSED); |
51 | 60 | } |
52 | 61 | } |
62 |
7 | 7 | size_t Monitor::s_delayInterval = 0; |
8 | 8 | size_t Monitor::s_repeatInterval = 0; |
9 | 9 | |
10 | // Update this array if Mode definitions in monitor.h change | |
11 | char const *const Monitor::s_modeID[] = | |
12 | { | |
13 | "ONCE", | |
14 | "KEEP", | |
15 | "TERMINATE", | |
16 | "TERMINATED", | |
17 | "SUPPRESS", | |
18 | "SUPPRESSED", | |
19 | "RELOAD" | |
20 | }; |
2 | 2 | void Monitor::handleKeepAliveOption() |
3 | 3 | { |
4 | 4 | string value; |
5 | if ((s_keepAlive = Arg::instance().option(&value, "keep-alive")) != 0) | |
5 | if ((s_keepAlive = d_arg.option(&value, "keep-alive")) != 0) | |
6 | 6 | { |
7 | s_repeatInterval = INT_MAX; | |
7 | s_repeatInterval = numeric_limits<int>::max(); | |
8 | 8 | Lock::setRunFilename(value); |
9 | 9 | } |
10 | 10 | } |
27 | 27 | if (s_mode == SUPPRESS || s_mode == SUPPRESSED) |
28 | 28 | s_mode = KEEP_ALIVE; |
29 | 29 | break; |
30 | ||
31 | case SIGPIPE: // RELOAD: changes KEEP_ALIVE | |
32 | if (s_mode == KEEP_ALIVE) // temporarily into RELOAD | |
33 | s_mode = RELOAD; // for processMode() to handle. | |
34 | break; | |
35 | ||
36 | default: | |
37 | return; | |
30 | 38 | } |
31 | 39 | |
32 | 40 | wakeup(); |
33 | 41 | signal(signum, handleProcessSignals); |
34 | 42 | } |
43 |
1 | 1 | |
2 | 2 | void Monitor::handleRepeatOption() |
3 | 3 | { |
4 | Arg &arg = Arg::instance(); | |
5 | ||
6 | 4 | string value; |
7 | 5 | |
8 | if (arg.option(&value, "repeat")) | |
6 | if (d_arg.option(&value, "repeat")) | |
9 | 7 | { |
10 | 8 | if (!s_keepAlive) |
11 | 9 | fmsg << "--repeat requires --keep-alive" << endl; |
22 | 20 | '\'' << endl; |
23 | 21 | s_repeatInterval = s_shortestRepeatInterval; |
24 | 22 | } |
25 | else if (s_repeatInterval > INT_MAX) | |
26 | s_repeatInterval = INT_MAX; | |
23 | else if (s_repeatInterval > | |
24 | static_cast<size_t>(numeric_limits<int>::max())) | |
25 | s_repeatInterval = numeric_limits<int>::max(); | |
27 | 26 | } |
28 | 27 | } |
29 | 28 |
11 | 11 | return; |
12 | 12 | } |
13 | 13 | |
14 | d_reporter->rewind(); // resets the `hasmail' variable | |
14 | d_reporter->rewind(); // resets the `hasmail' variable | |
15 | 15 | |
16 | if (Arg::instance().option('o')) // mail the report to stdout | |
16 | if (d_arg.option('o')) // mail the report to stdout | |
17 | 17 | { |
18 | 18 | imsg << "Monitor::mailReport() mails report to stdout" << endl; |
19 | 19 | cout << d_reporter->in().rdbuf() << endl; |
0 | 0 | #include "monitor.ih" |
1 | 1 | |
2 | 2 | Monitor::Monitor() |
3 | : | |
4 | d_arg(Arg::instance()), | |
5 | d_sorterPath(Util::makeAbsPath(d_arg[0])) | |
3 | 6 | { |
4 | 7 | processControlOptions(); // handle process control options |
5 | 8 | maybeBackground(); // maybe run Stealth in the background |
13 | 16 | signal(SIGTERM, Monitor::handleProcessSignals); |
14 | 17 | signal(SIGUSR1, Monitor::handleProcessSignals); |
15 | 18 | signal(SIGUSR2, Monitor::handleProcessSignals); |
19 | signal(SIGPIPE, Monitor::handleProcessSignals); | |
16 | 20 | } |
17 | 21 | |
18 | 22 |
7 | 7 | namespace FBB |
8 | 8 | { |
9 | 9 | class Selector; |
10 | class Arg; | |
10 | 11 | } |
11 | 12 | |
12 | 13 | class ConfigSorter; |
15 | 16 | |
16 | 17 | class Monitor |
17 | 18 | { |
19 | // Update s_modeID in data.cc if the following enum changes: | |
18 | 20 | enum Mode |
19 | 21 | { |
22 | // These numbers are here for debug output referential | |
23 | // purpose only. Their values are not otherwise used | |
20 | 24 | ONCE, // 0 single run |
21 | 25 | KEEP_ALIVE, // 1 multiple runs |
22 | 26 | TERMINATE, // 2 through SIGTERM |
23 | 27 | TERMINATED, // 3 automatically following TERMINATE |
24 | 28 | SUPPRESS, // 4 through SIGUSR1 (SIGUSR2: back to normal) |
25 | 29 | SUPPRESSED, // 5 automatically following SUPPRESS |
30 | RELOAD, // 6 reload the config files, through SIGPIPE | |
26 | 31 | }; |
27 | 32 | |
33 | static char const *const s_modeID[]; | |
28 | 34 | static Mode s_mode; |
29 | 35 | static bool s_quit; // passed to Scanner::run() for |
30 | 36 | // inspection |
31 | 37 | |
38 | FBB::Arg &d_arg; | |
39 | ||
40 | std::string d_sorterPath; | |
32 | 41 | std::unique_ptr<ConfigSorter> d_sorter; |
33 | 42 | std::unique_ptr<Reporter> d_reporter; |
34 | 43 | std::unique_ptr<Scanner> d_scanner; |
56 | 65 | static void handleProcessSignals(int signum); |
57 | 66 | |
58 | 67 | private: |
59 | ||
68 | void reload(); // reload the configuration files. | |
60 | 69 | void processMode(); // process the current mode |
61 | 70 | void processControlOptions(); // determine the running mode |
62 | 71 | void contactOtherStealth(); |
1 | 1 | |
2 | 2 | #include <string> |
3 | 3 | #include <signal.h> |
4 | #include <limits> | |
4 | 5 | |
5 | 6 | #include <bobcat/arg> |
6 | 7 | #include <bobcat/process> |
8 | 9 | #include <bobcat/errno> |
9 | 10 | #include <bobcat/datetime> |
10 | 11 | |
12 | #include "../util/util.h" | |
11 | 13 | #include "../lock/lock.h" |
12 | 14 | #include "../configsorter/configsorter.h" |
13 | 15 | #include "../scanner/scanner.h" |
14 | 16 | #include "../reporter/reporter.h" |
15 | 17 | |
16 | ||
18 | using namespace std; | |
17 | 19 | using namespace FBB; |
18 | using namespace std; | |
19 | 20 | |
20 | 21 | inline void Monitor::wakeup() |
21 | 22 | { |
7 | 7 | { |
8 | 8 | switch (s_mode) |
9 | 9 | { |
10 | default: | |
11 | d_scanner->run(&s_quit); | |
12 | ||
13 | if (s_mode == TERMINATE || s_mode == SUPPRESS) | |
14 | continue; | |
15 | return; | |
16 | ||
17 | case RELOAD: | |
18 | *d_reporter << | |
19 | "STEALTH reloads its configuration files after " << | |
20 | d_scanner->nScans() << " scans at " << now << endl; | |
21 | reload(); | |
22 | return; // next cycle: rerun a scan. | |
23 | ||
10 | 24 | case TERMINATE: |
11 | 25 | *d_reporter << |
12 | 26 | "STEALTH was terminated after " << d_scanner->nScans() << |
25 | 39 | case SUPPRESSED: |
26 | 40 | return; |
27 | 41 | |
28 | default: | |
29 | d_scanner->run(&s_quit); | |
30 | ||
31 | if (s_mode == TERMINATE || s_mode == SUPPRESS) | |
32 | continue; | |
33 | return; | |
34 | 42 | } |
35 | 43 | } |
36 | 44 | } |
3 | 3 | { |
4 | 4 | string delay; |
5 | 5 | |
6 | if (!Arg::instance().option(&delay, 'i')) | |
6 | if (not Arg::instance().option(&delay, 'i')) | |
7 | 7 | return; |
8 | 8 | |
9 | if (!Arg::instance().option(0, "repeat")) | |
9 | if (not Arg::instance().option(0, "repeat")) | |
10 | 10 | { |
11 | 11 | wmsg << "--random-interval ignored unless --repeat is specified" << |
12 | 12 | endl; |
0 | #include "monitor.ih" | |
1 | ||
2 | void Monitor::reload() | |
3 | { | |
4 | d_sorter->reload(); | |
5 | d_scanner.reset( new Scanner(*d_sorter, *d_reporter) ); | |
6 | ||
7 | // ->nScansReset(); | |
8 | // d_scanner->loadSkipFiles(); | |
9 | ||
10 | d_scanner->preamble(); | |
11 | ||
12 | // cerr << "RELOAD COMPLETED (" << d_sorterPath << ")\n"; | |
13 | } | |
14 | ||
15 |
6 | 6 | // is removed and an error message is issued. |
7 | 7 | |
8 | 8 | // The following signals are used (and processed by Scanner::processSignal()) |
9 | // SIGPIPE: reload configuration files | |
9 | 10 | // SIGTERM: terminate stealth |
10 | 11 | // SIGHUP: rerun stealth |
11 | 12 | // SIGUSR1: suppress stealth from starting a new run |
1 | 1 | |
2 | 2 | void Monitor::sleep() |
3 | 3 | { |
4 | s_selector.setAlarm(INT_MAX); | |
4 | s_selector.setAlarm(numeric_limits<int>::max()); | |
5 | 5 | } |
1 | 1 | |
2 | 2 | void Monitor::startStealth() |
3 | 3 | { |
4 | d_sorter.reset( new ConfigSorter(Arg::instance()[0]) ); | |
5 | d_reporter.reset(new Reporter((*d_sorter)["REPORT"])); | |
6 | d_scanner.reset(new Scanner(*d_sorter, *d_reporter)); | |
4 | d_sorter.reset( new ConfigSorter(d_sorterPath) ); | |
5 | d_reporter.reset( new Reporter((*d_sorter)["REPORT"]) ); | |
6 | d_scanner.reset( new Scanner(*d_sorter, *d_reporter) ); | |
7 | 7 | |
8 | 8 | handleKeepAliveOption(); |
9 | 9 | handleRepeatOption(); |
10 | 10 | |
11 | 11 | imsg << "Scanner::copy(): about to read child input" << endl; |
12 | 12 | |
13 | string s; | |
13 | string line; | |
14 | 14 | off_t length = 0; |
15 | while (getline(src, s)) | |
15 | while (getline(src, line)) | |
16 | 16 | { |
17 | if (!checkSize(fname, length += s.length() + 1)) | |
17 | if (!checkSize(fname, length += line.length() + 1)) | |
18 | 18 | return; |
19 | 19 | |
20 | imsg << "copy SAW: `" << s << '\'' << endl; | |
20 | imsg << "copy SAW: `" << line << '\'' << endl; | |
21 | 21 | |
22 | if (s.find(d_sentinel) == 0) | |
22 | if (line.find(d_sentinel) == 0) | |
23 | 23 | { |
24 | 24 | imsg << "GOT Sentinel" << endl; |
25 | 25 | break; |
26 | 26 | } |
27 | 27 | |
28 | if (not (this->*d_skip)(s)) | |
29 | currentReport << s << '\n'; | |
28 | s_split << line; // get the last word on this line | |
29 | string last = s_split[1]; | |
30 | ||
31 | if (not (this->*d_skip)(last)) | |
32 | currentReport << line << '\n'; | |
30 | 33 | } |
31 | testExitValue(src.str(), s); | |
34 | testExitValue(src.str(), line); | |
32 | 35 | } |
33 | 36 | |
34 | 37 |
11 | 11 | |
12 | 12 | imsg << "running checked command: `" << s_firstWord[0] << '\'' << endl; |
13 | 13 | |
14 | if (Arg::instance().option('n')) // -n (no go) option? | |
14 | if (d_arg.option('n')) // -n (no go) option? | |
15 | 15 | return true; // then indicate by implication that |
16 | 16 | // the command was processed without |
17 | 17 | // differing from the previous run |
2 | 2 | void Scanner::doPlainCommand(Process &child) |
3 | 3 | { |
4 | 4 | imsg << "running unchecked command: `" << s_firstWord[0] << '\'' << endl; |
5 | if (!Arg::instance().option('n')) // unless -n (no execute commands) | |
5 | if (!d_arg.option('n')) // unless -n (no execute commands) | |
6 | 6 | { |
7 | 7 | nextCommand(child, s_firstWord[0]); // start the next command |
8 | 8 | waitForSentinel(child); // read its output |
5 | 5 | if (!(s_firstWord << cmd)) // determine first word and the rest |
6 | 6 | d_reporter.error() << "Corrupt line in policy file: " << cmd << endl; |
7 | 7 | |
8 | if (Arg::instance().option("de"))// echo the command with -d, -e | |
8 | if (d_arg.option("de")) // echo the command with -d, -e | |
9 | 9 | cerr << *d_cmdIterator << '\n'; |
10 | 10 | |
11 | 11 | if (s_firstWord[1] == "LABEL") // set a label |
38 | 38 | imsg << "Scanner::get(): scp <client>:" << source << " " << |
39 | 39 | destination << endl; |
40 | 40 | |
41 | if (Arg::instance().option('n')) // no run if -n | |
41 | if (d_arg.option('n')) // no run if -n | |
42 | 42 | return; |
43 | 43 | |
44 | 44 | nextCommand(d_sshFork, // start the next command |
0 | #include "scanner.ih" | |
1 | ||
2 | void Scanner::loadSkipFiles() | |
3 | { | |
4 | d_skipFiles.clear(); | |
5 | ||
6 | if (d_skipFilePath.length()) // skip files | |
7 | setSkip(); | |
8 | else // or don't skip | |
9 | d_skip = &Scanner::dontSkip; | |
10 | ||
11 | imsg << "Scanner::loadSkipFiles(): " << d_skipFiles.size() << | |
12 | "lines" << endl; | |
13 | } | |
14 | ||
15 | ||
16 | ||
17 | ||
18 | ||
19 | ||
20 | ||
21 |
24 | 24 | imsg << "Scanner::noDifferences(): /bin/echo " << d_sentinel << |
25 | 25 | endl; |
26 | 26 | |
27 | // key is string, case sensitive. | |
27 | // status's key is a case sensitive string. | |
28 | 28 | // |
29 | 29 | // The last element of the lines produced by diff is used as the |
30 | 30 | // key. For the current function to operate sensibly, this should be |
31 | // a filename or path/file. | |
31 | // a filename or a path/file. | |
32 | 32 | // |
33 | 33 | // If the first character of the line is a < or >, then a modification is |
34 | 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 '<'. It is set to "skipping" at a '>' if the | |
38 | // pathname is found in the array of files to skip. Otherwise | |
35 | // 1. If the key already exists then its .first element is set to | |
36 | // "modified". When a '<' was seen and if the key doesn't yet | |
37 | // exist, it is set to "added". | |
38 | // If the pathname is found in the array of files to skip | |
39 | // or if the pathname truncated at the last '/' is found in the | |
40 | // d_skipFiles array it is set to "skipping". Otherwise | |
39 | 41 | // it's set to "removed". |
40 | 42 | // 2. The line itself is pushed back to the .second (vector) element |
41 | 43 | // of the pair. |
44 | 46 | // into the d_reporter and `false' is returned. If the hashtable contains |
45 | 47 | // no elements, 'true' is returned. |
46 | 48 | |
47 | string s; | |
49 | string line; | |
48 | 50 | |
49 | 51 | imsg << "Scanner::noDifferences(): starting to read lines" << endl; |
50 | 52 | |
51 | while (getline(d_shFork, s)) | |
53 | // get lines from diff, lines like: | |
54 | // | |
55 | // 33c33 | |
56 | // < 90d8b506d249634c4ff80b9018644567 out | |
57 | // --- | |
58 | // > b88d0b77db74cc4a742d7bc26cdd2a1e out | |
59 | ||
60 | while (getline(d_shFork, line)) | |
52 | 61 | { |
53 | imsg << "Scanner::noDifferences(): got: `" << s << "'\n" | |
62 | imsg << "Scanner::noDifferences(): got: `" << line << "'\n" | |
54 | 63 | "Scanner::noDifferences(): sentinel: `" << d_sentinel << '\'' << |
55 | 64 | endl; |
56 | if (s == d_sentinel) | |
65 | if (line == d_sentinel) // done at the sentinel | |
57 | 66 | break; |
58 | 67 | |
59 | if (!(s_split << s)) | |
60 | continue; // no match at empty lines ? | |
68 | if (not (s_split << line)) // at empty lines proceed to the | |
69 | continue; // next line | |
61 | 70 | |
62 | string key = s_split[1]; // get the key | |
63 | bool exists = status.count(key); | |
71 | string lastWord = s_split[1]; // get the last word on lines | |
72 | // the last word may be an element of a directory to skip or | |
73 | // not. If it is an element of a directory to skip, then the | |
74 | // filename part of lastWord kan be removed. | |
64 | 75 | |
65 | if (s[0] == '<') | |
66 | status[key].first = exists ? "MODIFIED" : "ADDED"; | |
67 | else if (s[0] == '>') // removal or skip, e.g., > b88d0b.... out | |
68 | { | |
69 | if ((this->*d_skip)(key)) | |
70 | status[key].first = "SKIPPING"; | |
71 | else | |
72 | status[key].first = exists ? "MODIFIED" : "REMOVED"; | |
73 | } | |
76 | // find out whether we have to skip this entry or not. If so | |
77 | // remove the filename from lastWord: | |
78 | bool skipEntry = (this->*d_skip)(lastWord); | |
79 | ||
80 | // now we'll process skipped elements by their path-name | |
81 | // and other elements remain as-is | |
82 | bool exists = status.count(lastWord); | |
83 | ||
84 | if (line[0] == '<') | |
85 | status[lastWord].first = exists ? "MODIFIED" : "ADDED"; | |
86 | else if (line[0] == '>') // removal or skip, e.g., > b88d0b.... out | |
87 | status[lastWord].first = skipEntry ? "SKIPPING" : | |
88 | exists ? "MODIFIED" : "REMOVED"; | |
74 | 89 | else |
75 | 90 | continue; |
76 | 91 | |
77 | status[key].second.push_back(s); | |
92 | status[lastWord].second.push_back(line); | |
78 | 93 | } |
79 | 94 | |
80 | 95 | if (!status.size()) // no elements ? |
37 | 37 | |
38 | 38 | string command = putCommand(source, destination); |
39 | 39 | |
40 | if (Arg::instance().option('n')) // no run if -n | |
40 | if (d_arg.option('n')) // no run if -n | |
41 | 41 | return; |
42 | 42 | |
43 | 43 | d_sshFork << command << endl; // flush |
11 | 11 | // add values to it, below. |
12 | 12 | string cmdNr; |
13 | 13 | |
14 | if (Arg::instance().option(&cmdNr, 'r')) // is there a command number? | |
14 | if (d_arg.option(&cmdNr, 'r')) // is there a command number? | |
15 | 15 | { |
16 | 16 | // if so, add its number to |
17 | 17 | // d_cmdIterator |
37 | 37 | } |
38 | 38 | } |
39 | 39 | |
40 | if (Arg::instance().option('d')) | |
40 | if (d_arg.option('d')) | |
41 | 41 | cerr << "Stealth: policy file processed\n"; |
42 | 42 | } |
43 | 43 |
10 | 10 | |
11 | 11 | Scanner::Scanner(ConfigSorter &sorter, Reporter &reporter) |
12 | 12 | : |
13 | d_arg(Arg::instance()), | |
13 | 14 | d_sorter(sorter), |
14 | 15 | d_reporter(reporter), // ostream |
15 | 16 | d_firstWord(*new Pattern("(\\S+)(\\s+(.*))?")), // firstword ([1]) and the |
34 | 35 | { |
35 | 36 | setSentinel(); |
36 | 37 | |
37 | Arg &arg = Arg::instance(); | |
38 | d_arg.option(&d_skipFilePath, 's'); | |
38 | 39 | |
39 | string name; | |
40 | if (arg.option(&name, 's')) // skip files | |
41 | setSkip(name); | |
42 | else // or skip none | |
43 | d_skip = &Scanner::dontSkip; | |
40 | loadSkipFiles(); | |
44 | 41 | |
45 | if (arg.option(&d_maxSizeStr, "max-size")) | |
42 | if (d_arg.option(&d_maxSizeStr, "max-size")) | |
46 | 43 | { |
47 | 44 | d_maxSize = A2x(d_maxSizeStr); |
48 | 45 | switch(d_maxSizeStr.find_first_not_of("0123456789")) |
9 | 9 | namespace FBB |
10 | 10 | { |
11 | 11 | class Pattern; |
12 | class Arg; | |
12 | 13 | } |
13 | 14 | |
14 | 15 | class ConfigSorter; |
19 | 20 | typedef std::vector<std::string> StringVector; |
20 | 21 | typedef StringVector::const_iterator const_iterator; |
21 | 22 | |
23 | FBB::Arg &d_arg; | |
22 | 24 | ConfigSorter &d_sorter; |
23 | 25 | Reporter &d_reporter; |
24 | 26 | FBB::Pattern &d_firstWord; |
33 | 35 | std::string d_maxSizeStr; |
34 | 36 | bool d_quit; |
35 | 37 | StringVector d_skipFiles; |
38 | std::string d_skipFilePath; | |
36 | 39 | // the abs path is at the end of |
37 | 40 | // the line. Any text may precede |
38 | 41 | // it |
39 | bool (Scanner::*d_skip)(std::string const &absPath); | |
42 | bool (Scanner::*d_skip)(std::string &absPath); | |
40 | 43 | |
41 | 44 | static FBB::Pattern s_split; |
42 | 45 | static FBB::Pattern s_firstWord; |
49 | 52 | return d_nScans; |
50 | 53 | } |
51 | 54 | void preamble(); |
55 | void loadSkipFiles(); | |
56 | void nScansReset(); | |
52 | 57 | // run one series of tests |
53 | 58 | void run(volatile bool *done); |
54 | 59 | |
138 | 143 | void removeLOG(); // remove LOG = from current command |
139 | 144 | |
140 | 145 | // true if filename not in d_skipFiles |
141 | bool skip(std::string const &line); | |
146 | bool skip(std::string &line); | |
142 | 147 | |
143 | 148 | // always indicates "don't skip" |
144 | bool dontSkip(std::string const &line); | |
149 | bool dontSkip(std::string &line); | |
145 | 150 | |
146 | 151 | // fill the d_skipFiles vector and set |
147 | // d_skip to &skipING | |
148 | void setSkip(std::string const &fname); | |
152 | void setSkip(); // d_skip to &skipING | |
149 | 153 | |
150 | 154 | static std::string fileName(std::string const &name); |
151 | 155 | static std::string datetime(); |
154 | 158 | static void add(std::string const &line, StringVector &skipFiles); |
155 | 159 | }; |
156 | 160 | |
157 | inline bool Scanner::dontSkip(std::string const &line) | |
161 | inline void Scanner::nScansReset() | |
158 | 162 | { |
159 | return false; // by returning false the question | |
160 | // 'skip line?' is always answered as 'no' | |
163 | d_nScans = 0; | |
161 | 164 | } |
162 | 165 | |
163 | 166 | #endif |
14 | 14 | #include <bobcat/stat> |
15 | 15 | #include <bobcat/datetime> |
16 | 16 | #include <bobcat/string> |
17 | #include <bobcat/stringline> | |
17 | 18 | |
18 | 19 | #include "../util/util.h" |
19 | 20 | #include "../configsorter/configsorter.h" |
20 | 21 | #include "../reporter/reporter.h" |
21 | 22 | |
23 | using namespace std; | |
24 | using namespace FBB; | |
22 | 25 | |
23 | using namespace FBB; | |
24 | using namespace std; | |
25 | ||
26 | struct Line: public string | |
27 | {}; | |
28 | ||
29 | ||
30 | inline istream &operator>>(istream &in, Line &line) | |
26 | inline bool Scanner::dontSkip(std::string &line) | |
31 | 27 | { |
32 | return getline(in, line); | |
28 | return false; // by returning false the question | |
29 | // 'skip line?' is always answered as 'no' | |
33 | 30 | } |
34 | 31 | |
35 | ||
36 | ||
37 | ||
38 |
0 | 0 | #include "scanner.ih" |
1 | 1 | |
2 | void Scanner::setSkip(string const &fname) | |
2 | void Scanner::setSkip() | |
3 | 3 | { |
4 | 4 | ifstream in; |
5 | Errno::open(in, fname); | |
5 | Errno::open(in, d_skipFilePath); | |
6 | 6 | |
7 | 7 | for_each( |
8 | istream_iterator<Line>(in), istream_iterator<Line>(), | |
8 | istream_iterator<StringLine>(in), istream_iterator<StringLine>(), | |
9 | 9 | [&](std::string const &line) |
10 | 10 | { |
11 | 11 | add(line, d_skipFiles); |
12 | 12 | } |
13 | 13 | ); |
14 | ||
14 | ||
15 | 15 | d_skip = &Scanner::skip; |
16 | 16 | } |
17 | 17 |
0 | 0 | #include "scanner.ih" |
1 | 1 | |
2 | bool Scanner::skip(string const &line) | |
2 | // line: the last entry of lines like | |
3 | // 9fd210eed1190870f69c9bc7544cfacb82099efd /root/aantekeningen | |
4 | // so: | |
5 | // /root/aantekeningen | |
6 | // | |
7 | // toSkip: a line from the `skipfiles' file | |
8 | // Files are skipped if: | |
9 | // 1. the filename in `line' equals toSkip: equal filenames | |
10 | // 2. the filename in `line' begins with toSkip and toSkip and | |
11 | // toSkip ends in /: toSkip is a directory, and line is an | |
12 | // entry in that directory | |
13 | ||
14 | bool Scanner::skip(string &lastWord) | |
3 | 15 | { |
4 | 16 | auto end = d_skipFiles.end(); |
5 | 17 | return find_if( |
6 | 18 | d_skipFiles.begin(), end, |
7 | [&](string const &targetLine) | |
19 | [&](string const &toSkip) // toSkip: entry to skip | |
8 | 20 | { |
9 | size_t idx = line.rfind(targetLine); | |
10 | return idx + targetLine.length() == line.length(); | |
21 | if (lastWord == toSkip) // lastWord contains toSkip | |
22 | return true; // skip this file | |
23 | ||
24 | if (toSkip.back() != '/') // toSkip isn't a dir.: | |
25 | return false; // don't skip this entry | |
26 | ||
27 | // skip this entry and remove the filename from | |
28 | // 'lastWord' if lastWord contains toSkip at 0 | |
29 | if (lastWord.find(toSkip) == 0) | |
30 | { | |
31 | lastWord = toSkip; | |
32 | return true; | |
33 | } | |
34 | ||
35 | return false; // don't skip this entry | |
11 | 36 | } |
12 | 37 | ) |
13 | 38 | != end; |
6 | 6 | |
7 | 7 | namespace{ |
8 | 8 | |
9 | Arg::LongOption longOpt_begin[] = | |
9 | Arg::LongOption longOption[] = | |
10 | 10 | { |
11 | 11 | {"debug", 'd'}, |
12 | 12 | {"echo-commands", 'e'}, |
27 | 27 | {"rerun", Arg::None}, // arg[0] is the runfilename |
28 | 28 | {"resume", Arg::None}, // also for this and the next options |
29 | 29 | {"suppress", Arg::None}, |
30 | {"reload", Arg::None}, | |
30 | 31 | {"terminate", Arg::None}, |
31 | 32 | }; |
32 | 33 | |
33 | Arg::LongOption const * const longOpt_end = | |
34 | longOpt_begin + sizeof(longOpt_begin) / sizeof(Arg::LongOption); | |
34 | auto endLongOption = longOption + sizeof(longOption) / sizeof(longOption[0]); | |
35 | 35 | |
36 | 36 | } // anonymous namespace ends |
37 | 37 | |
38 | 38 | int main(int argc, char **argv) |
39 | 39 | try |
40 | { | |
41 | // construct Arg object to process | |
42 | Arg &arg = Arg::initialize("cdehi:noqr:s:v", longOpt_begin, longOpt_end, | |
40 | { // construct Arg object to process | |
41 | Arg &arg = Arg::initialize("cdehi:noqr:s:v", longOption, endLongOption, | |
43 | 42 | argc, argv); |
44 | 43 | |
45 | 44 | arg.versionHelp(usage, version, 1); |
0 | # set up the non-default USE variables | |
1 | USE BASE /home/frank/stealth/ | |
2 | ||
3 | USE EMAIL frank@localhost | |
4 | USE MAILER /usr/bin/mail | |
5 | USE MAILARGS -s "localhost STEALTH report" | |
6 | ||
7 | USE SSH /usr/bin/ssh frank@localhost -q | |
8 | ||
9 | ||
10 | LABEL \nfiles in /home/frank/documents/reizen | |
11 | CHECK remote/reizen \ | |
12 | /usr/bin/find /home/frank/documents/reizen \ | |
13 | -type f -exec /usr/bin/sha1sum {} \; |
0 | /home/frank/documents/reizen/esta/ |
0 | ../tmp/bin/stealth⏎ |
51 | 51 | " policy: path to the policyfile\n" |
52 | 52 | "\n" |
53 | 53 | "Usage 2:\n" |
54 | " " << progname << " [--rerun|--resume|--suppress|--terminate] " | |
55 | "pidfile\n" | |
54 | " " << progname << | |
55 | " [--reload|--rerun|--resume|--suppress|--terminate] pidfile\n" | |
56 | 56 | "Where:\n" |
57 | " --reload: reload a " << progname << " process's configuration " | |
58 | "files\n" | |
57 | 59 | " --rerun: restart a " << progname << " integrity scan\n" |
58 | 60 | " --resume: resume " << progname << " following --suppress\n" |
59 | 61 | " --suppress: suppress " << progname << " activities\n" |
0 | #include "util.ih" | |
1 | ||
2 | string Util::makeAbsPath(string const &path) | |
3 | { | |
4 | string ret; | |
5 | ||
6 | if (path[0] == '/') | |
7 | ret = path; | |
8 | else | |
9 | ret = Stat(".").path() + '/' + path; | |
10 | ||
11 | return ret; | |
12 | } |