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)
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, 
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, 
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()
390  | {
391  |     TH_acquire_write_lockw(&queries_lock);
392  | }
393  | 
394  | /* PW_startqueries() */
395  | void
396  | PW_startqueries()
397  | {
398  |     TH_release_write_lockw(&queries_lock);
399  | }
400  | 
401  | /* PW_record_query_start() */
402  | void 
403  | PW_record_query_start()
404  | {
405  |     TH_acquire_read_lockw(&queries_lock);
406  | }
407  | 
408  | /* PW_record_query_end() */
409  | void 
410  | PW_record_query_end()
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) {
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(&copy_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(&copy_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, &copy_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), &copy_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( &copy_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()
610  | {
611  |     TH_init_read_write_lockw(&queries_lock);
612  | }
613  |