r15370: Fix more dependencies for shared libs
[ira/wip.git] / source4 / lib / ldb / tools / cmdline.c
1 /* 
2    ldb database library - command line handling for ldb tools
3
4    Copyright (C) Andrew Tridgell  2005
5
6      ** NOTE! The following LGPL license applies to the ldb
7      ** library. This does NOT imply that all of Samba is released
8      ** under the LGPL
9    
10    This library is free software; you can redistribute it and/or
11    modify it under the terms of the GNU Lesser General Public
12    License as published by the Free Software Foundation; either
13    version 2 of the License, or (at your option) any later version.
14
15    This library is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18    Lesser General Public License for more details.
19
20    You should have received a copy of the GNU Lesser General Public
21    License along with this library; if not, write to the Free Software
22    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 */
24
25 #include "includes.h"
26 #include "ldb/include/includes.h"
27 #include "ldb/tools/cmdline.h"
28
29 #ifdef _SAMBA_BUILD_
30 #include "lib/cmdline/popt_common.h"
31 #include "lib/ldb/samba/ldif_handlers.h"
32 #include "auth/auth.h"
33 #include "db_wrap.h"
34 #endif
35
36 /*
37   process command line options
38 */
39 struct ldb_cmdline *ldb_cmdline_process(struct ldb_context *ldb, int argc, const char **argv,
40                                         void (*usage)(void))
41 {
42         struct ldb_cmdline options, *ret=NULL;
43         poptContext pc;
44 #ifdef _SAMBA_BUILD_
45         int r;
46 #endif
47     int num_options = 0;
48         int opt;
49         struct poptOption popt_options[] = {
50                 POPT_AUTOHELP
51                 { "url",       'H', POPT_ARG_STRING, &options.url, 0, "database URL", "URL" },
52                 { "basedn",    'b', POPT_ARG_STRING, &options.basedn, 0, "base DN", "DN" },
53                 { "editor",    'e', POPT_ARG_STRING, &options.editor, 0, "external editor", "PROGRAM" },
54                 { "scope",     's', POPT_ARG_STRING, NULL, 's', "search scope", "SCOPE" },
55                 { "verbose",   'v', POPT_ARG_NONE, NULL, 'v', "increase verbosity", NULL },
56                 { "interactive", 'i', POPT_ARG_NONE, &options.interactive, 0, "input from stdin", NULL },
57                 { "recursive", 'r', POPT_ARG_NONE, &options.recursive, 0, "recursive delete", NULL },
58                 { "num-searches", 0, POPT_ARG_INT, &options.num_searches, 0, "number of test searches", NULL },
59                 { "num-records", 0, POPT_ARG_INT, &options.num_records, 0, "number of test records", NULL },
60                 { "all", 'a',    POPT_ARG_NONE, &options.all_records, 0, "(|(objectClass=*)(distinguishedName=*))", NULL },
61                 { "nosync", 0,   POPT_ARG_NONE, &options.nosync, 0, "non-synchronous transactions", NULL },
62                 { "sorted", 'S', POPT_ARG_NONE, &options.sorted, 0, "sort attributes", NULL },
63                 { "sasl-mechanism", 0, POPT_ARG_STRING, &options.sasl_mechanism, 0, "choose SASL mechanism", "MECHANISM" },
64                 { "input", 'I', POPT_ARG_STRING, &options.input, 0, "Input File", "Input" },
65                 { "output", 'O', POPT_ARG_STRING, &options.output, 0, "Output File", "Output" },
66                 { NULL,    'o', POPT_ARG_STRING, NULL, 'o', "ldb_connect option", "OPTION" },
67                 { "controls", 0, POPT_ARG_STRING, NULL, 'c', "controls", NULL },
68 #ifdef _SAMBA_BUILD_
69                 POPT_COMMON_SAMBA
70                 POPT_COMMON_CREDENTIALS
71                 POPT_COMMON_VERSION
72 #endif
73                 POPT_TABLEEND
74         };
75
76         ldb_global_init();
77
78 #ifdef _SAMBA_BUILD_
79         r = ldb_register_samba_handlers(ldb);
80         if (r != 0) {
81                 goto failed;
82         }
83
84 #endif
85
86         ret = talloc_zero(ldb, struct ldb_cmdline);
87         if (ret == NULL) {
88                 ldb_oom(ldb);
89                 goto failed;
90         }
91
92         options = *ret;
93         
94         /* pull in URL */
95         options.url = getenv("LDB_URL");
96
97         /* and editor (used by ldbedit) */
98         options.editor = getenv("VISUAL");
99         if (!options.editor) {
100                 options.editor = getenv("EDITOR");
101         }
102         if (!options.editor) {
103                 options.editor = "vi";
104         }
105
106         options.scope = LDB_SCOPE_DEFAULT;
107
108         pc = poptGetContext(argv[0], argc, argv, popt_options, 
109                             POPT_CONTEXT_KEEP_FIRST);
110
111         while((opt = poptGetNextOpt(pc)) != -1) {
112                 switch (opt) {
113                 case 's': {
114                         const char *arg = poptGetOptArg(pc);
115                         if (strcmp(arg, "base") == 0) {
116                                 options.scope = LDB_SCOPE_BASE;
117                         } else if (strcmp(arg, "sub") == 0) {
118                                 options.scope = LDB_SCOPE_SUBTREE;
119                         } else if (strcmp(arg, "one") == 0) {
120                                 options.scope = LDB_SCOPE_ONELEVEL;
121                         } else {
122                                 fprintf(stderr, "Invalid scope '%s'\n", arg);
123                                 goto failed;
124                         }
125                         break;
126                 }
127
128                 case 'v':
129                         options.verbose++;
130                         break;
131
132                 case 'o':
133                         options.options = talloc_realloc(ret, options.options, 
134                                                          const char *, num_options+3);
135                         if (options.options == NULL) {
136                                 ldb_oom(ldb);
137                                 goto failed;
138                         }
139                         options.options[num_options] = poptGetOptArg(pc);
140                         options.options[num_options+1] = NULL;
141                         num_options++;
142                         break;
143
144                 case 'c': {
145                         const char *cs = poptGetOptArg(pc);
146                         const char *p, *q;
147                         int cc;
148
149                         for (p = cs, cc = 1; (q = strchr(p, ',')); cc++, p = q + 1) ;
150
151                         options.controls = talloc_array(ret, char *, cc + 1);
152                         if (options.controls == NULL) {
153                                 ldb_oom(ldb);
154                                 goto failed;
155                         }
156                         for (p = cs, cc = 0; p != NULL; cc++) {
157                                 const char *t;
158
159                                 t = strchr(p, ',');
160                                 if (t == NULL) {
161                                         options.controls[cc] = talloc_strdup(options.controls, p);
162                                         p = NULL;
163                                 } else {
164                                         options.controls[cc] = talloc_strndup(options.controls, p, t-p);
165                                         p = t + 1;
166                                 }
167                         }
168                         options.controls[cc] = NULL;
169
170                         break;    
171                 }
172                 default:
173                         fprintf(stderr, "Invalid option %s: %s\n", 
174                                 poptBadOption(pc, 0), poptStrerror(opt));
175                         if (usage) usage();
176                         goto failed;
177                 }
178         }
179
180         /* setup the remaining options for the main program to use */
181         options.argv = poptGetArgs(pc);
182         if (options.argv) {
183                 options.argv++;
184                 while (options.argv[options.argc]) options.argc++;
185         }
186
187         *ret = options;
188
189         /* all utils need some option */
190         if (ret->url == NULL) {
191                 fprintf(stderr, "You must supply a url with -H or with $LDB_URL\n");
192                 if (usage) usage();
193                 goto failed;
194         }
195
196         if (strcmp(ret->url, "NONE") != 0) {
197                 int flags = 0;
198                 if (options.nosync) {
199                         flags |= LDB_FLG_NOSYNC;
200                 }
201
202 #ifdef _SAMBA_BUILD_
203                 /* Must be after we have processed command line options */
204                 gensec_init(); 
205
206                 if (ldb_set_opaque(ldb, "sessionInfo", system_session(ldb))) {
207                         goto failed;
208                 }
209                 if (ldb_set_opaque(ldb, "credentials", cmdline_credentials)) {
210                         goto failed;
211                 }
212                 ldb_set_utf8_fns(ldb, NULL, wrap_casefold);
213 #endif
214                 if (ldb_connect(ldb, ret->url, flags, ret->options) != 0) {
215                         fprintf(stderr, "Failed to connect to %s - %s\n", 
216                                 ret->url, ldb_errstring(ldb));
217                         goto failed;
218                 }
219         }
220
221         return ret;
222
223 failed:
224         talloc_free(ret);
225         exit(1);
226         return NULL;
227 }
228
229 struct ldb_control **parse_controls(void *mem_ctx, char **control_strings)
230 {
231         int i;
232         struct ldb_control **ctrl;
233
234         if (control_strings == NULL || control_strings[0] == NULL)
235                 return NULL;
236
237         for (i = 0; control_strings[i]; i++);
238
239         ctrl = talloc_array(mem_ctx, struct ldb_control *, i + 1);
240
241         for (i = 0; control_strings[i]; i++) {
242                 if (strncmp(control_strings[i], "vlv:", 4) == 0) {
243                         struct ldb_vlv_req_control *control;
244                         const char *p;
245                         char attr[1024];
246                         char ctxid[1024];
247                         int crit, bc, ac, os, cc, ret;
248
249                         attr[0] = '\0';
250                         ctxid[0] = '\0';
251                         p = &(control_strings[i][4]);
252                         ret = sscanf(p, "%d:%d:%d:%d:%d:%1023[^$]", &crit, &bc, &ac, &os, &cc, ctxid);
253                         if (ret < 5) {
254                                 ret = sscanf(p, "%d:%d:%d:%1023[^:]:%1023[^$]", &crit, &bc, &ac, attr, ctxid);
255                         }
256                                
257                         if ((ret < 4) || (crit < 0) || (crit > 1)) {
258                                 fprintf(stderr, "invalid server_sort control syntax\n");
259                                 fprintf(stderr, " syntax: crit(b):bc(n):ac(n):<os(n):cc(n)|attr(s)>[:ctxid(o)]\n");
260                                 fprintf(stderr, "   note: b = boolean, n = number, s = string, o = b64 binary blob\n");
261                                 return NULL;
262                         }
263                         ctrl[i] = talloc(ctrl, struct ldb_control);
264                         ctrl[i]->oid = LDB_CONTROL_VLV_REQ_OID;
265                         ctrl[i]->critical = crit;
266                         control = talloc(ctrl[i], struct ldb_vlv_req_control);
267                         control->beforeCount = bc;
268                         control->afterCount = ac;
269                         if (attr[0]) {
270                                 control->type = 1;
271                                 control->match.gtOrEq.value = talloc_strdup(control, attr);
272                                 control->match.gtOrEq.value_len = strlen(attr);
273                         } else {
274                                 control->type = 0;
275                                 control->match.byOffset.offset = os;
276                                 control->match.byOffset.contentCount = cc;
277                         }
278                         if (ctxid[0]) {
279                                 control->ctxid_len = ldb_base64_decode(ctxid);
280                                 control->contextId = talloc_memdup(control, ctxid, control->ctxid_len);
281                         } else {
282                                 control->ctxid_len = 0;
283                                 control->contextId = NULL;
284                         }
285                         ctrl[i]->data = control;
286
287                         continue;
288                 }
289
290                 if (strncmp(control_strings[i], "dirsync:", 8) == 0) {
291                         struct ldb_dirsync_control *control;
292                         const char *p;
293                         char cookie[1024];
294                         int crit, flags, max_attrs, ret;
295                        
296                         cookie[0] = '\0';
297                         p = &(control_strings[i][8]);
298                         ret = sscanf(p, "%d:%d:%d:%1023[^$]", &crit, &flags, &max_attrs, cookie);
299
300                         if ((ret < 3) || (crit < 0) || (crit > 1) || (flags < 0) || (max_attrs < 0)) {
301                                 fprintf(stderr, "invalid dirsync control syntax\n");
302                                 fprintf(stderr, " syntax: crit(b):flags(n):max_attrs(n)[:cookie(o)]\n");
303                                 fprintf(stderr, "   note: b = boolean, n = number, o = b64 binary blob\n");
304                                 return NULL;
305                         }
306
307                         /* w2k3 seems to ignore the parameter,
308                          * but w2k sends a wrong cookie when this value is to small
309                          * this would cause looping forever, while getting
310                          * the same data and same cookie forever
311                          */
312                         if (max_attrs == 0) max_attrs = 0x0FFFFFFF;
313
314                         ctrl[i] = talloc(ctrl, struct ldb_control);
315                         ctrl[i]->oid = LDB_CONTROL_DIRSYNC_OID;
316                         ctrl[i]->critical = crit;
317                         control = talloc(ctrl[i], struct ldb_dirsync_control);
318                         control->flags = flags;
319                         control->max_attributes = max_attrs;
320                         if (*cookie) {
321                                 control->cookie_len = ldb_base64_decode(cookie);
322                                 control->cookie = talloc_memdup(control, cookie, control->cookie_len);
323                         } else {
324                                 control->cookie = NULL;
325                                 control->cookie_len = 0;
326                         }
327                         ctrl[i]->data = control;
328
329                         continue;
330                 }
331
332                 if (strncmp(control_strings[i], "asq:", 4) == 0) {
333                         struct ldb_asq_control *control;
334                         const char *p;
335                         char attr[256];
336                         int crit, ret;
337
338                         attr[0] = '\0';
339                         p = &(control_strings[i][4]);
340                         ret = sscanf(p, "%d:%255[^$]", &crit, attr);
341                         if ((ret != 2) || (crit < 0) || (crit > 1) || (attr[0] == '\0')) {
342                                 fprintf(stderr, "invalid asq control syntax\n");
343                                 fprintf(stderr, " syntax: crit(b):attr(s)\n");
344                                 fprintf(stderr, "   note: b = boolean, s = string\n");
345                                 return NULL;
346                         }
347
348                         ctrl[i] = talloc(ctrl, struct ldb_control);
349                         ctrl[i]->oid = LDB_CONTROL_ASQ_OID;
350                         ctrl[i]->critical = crit;
351                         control = talloc(ctrl[i], struct ldb_asq_control);
352                         control->request = 1;
353                         control->source_attribute = talloc_strdup(control, attr);
354                         control->src_attr_len = strlen(attr);
355                         ctrl[i]->data = control;
356
357                         continue;
358                 }
359
360                 if (strncmp(control_strings[i], "extended_dn:", 12) == 0) {
361                         struct ldb_extended_dn_control *control;
362                         const char *p;
363                         int crit, type, ret;
364
365                         p = &(control_strings[i][12]);
366                         ret = sscanf(p, "%d:%d", &crit, &type);
367                         if ((ret != 2) || (crit < 0) || (crit > 1) || (type < 0) || (type > 1)) {
368                                 fprintf(stderr, "invalid extended_dn control syntax\n");
369                                 fprintf(stderr, " syntax: crit(b):type(b)\n");
370                                 fprintf(stderr, "   note: b = boolean\n");
371                                 return NULL;
372                         }
373
374                         ctrl[i] = talloc(ctrl, struct ldb_control);
375                         ctrl[i]->oid = LDB_CONTROL_EXTENDED_DN_OID;
376                         ctrl[i]->critical = crit;
377                         control = talloc(ctrl[i], struct ldb_extended_dn_control);
378                         control->type = type;
379                         ctrl[i]->data = control;
380
381                         continue;
382                 }
383
384                 if (strncmp(control_strings[i], "paged_results:", 14) == 0) {
385                         struct ldb_paged_control *control;
386                         const char *p;
387                         int crit, size, ret;
388                        
389                         p = &(control_strings[i][14]);
390                         ret = sscanf(p, "%d:%d", &crit, &size);
391
392                         if ((ret != 2) || (crit < 0) || (crit > 1) || (size < 0)) {
393                                 fprintf(stderr, "invalid paged_results control syntax\n");
394                                 fprintf(stderr, " syntax: crit(b):size(n)\n");
395                                 fprintf(stderr, "   note: b = boolean, n = number\n");
396                                 return NULL;
397                         }
398
399                         ctrl[i] = talloc(ctrl, struct ldb_control);
400                         ctrl[i]->oid = LDB_CONTROL_PAGED_RESULTS_OID;
401                         ctrl[i]->critical = crit;
402                         control = talloc(ctrl[i], struct ldb_paged_control);
403                         control->size = size;
404                         control->cookie = NULL;
405                         control->cookie_len = 0;
406                         ctrl[i]->data = control;
407
408                         continue;
409                 }
410
411                 if (strncmp(control_strings[i], "server_sort:", 12) == 0) {
412                         struct ldb_server_sort_control **control;
413                         const char *p;
414                         char attr[256];
415                         char rule[128];
416                         int crit, rev, ret;
417
418                         attr[0] = '\0';
419                         rule[0] = '\0';
420                         p = &(control_strings[i][12]);
421                         ret = sscanf(p, "%d:%d:%255[^:]:%127[^:]", &crit, &rev, attr, rule);
422                         if ((ret < 3) || (crit < 0) || (crit > 1) || (rev < 0 ) || (rev > 1) ||attr[0] == '\0') {
423                                 fprintf(stderr, "invalid server_sort control syntax\n");
424                                 fprintf(stderr, " syntax: crit(b):rev(b):attr(s)[:rule(s)]\n");
425                                 fprintf(stderr, "   note: b = boolean, s = string\n");
426                                 return NULL;
427                         }
428                         ctrl[i] = talloc(ctrl, struct ldb_control);
429                         ctrl[i]->oid = LDB_CONTROL_SERVER_SORT_OID;
430                         ctrl[i]->critical = crit;
431                         control = talloc_array(ctrl[i], struct ldb_server_sort_control *, 2);
432                         control[0] = talloc(control, struct ldb_server_sort_control);
433                         control[0]->attributeName = talloc_strdup(control, attr);
434                         if (rule[0])
435                                 control[0]->orderingRule = talloc_strdup(control, rule);
436                         else
437                                 control[0]->orderingRule = NULL;
438                         control[0]->reverse = rev;
439                         control[1] = NULL;
440                         ctrl[i]->data = control;
441
442                         continue;
443                 }
444
445                 if (strncmp(control_strings[i], "notification:", 13) == 0) {
446                         const char *p;
447                         int crit, ret;
448
449                         p = &(control_strings[i][13]);
450                         ret = sscanf(p, "%d", &crit);
451                         if ((ret != 1) || (crit < 0) || (crit > 1)) {
452                                 fprintf(stderr, "invalid notification control syntax\n");
453                                 fprintf(stderr, " syntax: crit(b)\n");
454                                 fprintf(stderr, "   note: b = boolean\n");
455                                 return NULL;
456                         }
457
458                         ctrl[i] = talloc(ctrl, struct ldb_control);
459                         ctrl[i]->oid = LDB_CONTROL_NOTIFICATION_OID;
460                         ctrl[i]->critical = crit;
461                         ctrl[i]->data = NULL;
462
463                         continue;
464                 }
465
466                 /* no controls matched, throw an error */
467                 fprintf(stderr, "Invalid control name\n");
468                 return NULL;
469         }
470
471         ctrl[i] = NULL;
472
473         return ctrl;
474 }
475
476
477 /* this function check controls reply and determines if more
478  * processing is needed setting up the request controls correctly
479  *
480  * returns:
481  *      -1 error
482  *      0 all ok
483  *      1 all ok, more processing required
484  */
485 int handle_controls_reply(struct ldb_control **reply, struct ldb_control **request)
486 {
487         int i, j;
488         int ret = 0;
489
490         if (reply == NULL || request == NULL) return -1;
491         
492         for (i = 0; reply[i]; i++) {
493                 if (strcmp(LDB_CONTROL_VLV_RESP_OID, reply[i]->oid) == 0) {
494                         struct ldb_vlv_resp_control *rep_control;
495
496                         rep_control = talloc_get_type(reply[i]->data, struct ldb_vlv_resp_control);
497                         
498                         /* check we have a matching control in the request */
499                         for (j = 0; request[j]; j++) {
500                                 if (strcmp(LDB_CONTROL_VLV_REQ_OID, request[j]->oid) == 0)
501                                         break;
502                         }
503                         if (! request[j]) {
504                                 fprintf(stderr, "Warning VLV reply received but no request have been made\n");
505                                 continue;
506                         }
507
508                         /* check the result */
509                         if (rep_control->vlv_result != 0) {
510                                 fprintf(stderr, "Warning: VLV not performed with error: %d\n", rep_control->vlv_result);
511                         } else {
512                                 fprintf(stderr, "VLV Info: target position = %d, content count = %d\n", rep_control->targetPosition, rep_control->contentCount);
513                         }
514
515                         continue;
516                 }
517
518                 if (strcmp(LDB_CONTROL_ASQ_OID, reply[i]->oid) == 0) {
519                         struct ldb_asq_control *rep_control;
520
521                         rep_control = talloc_get_type(reply[i]->data, struct ldb_asq_control);
522
523                         /* check the result */
524                         if (rep_control->result != 0) {
525                                 fprintf(stderr, "Warning: ASQ not performed with error: %d\n", rep_control->result);
526                         }
527
528                         continue;
529                 }
530
531                 if (strcmp(LDB_CONTROL_PAGED_RESULTS_OID, reply[i]->oid) == 0) {
532                         struct ldb_paged_control *rep_control, *req_control;
533
534                         rep_control = talloc_get_type(reply[i]->data, struct ldb_paged_control);
535                         if (rep_control->cookie_len == 0) /* we are done */
536                                 break;
537
538                         /* more processing required */
539                         /* let's fill in the request control with the new cookie */
540
541                         for (j = 0; request[j]; j++) {
542                                 if (strcmp(LDB_CONTROL_PAGED_RESULTS_OID, request[j]->oid) == 0)
543                                         break;
544                         }
545                         /* if there's a reply control we must find a request
546                          * control matching it */
547                         if (! request[j]) return -1;
548
549                         req_control = talloc_get_type(request[j]->data, struct ldb_paged_control);
550
551                         if (req_control->cookie)
552                                 talloc_free(req_control->cookie);
553                         req_control->cookie = talloc_memdup(req_control,
554                                                             rep_control->cookie,
555                                                             rep_control->cookie_len);
556                         req_control->cookie_len = rep_control->cookie_len;
557
558                         ret = 1;
559
560                         continue;
561                 }
562
563                 if (strcmp(LDB_CONTROL_SORT_RESP_OID, reply[i]->oid) == 0) {
564                         struct ldb_sort_resp_control *rep_control;
565
566                         rep_control = talloc_get_type(reply[i]->data, struct ldb_sort_resp_control);
567
568                         /* check we have a matching control in the request */
569                         for (j = 0; request[j]; j++) {
570                                 if (strcmp(LDB_CONTROL_SERVER_SORT_OID, request[j]->oid) == 0)
571                                         break;
572                         }
573                         if (! request[j]) {
574                                 fprintf(stderr, "Warning Server Sort reply received but no request found\n");
575                                 continue;
576                         }
577
578                         /* check the result */
579                         if (rep_control->result != 0) {
580                                 fprintf(stderr, "Warning: Sorting not performed with error: %d\n", rep_control->result);
581                         }
582
583                         continue;
584                 }
585
586                 if (strcmp(LDB_CONTROL_DIRSYNC_OID, reply[i]->oid) == 0) {
587                         struct ldb_dirsync_control *rep_control, *req_control;
588                         char *cookie;
589
590                         rep_control = talloc_get_type(reply[i]->data, struct ldb_dirsync_control);
591                         if (rep_control->cookie_len == 0) /* we are done */
592                                 break;
593
594                         /* more processing required */
595                         /* let's fill in the request control with the new cookie */
596
597                         for (j = 0; request[j]; j++) {
598                                 if (strcmp(LDB_CONTROL_DIRSYNC_OID, request[j]->oid) == 0)
599                                         break;
600                         }
601                         /* if there's a reply control we must find a request
602                          * control matching it */
603                         if (! request[j]) return -1;
604
605                         req_control = talloc_get_type(request[j]->data, struct ldb_dirsync_control);
606
607                         if (req_control->cookie)
608                                 talloc_free(req_control->cookie);
609                         req_control->cookie = talloc_memdup(req_control, 
610                                                             rep_control->cookie,
611                                                             rep_control->cookie_len);
612                         req_control->cookie_len = rep_control->cookie_len;
613
614                         cookie = ldb_base64_encode(req_control, rep_control->cookie, rep_control->cookie_len);
615                         printf("# DIRSYNC cookie returned was:\n# %s\n", cookie);
616
617                         continue;
618                 }
619
620                 /* no controls matched, throw a warning */
621                 fprintf(stderr, "Unknown reply control oid: %s\n", reply[i]->oid);
622         }
623
624         return ret;
625 }
626