1    | /***************************************
2    |   $Revision: 1.27 $
3    | 
4    | 
5    |   Sql module (sq).  This is a mysql implementation of an sql module.
6    | 
7    |   Status: NOT REVUED, NOT TESTED
8    | 
9    |   Note: this code has been heavily coupled to MySQL, and may need to be changed
10   |   (to improve performance) if a new RDBMS is used.
11   | 
12   |   ******************/ /******************
13   |   Filename            : query_instructions.c
14   |   Author              : ottrey@ripe.net
15   |   OSs Tested          : Solaris
16   |   Problems            : Moderately linked to MySQL.  Not sure which inverse
17   |                         attributes each option has.  Would like to modify this
18   |                         after re-designing the objects module.
19   |   Comments            : Not sure about the different keytypes.
20   |   ******************/ /******************
21   |   Copyright (c) 1999                              RIPE NCC
22   |  
23   |   All Rights Reserved
24   |   
25   |   Permission to use, copy, modify, and distribute this software and its
26   |   documentation for any purpose and without fee is hereby granted,
27   |   provided that the above copyright notice appear in all copies and that
28   |   both that copyright notice and this permission notice appear in
29   |   supporting documentation, and that the name of the author not be
30   |   used in advertising or publicity pertaining to distribution of the
31   |   software without specific, written prior permission.
32   |   
33   |   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
34   |   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
35   |   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
36   |   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
37   |   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
38   |   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
39   |   ***************************************/
40   | #include <stdio.h>
41   | #include <string.h>
42   | #include <glib.h>
43   | 
44   | #include "which_keytypes.h"
45   | #include "query_instructions.h"
46   | #include "mysql_driver.h"
47   | #include "rxroutines.h"
48   | #include "stubs.h"
49   | #include "constants.h"
50   | 
51   | /*+ String sizes +*/
52   | #define STR_S   63
53   | #define STR_M   255
54   | #define STR_L   1023
55   | #define STR_XL  4095
56   | #define STR_XXL 16383
57   | 
58   | 
59   | #include "QI_queries.def"
60   | 
61   | /* log_inst_print() */
62   | /*++++++++++++++++++++++++++++++++++++++
63   |   Log the instruction.
64   | 
65   |   char *str instruction to be logged.
66   |    
67   |   More:
68   |   +html+ <PRE>
69   |   Authors:
70   |         ottrey
71   |   +html+ </PRE><DL COMPACT>
72   |   +html+ <DT>Online References:
73   |   +html+ <DD><UL>
74   |   +html+ </UL></DL>
75   | 
76   |   ++++++++++++++++++++++++++++++++++++++*/
77   | void log_inst_print(char *str) {
78   |   FILE *logf;
79   | 
80   |   if (CO_get_instr_logging() == 1) {
81   |     if (strcmp(CO_get_instr_logfile(), "stdout") == 0) {
82   |       printf("%s", str);
83   |     }
84   |     else {
85   |       logf = fopen(CO_get_instr_logfile(), "a");
86   |       fprintf(logf, "%s", str);
87   |       fclose(logf);
88   |     }
89   |   }
90   | 
91   | } /* log_inst_print() */
92   | 
93   | /* create_name_query() */
94   | /*++++++++++++++++++++++++++++++++++++++
95   |   Create an sql query for the names table. 
96   | 
97   |   char *query_str
98   | 
99   |   const char *sql_query
100  | 
101  |   const char *keys
102  |    
103  |   More:
104  |   +html+ <PRE>
105  |   Authors:
106  |         ottrey
107  |   +html+ </PRE><DL COMPACT>
108  |   +html+ <DT>Online References:
109  |   +html+ <DD><UL>
110  |   +html+ </UL></DL>
111  | 
112  |   ++++++++++++++++++++++++++++++++++++++*/
113  | static void create_name_query(char *query_str, const char *sql_query, const char *keys) {
114  |   int i;
115  |   /* Allocate stuff */
116  |   GString *result = g_string_sized_new(STR_XXL);
117  |   GString *from_clause = g_string_sized_new(STR_XL);
118  |   GString *where_clause = g_string_sized_new(STR_XL);
119  |   gchar **words = g_strsplit(keys, " ", 0);
120  | 
121  |   g_string_sprintfa(from_clause, "names N%.2d", 0);
122  |   g_string_sprintfa(where_clause, "N%.2d.name='%s'", 0, words[0]);
123  | 
124  |   for (i=1; words[i] != NULL; i++) {
125  |     g_string_sprintfa(from_clause, ", names N%.2d", i);
126  |     g_string_sprintfa(where_clause, " AND N%.2d.name='%s' AND N00.object_id = N%.2d.object_id", i, words[i], i);
127  |   }
128  | 
129  |   sprintf(query_str, sql_query, from_clause->str, where_clause->str);
130  | 
131  |   /* Free up stuff */
132  |   g_strfreev(words);
133  |   g_string_free(where_clause, TRUE);
134  |   g_string_free(from_clause, TRUE);
135  | 
136  | } /* create_name_query() */
137  | 
138  | static void add_filter(char *query_str, const Query_command *qc) {
139  |   int i;
140  |   int qlen;
141  |   char filter_atom[STR_M];
142  | 
143  | /*
144  |   if (MA_bitcount(qc->object_type_bitmap) > 0) { 
145  |     g_string_sprintfa(query_str, " AND (");
146  |     for (i=0; i < C_END; i++) {
147  |       if (MA_isset(qc->object_type_bitmap, i)) {
148  |         g_string_sprintfa(query_str, "i.object_type = %d OR ", DF_get_class_dbase_code(i));
149  |       }
150  |     }
151  |     g_string_truncate(query_str, query_str->len-3);
152  |     g_string_append_c(query_str, ')');
153  |   }
154  | */
155  |   if (MA_bitcount(qc->object_type_bitmap) > 0) { 
156  |     strcat(query_str, " AND (");
157  |     for (i=0; i < C_END; i++) {
158  |       if (MA_isset(qc->object_type_bitmap, i)) {
159  |         strcpy(filter_atom, "");
160  |         sprintf(filter_atom, "i.object_type = %d OR ", DF_get_class_dbase_code(i));
161  |         strcat(query_str, filter_atom);
162  |       }
163  |     }
164  |     qlen = strlen(query_str);
165  |     query_str[qlen-3] = ')';
166  |     query_str[qlen-2] = '\0';
167  |     query_str[qlen-1] = '\0';
168  |   }
169  |   
170  | } /* add_filter() */
171  | 
172  | /* create_query() */
173  | /*++++++++++++++++++++++++++++++++++++++
174  |   Create an sql query from the query_command and the matching keytype and the
175  |   selected inverse attributes.
176  |   Note this clears the first inv_attribute it sees, so is called sequentially
177  |   until there are no inv_attributes left.
178  | 
179  |   WK_Type keytype The matching keytype.
180  | 
181  |   const Query_command *qc The query command.
182  | 
183  |   mask_t *inv_attrs_bitmap The selected inverse attributes.
184  |    
185  |   More:
186  |   +html+ <PRE>
187  |   Authors:
188  |         ottrey
189  |   +html+ </PRE><DL COMPACT>
190  |   +html+ <DT>Online References:
191  |   +html+ <DD><UL>
192  |   +html+ </UL></DL>
193  | 
194  |   ++++++++++++++++++++++++++++++++++++++*/
195  | static char *create_query(const Query_t q, const Query_command *qc) {
196  |   char *result=NULL;
197  |   char result_buff[STR_XL];
198  |   Q_Type_t querytype;
199  |   int conduct_test = 0;
200  | 
201  |   if (MA_bitcount(qc->inv_attrs_bitmap) > 0) {
202  |     querytype = Q_INVERSE;
203  |   }
204  |   else {
205  |     querytype = Q_LOOKUP;
206  |   }
207  | 
208  |   if ( (q.query != NULL) 
209  |     && (q.querytype == querytype) ) {
210  |     conduct_test=1;
211  |   }
212  | 
213  |   if (conduct_test == 1) {
214  | 
215  |     if (q.keytype == WK_NAME) { 
216  |       /* Name queries require special treatment. */
217  |        create_name_query(result_buff, q.query, qc->keys);
218  |     }
219  |     else {
220  |       sprintf(result_buff, q.query, qc->keys);
221  |     }
222  | 
223  |     if (q.class == -1) {
224  |       /* It is class type ANY so add the object filtering */
225  |       add_filter(result_buff, qc);
226  |     }
227  | 
228  |     result = (char *)malloc(strlen(result_buff)+1);
229  |     strcpy(result, result_buff);
230  |   }
231  | 
232  |   return result;
233  | } /* create_query() */
234  | 
235  | /* fast_output() */
236  | /*++++++++++++++++++++++++++++++++++++++
237  |   This is for the '-F' flag.
238  |   It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.
239  | 
240  |   Fast isn't fast anymore - it's just there for compatibility reasons.
241  |   This could be speed up if there were breaks out of the loops, once it matched something.
242  |   (Wanna add a goto Marek?  :-) ).
243  | 
244  |   const char *string The string to be "fast outputed".
245  |    
246  |   More:
247  |   +html+ <PRE>
248  |   Authors:
249  |         ottrey
250  |   +html+ </PRE><DL COMPACT>
251  |   +html+ <DT>Online References:
252  |   +html+ <DD><UL>
253  |   +html+ </UL></DL>
254  | 
255  |   ++++++++++++++++++++++++++++++++++++++*/
256  | char *fast_output(const char *str) {
257  |   int i,j;
258  |   char *result;
259  |   char result_bit[STR_L];
260  |   char result_buff[STR_XL];
261  |   gchar **lines = g_strsplit(str, "\n", 0);
262  |   char * const *attribute_names;
263  |   gboolean filtering_an_attribute = FALSE;
264  |   char *value;
265  |   
266  |   attribute_names = DF_get_attribute_names();
267  | 
268  |   strcpy(result_buff, "");
269  |   for (i=0; attribute_names[i] != NULL; i++) {
270  |     for (j=0; lines[j] != NULL; j++) {
271  |       if (strncmp(attribute_names[i], lines[j], strlen(attribute_names[i])) == 0) {
272  |         strcpy(result_bit, "");
273  |         /* This is the juicy bit that converts the likes of; "source: RIPE" to "*so: RIPE" */
274  |         value = strchr(lines[j], ':');
275  |         value++;
276  |         /* Now get rid of whitespace. */
277  |         while (*value == ' ' || *value == '\t') {
278  |           value++;
279  |         }
280  |         sprintf(result_bit, "*%s: %s\n", DF_get_attribute_code(i), value);
281  |         strcat(result_buff, result_bit);
282  |       }
283  |       else if (filtering_an_attribute == TRUE) {
284  |         switch (lines[j][0]) {
285  |           case ' ':
286  |           case '\t':
287  |           case '+':
288  |             strcpy(result_bit, "");
289  |             sprintf(result_bit, "%s\n", lines[j]);
290  |             strcat(result_buff, result_bit);
291  |           break;
292  | 
293  |           default:
294  |             filtering_an_attribute = FALSE;
295  |         }
296  |       }
297  |     }
298  |   }
299  | 
300  |   result = (char *)malloc(strlen(result_buff)+1);
301  |   strcpy(result, result_buff);
302  | 
303  |   return result;
304  | } /* fast_output() */
305  | 
306  | /* filter() */
307  | /*++++++++++++++++++++++++++++++++++++++
308  |   Basically it's for the '-K' flag for non-set (and non-radix) objects.
309  |   It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.
310  | 
311  |   This could be speed up if there were breaks out of the loops, once it matched something.
312  |   (Wanna add a goto Marek?  :-) ).
313  | 
314  |   const char *string The string to be filtered.
315  |    
316  |   More:
317  |   +html+ <PRE>
318  |   Authors:
319  |         ottrey
320  |   +html+ </PRE><DL COMPACT>
321  |   +html+ <DT>Online References:
322  |   +html+ <DD><UL>
323  |   +html+ </UL></DL>
324  | 
325  |   ++++++++++++++++++++++++++++++++++++++*/
326  | char *filter(const char *str) {
327  |   int i,j;
328  |   char *result;
329  |   char result_bit[STR_L];
330  |   char result_buff[STR_XL];
331  |   gchar **lines = g_strsplit(str, "\n", 0);
332  |   char * const *filter_names;
333  |   gboolean filtering_an_attribute = FALSE;
334  |   
335  |   filter_names = DF_get_filter_names();
336  | 
337  |   strcpy(result_buff, "");
338  |   for (i=0; filter_names[i] != NULL; i++) {
339  |     for (j=0; lines[j] != NULL; j++) {
340  |       if (strncmp(filter_names[i], lines[j], strlen(filter_names[i])) == 0) {
341  |         strcpy(result_bit, "");
342  |         sprintf(result_bit, "%s\n", lines[j]);
343  |         strcat(result_buff, result_bit);
344  |         filtering_an_attribute = TRUE;
345  |       }
346  |       else if (filtering_an_attribute == TRUE) {
347  |         switch (lines[j][0]) {
348  |           case ' ':
349  |           case '\t':
350  |           case '+':
351  |             strcpy(result_bit, "");
352  |             sprintf(result_bit, "%s\n", lines[j]);
353  |             strcat(result_buff, result_bit);
354  |           break;
355  | 
356  |           default:
357  |             filtering_an_attribute = FALSE;
358  |         }
359  |       }
360  |     }
361  |   }
362  | 
363  |   result = (char *)malloc(strlen(result_buff)+1);
364  |   strcpy(result, result_buff);
365  | 
366  |   return result;
367  | } /* filter() */
368  | 
369  | /* write_results() */
370  | /*++++++++++++++++++++++++++++++++++++++
371  |   Write the results to the client socket.
372  | 
373  |   SQ_result_set_t *result The result set returned from the sql query.
374  |   unsigned filtered       if the objects should go through a filter (-K)
375  |   sk_conn_st *condat      Connection data for the client    
376  |   int maxobjects          max # of objects to write
377  | 
378  |   XXX NB. this is very dependendant on what rows are returned in the result!!!
379  |    
380  |   More:
381  |   +html+ <PRE>
382  |   Authors:
383  |         ottrey
384  |   +html+ </PRE><DL COMPACT>
385  |   +html+ <DT>Online References:
386  |   +html+ <DD><UL>
387  |   +html+ </UL></DL>
388  | 
389  |   ++++++++++++++++++++++++++++++++++++++*/
390  | static int write_results(SQ_result_set_t *result, 
391  | 			 unsigned filtered,
392  | 			 unsigned fast,
393  | 			 sk_conn_st *condat,
394  | 			 int maxobjects /* -1 == unlimited */
395  | 			 ) {
396  |   SQ_row_t *row;
397  |   char *str;
398  |   char *filtrate;
399  |   char *fasted;
400  |   char log_str[STR_L];
401  |   int retrieved_objects=0;
402  | 
403  |   /* Get all the results - one at a time */
404  |   if (result != NULL) {
405  |     while ( (row = SQ_row_next(result)) != NULL 
406  | 	    && (maxobjects == -1 || retrieved_objects < maxobjects) ) {
407  |       str = SQ_get_column_string(result, row, 0);
408  |       if (str != NULL) {
409  |         strcpy(log_str, "");
410  |         sprintf(log_str, "Retrieved serial id = %d\n", atoi(str));
411  |         log_inst_print(log_str);
412  |       }
413  |       free(str);
414  | 
415  |       str = SQ_get_column_string(result, row, 2);
416  |       if (str != NULL) {
417  | 
418  |         /* The fast output stage */
419  |         if (fast == 1) {
420  |           fasted = fast_output(str);
421  |           free(str);
422  |           str = fasted;
423  |         }
424  | 
425  |         /* The filtering stage */
426  |         if (filtered == 0) {
427  |           SK_cd_puts(condat, str);
428  |         }
429  |         else {
430  |           filtrate = filter(str);
431  |           SK_cd_puts(condat, filtrate);
432  |           free(filtrate);
433  |         }
434  |         SK_cd_puts(condat, "\n");
435  |         retrieved_objects++;
436  |       }
437  |       free(str);
438  |     }
439  |   }
440  |   
441  |   return retrieved_objects;
442  | } /* write_results() */
443  | 
444  | /* write_objects() */
445  | /*++++++++++++++++++++++++++++++++++++++
446  |   This is linked into MySQL by the fact that MySQL doesn't have sub selects
447  |   (yet).  The queries are done in two stages.  Make some temporary tables and
448  |   insert into them.  Then use them in the next select.
449  | 
450  |   SQ_connection_t *sql_connection The connection to the database.
451  | 
452  |   char *id_table The id of the temporary table (This is a result of the hacky
453  |                   way we've tried to get MySQL to do sub-selects.)
454  |   
455  |   unsigned int recursive A recursive query.
456  | 
457  |   sk_conn_st *condat  Connection data for the client
458  | 
459  |   More:
460  |   +html+ <PRE>
461  |   Authors:
462  |         ottrey
463  |   +html+ </PRE><DL COMPACT>
464  |   ++++++++++++++++++++++++++++++++++++++*/
465  | static void write_objects(SQ_connection_t *sql_connection, 
466  | 			  char *id_table, 
467  | 			  unsigned int recursive, 
468  | 			  unsigned int filtered, 
469  | 			  unsigned int fast, 
470  | 			  sk_conn_st *condat,
471  | 			  acc_st    *acc_credit
472  | 			  ) {
473  |   /* XXX This should really return a linked list of the objects */
474  | 
475  |   SQ_result_set_t *result;
476  |   int retrieved_objects=0;
477  |   int retrieved_contacts=0;
478  | 
479  |   char sql_command[STR_XL];
480  |   char log_str[STR_L];
481  |     
482  |   strcpy(sql_command, "");
483  |   /* XXX These may and should change a lot. */
484  |   sprintf(sql_command, Q_OBJECTS, id_table, id_table);
485  |   result = SQ_execute_query(SQ_STORE, sql_connection, sql_command);
486  | 
487  |   retrieved_objects += write_results(result, filtered, fast, condat, 
488  | 				     acc_credit->public_objects);
489  | 
490  |   if( retrieved_objects < SQ_num_rows(result) ) {
491  |     SK_cd_puts(condat,
492  |      "% You have reached the limit of returned contact information objects.\n"
493  |      "% This connection will be terminated now.\n"
494  |      "% This is a mechanism to prevent abusive use of contact data in the RIPE Database.\n"
495  |      "% You will not be allowed to query for more CONTACT information for a while.\n"
496  |      "% Continued attempts to return excessive amounts of contact\n"
497  |      "% information will result in permanent denial of service.\n"
498  |      "% Please do not try to use CONTACT information information in the\n"
499  |      "% RIPE Database for non-operational purposes.\n"
500  |      "% Refer to http://www.ripe.net/db/dbcopyright.html for more information.\n"
501  |     );
502  |   }
503  |   SQ_free_result(result);
504  | 
505  |   /* Now for recursive queries */
506  |   if (recursive == 1) {
507  | 
508  |     /* create a table for recursive data */
509  |     sprintf(sql_command, "CREATE TABLE %s_R ( id int ) TYPE=HEAP", id_table);
510  |     SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
511  | 
512  |     /* find the contacts */
513  |     sprintf(sql_command, Q_REC, id_table, "admin_c", id_table, id_table);
514  |     SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
515  | 
516  |     sprintf(sql_command, Q_REC, id_table, "tech_c", id_table, id_table);
517  |     SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
518  |     
519  |     sprintf(sql_command, Q_REC, id_table, "zone_c", id_table, id_table);
520  |     SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
521  | 
522  |     /* XXX These may and should change a lot. */
523  |     sprintf(sql_command, Q_REC_OBJECTS, id_table, id_table, id_table);
524  |     result = SQ_execute_query(SQ_STORE, sql_connection, sql_command);
525  | 
526  |     retrieved_contacts += write_results(result, filtered, fast, condat, 
527  | 					acc_credit->private_objects);
528  |     if( retrieved_contacts < SQ_num_rows(result) ) {
529  |       SK_cd_puts(condat,
530  |        "% The contact data has been truncated due to inadequate privileges\n"
531  |        "% The amount of data you have requested is considered excessive\n"
532  |        "% You will be denied to get any more contact objects for some time\n"
533  |        "% Please do not check if the access has been restored more often\n"
534  |        "% than once every few hours or you will be denied access permanently\n"
535  |       );
536  |     }
537  |     SQ_free_result(result);
538  | 
539  |     /* Now drop the IDS recursive table */
540  |     sprintf(sql_command, "DROP TABLE %s_R", id_table);
541  |     SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
542  | 
543  |     acc_credit->private_objects -= retrieved_contacts;
544  |   }
545  | 
546  |   /* If nothing is retreived default to return the 0th object - I.e "not found" */
547  |   if (retrieved_objects == 0) {
548  |     result = SQ_execute_query(SQ_STORE, sql_connection, Q_NO_OBJECTS);
549  | 
550  |     if (result != NULL) {
551  |       write_results(result, filtered, fast, condat, 1);
552  |       SQ_free_result(result);
553  |     }
554  |     else {
555  |       fprintf(stderr, "QI - ERROR: Couldn't perform no objects query.\n");
556  |     }
557  |   }
558  |   else {
559  |     acc_credit->public_objects -= retrieved_objects;
560  |   }
561  |   
562  |   sprintf(log_str, "%d public / %d contact object(s) retrieved\n\n", 
563  | 	  retrieved_objects, retrieved_contacts );
564  |   log_inst_print(log_str);
565  | 
566  | } /* write_objects() */
567  | 
568  | /* insert_radix_serials() */
569  | /*++++++++++++++++++++++++++++++++++++++
570  |   Insert the radix serial numbers into a temporary table in the database.
571  | 
572  |   mask_t bitmap The bitmap of attribute to be converted.
573  |    
574  |   SQ_connection_t *sql_connection The connection to the database.
575  | 
576  |   char *id_table The id of the temporary table (This is a result of the hacky
577  |                   way we've tried to get MySQL to do sub-selects.)
578  |   
579  |   GList *datlist The list of data from the radix tree.
580  | 
581  |   XXX Hmmmmm this isn't really a good place to free things... infact it's quite nasty.  :-(
582  |   
583  |   More:
584  |   +html+ <PRE>
585  |   Authors:
586  |         ottrey
587  |   +html+ </PRE><DL COMPACT>
588  |   +html+ <DT>Online References:
589  |   +html+ <DD><UL>
590  |              <LI><A HREF="http://www.gtk.org/rdp/glib/glib-doubly-linked-lists.html">Glist</A>
591  |   +html+ </UL></DL>
592  | 
593  |   ++++++++++++++++++++++++++++++++++++++*/
594  | static void insert_radix_serials(SQ_connection_t *sql_connection, char *id_table, GList *datlist) {
595  |   GList    *qitem;
596  |   char sql_command[STR_XL];
597  |   int serial;
598  |   int i;
599  | 
600  |   for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
601  |     rx_datcpy_t *datcpy = qitem->data;
602  | 
603  |     serial = datcpy->leafcpy.data_key;
604  | 
605  |     sprintf(sql_command, "INSERT INTO %s values (%d)", id_table, serial);
606  |     SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
607  | 
608  |     wr_free(datcpy->leafcpy.data_ptr);
609  |   }
610  |   
611  |   g_list_foreach(datlist, rx_free_list_element, NULL);
612  |   g_list_free(datlist);
613  | 
614  | } /* insert_radix_serials() */
615  | 
616  | 
617  | /* write_radix_immediate() */
618  | /*++++++++++++++++++++++++++++++++++++++
619  |   Display the immediate data carried with the objects returned by the
620  |   radix tree.
621  | 
622  |   GList *datlist
623  |   sk_conn_st *condat  Connection data for the client
624  | More:
625  |   +html+ <PRE>
626  |   Authors:
627  |         marek
628  |   +html+ </PRE><DL COMPACT>
629  |   +html+ <DT>Online References:
630  |   +html+ <DD><UL>
631  |   +html+ </UL></DL>
632  |   
633  | 
634  |   Also free the list of answers.
635  | */
636  | static void write_radix_immediate(GList *datlist, sk_conn_st *condat) 
637  | {
638  |   GList    *qitem;
639  |   
640  |   for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
641  |     rx_datcpy_t *datcpy = qitem->data;
642  | 
643  |     SK_cd_puts(condat, datcpy->leafcpy.data_ptr );
644  |     SK_cd_puts(condat, "\n");
645  |     
646  |     wr_free(datcpy->leafcpy.data_ptr);
647  |   }
648  |   
649  |   g_list_foreach(datlist, rx_free_list_element, NULL);
650  |   g_list_free(datlist);
651  | } /* write_radix_immediate() */
652  | 
653  | 
654  | /* map_qc2rx() */
655  | /*++++++++++++++++++++++++++++++++++++++
656  |   The mapping between a query_command and a radix query.
657  | 
658  |   Query_instruction *qi The Query Instruction to be created from the mapping
659  |                         of the query command.
660  | 
661  |   const Query_command *qc The query command to be mapped.
662  | 
663  |   More:
664  |   +html+ <PRE>
665  |   Authors:
666  |         ottrey
667  |   +html+ </PRE><DL COMPACT>
668  |   +html+ <DT>Online References:
669  |   +html+ <DD><UL>
670  |   +html+ </UL></DL>
671  | 
672  |   ++++++++++++++++++++++++++++++++++++++*/
673  | static int map_qc2rx(Query_instruction *qi, const Query_command *qc) {
674  |   int result=0;
675  |   char log_str[STR_XL];
676  | 
677  |   qi->rx_keys = qc->keys;
678  | 
679  |   if (MA_bitcount(qc->object_type_bitmap) == 0) {
680  |     /* Ie. there was no object typed specified with the -T flag. */
681  |     result=1;
682  |   }
683  |   else {
684  |     switch(qi->family) {
685  |       case RX_FAM_IN:
686  |         if (MA_isset(qc->object_type_bitmap, C_IN)) {
687  |           result=1;
688  |         }
689  |       break;
690  | 
691  |       case RX_FAM_RT:
692  |         if (MA_isset(qc->object_type_bitmap, C_RT)) {
693  |           result=1;
694  |         }
695  |       break;
696  |       
697  |       default:
698  |         fprintf(stderr, "ERROR: Bad family type in radix query\n");
699  |     }
700  |   }
701  | 
702  |   if (result == 1) {
703  |     if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
704  |       qi->rx_srch_mode = RX_SRCH_EXLESS;
705  |       qi->rx_par_a = 0;
706  |     }
707  |     else if ( (qc->L == 1) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
708  |       qi->rx_srch_mode = RX_SRCH_LESS;
709  |       qi->rx_par_a = RX_ALL_DEPTHS;
710  |     }
711  |     else if ( (qc->L == 0) && (qc->M == 1) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
712  |       qi->rx_srch_mode = RX_SRCH_MORE;
713  |       qi->rx_par_a = RX_ALL_DEPTHS;
714  |     }
715  |     else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 1) && (qc->m == 0) && (qc->x == 0) ) {
716  |       qi->rx_srch_mode = RX_SRCH_LESS;
717  |       qi->rx_par_a = 1;
718  |     }
719  |     else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 1) && (qc->x == 0) ) {
720  |       qi->rx_srch_mode = RX_SRCH_MORE;
721  |       qi->rx_par_a = 1;
722  |     }
723  |     else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 1) ) {
724  |       qi->rx_srch_mode = RX_SRCH_EXACT;
725  |       qi->rx_par_a = 0;
726  |     }
727  |     else {
728  |       sprintf(log_str, "ERROR in qc2rx mapping: bad combination of flags\n");
729  |       log_inst_print(log_str);
730  |       result = 0;
731  |     }
732  |   }
733  | 
734  |   return result;
735  | 
736  | } /* map_qc2rx() */
737  | 
738  | /* run_referral() */
739  | /*
740  |    invoked when no such domain found. Goes through the domain table
741  |    and searches for shorter domains, then if it finds one with referral 
742  |    it performs it, otherwise it just returns nothing.
743  | 
744  |    to perform referral, it actually composes the referral query 
745  |    for a given host/port/type and calls the whois query function.
746  | 
747  |    Well, it returns nothing anyway (void). It just prints to the socket.
748  | 
749  | */
750  | void run_referral(SQ_connection_t *sql_connection, Query_instructions *qis,   Query_environ *qe, int qi_index) {
751  |   char *dot = qis->qc->keys;
752  |   char querystr[STR_L];
753  |   char *query;
754  |   char log_str[STR_L];
755  |   SQ_row_t *row;
756  |   SQ_result_set_t *result;
757  |   char sql_command[STR_XL];
758  |   int i;
759  |   int stop_loop=0;
760  |   char *ref_host;
761  |   char *ref_type;
762  |   char *ref_port;
763  |   int  ref_port_int;
764  | 
765  |   strcpy(querystr,"");
766  | 
767  |   while( !stop_loop && (dot=index(dot,'.')) != NULL ) {
768  |     dot++;
769  | 
770  |     sprintf(log_str, "run_referral: checking %s\n", dot);
771  |     log_inst_print(log_str);
772  | 
773  | /* Changed for RIPE4 - ottrey 27/12/1999
774  |     sprintf(sql_command, "SELECT * FROM domain WHERE domain = '%s'", dot);
775  | */
776  |     sprintf(sql_command, "SELECT domain.object_id, domain, type, port, host FROM domain, refer WHERE domain.object_id = refer.object_id AND domain = '%s'", dot);
777  |     result = SQ_execute_query(SQ_STORE, sql_connection, sql_command);
778  | 
779  |     switch( SQ_num_rows(result) ) {
780  |       case 0: /* no such domain -> no action, will try next chunk */
781  |       break;
782  | 
783  |       case 1: /* check for referral host and perform query if present
784  |                in any case end the loop */
785  |       stop_loop=1;
786  |       assert( (row = SQ_row_next(result)) != NULL);
787  |       
788  |       ref_host = SQ_get_column_string(result, row, 4);
789  |       sprintf(log_str, "referral host is >%s<\n",ref_host);
790  |       log_inst_print(log_str);
791  |       if( ref_host != NULL && strlen(ref_host) > 0 ) {
792  |         ref_type = SQ_get_column_string(result, row, 2);
793  |         ref_port = SQ_get_column_string(result, row, 3);
794  |         
795  |         /* get the integer value, it should be correct */
796  |         if( sscanf( ref_port, "%d",&ref_port_int) < 1 ) {
797  |           die;
798  |         }
799  |          
800  |         /* compose the query: */
801  | 
802  |         /* put -r if the reftype is RIPE and -r or -i were used */
803  |         if( strcmp(ref_type,"RIPE") == 0 
804  |             && (   Query[qis->instruction[qi_index]->queryindex]
805  |                    .querytype == Q_INVERSE       
806  |                    || qis->recursive > 0  )   ) {
807  |           strcat(querystr," -r ");
808  |         }
809  | 
810  |         /* prepend with -Vversion,IP for type CLIENTADDRESS */
811  |         if( strcmp(ref_type,"CLIENTADDRESS") == 0 ) {
812  |           char optv[STR_M];
813  | 
814  |           snprintf(optv,STR_M," -V%s,%s ","RIP0.88", qe->condat.ip);
815  |           strcat(querystr,optv);
816  |         }
817  | 
818  |         /* now set the search term - set to the stripped down version 
819  |            for inverse query, full-length otherwise */
820  |         if( Query[qis->instruction[qi_index]->queryindex].querytype == Q_INVERSE ) {
821  |           strcat(querystr,dot);
822  |         }
823  |         else {
824  |           strcat(querystr,qis->qc->keys);
825  |         }
826  |         
827  |         /* WH_sock(sock, host, port, query, maxlines, timeout)) */
828  |         switch( WH_sock(qe->condat.sock, ref_host, ref_port_int, querystr,  25, 5) ) {
829  |           case WH_TIMEOUT:
830  |           SK_cd_puts(&(qe->condat),"referral timeout\n");
831  |           break;
832  | 
833  |           case WH_MAXLINES:
834  |           SK_cd_puts(&(qe->condat),"referral maxlines exceeded\n");
835  |           break;
836  | 
837  |           default:
838  |           ;
839  |         } /*switch WH_sock */
840  |       }
841  |       break;
842  | 
843  |       default: /* more than one domain in this file: something broken */
844  |       die;
845  |     }
846  |     SQ_free_result(result);
847  |   }
848  | } /*run_referral*/
849  | 
850  | /* QI_execute() */
851  | /*++++++++++++++++++++++++++++++++++++++
852  |   Execute the query instructions.  This is called by a g_list_foreach
853  |   function, so each of the sources in the "database source" list can be passed
854  |   into this function.
855  | 
856  |   This function has bloated itself.  Can we split it up Marek?  (ottrey 13/12/99)
857  | 
858  |   void *database_voidptr Pointer to the database.
859  |   
860  |   void *qis_voidptr Pointer to the query_instructions.
861  |    
862  |   More:
863  |   +html+ <PRE>
864  |   Authors:
865  |         ottrey
866  |   +html+ </PRE><DL COMPACT>
867  |   +html+ <DT>Online References:
868  |   +html+ <DD><UL>
869  |              <LI><A
870  |              HREF="http://www.gtk.org/rdp/glib/glib-singly-linked-lists.html#G-SLIST-FOREACH">g_list_foreach</A>
871  |   +html+ </UL></DL>
872  | 
873  |   ++++++++++++++++++++++++++++++++++++++*/
874  | void QI_execute(void *database_voidptr, 
875  | 		Query_instructions *qis, 
876  | 		Query_environ *qe,	
877  | 		acc_st *acc_credit
878  | 		) {
879  |   char *database = (char *)database_voidptr;
880  |   Query_instruction **ins=NULL;
881  |   char id_table[STR_S];
882  |   char sql_command[STR_XL];
883  |   GList *datlist=NULL;
884  |   int i;
885  |   SQ_row_t *row;
886  |   char log_str[STR_L];
887  |   SQ_result_set_t *result;
888  |   SQ_connection_t *sql_connection=NULL;
889  | 
890  |   sql_connection = SQ_get_connection(CO_get_host(), CO_get_database_port(), database, CO_get_user(), CO_get_password() );
891  | 
892  |   if (sql_connection == NULL) {
893  |     SK_cd_puts(&(qe->condat), "% WARNING: Failed to make connection to ");
894  |     SK_cd_puts(&(qe->condat), database);
895  |     SK_cd_puts(&(qe->condat), " database mirror.\n\n");
896  | 
897  |     /* XXX void prevents us from sending any error code back. It is OK ? */
898  |     return;
899  |   }
900  |   
901  |   /* XXX This is a really bad thing to do.  
902  |      It should'nt _have_ to be called here.
903  |      But unfortunately it does.   -- Sigh. */
904  |   sprintf(id_table, "ID_%d", mysql_thread_id(sql_connection) );
905  | 
906  |   /* create a table for id's of all objects found */
907  |   sprintf(sql_command, "CREATE TABLE %s ( id int ) TYPE=HEAP", id_table);
908  |   SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
909  | 
910  |   /* create a table for individual subqueries (one keytype) */
911  |   sprintf(sql_command, "CREATE TABLE %s_S ( id int ) TYPE=HEAP", id_table);
912  |   SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
913  |     
914  |   /* Iterate through query instructions */
915  |   ins = qis->instruction;
916  |   for (i=0; ins[i] != NULL; i++) {
917  |     Query_instruction *qi = ins[i];
918  | 
919  |     switch ( qi->search_type ) {
920  |     case R_SQL:
921  |       if ( qi->query_str != NULL ) {
922  | 
923  | 	/* handle special cases first */
924  | 	if( Query[qi->queryindex].class == C_DN ) {
925  | 
926  | 	  char *countstr;
927  | 	  int   count;
928  | 
929  | 	  /* XXX if any more cases than just domain appear, we will be
930  | 	     cleaning the _S table from the previous query here */
931  | 	  
932  | 	  /* now query into the _S table */
933  | 	  sprintf(sql_command, "INSERT INTO %s_S %s", id_table, qi->query_str);
934  | 	  SQ_execute_query(SQ_NOSTORE, sql_connection, sql_command);
935  | 	  
936  | 	  /* if any results - copy to the id's table. 
937  | 	     Otherwise, run referral */
938  | 	  
939  | 	  sprintf(sql_command, "SELECT COUNT(*) FROM %s_S", id_table);
940  | 	  result = SQ_execute_query(SQ_STORE,sql_connection, sql_command);
941  | 	  row = SQ_row_next(result);
942  | 	  countstr = SQ_get_column_string(result, row, 0);
943  | 	  sscanf(countstr, "%d", &count);
944  | 	  SQ_free_result(result);
945  | 
946  | 	  sprintf(log_str, "DN lookup for %s found %d entries\n",
947  | 		  qis->qc->keys, count);
948  | 	  log_inst_print(log_str);
949  | 	  
950  | 	 
951  | 	  if( count ) {
952  | 	    sprintf(sql_command, "INSERT INTO %s SELECT id FROM %s_S", 
953  | 		    id_table, id_table);
954  | 	    SQ_execute_query(SQ_NOSTORE,sql_connection, sql_command);
955  | 	  }
956  | 
957  | 	  if( count == 0 
958  | 	      || Query[qi->queryindex].querytype == Q_INVERSE ) {
959  | 	    /* now: if the domain was not found, we run referral.
960  | 	       unless prohibited by a flag 
961  | 	      
962  | 	       But for inverse queries we return the things that were
963  | 	       or were not found AND also do the referral unless prohibited.
964  | 	    */
965  | 	    if (qis->qc->R == 0) {
966  | 	      run_referral(sql_connection, qis, qe, i);
967  | 	    }
968  | 	  }
969  | 	  
970  | 	}
971  | 	else {
972  | 	  sprintf(sql_command, "INSERT INTO %s %s", id_table, qi->query_str);
973  | 	  SQ_execute_query(SQ_NOSTORE,sql_connection, sql_command);
974  | 	}
975  |       }
976  |       break;
977  | 
978  | #define RIPE_REG 17
979  |     case R_RADIX:
980  |       if ( RX_asc_search(qi->rx_srch_mode, qi->rx_par_a, 0, qi->rx_keys, RIPE_REG, qi->space, qi->family, &datlist, RX_ANS_ALL) == RX_OK ) {
981  | 	sprintf(log_str, "After RX query (mode %d par %d spc %d fam %d reg %d key %s) datlist has %d objects\n",
982  | 		qi->rx_srch_mode, qi->rx_par_a, qi->space, qi->family, 
983  | 		RIPE_REG, 	qi->rx_keys,    
984  | 		g_list_length(datlist) );
985  | 	log_inst_print(log_str); 
986  | 	    
987  |       }
988  |       else {
989  | 	/* Skip query */
990  | 	sprintf(log_str, " /* Skip in query */\n");
991  | 	log_inst_print(log_str);
992  |       }
993  |       break;
994  | 
995  |     default: die;
996  |     } /* switch */
997  |   }
998  | 
999  |   /* post-processing */
1000 | 
1001 |   if( qis->filtered == 0 ) {
1002 |     /* add radix results to the end */
1003 |     insert_radix_serials(sql_connection, id_table, datlist);
1004 | 
1005 |     /* display objects */
1006 |     write_objects(sql_connection, id_table, qis->recursive, 0 /*nofilter*/, 
1007 | 		  qis->fast, &(qe->condat), acc_credit);
1008 |   }
1009 |   else {
1010 |     /* XXX TODO display filtered objects, expanding sets */
1011 |     /* right now only the ugly filter thing instead */
1012 | 
1013 |     /* write_set_objects */
1014 | 
1015 |     /* write the remaining (non-set,non-radix) objects through the filter.
1016 |        imply no recursion */
1017 |     write_objects(sql_connection, id_table, 0, qis->filtered, qis->fast, &(qe->condat), acc_credit);
1018 | 
1019 |     /* display the immediate data from the radix tree */
1020 |     /* XXX pass+decrease credit here */
1021 |     write_radix_immediate(datlist, &(qe->condat));
1022 |   }
1023 |   /* Now drop the _S table */
1024 |   sprintf(sql_command, "DROP TABLE %s_S", id_table);
1025 |   SQ_execute_query(SQ_NOSTORE,sql_connection, sql_command);
1026 | 
1027 |   /* Now drop the IDS table */
1028 |   sprintf(sql_command, "DROP TABLE %s", id_table);
1029 |   SQ_execute_query(SQ_NOSTORE,sql_connection, sql_command);
1030 |   SQ_close_connection(sql_connection);
1031 | 
1032 |   
1033 | } /* QI_execute() */
1034 | /* instruction_free() */
1035 | /*++++++++++++++++++++++++++++++++++++++
1036 |   Free the instruction.
1037 | 
1038 |   Query_instruction *qi query_instruction to be freed.
1039 |    
1040 |   More:
1041 |   +html+ <PRE>
1042 |   Authors:
1043 |         ottrey
1044 |   +html+ </PRE><DL COMPACT>
1045 |   +html+ <DT>Online References:
1046 |   +html+ <DD><UL>
1047 |   +html+ </UL></DL>
1048 | 
1049 |   ++++++++++++++++++++++++++++++++++++++*/
1050 | static void instruction_free(Query_instruction *qi) {
1051 |   if (qi != NULL) {
1052 |     if (qi->query_str != NULL) {
1053 |       free(qi->query_str);
1054 |     }
1055 |     free(qi);
1056 |   }
1057 | } /* instruction_free() */
1058 | 
1059 | /* QI_free() */
1060 | /*++++++++++++++++++++++++++++++++++++++
1061 |   Free the query_instructions.
1062 | 
1063 |   Query_instructions *qis Query_instructions to be freed.
1064 |    
1065 |   XXX This isn't working too well at the moment.
1066 | 
1067 |   More:
1068 |   +html+ <PRE>
1069 |   Authors:
1070 |         ottrey
1071 |   +html+ </PRE><DL COMPACT>
1072 |   +html+ <DT>Online References:
1073 |   +html+ <DD><UL>
1074 |   +html+ </UL></DL>
1075 | 
1076 |   ++++++++++++++++++++++++++++++++++++++*/
1077 | void QI_free(Query_instructions *qis) {
1078 |   /* XXX huh!?H?
1079 |   int i;
1080 | 
1081 |   for (i=0; qis[i] != NULL; i++) {
1082 |     instruction_free(*qis[i]);
1083 |   }
1084 |   */
1085 |   if (qis != NULL) {
1086 |     free(qis);
1087 |   }
1088 | 
1089 | } /* QI_free() */
1090 | 
1091 | /*++++++++++++++++++++++++++++++++++++++
1092 |   Determine if this query should be conducted or not.
1093 | 
1094 |   If it was an inverse query - it the attribute appears in the query command's bitmap.
1095 |   If it was a lookup query - if the attribute appears in the object type bitmap or
1096 |                              disregard if there is no object_type bitmap (Ie object filter).
1097 | 
1098 |   mask_t bitmap The bitmap of attribute to be converted.
1099 |    
1100 |   const Query_command *qc The query_command that the instructions are created
1101 |                           from.
1102 |   
1103 |   const Query_t q The query being investigated.
1104 | 
1105 |   ++++++++++++++++++++++++++++++++++++++*/
1106 | static int valid_query(const Query_command *qc, const Query_t q) {
1107 |   int result=0;
1108 | 
1109 |   if (MA_isset(qc->keytypes_bitmap, q.keytype) == 1) {
1110 |     if (q.query != NULL) {
1111 |       switch (q.querytype) {
1112 |         case Q_INVERSE:
1113 |           if (MA_isset(qc->inv_attrs_bitmap, q.attribute) ) {
1114 |             result = 1;
1115 |           }
1116 |         break;
1117 | 
1118 |         case Q_LOOKUP:
1119 |           if (MA_bitcount(qc->object_type_bitmap) == 0) {
1120 |             result=1;
1121 |           }
1122 |           else if (MA_isset(qc->object_type_bitmap, q.class)) {
1123 |             result=1;
1124 |           }
1125 |         break;
1126 | 
1127 |         default:
1128 |           fprintf(stderr, "qi:valid_query() -> Bad querytype\n");
1129 |       }
1130 |     }
1131 |   }
1132 | 
1133 |   return result;
1134 | } /* valid_query() */
1135 | 
1136 | /* QI_new() */
1137 | /*++++++++++++++++++++++++++++++++++++++
1138 |   Create a new set of query_instructions.
1139 | 
1140 |   const Query_command *qc The query_command that the instructions are created
1141 |                           from.
1142 | 
1143 |   const Query_environ *qe The environmental variables that they query is being
1144 |                           performed under.
1145 |   More:
1146 |   +html+ <PRE>
1147 |   Authors:
1148 |         ottrey
1149 |   +html+ </PRE><DL COMPACT>
1150 |   +html+ <DT>Online References:
1151 |   +html+ <DD><UL>
1152 |   +html+ </UL></DL>
1153 | 
1154 |   ++++++++++++++++++++++++++++++++++++++*/
1155 | Query_instructions *QI_new(const Query_command *qc, const Query_environ *qe) {
1156 |   Query_instructions *qis=NULL;
1157 |   Query_instruction *qi=NULL;
1158 |   int i_no=0,j;
1159 |   int i;
1160 |   char *query_str;
1161 | 
1162 |   char log_str[STR_L];
1163 | 
1164 |   qis = (Query_instructions *)calloc(1, sizeof(Query_instructions));
1165 | 
1166 |   qis->filtered = qc->filtered;
1167 |   qis->fast = qc->fast;
1168 |   qis->recursive = qc->recursive;
1169 |   qis->qc = (qc);
1170 | 
1171 | 
1172 |   for (i=0; Query[i].query != NULL; i++) {
1173 | 
1174 |     /* If a valid query. */
1175 |     if ( valid_query(qc, Query[i]) == 1) {
1176 |       qi = (Query_instruction *)calloc(1, sizeof(Query_instruction));
1177 | 
1178 |       qi->queryindex = i;
1179 | 
1180 |       /* SQL Query */
1181 |       if ( Query[i].refer == R_SQL) {
1182 |         qi->search_type = R_SQL;
1183 |         query_str = create_query(Query[i], qc);
1184 | 
1185 |         if (query_str!= NULL) {
1186 |           qi->query_str = query_str;
1187 |           qis->instruction[i_no++] = qi;
1188 |         }
1189 |       }
1190 |       /* Radix Query */
1191 |       else if (Query[i].refer == R_RADIX) {
1192 |         qi->search_type = R_RADIX;
1193 |         qi->space = Query[i].space;
1194 |         qi->family = Query[i].family;
1195 | 
1196 |         if (map_qc2rx(qi, qc) == 1) {
1197 |         int j;
1198 |         int found=0;
1199 | 
1200 |           /* check that there is no such query yet */
1201 |           for (j=0; j<i_no; j++) {
1202 |             Query_instruction *qij = qis->instruction[j];
1203 |             
1204 |             if( qij->search_type == R_RADIX
1205 |                 && qij->space       == qi->space
1206 |                 && qij->family      == qi->family) {
1207 |               found=1;
1208 |               break;
1209 |             }
1210 |           }
1211 | 
1212 |           if ( found ) {
1213 |             /* Discard the Query Instruction */
1214 |             free(qi);
1215 |           } 
1216 |           else {
1217 |             /* Add the query_instruction to the array */
1218 |             qis->instruction[i_no++] = qi;
1219 |           }
1220 |         }
1221 |       }
1222 |       else {
1223 |         sprintf(log_str, "ERROR: bad search_type\n");
1224 |         log_inst_print(log_str);
1225 |       }
1226 |     }
1227 |   }
1228 |   qis->instruction[i_no++] = NULL;
1229 | 
1230 |   return qis;
1231 | 
1232 | } /* QI_new() */