presence.c 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186
  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. *
  6. * License: GPL (version 3 or any later version).
  7. * See LICENSE for details.
  8. * END COPYRIGHT BLOCK **/
  9. #ifdef HAVE_CONFIG_H
  10. # include <config.h>
  11. #endif
  12. /**
  13. * IM Presence plug-in
  14. */
  15. #include <stdio.h>
  16. #include <ctype.h>
  17. #include <string.h>
  18. #include "portable.h"
  19. #include "nspr.h"
  20. #include "slapi-plugin.h"
  21. #include "slapi-private.h"
  22. #include "vattr_spi.h"
  23. #include "plhash.h"
  24. #include "ldif.h"
  25. #include "http_client.h"
  26. #include <sys/stat.h>
  27. /*** from proto-slap.h ***/
  28. int slapd_log_error_proc( char *subsystem, char *fmt, ... );
  29. /*** from ldaplog.h ***/
  30. /* edited ldaplog.h for LDAPDebug()*/
  31. #ifndef _LDAPLOG_H
  32. #define _LDAPLOG_H
  33. #ifdef __cplusplus
  34. extern "C" {
  35. #endif
  36. #define LDAP_DEBUG_TRACE 0x00001 /* 1 */
  37. #define LDAP_DEBUG_ANY 0x04000 /* 16384 */
  38. #define LDAP_DEBUG_PLUGIN 0x10000 /* 65536 */
  39. /* debugging stuff */
  40. extern int slapd_ldap_debug;
  41. #define LDAPDebugLevelIsSet( level ) ( slapd_ldap_debug & level )
  42. #define LDAPDebug( level, fmt, arg1, arg2, arg3 ) \
  43. { \
  44. if ( LDAPDebugLevelIsSet( level )) { \
  45. slapd_log_error_proc( NULL, fmt, arg1, arg2, arg3 ); \
  46. } \
  47. }
  48. #ifdef __cplusplus
  49. }
  50. #endif
  51. #endif /* _LDAP_H */
  52. #define PRESENCE_PLUGIN_SUBSYSTEM "presence-plugin"
  53. #define PRESENCE_PLUGIN_VERSION 0x00050050
  54. /**
  55. * this may become unnecessary when we are able to get
  56. * the plug-in DN dynamically (pete?)
  57. */
  58. #define PRESENCE_DN "cn=Presence,cn=plugins,cn=config" /* temporary */
  59. #define PRESENCE_SUCCESS 0
  60. #define PRESENCE_FAILURE -1
  61. /**
  62. * Presence vendor specific config parameters
  63. */
  64. #define NS_IM_ID "nsIM-ID"
  65. #define NS_IM_URL_TEXT "nsIM-URLText"
  66. #define NS_IM_URL_GRAPHIC "nsIM-URLGraphic"
  67. #define NS_IM_ON_VALUE_MAP_TEXT "nsIM-OnValueMapText"
  68. #define NS_IM_OFF_VALUE_MAP_TEXT "nsIM-OffValueMapText"
  69. #define NS_IM_ON_VALUE_MAP_GRAPHIC "nsIM-OnValueMapGraphic"
  70. #define NS_IM_OFF_VALUE_MAP_GRAPHIC "nsIM-OffValueMapGraphic"
  71. #define NS_IM_DISABLED_VALUE_MAP_GRAPHIC "nsIM-disabledValueMapGraphic"
  72. #define NS_IM_REQUEST_METHOD "nsIM-RequestMethod"
  73. #define NS_IM_URL_TEXT_RETURN_TYPE "nsIM-URLTextReturnType"
  74. #define NS_IM_URL_GRAPHIC_RETURN_TYPE "nsIM-URLGraphicReturnType"
  75. #define NS_IM_STATUS_TEXT "nsIM-StatusText"
  76. #define NS_IM_STATUS_GRAPHIC "nsIM-StatusGraphic"
  77. #define PRESENCE_STRING 1
  78. #define PRESENCE_BINARY 2
  79. #define PRESENCE_TEXT_RETURN_TYPE "TEXT"
  80. #define PRESENCE_BINARY_RETURN_TYPE "BINARY"
  81. #define PRESENCE_REQUEST_METHOD_GET "GET"
  82. #define PRESENCE_REQUEST_METHOD_REDIRECT "REDIRECT"
  83. #define PRESENCE_RETURNED_ON_TEXT "ONLINE"
  84. #define PRESENCE_RETURNED_OFF_TEXT "OFFLINE"
  85. #define PRESENCE_RETURNED_ERROR_TEXT "ERROR"
  86. static Slapi_PluginDesc pdesc = { "IM Presence",
  87. VENDOR,
  88. DS_PACKAGE_VERSION,
  89. "presence plugin" };
  90. /**
  91. * struct used to pass the argument to PL_Enumerator Callback
  92. */
  93. struct _vattrtypes
  94. {
  95. Slapi_Entry *entry;
  96. vattr_type_list_context *context;
  97. };
  98. /**
  99. * This structure holds the mapping between the virtual attributes and
  100. * the IM IDs. This information is used to find out whether this plugin
  101. * should service the attributes it was asked to. Also, it stores the
  102. * syntax of the attribute. 1 is String and 2 is binary.
  103. */
  104. struct _vattrmap {
  105. char *imID;
  106. int syntax;
  107. };
  108. typedef struct _vattrmap _Vmap;
  109. /**
  110. * struct to store the config values for each presence vendor
  111. */
  112. struct _defs {
  113. char *textURL;
  114. char *graphicURL;
  115. char *onTextMap;
  116. char *offTextMap;
  117. Slapi_Attr *onGraphicMap;
  118. Slapi_Attr *offGraphicMap;
  119. Slapi_Attr *disabledGraphicMap;
  120. char *requestMethod;
  121. char *textReturnType;
  122. char *graphicReturnType;
  123. };
  124. typedef struct _defs _ConfigEntry;
  125. static vattr_sp_handle *_VattrHandle = NULL;
  126. static void *_PluginID = NULL;
  127. static char *_PluginDN = NULL;
  128. static PLHashTable *_IdVattrMapTable = NULL;
  129. static PLHashTable *_IdConfigMapTable = NULL;
  130. static void **_HttpAPI = NULL;
  131. /**
  132. *
  133. * Presence plug-in management functions
  134. *
  135. */
  136. int presence_init(Slapi_PBlock *pb);
  137. int presence_start(Slapi_PBlock *pb);
  138. int presence_close(Slapi_PBlock *pb);
  139. /**
  140. *
  141. * Vattr operation callbacks functions
  142. *
  143. */
  144. static int presence_vattr_get(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_ValueSet** results,int *type_name_disposition, char** actual_type_name, int flags, int *free_flags, void *hint);
  145. static int presence_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint);
  146. static int presence_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags);
  147. /**
  148. *
  149. * Local operation functions
  150. *
  151. */
  152. static int loadPluginConfig();
  153. static int parseConfigEntry(Slapi_Entry *e);
  154. static int imIDExists(Slapi_Entry *e, char *type, char **value, _Vmap **map, _ConfigEntry **entry);
  155. static int makeHttpRequest(char *id, _Vmap *map, _ConfigEntry *info, char **buf, int *size);
  156. static char * replaceIdWithValue(char *str, char *id, char *value);
  157. static int setIMStatus(char *id, _Vmap *map, _ConfigEntry *info, char *returnedBUF, int size, Slapi_ValueSet **results);
  158. static int setTypes(PLHashEntry *he, PRIntn i, void *arg);
  159. static void deleteMapTables();
  160. static PRIntn destroyHashEntry(PLHashEntry *he, PRIntn index, void *arg);
  161. static void logGraphicAttributeValue( Slapi_Attr *attr, const char *attrname );
  162. static void toLowerCase(char* str);
  163. /**
  164. * utility function
  165. */
  166. void printMapTable();
  167. PRIntn printIdVattrMapTable(PLHashEntry *he, PRIntn i, void *arg);
  168. PRIntn printIdConfigMapTable(PLHashEntry *he, PRIntn i, void *arg);
  169. /**
  170. *
  171. * Get the presence plug-in version
  172. *
  173. */
  174. int presence_version()
  175. {
  176. return PRESENCE_PLUGIN_VERSION;
  177. }
  178. /**
  179. * Plugin identity mgmt
  180. */
  181. void setPluginID(void * pluginID)
  182. {
  183. _PluginID=pluginID;
  184. }
  185. void * getPluginID()
  186. {
  187. return _PluginID;
  188. }
  189. void setPluginDN(char *pluginDN)
  190. {
  191. _PluginDN = pluginDN;
  192. }
  193. char * getPluginDN()
  194. {
  195. return _PluginDN;
  196. }
  197. /*
  198. presence_init
  199. -------------
  200. adds our callbacks to the list
  201. */
  202. int presence_init( Slapi_PBlock *pb )
  203. {
  204. int status = PRESENCE_SUCCESS;
  205. char * plugin_identity=NULL;
  206. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_init -- BEGIN\n",0,0,0);
  207. /**
  208. * Store the plugin identity for later use.
  209. * Used for internal operations
  210. */
  211. slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
  212. PR_ASSERT (plugin_identity);
  213. setPluginID(plugin_identity);
  214. if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
  215. SLAPI_PLUGIN_VERSION_01 ) != 0 ||
  216. slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
  217. (void *) presence_start ) != 0 ||
  218. slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
  219. (void *) presence_close ) != 0 ||
  220. slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
  221. (void *)&pdesc ) != 0 )
  222. {
  223. slapi_log_error( SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
  224. "presence_init: failed to register plugin\n" );
  225. status = PRESENCE_FAILURE;
  226. }
  227. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_init -- END\n",0,0,0);
  228. return status;
  229. }
  230. /*
  231. presence_start
  232. --------------
  233. This function registers the computed attribute evaluator
  234. and loads the configuration parameters in the local cache.
  235. It is called after presence_init.
  236. */
  237. int presence_start( Slapi_PBlock *pb )
  238. {
  239. char * plugindn = NULL;
  240. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_start -- begin\n",0,0,0);
  241. if(slapi_apib_get_interface(HTTP_v1_0_GUID, &_HttpAPI))
  242. {
  243. /**
  244. * error cannot proceeed
  245. */
  246. return PRESENCE_FAILURE;
  247. }
  248. /**
  249. * register our vattr callbacks
  250. */
  251. if (slapi_vattrspi_register((vattr_sp_handle **)&_VattrHandle,
  252. presence_vattr_get,
  253. presence_vattr_compare,
  254. presence_vattr_types) != 0)
  255. {
  256. slapi_log_error( SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
  257. "presence_start: cannot register as service provider\n" );
  258. return PRESENCE_FAILURE;
  259. }
  260. /**
  261. * Get the plug-in target dn from the system
  262. * and store it for future use. This should avoid
  263. * hardcoding of DN's in the code.
  264. */
  265. slapi_pblock_get(pb, SLAPI_TARGET_DN, &plugindn);
  266. if (plugindn == NULL || strlen(plugindn) == 0)
  267. {
  268. /**
  269. * This is not required as the above statement
  270. * should work and give you a valid DN for this
  271. * plugin. ??? remove it later
  272. */
  273. plugindn = PRESENCE_DN;
  274. }
  275. setPluginDN(plugindn);
  276. /**
  277. * Load the config info for our plug-in in memory
  278. * In the 6.0 release this information will be stored
  279. * statically and if any change is done to this info a server
  280. * restart is necessary :-(. Probably if time permits then
  281. * state change plug-in would be used to notify the state
  282. * change. We also register the virtual attributes we are
  283. * interested in here.
  284. */
  285. if (loadPluginConfig() != PRESENCE_SUCCESS)
  286. {
  287. slapi_log_error( SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
  288. "presence_start: unable to load plug-in configuration\n" );
  289. return PRESENCE_FAILURE;
  290. }
  291. LDAPDebug( LDAP_DEBUG_PLUGIN, "presence: ready for service\n",0,0,0);
  292. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_start -- end\n",0,0,0);
  293. return PRESENCE_SUCCESS;
  294. }
  295. /*
  296. presence_close
  297. --------------
  298. closes down the cache
  299. */
  300. int presence_close( Slapi_PBlock *pb )
  301. {
  302. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_close\n",0,0,0);
  303. deleteMapTables();
  304. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_close\n",0,0,0);
  305. return PRESENCE_SUCCESS;
  306. }
  307. static int presence_vattr_get(vattr_sp_handle *handle,
  308. vattr_context *c,
  309. Slapi_Entry *e,
  310. char *type,
  311. Slapi_ValueSet** results,
  312. int *type_name_disposition,
  313. char** actual_type_name,
  314. int flags,
  315. int *free_flags,
  316. void *hint)
  317. {
  318. int status = PRESENCE_SUCCESS;
  319. char *id = NULL;
  320. char *returnedBUF = NULL;
  321. int size = 0;
  322. _Vmap *map = NULL;
  323. _ConfigEntry *info = NULL;
  324. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_vattr_get \n",0,0,0);
  325. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Type=[%s] \n",type,0,0);
  326. if (imIDExists(e, type, &id, &map, &info) != PRESENCE_SUCCESS)
  327. {
  328. /**
  329. * we didn't find any valid matching nsimid in this
  330. * entry so since we cannot process a request without
  331. * a valid nsimid we just return.
  332. */
  333. status = PRESENCE_FAILURE;
  334. goto cleanup;
  335. }
  336. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> ID=[%s] \n",id,0,0);
  337. /**
  338. * Now since we got a valid id we do a quick schema check
  339. * if schema checking is on to make sure that there is no
  340. * schema violation ?
  341. */
  342. /* do_schema_check() */
  343. /**
  344. * At this stage we have a valid attribute and we have to
  345. * get its value from the IM Server. so make an Http request
  346. * depending on whether it is a request for Text or Graphic
  347. */
  348. status = makeHttpRequest(id, map, info, &returnedBUF, &size);
  349. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> size=[%d] \n",size,0,0);
  350. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> buffer=[%s]\n",(returnedBUF) ? returnedBUF : "NULL",0,0);
  351. if(status == PRESENCE_SUCCESS)
  352. {
  353. status = setIMStatus(id, map, info, returnedBUF, size, results);
  354. }
  355. else
  356. {
  357. /**
  358. * Report all HTTP failures as a single predefined value of the
  359. * attribute
  360. */
  361. Slapi_Value *value =
  362. slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT);
  363. if (!*results) {
  364. *results = slapi_valueset_new();
  365. }
  366. slapi_valueset_add_value(*results, value);
  367. slapi_value_free(&value); /* slapi_valueset_add_value copies value */
  368. /**
  369. * It's a success only in the sense that we are returning a value
  370. */
  371. status = PRESENCE_SUCCESS;
  372. }
  373. if(status == PRESENCE_SUCCESS)
  374. {
  375. *free_flags = SLAPI_VIRTUALATTRS_RETURNED_COPIES;
  376. *actual_type_name = slapi_ch_strdup(type);
  377. *type_name_disposition = SLAPI_VIRTUALATTRS_TYPE_NAME_MATCHED_EXACTLY_OR_ALIAS;
  378. }
  379. cleanup:
  380. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Processed ID=[%s] \n",id,0,0);
  381. if (id != NULL ) {
  382. slapi_ch_free((void **)&id);
  383. }
  384. if (returnedBUF != NULL ) {
  385. PR_Free(returnedBUF);
  386. }
  387. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_vattr_get \n",0,0,0);
  388. return status;
  389. }
  390. static int presence_vattr_compare(vattr_sp_handle *handle, vattr_context *c, Slapi_Entry *e, char *type, Slapi_Value *test_this, int* result, int flags, void *hint)
  391. {
  392. int status = PRESENCE_SUCCESS;
  393. /**
  394. * not yet implemented ???
  395. */
  396. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_vattr_compare \n",0,0,0);
  397. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_vattr_compare \n",0,0,0);
  398. return status;
  399. }
  400. static int presence_vattr_types(vattr_sp_handle *handle,Slapi_Entry *e,vattr_type_list_context *type_context,int flags)
  401. {
  402. int status = PRESENCE_SUCCESS;
  403. struct _vattrtypes args;
  404. args.entry = e;
  405. args.context = type_context;
  406. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> presence_vattr_types\n",0,0,0);
  407. PL_HashTableEnumerateEntries(_IdVattrMapTable, setTypes, &args);
  408. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- presence_vattr_types\n",0,0,0);
  409. return status;
  410. }
  411. static int loadPluginConfig()
  412. {
  413. int status = PRESENCE_SUCCESS;
  414. int result;
  415. int i;
  416. Slapi_PBlock *search_pb;
  417. Slapi_Entry **entries = NULL;
  418. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> loadPluginConfig\n",0,0,0);
  419. search_pb = slapi_pblock_new();
  420. slapi_search_internal_set_pb(search_pb, PRESENCE_DN, LDAP_SCOPE_ONELEVEL,
  421. "objectclass=*", NULL, 0, NULL, NULL, getPluginID(), 0);
  422. slapi_search_internal_pb(search_pb);
  423. slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
  424. if (status != PRESENCE_SUCCESS)
  425. {
  426. slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
  427. "Error getting level1 presence configurations<%s>\n", getPluginDN());
  428. status = PRESENCE_FAILURE;
  429. goto cleanup;
  430. }
  431. slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
  432. if (NULL == entries || entries[0] == NULL)
  433. {
  434. slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
  435. "No entries found for <%s>\n", getPluginDN());
  436. status = PRESENCE_FAILURE;
  437. goto cleanup;
  438. }
  439. _IdVattrMapTable = PL_NewHashTable( 0,
  440. PL_HashString,
  441. PL_CompareStrings,
  442. PL_CompareValues,
  443. NULL,
  444. NULL
  445. );
  446. _IdConfigMapTable = PL_NewHashTable(0,
  447. PL_HashString,
  448. PL_CompareStrings,
  449. PL_CompareValues,
  450. NULL,
  451. NULL
  452. );
  453. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> parseConfigEntry \n",0,0,0);
  454. for (i = 0; (entries[i] != NULL); i++)
  455. {
  456. status = parseConfigEntry(entries[i]);
  457. if (status != PRESENCE_SUCCESS)
  458. {
  459. deleteMapTables();
  460. goto cleanup;
  461. }
  462. }
  463. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- parseConfigEntry \n",0,0,0);
  464. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- loadPluginConfig\n",0,0,0);
  465. cleanup:
  466. slapi_free_search_results_internal(search_pb);
  467. slapi_pblock_destroy(search_pb);
  468. return status;
  469. }
  470. static int parseConfigEntry(Slapi_Entry *e)
  471. {
  472. char *key = NULL;
  473. char *value = NULL;
  474. _ConfigEntry *entry = NULL;
  475. _Vmap *map = NULL;
  476. Slapi_Attr *attr = NULL;
  477. key = slapi_entry_attr_get_charptr(e, NS_IM_ID);
  478. if (!key) {
  479. /**
  480. * IM Id not defined in the config, unfortunately
  481. * cannot do anything without it so better not to
  482. * load the plug-in.
  483. */
  484. return PRESENCE_FAILURE;
  485. }
  486. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> key [%s] \n",key,0,0);
  487. /**
  488. * Now create the config entry which will hold all the
  489. * attributes of a presence vendor
  490. */
  491. entry = (_ConfigEntry*) slapi_ch_calloc(1, sizeof(_ConfigEntry));
  492. /**
  493. * Next 2 are the virtual attributes for which this plug-in
  494. * is responsible. Register them with the vattr system so
  495. * that the system can call us whenever their
  496. * values are requested. Also update these entries in the
  497. * map table for later access.
  498. */
  499. value = slapi_entry_attr_get_charptr(e, NS_IM_STATUS_TEXT);
  500. if (value) {
  501. slapi_vattrspi_regattr(_VattrHandle, value, "", NULL);
  502. map = (_Vmap*) slapi_ch_calloc(1, sizeof(_Vmap));
  503. map->imID = key;
  504. map->syntax = PRESENCE_STRING;
  505. toLowerCase(value);
  506. PL_HashTableAdd(_IdVattrMapTable, value, map);
  507. }
  508. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMStatusText [%s] \n",value,0,0);
  509. value = slapi_entry_attr_get_charptr(e, NS_IM_STATUS_GRAPHIC);
  510. if (value) {
  511. slapi_vattrspi_regattr(_VattrHandle, value, "", NULL);
  512. map = (_Vmap*) slapi_ch_calloc(1, sizeof(_Vmap));
  513. map->imID = key;
  514. map->syntax = PRESENCE_BINARY;
  515. toLowerCase(value);
  516. PL_HashTableAdd(_IdVattrMapTable, value, map);
  517. }
  518. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMStatusGraphic [%s] \n",value,0,0);
  519. value = slapi_entry_attr_get_charptr(e, NS_IM_URL_TEXT);
  520. if (value) {
  521. entry->textURL = value;
  522. }
  523. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMURLText [%s] \n",value,0,0);
  524. value = slapi_entry_attr_get_charptr(e, NS_IM_URL_GRAPHIC);
  525. if (value) {
  526. entry->graphicURL = value;
  527. }
  528. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMStatusGraphic [%s] \n",value,0,0);
  529. value = slapi_entry_attr_get_charptr(e, NS_IM_ON_VALUE_MAP_TEXT);
  530. if (value) {
  531. entry->onTextMap = value;
  532. }
  533. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMOnValueMapText [%s] \n",value,0,0);
  534. value = slapi_entry_attr_get_charptr(e, NS_IM_OFF_VALUE_MAP_TEXT);
  535. if (value) {
  536. entry->offTextMap = value;
  537. }
  538. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMOffValueMapText [%s] \n",value,0,0);
  539. /**
  540. * Next 3 are binary syntax types so needs special handling
  541. */
  542. slapi_entry_attr_find(e, NS_IM_ON_VALUE_MAP_GRAPHIC, &attr);
  543. if (attr) {
  544. entry->onGraphicMap = slapi_attr_dup(attr);
  545. logGraphicAttributeValue(attr,NS_IM_ON_VALUE_MAP_GRAPHIC);
  546. }
  547. slapi_entry_attr_find(e, NS_IM_OFF_VALUE_MAP_GRAPHIC, &attr);
  548. if (attr) {
  549. entry->offGraphicMap = slapi_attr_dup(attr);
  550. logGraphicAttributeValue(attr,NS_IM_OFF_VALUE_MAP_GRAPHIC);
  551. }
  552. slapi_entry_attr_find(e, NS_IM_DISABLED_VALUE_MAP_GRAPHIC, &attr);
  553. if (attr) {
  554. entry->disabledGraphicMap = slapi_attr_dup(attr);
  555. logGraphicAttributeValue(attr,NS_IM_DISABLED_VALUE_MAP_GRAPHIC);
  556. }
  557. value = slapi_entry_attr_get_charptr(e, NS_IM_REQUEST_METHOD);
  558. if (value) {
  559. entry->requestMethod = value;
  560. }
  561. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMRequestMethod [%s] \n",value,0,0);
  562. value = slapi_entry_attr_get_charptr(e, NS_IM_URL_TEXT_RETURN_TYPE);
  563. if (value) {
  564. entry->textReturnType = value;
  565. }
  566. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMURLTextReturnType [%s] \n",value,0,0);
  567. value = slapi_entry_attr_get_charptr(e, NS_IM_URL_GRAPHIC_RETURN_TYPE);
  568. if (value) {
  569. entry->graphicReturnType = value;
  570. }
  571. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> nsIMURLGraphicReturnType [%s] \n",value,0,0);
  572. /**
  573. * Finally add the entry to the map table
  574. */
  575. PL_HashTableAdd(_IdConfigMapTable, key, entry);
  576. return PRESENCE_SUCCESS;
  577. }
  578. /**
  579. * this function goes thru the valid stored ids
  580. * and return the correct one for which we have to
  581. * do further processing
  582. */
  583. static int imIDExists(Slapi_Entry *e, char *type, char **value, _Vmap **map, _ConfigEntry **entry)
  584. {
  585. int status = PRESENCE_SUCCESS;
  586. char *tValue = NULL;
  587. _ConfigEntry *tEntry = NULL;
  588. _Vmap *tMap = NULL;
  589. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> imIDExists \n",0,0,0);
  590. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Type [%s] \n",type,0,0);
  591. /**
  592. * The public function PL_HashTableLookup modifies the
  593. * the table while reading. so using this private function
  594. * which just does a lookup and doesn't modifies the
  595. * hashtable
  596. */
  597. toLowerCase(type);
  598. tMap = PL_HashTableLookupConst(_IdVattrMapTable, type);
  599. if (!tMap)
  600. {
  601. /**
  602. * this should not happen but no harm we just return
  603. */
  604. status = PRESENCE_FAILURE;
  605. slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
  606. "No hashtable for vattr types\n");
  607. goto bail;
  608. }
  609. /**
  610. * We found a matching id in the map table
  611. * now see if that id exists in the Slapi_Entry
  612. */
  613. tValue = slapi_entry_attr_get_charptr(e, tMap->imID);
  614. if (!tValue)
  615. {
  616. /**
  617. * we don't do anything here but just return
  618. */
  619. status = PRESENCE_FAILURE;
  620. goto bail;
  621. }
  622. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Value [%s] \n",tValue,0,0);
  623. tEntry = PL_HashTableLookupConst(_IdConfigMapTable, tMap->imID);
  624. *value = tValue;
  625. *entry = tEntry;
  626. *map = tMap;
  627. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- imIDExists \n",0,0,0);
  628. bail:
  629. return status;
  630. }
  631. static int makeHttpRequest(char *id, _Vmap *map, _ConfigEntry *info, char **BUF, int *size)
  632. {
  633. int status = PRESENCE_SUCCESS;
  634. char *buf = NULL;
  635. char *url = NULL;
  636. char *urltosend = NULL;
  637. int bytesRead;
  638. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> makeHttpRequest:: \n",0,0,0);
  639. if (map->syntax == PRESENCE_STRING) {
  640. url = info->textURL;
  641. } else {
  642. url = info->graphicURL;
  643. }
  644. if (url == NULL) {
  645. status = PRESENCE_FAILURE;
  646. goto bail;
  647. }
  648. urltosend = replaceIdWithValue(url, map->imID, id);
  649. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> URL [%s] \n",urltosend,0,0);
  650. /**
  651. * make an actual HTTP call now
  652. */
  653. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> RequestMethod [%s] \n", info->requestMethod,0,0);
  654. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Syntax [%d] \n", map->syntax,0,0);
  655. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> TextReturnType [%s] \n", info->textReturnType,0,0);
  656. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> GraphicReturnType [%s] \n", info->graphicReturnType,0,0);
  657. if (!strcasecmp(info->requestMethod, PRESENCE_REQUEST_METHOD_GET)) {
  658. if (map->syntax == PRESENCE_STRING) {
  659. if (!strcasecmp(info->textReturnType, PRESENCE_TEXT_RETURN_TYPE)) {
  660. status = http_get_text(_HttpAPI, urltosend, &buf, &bytesRead);
  661. } else {
  662. status = http_get_binary(_HttpAPI, urltosend, &buf, &bytesRead);
  663. }
  664. } else {
  665. if (!strcasecmp(info->graphicReturnType, PRESENCE_TEXT_RETURN_TYPE)) {
  666. status = http_get_text(_HttpAPI, urltosend, &buf, &bytesRead);
  667. } else {
  668. status = http_get_binary(_HttpAPI, urltosend, &buf, &bytesRead);
  669. }
  670. }
  671. } else if (!strcasecmp(info->requestMethod, PRESENCE_REQUEST_METHOD_REDIRECT)) {
  672. status = http_get_redirected_uri(_HttpAPI, urltosend, &buf, &bytesRead);
  673. } else {
  674. /**
  675. * error : unknown method
  676. * probably we should check at the time of loading
  677. * of the plugin itself that the config values are
  678. * properly checked and throw warning/errors in case
  679. * of any invalid entry
  680. */
  681. slapi_log_error(SLAPI_LOG_FATAL, PRESENCE_PLUGIN_SUBSYSTEM,
  682. "Unknown request type <%s>\n", info->requestMethod);
  683. status = PRESENCE_FAILURE;
  684. goto bail;
  685. }
  686. if (buf && status == PRESENCE_SUCCESS)
  687. {
  688. *BUF = buf;
  689. *size = bytesRead;
  690. }
  691. bail:
  692. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- makeHttpRequest:: <%d>\n",status,0,0);
  693. slapi_ch_free((void**)&urltosend);
  694. return status;
  695. }
  696. /**
  697. * This function replaces the occurrence of $ns[<vendor>]imid with its
  698. * actual value
  699. * e.g.
  700. * URL : http://opi.yahoo.com/online?u=$nsyimid
  701. * after replacing
  702. * newURL : http://opi.yahoo.com/online?u=srajam
  703. */
  704. static char * replaceIdWithValue(char *str, char *id, char *value)
  705. {
  706. int i=0;
  707. int k=0;
  708. char *newstr = NULL;
  709. char c;
  710. if (!str || !id || !value)
  711. {
  712. return NULL;
  713. }
  714. /* extra space for userids */
  715. newstr = (char *)slapi_ch_malloc(strlen(str) + strlen(value));
  716. while ((c=str[i]) != '\0')
  717. {
  718. if (c == '$')
  719. {
  720. int j = 0;
  721. i++; /*skip one char */
  722. /**
  723. * we found the begining of the string to be
  724. * substituted. Now skip the chars we want to replace
  725. */
  726. while (str[i] != '\0' && id[j] != '\0' &&
  727. (toupper(str[i]) == toupper(id[j])))
  728. {
  729. i++;
  730. j++;
  731. }
  732. j=0;
  733. while (value[j] != '\0')
  734. {
  735. newstr[k++] = value[j++];
  736. }
  737. }
  738. else
  739. {
  740. newstr[k++]=c;
  741. i++;
  742. }
  743. }
  744. newstr[k] = '\0';
  745. return newstr;
  746. }
  747. static int setIMStatus(char *id, _Vmap *map, _ConfigEntry *info,
  748. char *returnedBUF, int size, Slapi_ValueSet **results)
  749. {
  750. int status = PRESENCE_SUCCESS;
  751. char *ontxtmap = NULL;
  752. char *offtxtmap = NULL;
  753. Slapi_Value *value = NULL;
  754. Slapi_Value *value1 = NULL;
  755. Slapi_Value *value2 = NULL;
  756. struct berval bval;
  757. Slapi_Attr *attr = NULL;
  758. const struct berval *tmp = NULL;
  759. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> setIMStatus \n", 0,0,0);
  760. /**
  761. * we got some data back so lets try to map it to
  762. * the existing set of on/off data
  763. *
  764. * first we need to take a look at the
  765. * returned type and depending upon that parse
  766. * the data
  767. */
  768. if (map->syntax == PRESENCE_STRING) {
  769. /**
  770. * we had send a request for text
  771. * but chances are we might end up
  772. * getting an image back. So we need
  773. * to compare it to existing set of
  774. * images that we have in store ???
  775. */
  776. if (!strcasecmp(info->textReturnType, PRESENCE_TEXT_RETURN_TYPE)) {
  777. /* return value is in text format */
  778. ontxtmap = replaceIdWithValue(info->onTextMap, map->imID, id);
  779. offtxtmap = replaceIdWithValue(info->offTextMap, map->imID, id);
  780. if (!strcasecmp(ontxtmap, returnedBUF)) {
  781. /**
  782. * set the on value
  783. */
  784. value = slapi_value_new_string(PRESENCE_RETURNED_ON_TEXT);
  785. } else if (!strcasecmp(offtxtmap, returnedBUF)) {
  786. /**
  787. * set the off value
  788. */
  789. value = slapi_value_new_string(PRESENCE_RETURNED_OFF_TEXT);
  790. } else {
  791. value = slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT);
  792. }
  793. } else if (!strcasecmp(info->textReturnType, PRESENCE_BINARY_RETURN_TYPE)) {
  794. /**
  795. * call binary compare method
  796. */
  797. bval.bv_len = size;
  798. bval.bv_val = returnedBUF;
  799. value1 = slapi_value_new_berval(&bval);
  800. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> returned size [%d] \n", bval.bv_len,0,0);
  801. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> returned value [%s] \n", bval.bv_val,0,0);
  802. attr = info->onGraphicMap;
  803. if (attr) {
  804. slapi_attr_first_value(attr, &value2);
  805. tmp = slapi_value_get_berval(value2);
  806. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Stored size [%d] \n", tmp->bv_len,0,0);
  807. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> Stored value [%s] \n", tmp->bv_val,0,0);
  808. if (!slapi_value_compare(attr, value1, value2)) {
  809. value = slapi_value_new_string(PRESENCE_RETURNED_ON_TEXT);
  810. }
  811. }
  812. if (!value) {
  813. attr = info->offGraphicMap;
  814. if (attr) {
  815. slapi_attr_first_value(attr, &value2);
  816. if (!slapi_value_compare(attr, value1, value2)) {
  817. value = slapi_value_new_string(PRESENCE_RETURNED_OFF_TEXT);
  818. }
  819. }
  820. }
  821. if (!value) {
  822. attr = info->disabledGraphicMap;
  823. if (attr) {
  824. slapi_attr_first_value(attr, &value2);
  825. if (!slapi_value_compare(attr, value1, value2)) {
  826. value = slapi_value_new_string(PRESENCE_RETURNED_OFF_TEXT);
  827. }
  828. }
  829. }
  830. if (!value) {
  831. /* some error */
  832. value = slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT);
  833. }
  834. } else {
  835. /**
  836. * set the error condition
  837. */
  838. value = slapi_value_new_string(PRESENCE_RETURNED_ERROR_TEXT);
  839. }
  840. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> value [%s] \n", returnedBUF,0,0);
  841. } else {
  842. /**
  843. * we had send a request for image
  844. * so whatever we get back we just
  845. * return instead of analyzing it
  846. */
  847. if (!strcasecmp(info->graphicReturnType, PRESENCE_TEXT_RETURN_TYPE)) {
  848. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> value [%s] \n", returnedBUF,0,0);
  849. if (!strcasecmp(info->requestMethod, PRESENCE_REQUEST_METHOD_REDIRECT)) {
  850. /**
  851. * a redirect case in which we should probably have a
  852. * gif in store so return that value
  853. *
  854. * for now
  855. */
  856. ontxtmap = replaceIdWithValue(info->onTextMap, map->imID, id);
  857. offtxtmap = replaceIdWithValue(info->offTextMap, map->imID, id);
  858. if (!strcasecmp(ontxtmap, returnedBUF)) {
  859. /**
  860. * set the on value
  861. */
  862. attr = info->onGraphicMap;
  863. } else if (!strcasecmp(offtxtmap, returnedBUF)) {
  864. /**
  865. * set the off value
  866. */
  867. attr = info->offGraphicMap;
  868. } else {
  869. attr = info->disabledGraphicMap;
  870. }
  871. if (attr) {
  872. slapi_attr_first_value(attr, &value);
  873. }
  874. } else {
  875. /**
  876. * for now just set the returned value
  877. * should not happen in our case
  878. * ERROR
  879. */
  880. }
  881. } else {
  882. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> value [%s] \n", returnedBUF,0,0);
  883. bval.bv_len = size;
  884. bval.bv_val = returnedBUF;
  885. value = slapi_value_new_berval(&bval);
  886. }
  887. }
  888. if (!*results) {
  889. *results = slapi_valueset_new();
  890. }
  891. slapi_valueset_add_value(*results, value);
  892. if (ontxtmap) {
  893. slapi_ch_free((void **)&ontxtmap);
  894. }
  895. if (offtxtmap) {
  896. slapi_ch_free((void **)&offtxtmap);
  897. }
  898. if (value && map->syntax == PRESENCE_STRING) {
  899. slapi_value_free(&value);
  900. }
  901. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- setIMStatus \n", 0,0,0);
  902. return status;
  903. }
  904. static int setTypes(PLHashEntry *he, PRIntn i, void *arg)
  905. {
  906. int status;
  907. int props = SLAPI_ATTR_FLAG_OPATTR;
  908. Slapi_ValueSet *results = NULL;
  909. int type_name_disposition = 0;
  910. char *actual_type_name = 0;
  911. int free_flags = 0;
  912. struct _vattrtypes *args = arg;
  913. char *type = (char *)he->key;
  914. _Vmap *map = (_Vmap *)he->value;
  915. char *id = map->imID;
  916. LDAPDebug( LDAP_DEBUG_PLUGIN, "--> setTypes \n", 0,0,0);
  917. status = slapi_vattr_values_get_sp(NULL, args->entry, id, &results, &type_name_disposition, &actual_type_name, 0, &free_flags);
  918. if(status == PRESENCE_SUCCESS)
  919. {
  920. /* entry contains this attr */
  921. vattr_type_thang thang = {0};
  922. thang.type_name = type;
  923. thang.type_flags = props;
  924. slapi_vattrspi_add_type(args->context,&thang,0);
  925. slapi_vattr_values_free(&results, &actual_type_name, free_flags);
  926. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> ID [%s] Type[%s]\n", actual_type_name,type,0);
  927. }
  928. LDAPDebug( LDAP_DEBUG_PLUGIN, "<-- setTypes \n", 0,0,0);
  929. return HT_ENUMERATE_NEXT;
  930. }
  931. static void
  932. logGraphicAttributeValue( Slapi_Attr *attr, const char *attrname )
  933. {
  934. Slapi_Value *val = NULL;
  935. const struct berval *v = NULL;
  936. if ( LDAPDebugLevelIsSet( LDAP_DEBUG_PLUGIN )) {
  937. slapi_attr_first_value(attr, &val);
  938. v = slapi_value_get_berval(val);
  939. if (v) {
  940. char *ldifvalue;
  941. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> %s size [%d] \n",
  942. attrname,v->bv_len,0);
  943. ldifvalue = ldif_type_and_value_with_options(
  944. (char *)attrname, /* XXX: had to cast away const */
  945. v->bv_val, v->bv_len, 0 );
  946. if ( NULL != ldifvalue ) {
  947. LDAPDebug( LDAP_DEBUG_PLUGIN, "----------> %s value [\n%s]\n",
  948. attrname,ldifvalue,0);
  949. slapi_ch_free_string( &ldifvalue );
  950. }
  951. }
  952. }
  953. }
  954. static void deleteMapTables()
  955. {
  956. PL_HashTableEnumerateEntries(_IdConfigMapTable, destroyHashEntry, 0);
  957. if (_IdConfigMapTable)
  958. {
  959. PL_HashTableDestroy(_IdConfigMapTable);
  960. }
  961. PL_HashTableEnumerateEntries(_IdVattrMapTable, destroyHashEntry, 0);
  962. if (_IdVattrMapTable)
  963. {
  964. PL_HashTableDestroy(_IdVattrMapTable);
  965. }
  966. return;
  967. }
  968. static PRIntn destroyHashEntry(PLHashEntry *he, PRIntn index, void *arg)
  969. {
  970. void *value = NULL;
  971. if (he == NULL)
  972. {
  973. return HT_ENUMERATE_NEXT;
  974. }
  975. value = he->value;
  976. if (value)
  977. {
  978. slapi_ch_free(&value);
  979. }
  980. return HT_ENUMERATE_REMOVE;
  981. }
  982. static void toLowerCase(char* str)
  983. {
  984. if (str) {
  985. char* lstr = str;
  986. for(; (*lstr != '\0'); ++lstr) {
  987. *lstr = tolower(*lstr);
  988. }
  989. }
  990. }
  991. /**
  992. * utility function to print the array
  993. */
  994. void printMapTable()
  995. {
  996. PL_HashTableEnumerateEntries(_IdVattrMapTable, printIdVattrMapTable, 0);
  997. PL_HashTableEnumerateEntries(_IdConfigMapTable, printIdConfigMapTable, 0);
  998. }
  999. PRIntn printIdVattrMapTable(PLHashEntry *he, PRIntn i, void *arg)
  1000. {
  1001. char *key = (char *)he->key;
  1002. _Vmap *map = (_Vmap *)he->value;
  1003. printf("<---- Key -------> %s\n", key);
  1004. printf("<---- ImId ------> %s\n", map->imID);
  1005. printf("<---- syntax ----> %d\n", map->syntax);
  1006. return HT_ENUMERATE_NEXT;
  1007. }
  1008. PRIntn printIdConfigMapTable(PLHashEntry *he, PRIntn i, void *arg)
  1009. {
  1010. char *key = (char *)he->key;
  1011. _ConfigEntry *value = (_ConfigEntry *)he->value;
  1012. printf("<- Key ---------------------> %s\n", key);
  1013. printf("<---- text_url -------------> %s\n", value->textURL);
  1014. printf("<---- graphic_url ----------> %s\n", value->graphicURL);
  1015. printf("<---- on_text_map ----------> %s\n", value->onTextMap);
  1016. printf("<---- off_text_map ---------> %s\n", value->offTextMap);
  1017. printf("<---- request_method -------> %s\n", value->requestMethod);
  1018. printf("<---- text_return_type -----> %s\n", value->textReturnType);
  1019. printf("<---- graphic_return_type --> %s\n", value->graphicReturnType);
  1020. return HT_ENUMERATE_NEXT;
  1021. }