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");
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 | /* Free allocated memory */
280 | g_string_free(gbuff, TRUE);
281 | free(hostaddress);
282 | free(nrtm_q.source);
283 | SK_cd_close(&(condat));
284 | return;
285 | }
286 | else if(IS_G_QUERY(parse_result)){
287 | if(IS_PERSISTENT(parse_result))persistent_connection=1; else persistent_connection=0;
288 | }
289 | else {
290 | ER_dbg_va(FAC_PM, ASP_PM_ERESP,"[%s] -- Syntax error: %s", hostaddress, input);
291 | /* log the fact and exit */
292 | /* Free the hostaddress */
293 | sprintf(buff, "\n%%ERROR:405: syntax error\n\n");
294 | SK_cd_puts(&condat, buff);
295 | SK_cd_close(&(condat));
296 | free(hostaddress);
297 | free(nrtm_q.source);
298 | return;
299 |
300 | }
301 |
302 | /* otherwise this is -g query */
303 |
304 |
305 | 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);
306 |
307 | source_hdl = ca_get_SourceHandleByName(nrtm_q.source);
308 | if (source_hdl == NULL){
309 | ER_dbg_va(FAC_PM, ASP_PM_ERESP,"[%s] -- Unknown source %s", hostaddress, nrtm_q.source);
310 | sprintf(buff, "\n%%ERROR:403: unknown source\n\n");
311 | SK_cd_puts(&condat, buff);
312 | free(hostaddress);
313 | free(nrtm_q.source);
314 | SK_cd_close(&(condat));
315 | return;
316 | }
317 |
318 | /* check if the client is authorized to mirror */
319 | SK_getpeerip(sock, &address);
320 | if(!AA_can_mirror(&address, nrtm_q.source)){
321 | ER_inf_va(FAC_PM, ASP_PM_ERESP,"[%s] -- Not authorized to mirror the source %s", hostaddress, nrtm_q.source);
322 | sprintf(buff, "\n%%ERROR:402: not authorized to mirror the database\n\n");
323 | SK_cd_puts(&condat, buff);
324 | free(hostaddress);
325 | free(nrtm_q.source);
326 | SK_cd_close(&(condat));
327 | return;
328 | }
329 |
330 | /* get protocol version of the source */
331 | protocol_version = ca_get_srcnrtmprotocolvers(source_hdl);
332 |
333 | /* compare to the version requested */
334 | if(nrtm_q.version != protocol_version){
335 | ER_inf_va(FAC_PM, ASP_PM_ERESP,"[%s] -- Source does not support requested protocol %d", hostaddress, nrtm_q.version);
336 | sprintf(buff, "\n%%ERROR:404: version %d of protocol is not supported\n\n", nrtm_q.version);
337 | SK_cd_puts(&condat, buff);
338 | free(hostaddress);
339 | free(nrtm_q.source);
340 | SK_cd_close(&(condat));
341 | return;
342 | }
343 |
344 | /* get database */
345 | db_name = ca_get_srcdbname(source_hdl);
346 | /* get database host*/
347 | db_host = ca_get_srcdbmachine(source_hdl);
348 | /* get database port*/
349 | db_port = ca_get_srcdbport(source_hdl);
350 | /* get database user*/
351 | db_user = ca_get_srcdbuser(source_hdl);
352 | /* get database password*/
353 | db_pswd = ca_get_srcdbpassword(source_hdl);
354 |
355 | sql_connection = SQ_get_connection(db_host, db_port,db_name, db_user, db_pswd);
356 | if(!sql_connection) {
357 | ER_perror(FAC_PM, PM_NOSQLC," database='%s' [%d] %s",db_name, SQ_errno(sql_connection), SQ_error(sql_connection));
358 | return;
359 | }
360 | ER_dbg_va(FAC_PM, ASP_PM_INPUT,"[%s] -- Made SQL connection to %s@%s", hostaddress, db_name, db_host);
361 |
362 | /* free copies of the variables */
363 | free(db_host);
364 | free(db_name);
365 | free(db_user);
366 | free(db_pswd);
367 |
368 | current_serial=PM_get_current_serial(sql_connection);
369 | oldest_serial=PM_get_oldest_serial(sql_connection);
370 |
371 | if((current_serial==-1) || (oldest_serial==-1)) {
372 | ER_perror(FAC_PM, PM_NOSERN," database='%s' [%d] %s",db_name, SQ_errno(sql_connection), SQ_error(sql_connection));
373 | /* Free the hostaddress */
374 | SK_cd_close(&(condat));
375 | /* close the connection to SQL server */
376 | SQ_close_connection(sql_connection);
377 | free(hostaddress);
378 | free(nrtm_q.source);
379 | return;
380 | }
381 |
382 | /* zero indicates that LAST keyword has been used */
383 | if(nrtm_q.last==0)nrtm_q.last=current_serial;
384 | /* for persistent connections end of range has no meaning */
385 | if(persistent_connection)nrtm_q.last=current_serial;
386 |
387 |
388 | if((nrtm_q.first>nrtm_q.last) || (nrtm_q.first<oldest_serial) || (nrtm_q.last>current_serial) ||
389 | (nrtm_q.first<=0) || (nrtm_q.last<=0) )
390 | {
391 | ER_dbg_va(FAC_PM, ASP_PM_ERESP,"[%s] -- Invalid range: %ld-%ld", hostaddress, nrtm_q.first, nrtm_q.last);
392 | /* write error message back to the client */
393 | sprintf(buff, "\n%%ERROR:401: invalid range: Not within %ld-%ld\n\n", oldest_serial, current_serial);
394 | SK_cd_puts(&condat, buff);
395 | SK_cd_close(&(condat));
396 |
397 | /* close the connection to SQL server */
398 | SQ_close_connection(sql_connection);
399 |
400 | /* Free the hostaddress */
401 | free(hostaddress);
402 | free(nrtm_q.source);
403 | return;
404 | }
405 |
406 | current_serial=nrtm_q.first;
407 |
408 | /* print banner */
409 | {
410 | /* get the header string */
411 | char *resp_header = ca_get_pw_resp_header;
412 | SK_cd_puts(&condat, "\n");
413 | SK_cd_puts(&condat, resp_header);
414 | free(resp_header);
415 | SK_cd_puts(&condat, "\n");
416 | }
417 |
418 | sprintf(buff, "%%START Version: %d %s %ld-%ld\n\n", nrtm_q.version, nrtm_q.source, nrtm_q.first, nrtm_q.last);
419 | SK_cd_puts(&condat, buff);
420 |
421 | /* make a record for thread accounting */
422 | TA_setactivity(buff);
423 |
424 | /*************************** MAIN LOOP ****************************/
425 | /* now start feeding client with data */
426 | do {
427 |
428 | object=PM_get_serial_object(sql_connection, current_serial, &operation);
429 | /* there is a probability that mirroring interferes with HS cleanup */
430 | /* in such case serial may be deleted before it is read by mirrir client */
431 | /* null object will be returned in this case and we need to break the loop */
432 | if(object==NULL) break;
433 | if (operation == OP_ADD) SK_cd_puts(&condat, "ADD\n\n");
434 | else SK_cd_puts(&condat, "DEL\n\n");
435 |
436 | SK_cd_puts(&condat, object);
437 |
438 | SK_cd_puts(&condat, "\n");
439 |
440 | free(object);
441 | current_serial++;
442 |
443 | /* for real-time mirroring we need some piece of code */
444 | if(persistent_connection && (condat.rtc == 0) )
445 | {
446 | while(((nrtm_q.last = PM_get_current_serial(sql_connection))<current_serial)
447 | && (CO_get_do_server()==1))sleep(1);
448 | }
449 |
450 | } /* do while there are more serials, connection was not reset and XXX do_server is on*/
451 | while((current_serial<=nrtm_q.last) && (condat.rtc == 0) && (CO_get_do_server()==1));
452 | /*******************************************************************/
453 |
454 | sprintf(buff, "%%END %s\n\n\n", nrtm_q.source);
455 | SK_cd_puts(&condat, buff);
456 |
457 | ER_inf_va(FAC_PM, ASP_PM_INPUT,"[%s] -- <%s:%ld-%ld (%ld)> ",
458 | hostaddress, nrtm_q.source, nrtm_q.first, nrtm_q.last, nrtm_q.last-nrtm_q.first+1);
459 |
460 | /* make a record for thread accounting */
461 | TA_delete();
462 |
463 | /* close the connection to SQL server */
464 | SQ_close_connection(sql_connection);
465 | /* Free the hostaddress */
466 | free(hostaddress);
467 | free(nrtm_q.source);
468 |
469 |
470 |
471 | } /* PM_interact() */