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