1 /** 2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 Run in the background a continious purge of all process trees that do not have 7 a distssh client in the root. 8 */ 9 module distssh.purge; 10 11 import logger = std.experimental.logger; 12 import std.algorithm : splitter, map, filter, joiner; 13 import std.array : array, appender, empty; 14 import std.conv; 15 import std.exception : collectException; 16 import std.file; 17 import std.path; 18 import std.range : only; 19 import std.typecons : Flag; 20 21 import core.sys.posix.sys.types : uid_t; 22 23 import colorlog; 24 import my.set; 25 import proc; 26 27 import distssh.config; 28 import distssh.metric; 29 import distssh.types; 30 import distssh.utility; 31 32 @safe: 33 34 int cli(const Config fconf, Config.LocalPurge conf) @trusted nothrow { 35 Whitelist wl; 36 try { 37 wl = Whitelist(conf.whiteList); 38 } catch (Exception e) { 39 logger.error(e.msg).collectException; 40 return 1; 41 } 42 43 void iterate(alias fn)() { 44 auto pmap = () { 45 auto rval = makePidMap; 46 if (conf.userFilter) 47 return rval.filterByCurrentUser; 48 // assuming that processes owned by root such as init never 49 // interesting to kill. 4294967295 is a magic number used in linux 50 // for system processes. 51 return rval.removeUser(0).removeUser(4294967295); 52 }(); 53 updateProc(pmap); 54 debug logger.trace(pmap); 55 56 foreach (ref t; pmap.splitToSubMaps) { 57 bool hasWhiteListProc; 58 foreach (c; t.map.proc) { 59 if (wl.match(c)) { 60 hasWhiteListProc = true; 61 break; 62 } 63 } 64 65 fn(hasWhiteListProc, t.map, t.root); 66 } 67 } 68 69 void purgeKill(bool hasWhiteListProc, ref PidMap pmap, RawPid root) { 70 if (conf.kill && !hasWhiteListProc) { 71 auto killed = proc.kill(pmap, cast(Flag!"onlyCurrentUser") conf.userFilter); 72 reap(killed); 73 } 74 } 75 76 void purgePrint(bool hasWhiteListProc, ref PidMap pmap, RawPid root) { 77 import std.stdio : writeln; 78 79 if (conf.print) { 80 if (hasWhiteListProc && fconf.global.verbosity >= VerboseMode.info) { 81 logger.info("whitelist".color(Color.green), " process tree"); 82 writeln(toTreeString(pmap)); 83 } else if (!hasWhiteListProc) { 84 logger.info("terminate".color(Color.red), " process tree"); 85 writeln(toTreeString(pmap)); 86 } 87 } 88 } 89 90 try { 91 iterate!purgeKill(); 92 iterate!purgePrint(); 93 } catch (Exception e) { 94 logger.error(e.msg).collectException; 95 return 1; 96 } 97 98 return 0; 99 } 100 101 int cli(const Config fconf, Config.Purge conf) @trusted nothrow { 102 import std.algorithm : sort; 103 104 auto hosts = RemoteHostCache.make(fconf.global.dbPath, fconf.global.cluster).allRange; 105 106 if (hosts.empty) { 107 logger.errorf("No remote host online").collectException; 108 return 1; 109 } 110 111 auto failed = appender!(Host[])(); 112 auto econf = ExecuteOnHostConf(fconf.global.workDir, null, 113 fconf.global.importEnv, fconf.global.cloneEnv, fconf.global.noImportEnv); 114 115 foreach (a; hosts.map!(a => a.host)) { 116 logger.info("Connecting to ", a).collectException; 117 if (purgeServer(econf, conf, a, fconf.global.verbosity) != 0) { 118 failed.put(a); 119 } 120 } 121 122 if (!failed.data.empty) { 123 logger.warning("Failed to purge").collectException; 124 foreach (a; failed.data) { 125 logger.info(a).collectException; 126 } 127 } 128 129 return failed.data.empty ? 0 : 1; 130 } 131 132 int purgeServer(ExecuteOnHostConf econf, const Config.Purge pconf, Host host, 133 VerboseMode vmode = VerboseMode.init) @safe nothrow { 134 import std.file : thisExePath; 135 import std.process : escapeShellFileName; 136 137 econf.command = () { 138 string[] r; 139 try { 140 r = [thisExePath.escapeShellFileName]; 141 } catch (Exception e) { 142 r = ["distssh"]; 143 } 144 return r ~ "localpurge"; 145 }(); 146 147 try { 148 econf.command ~= ["-v", vmode.to!string]; 149 } catch (Exception e) { 150 } 151 152 if (pconf.print) { 153 econf.command ~= "-p"; 154 } 155 if (pconf.kill) { 156 econf.command ~= "-k"; 157 } 158 if (pconf.userFilter) { 159 econf.command ~= "--user-filter"; 160 } 161 162 Set!string wlist; 163 foreach (a; only(pconf.whiteList, readPurgeEnvWhiteList).joiner) { 164 wlist.add(a); 165 } 166 167 econf.command ~= wlist.toArray.map!(a => ["--whitelist", a]).joiner.array; 168 169 logger.trace("Purge command ", econf.command).collectException; 170 171 return () @trusted { return executeOnHost(econf, host); }(); 172 } 173 174 string[] readPurgeEnvWhiteList() @safe nothrow { 175 import std.process : environment; 176 import std..string : strip; 177 178 try { 179 return environment.get(globalEnvPurgeWhiteList, "").strip.splitter(";").map!(a => a.strip) 180 .filter!(a => !a.empty) 181 .array; 182 } catch (Exception e) { 183 } 184 185 return null; 186 } 187 188 private: 189 190 struct Whitelist { 191 import std.regex : regex, matchFirst, Regex; 192 193 Regex!char[] list; 194 195 this(string[] whitelist) { 196 foreach (w; whitelist) { 197 list ~= regex(w, "i"); 198 } 199 } 200 201 bool match(string s) { 202 foreach (ref l; list) { 203 auto m = matchFirst(s, l); 204 if (!m.empty) { 205 return true; 206 } 207 } 208 return false; 209 } 210 }