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