modules/pw/protocol_whois.c
/* [<][>][^][v][top][bottom][index][help] */
FUNCTIONS
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.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)
/* [<][>][^][v][top][bottom][index][help] */
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,
/* [<][>][^][v][top][bottom][index][help] */
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,
/* [<][>][^][v][top][bottom][index][help] */
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()
/* [<][>][^][v][top][bottom][index][help] */
390 {
391 TH_acquire_write_lockw(&queries_lock);
392 }
393
394 /* PW_startqueries() */
395 void
396 PW_startqueries()
/* [<][>][^][v][top][bottom][index][help] */
397 {
398 TH_release_write_lockw(&queries_lock);
399 }
400
401 /* PW_record_query_start() */
402 void
403 PW_record_query_start()
/* [<][>][^][v][top][bottom][index][help] */
404 {
405 TH_acquire_read_lockw(&queries_lock);
406 }
407
408 /* PW_record_query_end() */
409 void
410 PW_record_query_end()
/* [<][>][^][v][top][bottom][index][help] */
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) {
/* [<][>][^][v][top][bottom][index][help] */
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()
/* [<][>][^][v][top][bottom][index][help] */
610 {
611 TH_init_read_write_lockw(&queries_lock);
612 }
613