1 | /*************************************** 2 | $Revision: 1.59 $ 3 | 4 | Protocol whois module (pw). Whois protocol. 5 | 6 | Status: NOT REVUED, TESTED 7 | 8 | ******************/ /****************** 9 | Filename : protocol_whois.c 10 | Authors : ottrey@ripe.net - framework and draft implementation 11 | marek@ripe.net - rewritten and extended. 12 | OSs Tested : Solaris 2.6 13 | ******************/ /****************** 14 | Copyright (c) 1999 RIPE NCC 15 | 16 | All Rights Reserved 17 | 18 | Permission to use, copy, modify, and distribute this software and its 19 | documentation for any purpose and without fee is hereby granted, 20 | provided that the above copyright notice appear in all copies and that 21 | both that copyright notice and this permission notice appear in 22 | supporting documentation, and that the name of the author not be 23 | used in advertising or publicity pertaining to distribution of the 24 | software without specific, written prior permission. 25 | 26 | THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 27 | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL 28 | AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 29 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 30 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 31 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 32 | ***************************************/ 33 | #include <stdio.h> 34 | #include <glib.h> 35 | #include <sys/types.h> 36 | #include <sys/stat.h> 37 | #include <ctype.h> 38 | 39 | #include "NAME" 40 | 41 | #include "defs.h" 42 | #include "protocol_whois.h" 43 | #include "mysql_driver.h" 44 | #include "query_command.h" 45 | #include "query_instructions.h" 46 | #include "constants.h" 47 | 48 | #include "access_control.h" 49 | #include "sk.h" 50 | #include "stubs.h" 51 | 52 | #include "ca_configFns.h" 53 | #include "ca_macros.h" 54 | #include "ca_srcAttribs.h" 55 | 56 | #include "protocol_mirror.h" 57 | 58 | #include "ta.h" 59 | #include "timediff.h" 60 | 61 | #include "ut_string.h" 62 | 63 | #include "thread.h" 64 | 65 | #ifndef VERSION 66 | #define VERSION "3" 67 | #endif 68 | 69 | /*++++++++++++++++++++++++++++++++++++++ 70 | 71 | void 72 | display_file opens a file and displays its contents to the 73 | connection described in conn. structure. 74 | 75 | 76 | sk_conn_st *condat pointer to connection structure 77 | 78 | char *filename file name 79 | 80 | ++++++++++++++++++++++++++++++++++++++*/ 81 | static void 82 | display_file(sk_conn_st *condat, char *filename) 83 | { 84 | FILE *fp; 85 | char *buffer; 86 | struct stat sb; 87 | int bytes; 88 | int p; 89 | 90 | /* open our file */ 91 | fp = fopen(filename, "r"); 92 | if (fp == NULL) { 93 | ER_perror( FAC_PW, PW_CNTOPN, "fopen() failure \"%s\" : %s (%d)", 94 | filename, strerror(errno), errno); 95 | return; 96 | } 97 | 98 | /* get the size of the file */ 99 | if (fstat(fileno(fp), &sb) != 0) { 100 | ER_perror( FAC_PW, PW_CNTOPN, "fstat() failure \"%s\" : %s (%d)", 101 | filename, strerror(errno), errno); 102 | return; 103 | } 104 | 105 | /* allocate a buffer for the file */ 106 | buffer = UT_malloc(sb.st_size+1); 107 | 108 | /* read the file contents */ 109 | bytes = fread(buffer, 1, sb.st_size, fp); 110 | fclose(fp); 111 | 112 | /* can't read more bytes that we asked for */ 113 | dieif(bytes > sb.st_size); 114 | 115 | 116 | /* remove any newlines (actually any whitespace) at the end of the file */ 117 | /* we do this because we can have ONLY ONE newline at the end of the */ 118 | /* output - any more violates our OBJECT, "\n", OBJECT, "\n" format */ 119 | p = bytes-1; 120 | while ((p>=0) && isspace((int)buffer[p])) { 121 | p--; 122 | } 123 | 124 | /* NUL-terminate our string */ 125 | buffer[p+1] = '\0'; 126 | 127 | /* output our resulting buffer */ 128 | SK_cd_puts(condat, buffer); 129 | 130 | /* and enough blank lines */ 131 | SK_cd_puts(condat, "\n\n"); 132 | 133 | /* free the allocated memory */ 134 | UT_free(buffer); 135 | }/* display_file */ 136 | 137 | 138 | /*++++++++++++++++++++++++++++++++++++++ 139 | 140 | static void 141 | pw_log_query logs the query to a file after it has finished. 142 | Takes many parameters to have access to as much 143 | information as possible, including the original 144 | query, accounting, response time, status of the 145 | client connection, etc. 146 | 147 | 148 | Query_environ *qe query environment 149 | 150 | Query_command *qc query command structure 151 | 152 | acc_st *copy_credit numbers of objects returned / referrals made 153 | during this query 154 | (calculated as original credit assigned before 155 | the query minus what's left after the query). 156 | 157 | ut_timer_t begintime time the processing began 158 | 159 | ut_timer_t endtime time the processing finished 160 | 161 | char *hostaddress text address of the real IP 162 | 163 | char *input original query (trailing whitespaces chopped off) 164 | 165 | ++++++++++++++++++++++++++++++++++++++*/ 166 | static 167 | void pw_log_query( Query_environ *qe, 168 | Query_command *qc, 169 | acc_st *copy_credit, 170 | ut_timer_t begintime, 171 | ut_timer_t endtime, 172 | char *hostaddress, 173 | char *input) 174 | { 175 | char *qrystat = AC_credit_to_string(copy_credit); 176 | float elapsed; 177 | char *qrytypestr = 178 | qc->query_type == QC_REAL ? "" : QC_get_qrytype(qc->query_type); 179 | 180 | 181 | elapsed = UT_timediff( &begintime, &endtime); 182 | 183 | /* log the connection/query/#results/time/denial to file */ 184 | ER_inf_va(FAC_PW, ASP_PW_I_QRYLOG, 185 | "<%s> %s%s %.2fs [%s] -- %s", 186 | qrystat, 187 | qe->condat.rtc ? "INT " : "", 188 | qrytypestr, 189 | elapsed, hostaddress, input 190 | ); 191 | wr_free(qrystat); 192 | } /* pw_log_query */ 193 | 194 | 195 | 196 | 197 | /*++++++++++++++++++++++++++++++++++++++ 198 | 199 | void 200 | PW_process_qc processes the query commands determined in QC, 201 | This is where all the real action of the query 202 | part is invoked. 203 | 204 | Query_environ *qe query environment 205 | 206 | Query_command *qc query command structure 207 | 208 | acc_st *acc_credit credit assigned to this IP 209 | 210 | acl_st *acl_eip current acl record applicable to this IP 211 | 212 | ++++++++++++++++++++++++++++++++++++++*/ 213 | void PW_process_qc(Query_environ *qe, 214 | Query_command *qc, 215 | acc_st *acc_credit, 216 | acl_st *acl_eip ) 217 | { 218 | GList *qitem; 219 | Query_instructions *qis=NULL; 220 | er_ret_t err; 221 | 222 | switch( qc->query_type ) { 223 | case QC_SYNERR: 224 | SK_cd_puts(&(qe->condat), "\n"); 225 | SK_cd_puts(&(qe->condat), USAGE); 226 | break; 227 | case QC_PARERR: 228 | /* parameter error. relevant error message is already printed */ 229 | /* we still need an extra newline after this message though */ 230 | SK_cd_puts(&(qe->condat), "\n"); 231 | 232 | /* force disconnection on error */ 233 | qe->k = 0; 234 | break; 235 | case QC_NOKEY: 236 | /* no key (this is OK for some operational stuff, like -k) */ 237 | break; 238 | case QC_EMPTY: 239 | /* The user didn't specify a key, so 240 | - print moron banner 241 | - force disconnection of the user. */ 242 | SK_cd_puts(&(qe->condat), "\n"); 243 | { 244 | char *rep = ca_get_pw_err_nokey ; 245 | SK_cd_puts(&(qe->condat), rep); 246 | wr_free(rep); 247 | } 248 | /* 249 | EXTRA NEWLINE HERE, because we set condat.rtc, which prevents 250 | further output to user, and we need to end our output with multiple 251 | blank lines. 252 | */ 253 | SK_cd_puts(&(qe->condat), "\n\n"); 254 | qe->condat.rtc = SK_NOTEXT; 255 | break; 256 | case QC_HELP: 257 | SK_cd_puts(&(qe->condat), "\n"); 258 | { 259 | char *rep = ca_get_pw_help_file ; 260 | display_file( &(qe->condat), rep); 261 | wr_free(rep); 262 | } 263 | break; 264 | case QC_TEMPLATE: 265 | SK_cd_puts(&(qe->condat), "\n"); 266 | switch(qc->q) { 267 | case QC_Q_SOURCES: 268 | /* print source & mirroring info */ 269 | { 270 | GString *srcs = PM_get_nrtm_sources( & qe->condat.rIP, NULL); 271 | SK_cd_puts(&(qe->condat), srcs->str); 272 | g_string_free (srcs, TRUE); 273 | } 274 | break; 275 | case QC_Q_VERSION: 276 | SK_cd_puts(&(qe->condat), "% RIP version " VERSION "\n\n"); 277 | break; 278 | default: 279 | /* EMPTY */; 280 | } /* -q */ 281 | 282 | if (qc->t >= 0) { 283 | SK_cd_puts(&(qe->condat), DF_get_class_template(qc->t)); 284 | SK_cd_puts(&(qe->condat), "\n"); 285 | } 286 | if (qc->v >= 0) { 287 | SK_cd_puts(&(qe->condat), DF_get_class_template_v(qc->v)); 288 | /* no need for newline here, because it's all broken for any */ 289 | /* automated processor at this point anyway */ 290 | } 291 | break; 292 | 293 | case QC_FILTERED: 294 | { 295 | char *rep = ca_get_pw_k_filter ; 296 | SK_cd_puts(&(qe->condat), rep); 297 | wr_free(rep); 298 | } 299 | /* FALLTROUGH */ 300 | case QC_REAL: 301 | { 302 | char *rep = ca_get_pw_resp_header; 303 | SK_cd_puts(&(qe->condat), rep); 304 | wr_free(rep); 305 | SK_cd_puts(&(qe->condat), "\n"); 306 | } 307 | 308 | #if 1 309 | 310 | qis = QI_new(qc,qe); 311 | 312 | /* go through all sources, 313 | stop if connection broken - further action is meaningless */ 314 | for( qitem = g_list_first(qe->sources_list); 315 | qitem != NULL && qe->condat.rtc == 0; 316 | qitem = g_list_next(qitem)) { 317 | 318 | 319 | /* QI will decrement the credit counters */ 320 | err = QI_execute(qitem->data, qis, qe, acc_credit, acl_eip ); 321 | if( !NOERR(err) ) { 322 | if( err == QI_CANTDB ) { 323 | SK_cd_puts(&(qe->condat), "% WARNING: Failed to make connection to "); 324 | SK_cd_puts(&(qe->condat), (char *)qitem->data); 325 | SK_cd_puts(&(qe->condat), " database.\n\n"); 326 | } 327 | break; /* quit the loop after any error */ 328 | }/* if error*/ 329 | 330 | }/* for every source */ 331 | 332 | QI_free(qis); 333 | 334 | #else 335 | /* test mode: do not run a query, make up some accounting values */ 336 | { 337 | int i, m = random() & 0x0f; 338 | for( i=0 ; i<m ; i++ ) { 339 | AC_count_object( acc_credit, acl_eip, random() & 0x01 ); 340 | } 341 | } 342 | 343 | #endif 344 | 345 | if( AC_credit_isdenied(acc_credit) ) { 346 | /* host reached the limit of returned contact information */ 347 | char *rep = ca_get_pw_limit_reached ; 348 | SK_cd_puts(&(qe->condat), rep); 349 | wr_free(rep); 350 | SK_cd_puts(&(qe->condat), "\n"); 351 | } 352 | 353 | break; 354 | default: die; 355 | } 356 | } /* PW_process_qc */ 357 | 358 | /* 359 | Occasionally, we need to pause queries to the Whois database. This 360 | occurs, for instance, when the database is reloaded for one of the 361 | databases we mirror without using NRTM. 362 | 363 | The way this works is the caller of PW_stopqueries() gets a "write 364 | lock" on queries. Each query gets a "read lock". The lock mechanism 365 | that favors writers is used. 366 | 367 | This means that no new read locks can start once PW_stopqueries() is 368 | called, and that it doesn't return until all read locks have been 369 | released. At this point, queries are stopped and the caller can 370 | proceed to munge about the database safely. 371 | 372 | XXX: This is not the best possible solution, because on a very slow 373 | query (for instance one with a very common person name), the thread 374 | calling PW_stopqueries() as well as ALL query threads cannot proceed 375 | until that thread completes. An alternative with one lock per 376 | database was considered, and may be pursued in the future, but for 377 | now it is not a big problem, since operation occurs normally, just 378 | possibly with a delay in response for some users. 379 | 380 | PW_startqueries() releases the write lock, and queries proceed 381 | normally. 382 | */ 383 | 384 | /* pause queries using a thread lock that favors writers */ 385 | static rw_lock_t queries_lock; 386 | 387 | /* PW_stopqueries() */ 388 | void 389 | PW_stopqueries() 390 | { 391 | TH_acquire_write_lockw(&queries_lock); 392 | } 393 | 394 | /* PW_startqueries() */ 395 | void 396 | PW_startqueries() 397 | { 398 | TH_release_write_lockw(&queries_lock); 399 | } 400 | 401 | /* PW_record_query_start() */ 402 | void 403 | PW_record_query_start() 404 | { 405 | TH_acquire_read_lockw(&queries_lock); 406 | } 407 | 408 | /* PW_record_query_end() */ 409 | void 410 | PW_record_query_end() 411 | { 412 | TH_release_read_lockw(&queries_lock); 413 | } 414 | 415 | 416 | 417 | /*++++++++++++++++++++++++++++++++++++++ 418 | 419 | void 420 | PW_interact Main loop for interaction with a single client. 421 | The function sets up the accounting for the client, 422 | invokes parsing, execution, logging and accounting 423 | of the query. 424 | 425 | int sock Socket that client is connected to. 426 | 427 | ++++++++++++++++++++++++++++++++++++++*/ 428 | void PW_interact(int sock) { 429 | char input[MAX_INPUT_SIZE]; 430 | int read_result; 431 | char *hostaddress=NULL; 432 | acl_st acl_rip, acl_eip; 433 | acc_st acc_credit, copy_credit; 434 | Query_environ *qe=NULL; 435 | Query_command *qc=NULL; 436 | ut_timer_t begintime, endtime; 437 | 438 | /* Get the IP of the client */ 439 | hostaddress = SK_getpeername(sock); 440 | ER_dbg_va(FAC_PW, ASP_PW_CONN, "connection from %s", hostaddress); 441 | 442 | /* Initialize the query environment. */ 443 | qe = QC_environ_new(hostaddress, sock); 444 | 445 | /* init the connection structure, set timeout for reading the query */ 446 | SK_cd_make( &(qe->condat), sock, (unsigned) ca_get_keepopen); 447 | 448 | TA_setcondat(&(qe->condat)); 449 | 450 | /* see if we should be talking at all */ 451 | /* check the acl using the realIP, get a copy applicable to this IP */ 452 | AC_check_acl( &(qe->condat.rIP), NULL, &acl_rip); 453 | 454 | do { 455 | int unauth_pass=0; 456 | 457 | TA_setactivity("waiting for query"); 458 | /* Read input */ 459 | read_result = SK_cd_gets(&(qe->condat), input, MAX_INPUT_SIZE); 460 | /* trash trailing whitespaces(including \n) */ 461 | ut_string_chop(input); 462 | 463 | TA_setactivity(input); 464 | TA_increment(); 465 | 466 | UT_timeget( &begintime ); 467 | 468 | qc = QC_create(input, qe); 469 | 470 | { 471 | /* print the greeting text before the query */ 472 | char *rep = ca_get_pw_banner ; 473 | SK_cd_puts(&(qe->condat), rep); 474 | wr_free(rep); 475 | } 476 | 477 | /* ADDRESS PASSING: check if -V option has passed IP in it */ 478 | if( ! STRUCT_EQUAL(qe->pIP,IP_ADDR_UNSPEC)) { 479 | if(acl_rip.trustpass) { 480 | acc_st pass_acc; 481 | 482 | /* accounting */ 483 | memset(&pass_acc, 0, sizeof(acc_st)); 484 | pass_acc.addrpasses=1; 485 | AC_commit( &qe->condat.rIP, &pass_acc, &acl_rip); 486 | 487 | /* set eIP to this IP */ 488 | qe->condat.eIP = qe->pIP; 489 | } 490 | else { 491 | /* XXX shall we deny such user ? Now we can... */ 492 | ER_inf_va(FAC_PW, ASP_PW_I_PASSUN, 493 | "unauthorised address passing by %s", hostaddress); 494 | unauth_pass = 1; /* keep in mind ... */ 495 | } 496 | } /* if an address was passed */ 497 | 498 | /* start setting counters in the connection acc from here on 499 | decrement the credit counter (needed to prevent QI_execute from 500 | returning too many results */ 501 | 502 | /* check ACL. Get the proper acl record. Calculate credit */ 503 | AC_check_acl( &(qe->condat.eIP), &acc_credit, &acl_eip); 504 | /* save the original credit, later check how much was used */ 505 | copy_credit = acc_credit; 506 | 507 | copy_credit.connections ++; 508 | 509 | /* printing notices */ 510 | if( unauth_pass && ! acl_rip.deny ) { 511 | /* host not authorised to pass addresses with -V */ 512 | char *rep = ca_get_pw_acl_addrpass ; 513 | SK_cd_puts(&(qe->condat), "\n"); 514 | SK_cd_puts(&(qe->condat), rep); 515 | wr_free(rep); 516 | SK_cd_puts(&(qe->condat), "\n"); 517 | } 518 | if( acl_eip.deny || acl_rip.deny ) { 519 | /* access from host has been permanently denied */ 520 | char *rep = ca_get_pw_acl_permdeny ; 521 | SK_cd_puts(&(qe->condat), "\n"); 522 | SK_cd_puts(&(qe->condat), rep); 523 | wr_free(rep); 524 | SK_cd_puts(&(qe->condat), "\n"); 525 | } 526 | 527 | if( acl_eip.deny || acl_rip.deny || unauth_pass ) { 528 | copy_credit.denials ++; 529 | } 530 | else { 531 | /************ ACTUAL PROCESSING IS HERE ***********/ 532 | PW_record_query_start(); 533 | PW_process_qc(qe, qc, &acc_credit, &acl_eip); 534 | PW_record_query_end(); 535 | 536 | if( qc->query_type == QC_REAL ) { 537 | copy_credit.queries ++; 538 | } 539 | }/* if denied ... else */ 540 | 541 | /* calc. the credit used, result into copy_credit 542 | This step MUST NOT be forgotten. It must complement 543 | the initial calculation of a credit, otherwise accounting 544 | will go bgzzzzzt. 545 | */ 546 | AC_acc_addup(©_credit, &acc_credit, ACC_MINUS); 547 | 548 | /* now we can check how many results there were, etc. */ 549 | 550 | /* can say 'nothing found' only if: 551 | - the query did not just cause denial 552 | - was a 'real' query 553 | - nothing was returned 554 | */ 555 | 556 | if( ! AC_credit_isdenied(©_credit) 557 | && (qc->query_type == QC_REAL || qc->query_type == QC_FILTERED) 558 | && copy_credit.private_objects + copy_credit.public_objects 559 | + copy_credit.referrals == 0 ) { 560 | 561 | /* now: if the rtc flag is zero, the query ran to completion */ 562 | if( qe->condat.rtc == 0 ) { 563 | char *rep = ca_get_pw_notfound ; 564 | SK_cd_puts(&(qe->condat), rep); 565 | wr_free(rep); 566 | SK_cd_puts(&(qe->condat), "\n"); 567 | } 568 | else { 569 | /* something happened. Hope for working socket and display message 570 | (won't hurt even if socket not operable) 571 | */ 572 | char *rep = ca_get_pw_connclosed ; 573 | SK_cd_puts(&(qe->condat), rep); 574 | wr_free(rep); 575 | } 576 | } 577 | 578 | 579 | UT_timeget(&endtime); 580 | /* query logging */ 581 | pw_log_query(qe, qc, ©_credit, begintime, endtime, 582 | hostaddress, input); 583 | 584 | /* Commit the credit. This will deny if bonus limit hit 585 | and clear the copy */ 586 | AC_commit(&(qe->condat.eIP), ©_credit, &acl_eip); 587 | 588 | /* end-of-result -> ONE empty line */ 589 | SK_cd_puts(&(qe->condat), "\n"); 590 | 591 | QC_free(qc); 592 | } /* do */ 593 | while( qe->k && qe->condat.rtc == 0 594 | && AC_credit_isdenied( ©_credit ) == 0 595 | && CO_get_whois_suspended() == 0); 596 | 597 | /* Free the hostaddress */ 598 | wr_free(hostaddress); 599 | /* Free the connection struct's dynamic data */ 600 | SK_cd_free(&(qe->condat)); 601 | /* Free the query_environ */ 602 | QC_environ_free(qe); 603 | 604 | } /* PW_interact() */ 605 | 606 | 607 | /* *MUST* be called before any other PW functions */ 608 | void 609 | PW_init() 610 | { 611 | TH_init_read_write_lockw(&queries_lock); 612 | } 613 |