1    | /***************************************
2    | 
3    |   Protocol mirror module (pw). 
4    | 
5    |   Status: NOT REVUED, NOT TESTED
6    | 
7    |   ******************/ /******************
8    |   Filename            : protocol_mirror.c
9    |   Author              : andrei
10   |   OSs Tested          : Solaris
11   |   ******************/ /******************
12   |   Copyright (c) 2000                              RIPE NCC
13   |  
14   |   All Rights Reserved
15   |   
16   |   Permission to use, copy, modify, and distribute this software and its
17   |   documentation for any purpose and without fee is hereby granted,
18   |   provided that the above copyright notice appear in all copies and that
19   |   both that copyright notice and this permission notice appear in
20   |   supporting documentation, and that the name of the author not be
21   |   used in advertising or publicity pertaining to distribution of the
22   |   software without specific, written prior permission.
23   |   
24   |   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
25   |   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
26   |   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
27   |   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
28   |   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
29   |   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
30   |   ***************************************/
31   | #include <stdio.h>
32   | #include <glib.h>
33   | 
34   | #include "protocol_mirror.h"
35   | #include "mysql_driver.h"
36   | #include "constants.h"
37   | 
38   | //#include "access_control.h"
39   | #include "sk.h"
40   | #include "stubs.h"
41   | #include "ud.h"
42   | #include "ta.h"
43   | 
44   | #include "ca_configFns.h"
45   | #include "ca_dictionary.h"
46   | #include "ca_macros.h"
47   | #include "ca_srcAttribs.h"
48   | 
49   | #include "erroutines.h"
50   | 
51   | #include "getopt.h"
52   | 
53   | #define MIN_ARG_LENGTH  6
54   | #define NRTM_DELIM "-:"
55   | 
56   | #define MAX_OPT_ARG_C 3
57   | 
58   | #define Q_QUERY      0x01
59   | #define G_QUERY      0x02
60   | #define K_QUERY      0x04
61   | 
62   | #define IS_Q_QUERY(a)          ((a)&Q_QUERY)
63   | #define IS_G_QUERY(a)          ((a)&G_QUERY)
64   | #define IS_PERSISTENT(a)       ((a)&K_QUERY)
65   | 
66   | 
67   | /*
68   | * parses input and fills nrtm_q_t structure
69   | *
70   | * Returns:
71   | *  -1 in case of garbage
72   | *  1  in case of -q sources
73   | *  2  in case of valid -g
74   | *  3  in case of -k
75   | */
76   | static int parse_request(char *input, nrtm_q_t *nrtm_q)
77   | {
78   |  int res=0, err=0;
79   |  int opt_argc;
80   |  int c;
81   |  gchar **opt_argv;
82   |  getopt_state_t *gst = NULL;
83   | 
84   |   /* Create the arguments. */
85   |   /* This allows only a maximum of MAX_OPT_ARG_C words in the query. */
86   |   opt_argv = g_strsplit(input, " ", MAX_OPT_ARG_C);
87   | 
88   |   /* Determine the number of arguments. */
89   |   for (opt_argc=0; opt_argv[opt_argc] != NULL; opt_argc++);
90   | 
91   |   dieif( (gst = mg_new(0)) == NULL );
92   |   
93   |   while ((c = mg_getopt(opt_argc, opt_argv, "kq:g:", gst)) != EOF) 
94   |   {
95   |     switch (c) {
96   | 	    case 'k':
97   | 	       res |= K_QUERY; /* persistent connection */ 
98   | 	    break;
99   | 
100  | 	    case 'q':
101  | 	        if (gst->optarg != NULL) {
102  |                 char *token, *cursor = gst->optarg;
103  | 		   
104  | 	           res |= Q_QUERY;
105  | 		   err=strncmp(cursor, "sources", 7);
106  | 	           if(err!=0) break;
107  | 		   cursor+=7;
108  |                    g_strchug(cursor);
109  | 	           token=cursor;
110  | 		   /* if no sourses are specified - put NULL in nrtm_q->source and list them all */
111  | 	           if ((*token=='\0') || (*token=='\n') || ((int)*token==13))nrtm_q->source=NULL;
112  | 	           else {
113  |                      cursor=index(token, ' ');
114  |                      if (cursor) nrtm_q->source=g_strndup(token, (cursor-token));
115  |                      else {
116  |                        cursor=index(token, 13); /* search for ctrl-M - telnet loves this */
117  | 	               if (cursor) nrtm_q->source=g_strndup(token, (cursor-token));
118  |                        else {
119  |                          cursor=index(token, '\n');
120  | 		         if (cursor) nrtm_q->source=g_strndup(token, (cursor-token));
121  |                          else nrtm_q->source=g_strdup(token);
122  |                        }
123  |                      }
124  |                    }
125  | 		/* if source was specified - convert it to an upper case */
126  | 		if (nrtm_q->source) g_strup(nrtm_q->source);
127  |                 } else err=1;
128  | 	    break;
129  | 
130  |             case 'g':
131  | 	        if (gst->optarg != NULL) {
132  | 		char *cursor = gst->optarg;
133  | 		char **tokens;	
134  | 		
135  |                   res |= G_QUERY;
136  |                   g_strdelimit(cursor, NRTM_DELIM, ':');
137  |                   tokens=g_strsplit(cursor, ":", 4);
138  |                   if(tokens==NULL) { err=1; break; }
139  |  
140  |                   if(tokens[0]) {
141  |                   /* first token is source name */	 
142  |                      nrtm_q->source=g_strdup(tokens[0]);
143  | 		     /* convert it to an upper case */
144  | 		     g_strup(nrtm_q->source);
145  |                      if(tokens[1]) {
146  |                     /* second token is version number */
147  |                         nrtm_q->version=atoi(tokens[1]);
148  |                         if(tokens[2]) {
149  |                         /* this is first serial */      
150  |                            nrtm_q->first=atol(tokens[2]);
151  |                            if (nrtm_q->first>0) {
152  |                               if(tokens[3]) {
153  |                              /* this is last serial */
154  |                                  nrtm_q->last=atol(tokens[3]);
155  |                                  if (nrtm_q->last==0) 
156  |                                  if (strncasecmp(tokens[3], "LAST", 4)!=0) err=1;
157  |                               } else err=1;
158  |                            } else err=1;    
159  |                         } else err=1; 
160  |                      } else err=1;  
161  |                   } else err=1;   
162  |                   g_strfreev(tokens);
163  |  
164  |                 } else err=1;
165  | 	        
166  | 	    break;
167  | 	    default:
168  | 	        err=1;
169  | 	    break;
170  |     } /* switch */
171  |   } /* while there are arguments */
172  | 
173  |  free(gst);
174  |  g_strfreev(opt_argv);
175  | 
176  |  if (err) return(-1);
177  |  else return(res); 
178  |    
179  | }
180  | 
181  | 
182  | /* PM_interact() */
183  | /*++++++++++++++++++++++++++++++++++++++
184  |   Interact with the client.
185  | 
186  |   int sock Socket that client is connected to.
187  | 
188  |   More:
189  |   +html+ <PRE>
190  |   Authors:
191  |         ottrey
192  |         andrei
193  | 
194  |   +html+ </PRE><DL COMPACT>
195  |   +html+ <DT>Online References:
196  |   +html+ <DD><UL>
197  |   +html+ </UL></DL>
198  | 
199  |   ++++++++++++++++++++++++++++++++++++++*/
200  | void PM_interact(int sock) {
201  |   char input[MAX_PM_INPUT_SIZE+1];
202  |   char buff[STR_L];
203  |   ca_dbSource_t *source_hdl;
204  |   int read_result;
205  |   int parse_result;
206  |   ip_addr_t address;
207  | 
208  |   char *hostaddress=NULL;
209  |   sk_conn_st condat;
210  |   nrtm_q_t nrtm_q;
211  |   long current_serial;
212  |   long oldest_serial;
213  |   
214  |   char *object;
215  |   int operation;
216  |   
217  |   
218  |   char *db_host;
219  |   int  db_port;
220  |   char *db_name;
221  |   char *db_user;
222  |   char *db_pswd;
223  |   int protocol_version;
224  | 
225  |   GString *gbuff;
226  |   
227  |   SQ_connection_t *sql_connection;     
228  |   int persistent_connection;
229  | 
230  |   /* make a record for thread accounting */
231  |   TA_add(sock, "nrtm_srv");
232  | 
233  |   
234  |   /* Get the IP of the client */
235  |   hostaddress = SK_getpeername(sock);
236  |   
237  |   /* initialise the connection structure */
238  |   memset( &condat, 0, sizeof(sk_conn_st));
239  |   /* initialise the nrtm structure */
240  |   memset( &nrtm_q, 0, sizeof(nrtm_q_t));
241  |   /* set the connection data: both rIP and eIP to real IP */
242  |   condat.sock = sock;
243  |   condat.ip = hostaddress;
244  |   SK_getpeerip(sock, &(condat.rIP));
245  |   memcpy( &(condat.eIP), &(condat.rIP), sizeof(ip_addr_t));
246  | 
247  | 
248  |   /* Read input */
249  |   read_result = SK_cd_gets(&(condat), input, MAX_PM_INPUT_SIZE);
250  |     
251  |   /* read_result < 0 is an error and connection should be closed */
252  |   if (read_result < 0 ) {
253  |       /* log the fact, rtc was set */
254  |   }
255  | 
256  |     
257  |   parse_result = parse_request(input, &nrtm_q);
258  | 
259  |   
260  |   if (parse_result < 0 ) {
261  |       ER_dbg_va(FAC_PM, ASP_PM_ERESP,"[%s] -- Garbage received: %s", hostaddress, input);
262  |       /* log the fact and exit */
263  |       /* Free the hostaddress */
264  |       sprintf(buff, "\n%%ERROR:405: syntax error\n\n\n");
265  |       SK_cd_puts(&condat, buff);
266  |       SK_cd_close(&(condat));
267  |       free(hostaddress);
268  |       free(nrtm_q.source);
269  |       return;
270  |   }
271  |   
272  |   ER_dbg_va(FAC_PM, ASP_PM_INPUT,"[%s] -- input: [%s]", hostaddress, input); 
273  |   
274  |   /* this is -q sources query  - answer and return */    
275  |   if (IS_Q_QUERY(parse_result)) {
276  | 	  
277  |       gbuff=PM_get_nrtm_sources(&(condat.rIP), nrtm_q.source);
278  |       SK_cd_puts(&condat, gbuff->str);
279  |       /* end-of-result one extra line (2 in total) */
280  |       SK_cd_puts(&condat, "\n");
281  |       /* Free allocated memory  */
282  |       g_string_free(gbuff, TRUE);
283  |       free(hostaddress);
284  |       free(nrtm_q.source);
285  |       SK_cd_close(&(condat));
286  |       return;
287  |   }
288  |   else if(IS_G_QUERY(parse_result)){
289  |      if(IS_PERSISTENT(parse_result))persistent_connection=1; else persistent_connection=0;
290  |   }
291  |   else {
292  |       ER_dbg_va(FAC_PM, ASP_PM_ERESP,"[%s] -- Syntax error: %s", hostaddress, input);
293  |       /* log the fact and exit */
294  |       /* Free the hostaddress */
295  |       sprintf(buff, "\n%%ERROR:405: syntax error\n\n\n");
296  |       SK_cd_puts(&condat, buff);
297  |       SK_cd_close(&(condat));
298  |       free(hostaddress);
299  |       free(nrtm_q.source);
300  |       return;
301  | 	  
302  |   }
303  |   
304  |   /* otherwise this is -g query */
305  |   
306  |   
307  |   ER_dbg_va(FAC_PM, ASP_PM_INPUT,"[%s] -- input parsed: %s:%d:%ld-%ld", hostaddress, nrtm_q.source, nrtm_q.version, nrtm_q.first, nrtm_q.last);
308  |    
309  |   source_hdl = ca_get_SourceHandleByName(nrtm_q.source); 
310  |   if (source_hdl == NULL){
311  |      ER_dbg_va(FAC_PM, ASP_PM_ERESP,"[%s] --  Unknown source %s", hostaddress, nrtm_q.source);
312  |      sprintf(buff, "\n%%ERROR:403: unknown source\n\n\n");
313  |      SK_cd_puts(&condat, buff);
314  |      free(hostaddress);
315  |      free(nrtm_q.source);
316  |      SK_cd_close(&(condat));
317  |      return;
318  |   }
319  | 	 
320  |   /* check if the client is authorized to mirror */
321  |   SK_getpeerip(sock, &address);
322  |   if(!AA_can_mirror(&address, nrtm_q.source)){
323  |      ER_inf_va(FAC_PM, ASP_PM_ERESP,"[%s] --  Not authorized to mirror the source %s", hostaddress, nrtm_q.source);
324  |      sprintf(buff, "\n%%ERROR:402: not authorized to mirror the database\n\n\n");
325  |      SK_cd_puts(&condat, buff);
326  |      free(hostaddress);
327  |      free(nrtm_q.source);
328  |      SK_cd_close(&(condat));
329  |      return;
330  |   }
331  | 
332  |   /* get protocol version of the source */
333  |   protocol_version = ca_get_srcnrtmprotocolvers(source_hdl);    
334  | 
335  | /* XXX this is compatibility mode where we don't care about the protocol version */
336  | #if 0
337  |   /* compare to the version requested */
338  |   if(nrtm_q.version != protocol_version){
339  |      ER_inf_va(FAC_PM, ASP_PM_ERESP,"[%s] --  Source does not support requested protocol %d", hostaddress, nrtm_q.version);
340  |      sprintf(buff, "\n%%ERROR:404: version %d of protocol is not supported\n\n\n", nrtm_q.version);
341  |      SK_cd_puts(&condat, buff);
342  |      free(hostaddress);
343  |      free(nrtm_q.source);
344  |      SK_cd_close(&(condat));
345  |      return;
346  |   }
347  | #endif
348  |   
349  |       
350  |   /* get database */
351  |   db_name = ca_get_srcdbname(source_hdl);
352  |   /* get database host*/
353  |   db_host = ca_get_srcdbmachine(source_hdl);      
354  |   /* get database port*/
355  |   db_port = ca_get_srcdbport(source_hdl);        
356  |   /* get database user*/
357  |   db_user = ca_get_srcdbuser(source_hdl);          
358  |   /* get database password*/
359  |   db_pswd = ca_get_srcdbpassword(source_hdl);
360  |   
361  |   sql_connection = SQ_get_connection(db_host, db_port,db_name, db_user, db_pswd);
362  |   if(!sql_connection) {
363  |       ER_perror(FAC_PM, PM_NOSQLC," database='%s' [%d] %s",db_name, SQ_errno(sql_connection), SQ_error(sql_connection));
364  |       return;
365  |   }
366  |   ER_dbg_va(FAC_PM, ASP_PM_INPUT,"[%s] --  Made SQL connection to %s@%s", hostaddress, db_name, db_host); 
367  | 
368  |   /* free copies of the variables */
369  |   free(db_host);
370  |   free(db_name);
371  |   free(db_user);
372  |   free(db_pswd);
373  |   
374  |   /* Not to consume the last serial which may cause crash */							                                                      
375  |   current_serial=PM_get_current_serial(sql_connection) - SAFE_BACKLOG;
376  |   oldest_serial=PM_get_oldest_serial(sql_connection);
377  |     
378  |   if((current_serial==-1) || (oldest_serial==-1)) {
379  |       ER_perror(FAC_PM, PM_NOSERN," database='%s' [%d] %s",db_name, SQ_errno(sql_connection), SQ_error(sql_connection));
380  |       /* Free the hostaddress */
381  |       SK_cd_close(&(condat));
382  |       /* close the connection to SQL server */
383  |       SQ_close_connection(sql_connection); 
384  |       free(hostaddress);
385  |       free(nrtm_q.source);
386  |       return;
387  |   }
388  |   
389  |   /* zero indicates that LAST keyword has been used */    
390  |   if(nrtm_q.last==0)nrtm_q.last=current_serial;
391  |   /* for persistent connections end of range has no meaning */
392  |   if(persistent_connection)nrtm_q.last=current_serial;
393  | 
394  |     
395  |   if((nrtm_q.first>nrtm_q.last) || (nrtm_q.first<oldest_serial) || (nrtm_q.last>current_serial) ||
396  |      (nrtm_q.first<=0) || (nrtm_q.last<=0) ) 
397  |   {
398  |       ER_dbg_va(FAC_PM, ASP_PM_ERESP,"[%s] --  Invalid range: %ld-%ld", hostaddress, nrtm_q.first, nrtm_q.last);
399  |       /* write error message back to the client */
400  |       sprintf(buff, "\n%%ERROR:401: invalid range: Not within %ld-%ld\n\n\n", oldest_serial, current_serial);
401  |       SK_cd_puts(&condat, buff);
402  |       SK_cd_close(&(condat));
403  |       
404  |       /* close the connection to SQL server */
405  |       SQ_close_connection(sql_connection); 
406  | 
407  |       /* Free the hostaddress */
408  |       free(hostaddress);
409  |       free(nrtm_q.source);
410  |       return;
411  |   }
412  |   
413  |   current_serial=nrtm_q.first;
414  |  
415  |   /* print banner */
416  |   {
417  |   /* get the header string */
418  |   char *resp_header = ca_get_pw_resp_header;
419  |   SK_cd_puts(&condat, "\n");
420  |   SK_cd_puts(&condat, resp_header);
421  |   free(resp_header);
422  |   SK_cd_puts(&condat, "\n");
423  |   } 
424  |    
425  |   sprintf(buff, "%%START Version: %d %s %ld-%ld\n\n", nrtm_q.version, nrtm_q.source, nrtm_q.first, nrtm_q.last);
426  |   SK_cd_puts(&condat, buff);
427  | 
428  |   /* make a record for thread accounting */
429  |   TA_setactivity(buff);
430  | 
431  | /*************************** MAIN LOOP ****************************/   
432  | /* now start feeding client with data */    
433  |   do {    
434  | 
435  |     /************ ACTUAL PROCESSING IS HERE ***********/
436  |     /* this call will block if queries are paused */
437  |     object=PM_get_serial_object(sql_connection, current_serial, &operation);
438  |     
439  |     /* there is a probability that mirroring interferes with HS cleanup */
440  |     /* in such case serial may be deleted before it is read by mirrir client */
441  |     /* null object will be returned in this case and we need to break the loop */
442  |     if(object==NULL) break;
443  |     if (operation == OP_ADD) SK_cd_puts(&condat, "ADD\n\n");
444  |     else SK_cd_puts(&condat, "DEL\n\n");
445  |     
446  |     SK_cd_puts(&condat, object);
447  | 
448  |     SK_cd_puts(&condat, "\n");
449  |       
450  |     free(object);
451  |     current_serial++;
452  | 
453  |    /* for real-time mirroring we need some piece of code */
454  |     if(persistent_connection && (condat.rtc == 0) )
455  |     {
456  | 	while(((nrtm_q.last = PM_get_current_serial(sql_connection) - SAFE_BACKLOG)<current_serial) 
457  | 		&& (CO_get_do_server()==1))sleep(1);
458  |     }
459  | 
460  |   } /* do while there are more serials, connection was not reset and XXX do_server is on*/
461  |    while((current_serial<=nrtm_q.last) && (condat.rtc == 0) && (CO_get_do_server()==1));
462  | /*******************************************************************/
463  |   
464  |   sprintf(buff, "%%END %s\n\n\n", nrtm_q.source);
465  |   SK_cd_puts(&condat, buff);
466  | 
467  |   ER_inf_va(FAC_PM, ASP_PM_INPUT,"[%s] -- <%s:%ld-%ld (%ld)> ", 
468  |            hostaddress, nrtm_q.source, nrtm_q.first, nrtm_q.last, nrtm_q.last-nrtm_q.first+1); 
469  | 
470  |   /* make a record for thread accounting */
471  |   TA_delete();
472  | 
473  |   /* close the connection to SQL server */
474  |   SQ_close_connection(sql_connection); 
475  |   /* Free the hostaddress */
476  |   free(hostaddress);
477  |   free(nrtm_q.source);
478  | 
479  |   
480  |   
481  | } /* PM_interact() */