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