1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "remote/configpackageutility.hpp"
4 #include "remote/apilistener.hpp"
5 #include "base/application.hpp"
6 #include "base/exception.hpp"
7 #include "base/utility.hpp"
8 #include <boost/algorithm/string.hpp>
9 #include <boost/regex.hpp>
10 #include <algorithm>
11 #include <cctype>
12 #include <fstream>
13
14 using namespace icinga;
15
GetPackageDir()16 String ConfigPackageUtility::GetPackageDir()
17 {
18 return Configuration::DataDir + "/api/packages";
19 }
20
CreatePackage(const String & name)21 void ConfigPackageUtility::CreatePackage(const String& name)
22 {
23 String path = GetPackageDir() + "/" + name;
24
25 if (Utility::PathExists(path))
26 BOOST_THROW_EXCEPTION(std::invalid_argument("Package already exists."));
27
28 Utility::MkDirP(path, 0700);
29 WritePackageConfig(name);
30 }
31
DeletePackage(const String & name)32 void ConfigPackageUtility::DeletePackage(const String& name)
33 {
34 String path = GetPackageDir() + "/" + name;
35
36 if (!Utility::PathExists(path))
37 BOOST_THROW_EXCEPTION(std::invalid_argument("Package does not exist."));
38
39 ApiListener::Ptr listener = ApiListener::GetInstance();
40
41 /* config packages without API make no sense. */
42 if (!listener)
43 BOOST_THROW_EXCEPTION(std::invalid_argument("No ApiListener instance configured."));
44
45 listener->RemoveActivePackageStage(name);
46
47 Utility::RemoveDirRecursive(path);
48 Application::RequestRestart();
49 }
50
GetPackages()51 std::vector<String> ConfigPackageUtility::GetPackages()
52 {
53 String packageDir = GetPackageDir();
54
55 std::vector<String> packages;
56
57 /* Package directory does not exist, no packages have been created thus far. */
58 if (!Utility::PathExists(packageDir))
59 return packages;
60
61 Utility::Glob(packageDir + "/*", [&packages](const String& path) { packages.emplace_back(Utility::BaseName(path)); }, GlobDirectory);
62
63 return packages;
64 }
65
PackageExists(const String & name)66 bool ConfigPackageUtility::PackageExists(const String& name)
67 {
68 auto packages (GetPackages());
69 return std::find(packages.begin(), packages.end(), name) != packages.end();
70 }
71
CreateStage(const String & packageName,const Dictionary::Ptr & files)72 String ConfigPackageUtility::CreateStage(const String& packageName, const Dictionary::Ptr& files)
73 {
74 String stageName = Utility::NewUniqueID();
75
76 String path = GetPackageDir() + "/" + packageName;
77
78 if (!Utility::PathExists(path))
79 BOOST_THROW_EXCEPTION(std::invalid_argument("Package does not exist."));
80
81 path += "/" + stageName;
82
83 Utility::MkDirP(path, 0700);
84 Utility::MkDirP(path + "/conf.d", 0700);
85 Utility::MkDirP(path + "/zones.d", 0700);
86 WriteStageConfig(packageName, stageName);
87
88 bool foundDotDot = false;
89
90 if (files) {
91 ObjectLock olock(files);
92 for (const Dictionary::Pair& kv : files) {
93 if (ContainsDotDot(kv.first)) {
94 foundDotDot = true;
95 break;
96 }
97
98 String filePath = path + "/" + kv.first;
99
100 Log(LogInformation, "ConfigPackageUtility")
101 << "Updating configuration file: " << filePath;
102
103 // Pass the directory and generate a dir tree, if it does not already exist
104 Utility::MkDirP(Utility::DirName(filePath), 0750);
105 std::ofstream fp(filePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
106 fp << kv.second;
107 fp.close();
108 }
109 }
110
111 if (foundDotDot) {
112 Utility::RemoveDirRecursive(path);
113 BOOST_THROW_EXCEPTION(std::invalid_argument("Path must not contain '..'."));
114 }
115
116 return stageName;
117 }
118
WritePackageConfig(const String & packageName)119 void ConfigPackageUtility::WritePackageConfig(const String& packageName)
120 {
121 String stageName = GetActiveStage(packageName);
122
123 String includePath = GetPackageDir() + "/" + packageName + "/include.conf";
124 std::ofstream fpInclude(includePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
125 fpInclude << "include \"*/include.conf\"\n";
126 fpInclude.close();
127
128 String activePath = GetPackageDir() + "/" + packageName + "/active.conf";
129 std::ofstream fpActive(activePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
130 fpActive << "if (!globals.contains(\"ActiveStages\")) {\n"
131 << " globals.ActiveStages = {}\n"
132 << "}\n"
133 << "\n"
134 << "if (globals.contains(\"ActiveStageOverride\")) {\n"
135 << " var arr = ActiveStageOverride.split(\":\")\n"
136 << " if (arr[0] == \"" << packageName << "\") {\n"
137 << " if (arr.len() < 2) {\n"
138 << " log(LogCritical, \"Config\", \"Invalid value for ActiveStageOverride\")\n"
139 << " } else {\n"
140 << " ActiveStages[\"" << packageName << "\"] = arr[1]\n"
141 << " }\n"
142 << " }\n"
143 << "}\n"
144 << "\n"
145 << "if (!ActiveStages.contains(\"" << packageName << "\")) {\n"
146 << " ActiveStages[\"" << packageName << "\"] = \"" << stageName << "\"\n"
147 << "}\n";
148 fpActive.close();
149 }
150
WriteStageConfig(const String & packageName,const String & stageName)151 void ConfigPackageUtility::WriteStageConfig(const String& packageName, const String& stageName)
152 {
153 String path = GetPackageDir() + "/" + packageName + "/" + stageName + "/include.conf";
154 std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
155 fp << "include \"../active.conf\"\n"
156 << "if (ActiveStages[\"" << packageName << "\"] == \"" << stageName << "\") {\n"
157 << " include_recursive \"conf.d\"\n"
158 << " include_zones \"" << packageName << "\", \"zones.d\"\n"
159 << "}\n";
160 fp.close();
161 }
162
ActivateStage(const String & packageName,const String & stageName)163 void ConfigPackageUtility::ActivateStage(const String& packageName, const String& stageName)
164 {
165 SetActiveStage(packageName, stageName);
166
167 WritePackageConfig(packageName);
168 }
169
TryActivateStageCallback(const ProcessResult & pr,const String & packageName,const String & stageName,bool activate,bool reload)170 void ConfigPackageUtility::TryActivateStageCallback(const ProcessResult& pr, const String& packageName, const String& stageName, bool activate, bool reload)
171 {
172 String logFile = GetPackageDir() + "/" + packageName + "/" + stageName + "/startup.log";
173 std::ofstream fpLog(logFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
174 fpLog << pr.Output;
175 fpLog.close();
176
177 String statusFile = GetPackageDir() + "/" + packageName + "/" + stageName + "/status";
178 std::ofstream fpStatus(statusFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
179 fpStatus << pr.ExitStatus;
180 fpStatus.close();
181
182 /* validation went fine, activate stage and reload */
183 if (pr.ExitStatus == 0) {
184 if (activate) {
185 {
186 std::unique_lock<std::mutex> lock(GetStaticPackageMutex());
187
188 ActivateStage(packageName, stageName);
189 }
190
191 if (reload)
192 Application::RequestRestart();
193 }
194 } else {
195 Log(LogCritical, "ConfigPackageUtility")
196 << "Config validation failed for package '"
197 << packageName << "' and stage '" << stageName << "'.";
198 }
199 }
200
AsyncTryActivateStage(const String & packageName,const String & stageName,bool activate,bool reload)201 void ConfigPackageUtility::AsyncTryActivateStage(const String& packageName, const String& stageName, bool activate, bool reload)
202 {
203 VERIFY(Application::GetArgC() >= 1);
204
205 // prepare arguments
206 Array::Ptr args = new Array({
207 Application::GetExePath(Application::GetArgV()[0]),
208 });
209
210 // copy all arguments of parent process
211 for (int i = 1; i < Application::GetArgC(); i++) {
212 String argV = Application::GetArgV()[i];
213
214 if (argV == "-d" || argV == "--daemonize")
215 continue;
216
217 args->Add(argV);
218 }
219
220 // add arguments for validation
221 args->Add("--validate");
222 args->Add("--define");
223 args->Add("ActiveStageOverride=" + packageName + ":" + stageName);
224
225 Process::Ptr process = new Process(Process::PrepareCommand(args));
226 process->SetTimeout(Application::GetReloadTimeout());
227 process->Run([packageName, stageName, activate, reload](const ProcessResult& pr) { TryActivateStageCallback(pr, packageName, stageName, activate, reload); });
228 }
229
DeleteStage(const String & packageName,const String & stageName)230 void ConfigPackageUtility::DeleteStage(const String& packageName, const String& stageName)
231 {
232 String path = GetPackageDir() + "/" + packageName + "/" + stageName;
233
234 if (!Utility::PathExists(path))
235 BOOST_THROW_EXCEPTION(std::invalid_argument("Stage does not exist."));
236
237 if (GetActiveStage(packageName) == stageName)
238 BOOST_THROW_EXCEPTION(std::invalid_argument("Active stage cannot be deleted."));
239
240 Utility::RemoveDirRecursive(path);
241 }
242
GetStages(const String & packageName)243 std::vector<String> ConfigPackageUtility::GetStages(const String& packageName)
244 {
245 std::vector<String> stages;
246 Utility::Glob(GetPackageDir() + "/" + packageName + "/*", [&stages](const String& path) { stages.emplace_back(Utility::BaseName(path)); }, GlobDirectory);
247 return stages;
248 }
249
GetActiveStageFromFile(const String & packageName)250 String ConfigPackageUtility::GetActiveStageFromFile(const String& packageName)
251 {
252 /* Lock the transaction, reading this only happens on startup or when something really is broken. */
253 std::unique_lock<std::mutex> lock(GetStaticActiveStageMutex());
254
255 String path = GetPackageDir() + "/" + packageName + "/active-stage";
256
257 std::ifstream fp;
258 fp.open(path.CStr());
259
260 String stage;
261 std::getline(fp, stage.GetData());
262
263 fp.close();
264
265 if (fp.fail())
266 return ""; /* Don't use exceptions here. The caller must deal with empty stages at this point. Happens on initial package creation for example. */
267
268 return stage.Trim();
269 }
270
SetActiveStageToFile(const String & packageName,const String & stageName)271 void ConfigPackageUtility::SetActiveStageToFile(const String& packageName, const String& stageName)
272 {
273 std::unique_lock<std::mutex> lock(GetStaticActiveStageMutex());
274
275 String activeStagePath = GetPackageDir() + "/" + packageName + "/active-stage";
276
277 std::ofstream fpActiveStage(activeStagePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc); //TODO: fstream exceptions
278 fpActiveStage << stageName;
279 fpActiveStage.close();
280 }
281
GetActiveStage(const String & packageName)282 String ConfigPackageUtility::GetActiveStage(const String& packageName)
283 {
284 String activeStage;
285
286 ApiListener::Ptr listener = ApiListener::GetInstance();
287
288 /* If we don't have an API feature, just use the file storage without caching this.
289 * This happens when ScheduledDowntime objects generate Downtime objects.
290 * TODO: Make the API a first class citizen.
291 */
292 if (!listener)
293 return GetActiveStageFromFile(packageName);
294
295 /* First use runtime state. */
296 try {
297 activeStage = listener->GetActivePackageStage(packageName);
298 } catch (const std::exception& ex) {
299 /* Fallback to reading the file, happens on restarts. */
300 activeStage = GetActiveStageFromFile(packageName);
301
302 /* When we've read something, correct memory. */
303 if (!activeStage.IsEmpty())
304 listener->SetActivePackageStage(packageName, activeStage);
305 }
306
307 return activeStage;
308 }
309
SetActiveStage(const String & packageName,const String & stageName)310 void ConfigPackageUtility::SetActiveStage(const String& packageName, const String& stageName)
311 {
312 /* Update the marker on disk for restarts. */
313 SetActiveStageToFile(packageName, stageName);
314
315 ApiListener::Ptr listener = ApiListener::GetInstance();
316
317 /* No API, no caching. */
318 if (!listener)
319 return;
320
321 listener->SetActivePackageStage(packageName, stageName);
322 }
323
GetFiles(const String & packageName,const String & stageName)324 std::vector<std::pair<String, bool> > ConfigPackageUtility::GetFiles(const String& packageName, const String& stageName)
325 {
326 std::vector<std::pair<String, bool> > paths;
327 Utility::GlobRecursive(GetPackageDir() + "/" + packageName + "/" + stageName, "*", [&paths](const String& path) {
328 CollectPaths(path, paths);
329 }, GlobDirectory | GlobFile);
330
331 return paths;
332 }
333
CollectPaths(const String & path,std::vector<std::pair<String,bool>> & paths)334 void ConfigPackageUtility::CollectPaths(const String& path, std::vector<std::pair<String, bool> >& paths)
335 {
336 #ifndef _WIN32
337 struct stat statbuf;
338 int rc = lstat(path.CStr(), &statbuf);
339 if (rc < 0)
340 BOOST_THROW_EXCEPTION(posix_error()
341 << boost::errinfo_api_function("lstat")
342 << boost::errinfo_errno(errno)
343 << boost::errinfo_file_name(path));
344
345 paths.emplace_back(path, S_ISDIR(statbuf.st_mode));
346 #else /* _WIN32 */
347 struct _stat statbuf;
348 int rc = _stat(path.CStr(), &statbuf);
349 if (rc < 0)
350 BOOST_THROW_EXCEPTION(posix_error()
351 << boost::errinfo_api_function("_stat")
352 << boost::errinfo_errno(errno)
353 << boost::errinfo_file_name(path));
354
355 paths.emplace_back(path, ((statbuf.st_mode & S_IFMT) == S_IFDIR));
356 #endif /* _WIN32 */
357 }
358
ContainsDotDot(const String & path)359 bool ConfigPackageUtility::ContainsDotDot(const String& path)
360 {
361 std::vector<String> tokens = path.Split("/\\");
362
363 for (const String& part : tokens) {
364 if (part == "..")
365 return true;
366 }
367
368 return false;
369 }
370
ValidatePackageName(const String & packageName)371 bool ConfigPackageUtility::ValidatePackageName(const String& packageName)
372 {
373 return ValidateFreshName(packageName) || PackageExists(packageName);
374 }
375
ValidateFreshName(const String & name)376 bool ConfigPackageUtility::ValidateFreshName(const String& name)
377 {
378 if (name.IsEmpty())
379 return false;
380
381 /* check for path injection */
382 if (ContainsDotDot(name))
383 return false;
384
385 return std::all_of(name.Begin(), name.End(), [](char c) {
386 return std::isalnum(c, std::locale::classic()) || c == '_' || c == '-';
387 });
388 }
389
GetStaticPackageMutex()390 std::mutex& ConfigPackageUtility::GetStaticPackageMutex()
391 {
392 static std::mutex mutex;
393 return mutex;
394 }
395
GetStaticActiveStageMutex()396 std::mutex& ConfigPackageUtility::GetStaticActiveStageMutex()
397 {
398 static std::mutex mutex;
399 return mutex;
400 }
401