1 /* Copyright (c) 2018 MariaDB Corporation.
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 as published by
5    the Free Software Foundation; version 2 of the License.
6 
7    This program is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU General Public License for more details.
11 
12    You should have received a copy of the GNU General Public License
13    along with this program; if not, write to the Free Software
14    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA */
15 
16 /* Accepting connections on Windows */
17 
18 #include <my_global.h>
19 #include <sql_class.h>
20 #include <sql_connect.h>
21 #include <mysqld.h>
22 #include <mswsock.h>
23 #include <mysql/psi/mysql_socket.h>
24 #include <sddl.h>
25 
26 #include <handle_connections_win.h>
27 
28 /* From mysqld.cc */
29 extern HANDLE hEventShutdown;
30 extern MYSQL_SOCKET base_ip_sock, extra_ip_sock;
31 #ifdef HAVE_POOL_OF_THREADS
32 extern PTP_CALLBACK_ENVIRON get_threadpool_win_callback_environ();
33 extern void tp_win_callback_prolog();
34 #else
35 #define get_threadpool_win_callback_environ() 0
36 #define tp_win_callback_prolog() do{}while(0)
37 #endif
38 static SECURITY_ATTRIBUTES pipe_security;
39 
40 /**
41   Abstract base class for accepting new connection,
42   asynchronously (i.e the accept() operation can be posted,
43   and result is retrieved later) , and creating a new connection.
44 */
45 
46 struct Listener
47 {
48   /** Windows handle of the Listener.
49    Subclasses would use SOCKET or named pipe handle
50   */
51   HANDLE m_handle;
52   /** Required for all async IO*/
53   OVERLAPPED m_overlapped;
54 
55   /** Create new listener
56   @param handle - @see m_handle
57   @param wait_handle - usually, event handle or INVALID_HANDLE_VALUE
58                        @see wait_handle
59   */
ListenerListener60   Listener(HANDLE handle, HANDLE wait_handle):
61     m_handle(handle), m_overlapped()
62   {
63       m_overlapped.hEvent= wait_handle;
64   }
65 
66   /**
67     if not NULL, this handle can be be used in WaitForSingle/MultipleObject(s).
68     This handle will be closed when object is destroyed.
69 
70     If NULL, the completion notification happens in threadpool.
71   */
wait_handleListener72   HANDLE wait_handle()
73   {
74     return m_overlapped.hEvent;
75   }
76 
77   /* Start waiting for new client connection. */
78   virtual void begin_accept()= 0;
79 
80   /**
81    Completion callback,called whenever IO posted by begin_accept is finisjed
82    Listener needs to create a new THD then (or, call scheduler so it creates one)
83 
84    @param success - whether IO completed successfull
85   */
86   virtual void completion_callback(bool success)= 0;
87 
88   /**
89    Completion callback for Listener, that uses events for waiting
90    to IO. Not suitable for threadpool etc. Retrieves the status of
91    completed IO from the OVERLAPPED structure
92   */
completion_callbackListener93   void completion_callback()
94   {
95     DBUG_ASSERT(wait_handle() && (wait_handle() != INVALID_HANDLE_VALUE));
96     DWORD bytes;
97     return completion_callback(
98       GetOverlappedResult(wait_handle(), &m_overlapped, &bytes, FALSE));
99   }
100 
101   /** Cancel an in-progress IO. Useful for threadpool-bound IO */
cancelListener102   void cancel()
103   {
104     CancelIoEx(m_handle, &m_overlapped);
105   }
106 
107   /* Destructor. Closes wait handle, if it was passed in constructor */
~ListenerListener108   virtual ~Listener()
109   {
110     if (m_overlapped.hEvent)
111       CloseHandle(m_overlapped.hEvent);
112   };
113 };
114 
115 /* Winsock extension finctions. */
116 static LPFN_ACCEPTEX my_AcceptEx;
117 static LPFN_GETACCEPTEXSOCKADDRS my_GetAcceptExSockaddrs;
118 
119 /**
120   Listener that handles socket connections.
121   Can be threadpool-bound (i.e the completion is executed in threadpool thread),
122   or use events for waits.
123 
124   Threadpool-bound listener should be used with theradpool scheduler, for better
125   performance.
126 */
127 struct Socket_Listener: public Listener
128 {
129   /** Client socket passed to AcceptEx() call.*/
130   SOCKET m_client_socket;
131 
132   /** Buffer for sockaddrs passed to AcceptEx()/GetAcceptExSockaddrs() */
133   char m_buffer[2 * sizeof(sockaddr_storage) + 32];
134 
135   /* Threadpool IO struct.*/
136   PTP_IO m_tp_io;
137 
138   /**
139     Callback for  Windows threadpool's StartThreadpoolIo() function.
140   */
tp_accept_completion_callbackSocket_Listener141   static void CALLBACK tp_accept_completion_callback(
142     PTP_CALLBACK_INSTANCE, PVOID context, PVOID , ULONG io_result,
143     ULONG_PTR, PTP_IO io)
144   {
145     tp_win_callback_prolog();
146     Listener *listener= (Listener *)context;
147 
148     if (io_result == ERROR_OPERATION_ABORTED)
149     {
150       /* ERROR_OPERATION_ABORTED  caused by CancelIoEx()*/
151       CloseThreadpoolIo(io);
152       delete listener;
153       return;
154     }
155     listener->completion_callback(io_result == 0);
156   }
157 
158   /**
159   Constructor
160   @param listen_socket - listening socket
161   @PTP_CALLBACK_ENVIRON callback_environ  - threadpool environment, or NULL
162     if threadpool is not used for completion callbacks.
163   */
Socket_ListenerSocket_Listener164   Socket_Listener(MYSQL_SOCKET listen_socket, PTP_CALLBACK_ENVIRON callback_environ) :
165     Listener((HANDLE)listen_socket.fd,0),
166     m_client_socket(INVALID_SOCKET)
167   {
168     if (callback_environ)
169     {
170       /* Accept executed in threadpool. */
171       m_tp_io= CreateThreadpoolIo(m_handle,
172         tp_accept_completion_callback, this, callback_environ);
173     }
174     else
175     {
176       /* Completion signaled via event. */
177       m_tp_io= 0;
178       m_overlapped.hEvent= CreateEvent(0, FALSE , FALSE, 0);
179     }
180   }
181 
182   /*
183   Use AcceptEx to asynchronously wait for new connection;
184   */
begin_acceptSocket_Listener185   void begin_accept()
186   {
187 retry :
188     m_client_socket= socket(server_socket_ai_family, SOCK_STREAM, IPPROTO_TCP);
189     if (m_client_socket == INVALID_SOCKET)
190     {
191       sql_perror("socket() call failed.");
192       unireg_abort(1);
193     }
194 
195     DWORD bytes_received;
196     if (m_tp_io)
197       StartThreadpoolIo(m_tp_io);
198 
199     BOOL ret= my_AcceptEx(
200       (SOCKET)m_handle,
201       m_client_socket,
202       m_buffer,
203       0,
204       sizeof(sockaddr_storage) + 16,
205       sizeof(sockaddr_storage) + 16,
206       &bytes_received,
207       &m_overlapped);
208 
209     DWORD last_error=  ret? 0: WSAGetLastError();
210     if (last_error == WSAECONNRESET || last_error == ERROR_NETNAME_DELETED)
211     {
212       if (m_tp_io)
213         CancelThreadpoolIo(m_tp_io);
214       closesocket(m_client_socket);
215       goto retry;
216     }
217 
218     if (ret || last_error == ERROR_IO_PENDING || abort_loop)
219       return;
220 
221     sql_print_error("my_AcceptEx failed, last error %u", last_error);
222     abort();
223   }
224 
225   /* Create new socket connection.*/
completion_callbackSocket_Listener226   void completion_callback(bool success)
227   {
228     if (!success)
229     {
230       /* my_AcceptEx() returned error */
231       closesocket(m_client_socket);
232       begin_accept();
233       return;
234     }
235 
236     MYSQL_SOCKET s_client{m_client_socket};
237     MYSQL_SOCKET s_listen{(SOCKET)m_handle};
238 
239 #ifdef HAVE_PSI_SOCKET_INTERFACE
240     /* Parse socket addresses buffer filled by AcceptEx(),
241        only needed for PSI instrumentation. */
242     sockaddr *local_addr, *remote_addr;
243     int local_addr_len, remote_addr_len;
244 
245     my_GetAcceptExSockaddrs(m_buffer,
246       0, sizeof(sockaddr_storage) + 16, sizeof(sockaddr_storage) + 16,
247       &local_addr, &local_addr_len, &remote_addr, &remote_addr_len);
248 
249     s_client.m_psi= PSI_SOCKET_CALL(init_socket)
250       (key_socket_client_connection, (const my_socket*)&s_listen.fd, remote_addr, remote_addr_len);
251 #endif
252 
253     /* Start accepting new connection. After this point, do not use
254     any member data, they could be used by a different (threadpool) thread. */
255     begin_accept();
256 
257     /* Some chores post-AcceptEx() that we need to create a normal socket.*/
258     if (setsockopt(s_client.fd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
259       (char *)&s_listen.fd, sizeof(s_listen.fd)))
260     {
261       if (!abort_loop)
262       {
263         sql_perror("setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed.");
264         abort();
265       }
266     }
267 
268     /* Create a new connection.*/
269     handle_accepted_socket(s_client, s_listen);
270   }
271 
~Socket_ListenerSocket_Listener272   ~Socket_Listener()
273   {
274     if (m_client_socket != INVALID_SOCKET)
275       closesocket(m_client_socket);
276   }
277 
278   /*
279     Retrieve the pointer to the Winsock extension functions
280     AcceptEx and GetAcceptExSockaddrs.
281   */
init_winsock_extensionsSocket_Listener282   static void init_winsock_extensions()
283   {
284     SOCKET s= mysql_socket_getfd(base_ip_sock);
285     if (s == INVALID_SOCKET)
286       s= mysql_socket_getfd(extra_ip_sock);
287     if (s == INVALID_SOCKET)
288     {
289       /* --skip-networking was used*/
290       return;
291     }
292     GUID guid_AcceptEx= WSAID_ACCEPTEX;
293     GUID guid_GetAcceptExSockaddrs= WSAID_GETACCEPTEXSOCKADDRS;
294 
295     GUID *guids[]= { &guid_AcceptEx, &guid_GetAcceptExSockaddrs };
296     void *funcs[]= { &my_AcceptEx, &my_GetAcceptExSockaddrs };
297     DWORD bytes;
298     for (int i= 0; i < array_elements(guids); i++)
299     {
300       if (WSAIoctl(s,
301         SIO_GET_EXTENSION_FUNCTION_POINTER,
302         guids[i], sizeof(GUID),
303         funcs[i], sizeof(void *),
304         &bytes, 0, 0) == -1)
305       {
306         sql_print_error("WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER) failed");
307         unireg_abort(1);
308       }
309     }
310   }
311 };
312 
313 /*
314   Create a security descriptor for pipe.
315   - Use low integrity level, so that it is possible to connect
316   from any process.
317   - Give current user read/write access to pipe.
318   - Give Everyone read/write access to pipe minus FILE_CREATE_PIPE_INSTANCE
319 */
init_pipe_security_descriptor()320 static void init_pipe_security_descriptor()
321 {
322 #define SDDL_FMT "S:(ML;; NW;;; LW) D:(A;; 0x%08x;;; WD)(A;; FRFW;;; %s)"
323 #define EVERYONE_PIPE_ACCESS_MASK                                             \
324   (FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL |      \
325    SYNCHRONIZE | FILE_WRITE_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES)
326 
327 #ifndef SECURITY_MAX_SID_STRING_CHARACTERS
328 /* Old SDK does not have this constant */
329 #define SECURITY_MAX_SID_STRING_CHARACTERS 187
330 #endif
331 
332   /*
333     Figure out SID of the user that runs the server, then create SDDL string
334     for pipe permissions, and convert it to the security descriptor.
335   */
336   char sddl_string[sizeof(SDDL_FMT) + 8 + SECURITY_MAX_SID_STRING_CHARACTERS];
337   struct
338   {
339     TOKEN_USER token_user;
340     BYTE buffer[SECURITY_MAX_SID_SIZE];
341   } token_buffer;
342   HANDLE token;
343   DWORD tmp;
344 
345   if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
346     goto fail;
347 
348   if (!GetTokenInformation(token, TokenUser, &token_buffer,
349                            (DWORD) sizeof(token_buffer), &tmp))
350     goto fail;
351 
352   CloseHandle(token);
353 
354   char *current_user_string_sid;
355   if (!ConvertSidToStringSid(token_buffer.token_user.User.Sid,
356                              &current_user_string_sid))
357     goto fail;
358 
359   snprintf(sddl_string, sizeof(sddl_string), SDDL_FMT,
360            EVERYONE_PIPE_ACCESS_MASK, current_user_string_sid);
361   LocalFree(current_user_string_sid);
362 
363   if (ConvertStringSecurityDescriptorToSecurityDescriptor(sddl_string,
364       SDDL_REVISION_1, &pipe_security.lpSecurityDescriptor, 0))
365     return;
366 
367 fail:
368   sql_perror("Can't start server : Initialize security descriptor");
369   unireg_abort(1);
370 }
371 
372 /**
373   Pipe Listener.
374   Only event notification mode is implemented, no threadpool
375 */
376 struct Pipe_Listener : public Listener
377 {
378   PTP_CALLBACK_ENVIRON m_tp_env;
Pipe_ListenerPipe_Listener379   Pipe_Listener():
380     Listener(INVALID_HANDLE_VALUE, CreateEvent(0, FALSE, FALSE, 0)),
381     m_tp_env(get_threadpool_win_callback_environ())
382   {
383   }
384 
385   /*
386   Creates local named pipe instance \\.\pipe\$socket for named pipe connection.
387   */
create_named_pipePipe_Listener388   static HANDLE create_named_pipe()
389   {
390     static bool first_instance= true;
391     static char pipe_name[512];
392     DWORD open_mode= PIPE_ACCESS_DUPLEX |
393       FILE_FLAG_OVERLAPPED;
394 
395     if (first_instance)
396     {
397       snprintf(pipe_name, sizeof(pipe_name), "\\\\.\\pipe\\%s", mysqld_unix_port);
398       open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
399       init_pipe_security_descriptor();
400       pipe_security.nLength= sizeof(SECURITY_ATTRIBUTES);
401       pipe_security.bInheritHandle= FALSE;
402     }
403     HANDLE pipe_handle= CreateNamedPipe(pipe_name,
404       open_mode,
405       PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
406       PIPE_UNLIMITED_INSTANCES,
407       (int)global_system_variables.net_buffer_length,
408       (int)global_system_variables.net_buffer_length,
409       NMPWAIT_USE_DEFAULT_WAIT,
410       &pipe_security);
411     if (pipe_handle == INVALID_HANDLE_VALUE)
412     {
413       sql_perror("Create named pipe failed");
414       sql_print_error("Aborting");
415       exit(1);
416     }
417     first_instance= false;
418     return pipe_handle;
419   }
420 
create_pipe_connectionPipe_Listener421   static void create_pipe_connection(HANDLE pipe)
422   {
423     CONNECT *connect;
424     if (!(connect= new CONNECT) || !(connect->vio= vio_new_win32pipe(pipe)))
425     {
426       CloseHandle(pipe);
427       delete connect;
428       statistic_increment(aborted_connects, &LOCK_status);
429       statistic_increment(connection_errors_internal, &LOCK_status);
430       return;
431     }
432     connect->host= my_localhost;
433     create_new_thread(connect);
434   }
435 
436   /* Threadpool callback.*/
tp_create_pipe_connectionPipe_Listener437   static void CALLBACK tp_create_pipe_connection(
438   PTP_CALLBACK_INSTANCE,void *Context)
439   {
440     tp_win_callback_prolog();
441     create_pipe_connection(Context);
442   }
443 
begin_acceptPipe_Listener444   void begin_accept()
445   {
446     m_handle= create_named_pipe();
447     BOOL connected= ConnectNamedPipe(m_handle, &m_overlapped);
448     if (connected)
449     {
450       /*  Overlapped ConnectNamedPipe should return zero. */
451       sql_perror("Overlapped ConnectNamedPipe() already connected.");
452       abort();
453     }
454     DWORD last_error= GetLastError();
455     switch (last_error)
456     {
457       case ERROR_PIPE_CONNECTED:
458         /* Client is already connected, so signal an event.*/
459         {
460           /*
461             Cleanup overlapped (so that subsequent  GetOverlappedResult()
462             does not show results of previous IO
463           */
464           HANDLE e= m_overlapped.hEvent;
465           memset(&m_overlapped, 0, sizeof(m_overlapped));
466           m_overlapped.hEvent = e;
467         }
468         if (!SetEvent(m_overlapped.hEvent))
469         {
470           sql_perror("SetEvent() failed for connected pipe.");
471           abort();
472         }
473         break;
474       case ERROR_IO_PENDING:
475         break;
476       default:
477         sql_perror("ConnectNamedPipe() failed.");
478         abort();
479         break;
480     }
481   }
482 
completion_callbackPipe_Listener483   void completion_callback(bool success)
484   {
485     if (!success)
486     {
487 #ifdef DBUG_OFF
488       sql_print_warning("ConnectNamedPipe completed with %u", GetLastError());
489 #endif
490       CloseHandle(m_handle);
491       m_handle= INVALID_HANDLE_VALUE;
492       begin_accept();
493       return;
494     }
495     HANDLE pipe= m_handle;
496     begin_accept();
497     // If threadpool is on, create connection in threadpool thread
498     if (!m_tp_env || !TrySubmitThreadpoolCallback(tp_create_pipe_connection, pipe, m_tp_env))
499       create_pipe_connection(pipe);
500   }
501 
~Pipe_ListenerPipe_Listener502   ~Pipe_Listener()
503   {
504     if (m_handle != INVALID_HANDLE_VALUE)
505     {
506       CloseHandle(m_handle);
507     }
508   }
509 
cleanupPipe_Listener510   static void cleanup()
511   {
512     LocalFree(pipe_security.lpSecurityDescriptor);
513   }
514 };
515 
516 /**
517   Accept new client connections on Windows.
518 
519   Since we deal with pipe and sockets, they cannot be put into a select/loop.
520   But we can use asynchronous IO, and WaitForMultipleObject() loop.
521 
522   In addition, for slightly better performance, if we're using threadpool,
523   socket connections are accepted directly in the threadpool.
524 
525   The mode of operation is therefore
526 
527   1. There is WaitForMultipleObject() loop that waits for shutdown notification
528   (hEventShutdown),and possibly pipes and sockets(e.g if threadpool is not used)
529   This loop ends when shutdown notification is detected.
530 
531   2. If threadpool is used, new socket connections are accepted there.
532 */
533 
534 
535 #define MAX_WAIT_HANDLES 32
536 #define NUM_PIPE_LISTENERS 24
537 #define SHUTDOWN_IDX 0
538 #define LISTENER_START_IDX 1
539 
handle_connections_win()540 void handle_connections_win()
541 {
542   Listener* all_listeners[MAX_WAIT_HANDLES]= {};
543   HANDLE wait_events[MAX_WAIT_HANDLES]= {};
544   int n_listeners= 0;
545   int n_waits= 0;
546 
547   Socket_Listener::init_winsock_extensions();
548 
549   /* Listen for TCP connections on "extra-port" (no threadpool).*/
550   if (extra_ip_sock.fd != INVALID_SOCKET)
551     all_listeners[n_listeners++]= new Socket_Listener(extra_ip_sock, 0);
552 
553   /* Listen for named pipe connections */
554   if (mysqld_unix_port[0] && !opt_bootstrap && opt_enable_named_pipe)
555   {
556     /*
557       Use several listeners for pipe, to reduce ERROR_PIPE_BUSY on client side.
558     */
559     for (int i= 0; i < NUM_PIPE_LISTENERS; i++)
560       all_listeners[n_listeners++]= new Pipe_Listener();
561   }
562 
563   if (base_ip_sock.fd != INVALID_SOCKET)
564   {
565      /* Wait for TCP connections.*/
566     SetFileCompletionNotificationModes((HANDLE)base_ip_sock.fd, FILE_SKIP_SET_EVENT_ON_HANDLE);
567     all_listeners[n_listeners++]= new Socket_Listener(base_ip_sock, get_threadpool_win_callback_environ());
568   }
569 
570   if (!n_listeners && !opt_bootstrap)
571   {
572     sql_print_error("Either TCP connections or named pipe connections must be enabled.");
573     unireg_abort(1);
574   }
575 
576   wait_events[SHUTDOWN_IDX]= hEventShutdown;
577   n_waits = 1;
578 
579   for (int i= 0;  i < n_listeners; i++)
580   {
581     HANDLE wait_handle= all_listeners[i]->wait_handle();
582     if(wait_handle)
583     {
584       DBUG_ASSERT((i == 0) || (all_listeners[i-1]->wait_handle() != 0));
585       wait_events[n_waits++]= wait_handle;
586     }
587     all_listeners[i]->begin_accept();
588   }
589 
590   for (;;)
591   {
592     DWORD idx = WaitForMultipleObjects(n_waits ,wait_events, FALSE, INFINITE);
593     DBUG_ASSERT((int)idx >= 0 && (int)idx < n_waits);
594 
595     if (idx == SHUTDOWN_IDX)
596       break;
597 
598     all_listeners[idx - LISTENER_START_IDX]->completion_callback();
599   }
600 
601   /* Cleanup */
602   for (int i= 0; i < n_listeners; i++)
603   {
604     Listener *listener= all_listeners[i];
605     if (listener->wait_handle())
606       delete listener;
607     else
608       // Threadpool-bound listener will be deleted in threadpool
609       // Do not call destructor, because callback maybe running.
610       listener->cancel();
611   }
612   Pipe_Listener::cleanup();
613 }
614