/* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sql_class.h" #include #include #include #include "my_config.h" #include "my_sys.h" #include "mysqld_error.h" #include "m_string.h" #include "log.h" #include "named_pipe.h" bool is_existing_windows_group_name(const char *group_name) { // First, let's get a SID for the given group name... BYTE soughtSID[SECURITY_MAX_SID_SIZE]= { 0 }; DWORD size_sid= SECURITY_MAX_SID_SIZE; char referencedDomainName[MAX_PATH]; DWORD size_referencedDomainName= MAX_PATH; SID_NAME_USE sid_name_use; if (!LookupAccountName(nullptr, group_name, soughtSID, &size_sid, referencedDomainName, &size_referencedDomainName, &sid_name_use)) { return false; } // sid_name_use is SidTypeAlias when group_name is a local group if (sid_name_use != SidTypeAlias && sid_name_use != SidTypeWellKnownGroup) { return false; } return true; } /* return false on successfully checking group, true on error. */ static bool check_windows_group_for_everyone(const char *group_name, bool *is_everyone_group) { *is_everyone_group= false; if (!group_name || group_name[0] == '\0') { return false; } if (strcmp(group_name, DEFAULT_NAMED_PIPE_FULL_ACCESS_GROUP) == 0) { *is_everyone_group= true; return false; } else { TCHAR last_error_msg[256]; // First, let's get a SID for the given group name... BYTE soughtSID[SECURITY_MAX_SID_SIZE]= {0}; DWORD size_sought_sid= SECURITY_MAX_SID_SIZE; BYTE worldSID[SECURITY_MAX_SID_SIZE]= {0}; DWORD size_world_sid= SECURITY_MAX_SID_SIZE; char referencedDomainName[MAX_PATH]; DWORD size_referencedDomainName= MAX_PATH; SID_NAME_USE sid_name_use; if (!LookupAccountName(NULL, group_name, soughtSID, &size_sought_sid, referencedDomainName, &size_referencedDomainName, &sid_name_use)) { return false; } if (!CreateWellKnownSid(WinWorldSid, NULL, worldSID, &size_world_sid)) { DWORD last_error_num= GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error_num, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg, sizeof(last_error_msg) / sizeof(TCHAR), NULL); my_printf_error(ER_UNKNOWN_ERROR, "check_windows_group_for_everyone, CreateWellKnownSid failed: %s", MYF(0), last_error_msg); return true; } *is_everyone_group= EqualSid(soughtSID, worldSID); return false; } } bool is_valid_named_pipe_full_access_group(const char *group_name) { if (!group_name || group_name[0] == '\0'){ return true; } bool is_everyone_group= false; if(check_windows_group_for_everyone(group_name, &is_everyone_group)){ return false; } if (is_everyone_group || is_existing_windows_group_name(group_name)) { return true; } return false; } // return false on success, true on failure. bool my_security_attr_add_rights_to_group(SECURITY_ATTRIBUTES *psa, const char *group_name, DWORD group_rights) { TCHAR last_error_msg[256]; // First, let's get a SID for the given group name... BYTE soughtSID[SECURITY_MAX_SID_SIZE]= { 0 }; DWORD size_sid= SECURITY_MAX_SID_SIZE; char referencedDomainName[MAX_PATH]; DWORD size_referencedDomainName= MAX_PATH; SID_NAME_USE sid_name_use; bool is_everyone_group= false; if(check_windows_group_for_everyone(group_name, &is_everyone_group)){ return true; } if (is_everyone_group) { sql_print_warning(ER_DEFAULT(WARN_NAMED_PIPE_ACCESS_EVERYONE), group_name); if (current_thd) { push_warning_printf(current_thd, Sql_condition::SL_WARNING, WARN_NAMED_PIPE_ACCESS_EVERYONE, ER(WARN_NAMED_PIPE_ACCESS_EVERYONE), group_name); } } // Treat the DEFAULT_NAMED_PIPE_FULL_ACCESS_GROUP value // as a special case: we convert it to the "world" SID if (strcmp(group_name, DEFAULT_NAMED_PIPE_FULL_ACCESS_GROUP) == 0) { if (!CreateWellKnownSid(WinWorldSid, NULL, soughtSID, &size_sid)) { DWORD last_error_num= GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error_num, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg, sizeof(last_error_msg) / sizeof(TCHAR), NULL); my_printf_error(ER_UNKNOWN_ERROR, "my_security_attr_add_rights_to_group, CreateWellKnownSid failed: %s", MYF(0), last_error_msg); return true; } } else { if (!LookupAccountName(NULL, group_name, soughtSID, &size_sid, referencedDomainName, &size_referencedDomainName, &sid_name_use)) { DWORD last_error_num= GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error_num, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg, sizeof(last_error_msg) / sizeof(TCHAR), NULL); sql_print_error("my_security_attr_add_rights_to_group, LookupAccountName failed: %s", last_error_msg); return true; } // sid_name_use is SidTypeAlias when group_name is a local group if (sid_name_use != SidTypeAlias && sid_name_use != SidTypeWellKnownGroup) { sql_print_error("LookupAccountName failed: unexpected sid_name_use"); return true; } } PACL pNewDACL= nullptr; PACL pOldDACL= nullptr; BOOL dacl_present_in_descriptor= FALSE; BOOL dacl_defaulted= FALSE; if (!GetSecurityDescriptorDacl(psa->lpSecurityDescriptor, &dacl_present_in_descriptor, &pOldDACL, &dacl_defaulted) || !dacl_present_in_descriptor) { DWORD last_error_num= GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error_num, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg, sizeof(last_error_msg) / sizeof(TCHAR), NULL); sql_print_error("GetSecurityDescriptorDacl failed: %s", last_error_msg); return true; } // Just because GetSecurityDescriptorDacl succeeded doesn't mean we're out of // the woods: a NULL value for pOldDACL is a bad/unexpected thing, as is // dacl_defaulted == TRUE if (pOldDACL == nullptr || dacl_defaulted) { sql_print_error("Invalid DACL on named pipe: %s", (pOldDACL == nullptr) ? "NULL DACL" : "Defaulted DACL"); return true; } EXPLICIT_ACCESS ea; // Initialize an EXPLICIT_ACCESS structure for the new ACE. ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS)); ea.grfAccessPermissions= group_rights; ea.grfAccessMode= SET_ACCESS; ea.grfInheritance= NO_INHERITANCE; ea.Trustee.TrusteeForm= TRUSTEE_IS_SID; ea.Trustee.ptstrName= (LPSTR)soughtSID; // Create a new ACL that merges the new ACE // into the existing DACL. DWORD dwRes= SetEntriesInAcl(1, &ea, pOldDACL, &pNewDACL); if (ERROR_SUCCESS != dwRes) { char num_buff[20]; int10_to_str(dwRes, num_buff, 10); sql_print_error("SetEntriesInAcl to add group permissions failed: %s", num_buff); return true; } // Apply the new DACL to the existing security descriptor... if (!SetSecurityDescriptorDacl(psa->lpSecurityDescriptor, TRUE, pNewDACL, FALSE)) { DWORD last_error_num= GetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error_num, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg, sizeof(last_error_msg) / sizeof(TCHAR), NULL); sql_print_error("SetSecurityDescriptorDacl failed: %s", last_error_msg); return true; } return false; } /** Creates an instance of a named pipe and returns a handle. @param sec_attr Security attributes for the pipe. @param buffer_size Number of bytes to reserve for input and output buffers. @param name The name of the pipe. @param name_buf Output argument: null-terminated concatenation of "\\.\pipe\" and name. @param buflen The size of name_buff. @returns Pipe handle, or INVALID_HANDLE_VALUE in case of error. @note The entire pipe name string can be up to 256 characters long. Pipe names are not case sensitive. */ HANDLE create_server_named_pipe(SECURITY_ATTRIBUTES **ppsec_attr, DWORD buffer_size, const char *name, char *name_buf, size_t buflen, const char *full_access_group_name) { HANDLE ret_handle= INVALID_HANDLE_VALUE; TCHAR last_error_msg[256]; strxnmov(name_buf, buflen - 1, "\\\\.\\pipe\\", name, NullS); const char *perror= nullptr; // Set up security for the named pipe to provide full access to the owner // and minimal read/write access to others. if (my_security_attr_create(ppsec_attr, &perror, NAMED_PIPE_OWNER_PERMISSIONS, NAMED_PIPE_EVERYONE_PERMISSIONS) != 0) { sql_print_error("my_security_attr_create failed: %s", perror); return ret_handle; } if (full_access_group_name && full_access_group_name[0] != '\0') { if (my_security_attr_add_rights_to_group( *ppsec_attr, full_access_group_name, NAMED_PIPE_FULL_ACCESS_GROUP_PERMISSIONS)) { return ret_handle; } } ret_handle= CreateNamedPipe( name_buf, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, buffer_size, buffer_size, NMPWAIT_USE_DEFAULT_WAIT, *ppsec_attr); if (ret_handle == INVALID_HANDLE_VALUE) { DWORD last_error_num= GetLastError(); if (last_error_num == ERROR_ACCESS_DENIED) { my_printf_error(ER_CANT_START_SERVER_NAMED_PIPE, ER_DEFAULT(ER_CANT_START_SERVER_NAMED_PIPE), MYF(ME_FATALERROR), name); } else { FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, last_error_num, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg, sizeof(last_error_msg) / sizeof(TCHAR), NULL); char num_buff[20]; int10_to_str(last_error_num, num_buff, 10); sql_print_error("Can't start server : %s %s", last_error_msg, num_buff); } } return ret_handle; }