1 /* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23
24 #include "sql_class.h"
25 #include <AclAPI.h>
26 #include <accctrl.h>
27 #include <errno.h>
28
29 #include "my_config.h"
30 #include "my_sys.h"
31 #include "mysqld_error.h"
32 #include "m_string.h"
33 #include "log.h"
34 #include "named_pipe.h"
35
is_existing_windows_group_name(const char * group_name)36 bool is_existing_windows_group_name(const char *group_name)
37 {
38 // First, let's get a SID for the given group name...
39 BYTE soughtSID[SECURITY_MAX_SID_SIZE]= { 0 };
40 DWORD size_sid= SECURITY_MAX_SID_SIZE;
41 char referencedDomainName[MAX_PATH];
42 DWORD size_referencedDomainName= MAX_PATH;
43 SID_NAME_USE sid_name_use;
44
45 if (!LookupAccountName(nullptr, group_name, soughtSID, &size_sid,
46 referencedDomainName, &size_referencedDomainName,
47 &sid_name_use))
48 {
49 return false;
50 }
51
52 // sid_name_use is SidTypeAlias when group_name is a local group
53 if (sid_name_use != SidTypeAlias && sid_name_use != SidTypeWellKnownGroup)
54 {
55 return false;
56 }
57 return true;
58 }
59
60 /*
61 return false on successfully checking group, true on error.
62 */
check_windows_group_for_everyone(const char * group_name,bool * is_everyone_group)63 static bool check_windows_group_for_everyone(const char *group_name,
64 bool *is_everyone_group)
65 {
66 *is_everyone_group= false;
67 if (!group_name || group_name[0] == '\0')
68 {
69 return false;
70 }
71
72 if (strcmp(group_name, DEFAULT_NAMED_PIPE_FULL_ACCESS_GROUP) == 0)
73 {
74 *is_everyone_group= true;
75 return false;
76 }
77 else
78 {
79 TCHAR last_error_msg[256];
80 // First, let's get a SID for the given group name...
81 BYTE soughtSID[SECURITY_MAX_SID_SIZE]= {0};
82 DWORD size_sought_sid= SECURITY_MAX_SID_SIZE;
83 BYTE worldSID[SECURITY_MAX_SID_SIZE]= {0};
84 DWORD size_world_sid= SECURITY_MAX_SID_SIZE;
85 char referencedDomainName[MAX_PATH];
86 DWORD size_referencedDomainName= MAX_PATH;
87 SID_NAME_USE sid_name_use;
88
89 if (!LookupAccountName(NULL, group_name, soughtSID, &size_sought_sid,
90 referencedDomainName, &size_referencedDomainName,
91 &sid_name_use))
92 {
93 return false;
94 }
95
96 if (!CreateWellKnownSid(WinWorldSid, NULL, worldSID, &size_world_sid))
97 {
98 DWORD last_error_num= GetLastError();
99 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
100 NULL, last_error_num,
101 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
102 sizeof(last_error_msg) / sizeof(TCHAR), NULL);
103 my_printf_error(ER_UNKNOWN_ERROR,
104 "check_windows_group_for_everyone, CreateWellKnownSid failed: %s",
105 MYF(0), last_error_msg);
106 return true;
107 }
108
109 *is_everyone_group= EqualSid(soughtSID, worldSID);
110 return false;
111 }
112 }
113
is_valid_named_pipe_full_access_group(const char * group_name)114 bool is_valid_named_pipe_full_access_group(const char *group_name)
115 {
116 if (!group_name || group_name[0] == '\0'){
117 return true;
118 }
119
120 bool is_everyone_group= false;
121
122 if(check_windows_group_for_everyone(group_name, &is_everyone_group)){
123 return false;
124 }
125
126 if (is_everyone_group || is_existing_windows_group_name(group_name))
127 {
128 return true;
129 }
130 return false;
131 }
132
133 // return false on success, true on failure.
my_security_attr_add_rights_to_group(SECURITY_ATTRIBUTES * psa,const char * group_name,DWORD group_rights)134 bool my_security_attr_add_rights_to_group(SECURITY_ATTRIBUTES *psa,
135 const char *group_name,
136 DWORD group_rights)
137 {
138 TCHAR last_error_msg[256];
139 // First, let's get a SID for the given group name...
140 BYTE soughtSID[SECURITY_MAX_SID_SIZE]= { 0 };
141 DWORD size_sid= SECURITY_MAX_SID_SIZE;
142 char referencedDomainName[MAX_PATH];
143 DWORD size_referencedDomainName= MAX_PATH;
144 SID_NAME_USE sid_name_use;
145
146 bool is_everyone_group= false;
147
148 if(check_windows_group_for_everyone(group_name, &is_everyone_group)){
149 return true;
150 }
151
152 if (is_everyone_group)
153 {
154 sql_print_warning(ER_DEFAULT(WARN_NAMED_PIPE_ACCESS_EVERYONE), group_name);
155 if (current_thd)
156 {
157 push_warning_printf(current_thd, Sql_condition::SL_WARNING,
158 WARN_NAMED_PIPE_ACCESS_EVERYONE,
159 ER(WARN_NAMED_PIPE_ACCESS_EVERYONE), group_name);
160 }
161 }
162
163 // Treat the DEFAULT_NAMED_PIPE_FULL_ACCESS_GROUP value
164 // as a special case: we convert it to the "world" SID
165 if (strcmp(group_name, DEFAULT_NAMED_PIPE_FULL_ACCESS_GROUP) == 0)
166 {
167 if (!CreateWellKnownSid(WinWorldSid, NULL, soughtSID, &size_sid))
168 {
169 DWORD last_error_num= GetLastError();
170 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
171 NULL, last_error_num,
172 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
173 sizeof(last_error_msg) / sizeof(TCHAR), NULL);
174 my_printf_error(ER_UNKNOWN_ERROR,
175 "my_security_attr_add_rights_to_group, CreateWellKnownSid failed: %s",
176 MYF(0), last_error_msg);
177 return true;
178 }
179 }
180 else
181 {
182 if (!LookupAccountName(NULL, group_name, soughtSID, &size_sid,
183 referencedDomainName, &size_referencedDomainName,
184 &sid_name_use))
185 {
186 DWORD last_error_num= GetLastError();
187 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
188 NULL, last_error_num,
189 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
190 sizeof(last_error_msg) / sizeof(TCHAR), NULL);
191 sql_print_error("my_security_attr_add_rights_to_group, LookupAccountName failed: %s",
192 last_error_msg);
193 return true;
194 }
195
196 // sid_name_use is SidTypeAlias when group_name is a local group
197 if (sid_name_use != SidTypeAlias && sid_name_use != SidTypeWellKnownGroup)
198 {
199 sql_print_error("LookupAccountName failed: unexpected sid_name_use");
200 return true;
201 }
202 }
203
204 PACL pNewDACL= nullptr;
205 PACL pOldDACL= nullptr;
206 BOOL dacl_present_in_descriptor= FALSE;
207 BOOL dacl_defaulted= FALSE;
208 if (!GetSecurityDescriptorDacl(psa->lpSecurityDescriptor,
209 &dacl_present_in_descriptor, &pOldDACL,
210 &dacl_defaulted) ||
211 !dacl_present_in_descriptor)
212 {
213 DWORD last_error_num= GetLastError();
214 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
215 NULL, last_error_num,
216 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
217 sizeof(last_error_msg) / sizeof(TCHAR), NULL);
218 sql_print_error("GetSecurityDescriptorDacl failed: %s", last_error_msg);
219 return true;
220 }
221
222 // Just because GetSecurityDescriptorDacl succeeded doesn't mean we're out of
223 // the woods: a NULL value for pOldDACL is a bad/unexpected thing, as is
224 // dacl_defaulted == TRUE
225 if (pOldDACL == nullptr || dacl_defaulted)
226 {
227 sql_print_error("Invalid DACL on named pipe: %s",
228 (pOldDACL == nullptr) ? "NULL DACL" : "Defaulted DACL");
229 return true;
230 }
231
232 EXPLICIT_ACCESS ea;
233 // Initialize an EXPLICIT_ACCESS structure for the new ACE.
234
235 ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
236 ea.grfAccessPermissions= group_rights;
237 ea.grfAccessMode= SET_ACCESS;
238 ea.grfInheritance= NO_INHERITANCE;
239 ea.Trustee.TrusteeForm= TRUSTEE_IS_SID;
240 ea.Trustee.ptstrName= (LPSTR)soughtSID;
241
242 // Create a new ACL that merges the new ACE
243 // into the existing DACL.
244 DWORD dwRes= SetEntriesInAcl(1, &ea, pOldDACL, &pNewDACL);
245 if (ERROR_SUCCESS != dwRes)
246 {
247 char num_buff[20];
248 int10_to_str(dwRes, num_buff, 10);
249 sql_print_error("SetEntriesInAcl to add group permissions failed: %s", num_buff);
250 return true;
251 }
252
253 // Apply the new DACL to the existing security descriptor...
254 if (!SetSecurityDescriptorDacl(psa->lpSecurityDescriptor, TRUE, pNewDACL,
255 FALSE))
256 {
257 DWORD last_error_num= GetLastError();
258 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
259 NULL, last_error_num,
260 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
261 sizeof(last_error_msg) / sizeof(TCHAR), NULL);
262 sql_print_error("SetSecurityDescriptorDacl failed: %s", last_error_msg);
263 return true;
264 }
265
266 return false;
267 }
268
269 /**
270 Creates an instance of a named pipe and returns a handle.
271
272 @param sec_attr Security attributes for the pipe.
273 @param buffer_size Number of bytes to reserve for input and output buffers.
274 @param name The name of the pipe.
275 @param name_buf Output argument: null-terminated concatenation of
276 "\\.\pipe\" and name.
277 @param buflen The size of name_buff.
278
279 @returns Pipe handle, or INVALID_HANDLE_VALUE in case of error.
280
281 @note The entire pipe name string can be up to 256 characters long.
282 Pipe names are not case sensitive.
283 */
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)284 HANDLE create_server_named_pipe(SECURITY_ATTRIBUTES **ppsec_attr,
285 DWORD buffer_size, const char *name,
286 char *name_buf, size_t buflen,
287 const char *full_access_group_name)
288 {
289 HANDLE ret_handle= INVALID_HANDLE_VALUE;
290 TCHAR last_error_msg[256];
291
292 strxnmov(name_buf, buflen - 1, "\\\\.\\pipe\\", name, NullS);
293 const char *perror= nullptr;
294 // Set up security for the named pipe to provide full access to the owner
295 // and minimal read/write access to others.
296 if (my_security_attr_create(ppsec_attr, &perror, NAMED_PIPE_OWNER_PERMISSIONS,
297 NAMED_PIPE_EVERYONE_PERMISSIONS) != 0)
298 {
299 sql_print_error("my_security_attr_create failed: %s", perror);
300 return ret_handle;
301 }
302
303 if (full_access_group_name && full_access_group_name[0] != '\0')
304 {
305 if (my_security_attr_add_rights_to_group(
306 *ppsec_attr, full_access_group_name,
307 NAMED_PIPE_FULL_ACCESS_GROUP_PERMISSIONS))
308 {
309 return ret_handle;
310 }
311 }
312
313 ret_handle= CreateNamedPipe(
314 name_buf,
315 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |
316 FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC,
317 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES,
318 buffer_size, buffer_size, NMPWAIT_USE_DEFAULT_WAIT, *ppsec_attr);
319
320 if (ret_handle == INVALID_HANDLE_VALUE)
321 {
322 DWORD last_error_num= GetLastError();
323
324 if (last_error_num == ERROR_ACCESS_DENIED)
325 {
326 my_printf_error(ER_CANT_START_SERVER_NAMED_PIPE,
327 ER_DEFAULT(ER_CANT_START_SERVER_NAMED_PIPE),
328 MYF(ME_FATALERROR), name);
329 }
330 else
331 {
332 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
333 FORMAT_MESSAGE_MAX_WIDTH_MASK,
334 NULL, last_error_num,
335 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
336 sizeof(last_error_msg) / sizeof(TCHAR), NULL);
337 char num_buff[20];
338 int10_to_str(last_error_num, num_buff, 10);
339 sql_print_error("Can't start server : %s %s", last_error_msg, num_buff);
340 }
341 }
342
343 return ret_handle;
344 }
345