1    | /***************************************
2    |   $Revision: 1.10 $
3    | 
4    |   Query command module (qc).  This is what the whois query gets stored as in
5    |   memory.
6    | 
7    |   Status: NOT REVUED, NOT TESTED
8    | 
9    |   ******************/ /******************
10   |   Filename            : query_command.c
11   |   Author              : ottrey@ripe.net
12   |   OSs Tested          : Solaris
13   |   To Do               : Write some kind of options parser (to check for valid
14   |                         combinations of options.)
15   |   Comments            :
16   |   ******************/ /******************
17   |   Copyright (c) 1999                              RIPE NCC
18   |  
19   |   All Rights Reserved
20   |   
21   |   Permission to use, copy, modify, and distribute this software and its
22   |   documentation for any purpose and without fee is hereby granted,
23   |   provided that the above copyright notice appear in all copies and that
24   |   both that copyright notice and this permission notice appear in
25   |   supporting documentation, and that the name of the author not be
26   |   used in advertising or publicity pertaining to distribution of the
27   |   software without specific, written prior permission.
28   |   
29   |   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
30   |   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
31   |   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
32   |   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
33   |   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
34   |   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35   |   ***************************************/
36   | #include <stdlib.h>
37   | #include <stdio.h>
38   | 
39   | #include "query_command.h"
40   | #include "objects.h"
41   | #include "constants.h"
42   | #include "which_keytypes.h"
43   | 
44   | #define MAX_OPT_ARG_C 20
45   | 
46   | /*+ String sizes +*/
47   | #define STR_S   63
48   | #define STR_M   255
49   | #define STR_L   1023
50   | #define STR_XL  4095
51   | #define STR_XXL 16383
52   | 
53   | /* XXX These probably wont get used.  I'm using a switch statement instead.  -ottrey 5/7/99 */
54   | /*
55   | mask_t Inv_attr_mask;
56   | mask_t Object_mask;
57   | */
58   | 
59   | 
60   | /* QC_bitmap_to_string() */
61   | /*++++++++++++++++++++++++++++++++++++++
62   |   Convert the bitmap of attributes used in this query_command to a string.
63   | 
64   |   mask_t bitmap The bitmap of attribute to be converted.
65   |    
66   |   More:
67   |   +html+ <PRE>
68   |   Authors:
69   |         ottrey
70   |   +html+ </PRE><DL COMPACT>
71   |   +html+ <DT>Online References:
72   |   +html+ <DD><UL>
73   |   +html+ </UL></DL>
74   | 
75   |   ++++++++++++++++++++++++++++++++++++++*/
76   | char *QC_bitmap_to_string(mask_t bitmap) {
77   | 
78   |   return MA_to_string(bitmap, AT_get_attributes(), DUP_TOKENS, 0);
79   | 
80   | } /* QC_bitmap_to_string() */
81   | 
82   | /* my_getopt() */
83   | /*++++++++++++++++++++++++++++++++++++++
84   |   A thread safe version of getopt, used to get the options from the whois
85   |   query.
86   | 
87   |   int opt_argc The number of query arguments.
88   |   
89   |   char **opt_argv The query arguments.
90   |   
91   |   char *optstring The string containing valid options.
92   |   
93   |   int *my_optind_ptr A pointer to the index into the options of the option
94   |   returned.
95   |   
96   |   char **my_optarg_ptr A pointer to the arguments to be returned.
97   |    
98   |   More:
99   |   +html+ <PRE>
100  |   Authors:
101  |         ottrey
102  |   +html+ </PRE><DL COMPACT>
103  |   +html+ <DT>Online References:
104  |   +html+ <DD><UL>
105  |   +html+     <LI>man getopt
106  |   +html+ </UL></DL>
107  | 
108  |   ++++++++++++++++++++++++++++++++++++++*/
109  | static int my_getopt(int opt_argc, char **opt_argv, char *optstring, int *my_optind_ptr, char **my_optarg_ptr) {
110  |   int c='?';
111  |   int i, j;
112  |   int no_options;
113  |   int optind = *my_optind_ptr;
114  |   char option[3];
115  |   int option_matched=0;
116  |   
117  |   /* Get the number of options in the option string */
118  |   for(i=0, no_options=0; i < strlen(optstring) ; i++) {
119  |     if (optstring[i] != ':') {
120  |       no_options++;
121  |     }
122  |   }
123  | 
124  |   /* Iterate through all the option until it matches the current opt_argv */
125  |   /* Ie. opt_argv[optind] */
126  |   for (i=0, j=0; i <= no_options; i++, j++) {
127  |     /* Construct one option from the optstring */
128  |     option[0] = '-';
129  |     if (optstring[j] == ':') {
130  |       j++;
131  |     }
132  |     option[1] = optstring[j];
133  |     if ( optstring[j+1] == ':' ) {
134  |       option[2] = ':';
135  |     }
136  |     else {
137  |       option[2] = '\0';
138  |     }
139  |     option[3] = '\0';
140  | 
141  |     if (optind < opt_argc) {
142  |       if (strlen(opt_argv[optind]) > 0) {
143  |         /*
144  |         printf("opt_argv[%d] == option <==> %s == %s\n", optind, opt_argv[optind], option);
145  |         */
146  |         if (strncmp(opt_argv[optind], option, 2) == 0) {
147  |           /* Does the option have arguments. */
148  |           if (option[2] == ':') {
149  |             /* If the option has arguments */
150  |             if (strlen(opt_argv[optind]) > 2) {
151  |               /* If the arguments are in this token */
152  |               *my_optarg_ptr = (opt_argv[optind])+2;
153  |             }
154  |             else {
155  |               /* If the arguments are in the next token */
156  |               *my_optarg_ptr = opt_argv[optind+1];
157  |               optind++;
158  |             }
159  |           }
160  |           else {
161  |             /* There are no arguments to this token */
162  |             *my_optarg_ptr = NULL;
163  |           }
164  |           /* Option matched - break out of the search */
165  |           option_matched = 1;
166  |           break;
167  |         }
168  |       }
169  |     }
170  |   } /* for() */
171  |   
172  |   if ( option_matched == 1 ) {
173  |     /* This option was matched, return it. */
174  |     c = option[1];
175  | 
176  |     /* Move to the next opt_argv */
177  |     optind++;
178  |     *my_optind_ptr = optind;
179  |   }
180  |   else {
181  |     /* Discontinue search */
182  |     c = EOF;
183  |   }
184  | 
185  |   return c;
186  | 
187  | } /* my_getopt() */
188  | 
189  | /* QC_query_command_to_string() */
190  | /*++++++++++++++++++++++++++++++++++++++
191  |   Convert the query_command to a string.
192  | 
193  |   Query_command *query_command The query_command to be converted.
194  |    
195  |   More:
196  |   +html+ <PRE>
197  |   Authors:
198  |         ottrey
199  |   +html+ </PRE><DL COMPACT>
200  |   +html+ <DT>Online References:
201  |   +html+ <DD><UL>
202  |   +html+ </UL></DL>
203  | 
204  |   ++++++++++++++++++++++++++++++++++++++*/
205  | char *QC_query_command_to_string(Query_command *query_command) {
206  |   char *result;
207  |   char result_buf[STR_XL];
208  |   char *str1;
209  |   char *str2;
210  |   char *str3;
211  |   char *str4;
212  | 
213  |   str1 = QC_bitmap_to_string(query_command->inv_attrs_bitmap);
214  |   str2 = QC_bitmap_to_string(query_command->object_type_bitmap);
215  |   str3 = WK_to_string(query_command->keytypes_bitmap);
216  |   str4 = AT_sources_list_to_string(query_command->sources_list);
217  |   sprintf(result_buf, "Query_command : recursive=%d, inv_attrs=%s, k=%d, object_type=%s, sources=%s, (a=%d,g=%d,l=%d,m=%d,t=%d,v=%d,F=%d,L=%d,M=%d,R=%d,S=%d,V=%d), possible keytypes=%s, keys=[%s]\n",
218  |           query_command->recursive,
219  |           str1,
220  |           query_command->k,
221  |           str2,
222  |           str4,
223  |           query_command->a,
224  |           query_command->g,
225  |           query_command->l,
226  |           query_command->m,
227  |           query_command->t,
228  |           query_command->v,
229  |           query_command->F,
230  |           query_command->L,
231  |           query_command->M,
232  |           query_command->R,
233  |           query_command->S,
234  |           query_command->V,
235  |           str3,
236  |           query_command->keys);
237  |   free(str1);
238  |   free(str2);
239  |   free(str3);
240  |   free(str4);
241  | 
242  |   result = (char *)calloc(1, strlen(result_buf)+1);
243  |   strcpy(result, result_buf);
244  | 
245  |   return result;
246  |   
247  | } /* QC_query_command_to_string() */
248  | 
249  | /* log_command() */
250  | /*++++++++++++++++++++++++++++++++++++++
251  |   Log the command.
252  |   This is more to do with Tracing.  And should/will get merged with a tracing
253  |   module (when it is finalized.)
254  | 
255  |   char *query_str
256  |   
257  |   Query_command *query_command
258  |    
259  |   More:
260  |   +html+ <PRE>
261  |   Authors:
262  |         ottrey
263  |   +html+ </PRE><DL COMPACT>
264  |   +html+ <DT>Online References:
265  |   +html+ <DD><UL>
266  |   +html+ </UL></DL>
267  | 
268  |   ++++++++++++++++++++++++++++++++++++++*/
269  | static void log_command(char *query_str, Query_command *query_command) {
270  |   FILE *logf;
271  |   char *str;
272  | 
273  |   if (CO_get_comnd_logging() == 1) {
274  |     str = QC_query_command_to_string(query_command);
275  |     if (strcmp(CO_get_comnd_logfile(), "stdout") == 0) {
276  |       printf("query=[%s]\n%s", query_str, str);
277  |     }
278  |     else {
279  |       logf = fopen(CO_get_comnd_logfile(), "a");
280  |       fprintf(logf, "query=[%s]\n%s", query_str, str);
281  |       fclose(logf);
282  |     }
283  |     free(str);
284  |   }
285  | 
286  | } /* log_command() */
287  | 
288  | /* QC_free() */
289  | /*++++++++++++++++++++++++++++++++++++++
290  |   Free the query_command.
291  | 
292  |   Query_command *qc query_command to be freed.
293  | 
294  |   XXX I'm not sure the bitmaps will get freed.
295  |   qc->inv_attrs_bitmap
296  |   qc->object_type_bitmap
297  |   qc->keytypes_bitmap
298  | 
299  |   More:
300  |   +html+ <PRE>
301  |   Authors:
302  |         ottrey
303  |   +html+ </PRE><DL COMPACT>
304  |   +html+ <DT>Online References:
305  |   +html+ <DD><UL>
306  |   +html+ </UL></DL>
307  | 
308  |   ++++++++++++++++++++++++++++++++++++++*/
309  | void QC_free(Query_command *qc) {
310  |   if (qc != NULL) {
311  |     if (qc->keys != NULL) {
312  |       free(qc->keys);
313  |     }
314  | 
315  |     g_list_free(qc->sources_list);
316  | 
317  |     free(qc);
318  |   }
319  | } /* QC_free() */
320  | 
321  | /* QC_new() */
322  | /*++++++++++++++++++++++++++++++++++++++
323  |   Create a new query_command.
324  | 
325  |   char *query_str The garden variety whois query string.
326  | 
327  |   int sock The client socket.
328  | 
329  |   Pre-condition: OB_init() must be called before this.
330  |                  Ie the objects have to be created first.
331  | 
332  |   XXX sock shouldn't be passed here.  But it needs to in order to report errors to the client.
333  |   Doh!.... this needs some looking into.
334  |    
335  |   More:
336  |   +html+ <PRE>
337  |   Authors:
338  |         ottrey
339  |   +html+ </PRE><DL COMPACT>
340  |   +html+ <DT>Online References:
341  |   +html+ <DD><UL>
342  |   +html+ </UL></DL>
343  | 
344  |   ++++++++++++++++++++++++++++++++++++++*/
345  | Query_command *QC_new(char *query_str, int sock) {
346  |   char *my_optarg;
347  |   int my_optind;
348  |   int c;
349  |   int errflg = 0;
350  |   char *inv_attrs_str = NULL;
351  |   char *object_types_str = NULL;
352  |   char *sources_str = NULL;
353  |   int opt_argc;
354  |   char *opt_argv[MAX_OPT_ARG_C];
355  |   char *value;
356  |   char *tmp_query_str;
357  |   char tmp_query_str_buf[STR_L];
358  |   int key_length;
359  |   int i;
360  | 
361  |   Query_command *query_command;
362  | 
363  |   int index;
364  |   int type;
365  |   int attr;
366  |   int offset;
367  | 
368  |   char *str;
369  |   char str_buf[STR_XL];
370  | 
371  |   query_command = (Query_command *)calloc(1, sizeof(Query_command)+1);
372  |   query_command->a = 0;
373  |   query_command->g = 0;
374  |   query_command->inv_attrs_bitmap = MA_new(MA_END);
375  |   query_command->k = 0;
376  |   query_command->recursive = 1;
377  |   query_command->l = 0;
378  |   query_command->m = 0;
379  |   /* The 0th source is initialized by default. */
380  |   query_command->sources_list = g_list_append(query_command->sources_list, (void *)AT_get_source(0));
381  |   query_command->t = 0;
382  |   query_command->v = 0;
383  |   query_command->F = 0;
384  |   query_command->L = 0;
385  |   query_command->M = 0;
386  |   query_command->R = 0;
387  |   query_command->S = 0;
388  |   query_command->object_type_bitmap = MA_new(OBJECT_MASK);
389  |   query_command->V = 0;
390  |   /*
391  |   query_command->keytypes_bitmap = MA_new(MA_END);
392  |   */
393  |   query_command->keys = NULL;
394  | 
395  |   my_optind=1;
396  | 
397  |   /* This is so Marek can't crash me :-) */
398  |   /* Side Effect - query keys are subsequently cut short to STR_L size. */
399  |   /*
400  |   tmp_query_str = (char *)calloc(1, strlen(query_str));
401  |   strcpy(tmp_query_str, query_str, STR_L);
402  |   */
403  |   strncpy(tmp_query_str_buf, query_str, STR_L-1);
404  |   tmp_query_str_buf[STR_L-1] = '\0';
405  | 
406  |   opt_argv[0] = NULL;
407  |   opt_argv[1] = (char *)strtok(tmp_query_str_buf, " ");
408  |   opt_argc = 2;
409  |   while ( (opt_argv[opt_argc] = (char *)strtok(NULL, " ")) != NULL ) {
410  |     opt_argc++;
411  |     /* I really would like to put this statement in the while clause, but
412  |        I don't think we can be sure which order it will execute the two
413  |        parts in.  - So I'll leave it here. */
414  |     if ( opt_argc >= MAX_OPT_ARG_C ) break;
415  |   }
416  |   opt_argv[opt_argc] = NULL;
417  | 
418  | 
419  |   while ((c = my_getopt(opt_argc, opt_argv, "agi:klrms:t:v:FLMRST:V", &my_optind, &my_optarg)) != EOF) {
420  |     switch (c) {
421  |       case 'a':
422  |         /* The 0th source is initialized already by default. - so don't add it again. */
423  |         /* Add the rest of the sources to the list. */
424  |         for (i=1; AT_get_source(i) != NULL; i++) {
425  |           query_command->sources_list = g_list_append(query_command->sources_list, (void *)AT_get_source(i));
426  |         }
427  |       break;
428  | 
429  |       case 'g':
430  |         query_command->g=1;
431  |       break;
432  | 
433  |       case 'i':
434  |         if (my_optarg != NULL) {
435  |           inv_attrs_str = my_optarg;
436  |           while (*inv_attrs_str) {
437  |             index = getsubopt(&inv_attrs_str, AT_get_attributes(), &value);
438  |             if (index == -1) {
439  |               attr = -1;
440  |               strcpy(str_buf, "");
441  |               sprintf(str_buf, "Unkown attribute encountered.\n"); 
442  |               SK_puts(sock, str_buf);
443  |               errflg++;
444  |             }
445  |             else {
446  |               attr = index/DUP_TOKENS;
447  |               if ( MA_isset(OB_get_inv_attr_mask(), attr) == 1 ) {
448  |                 /* Add the attr to the bitmap. */
449  |                 MA_set(&(query_command->inv_attrs_bitmap), attr, 1);
450  |               }
451  |               else {
452  |                 strcpy(str_buf, "");
453  |                 sprintf(str_buf, "\"%s\" does not belong to inv_attr.\n", (AT_get_attributes())[index]); 
454  |                 SK_puts(sock, str_buf);
455  |                 errflg++;
456  |               }
457  |             } 
458  |           } /* while () */
459  |         } /* if () */
460  |       break;
461  | 
462  |       case 'k':
463  |         query_command->k = 1;
464  |       break;
465  | 
466  |       case 'r':
467  |         query_command->recursive = 0;
468  |       break;
469  | 
470  |       case 'l':
471  |         query_command->l=1;
472  |       break;
473  | 
474  |       case 'm':
475  |         query_command->m=1;
476  |       break;
477  | 
478  |       case 's':
479  |         if (my_optarg != NULL) {
480  |           sources_str = my_optarg;
481  |           /* The 0th source is initialized already by default. - so remove it first. */
482  |           query_command->sources_list = g_list_remove(query_command->sources_list, (void *)AT_get_source(0));
483  |           while (*sources_str) {
484  |             index = getsubopt(&sources_str, AT_get_sources(), &value);
485  |             if (index == -1) {
486  |               strcpy(str_buf, "");
487  |               sprintf(str_buf, "Unkown source encountered.\nNot one of: %s\n", AT_sources_to_string()); 
488  |               SK_puts(sock, str_buf);
489  |               errflg++;
490  |             }
491  |             else {
492  |               query_command->sources_list = g_list_append(query_command->sources_list, (void *)AT_get_source(index));
493  |             } 
494  |           } /* while () */
495  |         } /* if () */
496  |         /*
497  |         query_command->s=1;
498  |         */
499  |       break;
500  | 
501  |       case 't':
502  |         if (my_optarg != NULL) {
503  |           object_types_str = my_optarg;
504  |           while (*object_types_str) {
505  |             index = getsubopt(&object_types_str, AT_get_attributes(), &value);
506  |             if (index == -1) {
507  |               strcpy(str_buf, "");
508  |               sprintf(str_buf, "Unkown object encountered.\n"); 
509  |               SK_puts(sock, str_buf);
510  |               errflg++;
511  |             }
512  |             else {
513  |               type = index/DUP_TOKENS;
514  |               query_command->t=type;
515  |             }
516  |           }
517  |         }
518  |       break;
519  | 
520  |       case 'v':
521  |         if (my_optarg != NULL) {
522  |           object_types_str = my_optarg;
523  |           if (*object_types_str) {
524  |             index = getsubopt(&object_types_str, AT_get_attributes(), &value);
525  |             if (index == -1) {
526  |               strcpy(str_buf, "");
527  |               sprintf(str_buf, "Unkown object encountered.\n"); 
528  |               SK_puts(sock, str_buf);
529  |               errflg++;
530  |             }
531  |             else {
532  |               type = index/DUP_TOKENS;
533  |               query_command->v=type;
534  |             }
535  |           }
536  |         }
537  |       break;
538  | 
539  |       case 'F':
540  |         query_command->F=1;
541  |       break;
542  | 
543  |       case 'L':
544  |         query_command->L=1;
545  |       break;
546  | 
547  |       case 'M':
548  |         query_command->M=1;
549  |       break;
550  | 
551  |       case 'R':
552  |         query_command->R=1;
553  |       break;
554  | 
555  |       case 'S':
556  |         query_command->S=1;
557  |       break;
558  | 
559  |       case 'T':
560  |         /* XXX This is a bit tricky.
561  |            The bits are initialized to be all set.
562  |            Then each encountered bit is unset.
563  |            Finally the result is "not'ed by using XOR with the original. */
564  |         if (my_optarg != NULL) {
565  |           mask_t tmp = MA_new(OBJECT_MASK);
566  |           mask_t original = MA_new(OBJECT_MASK);
567  |           object_types_str = my_optarg;
568  |           while (*object_types_str) {
569  |             index = getsubopt(&object_types_str, AT_get_attributes(), &value);
570  |             if (index == -1) {
571  |               strcpy(str_buf, "");
572  |               sprintf(str_buf, "Unkown attribute encountered.\n"); 
573  |               SK_puts(sock, str_buf);
574  |               errflg++;
575  |             }
576  |             else {
577  |               type = index/DUP_TOKENS;
578  |               if ( MA_isset(OB_get_object_mask(), type) == 1 ) {
579  |                 /* Add the type to the bitmap. */
580  |                 MA_set(&tmp, type, 0);
581  |               }
582  |               else {
583  |                 strcpy(str_buf, "");
584  |                 sprintf(str_buf, "\"%s\" does not belong to object_type.\n", (AT_get_attributes())[index]); 
585  |                 SK_puts(sock, str_buf);
586  |                 errflg++;
587  |               }
588  |             }
589  |           }
590  |           query_command->object_type_bitmap = MA_xor(original, tmp);
591  |           MA_free(&original);
592  |           MA_free(&tmp);
593  |         }
594  |       break;
595  | 
596  |       case 'V':
597  |         query_command->V=1;
598  |       break;
599  | 
600  |       case '?':
601  |         errflg++;
602  |       break;
603  | 
604  |       default:
605  |         errflg++;
606  |     }
607  |   }
608  | 
609  |   /* XXX Report the error.  This could be improved. */
610  |   if (opt_argv[my_optind] != NULL) {
611  |     if ( (errflg) || (strncmp(opt_argv[my_optind], "-", 1) == 0) ) {
612  |       strncpy(str_buf, USAGE, STR_XL-1);
613  |       SK_puts(sock, str_buf);
614  |     }
615  |   }
616  |   else {
617  |     if (errflg) {
618  |       strncpy(str_buf, USAGE, STR_XL-1);
619  |       SK_puts(sock, str_buf);
620  |     }
621  |   }
622  |     
623  | 
624  |   /* Work out the length of space needed */
625  |   key_length = 0;
626  |   for (i=my_optind ; i < opt_argc; i++) {
627  |     /* length for the string + 1 for the '\0'+ 1 for the ' ' + 1 for good luck. */
628  |     if (opt_argv[i] != NULL) {
629  |       key_length += strlen(opt_argv[i])+3;
630  |     }
631  |   }
632  | 
633  |   query_command->keys = (char *)calloc(1, key_length+1);
634  |   strcpy(query_command->keys, "");
635  |   if (errflg == 0) {
636  |     for (i=my_optind; i < opt_argc; i++) {
637  |       strcat(query_command->keys, opt_argv[i]);
638  |       if ( (i + 1) < opt_argc) {
639  |         strcat(query_command->keys, " ");
640  |       }
641  |     }
642  |   } /* XXX - Be careful about where this brace goes. */
643  | 
644  |   /* Now we don't need this anymore */
645  |   /*
646  |   free(tmp_query_str);
647  |   */
648  | 
649  |   /* Now convert the key to uppercase. */
650  |   for (i=0; i <= key_length; i++) {
651  |     query_command->keys[i] = toupper(query_command->keys[i]);
652  |   }
653  | 
654  |   /* Now make the keytypes_bitmap. */
655  |   query_command->keytypes_bitmap = WK_new(query_command->keys);
656  | 
657  |   if ( CO_get_comnd_logging() == 1 ) {
658  |     log_command(query_str, query_command);
659  |   }
660  | 
661  |   return query_command;
662  | 
663  | } /* QC_new() */
664  | 
665  | /* QC_environ_update() */
666  | /*++++++++++++++++++++++++++++++++++++++
667  |   Update the query environment.
668  |   Return the new environment?
669  |   This will include things like setting recursion, and the sources, and other
670  |   options deemed to be of an environmental type.
671  |   XXX This hasn't been implemented yet.  Haven't quite decided what to do here
672  |   yet.
673  |   (I personally don't like functions that change two things at once, so that
674  |   may also be looked into aswell.)
675  | 
676  |   Query_command *qc The query command to be updated.
677  |   
678  |   Query_command *qe The current environment.
679  |    
680  |   More:
681  |   +html+ <PRE>
682  |   Authors:
683  |         ottrey
684  |   +html+ </PRE><DL COMPACT>
685  |   +html+ <DT>Online References:
686  |   +html+ <DD><UL>
687  |   +html+ </UL></DL>
688  | 
689  |   ++++++++++++++++++++++++++++++++++++++*/
690  | Query_command *QC_environ_update(Query_command *qc, Query_command *qe) {
691  |   Query_command *query_command;
692  | 
693  |   query_command = (Query_command *)calloc(1, sizeof(Query_command)+1);
694  | 
695  |   query_command->recursive = 1;
696  | 
697  |   return query_command;
698  | 
699  | } /* QC_environ_update() */