1 /*
2   Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
3 
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2.0,
6   as published by the Free Software Foundation.
7 
8   This program is also distributed with certain software (including
9   but not limited to OpenSSL) that is licensed under separate terms,
10   as designated in a particular file or component or in included license
11   documentation.  The authors of MySQL hereby grant you an additional
12   permission to link the program and your derivative works with the
13   separately licensed software that they have included with MySQL.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19 
20   You should have received a copy of the GNU General Public License
21   along with this program; if not, write to the Free Software
22   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 #include "mysql/harness/filesystem.h"
26 
27 #include <cstring>
28 #include <fstream>
29 #include <ostream>
30 #ifndef _WIN32
31 #include <fcntl.h>
32 #include <sys/stat.h>
33 #endif
34 
35 using std::string;
36 
37 namespace mysql_harness {
38 
39 #ifndef _WIN32
40 const perm_mode kStrictDirectoryPerm = S_IRWXU;
41 #else
42 const perm_mode kStrictDirectoryPerm = 0;
43 #endif
44 
45 ////////////////////////////////////////////////////////////////
46 // class Path members and free functions
47 
Path()48 Path::Path() noexcept : type_(FileType::EMPTY_PATH) {}
49 
50 // throws std::invalid_argument
Path(const std::string & path)51 Path::Path(const std::string &path)
52     : path_(path), type_(FileType::TYPE_UNKNOWN) {
53 #ifdef _WIN32
54   // in Windows, we normalize directory separator from \ to /, to not
55   // confuse the rest of the code, which assume \ to be an escape char
56   std::string::size_type p = path_.find('\\');
57   while (p != std::string::npos) {
58     path_[p] = '/';
59     p = path_.find('\\');
60   }
61 #endif
62   string::size_type pos = path_.find_last_not_of(directory_separator);
63   if (pos != string::npos)
64     path_.erase(pos + 1);
65   else if (path_.size() > 0)
66     path_.erase(1);
67   else
68     throw std::invalid_argument("Empty path");
69 }
70 
71 // throws std::invalid_argument
Path(const char * path)72 Path::Path(const char *path) : Path(string(path)) {}
73 
74 // throws std::invalid_argument
validate_non_empty_path() const75 void Path::validate_non_empty_path() const {
76   if (!is_set()) {
77     throw std::invalid_argument("Empty path");
78   }
79 }
80 
operator ==(const Path & rhs) const81 bool Path::operator==(const Path &rhs) const {
82   return real_path().str() == rhs.real_path().str();
83 }
84 
operator <(const Path & rhs) const85 bool Path::operator<(const Path &rhs) const { return path_ < rhs.path_; }
86 
basename() const87 Path Path::basename() const {
88   validate_non_empty_path();  // throws std::invalid_argument
89   string::size_type pos = path_.find_last_of(directory_separator);
90   if (pos == string::npos)
91     return *this;
92   else if (pos > 1)
93     return string(path_, pos + 1);
94   else
95     return Path(root_directory);
96 }
97 
dirname() const98 Path Path::dirname() const {
99   validate_non_empty_path();  // throws std::invalid_argument
100   string::size_type pos = path_.find_last_of(directory_separator);
101   if (pos == string::npos)
102     return Path(".");
103   else if (pos > 0)
104     return string(path_, 0, pos);
105   else
106     return Path(root_directory);
107 }
108 
is_directory() const109 bool Path::is_directory() const {
110   validate_non_empty_path();  // throws std::invalid_argument
111   return type() == FileType::DIRECTORY_FILE;
112 }
113 
is_regular() const114 bool Path::is_regular() const {
115   validate_non_empty_path();  // throws std::invalid_argument
116   return type() == FileType::REGULAR_FILE;
117 }
118 
is_absolute() const119 bool Path::is_absolute() const {
120   validate_non_empty_path();  // throws std::invalid_argument
121 #ifdef _WIN32
122   if (path_[0] == '\\' || path_[0] == '/' || path_[1] == ':') return true;
123   return false;
124 #else
125   if (path_[0] == '/') return true;
126   return false;
127 #endif
128 }
129 
exists() const130 bool Path::exists() const {
131   validate_non_empty_path();  // throws std::invalid_argument
132   return type() != FileType::FILE_NOT_FOUND && type() != FileType::STATUS_ERROR;
133 }
134 
is_readable() const135 bool Path::is_readable() const {
136   validate_non_empty_path();
137   return exists() && std::ifstream(real_path().str()).good();
138 }
139 
append(const Path & other)140 void Path::append(const Path &other) {
141   validate_non_empty_path();        // throws std::invalid_argument
142   other.validate_non_empty_path();  // throws std::invalid_argument
143   path_.append(directory_separator + other.path_);
144   type_ = FileType::TYPE_UNKNOWN;
145 }
146 
join(const Path & other) const147 Path Path::join(const Path &other) const {
148   validate_non_empty_path();        // throws std::invalid_argument
149   other.validate_non_empty_path();  // throws std::invalid_argument
150   Path result(*this);
151   result.append(other);
152   return result;
153 }
154 
file_type_name(Path::FileType type)155 static const char *file_type_name(Path::FileType type) {
156   switch (type) {
157     case Path::FileType::DIRECTORY_FILE:
158       return "a directory";
159     case Path::FileType::CHARACTER_FILE:
160       return "a character device";
161     case Path::FileType::BLOCK_FILE:
162       return "a block device";
163     case Path::FileType::EMPTY_PATH:
164       return "an empty path";
165     case Path::FileType::FIFO_FILE:
166       return "a FIFO";
167     case Path::FileType::FILE_NOT_FOUND:
168       return "not found";
169     case Path::FileType::REGULAR_FILE:
170       return "a regular file";
171     case Path::FileType::TYPE_UNKNOWN:
172       return "unknown";
173     case Path::FileType::STATUS_ERROR:
174       return "error";
175     case Path::FileType::SOCKET_FILE:
176       return "a socket";
177     case Path::FileType::SYMLINK_FILE:
178       return "a symlink";
179   }
180 
181   // in case a non-enum value is passed in, return 'undefined'
182   // [should never happen]
183   //
184   // note: don't use 'default:' in the switch to get a warning for
185   // 'unhandled enunaration' when new values are added.
186   return "undefined";
187 }
188 
operator <<(std::ostream & out,Path::FileType type)189 std::ostream &operator<<(std::ostream &out, Path::FileType type) {
190   out << file_type_name(type);
191   return out;
192 }
193 
194 ///////////////////////////////////////////////////////////
195 // Directory::Iterator members
196 
begin()197 Directory::DirectoryIterator Directory::begin() {
198   return DirectoryIterator(*this);
199 }
200 
glob(const string & pattern)201 Directory::DirectoryIterator Directory::glob(const string &pattern) {
202   return DirectoryIterator(*this, pattern);
203 }
204 
end()205 Directory::DirectoryIterator Directory::end() { return DirectoryIterator(); }
206 
207 ///////////////////////////////////////////////////////////
208 // Directory members
209 
210 Directory::~Directory() = default;
211 
212 // throws std::invalid_argument
Directory(const Path & path)213 Directory::Directory(const Path &path) : Path(path) {}
214 
215 ////////////////////////////////////////////////////////////////////////////////
216 //
217 // Utility free functions
218 //
219 ////////////////////////////////////////////////////////////////////////////////
220 
delete_dir_recursive(const std::string & dir)221 int delete_dir_recursive(const std::string &dir) noexcept {
222   mysql_harness::Directory d(dir);
223   try {
224     for (auto const &f : d) {
225       if (f.is_directory()) {
226         if (delete_dir_recursive(f.str()) < 0) return -1;
227       } else {
228         if (delete_file(f.str()) < 0) return -1;
229       }
230     }
231   } catch (...) {
232     return -1;
233   }
234   return delete_dir(dir);
235 }
236 
get_plugin_dir(const std::string & runtime_dir)237 std::string get_plugin_dir(const std::string &runtime_dir) {
238   std::string cur_dir = Path(runtime_dir.c_str()).basename().str();
239   if (cur_dir == "runtime_output_directory") {
240     // single configuration build
241     auto result = Path(runtime_dir.c_str()).dirname();
242     return result.join("plugin_output_directory").str();
243   } else {
244     // multiple configuration build
245     // in that case cur_dir has to be configuration name (Debug, Release etc.)
246     // we need to go 2 levels up
247     auto result = Path(runtime_dir.c_str()).dirname().dirname();
248     return result.join("plugin_output_directory").join(cur_dir).str();
249   }
250 }
251 
252 HARNESS_EXPORT
get_tests_data_dir(const std::string & runtime_dir)253 std::string get_tests_data_dir(const std::string &runtime_dir) {
254   std::string cur_dir = Path(runtime_dir.c_str()).basename().str();
255   if (cur_dir == "runtime_output_directory") {
256     // single configuration build
257     auto result = Path(runtime_dir.c_str()).dirname();
258     return result.join("router").join("tests").join("data").str();
259   } else {
260     // multiple configuration build
261     // in that case cur_dir has to be configuration name (Debug, Release etc.)
262     // we need to go 2 levels up
263     auto result = Path(runtime_dir.c_str()).dirname().dirname();
264     return result.join("router").join("tests").join("data").join(cur_dir).str();
265 
266     return result.str();
267   }
268 }
269 
270 int mkdir_wrapper(const std::string &dir, perm_mode mode);
271 
mkdir_recursive(const Path & path,perm_mode mode)272 int mkdir_recursive(const Path &path, perm_mode mode) {
273   if (path.str().empty() || path.c_str() == Path::root_directory) return -1;
274 
275   // "mkdir -p" on Unix succeeds even if the directory one tries to create
276   // exists, we want to mimic that
277   if (path.exists()) {
278     return path.is_directory() ? 0 : -1;
279   }
280 
281   const auto parent = path.dirname();
282   if (!parent.exists()) {
283     auto res = mkdir_recursive(parent, mode);
284     if (res != 0) return res;
285   }
286 
287   return mkdir_wrapper(path.str(), mode);
288 }
289 
mkdir(const std::string & dir,perm_mode mode,bool recursive)290 int mkdir(const std::string &dir, perm_mode mode, bool recursive) {
291   if (!recursive) {
292     return mkdir_wrapper(dir, mode);
293   }
294 
295   return mkdir_recursive(mysql_harness::Path(dir), mode);
296 }
297 
298 #ifdef _WIN32
299 
300 /**
301  * Verifies permissions of an access ACE entry.
302  *
303  * @param[in] access_ace Access ACE entry.
304  *
305  * @throw std::exception Everyone has access to the ACE access entry or
306  *                        an error occurred.
307  */
check_ace_access_rights(ACCESS_ALLOWED_ACE * access_ace)308 static void check_ace_access_rights(ACCESS_ALLOWED_ACE *access_ace) {
309   SID *sid = reinterpret_cast<SID *>(&access_ace->SidStart);
310   DWORD sid_size = SECURITY_MAX_SID_SIZE;
311 
312   std::unique_ptr<SID, decltype(&free)> everyone_sid(
313       static_cast<SID *>(malloc(sid_size)), &free);
314 
315   if (CreateWellKnownSid(WinWorldSid, nullptr, everyone_sid.get(), &sid_size) ==
316       FALSE) {
317     throw std::system_error(
318         std::error_code(GetLastError(), std::system_category()),
319         "CreateWellKnownSid() failed");
320   }
321 
322   if (EqualSid(sid, everyone_sid.get())) {
323     if (access_ace->Mask & (FILE_EXECUTE)) {
324       throw std::system_error(make_error_code(std::errc::permission_denied),
325                               "Expected no 'Execute' for 'Everyone'.");
326     }
327     if (access_ace->Mask &
328         (FILE_WRITE_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES)) {
329       throw std::system_error(make_error_code(std::errc::permission_denied),
330                               "Expected no 'Write' for 'Everyone'.");
331     }
332     if (access_ace->Mask &
333         (FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES)) {
334       throw std::system_error(make_error_code(std::errc::permission_denied),
335                               "Expected no 'Read' for 'Everyone'.");
336     }
337   }
338 }
339 
340 /**
341  * Verifies access permissions in a DACL.
342  *
343  * @param[in] dacl DACL to be verified.
344  *
345  * @throw std::exception DACL contains an ACL entry that grants full access to
346  *                        Everyone or an error occurred.
347  */
check_acl_access_rights(ACL * dacl)348 static void check_acl_access_rights(ACL *dacl) {
349   ACL_SIZE_INFORMATION dacl_size_info;
350 
351   if (GetAclInformation(dacl, &dacl_size_info, sizeof(dacl_size_info),
352                         AclSizeInformation) == FALSE) {
353     throw std::system_error(
354         std::error_code(GetLastError(), std::system_category()),
355         "GetAclInformation() failed");
356   }
357 
358   for (DWORD ace_idx = 0; ace_idx < dacl_size_info.AceCount; ++ace_idx) {
359     LPVOID ace = nullptr;
360 
361     if (GetAce(dacl, ace_idx, &ace) == FALSE) {
362       throw std::system_error(
363           std::error_code(GetLastError(), std::system_category()),
364           "GetAce() failed");
365       continue;
366     }
367 
368     if (static_cast<ACE_HEADER *>(ace)->AceType == ACCESS_ALLOWED_ACE_TYPE)
369       check_ace_access_rights(static_cast<ACCESS_ALLOWED_ACE *>(ace));
370   }
371 }
372 
373 /**
374  * Verifies access permissions in a security descriptor.
375  *
376  * @param[in] sec_desc Security descriptor to be verified.
377  *
378  * @throw std::exception Security descriptor grants full access to
379  *                        Everyone or an error occurred.
380  */
check_security_descriptor_access_rights(const std::unique_ptr<SECURITY_DESCRIPTOR,decltype(& free)> & sec_desc)381 static void check_security_descriptor_access_rights(
382     const std::unique_ptr<SECURITY_DESCRIPTOR, decltype(&free)> &sec_desc) {
383   BOOL dacl_present;
384   ACL *dacl;
385   BOOL dacl_defaulted;
386 
387   if (GetSecurityDescriptorDacl(sec_desc.get(), &dacl_present, &dacl,
388                                 &dacl_defaulted) == FALSE) {
389     throw std::system_error(
390         std::error_code(GetLastError(), std::system_category()),
391         "GetSecurityDescriptorDacl() failed");
392   }
393 
394   if (!dacl_present) {
395     // No DACL means: no access allowed. Which is fine.
396     return;
397   }
398 
399   if (!dacl) {
400     // Empty DACL means: all access allowed.
401     throw std::system_error(make_error_code(std::errc::permission_denied),
402                             "Expected access denied for 'Everyone'.");
403   }
404 
405   check_acl_access_rights(dacl);
406 }
407 
408 #endif  // _WIN32
409 
410 /**
411  * Verifies access permissions of a file.
412  *
413  * On Unix systems it throws if file's permissions differ from 600.
414  * On Windows it throws if file can be accessed by Everyone group.
415  *
416  * @param[in] file_name File to be verified.
417  *
418  * @throw std::exception File access rights are too permissive or
419  *                        an error occurred.
420  * @throw std::system_error OS and/or filesystem doesn't support file
421  *                           permissions.
422  */
check_file_access_rights(const std::string & file_name)423 void check_file_access_rights(const std::string &file_name) {
424 #ifdef _WIN32
425   try {
426     return check_security_descriptor_access_rights(
427         mysql_harness::get_security_descriptor(file_name));
428   } catch (const std::system_error &e) {
429     if (e.code() == std::errc::permission_denied) {
430       throw std::system_error(
431           e.code(),
432           "'" + file_name + "' has insecure permissions. " + e.what());
433     } else {
434       throw;
435     }
436   } catch (...) {
437     throw;
438   }
439 #else
440   struct stat status;
441 
442   if (stat(file_name.c_str(), &status) != 0) {
443     if (errno == ENOENT) return;
444     throw std::system_error(errno, std::generic_category(),
445                             "stat() failed for " + file_name + "'");
446   }
447 
448   static constexpr mode_t kFullAccessMask = (S_IRWXU | S_IRWXG | S_IRWXO);
449   static constexpr mode_t kRequiredAccessMask = (S_IRUSR | S_IWUSR);
450 
451   if ((status.st_mode & kFullAccessMask) != kRequiredAccessMask) {
452     throw std::system_error(
453         make_error_code(std::errc::permission_denied),
454         "'" + file_name + "' has insecure permissions. Expected u+rw only.");
455   }
456 #endif  // _WIN32
457 }
458 
459 #ifdef _WIN32
460 
461 /**
462  * Gets the SID of the current process user.
463  * The SID in Windows is the Security IDentifier, a security principal to which
464  * permissions are attached (machine, user group, user).
465  */
GetCurrentUserSid(std::unique_ptr<SID,decltype(& free)> & pSID)466 static void GetCurrentUserSid(std::unique_ptr<SID, decltype(&free)> &pSID) {
467   DWORD dw_size = 0;
468   HANDLE h_token;
469   TOKEN_INFORMATION_CLASS token_class = TokenUser;
470   // Gets security token of the current process
471   if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ | TOKEN_QUERY,
472                         &h_token)) {
473     throw std::runtime_error("OpenProcessToken() failed: " +
474                              std::to_string(GetLastError()));
475   }
476   // Gets the user token from the security token (this one only finds out the
477   // buffer size required)
478   if (!GetTokenInformation(h_token, token_class, NULL, 0, &dw_size)) {
479     DWORD dwResult = GetLastError();
480     if (dwResult != ERROR_INSUFFICIENT_BUFFER) {
481       throw std::runtime_error("GetTokenInformation() failed: " +
482                                std::to_string(dwResult));
483     }
484   }
485 
486   std::unique_ptr<TOKEN_USER, decltype(&free)> user(
487       static_cast<TOKEN_USER *>(malloc(dw_size)), &free);
488   if (user.get() == NULL) {
489     throw std::runtime_error("LocalAlloc() failed: " +
490                              std::to_string(GetLastError()));
491   }
492 
493   // Gets the user token from the security token (this one retrieves the actual
494   // user token)
495   if (!GetTokenInformation(h_token, token_class, user.get(), dw_size,
496                            &dw_size)) {
497     throw std::runtime_error("GetTokenInformation() failed: " +
498                              std::to_string(GetLastError()));
499   }
500   // Copies from the user token the SID
501   DWORD dw_sid_len = GetLengthSid(user->User.Sid);
502   pSID.reset(static_cast<SID *>(std::malloc(dw_sid_len)));
503   CopySid(dw_sid_len, pSID.get(), user->User.Sid);
504 }
505 
506 /**
507  * Makes a file fully accessible by the current process user and (read only or
508  * read/write depending on the second argument) for LocalService account (which
509  * is the account under which the MySQL router runs as service). And not
510  * accessible for everyone else.
511  */
make_file_private_win32(const std::string & filename,const bool read_only_for_local_service)512 static void make_file_private_win32(const std::string &filename,
513                                     const bool read_only_for_local_service) {
514   PACL new_dacl = NULL;
515   DWORD sid_size = SECURITY_MAX_SID_SIZE;
516   SID local_service_sid;
517   DWORD dw_res;
518   // Obtains the SID of the LocalService account (the account under which runs
519   // the Router as a service in Windows)
520   if (CreateWellKnownSid(WinLocalServiceSid, NULL, &local_service_sid,
521                          &sid_size) == FALSE) {
522     throw std::runtime_error("CreateWellKnownSid() failed: " +
523                              std::to_string(GetLastError()));
524   }
525 
526   std::unique_ptr<SID, decltype(&free)> current_user(nullptr, &free);
527   // Retrieves the current user process SID.
528   GetCurrentUserSid(current_user);
529 
530   // Sets the actual permissions: two ACEs (access control entries) (one for
531   // current user, one for LocalService) are configured and attached to a
532   // Security Descriptor's DACL (Discretionary Access Control List), then
533   // the Security Descriptors is used in SetFileSecurity API.
534   EXPLICIT_ACCESSA ea[2];
535   ZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESSA));
536   // Full acceess for current user
537   ea[0].grfAccessPermissions =
538       ACCESS_SYSTEM_SECURITY | READ_CONTROL | WRITE_DAC | GENERIC_ALL;
539   ea[0].grfAccessMode = GRANT_ACCESS;
540   ea[0].grfInheritance = NO_INHERITANCE;
541   ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
542   ea[0].Trustee.ptstrName = reinterpret_cast<char *>(current_user.get());
543 
544   // Read only or read/write access for LocalService account
545   DWORD serviceRights = GENERIC_READ;
546   if (!read_only_for_local_service) {
547     serviceRights |= GENERIC_WRITE;
548   }
549   ea[1].grfAccessPermissions = serviceRights;
550 
551   ea[1].grfAccessMode = GRANT_ACCESS;
552   ea[1].grfInheritance = NO_INHERITANCE;
553   ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
554   ea[1].Trustee.ptstrName = reinterpret_cast<char *>(&local_service_sid);
555   // Make a new DACL with the two ACEs
556   dw_res = SetEntriesInAclA(2, ea, NULL, &new_dacl);
557 
558   try {
559     if (ERROR_SUCCESS != dw_res) {
560       throw std::runtime_error("SetEntriesInAcl() failed: " +
561                                std::to_string(dw_res));
562     }
563 
564     // create and initialize a security descriptor.
565     std::unique_ptr<SECURITY_DESCRIPTOR, decltype(&free)> psd(
566         static_cast<SECURITY_DESCRIPTOR *>(
567             std::malloc(SECURITY_DESCRIPTOR_MIN_LENGTH)),
568         &free);
569     if (psd.get() == NULL) {
570       throw std::runtime_error("LocalAlloc() failed: " +
571                                std::to_string(GetLastError()));
572     }
573 
574     if (!InitializeSecurityDescriptor(psd.get(),
575                                       SECURITY_DESCRIPTOR_REVISION)) {
576       throw std::runtime_error("InitializeSecurityDescriptor failed: " +
577                                std::to_string(GetLastError()));
578     }
579     // attach the DACL to the security descriptor
580     if (!SetSecurityDescriptorDacl(psd.get(), TRUE, new_dacl, FALSE)) {
581       throw std::runtime_error("SetSecurityDescriptorDacl failed: " +
582                                std::to_string(GetLastError()));
583     }
584 
585     if (!SetFileSecurityA(filename.c_str(), DACL_SECURITY_INFORMATION,
586                           psd.get())) {
587       dw_res = GetLastError();
588       throw std::system_error(
589           dw_res, std::system_category(),
590           "SetFileSecurity failed: " + std::to_string(dw_res));
591     }
592     LocalFree((HLOCAL)new_dacl);
593   } catch (...) {
594     if (new_dacl != NULL) LocalFree((HLOCAL)new_dacl);
595     throw;
596   }
597 }
598 
599 /**
600  * Sets file permissions for Everyone group.
601  *
602  * @param[in] file_name File name.
603  * @param[in] mask Access rights mask for Everyone group.
604  *
605  * @throw std::exception Failed to change file permissions.
606  */
set_everyone_group_access_rights(const std::string & file_name,DWORD mask)607 static void set_everyone_group_access_rights(const std::string &file_name,
608                                              DWORD mask) {
609   // Create Everyone SID.
610   std::unique_ptr<SID, decltype(&free)> everyone_sid(
611       static_cast<SID *>(std::malloc(SECURITY_MAX_SID_SIZE)), &free);
612 
613   DWORD sid_size = SECURITY_MAX_SID_SIZE;
614   if (CreateWellKnownSid(WinWorldSid, NULL, everyone_sid.get(), &sid_size) ==
615       FALSE) {
616     throw std::runtime_error("CreateWellKnownSid() failed: " +
617                              std::to_string(GetLastError()));
618   }
619 
620   // Get file security descriptor.
621   ACL *old_dacl;
622   std::unique_ptr<SECURITY_DESCRIPTOR, decltype(&LocalFree)> sec_desc(
623       nullptr, &LocalFree);
624 
625   {
626     PSECURITY_DESCRIPTOR sec_desc_tmp;
627     auto result = GetNamedSecurityInfoA(file_name.c_str(), SE_FILE_OBJECT,
628                                         DACL_SECURITY_INFORMATION, NULL, NULL,
629                                         &old_dacl, NULL, &sec_desc_tmp);
630 
631     if (result != ERROR_SUCCESS) {
632       throw std::system_error(
633           result, std::system_category(),
634           "GetNamedSecurityInfo() failed: " + std::to_string(result));
635     }
636 
637     // If everything went fine, we move raw pointer to smart pointer.
638     sec_desc.reset(static_cast<SECURITY_DESCRIPTOR *>(sec_desc_tmp));
639   }
640 
641   // Setting access permissions for Everyone group.
642   EXPLICIT_ACCESSA ea[1];
643 
644   memset(&ea, 0, sizeof(ea));
645   ea[0].grfAccessPermissions = mask;
646   ea[0].grfAccessMode = SET_ACCESS;
647   ea[0].grfInheritance = NO_INHERITANCE;
648   ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
649   ea[0].Trustee.ptstrName = reinterpret_cast<char *>(everyone_sid.get());
650 
651   // Create new ACL permission set.
652   std::unique_ptr<ACL, decltype(&LocalFree)> new_dacl(nullptr, &LocalFree);
653 
654   {
655     ACL *new_dacl_tmp;
656     auto result = SetEntriesInAclA(1, &ea[0], old_dacl, &new_dacl_tmp);
657 
658     if (result != ERROR_SUCCESS) {
659       throw std::runtime_error("SetEntriesInAcl() failed: " +
660                                std::to_string(result));
661     }
662 
663     // If everything went fine, we move raw pointer to smart pointer.
664     new_dacl.reset(new_dacl_tmp);
665   }
666 
667   // Set file security descriptor.
668   auto result = SetNamedSecurityInfoA(const_cast<char *>(file_name.c_str()),
669                                       SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
670                                       NULL, NULL, new_dacl.get(), NULL);
671 
672   if (result != ERROR_SUCCESS) {
673     throw std::system_error(
674         result, std::system_category(),
675         "SetNamedSecurityInfo() failed: " + std::to_string(result));
676   }
677 }
678 
679 #else
680 
681 /**
682  * Sets access permissions for a file.
683  *
684  * @param[in] file_name File name.
685  * @param[in] mask Access permission mask.
686  *
687  * @throw std::exception Failed to change file permissions.
688  */
throwing_chmod(const std::string & file_name,mode_t mask)689 static void throwing_chmod(const std::string &file_name, mode_t mask) {
690   if (chmod(file_name.c_str(), mask) != 0) {
691     auto ec = std::error_code(errno, std::generic_category());
692     throw std::system_error(ec, "chmod() failed: " + file_name);
693   }
694 }
695 #endif  // _WIN32
696 
make_file_public(const std::string & file_name)697 void make_file_public(const std::string &file_name) {
698 #ifdef _WIN32
699   set_everyone_group_access_rights(
700       file_name, FILE_GENERIC_EXECUTE | FILE_GENERIC_WRITE | FILE_GENERIC_READ);
701 #else
702   throwing_chmod(file_name, S_IRWXU | S_IRWXG | S_IRWXO);
703 #endif
704 }
705 
make_file_private(const std::string & file_name,const bool read_only_for_local_service)706 void make_file_private(const std::string &file_name,
707                        const bool read_only_for_local_service) {
708 #ifdef _WIN32
709   try {
710     make_file_private_win32(file_name, read_only_for_local_service);
711   } catch (const std::system_error &e) {
712     throw std::system_error(e.code(), "Could not set permissions for file '" +
713                                           file_name + "': " + e.what());
714   }
715 #else
716   (void)read_only_for_local_service;  // only relevant for Windows
717   try {
718     throwing_chmod(file_name, S_IRUSR | S_IWUSR);
719   } catch (std::runtime_error &e) {
720     throw std::runtime_error("Could not set permissions for file '" +
721                              file_name + "': " + e.what());
722   }
723 #endif
724 }
725 
726 #ifdef _WIN32
make_file_readable_for_everyone(const std::string & file_name)727 void make_file_readable_for_everyone(const std::string &file_name) {
728   try {
729     set_everyone_group_access_rights(file_name, FILE_GENERIC_READ);
730   } catch (const std::system_error &e) {
731     throw std::system_error(e.code(), "Could not set permissions for file '" +
732                                           file_name + "': " + e.what());
733   }
734 }
735 #endif
736 
make_file_readonly(const std::string & file_name)737 void make_file_readonly(const std::string &file_name) {
738 #ifdef _WIN32
739   set_everyone_group_access_rights(file_name,
740                                    FILE_GENERIC_EXECUTE | FILE_GENERIC_READ);
741 #else
742   throwing_chmod(file_name,
743                  S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
744 #endif
745 }
746 
747 }  // namespace mysql_harness
748