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 |