summaryrefslogtreecommitdiff
path: root/snmplib/default_store.c
diff options
context:
space:
mode:
Diffstat (limited to 'snmplib/default_store.c')
-rw-r--r--snmplib/default_store.c572
1 files changed, 572 insertions, 0 deletions
diff --git a/snmplib/default_store.c b/snmplib/default_store.c
new file mode 100644
index 0000000..8dbc9bc
--- /dev/null
+++ b/snmplib/default_store.c
@@ -0,0 +1,572 @@
+/*
+ * default_store.c: storage space for defaults
+ */
+/* Portions of this file are subject to the following copyright(s). See
+ * the Net-SNMP's COPYING file for more details and other copyrights
+ * that may apply:
+ */
+/*
+ * Portions of this file are copyrighted by:
+ * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms specified in the COPYING file
+ * distributed with the Net-SNMP package.
+ */
+/** @defgroup default_store storage space for defaults
+ * @ingroup library
+ *
+ The purpose of the default storage is three-fold:
+
+ 1) To create a global storage space without creating a
+ whole bunch of globally accessible variables or a
+ whole bunch of access functions to work with more
+ privately restricted variables.
+
+ 2) To provide a single location where the thread lock-
+ ing needs to be implemented. At the time of this
+ writing, however, thread locking is not yet in
+ place.
+
+ 3) To reduce the number of cross dependencies between
+ code pieces that may or may not be linked together
+ in the long run. This provides for a single loca-
+ tion in which configuration data, for example, can
+ be stored for a separate section of code that may
+ not be linked in to the application in question.
+
+ The functions defined here implement these goals.
+
+ Currently, three data types are supported: booleans, inte-
+ gers, and strings. Each of these data types have separate
+ storage spaces. In addition, the storage space for each
+ data type is divided further by the application level.
+ Currently, there are two storage spaces. The first is
+ reserved for the SNMP library itself. The second is
+ intended for use in applications and is not modified or
+ checked by the library, and, therefore, this is the space
+ usable by you.
+
+ These definitions correspond with the "storid" argument to the API
+ - \#define NETSNMP_DS_LIBRARY_ID 0
+ - \#define NETSNMP_DS_APPLICATION_ID 1
+ - \#define NETSNMP_DS_TOKEN_ID 2
+
+ These definitions correspond with the "which" argument to the API,
+ when the storeid argument is NETSNMP_DS_LIBRARY_ID
+
+ library booleans
+
+ - \#define NETSNMP_DS_LIB_MIB_ERRORS 0
+ - \#define NETSNMP_DS_LIB_SAVE_MIB_DESCRS 1
+ - \#define NETSNMP_DS_LIB_MIB_COMMENT_TERM 2
+ - \#define NETSNMP_DS_LIB_MIB_PARSE_LABEL 3
+ - \#define NETSNMP_DS_LIB_DUMP_PACKET 4
+ - \#define NETSNMP_DS_LIB_LOG_TIMESTAMP 5
+ - \#define NETSNMP_DS_LIB_DONT_READ_CONFIGS 6
+ - \#define NETSNMP_DS_LIB_MIB_REPLACE 7 replace objects from latest module
+ - \#define NETSNMP_DS_LIB_PRINT_NUMERIC_ENUM 8 print only numeric enum values
+ - \#define NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS 9 print only numeric enum values
+ - \#define NETSNMP_DS_LIB_DONT_BREAKDOWN_OIDS 10 dont print oid indexes specially
+ - \#define NETSNMP_DS_LIB_ALARM_DONT_USE_SIG 11 don't use the alarm() signal
+ - \#define NETSNMP_DS_LIB_PRINT_FULL_OID 12 print fully qualified oids
+ - \#define NETSNMP_DS_LIB_QUICK_PRINT 13 print very brief output for parsing
+ - \#define NETSNMP_DS_LIB_RANDOM_ACCESS 14 random access to oid labels
+ - \#define NETSNMP_DS_LIB_REGEX_ACCESS 15 regex matching to oid labels
+ - \#define NETSNMP_DS_LIB_DONT_CHECK_RANGE 16 don't check values for ranges on send
+ - \#define NETSNMP_DS_LIB_NO_TOKEN_WARNINGS 17 no warn about unknown config tokens
+ - \#define NETSNMP_DS_LIB_NUMERIC_TIMETICKS 18 print timeticks as a number
+ - \#define NETSNMP_DS_LIB_ESCAPE_QUOTES 19 shell escape quote marks in oids
+ - \#define NETSNMP_DS_LIB_REVERSE_ENCODE 20 encode packets from back to front
+ - \#define NETSNMP_DS_LIB_PRINT_BARE_VALUE 21 just print value (not OID = value)
+ - \#define NETSNMP_DS_LIB_EXTENDED_INDEX 22 print extended index format [x1][x2]
+ - \#define NETSNMP_DS_LIB_PRINT_HEX_TEXT 23 print ASCII text along with hex strings
+ - \#define NETSNMP_DS_LIB_PRINT_UCD_STYLE_OID 24 print OID's using the UCD-style prefix suppression
+ - \#define NETSNMP_DS_LIB_READ_UCD_STYLE_OID 25 require top-level OIDs to be prefixed with a dot
+ - \#define NETSNMP_DS_LIB_HAVE_READ_PREMIB_CONFIG 26 have the pre-mib parsing config tokens been processed
+ - \#define NETSNMP_DS_LIB_HAVE_READ_CONFIG 27 have the config tokens been processed
+ - \#define NETSNMP_DS_LIB_QUICKE_PRINT 28
+ - \#define NETSNMP_DS_LIB_DONT_PRINT_UNITS 29 don't print UNITS suffix
+ - \#define NETSNMP_DS_LIB_NO_DISPLAY_HINT 30 don't apply DISPLAY-HINTs
+ - \#define NETSNMP_DS_LIB_16BIT_IDS 31 restrict requestIDs, etc to 16-bit values
+ - \#define NETSNMP_DS_LIB_DONT_PERSIST_STATE 32 don't save/load any persistant state
+ - \#define NETSNMP_DS_LIB_2DIGIT_HEX_OUTPUT 33 print a leading 0 on hex values <= 'f'
+
+
+ library integers
+
+ - \#define NETSNMP_DS_LIB_MIB_WARNINGS 0
+ - \#define NETSNMP_DS_LIB_SECLEVEL 1
+ - \#define NETSNMP_DS_LIB_SNMPVERSION 2
+ - \#define NETSNMP_DS_LIB_DEFAULT_PORT 3
+ - \#define NETSNMP_DS_LIB_OID_OUTPUT_FORMAT 4
+ - \#define NETSNMP_DS_LIB_STRING_OUTPUT_FORMAT 5
+
+ library strings
+
+ - \#define NETSNMP_DS_LIB_SECNAME 0
+ - \#define NETSNMP_DS_LIB_CONTEXT 1
+ - \#define NETSNMP_DS_LIB_PASSPHRASE 2
+ - \#define NETSNMP_DS_LIB_AUTHPASSPHRASE 3
+ - \#define NETSNMP_DS_LIB_PRIVPASSPHRASE 4
+ - \#define NETSNMP_DS_LIB_OPTIONALCONFIG 5
+ - \#define NETSNMP_DS_LIB_APPTYPE 6
+ - \#define NETSNMP_DS_LIB_COMMUNITY 7
+ - \#define NETSNMP_DS_LIB_PERSISTENT_DIR 8
+ - \#define NETSNMP_DS_LIB_CONFIGURATION_DIR 9
+ - \#define NETSNMP_DS_LIB_SECMODEL 10
+ - \#define NETSNMP_DS_LIB_MIBDIRS 11
+ - \#define NETSNMP_DS_LIB_OIDSUFFIX 12
+ - \#define NETSNMP_DS_LIB_OIDPREFIX 13
+ - \#define NETSNMP_DS_LIB_CLIENT_ADDR 14
+ - \#define NETSNMP_DS_LIB_TEMP_FILE_PATTERN 15
+ - \#define NETSNMP_DS_LIB_AUTHMASTERKEY 16
+ - \#define NETSNMP_DS_LIB_PRIVMASTERKEY 17
+ - \#define NETSNMP_DS_LIB_AUTHLOCALIZEDKEY 18
+ - \#define NETSNMP_DS_LIB_PRIVLOCALIZEDKEY 19
+
+ * @{
+ */
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-features.h>
+#include <sys/types.h>
+#if HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if HAVE_STRING_H
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_DMALLOC_H
+#include <dmalloc.h>
+#endif
+
+#include <net-snmp/types.h>
+#include <net-snmp/output_api.h>
+#include <net-snmp/config_api.h>
+#include <net-snmp/library/default_store.h> /* for "internal" definitions */
+#include <net-snmp/utilities.h>
+
+#include <net-snmp/library/snmp_api.h>
+
+netsnmp_feature_child_of(default_store_all, libnetsnmp)
+
+netsnmp_feature_child_of(default_store_void, default_store_all)
+
+#ifndef NETSNMP_FEATURE_REMOVE_DEFAULT_STORE_VOID
+#endif /* NETSNMP_FEATURE_REMOVE_DEFAULT_STORE_VOID */
+
+
+static const char * stores [NETSNMP_DS_MAX_IDS] = { "LIB", "APP", "TOK" };
+
+typedef struct netsnmp_ds_read_config_s {
+ u_char type;
+ char *token;
+ char *ftype;
+ int storeid;
+ int which;
+ struct netsnmp_ds_read_config_s *next;
+} netsnmp_ds_read_config;
+
+static netsnmp_ds_read_config *netsnmp_ds_configs = NULL;
+
+static int netsnmp_ds_integers[NETSNMP_DS_MAX_IDS][NETSNMP_DS_MAX_SUBIDS];
+static char netsnmp_ds_booleans[NETSNMP_DS_MAX_IDS][NETSNMP_DS_MAX_SUBIDS/8];
+static char *netsnmp_ds_strings[NETSNMP_DS_MAX_IDS][NETSNMP_DS_MAX_SUBIDS];
+#ifndef NETSNMP_FEATURE_REMOVE_DEFAULT_STORE_VOID
+static void *netsnmp_ds_voids[NETSNMP_DS_MAX_IDS][NETSNMP_DS_MAX_SUBIDS];
+#endif /* NETSNMP_FEATURE_REMOVE_DEFAULT_STORE_VOID */
+
+/*
+ * Prototype definitions
+ */
+void netsnmp_ds_handle_config(const char *token, char *line);
+
+/**
+ * Stores "true" or "false" given an int value for value into
+ * netsnmp_ds_booleans[store][which] slot.
+ *
+ * @param storeid an index to the boolean storage container's first index(store)
+ *
+ * @param which an index to the boolean storage container's second index(which)
+ *
+ * @param value if > 0, "true" is set into the slot otherwise "false"
+ *
+ * @return Returns SNMPPERR_GENERR if the storeid and which parameters do not
+ * correspond to a valid slot, or SNMPERR_SUCCESS otherwise.
+ */
+int
+netsnmp_ds_set_boolean(int storeid, int which, int value)
+{
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS) {
+ return SNMPERR_GENERR;
+ }
+
+ DEBUGMSGTL(("netsnmp_ds_set_boolean", "Setting %s:%d = %d/%s\n",
+ stores[storeid], which, value, ((value) ? "True" : "False")));
+
+ if (value > 0) {
+ netsnmp_ds_booleans[storeid][which/8] |= (1 << (which % 8));
+ } else {
+ netsnmp_ds_booleans[storeid][which/8] &= (0xff7f >> (7 - (which % 8)));
+ }
+
+ return SNMPERR_SUCCESS;
+}
+
+int
+netsnmp_ds_toggle_boolean(int storeid, int which)
+{
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS) {
+ return SNMPERR_GENERR;
+ }
+
+ if ((netsnmp_ds_booleans[storeid][which/8] & (1 << (which % 8))) == 0) {
+ netsnmp_ds_booleans[storeid][which/8] |= (1 << (which % 8));
+ } else {
+ netsnmp_ds_booleans[storeid][which/8] &= (0xff7f >> (7 - (which % 8)));
+ }
+
+ DEBUGMSGTL(("netsnmp_ds_toggle_boolean", "Setting %s:%d = %d/%s\n",
+ stores[storeid], which, netsnmp_ds_booleans[storeid][which/8],
+ ((netsnmp_ds_booleans[storeid][which/8]) ? "True" : "False")));
+
+ return SNMPERR_SUCCESS;
+}
+
+int
+netsnmp_ds_get_boolean(int storeid, int which)
+{
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS) {
+ return SNMPERR_GENERR;
+ }
+
+ return (netsnmp_ds_booleans[storeid][which/8] & (1 << (which % 8))) ? 1:0;
+}
+
+int
+netsnmp_ds_set_int(int storeid, int which, int value)
+{
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS) {
+ return SNMPERR_GENERR;
+ }
+
+ DEBUGMSGTL(("netsnmp_ds_set_int", "Setting %s:%d = %d\n",
+ stores[storeid], which, value));
+
+ netsnmp_ds_integers[storeid][which] = value;
+ return SNMPERR_SUCCESS;
+}
+
+int
+netsnmp_ds_get_int(int storeid, int which)
+{
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS) {
+ return SNMPERR_GENERR;
+ }
+
+ return netsnmp_ds_integers[storeid][which];
+}
+
+int
+netsnmp_ds_set_string(int storeid, int which, const char *value)
+{
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS) {
+ return SNMPERR_GENERR;
+ }
+
+ DEBUGMSGTL(("netsnmp_ds_set_string", "Setting %s:%d = \"%s\"\n",
+ stores[storeid], which, (value ? value : "(null)")));
+
+ /*
+ * is some silly person is calling us with our own pointer?
+ */
+ if (netsnmp_ds_strings[storeid][which] == value)
+ return SNMPERR_SUCCESS;
+
+ if (netsnmp_ds_strings[storeid][which] != NULL) {
+ free(netsnmp_ds_strings[storeid][which]);
+ netsnmp_ds_strings[storeid][which] = NULL;
+ }
+
+ if (value) {
+ netsnmp_ds_strings[storeid][which] = strdup(value);
+ } else {
+ netsnmp_ds_strings[storeid][which] = NULL;
+ }
+
+ return SNMPERR_SUCCESS;
+}
+
+char *
+netsnmp_ds_get_string(int storeid, int which)
+{
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS) {
+ return NULL;
+ }
+
+ return netsnmp_ds_strings[storeid][which];
+}
+
+#ifndef NETSNMP_FEATURE_REMOVE_DEFAULT_STORE_VOID
+int
+netsnmp_ds_set_void(int storeid, int which, void *value)
+{
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS) {
+ return SNMPERR_GENERR;
+ }
+
+ DEBUGMSGTL(("netsnmp_ds_set_void", "Setting %s:%d = %p\n",
+ stores[storeid], which, value));
+
+ netsnmp_ds_voids[storeid][which] = value;
+
+ return SNMPERR_SUCCESS;
+}
+
+void *
+netsnmp_ds_get_void(int storeid, int which)
+{
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS) {
+ return NULL;
+ }
+
+ return netsnmp_ds_voids[storeid][which];
+}
+#endif /* NETSNMP_FEATURE_REMOVE_DEFAULT_STORE_VOID */
+
+int
+netsnmp_ds_parse_boolean(char *line)
+{
+ char *value, *endptr;
+ int itmp;
+ char *st;
+
+ value = strtok_r(line, " \t\n", &st);
+ if (strcasecmp(value, "yes") == 0 ||
+ strcasecmp(value, "true") == 0) {
+ return 1;
+ } else if (strcasecmp(value, "no") == 0 ||
+ strcasecmp(value, "false") == 0) {
+ return 0;
+ } else {
+ itmp = strtol(value, &endptr, 10);
+ if (*endptr != 0 || itmp < 0 || itmp > 1) {
+ config_perror("Should be yes|no|true|false|0|1");
+ return -1;
+ }
+ return itmp;
+ }
+}
+
+void
+netsnmp_ds_handle_config(const char *token, char *line)
+{
+ netsnmp_ds_read_config *drsp;
+ char buf[SNMP_MAXBUF];
+ char *value, *endptr;
+ int itmp;
+ char *st;
+
+ DEBUGMSGTL(("netsnmp_ds_handle_config", "handling %s\n", token));
+
+ for (drsp = netsnmp_ds_configs;
+ drsp != NULL && strcasecmp(token, drsp->token) != 0;
+ drsp = drsp->next);
+
+ if (drsp != NULL) {
+ DEBUGMSGTL(("netsnmp_ds_handle_config",
+ "setting: token=%s, type=%d, id=%s, which=%d\n",
+ drsp->token, drsp->type, stores[drsp->storeid],
+ drsp->which));
+
+ switch (drsp->type) {
+ case ASN_BOOLEAN:
+ itmp = netsnmp_ds_parse_boolean(line);
+ if ( itmp != -1 )
+ netsnmp_ds_set_boolean(drsp->storeid, drsp->which, itmp);
+ DEBUGMSGTL(("netsnmp_ds_handle_config", "bool: %d\n", itmp));
+ break;
+
+ case ASN_INTEGER:
+ value = strtok_r(line, " \t\n", &st);
+ itmp = strtol(value, &endptr, 10);
+ if (*endptr != 0) {
+ config_perror("Bad integer value");
+ } else {
+ netsnmp_ds_set_int(drsp->storeid, drsp->which, itmp);
+ }
+ DEBUGMSGTL(("netsnmp_ds_handle_config", "int: %d\n", itmp));
+ break;
+
+ case ASN_OCTET_STR:
+ if (*line == '"') {
+ copy_nword(line, buf, sizeof(buf));
+ netsnmp_ds_set_string(drsp->storeid, drsp->which, buf);
+ } else {
+ netsnmp_ds_set_string(drsp->storeid, drsp->which, line);
+ }
+ DEBUGMSGTL(("netsnmp_ds_handle_config", "string: %s\n", line));
+ break;
+
+ default:
+ snmp_log(LOG_ERR, "netsnmp_ds_handle_config: type %d (0x%02x)\n",
+ drsp->type, drsp->type);
+ break;
+ }
+ } else {
+ snmp_log(LOG_ERR, "netsnmp_ds_handle_config: no registration for %s\n",
+ token);
+ }
+}
+
+
+int
+netsnmp_ds_register_config(u_char type, const char *ftype, const char *token,
+ int storeid, int which)
+{
+ netsnmp_ds_read_config *drsp;
+
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS || token == NULL) {
+ return SNMPERR_GENERR;
+ }
+
+ if (netsnmp_ds_configs == NULL) {
+ netsnmp_ds_configs = SNMP_MALLOC_TYPEDEF(netsnmp_ds_read_config);
+ if (netsnmp_ds_configs == NULL)
+ return SNMPERR_GENERR;
+ drsp = netsnmp_ds_configs;
+ } else {
+ for (drsp = netsnmp_ds_configs; drsp->next != NULL; drsp = drsp->next);
+ drsp->next = SNMP_MALLOC_TYPEDEF(netsnmp_ds_read_config);
+ if (drsp->next == NULL)
+ return SNMPERR_GENERR;
+ drsp = drsp->next;
+ }
+
+ drsp->type = type;
+ drsp->ftype = strdup(ftype);
+ drsp->token = strdup(token);
+ drsp->storeid = storeid;
+ drsp->which = which;
+
+ switch (type) {
+ case ASN_BOOLEAN:
+ register_config_handler(ftype, token, netsnmp_ds_handle_config, NULL,
+ "(1|yes|true|0|no|false)");
+ break;
+
+ case ASN_INTEGER:
+ register_config_handler(ftype, token, netsnmp_ds_handle_config, NULL,
+ "integerValue");
+ break;
+
+ case ASN_OCTET_STR:
+ register_config_handler(ftype, token, netsnmp_ds_handle_config, NULL,
+ "string");
+ break;
+
+ }
+ return SNMPERR_SUCCESS;
+}
+
+int
+netsnmp_ds_register_premib(u_char type, const char *ftype, const char *token,
+ int storeid, int which)
+{
+ netsnmp_ds_read_config *drsp;
+
+ if (storeid < 0 || storeid >= NETSNMP_DS_MAX_IDS ||
+ which < 0 || which >= NETSNMP_DS_MAX_SUBIDS || token == NULL) {
+ return SNMPERR_GENERR;
+ }
+
+ if (netsnmp_ds_configs == NULL) {
+ netsnmp_ds_configs = SNMP_MALLOC_TYPEDEF(netsnmp_ds_read_config);
+ if (netsnmp_ds_configs == NULL)
+ return SNMPERR_GENERR;
+ drsp = netsnmp_ds_configs;
+ } else {
+ for (drsp = netsnmp_ds_configs; drsp->next != NULL; drsp = drsp->next);
+ drsp->next = SNMP_MALLOC_TYPEDEF(netsnmp_ds_read_config);
+ if (drsp->next == NULL)
+ return SNMPERR_GENERR;
+ drsp = drsp->next;
+ }
+
+ drsp->type = type;
+ drsp->ftype = strdup(ftype);
+ drsp->token = strdup(token);
+ drsp->storeid = storeid;
+ drsp->which = which;
+
+ switch (type) {
+ case ASN_BOOLEAN:
+ register_prenetsnmp_mib_handler(ftype, token, netsnmp_ds_handle_config,
+ NULL, "(1|yes|true|0|no|false)");
+ break;
+
+ case ASN_INTEGER:
+ register_prenetsnmp_mib_handler(ftype, token, netsnmp_ds_handle_config,
+ NULL, "integerValue");
+ break;
+
+ case ASN_OCTET_STR:
+ register_prenetsnmp_mib_handler(ftype, token, netsnmp_ds_handle_config,
+ NULL, "string");
+ break;
+
+ }
+ return SNMPERR_SUCCESS;
+}
+
+void
+netsnmp_ds_shutdown(void)
+{
+ netsnmp_ds_read_config *drsp;
+ int i, j;
+
+ for (drsp = netsnmp_ds_configs; drsp; drsp = netsnmp_ds_configs) {
+ netsnmp_ds_configs = drsp->next;
+
+ if (drsp->ftype && drsp->token) {
+ unregister_config_handler(drsp->ftype, drsp->token);
+ }
+ if (drsp->ftype != NULL) {
+ free(drsp->ftype);
+ }
+ if (drsp->token != NULL) {
+ free(drsp->token);
+ }
+ free(drsp);
+ }
+
+ for (i = 0; i < NETSNMP_DS_MAX_IDS; i++) {
+ for (j = 0; j < NETSNMP_DS_MAX_SUBIDS; j++) {
+ if (netsnmp_ds_strings[i][j] != NULL) {
+ free(netsnmp_ds_strings[i][j]);
+ netsnmp_ds_strings[i][j] = NULL;
+ }
+ }
+ }
+}
+/** @} */