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(&copy_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(&copy_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, &copy_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), &copy_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( &copy_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  |