import from HEAD:
[freeradius.git] / src / main / radclient.c
index e4bea92..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,6 +54,7 @@ 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;
@@ -72,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;
@@ -98,7 +98,7 @@ static radclient_t *radclient_head = NULL;
 static radclient_t *radclient_tail = NULL;
 
 
-static void usage(void)
+static void NEVER_RETURNS usage(void)
 {
        fprintf(stderr, "Usage: radclient [options] server[:port] <command> [<secret>]\n");
 
@@ -106,12 +106,14 @@ static void usage(void)
        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");
 
@@ -119,7 +121,8 @@ 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(radclient_t *radclient)
 {
@@ -134,7 +137,7 @@ static void radclient_free(radclient_t *radclient)
        if (prev) {
                assert(radclient_head != radclient);
                prev->next = next;
-       } else {
+       } else if (radclient_head) {
                assert(radclient_head == radclient);
                radclient_head = next;
        }
@@ -142,7 +145,7 @@ static void radclient_free(radclient_t *radclient)
        if (next) {
                assert(radclient_tail != radclient);
                next->prev = prev;
-       } else {
+       } else if (radclient_tail) {
                assert(radclient_tail == radclient);
                radclient_tail = prev;
        }
@@ -209,19 +212,19 @@ static radclient_t *radclient_init(const char *filename)
                radclient->request->vps = readvp2(fp, &filedone, "radclient:");
                if (!radclient->request->vps) {
                        radclient_free(radclient);
-                       return NULL; /* memory leak "start" */
+                       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(vp->strvalue));
+                       strNcpy(radclient->password, (char *)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, (char *)vp->strvalue, sizeof(vp->strvalue));
+                       strNcpy(radclient->password, (char *)vp->strvalue, sizeof(radclient->password));
                } else {
                        radclient->password[0] = '\0';
                }
@@ -318,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.
@@ -419,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
@@ -483,12 +481,12 @@ 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;
@@ -506,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);
 
@@ -552,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++;
        }
@@ -560,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;
 }
@@ -612,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 */
        }
 
@@ -630,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 */
@@ -645,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;
@@ -697,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);
@@ -715,7 +711,7 @@ 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))
                                usage();
@@ -727,36 +723,36 @@ int main(int argc, char **argv)
                case 'f':
                        rbtree_insert(filename_tree, optarg);
                        break;
-               case 'q':
-                       do_output = 0;
-                       break;
-               case 'x':
-                       librad_debug++;
-                       break;
-               case 'r':
-                       if (!isdigit((int) *optarg))
-                               usage();
-                       retries = atoi(optarg);
-                       break;
                case 'i':
                        if (!isdigit((int) *optarg))
                                usage();
-                       id = atoi(optarg);
-                       if ((id < 0) || (id > 255)) {
+                       last_used_id = atoi(optarg);
+                       if ((last_used_id < 0) || (last_used_id > 255)) {
                                usage();
                        }
                        break;
-               case 's':
-                       do_summary = 1;
+
+               case 'n':
+                       persec = atoi(optarg);
+                       if (persec <= 0) usage();
                        break;
-               case 't':
+
+               case 'p':
+                       parallel = atoi(optarg);
+                       if (parallel <= 0) usage();
+                       break;
+
+               case 'q':
+                       do_output = 0;
+                       break;
+               case 'r':
                        if (!isdigit((int) *optarg))
                                usage();
-                       timeout = atof(optarg);
+                       retries = atoi(optarg);
+                       if ((retries == 0) || (retries > 1000)) usage();
                        break;
-               case 'v':
-                       printf("radclient: $Id$ built on " __DATE__ " at " __TIME__ "\n");
-                       exit(0);
+               case 's':
+                       do_summary = 1;
                        break;
                case 'S':
                       fp = fopen(optarg, "r");
@@ -786,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();
@@ -829,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;
@@ -879,7 +892,7 @@ int main(int argc, char **argv)
        /*
         *      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);
        }
 
@@ -901,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
@@ -913,6 +926,7 @@ int main(int argc, char **argv)
         *      loop.
         */
        do {
+               int n = parallel;
                radclient_t *next;
                const char *filename = NULL;
 
@@ -927,17 +941,83 @@ int main(int argc, char **argv)
                        next = this->next;
 
                        /*
+                        *      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.
+                        *      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;
-                               radclient_send(this);
-                               if (this->done) {
-                                       radclient_free(this);
+                               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 {
+                       } else { /* haven't sent this packet, we're not done */
                                assert(this->done == 0);
                                assert(this->reply == NULL);
                                done = 0;