/* * iwinfo - Wireless Information Library - Linux Wireless Extension Backend * * Copyright (C) 2009 Jo-Philipp Wich * * The iwinfo library is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * The iwinfo library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with the iwinfo library. If not, see http://www.gnu.org/licenses/. * * Parts of this code are derived from the Linux wireless tools, iwlib.c, * iwlist.c and iwconfig.c in particular. */ #include "iwinfo.h" #include "iwinfo_wext_scan.h" static int ioctl_socket = -1; static int wext_ioctl(const char *ifname, int cmd, struct iwreq *wrq) { /* prepare socket */ if( ioctl_socket == -1 ) ioctl_socket = socket(AF_INET, SOCK_DGRAM, 0); strncpy(wrq->ifr_name, ifname, IFNAMSIZ); return ioctl(ioctl_socket, cmd, wrq); } static double wext_freq2float(const struct iw_freq *in) { int i; double res = (double) in->m; for(i = 0; i < in->e; i++) res *= 10; return res; } static int wext_extract_event(struct stream_descr *stream, struct iw_event *iwe, int wev) { const struct iw_ioctl_description *descr = NULL; int event_type = 0; unsigned int event_len = 1; char *pointer; unsigned cmd_index; /* *MUST* be unsigned */ /* Check for end of stream */ if((stream->current + IW_EV_LCP_PK_LEN) > stream->end) return 0; /* 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); /* 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((wev <= 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 */ /* 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( (wev > 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)) { uint16_t alt_dlen = *((uint16_t *) pointer); unsigned int alt_token_len = alt_dlen * descr->token_size; 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; iwe->u.data.pointer = pointer; token_len = alt_token_len; } } /* 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 */ } } 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))) )) { 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; } static inline void wext_fill_wpa(unsigned char *iebuf, int buflen, struct iwinfo_scanlist_entry *e) { int ielen = iebuf[1] + 2; int offset = 2; /* Skip the IE id, and the length. */ unsigned char wpa1_oui[3] = {0x00, 0x50, 0xf2}; unsigned char wpa2_oui[3] = {0x00, 0x0f, 0xac}; unsigned char *wpa_oui; int i; uint16_t ver = 0; uint16_t cnt = 0; int wpa1 = 0, wpa2 = 0; char buf[256]; struct iwinfo_crypto_entry *ce = &e->crypto; if(ielen > buflen) ielen = buflen; switch(iebuf[0]) { case 0x30: /* WPA2 */ /* Check if we have enough data */ if(ielen < 4) return; wpa_oui = wpa2_oui; break; case 0xdd: /* WPA or else */ wpa_oui = wpa1_oui; /* Not all IEs that start with 0xdd are WPA. * * So check that the OUI is valid. */ if((ielen < 8) || ((memcmp(&iebuf[offset], wpa_oui, 3) != 0) && (iebuf[offset+3] == 0x01))) return; offset += 4; break; default: return; } /* Pick version number (little endian) */ ver = iebuf[offset] | (iebuf[offset + 1] << 8); offset += 2; if(iebuf[0] == 0xdd) wpa1 = 1; if(iebuf[0] == 0x30) wpa2 = 1; if( wpa1 && (ce->wpa_version == 2) ) ce->wpa_version = 3; else if( wpa2 && (ce->wpa_version == 1) ) ce->wpa_version = 3; else if( wpa1 && !ce->wpa_version ) ce->wpa_version = 1; else if( wpa2 && !ce->wpa_version ) ce->wpa_version = 2; if(ielen < (offset + 4)) { ce->group_ciphers |= (1<<2); /* TKIP */ ce->pair_ciphers |= (1<<2); /* TKIP */ ce->auth_suites |= (1<<2); /* PSK */ return; } if(memcmp(&iebuf[offset], wpa_oui, 3) != 0) ce->group_ciphers |= (1<<7); /* Proprietary */ else ce->group_ciphers |= (1<pair_ciphers |= (1<<2); /* TKIP */ ce->auth_suites |= (1<<2); /* PSK */ return; } /* Otherwise, we have some number of pairwise ciphers. */ cnt = iebuf[offset] | (iebuf[offset + 1] << 8); offset += 2; if(ielen < (offset + 4*cnt)) return; *buf = '\0'; for(i = 0; i < cnt; i++) { if(memcmp(&iebuf[offset], wpa_oui, 3) != 0) ce->pair_ciphers |= (1<<7); /* Proprietary */ else if(iebuf[offset+3] <= IW_IE_CYPHER_NUM) ce->pair_ciphers |= (1<pair_ciphers[ce->pair_cipher_num++] = 255; /* Unknown */ offset += 4; } /* Check if we are done */ if(ielen < (offset + 2)) return; /* Now, we have authentication suites. */ cnt = iebuf[offset] | (iebuf[offset + 1] << 8); offset += 2; *buf = '\0'; if(ielen < (offset + 4*cnt)) return; for(i = 0; i < cnt; i++) { if(memcmp(&iebuf[offset], wpa_oui, 3) != 0) ce->auth_suites |= (1<<7); /* Proprietary */ else if(iebuf[offset+3] <= IW_IE_KEY_MGMT_NUM) ce->auth_suites |= (1<auth_suites[ce->auth_suite_num++] = 255; /* Unknown */ offset += 4; } } static inline void wext_fill_entry(struct stream_descr *stream, struct iw_event *event, struct iw_range *iw_range, int has_range, struct iwinfo_scanlist_entry *e) { int i; double freq; /* Now, let's decode the event */ switch(event->cmd) { case SIOCGIWAP: memcpy(e->mac, &event->u.ap_addr.sa_data, 6); break; case SIOCGIWFREQ: if( event->u.freq.m >= 1000 ) { freq = wext_freq2float(&(event->u.freq)); for(i = 0; i < iw_range->num_frequency; i++) { if( wext_freq2float(&iw_range->freq[i]) == freq ) { e->channel = iw_range->freq[i].i; break; } } } else { e->channel = event->u.freq.m; } break; case SIOCGIWMODE: switch(event->u.mode) { case 1: sprintf((char *) e->mode, "Ad-Hoc"); break; case 2: case 3: sprintf((char *) e->mode, "Master"); break; default: sprintf((char *) e->mode, "Unknown"); } break; case SIOCGIWESSID: if( event->u.essid.pointer && event->u.essid.length && event->u.essid.flags ) memcpy(e->ssid, event->u.essid.pointer, event->u.essid.length); break; case SIOCGIWENCODE: e->crypto.enabled = !(event->u.data.flags & IW_ENCODE_DISABLED); break; case IWEVQUAL: e->signal = event->u.qual.level; e->quality = event->u.qual.qual; e->quality_max = iw_range->max_qual.qual; break; #if 0 case SIOCGIWRATE: if(state->val_index == 0) { lua_pushstring(L, "bitrates"); lua_newtable(L); } //iw_print_bitrate(buffer, sizeof(buffer), event->u.bitrate.value); snprintf(buffer, sizeof(buffer), "%d", event->u.bitrate.value); lua_pushinteger(L, state->val_index + 1); lua_pushstring(L, buffer); lua_settable(L, -3); /* Check for termination */ if(stream->value == NULL) { lua_settable(L, -3); state->val_index = 0; } else state->val_index++; break; #endif case IWEVGENIE: i = 0; while(i <= (event->u.data.length - 2)) { switch(((unsigned char *)event->u.data.pointer)[i]) { case 0xdd: /* WPA1 (and other) */ case 0x30: /* WPA2 */ wext_fill_wpa((unsigned char *)event->u.data.pointer + i, event->u.data.length, e); break; } i += ((unsigned char *)event->u.data.pointer)[i+1] + 2; } break; } } int wext_get_scanlist(const char *ifname, char *buf, int *len) { struct iwreq wrq; struct iw_scan_req scanopt; /* Options for 'set' */ unsigned char *buffer = NULL; /* Results */ int buflen = IW_SCAN_MAX_DATA; /* Min for compat WE<17 */ struct iw_range range; int has_range = 1; struct timeval tv; /* Select timeout */ int timeout = 15000000; /* 15s */ int entrylen = 0; struct iwinfo_scanlist_entry e; wrq.u.data.pointer = (caddr_t) ⦥ wrq.u.data.length = sizeof(struct iw_range); wrq.u.data.flags = 0; if( wext_ioctl(ifname, SIOCGIWRANGE, &wrq) >= 0 ) { /* Init timeout value -> 250ms between set and first get */ tv.tv_sec = 0; tv.tv_usec = 250000; /* Clean up set args */ memset(&scanopt, 0, sizeof(scanopt)); wrq.u.data.pointer = NULL; wrq.u.data.flags = 0; wrq.u.data.length = 0; /* Initiate Scanning */ if( wext_ioctl(ifname, SIOCSIWSCAN, &wrq) >= 0 ) { timeout -= tv.tv_usec; /* Forever */ while(1) { fd_set rfds; /* File descriptors for select */ int last_fd; /* Last fd */ int ret; /* Guess what ? We must re-generate rfds each time */ FD_ZERO(&rfds); last_fd = -1; /* In here, add the rtnetlink fd in the list */ /* Wait until something happens */ ret = select(last_fd + 1, &rfds, NULL, NULL, &tv); /* Check if there was an error */ if(ret < 0) { if(errno == EAGAIN || errno == EINTR) continue; return -1; } /* Check if there was a timeout */ if(ret == 0) { unsigned char *newbuf; realloc: /* (Re)allocate the buffer - realloc(NULL, len) == malloc(len) */ newbuf = realloc(buffer, buflen); if(newbuf == NULL) { if(buffer) free(buffer); 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( wext_ioctl(ifname, SIOCGIWSCAN, &wrq) ) { /* Check if buffer was too small (WE-17 only) */ if((errno == E2BIG) && (range.we_version_compiled > 16)) { /* 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; /* Try again */ goto realloc; } /* Check if results not available yet */ if(errno == EAGAIN) { /* Restart timer for only 100ms*/ tv.tv_sec = 0; tv.tv_usec = 100000; timeout -= tv.tv_usec; if(timeout > 0) continue; /* Try again later */ } /* Bad error */ free(buffer); return -1; } else { /* We have the results, go to process them */ break; } } } if( wrq.u.data.length ) { struct iw_event iwe; struct stream_descr stream; int ret; int first = 1; memset(&stream, 0, sizeof(stream)); stream.current = (char *)buffer; stream.end = (char *)buffer + wrq.u.data.length; do { /* Extract an event and print it */ ret = wext_extract_event(&stream, &iwe, range.we_version_compiled); if(ret >= 0) { if( (iwe.cmd == SIOCGIWAP) || (ret == 0) ) { if( first ) { first = 0; } else if( (entrylen + sizeof(struct iwinfo_scanlist_entry)) <= IWINFO_BUFSIZE ) { /* if encryption is off, clear the crypto strunct */ if( !e.crypto.enabled ) memset(&e.crypto, 0, sizeof(struct iwinfo_crypto_entry)); memcpy(&buf[entrylen], &e, sizeof(struct iwinfo_scanlist_entry)); entrylen += sizeof(struct iwinfo_scanlist_entry); } else { /* we exceed the callers buffer size, abort here ... */ break; } memset(&e, 0, sizeof(struct iwinfo_scanlist_entry)); } wext_fill_entry(&stream, &iwe, &range, has_range, &e); } } while(ret > 0); free(buffer); *len = entrylen; return 0; } free(buffer); return 0; } } return -1; }