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