import from HEAD:
[freeradius.git] / src / main / radclient.c
index 628b6f1..cd1d937 100644 (file)
@@ -24,7 +24,6 @@
 static const char rcsid[] = "$Id$";
 
 #include "autoconf.h"
-#include "libradius.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -55,12 +54,12 @@ static const char rcsid[] = "$Id$";
 #include "conf.h"
 #include "radpaths.h"
 #include "missing.h"
+#include "libradius.h"
 
 static int retries = 10;
 static float timeout = 3;
 static const char *secret = NULL;
 static int do_output = 1;
-static int filedone = 0;
 static int totalapp = 0;
 static int totaldeny = 0;
 static int totallost = 0;
@@ -73,7 +72,7 @@ static int done = 1;
 
 static int sockfd;
 static int radius_id[256];
-static int last_used_id = 0;
+static int last_used_id = -1;
 
 static rbtree_t *filename_tree = NULL;
 static rbtree_t *request_tree = NULL;
@@ -85,6 +84,7 @@ typedef struct radclient_t {
        struct          radclient_t *next;
 
        const char      *filename;
+       int             packet_number; /* in the file */
        char            password[256];
        time_t          timestamp;
        RADIUS_PACKET   *request;
@@ -98,28 +98,22 @@ static radclient_t *radclient_head = NULL;
 static radclient_t *radclient_tail = NULL;
 
 
-/*
- *     Read valuepairs from the fp up to End-Of-File.
- */
-static VALUE_PAIR *readvp(FILE *fp)
-{
-       return readvp2(fp, &filedone, "radclient:");
-}
-
-static void usage(void)
+static void NEVER_RETURNS usage(void)
 {
        fprintf(stderr, "Usage: radclient [options] server[:port] <command> [<secret>]\n");
-       
+
        fprintf(stderr, "  <command>    One of auth, acct, status, 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, "  -r retries  If timeout, retry sending the packet 'retries' times.\n");
-       fprintf(stderr, "  -t timeout  Wait 'timeout' seconds before retrying (may be a floating point number).\n");
        fprintf(stderr, "  -i id       Set request id to 'id'.  Values may be 0..255\n");
-       fprintf(stderr, "  -S file     read secret from file, not command line.\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");
 
@@ -127,31 +121,33 @@ static void usage(void)
 }
 
 /*
- *     Free a radclient struct
+ *     Free a radclient struct, which may (or may not)
+ *     already be in the list.
  */
-static void radclient_free(void *data)
+static void radclient_free(radclient_t *radclient)
 {
-       radclient_t *radclient = (radclient_t *) data;
-
-       if (!radclient) return;
+       radclient_t *prev, *next;
 
        if (radclient->request) rad_free(&radclient->request);
        if (radclient->reply) rad_free(&radclient->reply);
 
-       if (!radclient->prev) {
-               assert(radclient_head = radclient);
-               radclient_head = radclient->next;
-       } else {
+       prev = radclient->prev;
+       next = radclient->next;
+
+       if (prev) {
                assert(radclient_head != radclient);
-               radclient->prev->next = radclient->next;
+               prev->next = next;
+       } else if (radclient_head) {
+               assert(radclient_head == radclient);
+               radclient_head = next;
        }
 
-       if (!radclient->next) {
-               assert(radclient_tail = radclient);
-               radclient_tail = radclient->prev;
-       } else {
+       if (next) {
                assert(radclient_tail != radclient);
-               radclient->next->prev = radclient->prev;
+               next->prev = prev;
+       } else if (radclient_tail) {
+               assert(radclient_tail == radclient);
+               radclient_tail = prev;
        }
 
        free(radclient);
@@ -164,114 +160,131 @@ static radclient_t *radclient_init(const char *filename)
 {
        FILE *fp;
        VALUE_PAIR *vp;
-       radclient_t *radclient;
-
-       /*
-        *      Allocate it.
-        */
-       radclient = malloc(sizeof(*radclient));
-       if (!radclient) {
-               perror("radclient: ");
-               return NULL;
-       }
-       memset(radclient, 0, sizeof(*radclient));
-
-       radclient->request = rad_alloc(1);
-       if (!radclient->request) {
-               librad_perror("radclient: ");
-               radclient_free(radclient);
-               return NULL;
-       }
+       radclient_t *start, *radclient, *prev = NULL;
+       int filedone = 0;
+       int packet_number = 1;
 
-       radclient->filename = filename;
-       radclient->request->id = -1; /* allocate when sending */
+       start = NULL;
+       assert(filename != NULL);
 
        /*
-        *      Read valuepairs.
-        *      Maybe read them, from stdin, if there's no
-        *      filename, or if the filename is '-'.
+        *      Determine where to read the VP's from.
         */
-       if (filename && (strcmp(filename, "-") != 0)) {
+       if (strcmp(filename, "-") != 0) {
                fp = fopen(filename, "r");
                if (!fp) {
                        fprintf(stderr, "radclient: Error opening %s: %s\n",
                                filename, strerror(errno));
-                       radclient_free(radclient);
                        return NULL;
                }
        } else {
                fp = stdin;
        }
-       
-       /*
-        *      Read the VP's.
-        */
-       radclient->request->vps = readvp(fp);
-       if (fp != stdin) fclose(fp);
-       if (!radclient->request->vps) {
-               librad_perror("radclient: ");
-               radclient_free(radclient);
-               return NULL;
-       }
 
        /*
-        *      Keep a copy of the the User-Password attribute.
+        *      Loop until the file is done.
         */
-       if ((vp = pairfind(radclient->request->vps, PW_PASSWORD)) != NULL) {
-               strNcpy(radclient->password, (char *)vp->strvalue, sizeof(vp->strvalue));
+       do {
                /*
-                *      Otherwise keep a copy of the CHAP-Password attribute.
+                *      Allocate it.
                 */
-       } else if ((vp = pairfind(radclient->request->vps, PW_CHAP_PASSWORD)) != NULL) {
-               strNcpy(radclient->password, (char *)vp->strvalue, sizeof(vp->strvalue));
-       } 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;
+               radclient = malloc(sizeof(*radclient));
+               if (!radclient) {
+                       perror("radclient: ");
+                       return NULL; /* memory leak "start" */
+               }
+               memset(radclient, 0, sizeof(*radclient));
+
+               radclient->request = rad_alloc(1);
+               if (!radclient->request) {
+                       librad_perror("radclient: ");
+                       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_PASSWORD)) != NULL) {
+                       strNcpy(radclient->password, (char *)vp->strvalue, sizeof(radclient->password));
                        /*
-                        *      Allow it to set the packet type in
-                        *      the attributes read from the file.
+                        *      Otherwise keep a copy of the CHAP-Password attribute.
                         */
-               case PW_PACKET_TYPE:
-                       radclient->request->code = vp->lvalue;
-                       break;
-                       
-               case PW_PACKET_DST_PORT:
-                       radclient->request->dst_port = (vp->lvalue & 0xffff);
-                       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->strvalue[2], &vp->strvalue[0], vp->length);
-                       vp->strvalue[0] = vp->attribute - PW_DIGEST_REALM + 1;
-                       vp->length += 2;
-                       vp->strvalue[1] = vp->length;
-                       vp->attribute = PW_DIGEST_ATTRIBUTES;
-                       break;
+               } else if ((vp = pairfind(radclient->request->vps, PW_CHAP_PASSWORD)) != NULL) {
+                       strNcpy(radclient->password, (char *)vp->strvalue, sizeof(radclient->password));
+               } else {
+                       radclient->password[0] = '\0';
                }
-       } /* loop over the VP's we read in */
+
+               /*
+                *  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;
+
+                       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->strvalue[2], &vp->strvalue[0], vp->length);
+                               vp->strvalue[0] = vp->attribute - PW_DIGEST_REALM + 1;
+                               vp->length += 2;
+                               vp->strvalue[1] = vp->length;
+                               vp->attribute = PW_DIGEST_ATTRIBUTES;
+                               break;
+                       }
+               } /* loop over the VP's we read in */
+
+               if (!start) {
+                       start = radclient;
+                       prev = start;
+               } else {
+                       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 radclient;
+       return start;
 }
 
 
@@ -287,7 +300,8 @@ static int radclient_sane(radclient_t *radclient)
 
        if (radclient->request->code == 0) {
                if (packet_code == -1) {
-                       fprintf(stderr, "radclient: Request was \"auto\", but file %s did not contain Packet-Type\n", radclient->filename);
+                       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;
                }
 
@@ -307,11 +321,13 @@ static int filename_cmp(const void *one, const void *two)
        return strcmp((const char *) one, (const char *) two);
 }
 
-static int filename_walk(void *data)
+static int filename_walk(void *context, void *data)
 {
        const char      *filename = data;
        radclient_t     *radclient;
 
+       context = context;      /* -Wunused */
+
        /*
         *      Initialize the request we're about
         *      to send.
@@ -320,18 +336,23 @@ static int filename_walk(void *data)
        if (!radclient) {
                exit(1);
        }
-       
+
        if (!radclient_head) {
                assert(radclient_tail == NULL);
                radclient_head = radclient;
-               radclient_tail = radclient;
        } else {
                assert(radclient_tail->next == NULL);
                radclient_tail->next = radclient;
                radclient->prev = radclient_tail;
-               radclient_tail = radclient;
        }
 
+       /*
+        *      We may have had a list of "radclient" structures
+        *      returned to us.
+        */
+       while (radclient->next) radclient = radclient->next;
+       radclient_tail = radclient;
+
        return 0;
 }
 
@@ -403,14 +424,7 @@ static int send_one_packet(radclient_t *radclient)
 {
        int i;
 
-       /*
-        *      Sent this packet as many times as requested.
-        *      ignore it.
-        */
-       if (radclient->resend > resend_count) {
-               radclient->done = 1;
-               return 0;
-       }
+       assert(radclient->done == 0);
 
        /*
         *      Remember when we have to wake up, to re-send the
@@ -455,10 +469,10 @@ static int send_one_packet(radclient_t *radclient)
 
                assert(radclient->request->id != -1);
                assert(radclient->request->data == NULL);
-               
+
                librad_md5_calc(radclient->request->vector, radclient->request->vector,
                                sizeof(radclient->request->vector));
-               
+
                /*
                 *      Update the password, so it can be encrypted with the
                 *      new authentication vector.
@@ -467,13 +481,13 @@ static int send_one_packet(radclient_t *radclient)
                        VALUE_PAIR *vp;
 
                        if ((vp = pairfind(radclient->request->vps, PW_PASSWORD)) != NULL) {
-                               strNcpy((char *)vp->strvalue, radclient->password, strlen(radclient->password) + 1);
-                               vp->length = strlen(radclient->password);
-                               
+                               strNcpy((char *)vp->strvalue, radclient->password, sizeof(vp->strvalue));
+                               vp->length = strlen(vp->strvalue);
+
                        } else if ((vp = pairfind(radclient->request->vps, PW_CHAP_PASSWORD)) != NULL) {
-                               strNcpy((char *)vp->strvalue, radclient->password, strlen(radclient->password) + 1);
-                               vp->length = strlen(radclient->password);
-                               
+                               strNcpy((char *)vp->strvalue, radclient->password, sizeof(vp->strvalue));
+                               vp->length = strlen(vp->strvalue);
+
                                rad_chap_encode(radclient->request, (char *) vp->strvalue, radclient->request->id, vp);
                                vp->length = 17;
                        }
@@ -490,26 +504,6 @@ static int send_one_packet(radclient_t *radclient)
                        assert(0 == 1);
                }
 
-       } else if (radclient->tries == retries) {
-               rbnode_t *node;
-               assert(radclient->request->id >= 0);
-
-               /*
-                *      Delete the request from the tree of outstanding
-                *      requests.
-                */
-               node = rbtree_find(request_tree, radclient);
-               assert(node != NULL);
-
-               fprintf(stderr, "radclient: no response from server for ID %d\n", radclient->request->id);
-               rbtree_delete(request_tree, node);
-               totallost++;
-               return -1;
-
-               /*
-                *      FIXME: Do stuff for packet loss.
-                */
-
        } else {                /* radclient->request->id >= 0 */
                time_t now = time(NULL);
 
@@ -536,6 +530,37 @@ static int send_one_packet(radclient_t *radclient)
                        return 0;
                }
 
+               /*
+                *      We're not trying later, maybe the packet is done.
+                */
+               if (radclient->tries == retries) {
+                       rbnode_t *node;
+                       assert(radclient->request->id >= 0);
+                       
+                       /*
+                        *      Delete the request from the tree of
+                        *      outstanding requests.
+                        */
+                       node = rbtree_find(request_tree, radclient);
+                       assert(node != NULL);
+                       
+                       fprintf(stderr, "radclient: no response from server for ID %d\n", radclient->request->id);
+                       rbtree_delete(request_tree, node);
+                       
+                       /*
+                        *      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++;
        }
@@ -544,7 +569,10 @@ static int send_one_packet(radclient_t *radclient)
        /*
         *      Send the packet.
         */
-       rad_send(radclient->request, NULL, secret);
+       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);
+       }
 
        return 0;
 }
@@ -560,25 +588,25 @@ static int recv_one_packet(int wait_time)
        RADIUS_PACKET   myrequest, *reply;
        rbnode_t        *node;
 
-       
+
        /* And wait for reply, timing out as necessary */
        FD_ZERO(&set);
        FD_SET(sockfd, &set);
-       
+
        if (wait_time <= 0) {
                tv.tv_sec = 0;
        } else {
                tv.tv_sec = wait_time;
        }
        tv.tv_usec = 0;
-       
+
        /*
         *      No packet was received.
         */
        if (select(sockfd + 1, &set, NULL, NULL, &tv) != 1) {
                return 0;
        }
-       
+
        /*
         *      Look for the packet.
         */
@@ -596,6 +624,7 @@ static int recv_one_packet(int wait_time)
        node = rbtree_find(request_tree, &myclient);
        if (!node) {
                fprintf(stderr, "radclient: received response to request we did not send.\n");
+               rad_free(&reply);
                return -1;      /* got reply to packet we didn't send */
        }
 
@@ -614,7 +643,7 @@ static int recv_one_packet(int wait_time)
        if (rad_decode(reply, radclient->request, secret) != 0) {
                librad_perror("rad_decode");
                totallost++;
-               return -1;
+               goto packet_done; /* shared secret is incorrect */
        }
 
        /* libradius debug already prints out the value pairs for us */
@@ -629,33 +658,16 @@ static int recv_one_packet(int wait_time)
                totaldeny++;
        }
 
-       if (radclient->reply) rad_free(&radclient->reply);
-
-       return 0;
-}
-
-/*
- *     Walk over the tree, sending packets.
- */
-static int radclient_send(radclient_t *radclient)
-{
-       /*
-        *      Send the current packet.
-        */
-       send_one_packet(radclient);
-
-       /*
-        *      Do rad_recv(), and look for the response in the tree,
-        *      but don't wait for a response.
-        */
-       recv_one_packet(0);
+packet_done:
+       rad_free(&radclient->reply);
 
        /*
-        *      Still elements to wa
+        *      Once we've sent the packet as many times as requested,
+        *      mark it done.
         */
-       if (radclient->resend < resend_count) {
-               done = 0;
-               sleep_time = 0;
+       if (radclient->resend == resend_count) {
+               assert((node = rbtree_find(request_tree, radclient)) == NULL);
+               radclient->done = 1;
        }
 
        return 0;
@@ -681,10 +693,10 @@ int main(int argc, char **argv)
        char filesecret[256];
        FILE *fp;
        int do_summary = 0;
-       int id;
+       int persec = 0;
+       int parallel = 1;
        radclient_t     *this;
 
-       id = ((int)getpid() & 0xff);
        librad_debug = 0;
 
        filename_tree = rbtree_create(filename_cmp, NULL, 0);
@@ -699,9 +711,9 @@ int main(int argc, char **argv)
                exit(1);
        }
 
-       while ((c = getopt(argc, argv, "c:d:f:hi:qst:r:S:xv")) != EOF) switch(c) {
+       while ((c = getopt(argc, argv, "c:d:f:hi:n:p:qr:sS:t:vx")) != EOF) switch(c) {
                case 'c':
-                       if (!isdigit((int) *optarg)) 
+                       if (!isdigit((int) *optarg))
                                usage();
                        resend_count = atoi(optarg);
                        break;
@@ -711,37 +723,37 @@ int main(int argc, char **argv)
                case 'f':
                        rbtree_insert(filename_tree, optarg);
                        break;
+               case 'i':
+                       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;
+
+               case 'p':
+                       parallel = atoi(optarg);
+                       if (parallel <= 0) usage();
+                       break;
+
                case 'q':
                        do_output = 0;
                        break;
-               case 'x':
-                       librad_debug++;
-                       break;
                case 'r':
-                       if (!isdigit((int) *optarg)) 
+                       if (!isdigit((int) *optarg))
                                usage();
                        retries = atoi(optarg);
-                       break;
-               case 'i':
-                       if (!isdigit((int) *optarg)) 
-                               usage();
-                       id = atoi(optarg);
-                       if ((id < 0) || (id > 255)) {
-                               usage();
-                       }
+                       if ((retries == 0) || (retries > 1000)) usage();
                        break;
                case 's':
                        do_summary = 1;
                        break;
-               case 't':
-                       if (!isdigit((int) *optarg)) 
-                               usage();
-                       timeout = atof(optarg);
-                       break;
-               case 'v':
-                       printf("radclient: $Id$ built on " __DATE__ " at " __TIME__ "\n");
-                       exit(0);
-                       break;
                case 'S':
                       fp = fopen(optarg, "r");
                        if (!fp) {
@@ -770,6 +782,18 @@ int main(int argc, char **argv)
                        }
                        secret = filesecret;
                       break;
+               case 't':
+                       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();
@@ -813,6 +837,11 @@ int main(int argc, char **argv)
                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 (server_port == 0) server_port = getport("radacct");
                if (server_port == 0) server_port = PW_ACCT_UDP_PORT;
@@ -854,9 +883,16 @@ int main(int argc, char **argv)
        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, "-");
+       }
+
+       /*
         *      Walk over the list of filenames, creating the requests.
         */
-       if (rbtree_walk(filename_tree, filename_walk, InOrder) != 0) {
+       if (rbtree_walk(filename_tree, InOrder, filename_walk, NULL) != 0) {
                exit(1);
        }
 
@@ -878,7 +914,7 @@ int main(int argc, char **argv)
                }
        }
 
-       last_used_id = getpid() & 0xff;
+       if (last_used_id < 0) last_used_id = getpid() & 0xff;
 
        /*
         *      Walk over the packets to send, until
@@ -890,7 +926,9 @@ int main(int argc, char **argv)
         *      loop.
         */
        do {
+               int n = parallel;
                radclient_t *next;
+               const char *filename = NULL;
 
                done = 1;
                sleep_time = -1;
@@ -898,12 +936,91 @@ int main(int argc, char **argv)
                /*
                 *      Walk over the packets, sending them.
                 */
+
                for (this = radclient_head; this != NULL; this = next) {
                        next = this->next;
 
-                       radclient_send(this);
+                       /*
+                        *      If there's a packet to receive,
+                        *      receive it, but don't wait for a
+                        *      packet.
+                        */
+                       recv_one_packet(0);
+
+                       /*
+                        *      This packet is done.  Delete it.
+                        */
                        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);
+                               }
+
+                               /*
+                                *      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;
                        }
                }
 
@@ -912,6 +1029,8 @@ int main(int argc, char **argv)
                 */
                if (rbtree_num_elements(request_tree) > 0) {
                        done = 0;
+               } else {
+                       sleep_time = 0;
                }
 
                /*