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 |