/* * Walk through the offset tree, operating on the cache */ static int offset_walk(void *context, void *data) { offset_walk_t *walk = context; NAS_PORT *nas_port = data; struct radutmp utmp; radutmp_simul_t *user, myUser; /* * Seek to the entry, and possibly re-write it. */ if (lseek(walk->fd, nas_port->offset, SEEK_SET) < 0) { rad_assert(0 == 1); } if (read(walk->fd, &utmp, sizeof(utmp)) != sizeof(utmp)) { rad_assert(0 == 1); } /* * If the entry in the file is NEWER than the reboot * packet, don't re-write it, and don't delete it. */ if (utmp.time > walk->now) { return 0; } utmp.type = P_IDLE; utmp.time = walk->now; if (lseek(walk->fd, -(off_t)sizeof(utmp), SEEK_CUR) < 0) { radlog(L_ERR, "rlm_radutmp: offset_walk: failed in lseek: %s", strerror(errno)); return 1; } write(walk->fd, &utmp, sizeof(utmp)); strlcpy(myUser.login, utmp.login, sizeof(myUser.login)); user = rbtree_finddata(walk->inst->user_tree, &myUser); rad_assert(user != NULL); rad_assert(user->simul_count > 0); user->simul_count--; if (user->simul_count == 0) { rbtree_deletebydata(walk->inst->user_tree, user); } if (rbtree_deletebydata(walk->cache->nas_ports, nas_port) == 0) { radlog(L_ERR, "rlm_radutmp: Failed to delete entry from cache"); return 1; } /* * Insert the entry into the free list. */ nas_port->next = walk->cache->free_offsets; walk->cache->free_offsets = nas_port; return 0; }
/** Remove item from parent and fixup trees * * @param[in] parent to remove child from. * @param[in] child to remove. * @return * - The item removed. * - NULL if the item wasn't set. */ CONF_ITEM *cf_remove(CONF_ITEM *parent, CONF_ITEM *child) { CONF_ITEM *found; bool in_ident1, in_ident2; if (!parent || !parent->child) return NULL; if (parent != child->parent) return NULL; for (found = fr_cursor_head(&parent->cursor); found && (child != found); found = fr_cursor_next(&parent->cursor)); if (!found) return NULL; /* * Fixup the linked list */ found = fr_cursor_remove(&parent->cursor); if (!fr_cond_assert(found == child)) return NULL; in_ident1 = (rbtree_finddata(parent->ident1, child) == child); if (in_ident1 && (!rbtree_deletebydata(parent->ident1, child))) { rad_assert(0); return NULL; } in_ident2 = (rbtree_finddata(parent->ident2, child) == child); if (in_ident2 && (!rbtree_deletebydata(parent->ident2, child))) { rad_assert(0); return NULL; } /* * Look for twins */ for (found = fr_cursor_head(&parent->cursor); found && (in_ident1 || in_ident2); found = fr_cursor_next(&parent->cursor)) { if (in_ident1 && (_cf_ident1_cmp(found, child) == 0)) { rbtree_insert(parent->ident1, child); in_ident1 = false; } if (in_ident2 && (_cf_ident2_cmp(found, child) == 0)) { rbtree_insert(parent->ident2, child); in_ident2 = false; } } return child; }
void eap_handler_free(rlm_eap_t *inst, eap_handler_t *handler) { if (!handler) return; if (handler->identity) { talloc_free(handler->identity); handler->identity = NULL; } if (handler->prev_eapds) eap_ds_free(&(handler->prev_eapds)); if (handler->eap_ds) eap_ds_free(&(handler->eap_ds)); if ((handler->opaque) && (handler->free_opaque)) { handler->free_opaque(handler->opaque); handler->opaque = NULL; } handler->opaque = NULL; handler->free_opaque = NULL; if (handler->certs) pairfree(&handler->certs); PTHREAD_MUTEX_LOCK(&(inst->handler_mutex)); if (inst->handler_tree) { rbtree_deletebydata(inst->handler_tree, handler); } talloc_free(handler); PTHREAD_MUTEX_UNLOCK(&(inst->handler_mutex)); }
void client_delete(RADCLIENT_LIST *clients, RADCLIENT *client) { if (!client) return; if (!clients) clients = root_clients; if (!client->dynamic) return; rad_assert(client->ipaddr.prefix <= 128); client->dynamic = 2; /* signal to client_free */ #ifdef WITH_STATS rbtree_deletebydata(tree_num, client); #endif rbtree_deletebydata(clients->trees[client->ipaddr.prefix], client); }
/* * Find a cached entry. */ static rlm_cache_entry_t *cache_find(rlm_cache_t *inst, REQUEST *request, char const *key) { int ttl; rlm_cache_entry_t *c, my_c; VALUE_PAIR *vp; /* * Look at the expiry heap. */ c = fr_heap_peek(inst->heap); if (!c) { rad_assert(rbtree_num_elements(inst->cache) == 0); return NULL; } /* * If it's time to expire an old entry, do so now. */ if (c->expires < request->timestamp) { fr_heap_extract(inst->heap, c); rbtree_deletebydata(inst->cache, c); } /* * Is there an entry for this key? */ my_c.key = key; c = rbtree_finddata(inst->cache, &my_c); if (!c) return NULL; /* * Yes, but it expired, OR the "forget all" epoch has * passed. Delete it, and pretend it doesn't exist. */ if ((c->expires < request->timestamp) || (c->created < inst->epoch)) { delete: RDEBUG("Entry has expired, removing"); fr_heap_extract(inst->heap, c); rbtree_deletebydata(inst->cache, c); return NULL; }
static int _network_socket_free(fr_network_socket_t *s) { fr_network_t *nr = s->nr; fr_channel_data_t *cd; if (!s->dead) { if (fr_event_fd_delete(nr->el, s->fd, s->filter) < 0) { PERROR("Failed deleting socket from event loop in _network_socket_free"); rad_assert("Failed removing socket FD from event loop in _network_socket_free" == NULL); } } rbtree_deletebydata(nr->sockets, s); rbtree_deletebydata(nr->sockets_by_num, s); if (s->listen->app_io->close) { s->listen->app_io->close(s->listen->app_io_instance); } else { close(s->fd); } if (s->pending) { fr_message_done(&s->pending->m); s->pending = NULL; } /* * Clean up any queued entries. */ while ((cd = fr_heap_pop(s->waiting)) != NULL) { fr_message_done(&cd->m); } talloc_free(s->waiting); return 0; }
/** Unregister a map processor * * @param[in] proc to unregister. */ static int _map_proc_unregister(map_proc_t *proc) { map_proc_t find; map_proc_t *found; strlcpy(find.name, proc->name, sizeof(find.name)); find.length = strlen(find.name); found = rbtree_finddata(map_proc_root, &find); if (!found) return 0; rbtree_deletebydata(map_proc_root, found); return 0; }
/** Unregister an xlat function * * We can only have one function to call per name, so the passing of "func" * here is extraneous. * * @param[in] name xlat to unregister. * @param[in] func unused. * @param[in] instance data. */ void xlat_unregister(char const *name, UNUSED RAD_XLAT_FUNC func, void *instance) { xlat_t *c; xlat_t my_xlat; if (!name) return; strlcpy(my_xlat.name, name, sizeof(my_xlat.name)); my_xlat.length = strlen(my_xlat.name); c = rbtree_finddata(xlat_root, &my_xlat); if (!c) return; if (c->instance != instance) return; rbtree_deletebydata(xlat_root, c); }
/** * @brief Unregister an xlat function. * * We can only have one function to call per name, so the * passing of "func" here is extraneous. * * @param module xlat to unregister * @param func Unused * @return Void. */ void xlat_unregister(const char *module, RAD_XLAT_FUNC func, void *instance) { xlat_t *c; xlat_t my_xlat; func = func; /* -Wunused */ if (!module) return; strlcpy(my_xlat.module, module, sizeof(my_xlat.module)); my_xlat.length = strlen(my_xlat.module); c = rbtree_finddata(xlat_root, &my_xlat); if (!c) return; if (c->instance != instance) return; rbtree_deletebydata(xlat_root, c); }
static int filter_packet(RADIUS_PACKET *packet) { VALUE_PAIR *check_item; VALUE_PAIR *vp; unsigned int pass, fail; int compare; pass = fail = 0; for (vp = packet->vps; vp != NULL; vp = vp->next) { for (check_item = filter_vps; check_item != NULL; check_item = check_item->next) if ((check_item->da == vp->da) && (check_item->op != T_OP_SET)) { compare = paircmp(check_item, vp); if (compare == 1) pass++; else fail++; } } if (fail == 0 && pass != 0) { /* * Cache authentication requests, as the replies * may not match the RADIUS filter. */ if ((packet->code == PW_AUTHENTICATION_REQUEST) || (packet->code == PW_ACCOUNTING_REQUEST)) { rbtree_deletebydata(filter_tree, packet); if (!rbtree_insert(filter_tree, packet)) { oom: fprintf(stderr, "radsniff: Out of memory\n"); exit(1); } } return 0; /* matched */ } /* * Don't create erroneous matches. */ if ((packet->code == PW_AUTHENTICATION_REQUEST) || (packet->code == PW_ACCOUNTING_REQUEST)) { rbtree_deletebydata(filter_tree, packet); return 1; } /* * Else see if a previous Access-Request * matched. If so, also print out the * matching accept, reject, or challenge. */ if ((packet->code == PW_AUTHENTICATION_ACK) || (packet->code == PW_AUTHENTICATION_REJECT) || (packet->code == PW_ACCESS_CHALLENGE) || (packet->code == PW_ACCOUNTING_RESPONSE)) { RADIUS_PACKET *reply; /* * This swaps the various fields. */ reply = rad_alloc_reply(NULL, packet); if (!reply) goto oom; compare = 1; if (rbtree_finddata(filter_tree, reply)) { compare = 0; } rad_free(&reply); return compare; } return 1; }
static void got_packet(UNUSED uint8_t *args, const struct pcap_pkthdr *header, const uint8_t *data) { static int count = 1; /* Packets seen */ /* * Define pointers for packet's attributes */ const struct ip_header *ip; /* The IP header */ const struct udp_header *udp; /* The UDP header */ const uint8_t *payload; /* Packet payload */ /* * And define the size of the structures we're using */ int size_ethernet = sizeof(struct ethernet_header); int size_ip = sizeof(struct ip_header); int size_udp = sizeof(struct udp_header); /* * For FreeRADIUS */ RADIUS_PACKET *packet, *original; struct timeval elapsed; /* * Define our packet's attributes */ if ((data[0] == 2) && (data[1] == 0) && (data[2] == 0) && (data[3] == 0)) { ip = (const struct ip_header*) (data + 4); } else { ip = (const struct ip_header*)(data + size_ethernet); } udp = (const struct udp_header*)(((const uint8_t *) ip) + size_ip); payload = (const uint8_t *)(((const uint8_t *) udp) + size_udp); packet = rad_alloc(NULL, 0); if (!packet) { fprintf(stderr, "Out of memory\n"); return; } packet->src_ipaddr.af = AF_INET; packet->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr; packet->src_port = ntohs(udp->udp_sport); packet->dst_ipaddr.af = AF_INET; packet->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr; packet->dst_port = ntohs(udp->udp_dport); memcpy(&packet->data, &payload, sizeof(packet->data)); packet->data_len = header->len - (payload - data); if (!rad_packet_ok(packet, 0)) { DEBUG(log_dst, "Packet: %s\n", fr_strerror()); DEBUG(log_dst, " From %s:%d\n", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport)); DEBUG(log_dst, " To: %s:%d\n", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport)); DEBUG(log_dst, " Type: %s\n", fr_packet_codes[packet->code]); rad_free(&packet); return; } switch (packet->code) { case PW_COA_REQUEST: /* we need a 16 x 0 byte vector for decrypting encrypted VSAs */ original = nullpacket; break; case PW_AUTHENTICATION_ACK: /* look for a matching request and use it for decoding */ original = rbtree_finddata(request_tree, packet); break; case PW_AUTHENTICATION_REQUEST: /* save the request for later matching */ original = rad_alloc_reply(NULL, packet); if (original) { /* just ignore allocation failures */ rbtree_deletebydata(request_tree, original); rbtree_insert(request_tree, original); } /* fallthrough */ default: /* don't attempt to decode any encrypted attributes */ original = NULL; } /* * Decode the data without bothering to check the signatures. */ if (rad_decode(packet, original, radius_secret) != 0) { rad_free(&packet); fr_perror("decode"); return; } /* * We've seen a successfull reply to this, so delete it now */ if (original) rbtree_deletebydata(request_tree, original); if (filter_vps && filter_packet(packet)) { rad_free(&packet); DEBUG(log_dst, "Packet number %d doesn't match\n", count++); return; } if (out) { pcap_dump((void *) out, header, data); goto check_filter; } INFO(log_dst, "%s Id %d\t", fr_packet_codes[packet->code], packet->id); /* * Print the RADIUS packet */ INFO(log_dst, "%s:%d -> ", inet_ntoa(ip->ip_src), ntohs(udp->udp_sport)); INFO(log_dst, "%s:%d", inet_ntoa(ip->ip_dst), ntohs(udp->udp_dport)); DEBUG1(log_dst, "\t(%d packets)", count++); if (!start_pcap.tv_sec) { start_pcap = header->ts; } tv_sub(&header->ts, &start_pcap, &elapsed); INFO(log_dst, "\t+%u.%03u", (unsigned int) elapsed.tv_sec, (unsigned int) elapsed.tv_usec / 1000); if (fr_debug_flag > 1) { DEBUG(log_dst, "\n"); if (packet->vps) { if (do_sort) sort(packet); vp_printlist(log_dst, packet->vps); pairfree(&packet->vps); } } INFO(log_dst, "\n"); if (!to_stdout && (fr_debug_flag > 4)) { rad_print_hex(packet); } fflush(log_dst); check_filter: /* * If we're doing filtering, Access-Requests are cached in the * filter tree. */ if (!filter_vps || ((packet->code != PW_AUTHENTICATION_REQUEST) && (packet->code != PW_ACCOUNTING_REQUEST))) { rad_free(&packet); } }
static void rs_process_packet(rs_event_t *event, struct pcap_pkthdr const *header, uint8_t const *data) { static int count = 0; /* Packets seen */ rs_stats_t *stats = event->stats; decode_fail_t reason; /* * Pointers into the packet data we just received */ size_t len; uint8_t const *p = data; struct ip_header const *ip = NULL; /* The IP header */ struct ip_header6 const *ip6 = NULL; /* The IPv6 header */ struct udp_header const *udp; /* The UDP header */ uint8_t version; /* IP header version */ bool response = false; /* Was it a response code */ RADIUS_PACKET *current, *original; struct timeval elapsed; struct timeval latency; count++; if (header->caplen <= 5) { INFO("Packet too small, captured %i bytes", header->caplen); return; } /* * Loopback header */ if ((p[0] == 2) && (p[1] == 0) && (p[2] == 0) && (p[3] == 0)) { p += 4; /* * Ethernet header */ } else { p += sizeof(struct ethernet_header); } version = (p[0] & 0xf0) >> 4; switch (version) { case 4: ip = (struct ip_header const *)p; len = (0x0f & ip->ip_vhl) * 4; /* ip_hl specifies length in 32bit words */ p += len; break; case 6: ip6 = (struct ip_header6 const *)p; p += sizeof(struct ip_header6); break; default: DEBUG("IP version invalid %i", version); return; } /* * End of variable length bits, do basic check now to see if packet looks long enough */ len = (p - data) + sizeof(struct udp_header) + (sizeof(radius_packet_t) - 1); /* length value */ if (len > header->caplen) { DEBUG("Packet too small, we require at least %zu bytes, captured %i bytes", (size_t) len, header->caplen); return; } udp = (struct udp_header const *)p; p += sizeof(struct udp_header); /* * With artificial talloc memory limits there's a good chance we can * recover once some requests timeout, so make an effort to deal * with allocation failures gracefully. */ current = rad_alloc(NULL, 0); if (!current) { ERROR("Failed allocating memory to hold decoded packet"); return; } current->timestamp = header->ts; current->data_len = header->caplen - (data - p); memcpy(¤t->data, &p, sizeof(current->data)); /* * Populate IP/UDP fields from PCAP data */ if (ip) { current->src_ipaddr.af = AF_INET; current->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr; current->dst_ipaddr.af = AF_INET; current->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr; } else { current->src_ipaddr.af = AF_INET6; memcpy(¤t->src_ipaddr.ipaddr.ip6addr.s6_addr, &ip6->ip_src.s6_addr, sizeof(current->src_ipaddr.ipaddr.ip6addr.s6_addr)); current->dst_ipaddr.af = AF_INET6; memcpy(¤t->dst_ipaddr.ipaddr.ip6addr.s6_addr, &ip6->ip_dst.s6_addr, sizeof(current->dst_ipaddr.ipaddr.ip6addr.s6_addr)); } current->src_port = ntohs(udp->udp_sport); current->dst_port = ntohs(udp->udp_dport); if (!rad_packet_ok(current, 0, &reason)) { DEBUG("(%i) ** %s **", count, fr_strerror()); DEBUG("(%i) %s Id %i %s:%s:%d -> %s:%d\t+%u.%03u", count, fr_packet_codes[current->code], current->id, event->in->name, fr_inet_ntop(current->src_ipaddr.af, ¤t->src_ipaddr.ipaddr), current->src_port, fr_inet_ntop(current->dst_ipaddr.af, ¤t->dst_ipaddr.ipaddr), current->dst_port, (unsigned int) elapsed.tv_sec, ((unsigned int) elapsed.tv_usec / 1000)); rad_free(¤t); return; } switch (current->code) { case PW_CODE_COA_REQUEST: /* we need a 16 x 0 byte vector for decrypting encrypted VSAs */ original = nullpacket; break; case PW_CODE_ACCOUNTING_RESPONSE: case PW_CODE_AUTHENTICATION_REJECT: case PW_CODE_AUTHENTICATION_ACK: response = true; /* look for a matching request and use it for decoding */ original = rbtree_finddata(request_tree, current); break; case PW_CODE_ACCOUNTING_REQUEST: case PW_CODE_AUTHENTICATION_REQUEST: /* save the request for later matching */ original = rad_alloc_reply(event->conf, current); original->timestamp = header->ts; if (original) { /* just ignore allocation failures */ rbtree_deletebydata(request_tree, original); rbtree_insert(request_tree, original); } /* fallthrough */ default: /* don't attempt to decode any encrypted attributes */ original = NULL; } /* * Decode the data without bothering to check the signatures. */ if (rad_decode(current, original, event->conf->radius_secret) != 0) { rad_free(¤t); fr_perror("decode"); return; } if (filter_vps && rs_filter_packet(current)) { rad_free(¤t); DEBUG("Packet number %d doesn't match", count++); return; } if (event->out) { pcap_dump((void *) (event->out->dumper), header, data); goto check_filter; } rs_tv_sub(&header->ts, &start_pcap, &elapsed); rs_stats_update_count(&stats->gauge, current); if (original) { rs_tv_sub(¤t->timestamp, &original->timestamp, &latency); /* * Update stats for both the request and response types. * * This isn't useful for things like Access-Requests, but will be useful for * CoA and Disconnect Messages, as we get the average latency across both * response types. * * It also justifies allocating 255 instances rs_latency_t. */ rs_stats_update_latency(&stats->exchange[current->code], &latency); rs_stats_update_latency(&stats->exchange[original->code], &latency); /* * Print info about the response. */ DEBUG("(%i) %s Id %i %s:%s:%d %s %s:%d\t+%u.%03u\t+%u.%03u", count, fr_packet_codes[current->code], current->id, event->in->name, fr_inet_ntop(current->src_ipaddr.af, ¤t->src_ipaddr.ipaddr), current->src_port, response ? "<-" : "->", fr_inet_ntop(current->dst_ipaddr.af, ¤t->dst_ipaddr.ipaddr), current->dst_port, (unsigned int) elapsed.tv_sec, ((unsigned int) elapsed.tv_usec / 1000), (unsigned int) latency.tv_sec, ((unsigned int) latency.tv_usec / 1000)); /* * It's the original request */ } else { /* * Print info about the request */ DEBUG("(%i) %s Id %i %s:%s:%d %s %s:%d\t+%u.%03u", count, fr_packet_codes[current->code], current->id, event->in->name, fr_inet_ntop(current->src_ipaddr.af, ¤t->src_ipaddr.ipaddr), current->src_port, response ? "<-" : "->", fr_inet_ntop(current->dst_ipaddr.af, ¤t->dst_ipaddr.ipaddr), current->dst_port, (unsigned int) elapsed.tv_sec, ((unsigned int) elapsed.tv_usec / 1000)); } if (fr_debug_flag > 1) { if (current->vps) { if (event->conf->do_sort) { pairsort(¤t->vps, true); } vp_printlist(log_dst, current->vps); pairfree(¤t->vps); } } /* * We've seen a successful reply to this, so delete it now */ if (original) { rbtree_deletebydata(request_tree, original); } if (!event->conf->to_stdout && (fr_debug_flag > 4)) { rad_print_hex(current); } fflush(log_dst); check_filter: /* * If we're doing filtering, Access-Requests are cached in the * filter tree. */ if (!filter_vps || ((current->code != PW_CODE_AUTHENTICATION_REQUEST) && (current->code != PW_CODE_ACCOUNTING_REQUEST))) { rad_free(¤t); } }
int fr_packet_list_delete(fr_packet_list_t* pl, RADIUS_PACKET* request) { if(!pl || !request) return -1; return (rbtree_deletebydata(pl->tree,&request) == 0 ? -1 : 0); }
/* * Store logins in the RADIUS utmp file. */ static rlm_rcode_t radutmp_accounting(void *instance, REQUEST *request) { rlm_radutmp_t *inst = instance; struct radutmp utmp, u; VALUE_PAIR *vp; int status = -1; uint32_t nas_address = 0; uint32_t framed_address = 0; int protocol = -1; int fd; int port_seen = 0; char buffer[256]; char filename[1024]; char ip_name[32]; /* 255.255.255.255 */ const char *nas; NAS_PORT *nas_port, myPort; radutmp_cache_t *cache; int read_size; rbnode_t *node; /* * Which type is this. */ if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) { radlog(L_ERR, "rlm_radutmp: No Accounting-Status-Type record."); return RLM_MODULE_NOOP; } status = vp->vp_integer; /* * Look for weird reboot packets. * * ComOS (up to and including 3.5.1b20) does not send * standard PW_STATUS_ACCOUNTING_* messages. * * Check for: o no Acct-Session-Time, or time of 0 * o Acct-Session-Id of "00000000". * * We could also check for NAS-Port, that attribute * should NOT be present (but we don't right now). */ if ((status != PW_STATUS_ACCOUNTING_ON) && (status != PW_STATUS_ACCOUNTING_OFF)) do { int check1 = 0; int check2 = 0; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME, 0, TAG_ANY)) == NULL || vp->vp_date == 0) check1 = 1; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID, 0, TAG_ANY)) != NULL && vp->length == 8 && memcmp(vp->vp_strvalue, "00000000", 8) == 0) check2 = 1; if (check1 == 0 || check2 == 0) { #if 0 /* Cisco sometimes sends START records without username. */ radlog(L_ERR, "rlm_radutmp: no username in record"); return RLM_MODULE_FAIL; #else break; #endif } radlog(L_INFO, "rlm_radutmp: converting reboot records."); if (status == PW_STATUS_STOP) status = PW_STATUS_ACCOUNTING_OFF; if (status == PW_STATUS_START) status = PW_STATUS_ACCOUNTING_ON; } while(0); memset(&utmp, 0, sizeof(utmp)); utmp.porttype = 'A'; /* * First, find the interesting attributes. */ for (vp = request->packet->vps; vp; vp = vp->next) { switch (vp->da->attribute) { case PW_LOGIN_IP_HOST: case PW_FRAMED_IP_ADDRESS: framed_address = vp->vp_ipaddr; utmp.framed_address = vp->vp_ipaddr; break; case PW_FRAMED_PROTOCOL: protocol = vp->vp_integer; break; case PW_NAS_IP_ADDRESS: nas_address = vp->vp_ipaddr; utmp.nas_address = vp->vp_ipaddr; break; case PW_NAS_PORT: utmp.nas_port = vp->vp_integer; port_seen = 1; break; case PW_ACCT_DELAY_TIME: utmp.delay = vp->vp_integer; break; case PW_ACCT_SESSION_ID: /* * If it's too big, only use the * last bit. */ if (vp->length > sizeof(utmp.session_id)) { int length = vp->length - sizeof(utmp.session_id); /* * Ascend is br0ken - it * adds a \0 to the end * of any string. * Compensate. */ if (vp->vp_strvalue[vp->length - 1] == 0) { length--; } memcpy(utmp.session_id, vp->vp_strvalue + length, sizeof(utmp.session_id)); } else { memset(utmp.session_id, 0, sizeof(utmp.session_id)); memcpy(utmp.session_id, vp->vp_strvalue, vp->length); } break; case PW_NAS_PORT_TYPE: if (vp->vp_integer <= 4) utmp.porttype = porttypes[vp->vp_integer]; break; case PW_CALLING_STATION_ID: if(inst->callerid_ok) strlcpy(utmp.caller_id, (char *)vp->vp_strvalue, sizeof(utmp.caller_id)); break; } } /* * If we didn't find out the NAS address, use the * originator's IP address. */ if (nas_address == 0) { nas_address = request->packet->src_ipaddr; utmp.nas_address = nas_address; nas = request->client->shortname; } else if (request->packet->src_ipaddr.ipaddr.ip4addr.s_addr == nas_address) { /* might be a client, might not be. */ nas = request->client->shortname; } else { /* * The NAS isn't a client, it's behind * a proxy server. In that case, just * get the IP address. */ nas = ip_ntoa(ip_name, nas_address); } /* * Set the protocol field. */ if (protocol == PW_PPP) utmp.proto = 'P'; else if (protocol == PW_SLIP) utmp.proto = 'S'; else utmp.proto = 'T'; utmp.time = request->timestamp - utmp.delay; /* * Get the utmp filename, via xlat. */ radius_xlat(filename, sizeof(filename), inst->filename, request, NULL); /* * Future: look up filename in filename tree, to get * radutmp_cache_t pointer */ cache = &inst->cache; /* * For now, double-check the filename, to be sure it isn't * changing. */ if (!cache->filename) { cache->filename = strdup(filename); rad_assert(cache->filename != NULL); } else if (strcmp(cache->filename, filename) != 0) { radlog(L_ERR, "rlm_radutmp: We do not support dynamically named files."); return RLM_MODULE_FAIL; } /* * If the lookup failed, create a new one, and add it * to the filename tree, and cache the file, as below. */ /* * For aging, in the future. */ cache->last_used = request->timestamp; /* * If we haven't already read the file, then read the * entire file, in order to cache its entries. */ if (!cache->cached_file) { cache_file(inst, cache); } /* * See if this was a reboot. * * Hmm... we may not want to zap all of the users when * the NAS comes up, because of issues with receiving * UDP packets out of order. */ if (status == PW_STATUS_ACCOUNTING_ON && nas_address) { radlog(L_INFO, "rlm_radutmp: NAS %s restarted (Accounting-On packet seen)", nas); if (!radutmp_zap(inst, cache, nas_address, utmp.time)) { rad_assert(0 == 1); } return RLM_MODULE_OK; } if (status == PW_STATUS_ACCOUNTING_OFF && nas_address) { radlog(L_INFO, "rlm_radutmp: NAS %s rebooted (Accounting-Off packet seen)", nas); if (!radutmp_zap(inst, cache, nas_address, utmp.time)) { rad_assert(0 == 1); } return RLM_MODULE_OK; } /* * If we don't know this type of entry, then pretend we * succeeded. */ if (status != PW_STATUS_START && status != PW_STATUS_STOP && status != PW_STATUS_ALIVE) { radlog(L_ERR, "rlm_radutmp: NAS %s port %u unknown packet type %d, ignoring it.", nas, utmp.nas_port, status); return RLM_MODULE_NOOP; } /* * Perhaps we don't want to store this record into * radutmp. We skip records: * * - without a NAS-Port (telnet / tcp access) * - with the username "!root" (console admin login) */ if (!port_seen) { DEBUG2(" rlm_radutmp: No NAS-Port in the packet. Cannot do anything."); DEBUG2(" rlm_radumtp: WARNING: checkrad will probably not work!"); return RLM_MODULE_NOOP; } /* * Translate the User-Name attribute, or whatever else * they told us to use. */ *buffer = '\0'; radius_xlat(buffer, sizeof(buffer), inst->username, request, NULL); /* * Don't log certain things... */ if (strcmp(buffer, "!root") == 0) { DEBUG2(" rlm_radutmp: Not recording administrative user"); return RLM_MODULE_NOOP; } strlcpy(utmp.login, buffer, RUT_NAMESIZE); /* * First, try to open the file. If it doesn't exist, * nuke the existing caches, and try to create it. * * FIXME: Create any intermediate directories, as * appropriate. See rlm_detail. */ fd = open(cache->filename, O_RDWR, inst->permission); if (fd < 0) { if (errno == ENOENT) { DEBUG2(" rlm_radutmp: File %s doesn't exist, creating it.", cache->filename); if (!cache_reset(inst, cache)) return RLM_MODULE_FAIL; /* * Try to create the file. */ fd = open(cache->filename, O_RDWR | O_CREAT, inst->permission); } } else { /* exists, but may be empty */ struct stat buf; /* * If the file is empty, reset the cache. */ if ((stat(cache->filename, &buf) == 0) && (buf.st_size == 0) && (!cache_reset(inst, cache))) { return RLM_MODULE_FAIL; } DEBUG2(" rlm_radutmp: File %s was truncated. Resetting cache.", cache->filename); } /* * Error from creation, or error other than ENOENT: die. */ if (fd < 0) { radlog(L_ERR, "rlm_radutmp: Error accessing file %s: %s", cache->filename, strerror(errno)); return RLM_MODULE_FAIL; } /* * OK. Now that we've prepared everything we want to do, * let's see if we've cached the entry. */ myPort.nas_address = utmp.nas_address; myPort.nas_port = utmp.nas_port; pthread_mutex_lock(&cache->mutex); node = rbtree_find(cache->nas_ports, &myPort); pthread_mutex_unlock(&cache->mutex); if (node) { nas_port = rbtree_node2data(cache->nas_ports, node); #if 0 /* * stat the file, and get excited if it's been * truncated. * * i.e wipe out the cache, and re-read the file. */ /* * Now find the new entry. */ pthread_mutex_lock(&cache->mutex); node = rbtree_find(cache->nas_ports, &myPort); pthread_mutex_unlock(&cache->mutex); #endif } if (!node) { radutmp_simul_t *user; /* * Not found in the cache, and we're trying to * delete an existing record: ignore it. */ if (status == PW_STATUS_STOP) { DEBUG2(" rlm_radumtp: Logout entry for NAS %s port %u with no Login: ignoring it.", nas, utmp.nas_port); return RLM_MODULE_NOOP; } pthread_mutex_lock(&cache->mutex); /* * It's a START or ALIVE. Try to find a free * offset where we can store the new entry, or * create one, if one doesn't already exist. */ if (!cache->free_offsets) { cache->free_offsets = rad_malloc(sizeof(NAS_PORT)); memset(cache->free_offsets, 0, sizeof(*(cache->free_offsets))); cache->free_offsets->offset = cache->max_offset; cache->max_offset += sizeof(u); } /* * Grab the offset, and put it into the various * caches. */ nas_port = cache->free_offsets; cache->free_offsets = nas_port->next; nas_port->nas_address = nas_address; nas_port->nas_port = utmp.nas_port; if (!rbtree_insert(cache->nas_ports, nas_port)) { rad_assert(0 == 1); } /* * Allocate new entry, and add it * to the tree. */ user = rad_malloc(sizeof(user)); strlcpy(user->login, utmp.login, sizeof(user->login)); user->simul_count = 1; if (!rbtree_insert(inst->user_tree, user)) { rad_assert(0 == 1); } pthread_mutex_unlock(&cache->mutex); } /* * Entry was found, or newly created in the cache. * Seek to the place in the file. */ lseek(fd, nas_port->offset, SEEK_SET); /* * Lock the utmp file, prefer lockf() over flock(). */ rad_lockfd(fd, LOCK_LEN); /* * If it WAS found in the cache, double-check it against * what is in the file. */ if (node) { /* * If we didn't read anything, then this entry * doesn't exist. * * Similarly, if the entry in the file doesn't * match what we recall, then nuke the cache * entry. */ read_size = read(fd, &u, sizeof(u)); if ((read_size < 0) || ((read_size > 0) && (read_size != sizeof(u)))) { /* * Bad read, or bad record. */ radlog(L_ERR, "rlm_radutmp: Badly formed file %s", cache->filename); close(fd); return RLM_MODULE_FAIL; } rad_assert(read_size != 0); /* * We've read a record, go poke at it. */ if (read_size > 0) { /* * If these aren't true, then * * a) we have cached a "logout" entry, * which we don't do. * * b) we have cached the wrong NAS address * * c) we have cached the wrong NAS port. */ rad_assert(u.type == P_LOGIN); rad_assert(u.nas_address == utmp.nas_address); rad_assert(u.nas_port == utmp.nas_port); /* * An update for the same session. */ if (strncmp(utmp.session_id, u.session_id, sizeof(u.session_id)) == 0) { /* * It's a duplicate start, so we * don't bother writing it. */ if (status == PW_STATUS_START) { DEBUG2(" rlm_radutmp: Login entry for NAS %s port %u duplicate, ignoring it.", nas, u.nas_port); close(fd); return RLM_MODULE_OK; /* * ALIVE for this session, keep the * original login time. */ } else if (status == PW_STATUS_ALIVE) { utmp.time = u.time; /* * Stop: delete it from our cache. */ } else if (status == PW_STATUS_STOP) { radutmp_simul_t *user, myUser; pthread_mutex_lock(&cache->mutex); rbtree_deletebydata(cache->nas_ports, nas_port); strlcpy(myUser.login, u.login, sizeof(myUser.login)); user = rbtree_finddata(inst->user_tree, &myUser); rad_assert(user != NULL); rad_assert(user->simul_count > 0); user->simul_count--; if (user->simul_count == 0) { rbtree_deletebydata(inst->user_tree, user); } pthread_mutex_unlock(&cache->mutex); } else { /* * We don't know how to * handle this. */ rad_assert(0 == 1); } } else { /* session ID doesn't match */ /* * STOP for the right NAS & port, * but the Acct-Session-Id is * different. This means that * we missed the original "stop", * and a new "start". */ if (status == PW_STATUS_STOP) { radlog(L_ERR, "rlm_radutmp: Logout entry for NAS %s port %u has old Acct-Session-ID, ignoring it.", nas, u.nas_port); close(fd); return RLM_MODULE_OK; } } /* checked session ID's */ } /* else we haven't read anything from the file. */ } /* else the entry wasn't cached, but could have been inserted */ /* * Hmm... we may have received a start or alive packet * AFTER a stop or nas-down, in that case, we want to * discard the new packet. However, the original code * could over-write an idle record with a new login * record for another NAS && port, so we won't worry * about this case too much. */ /* * Seek to where the entry is, and write it blindly. */ lseek(fd, nas_port->offset, SEEK_SET); /* FIXME: err */ if (status != PW_STATUS_STOP) { utmp.type = P_LOGIN; rad_assert(nas_port != NULL); /* it WAS cached */ } else { /* FIXME: maybe assert that the entry was deleted... */ memcpy(&utmp, &u, sizeof(utmp)); utmp.type = P_IDLE; } write(fd, &utmp, sizeof(utmp)); /* FIXME: err */ close(fd); /* and implicitly release the locks */ return RLM_MODULE_OK; }
static void rs_packet_process(uint64_t count, rs_event_t *event, struct pcap_pkthdr const *header, uint8_t const *data) { rs_stats_t *stats = event->stats; struct timeval elapsed; struct timeval latency; /* * Pointers into the packet data we just received */ size_t len; uint8_t const *p = data; struct ip_header const *ip = NULL; /* The IP header */ struct ip_header6 const *ip6 = NULL; /* The IPv6 header */ struct udp_header const *udp; /* The UDP header */ uint8_t version; /* IP header version */ bool response; /* Was it a response code */ decode_fail_t reason; /* Why we failed decoding the packet */ static uint64_t captured = 0; RADIUS_PACKET *current; /* Current packet were processing */ rs_request_t *original; if (!start_pcap.tv_sec) { start_pcap = header->ts; } if (header->caplen <= 5) { INFO("Packet too small, captured %i bytes", header->caplen); return; } /* * Loopback header */ if ((p[0] == 2) && (p[1] == 0) && (p[2] == 0) && (p[3] == 0)) { p += 4; /* * Ethernet header */ } else { p += sizeof(struct ethernet_header); } version = (p[0] & 0xf0) >> 4; switch (version) { case 4: ip = (struct ip_header const *)p; len = (0x0f & ip->ip_vhl) * 4; /* ip_hl specifies length in 32bit words */ p += len; break; case 6: ip6 = (struct ip_header6 const *)p; p += sizeof(struct ip_header6); break; default: DEBUG("IP version invalid %i", version); return; } /* * End of variable length bits, do basic check now to see if packet looks long enough */ len = (p - data) + sizeof(struct udp_header) + (sizeof(radius_packet_t) - 1); /* length value */ if (len > header->caplen) { DEBUG("Packet too small, we require at least %zu bytes, captured %i bytes", (size_t) len, header->caplen); return; } udp = (struct udp_header const *)p; p += sizeof(struct udp_header); /* * With artificial talloc memory limits there's a good chance we can * recover once some requests timeout, so make an effort to deal * with allocation failures gracefully. */ current = rad_alloc(conf, 0); if (!current) { ERROR("Failed allocating memory to hold decoded packet"); rs_tv_add_ms(&header->ts, conf->stats.timeout, &stats->quiet); return; } current->timestamp = header->ts; current->data_len = header->caplen - (p - data); memcpy(¤t->data, &p, sizeof(current->data)); /* * Populate IP/UDP fields from PCAP data */ if (ip) { current->src_ipaddr.af = AF_INET; current->src_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_src.s_addr; current->dst_ipaddr.af = AF_INET; current->dst_ipaddr.ipaddr.ip4addr.s_addr = ip->ip_dst.s_addr; } else { current->src_ipaddr.af = AF_INET6; memcpy(¤t->src_ipaddr.ipaddr.ip6addr.s6_addr, &ip6->ip_src.s6_addr, sizeof(current->src_ipaddr.ipaddr.ip6addr.s6_addr)); current->dst_ipaddr.af = AF_INET6; memcpy(¤t->dst_ipaddr.ipaddr.ip6addr.s6_addr, &ip6->ip_dst.s6_addr, sizeof(current->dst_ipaddr.ipaddr.ip6addr.s6_addr)); } current->src_port = ntohs(udp->udp_sport); current->dst_port = ntohs(udp->udp_dport); if (!rad_packet_ok(current, 0, &reason)) { RIDEBUG("(%" PRIu64 ") ** %s **", count, fr_strerror()); RIDEBUG("(%" PRIu64 ") %s Id %i %s:%s:%d -> %s:%d\t+%u.%03u", count, fr_packet_codes[current->code], current->id, event->in->name, fr_inet_ntop(current->src_ipaddr.af, ¤t->src_ipaddr.ipaddr), current->src_port, fr_inet_ntop(current->dst_ipaddr.af, ¤t->dst_ipaddr.ipaddr), current->dst_port, (unsigned int) elapsed.tv_sec, ((unsigned int) elapsed.tv_usec / 1000)); rad_free(¤t); return; } switch (current->code) { case PW_CODE_ACCOUNTING_RESPONSE: case PW_CODE_AUTHENTICATION_REJECT: case PW_CODE_AUTHENTICATION_ACK: case PW_CODE_COA_NAK: case PW_CODE_COA_ACK: case PW_CODE_DISCONNECT_NAK: case PW_CODE_DISCONNECT_ACK: case PW_CODE_STATUS_CLIENT: { rs_request_t search; struct timeval when; rs_tv_add_ms(&header->ts, conf->stats.timeout, &when); /* look for a matching request and use it for decoding */ search.packet = current; original = rbtree_finddata(request_tree, &search); /* * Only decode attributes if we want to print them or filter on them * rad_packet_ok does checks to verify the packet is actually valid. */ if (filter_vps || conf->print_packet) { if (rad_decode(current, original ? original->packet : NULL, conf->radius_secret) != 0) { rad_free(¤t); fr_perror("decode"); return; } } /* * Check if we've managed to link it to a request */ if (original) { /* * Is this a retransmit? */ if (!original->linked) { original->stats_rsp = &stats->exchange[current->code]; } else { RDEBUG("(%" PRIu64 ") ** RETRANSMISSION **", count); original->rt_rsp++; rad_free(&original->linked); fr_event_delete(event->list, &original->event); } original->linked = talloc_steal(original, current); /* * Some RADIUS servers and proxy servers may not cache * Accounting-Responses (and possibly other code), * and may immediately re-use a RADIUS packet src * port/id combination on receipt of a response. */ if (conf->dequeue[current->code]) { fr_event_delete(event->list, &original->event); rbtree_deletebydata(request_tree, original); } else { if (!fr_event_insert(event->list, rs_packet_cleanup, original, &when, &original->event)) { ERROR("Failed inserting new event"); /* * Delete the original request/event, it's no longer valid * for statistics. */ original->forced_cleanup = true; fr_event_delete(event->list, &original->event); rbtree_deletebydata(request_tree, original); return; } } /* * No request seen, or request was dropped by attribute filter */ } else { /* * If filter_vps are set assume the original request was dropped, * the alternative is maintaining another 'filter', but that adds * complexity, reduces max capture rate, and is generally a PITA. */ if (filter_vps) { rad_free(¤t); RDEBUG2("(%" PRIu64 ") Dropped by attribute filter", count); return; } RDEBUG("(%" PRIu64 ") ** UNLINKED **", count); stats->exchange[current->code].interval.unlinked_total++; } response = true; } break; case PW_CODE_ACCOUNTING_REQUEST: case PW_CODE_AUTHENTICATION_REQUEST: case PW_CODE_COA_REQUEST: case PW_CODE_DISCONNECT_REQUEST: case PW_CODE_STATUS_SERVER: { rs_request_t search; struct timeval when; /* * Only decode attributes if we want to print them or filter on them * rad_packet_ok does checks to verify the packet is actually valid. */ if (filter_vps || conf->print_packet) { if (rad_decode(current, NULL, conf->radius_secret) != 0) { rad_free(¤t); fr_perror("decode"); return; } } /* * Now verify the packet passes the attribute filter */ if (filter_vps && !pairvalidate_relaxed(filter_vps, current->vps)) { rad_free(¤t); RDEBUG2("(%" PRIu64 ") Dropped by attribute filter", count); return; } /* * save the request for later matching */ search.packet = rad_alloc_reply(conf, current); if (!search.packet) { ERROR("Failed allocating memory to hold expected reply"); rs_tv_add_ms(&header->ts, conf->stats.timeout, &stats->quiet); rad_free(¤t); return; } search.packet->code = current->code; rs_tv_add_ms(&header->ts, conf->stats.timeout, &when); original = rbtree_finddata(request_tree, &search); /* * Upstream device re-used src/dst ip/port id without waiting * for the timeout period to expire, or a response. */ if (original && memcmp(original->packet->vector, current->vector, sizeof(original->packet->vector) != 0)) { RDEBUG2("(%" PRIu64 ") ** PREMATURE ID RE-USE **", count); stats->exchange[current->code].interval.reused_total++; original->forced_cleanup = true; fr_event_delete(event->list, &original->event); rbtree_deletebydata(request_tree, original); original = NULL; } if (original) { RDEBUG("(%" PRIu64 ") ** RETRANSMISSION **", count); original->rt_req++; rad_free(&original->packet); original->packet = talloc_steal(original, search.packet); /* We may of seen the response, but it may of been lost upstream */ rad_free(&original->linked); fr_event_delete(event->list, &original->event); } else { original = talloc_zero(conf, rs_request_t); talloc_set_destructor(original, _request_free); original->id = count; original->in = event->in; original->stats_req = &stats->exchange[current->code]; original->packet = talloc_steal(original, search.packet); rbtree_insert(request_tree, original); } /* update the timestamp in either case */ original->packet->timestamp = header->ts; if (!fr_event_insert(event->list, rs_packet_cleanup, original, &when, &original->event)) { ERROR("Failed inserting new event"); rbtree_deletebydata(request_tree, original); return; } response = false; } break; default: RDEBUG("** Unsupported code %i **", current->code); rad_free(¤t); return; } if (event->out) { pcap_dump((void *) (event->out->dumper), header, data); } rs_tv_sub(&header->ts, &start_pcap, &elapsed); /* * Increase received count */ stats->exchange[current->code].interval.received_total++; /* * It's a linked response */ if (original && original->linked) { rs_tv_sub(¤t->timestamp, &original->packet->timestamp, &latency); /* * Update stats for both the request and response types. * * This isn't useful for things like Access-Requests, but will be useful for * CoA and Disconnect Messages, as we get the average latency across both * response types. * * It also justifies allocating 255 instances rs_latency_t. */ rs_stats_update_latency(&stats->exchange[current->code], &latency); rs_stats_update_latency(&stats->exchange[original->packet->code], &latency); /* * Print info about the request/response. */ RIDEBUG("(%" PRIu64 ") %s Id %i %s:%s:%d %s %s:%d\t+%u.%03u\t+%u.%03u", count, fr_packet_codes[current->code], current->id, event->in->name, fr_inet_ntop(current->src_ipaddr.af, ¤t->src_ipaddr.ipaddr), current->src_port, response ? "<-" : "->", fr_inet_ntop(current->dst_ipaddr.af, ¤t->dst_ipaddr.ipaddr), current->dst_port, (unsigned int) elapsed.tv_sec, ((unsigned int) elapsed.tv_usec / 1000), (unsigned int) latency.tv_sec, ((unsigned int) latency.tv_usec / 1000)); /* * It's the original request */ } else { /* * Print info about the request */ RIDEBUG("(%" PRIu64 ") %s Id %i %s:%s:%d %s %s:%d\t+%u.%03u", count, fr_packet_codes[current->code], current->id, event->in->name, fr_inet_ntop(current->src_ipaddr.af, ¤t->src_ipaddr.ipaddr), current->src_port, response ? "<-" : "->", fr_inet_ntop(current->dst_ipaddr.af, ¤t->dst_ipaddr.ipaddr), current->dst_port, (unsigned int) elapsed.tv_sec, ((unsigned int) elapsed.tv_usec / 1000)); } if (conf->print_packet && (fr_debug_flag > 1) && current->vps) { pairsort(¤t->vps, true); vp_printlist(log_dst, current->vps); pairfree(¤t->vps); } if (!conf->to_stdout && (fr_debug_flag > 4)) { rad_print_hex(current); } fflush(log_dst); /* * If it's a request, a duplicate of the packet will of already been stored. * If it's a unlinked response, we need to free it explicitly, as it will * not be done by the event queue. */ if (!response || !original) { rad_free(¤t); } captured++; /* * We've hit our capture limit, break out of the event loop */ if ((conf->limit > 0) && (captured >= conf->limit)) { INFO("Captured %" PRIu64 " packets, exiting...", captured); fr_event_loop_exit(events, 1); } }
static void rs_packet_cleanup(void *ctx) { rs_request_t *request = talloc_get_type_abort(ctx, rs_request_t); RADIUS_PACKET *packet = request->packet; assert(request->stats_req); assert(!request->rt_rsp || request->stats_rsp); assert(packet); /* * Don't pollute stats or print spurious messages as radsniff closes. */ if (cleanup) { goto skip; } /* * Were at packet cleanup time which is when the packet was received + timeout * and it's not been linked with a forwarded packet or a response. * * We now count it as lost. */ if (!request->linked && !request->forced_cleanup) { request->stats_req->interval.lost_total++; RDEBUG("(%i) ** LOST **", request->id); RIDEBUG("(%i) %s Id %i %s:%s:%d -> %s:%d", request->id, fr_packet_codes[packet->code], packet->id, request->in->name, fr_inet_ntop(packet->dst_ipaddr.af, &packet->dst_ipaddr.ipaddr), packet->dst_port, fr_inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr), packet->src_port); } /* * Now the request is done, we can update the retransmission stats */ if (request->rt_req > RS_RETRANSMIT_MAX) { request->stats_req->interval.rt_total[RS_RETRANSMIT_MAX]++; } else { request->stats_req->interval.rt_total[request->rt_req]++; } if (request->rt_rsp) { if (request->rt_rsp > RS_RETRANSMIT_MAX) { request->stats_rsp->interval.rt_total[RS_RETRANSMIT_MAX]++; } else { request->stats_rsp->interval.rt_total[request->rt_rsp]++; } } skip: /* * If were attempting to cleanup the request, and it's no longer in the request_tree * something has gone very badly wrong. */ assert(rbtree_deletebydata(request_tree, request)); if (fr_event_list_num_elements(events) == 0) { fr_event_loop_exit(events, 1); } }