1    | /***************************************
2    |   $Revision: 1.75 $
3    | 
4    |   Query instructions (qi).  This is where the queries are executed.
5    | 
6    |   Status: NOT REVUED, TESTED
7    | 
8    |   ******************/ /******************
9    |   Filename            : query_instructions.c
10   |   Authors             : ottrey@ripe.net - framework and draft implementation
11   |                         marek@ripe.net - cleaned and extended, added referral,
12   | 			accounting support and watchdog cancellation.
13   |   OSs Tested          : Solaris
14   |   ******************/ /******************
15   |   Copyright (c) 1999                              RIPE NCC
16   |  
17   |   All Rights Reserved
18   |   
19   |   Permission to use, copy, modify, and distribute this software and its
20   |   documentation for any purpose and without fee is hereby granted,
21   |   provided that the above copyright notice appear in all copies and that
22   |   both that copyright notice and this permission notice appear in
23   |   supporting documentation, and that the name of the author not be
24   |   used in advertising or publicity pertaining to distribution of the
25   |   software without specific, written prior permission.
26   |   
27   |   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
28   |   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
29   |   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
30   |   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
31   |   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
32   |   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33   |   ***************************************/
34   | #include <stdio.h>
35   | #include <string.h>
36   | #include <glib.h>
37   | 
38   | #include "which_keytypes.h"
39   | #include "query_instructions.h"
40   | #include "mysql_driver.h"
41   | #include "rp.h"
42   | #include "stubs.h"
43   | #include "constants.h"
44   | #include "memwrap.h"
45   | #include "wh_queries.h"
46   | 
47   | #include "defs.h"
48   | 
49   | /*+ String sizes +*/
50   | #define STR_S   63
51   | #define STR_M   255
52   | #define STR_L   1023
53   | #define STR_XL  4095
54   | #define STR_XXL 16383
55   | 
56   | 
57   | /*++++++++++++++++++++++++++++++++++++++
58   |   Function invoked on query cancellation by the watchdog,
59   |   used from the sql_execute_watched() function.
60   |   
61   |   It aborts the running query (the abort function in sq kills and
62   |   reestablished the connection).
63   | 
64   |   void *qi_kill_body         result of sq_execute_query, int cast to (void*)
65   | 
66   |   void *arg                  pointer to sql connection
67   | 
68   |   Author:
69   |     marek.
70   |   
71   |   ++++++++++++++++++++++++++++++++++++++*/
72   | static
73   | void *qi_kill_body(void *arg)
74   | {
75   |   SQ_connection_t *sql_connection = arg;
76   |   ER_dbg_va(FAC_QI, ASP_QI_WATCH,
77   | 	      "rtc: killing SQL connection %d", (sql_connection)->thread_id);
78   |   /* abort the running query */
79   |   SQ_abort_query(sql_connection);
80   | 
81   |   return NULL;
82   | }
83   | 
84   | 
85   | 
86   | /*++++++++++++++++++++++++++++++++++++++
87   |   wrapper around sq_execute_query: starts a query 
88   |   in a separate thread and starts the socket watchdog to cancel the query 
89   |   if the socket is closed. If the socket has problems already (its 
90   |   reason-to-close flag is set) no query is attempted.
91   |   
92   |   The execution of the query or watchdog is not guaranteed at all!
93   | 
94   |   int sql_execute_watched        Returns the return code of SQ_execute_query,
95   |                                  Returns 0 for cancelled queries.
96   | 
97   |   sk_conn_st *condat             connection to watch
98   | 
99   |   SQ_connection_t **sql_connection  sql connection
100  | 
101  |   const char *query                 sql query to execute
102  | 
103  |   SQ_result_set_t **result_ptr      storage for the query result structure
104  |                                     (passed to SQ_execute_query). Must either
105  | 				    be NULL, or the pointer it points to must
106  | 				    be NULL - in that case the result struct.
107  | 				    will be allocated in SQ.
108  | 
109  |   Author:
110  |     marek.
111  |   ++++++++++++++++++++++++++++++++++++++*/
112  | int sql_execute_watched(sk_conn_st *condat, SQ_connection_t **sql_connection, 
113  | 			const char *query, SQ_result_set_t **result_ptr)
114  | {
115  |   int retval = 0; /* return value of sq_execute_query */
116  |   SQ_connection_t *tempcon;
117  | 
118  |   /* assert that, if defined, result_ptr is initialised to NULL 
119  |      prior to calling this function */
120  |   if( result_ptr != NULL ) {
121  |     dieif( *result_ptr != NULL );
122  |   }
123  | 
124  |   /* don't even try to perform the query/fire up watchdog
125  |      if rtc is already set. Do this only if not set yet. */
126  |   if( condat->rtc == 0 ) {
127  |     
128  |     /* make clean */
129  |     SK_watch_setclear(condat);
130  |     
131  |     /* set watchdog to execute the abort function */
132  |     SK_watch_setexec(condat, qi_kill_body, *sql_connection);
133  |     
134  |     /* start the watchdog */
135  |     SK_watchstart(condat);
136  |     
137  |     /* start query. An error may be returned if the query is aborted */
138  |     retval = SQ_execute_query(*sql_connection, query, result_ptr);
139  |     
140  |     /* but short queries will complete before the watchdog kills the
141  |        connection */
142  |     
143  |     SK_watchstop(condat);
144  |     
145  | 
146  |     /* if the watchdog triggered, then it is guaranteed that
147  |        the kill_body function was invoked and therefore the sql-connection
148  |        is now unusable... 
149  |        Close and reopen it for cleanup, use temporary connection
150  |        to keep the login details */
151  |     if( condat->rtc != 0 ) {
152  |       /* can't rely on the error code from mysql!
153  |        */ 
154  |     
155  |       /* one thing: this code must be entered ONLY if the kill_body
156  | 	 thing was invoked by the watchdog. 
157  |       */
158  |     
159  |       /* if result is defined, free it here before destroying the 
160  | 	 associated connection */
161  |       if( retval == 0 && result_ptr && *result_ptr ) {
162  | 	SQ_free_result( *result_ptr );
163  | 	*result_ptr = NULL;
164  |       }
165  |     
166  |       tempcon = SQ_duplicate_connection(*sql_connection);
167  |     
168  |       ER_dbg_va(FAC_QI, ASP_QI_WATCH,
169  | 		"rtc: closing SQL thread %d", (*sql_connection)->thread_id);
170  |       SQ_close_connection(*sql_connection);
171  |     
172  |       *sql_connection = tempcon;
173  |       ER_dbg_va(FAC_QI, ASP_QI_WATCH,
174  | 		"rtc: reopened as thread %d", (*sql_connection)->thread_id);
175  |     
176  |       /* make it look as if there was no error and 
177  | 	 the result is empty */
178  |       retval = 0;
179  |     } /* if watchdog set rtc */
180  |   
181  |   } /* if rtc not set before */
182  | 
183  |   return retval; 
184  | }
185  | 
186  | /* create_name_query() */
187  | /*++++++++++++++++++++++++++++++++++++++
188  |   Create an sql query for the names table. 
189  | 
190  |   char *query_str
191  | 
192  |   const char *sql_query
193  | 
194  |   const char *keys
195  |    
196  |   More:
197  |   +html+ <PRE>
198  |   Authors:
199  |     ottrey
200  |   +html+ </PRE>
201  |   ++++++++++++++++++++++++++++++++++++++*/
202  | static void create_name_query(char *query_str, const char *sql_query, const char *keys) {
203  |   int i;
204  |   /* Allocate stuff - use dynamic strings (initialised to some length) */
205  |   GString *from_clause = g_string_sized_new(STR_L);
206  |   GString *where_clause = g_string_sized_new(STR_L);
207  |   gchar **words = g_strsplit(keys, " ", 0);
208  | 
209  |   /* double quotes " are used in queries to allow querying for 
210  |      names like O'Hara */
211  | 
212  |   g_string_sprintfa(from_clause, "names N%.2d", 0);
213  |   g_string_sprintfa(where_clause, "N%.2d.name=\"%s\"", 0, words[0]);
214  | 
215  |   for (i=1; words[i] != NULL; i++) {
216  |     g_string_sprintfa(from_clause, ", names N%.2d", i);
217  |     g_string_sprintfa(where_clause, " AND N%.2d.name=\"%s\" AND N00.object_id = N%.2d.object_id", i, words[i], i);
218  |   }
219  | 
220  |   sprintf(query_str, sql_query, from_clause->str, where_clause->str);
221  | 
222  |   /* Free up stuff */
223  |   g_strfreev(words);
224  |   g_string_free(where_clause,/* CONSTCOND */ TRUE);
225  |   g_string_free(from_clause, /* CONSTCOND */ TRUE);
226  | 
227  | } /* create_name_query() */
228  | 
229  | 
230  | /*++++++++++++++++++++++++++++++++++++++
231  |   construct a range query for the as_block table
232  |   (a query for an AS block object) given a string like: 
233  |   AS1
234  |   AS1 - AS10
235  |   AS1-AS10
236  | 
237  |   int create_asblock_query    Returns 0 on success, -1 on failure 
238  |                              (search term not an AS# nor range)
239  | 
240  |   char *query_str             buffer for the final query (must be big enough)
241  | 
242  |   const char *sql_query       rest of the sql query (with %d %d formats for
243  |                               AS numbers)
244  | 
245  |   const char *keys            user-supplied search term.
246  | 
247  |   Author:
248  |     marek
249  |   ++++++++++++++++++++++++++++++++++++++*/
250  | static int create_asblock_query(char *query_str, 
251  | 				const char *sql_query, 
252  | 				const char *keys) {
253  |   char *keycopy = wr_string(keys);
254  |   char *token, *cursor = keycopy;
255  |   int  asnums[2] = {0,0};
256  |   int index = 0; /* index into the asnums array */
257  | 
258  | 
259  |   while( (token = strsep( &cursor, "-" )) != NULL && index < 2) {  
260  |     /* discard the letters (or leading whitespace), take the number */
261  |     if( sscanf(token, "%*[ AS]%d", &asnums[index++]) < 1 ) {
262  |       return -1; /* error */
263  |     }
264  |   }
265  |   /* if only beginning was supplied, copy it as end */
266  |   if( index == 1 ) {
267  |     asnums[1] = asnums[0];
268  |   }
269  |   
270  |   /* now construct the query */
271  |   sprintf(query_str, sql_query, asnums[0], asnums[1]);
272  | 
273  |   wr_free(keycopy);
274  |   return 0;
275  | }
276  | 
277  | 
278  | /*++++++++++++++++++++++++++++++++++++++
279  |   add_filter(): construct a query to limit the objects returned from the last
280  |   table to predefined types. 
281  | 
282  |   char *query_str           buffer for the final query, containing the initial
283  |                             part of the query (must be big enough)
284  | 
285  |   const Query_command *qc   query command structure with the bitmap of 
286  |                             object types to be included.
287  | 			    
288  |   Author:
289  |     ottrey.
290  |   ++++++++++++++++++++++++++++++++++++++*/
291  | static void add_filter(char *query_str, const Query_command *qc) {
292  |   unsigned i;
293  |   int qlen;
294  |   char filter_atom[STR_M];
295  | 
296  | #if 0
297  |   /* glib string manipulation - untested yet */
298  |   
299  |     if (MA_bitcount(qc->object_type_bitmap) != MASK_MAX) { 
300  |       g_string_sprintfa(query_str, " AND (");
301  |       for (i=0; i < C_END; i++) {
302  | 	if (MA_isset(qc->object_type_bitmap, i)) {
303  | 	  g_string_sprintfa(query_str, "i.object_type = %d OR ", DF_get_class_dbase_code(i));
304  | 	}
305  |       }
306  |       g_string_truncate(query_str, query_str->len-3);
307  |       g_string_append_c(query_str, ')');
308  |     }
309  | 
310  | #else /* classic string operations */
311  | 
312  |   /* add filters only if any bits are 0 (the number of 1's is < MAX_MAX */
313  |   if (MA_bitcount(qc->object_type_bitmap) != MASK_MAX) { 
314  |     strcat(query_str, " AND (");
315  |     for (i=0; i < C_END; i++) {
316  |       if (MA_isset(qc->object_type_bitmap, i)) {
317  |         strcpy(filter_atom, "");
318  |         sprintf(filter_atom, "i.object_type = %d OR ", i);
319  | 	                /* XXX class codes should be used instead:
320  | 			   DF_get_class_dbase_code(i)) 
321  | 			   but currently the tables contain values of enums
322  | 			   (C_IN, etc) and not codes
323  | 			*/
324  |         strcat(query_str, filter_atom);
325  |       }
326  |     }
327  |     qlen = strlen(query_str);
328  |     query_str[qlen-3] = ')';
329  |     query_str[qlen-2] = '\0';
330  |     query_str[qlen-1] = '\0';
331  |   }
332  | 
333  | #endif
334  |   
335  | } /* add_filter() */
336  | 
337  | /* create_query() */
338  | /*++++++++++++++++++++++++++++++++++++++
339  |   Create an sql query from the query_command and the matching keytype and the
340  |   selected inverse attributes.
341  |   Note this clears the first inv_attribute it sees, so is called sequentially
342  |   until there are no inv_attributes left.
343  | 
344  |   WK_Type keytype The matching keytype.
345  | 
346  |   const Query_command *qc The query command.
347  | 
348  |   mask_t *inv_attrs_bitmap The selected inverse attributes.
349  |    
350  |   More:
351  |   +html+ <PRE>
352  |   Authors:
353  |         ottrey
354  |   +html+ </PRE>
355  | 
356  |   ++++++++++++++++++++++++++++++++++++++*/
357  | static char *create_query(const Query_t q, const Query_command *qc) {
358  |   char *result=NULL;
359  |   char result_buff[STR_XL];
360  |   Q_Type_t querytype;
361  |   int addquery = 0; /* controls if the query should be added to the list */
362  | 
363  |   if (MA_bitcount(qc->inv_attrs_bitmap) > 0) {
364  |     querytype = Q_INVERSE;
365  |   }
366  |   else {
367  |     querytype = Q_LOOKUP;
368  |   }
369  | 
370  |   if ( (q.query != NULL) 
371  |     && (q.querytype == querytype) ) {
372  |     
373  |     /* addquery = 1; */
374  |     /* if it got here, it should be added, unless.(see asblock)*/
375  |     
376  |     if (q.keytype == WK_NAME) { 
377  |       /* Name queries require special treatment. */
378  |       create_name_query(result_buff, q.query, qc->keys);
379  |       addquery = 1;
380  |     }
381  |     else if( q.keytype == WK_IPADDRESS ) {  /* ifaddr sql lookups */
382  |       ip_range_t myrang;
383  |       unsigned   begin, end;
384  |       ip_keytype_t key_type;
385  |       
386  |       if (NOERR(IP_smart_range(qc->keys, &myrang, IP_EXPN, &key_type))) {
387  | 	if(IP_rang_b2_space(&myrang) == IP_V4 ) {
388  | 	  IP_rang_b2v4(&myrang, &begin, &end);
389  | 	  sprintf(result_buff, q.query, begin, end);
390  | 	  addquery = 1;
391  | 	}
392  | 	else {
393  | 	  die;
394  | 	}
395  |       }
396  |     }
397  |     else if( q.keytype == WK_ASRANGE ) {   /* as_block range composition */
398  |       if( create_asblock_query(result_buff, q.query, qc->keys) != 0 ) {
399  | 	addquery = 0; /* ... unless it's not correct */
400  |       }
401  |       else {
402  | 	addquery = 1;
403  |       }
404  |     }
405  |     else {
406  |       sprintf(result_buff, q.query, qc->keys);
407  |       addquery = 1;
408  |     }
409  | 
410  |     if (q.class == C_ANY && addquery == 1 ) {
411  |       /* It is class type ANY so add the object filtering */
412  |       add_filter(result_buff, qc);
413  |     }
414  |   }
415  |   
416  |   if( addquery == 1 ) {
417  |     dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);  
418  |     strcpy(result, result_buff);
419  |     return result;
420  |   } 
421  |   else {
422  |     return NULL;
423  |   }
424  | } /* create_query() */
425  | 
426  | /* QI_fast_output() */
427  | /*++++++++++++++++++++++++++++++++++++++
428  |   This is for the '-F' flag.
429  |   It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.
430  |   Fast isn't fast anymore - it's just there for compatibility reasons.
431  | 
432  |   const char *str        The object to be "fast output'ed".
433  |    
434  |   More:
435  |   +html+ <PRE>
436  |   Authors:
437  |         ottrey,
438  | 	marek - glib strings + small changes
439  |   +html+ </PRE>
440  |   ++++++++++++++++++++++++++++++++++++++*/
441  | char *QI_fast_output(const char *str) 
442  | {
443  |   int i,j;
444  |   char *result;
445  |   GString *result_buff = g_string_sized_new(STR_XL);
446  |   gchar **lines = g_strsplit(str, "\n", 0);
447  |   unsigned char *value, *colon;
448  |   char *attr;
449  | 
450  |   g_string_assign(result_buff, "");
451  |   
452  |   for (j=0; lines[j] != NULL; j++) {
453  | 
454  |     switch (lines[j][0]) {
455  |       /* line continuation */
456  |     case ' ':
457  |     case '\t':
458  |     case '+':
459  |       value = (unsigned char *) lines[j]+1;
460  |       while(*value != '\0' && isspace(*value)) {
461  | 	value++;
462  |       }      
463  |       g_string_append(result_buff, "\n+ ");
464  |       g_string_append(result_buff, (char *)value);
465  |       break;
466  |       
467  |     default:
468  |       /* a line of the form "attribute: value" */
469  |       /* first: close the last line (if there was any, i.e. j>0) */
470  |       if( j > 0 ) { 
471  | 	g_string_append_c(result_buff, '\n');
472  |       }
473  |       
474  |       /* get attribute name */
475  |       attr =  lines[j];
476  |       colon = (unsigned char *) strchr(lines[j], ':');
477  |       /* if there's no colon for whatever reason, dump the object
478  | 	 and report the condition */
479  |       if( colon == NULL ) {
480  | 	ER_perror(FAC_QI, QI_INVOBJ, " [%s]", lines[0]);
481  | 	goto fast_output_cleanup;
482  |       }
483  |       *colon = '\0';
484  |       for(value = colon+1; *value != '\0' && isspace(*value) ; value++) {
485  | 	;
486  |       }
487  | 
488  |       if( (i = DF_attribute_name2type(attr)) == -1 ) {
489  | 	  /* warning! error in the object format */
490  | 	ER_perror(FAC_QI, QI_INVOBJ, " [%s]", lines[0]);
491  | 	goto fast_output_cleanup;
492  | 		
493  |       }
494  |       else {
495  | 	/* This is the juicy bit that converts the likes of; "source: RIPE" to "*so: RIPE" */
496  | 	g_string_append_c(result_buff, '*');
497  | 	g_string_append(result_buff, DF_get_attribute_code(i));
498  | 	g_string_append(result_buff, ": ");
499  | 	g_string_append(result_buff, (char *)value);
500  |       }
501  |     } /* switch */  
502  |   } /* for every line */ 
503  | 
504  |  fast_output_cleanup:
505  | 
506  |   g_strfreev(lines);
507  |   
508  |   g_string_append_c(result_buff, '\n');
509  |   result = strdup(result_buff->str);
510  |   dieif(result == NULL);
511  |   
512  |   g_string_free(result_buff,/* CONSTCOND */ TRUE);
513  |   
514  |   return result;
515  | } /* fast_output() */
516  | 
517  | /* filter() */
518  | /*++++++++++++++++++++++++++++++++++++++
519  |   Basically it's for the '-K' flag for non-set (and non-radix) objects.
520  |   It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.
521  | 
522  |   This could be speed up if there were breaks out of the loops, once it matched something.
523  | 
524  |   const char *string          The string to be filtered.
525  |    
526  |   More:
527  |   +html+ <PRE>
528  |   Authors:
529  |         ottrey
530  |   +html+ </PRE>
531  | 
532  |   ++++++++++++++++++++++++++++++++++++++*/
533  | char *filter(const char *str) {
534  |   int i,j, passed=0;
535  |   char *result;
536  |   GString *result_buff = g_string_sized_new(STR_XL);
537  |   gchar **lines = g_strsplit(str, "\n", 0);
538  |   char * const *filter_names;
539  |   gboolean filtering_an_attribute = FALSE;
540  |   
541  |   filter_names = DF_get_filter_names();
542  | 
543  |   g_string_assign(result_buff, "");
544  |   
545  |   for (i=0; filter_names[i] != NULL; i++) {
546  |     for (j=0; lines[j] != NULL; j++) {
547  |       if (strncmp(filter_names[i], lines[j], strlen(filter_names[i])) == 0) {
548  | 
549  |         g_string_sprintfa(result_buff, "%s\n", lines[j]);
550  | 	passed++;
551  | 	
552  | 	/* CONSTCOND */
553  |         filtering_an_attribute = TRUE;
554  |       }
555  |       /* CONSTCOND */
556  |       else if (filtering_an_attribute == TRUE) {
557  |         switch (lines[j][0]) {
558  |           case ' ':
559  |           case '\t':
560  |           case '+':
561  | 
562  |             g_string_sprintfa(result_buff, "%s\n", lines[j]);
563  |             
564  |           break;
565  | 
566  |           default:
567  |             filtering_an_attribute = FALSE;
568  |         }
569  |       }
570  |     }
571  |   }
572  | 
573  |   g_strfreev(lines);
574  | 
575  |   if(passed) {
576  |     g_string_append(result_buff, "\n");
577  |   }
578  |   result = strdup(result_buff->str);
579  |   g_string_free(result_buff,/* CONSTCOND */ TRUE);
580  | 
581  |   return result;
582  | } /* filter() */
583  | 
584  | /* write_results() */
585  | /*++++++++++++++++++++++++++++++++++++++
586  |   Write the results to the client socket.
587  | 
588  |   SQ_result_set_t *result The result set returned from the sql query.
589  |   unsigned filtered       if the objects should go through a filter (-K)
590  |   sk_conn_st *condat      Connection data for the client    
591  | 
592  |   More:
593  |   +html+ <PRE>
594  |   Authors:
595  |         ottrey - initial design
596  | 	marek - rewritten for accounting and cancellation.
597  |   +html+ </PRE>
598  | 
599  |   ++++++++++++++++++++++++++++++++++++++*/
600  | static int write_results(SQ_result_set_t *result, 
601  | 			 unsigned filtered,
602  | 			 unsigned fast,
603  | 			 sk_conn_st *condat,
604  | 			 acc_st    *acc_credit,
605  | 			 acl_st    *acl
606  | 			 ) {
607  |   SQ_row_t *row;
608  |   char *str;
609  |   char *filtrate;
610  |   char *fasted;
611  |   int retrieved_objects=0;
612  |   char *objt;
613  |   int type;
614  | 
615  |   /* Get all the results - one at a time */
616  |   if (result != NULL) {
617  |     /* here we are making use of the mysql_store_result capability
618  |        of interrupting the cycle of reading rows. mysql_use_result
619  |        would not allow that, would have to be read until end */
620  |     
621  |     while ( condat->rtc == 0 
622  | 	    && AC_credit_isdenied( acc_credit ) == 0
623  | 	    && (row = SQ_row_next(result)) != NULL ) {
624  |       
625  |       if (  (str = SQ_get_column_string(result, row, 0)) == NULL
626  | 	    || (objt = SQ_get_column_string(result, row, 3)) == NULL )  { 
627  | 	/* handle it somehow ? */
628  | 	die; 
629  |       }
630  |       else  { 
631  | 	/* get + add object type */
632  | 	type = atoi(objt);
633  | 	
634  | 	/* ASP_QI_LAST_DET */
635  | 	ER_dbg_va(FAC_QI, ASP_QI_LAST_DET,
636  | 		  "Retrieved serial id = %d , type = %s", atoi(str), objt);
637  | 	
638  | 	wr_free(str);
639  | 	wr_free(objt);
640  |       }
641  |       
642  |       /* decrement credit for accounting purposes */
643  |       AC_count_object( acc_credit, acl, 
644  | 		       type == C_PN || type == C_RO ); /* is private? */
645  | 
646  |       /* break the loop if the credit has just been exceeded and 
647  | 	 further results denied */
648  |       if( AC_credit_isdenied( acc_credit ) ) {
649  | 	continue; 
650  |       }
651  |       
652  |       if ((str = SQ_get_column_string(result, row, 2)) == NULL) { die; } 
653  |       else {
654  | 	
655  |         /* The fast output stage */
656  |         if (fast == 1) {
657  |           fasted = QI_fast_output(str);
658  |           wr_free(str);
659  |           str = fasted;
660  |         }
661  | 	
662  |         /* The filtering stage */
663  |         if (filtered == 0) {
664  |           SK_cd_puts(condat, str);
665  | 	  SK_cd_puts(condat, "\n");
666  |         }
667  |         else { 
668  | 	  
669  | 	  /* XXX accounting should be done AFTER filtering, not to count
670  | 	     objects filtered out */
671  | 
672  |           filtrate = filter(str);
673  |           SK_cd_puts(condat, filtrate);
674  |           wr_free(filtrate);
675  |         }
676  |         retrieved_objects++;
677  |       }
678  |       wr_free(str);
679  |     }
680  |   }
681  |   
682  |   return retrieved_objects;
683  | } /* write_results() */
684  | 
685  | 
686  | /* write_objects() */
687  | /*++++++++++++++++++++++++++++++++++++++
688  |   
689  |   SQ_connection_t *sql_connection The connection to the database.
690  | 
691  |   char *id_table The id of the temporary table (This is a result of the hacky
692  |                   way we've tried to get MySQL to do sub-selects.)
693  | 
694  |   sk_conn_st *condat  Connection data for the client
695  | 
696  |   More:
697  |   +html+ <PRE>
698  |   Authors:
699  |         ottrey,
700  | 	marek.
701  |   +html+ </PRE>
702  |   ++++++++++++++++++++++++++++++++++++++*/
703  | static void write_objects(SQ_connection_t **sql_connection, 
704  | 			  char *id_table, 
705  | 			  unsigned int filtered, 
706  | 			  unsigned int fast, 
707  | 			  sk_conn_st *condat,
708  | 			  acc_st    *acc_credit,
709  | 			  acl_st    *acl
710  | 			  ) 
711  | {
712  |   SQ_result_set_t *result = NULL;
713  |   int retrieved_objects=0;
714  |   char sql_command[STR_XL];  
715  | 
716  |   sprintf(sql_command, Q_OBJECTS, id_table);
717  | 
718  |   dieif(sql_execute_watched(condat, sql_connection, sql_command, &result) == -1 );
719  |   
720  |   /* Problem: if the query was aborted, the result structure does not
721  |      refer to any existing connection anymore. So we check rtc here.
722  |   */
723  |   
724  |   if( condat->rtc == 0) {
725  |     retrieved_objects = write_results(result, filtered, fast, condat, 
726  | 				      acc_credit, acl);
727  |     SQ_free_result(result); 
728  |   }
729  | } /* write_objects() */
730  | 
731  | /* insert_radix_serials() */
732  | /*++++++++++++++++++++++++++++++++++++++
733  |   Insert the radix serial numbers into a temporary table in the database.
734  | 
735  |   mask_t bitmap The bitmap of attribute to be converted.
736  |    
737  |   SQ_connection_t *sql_connection The connection to the database.
738  | 
739  |   char *id_table The id of the temporary table (This is a result of the hacky
740  |                   way we've tried to get MySQL to do sub-selects.)
741  |   
742  |   GList *datlist The list of data from the radix tree.
743  | 
744  |   More:
745  |   +html+ <PRE>
746  |   Authors:
747  |         ottrey,
748  | 	marek
749  |   +html+ </PRE>
750  | 
751  |   ++++++++++++++++++++++++++++++++++++++*/
752  | static void insert_radix_serials(sk_conn_st *condat,
753  | 				 SQ_connection_t *sql_connection, 
754  | 				 char *id_table, GList *datlist) {
755  |   GList    *qitem;
756  |   char sql_command[STR_XL];
757  |   int serial;
758  | 
759  |   for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
760  |     rx_datcpy_t *datcpy = qitem->data;
761  | 
762  |     serial = datcpy->leafcpy.data_key;
763  | 
764  |     sprintf(sql_command, "INSERT INTO %s values (%d)", id_table, serial);
765  |     dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1);
766  | 
767  |     wr_free(datcpy->leafcpy.data_ptr);
768  | 
769  |     if(condat->rtc != 0) {
770  |       break;
771  |     }
772  |   }
773  | 
774  |   wr_clear_list( &datlist );
775  | 
776  | } /* insert_radix_serials() */
777  | 
778  | 
779  | /* write_radix_immediate() */
780  | /*++++++++++++++++++++++++++++++++++++++
781  |   Display the immediate data carried with the objects returned by the
782  |   radix tree.
783  | 
784  |   GList *datlist      The linked list of dataleaf copies
785  | 
786  |   sk_conn_st *condat  Connection data for the client
787  | 
788  |   acc_st  *acc_credit Accounting struct
789  | 
790  | More:
791  |   +html+ <PRE>
792  |   Authors:
793  |         marek
794  |   +html+ </PRE>
795  | 
796  |   Also free the list of answers.
797  | ++++++++++++++++++++++++++++++++++++++*/
798  | static void write_radix_immediate(GList *datlist, 
799  | 				  sk_conn_st *condat,
800  | 				  acc_st    *acc_credit,
801  | 				  acl_st    *acl) 
802  | {
803  |   GList    *qitem;
804  |   
805  |   for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
806  |     rx_datcpy_t *datcpy = qitem->data;
807  | 
808  |     SK_cd_puts(condat, datcpy->leafcpy.data_ptr );
809  |     SK_cd_puts(condat, "\n");
810  |     
811  |     wr_free(datcpy->leafcpy.data_ptr);
812  |     
813  |     AC_count_object(acc_credit, acl, 0 /* public object (private=0) */ );
814  | 
815  |     if(condat->rtc != 0) {
816  |       break;
817  |     }
818  |   }
819  |   
820  |   wr_clear_list( &datlist );
821  | } /* write_radix_immediate() */
822  | 
823  | 
824  | /* map_qc2rx() */
825  | /*++++++++++++++++++++++++++++++++++++++
826  |   The mapping between a query_command and a radix query.
827  | 
828  |   Query_instruction *qi The Query Instruction to be created from the mapping
829  |                         of the query command.
830  | 
831  |   const Query_command *qc The query command to be mapped.
832  | 
833  |   More:
834  |   +html+ <PRE>
835  |   Authors:
836  |         ottrey,
837  | 	marek - simplified the logic, added stealth -S option
838  |   +html+ </PRE>
839  | 
840  |   ++++++++++++++++++++++++++++++++++++++*/
841  | static int map_qc2rx(Query_instruction *qi, const Query_command *qc) {
842  |   int result=1;
843  |   int allflags = (qc->L == 1) + (qc->M == 1) + (qc->l == 1) 
844  |                + (qc->m == 1) + (qc->x == 1);
845  | 
846  |   qi->rx_keys = qc->keys;
847  | 
848  |   /* only one option can be active at a time */
849  | 
850  |   if( allflags > 1 ) {
851  |       /* user error  (this should have been checked before) */
852  |       
853  |       ER_dbg_va(FAC_QI, ASP_QI_SKIP, 
854  | 		"ERROR in qc2rx mapping: bad combination of flags");
855  |       result = 0;
856  |   }
857  |   if( allflags == 0 ) { 
858  |       /* no options active - default search */
859  |       qi->rx_srch_mode = RX_SRCH_EXLESS;
860  |       qi->rx_par_a = 0;
861  |   }
862  |   else if ( qc->L == 1 ) {
863  |       qi->rx_srch_mode = RX_SRCH_LESS;
864  |       qi->rx_par_a = RX_ALL_DEPTHS;
865  |   }
866  |   else if (qc->M == 1) {
867  |       qi->rx_srch_mode = RX_SRCH_MORE;
868  |       qi->rx_par_a = RX_ALL_DEPTHS;
869  |   }
870  |   else if (qc->l == 1) {
871  |       qi->rx_srch_mode = RX_SRCH_LESS;
872  |       qi->rx_par_a = 1;
873  |   }
874  |   else if (qc->m == 1) {
875  |       qi->rx_srch_mode = RX_SRCH_MORE;
876  |       qi->rx_par_a = 1;
877  |   }
878  |   else if (qc->x == 1) {
879  |       qi->rx_srch_mode = RX_SRCH_EXACT;
880  |       qi->rx_par_a = 0;
881  |   }
882  |   
883  |   if( qi->rx_srch_mode == RX_SRCH_MORE && (qc->S == 1) ) {
884  |       qi->rx_srch_mode = RX_SRCH_DBLS;
885  |   }
886  |   
887  |   return result;
888  |   
889  | } /* map_qc2rx() */
890  | 
891  | 
892  | /* run_referral() */
893  | /*++++++++++++++++++++++++++++++++++++++
894  |   
895  |    invoked when no such domain found. Goes through the domain table
896  |    and searches for shorter domains, then if it finds one with referral 
897  |    it performs it, otherwise it just returns nothing.
898  | 
899  |    to perform referral, it actually composes the referral query 
900  |    for a given host/port/type and calls the whois query function.
901  | 
902  |    Well, it returns nothing anyway (void). It just prints to the socket.
903  | 
904  |   char *ref_host           referral server host name
905  | 
906  |   unsigned ref_port_int    referral server port number
907  | 
908  |   char *ref_type           referral type name
909  | 
910  |   char *qry                query to be run
911  | 
912  |   Author:
913  |     marek
914  |   ++++++++++++++++++++++++++++++++++++++*/
915  | void run_referral(Query_environ *qe, 
916  | 		  char *ref_host,
917  | 		  unsigned ref_port_int,
918  | 		  char *ref_type,
919  | 		  char *qry)
920  | {
921  |   
922  | #if 1 /* switch off for testing */
923  |   er_ret_t err;
924  |   char *rep;
925  | 
926  |         /* WH_sock(sock, host, port, query, maxlines, timeout)) */
927  |   err= WH_cd_sock(&(qe->condat), ref_host, ref_port_int, qry, 
928  | 		  ca_get_referralmaxlines, ca_get_referraltimeout
929  | 		  );
930  | 
931  |   switch( err ) {
932  |   case SK_OK:
933  |     /* OK */
934  |     break;
935  |   case SK_TIMEOUT:
936  |     /* Referral timeout */
937  |     rep = ca_get_qi_ref_tmout ;
938  |     SK_cd_puts(&(qe->condat), rep);
939  |     wr_free(rep);
940  |     break;
941  | 	    
942  |   case SK_BADHOST:
943  |     /* Referral host not found */
944  |     rep = ca_get_qi_ref_badhost ;
945  |     SK_cd_puts(&(qe->condat), rep);
946  |     wr_free(rep);
947  |     break;
948  | 
949  |   case SK_CONNECT:
950  |     /* Referral host not responding */
951  |     rep = ca_get_qi_ref_hostnottresp ;
952  |     SK_cd_puts(&(qe->condat), rep);
953  |     wr_free(rep);
954  |     break;
955  | 
956  |   case SK_BIND:
957  |   case SK_SOCKET:
958  |     /* XXX internal server problem...  */
959  |     die; 
960  | 
961  |   case WH_MAXLINES:
962  |     /* Referral reply line limit exceeded */
963  |     rep = ca_get_qi_ref_overmaxlin ;
964  |     SK_cd_puts(&(qe->condat), rep);
965  |     wr_free(rep);
966  |     break;
967  |     
968  |   default: /* any other errors ? */
969  |     die;
970  |     ;
971  |   } /*switch WH_sock */
972  | #endif
973  |   
974  | }/*run_referral*/
975  | 
976  | 
977  | 
978  | 
979  | 
980  | /*++++++++++++++++++++++++++++++++++++++
981  |    
982  |    prepare and run the referral, displaying the results directly to the
983  |    client's connection. 
984  |    
985  |    XXX still missing protection against a referral loop
986  |    XXX handling inverse flag not needed, to be removed
987  | 
988  |   char *domain               domain being looked up
989  |  
990  |   Query_instructions *qis    original query instructions structure
991  | 
992  |   Query_environ *qe          original query environment structure
993  | 
994  |   Query_instruction *qi      specific query instruction triggered
995  | 
996  |   SQ_result_set_t *result    result of the lookup containing referral details
997  | 
998  |   SQ_row_t *row              first row (should be only 1) of the result
999  | 
1000 |   char *sourcename           name of the database "source"
1001 | 
1002 |   Author: 
1003 |      marek
1004 |   ++++++++++++++++++++++++++++++++++++++*/
1005 | static
1006 | void qi_prep_run_refer(char *domain, 
1007 | 		       Query_instructions *qis,   
1008 | 		       Query_environ *qe, 
1009 | 		       Query_instruction *qi,
1010 | 		       SQ_result_set_t *result, SQ_row_t *row, 
1011 | 		       char *sourcename )
1012 | {
1013 |     char *ref_host = SQ_get_column_string(result, row, 2);
1014 |     char *ref_type = SQ_get_column_string(result, row, 0);
1015 |     char *ref_port = SQ_get_column_string(result, row, 1);
1016 |     unsigned  ref_port_int;
1017 |     char querystr[STR_L];
1018 |       
1019 |     /* get the integer value, it should be correct */
1020 |     if( sscanf( ref_port, "%d",&ref_port_int) < 1 ) {
1021 | 	die;
1022 |     }
1023 |       
1024 |     strcpy(querystr,"");
1025 |       
1026 |     /* put -r if the reftype is RIPE and -r or -i were used */
1027 |     if( strcmp(ref_type,"RIPE") == 0 
1028 | 	&& ( Query[qi->queryindex].querytype == Q_INVERSE       
1029 | 	     || qis->recursive > 0  )   ) {
1030 | 	strcat(querystr," -r ");
1031 |     }
1032 |       
1033 |     /* prepend with -Vversion,IP for type CLIENTADDRESS */
1034 |     if( strcmp(ref_type,"CLIENTADDRESS") == 0 ) {
1035 | 	char optv[STR_M];
1036 | 	
1037 | 	snprintf(optv,STR_M," -V%s,%s ",VERSION, qe->condat.ip);
1038 | 	strcat(querystr,optv);
1039 |     }
1040 |       
1041 |     /* now set the search term - set to the stripped down version 
1042 |        for inverse query, full-length otherwise */
1043 |     if( Query[qi->queryindex].querytype == Q_INVERSE ) {
1044 | 	strcat(querystr, domain);
1045 |     }
1046 |     else {
1047 | 	strcat(querystr, qis->qc->keys);
1048 |     }
1049 | 
1050 |     {
1051 | 	/* the object is not from %s, 
1052 | 	   it comes from %s %d, use -R to see %s */
1053 | 	char *rep = ca_get_qi_fmt_refheader ;
1054 | 	SK_cd_printf(&(qe->condat), rep, 
1055 | 		     sourcename, 
1056 | 		     ref_host, ref_port_int,
1057 | 		     sourcename );
1058 | 	wr_free(rep);
1059 |     }
1060 | 	    
1061 |     /* do the referral */
1062 |     ER_dbg_va(FAC_QI, ASP_QI_REF_GEN, "referral host is %s", ref_host); 
1063 |       
1064 |     run_referral( qe, ref_host, ref_port_int, ref_type, querystr);
1065 |       
1066 |     { /* End of referred query result */
1067 | 	char *rep = ca_get_qi_reftrailer ; 
1068 | 	SK_cd_puts(&(qe->condat), rep);
1069 | 	wr_free(rep);
1070 |     }
1071 |     
1072 |     wr_free(ref_host);
1073 |     wr_free(ref_type);
1074 |     wr_free(ref_port);
1075 | }
1076 | 
1077 | 
1078 | /*++++++++++++++++++++++++++++++++++++++
1079 |   
1080 |   specific case of the object ID collection: the domains.
1081 |   Checks to see if the domain exists, and runs the referral if it is defined
1082 |   and the domain is missing.
1083 | 
1084 |   Arguments:
1085 | 
1086 |   char *sourcename                     name of the database "source"
1087 | 
1088 |   SQ_connection_t *sql_connection      sql connection dedicated to this thread
1089 |   
1090 |   char *id_table                       name of the temporary table to be used
1091 |   
1092 |   char *sub_table                      name of the temporary subtable
1093 |   
1094 |   Query_instructions *qis    original query instructions structure
1095 | 
1096 |   Query_environ *qe          original query environment structure
1097 | 
1098 |   Query_instruction *qi      specific query instruction triggered
1099 | 
1100 |   acc_st *acc_credit         credit for this client 
1101 |  
1102 |   Author:
1103 |      marek.
1104 |   ++++++++++++++++++++++++++++++++++++++*/
1105 | 
1106 | static int
1107 | qi_collect_domain(char *sourcename,
1108 | 		  SQ_connection_t *sql_connection, 
1109 | 		  char *id_table,
1110 | 		  char *sub_table,
1111 | 		  Query_instructions *qis,   
1112 | 		  Query_environ *qe, 
1113 | 		  Query_instruction *qi,
1114 | 		  acc_st *acc_credit)
1115 | {
1116 |   char *domain = qis->qc->keys;
1117 |   char *dot = domain;
1118 |   int subcount = 0;
1119 |   int foundcount = 0;
1120 | 
1121 |   /* while nothing found and still some pieces of the name left */
1122 |   while( dot != NULL && subcount == 0 ) { 
1123 |     int refcount = 0;
1124 |     SQ_row_t *row;
1125 |     SQ_result_set_t *result_referrals = NULL;
1126 |     char sql_command[STR_XL];
1127 | 
1128 |     ER_dbg_va(FAC_QI, ASP_QI_REF_DET, "run_referral: checking %s", dot);
1129 | 
1130 |     /* domain lookup -- query into the _S table */
1131 |     sprintf(sql_command, "INSERT INTO %s SELECT object_id FROM domain WHERE domain = '%s'", sub_table, dot);
1132 |     
1133 |     dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1);
1134 |     subcount = SQ_get_affected_rows(sql_connection); 
1135 | 
1136 |     if( subcount != 0 ) { /* domain exists in the database */
1137 | 
1138 | 	/* referral check. Always done except for -R and INVERSE queries */  
1139 | 	if( qis->qc->R == 0 && 
1140 | 	    Query[qi->queryindex].querytype != Q_INVERSE ) {
1141 | 	    sprintf(sql_command, "SELECT type, port, host FROM %s ID, refer WHERE ID.id = refer.object_id", sub_table);
1142 | 	    dieif( SQ_execute_query(sql_connection, sql_command, 
1143 | 				    &result_referrals) == -1);
1144 | 	    refcount = SQ_num_rows(result_referrals);
1145 | 	}
1146 | 
1147 | 	/* if referral allowed and defined, even if domain was found but 
1148 | 	   contained referral - refer the query */
1149 | 	if( refcount != 0 ) { 
1150 | 	    /* get the referral parameters from the first row
1151 | 	       and perform it 
1152 | 	    */
1153 | 
1154 | 	    row = SQ_row_next(result_referrals);
1155 | 	    /* now: query for the original domain */
1156 | 	    qi_prep_run_refer(domain,
1157 | 			      qis, qe, qi, result_referrals, row, sourcename);
1158 | 	    
1159 | 	    acc_credit->referrals -= 1;
1160 | 	}
1161 | 	else {
1162 | 	    /* domain found 
1163 | 	       and (referral undefined  or  disabled by -R or inverse)
1164 | 	       two possible outcomes depending on whether 'dot' is:
1165 | 	       * the original search term -> pass what's in _S and quit 
1166 | 	       * a 'stripped' domain name -> return no result and quit
1167 | 	    */
1168 | 	    if( dot == domain ) {
1169 | 		sprintf(sql_command, "INSERT INTO %s SELECT id FROM %s", 
1170 | 			id_table, sub_table);
1171 | 		dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1);
1172 | 		foundcount = SQ_get_affected_rows(sql_connection); 
1173 | 	    }
1174 | 	} 
1175 | 	dot = NULL; /* don't make another round */
1176 |     } /* a domain was found */
1177 | 
1178 |     if( result_referrals != NULL ) {
1179 | 	SQ_free_result(result_referrals);
1180 | 	result_referrals = NULL;
1181 |     }
1182 |     
1183 |     if( dot != NULL && (dot=index(dot,'.')) != NULL) {
1184 |       dot++;
1185 |     }
1186 |   }
1187 |     
1188 |   return foundcount;
1189 | } /* check_domain */
1190 | 
1191 | 
1192 | /* add_ref_name */
1193 | /*++++++++++++++++++++++++++++++++++++++
1194 | 
1195 |   Creates a SQL query for a reference-by-name lookup. Uses standard name
1196 |   lookup query generator (create_name_query), so the order of the names
1197 |   doesn't matter.
1198 | 
1199 |   SQ_connection_t *sql_connection   sql connection dedicated to this thread
1200 | 
1201 |   char *rectable       table in which to look up
1202 |   
1203 |   char *allnames       all name words to be looked up, space delimited.
1204 | 
1205 | ++++++++++++++++++++++++++++++++++++++*/
1206 | static
1207 | void 
1208 | add_ref_name(SQ_connection_t *sql_connection, 
1209 | 	     char *rectable,
1210 | 	     char *allnames
1211 | 	     )
1212 | {
1213 |   /* construct the query, allow zero-length list */
1214 |   if( strlen(allnames) > 0 ) {
1215 |     char final_query[STR_XL];
1216 |     char select_query[STR_XL];
1217 | 
1218 |     create_name_query(select_query, "SELECT N00.object_id FROM %s WHERE %s "
1219 | 		      "AND N00.object_type != 100 AND N00.thread_id = 0", 
1220 | 		      allnames);
1221 |     
1222 |     sprintf(final_query, "INSERT INTO %s %s",
1223 | 	    rectable,
1224 | 	    select_query);
1225 |     
1226 |     dieif(SQ_execute_query(sql_connection, final_query, NULL) == -1 );
1227 | 
1228 |     allnames[0]=0;
1229 |   }
1230 | }/* add_ref_name */
1231 | 
1232 | 
1233 | 
1234 | /* qi_collect_ids */
1235 | /*++++++++++++++++++++++++++++++++++++++ 
1236 |   
1237 |   collects object ID's from all queries defined in the Query_instructions 
1238 |   array. The results from RADIX trees are maintained in a linked list, the 
1239 |   results from SQL lookups are kept in a temporary table. For domains,
1240 |   a specific function is invoked that may run the referral. 
1241 |   Any sql lookup will be limited to the maximum number of objects allowed
1242 |   for the client (acl and credit are checked for this).
1243 |   The routine uses its own temporary _S table, destroyed at exit.
1244 |   
1245 |   ca_dbSource_t *dbhdl              source-specific identifier (defined in CA)
1246 |   
1247 |   char *sourcename                  name of the database "source"
1248 |   
1249 |   SQ_connection_t **sql_connection  sql connection dedicated to this thread
1250 |                                     (replaced on cancel)
1251 |  
1252 |   Query_instructions *qis           original query instructions structure
1253 | 
1254 |   Query_environ *qe                 original query environment structure
1255 |  
1256 |   char *id_table                    the table to store the ID's found
1257 |   
1258 |   GList **datlist                   the list  to store the Radix leaves found
1259 |   
1260 |   acc_st *acc_credit                credit for this client 
1261 |   
1262 |   acl_st *acl                       acl for this client 
1263 |   
1264 |   ++++++++++++++++++++++++++++++++++++++*/
1265 | static
1266 | void
1267 | qi_collect_ids(ca_dbSource_t *dbhdl,
1268 | 	       char *sourcename,
1269 | 	       SQ_connection_t **sql_connection,
1270 | 	       Query_instructions *qis,
1271 | 	       Query_environ *qe,	
1272 | 	       char *id_table,
1273 | 	       GList **datlist,
1274 | 	       acc_st *acc_credit,
1275 | 	       acl_st *acl
1276 | 	       )
1277 | {
1278 |   Query_instruction **ins=NULL;
1279 |   int i;
1280 |   int  count, errors=0;
1281 |   char sql_command[STR_XL];
1282 |   er_ret_t err;
1283 |   char sub_table[32];
1284 |   int limit ;
1285 |              /* a limit on the max number of objects to be returned
1286 | 		from a single search. For some queries the object types
1287 | 		are not known at this stage, so the limit must be
1288 | 		the higher number of the two: private / public,
1289 | 		or unlimited if any of them is 'unlimited'.
1290 | 	     */
1291 |   char limit_str[32];
1292 | 
1293 |   if( (limit = AC_get_higher_limit(acc_credit,acl)) == -1) {
1294 |     strcpy(limit_str,"");
1295 |   } else {
1296 |     sprintf(limit_str," LIMIT %d", limit+1); /* make sure we collect more
1297 | 						so that the client hits
1298 | 						the limit */
1299 |   }
1300 | 
1301 |   sprintf(sub_table, "%s_S ", id_table);
1302 |   
1303 |   /* see if there was a leftover table from a crashed session 
1304 |    * (assume the ID cannot be currently in use)
1305 |    */
1306 |   sprintf(sql_command, "DROP TABLE IF EXISTS %s", sub_table);
1307 |   dieif( SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
1308 | 
1309 |   /* create a table for special subqueries (domain only for now) */
1310 |   sprintf(sql_command, "CREATE TABLE %s ( id int ) TYPE=HEAP", sub_table);
1311 |   dieif( SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
1312 |   
1313 |   /* Iterate through query instructions */
1314 |   ins = qis->instruction;
1315 |   for (i=0; ins[i] != NULL && errors == 0; i++) {
1316 |     Query_instruction *qi = ins[i];
1317 |     
1318 |     /* check if the client is still there */
1319 |     if( qe->condat.rtc ) {
1320 |       break;
1321 |     }
1322 | 
1323 |     switch ( qi->search_type ) {
1324 |     case R_SQL:
1325 |       if ( qi->query_str != NULL ) {
1326 | 
1327 | 	/* handle special cases first */
1328 | 	if( Query[qi->queryindex].class == C_DN 
1329 | 	    && Query[qi->queryindex].querytype == Q_LOOKUP ) {
1330 | 	  
1331 | 	  /* if any more cases than just domain appear, we will be
1332 | 	     cleaning the _S table from the previous query here 
1333 | 	     
1334 | 	     "DELETE FROM %s_S"
1335 | 	  */
1336 | 	  
1337 | 	  count = qi_collect_domain(sourcename, *sql_connection, id_table, 
1338 | 				    sub_table, qis, qe, qi, acc_credit);
1339 | 	} /* if class DN and Straight lookup */
1340 | 	else {
1341 | 	  /* any other class of query */
1342 | 
1343 | 	  sprintf(sql_command, "INSERT INTO %s %s %s", 
1344 | 		  id_table, qi->query_str, limit_str);
1345 | 
1346 | 	  if(sql_execute_watched( &(qe->condat), sql_connection, 
1347 | 				  sql_command, NULL) == -1 ) {
1348 | 
1349 | 	    ER_perror(FAC_QI, QI_SQLERR," query='%s' [%d] %s", 
1350 | 		      sql_command,
1351 | 		      SQ_errno(*sql_connection), SQ_error(*sql_connection));
1352 | 	    errors++;
1353 | 	  }
1354 | 	  count = SQ_get_affected_rows(*sql_connection);
1355 | 	} /* not DN */
1356 |       } /* if SQL query not NULL */
1357 |       
1358 |       ER_dbg_va(FAC_QI, ASP_QI_COLL_DET,
1359 | 		"%d entries added in %s query for %s",
1360 | 		count, Query[qi->queryindex].descr, qis->qc->keys
1361 | 		);
1362 |       break;
1363 |       
1364 |     case R_RADIX:
1365 | 
1366 |      
1367 |       err = RP_asc_search(qi->rx_srch_mode, qi->rx_par_a, 0, 
1368 | 			  qi->rx_keys, dbhdl, 
1369 | 			  Query[qi->queryindex].attribute, 
1370 | 			  datlist, limit);
1371 |      
1372 | 
1373 |       if( NOERR(err)) {
1374 | 	if( ER_is_traced(FAC_QI, ASP_QI_COLL_DET ) ) {
1375 | 	  /* prevent unnecessary g_list_length call */
1376 | 	  
1377 | 	  ER_dbg_va(FAC_QI, ASP_QI_COLL_DET,
1378 | 		    "%d entries after %s (mode %d par %d reg %d) query for %s",
1379 | 		    g_list_length(*datlist),
1380 | 		    Query[qi->queryindex].descr,
1381 | 		    qi->rx_srch_mode, qi->rx_par_a, 
1382 | 		    dbhdl,
1383 | 		    qi->rx_keys);
1384 | 	}
1385 |       }
1386 |       else {
1387 | 	ER_inf_va(FAC_QI, ASP_QI_COLL_DET,
1388 | 		  "RP_asc_search returned %x ", err);
1389 |       }
1390 |       break;
1391 |       
1392 |     default: die;
1393 |     } /* switch */
1394 |     
1395 |   } /* for <every instruction> */
1396 | 
1397 |   /* Now drop the _S table */
1398 |   sprintf(sql_command, "DROP TABLE %s", sub_table);
1399 |   dieif(SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
1400 | 
1401 | }
1402 | 
1403 | /* qi_fetch_references */ 
1404 | /*++++++++++++++++++++++++++++++++++++++
1405 |   
1406 |   given the list of object ID's collects the references from these objects 
1407 |   to person and role objects. Uses its own temporary SQL table (_R)
1408 |   and upon completion transfers the results from it to the main
1409 |   temporary table. Runs queries in watched mode, to be able to cancel them.
1410 |   
1411 |   SQ_connection_t **sql_connection  sql connection dedicated to this thread
1412 |                                     (replaced on cancel)
1413 |   
1414 |   Query_environ *qe                 original query environment structure
1415 |   
1416 |   char *id_table                    the table with the ID's found
1417 |   
1418 |   acc_st *acc_credit                credit for this client 
1419 |   
1420 |   acl_st *acl                       acl for this client 
1421 | 
1422 | ++++++++++++++++++++++++++++++++++++++*/
1423 | static
1424 | void
1425 | qi_fetch_references(SQ_connection_t **sql_connection,
1426 | 		    Query_environ *qe,
1427 | 		    char *id_table,
1428 | 		    acc_st *acc_credit,
1429 | 		    acl_st *acl
1430 | 		    )
1431 | {
1432 | char rec_table[32];
1433 |     SQ_result_set_t *result = NULL;
1434 |     SQ_row_t *row;
1435 |     int thisid = 0;
1436 |     int oldid = 0;
1437 |     char allnames[STR_L];
1438 |     char sql_command[STR_XL];
1439 |  
1440 |     sprintf(rec_table, "%s_R", id_table);
1441 |     
1442 |     /* see if there was a leftover table from a crashed session 
1443 |      * (assume the ID cannot be currently in use)
1444 |      */
1445 |     sprintf(sql_command, "DROP TABLE IF EXISTS %s ", rec_table);
1446 |     dieif( SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
1447 | 
1448 |     /* a temporary table for recursive data must be created, because
1449 |        a query using the same table as a source and target is illegal
1450 |        ( like: INSERT into ID_123 SELECT * FROM ID_123,admin_c WHERE ... )
1451 |     */
1452 |     sprintf(sql_command, "CREATE TABLE %s ( id int PRIMARY KEY NOT NULL ) TYPE=HEAP", rec_table);
1453 |     dieif(SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
1454 |     
1455 |     /* find the contacts */      
1456 |     sprintf(sql_command, Q_REC, rec_table, id_table, "author");
1457 |     dieif(sql_execute_watched( &(qe->condat), sql_connection, sql_command, NULL) == -1 );
1458 |     
1459 |     sprintf(sql_command, Q_REC, rec_table, id_table, "admin_c");
1460 |     dieif(sql_execute_watched(&(qe->condat), sql_connection, sql_command, NULL) == -1 );
1461 |     
1462 |     sprintf(sql_command, Q_REC, rec_table, id_table, "tech_c" );
1463 |     dieif(sql_execute_watched(&(qe->condat), sql_connection, sql_command, NULL) == -1 );
1464 |     
1465 |     sprintf(sql_command, Q_REC, rec_table, id_table, "zone_c" );
1466 |     dieif(sql_execute_watched(&(qe->condat), sql_connection, sql_command, NULL) == -1 );
1467 |     
1468 |     
1469 |     /* replace references to dummies by references by name */
1470 |     sprintf(sql_command, 
1471 | 	    " SELECT id, name    FROM %s IDS STRAIGHT_JOIN names "
1472 | 	    " WHERE IDS.id = names.object_id "
1473 | 	    "      AND names.object_type = 100"
1474 | 	    " ORDER BY id",
1475 | 	    rec_table);
1476 |     
1477 |     dieif(sql_execute_watched(&(qe->condat), sql_connection, sql_command, 
1478 | 			      &result) == -1 );
1479 |     /* well, it might not be -1, but if the watchdog worked then the
1480 |        result is NULL */
1481 |     if( result != NULL ) {
1482 |       
1483 |       allnames[0]=0;
1484 |       /* now go through the results and collect names */
1485 |       while ( (qe->condat.rtc == 0)
1486 | 	      && (row = SQ_row_next(result)) != NULL ) {
1487 | 	char *id   = SQ_get_column_string(result, row, 0);
1488 | 	char *name = SQ_get_column_string(result, row, 1);
1489 | 	
1490 | 	thisid = atoi(id);
1491 | 	
1492 | 	/* when the id changes, the name is complete */
1493 | 	if( thisid != oldid && oldid != 0 ) {
1494 | 	  add_ref_name( *sql_connection, rec_table, allnames);
1495 | 	}
1496 | 	
1497 | 	strcat(allnames, name);
1498 | 	strcat(allnames, " ");
1499 | 	oldid = thisid;
1500 | 	wr_free(id);
1501 | 	wr_free(name);
1502 |       }
1503 |       /* also do the last name */
1504 |       add_ref_name( *sql_connection, rec_table, allnames);
1505 |       
1506 |       SQ_free_result(result); /* we can do it only because the watchdog */
1507 |       /* has not started between the check for non-NULL result and here */
1508 |     }
1509 |     
1510 |     /* now copy things back to the main temporary table   */
1511 |     sprintf(sql_command, "INSERT INTO %s SELECT * FROM %s", 
1512 | 	    id_table, rec_table);
1513 |     dieif(SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
1514 |     
1515 |     /* Now drop the IDS recursive table */
1516 |     sprintf(sql_command, "DROP TABLE %s", rec_table);
1517 |     dieif(SQ_execute_query(*sql_connection, sql_command, NULL) == -1 );
1518 | }
1519 | /* qi_fetch_references */ 
1520 | 
1521 | 
1522 | /* QI_execute() */
1523 | /*++++++++++++++++++++++++++++++++++++++
1524 |   Execute the query instructions.  This is called for each source.
1525 |   This is linked into MySQL by the fact that MySQL doesn't have sub selects
1526 |  (yet).  The queries are done in two stages.  Make some temporary tables and
1527 |   insert into them.  Then use them in the next select.
1528 | 
1529 |    
1530 |   ca_dbSource_t *dbhdl            source-specific identifier (defined in CA)
1531 | 
1532 |   Query_instructions *qis         query instructions.
1533 | 				 
1534 |   Query_environ *qe               query environment.
1535 | 				 
1536 |   acc_st *acc_credit              object display credit 
1537 | 				 
1538 |   acl_st *acl                     copy of the original acl for this client 
1539 | 
1540 |   More:
1541 |   +html+ <PRE>
1542 |   Authors:
1543 |         ottrey - original version,
1544 | 	marek - the rest.
1545 |   +html+ </PRE>
1546 | ++++++++++++++++++++++++++++++++++++++*/
1547 | er_ret_t QI_execute(ca_dbSource_t *dbhdl,
1548 | 		    Query_instructions *qis, 
1549 | 		    Query_environ *qe,	
1550 | 		    acc_st *acc_credit,
1551 | 		    acl_st *acl
1552 | 		    ) 
1553 | {
1554 |   /* those things must be freed after use! */
1555 |   char *dbhost = ca_get_srcdbmachine(dbhdl);
1556 |   char *dbname = ca_get_srcdbname(dbhdl);
1557 |   char *dbuser = ca_get_srcdbuser(dbhdl);
1558 |   char *dbpass = ca_get_srcdbpassword(dbhdl);
1559 |   char *srcnam = ca_get_srcname(dbhdl);
1560 |   unsigned dbport = ca_get_srcdbport(dbhdl);
1561 |   char id_table[STR_S];
1562 |   char sql_command[STR_XL];
1563 |   GList *datlist=NULL;
1564 |   SQ_connection_t *sql_connection=NULL;
1565 | 
1566 |   sql_connection = SQ_get_connection( dbhost, dbport,
1567 | 				      dbname, dbuser, dbpass );
1568 |   if (sql_connection == NULL) {
1569 |     ER_perror(FAC_QI, QI_CANTDB," database='%s' [%d] %s", 
1570 | 	      dbname, SQ_errno(sql_connection), SQ_error(sql_connection));
1571 |     return QI_CANTDB;
1572 |   }
1573 | 
1574 |   sprintf(id_table, "ID_%ld_%d",   mysql_thread_id(sql_connection),
1575 | 	  pthread_self());
1576 | 
1577 |   /* see if there was a leftover table from a crashed session 
1578 |    * (assume the ID cannot be currently in use)
1579 |    */
1580 |   sprintf(sql_command, "DROP TABLE IF EXISTS %s ", id_table);
1581 |   dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1582 |   
1583 |   /* create a table for id's of all objects found NOT NULL , UNIQUE(id) */
1584 |   sprintf(sql_command, "CREATE TABLE %s ( id int PRIMARY KEY NOT NULL ) TYPE=HEAP", id_table);
1585 |   dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1586 | 
1587 |   qi_collect_ids(dbhdl, srcnam, &sql_connection, qis, qe, id_table, 
1588 | 		 &datlist, acc_credit, acl);
1589 | 
1590 |   /* post-processing */
1591 |   if( qis->filtered == 0 ) {
1592 |     /* start the watchdog just to set the rtc flag */
1593 |     SK_watch_setclear(&(qe->condat));
1594 |     SK_watchstart(&(qe->condat));
1595 | 
1596 |     /* add radix results (only if -K is not active) */
1597 |     insert_radix_serials(&(qe->condat), sql_connection, id_table, datlist);
1598 | 
1599 |     SK_watchstop(&(qe->condat));
1600 |   }
1601 | 
1602 |   /* fetch recursive objects (ac,tc,zc,ah) */
1603 |   if ( qis->recursive ) {
1604 |     qi_fetch_references( &sql_connection, qe, id_table, acc_credit, acl);
1605 |   } /* if recursive */
1606 |   
1607 |   /* display */
1608 |   /* -K filtering: 
1609 |    * right now only filtering, no expanding sets like write_set_objects() 
1610 |    */
1611 |   
1612 |   /* display the immediate data from the radix tree */
1613 |   if( qis->filtered == 1 ) {
1614 |     write_radix_immediate(datlist, &(qe->condat), acc_credit, acl );
1615 |   }
1616 | 
1617 |   /* display objects from the IDs table */
1618 |   write_objects( &sql_connection, id_table, qis->filtered,
1619 | 		qis->fast, &(qe->condat), acc_credit, acl);
1620 | 
1621 |   /* Now drop the IDS table */
1622 |   sprintf(sql_command, "DROP TABLE %s", id_table);
1623 |   dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1624 |   SQ_close_connection(sql_connection);  
1625 | 
1626 |   /* free allocated parameters */
1627 |   wr_free(dbhost);
1628 |   wr_free(dbname);
1629 |   wr_free(dbuser);
1630 |   wr_free(dbpass);
1631 |   wr_free(srcnam);
1632 | 
1633 |   return QI_OK;
1634 | } /* QI_execute() */
1635 | 
1636 | 
1637 | /* instruction_free() */
1638 | /*++++++++++++++++++++++++++++++++++++++
1639 |   Free the instruction.
1640 | 
1641 |   Query_instruction *qi query_instruction to be freed.
1642 |    
1643 |   More:
1644 |   +html+ <PRE>
1645 |   Authors:
1646 |         ottrey
1647 |   +html+ </PRE>
1648 |   ++++++++++++++++++++++++++++++++++++++*/
1649 | static void instruction_free(Query_instruction *qi) {
1650 |   if (qi != NULL) {
1651 |     if (qi->query_str != NULL) {
1652 |       wr_free(qi->query_str);
1653 |     }
1654 |     wr_free(qi);
1655 |   }
1656 | } /* instruction_free() */
1657 | 
1658 | /* QI_free() */
1659 | /*++++++++++++++++++++++++++++++++++++++
1660 |   Free the query_instructions.
1661 | 
1662 |   Query_instructions *qis Query_instructions to be freed.
1663 |    
1664 |   More:
1665 |   +html+ <PRE>
1666 |   Authors:
1667 |         ottrey, marek
1668 |   +html+ </PRE>
1669 |   ++++++++++++++++++++++++++++++++++++++*/
1670 | void QI_free(Query_instructions *qis) {
1671 |   int i;
1672 | 
1673 |   for (i=0; qis->instruction[i] != NULL; i++) {
1674 |     instruction_free(qis->instruction[i]);
1675 |   } 
1676 | 
1677 |   if (qis != NULL) {
1678 |     wr_free(qis);
1679 |   }
1680 | 
1681 | } /* QI_free() */
1682 | 
1683 | /*++++++++++++++++++++++++++++++++++++++
1684 |   Determine if this query should be conducted or not.
1685 | 
1686 |   If it was an inverse query - if the attribute appears in the query command's bitmap.
1687 |   If it was a lookup query - if the attribute appears in the object type bitmap or
1688 |                              disregard if there is no object_type bitmap (Ie object filter).
1689 | 
1690 |   mask_t bitmap The bitmap of attribute to be converted.
1691 |    
1692 |   const Query_command *qc  The query_command that the instructions are created
1693 |                            from.
1694 |   
1695 |   const Query_t q          The query being considered.
1696 |   +html+ <PRE>
1697 |   Authors:
1698 |         ottrey,
1699 | 	marek.
1700 |   +html+ </PRE>
1701 |   ++++++++++++++++++++++++++++++++++++++*/
1702 | static int valid_query(const Query_command *qc, const Query_t q) {
1703 |   int result=0;
1704 | 
1705 |   if (MA_isset(qc->keytypes_bitmap, q.keytype) == 1) {
1706 |     if (q.query != NULL) {
1707 |       switch (q.querytype) {
1708 |         case Q_INVERSE:
1709 |           if (MA_isset(qc->inv_attrs_bitmap, q.attribute) ) {
1710 |             result = 1;
1711 |           }
1712 |         break;
1713 | 
1714 |         case Q_LOOKUP:
1715 | 	  if (q.class == C_ANY 
1716 | 	      || MA_isset(qc->object_type_bitmap, (unsigned) q.class)) {
1717 |             result=1;
1718 |           }
1719 |         break;
1720 | 
1721 |         default:
1722 |           /* XXX */fprintf(stderr, "qi:valid_query() -> Bad querytype\n");
1723 |       }
1724 |     }
1725 |   }
1726 | 
1727 |   return result;
1728 | } /* valid_query() */
1729 | 
1730 | /* QI_new() */
1731 | /*++++++++++++++++++++++++++++++++++++++
1732 |   Create a new set of query_instructions. Returns an allocated structure which
1733 |   must be freed after use with QI_free().
1734 | 
1735 |   const Query_command *qc The query_command that the instructions are created
1736 |                           from.
1737 | 
1738 |   const Query_environ *qe The environmental variables that they query is being
1739 |                           performed under.
1740 |   
1741 |   +html+ <PRE>
1742 |   Authors:
1743 |         ottrey,
1744 | 	marek.
1745 |   +html+ </PRE>
1746 |   ++++++++++++++++++++++++++++++++++++++*/
1747 | Query_instructions *QI_new(const Query_command *qc, const Query_environ *qe) {
1748 |   Query_instructions *qis=NULL;
1749 |   Query_instruction *qi=NULL;
1750 |   int i_no=0;
1751 |   int i;
1752 |   char *query_str;
1753 | 
1754 |   dieif(wr_calloc( (void **) & qis, 1, sizeof(Query_instructions)) != UT_OK);
1755 | 
1756 |   qis->filtered = qc->filtered;
1757 |   qis->fast = qc->fast;
1758 |   qis->recursive = qc->recursive;
1759 |   qis->qc = (qc);
1760 | 
1761 |   
1762 |   for (i=0; Query[i].query != NULL; i++) {
1763 | 
1764 |     /* If a valid query. */
1765 |     if ( valid_query(qc, Query[i]) == 1) {
1766 | 
1767 |       dieif( wr_calloc((void **) &qi, 1, sizeof(Query_instruction)) != UT_OK);
1768 | 
1769 |       qi->queryindex = i;
1770 | 
1771 |       /* SQL Query */
1772 |       if ( Query[i].refer == R_SQL) {
1773 |         qi->search_type = R_SQL;
1774 |         query_str = create_query(Query[i], qc);
1775 | 
1776 |         if (query_str!= NULL) {
1777 |           qi->query_str = query_str;
1778 |           qis->instruction[i_no++] = qi;
1779 |         }
1780 |       }
1781 |       /* Radix Query */
1782 |       else if (Query[i].refer == R_RADIX) {
1783 |         qi->search_type = R_RADIX;
1784 | 	
1785 |         if (map_qc2rx(qi, qc) == 1) {
1786 | 	  int j;
1787 | 	  int found=0;
1788 | 	  
1789 |           /* check that there is no such query yet, for example if
1790 | 	     more than one keytype (wk) matched */
1791 | 	  for (j=0; j<i_no; j++) {
1792 | 	    Query_instruction *qij = qis->instruction[j];
1793 | 	    
1794 | 	    if(    qij->search_type == R_RADIX
1795 | 		   && Query[qij->queryindex].attribute 
1796 | 		   == Query[qi ->queryindex].attribute) {
1797 | 	      
1798 |               found=1;
1799 |               break;
1800 |             }
1801 |           }
1802 | 	  
1803 |           if ( found ) {
1804 |             /* Discard the Query Instruction */
1805 |             wr_free(qi);
1806 |           } 
1807 |           else {
1808 |             /* Add the query_instruction to the array */
1809 |             qis->instruction[i_no++] = qi;
1810 |           }
1811 |         }
1812 |       }
1813 |       else {
1814 | 	  /* ERROR: bad search_type */
1815 | 	  die;
1816 |       }
1817 |     }
1818 |   }
1819 |   qis->instruction[i_no++] = NULL;
1820 | 
1821 | 
1822 |   {  /* tracing */
1823 |       char *descrstr = QI_queries_to_string(qis);
1824 | 
1825 |       ER_dbg_va(FAC_QI, ASP_QI_COLL_GEN, "Queries: %s", descrstr );
1826 |       wr_free( descrstr );
1827 |   }
1828 | 
1829 |   return qis;
1830 | 
1831 | } /* QI_new() */
1832 | 
1833 | 
1834 |  
1835 | 
1836 | 
1837 | /*++++++++++++++++++++++++++++++++++++++
1838 |   
1839 |   char *QI_queries_to_string    returns a list of descriptions for queries 
1840 |                                 that will be performed (debugging only).
1841 | 				Allocated text, must be freed after use.
1842 | 
1843 |   Query_instructions *qis       query instructions structure
1844 | 
1845 |   Author:
1846 |      marek.
1847 |   ++++++++++++++++++++++++++++++++++++++*/
1848 | 
1849 | char *QI_queries_to_string(Query_instructions *qis)
1850 | {
1851 |    Query_instruction *qi;
1852 |    int i;
1853 |    char *resstr = NULL;
1854 | 
1855 |    dieif( wr_realloc((void **)&resstr, 2 ) != UT_OK);
1856 |    strcpy(resstr, "{");
1857 | 
1858 |    for( i = 0; ( qi=qis->instruction[i] ) != NULL;  i++ ) {
1859 |        char *descr = Query[qi->queryindex].descr;
1860 |        int oldres = strlen( resstr );
1861 |        
1862 |        dieif( wr_realloc((void **)&resstr, oldres+strlen(descr)+2) != UT_OK);
1863 |        strcat(resstr, descr);
1864 |        strcat(resstr, ",");
1865 |    }
1866 |    if( i>0 ) {
1867 |        /* cancel the last comma */
1868 |        resstr[strlen(resstr)-1] = 0;
1869 |    }
1870 | 
1871 |    dieif( wr_realloc((void **)&resstr, strlen( resstr ) + 2 ) 
1872 | 	  != UT_OK);
1873 |    strcat(resstr, "}");
1874 |    
1875 |    return resstr;
1876 | }