1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2 
3 #include <string>
4 #include <vector>
5 #include <fstream>
6 #include <direct.h>
7 #include <windows.h>
8 #include <shlwapi.h>
9 #include <shellapi.h>
10 #include <shlobj.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 
GetIcingaInstallPath(void)14 static std::string GetIcingaInstallPath(void)
15 {
16 	char szFileName[MAX_PATH];
17 	if (!GetModuleFileName(nullptr, szFileName, sizeof(szFileName)))
18 		return "";
19 
20 	if (!PathRemoveFileSpec(szFileName))
21 		return "";
22 
23 	if (!PathRemoveFileSpec(szFileName))
24 		return "";
25 
26 	return szFileName;
27 }
28 
29 
ExecuteCommand(const std::string & app,const std::string & arguments)30 static bool ExecuteCommand(const std::string& app, const std::string& arguments)
31 {
32 	SHELLEXECUTEINFO sei = {};
33 	sei.cbSize = sizeof(sei);
34 	sei.fMask = SEE_MASK_NOCLOSEPROCESS;
35 	sei.lpFile = app.c_str();
36 	sei.lpParameters = arguments.c_str();
37 	sei.nShow = SW_HIDE;
38 	if (!ShellExecuteEx(&sei))
39 		return false;
40 
41 	if (!sei.hProcess)
42 		return false;
43 
44 	WaitForSingleObject(sei.hProcess, INFINITE);
45 
46 	DWORD exitCode;
47 	BOOL res = GetExitCodeProcess(sei.hProcess, &exitCode);
48 	CloseHandle(sei.hProcess);
49 
50 	if (!res)
51 		return false;
52 
53 	return exitCode == 0;
54 }
55 
ExecuteIcingaCommand(const std::string & arguments)56 static bool ExecuteIcingaCommand(const std::string& arguments)
57 {
58 	return ExecuteCommand(GetIcingaInstallPath() + "\\sbin\\icinga2.exe", arguments);
59 }
60 
DirName(const std::string & path)61 static std::string DirName(const std::string& path)
62 {
63 	char *spath = strdup(path.c_str());
64 
65 	if (!PathRemoveFileSpec(spath)) {
66 		free(spath);
67 		throw std::runtime_error("PathRemoveFileSpec failed");
68 	}
69 
70 	std::string result = spath;
71 
72 	free(spath);
73 
74 	return result;
75 }
76 
PathExists(const std::string & path)77 static bool PathExists(const std::string& path)
78 {
79 	struct _stat statbuf;
80 	return (_stat(path.c_str(), &statbuf) >= 0);
81 }
82 
GetIcingaDataPath(void)83 static std::string GetIcingaDataPath(void)
84 {
85 	char path[MAX_PATH];
86 	if (!SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, nullptr, 0, path)))
87 		throw std::runtime_error("SHGetFolderPath failed");
88 	return std::string(path) + "\\icinga2";
89 }
90 
MkDir(const std::string & path)91 static void MkDir(const std::string& path)
92 {
93 	if (mkdir(path.c_str()) < 0 && errno != EEXIST)
94 		throw std::runtime_error("mkdir failed");
95 }
96 
MkDirP(const std::string & path)97 static void MkDirP(const std::string& path)
98 {
99 	size_t pos = 0;
100 
101 	while (pos != std::string::npos) {
102 		pos = path.find_first_of("/\\", pos + 1);
103 
104 		std::string spath = path.substr(0, pos + 1);
105 		struct _stat statbuf;
106 		if (_stat(spath.c_str(), &statbuf) < 0 && errno == ENOENT)
107 			MkDir(path.substr(0, pos));
108 	}
109 }
110 
GetNSISInstallPath(void)111 static std::string GetNSISInstallPath(void)
112 {
113 	HKEY hKey;
114 	//TODO: Change hardcoded key
115 	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Icinga Development Team\\ICINGA2", 0,
116 		KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hKey) == ERROR_SUCCESS) {
117 		BYTE pvData[MAX_PATH];
118 		DWORD cbData = sizeof(pvData) - 1;
119 		DWORD lType;
120 		if (RegQueryValueEx(hKey, nullptr, nullptr, &lType, pvData, &cbData) == ERROR_SUCCESS && lType == REG_SZ) {
121 			pvData[cbData] = '\0';
122 
123 			return (char *)pvData;
124 		}
125 
126 		RegCloseKey(hKey);
127 	}
128 
129 	return "";
130 }
131 
CopyDirectory(const std::string & source,const std::string & destination)132 static bool CopyDirectory(const std::string& source, const std::string& destination)
133 {
134 	// SHFileOperation requires file names to be terminated with two \0s
135 	std::string tmpSource = source + std::string(1, '\0');
136 	std::string tmpDestination = destination + std::string(1, '\0');
137 
138 	SHFILEOPSTRUCT fop;
139 	fop.wFunc = FO_COPY;
140 	fop.pFrom = tmpSource.c_str();
141 	fop.pTo = tmpDestination.c_str();
142 	fop.fFlags = FOF_NO_UI;
143 
144 	return (SHFileOperation(&fop) == 0);
145 }
146 
DeleteDirectory(const std::string & dir)147 static bool DeleteDirectory(const std::string& dir)
148 {
149 	// SHFileOperation requires file names to be terminated with two \0s
150 	std::string tmpDir = dir + std::string(1, '\0');
151 
152 	SHFILEOPSTRUCT fop;
153 	fop.wFunc = FO_DELETE;
154 	fop.pFrom = tmpDir.c_str();
155 	fop.fFlags = FOF_NO_UI;
156 
157 	return (SHFileOperation(&fop) == 0);
158 }
159 
UpgradeNSIS(void)160 static int UpgradeNSIS(void)
161 {
162 	std::string installPath = GetNSISInstallPath();
163 
164 	if (installPath.empty())
165 		return 0;
166 
167 	std::string uninstallerPath = installPath + "\\uninstall.exe";
168 
169 	if (!PathExists(uninstallerPath))
170 		return 0;
171 
172 	std::string dataPath = GetIcingaDataPath();
173 
174 	if (dataPath.empty())
175 		return 1;
176 
177 	bool moveUserData = !PathExists(dataPath);
178 
179 	/* perform open heart surgery on the user's data dirs - yay */
180 	if (moveUserData) {
181 		MkDir(dataPath.c_str());
182 
183 		std::string oldNameEtc = installPath + "\\etc";
184 		std::string newNameEtc = dataPath + "\\etc";
185 		if (!CopyDirectory(oldNameEtc, newNameEtc))
186 			return 1;
187 
188 		std::string oldNameVar = installPath + "\\var";
189 		std::string newNameVar = dataPath + "\\var";
190 		if (!CopyDirectory(oldNameVar, newNameVar))
191 			return 1;
192 	}
193 
194 	ExecuteCommand(uninstallerPath, "/S _?=" + installPath);
195 
196 	_unlink(uninstallerPath.c_str());
197 
198 	if (moveUserData) {
199 		std::string oldNameEtc = installPath + "\\etc";
200 		if (!DeleteDirectory(oldNameEtc))
201 			return 1;
202 
203 		std::string oldNameVar = installPath + "\\var";
204 		if (!DeleteDirectory(oldNameVar))
205 			return 1;
206 
207 		_rmdir(installPath.c_str());
208 	}
209 
210 	return 0;
211 }
212 
InstallIcinga(void)213 static int InstallIcinga(void)
214 {
215 	std::string installDir = GetIcingaInstallPath();
216 	std::string skelDir = installDir + "\\share\\skel";
217 	std::string dataDir = GetIcingaDataPath();
218 
219 	if (!PathExists(dataDir)) {
220 		std::string sourceDir = skelDir + std::string(1, '\0');
221 		std::string destinationDir = dataDir + std::string(1, '\0');
222 
223 		SHFILEOPSTRUCT fop;
224 		fop.wFunc = FO_COPY;
225 		fop.pFrom = sourceDir.c_str();
226 		fop.pTo = destinationDir.c_str();
227 		fop.fFlags = FOF_NO_UI | FOF_NOCOPYSECURITYATTRIBS;
228 
229 		if (SHFileOperation(&fop) != 0)
230 			return 1;
231 
232 		MkDirP(dataDir + "/etc/icinga2/pki");
233 		MkDirP(dataDir + "/var/cache/icinga2");
234 		MkDirP(dataDir + "/var/lib/icinga2/certs");
235 		MkDirP(dataDir + "/var/lib/icinga2/certificate-requests");
236 		MkDirP(dataDir + "/var/lib/icinga2/agent/inventory");
237 		MkDirP(dataDir + "/var/lib/icinga2/api/config");
238 		MkDirP(dataDir + "/var/lib/icinga2/api/log");
239 		MkDirP(dataDir + "/var/lib/icinga2/api/zones");
240 		MkDirP(dataDir + "/var/log/icinga2/compat/archive");
241 		MkDirP(dataDir + "/var/log/icinga2/crash");
242 		MkDirP(dataDir + "/var/run/icinga2/cmd");
243 		MkDirP(dataDir + "/var/spool/icinga2/perfdata");
244 		MkDirP(dataDir + "/var/spool/icinga2/tmp");
245 	}
246 
247 	// Upgrade from versions older than 2.13 by making the windowseventlog feature available,
248 	// enable it by default and disable the old mainlog feature.
249 	if (!PathExists(dataDir + "/etc/icinga2/features-available/windowseventlog.conf")) {
250 		// Disable the old mainlog feature as it is replaced by windowseventlog by default.
251 		std::string mainlogEnabledFile = dataDir + "/etc/icinga2/features-enabled/mainlog.conf";
252 		if (PathExists(mainlogEnabledFile)) {
253 			if (DeleteFileA(mainlogEnabledFile.c_str()) == 0) {
254 				throw std::runtime_error("deleting '" + mainlogEnabledFile + "' failed");
255 			}
256 		}
257 
258 		// Install the new windowseventlog feature. As features-available/windowseventlog.conf is used as a marker file,
259 		// copy it as the last step, so that this is run again should the upgrade be interrupted.
260 		for (const std::string& d : {"features-enabled", "features-available"}) {
261 			std::string sourceFile = skelDir + "/etc/icinga2/" + d + "/windowseventlog.conf";
262 			std::string destinationFile = dataDir + "/etc/icinga2/" + d + "/windowseventlog.conf";
263 
264 			if (CopyFileA(sourceFile.c_str(), destinationFile.c_str(), false) == 0) {
265 				throw std::runtime_error("copying '" + sourceFile + "' to '" + destinationFile + "' failed");
266 			}
267 		}
268 	}
269 
270 	// TODO: In Icinga 2.14, rename features-available/mainlog.conf to mainlog.conf.deprecated
271 	//       so that it's no longer listed as an available feature.
272 
273 	ExecuteCommand("icacls", "\"" + dataDir + "\" /grant *S-1-5-20:(oi)(ci)m");
274 	ExecuteCommand("icacls", "\"" + dataDir + "\\etc\" /inheritance:r /grant:r *S-1-5-20:(oi)(ci)m *S-1-5-32-544:(oi)(ci)f");
275 
276 	ExecuteIcingaCommand("--scm-install daemon");
277 
278 	return 0;
279 }
280 
UninstallIcinga(void)281 static int UninstallIcinga(void)
282 {
283 	ExecuteIcingaCommand("--scm-uninstall");
284 
285 	return 0;
286 }
287 
288 /**
289 * Entry point for the installer application.
290 */
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)291 int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
292 {
293 	CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
294 
295 	//AllocConsole();
296 	int rc;
297 
298 	if (strcmp(lpCmdLine, "install") == 0) {
299 		rc = InstallIcinga();
300 	} else if (strcmp(lpCmdLine, "uninstall") == 0) {
301 		rc = UninstallIcinga();
302 	} else if (strcmp(lpCmdLine, "upgrade-nsis") == 0) {
303 		rc = UpgradeNSIS();
304 	} else {
305 		MessageBox(nullptr, "This application should only be run by the MSI installer package.", "Icinga 2 Installer", MB_ICONWARNING);
306 		rc = 1;
307 	}
308 
309 	//::Sleep(3000s);
310 
311 	return rc;
312 }
313