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