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