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