*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* Copyright 2000 The FreeRADIUS server project
- * Copyright 2000 Miquel von Smoorenburg <miquels@cistron.nl>
+ * Copyright 2000 Miquel van Smoorenburg <miquels@cistron.nl>
* Copyright 2000 Alan DeKok <aland@ox.org>
*/
static const char rcsid[] = "$Id$";
-#include "autoconf.h"
-#include "libradius.h"
+#include <freeradius-devel/autoconf.h>
-#include <stdio.h>
-#include <stdlib.h>
+#include <stdio.h>
+#include <stdlib.h>
-#if HAVE_UNISTD_H
-#include <unistd.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
#endif
-#include <string.h>
-#include <ctype.h>
-#include <netdb.h>
-#include <sys/socket.h>
+#include <string.h>
+#include <ctype.h>
+#include <netdb.h>
-#if HAVE_NETINET_IN_H
-#include <netinet/in.h>
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
#endif
-#if HAVE_SYS_SELECT_H
-# include <sys/select.h>
+#ifdef HAVE_GETOPT_H
+# include <getopt.h>
#endif
-#if HAVE_GETOPT_H
-# include <getopt.h>
-#endif
+#include <assert.h>
+
+#include <freeradius-devel/conf.h>
+#include <freeradius-devel/radpaths.h>
+#include <freeradius-devel/missing.h>
+#include <freeradius-devel/libradius.h>
+
+static int retries = 10;
+static float timeout = 3;
+static const char *secret = NULL;
+static int do_output = 1;
+static int totalapp = 0;
+static int totaldeny = 0;
+static int totallost = 0;
+
+static int server_port = 0;
+static int packet_code = 0;
+static lrad_ipaddr_t server_ipaddr;
+static int resend_count = 1;
+static int done = 1;
+
+static lrad_ipaddr_t client_ipaddr;
+static int client_port = 0;
+
+static int sockfd;
+static int last_used_id = -1;
+
+static rbtree_t *filename_tree = NULL;
+static lrad_packet_list_t *pl = NULL;
+
+static int sleep_time = -1;
+
+typedef struct radclient_t {
+ struct radclient_t *prev;
+ struct radclient_t *next;
+
+ const char *filename;
+ int packet_number; /* in the file */
+ char password[256];
+ time_t timestamp;
+ RADIUS_PACKET *request;
+ RADIUS_PACKET *reply;
+ int resend;
+ int tries;
+ int done;
+} radclient_t;
+
+static radclient_t *radclient_head = NULL;
+static radclient_t *radclient_tail = NULL;
+
+
+static void NEVER_RETURNS usage(void)
+{
+ fprintf(stderr, "Usage: radclient [options] server[:port] <command> [<secret>]\n");
+
+ fprintf(stderr, " <command> One of auth, acct, status, coa, or disconnect.\n");
+ fprintf(stderr, " -c count Send each packet 'count' times.\n");
+ fprintf(stderr, " -d raddb Set dictionary directory.\n");
+ fprintf(stderr, " -f file Read packets from file, not stdin.\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, " -p num Send 'num' packets from a file in parallel.\n");
+ fprintf(stderr, " -q Do not print anything out.\n");
+ fprintf(stderr, " -r retries If timeout, retry sending the packet 'retries' times.\n");
+ fprintf(stderr, " -s Print out summary information of auth results.\n");
+ fprintf(stderr, " -S file read secret from file, not command line.\n");
+ fprintf(stderr, " -t timeout Wait 'timeout' seconds before retrying (may be a floating point number).\n");
+ fprintf(stderr, " -v Show program version information.\n");
+ fprintf(stderr, " -x Debugging mode.\n");
+ fprintf(stderr, " -4 Use IPv4 address of server\n");
+ fprintf(stderr, " -6 Use IPv6 address of server.\n");
+
+ exit(1);
+}
+
+/*
+ * Free a radclient struct, which may (or may not)
+ * already be in the list.
+ */
+static void radclient_free(radclient_t *radclient)
+{
+ radclient_t *prev, *next;
+
+ if (radclient->request) rad_free(&radclient->request);
+ if (radclient->reply) rad_free(&radclient->reply);
-#include "conf.h"
-#include "radpaths.h"
-#include "missing.h"
+ prev = radclient->prev;
+ next = radclient->next;
-static int retries = 10;
-static float timeout = 3;
-static const char *secret = "secret";
-static int do_output = 1;
-static int do_summary = 0;
-static int filedone = 0;
-static int totalapp = 0;
-static int totaldeny = 0;
+ if (prev) {
+ assert(radclient_head != radclient);
+ prev->next = next;
+ } else if (radclient_head) {
+ assert(radclient_head == radclient);
+ radclient_head = next;
+ }
+
+ if (next) {
+ assert(radclient_tail != radclient);
+ next->prev = prev;
+ } else if (radclient_tail) {
+ assert(radclient_tail == radclient);
+ radclient_tail = prev;
+ }
+
+ free(radclient);
+}
/*
- * Read valuepairs from the fp up to End-Of-File.
+ * Initialize a radclient data structure
*/
-static VALUE_PAIR *readvp(FILE *fp)
+static radclient_t *radclient_init(const char *filename)
{
- char buf[128];
- int last_token;
- char *p;
- VALUE_PAIR *vp;
- VALUE_PAIR *list;
- int error = 0;
+ FILE *fp;
+ VALUE_PAIR *vp;
+ radclient_t *start, *radclient, *prev = NULL;
+ int filedone = 0;
+ int packet_number = 1;
+
+ start = NULL;
+ assert(filename != NULL);
+
+ /*
+ * Determine where to read the VP's from.
+ */
+ if (strcmp(filename, "-") != 0) {
+ fp = fopen(filename, "r");
+ if (!fp) {
+ fprintf(stderr, "radclient: Error opening %s: %s\n",
+ filename, strerror(errno));
+ return NULL;
+ }
+ } else {
+ fp = stdin;
+ }
+
+ /*
+ * Loop until the file is done.
+ */
+ do {
+ /*
+ * Allocate it.
+ */
+ radclient = malloc(sizeof(*radclient));
+ if (!radclient) {
+ perror("radclient: X");
+ return NULL; /* memory leak "start" */
+ }
+ memset(radclient, 0, sizeof(*radclient));
+
+ radclient->request = rad_alloc(1);
+ if (!radclient->request) {
+ librad_perror("radclient: Y");
+ radclient_free(radclient);
+ return NULL; /* memory leak "start" */
+ }
+
+ radclient->filename = filename;
+ radclient->request->id = -1; /* allocate when sending */
+ radclient->packet_number = packet_number++;
+
+ /*
+ * Read the VP's.
+ */
+ radclient->request->vps = readvp2(fp, &filedone, "radclient:");
+ if (!radclient->request->vps) {
+ radclient_free(radclient);
+ return start; /* done: return the list */
+ }
+
+ /*
+ * Keep a copy of the the User-Password attribute.
+ */
+ if ((vp = pairfind(radclient->request->vps, PW_USER_PASSWORD)) != NULL) {
+ strNcpy(radclient->password, vp->vp_strvalue,
+ sizeof(radclient->password));
+ /*
+ * Otherwise keep a copy of the CHAP-Password attribute.
+ */
+ } else if ((vp = pairfind(radclient->request->vps, PW_CHAP_PASSWORD)) != NULL) {
+ strNcpy(radclient->password, vp->vp_strvalue,
+ sizeof(radclient->password));
+ } else {
+ radclient->password[0] = '\0';
+ }
+
+ /*
+ * Fix up Digest-Attributes issues
+ */
+ for (vp = radclient->request->vps; vp != NULL; vp = vp->next) {
+ switch (vp->attribute) {
+ default:
+ break;
+
+ /*
+ * Allow it to set the packet type in
+ * the attributes read from the file.
+ */
+ case PW_PACKET_TYPE:
+ radclient->request->code = vp->lvalue;
+ break;
+
+ case PW_PACKET_DST_PORT:
+ radclient->request->dst_port = (vp->lvalue & 0xffff);
+ break;
- list = NULL;
+ case PW_PACKET_DST_IP_ADDRESS:
+ radclient->request->dst_ipaddr.af = AF_INET;
+ radclient->request->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->lvalue;
+ break;
- while (!error && fgets(buf, sizeof(buf), fp) != NULL) {
+ case PW_PACKET_DST_IPV6_ADDRESS:
+ radclient->request->dst_ipaddr.af = AF_INET6;
+ radclient->request->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
+ break;
- p = buf;
+ case PW_PACKET_SRC_PORT:
+ radclient->request->src_port = (vp->lvalue & 0xffff);
+ break;
- /* If we get a '\n' by itself, we assume that's the end of that VP */
- if((buf[0] == '\n') && (list)) {
- return error ? NULL: list;
- }
- if((buf[0] == '\n') && (!list)) {
- continue;
+ case PW_PACKET_SRC_IP_ADDRESS:
+ radclient->request->src_ipaddr.af = AF_INET;
+ radclient->request->src_ipaddr.ipaddr.ip4addr.s_addr = vp->lvalue;
+ break;
+
+ case PW_PACKET_SRC_IPV6_ADDRESS:
+ radclient->request->src_ipaddr.af = AF_INET6;
+ radclient->request->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
+ break;
+
+ case PW_DIGEST_REALM:
+ case PW_DIGEST_NONCE:
+ case PW_DIGEST_METHOD:
+ case PW_DIGEST_URI:
+ case PW_DIGEST_QOP:
+ case PW_DIGEST_ALGORITHM:
+ case PW_DIGEST_BODY_DIGEST:
+ case PW_DIGEST_CNONCE:
+ case PW_DIGEST_NONCE_COUNT:
+ case PW_DIGEST_USER_NAME:
+ /* overlapping! */
+ memmove(&vp->vp_octets[2], &vp->vp_octets[0],
+ vp->length);
+ vp->vp_octets[0] = vp->attribute - PW_DIGEST_REALM + 1;
+ vp->length += 2;
+ vp->vp_octets[1] = vp->length;
+ vp->attribute = PW_DIGEST_ATTRIBUTES;
+ break;
+ }
+ } /* loop over the VP's we read in */
+
+ if (!start) {
+ start = radclient;
+ prev = start;
} else {
- do {
- if ((vp = pairread(&p, &last_token)) == NULL) {
- librad_perror("radclient:");
- error = 1;
- break;
- }
- pairadd(&list, vp);
- } while (last_token == T_COMMA);
+ prev->next = radclient;
+ radclient->prev = prev;
+ prev = radclient;
+ }
+ } while (!filedone); /* loop until the file is done. */
+
+ if (fp != stdin) fclose(fp);
+
+ /*
+ * And we're done.
+ */
+ return start;
+}
+
+
+/*
+ * Sanity check each argument.
+ */
+static int radclient_sane(radclient_t *radclient)
+{
+ if (radclient->request->dst_port == 0) {
+ radclient->request->dst_port = server_port;
+ }
+ if (radclient->request->dst_ipaddr.af == AF_UNSPEC) {
+ if (server_ipaddr.af == AF_UNSPEC) {
+ fprintf(stderr, "radclient: No server was given, but request %d in file %s did not contain Packet-Dst-IP-Address\n",
+ radclient->packet_number, radclient->filename);
+ return -1;
+ }
+ radclient->request->dst_ipaddr = server_ipaddr;
+ }
+ if (radclient->request->code == 0) {
+ if (packet_code == -1) {
+ fprintf(stderr, "radclient: Request was \"auto\", but request %d in file %s did not contain Packet-Type\n",
+ radclient->packet_number, radclient->filename);
+ return -1;
}
+
+ radclient->request->code = packet_code;
}
- filedone = 1;
- return error ? NULL: list;
+ radclient->request->sockfd = -1;
+
+ return 0;
+}
+
+
+/*
+ * For request handline.
+ */
+static int filename_cmp(const void *one, const void *two)
+{
+ return strcmp((const char *) one, (const char *) two);
}
-static void usage(void)
+static int filename_walk(void *context, void *data)
{
- fprintf(stderr, "Usage: radclient [-c count] [-d raddb] [-f file] [-r retries] [-t timeout]\n [-i id] [-qvx] server acct|auth <secret>\n");
-
- fprintf(stderr, " -c count Send each packet 'count' times.\n");
- fprintf(stderr, " -d raddb Set dictionary directory.\n");
- fprintf(stderr, " -f file Read packets from file, not stdin.\n");
- fprintf(stderr, " -r retries If timeout, retry sending the packet 'retires' times.\n");
- fprintf(stderr, " -t timeout Wait 'timeout' seconds before retrying.\n");
- fprintf(stderr, " -i id Set request id to 'id'. Values may be 0..255\n");
- fprintf(stderr, " -q Do not print anything out.\n");
- fprintf(stderr, " -s Print out summary information of auth results.\n");
- fprintf(stderr, " -v Show program version information.\n");
- fprintf(stderr, " -x Debugging mode.\n");
+ const char *filename = data;
+ radclient_t *radclient;
- exit(1);
+ context = context; /* -Wunused */
+
+ /*
+ * Initialize the request we're about
+ * to send.
+ */
+ radclient = radclient_init(filename);
+ if (!radclient) {
+ exit(1);
+ }
+
+ if (!radclient_head) {
+ assert(radclient_tail == NULL);
+ radclient_head = radclient;
+ } else {
+ assert(radclient_tail->next == NULL);
+ radclient_tail->next = radclient;
+ radclient->prev = radclient_tail;
+ }
+
+ /*
+ * We may have had a list of "radclient" structures
+ * returned to us.
+ */
+ while (radclient->next) radclient = radclient->next;
+ radclient_tail = radclient;
+
+ return 0;
}
-static int getport(const char *name)
+
+/*
+ * Deallocate packet ID, etc.
+ */
+static void deallocate_id(radclient_t *radclient)
{
- struct servent *svp;
+ if (!radclient || !radclient->request ||
+ (radclient->request->id < 0)) {
+ return;
+ }
- svp = getservbyname (name, "udp");
- if (!svp) {
- return 0;
+ /*
+ * One more unused RADIUS ID.
+ */
+ lrad_packet_list_id_free(pl, radclient->request);
+ radclient->request->id = -1;
+
+ /*
+ * If we've already sent a packet, free up the old one,
+ * and ensure that the next packet has a unique
+ * authentication vector.
+ */
+ if (radclient->request->data) {
+ free(radclient->request->data);
+ radclient->request->data = NULL;
}
- return ntohs(svp->s_port);
+ if (radclient->reply) rad_free(&radclient->reply);
}
-static int send_packet(RADIUS_PACKET *req, RADIUS_PACKET **rep)
+
+static void print_hex(RADIUS_PACKET *packet)
{
int i;
- struct timeval tv;
- for (i = 0; i < retries; i++) {
- fd_set rdfdesc;
+ if (!packet->data) return;
+
+ printf(" Code:\t\t%u\n", packet->data[0]);
+ printf(" Id:\t\t%u\n", packet->data[1]);
+ printf(" Length:\t%u\n", ((packet->data[2] << 8) |
+ (packet->data[3])));
+ printf(" Vector:\t");
+ for (i = 4; i < 20; i++) {
+ printf("%02x", packet->data[i]);
+ }
+ printf("\n");
+
+ if (packet->data_len > 20) {
+ int total;
+ const uint8_t *ptr;
+ printf(" Data:");
+
+ total = packet->data_len - 20;
+ ptr = packet->data + 20;
+
+ while (total > 0) {
+ int attrlen;
+
+ printf("\t\t");
+ if (total < 2) { /* too short */
+ printf("%02x\n", *ptr);
+ break;
+ }
+
+ if (ptr[1] > total) { /* too long */
+ for (i = 0; i < total; i++) {
+ printf("%02x ", ptr[i]);
+ }
+ break;
+ }
- rad_send(req, secret);
+ printf("%02x %02x ", ptr[0], ptr[1]);
+ attrlen = ptr[1] - 2;
+ ptr += 2;
+ total -= 2;
- /* And wait for reply, timing out as necessary */
- FD_ZERO(&rdfdesc);
- FD_SET(req->sockfd, &rdfdesc);
+ for (i = 0; i < attrlen; i++) {
+ if ((i > 0) && ((i & 0x0f) == 0x00))
+ printf("\t\t\t");
+ printf("%02x ", ptr[i]);
+ if ((i & 0x0f) == 0x0f) printf("\n");
+ }
- tv.tv_sec = (int)timeout;
- tv.tv_usec = 1000000 * (timeout - (int)timeout);
+ if ((attrlen & 0x0f) != 0x00) printf("\n");
- /* Something's wrong if we don't get exactly one fd. */
- if (select(req->sockfd + 1, &rdfdesc, NULL, NULL, &tv) != 1) {
- continue;
+ ptr += attrlen;
+ total -= attrlen;
}
+ }
+ fflush(stdout);
+}
- *rep = rad_recv(req->sockfd);
- if (*rep != NULL) {
- break;
- } else { /* NULL: couldn't receive the packet */
- librad_perror("radclient:");
- exit(1);
+/*
+ * Send one packet.
+ */
+static int send_one_packet(radclient_t *radclient)
+{
+ assert(radclient->done == 0);
+
+ /*
+ * Remember when we have to wake up, to re-send the
+ * request, of we didn't receive a response.
+ */
+ if ((sleep_time == -1) ||
+ (sleep_time > (int) timeout)) {
+ sleep_time = (int) timeout;
+ }
+
+ /*
+ * Haven't sent the packet yet. Initialize it.
+ */
+ if (radclient->request->id == -1) {
+ int i, rcode;
+
+ assert(radclient->reply == NULL);
+
+ /*
+ * Didn't find a free packet ID, we're not done,
+ * we don't sleep, and we stop trying to process
+ * this packet.
+ */
+ retry:
+ rcode = lrad_packet_list_id_alloc(pl, radclient->request);
+ if (rcode < 0) {
+ int mysockfd;
+
+ mysockfd = lrad_socket(&client_ipaddr, 0);
+ if (!mysockfd) {
+ fprintf(stderr, "radclient: Can't open new socket\n");
+ exit(1);
+ }
+ if (!lrad_packet_list_socket_add(pl, mysockfd)) {
+ fprintf(stderr, "radclient: Can't add new socket\n");
+ exit(1);
+ }
+ goto retry;
+ }
+
+ if (rcode == 0) {
+ done = 0;
+ sleep_time = 0;
+ return 0;
+ }
+
+ assert(radclient->request->id != -1);
+ assert(radclient->request->data == NULL);
+
+ for (i = 0; i < 4; i++) {
+ *((uint32_t *) radclient->request->vector) = lrad_rand();
+ }
+
+ /*
+ * Update the password, so it can be encrypted with the
+ * new authentication vector.
+ */
+ if (radclient->password[0] != '\0') {
+ VALUE_PAIR *vp;
+
+ if ((vp = pairfind(radclient->request->vps, PW_USER_PASSWORD)) != NULL) {
+ strNcpy(vp->vp_strvalue, radclient->password,
+ sizeof(vp->vp_strvalue));
+ vp->length = strlen(vp->vp_strvalue);
+
+ } else if ((vp = pairfind(radclient->request->vps, PW_CHAP_PASSWORD)) != NULL) {
+ /*
+ * FIXME: AND there's no CHAP-Challenge,
+ * AND vp->length != 17
+ * AND rad_chap_encode() != vp->vp_octets
+ */
+ strNcpy(vp->vp_strvalue, radclient->password,
+ sizeof(vp->vp_strvalue));
+ vp->length = strlen(vp->vp_strvalue);
+
+ rad_chap_encode(radclient->request,
+ vp->vp_octets,
+ radclient->request->id, vp);
+ vp->length = 17;
+ }
+ }
+
+ radclient->timestamp = time(NULL);
+ radclient->tries = 1;
+ radclient->resend++;
+
+ /*
+ * Duplicate found. Serious error!
+ */
+ if (!lrad_packet_list_insert(pl, &radclient->request)) {
+ assert(0 == 1);
+ }
+
+
+ } else { /* radclient->request->id >= 0 */
+ time_t now = time(NULL);
+
+ /*
+ * FIXME: Accounting packets are never retried!
+ * The Acct-Delay-Time attribute is updated to
+ * reflect the delay, and the packet is re-sent
+ * from scratch!
+ */
+
+ /*
+ * Not time for a retry, do so.
+ */
+ if ((now - radclient->timestamp) < timeout) {
+ /*
+ * When we walk over the tree sending
+ * packets, we update the minimum time
+ * required to sleep.
+ */
+ if ((sleep_time == -1) ||
+ (sleep_time > (now - radclient->timestamp))) {
+ sleep_time = now - radclient->timestamp;
+ }
+ return 0;
}
+
+ /*
+ * We're not trying later, maybe the packet is done.
+ */
+ if (radclient->tries == retries) {
+ assert(radclient->request->id >= 0);
+
+ /*
+ * Delete the request from the tree of
+ * outstanding requests.
+ */
+ lrad_packet_list_yank(pl, radclient->request);
+
+ fprintf(stderr, "radclient: no response from server for ID %d socket %d\n", radclient->request->id, radclient->request->sockfd);
+ deallocate_id(radclient);
+
+ /*
+ * Normally we mark it "done" when we've received
+ * the response, but this is a special case.
+ */
+ if (radclient->resend == resend_count) {
+ radclient->done = 1;
+ }
+ totallost++;
+ return -1;
+ }
+
+ /*
+ * We are trying later.
+ */
+ radclient->timestamp = now;
+ radclient->tries++;
}
- /* No response or no data read (?) */
- if (i == retries) {
- fprintf(stderr, "radclient: no response from server\n");
- exit(1);
+
+ /*
+ * Send the packet.
+ */
+ if (rad_send(radclient->request, NULL, secret) < 0) {
+ fprintf(stderr, "radclient: Failed to send packet for ID %d: %s\n",
+ radclient->request->id, librad_errstr);
+ }
+
+ if (librad_debug > 2) print_hex(radclient->request);
+
+ return 0;
+}
+
+/*
+ * Receive one packet, maybe.
+ */
+static int recv_one_packet(int wait_time)
+{
+ fd_set set;
+ struct timeval tv;
+ radclient_t *radclient;
+ RADIUS_PACKET *reply, **request_p;
+ volatile int max_fd;
+
+ /* And wait for reply, timing out as necessary */
+ FD_ZERO(&set);
+
+ max_fd = lrad_packet_list_fd_set(pl, &set);
+ if (max_fd < 0) exit(1); /* no sockets to listen on! */
+
+ if (wait_time <= 0) {
+ tv.tv_sec = 0;
+ } else {
+ tv.tv_sec = wait_time;
}
+ tv.tv_usec = 0;
- if (rad_decode(*rep, req, secret) != 0) {
+ /*
+ * No packet was received.
+ */
+ if (select(max_fd, &set, NULL, NULL, &tv) <= 0) {
+ return 0;
+ }
+
+ /*
+ * Look for the packet.
+ */
+ reply = lrad_packet_list_recv(pl, &set);
+ if (!reply) {
+ fprintf(stderr, "radclient: received bad packet: %s\n",
+ librad_errstr);
+ return -1; /* bad packet */
+ }
+
+ if (librad_debug > 2) print_hex(reply);
+
+ request_p = lrad_packet_list_find_byreply(pl, reply);
+ if (!request_p) {
+ fprintf(stderr, "radclient: received response to request we did not send. (id=%d socket %d)\n", reply->id, reply->sockfd);
+ rad_free(&reply);
+ return -1; /* got reply to packet we didn't send */
+ }
+ radclient = lrad_packet2myptr(radclient_t, request, request_p);
+
+ /*
+ * Fails the signature validation: not a real reply.
+ * FIXME: Silently drop it and listen for another packet.
+ */
+ if (rad_verify(reply, radclient->request, secret) < 0) {
+ librad_perror("rad_verify");
+ totallost++;
+ goto packet_done; /* shared secret is incorrect */
+ }
+
+ lrad_packet_list_yank(pl, radclient->request);
+ deallocate_id(radclient);
+ radclient->reply = reply;
+
+ /*
+ * If this fails, we're out of memory.
+ */
+ if (rad_decode(reply, radclient->request, secret) != 0) {
librad_perror("rad_decode");
- exit(1);
+ totallost++;
+ goto packet_done;
}
/* libradius debug already prints out the value pairs for us */
if (!librad_debug && do_output) {
printf("Received response ID %d, code %d, length = %d\n",
- (*rep)->id, (*rep)->code, (*rep)->data_len);
- vp_printlist(stdout, (*rep)->vps);
+ reply->id, reply->code, reply->data_len);
+ vp_printlist(stdout, reply->vps);
}
- if((*rep)->code == PW_AUTHENTICATION_ACK) {
+ if (reply->code != PW_AUTHENTICATION_REJECT) {
totalapp++;
} else {
totaldeny++;
}
+packet_done:
+ rad_free(&radclient->reply);
+
+ /*
+ * Once we've sent the packet as many times as requested,
+ * mark it done.
+ */
+ if (radclient->resend == resend_count) {
+ assert(lrad_packet_list_find(pl, radclient->request) == NULL);
+ radclient->done = 1;
+ }
+
return 0;
}
+
+static int getport(const char *name)
+{
+ struct servent *svp;
+
+ svp = getservbyname (name, "udp");
+ if (!svp) {
+ return 0;
+ }
+
+ return ntohs(svp->s_port);
+}
+
int main(int argc, char **argv)
{
- RADIUS_PACKET *req;
- RADIUS_PACKET *rep = NULL;
- char *p;
- int c;
- int port = 0;
- const char *radius_dir = RADDBDIR;
- char *filename = NULL;
- FILE *fp;
- int count = 1;
- int loop;
- char password[256];
- VALUE_PAIR *vp;
- int id;
+ char *p;
+ int c;
+ const char *radius_dir = RADDBDIR;
+ char filesecret[256];
+ FILE *fp;
+ int do_summary = 0;
+ int persec = 0;
+ int parallel = 1;
+ radclient_t *this;
+ int force_af = AF_UNSPEC;
- id = ((int)getpid() & 0xff);
+ librad_debug = 0;
+
+ filename_tree = rbtree_create(filename_cmp, NULL, 0);
+ if (!filename_tree) {
+ fprintf(stderr, "radclient: Out of memory\n");
+ exit(1);
+ }
- while ((c = getopt(argc, argv, "c:d:f:hi:qst:r:xv")) != EOF) switch(c) {
+ while ((c = getopt(argc, argv, "46c:d:f:hi:n:p:qr:sS:t:vx")) != EOF) switch(c) {
+ case '4':
+ force_af = AF_INET;
+ break;
+ case '6':
+ force_af = AF_INET6;
+ break;
case 'c':
- if (!isdigit(*optarg)) usage();
- count = atoi(optarg);
+ if (!isdigit((int) *optarg))
+ usage();
+ resend_count = atoi(optarg);
break;
case 'd':
radius_dir = optarg;
break;
- case 'f':
- filename = optarg;
+ case 'f':
+ rbtree_insert(filename_tree, optarg);
+ break;
+ case 'i': /* currently broken */
+ if (!isdigit((int) *optarg))
+ usage();
+ last_used_id = atoi(optarg);
+ if ((last_used_id < 0) || (last_used_id > 255)) {
+ usage();
+ }
+ break;
+
+ case 'n':
+ persec = atoi(optarg);
+ if (persec <= 0) usage();
+ break;
+
+ /*
+ * Note that sending MANY requests in
+ * parallel can over-run the kernel
+ * queues, and Linux will happily discard
+ * packets. So even if the server responds,
+ * the client may not see the response.
+ */
+ case 'p':
+ parallel = atoi(optarg);
+ if (parallel <= 0) usage();
break;
+
case 'q':
do_output = 0;
break;
- case 'x':
- librad_debug = 1;
- break;
case 'r':
- if (!isdigit(*optarg)) usage();
- retries = atoi(optarg);
- break;
- case 'i':
- if (!isdigit(*optarg)) usage();
- id = atoi(optarg);
- if ((id < 0) || (id > 255)) {
+ if (!isdigit((int) *optarg))
usage();
- }
+ retries = atoi(optarg);
+ if ((retries == 0) || (retries > 1000)) usage();
break;
case 's':
do_summary = 1;
break;
+ case 'S':
+ fp = fopen(optarg, "r");
+ if (!fp) {
+ fprintf(stderr, "radclient: Error opening %s: %s\n",
+ optarg, strerror(errno));
+ exit(1);
+ }
+ if (fgets(filesecret, sizeof(filesecret), fp) == NULL) {
+ fprintf(stderr, "radclient: Error reading %s: %s\n",
+ optarg, strerror(errno));
+ exit(1);
+ }
+ fclose(fp);
+
+ /* truncate newline */
+ p = filesecret + strlen(filesecret) - 1;
+ while ((p >= filesecret) &&
+ (*p < ' ')) {
+ *p = '\0';
+ --p;
+ }
+
+ if (strlen(filesecret) < 2) {
+ fprintf(stderr, "radclient: Secret in %s is too short\n", optarg);
+ exit(1);
+ }
+ secret = filesecret;
+ break;
case 't':
- if (!isdigit(*optarg)) usage();
+ if (!isdigit((int) *optarg))
+ usage();
timeout = atof(optarg);
break;
case 'v':
printf("radclient: $Id$ built on " __DATE__ " at " __TIME__ "\n");
exit(0);
break;
+ case 'x':
+ librad_debug++;
+ break;
case 'h':
default:
usage();
argc -= (optind - 1);
argv += (optind - 1);
- if (argc < 4) {
+ if ((argc < 3) ||
+ ((secret == NULL) && (argc < 4))) {
usage();
}
return 1;
}
- if ((req = rad_alloc(1)) == NULL) {
- librad_perror("radclient");
- exit(1);
- }
-
- req->id = id;
-
/*
- * Strip port from hostname if needed.
+ * Resolve hostname.
*/
- if ((p = strchr(argv[1], ':')) != NULL) {
- *p++ = 0;
- port = atoi(p);
+ server_ipaddr.af = force_af;
+ if (strcmp(argv[1], "-") != 0) {
+ const char *hostname = argv[1];
+ const char *portname = argv[1];
+ char buffer[256];
+
+ if (*argv[1] == '[') { /* IPv6 URL encoded */
+ p = strchr(argv[1], ']');
+ if ((p - argv[1]) >= sizeof(buffer)) {
+ usage();
+ }
+
+ memcpy(buffer, argv[1] + 1, p - argv[1] - 1);
+ buffer[p - argv[1] - 1] = '\0';
+
+ hostname = buffer;
+ portname = p + 1;
+
+ }
+ p = strchr(portname, ':');
+ if (p && (strchr(p + 1, ':') == NULL)) {
+ *p = '\0';
+ portname = p + 1;
+ } else {
+ portname = NULL;
+ }
+
+ if (ip_hton(hostname, force_af, &server_ipaddr) < 0) {
+ fprintf(stderr, "radclient: Failed to find IP address for host %s: %s\n", hostname, strerror(errno));
+ exit(1);
+ }
+
+ /*
+ * Strip port from hostname if needed.
+ */
+ if (portname) server_port = atoi(portname);
}
/*
* See what kind of request we want to send.
*/
if (strcmp(argv[2], "auth") == 0) {
- if (port == 0) port = getport("radius");
- if (port == 0) port = PW_AUTH_UDP_PORT;
- req->code = PW_AUTHENTICATION_REQUEST;
+ if (server_port == 0) server_port = getport("radius");
+ if (server_port == 0) server_port = PW_AUTH_UDP_PORT;
+ packet_code = PW_AUTHENTICATION_REQUEST;
+
+ } else if (strcmp(argv[2], "challenge") == 0) {
+ if (server_port == 0) server_port = getport("radius");
+ if (server_port == 0) server_port = PW_AUTH_UDP_PORT;
+ packet_code = PW_ACCESS_CHALLENGE;
+
} else if (strcmp(argv[2], "acct") == 0) {
- if (port == 0) port = getport("radacct");
- if (port == 0) port = PW_ACCT_UDP_PORT;
- req->code = PW_ACCOUNTING_REQUEST;
+ if (server_port == 0) server_port = getport("radacct");
+ if (server_port == 0) server_port = PW_ACCT_UDP_PORT;
+ packet_code = PW_ACCOUNTING_REQUEST;
do_summary = 0;
- } else if (isdigit(argv[2][0])) {
- if (port == 0) port = PW_AUTH_UDP_PORT;
- port = atoi(argv[2]);
+
+ } else if (strcmp(argv[2], "status") == 0) {
+ if (server_port == 0) server_port = getport("radius");
+ if (server_port == 0) server_port = PW_AUTH_UDP_PORT;
+ packet_code = PW_STATUS_SERVER;
+
+ } else if (strcmp(argv[2], "disconnect") == 0) {
+ if (server_port == 0) server_port = PW_POD_UDP_PORT;
+ packet_code = PW_DISCONNECT_REQUEST;
+
+ } else if (strcmp(argv[2], "coa") == 0) {
+ if (server_port == 0) server_port = PW_POD_UDP_PORT;
+ packet_code = PW_COA_REQUEST;
+
+ } else if (strcmp(argv[2], "auto") == 0) {
+ packet_code = -1;
+
+ } else if (isdigit((int) argv[2][0])) {
+ if (server_port == 0) server_port = getport("radius");
+ if (server_port == 0) server_port = PW_AUTH_UDP_PORT;
+ packet_code = atoi(argv[2]);
} else {
usage();
}
/*
- * Resolve hostname.
+ * Add the secret.
*/
- req->dst_port = port;
- req->dst_ipaddr = ip_getaddr(argv[1]);
- if (req->dst_ipaddr == INADDR_NONE) {
- librad_perror("radclient: %s: ", argv[1]);
- exit(1);
+ if (argv[3]) secret = argv[3];
+
+ /*
+ * If no '-f' is specified, we're reading from stdin.
+ */
+ if (rbtree_num_elements(filename_tree) == 0) {
+ rbtree_insert(filename_tree, "-");
}
/*
- * Add the secret.
+ * Walk over the list of filenames, creating the requests.
*/
- if (argv[3]) secret = argv[3];
+ if (rbtree_walk(filename_tree, InOrder, filename_walk, NULL) != 0) {
+ exit(1);
+ }
/*
- * Read valuepairs.
- * Maybe read them, from stdin, if there's no
- * filename, or if the filename is '-'.
+ * No packets read. Die.
*/
- if (filename && (strcmp(filename, "-") != 0)) {
- fp = fopen(filename, "r");
- if (!fp) {
- fprintf(stderr, "radclient: Error opening %s: %s\n",
- filename, strerror(errno));
- exit(1);
- }
- } else {
- fp = stdin;
+ if (!radclient_head) {
+ fprintf(stderr, "radclient: Nothing to send.\n");
+ exit(1);
}
-
+
/*
- * Send request.
+ * Bind to the first specified IP address and port.
+ * This means we ignore later ones.
*/
- if ((req->sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
- perror("radclient: socket: ");
+ if (radclient_head->request->src_ipaddr.af == AF_UNSPEC) {
+ memset(&client_ipaddr, 0, sizeof(client_ipaddr));
+ client_ipaddr.af = server_ipaddr.af;
+ client_port = 0;
+ } else {
+ client_ipaddr = radclient_head->request->src_ipaddr;
+ client_port = radclient_head->request->src_port;
+ }
+ sockfd = lrad_socket(&client_ipaddr, client_port);
+ if (sockfd < 0) {
+ fprintf(stderr, "radclient: socket: %s\n", librad_errstr);
exit(1);
}
- while(!filedone) {
- if(req->vps) pairfree(&req->vps);
+ pl = lrad_packet_list_create(1);
+ if (!pl) {
+ fprintf(stderr, "radclient: Out of memory\n");
+ exit(1);
+ }
- if ((req->vps = readvp(fp)) == NULL) {
- break;
+ if (!lrad_packet_list_socket_add(pl, sockfd)) {
+ fprintf(stderr, "radclient: Out of memory\n");
+ exit(1);
+ }
+
+ /*
+ * Walk over the list of packets, sanity checking
+ * everything.
+ */
+ for (this = radclient_head; this != NULL; this = this->next) {
+ this->request->src_ipaddr = client_ipaddr;
+ this->request->src_port = client_port;
+ if (radclient_sane(this) != 0) {
+ exit(1);
}
-
+ }
- /*
- * Encrypt the Password attribute.
- */
- if ((vp = pairfind(req->vps, PW_PASSWORD)) != NULL) {
- strNcpy(password, (char *)vp->strvalue, sizeof(vp->strvalue));
-
- rad_pwencode((char *)vp->strvalue,
- &(vp->length),
- secret, (char *)req->vector);
-
- /*
- * Not there, encrypt the CHAP-Password attribute.
- */
- } else if ((vp = pairfind(req->vps, PW_CHAP_PASSWORD)) != NULL) {
- strNcpy(password, (char *)vp->strvalue, sizeof(vp->strvalue));
- rad_chap_encode(req, (char *) vp->strvalue, req->id, vp);
- vp->length = 17;
+ /*
+ * Walk over the packets to send, until
+ * we're all done.
+ *
+ * FIXME: This currently busy-loops until it receives
+ * all of the packets. It should really have some sort of
+ * send packet, get time to wait, select for time, etc.
+ * loop.
+ */
+ do {
+ int n = parallel;
+ radclient_t *next;
+ const char *filename = NULL;
+
+ done = 1;
+ sleep_time = -1;
- } else {
- *password = '\0';
- }
-
/*
- * Loop, sending the packet N times.
+ * Walk over the packets, sending them.
*/
- for (loop = 0; loop < count; loop++) {
- req->id++;
-
+
+ for (this = radclient_head; this != NULL; this = next) {
+ next = this->next;
+
+ /*
+ * If there's a packet to receive,
+ * receive it, but don't wait for a
+ * packet.
+ */
+ recv_one_packet(0);
+
/*
- * If we've already sent a packet, free up the old
- * one, and ensure that the next packet has a unique
- * ID and authentication vector.
+ * This packet is done. Delete it.
*/
- if (req->data) {
- free(req->data);
- req->data = NULL;
-
- if (*password != '\0') {
- vp = pairfind(req->vps, PW_PASSWORD);
- if (vp) {
- strNcpy((char *)vp->strvalue, password,
- (vp->length)+1);
- vp->length = strlen(password);
- }
+ if (this->done) {
+ radclient_free(this);
+ continue;
+ }
+
+ /*
+ * Packets from multiple '-f' are sent
+ * in parallel.
+ *
+ * Packets from one file are sent in
+ * series, unless '-p' is specified, in
+ * which case N packets from each file
+ * are sent in parallel.
+ */
+ if (this->filename != filename) {
+ filename = this->filename;
+ n = parallel;
+ }
+
+ if (n > 0) {
+ n--;
+
+ /*
+ * Send the current packet.
+ */
+ send_one_packet(this);
+
+ /*
+ * Wait a little before sending
+ * the next packet, if told to.
+ */
+ if (persec) {
+ struct timeval tv;
+
+ /*
+ * Don't sleep elsewhere.
+ */
+ sleep_time = 0;
+
+ if (persec == 1) {
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ } else {
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000000/persec;
+ }
+
+ /*
+ * Sleep for milliseconds,
+ * portably.
+ *
+ * If we get an error or
+ * a signal, treat it like
+ * a normal timeout.
+ */
+ select(0, NULL, NULL, NULL, &tv);
}
- librad_md5_calc(req->vector, req->vector,
- sizeof(req->vector));
+ /*
+ * If we haven't sent this packet
+ * often enough, we're not done,
+ * and we shouldn't sleep.
+ */
+ if (this->resend < resend_count) {
+ done = 0;
+ sleep_time = 0;
+ }
+ } else { /* haven't sent this packet, we're not done */
+ assert(this->done == 0);
+ assert(this->reply == NULL);
+ done = 0;
}
- send_packet(req, &rep);
- rad_free(&rep);
}
- }
- if(do_summary) {
+
+ /*
+ * Still have outstanding requests.
+ */
+ if (lrad_packet_list_num_elements(pl) > 0) {
+ done = 0;
+ } else {
+ sleep_time = 0;
+ }
+
+ /*
+ * Nothing to do until we receive a request, so
+ * sleep until then. Once we receive one packet,
+ * we go back, and walk through the whole list again,
+ * sending more packets (if necessary), and updating
+ * the sleep time.
+ */
+ if (!done && (sleep_time > 0)) {
+ recv_one_packet(sleep_time);
+ }
+ } while (!done);
+
+ rbtree_free(filename_tree);
+ lrad_packet_list_free(pl);
+
+ if (do_summary) {
printf("\n\t Total approved auths: %d\n", totalapp);
printf("\t Total denied auths: %d\n", totaldeny);
+ printf("\t Total lost auths: %d\n", totallost);
}
+
return 0;
}