1 /* Copyright (c) 2018, 2020, Oracle and/or its affiliates.
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(NULL, 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::WARN_LEVEL_WARN,
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= NULL;
205   PACL pOldDACL= NULL;
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 == NULL || dacl_defaulted)
226   {
227     sql_print_error("Invalid DACL on named pipe: %s",
228                     (pOldDACL == NULL) ? "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= NULL;
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       sql_print_error("Can't start server : Named Pipe \"%s\" already in use.", name);
327     }
328     else
329     {
330       FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
331                         FORMAT_MESSAGE_MAX_WIDTH_MASK,
332                     NULL, last_error_num,
333                     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), last_error_msg,
334                     sizeof(last_error_msg) / sizeof(TCHAR), NULL);
335       char num_buff[20];
336       int10_to_str(last_error_num, num_buff, 10);
337       sql_print_error("Can't start server : %s %s", last_error_msg, num_buff);
338     }
339   }
340 
341   return ret_handle;
342 }
343