/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following functions.
- display_file
- pw_log_query
- PW_process_qc
- PW_stopqueries
- PW_startqueries
- PW_record_query_start
- PW_record_query_end
- PW_interact
- PW_init
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)
/* [<][>][^][v][top][bottom][index][help] */
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,
/* [<][>][^][v][top][bottom][index][help] */
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,
/* [<][>][^][v][top][bottom][index][help] */
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()
/* [<][>][^][v][top][bottom][index][help] */
378 {
379 TH_acquire_write_lockw(&queries_lock);
380 }
381
382 /* PW_startqueries() */
383 void
384 PW_startqueries()
/* [<][>][^][v][top][bottom][index][help] */
385 {
386 TH_release_write_lockw(&queries_lock);
387 }
388
389 /* PW_record_query_start() */
390 void
391 PW_record_query_start()
/* [<][>][^][v][top][bottom][index][help] */
392 {
393 TH_acquire_read_lockw(&queries_lock);
394 }
395
396 /* PW_record_query_end() */
397 void
398 PW_record_query_end()
/* [<][>][^][v][top][bottom][index][help] */
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) {
/* [<][>][^][v][top][bottom][index][help] */
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()
/* [<][>][^][v][top][bottom][index][help] */
596 {
597 TH_init_read_write_lockw(&queries_lock);
598 }
599