OSDN Git Service

Update to v30-pre9
[android-x86/external-wireless-tools.git] / wireless_tools / iwlib.c
index ed3e357..cd22a8b 100644 (file)
 /*
  *     Wireless Tools
  *
- *             Jean II - HPLB 97->99 - HPL 99->01
+ *             Jean II - HPLB 97->99 - HPL 99->09
  *
  * Common subroutines to all the wireless tools...
  *
  * This file is released under the GPL license.
+ *     Copyright (c) 1997-2009 Jean Tourrilhes <jt@hpl.hp.com>
  */
 
-#include "iwlib.h"             /* Header */
+/***************************** INCLUDES *****************************/
+
+#include "iwlib-private.h"             /* Private header */
+
+/************************ CONSTANTS & MACROS ************************/
+
+/*
+ * Constants fof WE-9->15
+ */
+#define IW15_MAX_FREQUENCIES   16
+#define IW15_MAX_BITRATES      8
+#define IW15_MAX_TXPOWER       8
+#define IW15_MAX_ENCODING_SIZES        8
+#define IW15_MAX_SPY           8
+#define IW15_MAX_AP            8
+
+/****************************** TYPES ******************************/
+
+/*
+ *     Struct iw_range up to WE-15
+ */
+struct iw15_range
+{
+       __u32           throughput;
+       __u32           min_nwid;
+       __u32           max_nwid;
+       __u16           num_channels;
+       __u8            num_frequency;
+       struct iw_freq  freq[IW15_MAX_FREQUENCIES];
+       __s32           sensitivity;
+       struct iw_quality       max_qual;
+       __u8            num_bitrates;
+       __s32           bitrate[IW15_MAX_BITRATES];
+       __s32           min_rts;
+       __s32           max_rts;
+       __s32           min_frag;
+       __s32           max_frag;
+       __s32           min_pmp;
+       __s32           max_pmp;
+       __s32           min_pmt;
+       __s32           max_pmt;
+       __u16           pmp_flags;
+       __u16           pmt_flags;
+       __u16           pm_capa;
+       __u16           encoding_size[IW15_MAX_ENCODING_SIZES];
+       __u8            num_encoding_sizes;
+       __u8            max_encoding_tokens;
+       __u16           txpower_capa;
+       __u8            num_txpower;
+       __s32           txpower[IW15_MAX_TXPOWER];
+       __u8            we_version_compiled;
+       __u8            we_version_source;
+       __u16           retry_capa;
+       __u16           retry_flags;
+       __u16           r_time_flags;
+       __s32           min_retry;
+       __s32           max_retry;
+       __s32           min_r_time;
+       __s32           max_r_time;
+       struct iw_quality       avg_qual;
+};
+
+/*
+ * Union for all the versions of iwrange.
+ * Fortunately, I mostly only add fields at the end, and big-bang
+ * reorganisations are few.
+ */
+union  iw_range_raw
+{
+       struct iw15_range       range15;        /* WE 9->15 */
+       struct iw_range         range;          /* WE 16->current */
+};
+
+/*
+ * Offsets in iw_range struct
+ */
+#define iwr15_off(f)   ( ((char *) &(((struct iw15_range *) NULL)->f)) - \
+                         (char *) NULL)
+#define iwr_off(f)     ( ((char *) &(((struct iw_range *) NULL)->f)) - \
+                         (char *) NULL)
+
+/*
+ * Union to perform unaligned access when working around alignement issues
+ */
+union  iw_align_u16
+{
+       __u16           value;
+       unsigned char   byte[2];
+};
 
 /**************************** VARIABLES ****************************/
 
-const char *   iw_operation_mode[] = { "Auto",
+/* Modes as human readable strings */
+const char * const iw_operation_mode[] = { "Auto",
                                        "Ad-Hoc",
                                        "Managed",
                                        "Master",
                                        "Repeater",
-                                       "Secondary" };
+                                       "Secondary",
+                                       "Monitor",
+                                       "Unknown/bug" };
+
+/* Modulations as human readable strings */
+const struct iw_modul_descr    iw_modul_list[] = {
+  /* Start with aggregate types, so that they display first */
+  { IW_MODUL_11AG, "11ag",
+    "IEEE 802.11a + 802.11g (2.4 & 5 GHz, up to 54 Mb/s)" },
+  { IW_MODUL_11AB, "11ab",
+    "IEEE 802.11a + 802.11b (2.4 & 5 GHz, up to 54 Mb/s)" },
+  { IW_MODUL_11G, "11g", "IEEE 802.11g (2.4 GHz, up to 54 Mb/s)" },
+  { IW_MODUL_11A, "11a", "IEEE 802.11a (5 GHz, up to 54 Mb/s)" },
+  { IW_MODUL_11B, "11b", "IEEE 802.11b (2.4 GHz, up to 11 Mb/s)" },
+
+  /* Proprietary aggregates */
+  { IW_MODUL_TURBO | IW_MODUL_11A, "turboa",
+    "Atheros turbo mode at 5 GHz (up to 108 Mb/s)" },
+  { IW_MODUL_TURBO | IW_MODUL_11G, "turbog",
+    "Atheros turbo mode at 2.4 GHz (up to 108 Mb/s)" },
+  { IW_MODUL_PBCC | IW_MODUL_11B, "11+",
+    "TI 802.11+ (2.4 GHz, up to 22 Mb/s)" },
+
+  /* Individual modulations */
+  { IW_MODUL_OFDM_G, "OFDMg",
+    "802.11g higher rates, OFDM at 2.4 GHz (up to 54 Mb/s)" },
+  { IW_MODUL_OFDM_A, "OFDMa", "802.11a, OFDM at 5 GHz (up to 54 Mb/s)" },
+  { IW_MODUL_CCK, "CCK", "802.11b higher rates (2.4 GHz, up to 11 Mb/s)" },
+  { IW_MODUL_DS, "DS", "802.11 Direct Sequence (2.4 GHz, up to 2 Mb/s)" },
+  { IW_MODUL_FH, "FH", "802.11 Frequency Hopping (2,4 GHz, up to 2 Mb/s)" },
+
+  /* Proprietary modulations */
+  { IW_MODUL_TURBO, "turbo",
+    "Atheros turbo mode, channel bonding (up to 108 Mb/s)" },
+  { IW_MODUL_PBCC, "PBCC",
+    "TI 802.11+ higher rates (2.4 GHz, up to 22 Mb/s)" },
+  { IW_MODUL_CUSTOM, "custom",
+    "Driver specific modulation (check driver documentation)" },
+};
+
+/* Disable runtime version warning in iw_get_range_info() */
+int    iw_ignore_version = 0;
 
 /************************ SOCKET SUBROUTINES *************************/
 
@@ -30,95 +161,433 @@ const char *      iw_operation_mode[] = { "Auto",
 int
 iw_sockets_open(void)
 {
-        int ipx_sock = -1;              /* IPX socket                   */
-        int ax25_sock = -1;             /* AX.25 socket                 */
-        int inet_sock = -1;             /* INET socket                  */
-        int ddp_sock = -1;              /* Appletalk DDP socket         */
-
-        /*
-         * Now pick any (exisiting) useful socket family for generic queries
-        * Note : don't open all the socket, only returns when one matches,
-        * all protocols might not be valid.
-        * Workaround by Jim Kaba <jkaba@sarnoff.com>
-        * Note : in 99% of the case, we will just open the inet_sock.
-        * The remaining 1% case are not fully correct...
-         */
-        inet_sock=socket(AF_INET, SOCK_DGRAM, 0);
-        if(inet_sock!=-1)
-                return inet_sock;
-        ipx_sock=socket(AF_IPX, SOCK_DGRAM, 0);
-        if(ipx_sock!=-1)
-                return ipx_sock;
-        ax25_sock=socket(AF_AX25, SOCK_DGRAM, 0);
-        if(ax25_sock!=-1)
-                return ax25_sock;
-        ddp_sock=socket(AF_APPLETALK, SOCK_DGRAM, 0);
-        /*
-         * If this is -1 we have no known network layers and its time to jump.
-         */
-        return ddp_sock;
+  static const int families[] = {
+    AF_INET, AF_IPX, AF_AX25, AF_APPLETALK
+  };
+  unsigned int i;
+  int          sock;
+
+  /*
+   * Now pick any (exisiting) useful socket family for generic queries
+   * Note : don't open all the socket, only returns when one matches,
+   * all protocols might not be valid.
+   * Workaround by Jim Kaba <jkaba@sarnoff.com>
+   * Note : in 99% of the case, we will just open the inet_sock.
+   * The remaining 1% case are not fully correct...
+   */
+
+  /* Try all families we support */
+  for(i = 0; i < sizeof(families)/sizeof(int); ++i)
+    {
+      /* Try to open the socket, if success returns it */
+      sock = socket(families[i], SOCK_DGRAM, 0);
+      if(sock >= 0)
+       return sock;
+  }
+
+  return -1;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Extract the interface name out of /proc/net/wireless or /proc/net/dev.
+ */
+static inline char *
+iw_get_ifname(char *   name,   /* Where to store the name */
+             int       nsize,  /* Size of name buffer */
+             char *    buf)    /* Current position in buffer */
+{
+  char *       end;
+
+  /* Skip leading spaces */
+  while(isspace(*buf))
+    buf++;
+
+#ifndef IW_RESTRIC_ENUM
+  /* Get name up to the last ':'. Aliases may contain ':' in them,
+   * but the last one should be the separator */
+  end = strrchr(buf, ':');
+#else
+  /* Get name up to ": "
+   * Note : we compare to ": " to make sure to process aliased interfaces
+   * properly. Doesn't work on /proc/net/dev, because it doesn't guarantee
+   * a ' ' after the ':'*/
+  end = strstr(buf, ": ");
+#endif
+
+  /* Not found ??? To big ??? */
+  if((end == NULL) || (((end - buf) + 1) > nsize))
+    return(NULL);
+
+  /* Copy */
+  memcpy(name, buf, (end - buf));
+  name[end - buf] = '\0';
+
+  /* Return value currently unused, just make sure it's non-NULL */
+  return(end);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Enumerate devices and call specified routine
+ * The new way just use /proc/net/wireless, so get all wireless interfaces,
+ * whether configured or not. This is the default if available.
+ * The old way use SIOCGIFCONF, so get only configured interfaces (wireless
+ * or not).
+ */
+void
+iw_enum_devices(int            skfd,
+               iw_enum_handler fn,
+               char *          args[],
+               int             count)
+{
+  char         buff[1024];
+  FILE *       fh;
+  struct ifconf ifc;
+  struct ifreq *ifr;
+  int          i;
+
+#ifndef IW_RESTRIC_ENUM
+  /* Check if /proc/net/dev is available */
+  fh = fopen(PROC_NET_DEV, "r");
+#else
+  /* Check if /proc/net/wireless is available */
+  fh = fopen(PROC_NET_WIRELESS, "r");
+#endif
+
+  if(fh != NULL)
+    {
+      /* Success : use data from /proc/net/wireless */
+
+      /* Eat 2 lines of header */
+      fgets(buff, sizeof(buff), fh);
+      fgets(buff, sizeof(buff), fh);
+
+      /* Read each device line */
+      while(fgets(buff, sizeof(buff), fh))
+       {
+         char  name[IFNAMSIZ + 1];
+         char *s;
+
+         /* Skip empty or almost empty lines. It seems that in some
+          * cases fgets return a line with only a newline. */
+         if((buff[0] == '\0') || (buff[1] == '\0'))
+           continue;
+
+         /* Extract interface name */
+         s = iw_get_ifname(name, sizeof(name), buff);
+
+         if(!s)
+           {
+             /* Failed to parse, complain and continue */
+#ifndef IW_RESTRIC_ENUM
+             fprintf(stderr, "Cannot parse " PROC_NET_DEV "\n");
+#else
+             fprintf(stderr, "Cannot parse " PROC_NET_WIRELESS "\n");
+#endif
+           }
+         else
+           /* Got it, print info about this interface */
+           (*fn)(skfd, name, args, count);
+       }
+
+      fclose(fh);
+    }
+  else
+    {
+      /* Get list of configured devices using "traditional" way */
+      ifc.ifc_len = sizeof(buff);
+      ifc.ifc_buf = buff;
+      if(ioctl(skfd, SIOCGIFCONF, &ifc) < 0)
+       {
+         fprintf(stderr, "SIOCGIFCONF: %s\n", strerror(errno));
+         return;
+       }
+      ifr = ifc.ifc_req;
+
+      /* Print them */
+      for(i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; ifr++)
+       (*fn)(skfd, ifr->ifr_name, args, count);
+    }
 }
 
 /*********************** WIRELESS SUBROUTINES ************************/
 
 /*------------------------------------------------------------------*/
 /*
+ * Extract WE version number from /proc/net/wireless
+ * In most cases, you really want to get version information from
+ * the range info (range->we_version_compiled), see below...
+ *
+ * If we have WE-16 and later, the WE version is available at the
+ * end of the header line of the file.
+ * For version prior to that, we can only detect the change from
+ * v11 to v12, so we do an approximate job. Fortunately, v12 to v15
+ * are highly binary compatible (on the struct level).
+ */
+int
+iw_get_kernel_we_version(void)
+{
+  char         buff[1024];
+  FILE *       fh;
+  char *       p;
+  int          v;
+
+  /* Check if /proc/net/wireless is available */
+  fh = fopen(PROC_NET_WIRELESS, "r");
+
+  if(fh == NULL)
+    {
+      fprintf(stderr, "Cannot read " PROC_NET_WIRELESS "\n");
+      return(-1);
+    }
+
+  /* Read the first line of buffer */
+  fgets(buff, sizeof(buff), fh);
+
+  if(strstr(buff, "| WE") == NULL)
+    {
+      /* Prior to WE16, so explicit version not present */
+
+      /* Black magic */
+      if(strstr(buff, "| Missed") == NULL)
+       v = 11;
+      else
+       v = 15;
+      fclose(fh);
+      return(v);
+    }
+
+  /* Read the second line of buffer */
+  fgets(buff, sizeof(buff), fh);
+
+  /* Get to the last separator, to get the version */
+  p = strrchr(buff, '|');
+  if((p == NULL) || (sscanf(p + 1, "%d", &v) != 1))
+    {
+      fprintf(stderr, "Cannot parse " PROC_NET_WIRELESS "\n");
+      fclose(fh);
+      return(-1);
+    }
+
+  fclose(fh);
+  return(v);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Print the WE versions of the interface.
+ */
+static int
+print_iface_version_info(int   skfd,
+                        char * ifname,
+                        char * args[],         /* Command line args */
+                        int    count)          /* Args count */
+{
+  struct iwreq         wrq;
+  char                 buffer[sizeof(iwrange) * 2];    /* Large enough */
+  struct iw_range *    range;
+
+  /* Avoid "Unused parameter" warning */
+  args = args; count = count;
+
+  /* If no wireless name : no wireless extensions.
+   * This enable us to treat the SIOCGIWRANGE failure below properly. */
+  if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0)
+    return(-1);
+
+  /* Cleanup */
+  memset(buffer, 0, sizeof(buffer));
+
+  wrq.u.data.pointer = (caddr_t) buffer;
+  wrq.u.data.length = sizeof(buffer);
+  wrq.u.data.flags = 0;
+  if(iw_get_ext(skfd, ifname, SIOCGIWRANGE, &wrq) < 0)
+    {
+      /* Interface support WE (see above), but not IWRANGE */
+      fprintf(stderr, "%-8.16s  Driver has no Wireless Extension version information.\n\n", ifname);
+      return(0);
+    }
+
+  /* Copy stuff at the right place, ignore extra */
+  range = (struct iw_range *) buffer;
+
+  /* For new versions, we can check the version directly, for old versions
+   * we use magic. 300 bytes is a also magic number, don't touch... */
+  if(wrq.u.data.length >= 300)
+    {
+      /* Version is always at the same offset, so it's ok */
+      printf("%-8.16s  Recommend Wireless Extension v%d or later,\n",
+            ifname, range->we_version_source);
+      printf("          Currently compiled with Wireless Extension v%d.\n\n",
+            range->we_version_compiled);
+    }
+  else
+    {
+      fprintf(stderr, "%-8.16s  Wireless Extension version too old.\n\n",
+                     ifname);
+    }
+
+
+  return(0);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Print the WE versions of the tools.
+ */
+int
+iw_print_version_info(const char *     toolname)
+{
+  int          skfd;                   /* generic raw socket desc.     */
+  int          we_kernel_version;
+
+  /* Create a channel to the NET kernel. */
+  if((skfd = iw_sockets_open()) < 0)
+    {
+      perror("socket");
+      return -1;
+    }
+
+  /* Information about the tools themselves */
+  if(toolname != NULL)
+    printf("%-8.16s  Wireless-Tools version %d\n", toolname, WT_VERSION);
+  printf("          Compatible with Wireless Extension v11 to v%d.\n\n",
+        WE_MAX_VERSION);
+
+  /* Get version from kernel */
+  we_kernel_version = iw_get_kernel_we_version();
+  /* Only version >= 16 can be verified, other are guessed */
+  if(we_kernel_version > 15)
+    printf("Kernel    Currently compiled with Wireless Extension v%d.\n\n",
+          we_kernel_version);
+
+  /* Version for each device */
+  iw_enum_devices(skfd, &print_iface_version_info, NULL, 0);
+
+  iw_sockets_close(skfd);
+
+  return 0;
+}
+
+/*------------------------------------------------------------------*/
+/*
  * Get the range information out of the driver
  */
 int
 iw_get_range_info(int          skfd,
-                 char *        ifname,
+                 const char *  ifname,
                  iwrange *     range)
 {
   struct iwreq         wrq;
   char                 buffer[sizeof(iwrange) * 2];    /* Large enough */
+  union iw_range_raw * range_raw;
 
   /* Cleanup */
-  memset(buffer, 0, sizeof(range));
+  bzero(buffer, sizeof(buffer));
 
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
   wrq.u.data.pointer = (caddr_t) buffer;
-  wrq.u.data.length = 0;
+  wrq.u.data.length = sizeof(buffer);
   wrq.u.data.flags = 0;
-  if(ioctl(skfd, SIOCGIWRANGE, &wrq) < 0)
+  if(iw_get_ext(skfd, ifname, SIOCGIWRANGE, &wrq) < 0)
     return(-1);
 
-  /* Copy stuff at the right place, ignore extra */
-  memcpy((char *) range, buffer, sizeof(iwrange));
-
-  /* Lot's of people have driver and tools out of sync as far as Wireless
-   * Extensions are concerned. It's because /usr/include/linux/wireless.h
-   * and /usr/src/linux/include/linux/wireless.h are different.
-   * We try to catch this stuff here... */
+  /* Point to the buffer */
+  range_raw = (union iw_range_raw *) buffer;
 
   /* For new versions, we can check the version directly, for old versions
    * we use magic. 300 bytes is a also magic number, don't touch... */
-  if((WIRELESS_EXT > 10) && (wrq.u.data.length >= 300))
+  if(wrq.u.data.length < 300)
     {
-#if WIRELESS_EXT > 10
-      /* Version verification - for new versions */
-      if(range->we_version_compiled != WIRELESS_EXT)
-       {
-         fprintf(stderr, "Warning : Device %s has been compiled with version %d\n", ifname, range->we_version_compiled);
-         fprintf(stderr, "of Wireless Extension, while we are using version %d.\n", WIRELESS_EXT);
-         fprintf(stderr, "Some things may be broken...\n\n");
-       }
-#endif /* WIRELESS_EXT > 10 */
+      /* That's v10 or earlier. Ouch ! Let's make a guess...*/
+      range_raw->range.we_version_compiled = 9;
+    }
+
+  /* Check how it needs to be processed */
+  if(range_raw->range.we_version_compiled > 15)
+    {
+      /* This is our native format, that's easy... */
+      /* Copy stuff at the right place, ignore extra */
+      memcpy((char *) range, buffer, sizeof(iwrange));
     }
   else
     {
-      /* Version verification - for old versions */
-      if(wrq.u.data.length != sizeof(iwrange))
+      /* Zero unknown fields */
+      bzero((char *) range, sizeof(struct iw_range));
+
+      /* Initial part unmoved */
+      memcpy((char *) range,
+            buffer,
+            iwr15_off(num_channels));
+      /* Frequencies pushed futher down towards the end */
+      memcpy((char *) range + iwr_off(num_channels),
+            buffer + iwr15_off(num_channels),
+            iwr15_off(sensitivity) - iwr15_off(num_channels));
+      /* This one moved up */
+      memcpy((char *) range + iwr_off(sensitivity),
+            buffer + iwr15_off(sensitivity),
+            iwr15_off(num_bitrates) - iwr15_off(sensitivity));
+      /* This one goes after avg_qual */
+      memcpy((char *) range + iwr_off(num_bitrates),
+            buffer + iwr15_off(num_bitrates),
+            iwr15_off(min_rts) - iwr15_off(num_bitrates));
+      /* Number of bitrates has changed, put it after */
+      memcpy((char *) range + iwr_off(min_rts),
+            buffer + iwr15_off(min_rts),
+            iwr15_off(txpower_capa) - iwr15_off(min_rts));
+      /* Added encoding_login_index, put it after */
+      memcpy((char *) range + iwr_off(txpower_capa),
+            buffer + iwr15_off(txpower_capa),
+            iwr15_off(txpower) - iwr15_off(txpower_capa));
+      /* Hum... That's an unexpected glitch. Bummer. */
+      memcpy((char *) range + iwr_off(txpower),
+            buffer + iwr15_off(txpower),
+            iwr15_off(avg_qual) - iwr15_off(txpower));
+      /* Avg qual moved up next to max_qual */
+      memcpy((char *) range + iwr_off(avg_qual),
+            buffer + iwr15_off(avg_qual),
+            sizeof(struct iw_quality));
+    }
+
+  /* We are now checking much less than we used to do, because we can
+   * accomodate more WE version. But, there are still cases where things
+   * will break... */
+  if(!iw_ignore_version)
+    {
+      /* We don't like very old version (unfortunately kernel 2.2.X) */
+      if(range->we_version_compiled <= 10)
+       {
+         fprintf(stderr, "Warning: Driver for device %s has been compiled with an ancient version\n", ifname);
+         fprintf(stderr, "of Wireless Extension, while this program support version 11 and later.\n");
+         fprintf(stderr, "Some things may be broken...\n\n");
+       }
+
+      /* We don't like future versions of WE, because we can't cope with
+       * the unknown */
+      if(range->we_version_compiled > WE_MAX_VERSION)
        {
-         fprintf(stderr, "Warning : Device %s has been compiled with a different version\n", ifname);
-         fprintf(stderr, "of Wireless Extension than ours (we are using version %d).\n", WIRELESS_EXT);
+         fprintf(stderr, "Warning: Driver for device %s has been compiled with version %d\n", ifname, range->we_version_compiled);
+         fprintf(stderr, "of Wireless Extension, while this program supports up to version %d.\n", WE_MAX_VERSION);
          fprintf(stderr, "Some things may be broken...\n\n");
        }
+
+      /* Driver version verification */
+      if((range->we_version_compiled > 10) &&
+        (range->we_version_compiled < range->we_version_source))
+       {
+         fprintf(stderr, "Warning: Driver for device %s recommend version %d of Wireless Extension,\n", ifname, range->we_version_source);
+         fprintf(stderr, "but has been compiled with version %d, therefore some driver features\n", range->we_version_compiled);
+         fprintf(stderr, "may not be available...\n\n");
+       }
+      /* Note : we are only trying to catch compile difference, not source.
+       * If the driver source has not been updated to the latest, it doesn't
+       * matter because the new fields are set to zero */
     }
 
-  /* Note : we are only trying to catch compile difference, not source.
-   * If the driver source has not been updated to the latest, it doesn't
-   * matter because the new fields are set to zero */
+  /* Don't complain twice.
+   * In theory, the test apply to each individual driver, but usually
+   * all drivers are compiled from the same kernel. */
+  iw_ignore_version = 1;
 
   return(0);
 }
@@ -126,24 +595,70 @@ iw_get_range_info(int             skfd,
 /*------------------------------------------------------------------*/
 /*
  * Get information about what private ioctls are supported by the driver
+ *
+ * Note : there is one danger using this function. If it return 0, you
+ * still need to free() the buffer. Beware.
  */
 int
 iw_get_priv_info(int           skfd,
-                char *         ifname,
-                iwprivargs *   priv)
+                const char *   ifname,
+                iwprivargs **  ppriv)
 {
   struct iwreq         wrq;
+  iwprivargs *         priv = NULL;    /* Not allocated yet */
+  int                  maxpriv = 16;   /* Minimum for compatibility WE<13 */
+  iwprivargs *         newpriv;
+
+  /* Some driver may return a very large number of ioctls. Some
+   * others a very small number. We now use a dynamic allocation
+   * of the array to satisfy everybody. Of course, as we don't know
+   * in advance the size of the array, we try various increasing
+   * sizes. Jean II */
+  do
+    {
+      /* (Re)allocate the buffer */
+      newpriv = realloc(priv, maxpriv * sizeof(priv[0]));
+      if(newpriv == NULL)
+       {
+         fprintf(stderr, "%s: Allocation failed\n", __FUNCTION__);
+         break;
+       }
+      priv = newpriv;
 
-  /* Ask the driver */
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-  wrq.u.data.pointer = (caddr_t) priv;
-  wrq.u.data.length = 0;
-  wrq.u.data.flags = 0;
-  if(ioctl(skfd, SIOCGIWPRIV, &wrq) < 0)
-    return(-1);
+      /* Ask the driver if it's large enough */
+      wrq.u.data.pointer = (caddr_t) priv;
+      wrq.u.data.length = maxpriv;
+      wrq.u.data.flags = 0;
+      if(iw_get_ext(skfd, ifname, SIOCGIWPRIV, &wrq) >= 0)
+       {
+         /* Success. Pass the buffer by pointer */
+         *ppriv = priv;
+         /* Return the number of ioctls */
+         return(wrq.u.data.length);
+       }
+
+      /* Only E2BIG means the buffer was too small, abort on other errors */
+      if(errno != E2BIG)
+       {
+         /* Most likely "not supported". Don't barf. */
+         break;
+       }
+
+      /* Failed. We probably need a bigger buffer. Check if the kernel
+       * gave us any hints. */
+      if(wrq.u.data.length > maxpriv)
+       maxpriv = wrq.u.data.length;
+      else
+       maxpriv *= 2;
+    }
+  while(maxpriv < 1000);
+
+  /* Cleanup */
+  if(priv)
+    free(priv);
+  *ppriv = NULL;
 
-  /* Return the number of ioctls */
-  return(wrq.u.data.length);
+  return(-1);
 }
 
 /*------------------------------------------------------------------*/
@@ -156,7 +671,7 @@ iw_get_priv_info(int                skfd,
  */
 int
 iw_get_basic_config(int                        skfd,
-                   char *              ifname,
+                   const char *        ifname,
                    wireless_config *   info)
 {
   struct iwreq         wrq;
@@ -164,35 +679,35 @@ iw_get_basic_config(int                   skfd,
   memset((char *) info, 0, sizeof(struct wireless_config));
 
   /* Get wireless name */
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-  if(ioctl(skfd, SIOCGIWNAME, &wrq) < 0)
+  if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0)
     /* If no wireless name : no wireless extensions */
     return(-1);
   else
-    strcpy(info->name, wrq.u.name);
+    {
+      strncpy(info->name, wrq.u.name, IFNAMSIZ);
+      info->name[IFNAMSIZ] = '\0';
+    }
 
   /* Get network ID */
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-  if(ioctl(skfd, SIOCGIWNWID, &wrq) >= 0)
+  if(iw_get_ext(skfd, ifname, SIOCGIWNWID, &wrq) >= 0)
     {
       info->has_nwid = 1;
       memcpy(&(info->nwid), &(wrq.u.nwid), sizeof(iwparam));
     }
 
   /* Get frequency / channel */
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-  if(ioctl(skfd, SIOCGIWFREQ, &wrq) >= 0)
+  if(iw_get_ext(skfd, ifname, SIOCGIWFREQ, &wrq) >= 0)
     {
       info->has_freq = 1;
       info->freq = iw_freq2float(&(wrq.u.freq));
+      info->freq_flags = wrq.u.freq.flags;
     }
 
   /* Get encryption information */
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
   wrq.u.data.pointer = (caddr_t) info->key;
-  wrq.u.data.length = 0;
+  wrq.u.data.length = IW_ENCODING_TOKEN_MAX;
   wrq.u.data.flags = 0;
-  if(ioctl(skfd, SIOCGIWENCODE, &wrq) >= 0)
+  if(iw_get_ext(skfd, ifname, SIOCGIWENCODE, &wrq) >= 0)
     {
       info->has_key = 1;
       info->key_size = wrq.u.data.length;
@@ -200,23 +715,25 @@ iw_get_basic_config(int                   skfd,
     }
 
   /* Get ESSID */
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
   wrq.u.essid.pointer = (caddr_t) info->essid;
-  wrq.u.essid.length = 0;
+  wrq.u.essid.length = IW_ESSID_MAX_SIZE + 2;
   wrq.u.essid.flags = 0;
-  if(ioctl(skfd, SIOCGIWESSID, &wrq) >= 0)
+  if(iw_get_ext(skfd, ifname, SIOCGIWESSID, &wrq) >= 0)
     {
       info->has_essid = 1;
       info->essid_on = wrq.u.data.flags;
+      info->essid_len = wrq.u.essid.length;
     }
 
   /* Get operation mode */
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-  if(ioctl(skfd, SIOCGIWMODE, &wrq) >= 0)
+  if(iw_get_ext(skfd, ifname, SIOCGIWMODE, &wrq) >= 0)
     {
-      if((wrq.u.mode < 6) && (wrq.u.mode >= 0))
-       info->has_mode = 1;
-      info->mode = wrq.u.mode;
+      info->has_mode = 1;
+      /* Note : event->u.mode is unsigned, no need to check <= 0 */
+      if(wrq.u.mode < IW_NUM_OPER_MODE)
+       info->mode = wrq.u.mode;
+      else
+       info->mode = IW_NUM_OPER_MODE;  /* Unknown/bug */
     }
 
   return(0);
@@ -231,28 +748,29 @@ iw_get_basic_config(int                   skfd,
  */
 int
 iw_set_basic_config(int                        skfd,
-                   char *              ifname,
+                   const char *        ifname,
                    wireless_config *   info)
 {
   struct iwreq         wrq;
   int                  ret = 0;
 
   /* Get wireless name (check if interface is valid) */
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-  if(ioctl(skfd, SIOCGIWNAME, &wrq) < 0)
+  if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0)
     /* If no wireless name : no wireless extensions */
     return(-2);
 
-  /* Set Network ID, if available (this is for non-802.11 cards) */
-  if(info->has_nwid)
+  /* Set the current mode of operation
+   * Mode need to be first : some settings apply only in a specific mode
+   * (such as frequency).
+   */
+  if(info->has_mode)
     {
       strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-      memcpy(&(wrq.u.nwid), &(info->nwid), sizeof(iwparam));
-      wrq.u.nwid.fixed = 1;    /* Hum... When in Rome... */
+      wrq.u.mode = info->mode;
 
-      if(ioctl(skfd, SIOCSIWNWID, &wrq) < 0)
+      if(iw_get_ext(skfd, ifname, SIOCSIWMODE, &wrq) < 0)
        {
-         fprintf(stderr, "SIOCSIWNWID: %s\n", strerror(errno));
+         fprintf(stderr, "SIOCSIWMODE: %s\n", strerror(errno));
          ret = -1;
        }
     }
@@ -260,10 +778,9 @@ iw_set_basic_config(int                    skfd,
   /* Set frequency / channel */
   if(info->has_freq)
     {
-      strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
       iw_float2freq(info->freq, &(wrq.u.freq));
 
-      if(ioctl(skfd, SIOCSIWFREQ, &wrq) < 0)
+      if(iw_set_ext(skfd, ifname, SIOCSIWFREQ, &wrq) < 0)
        {
          fprintf(stderr, "SIOCSIWFREQ: %s\n", strerror(errno));
          ret = -1;
@@ -279,12 +796,11 @@ iw_set_basic_config(int                   skfd,
       if((flags & IW_ENCODE_INDEX) > 0)
        {
          /* Set the index */
-         strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
          wrq.u.data.pointer = (caddr_t) NULL;
-         wrq.u.data.flags = flags & (IW_ENCODE_INDEX);
+         wrq.u.data.flags = (flags & (IW_ENCODE_INDEX)) | IW_ENCODE_NOKEY;
          wrq.u.data.length = 0;
 
-         if(ioctl(skfd, SIOCSIWENCODE, &wrq) < 0)
+         if(iw_set_ext(skfd, ifname, SIOCSIWENCODE, &wrq) < 0)
            {
              fprintf(stderr, "SIOCSIWENCODE(%d): %s\n",
                      errno, strerror(errno));
@@ -296,12 +812,15 @@ iw_set_basic_config(int                   skfd,
       flags = flags & (~IW_ENCODE_INDEX);
 
       /* Set the key itself (set current key in this case) */
-      strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
       wrq.u.data.pointer = (caddr_t) info->key;
       wrq.u.data.length = info->key_size;
       wrq.u.data.flags = flags;
 
-      if(ioctl(skfd, SIOCSIWENCODE, &wrq) < 0)
+      /* Compatibility with WE<13 */
+      if(flags & IW_ENCODE_NOKEY)
+       wrq.u.data.pointer = NULL;
+
+      if(iw_set_ext(skfd, ifname, SIOCSIWENCODE, &wrq) < 0)
        {
          fprintf(stderr, "SIOCSIWENCODE(%d): %s\n",
                  errno, strerror(errno));
@@ -309,30 +828,38 @@ iw_set_basic_config(int                   skfd,
        }
     }
 
-  /* Set ESSID (extended network), if available */
-  if(info->has_essid)
+  /* Set Network ID, if available (this is for non-802.11 cards) */
+  if(info->has_nwid)
     {
-      strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-      wrq.u.essid.pointer = (caddr_t) info->essid;
-      wrq.u.essid.length = strlen(info->essid) + 1;
-      wrq.u.data.flags = info->essid_on;
+      memcpy(&(wrq.u.nwid), &(info->nwid), sizeof(iwparam));
+      wrq.u.nwid.fixed = 1;    /* Hum... When in Rome... */
 
-      if(ioctl(skfd, SIOCSIWESSID, &wrq) < 0)
+      if(iw_set_ext(skfd, ifname, SIOCSIWNWID, &wrq) < 0)
        {
-         fprintf(stderr, "SIOCSIWESSID: %s\n", strerror(errno));
+         fprintf(stderr, "SIOCSIWNWID: %s\n", strerror(errno));
          ret = -1;
        }
     }
 
-  /* Set the current mode of operation */
-  if(info->has_mode)
+  /* Set ESSID (extended network), if available.
+   * ESSID need to be last : most device re-perform the scanning/discovery
+   * when this is set, and things like encryption keys are better be
+   * defined if we want to discover the right set of APs/nodes.
+   */
+  if(info->has_essid)
     {
-      strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-      wrq.u.mode = info->mode;
+      int              we_kernel_version;
+      we_kernel_version = iw_get_kernel_we_version();
+
+      wrq.u.essid.pointer = (caddr_t) info->essid;
+      wrq.u.essid.length = strlen(info->essid);
+      wrq.u.data.flags = info->essid_on;
+      if(we_kernel_version < 21)
+       wrq.u.essid.length++;
 
-      if(ioctl(skfd, SIOCSIWMODE, &wrq) < 0)
+      if(iw_set_ext(skfd, ifname, SIOCSIWESSID, &wrq) < 0)
        {
-         fprintf(stderr, "SIOCSIWMODE: %s\n", strerror(errno));
+         fprintf(stderr, "SIOCSIWESSID: %s\n", strerror(errno));
          ret = -1;
        }
     }
@@ -340,140 +867,612 @@ iw_set_basic_config(int                 skfd,
   return(ret);
 }
 
-/********************** FREQUENCY SUBROUTINES ***********************/
+/*********************** PROTOCOL SUBROUTINES ***********************/
+/*
+ * Fun stuff with protocol identifiers (SIOCGIWNAME).
+ * We assume that drivers are returning sensible values in there,
+ * which is not always the case :-(
+ */
 
 /*------------------------------------------------------------------*/
 /*
- * Convert a floating point the our internal representation of
- * frequencies.
- * The kernel doesn't want to hear about floating point, so we use
- * this custom format instead.
+ * Compare protocol identifiers.
+ * We don't want to know if the two protocols are the exactly same,
+ * but if they interoperate at some level, and also if they accept the
+ * same type of config (ESSID vs NWID, freq...).
+ * This is supposed to work around the alphabet soup.
+ * Return 1 if protocols are compatible, 0 otherwise
  */
-void
-iw_float2freq(double   in,
-             iwfreq *  out)
+int
+iw_protocol_compare(const char *       protocol1,
+                   const char *        protocol2)
 {
-  out->e = (short) (floor(log10(in)));
-  if(out->e > 8)
-    {
-      out->m = ((long) (floor(in / pow(10,out->e - 6)))) * 100;
-      out->e -= 8;
-    }
-  else
+  const char * dot11 = "IEEE 802.11";
+  const char * dot11_ds = "Dbg";
+  const char * dot11_5g = "a";
+
+  /* If the strings are the same -> easy */
+  if(!strncmp(protocol1, protocol2, IFNAMSIZ))
+    return(1);
+
+  /* Are we dealing with one of the 802.11 variant ? */
+  if( (!strncmp(protocol1, dot11, strlen(dot11))) &&
+      (!strncmp(protocol2, dot11, strlen(dot11))) )
     {
-      out->m = in;
-      out->e = 0;
+      const char *     sub1 = protocol1 + strlen(dot11);
+      const char *     sub2 = protocol2 + strlen(dot11);
+      unsigned int     i;
+      int              isds1 = 0;
+      int              isds2 = 0;
+      int              is5g1 = 0;
+      int              is5g2 = 0;
+
+      /* Check if we find the magic letters telling it's DS compatible */
+      for(i = 0; i < strlen(dot11_ds); i++)
+       {
+         if(strchr(sub1, dot11_ds[i]) != NULL)
+           isds1 = 1;
+         if(strchr(sub2, dot11_ds[i]) != NULL)
+           isds2 = 1;
+       }
+      if(isds1 && isds2)
+       return(1);
+
+      /* Check if we find the magic letters telling it's 5GHz compatible */
+      for(i = 0; i < strlen(dot11_5g); i++)
+       {
+         if(strchr(sub1, dot11_5g[i]) != NULL)
+           is5g1 = 1;
+         if(strchr(sub2, dot11_5g[i]) != NULL)
+           is5g2 = 1;
+       }
+      if(is5g1 && is5g2)
+       return(1);
     }
+  /* Not compatible */
+  return(0);
 }
 
-/*------------------------------------------------------------------*/
+/************************ ESSID SUBROUTINES ************************/
 /*
- * Convert our internal representation of frequencies to a floating point.
+ * The ESSID identify 802.11 networks, and is an array if 32 bytes.
+ * Most people use it as an ASCII string, and are happy with it.
+ * However, any byte is valid, including the NUL character. Characters
+ * beyond the ASCII range are interpreted according to the locale and
+ * the OS, which is somethign we don't control (network of other
+ * people).
+ * Routines in here try to deal with that in asafe way.
  */
-double
-iw_freq2float(iwfreq * in)
-{
-  return ((double) in->m) * pow(10,in->e);
-}
-
-/************************ POWER SUBROUTINES *************************/
 
 /*------------------------------------------------------------------*/
 /*
- * Convert a value in dBm to a value in milliWatt.
+ * Escape non-ASCII characters from ESSID.
+ * This allow us to display those weirds characters to the user.
+ *
+ * Source is 32 bytes max.
+ * Destination buffer needs to be at least 129 bytes, will be NUL
+ * terminated.
  */
-int
-iw_dbm2mwatt(int       in)
+void
+iw_essid_escape(char *         dest,
+               const char *    src,
+               const int       slen)
 {
-  return((int) (floor(pow(10.0, (((double) in) / 10.0)))));
-}
+  const unsigned char *        s = (const unsigned char *) src;
+  const unsigned char *        e = s + slen;
+  char *               d = dest;
 
-/*------------------------------------------------------------------*/
-/*
- * Convert a value in milliWatt to a value in dBm.
- */
-int
-iw_mwatt2dbm(int       in)
-{
-  return((int) (ceil(10.0 * log10((double) in))));
-}
+  /* Look every character of the string */
+  while(s < e)
+    {
+      int      isescape;
+
+      /* Escape the escape to avoid ambiguity.
+       * We do a fast path test for performance reason. Compiler will
+       * optimise all that ;-) */
+      if(*s == '\\')
+       {
+         /* Check if we would confuse it with an escape sequence */
+         if((e-s) > 4 && (s[1] == 'x')
+            && (isxdigit(s[2])) && (isxdigit(s[3])))
+           {
+             isescape = 1;
+           }
+         else
+           isescape = 0;
+       }
+      else
+       isescape = 0;
+
+
+      /* Is it a non-ASCII character ??? */
+      if(isescape || !isascii(*s) || iscntrl(*s))
+       {
+         /* Escape */
+         sprintf(d, "\\x%02X", *s);
+         d += 4;
+       }
+      else
+       {
+         /* Plain ASCII, just copy */
+         *d = *s;
+         d++;
+       }
+      s++;
+    }
+
+  /* NUL terminate destination */
+  *d = '\0';
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Un-Escape non-ASCII characters from ESSID
+ * This allow the user to specify weird characters in ESSID.
+ *
+ * The source is a NUL terminated string.
+ * Destination buffer is at least the size of source (ESSID will shrink)
+ * Destination may contains NUL, therefore we return the length.
+ * This function still works is src and dest are the same ;-)
+ */
+int
+iw_essid_unescape(char *       dest,
+                 const char *  src)
+{
+  const char * s = src;
+  char *       d = dest;
+  char *       p;
+  int          len;
+
+  /* Look-up the next '\' sequence, stop when no more */
+  while((p = strchr(s, '\\')) != NULL)
+    {
+      /* Copy block of unescaped chars before the '\' */
+      len = p - s;
+      memcpy(d, s, len);
+      d += len;
+      s += len;                /* Identical to 's = p' */
+
+      /* Check if it is really an escape sequence. We do also check for NUL */
+      if((s[1] == 'x') && (isxdigit(s[2])) && (isxdigit(s[3])))
+       {
+         unsigned int  temp;
+         /* Valid Escape sequence, un-escape it */
+         sscanf(s + 2, "%2X", &temp);
+         *d = temp;
+         d++;
+         s+=4;
+       }
+      else
+       {
+         /* Not valid, don't un-escape it */
+         *d = *s;
+         d++;
+         s++;
+       }
+    }
+
+  /* Copy remaining of the string */
+  len = strlen(s);
+  memcpy(d, s, len + 1);
+  /* Return length */
+  return((d - dest) + len);
+}
+
+/********************** FREQUENCY SUBROUTINES ***********************/
+/*
+ * Note : the two functions below are the cause of troubles on
+ * various embeeded platforms, as they are the reason we require
+ * libm (math library).
+ * In this case, please use enable BUILD_NOLIBM in the makefile
+ *
+ * FIXME : check negative mantissa and exponent
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Convert a floating point the our internal representation of
+ * frequencies.
+ * The kernel doesn't want to hear about floating point, so we use
+ * this custom format instead.
+ */
+void
+iw_float2freq(double   in,
+             iwfreq *  out)
+{
+#ifdef WE_NOLIBM
+  /* Version without libm : slower */
+  out->e = 0;
+  while(in > 1e9)
+    {
+      in /= 10;
+      out->e++;
+    }
+  out->m = (long) in;
+#else  /* WE_NOLIBM */
+  /* Version with libm : faster */
+  out->e = (short) (floor(log10(in)));
+  if(out->e > 8)
+    {
+      out->m = ((long) (floor(in / pow(10,out->e - 6)))) * 100;
+      out->e -= 8;
+    }
+  else
+    {
+      out->m = (long) in;
+      out->e = 0;
+    }
+#endif /* WE_NOLIBM */
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Convert our internal representation of frequencies to a floating point.
+ */
+double
+iw_freq2float(const iwfreq *   in)
+{
+#ifdef WE_NOLIBM
+  /* Version without libm : slower */
+  int          i;
+  double       res = (double) in->m;
+  for(i = 0; i < in->e; i++)
+    res *= 10;
+  return(res);
+#else  /* WE_NOLIBM */
+  /* Version with libm : faster */
+  return ((double) in->m) * pow(10,in->e);
+#endif /* WE_NOLIBM */
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Output a frequency with proper scaling
+ */
+void
+iw_print_freq_value(char *     buffer,
+                   int         buflen,
+                   double      freq)
+{
+  if(freq < KILO)
+    snprintf(buffer, buflen, "%g", freq);
+  else
+    {
+      char     scale;
+      int      divisor;
+
+      if(freq >= GIGA)
+       {
+         scale = 'G';
+         divisor = GIGA;
+       }
+      else
+       {
+         if(freq >= MEGA)
+           {
+             scale = 'M';
+             divisor = MEGA;
+           }
+         else
+           {
+             scale = 'k';
+             divisor = KILO;
+           }
+       }
+      snprintf(buffer, buflen, "%g %cHz", freq / divisor, scale);
+    }
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Output a frequency with proper scaling
+ */
+void
+iw_print_freq(char *   buffer,
+             int       buflen,
+             double    freq,
+             int       channel,
+             int       freq_flags)
+{
+  char sep = ((freq_flags & IW_FREQ_FIXED) ? '=' : ':');
+  char vbuf[16];
+
+  /* Print the frequency/channel value */
+  iw_print_freq_value(vbuf, sizeof(vbuf), freq);
+
+  /* Check if channel only */
+  if(freq < KILO)
+    snprintf(buffer, buflen, "Channel%c%s", sep, vbuf);
+  else
+    {
+      /* Frequency. Check if we have a channel as well */
+      if(channel >= 0)
+       snprintf(buffer, buflen, "Frequency%c%s (Channel %d)",
+                sep, vbuf, channel);
+      else
+       snprintf(buffer, buflen, "Frequency%c%s", sep, vbuf);
+    }
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Convert a frequency to a channel (negative -> error)
+ */
+int
+iw_freq_to_channel(double                      freq,
+                  const struct iw_range *      range)
+{
+  double       ref_freq;
+  int          k;
+
+  /* Check if it's a frequency or not already a channel */
+  if(freq < KILO)
+    return(-1);
+
+  /* We compare the frequencies as double to ignore differences
+   * in encoding. Slower, but safer... */
+  for(k = 0; k < range->num_frequency; k++)
+    {
+      ref_freq = iw_freq2float(&(range->freq[k]));
+      if(freq == ref_freq)
+       return(range->freq[k].i);
+    }
+  /* Not found */
+  return(-2);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Convert a channel to a frequency (negative -> error)
+ * Return the channel on success
+ */
+int
+iw_channel_to_freq(int                         channel,
+                  double *                     pfreq,
+                  const struct iw_range *      range)
+{
+  int          has_freq = 0;
+  int          k;
+
+  /* Check if the driver support only channels or if it has frequencies */
+  for(k = 0; k < range->num_frequency; k++)
+    {
+      if((range->freq[k].e != 0) || (range->freq[k].m > (int) KILO))
+       has_freq = 1;
+    }
+  if(!has_freq)
+    return(-1);
+
+  /* Find the correct frequency in the list */
+  for(k = 0; k < range->num_frequency; k++)
+    {
+      if(range->freq[k].i == channel)
+       {
+         *pfreq = iw_freq2float(&(range->freq[k]));
+         return(channel);
+       }
+    }
+  /* Not found */
+  return(-2);
+}
+
+/*********************** BITRATE SUBROUTINES ***********************/
+
+/*------------------------------------------------------------------*/
+/*
+ * Output a bitrate with proper scaling
+ */
+void
+iw_print_bitrate(char *        buffer,
+                int    buflen,
+                int    bitrate)
+{
+  double       rate = bitrate;
+  char         scale;
+  int          divisor;
+
+  if(rate >= GIGA)
+    {
+      scale = 'G';
+      divisor = GIGA;
+    }
+  else
+    {
+      if(rate >= MEGA)
+       {
+         scale = 'M';
+         divisor = MEGA;
+       }
+      else
+       {
+         scale = 'k';
+         divisor = KILO;
+       }
+    }
+  snprintf(buffer, buflen, "%g %cb/s", rate / divisor, scale);
+}
+
+/************************ POWER SUBROUTINES *************************/
+
+/*------------------------------------------------------------------*/
+/*
+ * Convert a value in dBm to a value in milliWatt.
+ */
+int
+iw_dbm2mwatt(int       in)
+{
+#ifdef WE_NOLIBM
+  /* Version without libm : slower */
+  int          ip = in / 10;
+  int          fp = in % 10;
+  int          k;
+  double       res = 1.0;
+
+  /* Split integral and floating part to avoid accumulating rounding errors */
+  for(k = 0; k < ip; k++)
+    res *= 10;
+  for(k = 0; k < fp; k++)
+    res *= LOG10_MAGIC;
+  return((int) res);
+#else  /* WE_NOLIBM */
+  /* Version with libm : faster */
+  return((int) (floor(pow(10.0, (((double) in) / 10.0)))));
+#endif /* WE_NOLIBM */
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Convert a value in milliWatt to a value in dBm.
+ */
+int
+iw_mwatt2dbm(int       in)
+{
+#ifdef WE_NOLIBM
+  /* Version without libm : slower */
+  double       fin = (double) in;
+  int          res = 0;
+
+  /* Split integral and floating part to avoid accumulating rounding errors */
+  while(fin > 10.0)
+    {
+      res += 10;
+      fin /= 10.0;
+    }
+  while(fin > 1.000001)        /* Eliminate rounding errors, take ceil */
+    {
+      res += 1;
+      fin /= LOG10_MAGIC;
+    }
+  return(res);
+#else  /* WE_NOLIBM */
+  /* Version with libm : faster */
+  return((int) (ceil(10.0 * log10((double) in))));
+#endif /* WE_NOLIBM */
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Output a txpower with proper conversion
+ */
+void
+iw_print_txpower(char *                        buffer,
+                int                    buflen,
+                struct iw_param *      txpower)
+{
+  int          dbm;
+
+  /* Check if disabled */
+  if(txpower->disabled)
+    {
+      snprintf(buffer, buflen, "off");
+    }
+  else
+    {
+      /* Check for relative values */
+      if(txpower->flags & IW_TXPOW_RELATIVE)
+       {
+         snprintf(buffer, buflen, "%d", txpower->value);
+       }
+      else
+       {
+         /* Convert everything to dBm */
+         if(txpower->flags & IW_TXPOW_MWATT)
+           dbm = iw_mwatt2dbm(txpower->value);
+         else
+           dbm = txpower->value;
+
+         /* Display */
+         snprintf(buffer, buflen, "%d dBm", dbm);
+       }
+    }
+}
 
 /********************** STATISTICS SUBROUTINES **********************/
 
 /*------------------------------------------------------------------*/
 /*
  * Read /proc/net/wireless to get the latest statistics
+ * Note : strtok not thread safe, not used in WE-12 and later.
  */
 int
-iw_get_stats(int       skfd,
-            char *     ifname,
-            iwstats *  stats)
+iw_get_stats(int               skfd,
+            const char *       ifname,
+            iwstats *          stats,
+            const iwrange *    range,
+            int                has_range)
 {
-#if WIRELESS_EXT > 11
-  struct iwreq         wrq;
-  wrq.u.data.pointer = (caddr_t) stats;
-  wrq.u.data.length = 0;
-  wrq.u.data.flags = 1;                /* Clear updated flag */
-  strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
-  if(ioctl(skfd, SIOCGIWSTATS, &wrq) < 0)
-    return(-1);
+  /* Fortunately, we can always detect this condition properly */
+  if((has_range) && (range->we_version_compiled > 11))
+    {
+      struct iwreq             wrq;
+      wrq.u.data.pointer = (caddr_t) stats;
+      wrq.u.data.length = sizeof(struct iw_statistics);
+      wrq.u.data.flags = 1;            /* Clear updated flag */
+      strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
+      if(iw_get_ext(skfd, ifname, SIOCGIWSTATS, &wrq) < 0)
+       return(-1);
 
-  return(0);
-#else /* WIRELESS_EXT > 11 */
-  FILE *       f=fopen("/proc/net/wireless","r");
-  char         buf[256];
-  char *       bp;
-  int          t;
-  if(f==NULL)
-    return -1;
-  /* Loop on all devices */
-  while(fgets(buf,255,f))
-    {
-      bp=buf;
-      while(*bp&&isspace(*bp))
-       bp++;
-      /* Is it the good device ? */
-      if(strncmp(bp,ifname,strlen(ifname))==0 && bp[strlen(ifname)]==':')
-       {
-         /* Skip ethX: */
-         bp=strchr(bp,':');
-         bp++;
-         /* -- status -- */
-         bp = strtok(bp, " ");
-         sscanf(bp, "%X", &t);
-         stats->status = (unsigned short) t;
-         /* -- link quality -- */
-         bp = strtok(NULL, " ");
-         if(strchr(bp,'.') != NULL)
-           stats->qual.updated |= 1;
-         sscanf(bp, "%d", &t);
-         stats->qual.qual = (unsigned char) t;
-         /* -- signal level -- */
-         bp = strtok(NULL, " ");
-         if(strchr(bp,'.') != NULL)
-           stats->qual.updated |= 2;
-         sscanf(bp, "%d", &t);
-         stats->qual.level = (unsigned char) t;
-         /* -- noise level -- */
-         bp = strtok(NULL, " ");
-         if(strchr(bp,'.') != NULL)
-           stats->qual.updated += 4;
-         sscanf(bp, "%d", &t);
-         stats->qual.noise = (unsigned char) t;
-         /* -- discarded packets -- */
-         bp = strtok(NULL, " ");
-         sscanf(bp, "%d", &stats->discard.nwid);
-         bp = strtok(NULL, " ");
-         sscanf(bp, "%d", &stats->discard.code);
-         bp = strtok(NULL, " ");
-         sscanf(bp, "%d", &stats->discard.misc);
-         fclose(f);
-         return 0;
-       }
-    }
-  fclose(f);
-  return -1;
-#endif /* WIRELESS_EXT > 11 */
+      /* Format has not changed since WE-12, no conversion */
+      return(0);
+    }
+  else
+    {
+      FILE *   f = fopen(PROC_NET_WIRELESS, "r");
+      char     buf[256];
+      char *   bp;
+      int      t;
+
+      if(f==NULL)
+       return -1;
+      /* Loop on all devices */
+      while(fgets(buf,255,f))
+       {
+         bp=buf;
+         while(*bp&&isspace(*bp))
+           bp++;
+         /* Is it the good device ? */
+         if(strncmp(bp,ifname,strlen(ifname))==0 && bp[strlen(ifname)]==':')
+           {
+             /* Skip ethX: */
+             bp=strchr(bp,':');
+             bp++;
+             /* -- status -- */
+             bp = strtok(bp, " ");
+             sscanf(bp, "%X", &t);
+             stats->status = (unsigned short) t;
+             /* -- link quality -- */
+             bp = strtok(NULL, " ");
+             if(strchr(bp,'.') != NULL)
+               stats->qual.updated |= 1;
+             sscanf(bp, "%d", &t);
+             stats->qual.qual = (unsigned char) t;
+             /* -- signal level -- */
+             bp = strtok(NULL, " ");
+             if(strchr(bp,'.') != NULL)
+               stats->qual.updated |= 2;
+             sscanf(bp, "%d", &t);
+             stats->qual.level = (unsigned char) t;
+             /* -- noise level -- */
+             bp = strtok(NULL, " ");
+             if(strchr(bp,'.') != NULL)
+               stats->qual.updated += 4;
+             sscanf(bp, "%d", &t);
+             stats->qual.noise = (unsigned char) t;
+             /* -- discarded packets -- */
+             bp = strtok(NULL, " ");
+             sscanf(bp, "%d", &stats->discard.nwid);
+             bp = strtok(NULL, " ");
+             sscanf(bp, "%d", &stats->discard.code);
+             bp = strtok(NULL, " ");
+             sscanf(bp, "%d", &stats->discard.misc);
+             fclose(f);
+             /* No conversion needed */
+             return 0;
+           }
+       }
+      fclose(f);
+      return -1;
+    }
 }
 
 /*------------------------------------------------------------------*/
@@ -482,40 +1481,143 @@ iw_get_stats(int        skfd,
  */
 void
 iw_print_stats(char *          buffer,
-              iwqual *         qual,
-              iwrange *        range,
+              int              buflen,
+              const iwqual *   qual,
+              const iwrange *  range,
               int              has_range)
 {
-  /* Just do it */
-  if(has_range && (qual->level != 0))
+  int          len;
+
+  /* People are very often confused by the 8 bit arithmetic happening
+   * here.
+   * All the values here are encoded in a 8 bit integer. 8 bit integers
+   * are either unsigned [0 ; 255], signed [-128 ; +127] or
+   * negative [-255 ; 0].
+   * Further, on 8 bits, 0x100 == 256 == 0.
+   *
+   * Relative/percent values are always encoded unsigned, between 0 and 255.
+   * Absolute/dBm values are always encoded between -192 and 63.
+   * (Note that up to version 28 of Wireless Tools, dBm used to be
+   *  encoded always negative, between -256 and -1).
+   *
+   * How do we separate relative from absolute values ?
+   * The old way is to use the range to do that. As of WE-19, we have
+   * an explicit IW_QUAL_DBM flag in updated...
+   * The range allow to specify the real min/max of the value. As the
+   * range struct only specify one bound of the value, we assume that
+   * the other bound is 0 (zero).
+   * For relative values, range is [0 ; range->max].
+   * For absolute values, range is [range->max ; 63].
+   *
+   * Let's take two example :
+   * 1) value is 75%. qual->value = 75 ; range->max_qual.value = 100
+   * 2) value is -54dBm. noise floor of the radio is -104dBm.
+   *    qual->value = -54 = 202 ; range->max_qual.value = -104 = 152
+   *
+   * Jean II
+   */
+
+  /* Just do it...
+   * The old way to detect dBm require both the range and a non-null
+   * level (which confuse the test). The new way can deal with level of 0
+   * because it does an explicit test on the flag. */
+  if(has_range && ((qual->level != 0)
+                  || (qual->updated & (IW_QUAL_DBM | IW_QUAL_RCPI))))
     {
-      /* If the statistics are in dBm */
-      if(qual->level > range->max_qual.level)
+      /* Deal with quality : always a relative value */
+      if(!(qual->updated & IW_QUAL_QUAL_INVALID))
+       {
+         len = snprintf(buffer, buflen, "Quality%c%d/%d  ",
+                        qual->updated & IW_QUAL_QUAL_UPDATED ? '=' : ':',
+                        qual->qual, range->max_qual.qual);
+         buffer += len;
+         buflen -= len;
+       }
+
+      /* Check if the statistics are in RCPI (IEEE 802.11k) */
+      if(qual->updated & IW_QUAL_RCPI)
        {
-         /* Statistics are in dBm (absolute power measurement) */
-         sprintf(buffer,
-                 "Quality:%d/%d  Signal level:%d dBm  Noise level:%d dBm%s",
-                 qual->qual, range->max_qual.qual,
-                 qual->level - 0x100, qual->noise - 0x100,
-                 (qual->updated & 0x7) ? " (updated)" : "");
+         /* Deal with signal level in RCPI */
+         /* RCPI = int{(Power in dBm +110)*2} for 0dbm > Power > -110dBm */
+         if(!(qual->updated & IW_QUAL_LEVEL_INVALID))
+           {
+             double    rcpilevel = (qual->level / 2.0) - 110.0;
+             len = snprintf(buffer, buflen, "Signal level%c%g dBm  ",
+                            qual->updated & IW_QUAL_LEVEL_UPDATED ? '=' : ':',
+                            rcpilevel);
+             buffer += len;
+             buflen -= len;
+           }
+
+         /* Deal with noise level in dBm (absolute power measurement) */
+         if(!(qual->updated & IW_QUAL_NOISE_INVALID))
+           {
+             double    rcpinoise = (qual->noise / 2.0) - 110.0;
+             len = snprintf(buffer, buflen, "Noise level%c%g dBm",
+                            qual->updated & IW_QUAL_NOISE_UPDATED ? '=' : ':',
+                            rcpinoise);
+           }
        }
       else
        {
-         /* Statistics are relative values (0 -> max) */
-         sprintf(buffer,
-                 "Quality:%d/%d  Signal level:%d/%d  Noise level:%d/%d%s",
-                 qual->qual, range->max_qual.qual,
-                 qual->level, range->max_qual.level,
-                 qual->noise, range->max_qual.noise,
-                 (qual->updated & 0x7) ? " (updated)" : "");
+         /* Check if the statistics are in dBm */
+         if((qual->updated & IW_QUAL_DBM)
+            || (qual->level > range->max_qual.level))
+           {
+             /* Deal with signal level in dBm  (absolute power measurement) */
+             if(!(qual->updated & IW_QUAL_LEVEL_INVALID))
+               {
+                 int   dblevel = qual->level;
+                 /* Implement a range for dBm [-192; 63] */
+                 if(qual->level >= 64)
+                   dblevel -= 0x100;
+                 len = snprintf(buffer, buflen, "Signal level%c%d dBm  ",
+                                qual->updated & IW_QUAL_LEVEL_UPDATED ? '=' : ':',
+                                dblevel);
+                 buffer += len;
+                 buflen -= len;
+               }
+
+             /* Deal with noise level in dBm (absolute power measurement) */
+             if(!(qual->updated & IW_QUAL_NOISE_INVALID))
+               {
+                 int   dbnoise = qual->noise;
+                 /* Implement a range for dBm [-192; 63] */
+                 if(qual->noise >= 64)
+                   dbnoise -= 0x100;
+                 len = snprintf(buffer, buflen, "Noise level%c%d dBm",
+                                qual->updated & IW_QUAL_NOISE_UPDATED ? '=' : ':',
+                                dbnoise);
+               }
+           }
+         else
+           {
+             /* Deal with signal level as relative value (0 -> max) */
+             if(!(qual->updated & IW_QUAL_LEVEL_INVALID))
+               {
+                 len = snprintf(buffer, buflen, "Signal level%c%d/%d  ",
+                                qual->updated & IW_QUAL_LEVEL_UPDATED ? '=' : ':',
+                                qual->level, range->max_qual.level);
+                 buffer += len;
+                 buflen -= len;
+               }
+
+             /* Deal with noise level as relative value (0 -> max) */
+             if(!(qual->updated & IW_QUAL_NOISE_INVALID))
+               {
+                 len = snprintf(buffer, buflen, "Noise level%c%d/%d",
+                                qual->updated & IW_QUAL_NOISE_UPDATED ? '=' : ':',
+                                qual->noise, range->max_qual.noise);
+               }
+           }
        }
     }
   else
     {
       /* We can't read the range, so we don't know... */
-      sprintf(buffer, "Quality:%d  Signal level:%d  Noise level:%d%s",
-             qual->qual, qual->level, qual->noise,
-             (qual->updated & 0x7) ? " (updated)" : "");
+      snprintf(buffer, buflen,
+              "Quality:%d  Signal level:%d  Noise level:%d",
+              qual->qual, qual->level, qual->noise);
     }
 }
 
@@ -526,42 +1628,225 @@ iw_print_stats(char *           buffer,
  * Output the encoding key, with a nice formating
  */
 void
-iw_print_key(char *            buffer,
-            unsigned char *    key,
-            int                key_size,
-            int                key_flags)
+iw_print_key(char *                    buffer,
+            int                        buflen,
+            const unsigned char *      key,            /* Must be unsigned */
+            int                        key_size,
+            int                        key_flags)
 {
   int  i;
 
+  /* Check buffer size -> 1 bytes => 2 digits + 1/2 separator */
+  if((key_size * 3) > buflen)
+    {
+      snprintf(buffer, buflen, "<too big>");
+      return;
+    }
+
   /* Is the key present ??? */
   if(key_flags & IW_ENCODE_NOKEY)
     {
-      /* Nope : print dummy */
-      strcpy(buffer, "**");
-      buffer +=2;
-      for(i = 1; i < key_size; i++)
+      /* Nope : print on or dummy */
+      if(key_size <= 0)
+       strcpy(buffer, "on");                   /* Size checked */
+      else
        {
-         if((i & 0x1) == 0)
-           strcpy(buffer++, "-");
-         strcpy(buffer, "**");
+         strcpy(buffer, "**");                 /* Size checked */
          buffer +=2;
+         for(i = 1; i < key_size; i++)
+           {
+             if((i & 0x1) == 0)
+               strcpy(buffer++, "-");          /* Size checked */
+             strcpy(buffer, "**");             /* Size checked */
+             buffer +=2;
+           }
        }
     }
   else
     {
       /* Yes : print the key */
-      sprintf(buffer, "%.2X", key[0]);
+      sprintf(buffer, "%.2X", key[0]);         /* Size checked */
       buffer +=2;
       for(i = 1; i < key_size; i++)
        {
          if((i & 0x1) == 0)
-           strcpy(buffer++, "-");
-         sprintf(buffer, "%.2X", key[i]);
+           strcpy(buffer++, "-");              /* Size checked */
+         sprintf(buffer, "%.2X", key[i]);      /* Size checked */
          buffer +=2;
        }
     }
 }
 
+/*------------------------------------------------------------------*/
+/*
+ * Convert a passphrase into a key
+ * ### NOT IMPLEMENTED ###
+ * Return size of the key, or 0 (no key) or -1 (error)
+ */
+static int
+iw_pass_key(const char *       input,
+           unsigned char *     key)
+{
+  input = input; key = key;
+  fprintf(stderr, "Error: Passphrase not implemented\n");
+  return(-1);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Parse a key from the command line.
+ * Return size of the key, or 0 (no key) or -1 (error)
+ * If the key is too long, it's simply truncated...
+ */
+int
+iw_in_key(const char *         input,
+         unsigned char *       key)
+{
+  int          keylen = 0;
+
+  /* Check the type of key */
+  if(!strncmp(input, "s:", 2))
+    {
+      /* First case : as an ASCII string (Lucent/Agere cards) */
+      keylen = strlen(input + 2);              /* skip "s:" */
+      if(keylen > IW_ENCODING_TOKEN_MAX)
+       keylen = IW_ENCODING_TOKEN_MAX;
+      memcpy(key, input + 2, keylen);
+    }
+  else
+    if(!strncmp(input, "p:", 2))
+      {
+       /* Second case : as a passphrase (PrismII cards) */
+       return(iw_pass_key(input + 2, key));            /* skip "p:" */
+      }
+    else
+      {
+       const char *    p;
+       int             dlen;   /* Digits sequence length */
+       unsigned char   out[IW_ENCODING_TOKEN_MAX];
+
+       /* Third case : as hexadecimal digits */
+       p = input;
+       dlen = -1;
+
+       /* Loop until we run out of chars in input or overflow the output */
+       while(*p != '\0')
+         {
+           int temph;
+           int templ;
+           int count;
+           /* No more chars in this sequence */
+           if(dlen <= 0)
+             {
+               /* Skip separator */
+               if(dlen == 0)
+                 p++;
+               /* Calculate num of char to next separator */
+               dlen = strcspn(p, "-:;.,");
+             }
+           /* Get each char separatly (and not by two) so that we don't
+            * get confused by 'enc' (=> '0E'+'0C') and similar */
+           count = sscanf(p, "%1X%1X", &temph, &templ);
+           if(count < 1)
+             return(-1);               /* Error -> non-hex char */
+           /* Fixup odd strings such as '123' is '01'+'23' and not '12'+'03'*/
+           if(dlen % 2)
+             count = 1;
+           /* Put back two chars as one byte and output */
+           if(count == 2)
+             templ |= temph << 4;
+           else
+             templ = temph;
+           out[keylen++] = (unsigned char) (templ & 0xFF);
+           /* Check overflow in output */
+           if(keylen >= IW_ENCODING_TOKEN_MAX)
+             break;
+           /* Move on to next chars */
+           p += count;
+           dlen -= count;
+         }
+       /* We use a temporary output buffer 'out' so that if there is
+        * an error, we don't overwrite the original key buffer.
+        * Because of the way iwconfig loop on multiple key/enc arguments
+        * until it finds an error in here, this is necessary to avoid
+        * silently corrupting the encryption key... */
+       memcpy(key, out, keylen);
+      }
+
+#ifdef DEBUG
+  {
+    char buf[IW_ENCODING_TOKEN_MAX * 3];
+    iw_print_key(buf, sizeof(buf), key, keylen, 0);
+    printf("Got key : %d [%s]\n", keylen, buf);
+  }
+#endif
+
+  return(keylen);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Parse a key from the command line.
+ * Return size of the key, or 0 (no key) or -1 (error)
+ */
+int
+iw_in_key_full(int             skfd,
+              const char *     ifname,
+              const char *     input,
+              unsigned char *  key,
+              __u16 *          flags)
+{
+  int          keylen = 0;
+  char *       p;
+
+  if(!strncmp(input, "l:", 2))
+    {
+      struct iw_range  range;
+
+      /* Extra case : as a login (user:passwd - Cisco LEAP) */
+      keylen = strlen(input + 2) + 1;          /* skip "l:", add '\0' */
+      /* Most user/password is 8 char, so 18 char total, < 32 */
+      if(keylen > IW_ENCODING_TOKEN_MAX)
+       keylen = IW_ENCODING_TOKEN_MAX;
+      memcpy(key, input + 2, keylen);
+
+      /* Separate the two strings */
+      p = strchr((char *) key, ':');
+      if(p == NULL)
+       {
+         fprintf(stderr, "Error: Invalid login format\n");
+         return(-1);
+       }
+      *p = '\0';
+
+      /* Extract range info */
+      if(iw_get_range_info(skfd, ifname, &range) < 0)
+       /* Hum... Maybe we should return an error ??? */
+       memset(&range, 0, sizeof(range));
+
+      if(range.we_version_compiled > 15)
+       {
+
+         printf("flags = %X, index = %X\n",
+                *flags, range.encoding_login_index);
+         if((*flags & IW_ENCODE_INDEX) == 0)
+           {
+             /* Extract range info */
+             if(iw_get_range_info(skfd, ifname, &range) < 0)
+               memset(&range, 0, sizeof(range));
+             printf("flags = %X, index = %X\n", *flags, range.encoding_login_index);
+             /* Set the index the driver expects */
+             *flags |= range.encoding_login_index & IW_ENCODE_INDEX;
+           }
+         printf("flags = %X, index = %X\n", *flags, range.encoding_login_index);
+       }
+    }
+  else
+    /* Simpler routine above */
+    keylen = iw_in_key(input, key);
+
+  return(keylen);
+}
 
 /******************* POWER MANAGEMENT SUBROUTINES *******************/
 
@@ -571,46 +1856,68 @@ iw_print_key(char *              buffer,
  */
 void
 iw_print_pm_value(char *       buffer,
+                 int           buflen,
                  int           value,
-                 int           flags)
+                 int           flags,
+                 int           we_version)
 {
+  /* Check size */
+  if(buflen < 25)
+    {
+      snprintf(buffer, buflen, "<too big>");
+      return;
+    }
+  buflen -= 25;
+
   /* Modifiers */
   if(flags & IW_POWER_MIN)
     {
-      strcpy(buffer, " min");
+      strcpy(buffer, " min");                          /* Size checked */
       buffer += 4;
     }
   if(flags & IW_POWER_MAX)
     {
-      strcpy(buffer, " max");
+      strcpy(buffer, " max");                          /* Size checked */
       buffer += 4;
     }
 
   /* Type */
   if(flags & IW_POWER_TIMEOUT)
     {
-      strcpy(buffer, " timeout:");
+      strcpy(buffer, " timeout:");                     /* Size checked */
       buffer += 9;
     }
   else
     {
-      strcpy(buffer, " period:");
-      buffer += 8;
+      if(flags & IW_POWER_SAVING)
+       {
+         strcpy(buffer, " saving:");                   /* Size checked */
+         buffer += 8;
+       }
+      else
+       {
+         strcpy(buffer, " period:");                   /* Size checked */
+         buffer += 8;
+       }
     }
 
   /* Display value without units */
   if(flags & IW_POWER_RELATIVE)
-    sprintf(buffer, "%g  ", ((double) value) / MEGA);
+    {
+      if(we_version < 21)
+       value /= MEGA;
+      snprintf(buffer, buflen, "%d", value);
+    }
   else
     {
       /* Display value with units */
       if(value >= (int) MEGA)
-       sprintf(buffer, "%gs  ", ((double) value) / MEGA);
+       snprintf(buffer, buflen, "%gs", ((double) value) / MEGA);
       else
        if(value >= (int) KILO)
-         sprintf(buffer, "%gms  ", ((double) value) / KILO);
+         snprintf(buffer, buflen, "%gms", ((double) value) / KILO);
        else
-         sprintf(buffer, "%dus  ", value);
+         snprintf(buffer, buflen, "%dus", value);
     }
 }
 
@@ -620,93 +1927,181 @@ iw_print_pm_value(char *        buffer,
  */
 void
 iw_print_pm_mode(char *        buffer,
+                int    buflen,
                 int    flags)
 {
+  /* Check size */
+  if(buflen < 28)
+    {
+      snprintf(buffer, buflen, "<too big>");
+      return;
+    }
+
   /* Print the proper mode... */
   switch(flags & IW_POWER_MODE)
     {
     case IW_POWER_UNICAST_R:
-      strcpy(buffer, " mode:Unicast only received");
+      strcpy(buffer, "mode:Unicast only received");    /* Size checked */
       break;
     case IW_POWER_MULTICAST_R:
-      strcpy(buffer, " mode:Multicast only received");
+      strcpy(buffer, "mode:Multicast only received");  /* Size checked */
       break;
     case IW_POWER_ALL_R:
-      strcpy(buffer, " mode:All packets received");
+      strcpy(buffer, "mode:All packets received");     /* Size checked */
       break;
     case IW_POWER_FORCE_S:
-      strcpy(buffer, " mode:Force sending");
+      strcpy(buffer, "mode:Force sending");            /* Size checked */
       break;
     case IW_POWER_REPEATER:
-      strcpy(buffer, " mode:Repeat multicasts");
+      strcpy(buffer, "mode:Repeat multicasts");                /* Size checked */
       break;
     default:
+      strcpy(buffer, "");                              /* Size checked */
+      break;
     }
 }
 
 /***************** RETRY LIMIT/LIFETIME SUBROUTINES *****************/
 
-#if WIRELESS_EXT > 10
 /*------------------------------------------------------------------*/
 /*
  * Output a retry value with all attributes...
  */
 void
 iw_print_retry_value(char *    buffer,
+                    int        buflen,
                     int        value,
-                    int        flags)
+                    int        flags,
+                    int        we_version)
 {
+  /* Check buffer size */
+  if(buflen < 20)
+    {
+      snprintf(buffer, buflen, "<too big>");
+      return;
+    }
+  buflen -= 20;
+
   /* Modifiers */
   if(flags & IW_RETRY_MIN)
     {
-      strcpy(buffer, " min");
+      strcpy(buffer, " min");                          /* Size checked */
       buffer += 4;
     }
   if(flags & IW_RETRY_MAX)
     {
-      strcpy(buffer, " max");
+      strcpy(buffer, " max");                          /* Size checked */
       buffer += 4;
     }
+  if(flags & IW_RETRY_SHORT)
+    {
+      strcpy(buffer, " short");                                /* Size checked */
+      buffer += 6;
+    }
+  if(flags & IW_RETRY_LONG)
+    {
+      strcpy(buffer, "  long");                                /* Size checked */
+      buffer += 6;
+    }
 
   /* Type lifetime of limit */
   if(flags & IW_RETRY_LIFETIME)
     {
-      strcpy(buffer, " lifetime:");
+      strcpy(buffer, " lifetime:");                    /* Size checked */
       buffer += 10;
 
       /* Display value without units */
-      if(flags & IW_POWER_RELATIVE)
-       sprintf(buffer, "%g", ((double) value) / MEGA);
+      if(flags & IW_RETRY_RELATIVE)
+       {
+         if(we_version < 21)
+           value /= MEGA;
+         snprintf(buffer, buflen, "%d", value);
+       }
       else
        {
          /* Display value with units */
          if(value >= (int) MEGA)
-           sprintf(buffer, "%gs", ((double) value) / MEGA);
+           snprintf(buffer, buflen, "%gs", ((double) value) / MEGA);
          else
            if(value >= (int) KILO)
-             sprintf(buffer, "%gms", ((double) value) / KILO);
+             snprintf(buffer, buflen, "%gms", ((double) value) / KILO);
            else
-             sprintf(buffer, "%dus", value);
+             snprintf(buffer, buflen, "%dus", value);
        }
     }
   else
-    sprintf(buffer, " limit:%d", value);
+    snprintf(buffer, buflen, " limit:%d", value);
+}
+
+/************************* TIME SUBROUTINES *************************/
+
+/*------------------------------------------------------------------*/
+/*
+ * Print timestamps
+ * Inspired from irdadump...
+ */
+void
+iw_print_timeval(char *                                buffer,
+                int                            buflen,
+                const struct timeval *         timev,
+                const struct timezone *        tz)
+{
+        int s;
+
+       s = (timev->tv_sec - tz->tz_minuteswest * 60) % 86400;
+       snprintf(buffer, buflen, "%02d:%02d:%02d.%06u", 
+               s / 3600, (s % 3600) / 60, 
+               s % 60, (u_int32_t) timev->tv_usec);
 }
-#endif /* WIRELESS_EXT > 10 */
 
 /*********************** ADDRESS SUBROUTINES ************************/
 /*
  * This section is mostly a cut & past from net-tools-1.2.0
+ * (Well... This has evolved over the years)
  * manage address display and input...
  */
 
 /*------------------------------------------------------------------*/
 /*
- * Check if interface support the right address types...
+ * Check if interface support the right MAC address type...
+ */
+int
+iw_check_mac_addr_type(int             skfd,
+                      const char *     ifname)
+{
+  struct ifreq         ifr;
+
+  /* Get the type of hardware address */
+  strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+  if((ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0) ||
+     ((ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
+      && (ifr.ifr_hwaddr.sa_family != ARPHRD_IEEE80211)))
+    {
+      /* Deep trouble... */
+      fprintf(stderr, "Interface %s doesn't support MAC addresses\n",
+            ifname);
+      return(-1);
+    }
+
+#ifdef DEBUG
+  {
+    char buf[20];
+    printf("Hardware : %d - %s\n", ifr.ifr_hwaddr.sa_family,
+          iw_saether_ntop(&ifr.ifr_hwaddr, buf));
+  }
+#endif
+
+  return(0);
+}
+
+
+/*------------------------------------------------------------------*/
+/*
+ * Check if interface support the right interface address type...
  */
 int
-iw_check_addr_type(int         skfd,
-                  char *       ifname)
+iw_check_if_addr_type(int              skfd,
+                     const char *      ifname)
 {
   struct ifreq         ifr;
 
@@ -725,148 +2120,190 @@ iw_check_addr_type(int         skfd,
         *((unsigned long *) ifr.ifr_addr.sa_data));
 #endif
 
-  /* Get the type of hardware address */
-  strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
-  if((ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0) ||
-     (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER))
-    {
-      /* Deep trouble... */
-      fprintf(stderr, "Interface %s doesn't support MAC addresses\n",
-            ifname);
-      return(-1);
-    }
-
-#ifdef DEBUG
-  printf("Hardware : %d - %s\n", ifr.ifr_hwaddr.sa_family,
-        pr_ether(ifr.ifr_hwaddr.sa_data));
-#endif
-
   return(0);
 }
 
+/*------------------------------------------------------------------*/
+/*
+ * Display an arbitrary length MAC address in readable format.
+ */
+char *
+iw_mac_ntop(const unsigned char *      mac,
+           int                         maclen,
+           char *                      buf,
+           int                         buflen)
+{
+  int  i;
+
+  /* Overflow check (don't forget '\0') */
+  if(buflen < (maclen * 3 - 1 + 1))
+    return(NULL);
+
+  /* First byte */
+  sprintf(buf, "%02X", mac[0]);
+
+  /* Other bytes */
+  for(i = 1; i < maclen; i++)
+    sprintf(buf + (i * 3) - 1, ":%02X", mac[i]);
+  return(buf);
+}
 
 /*------------------------------------------------------------------*/
 /*
  * Display an Ethernet address in readable format.
  */
+void
+iw_ether_ntop(const struct ether_addr *        eth,
+             char *                    buf)
+{
+  sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X",
+         eth->ether_addr_octet[0], eth->ether_addr_octet[1],
+         eth->ether_addr_octet[2], eth->ether_addr_octet[3],
+         eth->ether_addr_octet[4], eth->ether_addr_octet[5]);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Display an Wireless Access Point Socket Address in readable format.
+ * Note : 0x44 is an accident of history, that's what the Orinoco/PrismII
+ * chipset report, and the driver doesn't filter it.
+ */
 char *
-iw_pr_ether(char *             buffer,
-           unsigned char *     ptr)
+iw_sawap_ntop(const struct sockaddr *  sap,
+             char *                    buf)
 {
-  sprintf(buffer, "%02X:%02X:%02X:%02X:%02X:%02X",
-         (ptr[0] & 0xFF), (ptr[1] & 0xFF), (ptr[2] & 0xFF),
-         (ptr[3] & 0xFF), (ptr[4] & 0xFF), (ptr[5] & 0xFF)
-  );
-  return(buffer);
+  const struct ether_addr ether_zero = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }};
+  const struct ether_addr ether_bcast = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }};
+  const struct ether_addr ether_hack = {{ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44 }};
+  const struct ether_addr * ether_wap = (const struct ether_addr *) sap->sa_data;
+
+  if(!iw_ether_cmp(ether_wap, &ether_zero))
+    sprintf(buf, "Not-Associated");
+  else
+    if(!iw_ether_cmp(ether_wap, &ether_bcast))
+      sprintf(buf, "Invalid");
+    else
+      if(!iw_ether_cmp(ether_wap, &ether_hack))
+       sprintf(buf, "None");
+      else
+       iw_ether_ntop(ether_wap, buf);
+  return(buf);
 }
 
 /*------------------------------------------------------------------*/
 /*
- * Input an Ethernet address and convert to binary.
+ * Input an arbitrary length MAC address and convert to binary.
+ * Return address size.
  */
 int
-iw_in_ether(char *bufp, struct sockaddr *sap)
-{
-  unsigned char *ptr;
-  char c, *orig;
-  int i, val;
-
-  sap->sa_family = ARPHRD_ETHER;
-  ptr = sap->sa_data;
-
-  i = 0;
-  orig = bufp;
-  while((*bufp != '\0') && (i < ETH_ALEN)) {
-       val = 0;
-       c = *bufp++;
-       if (isdigit(c)) val = c - '0';
-         else if (c >= 'a' && c <= 'f') val = c - 'a' + 10;
-         else if (c >= 'A' && c <= 'F') val = c - 'A' + 10;
-         else {
-#ifdef DEBUG
-               fprintf(stderr, "in_ether(%s): invalid ether address!\n", orig);
-#endif
-               errno = EINVAL;
-               return(-1);
-       }
-       val <<= 4;
-       c = *bufp++;
-       if (isdigit(c)) val |= c - '0';
-         else if (c >= 'a' && c <= 'f') val |= c - 'a' + 10;
-         else if (c >= 'A' && c <= 'F') val |= c - 'A' + 10;
-         else {
+iw_mac_aton(const char *       orig,
+           unsigned char *     mac,
+           int                 macmax)
+{
+  const char * p = orig;
+  int          maclen = 0;
+
+  /* Loop on all bytes of the string */
+  while(*p != '\0')
+    {
+      int      temph;
+      int      templ;
+      int      count;
+      /* Extract one byte as two chars */
+      count = sscanf(p, "%1X%1X", &temph, &templ);
+      if(count != 2)
+       break;                  /* Error -> non-hex chars */
+      /* Output two chars as one byte */
+      templ |= temph << 4;
+      mac[maclen++] = (unsigned char) (templ & 0xFF);
+
+      /* Check end of string */
+      p += 2;
+      if(*p == '\0')
+       {
 #ifdef DEBUG
-               fprintf(stderr, "in_ether(%s): invalid ether address!\n", orig);
+         char buf[20];
+         iw_ether_ntop((const struct ether_addr *) mac, buf);
+         fprintf(stderr, "iw_mac_aton(%s): %s\n", orig, buf);
 #endif
-               errno = EINVAL;
-               return(-1);
+         return(maclen);               /* Normal exit */
        }
-       *ptr++ = (unsigned char) (val & 0377);
-       i++;
 
-       /* We might get a semicolon here - not required. */
-       if (*bufp == ':') {
-               if (i == ETH_ALEN) {
+      /* Check overflow */
+      if(maclen >= macmax)
+       {
 #ifdef DEBUG
-                       fprintf(stderr, "in_ether(%s): trailing : ignored!\n",
-                               orig)
+         fprintf(stderr, "iw_mac_aton(%s): trailing junk!\n", orig);
 #endif
-                                               ; /* nothing */
-               }
-               bufp++;
+         errno = E2BIG;
+         return(0);                    /* Error -> overflow */
        }
-  }
 
-  /* That's it.  Any trailing junk? */
-  if ((i == ETH_ALEN) && (*bufp != '\0')) {
-#ifdef DEBUG
-       fprintf(stderr, "in_ether(%s): trailing junk!\n", orig);
-       errno = EINVAL;
-       return(-1);
-#endif
-  }
+      /* Check separator */
+      if(*p != ':')
+       break;
+      p++;
+    }
 
+  /* Error... */
 #ifdef DEBUG
-  fprintf(stderr, "in_ether(%s): %s\n", orig, pr_ether(sap->sa_data));
+  fprintf(stderr, "iw_mac_aton(%s): invalid ether address!\n", orig);
 #endif
-
+  errno = EINVAL;
   return(0);
 }
 
 /*------------------------------------------------------------------*/
 /*
+ * Input an Ethernet address and convert to binary.
+ */
+int
+iw_ether_aton(const char *orig, struct ether_addr *eth)
+{
+  int  maclen;
+  maclen = iw_mac_aton(orig, (unsigned char *) eth, ETH_ALEN);
+  if((maclen > 0) && (maclen < ETH_ALEN))
+    {
+      errno = EINVAL;
+      maclen = 0;
+    }
+  return(maclen);
+}
+
+/*------------------------------------------------------------------*/
+/*
  * Input an Internet address and convert to binary.
  */
 int
-iw_in_inet(char *bufp, struct sockaddr *sap)
+iw_in_inet(char *name, struct sockaddr *sap)
 {
   struct hostent *hp;
   struct netent *np;
-  char *name = bufp;
-  struct sockaddr_in *sin = (struct sockaddr_in *) sap;
+  struct sockaddr_in *sain = (struct sockaddr_in *) sap;
 
   /* Grmpf. -FvK */
-  sin->sin_family = AF_INET;
-  sin->sin_port = 0;
+  sain->sin_family = AF_INET;
+  sain->sin_port = 0;
 
   /* Default is special, meaning 0.0.0.0. */
   if (!strcmp(name, "default")) {
-       sin->sin_addr.s_addr = INADDR_ANY;
+       sain->sin_addr.s_addr = INADDR_ANY;
        return(1);
   }
 
   /* Try the NETWORKS database to see if this is a known network. */
   if ((np = getnetbyname(name)) != (struct netent *)NULL) {
-       sin->sin_addr.s_addr = htonl(np->n_net);
+       sain->sin_addr.s_addr = htonl(np->n_net);
        strcpy(name, np->n_name);
        return(1);
   }
 
+  /* Always use the resolver (DNS name + IP addresses) */
   if ((hp = gethostbyname(name)) == (struct hostent *)NULL) {
        errno = h_errno;
        return(-1);
   }
-  memcpy((char *) &sin->sin_addr, (char *) hp->h_addr_list[0], hp->h_length);
+  memcpy((char *) &sain->sin_addr, (char *) hp->h_addr_list[0], hp->h_length);
   strcpy(name, hp->h_name);
   return(0);
 }
@@ -877,16 +2314,23 @@ iw_in_inet(char *bufp, struct sockaddr *sap)
  */
 int
 iw_in_addr(int         skfd,
-          char *       ifname,
+          const char * ifname,
           char *       bufp,
           struct sockaddr *sap)
 {
   /* Check if it is a hardware or IP address */
-  if(index(bufp, ':') == NULL)
+  if(strchr(bufp, ':') == NULL)
     {
       struct sockaddr  if_address;
       struct arpreq    arp_query;
 
+      /* Check if we have valid interface address type */
+      if(iw_check_if_addr_type(skfd, ifname) < 0)
+       {
+         fprintf(stderr, "%-8.16s  Interface doesn't support IP addresses\n", ifname);
+         return(-1);
+       }
+
       /* Read interface address */
       if(iw_in_inet(bufp, &if_address) < 0)
        {
@@ -917,20 +2361,35 @@ iw_in_addr(int           skfd,
             sizeof(struct sockaddr));
 
 #ifdef DEBUG
-      printf("IP Address %s => Hw Address = %s\n",
-            bufp, pr_ether(sap->sa_data));
+      {
+       char buf[20];
+       printf("IP Address %s => Hw Address = %s\n",
+              bufp, iw_saether_ntop(sap, buf));
+      }
 #endif
     }
   else /* If it's an hardware address */
-    /* Get the hardware address */
-    if(iw_in_ether(bufp, sap) < 0)
-      {
-       fprintf(stderr, "Invalid hardware address %s\n", bufp);
-       return(-1);
-      }
+    {
+      /* Check if we have valid mac address type */
+      if(iw_check_mac_addr_type(skfd, ifname) < 0)
+       {
+         fprintf(stderr, "%-8.16s  Interface doesn't support MAC addresses\n", ifname);
+         return(-1);
+       }
+
+      /* Get the hardware address */
+      if(iw_saether_aton(bufp, sap) == 0)
+       {
+         fprintf(stderr, "Invalid hardware address %s\n", bufp);
+         return(-1);
+       }
+    }
 
 #ifdef DEBUG
-  printf("Hw Address = %s\n", pr_ether(sap->sa_data));
+  {
+    char buf[20];
+    printf("Hw Address = %s\n", iw_saether_ntop(sap, buf));
+  }
 #endif
 
   return(0);
@@ -938,21 +2397,926 @@ iw_in_addr(int          skfd,
 
 /************************* MISC SUBROUTINES **************************/
 
+/* Size (in bytes) of various events */
+static const int priv_type_size[] = {
+       0,                              /* IW_PRIV_TYPE_NONE */
+       1,                              /* IW_PRIV_TYPE_BYTE */
+       1,                              /* IW_PRIV_TYPE_CHAR */
+       0,                              /* Not defined */
+       sizeof(__u32),                  /* IW_PRIV_TYPE_INT */
+       sizeof(struct iw_freq),         /* IW_PRIV_TYPE_FLOAT */
+       sizeof(struct sockaddr),        /* IW_PRIV_TYPE_ADDR */
+       0,                              /* Not defined */
+};
+
+/*------------------------------------------------------------------*/
+/*
+ * Max size in bytes of an private argument.
+ */
+int
+iw_get_priv_size(int   args)
+{
+  int  num = args & IW_PRIV_SIZE_MASK;
+  int  type = (args & IW_PRIV_TYPE_MASK) >> 12;
+
+  return(num * priv_type_size[type]);
+}
+
+/************************ EVENT SUBROUTINES ************************/
+/*
+ * The Wireless Extension API 14 and greater define Wireless Events,
+ * that are used for various events and scanning.
+ * Those functions help the decoding of events, so are needed only in
+ * this case.
+ */
+
+/* -------------------------- CONSTANTS -------------------------- */
+
+/* Type of headers we know about (basically union iwreq_data) */
+#define IW_HEADER_TYPE_NULL    0       /* Not available */
+#define IW_HEADER_TYPE_CHAR    2       /* char [IFNAMSIZ] */
+#define IW_HEADER_TYPE_UINT    4       /* __u32 */
+#define IW_HEADER_TYPE_FREQ    5       /* struct iw_freq */
+#define IW_HEADER_TYPE_ADDR    6       /* struct sockaddr */
+#define IW_HEADER_TYPE_POINT   8       /* struct iw_point */
+#define IW_HEADER_TYPE_PARAM   9       /* struct iw_param */
+#define IW_HEADER_TYPE_QUAL    10      /* struct iw_quality */
+
+/* Handling flags */
+/* Most are not implemented. I just use them as a reminder of some
+ * cool features we might need one day ;-) */
+#define IW_DESCR_FLAG_NONE     0x0000  /* Obvious */
+/* Wrapper level flags */
+#define IW_DESCR_FLAG_DUMP     0x0001  /* Not part of the dump command */
+#define IW_DESCR_FLAG_EVENT    0x0002  /* Generate an event on SET */
+#define IW_DESCR_FLAG_RESTRICT 0x0004  /* GET : request is ROOT only */
+                               /* SET : Omit payload from generated iwevent */
+#define IW_DESCR_FLAG_NOMAX    0x0008  /* GET : no limit on request size */
+/* Driver level flags */
+#define IW_DESCR_FLAG_WAIT     0x0100  /* Wait for driver event */
+
+/* ---------------------------- TYPES ---------------------------- */
+
+/*
+ * Describe how a standard IOCTL looks like.
+ */
+struct iw_ioctl_description
+{
+       __u8    header_type;            /* NULL, iw_point or other */
+       __u8    token_type;             /* Future */
+       __u16   token_size;             /* Granularity of payload */
+       __u16   min_tokens;             /* Min acceptable token number */
+       __u16   max_tokens;             /* Max acceptable token number */
+       __u32   flags;                  /* Special handling of the request */
+};
+
+/* -------------------------- VARIABLES -------------------------- */
+
+/*
+ * Meta-data about all the standard Wireless Extension request we
+ * know about.
+ */
+static const struct iw_ioctl_description standard_ioctl_descr[] = {
+       [SIOCSIWCOMMIT  - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_NULL,
+       },
+       [SIOCGIWNAME    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_CHAR,
+               .flags          = IW_DESCR_FLAG_DUMP,
+       },
+       [SIOCSIWNWID    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+               .flags          = IW_DESCR_FLAG_EVENT,
+       },
+       [SIOCGIWNWID    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+               .flags          = IW_DESCR_FLAG_DUMP,
+       },
+       [SIOCSIWFREQ    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_FREQ,
+               .flags          = IW_DESCR_FLAG_EVENT,
+       },
+       [SIOCGIWFREQ    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_FREQ,
+               .flags          = IW_DESCR_FLAG_DUMP,
+       },
+       [SIOCSIWMODE    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_UINT,
+               .flags          = IW_DESCR_FLAG_EVENT,
+       },
+       [SIOCGIWMODE    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_UINT,
+               .flags          = IW_DESCR_FLAG_DUMP,
+       },
+       [SIOCSIWSENS    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCGIWSENS    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCSIWRANGE   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_NULL,
+       },
+       [SIOCGIWRANGE   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = sizeof(struct iw_range),
+               .flags          = IW_DESCR_FLAG_DUMP,
+       },
+       [SIOCSIWPRIV    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_NULL,
+       },
+       [SIOCGIWPRIV    - SIOCIWFIRST] = { /* (handled directly by us) */
+               .header_type    = IW_HEADER_TYPE_NULL,
+       },
+       [SIOCSIWSTATS   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_NULL,
+       },
+       [SIOCGIWSTATS   - SIOCIWFIRST] = { /* (handled directly by us) */
+               .header_type    = IW_HEADER_TYPE_NULL,
+               .flags          = IW_DESCR_FLAG_DUMP,
+       },
+       [SIOCSIWSPY     - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = sizeof(struct sockaddr),
+               .max_tokens     = IW_MAX_SPY,
+       },
+       [SIOCGIWSPY     - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = sizeof(struct sockaddr) +
+                                 sizeof(struct iw_quality),
+               .max_tokens     = IW_MAX_SPY,
+       },
+       [SIOCSIWTHRSPY  - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = sizeof(struct iw_thrspy),
+               .min_tokens     = 1,
+               .max_tokens     = 1,
+       },
+       [SIOCGIWTHRSPY  - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = sizeof(struct iw_thrspy),
+               .min_tokens     = 1,
+               .max_tokens     = 1,
+       },
+       [SIOCSIWAP      - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_ADDR,
+       },
+       [SIOCGIWAP      - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_ADDR,
+               .flags          = IW_DESCR_FLAG_DUMP,
+       },
+       [SIOCSIWMLME    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .min_tokens     = sizeof(struct iw_mlme),
+               .max_tokens     = sizeof(struct iw_mlme),
+       },
+       [SIOCGIWAPLIST  - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = sizeof(struct sockaddr) +
+                                 sizeof(struct iw_quality),
+               .max_tokens     = IW_MAX_AP,
+               .flags          = IW_DESCR_FLAG_NOMAX,
+       },
+       [SIOCSIWSCAN    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .min_tokens     = 0,
+               .max_tokens     = sizeof(struct iw_scan_req),
+       },
+       [SIOCGIWSCAN    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_SCAN_MAX_DATA,
+               .flags          = IW_DESCR_FLAG_NOMAX,
+       },
+       [SIOCSIWESSID   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_ESSID_MAX_SIZE + 1,
+               .flags          = IW_DESCR_FLAG_EVENT,
+       },
+       [SIOCGIWESSID   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_ESSID_MAX_SIZE + 1,
+               .flags          = IW_DESCR_FLAG_DUMP,
+       },
+       [SIOCSIWNICKN   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_ESSID_MAX_SIZE + 1,
+       },
+       [SIOCGIWNICKN   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_ESSID_MAX_SIZE + 1,
+       },
+       [SIOCSIWRATE    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCGIWRATE    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCSIWRTS     - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCGIWRTS     - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCSIWFRAG    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCGIWFRAG    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCSIWTXPOW   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCGIWTXPOW   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCSIWRETRY   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCGIWRETRY   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCSIWENCODE  - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               /* Hack : this never returns any payload in event.
+                * Fix the 64->32 bit hack... */
+               .token_size     = 0,
+               .max_tokens     = IW_ENCODING_TOKEN_MAX,
+               .flags          = IW_DESCR_FLAG_EVENT | IW_DESCR_FLAG_RESTRICT,
+       },
+       [SIOCGIWENCODE  - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               /* Hack : this never returns any payload in event.
+                * Fix the 64->32 bit hack... */
+               .token_size     = 0,
+               .max_tokens     = IW_ENCODING_TOKEN_MAX,
+               .flags          = IW_DESCR_FLAG_DUMP | IW_DESCR_FLAG_RESTRICT,
+       },
+       [SIOCSIWPOWER   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCGIWPOWER   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCSIWMODUL   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCGIWMODUL   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCSIWGENIE   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_GENERIC_IE_MAX,
+       },
+       [SIOCGIWGENIE   - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_GENERIC_IE_MAX,
+       },
+       [SIOCSIWAUTH    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCGIWAUTH    - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_PARAM,
+       },
+       [SIOCSIWENCODEEXT - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .min_tokens     = sizeof(struct iw_encode_ext),
+               .max_tokens     = sizeof(struct iw_encode_ext) +
+                                 IW_ENCODING_TOKEN_MAX,
+       },
+       [SIOCGIWENCODEEXT - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .min_tokens     = sizeof(struct iw_encode_ext),
+               .max_tokens     = sizeof(struct iw_encode_ext) +
+                                 IW_ENCODING_TOKEN_MAX,
+       },
+       [SIOCSIWPMKSA - SIOCIWFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .min_tokens     = sizeof(struct iw_pmksa),
+               .max_tokens     = sizeof(struct iw_pmksa),
+       },
+};
+static const unsigned int standard_ioctl_num = (sizeof(standard_ioctl_descr) /
+                                               sizeof(struct iw_ioctl_description));
+
+/*
+ * Meta-data about all the additional standard Wireless Extension events
+ * we know about.
+ */
+static const struct iw_ioctl_description standard_event_descr[] = {
+       [IWEVTXDROP     - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_ADDR,
+       },
+       [IWEVQUAL       - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_QUAL,
+       },
+       [IWEVCUSTOM     - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_CUSTOM_MAX,
+       },
+       [IWEVREGISTERED - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_ADDR,
+       },
+       [IWEVEXPIRED    - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_ADDR, 
+       },
+       [IWEVGENIE      - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_GENERIC_IE_MAX,
+       },
+       [IWEVMICHAELMICFAILURE  - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT, 
+               .token_size     = 1,
+               .max_tokens     = sizeof(struct iw_michaelmicfailure),
+       },
+       [IWEVASSOCREQIE - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_GENERIC_IE_MAX,
+       },
+       [IWEVASSOCRESPIE        - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = IW_GENERIC_IE_MAX,
+       },
+       [IWEVPMKIDCAND  - IWEVFIRST] = {
+               .header_type    = IW_HEADER_TYPE_POINT,
+               .token_size     = 1,
+               .max_tokens     = sizeof(struct iw_pmkid_cand),
+       },
+};
+static const unsigned int standard_event_num = (sizeof(standard_event_descr) /
+                                               sizeof(struct iw_ioctl_description));
+
+/* Size (in bytes) of various events */
+static const int event_type_size[] = {
+       IW_EV_LCP_PK_LEN,       /* IW_HEADER_TYPE_NULL */
+       0,
+       IW_EV_CHAR_PK_LEN,      /* IW_HEADER_TYPE_CHAR */
+       0,
+       IW_EV_UINT_PK_LEN,      /* IW_HEADER_TYPE_UINT */
+       IW_EV_FREQ_PK_LEN,      /* IW_HEADER_TYPE_FREQ */
+       IW_EV_ADDR_PK_LEN,      /* IW_HEADER_TYPE_ADDR */
+       0,
+       IW_EV_POINT_PK_LEN,     /* Without variable payload */
+       IW_EV_PARAM_PK_LEN,     /* IW_HEADER_TYPE_PARAM */
+       IW_EV_QUAL_PK_LEN,      /* IW_HEADER_TYPE_QUAL */
+};
+
+/*------------------------------------------------------------------*/
+/*
+ * Initialise the struct stream_descr so that we can extract
+ * individual events from the event stream.
+ */
+void
+iw_init_event_stream(struct stream_descr *     stream, /* Stream of events */
+                    char *                     data,
+                    int                        len)
+{
+  /* Cleanup */
+  memset((char *) stream, '\0', sizeof(struct stream_descr));
+
+  /* Set things up */
+  stream->current = data;
+  stream->end = data + len;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Extract the next event from the event stream.
+ */
+int
+iw_extract_event_stream(struct stream_descr *  stream, /* Stream of events */
+                       struct iw_event *       iwe,    /* Extracted event */
+                       int                     we_version)
+{
+  const struct iw_ioctl_description *  descr = NULL;
+  int          event_type = 0;
+  unsigned int event_len = 1;          /* Invalid */
+  char *       pointer;
+  /* Don't "optimise" the following variable, it will crash */
+  unsigned     cmd_index;              /* *MUST* be unsigned */
+
+  /* Check for end of stream */
+  if((stream->current + IW_EV_LCP_PK_LEN) > stream->end)
+    return(0);
+
+#ifdef DEBUG
+  printf("DBG - stream->current = %p, stream->value = %p, stream->end = %p\n",
+        stream->current, stream->value, stream->end);
+#endif
+
+  /* Extract the event header (to get the event id).
+   * Note : the event may be unaligned, therefore copy... */
+  memcpy((char *) iwe, stream->current, IW_EV_LCP_PK_LEN);
+
+#ifdef DEBUG
+  printf("DBG - iwe->cmd = 0x%X, iwe->len = %d\n",
+        iwe->cmd, iwe->len);
+#endif
+
+  /* Check invalid events */
+  if(iwe->len <= IW_EV_LCP_PK_LEN)
+    return(-1);
+
+  /* Get the type and length of that event */
+  if(iwe->cmd <= SIOCIWLAST)
+    {
+      cmd_index = iwe->cmd - SIOCIWFIRST;
+      if(cmd_index < standard_ioctl_num)
+       descr = &(standard_ioctl_descr[cmd_index]);
+    }
+  else
+    {
+      cmd_index = iwe->cmd - IWEVFIRST;
+      if(cmd_index < standard_event_num)
+       descr = &(standard_event_descr[cmd_index]);
+    }
+  if(descr != NULL)
+    event_type = descr->header_type;
+  /* Unknown events -> event_type=0 => IW_EV_LCP_PK_LEN */
+  event_len = event_type_size[event_type];
+  /* Fixup for earlier version of WE */
+  if((we_version <= 18) && (event_type == IW_HEADER_TYPE_POINT))
+    event_len += IW_EV_POINT_OFF;
+
+  /* Check if we know about this event */
+  if(event_len <= IW_EV_LCP_PK_LEN)
+    {
+      /* Skip to next event */
+      stream->current += iwe->len;
+      return(2);
+    }
+  event_len -= IW_EV_LCP_PK_LEN;
+
+  /* Set pointer on data */
+  if(stream->value != NULL)
+    pointer = stream->value;                   /* Next value in event */
+  else
+    pointer = stream->current + IW_EV_LCP_PK_LEN;      /* First value in event */
+
+#ifdef DEBUG
+  printf("DBG - event_type = %d, event_len = %d, pointer = %p\n",
+        event_type, event_len, pointer);
+#endif
+
+  /* Copy the rest of the event (at least, fixed part) */
+  if((pointer + event_len) > stream->end)
+    {
+      /* Go to next event */
+      stream->current += iwe->len;
+      return(-2);
+    }
+  /* Fixup for WE-19 and later : pointer no longer in the stream */
+  /* Beware of alignement. Dest has local alignement, not packed */
+  if((we_version > 18) && (event_type == IW_HEADER_TYPE_POINT))
+    memcpy((char *) iwe + IW_EV_LCP_LEN + IW_EV_POINT_OFF,
+          pointer, event_len);
+  else
+    memcpy((char *) iwe + IW_EV_LCP_LEN, pointer, event_len);
+
+  /* Skip event in the stream */
+  pointer += event_len;
+
+  /* Special processing for iw_point events */
+  if(event_type == IW_HEADER_TYPE_POINT)
+    {
+      /* Check the length of the payload */
+      unsigned int     extra_len = iwe->len - (event_len + IW_EV_LCP_PK_LEN);
+      if(extra_len > 0)
+       {
+         /* Set pointer on variable part (warning : non aligned) */
+         iwe->u.data.pointer = pointer;
+
+         /* Check that we have a descriptor for the command */
+         if(descr == NULL)
+           /* Can't check payload -> unsafe... */
+           iwe->u.data.pointer = NULL; /* Discard paylod */
+         else
+           {
+             /* Those checks are actually pretty hard to trigger,
+              * because of the checks done in the kernel... */
+
+             unsigned int      token_len = iwe->u.data.length * descr->token_size;
+
+             /* Ugly fixup for alignement issues.
+              * If the kernel is 64 bits and userspace 32 bits,
+              * we have an extra 4+4 bytes.
+              * Fixing that in the kernel would break 64 bits userspace. */
+             if((token_len != extra_len) && (extra_len >= 4))
+               {
+                 union iw_align_u16    alt_dlen;
+                 unsigned int          alt_token_len;
+                 /* Usespace seems to not always like unaligned access,
+                  * so be careful and make sure to align value.
+                  * I hope gcc won't play any of its aliasing tricks... */
+                 alt_dlen.byte[0] = *(pointer);
+                 alt_dlen.byte[1] = *(pointer + 1);
+                 alt_token_len = alt_dlen.value * descr->token_size;
+#ifdef DEBUG
+                 printf("DBG - alt_token_len = %d\n", alt_token_len);
+#endif
+                 /* Verify that data is consistent if assuming 64 bit
+                  * alignement... */
+                 if((alt_token_len + 8) == extra_len)
+                   {
+                     /* Ok, let's redo everything */
+                     pointer -= event_len;
+                     pointer += 4;
+                     /* Dest has local alignement, not packed */
+                     memcpy((char *) iwe + IW_EV_LCP_LEN + IW_EV_POINT_OFF,
+                            pointer, event_len);
+                     pointer += event_len + 4;
+                     token_len = alt_token_len;
+                     /* We may have no payload */
+                     if(alt_token_len)
+                       iwe->u.data.pointer = pointer;
+                     else
+                       iwe->u.data.pointer = NULL;
+                   }
+               }
+
+             /* Discard bogus events which advertise more tokens than
+              * what they carry... */
+             if(token_len > extra_len)
+               iwe->u.data.pointer = NULL;     /* Discard paylod */
+             /* Check that the advertised token size is not going to
+              * produce buffer overflow to our caller... */
+             if((iwe->u.data.length > descr->max_tokens)
+                && !(descr->flags & IW_DESCR_FLAG_NOMAX))
+               iwe->u.data.pointer = NULL;     /* Discard paylod */
+             /* Same for underflows... */
+             if(iwe->u.data.length < descr->min_tokens)
+               iwe->u.data.pointer = NULL;     /* Discard paylod */
+#ifdef DEBUG
+             printf("DBG - extra_len = %d, token_len = %d, token = %d, max = %d, min = %d\n",
+                    extra_len, token_len, iwe->u.data.length, descr->max_tokens, descr->min_tokens);
+#endif
+           }
+       }
+      else
+       /* No data */
+       iwe->u.data.pointer = NULL;
+
+      /* Go to next event */
+      stream->current += iwe->len;
+    }
+  else
+    {
+      /* Ugly fixup for alignement issues.
+       * If the kernel is 64 bits and userspace 32 bits,
+       * we have an extra 4 bytes.
+       * Fixing that in the kernel would break 64 bits userspace. */
+      if((stream->value == NULL)
+        && ((((iwe->len - IW_EV_LCP_PK_LEN) % event_len) == 4)
+            || ((iwe->len == 12) && ((event_type == IW_HEADER_TYPE_UINT) ||
+                                     (event_type == IW_HEADER_TYPE_QUAL))) ))
+       {
+#ifdef DEBUG
+         printf("DBG - alt iwe->len = %d\n", iwe->len - 4);
+#endif
+         pointer -= event_len;
+         pointer += 4;
+         /* Beware of alignement. Dest has local alignement, not packed */
+         memcpy((char *) iwe + IW_EV_LCP_LEN, pointer, event_len);
+         pointer += event_len;
+       }
+
+      /* Is there more value in the event ? */
+      if((pointer + event_len) <= (stream->current + iwe->len))
+       /* Go to next value */
+       stream->value = pointer;
+      else
+       {
+         /* Go to next event */
+         stream->value = NULL;
+         stream->current += iwe->len;
+       }
+    }
+  return(1);
+}
+
+/*********************** SCANNING SUBROUTINES ***********************/
+/*
+ * The Wireless Extension API 14 and greater define Wireless Scanning.
+ * The normal API is complex, this is an easy API that return
+ * a subset of the scanning results. This should be enough for most
+ * applications that want to use Scanning.
+ * If you want to have use the full/normal API, check iwlist.c...
+ *
+ * Precaution when using scanning :
+ * The scanning operation disable normal network traffic, and therefore
+ * you should not abuse of scan.
+ * The scan need to check the presence of network on other frequencies.
+ * While you are checking those other frequencies, you can *NOT* be on
+ * your normal frequency to listen to normal traffic in the cell.
+ * You need typically in the order of one second to actively probe all
+ * 802.11b channels (do the maths). Some cards may do that in background,
+ * to reply to scan commands faster, but they still have to do it.
+ * Leaving the cell for such an extended period of time is pretty bad.
+ * Any kind of streaming/low latency traffic will be impacted, and the
+ * user will perceive it (easily checked with telnet). People trying to
+ * send traffic to you will retry packets and waste bandwidth. Some
+ * applications may be sensitive to those packet losses in weird ways,
+ * and tracing those weird behavior back to scanning may take time.
+ * If you are in ad-hoc mode, if two nodes scan approx at the same
+ * time, they won't see each other, which may create associations issues.
+ * For those reasons, the scanning activity should be limited to
+ * what's really needed, and continuous scanning is a bad idea.
+ * Jean II
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Process/store one element from the scanning results in wireless_scan
+ */
+static inline struct wireless_scan *
+iw_process_scanning_token(struct iw_event *            event,
+                         struct wireless_scan *        wscan)
+{
+  struct wireless_scan *       oldwscan;
+
+  /* Now, let's decode the event */
+  switch(event->cmd)
+    {
+    case SIOCGIWAP:
+      /* New cell description. Allocate new cell descriptor, zero it. */
+      oldwscan = wscan;
+      wscan = (struct wireless_scan *) malloc(sizeof(struct wireless_scan));
+      if(wscan == NULL)
+       return(wscan);
+      /* Link at the end of the list */
+      if(oldwscan != NULL)
+       oldwscan->next = wscan;
+
+      /* Reset it */
+      bzero(wscan, sizeof(struct wireless_scan));
+
+      /* Save cell identifier */
+      wscan->has_ap_addr = 1;
+      memcpy(&(wscan->ap_addr), &(event->u.ap_addr), sizeof (sockaddr));
+      break;
+    case SIOCGIWNWID:
+      wscan->b.has_nwid = 1;
+      memcpy(&(wscan->b.nwid), &(event->u.nwid), sizeof(iwparam));
+      break;
+    case SIOCGIWFREQ:
+      wscan->b.has_freq = 1;
+      wscan->b.freq = iw_freq2float(&(event->u.freq));
+      wscan->b.freq_flags = event->u.freq.flags;
+      break;
+    case SIOCGIWMODE:
+      wscan->b.mode = event->u.mode;
+      if((wscan->b.mode < IW_NUM_OPER_MODE) && (wscan->b.mode >= 0))
+       wscan->b.has_mode = 1;
+      break;
+    case SIOCGIWESSID:
+      wscan->b.has_essid = 1;
+      wscan->b.essid_on = event->u.data.flags;
+      memset(wscan->b.essid, '\0', IW_ESSID_MAX_SIZE + 1);
+      if((event->u.essid.pointer) && (event->u.essid.length))
+       memcpy(wscan->b.essid, event->u.essid.pointer, event->u.essid.length);
+      break;
+    case SIOCGIWENCODE:
+      wscan->b.has_key = 1;
+      wscan->b.key_size = event->u.data.length;
+      wscan->b.key_flags = event->u.data.flags;
+      if(event->u.data.pointer)
+       memcpy(wscan->b.key, event->u.essid.pointer, event->u.data.length);
+      else
+       wscan->b.key_flags |= IW_ENCODE_NOKEY;
+      break;
+    case IWEVQUAL:
+      /* We don't get complete stats, only qual */
+      wscan->has_stats = 1;
+      memcpy(&wscan->stats.qual, &event->u.qual, sizeof(struct iw_quality));
+      break;
+    case SIOCGIWRATE:
+      /* Scan may return a list of bitrates. As we have space for only
+       * a single bitrate, we only keep the largest one. */
+      if((!wscan->has_maxbitrate) ||
+        (event->u.bitrate.value > wscan->maxbitrate.value))
+       {
+         wscan->has_maxbitrate = 1;
+         memcpy(&(wscan->maxbitrate), &(event->u.bitrate), sizeof(iwparam));
+       }
+    case IWEVCUSTOM:
+      /* How can we deal with those sanely ? Jean II */
+    default:
+      break;
+   }   /* switch(event->cmd) */
+
+  return(wscan);
+}
+
 /*------------------------------------------------------------------*/
 /*
+ * Initiate the scan procedure, and process results.
+ * This is a non-blocking procedure and it will return each time
+ * it would block, returning the amount of time the caller should wait
+ * before calling again.
+ * Return -1 for error, delay to wait for (in ms), or 0 for success.
+ * Error code is in errno
  */
 int
-iw_byte_size(int       args)
+iw_process_scan(int                    skfd,
+               char *                  ifname,
+               int                     we_version,
+               wireless_scan_head *    context)
 {
-  int  ret = args & IW_PRIV_SIZE_MASK;
+  struct iwreq         wrq;
+  unsigned char *      buffer = NULL;          /* Results */
+  int                  buflen = IW_SCAN_MAX_DATA; /* Min for compat WE<17 */
+  unsigned char *      newbuf;
+
+  /* Don't waste too much time on interfaces (150 * 100 = 15s) */
+  context->retry++;
+  if(context->retry > 150)
+    {
+      errno = ETIME;
+      return(-1);
+    }
+
+  /* If we have not yet initiated scanning on the interface */
+  if(context->retry == 1)
+    {
+      /* Initiate Scan */
+      wrq.u.data.pointer = NULL;               /* Later */
+      wrq.u.data.flags = 0;
+      wrq.u.data.length = 0;
+      /* Remember that as non-root, we will get an EPERM here */
+      if((iw_set_ext(skfd, ifname, SIOCSIWSCAN, &wrq) < 0)
+        && (errno != EPERM))
+       return(-1);
+      /* Success : now, just wait for event or results */
+      return(250);     /* Wait 250 ms */
+    }
+
+ realloc:
+  /* (Re)allocate the buffer - realloc(NULL, len) == malloc(len) */
+  newbuf = realloc(buffer, buflen);
+  if(newbuf == NULL)
+    {
+      /* man says : If realloc() fails the original block is left untouched */
+      if(buffer)
+       free(buffer);
+      errno = ENOMEM;
+      return(-1);
+    }
+  buffer = newbuf;
+
+  /* Try to read the results */
+  wrq.u.data.pointer = buffer;
+  wrq.u.data.flags = 0;
+  wrq.u.data.length = buflen;
+  if(iw_get_ext(skfd, ifname, SIOCGIWSCAN, &wrq) < 0)
+    {
+      /* Check if buffer was too small (WE-17 only) */
+      if((errno == E2BIG) && (we_version > 16) && (buflen < 0xFFFF))
+       {
+         /* Some driver may return very large scan results, either
+          * because there are many cells, or because they have many
+          * large elements in cells (like IWEVCUSTOM). Most will
+          * only need the regular sized buffer. We now use a dynamic
+          * allocation of the buffer to satisfy everybody. Of course,
+          * as we don't know in advance the size of the array, we try
+          * various increasing sizes. Jean II */
+
+         /* Check if the driver gave us any hints. */
+         if(wrq.u.data.length > buflen)
+           buflen = wrq.u.data.length;
+         else
+           buflen *= 2;
+
+         /* wrq.u.data.length is 16 bits so max size is 65535 */
+         if(buflen > 0xFFFF)
+           buflen = 0xFFFF;
+
+         /* Try again */
+         goto realloc;
+       }
+
+      /* Check if results not available yet */
+      if(errno == EAGAIN)
+       {
+         free(buffer);
+         /* Wait for only 100ms from now on */
+         return(100);  /* Wait 100 ms */
+       }
+
+      free(buffer);
+      /* Bad error, please don't come back... */
+      return(-1);
+    }
+
+  /* We have the results, process them */
+  if(wrq.u.data.length)
+    {
+      struct iw_event          iwe;
+      struct stream_descr      stream;
+      struct wireless_scan *   wscan = NULL;
+      int                      ret;
+#ifdef DEBUG
+      /* Debugging code. In theory useless, because it's debugged ;-) */
+      int      i;
+      printf("Scan result [%02X", buffer[0]);
+      for(i = 1; i < wrq.u.data.length; i++)
+       printf(":%02X", buffer[i]);
+      printf("]\n");
+#endif
 
-  if(((args & IW_PRIV_TYPE_MASK) == IW_PRIV_TYPE_INT) ||
-     ((args & IW_PRIV_TYPE_MASK) == IW_PRIV_TYPE_FLOAT))
-    ret <<= 2;
+      /* Init */
+      iw_init_event_stream(&stream, (char *) buffer, wrq.u.data.length);
+      /* This is dangerous, we may leak user data... */
+      context->result = NULL;
 
-  if((args & IW_PRIV_TYPE_MASK) == IW_PRIV_TYPE_NONE)
-    return 0;
+      /* Look every token */
+      do
+       {
+         /* Extract an event and print it */
+         ret = iw_extract_event_stream(&stream, &iwe, we_version);
+         if(ret > 0)
+           {
+             /* Convert to wireless_scan struct */
+             wscan = iw_process_scanning_token(&iwe, wscan);
+             /* Check problems */
+             if(wscan == NULL)
+               {
+                 free(buffer);
+                 errno = ENOMEM;
+                 return(-1);
+               }
+             /* Save head of list */
+             if(context->result == NULL)
+               context->result = wscan;
+           }
+       }
+      while(ret > 0);
+    }
 
-  return ret;
+  /* Done with this interface - return success */
+  free(buffer);
+  return(0);
 }
 
+/*------------------------------------------------------------------*/
+/*
+ * Perform a wireless scan on the specified interface.
+ * This is a blocking procedure and it will when the scan is completed
+ * or when an error occur.
+ *
+ * The scan results are given in a linked list of wireless_scan objects.
+ * The caller *must* free the result himself (by walking the list).
+ * If there is an error, -1 is returned and the error code is available
+ * in errno.
+ *
+ * The parameter we_version can be extracted from the range structure
+ * (range.we_version_compiled - see iw_get_range_info()), or using
+ * iw_get_kernel_we_version(). For performance reason, you should
+ * cache this parameter when possible rather than querying it every time.
+ *
+ * Return -1 for error and 0 for success.
+ */
+int
+iw_scan(int                    skfd,
+       char *                  ifname,
+       int                     we_version,
+       wireless_scan_head *    context)
+{
+  int          delay;          /* in ms */
+
+  /* Clean up context. Potential memory leak if(context.result != NULL) */
+  context->result = NULL;
+  context->retry = 0;
+
+  /* Wait until we get results or error */
+  while(1)
+    {
+      /* Try to get scan results */
+      delay = iw_process_scan(skfd, ifname, we_version, context);
+
+      /* Check termination */
+      if(delay <= 0)
+       break;
+
+      /* Wait a bit */
+      usleep(delay * 1000);
+    }
+
+  /* End - return -1 or 0 */
+  return(delay);
+}