/*
    This file is part of Kismet

    Kismet 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.

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

#include "config.h"
#include "iwcontrol.h"

// We need this to make uclibc happy since they don't even have rintf...
#ifndef rintf
#define rintf(x) (float) rint((double) (x))
#endif

#ifdef HAVE_LINUX_WIRELESS

float IwFreq2Float(iwreq *inreq) {
    return ((float) inreq->u.freq.m) * pow(10,inreq->u.freq.e);
}

void IwFloat2Freq(double in_val, struct iw_freq *out_freq) {
	if (in_val <= 165) {
		out_freq->m = (uint32_t) in_val;
		out_freq->e = 0;
		return;
	}

    out_freq->e = (short) (floor(log10(in_val)));
    if(out_freq->e > 8) {  
        out_freq->m = ((long) (floor(in_val / pow(10,out_freq->e - 6)))) * 100; 
        out_freq->e -= 8;
    } else {  
        out_freq->m = (uint32_t) in_val;            
        out_freq->e = 0;
    }  
}

int FloatChan2Int(float in_chan) {
	if (in_chan > 0 && in_chan <= 165)
		return (int) in_chan;
	
    int mod_chan = (int) rintf(in_chan / 1000000);
    int x = 0;
    // 80211b frequencies to channels
    int IEEE80211Freq[] = {
        2412, 2417, 2422, 2427, 2432,
        2437, 2442, 2447, 2452, 2457,
        2462, 2467, 2472, 2484,
        5180, 5200, 5210, 5220, 5240,
        5250, 5260, 5280, 5290, 5300, 
        5320, 5745, 5760, 5765, 5785, 
        5800, 5805, 5825,
        -1
    };

    int IEEE80211Ch[] = {
        1, 2, 3, 4, 5,
        6, 7, 8, 9, 10,
        11, 12, 13, 14,
        36, 40, 42, 44, 48,
        50, 52, 56, 58, 60,
        64, 149, 152, 153, 157,
        160, 161, 165
    };

    while (IEEE80211Freq[x] != -1) {
        if (IEEE80211Freq[x] == mod_chan) {
            return IEEE80211Ch[x];
        }
        x++;
    }

    return 0;
}

int Iwconfig_Set_SSID(const char *in_dev, char *errstr, char *in_essid) {
    struct iwreq wrq;
    int skfd;
    char essid[IW_ESSID_MAX_SIZE + 1];

    if (in_essid == NULL) {
        essid[0] = '\0';
    } else {
        // Trim transparently
        snprintf(essid, IW_ESSID_MAX_SIZE+1, "%s", in_essid);
    }
    
    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }

    // Zero the ssid
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);
    wrq.u.essid.pointer = (caddr_t) essid;
    wrq.u.essid.length = strlen(essid)+1;
    wrq.u.essid.flags = 1;

    if (ioctl(skfd, SIOCSIWESSID, &wrq) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to set SSID %d:%s", errno, 
                 strerror(errno));
        close(skfd);
        return -1;
    }

    close(skfd);
    return 0;
}

int Iwconfig_Get_SSID(const char *in_dev, char *errstr, char *in_essid) {
    struct iwreq wrq;
    int skfd;
    char essid[IW_ESSID_MAX_SIZE + 1];

    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }

    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);
    wrq.u.essid.pointer = (caddr_t) essid;
    wrq.u.essid.length = IW_ESSID_MAX_SIZE+1;
    wrq.u.essid.flags = 0;

    if (ioctl(skfd, SIOCGIWESSID, &wrq) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to get SSID %d:%s", errno, 
                 strerror(errno));
        close(skfd);
        return -1;
    }

    snprintf(in_essid, kismin(IW_ESSID_MAX_SIZE, wrq.u.essid.length) + 1, "%s", 
             (char *) wrq.u.essid.pointer);

    close(skfd);
    return 0;
}

int Iwconfig_Get_Name(const char *in_dev, char *errstr, char *in_name) {
    struct iwreq wrq;
    int skfd;

    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }

    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);

    if (ioctl(skfd, SIOCGIWNAME, &wrq) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to get NAME %d:%s", errno, 
                 strerror(errno));
        close(skfd);
        return -1;
    }

    snprintf(in_name, IFNAMSIZ, "%s", wrq.u.name);

    close(skfd);
    return 0;
}

// Set a private ioctl that takes 1 or 2 integer parameters
// A return of -2 means no privctl found that matches, so that the caller
// can return a more detailed failure message
//
// Code largely taken from wireless_tools
int Iwconfig_Set_IntPriv(const char *in_dev, const char *privcmd, 
                         int val1, int val2, char *errstr) {
    struct iwreq wrq;
    int skfd;
    struct iw_priv_args priv[IW_MAX_PRIV_DEF];
    u_char buffer[4096];
    int subcmd = 0;
    int offset = 0;

    memset(priv, 0, sizeof(priv));

    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }

    memset(&wrq, 0, sizeof(struct iwreq));
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);

    wrq.u.data.pointer = (caddr_t) priv;
    wrq.u.data.length = IW_MAX_PRIV_DEF;
    wrq.u.data.flags = 0;

    if (ioctl(skfd, SIOCGIWPRIV, &wrq) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to retrieve list of private ioctls %d:%s",
                 errno, strerror(errno));
        close(skfd);
        return -1;
    }

    int pn = -1;
    while ((++pn < wrq.u.data.length) && strcmp(priv[pn].name, privcmd));

    if (pn == wrq.u.data.length) {
        snprintf(errstr, STATUS_MAX, "Unable to find private ioctl '%s'", privcmd);
        close(skfd);
        return -2;
    }

    // Find subcmds, as if this isn't ugly enough already
    if (priv[pn].cmd < SIOCDEVPRIVATE) {
        int j = -1;

        while ((++j < wrq.u.data.length) && ((priv[j].name[0] != '\0') ||
                                             (priv[j].set_args != priv[pn].set_args) ||
                                             (priv[j].get_args != priv[pn].get_args)));
        
        if (j == wrq.u.data.length) {
            snprintf(errstr, STATUS_MAX, "Unable to find subioctl '%s'", privcmd);
            close(skfd);
            return -2;
        }

        subcmd = priv[pn].cmd;
        offset = sizeof(__u32);
        pn = j;
    }

    // Make sure its an iwpriv we can set
    if ((priv[pn].set_args & IW_PRIV_TYPE_MASK) == 0 ||
        (priv[pn].set_args & IW_PRIV_SIZE_MASK) == 0) {
        snprintf(errstr, STATUS_MAX, "Unable to set values for private ioctl '%s'", 
                 privcmd);
        close(skfd);
        return -1;
    }
  
    if ((priv[pn].set_args & IW_PRIV_TYPE_MASK) != IW_PRIV_TYPE_INT) {
        snprintf(errstr, STATUS_MAX, "'%s' does not accept integer parameters.",
                 privcmd);
        close(skfd);
        return -1;
    }
    
    // Find out how many arguments it takes and die if we can't handle it
    int nargs = (priv[pn].set_args & IW_PRIV_SIZE_MASK);
    if (nargs > 2) {
        snprintf(errstr, STATUS_MAX, "Private ioctl expects more than 2 arguments.");
        close(skfd);
        return -1;
    }

    // Build the set request
    memset(&wrq, 0, sizeof(struct iwreq));
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);

    // Assign the arguments
    wrq.u.data.length = nargs;     
    ((__s32 *) buffer)[0] = (__s32) val1;
    if (nargs > 1) {
        ((__s32 *) buffer)[1] = (__s32) val2;
    }
       
    // This is terrible!
    // This is also simplified from what iwpriv.c does, because we don't
    // need to worry about get-no-set ioctls
    if ((priv[pn].set_args & IW_PRIV_SIZE_FIXED) &&
        ((sizeof(__u32) * nargs) + offset <= IFNAMSIZ)) {
        if (offset)
            wrq.u.mode = subcmd;
        memcpy(wrq.u.name + offset, buffer, IFNAMSIZ - offset);
    } else {
        wrq.u.data.pointer = (caddr_t) buffer;
        wrq.u.data.flags = 0;
    }

    // Actually do it.
    if (ioctl(skfd, priv[pn].cmd, &wrq) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to set private ioctl '%s': %s",
                 privcmd, strerror(errno));
        close(skfd);
        return -1;
    }

    close(skfd);
    return 0;
}

int Iwconfig_Get_IntPriv(const char *in_dev, const char *privcmd,
                         int *val, char *errstr) {
    struct iwreq wrq;
    int skfd;
    struct iw_priv_args priv[IW_MAX_PRIV_DEF];
    u_char buffer[4096];
    int subcmd = 0;
    int offset = 0;

    memset(priv, 0, sizeof(priv));

    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }

    memset(&wrq, 0, sizeof(struct iwreq));
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);

    wrq.u.data.pointer = (caddr_t) priv;
    wrq.u.data.length = IW_MAX_PRIV_DEF;
    wrq.u.data.flags = 0;

    if (ioctl(skfd, SIOCGIWPRIV, &wrq) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to retrieve list of private ioctls %d:%s",
                 errno, strerror(errno));
        close(skfd);
        return -1;
    }

    int pn = -1;
    while ((++pn < wrq.u.data.length) && strcmp(priv[pn].name, privcmd));

    if (pn == wrq.u.data.length) {
        snprintf(errstr, STATUS_MAX, "Unable to find private ioctl '%s'", privcmd);
        close(skfd);
        return -2;
    }

    // Find subcmds, as if this isn't ugly enough already
    if (priv[pn].cmd < SIOCDEVPRIVATE) {
        int j = -1;

        while ((++j < wrq.u.data.length) && ((priv[j].name[0] != '\0') ||
                                             (priv[j].set_args != priv[pn].set_args) ||
                                             (priv[j].get_args != priv[pn].get_args)));
        
        if (j == wrq.u.data.length) {
            snprintf(errstr, STATUS_MAX, "Unable to find subioctl '%s'", privcmd);
            close(skfd);
            return -2;
        }

        subcmd = priv[pn].cmd;
        offset = sizeof(__u32);
        pn = j;
    }

    // Make sure its an iwpriv we can set
    if ((priv[pn].get_args & IW_PRIV_TYPE_MASK) == 0 ||
        (priv[pn].get_args & IW_PRIV_SIZE_MASK) == 0) {
        snprintf(errstr, STATUS_MAX, "Unable to get values for private ioctl '%s'", 
                 privcmd);
        close(skfd);
        return -1;
    }
  
    if ((priv[pn].get_args & IW_PRIV_TYPE_MASK) != IW_PRIV_TYPE_INT) {
        snprintf(errstr, STATUS_MAX, "'%s' does not return integer parameters.",
                 privcmd);
        close(skfd);
        return -1;
    }
    
    // Find out how many arguments it takes and die if we can't handle it
    int nargs = (priv[pn].get_args & IW_PRIV_SIZE_MASK);
    if (nargs > 1) {
        snprintf(errstr, STATUS_MAX, "Private ioctl returns more than 1 parameter and we can't handle that.");
        close(skfd);
        return -1;
    }

    // Build the get request
    memset(&wrq, 0, sizeof(struct iwreq));
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);

    // Assign the arguments
    wrq.u.data.length = 0L;     
       
    // This is terrible!
    // Simplified (again) from iwpriv, since we split the command into
    // a set and a get instead of combining the cases
    if ((priv[pn].get_args & IW_PRIV_SIZE_FIXED) &&
        ((sizeof(__u32) * nargs) + offset <= IFNAMSIZ)) {
        /* Second case : no SET args, GET args fit within wrq */
        if (offset)
            wrq.u.mode = subcmd;
    } else {
        wrq.u.data.pointer = (caddr_t) buffer;
        wrq.u.data.flags = 0;
    }

    // Actually do it.
    if (ioctl(skfd, priv[pn].cmd, &wrq) < 0) {
        snprintf(errstr, STATUS_MAX, "Failed to call get private ioctl '%s': %s",
                 privcmd, strerror(errno));
        close(skfd);
        return -1;
    }

    // Where do we get the data from?
    if ((priv[pn].get_args & IW_PRIV_SIZE_FIXED) &&
        ((sizeof(__u32) * nargs) + offset <= IFNAMSIZ))
        memcpy(buffer, wrq.u.name, IFNAMSIZ);

    // Return the value of the ioctl
    (*val) = ((__s32 *) buffer)[0];

    close(skfd);
    return 0;
}

int Iwconfig_Get_Levels(const char *in_dev, char *in_err, int *level, int *noise) {
    struct iwreq wrq;
    struct iw_range range;
    struct iw_statistics stats;
    char buffer[sizeof(iw_range) * 2];
    int skfd;

    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(in_err, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }

    // Fetch the range
    memset(buffer, 0, sizeof(iw_range) * 2);
    memset(&wrq, 0, sizeof(struct iwreq));
    wrq.u.data.pointer = (caddr_t) buffer;
    wrq.u.data.length = sizeof(buffer);
    wrq.u.data.flags = 0;
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);

    if (ioctl(skfd, SIOCGIWRANGE, &wrq) < 0) {
        snprintf(in_err, STATUS_MAX, "Failed to fetch signal range, %s", strerror(errno));
        close(skfd);
        return -1;
    }

    // Pull it out
    memcpy((char *) &range, buffer, sizeof(iw_range));

    // Fetch the stats
    wrq.u.data.pointer = (caddr_t) &stats;
    wrq.u.data.length = 0;
    wrq.u.data.flags = 1;     /* Clear updated flag */
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);

    if (ioctl(skfd, SIOCGIWSTATS, &wrq) < 0) {
        snprintf(in_err, STATUS_MAX, "Failed to fetch signal stats, %s", strerror(errno));
        close(skfd);
        return -1;
    }

    /*
    if (stats.qual.level <= range.max_qual.level) {
        *level = 0;
        *noise = 0;
        close(skfd);
        return 0;
    }
    */

    *level = stats.qual.level - 0x100;
    *noise = stats.qual.noise - 0x100;

    close(skfd);

    return 0;
}

int Iwconfig_Get_Channel(const char *in_dev, char *in_err) {
    struct iwreq wrq;
    int skfd;

    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(in_err, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }

    memset(&wrq, 0, sizeof(struct iwreq));
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);

    if (ioctl(skfd, SIOCGIWFREQ, &wrq) < 0) {
        snprintf(in_err, STATUS_MAX, "channel get ioctl failed %d:%s",
                 errno, strerror(errno));
        close(skfd);
        return -1;
    }

    close(skfd);

    return (FloatChan2Int(IwFreq2Float(&wrq)));
}

int Iwconfig_Set_Channel(const char *in_dev, int in_ch, char *in_err) {
    struct iwreq wrq;
    int skfd;

    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(in_err, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }
    // Set a channel
    memset(&wrq, 0, sizeof(struct iwreq));

    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);
#ifdef HAVE_LINUX_IWFREQFLAG
	wrq.u.freq.flags = IW_FREQ_FIXED;
#endif
    IwFloat2Freq(in_ch, &wrq.u.freq);

    // Try twice with a tiny delay, some cards (madwifi) need a second chance...
    if (ioctl(skfd, SIOCSIWFREQ, &wrq) < 0) {
        struct timeval tm;
        tm.tv_sec = 0;
        tm.tv_usec = 5000;
        select(0, NULL, NULL, NULL, &tm);

        if (ioctl(skfd, SIOCSIWFREQ, &wrq) < 0) {
            snprintf(in_err, STATUS_MAX, "Failed to set channel %d %d:%s", in_ch,
                     errno, strerror(errno));
            close(skfd);
            return -1;
        }
    }

    close(skfd);
    return 0;
}

int Iwconfig_Get_Mode(const char *in_dev, char *in_err, int *in_mode) {
    struct iwreq wrq;
    int skfd;

    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(in_err, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }

    memset(&wrq, 0, sizeof(struct iwreq));
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);

    if (ioctl(skfd, SIOCGIWMODE, &wrq) < 0) {
        snprintf(in_err, STATUS_MAX, "mode get ioctl failed %d:%s",
                 errno, strerror(errno));
        close(skfd);
        return -1;
    }

    (*in_mode) = wrq.u.mode;
    
    close(skfd);
    return 0;
}

int Iwconfig_Set_Mode(const char *in_dev, char *in_err, int in_mode) {
    struct iwreq wrq;
    int skfd;

    if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        snprintf(in_err, STATUS_MAX, "Failed to create AF_INET DGRAM socket %d:%s", 
                 errno, strerror(errno));
        return -1;
    }

    memset(&wrq, 0, sizeof(struct iwreq));
    strncpy(wrq.ifr_name, in_dev, IFNAMSIZ);
    wrq.u.mode = in_mode;

    if (ioctl(skfd, SIOCSIWMODE, &wrq) < 0) {
        snprintf(in_err, STATUS_MAX, "mode set ioctl failed %d:%s",
                 errno, strerror(errno));
        close(skfd);
        return -1;
    }

    close(skfd);
    return 0;
}

#endif

