usrcache.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. /** BEGIN COPYRIGHT BLOCK
  2. * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
  3. * Copyright (C) 2005 Red Hat, Inc.
  4. * All rights reserved.
  5. * END COPYRIGHT BLOCK **/
  6. /* #define DBG_PRINT */
  7. #include <netsite.h>
  8. extern "C" {
  9. #include <secitem.h>
  10. }
  11. #include <base/crit.h>
  12. #include <ldaputil/errors.h>
  13. #include <libaccess/usrcache.h>
  14. #include <libaccess/las.h>
  15. #include <libaccess/authdb.h>
  16. #include "permhash.h"
  17. /* uid is unique within a database. The user cache tables are stored per
  18. * database. The following table maps a database name to the corresponding
  19. * user cache table. The user cache table is another hash table which stores
  20. * the UserCacheObj instances.
  21. */
  22. static PRHashTable *databaseUserCacheTable = 0;
  23. static time_t acl_usr_cache_lifetime = (time_t)120;
  24. static PRCList *usrobj_list = 0;
  25. static const int num_usrobj = 200;
  26. static CRITICAL usr_hash_crit = NULL; /* Controls user cache hash tables & */
  27. /* usrobj link list */
  28. static pool_handle_t *usrcache_pool = NULL;
  29. static PRHashTable *singleDbTable = 0;
  30. #define USEROBJ_PTR(l) \
  31. ((UserCacheObj*) ((char*) (l) - offsetof(UserCacheObj, list)))
  32. static void user_hash_crit_enter (void)
  33. {
  34. /* Caching may be disabled (usr_hash_crit will be NULL) */
  35. if (usr_hash_crit) crit_enter(usr_hash_crit);
  36. }
  37. static void user_hash_crit_exit (void)
  38. {
  39. /* Caching may be disabled (usr_hash_crit will be NULL) */
  40. if (usr_hash_crit) crit_exit(usr_hash_crit);
  41. }
  42. static void user_hash_crit_init (void)
  43. {
  44. usr_hash_crit = crit_init();
  45. }
  46. static PRHashNumber
  47. usr_cache_hash_cert(const void *key)
  48. {
  49. PRHashNumber h;
  50. const unsigned char *s;
  51. unsigned int i = 0;
  52. SECItem *derCert = (SECItem *)key;
  53. unsigned int len = derCert->len;
  54. h = 0;
  55. for (s = (const unsigned char *)derCert->data; i < len; s++, i++)
  56. h = (h >> 28) ^ (h << 4) ^ *s;
  57. return h;
  58. }
  59. static PRHashNumber
  60. usr_cache_hash_fn (const void *key)
  61. {
  62. UserCacheObj *usrObj = (UserCacheObj *)key;
  63. if (usrObj->derCert)
  64. return usr_cache_hash_cert(usrObj->derCert);
  65. else
  66. return PR_HashCaseString(usrObj->uid);
  67. }
  68. static int
  69. usr_cache_compare_certs(const void *v1, const void *v2)
  70. {
  71. const SECItem *c1 = (const SECItem *)v1;
  72. const SECItem *c2 = (const SECItem *)v2;
  73. return (c1->len == c2 ->len && !memcmp(c1->data, c2->data, c1->len));
  74. }
  75. static int
  76. usr_cache_compare_fn(const void *v1, const void *v2)
  77. {
  78. UserCacheObj *usrObj1 = (UserCacheObj *)v1;
  79. UserCacheObj *usrObj2 = (UserCacheObj *)v2;
  80. if (usrObj1->derCert && usrObj2->derCert)
  81. return usr_cache_compare_certs(usrObj1->derCert, usrObj2->derCert);
  82. else if (!usrObj1->derCert && !usrObj2->derCert)
  83. return PR_CompareCaseStrings(usrObj1->uid, usrObj1->uid);
  84. else
  85. return 0;
  86. }
  87. static PRHashTable *alloc_db2uid_table ()
  88. {
  89. return PR_NewHashTable(0,
  90. usr_cache_hash_fn,
  91. usr_cache_compare_fn,
  92. PR_CompareValues,
  93. &ACLPermAllocOps,
  94. usrcache_pool);
  95. }
  96. int acl_usr_cache_set_timeout (const int nsec)
  97. {
  98. acl_usr_cache_lifetime = (time_t)nsec;
  99. return 0;
  100. }
  101. int acl_usr_cache_enabled ()
  102. {
  103. return (acl_usr_cache_lifetime > 0);
  104. }
  105. int acl_usr_cache_init ()
  106. {
  107. UserCacheObj *usrobj;
  108. int i;
  109. if (acl_usr_cache_lifetime <= 0) {
  110. /* Caching is disabled */
  111. DBG_PRINT1("usrcache is disabled");
  112. return 0;
  113. }
  114. usrcache_pool = pool_create();
  115. user_hash_crit_init();
  116. if (acl_num_databases() == 0) {
  117. /* Something wrong -- No databases registered yet! */
  118. return -1;
  119. }
  120. else if (acl_num_databases() == 1) {
  121. /* Optimize for single database */
  122. DBG_PRINT1("Optimizing usrcache for single db");
  123. singleDbTable = alloc_db2uid_table();
  124. }
  125. else {
  126. singleDbTable = 0;
  127. databaseUserCacheTable = PR_NewHashTable(0,
  128. PR_HashCaseString,
  129. PR_CompareCaseStrings,
  130. PR_CompareValues,
  131. &ACLPermAllocOps,
  132. usrcache_pool);
  133. }
  134. /* Allocate first UserCacheObj and initialize the circular link list */
  135. usrobj = (UserCacheObj *)pool_malloc(usrcache_pool, sizeof(UserCacheObj));
  136. if (!usrobj) return -1;
  137. memset((void *)usrobj, 0, sizeof(UserCacheObj));
  138. usrobj_list = &usrobj->list;
  139. PR_INIT_CLIST(usrobj_list);
  140. /* Allocate rest of the UserCacheObj and put them in the link list */
  141. for(i = 0; i < num_usrobj; i++){
  142. usrobj = (UserCacheObj *)pool_malloc(usrcache_pool,
  143. sizeof(UserCacheObj));
  144. if (!usrobj) return -1;
  145. memset((void *)usrobj, 0, sizeof(UserCacheObj));
  146. PR_INSERT_AFTER(&usrobj->list, usrobj_list);
  147. }
  148. return (singleDbTable || databaseUserCacheTable) ? 0 : -1;
  149. }
  150. /* If the user hash table exists in the databaseUserCacheTable then return it.
  151. * Otherwise, create a new hash table, insert it in the databaseUserCacheTable
  152. * and then return it.
  153. */
  154. static int usr_cache_table_get (const char *dbname, PRHashTable **usrTable)
  155. {
  156. PRHashTable *table;
  157. if (singleDbTable) {
  158. *usrTable = singleDbTable;
  159. return LAS_EVAL_TRUE;
  160. }
  161. user_hash_crit_enter();
  162. table = (PRHashTable *)PR_HashTableLookup(databaseUserCacheTable,
  163. dbname);
  164. if (!table) {
  165. /* create a new table and insert it in the databaseUserCacheTable */
  166. table = alloc_db2uid_table();
  167. if (table) {
  168. PR_HashTableAdd(databaseUserCacheTable,
  169. pool_strdup(usrcache_pool, dbname),
  170. table);
  171. }
  172. }
  173. *usrTable = table;
  174. user_hash_crit_exit();
  175. return table ? LAS_EVAL_TRUE : LAS_EVAL_FAIL;
  176. }
  177. int acl_usr_cache_insert (const char *uid, const char *dbname,
  178. const char *userdn, const char *passwd,
  179. const char *group,
  180. const SECItem *derCert, const time_t time)
  181. {
  182. PRHashTable *usrTable;
  183. UserCacheObj *usrobj;
  184. UserCacheObj key;
  185. int rv;
  186. if (acl_usr_cache_lifetime <= 0) {
  187. /* Caching is disabled */
  188. return LAS_EVAL_TRUE;
  189. }
  190. rv = usr_cache_table_get (dbname, &usrTable);
  191. if (rv != LAS_EVAL_TRUE) return rv;
  192. user_hash_crit_enter();
  193. key.uid = (char *)uid;
  194. key.derCert = (SECItem *)derCert;
  195. usrobj = (UserCacheObj *)PR_HashTableLookup(usrTable, &key);
  196. if (usrobj) {
  197. time_t elapsed = time - usrobj->time;
  198. int expired = (elapsed >= acl_usr_cache_lifetime);
  199. /* Free & reset the old values in usrobj if -- there is an old value
  200. * and if the new value is given then it is different or the usrobj
  201. * has expired */
  202. /* Set the field if the new value is given and the field is not set */
  203. /* If the usrobj has not expired then we only want to update the field
  204. * whose new value is non-NULL and different */
  205. /* Work on the 'uid' field */
  206. if (usrobj->uid &&
  207. (uid ? strcmp(usrobj->uid, uid) : expired))
  208. {
  209. pool_free(usrcache_pool, usrobj->uid);
  210. usrobj->uid = 0;
  211. }
  212. if (uid && !usrobj->uid) {
  213. usrobj->uid = pool_strdup(usrcache_pool, uid);
  214. }
  215. /* Work on the 'userdn' field */
  216. if (usrobj->userdn &&
  217. (userdn ? strcmp(usrobj->userdn, userdn) : expired))
  218. {
  219. pool_free(usrcache_pool, usrobj->userdn);
  220. usrobj->userdn = 0;
  221. }
  222. if (userdn && !usrobj->userdn) {
  223. usrobj->userdn = pool_strdup(usrcache_pool, userdn);
  224. }
  225. /* Work on the 'passwd' field */
  226. if (usrobj->passwd &&
  227. (passwd ? strcmp(usrobj->passwd, passwd) : expired))
  228. {
  229. pool_free(usrcache_pool, usrobj->passwd);
  230. usrobj->passwd = 0;
  231. }
  232. if (passwd && !usrobj->passwd) {
  233. usrobj->passwd = pool_strdup(usrcache_pool, passwd);
  234. }
  235. /* Work on the 'group' field -- not replace a valid group */
  236. if (!expired && usrobj->group &&
  237. (group ? strcmp(usrobj->group, group) : expired))
  238. {
  239. pool_free(usrcache_pool, usrobj->group);
  240. usrobj->group = 0;
  241. }
  242. if (group && !usrobj->group) {
  243. usrobj->group = pool_strdup(usrcache_pool, group);
  244. }
  245. /* Work on the 'derCert' field */
  246. if (usrobj->derCert &&
  247. (derCert ? (derCert->len != usrobj->derCert->len ||
  248. memcmp(usrobj->derCert->data, derCert->data,
  249. derCert->len))
  250. : expired))
  251. {
  252. SECITEM_FreeItem(usrobj->derCert, PR_TRUE);
  253. usrobj->derCert = 0;
  254. }
  255. if (derCert && !usrobj->derCert) {
  256. usrobj->derCert = SECITEM_DupItem((SECItem *)derCert);
  257. }
  258. /* Reset the time only if the usrobj has expired */
  259. if (expired) {
  260. DBG_PRINT1("Replace ");
  261. usrobj->time = time;
  262. }
  263. else {
  264. DBG_PRINT1("Update ");
  265. }
  266. }
  267. else {
  268. /* Get the last usrobj from the link list, erase it and use it */
  269. /* Maybe the last usrobj is not invalid yet but we don't want to grow
  270. * the list of usrobjs. The last obj is the best candidate for being
  271. * not valid. We don't want to compare the time -- just use it.
  272. */
  273. PRCList *tail = PR_LIST_TAIL(usrobj_list);
  274. usrobj = USEROBJ_PTR(tail);
  275. /* If the removed usrobj is in the hashtable, remove it from there */
  276. if (usrobj->hashtable) {
  277. PR_HashTableRemove(usrobj->hashtable, usrobj);
  278. }
  279. /* Free the memory associated with the usrobj */
  280. if (usrobj->userdn) pool_free(usrcache_pool, usrobj->userdn);
  281. if (usrobj->passwd) pool_free(usrcache_pool, usrobj->passwd);
  282. if (usrobj->group) pool_free(usrcache_pool, usrobj->group);
  283. if (usrobj->derCert) SECITEM_FreeItem(usrobj->derCert, PR_TRUE);
  284. if (usrobj->uid) pool_free(usrcache_pool, usrobj->uid);
  285. /* Fill in the usrobj with the current data */
  286. usrobj->uid = pool_strdup(usrcache_pool, uid);
  287. usrobj->userdn = userdn ? pool_strdup(usrcache_pool, userdn) : 0;
  288. usrobj->passwd = passwd ? pool_strdup(usrcache_pool, passwd) : 0;
  289. usrobj->derCert = derCert ? SECITEM_DupItem((SECItem *)derCert) : 0;
  290. usrobj->group = group ? pool_strdup(usrcache_pool, group) : 0;
  291. usrobj->time = time;
  292. /* Add the usrobj to the user hash table */
  293. PR_HashTableAdd(usrTable, usrobj, usrobj);
  294. usrobj->hashtable = usrTable;
  295. DBG_PRINT1("Insert ");
  296. }
  297. /* Move the usrobj to the head of the list */
  298. PR_REMOVE_LINK(&usrobj->list);
  299. PR_INSERT_AFTER(&usrobj->list, usrobj_list);
  300. /* Set the time in the UserCacheObj */
  301. if (usrobj) {
  302. rv = LAS_EVAL_TRUE;
  303. }
  304. else {
  305. rv = LAS_EVAL_FAIL;
  306. }
  307. DBG_PRINT4("acl_usr_cache_insert: derCert = \"%s\" uid = \"%s\" at time = %ld\n",
  308. usrobj->derCert ? (char *)usrobj->derCert->data : "<NONE>",
  309. uid, time);
  310. user_hash_crit_exit();
  311. return rv;
  312. }
  313. static int acl_usr_cache_get_usrobj (const char *uid, const SECItem *derCert,
  314. const char *dbname, const time_t time,
  315. UserCacheObj **usrobj_out)
  316. {
  317. PRHashTable *usrtable;
  318. UserCacheObj *usrobj;
  319. UserCacheObj key;
  320. time_t elapsed;
  321. int rv;
  322. *usrobj_out = 0;
  323. if (acl_usr_cache_lifetime <= 0) {
  324. /* Caching is disabled */
  325. return LAS_EVAL_FALSE;
  326. }
  327. rv = usr_cache_table_get(dbname, &usrtable);
  328. if (!usrtable) return LAS_EVAL_FALSE;
  329. key.uid = (char *)uid;
  330. key.derCert = (SECItem *)derCert;
  331. usrobj = (UserCacheObj *)PR_HashTableLookup(usrtable, &key);
  332. if (!usrobj) return LAS_EVAL_FALSE;
  333. rv = LAS_EVAL_FALSE;
  334. elapsed = time - usrobj->time;
  335. /* If the cache is valid, return the usrobj */
  336. if (elapsed < acl_usr_cache_lifetime) {
  337. rv = LAS_EVAL_TRUE;
  338. *usrobj_out = usrobj;
  339. DBG_PRINT4("usr_cache found: derCert = \"%s\" uid = \"%s\" at time = %ld\n",
  340. usrobj->derCert ? (char *)usrobj->derCert->data : "<NONE>",
  341. usrobj->uid, time);
  342. }
  343. else {
  344. DBG_PRINT4("usr_cache expired: derCert = \"%s\" uid = \"%s\" at time = %ld\n",
  345. usrobj->derCert ? (char *)usrobj->derCert->data : "<NONE>",
  346. usrobj->uid, time);
  347. }
  348. return rv;
  349. }
  350. int acl_usr_cache_passwd_check (const char *uid, const char *dbname,
  351. const char *passwd,
  352. const time_t time, char **dn,
  353. pool_handle_t *pool)
  354. {
  355. UserCacheObj *usrobj;
  356. int rv;
  357. user_hash_crit_enter();
  358. rv = acl_usr_cache_get_usrobj(uid, 0, dbname, time, &usrobj);
  359. if (rv == LAS_EVAL_TRUE && usrobj->passwd && passwd &&
  360. !strcmp(usrobj->passwd, passwd))
  361. {
  362. /* extract dn from the usrobj */
  363. *dn = usrobj->userdn ? pool_strdup(pool, usrobj->userdn) : 0;
  364. rv = LAS_EVAL_TRUE;
  365. DBG_PRINT1("Success ");
  366. }
  367. else {
  368. rv = LAS_EVAL_FALSE;
  369. DBG_PRINT1("Failed ");
  370. }
  371. DBG_PRINT3("acl_usr_cache_passwd_check: uid = \"%s\" at time = %ld\n",
  372. uid, time);
  373. user_hash_crit_exit();
  374. return rv;
  375. }
  376. int acl_usr_cache_group_check (const char *uid, const char *dbname,
  377. const char *group, const time_t time)
  378. {
  379. UserCacheObj *usrobj;
  380. int rv;
  381. user_hash_crit_enter();
  382. rv = acl_usr_cache_get_usrobj(uid, 0, dbname, time, &usrobj);
  383. if (rv == LAS_EVAL_TRUE && usrobj->group && group &&
  384. !strcmp(usrobj->group, group))
  385. {
  386. DBG_PRINT1("Success ");
  387. }
  388. else {
  389. rv = LAS_EVAL_FALSE;
  390. DBG_PRINT1("Failed ");
  391. }
  392. DBG_PRINT3("acl_usr_cache_group_check: uid = \"%s\" group = \"%s\"\n",
  393. uid, group ? group : "<NONE>");
  394. user_hash_crit_exit();
  395. return rv;
  396. }
  397. int acl_usr_cache_group_len_check (const char *uid, const char *dbname,
  398. const char *group, const int len,
  399. const time_t time)
  400. {
  401. UserCacheObj *usrobj;
  402. int rv;
  403. user_hash_crit_enter();
  404. rv = acl_usr_cache_get_usrobj(uid, 0, dbname, time, &usrobj);
  405. if (rv == LAS_EVAL_TRUE && usrobj->group && group &&
  406. !strncmp(usrobj->group, group, len))
  407. {
  408. rv = LAS_EVAL_TRUE;
  409. DBG_PRINT1("Success ");
  410. }
  411. else {
  412. rv = LAS_EVAL_FALSE;
  413. DBG_PRINT1("Failed ");
  414. }
  415. DBG_PRINT3("acl_usr_cache_group_check: uid = \"%s\" group = \"%s\"\n",
  416. uid, group);
  417. user_hash_crit_exit();
  418. return rv;
  419. }
  420. int acl_usr_cache_get_userdn (const char *uid, const char *dbname,
  421. const time_t time, char **userdn,
  422. pool_handle_t *pool)
  423. {
  424. UserCacheObj *usrobj;
  425. int rv;
  426. *userdn = 0;
  427. user_hash_crit_enter();
  428. rv = acl_usr_cache_get_usrobj(uid, 0, dbname, time, &usrobj);
  429. if (rv == LAS_EVAL_TRUE) {
  430. *userdn = usrobj->userdn ? pool_strdup(pool, usrobj->userdn) : 0;
  431. DBG_PRINT1("Success ");
  432. }
  433. else {
  434. DBG_PRINT1("Failed ");
  435. }
  436. DBG_PRINT3("acl_usr_cache_get_userdn: uid = \"%s\" userdn = \"%s\"\n",
  437. uid, *userdn ? *userdn : "<NONE>");
  438. user_hash_crit_exit();
  439. return *userdn ? LAS_EVAL_TRUE : LAS_EVAL_FALSE;
  440. }
  441. int acl_usr_cache_userdn_check (const char *uid, const char *dbname,
  442. const char *userdn, const time_t time)
  443. {
  444. UserCacheObj *usrobj;
  445. int rv;
  446. user_hash_crit_enter();
  447. rv = acl_usr_cache_get_usrobj(uid, 0, dbname, time, &usrobj);
  448. if (rv == LAS_EVAL_TRUE && usrobj->userdn && userdn &&
  449. !strcmp(usrobj->userdn, userdn))
  450. {
  451. DBG_PRINT1("Success ");
  452. }
  453. else {
  454. rv = LAS_EVAL_FALSE;
  455. DBG_PRINT1("Failed ");
  456. }
  457. DBG_PRINT3("acl_usr_cache_userdn_check: uid = \"%s\" userdn = \"%s\"\n",
  458. uid, userdn ? userdn : "<NONE>");
  459. user_hash_crit_exit();
  460. return rv;
  461. }
  462. int acl_usr_cache_set_userdn (const char *uid, const char *dbname,
  463. const char *userdn, const time_t time)
  464. {
  465. int rv;
  466. /* acl_usr_cache_insert updates the existing un-expired entry or creates a
  467. * new one */
  468. rv = acl_usr_cache_insert(uid, dbname, userdn, 0, 0, 0, time);
  469. return rv;
  470. }
  471. int acl_usr_cache_get_group (const char *uid, const char *dbname,
  472. const time_t time, char **group,
  473. pool_handle_t *pool)
  474. {
  475. UserCacheObj *usrobj;
  476. int rv;
  477. *group = 0;
  478. user_hash_crit_enter();
  479. rv = acl_usr_cache_get_usrobj(uid, 0, dbname, time, &usrobj);
  480. if (rv == LAS_EVAL_TRUE) {
  481. *group = usrobj->group ? pool_strdup(pool, usrobj->group) : 0;
  482. DBG_PRINT1("Success ");
  483. }
  484. else {
  485. DBG_PRINT1("Failed ");
  486. }
  487. DBG_PRINT3("acl_usr_cache_get_group: uid = \"%s\" group = \"%s\"\n",
  488. uid, *group ? *group : "<NONE>");
  489. user_hash_crit_exit();
  490. return *group ? LAS_EVAL_TRUE : LAS_EVAL_FALSE;
  491. }
  492. int acl_usr_cache_set_group (const char *uid, const char *dbname,
  493. const char *group, const time_t time)
  494. {
  495. int rv;
  496. /* acl_usr_cache_insert updates the existing un-expired entry or creates a
  497. * new one */
  498. rv = acl_usr_cache_insert(uid, dbname, 0, 0, group, 0, time);
  499. return rv;
  500. }
  501. int acl_cert_cache_insert (void *cert_in, const char *dbname,
  502. const char *uid, const char *dn,
  503. const time_t time)
  504. {
  505. CERTCertificate *cert = (CERTCertificate *)cert_in;
  506. SECItem derCert = cert->derCert;
  507. int rv;
  508. rv = acl_usr_cache_insert(uid, dbname, dn, 0, 0, &derCert, time);
  509. return rv;
  510. }
  511. /* Returns LAS_EVAL_TRUE if the user's cache is valid and returns uid */
  512. int acl_cert_cache_get_uid (void *cert_in, const char *dbname,
  513. const time_t time, char **uid, char **dn,
  514. pool_handle_t *pool)
  515. {
  516. CERTCertificate *cert = (CERTCertificate *)cert_in;
  517. SECItem derCert = cert->derCert;
  518. UserCacheObj *usrobj = 0;
  519. int rv;
  520. rv = acl_usr_cache_get_usrobj(0, &derCert, dbname, time, &usrobj);
  521. if (rv == LAS_EVAL_TRUE && usrobj && usrobj->uid) {
  522. *uid = pool_strdup(pool, usrobj->uid);
  523. *dn = usrobj->userdn ? pool_strdup(pool, usrobj->userdn) : 0;
  524. }
  525. else {
  526. *uid = 0;
  527. *dn = 0;
  528. rv = LAS_EVAL_FALSE;
  529. }
  530. return rv;
  531. }