1 /************************************************************************************
2    Copyright (C) 2015 MariaDB Corporation AB
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public
15    License along with this library; if not see <http://www.gnu.org/licenses>
16    or write to the Free Software Foundation, Inc.,
17    51 Franklin St., Fifth Floor, Boston, MA 02110, USA
18 *************************************************************************************/
19 #ifndef _WIN32
20 #define _GNU_SOURCE 1
21 #endif
22 
23 #include <ma_global.h>
24 #include <mysql.h>
25 #include <mysql/client_plugin.h>
26 #include <string.h>
27 #include <memory.h>
28 
29 #ifndef WIN32
30 #include <dlfcn.h>
31 #endif
32 
33 #define READ  0
34 #define WRITE 1
35 
36 /* function prototypes */
37 static int trace_init(char *errormsg,
38                       size_t errormsg_size,
39                       int unused      __attribute__((unused)),
40                       va_list unused1 __attribute__((unused)));
41 static int trace_deinit(void);
42 
43 int (*register_callback)(my_bool register_callback,
44                          void (*callback_function)(int mode, MYSQL *mysql, const uchar *buffer, size_t length));
45 void trace_callback(int mode, MYSQL *mysql, const uchar *buffer, size_t length);
46 
47 #ifndef HAVE_TRACE_EXAMPLE_PLUGIN_DYNAMIC
48 struct st_mysql_client_plugin trace_example_plugin=
49 #else
50 struct st_mysql_client_plugin _mysql_client_plugin_declaration_ =
51 #endif
52 {
53   MARIADB_CLIENT_TRACE_PLUGIN,
54   MARIADB_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION,
55   "trace_example",
56   "Georg Richter",
57   "Trace example plugin",
58   {1,0,0},
59   "LGPL",
60   NULL,
61   &trace_init,
62   &trace_deinit,
63   NULL
64 };
65 
66 static const char *commands[]= {
67   "COM_SLEEP",
68   "COM_QUIT",
69   "COM_INIT_DB",
70   "COM_QUERY",
71   "COM_FIELD_LIST",
72   "COM_CREATE_DB",
73   "COM_DROP_DB",
74   "COM_REFRESH",
75   "COM_SHUTDOWN",
76   "COM_STATISTICS",
77   "COM_PROCESS_INFO",
78   "COM_CONNECT",
79   "COM_PROCESS_KILL",
80   "COM_DEBUG",
81   "COM_PING",
82   "COM_TIME",
83   "COM_DELAYED_INSERT",
84   "COM_CHANGE_USER",
85   "COM_BINLOG_DUMP",
86   "COM_TABLE_DUMP",
87   "COM_CONNECT_OUT",
88   "COM_REGISTER_SLAVE",
89   "COM_STMT_PREPARE",
90   "COM_STMT_EXECUTE",
91   "COM_STMT_SEND_LONG_DATA",
92   "COM_STMT_CLOSE",
93   "COM_STMT_RESET",
94   "COM_SET_OPTION",
95   "COM_STMT_FETCH",
96   "COM_DAEMON",
97   "COM_END"
98 };
99 
100 typedef struct {
101   unsigned long thread_id;
102   int last_command; /* COM_* values, -1 for handshake */
103   unsigned int max_packet_size;
104   unsigned int num_commands;
105   size_t total_size[2];
106   unsigned int client_flags;
107   char *username;
108   char *db;
109   char *command;
110   char *filename;
111   unsigned long refid; /* stmt_id, thread_id for kill */
112   uchar charset;
113   void *next;
114   int local_infile;
115   unsigned long pkt_length;
116 } TRACE_INFO;
117 
118 #define TRACE_STATUS(a) ((!a) ? "ok" : "error")
119 
120 TRACE_INFO *trace_info= NULL;
121 
get_trace_info(unsigned long thread_id)122 static TRACE_INFO *get_trace_info(unsigned long thread_id)
123 {
124   TRACE_INFO *info= trace_info;
125 
126   /* search connection */
127   while (info)
128   {
129     if (info->thread_id == thread_id)
130       return info;
131     else
132       info= (TRACE_INFO *)info->next;
133   }
134 
135   if (!(info= (TRACE_INFO *)calloc(sizeof(TRACE_INFO), 1)))
136     return NULL;
137   info->thread_id= thread_id;
138   info->next= trace_info;
139   trace_info= info;
140   return info;
141 }
142 
delete_trace_info(unsigned long thread_id)143 static void delete_trace_info(unsigned long thread_id)
144 {
145   TRACE_INFO *last= NULL, *current;
146   current= trace_info;
147 
148   while (current)
149   {
150     if (current->thread_id == thread_id)
151     {
152       printf("deleting thread %lu\n", thread_id);
153 
154       if (last)
155         last->next= current->next;
156       else
157         trace_info= (TRACE_INFO *)current->next;
158       if (current->command)
159         free(current->command);
160       if (current->db)
161         free(current->db);
162       if (current->username)
163         free(current->username);
164       if (current->filename)
165         free(current->filename);
166       free(current);
167     }
168     last= current;
169     current= (TRACE_INFO *)current->next;
170   }
171 
172 }
173 
174 
175 /* {{{ static int trace_init */
176 /*
177   Initialization routine
178 
179   SYNOPSIS
180     trace_init
181       unused1
182       unused2
183       unused3
184       unused4
185 
186   DESCRIPTION
187     Init function registers a callback handler for PVIO interface.
188 
189   RETURN
190     0           success
191 */
trace_init(char * errormsg,size_t errormsg_size,int unused1,va_list unused2)192 static int trace_init(char *errormsg,
193                       size_t errormsg_size,
194                       int unused1 __attribute__((unused)),
195                       va_list unused2 __attribute__((unused)))
196 {
197   void *func;
198 
199 #ifdef WIN32
200   if (!(func= GetProcAddress(GetModuleHandle(NULL), "ma_pvio_register_callback")))
201 #else
202   if (!(func= dlsym(RTLD_DEFAULT, "ma_pvio_register_callback")))
203 #endif
204   {
205     strncpy(errormsg, "Can't find ma_pvio_register_callback function", errormsg_size);
206     return 1;
207   }
208   register_callback= func;
209   register_callback(TRUE, trace_callback);
210 
211   return 0;
212 }
213 /* }}} */
214 
trace_deinit(void)215 static int trace_deinit(void)
216 {
217   /* unregister plugin */
218   while(trace_info)
219   {
220     printf("Warning: Connection for thread %lu not properly closed\n", trace_info->thread_id);
221     trace_info= (TRACE_INFO *)trace_info->next;
222   }
223   register_callback(FALSE, trace_callback);
224   return 0;
225 }
226 
trace_set_command(TRACE_INFO * info,char * buffer,size_t size)227 static void trace_set_command(TRACE_INFO *info, char *buffer, size_t size)
228 {
229   if (info->command)
230     free(info->command);
231 
232   info->command= calloc(1, size + 1);
233   memcpy(info->command, buffer, size);
234 }
235 
dump_buffer(uchar * buffer,size_t len)236 void dump_buffer(uchar *buffer, size_t len)
237 {
238   uchar *p= buffer;
239   while (p < buffer + len)
240   {
241     printf("%02x ", *p);
242     p++;
243   }
244   printf("\n");
245 }
246 
dump_simple(TRACE_INFO * info,my_bool is_error)247 static void dump_simple(TRACE_INFO *info, my_bool is_error)
248 {
249   printf("%8lu: %s %s\n", info->thread_id, commands[info->last_command], TRACE_STATUS(is_error));
250 }
251 
dump_reference(TRACE_INFO * info,my_bool is_error)252 static void dump_reference(TRACE_INFO *info, my_bool is_error)
253 {
254   printf("%8lu: %s(%lu) %s\n", info->thread_id, commands[info->last_command], (long)info->refid, TRACE_STATUS(is_error));
255 }
256 
dump_command(TRACE_INFO * info,my_bool is_error)257 static void dump_command(TRACE_INFO *info, my_bool is_error)
258 {
259   size_t i;
260   printf("%8lu: %s(",  info->thread_id, commands[info->last_command]);
261   for (i= 0; info->command && i < strlen(info->command); i++)
262     if (info->command[i] == '\n')
263       printf("\\n");
264     else if (info->command[i] == '\r')
265       printf("\\r");
266     else if (info->command[i] == '\t')
267       printf("\\t");
268     else
269       printf("%c", info->command[i]);
270   printf(") %s\n", TRACE_STATUS(is_error));
271 }
272 
trace_callback(int mode,MYSQL * mysql,const uchar * buffer,size_t length)273 void trace_callback(int mode, MYSQL *mysql, const uchar *buffer, size_t length)
274 {
275   unsigned long thread_id= mysql->thread_id;
276   TRACE_INFO *info;
277 
278   /* check if package is server greeting package,
279    * and set thread_id */
280   if (!thread_id && mode == READ)
281   {
282     char *p= (char *)buffer;
283     p+= 4; /* packet length */
284     if ((uchar)*p != 0xFF) /* protocol version 0xFF indicates error */
285     {
286       p+= strlen(p + 1) + 2;
287       thread_id= uint4korr(p);
288     }
289     info= get_trace_info(thread_id);
290     info->last_command= -1;
291   }
292   else
293   {
294     char *p= (char *)buffer;
295     info= get_trace_info(thread_id);
296 
297     if (info->last_command == -1)
298     {
299       if (mode == WRITE)
300       {
301         /* client authentication reply packet:
302          *
303          *  ofs description        length
304          *  ------------------------
305          *  0   length             3
306          *  3   packet_no          1
307          *  4   client capab.      4
308          *  8   max_packet_size    4
309          *  12  character set      1
310          *  13  reserved          23
311          *  ------------------------
312          *  36  username (zero terminated)
313          *      len (1 byte) + password or
314          */
315 
316         p+= 4;
317         info->client_flags= uint4korr(p);
318         p+= 4;
319         info->max_packet_size= uint4korr(p);
320         p+= 4;
321         info->charset= *p;
322         p+= 24;
323         info->username= strdup(p);
324         p+= strlen(p) + 1;
325         if (*p) /* we are not interested in authentication data */
326           p+= *p;
327         p++;
328         if (info->client_flags & CLIENT_CONNECT_WITH_DB)
329           info->db= strdup(p);
330       }
331       else
332       {
333         p++;
334         if ((uchar)*p == 0xFF)
335           printf("%8lu: CONNECT_ERROR(%d)\n", info->thread_id, uint4korr(p+1));
336         else
337           printf("%8lu: CONNECT_SUCCESS(host=%s,user=%s,db=%s)\n", info->thread_id,
338                  mysql->host, info->username, info->db ? info->db : "'none'");
339         info->last_command= COM_SLEEP;
340       }
341     }
342     else {
343       char *p= (char *)buffer;
344       int len;
345 
346       if (mode == WRITE)
347       {
348         if (info->pkt_length > 0)
349         {
350           info->pkt_length-= length;
351           return;
352         }
353         len= uint3korr(p);
354         info->pkt_length= len + 4 - length;
355         p+= 4;
356         info->last_command= *p;
357         p++;
358 
359         switch (info->last_command) {
360         case COM_INIT_DB:
361         case COM_DROP_DB:
362         case COM_CREATE_DB:
363         case COM_DEBUG:
364         case COM_QUERY:
365         case COM_STMT_PREPARE:
366           trace_set_command(info, p, len - 1);
367           break;
368         case COM_PROCESS_KILL:
369           info->refid= uint4korr(p);
370           break;
371         case COM_QUIT:
372           printf("%8lu: COM_QUIT\n", info->thread_id);
373           delete_trace_info(info->thread_id);
374           break;
375         case COM_PING:
376           printf("%8lu: COM_PING\n", info->thread_id);
377           break;
378         case COM_STMT_EXECUTE:
379         case COM_STMT_RESET:
380         case COM_STMT_CLOSE:
381           info->refid= uint4korr(p);
382           break;
383         case COM_CHANGE_USER:
384           break;
385         default:
386           if (info->local_infile == 1)
387           {
388             printf("%8lu: SEND_LOCAL_INFILE(%s) ", info->thread_id, info->filename);
389             if (len)
390               printf("sent %d bytes\n", len);
391             else
392               printf("- error\n");
393             info->local_infile= 2;
394           }
395           else
396             printf("%8lu: UNKNOWN_COMMAND: %d\n", info->thread_id, info->last_command);
397           break;
398         }
399       }
400       else
401       {
402         my_bool is_error;
403 
404         len= uint3korr(p);
405         p+= 4;
406 
407         is_error= (len == -1);
408 
409         switch(info->last_command) {
410         case COM_STMT_EXECUTE:
411         case COM_STMT_RESET:
412         case COM_STMT_CLOSE:
413         case COM_PROCESS_KILL:
414           dump_reference(info, is_error);
415           info->refid= 0;
416           info->last_command= 0;
417           break;
418         case COM_QUIT:
419           dump_simple(info, is_error);
420           break;
421         case COM_QUERY:
422         case COM_INIT_DB:
423         case COM_DROP_DB:
424         case COM_CREATE_DB:
425         case COM_DEBUG:
426         case COM_CHANGE_USER:
427           if (info->last_command == COM_QUERY && (uchar)*p == 251)
428           {
429             info->local_infile= 1;
430             p++;
431             info->filename= (char *)malloc(len);
432             strncpy(info->filename, (char *)p, len);
433             dump_command(info, is_error);
434             break;
435           }
436           dump_command(info, is_error);
437           if (info->local_infile != 1)
438           {
439             free(info->command);
440             info->command= NULL;
441           }
442           break;
443         case COM_STMT_PREPARE:
444           printf("%8lu: COM_STMT_PREPARE(%s) ", info->thread_id, info->command);
445           if (!*p)
446           {
447             unsigned long stmt_id= uint4korr(p+1);
448             printf("-> stmt_id(%lu)\n", stmt_id);
449           }
450           else
451             printf("error\n");
452           break;
453         }
454       }
455     }
456   }
457   info->total_size[mode]+= length;
458 }
459