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() */