/* * agent_index.c * * Maintain a registry of index allocations * (Primarily required for AgentX support, * but it could be more widely useable). */ #include #include #include #if HAVE_STRING_H #include #endif #if HAVE_STDLIB_H #include #endif #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #if HAVE_NETINET_IN_H #include #endif #include #include #include #include #include "snmpd.h" #include "agent_global_vars.h" #include "mibgroup/struct.h" #include #include #ifdef USING_AGENTX_SUBAGENT_MODULE #include "agentx/subagent.h" #include "agentx/client.h" #endif netsnmp_feature_child_of(agent_index_all, libnetsnmpagent) netsnmp_feature_child_of(remove_index, agent_index_all) /* * Initial support for index allocation */ struct snmp_index { netsnmp_variable_list *varbind; /* or pointer to var_list ? */ int allocated; netsnmp_session *session; struct snmp_index *next_oid; struct snmp_index *prev_oid; struct snmp_index *next_idx; } *snmp_index_head = NULL; /* * The caller is responsible for free()ing the memory returned by * this function. */ char * register_string_index(oid * name, size_t name_len, char *cp) { netsnmp_variable_list varbind, *res; memset(&varbind, 0, sizeof(netsnmp_variable_list)); varbind.type = ASN_OCTET_STR; snmp_set_var_objid(&varbind, name, name_len); if (cp != ANY_STRING_INDEX) { snmp_set_var_value(&varbind, (u_char *) cp, strlen(cp)); res = register_index(&varbind, ALLOCATE_THIS_INDEX, main_session); } else { res = register_index(&varbind, ALLOCATE_ANY_INDEX, main_session); } if (res == NULL) { return NULL; } else { char *rv = (char *)malloc(res->val_len + 1); if (rv) { memcpy(rv, res->val.string, res->val_len); rv[res->val_len] = 0; } free(res); return rv; } } int register_int_index(oid * name, size_t name_len, int val) { netsnmp_variable_list varbind, *res; memset(&varbind, 0, sizeof(netsnmp_variable_list)); varbind.type = ASN_INTEGER; snmp_set_var_objid(&varbind, name, name_len); varbind.val.string = varbind.buf; if (val != ANY_INTEGER_INDEX) { varbind.val_len = sizeof(long); *varbind.val.integer = val; res = register_index(&varbind, ALLOCATE_THIS_INDEX, main_session); } else { res = register_index(&varbind, ALLOCATE_ANY_INDEX, main_session); } if (res == NULL) { return -1; } else { int rv = *(res->val.integer); free(res); return rv; } } /* * The caller is responsible for free()ing the memory returned by * this function. */ netsnmp_variable_list * register_oid_index(oid * name, size_t name_len, oid * value, size_t value_len) { netsnmp_variable_list varbind; memset(&varbind, 0, sizeof(netsnmp_variable_list)); varbind.type = ASN_OBJECT_ID; snmp_set_var_objid(&varbind, name, name_len); if (value != ANY_OID_INDEX) { snmp_set_var_value(&varbind, (u_char *) value, value_len * sizeof(oid)); return register_index(&varbind, ALLOCATE_THIS_INDEX, main_session); } else { return register_index(&varbind, ALLOCATE_ANY_INDEX, main_session); } } /* * The caller is responsible for free()ing the memory returned by * this function. */ netsnmp_variable_list * register_index(netsnmp_variable_list * varbind, int flags, netsnmp_session * ss) { netsnmp_variable_list *rv = NULL; struct snmp_index *new_index, *idxptr, *idxptr2; struct snmp_index *prev_oid_ptr, *prev_idx_ptr; int res, res2, i; DEBUGMSGTL(("register_index", "register ")); DEBUGMSGVAR(("register_index", varbind)); DEBUGMSG(("register_index", "for session %8p\n", ss)); #if defined(USING_AGENTX_SUBAGENT_MODULE) && !defined(TESTING) if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE) == SUB_AGENT) { return (agentx_register_index(ss, varbind, flags)); } #endif /* * Look for the requested OID entry */ prev_oid_ptr = NULL; prev_idx_ptr = NULL; res = 1; res2 = 1; for (idxptr = snmp_index_head; idxptr != NULL; prev_oid_ptr = idxptr, idxptr = idxptr->next_oid) { if ((res = snmp_oid_compare(varbind->name, varbind->name_length, idxptr->varbind->name, idxptr->varbind->name_length)) <= 0) break; } /* * Found the OID - now look at the registered indices */ if (res == 0 && idxptr) { if (varbind->type != idxptr->varbind->type) return NULL; /* wrong type */ /* * If we've been asked for an arbitrary new value, * then find the end of the list. * If we've been asked for any arbitrary value, * then look for an unused entry, and use that. * If there aren't any, continue as for new. * Otherwise, locate the given value in the (sorted) * list of already allocated values */ if (flags & ALLOCATE_ANY_INDEX) { for (idxptr2 = idxptr; idxptr2 != NULL; prev_idx_ptr = idxptr2, idxptr2 = idxptr2->next_idx) { if (flags == ALLOCATE_ANY_INDEX && !(idxptr2->allocated)) { if ((rv = snmp_clone_varbind(idxptr2->varbind)) != NULL) { idxptr2->session = ss; idxptr2->allocated = 1; } return rv; } } } else { for (idxptr2 = idxptr; idxptr2 != NULL; prev_idx_ptr = idxptr2, idxptr2 = idxptr2->next_idx) { switch (varbind->type) { case ASN_INTEGER: res2 = (*varbind->val.integer - *idxptr2->varbind->val.integer); break; case ASN_OCTET_STR: i = SNMP_MIN(varbind->val_len, idxptr2->varbind->val_len); res2 = memcmp(varbind->val.string, idxptr2->varbind->val.string, i); break; case ASN_OBJECT_ID: res2 = snmp_oid_compare(varbind->val.objid, varbind->val_len / sizeof(oid), idxptr2->varbind->val.objid, idxptr2->varbind->val_len / sizeof(oid)); break; default: return NULL; /* wrong type */ } if (res2 <= 0) break; } if (res2 == 0) { if (idxptr2->allocated) { /* * No good: the index is in use. */ return NULL; } else { /* * Okay, it's unallocated, we can just claim ownership * here. */ if ((rv = snmp_clone_varbind(idxptr2->varbind)) != NULL) { idxptr2->session = ss; idxptr2->allocated = 1; } return rv; } } } } /* * OK - we've now located where the new entry needs to * be fitted into the index registry tree * To recap: * 'prev_oid_ptr' points to the head of the OID index * list prior to this one. If this is null, then * it means that this is the first OID in the list. * 'idxptr' points either to the head of this OID list, * or the next OID (if this is a new OID request) * These can be distinguished by the value of 'res'. * * 'prev_idx_ptr' points to the index entry that sorts * immediately prior to the requested value (if any). * If an arbitrary value is required, then this will * point to the last allocated index. * If this pointer is null, then either this is a new * OID request, or the requested value is the first * in the list. * 'idxptr2' points to the next sorted index (if any) * but is not actually needed any more. * * Clear? Good! * I hope you've been paying attention. * There'll be a test later :-) */ /* * We proceed by creating the new entry * (by copying the entry provided) */ new_index = (struct snmp_index *) calloc(1, sizeof(struct snmp_index)); if (new_index == NULL) return NULL; if (NULL == snmp_varlist_add_variable(&new_index->varbind, varbind->name, varbind->name_length, varbind->type, varbind->val.string, varbind->val_len)) { /* * if (snmp_clone_var( varbind, new_index->varbind ) != 0 ) */ free(new_index); return NULL; } new_index->session = ss; new_index->allocated = 1; if (varbind->type == ASN_OCTET_STR && flags == ALLOCATE_THIS_INDEX) new_index->varbind->val.string[new_index->varbind->val_len] = 0; /* * If we've been given a value, then we can use that, but * otherwise, we need to create a new value for this entry. * Note that ANY_INDEX and NEW_INDEX are both covered by this * test (since NEW_INDEX & ANY_INDEX = ANY_INDEX, remember?) */ if (flags & ALLOCATE_ANY_INDEX) { if (prev_idx_ptr) { if (snmp_clone_var(prev_idx_ptr->varbind, new_index->varbind) != 0) { free(new_index); return NULL; } } else new_index->varbind->val.string = new_index->varbind->buf; switch (varbind->type) { case ASN_INTEGER: if (prev_idx_ptr) { (*new_index->varbind->val.integer)++; } else *(new_index->varbind->val.integer) = 1; new_index->varbind->val_len = sizeof(long); break; case ASN_OCTET_STR: if (prev_idx_ptr) { i = new_index->varbind->val_len - 1; while (new_index->varbind->buf[i] == 'z') { new_index->varbind->buf[i] = 'a'; i--; if (i < 0) { i = new_index->varbind->val_len; new_index->varbind->buf[i] = 'a'; new_index->varbind->buf[i + 1] = 0; } } new_index->varbind->buf[i]++; } else strcpy((char *) new_index->varbind->buf, "aaaa"); new_index->varbind->val_len = strlen((char *) new_index->varbind->buf); break; case ASN_OBJECT_ID: if (prev_idx_ptr) { i = prev_idx_ptr->varbind->val_len / sizeof(oid) - 1; while (new_index->varbind->val.objid[i] == 255) { new_index->varbind->val.objid[i] = 1; i--; if (i == 0 && new_index->varbind->val.objid[0] == 2) { new_index->varbind->val.objid[0] = 1; i = new_index->varbind->val_len / sizeof(oid); new_index->varbind->val.objid[i] = 0; new_index->varbind->val_len += sizeof(oid); } } new_index->varbind->val.objid[i]++; } else { /* * If the requested OID name is small enough, * * append another OID (1) and use this as the * * default starting value for new indexes. */ if ((varbind->name_length + 1) * sizeof(oid) <= 40) { for (i = 0; i < (int) varbind->name_length; i++) new_index->varbind->val.objid[i] = varbind->name[i]; new_index->varbind->val.objid[varbind->name_length] = 1; new_index->varbind->val_len = (varbind->name_length + 1) * sizeof(oid); } else { /* * Otherwise use '.1.1.1.1...' */ i = 40 / sizeof(oid); if (i > 4) i = 4; new_index->varbind->val_len = i * (sizeof(oid)); for (i--; i >= 0; i--) new_index->varbind->val.objid[i] = 1; } } break; default: snmp_free_var(new_index->varbind); free(new_index); return NULL; /* Index type not supported */ } } /* * Try to duplicate the new varbind for return. */ if ((rv = snmp_clone_varbind(new_index->varbind)) == NULL) { snmp_free_var(new_index->varbind); free(new_index); return NULL; } /* * Right - we've set up the new entry. * All that remains is to link it into the tree. * There are a number of possible cases here, * so watch carefully. */ if (prev_idx_ptr) { new_index->next_idx = prev_idx_ptr->next_idx; new_index->next_oid = prev_idx_ptr->next_oid; prev_idx_ptr->next_idx = new_index; } else { if (res == 0 && idxptr) { new_index->next_idx = idxptr; new_index->next_oid = idxptr->next_oid; } else { new_index->next_idx = NULL; new_index->next_oid = idxptr; } if (prev_oid_ptr) { while (prev_oid_ptr) { prev_oid_ptr->next_oid = new_index; prev_oid_ptr = prev_oid_ptr->next_idx; } } else snmp_index_head = new_index; } return rv; } /* * Release an allocated index, * to allow it to be used elsewhere */ netsnmp_feature_child_of(release_index,netsnmp_unused) #ifndef NETSNMP_FEATURE_REMOVE_RELEASE_INDEX int release_index(netsnmp_variable_list * varbind) { return (unregister_index(varbind, TRUE, NULL)); } #endif /* NETSNMP_FEATURE_REMOVE_RELEASE_INDEX */ #ifndef NETSNMP_FEATURE_REMOVE_REMOVE_INDEX /* * Completely remove an allocated index, * due to errors in the registration process. */ int remove_index(netsnmp_variable_list * varbind, netsnmp_session * ss) { return (unregister_index(varbind, FALSE, ss)); } #endif /* NETSNMP_FEATURE_REMOVE_REMOVE_INDEX */ void unregister_index_by_session(netsnmp_session * ss) { struct snmp_index *idxptr, *idxptr2; for (idxptr = snmp_index_head; idxptr != NULL; idxptr = idxptr->next_oid) for (idxptr2 = idxptr; idxptr2 != NULL; idxptr2 = idxptr2->next_idx) if (idxptr2->session == ss) { idxptr2->allocated = 0; idxptr2->session = NULL; } } int unregister_index(netsnmp_variable_list * varbind, int remember, netsnmp_session * ss) { struct snmp_index *idxptr, *idxptr2; struct snmp_index *prev_oid_ptr, *prev_idx_ptr; int res, res2, i; #if defined(USING_AGENTX_SUBAGENT_MODULE) && !defined(TESTING) if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE) == SUB_AGENT) { return (agentx_unregister_index(ss, varbind)); } #endif /* * Look for the requested OID entry */ prev_oid_ptr = NULL; prev_idx_ptr = NULL; res = 1; res2 = 1; for (idxptr = snmp_index_head; idxptr != NULL; prev_oid_ptr = idxptr, idxptr = idxptr->next_oid) { if ((res = snmp_oid_compare(varbind->name, varbind->name_length, idxptr->varbind->name, idxptr->varbind->name_length)) <= 0) break; } if (res != 0) return INDEX_ERR_NOT_ALLOCATED; if (varbind->type != idxptr->varbind->type) return INDEX_ERR_WRONG_TYPE; for (idxptr2 = idxptr; idxptr2 != NULL; prev_idx_ptr = idxptr2, idxptr2 = idxptr2->next_idx) { switch (varbind->type) { case ASN_INTEGER: res2 = (*varbind->val.integer - *idxptr2->varbind->val.integer); break; case ASN_OCTET_STR: i = SNMP_MIN(varbind->val_len, idxptr2->varbind->val_len); res2 = memcmp(varbind->val.string, idxptr2->varbind->val.string, i); break; case ASN_OBJECT_ID: res2 = snmp_oid_compare(varbind->val.objid, varbind->val_len / sizeof(oid), idxptr2->varbind->val.objid, idxptr2->varbind->val_len / sizeof(oid)); break; default: return INDEX_ERR_WRONG_TYPE; /* wrong type */ } if (res2 <= 0) break; } if (res2 != 0 || (res2 == 0 && !idxptr2->allocated)) { return INDEX_ERR_NOT_ALLOCATED; } if (ss != idxptr2->session) return INDEX_ERR_WRONG_SESSION; /* * If this is a "normal" index unregistration, * mark the index entry as unused, but leave * it in situ. This allows differentiation * between ANY_INDEX and NEW_INDEX */ if (remember) { idxptr2->allocated = 0; /* Unused index */ idxptr2->session = NULL; return SNMP_ERR_NOERROR; } /* * If this is a failed attempt to register a * number of indexes, the successful ones * must be removed completely. */ if (prev_idx_ptr) { prev_idx_ptr->next_idx = idxptr2->next_idx; } else if (prev_oid_ptr) { if (idxptr2->next_idx) /* Use p_idx_ptr as a temp variable */ prev_idx_ptr = idxptr2->next_idx; else prev_idx_ptr = idxptr2->next_oid; while (prev_oid_ptr) { prev_oid_ptr->next_oid = prev_idx_ptr; prev_oid_ptr = prev_oid_ptr->next_idx; } } else { if (idxptr2->next_idx) snmp_index_head = idxptr2->next_idx; else snmp_index_head = idxptr2->next_oid; } snmp_free_var(idxptr2->varbind); free(idxptr2); return SNMP_ERR_NOERROR; } netsnmp_feature_child_of(unregister_indexes,netsnmp_unused) #ifndef NETSNMP_FEATURE_REMOVE_UNREGISTER_INDEXES int unregister_string_index(oid * name, size_t name_len, char *cp) { netsnmp_variable_list varbind; memset(&varbind, 0, sizeof(netsnmp_variable_list)); varbind.type = ASN_OCTET_STR; snmp_set_var_objid(&varbind, name, name_len); snmp_set_var_value(&varbind, (u_char *) cp, strlen(cp)); return (unregister_index(&varbind, FALSE, main_session)); } int unregister_int_index(oid * name, size_t name_len, int val) { netsnmp_variable_list varbind; memset(&varbind, 0, sizeof(netsnmp_variable_list)); varbind.type = ASN_INTEGER; snmp_set_var_objid(&varbind, name, name_len); varbind.val.string = varbind.buf; varbind.val_len = sizeof(long); *varbind.val.integer = val; return (unregister_index(&varbind, FALSE, main_session)); } int unregister_oid_index(oid * name, size_t name_len, oid * value, size_t value_len) { netsnmp_variable_list varbind; memset(&varbind, 0, sizeof(netsnmp_variable_list)); varbind.type = ASN_OBJECT_ID; snmp_set_var_objid(&varbind, name, name_len); snmp_set_var_value(&varbind, (u_char *) value, value_len * sizeof(oid)); return (unregister_index(&varbind, FALSE, main_session)); } #endif /* NETSNMP_FEATURE_REMOVE_UNREGISTER_INDEXES */ void dump_idx_registry(void) { struct snmp_index *idxptr, *idxptr2; u_char *sbuf = NULL, *ebuf = NULL; size_t sbuf_len = 0, sout_len = 0, ebuf_len = 0, eout_len = 0; if (snmp_index_head != NULL) { printf("\nIndex Allocations:\n"); } for (idxptr = snmp_index_head; idxptr != NULL; idxptr = idxptr->next_oid) { sout_len = 0; if (sprint_realloc_objid(&sbuf, &sbuf_len, &sout_len, 1, idxptr->varbind->name, idxptr->varbind->name_length)) { printf("%s indexes:\n", sbuf); } else { printf("%s [TRUNCATED] indexes:\n", sbuf); } for (idxptr2 = idxptr; idxptr2 != NULL; idxptr2 = idxptr2->next_idx) { switch (idxptr2->varbind->type) { case ASN_INTEGER: printf(" %ld for session %8p, allocated %d\n", *idxptr2->varbind->val.integer, idxptr2->session, idxptr2->allocated); break; case ASN_OCTET_STR: printf(" \"%s\" for session %8p, allocated %d\n", idxptr2->varbind->val.string, idxptr2->session, idxptr2->allocated); break; case ASN_OBJECT_ID: eout_len = 0; if (sprint_realloc_objid(&ebuf, &ebuf_len, &eout_len, 1, idxptr2->varbind->val.objid, idxptr2->varbind->val_len / sizeof(oid))) { printf(" %s for session %8p, allocated %d\n", ebuf, idxptr2->session, idxptr2->allocated); } else { printf (" %s [TRUNCATED] for sess %8p, allocated %d\n", ebuf, idxptr2->session, idxptr2->allocated); } break; default: printf("unsupported type (%d/0x%02x)\n", idxptr2->varbind->type, idxptr2->varbind->type); } } } if (sbuf != NULL) { free(sbuf); } if (ebuf != NULL) { free(ebuf); } } netsnmp_feature_child_of(count_indexes, netsnmp_unused) #ifndef NETSNMP_FEATURE_REMOVE_UNUSED unsigned long count_indexes(oid * name, size_t namelen, int include_unallocated) { struct snmp_index *i = NULL, *j = NULL; unsigned long n = 0; for (i = snmp_index_head; i != NULL; i = i->next_oid) { if (netsnmp_oid_equals(name, namelen, i->varbind->name, i->varbind->name_length) == 0) { for (j = i; j != NULL; j = j->next_idx) { if (j->allocated || include_unallocated) { n++; } } } } return n; } #endif /* NETSNMP_FEATURE_REMOVE_UNUSED */ #ifdef TESTING netsnmp_variable_list varbind; netsnmp_session main_sess, *main_session = &main_sess; void test_string_register(int n, char *cp) { varbind->name[4] = n; if (register_string_index(varbind->name, varbind.name_length, cp) == NULL) printf("allocating %s failed\n", cp); } void test_int_register(int n, int val) { varbind->name[4] = n; if (register_int_index(varbind->name, varbind.name_length, val) == -1) printf("allocating %d/%d failed\n", n, val); } void test_oid_register(int n, int subid) { netsnmp_variable_list *res; varbind->name[4] = n; if (subid != -1) { varbind->val.objid[5] = subid; res = register_oid_index(varbind->name, varbind.name_length, varbind->val.objid, varbind->val_len / sizeof(oid)); } else res = register_oid_index(varbind->name, varbind.name_length, NULL, 0); if (res == NULL) printf("allocating %d/%d failed\n", n, subid); } void main(int argc, char argv[]) { oid name[] = { 1, 2, 3, 4, 0 }; int i; memset(&varbind, 0, sizeof(netsnmp_variable_list)); snmp_set_var_objid(&varbind, name, 5); varbind->type = ASN_OCTET_STR; /* * Test index structure linking: * a) sorted by OID */ test_string_register(20, "empty OID"); test_string_register(10, "first OID"); test_string_register(40, "last OID"); test_string_register(30, "middle OID"); /* * b) sorted by index value */ test_string_register(25, "eee: empty IDX"); test_string_register(25, "aaa: first IDX"); test_string_register(25, "zzz: last IDX"); test_string_register(25, "mmm: middle IDX"); printf("This next one should fail....\n"); test_string_register(25, "eee: empty IDX"); /* duplicate */ printf("done\n"); /* * c) test initial index linking */ test_string_register(5, "eee: empty initial IDX"); test_string_register(5, "aaa: replace initial IDX"); /* * Did it all work? */ dump_idx_registry(); unregister_index_by_session(main_session); /* * Now test index allocation * a) integer values */ test_int_register(110, -1); /* empty */ test_int_register(110, -1); /* append */ test_int_register(110, 10); /* append exact */ printf("This next one should fail....\n"); test_int_register(110, 10); /* exact duplicate */ printf("done\n"); test_int_register(110, -1); /* append */ test_int_register(110, 5); /* insert exact */ /* * b) string values */ test_string_register(120, NULL); /* empty */ test_string_register(120, NULL); /* append */ test_string_register(120, "aaaz"); test_string_register(120, NULL); /* minor rollover */ test_string_register(120, "zzzz"); test_string_register(120, NULL); /* major rollover */ /* * c) OID values */ test_oid_register(130, -1); /* empty */ test_oid_register(130, -1); /* append */ varbind->val_len = varbind.name_length * sizeof(oid); memcpy(varbind->buf, varbind.name, varbind.val_len); varbind->val.objid = (oid *) varbind.buf; varbind->val_len += sizeof(oid); test_oid_register(130, 255); /* append exact */ test_oid_register(130, -1); /* minor rollover */ test_oid_register(130, 100); /* insert exact */ printf("This next one should fail....\n"); test_oid_register(130, 100); /* exact duplicate */ printf("done\n"); varbind->val.objid = (oid *) varbind.buf; for (i = 0; i < 6; i++) varbind->val.objid[i] = 255; varbind->val.objid[0] = 1; test_oid_register(130, 255); /* set up rollover */ test_oid_register(130, -1); /* medium rollover */ for (i = 0; i < 6; i++) varbind->val.objid[i] = 255; varbind->val.objid[0] = 2; test_oid_register(130, 255); /* set up rollover */ test_oid_register(130, -1); /* major rollover */ /* * Did it all work? */ dump_idx_registry(); /* * Test the various "invalid" requests * (unsupported types, mis-matched types, etc) */ printf("The rest of these should fail....\n"); test_oid_register(110, -1); test_oid_register(110, 100); test_oid_register(120, -1); test_oid_register(120, 100); test_string_register(110, NULL); test_string_register(110, "aaaa"); test_string_register(130, NULL); test_string_register(130, "aaaa"); test_int_register(120, -1); test_int_register(120, 1); test_int_register(130, -1); test_int_register(130, 1); printf("done - this dump should be the same as before\n"); dump_idx_registry(); } #endif