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