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