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