static int ipproto = IPPROTO_UDP;
-static bool do_coa = false;
-static int coafd;
-static uint16_t coa_port = FR_COA_UDP_PORT;
-static fr_rb_tree_t *coa_tree = NULL;
-
static fr_packet_list_t *packet_list = NULL;
static fr_dlist_head_t rc_request_list;
static fr_dict_attr_t const *attr_user_name;
static fr_dict_attr_t const *attr_user_password;
-static fr_dict_attr_t const *attr_radclient_coa_filename;
-static fr_dict_attr_t const *attr_radclient_coa_filter;
-
-static fr_dict_attr_t const *attr_coa_filter = NULL;
-
extern fr_dict_attr_autoload_t radclient_dict_attr[];
fr_dict_attr_autoload_t radclient_dict_attr[] = {
{ .out = &attr_cleartext_password, .name = "Password.Cleartext", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
{ .out = &attr_radclient_test_name, .name = "Radclient-Test-Name", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
{ .out = &attr_request_authenticator, .name = "Request-Authenticator", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
- { .out = &attr_radclient_coa_filename, .name = "Radclient-CoA-Filename", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
- { .out = &attr_radclient_coa_filter, .name = "Radclient-CoA-Filter", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
-
{ .out = &attr_chap_password, .name = "CHAP-Password", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
{ .out = &attr_chap_challenge, .name = "CHAP-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
{ .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius },
fprintf(stderr, " <command> One of auth, acct, status, coa, disconnect or auto.\n");
fprintf(stderr, " -4 Use IPv4 address of server\n");
fprintf(stderr, " -6 Use IPv6 address of server.\n");
- fprintf(stderr, " -A <attribute> Use named 'attribute' to match CoA requests to packets. Default is User-Name\n");
fprintf(stderr, " -C [<client_ip>:]<client_port> Client source port and source IP address. Port values may be 1..65535\n");
fprintf(stderr, " -c <count> Send each packet 'count' times.\n");
fprintf(stderr, " -d <raddb> Set user dictionary directory (defaults to " RADDBDIR ").\n");
fprintf(stderr, " -h Print usage help information.\n");
fprintf(stderr, " -i <id> Set request id to 'id'. Values may be 0..255\n");
fprintf(stderr, " -n <num> Send N requests/s\n");
- fprintf(stderr, " -o <port> Set CoA listening port (defaults to 3799)\n");
fprintf(stderr, " -p <num> Send 'num' packets from a file in parallel.\n");
fprintf(stderr, " -P <proto> Use proto (tcp or udp) for transport.\n");
fprintf(stderr, " -r <retries> If timeout, retry sending the packet 'retries' times.\n");
{
fr_dlist_remove(&rc_request_list, request);
- if (do_coa) (void) fr_rb_delete_by_inline_node(coa_tree, &request->node);
-
return 0;
}
return false;
}
-/*
- * Read one CoA reply amd possibly filter
- */
-static int coa_init(rc_request_t *parent, FILE *coa_reply, char const *reply_filename, bool *coa_reply_done, FILE *coa_filter, char const *filter_filename, bool *coa_filter_done)
-{
- rc_request_t *request;
- fr_pair_t *vp;
-
- /*
- * Allocate it.
- */
- request = talloc_zero(parent, rc_request_t);
- if (!request) {
- ERROR("Out of memory");
- return -1;
- }
-
- request->reply = fr_radius_packet_alloc(request, false);
- if (!request->reply) {
-
- ERROR("Out of memory");
- error:
- talloc_free(request);
- return -1;
- }
-
- /*
- * Don't initialize src/dst IP/port, or anything else. That will be read from the network.
- */
- fr_pair_list_init(&request->filter);
- fr_pair_list_init(&request->request_pairs);
- fr_pair_list_init(&request->reply_pairs);
-
- /*
- * Read the reply VP's.
- */
- if (fr_pair_list_afrom_file(request, dict_radius,
- &request->reply_pairs, coa_reply, coa_reply_done) < 0) {
- REDEBUG("Error parsing \"%s\"", reply_filename);
- goto error;
- }
-
- /*
- * The reply can be empty. In which case we just send an empty ACK.
- */
- vp = fr_pair_find_by_da(&request->reply_pairs, NULL, attr_packet_type);
- if (vp) request->reply->code = vp->vp_uint32;
-
- /*
- * Read in filter VP's.
- */
- if (coa_filter) {
- if (fr_pair_list_afrom_file(request, dict_radius,
- &request->filter, coa_filter, coa_filter_done) < 0) {
- REDEBUG("Error parsing \"%s\"", filter_filename);
- goto error;
- }
-
- if (*coa_filter_done && !*coa_reply_done) {
- REDEBUG("Differing number of replies/filters in %s:%s "
- "(too many replies))", reply_filename, filter_filename);
- goto error;
- }
-
- if (!*coa_filter_done && *coa_reply_done) {
- REDEBUG("Differing number of replies/filters in %s:%s "
- "(too many filters))", reply_filename, filter_filename);
- goto error;
- }
-
- /*
- * This allows efficient list comparisons later
- */
- fr_pair_list_sort(&request->filter, fr_pair_cmp_by_da);
- }
-
- request->name = parent->name;
-
- /*
- * Automatically set the response code from the request code
- * (if one wasn't already set).
- */
- if (request->filter_code == FR_RADIUS_CODE_UNDEFINED) {
- request->filter_code = FR_RADIUS_CODE_COA_REQUEST;
- }
-
- parent->coa = request;
-
- return 0;
-}
-
/*
* Initialize a radclient data structure and add it to
* the global linked list.
bool packets_done = false;
uint64_t num = 0;
- FILE *coa_reply = NULL;
- FILE *coa_filter = NULL;
- bool coa_reply_done = false;
- bool coa_filter_done = false;
-
assert(files->packets != NULL);
/*
goto error;
}
}
-
- if (files->coa_reply) {
- coa_reply = fopen(files->coa_reply, "r");
- if (!coa_reply) {
- ERROR("Error opening %s: %s", files->coa_reply, fr_syserror(errno));
- goto error;
- }
- }
-
- if (files->coa_filter) {
- coa_filter = fopen(files->coa_filter, "r");
- if (!coa_filter) {
- ERROR("Error opening %s: %s", files->coa_filter, fr_syserror(errno));
- goto error;
- }
- }
} else {
packets = stdin;
}
* Loop until the file is done.
*/
do {
- char const *coa_reply_filename = NULL;
- char const *coa_filter_filename = NULL;
-
/*
* Allocate it.
*/
} else if (vp->da == attr_radclient_test_name) {
request->name = vp->vp_strvalue;
- } else if (vp->da == attr_radclient_coa_filename) {
- coa_reply_filename = vp->vp_strvalue;
-
- } else if (vp->da == attr_radclient_coa_filter) {
- coa_filter_filename = vp->vp_strvalue;
}
} /* loop over the VP's we read in */
}
}
- /*
- * Read in the CoA filename and filter.
- */
- if (coa_reply_filename) {
- if (coa_reply) {
- RDEBUG("Cannot specify CoA file on both the command line and via Radclient-CoA-Filename");
- goto error;
- }
-
- coa_reply = fopen(coa_reply_filename, "r");
- if (!coa_reply) {
- ERROR("Error opening %s: %s", coa_reply_filename, fr_syserror(errno));
- goto error;
- }
-
- if (coa_filter_filename) {
- coa_filter = fopen(coa_filter_filename, "r");
- if (!coa_filter) {
- ERROR("Error opening %s: %s", coa_filter_filename, fr_syserror(errno));
- goto error;
- }
- } else {
- coa_filter = NULL;
- }
-
- if (coa_init(request, coa_reply, coa_reply_filename, &coa_reply_done,
- coa_filter, coa_filter_filename, &coa_filter_done) < 0) {
- goto error;
- }
-
- fclose(coa_reply);
- coa_reply = NULL;
- if (coa_filter) {
- fclose(coa_filter);
- coa_filter = NULL;
- }
- do_coa = true;
-
- } else if (coa_reply) {
- if (coa_init(request, coa_reply, coa_reply_filename, &coa_reply_done,
- coa_filter, coa_filter_filename, &coa_filter_done) < 0) {
- goto error;
- }
-
- if (coa_reply_done != packets_done) {
- REDEBUG("Differing number of packets in input file and coa_reply in %s:%s ",
- files->packets, files->coa_reply);
- goto error;
-
- }
- }
-
/*
* Add it to the tail of the list.
*/
if (packets != stdin) fclose(packets);
if (filters) fclose(filters);
- if (coa_reply) fclose(coa_reply);
- if (coa_filter) fclose(coa_filter);
/*
* And we're done.
if (packets != stdin) fclose(packets);
if (filters) fclose(filters);
- if (coa_reply) fclose(coa_reply);
- if (coa_filter) fclose(coa_filter);
return -1;
}
}
-static int8_t request_cmp(void const *one, void const *two)
-{
- rc_request_t const *a = one, *b = two;
- fr_pair_t *vp1, *vp2;
-
- vp1 = fr_pair_find_by_da(&a->request_pairs, NULL, attr_coa_filter);
- vp2 = fr_pair_find_by_da(&b->request_pairs, NULL, attr_coa_filter);
-
- if (!vp1) return -1;
- if (!vp2) return +1;
-
- return fr_value_box_cmp(&vp1->data, &vp2->data);
-}
-
-
/*
* Deallocate packet ID, etc.
*/
return 0;
}
-/*
- * Receive a CoA packet, maybe.
- */
-static int recv_coa_packet(fr_time_delta_t wait_time)
-{
- fd_set set;
- fr_time_delta_t our_wait_time;
- rc_request_t *request, *parent;
- fr_radius_packet_t *packet;
-
-#ifdef STATIC_ANALYZER
- if (!secret) fr_exit_now(1);
-#endif
-
- /* And wait for reply, timing out as necessary */
- FD_ZERO(&set);
- FD_SET(coafd, &set);
-
- our_wait_time = !fr_time_delta_ispos(wait_time) ? fr_time_delta_from_sec(0) : wait_time;
-
- /*
- * No packet was received.
- */
- if (select(coafd + 1, &set, NULL, NULL, &fr_time_delta_to_timeval(our_wait_time)) <= 0) return 0;
-
- /*
- * Read a packet from a network.
- */
- packet = fr_radius_packet_recv(NULL, coafd, 0, 200, false);
- if (!packet) {
- DEBUG("Failed reading CoA packet");
- return 0;
- }
-
- /*
- * Fails the signature validation: not a real reply.
- */
- if (fr_radius_packet_verify(packet, NULL, secret) < 0) {
- DEBUG("CoA verification failed");
- return 0;
- }
-
- /*
- * Find a Access-Request which has the same User-Name / etc. as this CoA packet.
- */
- parent = fr_rb_find(coa_tree, &(rc_request_t) {
- .packet = packet,
- });
- if (!parent) {
- DEBUG("No matching request packet");
- return 0;
- }
- assert(parent->coa);
-
- request = parent->coa;
- request->packet = talloc_steal(request, packet);
-
- /*
- * Decode the packet
- */
- if (fr_radius_decode_simple(request, &request->request_pairs,
- request->packet->data, request->packet->data_len,
- NULL, secret) < 0) {
- REDEBUG("Failed decoding CoA packet");
- return 0;
- }
-
- fr_packet_log(&default_log, request->packet, &request->request_pairs, true);
-
- /*
- * If we had an expected response code, check to see if the
- * packet matched that.
- */
- if (request->reply->code != request->filter_code) {
- if (FR_RADIUS_PACKET_CODE_VALID(request->reply->code)) {
- REDEBUG("%s: Expected %s got %s", request->name, fr_radius_packet_names[request->filter_code],
- fr_radius_packet_names[request->reply->code]);
- } else {
- REDEBUG("%s: Expected %u got %i", request->name, request->filter_code,
- request->reply->code);
- }
- stats.failed++;
-
- /*
- * Check if the contents of the packet matched the filter
- */
- } else if (fr_pair_list_empty(&request->filter)) {
- stats.passed++;
-
- } else {
- fr_pair_t const *failed[2];
-
- fr_pair_list_sort(&request->request_pairs, fr_pair_cmp_by_da);
- if (fr_pair_validate(failed, &request->filter, &request->request_pairs)) {
- RDEBUG("%s: CoA request passed filter", request->name);
- stats.passed++;
- } else {
- fr_pair_validate_debug(request, failed);
- REDEBUG("%s: CoA Request for failed filter", request->name);
- stats.failed++;
- }
- }
-
- request->reply->socket.fd = coafd;
- request->reply->socket.inet.src_ipaddr = client_ipaddr;
- request->reply->socket.inet.src_port = coa_port;
- request->reply->socket.inet.dst_ipaddr = packet->socket.inet.src_ipaddr;
- request->reply->socket.inet.dst_port = packet->socket.inet.src_port;
-
- if (!request->reply->code) switch (packet->code) {
- case FR_RADIUS_CODE_COA_REQUEST:
- request->reply->code = FR_RADIUS_CODE_COA_ACK;
- break;
-
- case FR_RADIUS_CODE_DISCONNECT_REQUEST:
- request->reply->code = FR_RADIUS_CODE_DISCONNECT_ACK;
- break;
-
- default:
- RDEBUG("Failed getting reply packet type");
- return 0;
- }
-
- /*
- * Send reply.
- */
- if (fr_radius_packet_send(request->reply, &request->reply_pairs, packet, secret) < 0) {
- REDEBUG("Failed sending CoA reply");
- return 0;
- }
-
- /*
- * No longer waiting for a CoA packet for this request.
- */
- TALLOC_FREE(parent->coa);
- return 0;
-}
-
-
/*
* Receive one packet, maybe.
*/
default_log.fd = STDOUT_FILENO;
default_log.print_level = false;
- while ((c = getopt(argc, argv, "46c:A:C:d:D:f:Fhi:n:o:p:P:r:sS:t:vx")) != -1) switch (c) {
+ while ((c = getopt(argc, argv, "46c:C:d:D:f:Fhi:n:p:P:r:sS:t:vx")) != -1) switch (c) {
case '4':
force_af = AF_INET;
break;
force_af = AF_INET6;
break;
- case 'A':
- attr_coa_filter = fr_dict_attr_by_name(NULL, fr_dict_root(dict_radius), optarg);
- if (!attr_coa_filter) {
- ERROR("Unknown or invalid attribute %s", optarg);
- fr_exit_now(1);
- }
- break;
-
case 'c':
if (!isdigit((uint8_t) *optarg)) usage();
break;
/*
- * packet,filter,coa_reply,coa_filter
+ * packet,filter
*/
case 'f':
{
files->packets = optarg;
files->filters = NULL;
} else {
- char *q;
-
files->packets = talloc_strndup(files, optarg, p - optarg);
if (!files->packets) goto oom;
files->filters = p + 1;
-
- /*
- * Look for CoA filename
- */
- q = strchr(files->filters, c);
- if (q) {
- do_coa = true;
-
- *(q++) = '\0';
- files->coa_reply = q;
-
- q = strchr(files->coa_reply, c);
- if (q) {
- *(q++) = '\0';
- files->coa_filter = q;
- }
- }
}
fr_dlist_insert_tail(&filenames, files);
}
if (persec <= 0) usage();
break;
- case 'o':
- coa_port = atoi(optarg);
- break;
-
/*
* Note that sending MANY requests in
* parallel can over-run the kernel
}
}
- if (do_coa) {
- coafd = fr_socket_server_udp(&client_ipaddr, &coa_port, NULL, false);
- if (coafd < 0) {
- fr_perror("Error opening CoA socket");
- return -1;
- }
-
- if (fr_socket_bind(coafd, NULL, &client_ipaddr, &coa_port) < 0) {
- fr_perror("Error binding socket");
- return -1;
- }
-
- /*
- * If there's no attribute given to match CoA to requests, use User-Name
- */
- if (!attr_coa_filter) attr_coa_filter = attr_user_name;
-
- coa_tree = fr_rb_inline_talloc_alloc(NULL, rc_request_t, node, request_cmp, NULL);
- if (!coa_tree) goto oom;
- }
-
packet_list = fr_packet_list_create(1);
if (!packet_list) {
ERROR("Out of memory");
if (radclient_sane(this) != 0) {
fr_exit_now(1);
}
-
- /*
- * Ensure that the packet is also tracked in the CoA tree.
- */
- if (coa_tree && this->coa && !fr_rb_insert(coa_tree, this)) {
- ERROR("Failed inserting into CoA tree");
- fr_exit_now(1);
- }
}
/*
* This packet is done. Delete it.
*/
if (this->done) {
- /*
- * We still have a CoA reply to
- * receive for this packet.
- */
- if (this->coa) {
- recv_coa_packet(fr_time_delta_wrap(0));
- if (this->coa) continue;
- }
-
talloc_free(this);
continue;
}
fr_dlist_talloc_free(&rc_request_list);
- talloc_free(coa_tree);
-
talloc_free(secret);
fr_radius_global_free();