#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <net/if.h>
#include <net/if_media.h>
#include <net/bpf.h>
#include <net80211/ieee80211_ioctl.h>
#include <net80211/ieee80211.h>
#include <net/ethernet.h>
#include <net80211/ieee80211_radiotap.h>
#include <sys/endian.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <curses.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
int hopfreq = 500;
int sig_reset = 1*1000;
int ioctl_s = -1;
int bpf_s = -1;
struct chan_info {
int locked;
int chan;
struct ieee80211req ireq;
struct timeval last_hop;
} chaninfo;
#define CRYPT_NONE 0
#define CRYPT_WEP 1
#define CRYPT_WPA1 2
#define CRYPT_WPA 3
#define CRYPT_WPA1_TKIP 4
#define CRYPT_WPA1_TKIP_PSK 5
#define CRYPT_WPA1_CCMP 6
#define CRYPT_WPA1_CCMP_PSK 7
#define CRYPT_80211i 8
#define CRYPT_80211i_TKIP 9
#define CRYPT_80211i_TKIP_PSK 10
struct node_info {
unsigned char mac[6];
int signal;
int noise;
int max;
unsigned char ssid[256];
int chan;
int wep;
int pos;
int ap;
struct timeval seen;
struct node_info* prev;
struct node_info* next;
} *nodes = 0;
void clean_crap() {
struct node_info* next;
if (ioctl_s != -1)
close(ioctl_s);
if (bpf_s != -1)
close(bpf_s);
while (nodes) {
next = nodes->next;
free(nodes);
nodes = next;
}
}
char* mac2str(unsigned char* mac) {
static char ret[6*3];
sprintf(ret, "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return ret;
}
char* wep2str(int w) {
char* wep = 0;
static char res[14];
switch (w) {
case CRYPT_NONE:
wep = "";
break;
case CRYPT_WEP:
wep = "WEP";
break;
case CRYPT_WPA1:
wep = "WPA1";
break;
case CRYPT_WPA:
wep = "WPA?";
break;
case CRYPT_WPA1_TKIP:
wep = "WPA1-TKIP";
break;
case CRYPT_WPA1_TKIP_PSK:
wep = "WPA1-TKIP-PSK";
break;
case CRYPT_WPA1_CCMP:
wep = "WPA1-CCMP";
break;
case CRYPT_WPA1_CCMP_PSK:
wep = "WPA1-CCMP-PSK";
break;
case CRYPT_80211i:
wep = "i";
break;
case CRYPT_80211i_TKIP:
wep = "11i-TKIP";
break;
case CRYPT_80211i_TKIP_PSK:
wep = "11i-TKIP-PSK";
break;
default:
wep = "FIXME!";
break;
}
memset(res, ' ', sizeof(res));
assert(strlen(wep) < sizeof(res));
memcpy(res, wep, strlen(wep));
res[sizeof(res)-1] = 0;
return res;
}
char* ssid2str(struct node_info* node) {
static char res[24];
memset(res, ' ', sizeof(res));
res[0] = '[';
strcpy(&res[sizeof(res)-2], "]");
if (node->ap) {
int left = sizeof(res) - 3;
if (strlen(node->ssid) < left)
left = strlen(node->ssid);
memcpy(&res[1], node->ssid, left);
}
else {
memcpy(&res[1], "<client>", 8);
}
return res;
}
void save_state() {
FILE* f;
struct node_info* node = nodes;
f = fopen("stumbler.log", "w");
if (!f) {
perror("fopen()");
exit(1);
}
while (node) {
struct tm* t;
char tim[16];
t = localtime( (time_t*) &node->seen.tv_sec);
if (!t) {
perror("localtime()");
exit(1);
}
tim[0] = 0;
strftime(tim, sizeof(tim), "%H:%M:%S", t);
fprintf(f, "%s %s %s %2d %s 0x%.2x\n", tim,
mac2str(node->mac), wep2str(node->wep),
node->chan, ssid2str(node), node->max);
node = node->next;
}
fclose(f);
}
void cleanup(int x) {
endwin();
clean_crap();
exit(0);
}
void die(int p, char* msg) {
endwin();
if (p)
perror(msg);
else
printf("%s\n", msg);
clean_crap();
exit(1);
}
void display_chan() {
int x, y;
char tmp[3];
x = COLS - 2;
y = LINES - 1;
snprintf(tmp, sizeof(tmp), "%.2d", chaninfo.chan);
mvaddstr(y, x, tmp);
refresh();
}
void set_chan(int c) {
chaninfo.ireq.i_val = c;
if (ioctl(ioctl_s, SIOCS80211, &chaninfo.ireq) == -1)
die(1, "ioctl(SIOCS80211) [chan]");
chaninfo.chan = c;
if (gettimeofday(&chaninfo.last_hop, NULL) == -1)
die(1, "gettimeofday()");
display_chan();
}
void setup_if(char *dev) {
struct ifreq ifr;
unsigned int flags;
memset(&chaninfo.ireq, 0, sizeof(chaninfo.ireq));
strcpy(chaninfo.ireq.i_name, dev);
chaninfo.ireq.i_type = IEEE80211_IOC_CHANNEL;
set_chan(1);
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, dev);
if (ioctl(ioctl_s, SIOCGIFFLAGS, &ifr) == -1)
die(1, "ioctl(SIOCGIFFLAGS)");
flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16);
flags |= IFF_UP | IFF_PPROMISC;
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, dev);
ifr.ifr_flags = flags & 0xffff;
ifr.ifr_flagshigh = flags >> 16;
if (ioctl(ioctl_s, SIOCSIFFLAGS, &ifr) == -1)
die(1, "ioctl(SIOCSIFFLAGS)");
}
void open_bpf(char *dev, int dlt) {
int i;
char buf[64];
int fd = -1;
struct ifreq ifr;
for(i = 0;i < 16; i++) {
sprintf(buf, "/dev/bpf%d", i);
fd = open(buf, O_RDWR);
if(fd < 0) {
if(errno != EBUSY)
die(1,"can't open /dev/bpf");
continue;
}
else
break;
}
if(fd < 0)
die(1, "can't open /dev/bpf");
strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)-1);
ifr.ifr_name[sizeof(ifr.ifr_name)-1] = 0;
if(ioctl(fd, BIOCSETIF, &ifr) < 0)
die(1, "ioctl(BIOCSETIF)");
if (ioctl(fd, BIOCSDLT, &dlt) < 0)
die(1, "ioctl(BIOCSDLT)");
i = 1;
if(ioctl(fd, BIOCIMMEDIATE, &i) < 0)
die(1, "ioctl(BIOCIMMEDIATE)");
bpf_s = fd;
}
void user_input() {
static char chan[3];
static int pos = 0;
int c;
c = getch();
switch (c) {
case 'w':
save_state();
break;
case 'q':
cleanup(0);
break;
case 'c':
chaninfo.locked = !chaninfo.locked;
break;
case ERR:
die(0, "getch()");
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
chan[pos++] = c;
if (pos == 2) {
int ch = atoi(chan);
if (ch <= 11 && ch >= 1) {
set_chan(atoi(chan));
chaninfo.locked = 1;
}
pos = 0;
}
break;
default:
pos = 0;
break;
}
}
void display_node(struct node_info* node) {
int x = 0;
int y = 0;
int i;
char chan[3];
char* ssid = 0;
int sig, max, left, noise;
char* wep = 0;
y = node->pos;
if (y == -1)
return;
assert(y < LINES);
mvaddstr(y, x, mac2str(node->mac));
x += 6*3;
wep = wep2str(node->wep);
assert(wep);
mvaddstr(y, x, wep);
x += strlen(wep);
x++;
sprintf(chan, "%.2d", node->chan);
mvaddstr(y, x, chan);
x += 3;
ssid = ssid2str(node);
assert(ssid);
mvaddstr(y, x, ssid);
x += strlen(ssid);
x++;
left = COLS - x - 1;
sig = (int) ( ((double)node->signal)*left/100.0 );
noise=(int) ( ((double)node->noise)*left/100.0 );
max = (int) ( ((double)node->max)*left/100.0 );
for (i = 0; i < noise; i++)
mvaddch(y, x++, 'N');
for (; i < sig; i++)
mvaddch(y,x++, 'X');
for (; i < max; i++)
mvaddch(y,x++, ' ');
mvaddch(y,x++, '|');
for (; x < COLS-1; x++)
mvaddch(y, x, ' ');
assert (x <= COLS);
}
void update_node(struct node_info* data) {
struct node_info* node;
int sort = 0;
assert(data->signal <= 100);
node = nodes;
if (!node) {
node = (struct node_info*) malloc(sizeof(struct node_info));
if (!node)
die(1, "malloc()");
memset(node, 0, sizeof(*node));
memcpy(node->mac, data->mac, 6);
nodes = node;
}
while (node) {
if (memcmp(node->mac, data->mac, 6) == 0)
break;
if (!node->next) {
node->next = (struct node_info*)
malloc(sizeof(struct node_info));
if (!node->next)
die(1, "malloc()");
memset(node->next, 0, sizeof(*node->next));
memcpy(node->next->mac, data->mac, 6);
node->next->prev = node;
node->next->pos = node->pos+1;
node = node->next;
if (node->pos == LINES)
sort = 1;
break;
}
node = node->next;
}
assert(node);
if (sort) {
struct node_info* ni = nodes;
while (ni) {
if (ni->pos != -1)
ni->pos--;
display_node(ni);
ni = ni->next;
}
}
node->signal = data->signal;
if (data->signal > node->max)
node->max = data->signal;
if (gettimeofday(&node->seen, NULL) == -1)
die(1, "gettimeofday()");
if (data->ssid[0] != 0)
strcpy(node->ssid, data->ssid);
if (data->chan != -1)
node->chan = data->chan;
if (data->wep != -1) {
if (node->wep != CRYPT_WEP &&
node->wep != CRYPT_NONE &&
data->wep == CRYPT_WEP) {
}
else
node->wep = data->wep;
}
if (data->ap != -1)
node->ap = data->ap;
display_node(node);
refresh();
}
void get_beacon_info(unsigned char* data, int rd,
struct node_info* node) {
int blen = 8 + 2 + 2;
strcpy(node->ssid, "<hidden>");
node->chan = 0;
node->wep = CRYPT_NONE;
assert(rd >= blen);
if (IEEE80211_BEACON_CAPABILITY(data) & IEEE80211_CAPINFO_PRIVACY)
node->wep = CRYPT_WEP;
data += blen;
rd -= blen;
while (rd > 2) {
int eid, elen;
eid = *data;
data++;
elen = *data;
data++;
rd -= 2;
if (rd < elen) {
return;
}
if (eid == 0) {
if (elen == 1 && data[0] == 0) {
}
else {
memcpy(node->ssid, data, elen);
node->ssid[elen] = 0;
}
}
else if(eid == 3) {
if( elen != 1)
goto next;
node->chan = *data;
}
else if (eid == 221 && node->wep == CRYPT_WEP) {
struct ieee80211_ie_wpa* wpa;
wpa = (struct ieee80211_ie_wpa*) data;
if (elen < 6)
goto next;
if (!memcmp(wpa->wpa_oui, "\x00\x50\xf2", 3)) {
}
else
goto next;
if (wpa->wpa_type == WPA_OUI_TYPE &&
le16toh(wpa->wpa_version) == WPA_VERSION) {
int cipher, auth;
unsigned char* ptr;
node->wep = CRYPT_WPA1;
if (elen < 12)
goto next;
cipher = ((unsigned char*) wpa->wpa_mcipher)[3];
ptr = (unsigned char*)wpa + 12 +
4 * le16toh(wpa->wpa_uciphercnt);
if (elen < (ptr - data + 6))
goto next;
if ( *((unsigned short*) ptr) == 0)
goto next;
ptr += 2 + 3;
auth = *ptr;
if (cipher == WPA_CSE_TKIP) {
node->wep = CRYPT_WPA1_TKIP;
if (auth == WPA_ASE_8021X_PSK)
node->wep = CRYPT_WPA1_TKIP_PSK;
}
if (cipher == WPA_CSE_CCMP) {
node->wep = CRYPT_WPA1_CCMP;
if (auth == WPA_ASE_8021X_PSK)
node->wep = CRYPT_WPA1_CCMP_PSK;
}
}
}
else if (eid == 48 && node->wep == CRYPT_WEP) {
unsigned char* ptr;
ptr = data;
if (ptr[0] == 1 && ptr[1] == 0) {
unsigned short* count;
int cipher = 0;
ptr += 2;
node->wep = CRYPT_80211i;
if (!memcmp(ptr, "\x00\x0f\xac\x02", 4)) {
node->wep = CRYPT_80211i_TKIP;
cipher = 1;
}
ptr += 4;
count = (unsigned short*) ptr;
ptr +=2 + *count*4;
count = (unsigned short*) ptr;
if (*count) {
ptr += 2;
if (!memcmp(ptr,"\x00\x0f\xac\x02", 4)) {
if (cipher)
node->wep = CRYPT_80211i_TKIP_PSK;
}
}
}
}
next:
data += elen;
rd -= elen;
}
}
int get_packet_info(struct ieee80211_frame* wh,
unsigned char* body, int bodylen,
struct node_info* node) {
int type, stype;
node->chan = chaninfo.chan;
node->wep = -1;
node->ssid[0] = 0;
node->ap = -1;
type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
if (type == IEEE80211_FC0_TYPE_CTL)
return 0;
#if 0
if (wh->i_addr2[0] != 0) {
mvprintw(30,30,"%s %x",mac2str(wh->i_addr2), wh->i_fc[0]);
}
#endif
stype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
if (type == IEEE80211_FC0_TYPE_MGT &&
stype == IEEE80211_FC0_SUBTYPE_BEACON) {
get_beacon_info(body, bodylen, node);
node->ap = 1;
}
else if (type == IEEE80211_FC0_TYPE_DATA &&
stype == IEEE80211_FC0_SUBTYPE_DATA) {
if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
unsigned char* iv;
node->wep = CRYPT_WEP;
iv = body;
iv += 3;
if (*iv & (1 << 1)) {
#if 0
node->wep = CRYPT_WPA;
mvprintw(20,20, "shei");
exit(1);
#endif
}
}
if (wh->i_fc[1] & IEEE80211_FC1_DIR_FROMDS)
node->ap = 1;
else
node->ap = 0;
}
memcpy(node->mac, wh->i_addr2, 6);
return 1;
}
void radiotap(unsigned char* data, int rd) {
struct ieee80211_radiotap_header* rth;
struct ieee80211_frame* wh;
char* body;
struct node_info node;
int8_t signal_dbm, noise_dbm;
uint8_t signal_db, noise_db;
int dbm = 0;
int signal = 0;
int i;
rd -= 4;
rth = (struct ieee80211_radiotap_header*) data;
wh = (struct ieee80211_frame*)
((char*)rth + rth->it_len);
rd -= rth->it_len;
assert (rd >= 0);
body = (char*) wh + sizeof(*wh);
rd -= sizeof(*wh);
if (!get_packet_info(wh, body, rd, &node))
return;
body = (char*) rth + sizeof(*rth);
signal_dbm = noise_dbm = signal_db = noise_db = 0;
for (i = IEEE80211_RADIOTAP_TSFT; i <= IEEE80211_RADIOTAP_EXT; i++) {
if (!(rth->it_present & (1 << i)))
continue;
switch (i) {
case IEEE80211_RADIOTAP_TSFT:
body += sizeof(uint64_t);
break;
case IEEE80211_RADIOTAP_FLAGS:
case IEEE80211_RADIOTAP_RATE:
body += sizeof(uint8_t);
break;
case IEEE80211_RADIOTAP_CHANNEL:
body += sizeof(uint16_t)*2;
break;
case IEEE80211_RADIOTAP_FHSS:
body += sizeof(uint16_t);
break;
case IEEE80211_RADIOTAP_DBM_ANTSIGNAL:
signal_dbm = *body;
body++;
dbm = 1;
break;
case IEEE80211_RADIOTAP_DBM_ANTNOISE:
noise_dbm = *body;
body++;
break;
case IEEE80211_RADIOTAP_DB_ANTSIGNAL:
signal_db = *((unsigned char*)body);
body++;
break;
case IEEE80211_RADIOTAP_DB_ANTNOISE:
noise_db = *((unsigned char*)body);
body++;
break;
case IEEE80211_RADIOTAP_EXT:
abort();
break;
}
}
if (dbm) {
signal = signal_dbm - noise_dbm;
}
else {
signal = signal_db - noise_db;
}
if (signal < 0)
signal = 0;
node.signal = signal;
#if 0
if (node.signal > 100 || node.signal < 0) {
mvprintw(25,25, "sig=%d", node.signal);
}
#else
assert (node.signal <= 100 && node.signal >= 0);
#endif
update_node(&node);
}
void bpf_input() {
static unsigned char buf[4096];
int rd;
struct bpf_hdr* bpfh;
unsigned char* data;
rd = read(bpf_s, buf, sizeof(buf));
if (rd == -1)
die(1,"read()");
bpfh = (struct bpf_hdr*) buf;
rd -= bpfh->bh_hdrlen;
if (rd != bpfh->bh_caplen) {
assert( rd > bpfh->bh_caplen);
rd = bpfh->bh_caplen;
}
data = (unsigned char*) bpfh + bpfh->bh_hdrlen;
radiotap(data, rd);
}
unsigned long elapsed_ms(struct timeval* now, struct timeval* prev) {
unsigned long elapsed = 0;
if (now->tv_sec > prev->tv_sec)
elapsed = 1000*1000 - prev->tv_usec +
now->tv_usec;
else {
assert(now->tv_sec == prev->tv_sec);
elapsed = now->tv_usec - prev->tv_usec;
}
elapsed /= 1000;
elapsed += (now->tv_sec - prev->tv_sec)*1000;
return elapsed;
}
void chanhop(struct timeval* tv) {
unsigned long elapsed = 0;
if (gettimeofday(tv, NULL) == -1)
die(1, "gettimeofday()");
elapsed = elapsed_ms(tv, &chaninfo.last_hop);
if (elapsed >= hopfreq) {
int c;
c = chaninfo.chan + 1;
if (c > 11)
c = 1;
set_chan(c);
elapsed = hopfreq;
}
else {
elapsed = hopfreq - elapsed;
}
tv->tv_sec = elapsed/1000;
tv->tv_usec = (elapsed - tv->tv_sec*1000)*1000;
}
void check_seen(struct timeval* tv) {
unsigned long elapsed = 0;
struct timeval now;
int need_refresh = 0;
unsigned long min_wait = 0;
unsigned long will_wait;
will_wait = tv->tv_sec*1000+tv->tv_usec/1000;
min_wait = will_wait;
struct node_info* node = nodes;
if (gettimeofday(&now, NULL) == -1)
die(1, "gettimeofday()");
while(node) {
if (node->signal) {
elapsed = elapsed_ms(&now, &node->seen);
if (elapsed >= sig_reset) {
node->signal = 0;
display_node(node);
need_refresh = 1;
}
else {
unsigned long left;
left = sig_reset - elapsed;
if (left < min_wait)
left = min_wait;
}
}
node = node->next;
}
if (need_refresh)
refresh();
if (min_wait < will_wait) {
tv->tv_sec = min_wait/1000;
tv->tv_usec = (min_wait - tv->tv_sec*1000)*1000;
}
}
void own(char* ifname) {
int rd;
fd_set fds;
struct timeval tv;
int dlt = DLT_IEEE802_11_RADIO;
hopfreq = 1000;
setup_if(ifname);
open_bpf(ifname, dlt);
while(1) {
if (!chaninfo.locked)
chanhop(&tv);
else {
tv.tv_sec = 1;
tv.tv_usec = 0;
}
check_seen(&tv);
FD_ZERO(&fds);
FD_SET(0, &fds);
FD_SET(bpf_s, &fds);
rd = select(bpf_s+1, &fds,NULL , NULL, &tv);
if (rd == -1)
die(1, "select()");
if (FD_ISSET(0, &fds))
user_input();
if (FD_ISSET(bpf_s, &fds))
bpf_input();
}
}
void init_globals() {
ioctl_s = socket(PF_INET, SOCK_DGRAM, 0);
if (ioctl_s == -1) {
perror("socket()");
exit(1);
}
chaninfo.locked = 0;
chaninfo.chan = 0;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <iface>\n", argv[0]);
exit(1);
}
init_globals();
initscr(); cbreak(); noecho();
nonl();
intrflush(stdscr, FALSE);
keypad(stdscr, TRUE);
curs_set(0);
clear();
refresh();
signal(SIGINT, cleanup);
signal(SIGTERM, cleanup);
own(argv[1]);
cleanup(0);
exit(0);
}