]> git.sur5r.net Git - openldap/blob - servers/slapd/overlays/retcode.c
Just return API errors to the frontend, don't attempt to send them.
[openldap] / servers / slapd / overlays / retcode.c
1 /* retcode.c - customizable response for client testing purposes */
2 /* $OpenLDAP$ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4  *
5  * Copyright 2005-2007 The OpenLDAP Foundation.
6  * Portions Copyright 2005 Pierangelo Masarati <ando@sys-net.it>
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted only as authorized by the OpenLDAP
11  * Public License.
12  *
13  * A copy of this license is available in the file LICENSE in the
14  * top-level directory of the distribution or, alternatively, at
15  * <http://www.OpenLDAP.org/license.html>.
16  */
17 /* ACKNOWLEDGEMENTS:
18  * This work was initially developed by Pierangelo Masarati for inclusion
19  * in OpenLDAP Software.
20  */
21
22 #include "portable.h"
23
24 #ifdef SLAPD_OVER_RETCODE
25
26 #include <stdio.h>
27
28 #include <ac/unistd.h>
29 #include <ac/string.h>
30 #include <ac/ctype.h>
31 #include <ac/socket.h>
32
33 #include "slap.h"
34 #include "lutil.h"
35
36 static slap_overinst            retcode;
37
38 static AttributeDescription     *ad_errCode;
39 static AttributeDescription     *ad_errText;
40 static AttributeDescription     *ad_errOp;
41 static AttributeDescription     *ad_errSleepTime;
42 static AttributeDescription     *ad_errMatchedDN;
43 static ObjectClass              *oc_errAbsObject;
44 static ObjectClass              *oc_errObject;
45 static ObjectClass              *oc_errAuxObject;
46
47 typedef enum retcode_op_e {
48         SN_DG_OP_NONE           = 0x0000,
49         SN_DG_OP_ADD            = 0x0001,
50         SN_DG_OP_BIND           = 0x0002,
51         SN_DG_OP_COMPARE        = 0x0004,
52         SN_DG_OP_DELETE         = 0x0008,
53         SN_DG_OP_MODIFY         = 0x0010,
54         SN_DG_OP_RENAME         = 0x0020,
55         SN_DG_OP_SEARCH         = 0x0040,
56         SN_DG_EXTENDED          = 0x0080,
57         SN_DG_OP_AUTH           = SN_DG_OP_BIND,
58         SN_DG_OP_READ           = (SN_DG_OP_COMPARE|SN_DG_OP_SEARCH),
59         SN_DG_OP_WRITE          = (SN_DG_OP_ADD|SN_DG_OP_DELETE|SN_DG_OP_MODIFY|SN_DG_OP_RENAME),
60         SN_DG_OP_ALL            = (SN_DG_OP_AUTH|SN_DG_OP_READ|SN_DG_OP_WRITE|SN_DG_EXTENDED)
61 } retcode_op_e;
62
63 typedef struct retcode_item_t {
64         struct berval           rdi_dn;
65         struct berval           rdi_ndn;
66         struct berval           rdi_text;
67         struct berval           rdi_matched;
68         int                     rdi_err;
69         BerVarray               rdi_ref;
70         int                     rdi_sleeptime;
71         Entry                   rdi_e;
72         slap_mask_t             rdi_mask;
73         struct retcode_item_t   *rdi_next;
74 } retcode_item_t;
75
76 typedef struct retcode_t {
77         struct berval           rd_pdn;
78         struct berval           rd_npdn;
79
80         int                     rd_sleep;
81
82         retcode_item_t          *rd_item;
83
84         unsigned                rd_flags;
85 #define RETCODE_FNONE           0x00
86 #define RETCODE_FINDIR          0x01
87 #define RETCODE_INDIR( rd )     ( (rd)->rd_flags & RETCODE_FINDIR )
88 } retcode_t;
89
90 static int
91 retcode_entry_response( Operation *op, SlapReply *rs, BackendInfo *bi, Entry *e );
92
93 static unsigned int
94 retcode_sleep( int s )
95 {
96         unsigned int r = 0;
97
98         /* sleep as required */
99         if ( s < 0 ) {
100 #if 0   /* use high-order bits for better randomness (Numerical Recipes in "C") */
101                 r = rand() % (-s);
102 #endif
103                 r = ((double)(-s))*rand()/(RAND_MAX + 1.0);
104         } else if ( s > 0 ) {
105                 r = (unsigned int)s;
106         }
107         if ( r ) {
108                 sleep( r );
109         }
110
111         return r;
112 }
113
114 static int
115 retcode_cleanup_cb( Operation *op, SlapReply *rs )
116 {
117         rs->sr_matched = NULL;
118         rs->sr_text = NULL;
119
120         if ( rs->sr_ref != NULL ) {
121                 ber_bvarray_free( rs->sr_ref );
122                 rs->sr_ref = NULL;
123         }
124
125         ch_free( op->o_callback );
126         op->o_callback = NULL;
127
128         return SLAP_CB_CONTINUE;
129 }
130
131 static int
132 retcode_send_onelevel( Operation *op, SlapReply *rs )
133 {
134         slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
135         retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
136
137         retcode_item_t  *rdi;
138         
139         for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) {
140                 if ( op->o_abandon ) {
141                         return rs->sr_err = SLAPD_ABANDON;
142                 }
143
144                 rs->sr_err = test_filter( op, &rdi->rdi_e, op->ors_filter );
145                 if ( rs->sr_err == LDAP_COMPARE_TRUE ) {
146                         if ( op->ors_slimit == rs->sr_nentries ) {
147                                 rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
148                                 goto done;
149                         }
150
151                         /* safe default */
152                         rs->sr_attrs = op->ors_attrs;
153                         rs->sr_operational_attrs = NULL;
154                         rs->sr_ctrls = NULL;
155                         rs->sr_flags = 0;
156                         rs->sr_err = LDAP_SUCCESS;
157                         rs->sr_entry = &rdi->rdi_e;
158
159                         rs->sr_err = send_search_entry( op, rs );
160                         rs->sr_entry = NULL;
161
162                         switch ( rs->sr_err ) {
163                         case LDAP_UNAVAILABLE:  /* connection closed */
164                                 rs->sr_err = LDAP_OTHER;
165                                 /* fallthru */
166                         case LDAP_SIZELIMIT_EXCEEDED:
167                                 goto done;
168                         }
169                 }
170                 rs->sr_err = LDAP_SUCCESS;
171         }
172
173 done:;
174
175         send_ldap_result( op, rs );
176
177         return rs->sr_err;
178 }
179
180 static int
181 retcode_op_add( Operation *op, SlapReply *rs )
182 {
183         return retcode_entry_response( op, rs, NULL, op->ora_e );
184 }
185
186 typedef struct retcode_cb_t {
187         BackendInfo     *rdc_info;
188         unsigned        rdc_flags;
189         ber_tag_t       rdc_tag;
190         AttributeName   *rdc_attrs;
191 } retcode_cb_t;
192
193 static int
194 retcode_cb_response( Operation *op, SlapReply *rs )
195 {
196         retcode_cb_t    *rdc = (retcode_cb_t *)op->o_callback->sc_private;
197
198         if ( rs->sr_type == REP_SEARCH ) {
199                 ber_tag_t       o_tag = op->o_tag;
200                 int             rc;
201
202                 op->o_tag = rdc->rdc_tag;
203                 if ( op->o_tag == LDAP_REQ_SEARCH ) {
204                         rs->sr_attrs = rdc->rdc_attrs;
205                 }
206                 rc = retcode_entry_response( op, rs, rdc->rdc_info, rs->sr_entry );
207                 op->o_tag = o_tag;
208
209                 return rc;
210         }
211
212         if ( rs->sr_err == LDAP_SUCCESS ) {
213                 if ( !op->o_abandon ) {
214                         rdc->rdc_flags = SLAP_CB_CONTINUE;
215                 }
216                 return 0;
217         }
218
219         return SLAP_CB_CONTINUE;
220 }
221
222 static int
223 retcode_op_internal( Operation *op, SlapReply *rs )
224 {
225         slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
226
227         Operation       op2 = *op;
228         BackendDB       db = *op->o_bd;
229         slap_callback   sc = { 0 };
230         retcode_cb_t    rdc;
231
232         int             rc;
233
234         op2.o_tag = LDAP_REQ_SEARCH;
235         op2.ors_scope = LDAP_SCOPE_BASE;
236         op2.ors_deref = LDAP_DEREF_NEVER;
237         op2.ors_tlimit = SLAP_NO_LIMIT;
238         op2.ors_slimit = SLAP_NO_LIMIT;
239         op2.ors_limit = NULL;
240         op2.ors_attrsonly = 0;
241         op2.ors_attrs = slap_anlist_all_attributes;
242
243         ber_str2bv_x( "(objectClass=errAbsObject)",
244                 STRLENOF( "(objectClass=errAbsObject)" ),
245                 1, &op2.ors_filterstr, op2.o_tmpmemctx );
246         op2.ors_filter = str2filter_x( &op2, op2.ors_filterstr.bv_val );
247
248         db.bd_info = on->on_info->oi_orig;
249         op2.o_bd = &db;
250
251         rdc.rdc_info = on->on_info->oi_orig;
252         rdc.rdc_flags = RETCODE_FINDIR;
253         if ( op->o_tag == LDAP_REQ_SEARCH ) {
254                 rdc.rdc_attrs = op->ors_attrs;
255         }
256         rdc.rdc_tag = op->o_tag;
257         sc.sc_response = retcode_cb_response;
258         sc.sc_private = &rdc;
259         op2.o_callback = &sc;
260
261         rc = op2.o_bd->be_search( &op2, rs );
262         op->o_abandon = op2.o_abandon;
263
264         filter_free_x( &op2, op2.ors_filter );
265         ber_memfree_x( op2.ors_filterstr.bv_val, op2.o_tmpmemctx );
266
267         if ( rdc.rdc_flags == SLAP_CB_CONTINUE ) {
268                 return SLAP_CB_CONTINUE;
269         }
270
271         return rc;
272 }
273
274 static int
275 retcode_op_func( Operation *op, SlapReply *rs )
276 {
277         slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
278         retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
279
280         retcode_item_t  *rdi;
281         struct berval           nrdn, npdn;
282
283         slap_callback           *cb = NULL;
284
285         /* sleep as required */
286         retcode_sleep( rd->rd_sleep );
287
288         if ( !dnIsSuffix( &op->o_req_ndn, &rd->rd_npdn ) ) {
289                 if ( RETCODE_INDIR( rd ) ) {
290                         switch ( op->o_tag ) {
291                         case LDAP_REQ_ADD:
292                                 return retcode_op_add( op, rs );
293
294                         case LDAP_REQ_BIND:
295                                 /* skip if rootdn */
296                                 if ( be_isroot_pw( op ) ) {
297                                         return SLAP_CB_CONTINUE;
298                                 }
299                                 return retcode_op_internal( op, rs );
300
301                         case LDAP_REQ_SEARCH:
302                                 if ( op->ors_scope == LDAP_SCOPE_BASE ) {
303                                         rs->sr_err = retcode_op_internal( op, rs );
304                                         switch ( rs->sr_err ) {
305                                         case SLAP_CB_CONTINUE:
306                                                 if ( rs->sr_nentries == 0 ) {
307                                                         break;
308                                                 }
309                                                 rs->sr_err = LDAP_SUCCESS;
310                                                 /* fallthru */
311
312                                         default:
313                                                 send_ldap_result( op, rs );
314                                                 break;
315                                         }
316                                         return rs->sr_err;
317                                 }
318                                 break;
319
320                         case LDAP_REQ_MODIFY:
321                         case LDAP_REQ_DELETE:
322                         case LDAP_REQ_MODRDN:
323                         case LDAP_REQ_COMPARE:
324                                 return retcode_op_internal( op, rs );
325                         }
326                 }
327
328                 return SLAP_CB_CONTINUE;
329         }
330
331         if ( op->o_tag == LDAP_REQ_SEARCH
332                         && op->ors_scope != LDAP_SCOPE_BASE
333                         && op->o_req_ndn.bv_len == rd->rd_npdn.bv_len )
334         {
335                 return retcode_send_onelevel( op, rs );
336         }
337
338         dnParent( &op->o_req_ndn, &npdn );
339         if ( npdn.bv_len != rd->rd_npdn.bv_len ) {
340                 rs->sr_err = LDAP_NO_SUCH_OBJECT;
341                 rs->sr_matched = rd->rd_pdn.bv_val;
342                 send_ldap_result( op, rs );
343                 rs->sr_matched = NULL;
344                 return rs->sr_err;
345         }
346
347         dnRdn( &op->o_req_ndn, &nrdn );
348
349         for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) {
350                 struct berval   rdi_nrdn;
351
352                 dnRdn( &rdi->rdi_ndn, &rdi_nrdn );
353                 if ( dn_match( &nrdn, &rdi_nrdn ) ) {
354                         break;
355                 }
356         }
357
358         if ( rdi != NULL && rdi->rdi_mask != SN_DG_OP_ALL ) {
359                 retcode_op_e    o_tag = SN_DG_OP_NONE;
360
361                 switch ( op->o_tag ) {
362                 case LDAP_REQ_ADD:
363                         o_tag = SN_DG_OP_ADD;
364                         break;
365
366                 case LDAP_REQ_BIND:
367                         o_tag = SN_DG_OP_BIND;
368                         break;
369
370                 case LDAP_REQ_COMPARE:
371                         o_tag = SN_DG_OP_COMPARE;
372                         break;
373
374                 case LDAP_REQ_DELETE:
375                         o_tag = SN_DG_OP_DELETE;
376                         break;
377
378                 case LDAP_REQ_MODIFY:
379                         o_tag = SN_DG_OP_MODIFY;
380                         break;
381
382                 case LDAP_REQ_MODRDN:
383                         o_tag = SN_DG_OP_RENAME;
384                         break;
385
386                 case LDAP_REQ_SEARCH:
387                         o_tag = SN_DG_OP_SEARCH;
388                         break;
389
390                 case LDAP_REQ_EXTENDED:
391                         o_tag = SN_DG_EXTENDED;
392                         break;
393
394                 default:
395                         /* Should not happen */
396                         break;
397                 }
398
399                 if ( !( o_tag & rdi->rdi_mask ) ) {
400                         return SLAP_CB_CONTINUE;
401                 }
402         }
403
404         if ( rdi == NULL ) {
405                 rs->sr_matched = rd->rd_pdn.bv_val;
406                 rs->sr_err = LDAP_NO_SUCH_OBJECT;
407                 rs->sr_text = "retcode not found";
408
409         } else {
410                 rs->sr_err = rdi->rdi_err;
411                 rs->sr_text = rdi->rdi_text.bv_val;
412                 rs->sr_matched = rdi->rdi_matched.bv_val;
413
414                 /* FIXME: we only honor the rdi_ref field in case rdi_err
415                  * is LDAP_REFERRAL otherwise send_ldap_result() bails out */
416                 if ( rs->sr_err == LDAP_REFERRAL ) {
417                         BerVarray       ref;
418
419                         if ( rdi->rdi_ref != NULL ) {
420                                 ref = rdi->rdi_ref;
421                         } else {
422                                 ref = default_referral;
423                         }
424
425                         if ( ref != NULL ) {
426                                 rs->sr_ref = referral_rewrite( ref,
427                                         NULL, &op->o_req_dn, LDAP_SCOPE_DEFAULT );
428
429                         } else {
430                                 rs->sr_err = LDAP_OTHER;
431                                 rs->sr_text = "bad referral object";
432                         }
433                 }
434
435                 retcode_sleep( rdi->rdi_sleeptime );
436         }
437
438         switch ( op->o_tag ) {
439         case LDAP_REQ_EXTENDED:
440                 if ( rdi == NULL ) {
441                         break;
442                 }
443                 cb = ( slap_callback * )ch_malloc( sizeof( slap_callback ) );
444                 memset( cb, 0, sizeof( slap_callback ) );
445                 cb->sc_cleanup = retcode_cleanup_cb;
446                 op->o_callback = cb;
447                 break;
448
449         default:
450                 send_ldap_result( op, rs );
451                 if ( rs->sr_ref != NULL ) {
452                         ber_bvarray_free( rs->sr_ref );
453                         rs->sr_ref = NULL;
454                 }
455                 rs->sr_matched = NULL;
456                 rs->sr_text = NULL;
457                 break;
458         }
459
460         return rs->sr_err;
461 }
462
463 static int
464 retcode_op2str( ber_tag_t op, struct berval *bv )
465 {
466         switch ( op ) {
467         case LDAP_REQ_BIND:
468                 BER_BVSTR( bv, "bind" );
469                 return 0;
470         case LDAP_REQ_ADD:
471                 BER_BVSTR( bv, "add" );
472                 return 0;
473         case LDAP_REQ_DELETE:
474                 BER_BVSTR( bv, "delete" );
475                 return 0;
476         case LDAP_REQ_MODRDN:
477                 BER_BVSTR( bv, "modrdn" );
478                 return 0;
479         case LDAP_REQ_MODIFY:
480                 BER_BVSTR( bv, "modify" );
481                 return 0;
482         case LDAP_REQ_COMPARE:
483                 BER_BVSTR( bv, "compare" );
484                 return 0;
485         case LDAP_REQ_SEARCH:
486                 BER_BVSTR( bv, "search" );
487                 return 0;
488         case LDAP_REQ_EXTENDED:
489                 BER_BVSTR( bv, "extended" );
490                 return 0;
491         }
492         return -1;
493 }
494
495 static int
496 retcode_entry_response( Operation *op, SlapReply *rs, BackendInfo *bi, Entry *e )
497 {
498         Attribute       *a;
499         int             err;
500         char            *next;
501
502         if ( get_manageDSAit( op ) ) {
503                 return SLAP_CB_CONTINUE;
504         }
505
506         if ( !is_entry_objectclass_or_sub( e, oc_errAbsObject ) ) {
507                 return SLAP_CB_CONTINUE;
508         }
509
510         /* operation */
511         a = attr_find( e->e_attrs, ad_errOp );
512         if ( a != NULL ) {
513                 int             i,
514                                 gotit = 0;
515                 struct berval   bv = BER_BVNULL;
516
517                 (void)retcode_op2str( op->o_tag, &bv );
518
519                 if ( BER_BVISNULL( &bv ) ) {
520                         return SLAP_CB_CONTINUE;
521                 }
522
523                 for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
524                         if ( bvmatch( &a->a_nvals[ i ], &bv ) ) {
525                                 gotit = 1;
526                                 break;
527                         }
528                 }
529
530                 if ( !gotit ) {
531                         return SLAP_CB_CONTINUE;
532                 }
533         }
534
535         /* error code */
536         a = attr_find( e->e_attrs, ad_errCode );
537         if ( a == NULL ) {
538                 return SLAP_CB_CONTINUE;
539         }
540         err = strtol( a->a_nvals[ 0 ].bv_val, &next, 0 );
541         if ( next == a->a_nvals[ 0 ].bv_val || next[ 0 ] != '\0' ) {
542                 return SLAP_CB_CONTINUE;
543         }
544         rs->sr_err = err;
545
546         /* sleep time */
547         a = attr_find( e->e_attrs, ad_errSleepTime );
548         if ( a != NULL && a->a_nvals[ 0 ].bv_val[ 0 ] != '-' ) {
549                 int     sleepTime;
550
551                 if ( lutil_atoi( &sleepTime, a->a_nvals[ 0 ].bv_val ) == 0 ) {
552                         retcode_sleep( sleepTime );
553                 }
554         }
555
556         if ( rs->sr_err != LDAP_SUCCESS && !LDAP_API_ERROR( rs->sr_err )) {
557                 BackendDB       db = *op->o_bd,
558                                 *o_bd = op->o_bd;
559                 void            *o_callback = op->o_callback;
560
561                 /* message text */
562                 a = attr_find( e->e_attrs, ad_errText );
563                 if ( a != NULL ) {
564                         rs->sr_text = a->a_vals[ 0 ].bv_val;
565                 }
566
567                 /* matched DN */
568                 a = attr_find( e->e_attrs, ad_errMatchedDN );
569                 if ( a != NULL ) {
570                         rs->sr_matched = a->a_vals[ 0 ].bv_val;
571                 }
572
573                 if ( bi == NULL ) {
574                         slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
575
576                         bi = on->on_info->oi_orig;
577                 }
578
579                 db.bd_info = bi;
580                 op->o_bd = &db;
581                 op->o_callback = NULL;
582
583                 /* referral */
584                 if ( rs->sr_err == LDAP_REFERRAL ) {
585                         BerVarray       refs = default_referral;
586
587                         a = attr_find( e->e_attrs, slap_schema.si_ad_ref );
588                         if ( a != NULL ) {
589                                 refs = a->a_vals;
590                         }
591                         rs->sr_ref = referral_rewrite( refs,
592                                 NULL, &op->o_req_dn, op->oq_search.rs_scope );
593         
594                         send_search_reference( op, rs );
595                         ber_bvarray_free( rs->sr_ref );
596                         rs->sr_ref = NULL;
597
598                 } else {
599                         send_ldap_result( op, rs );
600                 }
601
602                 rs->sr_text = NULL;
603                 rs->sr_matched = NULL;
604                 op->o_bd = o_bd;
605                 op->o_callback = o_callback;
606         }
607         
608         if ( rs->sr_err != LDAP_SUCCESS ) {
609                 op->o_abandon = 1;
610                 return rs->sr_err;
611         }
612
613         return SLAP_CB_CONTINUE;
614 }
615
616 static int
617 retcode_response( Operation *op, SlapReply *rs )
618 {
619         slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
620         retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
621
622         if ( rs->sr_type != REP_SEARCH || !RETCODE_INDIR( rd ) ) {
623                 return SLAP_CB_CONTINUE;
624         }
625
626         return retcode_entry_response( op, rs, NULL, rs->sr_entry );
627 }
628
629 static int
630 retcode_db_init( BackendDB *be )
631 {
632         slap_overinst   *on = (slap_overinst *)be->bd_info;
633         retcode_t       *rd;
634
635         srand( getpid() );
636
637         rd = (retcode_t *)ch_malloc( sizeof( retcode_t ) );
638         memset( rd, 0, sizeof( retcode_t ) );
639
640         on->on_bi.bi_private = (void *)rd;
641
642         return 0;
643 }
644
645 static int
646 retcode_db_config(
647         BackendDB       *be,
648         const char      *fname,
649         int             lineno,
650         int             argc,
651         char            **argv )
652 {
653         slap_overinst   *on = (slap_overinst *)be->bd_info;
654         retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
655
656         char                    *argv0 = argv[ 0 ] + STRLENOF( "retcode-" );
657
658         if ( strncasecmp( argv[ 0 ], "retcode-", STRLENOF( "retcode-" ) ) != 0 ) {
659                 return SLAP_CONF_UNKNOWN;
660         }
661
662         if ( strcasecmp( argv0, "parent" ) == 0 ) {
663                 struct berval   dn;
664                 int             rc;
665
666                 if ( argc != 2 ) {
667                         fprintf( stderr, "%s: line %d: retcode: "
668                                 "\"retcode-parent <DN>\": missing <DN>\n",
669                                 fname, lineno );
670                         return 1;
671                 }
672
673                 if ( !BER_BVISNULL( &rd->rd_pdn ) ) {
674                         fprintf( stderr, "%s: line %d: retcode: "
675                                 "parent already defined.\n", fname, lineno );
676                         return 1;
677                 }
678
679                 ber_str2bv( argv[ 1 ], 0, 0, &dn );
680
681                 rc = dnPrettyNormal( NULL, &dn, &rd->rd_pdn, &rd->rd_npdn, NULL );
682                 if ( rc != LDAP_SUCCESS ) {
683                         fprintf( stderr, "%s: line %d: retcode: "
684                                 "unable to normalize parent DN \"%s\": %d\n",
685                                 fname, lineno, argv[ 1 ], rc );
686                         return 1;
687                 }
688
689         } else if ( strcasecmp( argv0, "item" ) == 0 ) {
690                 retcode_item_t  rdi = { BER_BVNULL }, **rdip;
691                 struct berval           bv, rdn, nrdn;
692                 int                     rc;
693                 char                    *next = NULL;
694
695                 if ( argc < 3 ) {
696                         fprintf( stderr, "%s: line %d: retcode: "
697                                 "\"retcode-item <RDN> <retcode> [<text>]\": "
698                                 "missing args\n",
699                                 fname, lineno );
700                         return 1;
701                 }
702
703                 ber_str2bv( argv[ 1 ], 0, 0, &bv );
704                 
705                 rc = dnPrettyNormal( NULL, &bv, &rdn, &nrdn, NULL );
706                 if ( rc != LDAP_SUCCESS ) {
707                         fprintf( stderr, "%s: line %d: retcode: "
708                                 "unable to normalize RDN \"%s\": %d\n",
709                                 fname, lineno, argv[ 1 ], rc );
710                         return 1;
711                 }
712
713                 if ( !dnIsOneLevelRDN( &nrdn ) ) {
714                         fprintf( stderr, "%s: line %d: retcode: "
715                                 "value \"%s\" is not a RDN\n",
716                                 fname, lineno, argv[ 1 ] );
717                         return 1;
718                 }
719
720                 if ( BER_BVISNULL( &rd->rd_npdn ) ) {
721                         /* FIXME: we use the database suffix */
722                         if ( be->be_nsuffix == NULL ) {
723                                 fprintf( stderr, "%s: line %d: retcode: "
724                                         "either \"retcode-parent\" "
725                                         "or \"suffix\" must be defined.\n",
726                                         fname, lineno );
727                                 return 1;
728                         }
729
730                         ber_dupbv( &rd->rd_pdn, &be->be_suffix[ 0 ] );
731                         ber_dupbv( &rd->rd_npdn, &be->be_nsuffix[ 0 ] );
732                 }
733
734                 build_new_dn( &rdi.rdi_dn, &rd->rd_pdn, &rdn, NULL );
735                 build_new_dn( &rdi.rdi_ndn, &rd->rd_npdn, &nrdn, NULL );
736
737                 ch_free( rdn.bv_val );
738                 ch_free( nrdn.bv_val );
739
740                 rdi.rdi_err = strtol( argv[ 2 ], &next, 0 );
741                 if ( next == argv[ 2 ] || next[ 0 ] != '\0' ) {
742                         fprintf( stderr, "%s: line %d: retcode: "
743                                 "unable to parse return code \"%s\"\n",
744                                 fname, lineno, argv[ 2 ] );
745                         return 1;
746                 }
747
748                 rdi.rdi_mask = SN_DG_OP_ALL;
749
750                 if ( argc > 3 ) {
751                         int     i;
752
753                         for ( i = 3; i < argc; i++ ) {
754                                 if ( strncasecmp( argv[ i ], "op=", STRLENOF( "op=" ) ) == 0 )
755                                 {
756                                         char            **ops;
757                                         int             j;
758
759                                         ops = ldap_str2charray( &argv[ i ][ STRLENOF( "op=" ) ], "," );
760                                         assert( ops != NULL );
761
762                                         rdi.rdi_mask = SN_DG_OP_NONE;
763
764                                         for ( j = 0; ops[ j ] != NULL; j++ ) {
765                                                 if ( strcasecmp( ops[ j ], "add" ) == 0 ) {
766                                                         rdi.rdi_mask |= SN_DG_OP_ADD;
767
768                                                 } else if ( strcasecmp( ops[ j ], "bind" ) == 0 ) {
769                                                         rdi.rdi_mask |= SN_DG_OP_BIND;
770
771                                                 } else if ( strcasecmp( ops[ j ], "compare" ) == 0 ) {
772                                                         rdi.rdi_mask |= SN_DG_OP_COMPARE;
773
774                                                 } else if ( strcasecmp( ops[ j ], "delete" ) == 0 ) {
775                                                         rdi.rdi_mask |= SN_DG_OP_DELETE;
776
777                                                 } else if ( strcasecmp( ops[ j ], "modify" ) == 0 ) {
778                                                         rdi.rdi_mask |= SN_DG_OP_MODIFY;
779
780                                                 } else if ( strcasecmp( ops[ j ], "rename" ) == 0
781                                                         || strcasecmp( ops[ j ], "modrdn" ) == 0 )
782                                                 {
783                                                         rdi.rdi_mask |= SN_DG_OP_RENAME;
784
785                                                 } else if ( strcasecmp( ops[ j ], "search" ) == 0 ) {
786                                                         rdi.rdi_mask |= SN_DG_OP_SEARCH;
787
788                                                 } else if ( strcasecmp( ops[ j ], "extended" ) == 0 ) {
789                                                         rdi.rdi_mask |= SN_DG_EXTENDED;
790
791                                                 } else if ( strcasecmp( ops[ j ], "auth" ) == 0 ) {
792                                                         rdi.rdi_mask |= SN_DG_OP_AUTH;
793
794                                                 } else if ( strcasecmp( ops[ j ], "read" ) == 0 ) {
795                                                         rdi.rdi_mask |= SN_DG_OP_READ;
796
797                                                 } else if ( strcasecmp( ops[ j ], "write" ) == 0 ) {
798                                                         rdi.rdi_mask |= SN_DG_OP_WRITE;
799
800                                                 } else if ( strcasecmp( ops[ j ], "all" ) == 0 ) {
801                                                         rdi.rdi_mask |= SN_DG_OP_ALL;
802
803                                                 } else {
804                                                         fprintf( stderr, "retcode: unknown op \"%s\"\n",
805                                                                 ops[ j ] );
806                                                         ldap_charray_free( ops );
807                                                         return 1;
808                                                 }
809                                         }
810
811                                         ldap_charray_free( ops );
812
813                                 } else if ( strncasecmp( argv[ i ], "text=", STRLENOF( "text=" ) ) == 0 )
814                                 {
815                                         if ( !BER_BVISNULL( &rdi.rdi_text ) ) {
816                                                 fprintf( stderr, "%s: line %d: retcode: "
817                                                         "\"text\" already provided.\n",
818                                                         fname, lineno );
819                                                 return 1;
820                                         }
821                                         ber_str2bv( &argv[ i ][ STRLENOF( "text=" ) ], 0, 1, &rdi.rdi_text );
822
823                                 } else if ( strncasecmp( argv[ i ], "matched=", STRLENOF( "matched=" ) ) == 0 )
824                                 {
825                                         struct berval   dn;
826
827                                         if ( !BER_BVISNULL( &rdi.rdi_matched ) ) {
828                                                 fprintf( stderr, "%s: line %d: retcode: "
829                                                         "\"matched\" already provided.\n",
830                                                         fname, lineno );
831                                                 return 1;
832                                         }
833                                         ber_str2bv( &argv[ i ][ STRLENOF( "matched=" ) ], 0, 0, &dn );
834                                         if ( dnPretty( NULL, &dn, &rdi.rdi_matched, NULL ) != LDAP_SUCCESS ) {
835                                                 fprintf( stderr, "%s: line %d: retcode: "
836                                                         "unable to prettify matched DN \"%s\".\n",
837                                                         fname, lineno, &argv[ i ][ STRLENOF( "matched=" ) ] );
838                                                 return 1;
839                                         }
840
841                                 } else if ( strncasecmp( argv[ i ], "ref=", STRLENOF( "ref=" ) ) == 0 )
842                                 {
843                                         char            **refs;
844                                         int             j;
845
846                                         if ( rdi.rdi_ref != NULL ) {
847                                                 fprintf( stderr, "%s: line %d: retcode: "
848                                                         "\"ref\" already provided.\n",
849                                                         fname, lineno );
850                                                 return 1;
851                                         }
852
853                                         if ( rdi.rdi_err != LDAP_REFERRAL ) {
854                                                 fprintf( stderr, "%s: line %d: retcode: "
855                                                         "providing \"ref\"\n"
856                                                         "\talong with a non-referral "
857                                                         "resultCode may cause slapd failures\n"
858                                                         "\trelated to internal checks.\n",
859                                                         fname, lineno );
860                                         }
861
862                                         refs = ldap_str2charray( &argv[ i ][ STRLENOF( "ref=" ) ], " " );
863                                         assert( refs != NULL );
864
865                                         for ( j = 0; refs[ j ] != NULL; j++ ) {
866                                                 struct berval   bv;
867
868                                                 ber_str2bv( refs[ j ], 0, 1, &bv );
869                                                 ber_bvarray_add( &rdi.rdi_ref, &bv );
870                                         }
871
872                                         ldap_charray_free( refs );
873
874                                 } else if ( strncasecmp( argv[ i ], "sleeptime=", STRLENOF( "sleeptime=" ) ) == 0 )
875                                 {
876                                         if ( rdi.rdi_sleeptime != 0 ) {
877                                                 fprintf( stderr, "%s: line %d: retcode: "
878                                                         "\"sleeptime\" already provided.\n",
879                                                         fname, lineno );
880                                                 return 1;
881                                         }
882
883                                         if ( lutil_atoi( &rdi.rdi_sleeptime, &argv[ i ][ STRLENOF( "sleeptime=" ) ] ) ) {
884                                                 fprintf( stderr, "%s: line %d: retcode: "
885                                                         "unable to parse \"sleeptime=%s\".\n",
886                                                         fname, lineno, &argv[ i ][ STRLENOF( "sleeptime=" ) ] );
887                                                 return 1;
888                                         }
889
890                                 } else {
891                                         fprintf( stderr, "%s: line %d: retcode: "
892                                                 "unknown option \"%s\".\n",
893                                                         fname, lineno, argv[ i ] );
894                                         return 1;
895                                 }
896                         }
897                 }
898
899                 for ( rdip = &rd->rd_item; *rdip; rdip = &(*rdip)->rdi_next )
900                         /* go to last */ ;
901
902                 
903                 *rdip = ( retcode_item_t * )ch_malloc( sizeof( retcode_item_t ) );
904                 *(*rdip) = rdi;
905
906         } else if ( strcasecmp( argv0, "indir" ) == 0 ) {
907                 rd->rd_flags |= RETCODE_FINDIR;
908
909         } else if ( strcasecmp( argv0, "sleep" ) == 0 ) {
910                 switch ( argc ) {
911                 case 1:
912                         fprintf( stderr, "%s: line %d: retcode: "
913                                 "\"retcode-sleep <time>\": missing <time>\n",
914                                 fname, lineno );
915                         return 1;
916
917                 case 2:
918                         break;
919
920                 default:
921                         fprintf( stderr, "%s: line %d: retcode: "
922                                 "\"retcode-sleep <time>\": extra cruft after <time>\n",
923                                 fname, lineno );
924                         return 1;
925                 }
926
927                 if ( lutil_atoi( &rd->rd_sleep, argv[ 1 ] ) != 0 ) {
928                         fprintf( stderr, "%s: line %d: retcode: "
929                                 "\"retcode-sleep <time>\": unable to parse <time>\n",
930                                 fname, lineno );
931                         return 1;
932                 }
933
934         } else {
935                 return SLAP_CONF_UNKNOWN;
936         }
937
938         return 0;
939 }
940
941 static int
942 retcode_db_open( BackendDB *be )
943 {
944         slap_overinst   *on = (slap_overinst *)be->bd_info;
945         retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
946
947         retcode_item_t  *rdi;
948
949         for ( rdi = rd->rd_item; rdi; rdi = rdi->rdi_next ) {
950                 LDAPRDN                 rdn = NULL;
951                 int                     rc, j;
952                 char*                   p;
953                 struct berval           val[ 3 ];
954                 char                    buf[ SLAP_TEXT_BUFLEN ];
955
956                 /* DN */
957                 rdi->rdi_e.e_name = rdi->rdi_dn;
958                 rdi->rdi_e.e_nname = rdi->rdi_ndn;
959
960                 /* objectClass */
961                 val[ 0 ] = oc_errObject->soc_cname;
962                 val[ 1 ] = slap_schema.si_oc_extensibleObject->soc_cname;
963                 BER_BVZERO( &val[ 2 ] );
964
965                 attr_merge( &rdi->rdi_e, slap_schema.si_ad_objectClass, val, NULL );
966
967                 /* RDN avas */
968                 rc = ldap_bv2rdn( &rdi->rdi_dn, &rdn, (char **) &p,
969                                 LDAP_DN_FORMAT_LDAP );
970
971                 assert( rc == LDAP_SUCCESS );
972
973                 for ( j = 0; rdn[ j ]; j++ ) {
974                         LDAPAVA                 *ava = rdn[ j ];
975                         AttributeDescription    *ad = NULL;
976                         const char              *text;
977
978                         rc = slap_bv2ad( &ava->la_attr, &ad, &text );
979                         assert( rc == LDAP_SUCCESS );
980                         
981                         attr_merge_normalize_one( &rdi->rdi_e, ad,
982                                         &ava->la_value, NULL );
983                 }
984
985                 ldap_rdnfree( rdn );
986
987                 /* error code */
988                 snprintf( buf, sizeof( buf ), "%d", rdi->rdi_err );
989                 ber_str2bv( buf, 0, 0, &val[ 0 ] );
990
991                 attr_merge_one( &rdi->rdi_e, ad_errCode, &val[ 0 ], NULL );
992
993                 if ( rdi->rdi_ref != NULL ) {
994                         attr_merge_normalize( &rdi->rdi_e, slap_schema.si_ad_ref,
995                                 rdi->rdi_ref, NULL );
996                 }
997
998                 /* text */
999                 if ( !BER_BVISNULL( &rdi->rdi_text ) ) {
1000                         val[ 0 ] = rdi->rdi_text;
1001
1002                         attr_merge_normalize_one( &rdi->rdi_e, ad_errText, &val[ 0 ], NULL );
1003                 }
1004
1005                 /* matched */
1006                 if ( !BER_BVISNULL( &rdi->rdi_matched ) ) {
1007                         val[ 0 ] = rdi->rdi_matched;
1008
1009                         attr_merge_normalize_one( &rdi->rdi_e, ad_errMatchedDN, &val[ 0 ], NULL );
1010                 }
1011
1012                 /* sleep time */
1013                 if ( rdi->rdi_sleeptime ) {
1014                         snprintf( buf, sizeof( buf ), "%d", rdi->rdi_sleeptime );
1015                         ber_str2bv( buf, 0, 0, &val[ 0 ] );
1016
1017                         attr_merge_one( &rdi->rdi_e, ad_errSleepTime, &val[ 0 ], NULL );
1018                 }
1019
1020                 /* operations */
1021                 if ( rdi->rdi_mask & SN_DG_OP_ADD ) {
1022                         BER_BVSTR( &val[ 0 ], "add" );
1023                         attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
1024                 }
1025
1026                 if ( rdi->rdi_mask & SN_DG_OP_BIND ) {
1027                         BER_BVSTR( &val[ 0 ], "bind" );
1028                         attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
1029                 }
1030
1031                 if ( rdi->rdi_mask & SN_DG_OP_COMPARE ) {
1032                         BER_BVSTR( &val[ 0 ], "compare" );
1033                         attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
1034                 }
1035
1036                 if ( rdi->rdi_mask & SN_DG_OP_DELETE ) {
1037                         BER_BVSTR( &val[ 0 ], "delete" );
1038                         attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
1039                 }
1040
1041                 if ( rdi->rdi_mask & SN_DG_EXTENDED ) {
1042                         BER_BVSTR( &val[ 0 ], "extended" );
1043                         attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
1044                 }
1045
1046                 if ( rdi->rdi_mask & SN_DG_OP_MODIFY ) {
1047                         BER_BVSTR( &val[ 0 ], "modify" );
1048                         attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
1049                 }
1050
1051                 if ( rdi->rdi_mask & SN_DG_OP_RENAME ) {
1052                         BER_BVSTR( &val[ 0 ], "rename" );
1053                         attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
1054                 }
1055
1056                 if ( rdi->rdi_mask & SN_DG_OP_SEARCH ) {
1057                         BER_BVSTR( &val[ 0 ], "search" );
1058                         attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
1059                 }
1060         }
1061
1062         return 0;
1063 }
1064
1065 static int
1066 retcode_db_destroy( BackendDB *be )
1067 {
1068         slap_overinst   *on = (slap_overinst *)be->bd_info;
1069         retcode_t       *rd = (retcode_t *)on->on_bi.bi_private;
1070
1071         if ( rd ) {
1072                 retcode_item_t  *rdi, *next;
1073
1074                 for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) {
1075                         ber_memfree( rdi->rdi_dn.bv_val );
1076                         ber_memfree( rdi->rdi_ndn.bv_val );
1077
1078                         if ( !BER_BVISNULL( &rdi->rdi_text ) ) {
1079                                 ber_memfree( rdi->rdi_text.bv_val );
1080                         }
1081
1082                         if ( !BER_BVISNULL( &rdi->rdi_matched ) ) {
1083                                 ber_memfree( rdi->rdi_matched.bv_val );
1084                         }
1085
1086                         if ( rdi->rdi_ref ) {
1087                                 ber_bvarray_free( rdi->rdi_ref );
1088                         }
1089
1090                         BER_BVZERO( &rdi->rdi_e.e_name );
1091                         BER_BVZERO( &rdi->rdi_e.e_nname );
1092
1093                         entry_clean( &rdi->rdi_e );
1094
1095                         next = rdi->rdi_next;
1096
1097                         ch_free( rdi );
1098                 }
1099
1100                 if ( !BER_BVISNULL( &rd->rd_pdn ) ) {
1101                         ber_memfree( rd->rd_pdn.bv_val );
1102                 }
1103
1104                 if ( !BER_BVISNULL( &rd->rd_npdn ) ) {
1105                         ber_memfree( rd->rd_npdn.bv_val );
1106                 }
1107
1108                 ber_memfree( rd );
1109         }
1110
1111         return 0;
1112 }
1113
1114 #if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC
1115 static
1116 #endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */
1117 int
1118 retcode_initialize( void )
1119 {
1120         int             i, code;
1121         const char      *err;
1122
1123         static struct {
1124                 char                    *desc;
1125                 AttributeDescription    **ad;
1126         } retcode_at[] = {
1127                 { "( 1.3.6.1.4.1.4203.666.11.4.1.1 "
1128                         "NAME ( 'errCode' ) "
1129                         "DESC 'LDAP error code' "
1130                         "EQUALITY integerMatch "
1131                         "ORDERING integerOrderingMatch "
1132                         "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
1133                         "SINGLE-VALUE )",
1134                         &ad_errCode },
1135                 { "( 1.3.6.1.4.1.4203.666.11.4.1.2 "
1136                         "NAME ( 'errOp' ) "
1137                         "DESC 'Operations the errObject applies to' "
1138                         "EQUALITY caseIgnoreMatch "
1139                         "SUBSTR caseIgnoreSubstringsMatch "
1140                         "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
1141                         &ad_errOp},
1142                 { "( 1.3.6.1.4.1.4203.666.11.4.1.3 "
1143                         "NAME ( 'errText' ) "
1144                         "DESC 'LDAP error textual description' "
1145                         "EQUALITY caseIgnoreMatch "
1146                         "SUBSTR caseIgnoreSubstringsMatch "
1147                         "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
1148                         "SINGLE-VALUE )",
1149                         &ad_errText },
1150                 { "( 1.3.6.1.4.1.4203.666.11.4.1.4 "
1151                         "NAME ( 'errSleepTime' ) "
1152                         "DESC 'Time to wait before returning the error' "
1153                         "EQUALITY integerMatch "
1154                         "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
1155                         "SINGLE-VALUE )",
1156                         &ad_errSleepTime },
1157                 { "( 1.3.6.1.4.1.4203.666.11.4.1.5 "
1158                         "NAME ( 'errMatchedDN' ) "
1159                         "DESC 'Value to be returned as matched DN' "
1160                         "EQUALITY distinguishedNameMatch "
1161                         "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
1162                         "SINGLE-VALUE )",
1163                         &ad_errMatchedDN },
1164                 { NULL }
1165         };
1166
1167         static struct {
1168                 char            *desc;
1169                 ObjectClass     **oc;
1170         } retcode_oc[] = {
1171                 { "( 1.3.6.1.4.1.4203.666.11.4.3.0 "
1172                         "NAME ( 'errAbsObject' ) "
1173                         "SUP top ABSTRACT "
1174                         "MUST ( errCode ) "
1175                         "MAY ( "
1176                                 "cn "
1177                                 "$ description "
1178                                 "$ errOp "
1179                                 "$ errText "
1180                                 "$ errSleepTime "
1181                                 "$ errMatchedDN "
1182                         ") )",
1183                         &oc_errAbsObject },
1184                 { "( 1.3.6.1.4.1.4203.666.11.4.3.1 "
1185                         "NAME ( 'errObject' ) "
1186                         "SUP errAbsObject STRUCTURAL "
1187                         ")",
1188                         &oc_errObject },
1189                 { "( 1.3.6.1.4.1.4203.666.11.4.3.2 "
1190                         "NAME ( 'errAuxObject' ) "
1191                         "SUP errAbsObject AUXILIARY "
1192                         ")",
1193                         &oc_errAuxObject },
1194                 { NULL }
1195         };
1196
1197
1198         for ( i = 0; retcode_at[ i ].desc != NULL; i++ ) {
1199                 code = register_at( retcode_at[ i ].desc, retcode_at[ i ].ad, 0 );
1200                 if ( code ) {
1201                         Debug( LDAP_DEBUG_ANY,
1202                                 "retcode: register_at failed\n", 0, 0, 0 );
1203                         return code;
1204                 }
1205         }
1206
1207         for ( i = 0; retcode_oc[ i ].desc != NULL; i++ ) {
1208                 code = register_oc( retcode_oc[ i ].desc, retcode_oc[ i ].oc, 0 );
1209                 if ( code ) {
1210                         Debug( LDAP_DEBUG_ANY,
1211                                 "retcode: register_oc failed\n", 0, 0, 0 );
1212                         return code;
1213                 }
1214         }
1215
1216         retcode.on_bi.bi_type = "retcode";
1217
1218         retcode.on_bi.bi_db_init = retcode_db_init;
1219         retcode.on_bi.bi_db_config = retcode_db_config;
1220         retcode.on_bi.bi_db_open = retcode_db_open;
1221         retcode.on_bi.bi_db_destroy = retcode_db_destroy;
1222
1223         retcode.on_bi.bi_op_add = retcode_op_func;
1224         retcode.on_bi.bi_op_bind = retcode_op_func;
1225         retcode.on_bi.bi_op_compare = retcode_op_func;
1226         retcode.on_bi.bi_op_delete = retcode_op_func;
1227         retcode.on_bi.bi_op_modify = retcode_op_func;
1228         retcode.on_bi.bi_op_modrdn = retcode_op_func;
1229         retcode.on_bi.bi_op_search = retcode_op_func;
1230
1231         retcode.on_bi.bi_extended = retcode_op_func;
1232
1233         retcode.on_response = retcode_response;
1234
1235         return overlay_register( &retcode );
1236 }
1237
1238 #if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC
1239 int
1240 init_module( int argc, char *argv[] )
1241 {
1242         return retcode_initialize();
1243 }
1244 #endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */
1245
1246 #endif /* SLAPD_OVER_RETCODE */