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() */