Commit message: Refactored SNMP engine after vulnerabilities
CVE: CVE-2020-12141
CWE: CWE-125
Vulnerability description: An out-of-bounds read in the SNMP stack in Contiki-NG 4.4 and earlier allows an attacker to cause a denial of service and potentially disclose information via crafted SNMP packets to snmp_ber_decode_string_len_buffer in os/net/app-layer/snmp/snmp-ber.c.

Project: contiki-ng

snmp_ber_decode_type(snmp_packet_t *snmp_packet, uint8_t *type)
{
  if(snmp_packet->used == 0) {
    return 0;
  }

  *type = *snmp_packet->in++;
  snmp_packet->used--;

  return 1;
}

Project: contiki-ng

sysName_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_string(varbind, oid, "Contiki-NG - "CONTIKI_TARGET_STRING);
}

Project: contiki-ng

sysLocation_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_string(varbind, oid, "");
}

Project: contiki-ng

snmp_mib_find_next(snmp_oid_t *oid)
{
  snmp_mib_resource_t *resource;

  resource = NULL;
  for(resource = list_head(snmp_mib);
      resource; resource = resource->next) {

    if(snmp_mib_cmp_oid(&resource->oid, oid) > 0) {
      return resource;
    }
  }

  return NULL;
}

Project: contiki-ng

snmp_engine(snmp_packet_t *snmp_packet)
{
  snmp_header_t header;
  snmp_varbind_t varbinds[SNMP_MAX_NR_VALUES];

  memset(&header, 0, sizeof(header));
  memset(varbinds, 0, sizeof(varbinds));

  if(!snmp_message_decode(snmp_packet, &header, varbinds)) {
    return 0;
  }

  if(header.version != SNMP_VERSION_1) {
    if(strncmp(header.community.community, SNMP_COMMUNITY, header.community.length)) {
      LOG_ERR("Request with invalid community\n");
      return 0;
    }
  }

  /*
   * Now handle the SNMP requests depending on their type
   */
  switch(header.pdu_type) {
  case BER_DATA_TYPE_PDU_GET_REQUEST:
    if(snmp_engine_get(&header, varbinds) == -1) {
      return 0;
    }
    break;

  case BER_DATA_TYPE_PDU_GET_NEXT_REQUEST:
    if(snmp_engine_get_next(&header, varbinds) == -1) {
      return 0;
    }
    break;

  case BER_DATA_TYPE_PDU_GET_BULK:
    if(snmp_engine_get_bulk(&header, varbinds) == -1) {
      return 0;
    }
    break;

  default:
    LOG_ERR("Invalid request type");
    return 0;
  }

  header.pdu_type = BER_DATA_TYPE_PDU_GET_RESPONSE;

  return snmp_message_encode(snmp_packet, &header, varbinds);
}

Project: contiki-ng

snmp_ber_decode_null(snmp_packet_t *snmp_packet)
{
  if(snmp_packet->used == 0) {
    return 0;
  }

  snmp_packet->in++;
  snmp_packet->used--;

  if(snmp_packet->used == 0) {
    return 0;
  }

  snmp_packet->in++;
  snmp_packet->used--;

  return 1;
}

Project: contiki-ng

snmp_mib_init(void)
{
  list_init(snmp_mib);
}

Project: contiki-ng

snmp_ber_encode_integer(snmp_packet_t *snmp_packet, uint32_t number)
{
  return snmp_ber_encode_unsigned_integer(snmp_packet, BER_DATA_TYPE_INTEGER, number);
}

Project: contiki-ng

snmp_ber_encode_string_len(snmp_packet_t *snmp_packet, const char *str, uint32_t length)
{
  uint32_t i;

  str += length - 1;
  for(i = 0; i < length; ++i) {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)*str--;
    snmp_packet->used++;
  }

  if(!snmp_ber_encode_length(snmp_packet, length)) {
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_OCTET_STRING)) {
    return 0;
  }

  return 1;
}

Project: contiki-ng

snmp_api_set_time_ticks(snmp_varbind_t *varbind, snmp_oid_t *oid, uint32_t integer)
{
  memcpy(&varbind->oid, oid, sizeof(snmp_oid_t));
  varbind->value_type = BER_DATA_TYPE_TIMETICKS;
  varbind->value.integer = integer;
}

Project: contiki-ng

sysServices_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_time_ticks(varbind, oid, clock_seconds() * 100);
}

Project: contiki-ng

snmp_ber_encode_unsigned_integer(snmp_packet_t *snmp_packet, uint8_t type, uint32_t number)
{
  uint16_t original_out_len;

  original_out_len = snmp_packet->used;
  do {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)number & 0xFF;
    snmp_packet->used++;
    /* I'm not sure why but on MSPGCC the >> 8 operation goes haywire here */
#ifdef __MSPGCC__
    number >>= 4;
    number >>= 4;
#else /* __MSPGCC__ */
    number >>= 8;
#endif /* __MSPGCC__ */
  } while(number);

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used - original_out_len)) {
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, type)) {
    return 0;
  }

  return 1;
}

Project: contiki-ng

snmp_engine_get_next(snmp_header_t *header, snmp_varbind_t *varbinds)
{
  snmp_mib_resource_t *resource;
  uint8_t i;

  i = 0;
  while(varbinds[i].value_type != BER_DATA_TYPE_EOC && i < SNMP_MAX_NR_VALUES) {
    resource = snmp_mib_find_next(&varbinds[i].oid);
    if(!resource) {
      switch(header->version) {
      case SNMP_VERSION_1:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        /*
         * Varbinds are 1 indexed
         */
        header->error_index = i + 1;
        break;
      case SNMP_VERSION_2C:
        (&varbinds[i])->value_type = BER_DATA_TYPE_END_OF_MIB_VIEW;
        break;
      default:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        header->error_index = 0;
      }
    } else {
      resource->handler(&varbinds[i], &resource->oid);
    }

    i++;
  }

  return 0;
}

Project: contiki-ng

snmp_message_encode(snmp_packet_t *snmp_packet, snmp_header_t *header, snmp_varbind_t *varbinds)
{
  uint32_t last_out_len;
  int8_t i;

  for(i = SNMP_MAX_NR_VALUES - 1; i >= 0; i--) {
    if(varbinds[i].value_type == BER_DATA_TYPE_EOC) {
      continue;
    }

    last_out_len = snmp_packet->used;

    switch(varbinds[i].value_type) {
    case BER_DATA_TYPE_INTEGER:
      if(!snmp_ber_encode_integer(snmp_packet, varbinds[i].value.integer)) {
        LOG_DBG("Could not encode integer type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_TIMETICKS:
      if(!snmp_ber_encode_timeticks(snmp_packet, varbinds[i].value.integer)) {
        LOG_DBG("Could not encode timeticks type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_OCTET_STRING:
      if(!snmp_ber_encode_string_len(snmp_packet, varbinds[i].value.string.string, varbinds[i].value.string.length)) {
        LOG_DBG("Could not encode octet string type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_OBJECT_IDENTIFIER:
      if(!snmp_ber_encode_oid(snmp_packet, &varbinds[i].value.oid)) {
        LOG_DBG("Could not encode oid type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_NULL:
    case BER_DATA_TYPE_NO_SUCH_INSTANCE:
    case BER_DATA_TYPE_END_OF_MIB_VIEW:
      if(!snmp_ber_encode_null(snmp_packet, varbinds[i].value_type)) {
        LOG_DBG("Could not encode null type\n");
        return 0;
      }
      break;
    default:
      LOG_DBG("Could not encode invlid type\n");
      return 0;
    }

    if(!snmp_ber_encode_oid(snmp_packet, &varbinds[i].oid)) {
      LOG_DBG("Could not encode oid\n");
      return 0;
    }

    if(!snmp_ber_encode_length(snmp_packet, (snmp_packet->used - last_out_len))) {
      LOG_DBG("Could not encode length\n");
      return 0;
    }

    if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_SEQUENCE)) {
      LOG_DBG("Could not encode type\n");
      return 0;
    }
  }

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used)) {
    LOG_DBG("Could not encode length\n");
    return 0;
  }
  if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_SEQUENCE)) {
    LOG_DBG("Could not encode type\n");
    return 0;
  }

  switch(header->pdu_type) {
  case BER_DATA_TYPE_PDU_GET_BULK:
    if(!snmp_ber_encode_integer(snmp_packet, header->max_repetitions)) {
      LOG_DBG("Could not encode max repetition\n");
      return 0;
    }

    if(!snmp_ber_encode_integer(snmp_packet, header->non_repeaters)) {
      LOG_DBG("Could not encode non repeaters\n");
      return 0;
    }
    break;
  default:
    if(!snmp_ber_encode_integer(snmp_packet, header->error_index)) {
      LOG_DBG("Could not encode error index\n");
      return 0;
    }

    if(!snmp_ber_encode_integer(snmp_packet, header->error_status)) {
      LOG_DBG("Could not encode error status\n");
      return 0;
    }
    break;
  }

  if(!snmp_ber_encode_integer(snmp_packet, header->request_id)) {
    LOG_DBG("Could not encode request id\n");
    return 0;
  }

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used)) {
    LOG_DBG("Could not encode length\n");
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, header->pdu_type)) {
    LOG_DBG("Could not encode pdu type\n");
    return 0;
  }

  if(!snmp_ber_encode_string_len(snmp_packet, header->community.community, header->community.length)) {
    LOG_DBG("Could not encode community\n");
    return 0;
  }

  if(!snmp_ber_encode_integer(snmp_packet, header->version)) {
    LOG_DBG("Could not encode version\n");
    return 0;
  }

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used)) {
    LOG_DBG("Could not encode length\n");
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_SEQUENCE)) {
    LOG_DBG("Could not encode type\n");
    return 0;
  }

  /* Move the pointer to the last position */
  snmp_packet->out++;
  return 1;
}

Project: contiki-ng

snmp_ber_encode_oid(snmp_packet_t *snmp_packet, snmp_oid_t *oid)
{
  uint32_t val;
  uint16_t original_out_len;
  uint8_t pos;

  original_out_len = snmp_packet->used;

  pos = oid->length - 1;
  while(pos) {
    val = oid->data[pos];

    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)(val & 0x7F);
    snmp_packet->used++;
    val >>= 7;

    while(val) {
      if(snmp_packet->used == snmp_packet->max) {
        return 0;
      }

      *snmp_packet->out-- = (uint8_t)((val & 0x7F) | 0x80);
      snmp_packet->used++;

      val >>= 7;
    }
    pos--;
  }

  if(snmp_packet->used == snmp_packet->max) {
    return 0;
  }

  val = *(snmp_packet->out + 1) + 40 * oid->data[pos];
  snmp_packet->used--;
  snmp_packet->out++;

  if(snmp_packet->used == snmp_packet->max) {
    return 0;
  }

  *snmp_packet->out-- = (uint8_t)(val & 0x7F);
  snmp_packet->used++;

  val >>= 7;

  while(val) {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)((val & 0x7F) | 0x80);
    snmp_packet->used++;

    val >>= 7;
  }

  if(!snmp_ber_encode_length(snmp_packet, snmp_packet->used - original_out_len)) {
    return 0;
  }

  if(!snmp_ber_encode_type(snmp_packet, BER_DATA_TYPE_OBJECT_IDENTIFIER)) {
    return 0;
  }

  return 1;
}

Project: contiki-ng

snmp_ber_decode_timeticks(snmp_packet_t *snmp_packet, uint32_t *timeticks)
{
  return snmp_ber_decode_unsigned_integer(snmp_packet, BER_DATA_TYPE_TIMETICKS, timeticks);
}

Project: contiki-ng

snmp_ber_encode_null(snmp_packet_t *snmp_packet, uint8_t type)
{

  if(snmp_packet->used == snmp_packet->max) {
    return 0;
  }

  *snmp_packet->out-- = 0x00;
  snmp_packet->used++;

  return snmp_ber_encode_type(snmp_packet, type);
}

Project: contiki-ng

snmp_engine_get_bulk(snmp_header_t *header, snmp_varbind_t *varbinds)
{
  snmp_mib_resource_t *resource;
  snmp_oid_t oids[SNMP_MAX_NR_VALUES];
  uint32_t j, original_varbinds_length;
  uint8_t repeater;
  uint8_t i, varbinds_length;

  /*
   * A local copy of the requested oids must be kept since
   *  the varbinds are modified on the fly
   */
  original_varbinds_length = 0;
  while(varbinds[original_varbinds_length].value_type != BER_DATA_TYPE_EOC && original_varbinds_length < SNMP_MAX_NR_VALUES) {
    memcpy(&oids[original_varbinds_length], &varbinds[original_varbinds_length].oid, sizeof(snmp_oid_t));
    original_varbinds_length++;
  }

  varbinds_length = 0;
  for(i = 0; i < original_varbinds_length; i++) {
    if(i >= header->non_repeaters) {
      break;
    }

    resource = snmp_mib_find_next(&oids[i]);
    if(!resource) {
      switch(header->version) {
      case SNMP_VERSION_1:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        /*
         * Varbinds are 1 indexed
         */
        header->error_index = i + 1;
        break;
      case SNMP_VERSION_2C:
        (&varbinds[i])->value_type = BER_DATA_TYPE_END_OF_MIB_VIEW;
        break;
      default:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        header->error_index = 0;
      }
    } else {
      if(varbinds_length < SNMP_MAX_NR_VALUES) {
        resource->handler(&varbinds[varbinds_length], &resource->oid);
        (varbinds_length)++;
      } else {
        return -1;
      }
    }
  }

  for(i = 0; i < header->max_repetitions; i++) {
    repeater = 0;
    for(j = header->non_repeaters; j < original_varbinds_length; j++) {
      resource = snmp_mib_find_next(&oids[j]);
      if(!resource) {
        switch(header->version) {
        case SNMP_VERSION_1:
          header->error_status = SNMP_STATUS_NO_SUCH_NAME;
          /*
           * Varbinds are 1 indexed
           */
          header->error_index = varbinds_length + 1;
          break;
        case SNMP_VERSION_2C:
          if(varbinds_length < SNMP_MAX_NR_VALUES) {
            (&varbinds[varbinds_length])->value_type = BER_DATA_TYPE_END_OF_MIB_VIEW;
            memcpy(&varbinds[varbinds_length].oid, &oids[j], sizeof(snmp_oid_t));
            (varbinds_length)++;
          } else {
            return -1;
          }
          break;
        default:
          header->error_status = SNMP_STATUS_NO_SUCH_NAME;
          header->error_index = 0;
        }
      } else {
        if(varbinds_length < SNMP_MAX_NR_VALUES) {
          resource->handler(&varbinds[varbinds_length], &resource->oid);
          (varbinds_length)++;
          memcpy(&oids[j], &resource->oid, sizeof(snmp_oid_t));
          repeater++;
        } else {
          return -1;
        }
      }
    }
    if(repeater == 0) {
      break;
    }
  }

  return 0;
}

Project: contiki-ng

snmp_mib_find(snmp_oid_t *oid)
{
  snmp_mib_resource_t *resource;

  resource = NULL;
  for(resource = list_head(snmp_mib);
      resource; resource = resource->next) {

    if(!snmp_mib_cmp_oid(oid, &resource->oid)) {
      return resource;
    }
  }

  return NULL;
}

Project: contiki-ng

snmp_ber_decode_length(snmp_packet_t *snmp_packet, uint8_t *length)
{
  if(snmp_packet->used == 0) {
    return 0;
  }

  *length = *snmp_packet->in++;
  snmp_packet->used--;

  return 1;
}

Project: contiki-ng

sysContact_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_string(varbind, oid, "Contiki-NG, https://github.com/contiki-ng/contiki-ng");
}

Project: contiki-ng

snmp_api_set_string(snmp_varbind_t *varbind, snmp_oid_t *oid, char *string)
{
  memcpy(&varbind->oid, oid, sizeof(snmp_oid_t));
  varbind->value_type = BER_DATA_TYPE_OCTET_STRING;
  varbind->value.string.string = string;
  varbind->value.string.length = strlen(string);
}

Project: contiki-ng

snmp_mib_add(snmp_mib_resource_t *new_resource)
{
  snmp_mib_resource_t *resource;
  uint8_t i;

  for(resource = list_head(snmp_mib);
      resource; resource = resource->next) {

    if(snmp_mib_cmp_oid(&resource->oid, &new_resource->oid) > 0) {
      break;
    }
  }
  if(resource == NULL) {
    list_add(snmp_mib, new_resource);
  } else {
    list_insert(snmp_mib, new_resource, resource);
  }

  if(LOG_DBG_ENABLED) {
    /*
     * We print the entire resource table
     */
    LOG_DBG("Table after insert.\n");
    for(resource = list_head(snmp_mib);
        resource; resource = resource->next) {

      i = 0;
      LOG_DBG("{");
      while(i < resource->oid.length) {
        LOG_DBG_("%lu", (unsigned long)resource->oid.data[i]);
        i++;
        if(i < resource->oid.length) {
          LOG_DBG_(".");
        }
      }
      LOG_DBG_("}\n");
    }
  }
}

Project: contiki-ng

snmp_api_set_oid(snmp_varbind_t *varbind, snmp_oid_t *oid, snmp_oid_t *ret_oid)
{
  memcpy(&varbind->oid, oid, sizeof(snmp_oid_t));
  varbind->value_type = BER_DATA_TYPE_OBJECT_IDENTIFIER;
  memcpy(&varbind->value.oid, ret_oid, sizeof(snmp_oid_t));
}

Project: contiki-ng

snmp_api_add_resource(snmp_mib_resource_t *new_resource)
{
  return snmp_mib_add(new_resource);
}

Project: contiki-ng

snmp_message_decode(snmp_packet_t *snmp_packet, snmp_header_t *header, snmp_varbind_t *varbinds)
{
  uint8_t type, len, i;

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    LOG_DBG("Could not decode type\n");
    return 0;
  }

  if(type != BER_DATA_TYPE_SEQUENCE) {
    LOG_DBG("Invalid type\n");
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    LOG_DBG("Could not decode length\n");
    return 0;
  }

  if(!snmp_ber_decode_integer(snmp_packet, &header->version)) {
    LOG_DBG("Could not decode version\n");
    return 0;
  }

  switch(header->version) {
  case SNMP_VERSION_1:
  case SNMP_VERSION_2C:
    break;
  default:
    LOG_DBG("Invalid version\n");
    return 0;
  }

  if(!snmp_ber_decode_string_len_buffer(snmp_packet, &header->community.community, &header->community.length)) {
    LOG_DBG("Could not decode community\n");
    return 0;
  }

  if(!snmp_ber_decode_type(snmp_packet, &header->pdu_type)) {
    LOG_DBG("Could not decode pdu type\n");
    return 0;
  }

  switch(header->pdu_type) {
  case BER_DATA_TYPE_PDU_GET_REQUEST:
  case BER_DATA_TYPE_PDU_GET_NEXT_REQUEST:
  case BER_DATA_TYPE_PDU_GET_RESPONSE:
  case BER_DATA_TYPE_PDU_SET_REQUEST:
  case BER_DATA_TYPE_PDU_GET_BULK:
    break;
  default:
    LOG_DBG("Invalid version\n");
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    LOG_DBG("Could not decode length\n");
    return 0;
  }

  if(!snmp_ber_decode_integer(snmp_packet, &header->request_id)) {
    LOG_DBG("Could not decode request id\n");
    return 0;
  }

  switch(header->pdu_type) {
  case BER_DATA_TYPE_PDU_GET_BULK:
    if(!snmp_ber_decode_integer(snmp_packet, &header->non_repeaters)) {
      LOG_DBG("Could not decode non repeaters\n");
      return 0;
    }

    if(!snmp_ber_decode_integer(snmp_packet, &header->max_repetitions)) {
      LOG_DBG("Could not decode max repetition\n");
      return 0;
    }
    break;
  default:
    if(!snmp_ber_decode_integer(snmp_packet, &header->error_status)) {
      LOG_DBG("Could not decode error status\n");
      return 0;
    }

    if(!snmp_ber_decode_integer(snmp_packet, &header->error_index)) {
      LOG_DBG("Could not decode error index\n");
      return 0;
    }
    break;
  }

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    LOG_DBG("Could not decode type\n");
    return 0;
  }

  if(type != BER_DATA_TYPE_SEQUENCE) {
    LOG_DBG("Invalid type\n");
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    LOG_DBG("Could not decode length\n");
    return 0;
  }

  for(i = 0; snmp_packet->used > 0; ++i) {
    if(i >= SNMP_MAX_NR_VALUES) {
      LOG_DBG("OID's overflow\n");
      return 0;
    }

    if(!snmp_ber_decode_type(snmp_packet, &type)) {
      LOG_DBG("Could not decode type\n");
      return 0;
    }

    if(type != BER_DATA_TYPE_SEQUENCE) {
      LOG_DBG("Invalid (%X) type\n", type);
      return 0;
    }

    if(!snmp_ber_decode_length(snmp_packet, &len)) {
      LOG_DBG("Could not decode length\n");
      return 0;
    }

    if(!snmp_ber_decode_oid(snmp_packet, &varbinds[i].oid)) {
      LOG_DBG("Could not decode oid\n");
      return 0;
    }

    varbinds[i].value_type = *snmp_packet->in;

    switch(varbinds[i].value_type) {
    case BER_DATA_TYPE_INTEGER:
      if(!snmp_ber_decode_integer(snmp_packet, &varbinds[i].value.integer)) {
        LOG_DBG("Could not decode integer type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_TIMETICKS:
      if(!snmp_ber_decode_timeticks(snmp_packet, &varbinds[i].value.integer)) {
        LOG_DBG("Could not decode timeticks type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_OCTET_STRING:
      if(!snmp_ber_decode_string_len_buffer(snmp_packet, &varbinds[i].value.string.string, &varbinds[i].value.string.length)) {
        LOG_DBG("Could not decode octed string type\n");
        return 0;
      }
      break;
    case BER_DATA_TYPE_NULL:
      if(!snmp_ber_decode_null(snmp_packet)) {
        LOG_DBG("Could not decode null type\n");
        return 0;
      }
      break;
    default:
      LOG_DBG("Invalid varbind type\n");
      return 0;
    }
  }

  return 1;
}

Project: contiki-ng

snmp_ber_decode_string_len_buffer(snmp_packet_t *snmp_packet, const char **str, uint32_t *length)
{
  uint8_t type, i, length_bytes;

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    return 0;
  }

  if(type != BER_DATA_TYPE_OCTET_STRING) {
    /*
     * Sanity check
     * Invalid type in buffer
     */
    return 0;
  }

  if((*snmp_packet->in & 0x80) == 0) {

    if(snmp_packet->used == 0) {
      return 0;
    }

    *length = (uint32_t)*snmp_packet->in++;
    snmp_packet->used--;
  } else {

    if(snmp_packet->used == 0) {
      return 0;
    }

    length_bytes = (uint8_t)(*snmp_packet->in++ & 0x7F);
    snmp_packet->used--;

    if(length_bytes > 4) {
      /*
       * Sanity check
       * It will not fit in the uint32_t
       */
      return 0;
    }

    if(snmp_packet->used == 0) {
      return 0;
    }

    *length = (uint32_t)*snmp_packet->in++;
    snmp_packet->used--;

    for(i = 1; i < length_bytes; ++i) {
      *length <<= 8;

      if(snmp_packet->used == 0) {
        return 0;
      }

      *length |= *snmp_packet->in++;
      snmp_packet->used--;
    }
  }

  *str = (const char *)snmp_packet->in;

  if(snmp_packet->used == 0 || snmp_packet->used - *length <= 0) {
    return 0;
  }

  snmp_packet->used -= *length;
  snmp_packet->in += *length;

  return 1;
}

Project: contiki-ng

snmp_ber_encode_timeticks(snmp_packet_t *snmp_packet, uint32_t timeticks)
{
  return snmp_ber_encode_unsigned_integer(snmp_packet, BER_DATA_TYPE_TIMETICKS, timeticks);
}

Project: contiki-ng

sysDescr_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_string(varbind, oid, CONTIKI_VERSION_STRING);
}

Project: contiki-ng

snmp_ber_decode_integer(snmp_packet_t *snmp_packet, uint32_t *num)
{
  return snmp_ber_decode_unsigned_integer(snmp_packet, BER_DATA_TYPE_INTEGER, num);
}

Project: contiki-ng

snmp_ber_encode_type(snmp_packet_t *snmp_packet, uint8_t type)
{
  if(snmp_packet->used == snmp_packet->max) {
    return 0;
  }

  *snmp_packet->out-- = type;
  snmp_packet->used++;

  return 1;
}

Project: contiki-ng

snmp_ber_decode_unsigned_integer(snmp_packet_t *snmp_packet, uint8_t expected_type, uint32_t *num)
{
  uint8_t i, len, type;

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    return 0;
  }

  if(type != expected_type) {
    /*
     * Sanity check
     * Invalid type in buffer
     */
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    return 0;
  }

  if(len > 4) {
    /*
     * Sanity check
     * It will not fit in the uint32_t
     */
    return 0;
  }

  if(snmp_packet->used == 0) {
    return 0;
  }

  *num = (uint32_t)(*snmp_packet->in++ & 0xFF);
  snmp_packet->used--;

  for(i = 1; i < len; ++i) {
    *num <<= 8;
    if(snmp_packet->used == 0) {
      return 0;
    }
    *num |= (uint8_t)(*snmp_packet->in++ & 0xFF);
    snmp_packet->used--;
  }

  return 1;
}

Project: contiki-ng

snmp_init()
{
  snmp_mib_init();
  process_start(&snmp_process, NULL);
}

Project: contiki-ng

sysObjectID_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  OID(sysObjectID_oid, 1, 3, 6, 1, 4, 1, 54352);
  snmp_api_set_oid(varbind, oid, &sysObjectID_oid);
}

Project: contiki-ng

sysUpTime_handler(snmp_varbind_t *varbind, snmp_oid_t *oid)
{
  snmp_api_set_time_ticks(varbind, oid, clock_seconds() * 100);
}

Project: contiki-ng

snmp_ber_encode_length(snmp_packet_t *snmp_packet, uint16_t length)
{
  if(length > 0xFF) {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)length & 0xFF;
    snmp_packet->used++;

    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)(length >> 8) & 0xFF;
    snmp_packet->used++;

    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = 0x82;
    snmp_packet->used++;
  } else if(length > 0x7F) {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)length & 0xFF;
    snmp_packet->used++;

    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = 0x81;
    snmp_packet->used++;
  } else {
    if(snmp_packet->used == snmp_packet->max) {
      return 0;
    }

    *snmp_packet->out-- = (uint8_t)length & 0x7F;
    snmp_packet->used++;
  }

  return 1;
}

Project: contiki-ng

PROCESS_THREAD(snmp_process, ev, data)
{
  PROCESS_BEGIN();

  snmp_packet_t snmp_packet;

  /* new connection with remote host */
  snmp_udp_conn = udp_new(NULL, 0, NULL);
  udp_bind(snmp_udp_conn, SNMP_SERVER_PORT);
  LOG_DBG("Listening on port %u\n", uip_ntohs(snmp_udp_conn->lport));

  while(1) {
    PROCESS_YIELD();

    if(ev != tcpip_event) {
      continue;
    }

    if(!uip_newdata()) {
      continue;
    }

    LOG_DBG("receiving UDP datagram from [");
    LOG_DBG_6ADDR(&UIP_IP_BUF->srcipaddr);
    LOG_DBG_("]:%u", uip_ntohs(UIP_UDP_BUF->srcport));
    LOG_DBG_(" Length: %u\n", uip_datalen());

    /* Setup SNMP packet */
    snmp_packet.in = (uint8_t *)uip_appdata;
    snmp_packet.used = uip_datalen();

    snmp_packet.out = (uint8_t *)(uip_appdata + UIP_BUFSIZE - UIP_IPUDPH_LEN);
    snmp_packet.max = UIP_BUFSIZE - UIP_IPUDPH_LEN;

    /* Handle the request */
    if(!snmp_engine(&snmp_packet)) {
      LOG_DBG("Error while handling the request\n");
      continue;
    }

    LOG_DBG("Sending response\n");
    /* Send the response */
    uip_udp_packet_sendto(snmp_udp_conn, snmp_packet.out, snmp_packet.used, &UIP_IP_BUF->srcipaddr, UIP_UDP_BUF->srcport);
  } /* while (1) */

  PROCESS_END();
}

Project: contiki-ng

snmp_ber_decode_oid(snmp_packet_t *snmp_packet, snmp_oid_t *oid)
{
  uint8_t *buf_end, type;
  uint8_t len, j;
  div_t first;

  if(!snmp_ber_decode_type(snmp_packet, &type)) {
    return 0;
  }

  if(type != BER_DATA_TYPE_OBJECT_IDENTIFIER) {
    return 0;
  }

  if(!snmp_ber_decode_length(snmp_packet, &len)) {
    return 0;
  }

  buf_end = snmp_packet->in + len;

  if(snmp_packet->used == 0) {
    return 0;
  }

  snmp_packet->used--;
  first = div(*snmp_packet->in++, 40);

  oid->length = 0;

  oid->data[oid->length++] = (uint32_t)first.quot;
  oid->data[oid->length++] = (uint32_t)first.rem;

  while(snmp_packet->in != buf_end) {
    if(oid->length >= SNMP_MSG_OID_MAX_LEN) {
      return 0;
    }

    if(snmp_packet->used == 0) {
      return 0;
    }
    oid->data[oid->length] = (uint32_t)(*snmp_packet->in & 0x7F);
    for(j = 0; j < 4; j++) {
      snmp_packet->used--;
      if((*snmp_packet->in++ & 0x80) == 0) {
        break;
      }

      if(snmp_packet->used == 0) {
        return 0;
      }

      oid->data[oid->length] <<= 7;
      oid->data[oid->length] |= (*snmp_packet->in & 0x7F);
    }

    oid->length++;
  }

  return 1;
}

Project: contiki-ng

snmp_engine_get(snmp_header_t *header, snmp_varbind_t *varbinds)
{
  snmp_mib_resource_t *resource;
  uint8_t i;

  i = 0;
  while(varbinds[i].value_type != BER_DATA_TYPE_EOC && i < SNMP_MAX_NR_VALUES) {
    resource = snmp_mib_find(&varbinds[i].oid);
    if(!resource) {
      switch(header->version) {
      case SNMP_VERSION_1:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        /*
         * Varbinds are 1 indexed
         */
        header->error_index = i + 1;
        break;
      case SNMP_VERSION_2C:
        (&varbinds[i])->value_type = BER_DATA_TYPE_NO_SUCH_INSTANCE;
        break;
      default:
        header->error_status = SNMP_STATUS_NO_SUCH_NAME;
        header->error_index = 0;
      }
    } else {
      resource->handler(&varbinds[i], &resource->oid);
    }

    i++;
  }

  return 0;
}

Project: contiki-ng

snmp_mib_cmp_oid(snmp_oid_t *oid1, snmp_oid_t *oid2)
{
  uint8_t i;

  i = 0;
  while(i < oid1->length && i < oid2->length) {
    if(oid1->data[i] != oid2->data[i]) {
      if(oid1->data[i] < oid2->data[i]) {
        return -1;
      }
      return 1;
    }
    i++;
  }

  if(i == oid1->length &&
     i < oid2->length) {
    return -1;
  }

  if(i < oid1->length &&
     i == oid2->length) {
    return 1;
  }

  return 0;
}