/*
    This file is part of AirSnort.

    AirSnort is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    AirSnort 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 AirSnort; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*
   4/17/02 - Orinoco's now handle beacons properly thanks to monitor mode patch
             fix by johnp@chem...
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/wireless.h>

#include "capture.h"
#include "crack.h"
#include "bssidlist.h"
#include <pcap.h>

#ifndef SIOCIWFIRSTPRIV
#define SIOCIWFIRSTPRIV SIOCDEVPRIVATE
#endif
#define P80211_IFREQ (SIOCIWFIRSTPRIV  + 1)

#define ADDR1(x) ((x)+4)
#define ADDR2(x) ((x)+10)
#define ADDR3(x) ((x)+16)

//PROTOTYPES
void quick_message(char *title, char *message);
void dump(unsigned char *pkt, int len);
int do_ioctl(unsigned char *userMsg);
void setPointers(CaptureRec *cr, const unsigned char *pkt);
void changeChannel(int x);
int resetMAC(void);

extern int scan;
extern int chan;
extern int doCapture;
extern int logfile;
extern int readPcapFile;
extern int oldscan;
extern int cardType;
extern int breadth40;
extern int breadth128;

extern char cmd[80];
char dev[WLAN_DEVNAMELEN_MAX] = "wlan0";
int count = 0;
int lasttime = 0;

//initialize pointers according to TO_DS,
//FROM_DS flags.  This saves time during packet decoding
void setPointers(CaptureRec *cr, const unsigned char *pkt) {
   cr->iv = pkt + 24;
   switch (pkt[1] & 0x03) {
      case 0:  //STA to STA, or management and control
         cr->bssid = ADDR3(pkt);
         cr->sta =  ADDR2(pkt);
         break;
      case 2:  //AP to STA data
         cr->bssid = ADDR2(pkt);
         cr->sta = ADDR1(pkt); 
         break;
      case 3:  //AP to AP 
         cr->iv += 6;
      case 1:  //STA to AP data
         cr->bssid = ADDR1(pkt);
         cr->sta = ADDR2(pkt); 
         break;
   }
}

void dump(unsigned char *pkt, int len) {
   int i = 0;
   int j = 0;
   for (i = 0; i < len; i += 16) {
      for (j = 0; j < 16; j++) {
         if (i + j >= len) {
            fprintf(stdout, "   "); 
         }
         else {
            fprintf(stdout, "%2.2x ", pkt[i + j]); 
         }
      }
      fprintf(stdout, "  "); 
      for (j = 0; j < 16; j++) {
         if (i + j >= len) break;
         if (isprint(pkt[i + j])) {
            fprintf(stdout, "%c", pkt[i + j]); 
         }
         else {
            fprintf(stdout, "."); 
         }
      }
      fprintf(stdout, "\n"); 
   }
   fprintf(stdout, "\n----------------------------------------------------------------\n\n"); 
}

//the thread function for the packet capture thread.  It opens a packet 
//source and collects packets until instructed to stop. Packets are 
//parsed for interesting info and placed on the packet queue for consumption
//by the cracking thread
void *capture(void *arg) {
   CaptureArg *parms = (CaptureArg*) arg;
   int namelen, whichType, channelLoc, isData, crclen;
   PacketInfo pi;
   CaptureRec cap;
   BssidList *apNode = NULL;
   const unsigned char *pkt;
   unsigned char fc0 = 0, type = 0, subType = 0;
   struct pcap_pkthdr pktHdr;
   int allF = 0xFFFFFFFF;

   cap.pInfo = &pi;

   while (doCapture) {
      pi.pack = NULL;
      pi.name = NULL;
      pi.wep = 0;
      apNode = NULL;
      isData = 0;

      pkt = pcap_next(parms->pcap, &pktHdr);
      if (pkt == NULL) {
         if (readPcapFile) break;  //reached end of file
         continue;
      }
      if (parms->dump) {
         pcap_dump((unsigned char*)parms->dump, &pktHdr, pkt);
      }
      //test for prism2 appended dummy crc
      crclen = memcmp(pkt + pktHdr.len - 4, &allF, 4) ? 0 : 4;
      pktHdr.len -= crclen;
      pi.channel = -1;
      pkt += parms->offset;
      pi.raw = pkt;

      whichType = pkt[1] & 0x03;
      setPointers(&cap, pkt);
      fc0 = pkt[0];
      type = fc0 & 0x0C;
      subType = (fc0 >> 4);
      
      switch (type) {
      case 0:  //management
         if ((subType == 5) || (subType == 8)) {
            //beacon or probe response frame
            namelen = pkt[37];
            channelLoc = 38 + namelen + 2;
            channelLoc += pkt[channelLoc - 1];
            if (pkt[channelLoc] == 3) {
               pi.channel = pkt[channelLoc + 2];
            }
            apNode = bssidFind(pkt + 16);  //ADDR3
            if (apNode) {
               if (apNode->name) {
                  if (subType == 8) { //don't take name more than once from a beacon
                     break;
                  }
                  free(apNode->name);
               }
               apNode->name = (char*) malloc(namelen + 1);
               strncpy(apNode->name, pkt + 38, namelen);
               (apNode->name)[namelen] = 0;
               break;
            }
          //might want to check elementid at pkt[36] == 0 as well
            pi.name = (char*) malloc(namelen + 1);
            strncpy(pi.name, pkt + 38, namelen);
            pi.name[namelen] = 0;
            pi.wep = pkt[34] & 0x10;
         }
         break;
      case 0x04:  //control
         //break;
         continue;
      case 0x08:
         if (subType != 0) break;  //not a data frame
         isData = 1;
         pktHdr.len -= whichType == 3 ? (parms->offset + 30) 
                                      : (parms->offset + 24);  //adjust len to size of frame body

         //this is a sloppy way to detect unencrypted packets, but since 0xAA is not
         //an IV byte of interest anyway, its probably not a big deal
         if (*((unsigned short *)cap.iv) != 0xAAAA || (pkt[1] & 0x40)) {
            pi.wep = 1;
            if (isResolved(cap.iv)) {
              pi.pack = (Packet*) malloc(sizeof(Packet));
              pi.pack->len = pktHdr.len;  //i.e. 802.11b "Frame Body"
              pi.pack->buf = (unsigned char*) malloc(pktHdr.len);
              memcpy(pi.pack->buf, cap.iv, pktHdr.len);
              pi.pack->next = NULL;
            }
         }
      } //switch
      addPacket(apNode, &cap, isData);
   }

   resetMAC();
   if (parms->dump) {
      pcap_dump_close(parms->dump);
   }
   pcap_close(parms->pcap);
   free(parms);
   if (readPcapFile) {
      readPcapFile = 0;
      scan = oldscan;
   }
   doCapture = 0;
   pthread_exit(NULL);
}

void changeChannel(int x) {
   if (doCapture && scan) {
      chan = (chan % 11) + 1;
      setChannel(chan);
   }
}

/* 
* The setChannel function was lifted from the wlanctl source file:
*
*src/wlanctl/wlanctl.c
*
* user utility for the wlan card
*
* Copyright (C) 1999 AbsoluteValue Systems, Inc.  All Rights Reserved.
* --------------------------------------------------------------------
*
* linux-wlan
*
    linux-wlan is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    linux-wlan 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 linux-wlan; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* --------------------------------------------------------------------
*
* Inquiries regarding the linux-wlan Open Source project can be
* made directly to:
*
* AbsoluteValue Systems Inc.
* info@linux-wlan.com
* http://www.linux-wlan.com
*
* --------------------------------------------------------------------
*/

/*
 * Set the NIC into promiscuous mode on the indicated channel
 * This code was distilled from wlanctl.c which is part of 
 * the linux-wlan-ng package.  This is the only wlanctl functionality
 * needed in airsnort, so I put it here rather than shelling out
 * to invoke wlanctl-ng every time I wanted to change channels. CSE.
 */

#define MAKE_SYS_CALL
/* 
 * as opposed to making ioctl calls directly.  We can make ioctls 
 * directly if we can tie into the linux-wlan-ng header files
 */

int setChannel( unsigned char channel )
{
   int result = 0;
   static int fd = -1;
   struct iwreq ireq;  //for Orinoco
   int *ptr;
   if (fd == -1) {
      fd = socket(AF_INET, SOCK_STREAM, 0);
   }
   if (cardType == ORINOCO) {  //assumes patched pcmcia-cs-3.1.31 and using orinoco_cs
      /* get a socket */
//      fd = socket(AF_INET, SOCK_STREAM, 0);
      if ( fd == -1 ) {
         return -1;
      }
      ptr = (int *) ireq.u.name;
      ptr[0] = 1;
      ptr[1] = chan;
      strcpy(ireq.ifr_ifrn.ifrn_name, dev);
      result = ioctl( fd, SIOCIWFIRSTPRIV + 0x8, &ireq);
//      close(fd);
   }
   else if (cardType == PRISM) {
#ifndef MAKE_SYS_CALL
      p80211msg_lnxreq_wlansniff_t sniff;
      memset(&sniff, 0, sizeof(p80211msg_lnxreq_wlansniff_t));
      sniff.msgcode = DIDmsg_lnxreq_wlansniff;
      sniff.msglen = sizeof(p80211msg_lnxreq_wlansniff_t);
      strcpy((char*) sniff.devname, dev);

      sniff.enable.did = DIDmsg_lnxreq_wlansniff_enable;
      sniff.enable.len = 4;
      sniff.enable.status = 0;
      sniff.enable.data = P80211ENUM_truth_true;

      sniff.channel.did = DIDmsg_lnxreq_wlansniff_channel;
      sniff.channel.len = 4;
      sniff.channel.status = 0;
      sniff.channel.data = channel;

      sniff.prismheader.did = DIDmsg_lnxreq_wlansniff_prismheader;
      sniff.prismheader.len = 4;
      sniff.prismheader.status = 0;
      sniff.prismheader.data = P80211ENUM_truth_true;

      sniff.keepwepflags.did = DIDmsg_lnxreq_wlansniff_keepwepflags;
      sniff.keepwepflags.len = 4;
      sniff.keepwepflags.status = 0;
      sniff.keepwepflags.data = P80211ENUM_truth_true;

      sniff.resultcode.did = DIDmsg_lnxreq_wlansniff_resultcode;
      sniff.resultcode.status = P80211ENUM_msgitem_status_no_value;
      sniff.resultcode.len = 4;

      result = do_ioctl((uint8_t*) &sniff);
#else
      char cmd[128];
      static char *parms = "keepwepflags=false prismheader=true";
      sprintf(cmd, "wlanctl-ng %s lnxreq_wlansniff enable=true channel=%d %s > /dev/null", 
                    dev, channel, parms);
      result = system(cmd);
#endif
   }
   return result;
}

int resetMAC()
{
   int result = 0;
   int fd;
   struct iwreq ireq;  //for Orinoco
   int *ptr;

   if (cardType == ORINOCO) {  //assumes patched pcmcia-cs-3.1.31 and using orinoco_cs
      /* get a socket */
      fd = socket(AF_INET, SOCK_STREAM, 0);

      if ( fd == -1 ) {
         return -1;
      }
      ptr = (int *) ireq.u.name;
      ptr[0] = 0;
      ptr[1] = 0;
      strcpy(ireq.ifr_ifrn.ifrn_name, dev);
      result = ioctl( fd, SIOCIWFIRSTPRIV + 0x8, &ireq);
      close(fd);
   }
   else if (cardType == PRISM) {
#ifndef MAKE_SYS_CALL
      p80211msg_lnxreq_wlansniff_t sniff;
      memset(&sniff, 0, sizeof(p80211msg_lnxreq_wlansniff_t));
      sniff.msgcode = DIDmsg_lnxreq_wlansniff;
      sniff.msglen = sizeof(p80211msg_lnxreq_wlansniff_t);
      strcpy((char*) sniff.devname, dev);

      sniff.enable.did = DIDmsg_lnxreq_wlansniff_enable;
      sniff.enable.len = 4;
      sniff.enable.data = P80211ENUM_truth_false;

      sniff.channel.did = DIDmsg_lnxreq_wlansniff_channel;
      sniff.channel.status = P80211ENUM_msgitem_status_no_value;
      sniff.channel.len = 4;

      sniff.resultcode.did = DIDmsg_lnxreq_wlansniff_resultcode;
      sniff.resultcode.status = P80211ENUM_msgitem_status_no_value;
      sniff.resultcode.len = 4;

      result = do_ioctl((uint8_t*) &sniff);
#else
      char cmd[80];
      sprintf(cmd, "wlanctl-ng %s lnxreq_wlansniff enable=false > /dev/null", dev);
      result = system(cmd);
#endif
   }
   return result;
}

int do_ioctl( unsigned char *userMsg )
{
   int result = -1;
   int fd;
   p80211ioctl_req_t req;
   p80211msg_t *msg = (p80211msg_t *) userMsg;

   strcpy(msg->devname, dev);

   /* set the magic */
   req.magic = P80211_IOCTL_MAGIC;

   /* get a socket */
   fd = socket(AF_INET, SOCK_STREAM, 0);

   if ( fd == -1 ) {
      return result;
   }

   req.len = msg->msglen;
   req.data = msg;   
   strcpy( req.name, dev);
   req.result = 0;

   result = ioctl( fd, P80211_IFREQ, &req);
   close(fd);
   return result;

}

//load user settings from $HOME/.airsnortrc
void loadOpts() {
   char *home = getenv("HOME");
   char rcfile[128];
   FILE *rc;
   char *index;
   int r;
   if (home) {
      sprintf(rcfile, "%s/.airsnortrc", home);
      rc = fopen(rcfile, "r");
      if (rc) {
         while (fgets(rcfile, 128, rc)) {
            index = strchr(rcfile, '\n');
            if (index) *index = 0;
            index = strchr(rcfile, ':');
            if (index) {
               *index = 0;
               if (!strcmp(rcfile, "dev")) {
                  strncpy(dev, index + 1, WLAN_DEVNAMELEN_MAX);
                  dev[WLAN_DEVNAMELEN_MAX - 1] = 0;
               }
               else if (!strcmp(rcfile, "driver")) {
                  r = atoi(index + 1);
                  cardType = r < 0 || r > 2 ? 0 : r;
               }
               else if (!strcmp(rcfile, "breadth128")) {
                  r = atoi(index + 1);
                  breadth128 = r < 1 || r > 20 ? 1 : r;
               }
               else if (!strcmp(rcfile, "breadth40")) {
                  r = atoi(index + 1);
                  breadth40 = r < 1 || r > 20 ? 1 : r;
               }
               else if (!strcmp(rcfile, "channel")) {
                  r = atoi(index + 1);
                  chan = r < 1 || r > 11 ? 6 : r;
               }
            }
         }
         fclose(rc);
      }
   }
}

//save user settings to $HOME/.airsnortrc
void saveOpts() {
   char *home = getenv("HOME");
   char rcfile[128];
   FILE *rc;
   if (home) {
      sprintf(rcfile, "%s/.airsnortrc", home);
      rc = fopen(rcfile, "w");
      if (rc) {
         fprintf(rc, "dev:%s\n", dev);
         fprintf(rc, "driver:%d\n", cardType);
         fprintf(rc, "breadth128:%d\n", breadth128);
         fprintf(rc, "breadth40:%d\n", breadth40);
         fprintf(rc, "channel:%d\n", chan);
         fclose(rc);
      }
   }
}

