1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24
25 /*
26 * DESCRIPTION: Contains top level functions to read/write to the DIT. These
27 * are the API between the shim and the mapping system.
28 * Things calling these should have no knowledge of LDAP. Things
29 * called by them should have no knowledge of NIS.
30 *
31 * Error handling here may appear to be limited but, because the
32 * NIS protocol cannot carry meaningful information about why a
33 * N2L operation failed, functions that don't work log
34 * an error and then just return FAILURE.
35 *
36 */
37
38 /*
39 * Includes. WE WANT TO USE REAL DBM FUNCTIONS SO DO NOT INCLUDE SHIM_HOOKS.H.
40 */
41 #include <unistd.h>
42 #include <syslog.h>
43 #include <ndbm.h>
44 #include <sys/systeminfo.h>
45 #include <string.h>
46 #include <lber.h>
47 #include <ldap.h>
48 #include <errno.h>
49 #include "ypsym.h"
50 #include "ypdefs.h"
51 #include "shim.h"
52 #include "../ldap_structs.h"
53 #include "../ldap_parse.h"
54 #include "../nisdb_ldap.h"
55 #include "../ldap_util.h"
56 #include "../ldap_op.h"
57 #include "../ldap_attr.h"
58 #include "../nis_parse_ldap_conf.h"
59 #include "../nisdb_mt.h"
60 #include "yptol.h"
61 #include "dit_access_utils.h"
62 #include "stdio.h"
63
64 extern bool delete_map(char *name);
65 extern bool rename_map(char *from, char *to, bool_t secure_map);
66
67 /* Enable standard YP code features defined in ypdefs.h */
68 USE_YP_MASTER_NAME
69 USE_YP_DOMAIN_NAME
70 USE_YP_SECURE
71 USE_YP_INTERDOMAIN
72
73 /*
74 * Decs
75 */
76 suc_code add_special_entries(DBM *, map_ctrl *, bool_t *);
77 void free_null_terminated_list(char **list);
78
79
80 /*
81 * FUNCTION: is_yptol_mode();
82 *
83 * DESCRIPTION: Determines if we should run in N2L or traditional mode based
84 * on the presence of the N2L mapping file. If there are problems
85 * with the file, e.g. unreadable, this will be picked up latter.
86 *
87 * INPUTS: Nothing
88 *
89 * OUTPUTS: TRUE = Run in N2L mode
90 * FALSE = Run in traditional mode.
91 */
92 bool_t
is_yptol_mode()93 is_yptol_mode()
94 {
95 struct stat filestat;
96
97 if (stat(YP_DEFAULTCONFFILE, &filestat) != -1)
98 return (TRUE);
99
100 return (FALSE);
101 }
102
103 /*
104 * FUNCTION: read_from_dit();
105 *
106 * DESCRIPTION: Read (i.e. get and map) a single NIS entry from the LDAP DIT.
107 * Also handles retry attempts, on failure, and interpretation of
108 * internal error codes.
109 *
110 * INPUTS: Map name (unqualified)
111 * Domain name
112 * Entry key
113 * Pointer to return location
114 *
115 * OUTPUTS: If successful DBM datum containing result.
116 * On error DBM datum pointing to NULL and, if the cached value
117 * is not to be used, an error code.
118 */
119 int
read_from_dit(char * map,char * domain,datum * key,datum * value)120 read_from_dit(char *map, char *domain, datum *key, datum *value)
121 {
122 int count;
123 int res;
124 __nisdb_retry_t *retrieveRetry;
125
126 /* Initialize tsd */
127 __nisdb_get_tsd()->domainContext = 0;
128 __nisdb_get_tsd()->escapeFlag = '\0';
129
130 for (count = 0; count < ypDomains.numDomains; count++) {
131 if (0 == ypDomains.domainLabels[count])
132 continue;
133 if (0 == strcasecmp(domain, ypDomains.domainLabels[count])) {
134 __nisdb_get_tsd()->domainContext =
135 ypDomains.domains[count];
136 break;
137 }
138 }
139
140 retrieveRetry = &ldapDBTableMapping.retrieveErrorRetry;
141
142 /* Loop 'attempts' times of forever if -1 */
143 for (count = retrieveRetry->attempts; (0 <= count) ||
144 (-1 == retrieveRetry->attempts); count --) {
145 if (TRUE == singleReadFromDIT(map, domain, key, value, &res))
146 /* It worked, return value irrelevant */
147 return (0);
148
149 if (LDAP_TIMEOUT == res) { /* Exceeded search timeout */
150 value->dptr = NULL;
151 return (0);
152 }
153
154 if (is_fatal_error(res))
155 break;
156
157 /*
158 * Didn't work. If not the special case where no repeats are
159 * done sleep.
160 */
161 if (0 != retrieveRetry->attempts)
162 (void) poll(NULL, 0, retrieveRetry->timeout*1000);
163 }
164
165 /* Make sure returned pointer is NULL */
166 value->dptr = NULL;
167
168 /* If we get here access failed work out what to return */
169 if (ldapDBTableMapping.retrieveError == use_cached)
170 return (0);
171
172 return (res);
173 }
174
175 /*
176 * FUNCTION: write_to_dit();
177 *
178 * DESCRIPTION: Maps and writes a NIS entry to the LDAP DIT.
179 * Also handles retry attempts, on failure, and interpretation of
180 * internal error codes.
181 *
182 * INPUTS: Pointer to (unqualified) map name
183 * Pointer to domain name
184 * The entries key
185 * What to write
186 * Replace flag indicating
187 * TRUE = Replace (overwrite) any existing entries
188 * FALSE = Return error if there are existing entries
189 * Flag indicating if we should tolerate mapping errors.
190 *
191 * OUTPUTS: SUCCESS = Write was successful
192 * FAILURE = Write failed
193 *
194 */
195 suc_code
write_to_dit(char * map,char * domain,datum key,datum value,bool_t replace,bool_t ignore_map_errs)196 write_to_dit(char *map, char *domain, datum key, datum value,
197 bool_t replace, bool_t ignore_map_errs)
198 {
199 int count;
200 int res;
201 __nisdb_retry_t *storeRetry = &ldapDBTableMapping.storeErrorRetry;
202
203 /* Initialize tsd */
204 __nisdb_get_tsd()->domainContext = 0;
205 __nisdb_get_tsd()->escapeFlag = '\0';
206
207 for (count = 0; count < ypDomains.numDomains; count++) {
208 if (0 == ypDomains.domainLabels[count])
209 continue;
210 if (0 == strcasecmp(domain, ypDomains.domainLabels[count])) {
211 __nisdb_get_tsd()->domainContext =
212 ypDomains.domains[count];
213 break;
214 }
215 }
216
217 storeRetry = &ldapDBTableMapping.storeErrorRetry;
218
219 /* Loop 'attempts' times of forever if -1 */
220 for (count = storeRetry->attempts; (0 <= count) ||
221 (-1 == storeRetry->attempts); count --) {
222 res = singleWriteToDIT(map, domain, &key, &value, replace);
223 if (LDAP_SUCCESS == res)
224 return (SUCCESS);
225
226 if (is_fatal_error(res)) {
227 /*
228 * The mapping failed and will fail again if it is
229 * retried. However there are some cases where an
230 * actual mapping fault (rather than a LDAP problem)
231 * may be ignored.
232 */
233 if (ignore_map_errs) {
234 switch (res) {
235 case LDAP_INVALID_DN_SYNTAX:
236 case LDAP_OBJECT_CLASS_VIOLATION:
237 case LDAP_NOT_ALLOWED_ON_RDN:
238 case MAP_NAMEFIELD_MATCH_ERROR:
239 case MAP_NO_DN:
240 return (SUCCESS);
241 default:
242 break;
243 }
244 }
245 return (FAILURE);
246 }
247
248 if (ldapDBTableMapping.storeError != sto_retry)
249 return (FAILURE);
250
251 /*
252 * Didn't work. If not the special case where no repeats are
253 * done sleep.
254 */
255 if (0 != storeRetry->attempts)
256 (void) poll(NULL, 0, storeRetry->timeout*1000);
257
258 }
259 return (FAILURE);
260 }
261
262 /*
263 * FUNCTION : get_ttl_value()
264 *
265 * DESCRIPTION: Get the TTL value, derived from mapping file or DIT, for a
266 * entry.
267 *
268 * GIVEN : Pointer to map
269 * A flag indication if TTL should be max, min or random
270 *
271 * RETURNS : TTL value in seconds.
272 * -1 on failure
273 */
274 int
get_ttl_value(map_ctrl * map,TTL_TYPE type)275 get_ttl_value(map_ctrl *map, TTL_TYPE type)
276 {
277 __nis_table_mapping_t *table_map;
278 int interval, res;
279 char *myself = "get_ttl_value";
280
281 /* Get the mapping structure corresponding to `map.domain' */
282 table_map = mappingFromMap(map->map_name, map->domain, &res);
283
284 if (0 == table_map) {
285 logmsg(MSG_NOTIMECHECK, LOG_ERR,
286 "Get TTL request could not access map %s in domain %s "
287 "(error %d)", map->map_name, map->domain, res);
288 return (-1);
289 }
290
291 switch (type) {
292 case TTL_MAX:
293 return (table_map->initTtlHi);
294
295 case TTL_MIN:
296 return (table_map->initTtlLo);
297
298 default:
299 logmsg(MSG_NOTIMECHECK, LOG_INFO,
300 "%s passed illegal TTL type (%d)", myself, type);
301 /* If unknown TTL type drop through to TTL_RAND */
302 /* FALLTHROUGH */
303
304 case TTL_RAND:
305 interval = table_map->initTtlHi - table_map->initTtlLo;
306 if (0 >= interval)
307 return (table_map->initTtlLo);
308
309 /*
310 * Must get a random value. We assume srand48() got
311 * called at initialization.
312 */
313 return (lrand48() % interval);
314
315 case TTL_RUNNING:
316 return (table_map->ttl);
317
318
319 }
320 }
321
322 /*
323 * FUNCTION : get_mapping_domain_list()
324 *
325 * DESCRIPTION: Gets a list of domain names specified, by nisLDAPdomainContext
326 * attributes, in the mapping file. This is used only for initial
327 * DIT setup. Once the DIT has been set up get_domain_list() is
328 * used instead.
329 *
330 * GIVEN : Pointer returned array.
331 *
332 * RETURNS : Number of element in returned array.
333 * Array of elements this is in static memory
334 * and must not be freed by the caller.
335 */
336 int
get_mapping_domain_list(char *** ptr)337 get_mapping_domain_list(char ***ptr)
338 {
339 *ptr = ypDomains.domainLabels;
340 return (ypDomains.numDomains);
341 }
342
343 /*
344 * FUNCTION : get_mapping_yppasswdd_domain_list()
345 *
346 * DESCRIPTION: Gets a list of domain names specified, by the
347 * nisLDAPyppasswddDomains attribute, in the mapping file. This
348 * is the list of domains for which passwords should be changed.
349 *
350 * GIVEN : Pointer returned array
351 *
352 * RETURNS : Number of element in returned array.
353 * 0 if no nisLDAPyppasswddDomains attribute is present.
354 * Array of elements this is in static memory
355 * and must not be freed by the caller.
356 */
357 int
get_mapping_yppasswdd_domain_list(char *** ptr)358 get_mapping_yppasswdd_domain_list(char ***ptr)
359 {
360 *ptr = ypDomains.yppasswddDomainLabels;
361 return (ypDomains.numYppasswdd);
362 }
363
364 /*
365 * FUNCTION : free_map_list()
366 *
367 * DESCRIPTION: Frees a map list.
368 *
369 * GIVEN : Pointer to the map list.
370 *
371 * RETURNS : Nothing
372 */
373 void
free_map_list(char ** map_list)374 free_map_list(char **map_list)
375 {
376 free_null_terminated_list(map_list);
377 }
378
379 /*
380 * FUNCTION : get_passwd_list()
381 *
382 * DESCRIPTION: Gets a list of either passwd or passwd.adjunct map files
383 * defined in the mapping file. These are the files which have
384 * 'magic' nisLDAPdatabaseIdMapping entries aliasing them to
385 * passwd or passwd.adjunct. This function is required so that
386 * yppasswdd can work out which maps to synchronize with any
387 * password changes.
388 *
389 * This information is not currently stored by the parser but
390 * we can recover it from the hash table. This makes hard work but
391 * passwords should not be changed very frequently
392 *
393 * GIVEN : Flag indicating if a list is required for passwd or
394 * passwd.adjunct
395 * Domain to return the list for.
396 *
397 * RETURNS : Null terminated list of map names in malloced memory. To be
398 * freed by caller. (Possibly empty if no passwd maps found)
399 * NULL on error
400 */
401 char **
get_passwd_list(bool_t adjunct,char * domain)402 get_passwd_list(bool_t adjunct, char *domain)
403 {
404 char *myself = "get_passwd_list";
405 __nis_hash_item_mt *it;
406 int i, size;
407 char *end_ptr;
408 char *target; /* What we are looking for */
409 int target_len;
410 int domain_len;
411 char **res; /* Result array */
412 char **res_old; /* Old value of res during realloc */
413 int array_size; /* Current malloced size */
414 int res_count = 0; /* Current result count */
415
416 /*
417 * Always need an array even if just for terminator. Normally one
418 * chunk will be enough.
419 */
420 res = am(myself, ARRAY_CHUNK * sizeof (char *));
421 if (NULL == res)
422 return (NULL);
423 array_size = ARRAY_CHUNK;
424
425 /* Set up target */
426 if (adjunct)
427 target = PASSWD_ADJUNCT_PREFIX;
428 else
429 target = PASSWD_PREFIX;
430 target_len = strlen(target);
431 domain_len = strlen(domain);
432
433 /* Work out hash table length */
434 size = sizeof (ldapMappingList.keys) / sizeof (ldapMappingList.keys[0]);
435 /* For all hash table entries */
436 for (i = 0; i < size; i++) {
437 /* Walk linked list for this hash table entry */
438 for (it = ldapMappingList.keys[i]; NULL != it; it = it->next) {
439 /* Check right map */
440 if ((target_len + domain_len + 1) > strlen(it->name))
441 continue;
442 if (0 != strncmp(it->name, target, target_len))
443 continue;
444
445 /* Check right domain (minus trailing dot) */
446 if (strlen(domain) >= strlen(it->name))
447 continue;
448 end_ptr = it->name + strlen(it->name) -
449 strlen(domain) - 1;
450 if (',' != *(end_ptr - 1))
451 continue;
452 if (0 != strncmp(end_ptr, domain, strlen(domain)))
453 continue;
454
455 /* Check if we need to enlarge array */
456 if ((res_count + 1) >= array_size) {
457 array_size += ARRAY_CHUNK;
458 res_old = res;
459 res = realloc(res, array_size *
460 sizeof (char *));
461 if (NULL == res) {
462 res_old[res_count] = NULL;
463 free_passwd_list(res_old);
464 return (NULL);
465 }
466 }
467
468 /* What we really need is strndup() */
469 res[res_count] = am(myself, end_ptr - it->name + 1);
470 if (NULL == res[res_count]) {
471 free_passwd_list(res);
472 return (NULL);
473 }
474
475 /* Copy from start to end_ptr */
476 (void) memcpy(res[res_count], it->name,
477 end_ptr-it->name - 1);
478 res_count ++;
479 }
480 }
481
482 /* Terminate array */
483 res[res_count] = NULL;
484 return (res);
485 }
486
487 /*
488 * FUNCTION : free_passwd_list()
489 *
490 * DESCRIPTION: Frees a password list obtained with get_passwd_list()
491 *
492 * INPUTS : Address of list to free.
493 *
494 * OUTPUTS : Nothing
495 */
496 void
free_passwd_list(char ** list)497 free_passwd_list(char **list)
498 {
499 free_null_terminated_list(list);
500 }
501
502 /*
503 * FUNCTION : free_null_terminated_list()
504 *
505 * DESCRIPTION: Frees a generic null terminated list.
506 *
507 * INPUTS : Address of list to free.
508 *
509 * OUTPUTS : Nothing
510 */
511 void
free_null_terminated_list(char ** list)512 free_null_terminated_list(char **list)
513 {
514 int index;
515
516 /* Free all the strings */
517 for (index = 0; NULL != list[index]; index ++)
518 sfree(list[index]);
519
520 /* Free the array */
521 sfree(list);
522 }
523
524
525 /*
526 * FUNCTION : add_special_entries()
527 *
528 * DESCRIPTION: Adds the special (YP_*) entries to a map.
529 *
530 * Part of dit_access because requires access to the mapping
531 * file in order to work out if secure and interdomain entries
532 * should be created.
533 *
534 * GIVEN : Pointer to an open, temporary, DBM file
535 * Pointer to map information (do not use DBM fields).
536 * Pointer to a location in which to return security flag
537 *
538 * RETURNS : SUCCESS = All entries created
539 * FAILURE = Some entries not created
540 */
541 suc_code
add_special_entries(DBM * db,map_ctrl * map,bool_t * secure_flag)542 add_special_entries(DBM *db, map_ctrl *map, bool_t *secure_flag)
543 {
544 char local_host[MAX_MASTER_NAME];
545 __nis_table_mapping_t *table_map;
546 int res;
547
548 /* Last modified time is now */
549 update_timestamp(db);
550
551 /* Add domain name */
552 addpair(db, yp_domain_name, map->domain);
553
554 /* For N2L mode local machine is always the master */
555 sysinfo(SI_HOSTNAME, local_host, sizeof (local_host));
556 addpair(db, yp_master_name, local_host);
557
558 /* Get the mapping structure corresponding to `map.domain' */
559 table_map = mappingFromMap(map->map_name, map->domain, &res);
560 if (0 == table_map)
561 return (FAILURE);
562
563 /* Add secure and interdomain flags if required */
564 if (table_map->securemap_flag) {
565 addpair(db, yp_secure, "");
566 *secure_flag = TRUE;
567 } else {
568 *secure_flag = FALSE;
569 }
570 if (table_map->usedns_flag)
571 addpair(db, yp_interdomain, "");
572
573 return (SUCCESS);
574 }
575
576 /*
577 * FUNCTION: update_map_from_dit()
578 *
579 * DESCRIPTION: Core code called to update an entire map.
580 * Information is recovered from LDAP and used to build a duplicate
581 * copy of the live maps. When this is complete the maps are
582 * locked and then overwritten by the new copy.
583 *
584 * INPUTS: map_ctrl containing lots of information about the map and a
585 * pointer to it's lock which will be required.
586 * Flag indicating if progress logging is required.
587 *
588 * OUTPUTS: SUCCESS = Map updated
589 * FAILURE = Map not updated
590 */
591 suc_code
update_map_from_dit(map_ctrl * map,bool_t log_flag)592 update_map_from_dit(map_ctrl *map, bool_t log_flag) {
593 __nis_table_mapping_t *t;
594 __nis_rule_value_t *rv;
595 __nis_ldap_search_t *ls;
596 __nis_object_dn_t *objectDN = NULL;
597 datum *datval, *datkey;
598 int nr = 0, i, j, nv, numDNs;
599 int statP = SUCCESS, flag;
600 char *objname, **dn;
601 /* Name of temporary entries DBM file */
602 char *temp_entries;
603 /* Name of temporary TTL DBM file */
604 char *temp_ttl;
605 /* Temporary DBM handles */
606 DBM *temp_entries_db;
607 DBM *temp_ttl_db;
608 map_ctrl temp_map;
609 datum key;
610 char *myself = "update_map_from_dit";
611 bool_t secure_flag;
612 int entry_count = 1;
613 int next_print = PRINT_FREQ;
614 int search_flag = SUCCESS;
615
616 int m;
617
618 /* list of maps whose keys will be transliterated to lowercase */
619 char *xlate_to_lcase_maps[] = {
620 "hosts.byname",
621 "ipnodes.byname",
622 NULL
623 };
624 bool_t xlate_to_lcase = FALSE;
625
626 if (!map || !map->map_name || !map->domain) {
627 return (FAILURE);
628 }
629
630 __nisdb_get_tsd()->escapeFlag = '\0';
631
632 /*
633 * netgroup.byxxx maps are a special case. They are regenerated from
634 * the netgroup map, not the DIT, so handle special case.
635 */
636 if ((0 == strcmp(map->map_name, NETGROUP_BYHOST)) ||
637 0 == (strcmp(map->map_name, NETGROUP_BYUSER))) {
638 return (update_netgroup_byxxx(map));
639 }
640
641 /* Get the mapping information for the map */
642 if ((t = mappingFromMap(map->map_name, map->domain, &statP)) == 0) {
643 if (statP == MAP_NO_MAPPING_EXISTS)
644 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
645 "%s: No mapping information available for %s,%s",
646 myself, map->map_name, map->domain);
647 return (FAILURE);
648 }
649
650 /* Allocate and set up names */
651 if (SUCCESS != alloc_temp_names(map->map_path,
652 &temp_entries, &temp_ttl)) {
653 logmsg(MSG_NOTIMECHECK, LOG_ERR,
654 "%s: Unable to create map names for %s",
655 myself, map->map_path);
656 return (FAILURE);
657 }
658
659 /* Create temp entry and TTL file */
660 if ((temp_entries_db = dbm_open(temp_entries, O_RDWR | O_CREAT, 0644))
661 == NULL) {
662 logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s",
663 myself, temp_entries);
664 sfree(temp_entries);
665 sfree(temp_ttl);
666 return (FAILURE);
667 }
668
669 if ((temp_ttl_db = dbm_open(temp_ttl, O_RDWR | O_CREAT, 0644))
670 == NULL) {
671 logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s",
672 myself, temp_ttl);
673 dbm_close(temp_entries_db);
674 delete_map(temp_entries);
675 sfree(temp_entries);
676 sfree(temp_ttl);
677 return (FAILURE);
678 }
679
680 /* Initialize domainContext tsd */
681 __nisdb_get_tsd()->domainContext = 0;
682 for (i = 0; i < ypDomains.numDomains; i++) {
683 if (0 == ypDomains.domainLabels[i])
684 continue;
685 if (0 == strcasecmp(map->domain, ypDomains.domainLabels[i])) {
686 __nisdb_get_tsd()->domainContext = ypDomains.domains[i];
687 break;
688 }
689 }
690
691 if (!(objname = getFullMapName(map->map_name, map->domain))) {
692 if (temp_entries_db)
693 dbm_close(temp_entries_db);
694 if (temp_ttl_db)
695 dbm_close(temp_ttl_db);
696 delete_map(temp_entries);
697 sfree(temp_entries);
698 delete_map(temp_ttl);
699 sfree(temp_ttl);
700 return (FAILURE);
701 }
702
703 /*
704 * set xlate_to_lcase to TRUE if map_name is found in
705 * xlate_to_lcase_maps[]
706 */
707 m = 0;
708 while (xlate_to_lcase_maps[m] != NULL) {
709 if (strncmp(map->map_name, xlate_to_lcase_maps[m],
710 strlen(xlate_to_lcase_maps[m])) == 0) {
711 xlate_to_lcase = TRUE;
712 break;
713 }
714 ++m;
715 }
716
717 /* Try each mapping for the map */
718 for (flag = 0; t != 0 && search_flag != FAILURE; t = t->next) {
719
720 /* Check if the mapping is the correct one */
721 if (strcmp(objname, t->objName) != 0) {
722 continue;
723 }
724
725 /* Check if rulesFromLDAP are provided */
726 if (t->numRulesFromLDAP == 0) {
727 logmsg(MSG_NOTIMECHECK, LOG_ERR,
728 "%s: No rulesFromLDAP available for %s (%s)",
729 myself, t->dbId, map->map_name);
730 continue;
731 }
732
733 /* Set flag to indicate update is enabled */
734 flag = 1;
735 /* Create ldap request for enumeration */
736 for (objectDN = t->objectDN;
737 objectDN && objectDN->read.base;
738 objectDN = objectDN->next) {
739 if ((ls = createLdapRequest(t, 0, 0, 1, NULL,
740 objectDN)) == 0) {
741 logmsg(MSG_NOTIMECHECK, LOG_ERR,
742 "%s: Failed to create "
743 "ldapSearch request for "
744 "%s (%s) for base %s",
745 myself, t->dbId,
746 map->map_name,
747 objectDN->read.base);
748 statP = FAILURE;
749 search_flag = FAILURE;
750 break;
751 }
752
753 if (log_flag) {
754 printf("Waiting for LDAP search results.\n");
755 }
756
757 /* Query LDAP */
758 nr = (ls->isDN)?0:-1;
759 rv = ldapSearch(ls, &nr, 0, &statP);
760 freeLdapSearch(ls);
761 if (rv == 0) {
762 if (statP == LDAP_NO_SUCH_OBJECT) {
763 /*
764 * No Entry exists in the ldap server. Not
765 * a problem. Maybe there are just no entries
766 * in this map.
767 */
768 continue;
769 }
770 logmsg(MSG_NOTIMECHECK, LOG_ERR,
771 "%s: ldapSearch error %d "
772 "(%s) for %s (%s) for base %s",
773 myself, statP, ldap_err2string(statP),
774 t->dbId, map->map_name,
775 objectDN->read.base);
776 statP = FAILURE;
777 search_flag = FAILURE;
778 break;
779 }
780
781 if (log_flag) {
782 printf("Processing search results.\n");
783 }
784
785 /* Obtain list of DNs for logging */
786 if ((dn = findDNs(myself, rv, nr, 0, &numDNs)) == 0) {
787 statP = FAILURE;
788 search_flag = FAILURE;
789 break;
790 }
791
792 /* For each entry in the result do the following */
793 for (i = 0; i < nr; i++) {
794 /* Convert LDAP data to NIS equivalents */
795 statP = buildNISRuleValue(t, &rv[i],
796 map->domain);
797 if (statP == MAP_INDEXLIST_ERROR)
798 continue;
799 if (statP != SUCCESS) {
800 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
801 "%s: Conversion error %d (LDAP to "
802 "name=value pairs) "
803 "for (dn: %s) for "
804 "%s (%s) for base %s",
805 myself, statP, NIL(dn[i]),
806 t->dbId, map->map_name,
807 objectDN->read.base);
808 continue;
809 }
810
811 /* Obtain the datum for value */
812 datval = ruleValueToDatum(t, &rv[i], &statP);
813 if (datval == 0) {
814 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
815 "%s: Conversion error %d "
816 "(name=value pairs to NIS)"
817 " for (dn: %s) for "
818 "%s (%s) for base %s",
819 myself, statP, NIL(dn[i]),
820 t->dbId, map->map_name,
821 objectDN->read.base);
822 continue;
823 }
824
825 /* Obtain the datum for key */
826 datkey = getKeyFromRuleValue(t, &rv[i],
827 &nv, &statP, xlate_to_lcase);
828 if (datkey == 0) {
829 logmsg(MSG_NOTIMECHECK, LOG_WARNING,
830 "%s: Unable to obtain NIS "
831 "key from LDAP data (dn:%s) "
832 "for %s (%s) for base %s",
833 myself, NIL(dn[i]), t->dbId,
834 map->map_name,
835 objectDN->read.base);
836 sfree(datval->dptr);
837 sfree(datval);
838 continue;
839 }
840
841 /* Write to the temporary map */
842 for (j = 0; j < nv; j++, entry_count ++) {
843 if (datkey[j].dsize == 0)
844 continue;
845 errno = 0;
846 /* DBM_INSERT to match */
847 /* singleReadFromDIT */
848 if (dbm_store(temp_entries_db,
849 datkey[j],
850 *datval,
851 DBM_INSERT) < 0) {
852 /*
853 * For some cases errno may
854 * still be 0 but dbm_error
855 * isn't informative at all.
856 */
857 logmsg(MSG_NOTIMECHECK,
858 LOG_WARNING,
859 "%s: dbm store error "
860 "(errno=%d) "
861 "for (key=%s, value=%s) "
862 "for %s (%s) for base %s",
863 myself,
864 errno,
865 datkey[j].dptr,
866 datval->dptr, t->dbId,
867 map->map_name,
868 objectDN->read.base);
869 /* clear the error */
870 dbm_clearerr(temp_entries_db);
871 }
872 sfree(datkey[j].dptr);
873
874 if (log_flag && (entry_count >=
875 next_print)) {
876 printf("%d entries processed\n",
877 entry_count);
878 next_print *= 2;
879 }
880
881 }
882 sfree(datkey);
883 sfree(datval->dptr);
884 sfree(datval);
885 }
886
887 freeRuleValue(rv, nr);
888 freeDNs(dn, numDNs);
889 } /* End of for over objectDN */
890 }
891 sfree(objname);
892
893 if (t != 0 || flag == 0 || search_flag == FAILURE) {
894 if (temp_entries_db)
895 dbm_close(temp_entries_db);
896 if (temp_ttl_db)
897 dbm_close(temp_ttl_db);
898 delete_map(temp_entries);
899 sfree(temp_entries);
900 delete_map(temp_ttl);
901 sfree(temp_ttl);
902 return (statP);
903 }
904 /* Set up enough of map_ctrl to call update_entry_ttl */
905 temp_map.map_name = map->map_name;
906 temp_map.domain = map->domain;
907 temp_map.ttl = temp_ttl_db;
908
909 /* Generate new TTL file */
910 key = dbm_firstkey(temp_entries_db);
911 while (key.dptr != 0) {
912 if (!is_special_key(&key))
913 /*
914 * We don't want all the entries to time out at the
915 * same time so create random TTLs.
916 */
917 if (FAILURE == update_entry_ttl(&temp_map, &key,
918 TTL_RAND))
919 logmsg(MSG_NOTIMECHECK, LOG_ERR,
920 "%s: Could not update TTL for "
921 "(key=%s) for map %s,%s",
922 myself, NIL(key.dptr), map->map_name,
923 map->domain);
924 key = dbm_nextkey(temp_entries_db);
925 }
926
927 /* Update map TTL */
928 if (SUCCESS != update_map_ttl(&temp_map)) {
929 logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not update map TTL "
930 "for %s,%s", myself, map->map_name, map->domain);
931 }
932
933 /* Set up 'special' nis entries */
934 add_special_entries(temp_entries_db, map, &secure_flag);
935
936 /* Close temp DBM files */
937 dbm_close(temp_entries_db);
938 dbm_close(temp_ttl_db);
939
940 /* Lock access to the map for copy */
941 lock_map_ctrl(map);
942
943 /* Move temp maps to real ones */
944 rename_map(temp_entries, map->map_path, secure_flag);
945 rename_map(temp_ttl, map->ttl_path, secure_flag);
946
947 /* Free file names */
948 sfree(temp_entries);
949 sfree(temp_ttl);
950
951 /* Unlock map */
952 unlock_map_ctrl(map);
953
954 return (SUCCESS);
955 }
956
957 /*
958 * FUNCTION : get_mapping_map_list()
959 *
960 * DESCRIPTION: Gets a list of nis maps for a given domain specified in the
961 * mapping file. This information is not saved so have to go
962 * through the entire hash table. At least this is only done at
963 * initialization time.
964 *
965 * GIVEN : Domain name
966 *
967 * RETURNS : List of map names in malloced memory. MUST BE FREED BY CALLER.
968 */
969 char **
get_mapping_map_list(char * domain)970 get_mapping_map_list(char *domain)
971 {
972 char *myself = "get_mapping_map_list";
973 __nis_hash_item_mt *it;
974 int i, j, size;
975 char *end_ptr;
976 char **res; /* Result array */
977 char **res_old; /* Old value of res during realloc */
978 int array_size; /* Current malloced size */
979 int res_count = 0; /* Current result count */
980
981 /*
982 * Always need an array even if just for terminator. Normally one
983 * chunk will be enough.
984 */
985 res = am(myself, ARRAY_CHUNK * sizeof (char *));
986 if (NULL == res)
987 return (NULL);
988 array_size = ARRAY_CHUNK;
989
990 /* Work out hash table length */
991 size = sizeof (ldapMappingList.keys) / sizeof (ldapMappingList.keys[0]);
992 /* For all hash table entries */
993 for (i = 0; i < size; i++) {
994 /* Walk linked list for this hash table entry */
995 for (it = ldapMappingList.keys[i]; NULL != it; it = it->next) {
996
997 /* Check it's not a split field entry */
998 if (0 != ((__nis_table_mapping_t *)it)->numSplits)
999 continue;
1000
1001 /* Check right domain (minus trailing dot) */
1002 if (strlen(domain) >= strlen(it->name))
1003 continue;
1004 end_ptr = it->name + strlen(it->name) -
1005 strlen(domain) - 1;
1006 if (',' != *(end_ptr - 1))
1007 continue;
1008 if (0 != strncmp(end_ptr, domain, strlen(domain)))
1009 continue;
1010
1011 /* Check if we need to enlarge array */
1012 if ((res_count + 1) >= array_size) {
1013 array_size += ARRAY_CHUNK;
1014 res_old = res;
1015 res = realloc(res, array_size *
1016 sizeof (char *));
1017 if (NULL == res) {
1018 res_old[res_count] = NULL;
1019 free_passwd_list(res_old);
1020 return (NULL);
1021 }
1022 }
1023
1024 /*
1025 * We will need the sequence number when we come to
1026 * sort the entries so for now store a pointer to
1027 * the __nis_hash_item_mt.
1028 */
1029 res[res_count] = (char *)it;
1030 res_count ++;
1031 }
1032 }
1033
1034 /* Terminate array */
1035 res[res_count] = NULL;
1036
1037 /* Bubble sort entries into the same order as mapping file */
1038 for (i = res_count - 2; 0 <= i; i--) {
1039 for (j = 0; j <= i; j++) {
1040 if (((__nis_table_mapping_t *)res[j + 1])->seq_num <
1041 ((__nis_table_mapping_t *)res[j])->seq_num) {
1042 end_ptr = res[j];
1043 res[j] = res[j+1];
1044 res[j + 1] = end_ptr;
1045 }
1046 }
1047 }
1048
1049 /* Finally copy the real strings in to each entry */
1050 for (i = 0; NULL != res[i]; i ++) {
1051
1052 /* Get hash table entry back */
1053 it = (__nis_hash_item_mt *)res[i];
1054
1055 end_ptr = it->name + strlen(it->name) - strlen(domain) - 1;
1056
1057 /* What we really need is strndup() */
1058 res[i] = am(myself, end_ptr - it->name + 1);
1059 if (NULL == res[i]) {
1060 free_map_list(res);
1061 return (NULL);
1062 }
1063
1064 /* Copy from start to end_ptr */
1065 (void) memcpy(res[i], it->name, end_ptr-it->name - 1);
1066 }
1067
1068 return (res);
1069 }
1070
1071 /*
1072 * FUNCTION : make_nis_container()
1073 *
1074 * DESCRIPTION: Sets up container for map_name in the DIT.
1075 *
1076 * GIVEN : Map name
1077 * The domain name.
1078 * Flag indicating if container should be created.
1079 *
1080 * RETURNS : SUCCESS = It worked
1081 * FAILURE = There was a problem.
1082 */
1083 suc_code
make_nis_container(char * map_name,char * domain,bool_t init_containers)1084 make_nis_container(char *map_name, char *domain, bool_t init_containers) {
1085 int i, rc, statP = SUCCESS;
1086 __nis_table_mapping_t *t;
1087 char *dn;
1088 char *myself = "make_nis_container";
1089
1090 if (!map_name || !domain)
1091 return (FAILURE);
1092
1093 if (FALSE == init_containers) {
1094 /*
1095 * If we are not creating containers it is debatable what we
1096 * should do . Maybe we should check for a pre-
1097 * existing container and return failure if it does not exist.
1098 *
1099 * For now we assume the user will not have called us in this
1100 * mode unless they know what they are doing. So return
1101 * success. If they have got it wrong then latter writes will
1102 * fail.
1103 */
1104 return (SUCCESS);
1105 }
1106
1107 /* Get the mapping information for the map */
1108 if ((t = mappingFromMap(map_name, domain, &statP)) == 0) {
1109 if (statP == MAP_NO_MAPPING_EXISTS)
1110 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1111 "%s: No mapping information available for %s,%s",
1112 myself, NIL(map_name), NIL(domain));
1113 return (FAILURE);
1114 }
1115
1116 /* Two times. One for readDN and other for writeDN */
1117 for (i = 0; i < 2; i++) {
1118 if (i == 0)
1119 dn = t->objectDN->read.base;
1120 else {
1121 if (t->objectDN->write.base == 0) {
1122 logmsg(MSG_NOTIMECHECK, LOG_INFO,
1123 "%s: No baseDN in writespec. Write "
1124 "disabled for %s,%s",
1125 myself, map_name, domain);
1126 break;
1127 }
1128 if (!strcasecmp(dn, t->objectDN->write.base))
1129 break;
1130 dn = t->objectDN->write.base;
1131 }
1132
1133 if ((rc = makeNISObject(0, dn)) == FAILURE) {
1134 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1135 "%s: Unable to create ldap container (dn: %s) "
1136 "for %s,%s", myself, dn, map_name, domain);
1137 return (FAILURE);
1138 }
1139 }
1140 return (SUCCESS);
1141 }
1142
1143 /*
1144 * FUNCTION : make_nis_domain()
1145 *
1146 * DESCRIPTION: Sets up a nisDomainObject in the DIT
1147 *
1148 * GIVEN: Name of the domain
1149 * Flag indicating if domain should be create or possibly just
1150 * checked for.
1151 */
1152 suc_code
make_nis_domain(char * domain,bool_t init_containers)1153 make_nis_domain(char *domain, bool_t init_containers) {
1154
1155 if (FALSE == init_containers) {
1156 /*
1157 * If we are not creating containers it is debatable what we
1158 * should do with domains. Maybe we should check for a pre-
1159 * existing domain and return failure if it does not exist.
1160 *
1161 * For now we assume the user will not have called us in this
1162 * mode unless they know what they are doing. So return
1163 * success. If they have got it wrong then latter writes will
1164 * fail.
1165 */
1166 return (SUCCESS);
1167 }
1168
1169 /* Create the domain */
1170 return (makeNISObject(domain, 0));
1171 }
1172
1173 /*
1174 * FUNCTION: update_netgroup_byxxx()
1175 *
1176 * DESCRIPTION: Updates the netgroup.byxxx series of maps based on the current
1177 * netgroup file. We invoke revnetgroup so that if any changes
1178 * are made to this utility the same changes are made here.
1179 *
1180 * INPUTS: map_ctrl containing lots of information about the map and a
1181 * pointer to it's lock which will be required.
1182 *
1183 * OUTPUTS: SUCCESS = Map updated
1184 * FAILURE = Map not updated
1185 */
1186 suc_code
update_netgroup_byxxx(map_ctrl * map)1187 update_netgroup_byxxx(map_ctrl *map) {
1188 /* Name of temporary entries DBM file */
1189 char *temp_entries;
1190 /* Name of temporary TTL DBM file */
1191 char *temp_ttl;
1192 /* Temporary DBM handles */
1193 DBM *temp_entries_db;
1194 DBM *temp_ttl_db;
1195 map_ctrl temp_map;
1196 char *myself = "update_netgroup_byxxx";
1197 char *cmdbuf;
1198 int cmd_length;
1199 datum key;
1200 map_ctrl *netgroupmap;
1201 int res;
1202 /* Temporary revnetgroup files */
1203 const char *byhost = NETGROUP_BYHOST "_REV" TEMP_POSTFIX;
1204 const char *byuser = NETGROUP_BYUSER "_REV" TEMP_POSTFIX;
1205 const char *temp_file_name;
1206
1207
1208 /*
1209 * We need to use two different temporary files: one for netgroup.byhost
1210 * and other for netgroup.byuser, since these two maps can be updated
1211 * simultaneously. These temporary files will hold the output of
1212 * revnetgroup [-h|-u] command. They are then used to generate the
1213 * corresponding dbm files and thereafter deleted.
1214 */
1215 if (0 == strcmp(map->map_name, NETGROUP_BYHOST))
1216 temp_file_name = byhost;
1217 else
1218 temp_file_name = byuser;
1219
1220 /* Alloc enough cmd buf for revnet cmd */
1221 cmd_length = strlen("/usr/sbin/makedbm -u ") +
1222 (strlen(map->map_path) - strlen(map->map_name)) +
1223 strlen(NETGROUP_MAP) +
1224 strlen(" | /usr/sbin/revnetgroup -h > ") +
1225 (strlen(map->map_path) - strlen(map->map_name)) +
1226 strlen(temp_file_name) + 1;
1227 cmdbuf = am(myself, cmd_length);
1228
1229 if (NULL == cmdbuf) {
1230 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1231 "%s: Could not alloc cmdbuf.", myself);
1232 return (FAILURE);
1233 }
1234
1235 /*
1236 * If necessary update (and wait for) netgroups map. This is a lot of
1237 * work but if the netgroup map itself is not being accessed it may
1238 * contain information that is not up to date with the DIT.
1239 *
1240 * We use the cmdbuf to store the qualified netgroup map name there will
1241 * be enough space for this but we are not yet creating the cmd.
1242 */
1243 strlcpy(cmdbuf, map->map_path, strlen(map->map_path) -
1244 strlen(map->map_name) + 1);
1245 strcat(cmdbuf, NETGROUP_MAP);
1246 netgroupmap = (map_ctrl *)shim_dbm_open(cmdbuf,
1247 O_RDWR | O_CREAT, 0644);
1248 if (NULL == netgroupmap) {
1249 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1250 "%s: Could not update %s.", myself, cmdbuf);
1251 sfree(cmdbuf);
1252 return (FAILURE);
1253 }
1254
1255 if (has_map_expired(netgroupmap)) {
1256 lock_map_ctrl(netgroupmap);
1257 update_map_if_required(netgroupmap, TRUE);
1258 unlock_map_ctrl(netgroupmap);
1259 }
1260 shim_dbm_close((DBM *)netgroupmap);
1261
1262 /* Dump netgroup file through revnetgroup to a temp file */
1263 strcpy(cmdbuf, "/usr/sbin/makedbm -u ");
1264
1265 /* Unmake the netgroup file in same domain as map */
1266 strncat(cmdbuf, map->map_path, strlen(map->map_path) -
1267 strlen(map->map_name));
1268 strcat(cmdbuf, NETGROUP_MAP);
1269
1270 if (0 == strcmp(map->map_name, NETGROUP_BYHOST)) {
1271 strcat(cmdbuf, " | /usr/sbin/revnetgroup -h > ");
1272 } else {
1273 strcat(cmdbuf, " | /usr/sbin/revnetgroup -u > ");
1274 }
1275
1276 /* Create temp file file in same domain as map */
1277 strncat(cmdbuf, map->map_path, strlen(map->map_path) -
1278 strlen(map->map_name));
1279 strcat(cmdbuf, temp_file_name);
1280
1281 if (0 > system(cmdbuf)) {
1282 logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not run \"%s\" "
1283 "(errno=%d)", myself, cmdbuf, errno);
1284 sfree(cmdbuf);
1285 return (FAILURE);
1286 }
1287 sfree(cmdbuf);
1288
1289 /* Allocate and set up names */
1290 if (SUCCESS != alloc_temp_names(map->map_path,
1291 &temp_entries, &temp_ttl)) {
1292 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1293 "%s: Unable to create map names for %s",
1294 myself, map->map_path);
1295 return (FAILURE);
1296 }
1297
1298 /* Make the temporary DBM file */
1299 cmdbuf = am(myself, strlen("/usr/sbin/makedbm") +
1300 (strlen(map->map_path) - strlen(map->map_name)) +
1301 strlen(temp_file_name) +
1302 strlen(temp_entries) + 3);
1303 if (NULL == cmdbuf) {
1304 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1305 "%s: Could not allocate cmdbuf.", myself);
1306 sfree(temp_entries);
1307 sfree(temp_ttl);
1308 return (FAILURE);
1309 }
1310
1311 strcpy(cmdbuf, "/usr/sbin/makedbm ");
1312 strncat(cmdbuf, map->map_path, strlen(map->map_path) -
1313 strlen(map->map_name));
1314 strcat(cmdbuf, temp_file_name);
1315 strcat(cmdbuf, " ");
1316 strcat(cmdbuf, temp_entries);
1317
1318 if (0 > system(cmdbuf)) {
1319 logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not run \"%s\" "
1320 "(errno=%d)", myself, cmdbuf, errno);
1321 sfree(cmdbuf);
1322 sfree(temp_entries);
1323 sfree(temp_ttl);
1324 return (FAILURE);
1325 }
1326
1327 /* Already have enough command buffer to rm temporary file */
1328 strlcpy(cmdbuf, map->map_path, strlen(map->map_path) -
1329 strlen(map->map_name) + 1);
1330 strcat(cmdbuf, temp_file_name);
1331 res = unlink(cmdbuf);
1332 /* If the temp file did not exist no problem. Probably had no entries */
1333 if ((0 != res) && (ENOENT != errno)) {
1334 logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not delete \"%s\" "
1335 "(errno=%d)", myself, cmdbuf, errno);
1336 sfree(temp_entries);
1337 sfree(temp_ttl);
1338 sfree(cmdbuf);
1339 return (FAILURE);
1340 }
1341 sfree(cmdbuf);
1342
1343 if ((temp_entries_db = dbm_open(temp_entries, O_RDWR | O_CREAT, 0644))
1344 == NULL) {
1345 logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s",
1346 myself, temp_entries);
1347 sfree(temp_entries);
1348 sfree(temp_ttl);
1349 return (FAILURE);
1350 }
1351
1352 if ((temp_ttl_db = dbm_open(temp_ttl, O_RDWR | O_CREAT, 0644))
1353 == NULL) {
1354 logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s",
1355 myself, temp_ttl);
1356 dbm_close(temp_entries_db);
1357 sfree(temp_entries);
1358 sfree(temp_ttl);
1359 return (FAILURE);
1360 }
1361
1362 /*
1363 * Set up enough of map_ctrl to call update_entry_ttl. Since there is
1364 * no mapping, and thus not TTL, defined for these maps use the TTL
1365 * values for netgroup map
1366 */
1367 temp_map.map_name = NETGROUP_MAP;
1368 temp_map.domain = map->domain;
1369 temp_map.ttl = temp_ttl_db;
1370
1371 /*
1372 * Generate new TTL file. Since these maps work only on the whole map
1373 * expiry these will not actually be used but there presence makes it
1374 * easier to handle these maps in the same way as other maps.
1375 */
1376 key = dbm_firstkey(temp_entries_db);
1377 while (key.dptr != 0) {
1378 if (!is_special_key(&key))
1379 /*
1380 * For these maps want all timouts to be maximum
1381 */
1382 if (FAILURE == update_entry_ttl(&temp_map, &key,
1383 TTL_MAX))
1384 logmsg(MSG_NOTIMECHECK, LOG_ERR,
1385 "%s: Could not update TTL for "
1386 "(key=%s) for map %s,%s",
1387 myself, NIL(key.dptr), map->map_name,
1388 map->domain);
1389 key = dbm_nextkey(temp_entries_db);
1390 }
1391
1392 /* Update map TTL */
1393 update_map_ttl(&temp_map);
1394
1395 /* Close temp DBM files */
1396 dbm_close(temp_entries_db);
1397 dbm_close(temp_ttl_db);
1398
1399 /* Lock access to the map for copy */
1400 lock_map_ctrl(map);
1401
1402 /* Move temp maps to real ones */
1403 rename_map(temp_entries, map->map_path, FALSE);
1404 rename_map(temp_ttl, map->ttl_path, FALSE);
1405
1406 /* Free file names */
1407 sfree(temp_entries);
1408 sfree(temp_ttl);
1409
1410 /* Unlock map */
1411 unlock_map_ctrl(map);
1412
1413 return (SUCCESS);
1414 }
1415